Add Leptonica source code 08/91608/2 tizen_dev
authorTae-Young Chung <ty83.chung@samsung.com>
Mon, 10 Oct 2016 10:31:29 +0000 (19:31 +0900)
committerTae-Young Chung <ty83.chung@samsung.com>
Mon, 10 Oct 2016 10:51:10 +0000 (19:51 +0900)
Change-Id: Ibd25b7645e23201dc52e210f46315c746b247911
Signed-off-by: Tae-Young Chung <ty83.chung@samsung.com>
249 files changed:
CMakeLists.txt [new file with mode: 0644]
LICENSE.BSD-2-Clause [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
Makefile.in [new file with mode: 0644]
README.html [new file with mode: 0644]
aclocal.m4 [new file with mode: 0644]
autobuild [new file with mode: 0755]
cmake/Configure.cmake [new file with mode: 0644]
cmake/templates/LeptonicaConfig-version.cmake.in [new file with mode: 0644]
cmake/templates/LeptonicaConfig.cmake.in [new file with mode: 0644]
config/config.guess [new file with mode: 0755]
config/config.h.in [new file with mode: 0644]
config/config.h.in~ [new file with mode: 0644]
config/config.sub [new file with mode: 0755]
config/depcomp [new file with mode: 0755]
config/install-sh [new file with mode: 0755]
config/ltmain.sh [new file with mode: 0644]
config/missing [new file with mode: 0755]
configure [new file with mode: 0755]
configure.ac [new file with mode: 0644]
endiantest.c [new file with mode: 0644]
lept.pc.in [new file with mode: 0644]
leptonica-license.txt [new file with mode: 0644]
leptonica.manifest [new file with mode: 0644]
make-for-auto [new file with mode: 0755]
make-for-local [new file with mode: 0755]
packaging/leptonica.spec [new file with mode: 0644]
src/CMakeLists.txt [new file with mode: 0644]
src/Makefile.am [new file with mode: 0644]
src/Makefile.in [new file with mode: 0644]
src/adaptmap.c [new file with mode: 0644]
src/affine.c [new file with mode: 0644]
src/affinecompose.c [new file with mode: 0644]
src/allheaders.h [new file with mode: 0644]
src/allheaders_bot.txt [new file with mode: 0644]
src/allheaders_top.txt [new file with mode: 0644]
src/alltypes.h [new file with mode: 0644]
src/array.h [new file with mode: 0644]
src/arrayaccess.c [new file with mode: 0644]
src/arrayaccess.h [new file with mode: 0644]
src/bardecode.c [new file with mode: 0644]
src/baseline.c [new file with mode: 0644]
src/bbuffer.c [new file with mode: 0644]
src/bbuffer.h [new file with mode: 0644]
src/bilateral.c [new file with mode: 0644]
src/bilateral.h [new file with mode: 0644]
src/bilinear.c [new file with mode: 0644]
src/binarize.c [new file with mode: 0644]
src/binexpand.c [new file with mode: 0644]
src/binreduce.c [new file with mode: 0644]
src/blend.c [new file with mode: 0644]
src/bmf.c [new file with mode: 0644]
src/bmf.h [new file with mode: 0644]
src/bmfdata.h [new file with mode: 0644]
src/bmp.h [new file with mode: 0644]
src/bmpio.c [new file with mode: 0644]
src/bmpiostub.c [new file with mode: 0644]
src/bootnumgen1.c [new file with mode: 0644]
src/bootnumgen2.c [new file with mode: 0644]
src/boxbasic.c [new file with mode: 0644]
src/boxfunc1.c [new file with mode: 0644]
src/boxfunc2.c [new file with mode: 0644]
src/boxfunc3.c [new file with mode: 0644]
src/boxfunc4.c [new file with mode: 0644]
src/bytearray.c [new file with mode: 0644]
src/ccbord.c [new file with mode: 0644]
src/ccbord.h [new file with mode: 0644]
src/ccthin.c [new file with mode: 0644]
src/classapp.c [new file with mode: 0644]
src/colorcontent.c [new file with mode: 0644]
src/coloring.c [new file with mode: 0644]
src/colormap.c [new file with mode: 0644]
src/colormorph.c [new file with mode: 0644]
src/colorquant1.c [new file with mode: 0644]
src/colorquant2.c [new file with mode: 0644]
src/colorseg.c [new file with mode: 0644]
src/colorspace.c [new file with mode: 0644]
src/compare.c [new file with mode: 0644]
src/conncomp.c [new file with mode: 0644]
src/convertfiles.c [new file with mode: 0644]
src/convolve.c [new file with mode: 0644]
src/correlscore.c [new file with mode: 0644]
src/dewarp.h [new file with mode: 0644]
src/dewarp1.c [new file with mode: 0644]
src/dewarp2.c [new file with mode: 0644]
src/dewarp3.c [new file with mode: 0644]
src/dewarp4.c [new file with mode: 0644]
src/dnabasic.c [new file with mode: 0644]
src/dwacomb.2.c [new file with mode: 0644]
src/dwacomblow.2.c [new file with mode: 0644]
src/edge.c [new file with mode: 0644]
src/encoding.c [new file with mode: 0644]
src/endianness.h [new file with mode: 0644]
src/endianness.h.dist [new file with mode: 0644]
src/endianness.h.in [new file with mode: 0644]
src/endiantest.c [new file with mode: 0644]
src/enhance.c [new file with mode: 0644]
src/environ.h [new file with mode: 0644]
src/fhmtauto.c [new file with mode: 0644]
src/fhmtgen.1.c [new file with mode: 0644]
src/fhmtgenlow.1.c [new file with mode: 0644]
src/finditalic.c [new file with mode: 0644]
src/flipdetect.c [new file with mode: 0644]
src/fliphmtgen.c [new file with mode: 0644]
src/fmorphauto.c [new file with mode: 0644]
src/fmorphgen.1.c [new file with mode: 0644]
src/fmorphgenlow.1.c [new file with mode: 0644]
src/fpix1.c [new file with mode: 0644]
src/fpix2.c [new file with mode: 0644]
src/freetype.c [new file with mode: 0644]
src/freetype.h [new file with mode: 0644]
src/gifio.c [new file with mode: 0644]
src/gifiostub.c [new file with mode: 0644]
src/gplot.c [new file with mode: 0644]
src/gplot.h [new file with mode: 0644]
src/graphics.c [new file with mode: 0644]
src/graymorph.c [new file with mode: 0644]
src/grayquant.c [new file with mode: 0644]
src/grayquantlow.c [new file with mode: 0644]
src/heap.c [new file with mode: 0644]
src/heap.h [new file with mode: 0644]
src/hmttemplate1.txt [new file with mode: 0644]
src/hmttemplate2.txt [new file with mode: 0644]
src/imageio.h [new file with mode: 0644]
src/jbclass.c [new file with mode: 0644]
src/jbclass.h [new file with mode: 0644]
src/jp2kheader.c [new file with mode: 0644]
src/jp2kheaderstub.c [new file with mode: 0644]
src/jp2kio.c [new file with mode: 0644]
src/jp2kiostub.c [new file with mode: 0644]
src/jpegio.c [new file with mode: 0644]
src/jpegiostub.c [new file with mode: 0644]
src/kernel.c [new file with mode: 0644]
src/leptonica-license.txt [new file with mode: 0644]
src/leptwin.c [new file with mode: 0644]
src/leptwin.h [new file with mode: 0644]
src/libversions.c [new file with mode: 0644]
src/list.c [new file with mode: 0644]
src/list.h [new file with mode: 0644]
src/makefile.static [new file with mode: 0644]
src/map.c [new file with mode: 0644]
src/maze.c [new file with mode: 0644]
src/morph.c [new file with mode: 0644]
src/morph.h [new file with mode: 0644]
src/morphapp.c [new file with mode: 0644]
src/morphdwa.c [new file with mode: 0644]
src/morphseq.c [new file with mode: 0644]
src/morphtemplate1.txt [new file with mode: 0644]
src/morphtemplate2.txt [new file with mode: 0644]
src/numabasic.c [new file with mode: 0644]
src/numafunc1.c [new file with mode: 0644]
src/numafunc2.c [new file with mode: 0644]
src/pageseg.c [new file with mode: 0644]
src/paintcmap.c [new file with mode: 0644]
src/parseprotos.c [new file with mode: 0644]
src/partition.c [new file with mode: 0644]
src/pdfio1.c [new file with mode: 0644]
src/pdfio1stub.c [new file with mode: 0644]
src/pdfio2.c [new file with mode: 0644]
src/pdfio2stub.c [new file with mode: 0644]
src/pix.h [new file with mode: 0644]
src/pix1.c [new file with mode: 0644]
src/pix2.c [new file with mode: 0644]
src/pix3.c [new file with mode: 0644]
src/pix4.c [new file with mode: 0644]
src/pix5.c [new file with mode: 0644]
src/pixabasic.c [new file with mode: 0644]
src/pixacc.c [new file with mode: 0644]
src/pixafunc1.c [new file with mode: 0644]
src/pixafunc2.c [new file with mode: 0644]
src/pixalloc.c [new file with mode: 0644]
src/pixarith.c [new file with mode: 0644]
src/pixcomp.c [new file with mode: 0644]
src/pixconv.c [new file with mode: 0644]
src/pixlabel.c [new file with mode: 0644]
src/pixtiling.c [new file with mode: 0644]
src/pngio.c [new file with mode: 0644]
src/pngiostub.c [new file with mode: 0644]
src/pnmio.c [new file with mode: 0644]
src/pnmiostub.c [new file with mode: 0644]
src/projective.c [new file with mode: 0644]
src/psio1.c [new file with mode: 0644]
src/psio1stub.c [new file with mode: 0644]
src/psio2.c [new file with mode: 0644]
src/psio2stub.c [new file with mode: 0644]
src/ptabasic.c [new file with mode: 0644]
src/ptafunc1.c [new file with mode: 0644]
src/ptra.c [new file with mode: 0644]
src/ptra.h [new file with mode: 0644]
src/quadtree.c [new file with mode: 0644]
src/queue.c [new file with mode: 0644]
src/queue.h [new file with mode: 0644]
src/rank.c [new file with mode: 0644]
src/rbtree.c [new file with mode: 0644]
src/rbtree.h [new file with mode: 0644]
src/readbarcode.c [new file with mode: 0644]
src/readbarcode.h [new file with mode: 0644]
src/readfile.c [new file with mode: 0644]
src/recog.h [new file with mode: 0644]
src/recogbasic.c [new file with mode: 0644]
src/recogdid.c [new file with mode: 0644]
src/recogident.c [new file with mode: 0644]
src/recogtrain.c [new file with mode: 0644]
src/regutils.c [new file with mode: 0644]
src/regutils.h [new file with mode: 0644]
src/rop.c [new file with mode: 0644]
src/ropiplow.c [new file with mode: 0644]
src/roplow.c [new file with mode: 0644]
src/rotate.c [new file with mode: 0644]
src/rotateam.c [new file with mode: 0644]
src/rotateamlow.c [new file with mode: 0644]
src/rotateorth.c [new file with mode: 0644]
src/rotateshear.c [new file with mode: 0644]
src/runlength.c [new file with mode: 0644]
src/sarray.c [new file with mode: 0644]
src/scale.c [new file with mode: 0644]
src/scalelow.c [new file with mode: 0644]
src/seedfill.c [new file with mode: 0644]
src/seedfilllow.c [new file with mode: 0644]
src/sel1.c [new file with mode: 0644]
src/sel2.c [new file with mode: 0644]
src/selgen.c [new file with mode: 0644]
src/shear.c [new file with mode: 0644]
src/skew.c [new file with mode: 0644]
src/spixio.c [new file with mode: 0644]
src/stack.c [new file with mode: 0644]
src/stack.h [new file with mode: 0644]
src/stringcode.c [new file with mode: 0644]
src/stringcode.h [new file with mode: 0644]
src/stringtemplate1.txt [new file with mode: 0644]
src/stringtemplate2.txt [new file with mode: 0644]
src/sudoku.c [new file with mode: 0644]
src/sudoku.h [new file with mode: 0644]
src/textops.c [new file with mode: 0644]
src/tiffio.c [new file with mode: 0644]
src/tiffiostub.c [new file with mode: 0644]
src/utils.c [new file with mode: 0644]
src/viewfiles.c [new file with mode: 0644]
src/warper.c [new file with mode: 0644]
src/watershed.c [new file with mode: 0644]
src/watershed.h [new file with mode: 0644]
src/webpio.c [new file with mode: 0644]
src/webpiostub.c [new file with mode: 0644]
src/writefile.c [new file with mode: 0644]
src/xtractprotos.c [new file with mode: 0644]
src/zlibmem.c [new file with mode: 0644]
src/zlibmemstub.c [new file with mode: 0644]
style-guide.txt [new file with mode: 0644]
version-notes.html [new file with mode: 0644]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c914669
--- /dev/null
@@ -0,0 +1,129 @@
+#
+# leptonica
+#
+
+###############################################################################
+#
+# cmake settings
+#
+###############################################################################
+
+cmake_minimum_required(VERSION 2.8.11)
+
+# In-source builds are disabled.
+if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
+    message(FATAL_ERROR
+        "CMake generation is not possible within the source directory!"
+        "\n Remove the CMakeCache.txt file and try again from another folder, e.g.:"
+        "\n "
+        "\n rm CMakeCache.txt"
+        "\n mkdir build"
+        "\n cd build"
+        "\n cmake .."
+    )
+endif()
+
+set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_SOURCE_DIR}/cmake")
+
+set(EXECUTABLE_OUTPUT_PATH "${CMAKE_BINARY_DIR}/bin")
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${EXECUTABLE_OUTPUT_PATH}")
+
+# Use solution folders.
+set_property(GLOBAL PROPERTY USE_FOLDERS ON)
+set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMake Targets")
+
+###############################################################################
+#
+# project settings
+#
+###############################################################################
+
+project(leptonica C CXX)
+
+set(VERSION_MAJOR 1)
+set(VERSION_MINOR 73)
+set(VERSION_PLAIN ${VERSION_MAJOR}.${VERSION_MINOR})
+
+find_package(GIF)
+#find_package(JP2K)
+find_package(JPEG)
+find_package(PNG)
+find_package(TIFF)
+#find_package(WEBP)
+find_package(ZLIB)
+
+###############################################################################
+#
+# compiler and linker
+#
+###############################################################################
+
+set(LIBRARY_TYPE SHARED)
+if (STATIC)
+    set(LIBRARY_TYPE)
+endif()
+
+if (WIN32)
+    if (MSVC)
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W0")
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
+    endif()
+endif()
+
+###############################################################################
+#
+# configure
+#
+###############################################################################
+
+set(AUTOCONFIG_SRC ${CMAKE_BINARY_DIR}/config_auto.h.in)
+set(AUTOCONFIG ${CMAKE_BINARY_DIR}/src/config_auto.h)
+
+include(Configure)
+
+configure_file(${AUTOCONFIG_SRC} ${AUTOCONFIG} @ONLY)
+
+set(INCLUDE_DIR ${CMAKE_SOURCE_DIR}/src)
+
+configure_file(
+    ${CMAKE_SOURCE_DIR}/cmake/templates/LeptonicaConfig-version.cmake.in
+    ${CMAKE_BINARY_DIR}/LeptonicaConfig-version.cmake @ONLY)
+configure_file(
+    ${CMAKE_SOURCE_DIR}/cmake/templates/LeptonicaConfig.cmake.in
+    ${CMAKE_BINARY_DIR}/LeptonicaConfig.cmake @ONLY)
+
+###############################################################################
+#
+# build
+#
+###############################################################################
+
+add_definitions(-DHAVE_CONFIG_H)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/src)
+
+########################################
+# FUNCTION add_prog_target
+########################################
+function(add_prog_target target)
+    set                             (${target}_src "${ARGN}")
+    if (WIN32)
+        set_source_files_properties (${${target}_src} PROPERTIES LANGUAGE CXX)
+    endif()
+    add_executable                  (${target} ${${target}_src})
+    if (NOT STATIC)
+        target_compile_definitions  (${target} PRIVATE -DLIBLEPT_IMPORTS)
+    endif()
+    target_link_libraries           (${target} leptonica)
+    set_target_properties           (${target} PROPERTIES FOLDER prog)
+endfunction(add_prog_target)
+########################################
+
+if (BUILD_PROG)
+add_subdirectory(prog)
+endif()
+
+add_subdirectory(src)
+
+###############################################################################
diff --git a/LICENSE.BSD-2-Clause b/LICENSE.BSD-2-Clause
new file mode 100644 (file)
index 0000000..da7520b
--- /dev/null
@@ -0,0 +1,26 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..b38a8a9
--- /dev/null
@@ -0,0 +1,8 @@
+ACLOCAL_AMFLAGS = -I m4
+AUTOMAKE_OPTIONS = foreign
+EXTRA_DIST = config README.html leptonica-license.txt version-notes.html make-for-auto make-for-local autobuild
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = lept.pc
+
+SUBDIRS = src
diff --git a/Makefile.in b/Makefile.in
new file mode 100644 (file)
index 0000000..6d15662
--- /dev/null
@@ -0,0 +1,815 @@
+# Makefile.in generated by automake 1.11.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software
+# Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = .
+DIST_COMMON = $(am__configure_deps) $(srcdir)/Makefile.am \
+       $(srcdir)/Makefile.in $(srcdir)/lept.pc.in \
+       $(top_srcdir)/config/config.h.in $(top_srcdir)/configure \
+       config/config.guess config/config.sub config/depcomp \
+       config/install-sh config/ltmain.sh config/missing
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+       $(ACLOCAL_M4)
+am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
+ configure.lineno config.status.lineno
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = config_auto.h
+CONFIG_CLEAN_FILES = lept.pc
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN   " $@;
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
+       html-recursive info-recursive install-data-recursive \
+       install-dvi-recursive install-exec-recursive \
+       install-html-recursive install-info-recursive \
+       install-pdf-recursive install-ps-recursive install-recursive \
+       installcheck-recursive installdirs-recursive pdf-recursive \
+       ps-recursive uninstall-recursive
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+  test -z "$$files" \
+    || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+    || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+         $(am__cd) "$$dir" && rm -f $$files; }; \
+  }
+am__installdirs = "$(DESTDIR)$(pkgconfigdir)"
+DATA = $(pkgconfig_DATA)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive        \
+  distclean-recursive maintainer-clean-recursive
+AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \
+       $(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \
+       distdir dist dist-all distcheck
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+distdir = $(PACKAGE)-$(VERSION)
+top_distdir = $(distdir)
+am__remove_distdir = \
+  if test -d "$(distdir)"; then \
+    find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \
+      && rm -rf "$(distdir)" \
+      || { sleep 5 && rm -rf "$(distdir)"; }; \
+  else :; fi
+am__relativize = \
+  dir0=`pwd`; \
+  sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+  sed_rest='s,^[^/]*/*,,'; \
+  sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+  sed_butlast='s,/*[^/]*$$,,'; \
+  while test -n "$$dir1"; do \
+    first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+    if test "$$first" != "."; then \
+      if test "$$first" = ".."; then \
+        dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+        dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+      else \
+        first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+        if test "$$first2" = "$$first"; then \
+          dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+        else \
+          dir2="../$$dir2"; \
+        fi; \
+        dir0="$$dir0"/"$$first"; \
+      fi; \
+    fi; \
+    dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+  done; \
+  reldir="$$dir2"
+DIST_ARCHIVES = $(distdir).tar.gz
+GZIP_ENV = --best
+distuninstallcheck_listfiles = find . -type f -print
+am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \
+  | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$'
+distcleancheck_listfiles = find . -type f -print
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPLE_UNIVERSAL_BUILD = @APPLE_UNIVERSAL_BUILD@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ENDIANNESS = @ENDIANNESS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GDI_LIBS = @GDI_LIBS@
+GIFLIB_LIBS = @GIFLIB_LIBS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+JPEG_LIBS = @JPEG_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBJP2K_LIBS = @LIBJP2K_LIBS@
+LIBM = @LIBM@
+LIBOBJS = @LIBOBJS@
+LIBPNG_LIBS = @LIBPNG_LIBS@
+LIBS = @LIBS@
+LIBTIFF_LIBS = @LIBTIFF_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBWEBP_LIBS = @LIBWEBP_LIBS@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+ZLIB_LIBS = @ZLIB_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+ACLOCAL_AMFLAGS = -I m4
+AUTOMAKE_OPTIONS = foreign
+EXTRA_DIST = config README.html leptonica-license.txt moller52.jpg version-notes.html make-for-auto make-for-local autobuild
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = lept.pc
+SUBDIRS = src prog
+all: config_auto.h
+       $(MAKE) $(AM_MAKEFLAGS) all-recursive
+
+.SUFFIXES:
+am--refresh: Makefile
+       @:
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+       @for dep in $?; do \
+         case '$(am__configure_deps)' in \
+           *$$dep*) \
+             echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \
+             $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \
+               && exit 0; \
+             exit 1;; \
+         esac; \
+       done; \
+       echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \
+       $(am__cd) $(top_srcdir) && \
+         $(AUTOMAKE) --foreign Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+       @case '$?' in \
+         *config.status*) \
+           echo ' $(SHELL) ./config.status'; \
+           $(SHELL) ./config.status;; \
+         *) \
+           echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \
+           cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \
+       esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+       $(SHELL) ./config.status --recheck
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+       $(am__cd) $(srcdir) && $(AUTOCONF)
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+       $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
+$(am__aclocal_m4_deps):
+
+config_auto.h: stamp-h1
+       @if test ! -f $@; then rm -f stamp-h1; else :; fi
+       @if test ! -f $@; then $(MAKE) $(AM_MAKEFLAGS) stamp-h1; else :; fi
+
+stamp-h1: $(top_srcdir)/config/config.h.in $(top_builddir)/config.status
+       @rm -f stamp-h1
+       cd $(top_builddir) && $(SHELL) ./config.status config_auto.h
+$(top_srcdir)/config/config.h.in:  $(am__configure_deps) 
+       ($(am__cd) $(top_srcdir) && $(AUTOHEADER))
+       rm -f stamp-h1
+       touch $@
+
+distclean-hdr:
+       -rm -f config_auto.h stamp-h1
+lept.pc: $(top_builddir)/config.status $(srcdir)/lept.pc.in
+       cd $(top_builddir) && $(SHELL) ./config.status $@
+
+mostlyclean-libtool:
+       -rm -f *.lo
+
+clean-libtool:
+       -rm -rf .libs _libs
+
+distclean-libtool:
+       -rm -f libtool config.lt
+install-pkgconfigDATA: $(pkgconfig_DATA)
+       @$(NORMAL_INSTALL)
+       test -z "$(pkgconfigdir)" || $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)"
+       @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \
+       for p in $$list; do \
+         if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+         echo "$$d$$p"; \
+       done | $(am__base_list) | \
+       while read files; do \
+         echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \
+         $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \
+       done
+
+uninstall-pkgconfigDATA:
+       @$(NORMAL_UNINSTALL)
+       @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \
+       files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+       dir='$(DESTDIR)$(pkgconfigdir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run `make' without going through this Makefile.
+# To change the values of `make' variables: instead of editing Makefiles,
+# (1) if the variable is set in `config.status', edit `config.status'
+#     (which will cause the Makefiles to be regenerated when you run `make');
+# (2) otherwise, pass the desired values on the `make' command line.
+$(RECURSIVE_TARGETS):
+       @fail= failcom='exit 1'; \
+       for f in x $$MAKEFLAGS; do \
+         case $$f in \
+           *=* | --[!k]*);; \
+           *k*) failcom='fail=yes';; \
+         esac; \
+       done; \
+       dot_seen=no; \
+       target=`echo $@ | sed s/-recursive//`; \
+       list='$(SUBDIRS)'; for subdir in $$list; do \
+         echo "Making $$target in $$subdir"; \
+         if test "$$subdir" = "."; then \
+           dot_seen=yes; \
+           local_target="$$target-am"; \
+         else \
+           local_target="$$target"; \
+         fi; \
+         ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+         || eval $$failcom; \
+       done; \
+       if test "$$dot_seen" = "no"; then \
+         $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+       fi; test -z "$$fail"
+
+$(RECURSIVE_CLEAN_TARGETS):
+       @fail= failcom='exit 1'; \
+       for f in x $$MAKEFLAGS; do \
+         case $$f in \
+           *=* | --[!k]*);; \
+           *k*) failcom='fail=yes';; \
+         esac; \
+       done; \
+       dot_seen=no; \
+       case "$@" in \
+         distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+         *) list='$(SUBDIRS)' ;; \
+       esac; \
+       rev=''; for subdir in $$list; do \
+         if test "$$subdir" = "."; then :; else \
+           rev="$$subdir $$rev"; \
+         fi; \
+       done; \
+       rev="$$rev ."; \
+       target=`echo $@ | sed s/-recursive//`; \
+       for subdir in $$rev; do \
+         echo "Making $$target in $$subdir"; \
+         if test "$$subdir" = "."; then \
+           local_target="$$target-am"; \
+         else \
+           local_target="$$target"; \
+         fi; \
+         ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+         || eval $$failcom; \
+       done && test -z "$$fail"
+tags-recursive:
+       list='$(SUBDIRS)'; for subdir in $$list; do \
+         test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
+       done
+ctags-recursive:
+       list='$(SUBDIRS)'; for subdir in $$list; do \
+         test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
+       done
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+       list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+       unique=`for i in $$list; do \
+           if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+         done | \
+         $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+             END { if (nonempty) { for (i in files) print i; }; }'`; \
+       mkid -fID $$unique
+tags: TAGS
+
+TAGS: tags-recursive $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+               $(TAGS_FILES) $(LISP)
+       set x; \
+       here=`pwd`; \
+       if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+         include_option=--etags-include; \
+         empty_fix=.; \
+       else \
+         include_option=--include; \
+         empty_fix=; \
+       fi; \
+       list='$(SUBDIRS)'; for subdir in $$list; do \
+         if test "$$subdir" = .; then :; else \
+           test ! -f $$subdir/TAGS || \
+             set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+         fi; \
+       done; \
+       list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+       unique=`for i in $$list; do \
+           if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+         done | \
+         $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+             END { if (nonempty) { for (i in files) print i; }; }'`; \
+       shift; \
+       if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+         test -n "$$unique" || unique=$$empty_fix; \
+         if test $$# -gt 0; then \
+           $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+             "$$@" $$unique; \
+         else \
+           $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+             $$unique; \
+         fi; \
+       fi
+ctags: CTAGS
+CTAGS: ctags-recursive $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+               $(TAGS_FILES) $(LISP)
+       list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+       unique=`for i in $$list; do \
+           if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+         done | \
+         $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+             END { if (nonempty) { for (i in files) print i; }; }'`; \
+       test -z "$(CTAGS_ARGS)$$unique" \
+         || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+            $$unique
+
+GTAGS:
+       here=`$(am__cd) $(top_builddir) && pwd` \
+         && $(am__cd) $(top_srcdir) \
+         && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+       -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+       $(am__remove_distdir)
+       test -d "$(distdir)" || mkdir "$(distdir)"
+       @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       list='$(DISTFILES)'; \
+         dist_files=`for file in $$list; do echo $$file; done | \
+         sed -e "s|^$$srcdirstrip/||;t" \
+             -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+       case $$dist_files in \
+         */*) $(MKDIR_P) `echo "$$dist_files" | \
+                          sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+                          sort -u` ;; \
+       esac; \
+       for file in $$dist_files; do \
+         if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+         if test -d $$d/$$file; then \
+           dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+           if test -d "$(distdir)/$$file"; then \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+             cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+         else \
+           test -f "$(distdir)/$$file" \
+           || cp -p $$d/$$file "$(distdir)/$$file" \
+           || exit 1; \
+         fi; \
+       done
+       @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+         if test "$$subdir" = .; then :; else \
+           test -d "$(distdir)/$$subdir" \
+           || $(MKDIR_P) "$(distdir)/$$subdir" \
+           || exit 1; \
+         fi; \
+       done
+       @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+         if test "$$subdir" = .; then :; else \
+           dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+           $(am__relativize); \
+           new_distdir=$$reldir; \
+           dir1=$$subdir; dir2="$(top_distdir)"; \
+           $(am__relativize); \
+           new_top_distdir=$$reldir; \
+           echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+           echo "     am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+           ($(am__cd) $$subdir && \
+             $(MAKE) $(AM_MAKEFLAGS) \
+               top_distdir="$$new_top_distdir" \
+               distdir="$$new_distdir" \
+               am__remove_distdir=: \
+               am__skip_length_check=: \
+               am__skip_mode_fix=: \
+               distdir) \
+             || exit 1; \
+         fi; \
+       done
+       -test -n "$(am__skip_mode_fix)" \
+       || find "$(distdir)" -type d ! -perm -755 \
+               -exec chmod u+rwx,go+rx {} \; -o \
+         ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
+         ! -type d ! -perm -400 -exec chmod a+r {} \; -o \
+         ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
+       || chmod -R a+r "$(distdir)"
+dist-gzip: distdir
+       tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
+       $(am__remove_distdir)
+
+dist-bzip2: distdir
+       tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2
+       $(am__remove_distdir)
+
+dist-lzip: distdir
+       tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz
+       $(am__remove_distdir)
+
+dist-lzma: distdir
+       tardir=$(distdir) && $(am__tar) | lzma -9 -c >$(distdir).tar.lzma
+       $(am__remove_distdir)
+
+dist-xz: distdir
+       tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz
+       $(am__remove_distdir)
+
+dist-tarZ: distdir
+       tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
+       $(am__remove_distdir)
+
+dist-shar: distdir
+       shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz
+       $(am__remove_distdir)
+
+dist-zip: distdir
+       -rm -f $(distdir).zip
+       zip -rq $(distdir).zip $(distdir)
+       $(am__remove_distdir)
+
+dist dist-all: distdir
+       tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
+       $(am__remove_distdir)
+
+# This target untars the dist file and tries a VPATH configuration.  Then
+# it guarantees that the distribution is self-contained by making another
+# tarfile.
+distcheck: dist
+       case '$(DIST_ARCHIVES)' in \
+       *.tar.gz*) \
+         GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\
+       *.tar.bz2*) \
+         bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\
+       *.tar.lzma*) \
+         lzma -dc $(distdir).tar.lzma | $(am__untar) ;;\
+       *.tar.lz*) \
+         lzip -dc $(distdir).tar.lz | $(am__untar) ;;\
+       *.tar.xz*) \
+         xz -dc $(distdir).tar.xz | $(am__untar) ;;\
+       *.tar.Z*) \
+         uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
+       *.shar.gz*) \
+         GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\
+       *.zip*) \
+         unzip $(distdir).zip ;;\
+       esac
+       chmod -R a-w $(distdir); chmod a+w $(distdir)
+       mkdir $(distdir)/_build
+       mkdir $(distdir)/_inst
+       chmod a-w $(distdir)
+       test -d $(distdir)/_build || exit 0; \
+       dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \
+         && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \
+         && am__cwd=`pwd` \
+         && $(am__cd) $(distdir)/_build \
+         && ../configure --srcdir=.. --prefix="$$dc_install_base" \
+           $(AM_DISTCHECK_CONFIGURE_FLAGS) \
+           $(DISTCHECK_CONFIGURE_FLAGS) \
+         && $(MAKE) $(AM_MAKEFLAGS) \
+         && $(MAKE) $(AM_MAKEFLAGS) dvi \
+         && $(MAKE) $(AM_MAKEFLAGS) check \
+         && $(MAKE) $(AM_MAKEFLAGS) install \
+         && $(MAKE) $(AM_MAKEFLAGS) installcheck \
+         && $(MAKE) $(AM_MAKEFLAGS) uninstall \
+         && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \
+               distuninstallcheck \
+         && chmod -R a-w "$$dc_install_base" \
+         && ({ \
+              (cd ../.. && umask 077 && mkdir "$$dc_destdir") \
+              && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \
+              && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \
+              && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \
+                   distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \
+             } || { rm -rf "$$dc_destdir"; exit 1; }) \
+         && rm -rf "$$dc_destdir" \
+         && $(MAKE) $(AM_MAKEFLAGS) dist \
+         && rm -rf $(DIST_ARCHIVES) \
+         && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \
+         && cd "$$am__cwd" \
+         || exit 1
+       $(am__remove_distdir)
+       @(echo "$(distdir) archives ready for distribution: "; \
+         list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
+         sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
+distuninstallcheck:
+       @test -n '$(distuninstallcheck_dir)' || { \
+         echo 'ERROR: trying to run $@ with an empty' \
+              '$$(distuninstallcheck_dir)' >&2; \
+         exit 1; \
+       }; \
+       $(am__cd) '$(distuninstallcheck_dir)' || { \
+         echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \
+         exit 1; \
+       }; \
+       test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \
+          || { echo "ERROR: files left after uninstall:" ; \
+               if test -n "$(DESTDIR)"; then \
+                 echo "  (check DESTDIR support)"; \
+               fi ; \
+               $(distuninstallcheck_listfiles) ; \
+               exit 1; } >&2
+distcleancheck: distclean
+       @if test '$(srcdir)' = . ; then \
+         echo "ERROR: distcleancheck can only run from a VPATH build" ; \
+         exit 1 ; \
+       fi
+       @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \
+         || { echo "ERROR: files left in build directory after distclean:" ; \
+              $(distcleancheck_listfiles) ; \
+              exit 1; } >&2
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(DATA) config_auto.h
+installdirs: installdirs-recursive
+installdirs-am:
+       for dir in "$(DESTDIR)$(pkgconfigdir)"; do \
+         test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+       done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+       @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+       if test -z '$(STRIP)'; then \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+             install; \
+       else \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+           "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+       fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+       -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+       -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+       @echo "This command is intended for maintainers to use"
+       @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+       -rm -f $(am__CONFIG_DISTCLEAN_FILES)
+       -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-hdr \
+       distclean-libtool distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-pkgconfigDATA
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+       -rm -f $(am__CONFIG_DISTCLEAN_FILES)
+       -rm -rf $(top_srcdir)/autom4te.cache
+       -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-pkgconfigDATA
+
+.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) all \
+       ctags-recursive install-am install-strip tags-recursive
+
+.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
+       all all-am am--refresh check check-am clean clean-generic \
+       clean-libtool ctags ctags-recursive dist dist-all dist-bzip2 \
+       dist-gzip dist-lzip dist-lzma dist-shar dist-tarZ dist-xz \
+       dist-zip distcheck distclean distclean-generic distclean-hdr \
+       distclean-libtool distclean-tags distcleancheck distdir \
+       distuninstallcheck dvi dvi-am html html-am info info-am \
+       install install-am install-data install-data-am install-dvi \
+       install-dvi-am install-exec install-exec-am install-html \
+       install-html-am install-info install-info-am install-man \
+       install-pdf install-pdf-am install-pkgconfigDATA install-ps \
+       install-ps-am install-strip installcheck installcheck-am \
+       installdirs installdirs-am maintainer-clean \
+       maintainer-clean-generic mostlyclean mostlyclean-generic \
+       mostlyclean-libtool pdf pdf-am ps ps-am tags tags-recursive \
+       uninstall uninstall-am uninstall-pkgconfigDATA
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/README.html b/README.html
new file mode 100644 (file)
index 0000000..409c724
--- /dev/null
@@ -0,0 +1,1084 @@
+<html>
+<body BGCOLOR=FFFFE4>
+
+<!-- JS Window Closer -----
+<form>
+<center>
+<input type="button" onclick="window.close();" value="Close this window">
+</center>
+</form>
+----- JS Window Closer -->
+
+
+<!-- Creative Commons License -->
+<a rel="license" href="http://creativecommons.org/licenses/by/2.5/"><img alt="Creative Commons License" border="0" src="http://creativecommons.org/images/public/somerights20.gif" /></a>
+This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/2.5/">Creative Commons Attribution 2.5 License</a>.
+<!-- /Creative Commons License -->
+
+
+<!--
+
+<rdf:RDF xmlns="http://web.resource.org/cc/"
+  xmlns:dc="http://purl.org/dc/elements/1.1/"
+  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<Work rdf:about="">
+  <dc:title>leptonica</dc:title>
+  <dc:date>2001</dc:date>
+  <dc:description>An open source C library for efficient image processing and image analysis operations</dc:description>
+  <dc:creator><Agent>
+    <dc:title>Dan S. Bloomberg</dc:title>
+  </Agent></dc:creator>
+  <dc:rights><Agent>
+    <dc:title>Dan S. Bloomberg</dc:title>
+  </Agent></dc:rights>
+  <dc:type rdf:resource="http://purl.org/dc/dcmitype/Text" />
+  <dc:source rdf:resource="www.leptonica.com"/>
+  <license rdf:resource="http://creativecommons.org/licenses/by/2.5/" />
+</Work>
+
+<License rdf:about="http://creativecommons.org/licenses/by/2.5/">
+  <permits rdf:resource="http://web.resource.org/cc/Reproduction" />
+  <permits rdf:resource="http://web.resource.org/cc/Distribution" />
+  <requires rdf:resource="http://web.resource.org/cc/Notice" />
+  <requires rdf:resource="http://web.resource.org/cc/Attribution" />
+  <permits rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+</License>
+
+</rdf:RDF>
+
+-->
+
+<pre>
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+README  (1.73: 25 Jan 2016)
+---------------------------
+
+gunzip leptonica-1.73.tar.gz
+tar -xvf leptonica-1.73.tar
+
+</pre>
+
+<!--Navigation Panel-->
+<hr>
+<P>
+<A HREF="#BUILDING">Building leptonica</A><br>
+<A HREF="#DEPENDENCIES">I/O libraries leptonica is dependent on</A><br>
+<A HREF="#DEVELOP">Developing with leptonica</A><br>
+<A HREF="#CONTENTS">What's in leptonica?</A><br>
+<P>
+<hr>
+<!--End of Navigation Panel-->
+
+
+<h2> <A NAME="BUILDING">
+Building leptonica
+</h2>
+
+<pre>
+1. Top view
+
+  This tar includes:
+    (1) src: library source and function prototypes for building liblept
+    (2) prog: source for regression test, usage example programs, and
+        sample images
+  for building on these platforms:
+     -  Linux on x86 (i386) and AMD 64 (x64)
+     -  OSX (both powerPC and x86).
+     -  Cygwin, msys and mingw on x86
+  There is an additional zip file for building with MS Visual Studio.
+
+  Libraries, executables and prototypes are easily made, as described below.
+
+  When you extract from the archive, all files are put in a
+  subdirectory 'leptonica-1.73'.  In that directory you will
+  find a src directory containing the source files for the library,
+  and a prog directory containing source files for various
+  testing and example programs.
+
+2. Building on Linux/Unix/MacOS
+
+  There are three ways to build the library:
+
+    (1) By customization:  Use the existing static makefile,
+        src/makefile.static and customize the build by setting flags
+        in src/environ.h.  See src/environ.h and src/makefile for details.
+        Note: if you are going to develop with leptonica, I encourage
+        you to use the static makefiles.
+
+    (2) Using autoconf (supported by James Le Cuirot).
+        Run ./configure in this directory to
+        build Makefiles here and in src.  Autoconf handles the
+        following automatically:
+            * architecture endianness
+            * enabling Leptonica I/O image read/write functions that
+              depend on external libraries (if the libraries exist)
+            * enabling functions for redirecting formatted image stream
+              I/O to memory (on linux only)
+        After running ./configure: make; make install.  There's also
+        a 'make check' for testing.
+
+    (3) Using cmake (supported by Egor Pugin).
+        The build must always be in a different directory from the root
+        of the source (here).  It is common to build in a subdirectory
+        of the root.  From here:
+          mkdir build
+          cd build
+          cmake ..
+          make
+        Alternatively, from here:
+          mkdir build
+          cmake -H . -Bbuild   (-H means the source directory,
+                                -B means the director for the build
+          make
+        To clean out the current build, just remove everything in
+        the build subdirectory.
+
+  In more detail:
+
+    (1) Customization using the static makefiles:
+
+       * FIRST THING: Run make-for-local.  This simply renames
+               src/makefile.static  -->  src/makefile
+               prog/makefile.static -->  prog/makefile
+         [Note: the autoconf build will not work if you have any files
+          named "makefile" in src or prog.  If you've already run
+          make-for-local and renamed the static makefiles, and you then
+          want to build with autoconf, run make-for-auto to rename them
+          back to makefile.static.]
+
+       * You can customize for:
+         (a) Including Leptonica I/O functions that depend on external
+             libraries [use flags in src/environ.h]
+         (b) Adding functions for redirecting formatted image stream
+             I/O to memory [use flag in src/environ.h]
+         (c) Specifying the location of the object code.  By default it
+             goes into a tree whose root is also the parent of the src
+             and prog directories.  This can be changed using the
+             ROOT_DIR variable in makefile.
+
+       * Build the library:
+         - To make an optimized version of the library (in src):
+               make
+         - To make a debug version of the library (in src):
+               make DEBUG=yes debug
+         - To make a shared library version (in src):
+               make SHARED=yes shared
+         - To make the prototype extraction program (in src):
+               make   (to make the library first)
+               make xtractprotos
+
+       * To use shared libraries, you need to include the location of
+         the shared libraries in your LD_LIBRARY_PATH.
+
+       * To make the programs in the prog directory, first make liblept
+         in src.  Then in prog you can customize the makefile for linking
+         the external libraries:
+         Finally, do 'make' in the prog directory.
+
+       VERY IMPORTANT: the 240+ programs in the prog directory are
+       an integral part of this package.  These can be divided into
+       four groups:
+         (1) Programs that are useful applications for running on the
+             command line.  They can be installed from autoconf builds
+             using 'make install'.  Examples of these are the PostScript
+             and pdf conversion programs: converttopdf, converttops,
+             convertfilestopdf, convertfilestops, convertsegfilestopdf,
+             convertsegfilestops, printimage and printsplitimage.
+         (2) Programs that are used as regression tests in alltests_reg.
+             These are named *_reg, and 66 of them are invoked together
+             (alltests_reg).  The regression test framework has been
+             standardized, and regresstion tests are relatively easy
+             to write.  See regutils.h for details.
+         (3) Other regression tests, some of which have not (yet) been
+             put into the framework.  They are also named *_reg.
+         (4) Programs that were used to test library functions or
+             auto-generate library code.  These are useful for testing
+             the behavior of small sets of functions, and for
+             providing example code.
+
+    (2) Building using autoconf  (Thanks to James Le Cuirot)
+
+       Use the standard incantation, in the root directory (the
+       directory with configure):
+          ./configure    [build the Makefile]
+          make   [builds the library and shared library versions of
+                  all the progs]
+          make install  [as root; this puts liblept.a into /usr/local/lib/
+                         and 13 of the the progs into /usr/local/bin/ ]
+          make [-j2] check  [runs the alltests_reg set of regression tests.
+                             This works even if you build in a different
+                             place from the distribution. The -j parameter
+                             should not exceed half the number of cores.
+                             If the test fails, just run with 'make check']
+
+       Configure also supports building in a separate directory from the
+       source.  Run "/(path-to)/leptonica-1.73/configure" and then "make"
+       from the desired build directory.
+
+       Configure has a number of useful options; run "configure --help" for
+       details.  If you're not planning to modify the library, adding the
+       "--disable-dependency-tracking" option will speed up the build.  By
+       default, both static and shared versions of the library are built.  Add
+       the "--disable-shared" or "--disable-static" option if one or the other
+       isn't needed.  To skip building the programs, use "--disable-programs".
+
+       By default, the library is built with debugging symbols.  If you do not
+       want these, use "CFLAGS=-O2 ./configure" to eliminate symbols for
+       subsequent compilations, or "make CFLAGS=-O2" to override the default
+       for compilation only.  Another option is to use the 'install-strip'
+       target (i.e., "make install-strip") to remove the debugging symbols
+       when the library is installed.
+
+       Finally, if you find that the installed programs are unable to link
+       at runtime to the installed library, which is in /usr/local/lib,
+       try to run configure in this way:
+           LDFLAGS="-Wl,-rpath -Wl,/usr/local/lib" ./configure
+       which causes the compiler to pass those options through to the linker.
+
+       For the debian distribution, out of all the programs in the prog
+       directory, we only build a small subset of general purpose
+       utility programs.  This subset is the same set of programs that
+       'make install' puts into /usr/local/bin.  It has no dependency on
+       the image files that are bundled in the prog directory for testing.
+
+    (3) Using cmake
+
+       There are a couple of flags you can use on the cmake line to
+       determine what is built here.
+
+       * By default, cmake builds a shared library.  To make a static
+         library: 
+            cmake .. -DSTATIC=1
+
+       * By default, cmake only builds the library, not the programs.
+         To make progs from the build subdirectory:
+            cmake .. -DBUILD_PROG=1
+
+    (4) Cross-compiling for windows
+
+        You can use src/makefile.mingw for cross-compiling in linux.
+
+
+3. Building on Windows
+
+   (a) Building with Visual Studio
+
+       Tom Powers has provided a set of developer notes and project files
+       for building the library and applications under windows with VC++ 2008:
+     </pre>
+     <p style='margin-left:5em;'>
+        <A href="http://www.leptonica.org/vs2008doc/index.html">
+                http:///www.leptonica.org/vs2008doc/index.html</A><br>
+        <A href="http://www.leptonica.org/download.html#VS2008">
+                http:///www.leptonica.org/download.html#VS2008</A>
+     <pre>
+
+       He has also supplied a zip file that contains the entire 'lib'
+       and 'include' directories needed to build Windows-based programs
+       using static or dynamic versions of the leptonica library
+       (including static library versions of zlib, libpng, libjpeg,
+       libtiff, and giflib).
+       </pre>
+       <p style='margin-left:5em;'>
+      <A href="http://www.leptonica.org/source/leptonica-1.68-win32-lib-include-dirs.zip">
+              leptonica-1.68-win32-lib-include-dirs.zip</A>
+       <pre>
+
+       You can download Tom's vs2008 package either from the download
+       page or from code.google.com/p/leptonica.
+
+   (b) Building for mingw with <a href="http://www.mingw.org/">MSYS</a>
+       (Thanks to David Bryan)
+
+       MSYS is a Unix-compatible build environment for the mingw compiler.
+       Installing the "MSYS Base System" and "MinGW Compiler Suite" will allow
+       building the library with autoconf as in (2) above.  It will also allow
+       building with the static makefile as in (1) above if this option
+       is added to the make command:
+
+         CC="gcc -D_BSD_SOURCE -DANSI"
+
+       Only the static library may be built this way; the autoconf method must
+       be used if a shared (DLL) library is desired.
+
+       External image libraries (see below) must be downloaded separately,
+       built, and installed before building the library.  Pre-built libraries
+       are available from
+          <a href="http://sourceforge.net/projects/ezwinports/">ezwinports project</a>.
+
+   (c) Building for <a href="http://www.cygwin.com/">Cygwin</a>
+       (Thanks to David Bryan)
+
+       Cygwin is a Unix-compatible build and runtime environment.  Installing
+       the "Base" and "Devel" packages, plus the desired graphics libraries
+       from the "Graphics" and "Libs" packages, will allow building the
+       library with autoconf as in (2) above.  If the graphics libraries
+       are not present in the /lib, /usr/lib, or /usr/local/lib directories,
+       you must run make with the "LDFLAGS=-L/(path-to-image)/lib" option.
+       It will also allow building with the static makefile as in (1)
+       above if this option is added to the make command:
+
+         CC="gcc -ansi -D_BSD_SOURCE -DANSI"
+
+       Only the static library may be built this way; the autoconf method must
+       be used if a shared (DLL) library is desired.
+</pre>
+
+
+<h2> <A NAME="DEPENDENCIES">
+I/O libraries leptonica is dependent on
+</h2>
+
+<pre>
+   Leptonica is configured to handle image I/O using these external
+   libraries: libjpeg, libtiff, libpng, libz, libgif, libwebp, libopenjp2
+
+   These libraries are easy to obtain.  For example, using the
+   debian package manager:
+       sudo apt-get install <package>
+   where <package> = {libpng12-dev, libjpeg62-dev, libtiff4-dev}.
+
+   Leptonica also allows image I/O with bmp and pnm formats, for which
+   we provide the serializers (encoders and decoders).  It also
+   gives output drivers for wrapping images in PostScript and PDF, which
+   in turn use tiffg4, jpeg and flate (i.e., zlib) encoding.  PDF will
+   also wrap jpeg2000 images.
+
+   There is a programmatic interface to gnuplot.  To use it, you
+   need only the gnuplot executable (suggest version 3.7.2 or later);
+   the gnuplot library is not required.
+
+   If you build with automake, libraries on your system will be
+   automatically found and used.
+
+   The rest of this section is for building with the static makefiles.
+   The entries in environ.h specify which of these libraries to use.
+   The default is to link to these four libraries:
+      libjpeg.a  (standard jfif jpeg library, version 6b or 7, 8 or 9))
+      libtiff.a  (standard Leffler tiff library, version 3.7.4 or later;
+      libpng.a   (standard png library, suggest version 1.4.0 or later)
+      libz.a     (standard gzip library, suggest version 1.2.3)
+                  current non-beta version is 3.8.2)
+
+   These libraries (and their shared versions) should be in /usr/lib.
+   (If they're not, you can change the LDFLAGS variable in the makefile.)
+   Additionally, for compilation, the following header files are
+   assumed to be in /usr/include:
+      jpeg:  jconfig.h
+      png:   png.h, pngconf.h
+      tiff:  tiff.h, tiffio.h
+
+   If for some reason you do not want to link to specific libraries,
+   even if you have them, stub files are included for the ten
+   different output formats:
+        bmp, jpeg, png, pnm, ps, pdf, tiff, gif, webp and jp2.
+   For example, if you don't want to include the tiff library,
+   in environ.h set:
+       #define  HAVE_LIBTIFF   0
+   and the stubs will be linked in.
+
+   To read and write webp files:
+      (1) Download libwebp from sourceforge
+      (2) #define HAVE_LIBWEBP   1  (in environ.h)
+      (3) In prog/makefile, edit ALL_LIBS to include -lwebp
+      (4) The library will be installed into /usr/local/lib.
+          You may need to add that directory to LDFLAGS; or, equivalently,
+          add that path to the LD_LIBRARY_PATH environment variable.
+
+   To read and write jpeg2000 files:
+      (1) Download libopenjp2, version 2.X, from their distribution,
+          along with cmake.  There is no debian version of openjpeg 2.X
+          as of 12/26/2014.
+      (2) #define HAVE_LIBJP2K   1  (in environ.h)
+      (2a) If you have version 2.X, X != 1, edit LIBJP2K_HEADER  (in environ.h)
+      (3) In prog/makefile, edit ALL_LIBS to include -lopenjp2
+      (4) The library will be installed into /usr/local/lib.
+
+   To read and write gif files:
+      (1) Download version giflib-5.X.X from souceforge
+      (2) #define  HAVE_LIBGIF   1  (in environ.h)
+      (3) In prog/makefile, edit ALL_LIBS to include -lgif
+      (4) The library will be installed into /usr/local/lib.
+      (5) Note: do not use giflib-4.1.4: binary comp and decomp
+          don't pack the pixel data and are ridiculously slow.
+</pre>
+
+
+<h2> <A NAME="DEVELOP">
+Developing with leptonica
+</h2>
+
+<pre>
+You are encouraged to use the static makefiles if you are developing
+applications using leptonica.  The following instructions assume
+that you are using the static makefiles and customizing environ.h.
+
+1. Automatic generation of prototypes
+
+   The prototypes are automatically generated by the program xtractprotos.
+   They can either be put in-line into allheaders.h, or they can be
+   written to a file leptprotos.h, which is #included in allheaders.h.
+   Note: (1) We supply the former version of allheaders.h.
+         (2) all .c files simply include allheaders.h.
+
+   First, make xtractprotos:
+       make xtractprotos
+
+   Then to generate the prototypes and make allheaders.h, do one of
+   these two things:
+       make allheaders  [puts everything into allheaders.h]
+       make allprotos   [generates a file leptprotos.h containing the
+                         function prototypes, and includes it in allheaders.h]
+
+   Things to note about xtractprotos, assuming that you are developing
+   in Leptonica and need to regenerate the prototypes in allheaders.h:
+
+     (1) xtractprotos is part of Leptonica.  You can 'make' it in either
+         src or prog (see the makefile).
+     (2) You can output the prototypes for any C file to stdout by running:
+             xtractprotos <cfile>     or
+             xtractprotos -prestring=[string] <cfile>
+     (3) The source for xtractprotos has been packaged up into a tar
+         containing just the Leptonica files necessary for building it
+         in linux.  The tar file is available at:
+             www.leptonica.com/source/xtractlib-1.5.tar.gz
+
+2. GNU runtime functions for stream redirection to memory
+
+   There are two non-standard gnu functions, fmemopen() and open_memstream(),
+   that only work on linux and conveniently allow memory I/O with a file
+   stream interface.  This is convenient for compressing and decompressing
+   image data to memory rather than to file.  Stubs are provided
+   for all these I/O functions.  Default is to enable them; OSX developers
+   must disable by setting #define HAVE_FMEMOPEN  0  (in environ.h).
+   If these functions are not enabled, raster to compressed data in
+   memory is accomplished safely but through a temporary file.
+   See 9 for more details on image I/O formats.
+
+   If you're building with the autoconf programs, these two functions are
+   automatically enabled if available.
+
+3. Typedefs
+
+   A deficiency of C is that no standard has been universally
+   adopted for typedefs of the built-in types.  As a result,
+   typedef conflicts are common, and cause no end of havoc when
+   you try to link different libraries.  If you're lucky, you
+   can find an order in which the libraries can be linked
+   to avoid these conflicts, but the state of affairs is aggravating.
+
+   The most common typedefs use lower case variables: uint8, int8, ...
+   The png library avoids typedef conflicts by altruistically
+   appending "png_" to the type names.  Following that approach,
+   Leptonica appends "l_" to the type name.  This should avoid
+   just about all conflicts.  In the highly unlikely event that it doesn't,
+   here's a simple way to change the type declarations throughout
+   the Leptonica code:
+    (1) customize a file "converttypes.sed" with the following lines:
+        /l_uint8/s//YOUR_UINT8_NAME/g
+        /l_int8/s//YOUR_INT8_NAME/g
+        /l_uint16/s//YOUR_UINT16_NAME/g
+        /l_int16/s//YOUR_INT16_NAME/g
+        /l_uint32/s//YOUR_UINT32_NAME/g
+        /l_int32/s//YOUR_INT32_NAME/g
+        /l_float32/s//YOUR_FLOAT32_NAME/g
+        /l_float64/s//YOUR_FLOAT64_NAME/g
+    (2) in the src and prog directories:
+       - if you have a version of sed that does in-place conversion:
+            sed -i -f converttypes.sed *
+       - else, do something like (in csh)
+           foreach file (*)
+           sed -f converttypes.sed $file > tempdir/$file
+           end
+
+   If you are using Leptonica with a large code base that typedefs the
+   built-in types differently from Leptonica, just edit the typedefs
+   in environ.h.  This should have no side-effects with other libraries,
+   and no issues should arise with the location in which liblept is
+   included.
+
+   For compatibility with 64 bit hardware and compilers, where
+   necessary we use the typedefs in stdint.h to specify the pointer
+   size (either 4 or 8 byte).
+
+4. Compile-time control over stderr output (see environ.h)
+
+   Leptonica provides both compile-time and run-time control over
+   messages and debug output (thanks to Dave Bryan).  Both compile-time
+   and run-time severity thresholds can be set.  The run-time threshold
+   can also be set by an environmental variable.  Messages are
+   vararg-formatted and of 3 types: error, warning, informational.
+   These are all macros, and can be further suppressed when
+   NO_CONSOLE_IO is defined on the compile line.  For production code
+   where no output is to go to stderr, compile with -DNO_CONSOLE_IO.
+
+5. In-memory raster format (Pix)
+
+   Unlike many other open source packages, Leptonica uses packed
+   data for images with all bit/pixel (bpp) depths, allowing us
+   to process pixels in parallel.  For example, rasterops works
+   on all depths with 32-bit parallel operations throughout.
+   Leptonica is also explicitly configured to work on both little-endian
+   and big-endian hardware.  RGB image pixels are always stored
+   in 32-bit words, and a few special functions are provided for
+   scaling and rotation of RGB images that have been optimized by
+   making explicit assumptions about the location of the R, G and B
+   components in the 32-bit pixel.  In such cases, the restriction
+   is documented in the function header.  The in-memory data structure
+   used throughout Leptonica to hold the packed data is a Pix,
+   which is defined and documented in pix.h.  The alpha component
+   in RGB images is significantly better supported, starting in 1.70.
+
+   Additionally, a FPix is provided for handling 2D arrays of floats,
+   and a DPix is provided for 2D arrays of doubles.  Converters
+   between these and the Pix are given.
+
+6. Conversion between Pix and other in-memory raster formats
+
+ . If you use Leptonica with other imaging libraries, you will need
+   functions to convert between the Pix and other image data
+   structures.  To make a Pix from other image data structures, you
+   will need to understand pixel packing, pixel padding, component
+   ordering and byte ordering on raster lines.  See the file pix.h
+   for the specification of image data in the pix.
+
+7. Custom memory management
+
+   Leptonica allows you to use custom memory management (allocator,
+   deallocator).  For Pix, which tend to be large, the alloc/dealloc
+   functions can be set programmatically.  For all other structs and arrays,
+   the allocators are specified in environ.h.  Default functions
+   are malloc and free.  We have also provided a sample custom
+   allocator/deallocator for Pix, in pixalloc.c.
+</pre>
+
+
+<h2> <A NAME="CONTENTS">
+What's in leptonica?
+</h2>
+<pre>
+1. Rasterops
+
+   This is a source for a clean, fast implementation of rasterops.
+   You can find details starting at the Leptonica home page,
+   and also by looking directly at the source code.
+   The low-level code is in roplow.c and ropiplow.c, and an
+   interface is given in rop.c to the simple Pix image data structure.
+
+2. Binary morphology
+
+   This is a source for efficient implementations of binary morphology
+   Details are found starting at the Leptonica home page, and by reading
+   the source code.
+
+   Binary morphology is implemented two ways:
+
+     (a) Successive full image rasterops for arbitrary
+         structuring elements (Sels)
+
+     (b) Destination word accumulation (dwa) for specific Sels.
+         This code is automatically generated.  See, for example,
+         the code in fmorphgen.1.c and fmorphgenlow.1.c.
+         These files were generated by running the program
+         prog/fmorphautogen.c. Results can be checked by comparing dwa
+         and full image rasterops; e.g., prog/fmorphauto_reg.c.
+
+   Method (b) is considerably faster than (a), which is the
+   reason we've gone to the effort of supporting the use
+   of this method for all Sels.  We also support two different
+   boundary conditions for erosion.
+
+   Similarly, dwa code for the general hit-miss transform can
+   be auto-generated from an array of hit-miss Sels.
+   When prog/fhmtautogen.c is compiled and run, it generates
+   the dwa C code in fhmtgen.1.c and fhmtgenlow.1.c.  These
+   files can then be compiled into the libraries or into other programs.
+   Results can be checked by comparing dwa and rasterop results;
+   e.g., prog/fhmtauto_reg.c
+
+   Several functions with simple parsers are provided to execute a
+   sequence of morphological operations (plus binary rank reduction
+   and replicative expansion).  See morphseq.c.
+
+   The structuring element is represented by a simple Sel data structure
+   defined in morph.h.  We provide (at least) seven ways to generate
+   Sels in sel1.c, and several simple methods to generate hit-miss
+   Sels for pattern finding in selgen.c.
+
+   In use, the most common morphological Sels are separable bricks,
+   of dimension n x m (where either n or m, but not both, is commonly 1).
+   Accordingly, we provide separable morphological operations on brick
+   Sels, using for binary both rasterops and dwa.  Parsers are provided
+   for a sequence of separable binary (rasterop and dwa) and grayscale
+   brick morphological operations, in morphseq.c.  The main
+   advantage in using the parsers is that you don't have to create
+   and destroy Sels, or do any of the intermediate image bookkeeping.
+
+   We also give composable separable brick functions for binary images,
+   for both rasterop and dwa.  These decompose each of the linear
+   operations into a sequence of two operations at different scales,
+   reducing the operation count to a sum of decomposition factors,
+   rather than the (un-decomposed) product of factors.
+   As always, parsers are provided for a sequence of such operations.
+
+3. Grayscale morphology and rank order filters
+
+   We give an efficient implementation of grayscale morphology for brick
+   Sels.  See the Leptonica home page and the source code.
+
+   Brick Sels are separable into linear horizontal and vertical elements.
+   We use the van Herk/Gil-Werman algorithm, that performs the calculations
+   in a time that is independent of the size of the Sels.  Implementations
+   of tophat and hdome are also given.  The low-level code is in graymorphlow.c.
+
+   We also provide grayscale rank order filters for brick filters.
+   The rank order filter is a generalization of grayscale morphology,
+   that selects the rank-valued pixel (rather than the min or max).
+   A color rank order filter applies the grayscale rank operation
+   independently to each of the (r,g,b) components.
+
+4. Image scaling
+
+   Leptonica provides many simple and relatively efficient
+   implementations of image scaling.  Some of them are listed here;
+   for the full set see the web page and the source code.
+
+   Grayscale and color images are scaled using:
+      - sampling
+      - lowpass filtering followed by sampling,
+      - area mapping
+      -  linear interpolation
+
+   Scaling operations with antialiased sampling, area mapping,
+   and linear interpolation are limited to 2, 4 and 8 bpp gray,
+   24 bpp full RGB color, and 2, 4 and 8 bpp colormapped
+   (bpp == bits/pixel).  Scaling operations with simple sampling
+   can be done at 1, 2, 4, 8, 16 and 32 bpp.  Linear interpolation
+   is slower but gives better results, especially for upsampling.
+   For moderate downsampling, best results are obtained with area
+   mapping scaling.  With very high downsampling, either area mapping
+   or antialias sampling (lowpass filter followed by sampling) give
+   good results.  Fast area map with power-of-2 reduction are also
+   provided.  Optional sharpening after resampling is provided to
+   improve appearance by reducing the visual effect of averaging
+   across sharp boundaries.
+
+   For fast analysis of grayscale and color images, it is useful to
+   have integer subsampling combined with pixel depth reduction.
+   RGB color images can thus be converted to low-resolution
+   grayscale and binary images.
+
+   For binary scaling, the dest pixel can be selected from the
+   closest corresponding source pixel.  For the special case of
+   power-of-2 binary reduction, low-pass rank-order filtering can be
+   done in advance.  Isotropic integer expansion is done by pixel replication.
+
+   We also provide 2x, 3x, 4x, 6x, 8x, and 16x scale-to-gray reduction
+   on binary images, to produce high quality reduced grayscale images.
+   These are integrated into a scale-to-gray function with arbitrary
+   reduction.
+
+   Conversely, we have special 2x and 4x scale-to-binary expansion
+   on grayscale images, using linear interpolation on grayscale
+   raster line buffers followed by either thresholding or dithering.
+
+   There are also image depth converters that don't have scaling,
+   such as unpacking operations from 1 bpp to grayscale, and
+   thresholding and dithering operations from grayscale to 1, 2 and 4 bpp.
+
+5. Image shear and rotation (and affine, projective, ...)
+
+   Image shear is implemented with both rasterops and linear interpolation.
+   The rasterop implementation is faster and has no constraints on image
+   depth.  We provide horizontal and vertical shearing about an
+   arbitrary point (really, a line), both in-place and from source to dest.
+   The interpolated shear is used on 8 bpp and 32 bpp images, and
+   gives a smoother result.  Shear is used for the fastest implementations
+   of rotation.
+
+   There are three different types of general image rotators:
+
+     a.  Grayscale rotation using area mapping
+         - pixRotateAM() for 8 bit gray and 24 bit color, about center
+         - pixRotateAMCorner() for 8 bit gray, about image UL corner
+         - pixRotateAMColorFast() for faster 24 bit color, about center
+
+     b.  Rotation of an image of arbitrary bit depth, using
+         either 2 or 3 shears.  These rotations can be done
+         about an arbitrary point, and they can be either
+         from source to dest or in-place; e.g.
+         - pixRotateShear()
+         - pixRotateShearIP()
+
+     c.  Rotation by sampling.  This can be used on images of arbitrary
+         depth, and done about an arbitrary point.  Colormaps are retained.
+
+   The area mapping rotations are slower and more accurate,
+   because each new pixel is composed using an average of four
+   neighboring pixels in the original image; this is sometimes
+   also called "antialiasing".  Very fast color area mapping
+   rotation is provided.  The low-level code is in rotateamlow.c.
+
+   The shear rotations are much faster, and work on images
+   of arbitrary pixel depth, but they just move pixels
+   around without doing any averaging.  The pixRotateShearIP()
+   operates on the image in-place.
+
+   We also provide orthogonal rotators (90, 180, 270 degree; left-right
+   flip and top-bottom flip) for arbitrary image depth.
+   And we provide implementations of affine, projective and bilinear
+   transforms, with both sampling (for speed) and interpolation
+   (for antialiasing).
+
+6. Sequential algorithms
+
+   We provide a number of fast sequential algorithms, including
+   binary and grayscale seedfill, and the distance function for
+   a binary image.  The most efficient binary seedfill is
+   pixSeedfill(), which uses Luc Vincent's algorithm to iterate
+   raster- and antiraster-ordered propagation, and can be used
+   for either 4- or 8-connected fills.  Similar raster/antiraster
+   sequential algorithms are used to generate a distance map from
+   a binary image, and for grayscale seedfill.  We also use Heckbert's
+   stack-based filling algorithm for identifying 4- and 8-connected
+   components in a binary image.  A fast implementation of the
+   watershed transform, using a priority queue, is included.
+
+7. Image enhancement
+
+   A few simple image enhancement routines for grayscale and
+   color images have been provided.  These include intensity mapping
+   with gamma correction and contrast enhancement, as well as edge
+   sharpening, smoothing, and hue and saturation modification.
+
+8. Convolution and cousins
+
+   A number of standard image processing operations are also
+   included, such as block convolution, binary block rank filtering,
+   grayscale and rgb rank order filtering, and edge and local
+   minimum/maximum extraction.   Generic convolution is included,
+   for both separable and non-separable kernels, using float arrays
+   in the Pix.  Two implementations are included for grayscale and
+   color bilateral filtering: a straightforward (slow) one, and a
+   fast, approximate, separable one.
+
+9. Image I/O
+
+   Some facilities have been provided for image input and output.
+   This is of course required to build executables that handle images,
+   and many examples of such programs, most of which are for
+   testing, can be built in the prog directory.  Functions have been
+   provided to allow reading and writing of files in JPEG, PNG,
+   TIFF, BMP, PNM ,GIF, WEBP and JP2 formats.  These formats were chosen
+   for the following reasons:
+
+    - JFIF JPEG is the standard method for lossy compression
+      of grayscale and color images.  It is supported natively
+      in all browsers, and uses a good open source compression
+      library.  Decompression is supported by the rasterizers
+      in PS and PDF, for level 2 and above.  It has a progressive
+      mode that compresses about 10% better than standard, but
+      is considerably slower to decompress.  See jpegio.c.
+
+    - PNG is the standard method for lossless compression
+      of binary, grayscale and color images.  It is supported
+      natively in all browsers, and uses a good open source
+      compression library (zlib).  It is superior in almost every
+      respect to GIF (which, until recently, contained proprietary
+      LZW compression).  See pngio.c.
+
+    - TIFF is a common interchange format, which supports different
+      depths, colormaps, etc., and also has a relatively good and
+      widely used binary compression format (CCITT Group 4).
+      Decompression of G4 is supported by rasterizers in PS and PDF,
+      level 2 and above.  G4 compresses better than PNG for most
+      text and line art images, but it does quite poorly for halftones.
+      It has good and stable support by Leffler's open source library,
+      which is clean and small.  Tiff also supports multipage
+      images through a directory structure.  See tiffio.c
+
+    - BMP has (until recently) had no compression.  It is a simple
+      format with colormaps that requires no external libraries.
+      It is commonly used because it is a Microsoft standard,
+      but has little besides simplicity to recommend it.  See bmpio.c.
+
+    - PNM is a very simple, old format that still has surprisingly
+      wide use in the image processing community.  It does not
+      support compression or colormaps, but it does support binary,
+      grayscale and rgb images.  Like BMP, the implementation
+      is simple and requires no external libraries.  See pnmio.c.
+
+    - WEBP is a new wavelet encoding method derived from libvpx,
+      a video compression library.  It is rapidly growing in acceptance,
+      and is supported natively in several browsers.  Leptonica provides
+      an interface through webp into the underlying codec.  You need
+      to download libwebp.
+
+    - JP2K (jpeg2000) is a wavelet encoding method, that has clear
+      advantages over jpeg in compression and quality (especially when
+      the image has sharp edges, such as scanned documents), but is
+      only slowly growing in acceptance.  For it to be widely supported,
+      it will require support on a major browser (as with webp).
+      Leptonica provides an interface through openjpeg into the underlying
+      codec.  You need to download libopenjp2, version 2.X.
+
+    - GIF is still widely used in the world.  With the expiration
+      of the LZW patent, it is practical to add support for GIF files.
+      The open source gif library is relatively incomplete and
+      unsupported (because of the Sperry-Rand-Burroughs-Univac
+      patent history).   See gifio.c.
+
+   Here's a summary of compression support and limitations:
+      - All formats except JPEG, WEBP and JP2K support 1 bpp binary.
+      - All formats support 8 bpp grayscale (GIF must have a colormap).
+      - All formats except GIF support rgb color.
+      - All formats except PNM, JPEG, WEBP and JP2K support 8 bpp colormap.
+      - PNG and PNM support 2 and 4 bpp images.
+      - PNG supports 2 and 4 bpp colormap, and 16 bpp without colormap.
+      - PNG, JPEG, TIFF, WEBP, JP2K and GIF support image compression;
+        PNM and BMP do not.
+      - WEBP supports rgb color and rgba.
+      - JP2K supports 8 bpp grayscale, rgb color and rgba.
+   Use prog/ioformats_reg for a regression test on all formats, including
+   thorough testing on TIFF.
+   For more thorough testing on other formats, use:
+      - prog/pngio_reg for PNG.
+      - prog/gifio_reg for GIF
+      - prog/webpio_reg for WEBP
+      - prog/jp2kio_reg for JP2K
+
+   We provide generators for PS output, from all types of input images.
+   The output can be either uncompressed or compressed with level 2
+   (ccittg4 or dct) or level 3 (flate) encoding.  You have flexibility
+   for scaling and placing of images, and for printing at different
+   resolutions.  You can also compose mixed raster (text, image) PS.
+   See psio1.c for examples of how to output PS for different applications.
+   As examples of usage, see:
+     * prog/converttops.c for a general image --> PS conversion
+           for printing.  You can specify compression level (1, 2, or 3).
+     * prog/convertfilestops.c to generate a multipage level 3 compressed
+           PS file that can then be converted to pdf with ps2pdf.
+     * prog/convertsegfilestops.c to generate a multipage, mixed raster,
+           level 2 compressed PS file.
+
+   We provide generators for PDF output, again from all types of input
+   images, and with ccittg4, dct, flate and jpx (jpeg2000) compression.
+   You can do the following for PDF:
+     * Put any number of images onto a page, with specified input
+       resolution, location and compression.
+     * Write a mixed raster PDF, given an input image and a segmentation
+       mask.  Non-image regions are written in G4 (fax) encoding.
+     * Concatenate single-page PDF wrapped images into a single PDF file.
+     * Build a PDF file of all images in a directory or array of file names.
+   As examples of usage, see:
+     * prog/converttopdf.c: fast pdf generation with one image/page.
+       For speed, this avoids transcoding whenever possible.
+     * prog/convertfilestopdf.c: more flexibility in the output.  You
+       can set the resolution, scaling, encoding type and jpeg quality.
+     * prog/convertsegfilestopdf.c: generates a multipage, mixed raster pdf,
+       with separate controls for compressing text and non-text regions.
+
+   Note: any or all of these I/O library calls can be stubbed out at
+         compile time, using the environment variables in environ.h.
+
+   For all formatted reads and writes, we support read from memory
+   and write to memory.  (We cheat with gif, using a file intermediary.)
+   For all formats except for TIFF, these memory I/O functions
+   are supported through open_memstream() and fmemopen(),
+   which only is available with the gnu C runtime library (glibc).
+   Therefore, except for TIFF, you will not be able to do memory
+   supported read/writes on these platforms:
+       OSX, Windows, Solaris
+   To enable/disable memory I/O for image read/write, see environ.h.
+
+   We also provide fast serialization and deserialization between a pix
+   in memory and a file (spixio.c).  This works on all types of pix images.
+
+10. Colormap removal and color quantization
+
+   Leptonica provides functions that remove colormaps, for conversion
+   to either 8 bpp gray or 24 bpp RGB.  It also provides the inverse
+   function to colormap removal; namely, color quantization
+   from 24 bpp full color to 8 bpp colormap with some number
+   of colormap colors.  Several versions are provided, some that
+   use a fast octree vector quantizer and others that use
+   a variation of the median cut quantizer.  For high-level interfaces,
+   see for example: pixConvertRGBToColormap(), pixOctreeColorQuant(),
+   pixOctreeQuantByPopulation(), pixFixedOctcubeQuant256(),
+   and pixMedianCutQuant().
+
+11. Programmatic image display
+
+   For debugging, several pixDisplay* functions in writefile.c are given.
+   Two (pixDisplay and pixDisplayWithTitle) can be called to display
+   an image using one of several display programs (xzgv, xli, xv, l_view).
+   If necessary to fit on the screen, the image is reduced in size,
+   with 1 bpp images being converted to grayscale for readability.
+   (This is much better than letting xv do the reduction, for example).
+   Another function, pixDisplayWrite(), writes images to disk under
+   control of a reduction/disable flag, which then allows
+   either viewing with pixDisplayMultiple(), or the generation
+   of a composite image using, for example, pixaDisplayTiledAndScaled().
+   These files can also be gathered up into a compressed PDF or PostScript
+   file and viewed with evince.  Common image display programs are: xzgv,
+   xli, xv, display, gthumb, gqview, evince, gv and acroread.  Finally,
+   a set of images can be saved into a pixa (array of pix), specifying the
+   eventual layout into a single pix, using pixaDisplay*().
+
+12. Document image analysis
+
+   Many functions have been included specifically to help with
+   document image analysis.  These include skew and text orientation
+   detection; page segmentation; baseline finding for text;
+   unsupervised classification of connected components, characters
+   and words; dewarping camera images; adaptive binarization; and
+   a simple book-adaptive classifier for various character sets,
+   segmentation for newspaper articles, etc.
+
+13. Data structures
+
+   Several simple data structures are provided for safe and efficient handling
+   of arrays of numbers, strings, pointers, and bytes.  The generic
+   pointer array is implemented in four ways: as a stack, a queue,
+   a heap (used to implement a priority queue), and an array with
+   insertion and deletion, from which the stack operations form a subset.
+   Byte arrays are implemented both as a wrapper around the actual
+   array and as a queue.  The string arrays are particularly useful
+   for both parsing and composing text.  Generic lists with
+   doubly-linked cons cells are also provided.
+
+14. Examples of programs that are easily built using the library:
+
+    - for plotting x-y data, we give a programmatic interface
+      to the gnuplot program, with output to X11, png, ps or eps.
+      We also allow serialization of the plot data, in a form
+      such that the data can be read, the commands generated,
+      and (finally) the plot constructed by running gnuplot.
+
+    - a simple jbig2-type classifier, using various distance
+      metrics between image components (correlation, rank
+      hausdorff); see prog/jbcorrelation.c, prog/jbrankhaus.c.
+
+    - a simple color segmenter, giving a smoothed image
+      with a small number of the most significant colors.
+
+    - a program for converting all images in a directory
+      to a PostScript file, and a program for printing an image
+      in any (supported) format to a PostScript printer.
+
+    - various programs for generating pdf files from compressed
+      images, including very fast ones that don't scale and
+      avoid transcoding if possible.
+
+    - converters between binary images and SVG format.
+
+    - an adaptive recognition utility for training and identifying
+      text characters in a multipage document such as a book.
+
+    - a bitmap font facility that allows painting text onto
+      images.  We currently support one font in several sizes.
+      The font images and postscript programs for generating
+      them are stored in prog/fonts/, and also as compiled strings
+      in bmfdata.h.
+
+    - a binary maze game lets you generate mazes and find shortest
+      paths between two arbitrary points, if such a path exists.
+      You can also compute the "shortest" (i.e., least cost) path
+      between points on a grayscale image.
+
+    - a 1D barcode reader.  This is still in an early stage of development,
+      with little testing, and it only decodes 6 formats.
+
+    - a utility that will dewarp images of text that were captured
+      with a camera at close range.
+
+    - a sudoku solver, including a pretty good test for uniqueness
+
+    - see (13, above) for other document image applications.
+
+15. JBig2 encoder
+
+   Leptonica supports an open source jbig2 encoder (yes, there is one!),
+   which can be downloaded from:
+       http://www.imperialviolet.org/jbig2.html.
+   To build the encoder, use the most recent version.  This bundles
+   Leptonica 1.63.  Once you've built the encoder, use it to compress
+   a set of input image files:  (e.g.)
+       ./jbig2 -v -s <imagefile1 ...>  >   <jbig2_file>
+   You can also generate a pdf wrapping for the output jbig2.  To do that,
+   call jbig2 with the -p arg, which generates a symbol file (output.sym)
+   plus a set of location files for each input image (output.0000, ...):
+        ./jbig2 -v -s -p <imagefile1 ...>
+   and then generate the pdf:
+       python pdf.py output  >  <pdf_file>
+   See the usage documentation for the jbig2 compressor at:
+       http://www.imperialviolet.org/binary/jbig2enc.html
+   You can uncompress the jbig2 files using jbig2dec, which can be
+   downloaded and built from:
+       http://jbig2dec.sourceforge.net/
+
+16. Versions
+
+   New versions of the Leptonica library are released several times
+   a year, and version numbers are provided for each release in
+   the makefile and in allheaders.h.  All even versions from 1.42 to 1.60
+   have been archived at http://code.google.com/p/leptonica, as well as all
+   versions after 1.60.  However, code.google.com no longer supports
+   uploads of new distributions, which you can get at the leptonica.org
+   web site.
+
+   The number of downloads of leptonica increased by nearly an order
+   of magnitude with 1.69, due to bundling with tesseract and
+   incorporation in ubuntu 12-04.  Leptonica has about 2400 functions,
+   and the binary API changed slightly with the new 1.71 release.  Having
+   a proper binary release version is required for all debian packages.
+   The binary release versions are:
+        1.69 : 3.0.0
+        1.70 : 4.0.0
+        1.71 : 4.2.0
+        1.72 : 4.3.0
+        1.73 : 4.4.0
+
+   A brief version chronology is maintained in version-notes.html.
+   Starting with gcc 4.3.3, error warnings (-Werror) are given for
+   minor infractions like not checking return values of built-in C
+   functions.  I have attempted to eliminate these warnings.
+   In any event, you will see warnings with the -Wall flag.
+
+</pre>
+
+<!-- JS Window Closer -----
+<form>
+<center>
+<input type="button" onclick="window.close();" value="Close this window">
+</center>
+</form>
+----- JS Window Closer -->
+
+</body>
+</html>
diff --git a/aclocal.m4 b/aclocal.m4
new file mode 100644 (file)
index 0000000..042d5f2
--- /dev/null
@@ -0,0 +1,9634 @@
+# generated automatically by aclocal 1.11.3 -*- Autoconf -*-
+
+# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+# 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation,
+# Inc.
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+m4_ifndef([AC_AUTOCONF_VERSION],
+  [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.68],,
+[m4_warning([this file was generated for autoconf 2.68.
+You have another version of autoconf.  It may work, but is not guaranteed to.
+If you have problems, you may need to regenerate the build system entirely.
+To do so, use the procedure documented by the package, typically `autoreconf'.])])
+
+# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*-
+#
+#   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
+#                 2006, 2007, 2008, 2009, 2010, 2011 Free Software
+#                 Foundation, Inc.
+#   Written by Gordon Matzigkeit, 1996
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+m4_define([_LT_COPYING], [dnl
+#   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
+#                 2006, 2007, 2008, 2009, 2010, 2011 Free Software
+#                 Foundation, Inc.
+#   Written by Gordon Matzigkeit, 1996
+#
+#   This file is part of GNU Libtool.
+#
+# GNU Libtool is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+#
+# As a special exception to the GNU General Public License,
+# if you distribute this file as part of a program or library that
+# is built using GNU Libtool, you may include this file under the
+# same distribution terms that you use for the rest of that program.
+#
+# GNU Libtool is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Libtool; see the file COPYING.  If not, a copy
+# can be downloaded from http://www.gnu.org/licenses/gpl.html, or
+# obtained by writing to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+])
+
+# serial 57 LT_INIT
+
+
+# LT_PREREQ(VERSION)
+# ------------------
+# Complain and exit if this libtool version is less that VERSION.
+m4_defun([LT_PREREQ],
+[m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1,
+       [m4_default([$3],
+                  [m4_fatal([Libtool version $1 or higher is required],
+                            63)])],
+       [$2])])
+
+
+# _LT_CHECK_BUILDDIR
+# ------------------
+# Complain if the absolute build directory name contains unusual characters
+m4_defun([_LT_CHECK_BUILDDIR],
+[case `pwd` in
+  *\ * | *\    *)
+    AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;;
+esac
+])
+
+
+# LT_INIT([OPTIONS])
+# ------------------
+AC_DEFUN([LT_INIT],
+[AC_PREREQ([2.58])dnl We use AC_INCLUDES_DEFAULT
+AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl
+AC_BEFORE([$0], [LT_LANG])dnl
+AC_BEFORE([$0], [LT_OUTPUT])dnl
+AC_BEFORE([$0], [LTDL_INIT])dnl
+m4_require([_LT_CHECK_BUILDDIR])dnl
+
+dnl Autoconf doesn't catch unexpanded LT_ macros by default:
+m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl
+m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl
+dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4
+dnl unless we require an AC_DEFUNed macro:
+AC_REQUIRE([LTOPTIONS_VERSION])dnl
+AC_REQUIRE([LTSUGAR_VERSION])dnl
+AC_REQUIRE([LTVERSION_VERSION])dnl
+AC_REQUIRE([LTOBSOLETE_VERSION])dnl
+m4_require([_LT_PROG_LTMAIN])dnl
+
+_LT_SHELL_INIT([SHELL=${CONFIG_SHELL-/bin/sh}])
+
+dnl Parse OPTIONS
+_LT_SET_OPTIONS([$0], [$1])
+
+# This can be used to rebuild libtool when needed
+LIBTOOL_DEPS="$ltmain"
+
+# Always use our own libtool.
+LIBTOOL='$(SHELL) $(top_builddir)/libtool'
+AC_SUBST(LIBTOOL)dnl
+
+_LT_SETUP
+
+# Only expand once:
+m4_define([LT_INIT])
+])# LT_INIT
+
+# Old names:
+AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT])
+AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_PROG_LIBTOOL], [])
+dnl AC_DEFUN([AM_PROG_LIBTOOL], [])
+
+
+# _LT_CC_BASENAME(CC)
+# -------------------
+# Calculate cc_basename.  Skip known compiler wrappers and cross-prefix.
+m4_defun([_LT_CC_BASENAME],
+[for cc_temp in $1""; do
+  case $cc_temp in
+    compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;;
+    distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;;
+    \-*) ;;
+    *) break;;
+  esac
+done
+cc_basename=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"`
+])
+
+
+# _LT_FILEUTILS_DEFAULTS
+# ----------------------
+# It is okay to use these file commands and assume they have been set
+# sensibly after `m4_require([_LT_FILEUTILS_DEFAULTS])'.
+m4_defun([_LT_FILEUTILS_DEFAULTS],
+[: ${CP="cp -f"}
+: ${MV="mv -f"}
+: ${RM="rm -f"}
+])# _LT_FILEUTILS_DEFAULTS
+
+
+# _LT_SETUP
+# ---------
+m4_defun([_LT_SETUP],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+AC_REQUIRE([_LT_PREPARE_SED_QUOTE_VARS])dnl
+AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl
+
+_LT_DECL([], [PATH_SEPARATOR], [1], [The PATH separator for the build system])dnl
+dnl
+_LT_DECL([], [host_alias], [0], [The host system])dnl
+_LT_DECL([], [host], [0])dnl
+_LT_DECL([], [host_os], [0])dnl
+dnl
+_LT_DECL([], [build_alias], [0], [The build system])dnl
+_LT_DECL([], [build], [0])dnl
+_LT_DECL([], [build_os], [0])dnl
+dnl
+AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([LT_PATH_LD])dnl
+AC_REQUIRE([LT_PATH_NM])dnl
+dnl
+AC_REQUIRE([AC_PROG_LN_S])dnl
+test -z "$LN_S" && LN_S="ln -s"
+_LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl
+dnl
+AC_REQUIRE([LT_CMD_MAX_LEN])dnl
+_LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl
+_LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl
+dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_CHECK_SHELL_FEATURES])dnl
+m4_require([_LT_PATH_CONVERSION_FUNCTIONS])dnl
+m4_require([_LT_CMD_RELOAD])dnl
+m4_require([_LT_CHECK_MAGIC_METHOD])dnl
+m4_require([_LT_CHECK_SHAREDLIB_FROM_LINKLIB])dnl
+m4_require([_LT_CMD_OLD_ARCHIVE])dnl
+m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl
+m4_require([_LT_WITH_SYSROOT])dnl
+
+_LT_CONFIG_LIBTOOL_INIT([
+# See if we are running on zsh, and set the options which allow our
+# commands through without removal of \ escapes INIT.
+if test -n "\${ZSH_VERSION+set}" ; then
+   setopt NO_GLOB_SUBST
+fi
+])
+if test -n "${ZSH_VERSION+set}" ; then
+   setopt NO_GLOB_SUBST
+fi
+
+_LT_CHECK_OBJDIR
+
+m4_require([_LT_TAG_COMPILER])dnl
+
+case $host_os in
+aix3*)
+  # AIX sometimes has problems with the GCC collect2 program.  For some
+  # reason, if we set the COLLECT_NAMES environment variable, the problems
+  # vanish in a puff of smoke.
+  if test "X${COLLECT_NAMES+set}" != Xset; then
+    COLLECT_NAMES=
+    export COLLECT_NAMES
+  fi
+  ;;
+esac
+
+# Global variables:
+ofile=libtool
+can_build_shared=yes
+
+# All known linkers require a `.a' archive for static linking (except MSVC,
+# which needs '.lib').
+libext=a
+
+with_gnu_ld="$lt_cv_prog_gnu_ld"
+
+old_CC="$CC"
+old_CFLAGS="$CFLAGS"
+
+# Set sane defaults for various variables
+test -z "$CC" && CC=cc
+test -z "$LTCC" && LTCC=$CC
+test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS
+test -z "$LD" && LD=ld
+test -z "$ac_objext" && ac_objext=o
+
+_LT_CC_BASENAME([$compiler])
+
+# Only perform the check for file, if the check method requires it
+test -z "$MAGIC_CMD" && MAGIC_CMD=file
+case $deplibs_check_method in
+file_magic*)
+  if test "$file_magic_cmd" = '$MAGIC_CMD'; then
+    _LT_PATH_MAGIC
+  fi
+  ;;
+esac
+
+# Use C for the default configuration in the libtool script
+LT_SUPPORTED_TAG([CC])
+_LT_LANG_C_CONFIG
+_LT_LANG_DEFAULT_CONFIG
+_LT_CONFIG_COMMANDS
+])# _LT_SETUP
+
+
+# _LT_PREPARE_SED_QUOTE_VARS
+# --------------------------
+# Define a few sed substitution that help us do robust quoting.
+m4_defun([_LT_PREPARE_SED_QUOTE_VARS],
+[# Backslashify metacharacters that are still active within
+# double-quoted strings.
+sed_quote_subst='s/\([["`$\\]]\)/\\\1/g'
+
+# Same as above, but do not quote variable references.
+double_quote_subst='s/\([["`\\]]\)/\\\1/g'
+
+# Sed substitution to delay expansion of an escaped shell variable in a
+# double_quote_subst'ed string.
+delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g'
+
+# Sed substitution to delay expansion of an escaped single quote.
+delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g'
+
+# Sed substitution to avoid accidental globbing in evaled expressions
+no_glob_subst='s/\*/\\\*/g'
+])
+
+# _LT_PROG_LTMAIN
+# ---------------
+# Note that this code is called both from `configure', and `config.status'
+# now that we use AC_CONFIG_COMMANDS to generate libtool.  Notably,
+# `config.status' has no value for ac_aux_dir unless we are using Automake,
+# so we pass a copy along to make sure it has a sensible value anyway.
+m4_defun([_LT_PROG_LTMAIN],
+[m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl
+_LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir'])
+ltmain="$ac_aux_dir/ltmain.sh"
+])# _LT_PROG_LTMAIN
+
+
+
+# So that we can recreate a full libtool script including additional
+# tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS
+# in macros and then make a single call at the end using the `libtool'
+# label.
+
+
+# _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS])
+# ----------------------------------------
+# Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later.
+m4_define([_LT_CONFIG_LIBTOOL_INIT],
+[m4_ifval([$1],
+          [m4_append([_LT_OUTPUT_LIBTOOL_INIT],
+                     [$1
+])])])
+
+# Initialize.
+m4_define([_LT_OUTPUT_LIBTOOL_INIT])
+
+
+# _LT_CONFIG_LIBTOOL([COMMANDS])
+# ------------------------------
+# Register COMMANDS to be passed to AC_CONFIG_COMMANDS later.
+m4_define([_LT_CONFIG_LIBTOOL],
+[m4_ifval([$1],
+          [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS],
+                     [$1
+])])])
+
+# Initialize.
+m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS])
+
+
+# _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS])
+# -----------------------------------------------------
+m4_defun([_LT_CONFIG_SAVE_COMMANDS],
+[_LT_CONFIG_LIBTOOL([$1])
+_LT_CONFIG_LIBTOOL_INIT([$2])
+])
+
+
+# _LT_FORMAT_COMMENT([COMMENT])
+# -----------------------------
+# Add leading comment marks to the start of each line, and a trailing
+# full-stop to the whole comment if one is not present already.
+m4_define([_LT_FORMAT_COMMENT],
+[m4_ifval([$1], [
+m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])],
+              [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.])
+)])
+
+
+
+
+
+# _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?])
+# -------------------------------------------------------------------
+# CONFIGNAME is the name given to the value in the libtool script.
+# VARNAME is the (base) name used in the configure script.
+# VALUE may be 0, 1 or 2 for a computed quote escaped value based on
+# VARNAME.  Any other value will be used directly.
+m4_define([_LT_DECL],
+[lt_if_append_uniq([lt_decl_varnames], [$2], [, ],
+    [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name],
+       [m4_ifval([$1], [$1], [$2])])
+    lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3])
+    m4_ifval([$4],
+       [lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])])
+    lt_dict_add_subkey([lt_decl_dict], [$2],
+       [tagged?], [m4_ifval([$5], [yes], [no])])])
+])
+
+
+# _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION])
+# --------------------------------------------------------
+m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])])
+
+
+# lt_decl_tag_varnames([SEPARATOR], [VARNAME1...])
+# ------------------------------------------------
+m4_define([lt_decl_tag_varnames],
+[_lt_decl_filter([tagged?], [yes], $@)])
+
+
+# _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..])
+# ---------------------------------------------------------
+m4_define([_lt_decl_filter],
+[m4_case([$#],
+  [0], [m4_fatal([$0: too few arguments: $#])],
+  [1], [m4_fatal([$0: too few arguments: $#: $1])],
+  [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)],
+  [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)],
+  [lt_dict_filter([lt_decl_dict], $@)])[]dnl
+])
+
+
+# lt_decl_quote_varnames([SEPARATOR], [VARNAME1...])
+# --------------------------------------------------
+m4_define([lt_decl_quote_varnames],
+[_lt_decl_filter([value], [1], $@)])
+
+
+# lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...])
+# ---------------------------------------------------
+m4_define([lt_decl_dquote_varnames],
+[_lt_decl_filter([value], [2], $@)])
+
+
+# lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...])
+# ---------------------------------------------------
+m4_define([lt_decl_varnames_tagged],
+[m4_assert([$# <= 2])dnl
+_$0(m4_quote(m4_default([$1], [[, ]])),
+    m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]),
+    m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))])
+m4_define([_lt_decl_varnames_tagged],
+[m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])])
+
+
+# lt_decl_all_varnames([SEPARATOR], [VARNAME1...])
+# ------------------------------------------------
+m4_define([lt_decl_all_varnames],
+[_$0(m4_quote(m4_default([$1], [[, ]])),
+     m4_if([$2], [],
+          m4_quote(lt_decl_varnames),
+       m4_quote(m4_shift($@))))[]dnl
+])
+m4_define([_lt_decl_all_varnames],
+[lt_join($@, lt_decl_varnames_tagged([$1],
+                       lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl
+])
+
+
+# _LT_CONFIG_STATUS_DECLARE([VARNAME])
+# ------------------------------------
+# Quote a variable value, and forward it to `config.status' so that its
+# declaration there will have the same value as in `configure'.  VARNAME
+# must have a single quote delimited value for this to work.
+m4_define([_LT_CONFIG_STATUS_DECLARE],
+[$1='`$ECHO "$][$1" | $SED "$delay_single_quote_subst"`'])
+
+
+# _LT_CONFIG_STATUS_DECLARATIONS
+# ------------------------------
+# We delimit libtool config variables with single quotes, so when
+# we write them to config.status, we have to be sure to quote all
+# embedded single quotes properly.  In configure, this macro expands
+# each variable declared with _LT_DECL (and _LT_TAGDECL) into:
+#
+#    <var>='`$ECHO "$<var>" | $SED "$delay_single_quote_subst"`'
+m4_defun([_LT_CONFIG_STATUS_DECLARATIONS],
+[m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames),
+    [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])])
+
+
+# _LT_LIBTOOL_TAGS
+# ----------------
+# Output comment and list of tags supported by the script
+m4_defun([_LT_LIBTOOL_TAGS],
+[_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl
+available_tags="_LT_TAGS"dnl
+])
+
+
+# _LT_LIBTOOL_DECLARE(VARNAME, [TAG])
+# -----------------------------------
+# Extract the dictionary values for VARNAME (optionally with TAG) and
+# expand to a commented shell variable setting:
+#
+#    # Some comment about what VAR is for.
+#    visible_name=$lt_internal_name
+m4_define([_LT_LIBTOOL_DECLARE],
+[_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1],
+                                          [description])))[]dnl
+m4_pushdef([_libtool_name],
+    m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl
+m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])),
+    [0], [_libtool_name=[$]$1],
+    [1], [_libtool_name=$lt_[]$1],
+    [2], [_libtool_name=$lt_[]$1],
+    [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl
+m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl
+])
+
+
+# _LT_LIBTOOL_CONFIG_VARS
+# -----------------------
+# Produce commented declarations of non-tagged libtool config variables
+# suitable for insertion in the LIBTOOL CONFIG section of the `libtool'
+# script.  Tagged libtool config variables (even for the LIBTOOL CONFIG
+# section) are produced by _LT_LIBTOOL_TAG_VARS.
+m4_defun([_LT_LIBTOOL_CONFIG_VARS],
+[m4_foreach([_lt_var],
+    m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)),
+    [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])])
+
+
+# _LT_LIBTOOL_TAG_VARS(TAG)
+# -------------------------
+m4_define([_LT_LIBTOOL_TAG_VARS],
+[m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames),
+    [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])])
+
+
+# _LT_TAGVAR(VARNAME, [TAGNAME])
+# ------------------------------
+m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])])
+
+
+# _LT_CONFIG_COMMANDS
+# -------------------
+# Send accumulated output to $CONFIG_STATUS.  Thanks to the lists of
+# variables for single and double quote escaping we saved from calls
+# to _LT_DECL, we can put quote escaped variables declarations
+# into `config.status', and then the shell code to quote escape them in
+# for loops in `config.status'.  Finally, any additional code accumulated
+# from calls to _LT_CONFIG_LIBTOOL_INIT is expanded.
+m4_defun([_LT_CONFIG_COMMANDS],
+[AC_PROVIDE_IFELSE([LT_OUTPUT],
+       dnl If the libtool generation code has been placed in $CONFIG_LT,
+       dnl instead of duplicating it all over again into config.status,
+       dnl then we will have config.status run $CONFIG_LT later, so it
+       dnl needs to know what name is stored there:
+        [AC_CONFIG_COMMANDS([libtool],
+            [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])],
+    dnl If the libtool generation code is destined for config.status,
+    dnl expand the accumulated commands and init code now:
+    [AC_CONFIG_COMMANDS([libtool],
+        [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])])
+])#_LT_CONFIG_COMMANDS
+
+
+# Initialize.
+m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT],
+[
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+sed_quote_subst='$sed_quote_subst'
+double_quote_subst='$double_quote_subst'
+delay_variable_subst='$delay_variable_subst'
+_LT_CONFIG_STATUS_DECLARATIONS
+LTCC='$LTCC'
+LTCFLAGS='$LTCFLAGS'
+compiler='$compiler_DEFAULT'
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+  eval 'cat <<_LTECHO_EOF
+\$[]1
+_LTECHO_EOF'
+}
+
+# Quote evaled strings.
+for var in lt_decl_all_varnames([[ \
+]], lt_decl_quote_varnames); do
+    case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+    *[[\\\\\\\`\\"\\\$]]*)
+      eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\""
+      ;;
+    *)
+      eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+      ;;
+    esac
+done
+
+# Double-quote double-evaled strings.
+for var in lt_decl_all_varnames([[ \
+]], lt_decl_dquote_varnames); do
+    case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+    *[[\\\\\\\`\\"\\\$]]*)
+      eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\""
+      ;;
+    *)
+      eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+      ;;
+    esac
+done
+
+_LT_OUTPUT_LIBTOOL_INIT
+])
+
+# _LT_GENERATED_FILE_INIT(FILE, [COMMENT])
+# ------------------------------------
+# Generate a child script FILE with all initialization necessary to
+# reuse the environment learned by the parent script, and make the
+# file executable.  If COMMENT is supplied, it is inserted after the
+# `#!' sequence but before initialization text begins.  After this
+# macro, additional text can be appended to FILE to form the body of
+# the child script.  The macro ends with non-zero status if the
+# file could not be fully written (such as if the disk is full).
+m4_ifdef([AS_INIT_GENERATED],
+[m4_defun([_LT_GENERATED_FILE_INIT],[AS_INIT_GENERATED($@)])],
+[m4_defun([_LT_GENERATED_FILE_INIT],
+[m4_require([AS_PREPARE])]dnl
+[m4_pushdef([AS_MESSAGE_LOG_FD])]dnl
+[lt_write_fail=0
+cat >$1 <<_ASEOF || lt_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+$2
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$1 <<\_ASEOF || lt_write_fail=1
+AS_SHELL_SANITIZE
+_AS_PREPARE
+exec AS_MESSAGE_FD>&1
+_ASEOF
+test $lt_write_fail = 0 && chmod +x $1[]dnl
+m4_popdef([AS_MESSAGE_LOG_FD])])])# _LT_GENERATED_FILE_INIT
+
+# LT_OUTPUT
+# ---------
+# This macro allows early generation of the libtool script (before
+# AC_OUTPUT is called), incase it is used in configure for compilation
+# tests.
+AC_DEFUN([LT_OUTPUT],
+[: ${CONFIG_LT=./config.lt}
+AC_MSG_NOTICE([creating $CONFIG_LT])
+_LT_GENERATED_FILE_INIT(["$CONFIG_LT"],
+[# Run this file to recreate a libtool stub with the current configuration.])
+
+cat >>"$CONFIG_LT" <<\_LTEOF
+lt_cl_silent=false
+exec AS_MESSAGE_LOG_FD>>config.log
+{
+  echo
+  AS_BOX([Running $as_me.])
+} >&AS_MESSAGE_LOG_FD
+
+lt_cl_help="\
+\`$as_me' creates a local libtool stub from the current configuration,
+for use in further configure time tests before the real libtool is
+generated.
+
+Usage: $[0] [[OPTIONS]]
+
+  -h, --help      print this help, then exit
+  -V, --version   print version number, then exit
+  -q, --quiet     do not print progress messages
+  -d, --debug     don't remove temporary files
+
+Report bugs to <bug-libtool@gnu.org>."
+
+lt_cl_version="\
+m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl
+m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION])
+configured by $[0], generated by m4_PACKAGE_STRING.
+
+Copyright (C) 2011 Free Software Foundation, Inc.
+This config.lt script is free software; the Free Software Foundation
+gives unlimited permision to copy, distribute and modify it."
+
+while test $[#] != 0
+do
+  case $[1] in
+    --version | --v* | -V )
+      echo "$lt_cl_version"; exit 0 ;;
+    --help | --h* | -h )
+      echo "$lt_cl_help"; exit 0 ;;
+    --debug | --d* | -d )
+      debug=: ;;
+    --quiet | --q* | --silent | --s* | -q )
+      lt_cl_silent=: ;;
+
+    -*) AC_MSG_ERROR([unrecognized option: $[1]
+Try \`$[0] --help' for more information.]) ;;
+
+    *) AC_MSG_ERROR([unrecognized argument: $[1]
+Try \`$[0] --help' for more information.]) ;;
+  esac
+  shift
+done
+
+if $lt_cl_silent; then
+  exec AS_MESSAGE_FD>/dev/null
+fi
+_LTEOF
+
+cat >>"$CONFIG_LT" <<_LTEOF
+_LT_OUTPUT_LIBTOOL_COMMANDS_INIT
+_LTEOF
+
+cat >>"$CONFIG_LT" <<\_LTEOF
+AC_MSG_NOTICE([creating $ofile])
+_LT_OUTPUT_LIBTOOL_COMMANDS
+AS_EXIT(0)
+_LTEOF
+chmod +x "$CONFIG_LT"
+
+# configure is writing to config.log, but config.lt does its own redirection,
+# appending to config.log, which fails on DOS, as config.log is still kept
+# open by configure.  Here we exec the FD to /dev/null, effectively closing
+# config.log, so it can be properly (re)opened and appended to by config.lt.
+lt_cl_success=:
+test "$silent" = yes &&
+  lt_config_lt_args="$lt_config_lt_args --quiet"
+exec AS_MESSAGE_LOG_FD>/dev/null
+$SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false
+exec AS_MESSAGE_LOG_FD>>config.log
+$lt_cl_success || AS_EXIT(1)
+])# LT_OUTPUT
+
+
+# _LT_CONFIG(TAG)
+# ---------------
+# If TAG is the built-in tag, create an initial libtool script with a
+# default configuration from the untagged config vars.  Otherwise add code
+# to config.status for appending the configuration named by TAG from the
+# matching tagged config vars.
+m4_defun([_LT_CONFIG],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+_LT_CONFIG_SAVE_COMMANDS([
+  m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl
+  m4_if(_LT_TAG, [C], [
+    # See if we are running on zsh, and set the options which allow our
+    # commands through without removal of \ escapes.
+    if test -n "${ZSH_VERSION+set}" ; then
+      setopt NO_GLOB_SUBST
+    fi
+
+    cfgfile="${ofile}T"
+    trap "$RM \"$cfgfile\"; exit 1" 1 2 15
+    $RM "$cfgfile"
+
+    cat <<_LT_EOF >> "$cfgfile"
+#! $SHELL
+
+# `$ECHO "$ofile" | sed 's%^.*/%%'` - Provide generalized library-building support services.
+# Generated automatically by $as_me ($PACKAGE$TIMESTAMP) $VERSION
+# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+# NOTE: Changes made to this file will be lost: look at ltmain.sh.
+#
+_LT_COPYING
+_LT_LIBTOOL_TAGS
+
+# ### BEGIN LIBTOOL CONFIG
+_LT_LIBTOOL_CONFIG_VARS
+_LT_LIBTOOL_TAG_VARS
+# ### END LIBTOOL CONFIG
+
+_LT_EOF
+
+  case $host_os in
+  aix3*)
+    cat <<\_LT_EOF >> "$cfgfile"
+# AIX sometimes has problems with the GCC collect2 program.  For some
+# reason, if we set the COLLECT_NAMES environment variable, the problems
+# vanish in a puff of smoke.
+if test "X${COLLECT_NAMES+set}" != Xset; then
+  COLLECT_NAMES=
+  export COLLECT_NAMES
+fi
+_LT_EOF
+    ;;
+  esac
+
+  _LT_PROG_LTMAIN
+
+  # We use sed instead of cat because bash on DJGPP gets confused if
+  # if finds mixed CR/LF and LF-only lines.  Since sed operates in
+  # text mode, it properly converts lines to CR/LF.  This bash problem
+  # is reportedly fixed, but why not run on old versions too?
+  sed '$q' "$ltmain" >> "$cfgfile" \
+     || (rm -f "$cfgfile"; exit 1)
+
+  _LT_PROG_REPLACE_SHELLFNS
+
+   mv -f "$cfgfile" "$ofile" ||
+    (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile")
+  chmod +x "$ofile"
+],
+[cat <<_LT_EOF >> "$ofile"
+
+dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded
+dnl in a comment (ie after a #).
+# ### BEGIN LIBTOOL TAG CONFIG: $1
+_LT_LIBTOOL_TAG_VARS(_LT_TAG)
+# ### END LIBTOOL TAG CONFIG: $1
+_LT_EOF
+])dnl /m4_if
+],
+[m4_if([$1], [], [
+    PACKAGE='$PACKAGE'
+    VERSION='$VERSION'
+    TIMESTAMP='$TIMESTAMP'
+    RM='$RM'
+    ofile='$ofile'], [])
+])dnl /_LT_CONFIG_SAVE_COMMANDS
+])# _LT_CONFIG
+
+
+# LT_SUPPORTED_TAG(TAG)
+# ---------------------
+# Trace this macro to discover what tags are supported by the libtool
+# --tag option, using:
+#    autoconf --trace 'LT_SUPPORTED_TAG:$1'
+AC_DEFUN([LT_SUPPORTED_TAG], [])
+
+
+# C support is built-in for now
+m4_define([_LT_LANG_C_enabled], [])
+m4_define([_LT_TAGS], [])
+
+
+# LT_LANG(LANG)
+# -------------
+# Enable libtool support for the given language if not already enabled.
+AC_DEFUN([LT_LANG],
+[AC_BEFORE([$0], [LT_OUTPUT])dnl
+m4_case([$1],
+  [C],                 [_LT_LANG(C)],
+  [C++],               [_LT_LANG(CXX)],
+  [Go],                        [_LT_LANG(GO)],
+  [Java],              [_LT_LANG(GCJ)],
+  [Fortran 77],                [_LT_LANG(F77)],
+  [Fortran],           [_LT_LANG(FC)],
+  [Windows Resource],  [_LT_LANG(RC)],
+  [m4_ifdef([_LT_LANG_]$1[_CONFIG],
+    [_LT_LANG($1)],
+    [m4_fatal([$0: unsupported language: "$1"])])])dnl
+])# LT_LANG
+
+
+# _LT_LANG(LANGNAME)
+# ------------------
+m4_defun([_LT_LANG],
+[m4_ifdef([_LT_LANG_]$1[_enabled], [],
+  [LT_SUPPORTED_TAG([$1])dnl
+  m4_append([_LT_TAGS], [$1 ])dnl
+  m4_define([_LT_LANG_]$1[_enabled], [])dnl
+  _LT_LANG_$1_CONFIG($1)])dnl
+])# _LT_LANG
+
+
+m4_ifndef([AC_PROG_GO], [
+# NOTE: This macro has been submitted for inclusion into   #
+#  GNU Autoconf as AC_PROG_GO.  When it is available in    #
+#  a released version of Autoconf we should remove this    #
+#  macro and use it instead.                               #
+m4_defun([AC_PROG_GO],
+[AC_LANG_PUSH(Go)dnl
+AC_ARG_VAR([GOC],     [Go compiler command])dnl
+AC_ARG_VAR([GOFLAGS], [Go compiler flags])dnl
+_AC_ARG_VAR_LDFLAGS()dnl
+AC_CHECK_TOOL(GOC, gccgo)
+if test -z "$GOC"; then
+  if test -n "$ac_tool_prefix"; then
+    AC_CHECK_PROG(GOC, [${ac_tool_prefix}gccgo], [${ac_tool_prefix}gccgo])
+  fi
+fi
+if test -z "$GOC"; then
+  AC_CHECK_PROG(GOC, gccgo, gccgo, false)
+fi
+])#m4_defun
+])#m4_ifndef
+
+
+# _LT_LANG_DEFAULT_CONFIG
+# -----------------------
+m4_defun([_LT_LANG_DEFAULT_CONFIG],
+[AC_PROVIDE_IFELSE([AC_PROG_CXX],
+  [LT_LANG(CXX)],
+  [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])])
+
+AC_PROVIDE_IFELSE([AC_PROG_F77],
+  [LT_LANG(F77)],
+  [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])])
+
+AC_PROVIDE_IFELSE([AC_PROG_FC],
+  [LT_LANG(FC)],
+  [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])])
+
+dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal
+dnl pulling things in needlessly.
+AC_PROVIDE_IFELSE([AC_PROG_GCJ],
+  [LT_LANG(GCJ)],
+  [AC_PROVIDE_IFELSE([A][M_PROG_GCJ],
+    [LT_LANG(GCJ)],
+    [AC_PROVIDE_IFELSE([LT_PROG_GCJ],
+      [LT_LANG(GCJ)],
+      [m4_ifdef([AC_PROG_GCJ],
+       [m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])])
+       m4_ifdef([A][M_PROG_GCJ],
+       [m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])])
+       m4_ifdef([LT_PROG_GCJ],
+       [m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])])
+
+AC_PROVIDE_IFELSE([AC_PROG_GO],
+  [LT_LANG(GO)],
+  [m4_define([AC_PROG_GO], defn([AC_PROG_GO])[LT_LANG(GO)])])
+
+AC_PROVIDE_IFELSE([LT_PROG_RC],
+  [LT_LANG(RC)],
+  [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])])
+])# _LT_LANG_DEFAULT_CONFIG
+
+# Obsolete macros:
+AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)])
+AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)])
+AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)])
+AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)])
+AU_DEFUN([AC_LIBTOOL_RC], [LT_LANG(Windows Resource)])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_CXX], [])
+dnl AC_DEFUN([AC_LIBTOOL_F77], [])
+dnl AC_DEFUN([AC_LIBTOOL_FC], [])
+dnl AC_DEFUN([AC_LIBTOOL_GCJ], [])
+dnl AC_DEFUN([AC_LIBTOOL_RC], [])
+
+
+# _LT_TAG_COMPILER
+# ----------------
+m4_defun([_LT_TAG_COMPILER],
+[AC_REQUIRE([AC_PROG_CC])dnl
+
+_LT_DECL([LTCC], [CC], [1], [A C compiler])dnl
+_LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl
+_LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl
+_LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+])# _LT_TAG_COMPILER
+
+
+# _LT_COMPILER_BOILERPLATE
+# ------------------------
+# Check for compiler boilerplate output or warnings with
+# the simple compiler test code.
+m4_defun([_LT_COMPILER_BOILERPLATE],
+[m4_require([_LT_DECL_SED])dnl
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_compile_test_code" >conftest.$ac_ext
+eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_compiler_boilerplate=`cat conftest.err`
+$RM conftest*
+])# _LT_COMPILER_BOILERPLATE
+
+
+# _LT_LINKER_BOILERPLATE
+# ----------------------
+# Check for linker boilerplate output or warnings with
+# the simple link test code.
+m4_defun([_LT_LINKER_BOILERPLATE],
+[m4_require([_LT_DECL_SED])dnl
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_link_test_code" >conftest.$ac_ext
+eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_linker_boilerplate=`cat conftest.err`
+$RM -r conftest*
+])# _LT_LINKER_BOILERPLATE
+
+# _LT_REQUIRED_DARWIN_CHECKS
+# -------------------------
+m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[
+  case $host_os in
+    rhapsody* | darwin*)
+    AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:])
+    AC_CHECK_TOOL([NMEDIT], [nmedit], [:])
+    AC_CHECK_TOOL([LIPO], [lipo], [:])
+    AC_CHECK_TOOL([OTOOL], [otool], [:])
+    AC_CHECK_TOOL([OTOOL64], [otool64], [:])
+    _LT_DECL([], [DSYMUTIL], [1],
+      [Tool to manipulate archived DWARF debug symbol files on Mac OS X])
+    _LT_DECL([], [NMEDIT], [1],
+      [Tool to change global to local symbols on Mac OS X])
+    _LT_DECL([], [LIPO], [1],
+      [Tool to manipulate fat objects and archives on Mac OS X])
+    _LT_DECL([], [OTOOL], [1],
+      [ldd/readelf like tool for Mach-O binaries on Mac OS X])
+    _LT_DECL([], [OTOOL64], [1],
+      [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4])
+
+    AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod],
+      [lt_cv_apple_cc_single_mod=no
+      if test -z "${LT_MULTI_MODULE}"; then
+       # By default we will add the -single_module flag. You can override
+       # by either setting the environment variable LT_MULTI_MODULE
+       # non-empty at configure time, or by adding -multi_module to the
+       # link flags.
+       rm -rf libconftest.dylib*
+       echo "int foo(void){return 1;}" > conftest.c
+       echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+-dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD
+       $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+         -dynamiclib -Wl,-single_module conftest.c 2>conftest.err
+        _lt_result=$?
+       # If there is a non-empty error log, and "single_module"
+       # appears in it, assume the flag caused a linker warning
+        if test -s conftest.err && $GREP single_module conftest.err; then
+         cat conftest.err >&AS_MESSAGE_LOG_FD
+       # Otherwise, if the output was created with a 0 exit code from
+       # the compiler, it worked.
+       elif test -f libconftest.dylib && test $_lt_result -eq 0; then
+         lt_cv_apple_cc_single_mod=yes
+       else
+         cat conftest.err >&AS_MESSAGE_LOG_FD
+       fi
+       rm -rf libconftest.dylib*
+       rm -f conftest.*
+      fi])
+
+    AC_CACHE_CHECK([for -exported_symbols_list linker flag],
+      [lt_cv_ld_exported_symbols_list],
+      [lt_cv_ld_exported_symbols_list=no
+      save_LDFLAGS=$LDFLAGS
+      echo "_main" > conftest.sym
+      LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym"
+      AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])],
+       [lt_cv_ld_exported_symbols_list=yes],
+       [lt_cv_ld_exported_symbols_list=no])
+       LDFLAGS="$save_LDFLAGS"
+    ])
+
+    AC_CACHE_CHECK([for -force_load linker flag],[lt_cv_ld_force_load],
+      [lt_cv_ld_force_load=no
+      cat > conftest.c << _LT_EOF
+int forced_loaded() { return 2;}
+_LT_EOF
+      echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD
+      $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD
+      echo "$AR cru libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD
+      $AR cru libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD
+      echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD
+      $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD
+      cat > conftest.c << _LT_EOF
+int main() { return 0;}
+_LT_EOF
+      echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&AS_MESSAGE_LOG_FD
+      $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err
+      _lt_result=$?
+      if test -s conftest.err && $GREP force_load conftest.err; then
+       cat conftest.err >&AS_MESSAGE_LOG_FD
+      elif test -f conftest && test $_lt_result -eq 0 && $GREP forced_load conftest >/dev/null 2>&1 ; then
+       lt_cv_ld_force_load=yes
+      else
+       cat conftest.err >&AS_MESSAGE_LOG_FD
+      fi
+        rm -f conftest.err libconftest.a conftest conftest.c
+        rm -rf conftest.dSYM
+    ])
+    case $host_os in
+    rhapsody* | darwin1.[[012]])
+      _lt_dar_allow_undefined='${wl}-undefined ${wl}suppress' ;;
+    darwin1.*)
+      _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;;
+    darwin*) # darwin 5.x on
+      # if running on 10.5 or later, the deployment target defaults
+      # to the OS version, if on x86, and 10.4, the deployment
+      # target defaults to 10.4. Don't you love it?
+      case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in
+       10.0,*86*-darwin8*|10.0,*-darwin[[91]]*)
+         _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;;
+       10.[[012]]*)
+         _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;;
+       10.*)
+         _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;;
+      esac
+    ;;
+  esac
+    if test "$lt_cv_apple_cc_single_mod" = "yes"; then
+      _lt_dar_single_mod='$single_module'
+    fi
+    if test "$lt_cv_ld_exported_symbols_list" = "yes"; then
+      _lt_dar_export_syms=' ${wl}-exported_symbols_list,$output_objdir/${libname}-symbols.expsym'
+    else
+      _lt_dar_export_syms='~$NMEDIT -s $output_objdir/${libname}-symbols.expsym ${lib}'
+    fi
+    if test "$DSYMUTIL" != ":" && test "$lt_cv_ld_force_load" = "no"; then
+      _lt_dsymutil='~$DSYMUTIL $lib || :'
+    else
+      _lt_dsymutil=
+    fi
+    ;;
+  esac
+])
+
+
+# _LT_DARWIN_LINKER_FEATURES([TAG])
+# ---------------------------------
+# Checks for linker and compiler features on darwin
+m4_defun([_LT_DARWIN_LINKER_FEATURES],
+[
+  m4_require([_LT_REQUIRED_DARWIN_CHECKS])
+  _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+  _LT_TAGVAR(hardcode_direct, $1)=no
+  _LT_TAGVAR(hardcode_automatic, $1)=yes
+  _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported
+  if test "$lt_cv_ld_force_load" = "yes"; then
+    _LT_TAGVAR(whole_archive_flag_spec, $1)='`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience ${wl}-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`'
+    m4_case([$1], [F77], [_LT_TAGVAR(compiler_needs_object, $1)=yes],
+                  [FC],  [_LT_TAGVAR(compiler_needs_object, $1)=yes])
+  else
+    _LT_TAGVAR(whole_archive_flag_spec, $1)=''
+  fi
+  _LT_TAGVAR(link_all_deplibs, $1)=yes
+  _LT_TAGVAR(allow_undefined_flag, $1)="$_lt_dar_allow_undefined"
+  case $cc_basename in
+     ifort*) _lt_dar_can_shared=yes ;;
+     *) _lt_dar_can_shared=$GCC ;;
+  esac
+  if test "$_lt_dar_can_shared" = "yes"; then
+    output_verbose_link_cmd=func_echo_all
+    _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}"
+    _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}"
+    _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}"
+    _LT_TAGVAR(module_expsym_cmds, $1)="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}"
+    m4_if([$1], [CXX],
+[   if test "$lt_cv_apple_cc_single_mod" != "yes"; then
+      _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dsymutil}"
+      _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dar_export_syms}${_lt_dsymutil}"
+    fi
+],[])
+  else
+  _LT_TAGVAR(ld_shlibs, $1)=no
+  fi
+])
+
+# _LT_SYS_MODULE_PATH_AIX([TAGNAME])
+# ----------------------------------
+# Links a minimal program and checks the executable
+# for the system default hardcoded library path. In most cases,
+# this is /usr/lib:/lib, but when the MPI compilers are used
+# the location of the communication and MPI libs are included too.
+# If we don't find anything, use the default library path according
+# to the aix ld manual.
+# Store the results from the different compilers for each TAGNAME.
+# Allow to override them for all tags through lt_cv_aix_libpath.
+m4_defun([_LT_SYS_MODULE_PATH_AIX],
+[m4_require([_LT_DECL_SED])dnl
+if test "${lt_cv_aix_libpath+set}" = set; then
+  aix_libpath=$lt_cv_aix_libpath
+else
+  AC_CACHE_VAL([_LT_TAGVAR([lt_cv_aix_libpath_], [$1])],
+  [AC_LINK_IFELSE([AC_LANG_PROGRAM],[
+  lt_aix_libpath_sed='[
+      /Import File Strings/,/^$/ {
+         /^0/ {
+             s/^0  *\([^ ]*\) *$/\1/
+             p
+         }
+      }]'
+  _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  # Check for a 64-bit object if we didn't find anything.
+  if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then
+    _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  fi],[])
+  if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then
+    _LT_TAGVAR([lt_cv_aix_libpath_], [$1])="/usr/lib:/lib"
+  fi
+  ])
+  aix_libpath=$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])
+fi
+])# _LT_SYS_MODULE_PATH_AIX
+
+
+# _LT_SHELL_INIT(ARG)
+# -------------------
+m4_define([_LT_SHELL_INIT],
+[m4_divert_text([M4SH-INIT], [$1
+])])# _LT_SHELL_INIT
+
+
+
+# _LT_PROG_ECHO_BACKSLASH
+# -----------------------
+# Find how we can fake an echo command that does not interpret backslash.
+# In particular, with Autoconf 2.60 or later we add some code to the start
+# of the generated configure script which will find a shell with a builtin
+# printf (which we can use as an echo command).
+m4_defun([_LT_PROG_ECHO_BACKSLASH],
+[ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO
+
+AC_MSG_CHECKING([how to print strings])
+# Test print first, because it will be a builtin if present.
+if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \
+   test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then
+  ECHO='print -r --'
+elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then
+  ECHO='printf %s\n'
+else
+  # Use this function as a fallback that always works.
+  func_fallback_echo ()
+  {
+    eval 'cat <<_LTECHO_EOF
+$[]1
+_LTECHO_EOF'
+  }
+  ECHO='func_fallback_echo'
+fi
+
+# func_echo_all arg...
+# Invoke $ECHO with all args, space-separated.
+func_echo_all ()
+{
+    $ECHO "$*" 
+}
+
+case "$ECHO" in
+  printf*) AC_MSG_RESULT([printf]) ;;
+  print*) AC_MSG_RESULT([print -r]) ;;
+  *) AC_MSG_RESULT([cat]) ;;
+esac
+
+m4_ifdef([_AS_DETECT_SUGGESTED],
+[_AS_DETECT_SUGGESTED([
+  test -n "${ZSH_VERSION+set}${BASH_VERSION+set}" || (
+    ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+    ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO
+    ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO
+    PATH=/empty FPATH=/empty; export PATH FPATH
+    test "X`printf %s $ECHO`" = "X$ECHO" \
+      || test "X`print -r -- $ECHO`" = "X$ECHO" )])])
+
+_LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts])
+_LT_DECL([], [ECHO], [1], [An echo program that protects backslashes])
+])# _LT_PROG_ECHO_BACKSLASH
+
+
+# _LT_WITH_SYSROOT
+# ----------------
+AC_DEFUN([_LT_WITH_SYSROOT],
+[AC_MSG_CHECKING([for sysroot])
+AC_ARG_WITH([sysroot],
+[  --with-sysroot[=DIR] Search for dependent libraries within DIR
+                        (or the compiler's sysroot if not specified).],
+[], [with_sysroot=no])
+
+dnl lt_sysroot will always be passed unquoted.  We quote it here
+dnl in case the user passed a directory name.
+lt_sysroot=
+case ${with_sysroot} in #(
+ yes)
+   if test "$GCC" = yes; then
+     lt_sysroot=`$CC --print-sysroot 2>/dev/null`
+   fi
+   ;; #(
+ /*)
+   lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"`
+   ;; #(
+ no|'')
+   ;; #(
+ *)
+   AC_MSG_RESULT([${with_sysroot}])
+   AC_MSG_ERROR([The sysroot must be an absolute path.])
+   ;;
+esac
+
+ AC_MSG_RESULT([${lt_sysroot:-no}])
+_LT_DECL([], [lt_sysroot], [0], [The root where to search for ]dnl
+[dependent libraries, and in which our libraries should be installed.])])
+
+# _LT_ENABLE_LOCK
+# ---------------
+m4_defun([_LT_ENABLE_LOCK],
+[AC_ARG_ENABLE([libtool-lock],
+  [AS_HELP_STRING([--disable-libtool-lock],
+    [avoid locking (might break parallel builds)])])
+test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes
+
+# Some flags need to be propagated to the compiler or linker for good
+# libtool support.
+case $host in
+ia64-*-hpux*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if AC_TRY_EVAL(ac_compile); then
+    case `/usr/bin/file conftest.$ac_objext` in
+      *ELF-32*)
+       HPUX_IA64_MODE="32"
+       ;;
+      *ELF-64*)
+       HPUX_IA64_MODE="64"
+       ;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+*-*-irix6*)
+  # Find out which ABI we are using.
+  echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext
+  if AC_TRY_EVAL(ac_compile); then
+    if test "$lt_cv_prog_gnu_ld" = yes; then
+      case `/usr/bin/file conftest.$ac_objext` in
+       *32-bit*)
+         LD="${LD-ld} -melf32bsmip"
+         ;;
+       *N32*)
+         LD="${LD-ld} -melf32bmipn32"
+         ;;
+       *64-bit*)
+         LD="${LD-ld} -melf64bmip"
+       ;;
+      esac
+    else
+      case `/usr/bin/file conftest.$ac_objext` in
+       *32-bit*)
+         LD="${LD-ld} -32"
+         ;;
+       *N32*)
+         LD="${LD-ld} -n32"
+         ;;
+       *64-bit*)
+         LD="${LD-ld} -64"
+         ;;
+      esac
+    fi
+  fi
+  rm -rf conftest*
+  ;;
+
+x86_64-*kfreebsd*-gnu|x86_64-*linux*|ppc*-*linux*|powerpc*-*linux*| \
+s390*-*linux*|s390*-*tpf*|sparc*-*linux*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if AC_TRY_EVAL(ac_compile); then
+    case `/usr/bin/file conftest.o` in
+      *32-bit*)
+       case $host in
+         x86_64-*kfreebsd*-gnu)
+           LD="${LD-ld} -m elf_i386_fbsd"
+           ;;
+         x86_64-*linux*)
+           LD="${LD-ld} -m elf_i386"
+           ;;
+         ppc64-*linux*|powerpc64-*linux*)
+           LD="${LD-ld} -m elf32ppclinux"
+           ;;
+         s390x-*linux*)
+           LD="${LD-ld} -m elf_s390"
+           ;;
+         sparc64-*linux*)
+           LD="${LD-ld} -m elf32_sparc"
+           ;;
+       esac
+       ;;
+      *64-bit*)
+       case $host in
+         x86_64-*kfreebsd*-gnu)
+           LD="${LD-ld} -m elf_x86_64_fbsd"
+           ;;
+         x86_64-*linux*)
+           LD="${LD-ld} -m elf_x86_64"
+           ;;
+         ppc*-*linux*|powerpc*-*linux*)
+           LD="${LD-ld} -m elf64ppc"
+           ;;
+         s390*-*linux*|s390*-*tpf*)
+           LD="${LD-ld} -m elf64_s390"
+           ;;
+         sparc*-*linux*)
+           LD="${LD-ld} -m elf64_sparc"
+           ;;
+       esac
+       ;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+
+*-*-sco3.2v5*)
+  # On SCO OpenServer 5, we need -belf to get full-featured binaries.
+  SAVE_CFLAGS="$CFLAGS"
+  CFLAGS="$CFLAGS -belf"
+  AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf,
+    [AC_LANG_PUSH(C)
+     AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no])
+     AC_LANG_POP])
+  if test x"$lt_cv_cc_needs_belf" != x"yes"; then
+    # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf
+    CFLAGS="$SAVE_CFLAGS"
+  fi
+  ;;
+*-*solaris*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if AC_TRY_EVAL(ac_compile); then
+    case `/usr/bin/file conftest.o` in
+    *64-bit*)
+      case $lt_cv_prog_gnu_ld in
+      yes*)
+        case $host in
+        i?86-*-solaris*)
+          LD="${LD-ld} -m elf_x86_64"
+          ;;
+        sparc*-*-solaris*)
+          LD="${LD-ld} -m elf64_sparc"
+          ;;
+        esac
+        # GNU ld 2.21 introduced _sol2 emulations.  Use them if available.
+        if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then
+          LD="${LD-ld}_sol2"
+        fi
+        ;;
+      *)
+       if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then
+         LD="${LD-ld} -64"
+       fi
+       ;;
+      esac
+      ;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+esac
+
+need_locks="$enable_libtool_lock"
+])# _LT_ENABLE_LOCK
+
+
+# _LT_PROG_AR
+# -----------
+m4_defun([_LT_PROG_AR],
+[AC_CHECK_TOOLS(AR, [ar], false)
+: ${AR=ar}
+: ${AR_FLAGS=cru}
+_LT_DECL([], [AR], [1], [The archiver])
+_LT_DECL([], [AR_FLAGS], [1], [Flags to create an archive])
+
+AC_CACHE_CHECK([for archiver @FILE support], [lt_cv_ar_at_file],
+  [lt_cv_ar_at_file=no
+   AC_COMPILE_IFELSE([AC_LANG_PROGRAM],
+     [echo conftest.$ac_objext > conftest.lst
+      lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&AS_MESSAGE_LOG_FD'
+      AC_TRY_EVAL([lt_ar_try])
+      if test "$ac_status" -eq 0; then
+       # Ensure the archiver fails upon bogus file names.
+       rm -f conftest.$ac_objext libconftest.a
+       AC_TRY_EVAL([lt_ar_try])
+       if test "$ac_status" -ne 0; then
+          lt_cv_ar_at_file=@
+        fi
+      fi
+      rm -f conftest.* libconftest.a
+     ])
+  ])
+
+if test "x$lt_cv_ar_at_file" = xno; then
+  archiver_list_spec=
+else
+  archiver_list_spec=$lt_cv_ar_at_file
+fi
+_LT_DECL([], [archiver_list_spec], [1],
+  [How to feed a file listing to the archiver])
+])# _LT_PROG_AR
+
+
+# _LT_CMD_OLD_ARCHIVE
+# -------------------
+m4_defun([_LT_CMD_OLD_ARCHIVE],
+[_LT_PROG_AR
+
+AC_CHECK_TOOL(STRIP, strip, :)
+test -z "$STRIP" && STRIP=:
+_LT_DECL([], [STRIP], [1], [A symbol stripping program])
+
+AC_CHECK_TOOL(RANLIB, ranlib, :)
+test -z "$RANLIB" && RANLIB=:
+_LT_DECL([], [RANLIB], [1],
+    [Commands used to install an old-style archive])
+
+# Determine commands to create old-style static archives.
+old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs'
+old_postinstall_cmds='chmod 644 $oldlib'
+old_postuninstall_cmds=
+
+if test -n "$RANLIB"; then
+  case $host_os in
+  openbsd*)
+    old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib"
+    ;;
+  *)
+    old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib"
+    ;;
+  esac
+  old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib"
+fi
+
+case $host_os in
+  darwin*)
+    lock_old_archive_extraction=yes ;;
+  *)
+    lock_old_archive_extraction=no ;;
+esac
+_LT_DECL([], [old_postinstall_cmds], [2])
+_LT_DECL([], [old_postuninstall_cmds], [2])
+_LT_TAGDECL([], [old_archive_cmds], [2],
+    [Commands used to build an old-style archive])
+_LT_DECL([], [lock_old_archive_extraction], [0],
+    [Whether to use a lock for old archive extraction])
+])# _LT_CMD_OLD_ARCHIVE
+
+
+# _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS,
+#              [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE])
+# ----------------------------------------------------------------
+# Check whether the given compiler option works
+AC_DEFUN([_LT_COMPILER_OPTION],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_SED])dnl
+AC_CACHE_CHECK([$1], [$2],
+  [$2=no
+   m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4])
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+   lt_compiler_flag="$3"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   # The option is referenced via a variable to avoid confusing sed.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD)
+   (eval "$lt_compile" 2>conftest.err)
+   ac_status=$?
+   cat conftest.err >&AS_MESSAGE_LOG_FD
+   echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+   if (exit $ac_status) && test -s "$ac_outfile"; then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings other than the usual output.
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+     $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+     if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+       $2=yes
+     fi
+   fi
+   $RM conftest*
+])
+
+if test x"[$]$2" = xyes; then
+    m4_if([$5], , :, [$5])
+else
+    m4_if([$6], , :, [$6])
+fi
+])# _LT_COMPILER_OPTION
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], [])
+
+
+# _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS,
+#                  [ACTION-SUCCESS], [ACTION-FAILURE])
+# ----------------------------------------------------
+# Check whether the given linker option works
+AC_DEFUN([_LT_LINKER_OPTION],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_SED])dnl
+AC_CACHE_CHECK([$1], [$2],
+  [$2=no
+   save_LDFLAGS="$LDFLAGS"
+   LDFLAGS="$LDFLAGS $3"
+   echo "$lt_simple_link_test_code" > conftest.$ac_ext
+   if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+     # The linker can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     if test -s conftest.err; then
+       # Append any errors to the config.log.
+       cat conftest.err 1>&AS_MESSAGE_LOG_FD
+       $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+       $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+       if diff conftest.exp conftest.er2 >/dev/null; then
+         $2=yes
+       fi
+     else
+       $2=yes
+     fi
+   fi
+   $RM -r conftest*
+   LDFLAGS="$save_LDFLAGS"
+])
+
+if test x"[$]$2" = xyes; then
+    m4_if([$4], , :, [$4])
+else
+    m4_if([$5], , :, [$5])
+fi
+])# _LT_LINKER_OPTION
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], [])
+
+
+# LT_CMD_MAX_LEN
+#---------------
+AC_DEFUN([LT_CMD_MAX_LEN],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+# find the maximum length of command line arguments
+AC_MSG_CHECKING([the maximum length of command line arguments])
+AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl
+  i=0
+  teststring="ABCD"
+
+  case $build_os in
+  msdosdjgpp*)
+    # On DJGPP, this test can blow up pretty badly due to problems in libc
+    # (any single argument exceeding 2000 bytes causes a buffer overrun
+    # during glob expansion).  Even if it were fixed, the result of this
+    # check would be larger than it should be.
+    lt_cv_sys_max_cmd_len=12288;    # 12K is about right
+    ;;
+
+  gnu*)
+    # Under GNU Hurd, this test is not required because there is
+    # no limit to the length of command line arguments.
+    # Libtool will interpret -1 as no limit whatsoever
+    lt_cv_sys_max_cmd_len=-1;
+    ;;
+
+  cygwin* | mingw* | cegcc*)
+    # On Win9x/ME, this test blows up -- it succeeds, but takes
+    # about 5 minutes as the teststring grows exponentially.
+    # Worse, since 9x/ME are not pre-emptively multitasking,
+    # you end up with a "frozen" computer, even though with patience
+    # the test eventually succeeds (with a max line length of 256k).
+    # Instead, let's just punt: use the minimum linelength reported by
+    # all of the supported platforms: 8192 (on NT/2K/XP).
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  mint*)
+    # On MiNT this can take a long time and run out of memory.
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  amigaos*)
+    # On AmigaOS with pdksh, this test takes hours, literally.
+    # So we just punt and use a minimum line length of 8192.
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  netbsd* | freebsd* | openbsd* | darwin* | dragonfly*)
+    # This has been around since 386BSD, at least.  Likely further.
+    if test -x /sbin/sysctl; then
+      lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax`
+    elif test -x /usr/sbin/sysctl; then
+      lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax`
+    else
+      lt_cv_sys_max_cmd_len=65536      # usable default for all BSDs
+    fi
+    # And add a safety zone
+    lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+    lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+    ;;
+
+  interix*)
+    # We know the value 262144 and hardcode it with a safety zone (like BSD)
+    lt_cv_sys_max_cmd_len=196608
+    ;;
+
+  os2*)
+    # The test takes a long time on OS/2.
+    lt_cv_sys_max_cmd_len=8192
+    ;;
+
+  osf*)
+    # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure
+    # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not
+    # nice to cause kernel panics so lets avoid the loop below.
+    # First set a reasonable default.
+    lt_cv_sys_max_cmd_len=16384
+    #
+    if test -x /sbin/sysconfig; then
+      case `/sbin/sysconfig -q proc exec_disable_arg_limit` in
+        *1*) lt_cv_sys_max_cmd_len=-1 ;;
+      esac
+    fi
+    ;;
+  sco3.2v5*)
+    lt_cv_sys_max_cmd_len=102400
+    ;;
+  sysv5* | sco5v6* | sysv4.2uw2*)
+    kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null`
+    if test -n "$kargmax"; then
+      lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[[        ]]//'`
+    else
+      lt_cv_sys_max_cmd_len=32768
+    fi
+    ;;
+  *)
+    lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null`
+    if test -n "$lt_cv_sys_max_cmd_len"; then
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+    else
+      # Make teststring a little bigger before we do anything with it.
+      # a 1K string should be a reasonable start.
+      for i in 1 2 3 4 5 6 7 8 ; do
+        teststring=$teststring$teststring
+      done
+      SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}}
+      # If test is not a shell built-in, we'll probably end up computing a
+      # maximum length that is only half of the actual maximum length, but
+      # we can't tell.
+      while { test "X"`env echo "$teststring$teststring" 2>/dev/null` \
+                = "X$teststring$teststring"; } >/dev/null 2>&1 &&
+             test $i != 17 # 1/2 MB should be enough
+      do
+        i=`expr $i + 1`
+        teststring=$teststring$teststring
+      done
+      # Only check the string length outside the loop.
+      lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1`
+      teststring=
+      # Add a significant safety factor because C++ compilers can tack on
+      # massive amounts of additional arguments before passing them to the
+      # linker.  It appears as though 1/2 is a usable value.
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2`
+    fi
+    ;;
+  esac
+])
+if test -n $lt_cv_sys_max_cmd_len ; then
+  AC_MSG_RESULT($lt_cv_sys_max_cmd_len)
+else
+  AC_MSG_RESULT(none)
+fi
+max_cmd_len=$lt_cv_sys_max_cmd_len
+_LT_DECL([], [max_cmd_len], [0],
+    [What is the maximum length of a command?])
+])# LT_CMD_MAX_LEN
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], [])
+
+
+# _LT_HEADER_DLFCN
+# ----------------
+m4_defun([_LT_HEADER_DLFCN],
+[AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl
+])# _LT_HEADER_DLFCN
+
+
+# _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE,
+#                      ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING)
+# ----------------------------------------------------------------
+m4_defun([_LT_TRY_DLOPEN_SELF],
+[m4_require([_LT_HEADER_DLFCN])dnl
+if test "$cross_compiling" = yes; then :
+  [$4]
+else
+  lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+  lt_status=$lt_dlunknown
+  cat > conftest.$ac_ext <<_LT_EOF
+[#line $LINENO "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+#  define LT_DLGLOBAL          RTLD_GLOBAL
+#else
+#  ifdef DL_GLOBAL
+#    define LT_DLGLOBAL                DL_GLOBAL
+#  else
+#    define LT_DLGLOBAL                0
+#  endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+   find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+#  ifdef RTLD_LAZY
+#    define LT_DLLAZY_OR_NOW           RTLD_LAZY
+#  else
+#    ifdef DL_LAZY
+#      define LT_DLLAZY_OR_NOW         DL_LAZY
+#    else
+#      ifdef RTLD_NOW
+#        define LT_DLLAZY_OR_NOW       RTLD_NOW
+#      else
+#        ifdef DL_NOW
+#          define LT_DLLAZY_OR_NOW     DL_NOW
+#        else
+#          define LT_DLLAZY_OR_NOW     0
+#        endif
+#      endif
+#    endif
+#  endif
+#endif
+
+/* When -fvisbility=hidden is used, assume the code has been annotated
+   correspondingly for the symbols needed.  */
+#if defined(__GNUC__) && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3))
+int fnord () __attribute__((visibility("default")));
+#endif
+
+int fnord () { return 42; }
+int main ()
+{
+  void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+  int status = $lt_dlunknown;
+
+  if (self)
+    {
+      if (dlsym (self,"fnord"))       status = $lt_dlno_uscore;
+      else
+        {
+         if (dlsym( self,"_fnord"))  status = $lt_dlneed_uscore;
+          else puts (dlerror ());
+       }
+      /* dlclose (self); */
+    }
+  else
+    puts (dlerror ());
+
+  return status;
+}]
+_LT_EOF
+  if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext} 2>/dev/null; then
+    (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null
+    lt_status=$?
+    case x$lt_status in
+      x$lt_dlno_uscore) $1 ;;
+      x$lt_dlneed_uscore) $2 ;;
+      x$lt_dlunknown|x*) $3 ;;
+    esac
+  else :
+    # compilation failed
+    $3
+  fi
+fi
+rm -fr conftest*
+])# _LT_TRY_DLOPEN_SELF
+
+
+# LT_SYS_DLOPEN_SELF
+# ------------------
+AC_DEFUN([LT_SYS_DLOPEN_SELF],
+[m4_require([_LT_HEADER_DLFCN])dnl
+if test "x$enable_dlopen" != xyes; then
+  enable_dlopen=unknown
+  enable_dlopen_self=unknown
+  enable_dlopen_self_static=unknown
+else
+  lt_cv_dlopen=no
+  lt_cv_dlopen_libs=
+
+  case $host_os in
+  beos*)
+    lt_cv_dlopen="load_add_on"
+    lt_cv_dlopen_libs=
+    lt_cv_dlopen_self=yes
+    ;;
+
+  mingw* | pw32* | cegcc*)
+    lt_cv_dlopen="LoadLibrary"
+    lt_cv_dlopen_libs=
+    ;;
+
+  cygwin*)
+    lt_cv_dlopen="dlopen"
+    lt_cv_dlopen_libs=
+    ;;
+
+  darwin*)
+  # if libdl is installed we need to link against it
+    AC_CHECK_LIB([dl], [dlopen],
+               [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"],[
+    lt_cv_dlopen="dyld"
+    lt_cv_dlopen_libs=
+    lt_cv_dlopen_self=yes
+    ])
+    ;;
+
+  *)
+    AC_CHECK_FUNC([shl_load],
+         [lt_cv_dlopen="shl_load"],
+      [AC_CHECK_LIB([dld], [shl_load],
+           [lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-ldld"],
+       [AC_CHECK_FUNC([dlopen],
+             [lt_cv_dlopen="dlopen"],
+         [AC_CHECK_LIB([dl], [dlopen],
+               [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"],
+           [AC_CHECK_LIB([svld], [dlopen],
+                 [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld"],
+             [AC_CHECK_LIB([dld], [dld_link],
+                   [lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-ldld"])
+             ])
+           ])
+         ])
+       ])
+      ])
+    ;;
+  esac
+
+  if test "x$lt_cv_dlopen" != xno; then
+    enable_dlopen=yes
+  else
+    enable_dlopen=no
+  fi
+
+  case $lt_cv_dlopen in
+  dlopen)
+    save_CPPFLAGS="$CPPFLAGS"
+    test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H"
+
+    save_LDFLAGS="$LDFLAGS"
+    wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\"
+
+    save_LIBS="$LIBS"
+    LIBS="$lt_cv_dlopen_libs $LIBS"
+
+    AC_CACHE_CHECK([whether a program can dlopen itself],
+         lt_cv_dlopen_self, [dnl
+         _LT_TRY_DLOPEN_SELF(
+           lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes,
+           lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross)
+    ])
+
+    if test "x$lt_cv_dlopen_self" = xyes; then
+      wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\"
+      AC_CACHE_CHECK([whether a statically linked program can dlopen itself],
+         lt_cv_dlopen_self_static, [dnl
+         _LT_TRY_DLOPEN_SELF(
+           lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes,
+           lt_cv_dlopen_self_static=no,  lt_cv_dlopen_self_static=cross)
+      ])
+    fi
+
+    CPPFLAGS="$save_CPPFLAGS"
+    LDFLAGS="$save_LDFLAGS"
+    LIBS="$save_LIBS"
+    ;;
+  esac
+
+  case $lt_cv_dlopen_self in
+  yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;;
+  *) enable_dlopen_self=unknown ;;
+  esac
+
+  case $lt_cv_dlopen_self_static in
+  yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;;
+  *) enable_dlopen_self_static=unknown ;;
+  esac
+fi
+_LT_DECL([dlopen_support], [enable_dlopen], [0],
+        [Whether dlopen is supported])
+_LT_DECL([dlopen_self], [enable_dlopen_self], [0],
+        [Whether dlopen of programs is supported])
+_LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0],
+        [Whether dlopen of statically linked programs is supported])
+])# LT_SYS_DLOPEN_SELF
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], [])
+
+
+# _LT_COMPILER_C_O([TAGNAME])
+# ---------------------------
+# Check to see if options -c and -o are simultaneously supported by compiler.
+# This macro does not hard code the compiler like AC_PROG_CC_C_O.
+m4_defun([_LT_COMPILER_C_O],
+[m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_TAG_COMPILER])dnl
+AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext],
+  [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)],
+  [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no
+   $RM -r conftest 2>/dev/null
+   mkdir conftest
+   cd conftest
+   mkdir out
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+   lt_compiler_flag="-o out/conftest2.$ac_objext"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD)
+   (eval "$lt_compile" 2>out/conftest.err)
+   ac_status=$?
+   cat out/conftest.err >&AS_MESSAGE_LOG_FD
+   echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+   if (exit $ac_status) && test -s out/conftest2.$ac_objext
+   then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+     $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+     if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+       _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes
+     fi
+   fi
+   chmod u+w . 2>&AS_MESSAGE_LOG_FD
+   $RM conftest*
+   # SGI C++ compiler will create directory out/ii_files/ for
+   # template instantiation
+   test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+   $RM out/* && rmdir out
+   cd ..
+   $RM -r conftest
+   $RM conftest*
+])
+_LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1],
+       [Does compiler simultaneously support -c and -o options?])
+])# _LT_COMPILER_C_O
+
+
+# _LT_COMPILER_FILE_LOCKS([TAGNAME])
+# ----------------------------------
+# Check to see if we can do hard links to lock some files if needed
+m4_defun([_LT_COMPILER_FILE_LOCKS],
+[m4_require([_LT_ENABLE_LOCK])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+_LT_COMPILER_C_O([$1])
+
+hard_links="nottested"
+if test "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" = no && test "$need_locks" != no; then
+  # do not overwrite the value of need_locks provided by the user
+  AC_MSG_CHECKING([if we can lock with hard links])
+  hard_links=yes
+  $RM conftest*
+  ln conftest.a conftest.b 2>/dev/null && hard_links=no
+  touch conftest.a
+  ln conftest.a conftest.b 2>&5 || hard_links=no
+  ln conftest.a conftest.b 2>/dev/null && hard_links=no
+  AC_MSG_RESULT([$hard_links])
+  if test "$hard_links" = no; then
+    AC_MSG_WARN([`$CC' does not support `-c -o', so `make -j' may be unsafe])
+    need_locks=warn
+  fi
+else
+  need_locks=no
+fi
+_LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?])
+])# _LT_COMPILER_FILE_LOCKS
+
+
+# _LT_CHECK_OBJDIR
+# ----------------
+m4_defun([_LT_CHECK_OBJDIR],
+[AC_CACHE_CHECK([for objdir], [lt_cv_objdir],
+[rm -f .libs 2>/dev/null
+mkdir .libs 2>/dev/null
+if test -d .libs; then
+  lt_cv_objdir=.libs
+else
+  # MS-DOS does not allow filenames that begin with a dot.
+  lt_cv_objdir=_libs
+fi
+rmdir .libs 2>/dev/null])
+objdir=$lt_cv_objdir
+_LT_DECL([], [objdir], [0],
+         [The name of the directory that contains temporary libtool files])dnl
+m4_pattern_allow([LT_OBJDIR])dnl
+AC_DEFINE_UNQUOTED(LT_OBJDIR, "$lt_cv_objdir/",
+  [Define to the sub-directory in which libtool stores uninstalled libraries.])
+])# _LT_CHECK_OBJDIR
+
+
+# _LT_LINKER_HARDCODE_LIBPATH([TAGNAME])
+# --------------------------------------
+# Check hardcoding attributes.
+m4_defun([_LT_LINKER_HARDCODE_LIBPATH],
+[AC_MSG_CHECKING([how to hardcode library paths into programs])
+_LT_TAGVAR(hardcode_action, $1)=
+if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" ||
+   test -n "$_LT_TAGVAR(runpath_var, $1)" ||
+   test "X$_LT_TAGVAR(hardcode_automatic, $1)" = "Xyes" ; then
+
+  # We can hardcode non-existent directories.
+  if test "$_LT_TAGVAR(hardcode_direct, $1)" != no &&
+     # If the only mechanism to avoid hardcoding is shlibpath_var, we
+     # have to relink, otherwise we might link with an installed library
+     # when we should be linking with a yet-to-be-installed one
+     ## test "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" != no &&
+     test "$_LT_TAGVAR(hardcode_minus_L, $1)" != no; then
+    # Linking always hardcodes the temporary library directory.
+    _LT_TAGVAR(hardcode_action, $1)=relink
+  else
+    # We can link without hardcoding, and we can hardcode nonexisting dirs.
+    _LT_TAGVAR(hardcode_action, $1)=immediate
+  fi
+else
+  # We cannot hardcode anything, or else we can only hardcode existing
+  # directories.
+  _LT_TAGVAR(hardcode_action, $1)=unsupported
+fi
+AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)])
+
+if test "$_LT_TAGVAR(hardcode_action, $1)" = relink ||
+   test "$_LT_TAGVAR(inherit_rpath, $1)" = yes; then
+  # Fast installation is not supported
+  enable_fast_install=no
+elif test "$shlibpath_overrides_runpath" = yes ||
+     test "$enable_shared" = no; then
+  # Fast installation is not necessary
+  enable_fast_install=needless
+fi
+_LT_TAGDECL([], [hardcode_action], [0],
+    [How to hardcode a shared library path into an executable])
+])# _LT_LINKER_HARDCODE_LIBPATH
+
+
+# _LT_CMD_STRIPLIB
+# ----------------
+m4_defun([_LT_CMD_STRIPLIB],
+[m4_require([_LT_DECL_EGREP])
+striplib=
+old_striplib=
+AC_MSG_CHECKING([whether stripping libraries is possible])
+if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then
+  test -z "$old_striplib" && old_striplib="$STRIP --strip-debug"
+  test -z "$striplib" && striplib="$STRIP --strip-unneeded"
+  AC_MSG_RESULT([yes])
+else
+# FIXME - insert some real tests, host_os isn't really good enough
+  case $host_os in
+  darwin*)
+    if test -n "$STRIP" ; then
+      striplib="$STRIP -x"
+      old_striplib="$STRIP -S"
+      AC_MSG_RESULT([yes])
+    else
+      AC_MSG_RESULT([no])
+    fi
+    ;;
+  *)
+    AC_MSG_RESULT([no])
+    ;;
+  esac
+fi
+_LT_DECL([], [old_striplib], [1], [Commands to strip libraries])
+_LT_DECL([], [striplib], [1])
+])# _LT_CMD_STRIPLIB
+
+
+# _LT_SYS_DYNAMIC_LINKER([TAG])
+# -----------------------------
+# PORTME Fill in your ld.so characteristics
+m4_defun([_LT_SYS_DYNAMIC_LINKER],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_OBJDUMP])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_CHECK_SHELL_FEATURES])dnl
+AC_MSG_CHECKING([dynamic linker characteristics])
+m4_if([$1],
+       [], [
+if test "$GCC" = yes; then
+  case $host_os in
+    darwin*) lt_awk_arg="/^libraries:/,/LR/" ;;
+    *) lt_awk_arg="/^libraries:/" ;;
+  esac
+  case $host_os in
+    mingw* | cegcc*) lt_sed_strip_eq="s,=\([[A-Za-z]]:\),\1,g" ;;
+    *) lt_sed_strip_eq="s,=/,/,g" ;;
+  esac
+  lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq`
+  case $lt_search_path_spec in
+  *\;*)
+    # if the path contains ";" then we assume it to be the separator
+    # otherwise default to the standard path separator (i.e. ":") - it is
+    # assumed that no part of a normal pathname contains ";" but that should
+    # okay in the real world where ";" in dirpaths is itself problematic.
+    lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'`
+    ;;
+  *)
+    lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"`
+    ;;
+  esac
+  # Ok, now we have the path, separated by spaces, we can step through it
+  # and add multilib dir if necessary.
+  lt_tmp_lt_search_path_spec=
+  lt_multi_os_dir=`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null`
+  for lt_sys_path in $lt_search_path_spec; do
+    if test -d "$lt_sys_path/$lt_multi_os_dir"; then
+      lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path/$lt_multi_os_dir"
+    else
+      test -d "$lt_sys_path" && \
+       lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path"
+    fi
+  done
+  lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk '
+BEGIN {RS=" "; FS="/|\n";} {
+  lt_foo="";
+  lt_count=0;
+  for (lt_i = NF; lt_i > 0; lt_i--) {
+    if ($lt_i != "" && $lt_i != ".") {
+      if ($lt_i == "..") {
+        lt_count++;
+      } else {
+        if (lt_count == 0) {
+          lt_foo="/" $lt_i lt_foo;
+        } else {
+          lt_count--;
+        }
+      }
+    }
+  }
+  if (lt_foo != "") { lt_freq[[lt_foo]]++; }
+  if (lt_freq[[lt_foo]] == 1) { print lt_foo; }
+}'`
+  # AWK program above erroneously prepends '/' to C:/dos/paths
+  # for these hosts.
+  case $host_os in
+    mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\
+      $SED 's,/\([[A-Za-z]]:\),\1,g'` ;;
+  esac
+  sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP`
+else
+  sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
+fi])
+library_names_spec=
+libname_spec='lib$name'
+soname_spec=
+shrext_cmds=".so"
+postinstall_cmds=
+postuninstall_cmds=
+finish_cmds=
+finish_eval=
+shlibpath_var=
+shlibpath_overrides_runpath=unknown
+version_type=none
+dynamic_linker="$host_os ld.so"
+sys_lib_dlsearch_path_spec="/lib /usr/lib"
+need_lib_prefix=unknown
+hardcode_into_libs=no
+
+# when you set need_version to no, make sure it does not cause -set_version
+# flags to be left without arguments
+need_version=unknown
+
+case $host_os in
+aix3*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a'
+  shlibpath_var=LIBPATH
+
+  # AIX 3 has no versioning support, so we append a major version to the name.
+  soname_spec='${libname}${release}${shared_ext}$major'
+  ;;
+
+aix[[4-9]]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  hardcode_into_libs=yes
+  if test "$host_cpu" = ia64; then
+    # AIX 5 supports IA64
+    library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}'
+    shlibpath_var=LD_LIBRARY_PATH
+  else
+    # With GCC up to 2.95.x, collect2 would create an import file
+    # for dependence libraries.  The import file would start with
+    # the line `#! .'.  This would cause the generated library to
+    # depend on `.', always an invalid library.  This was fixed in
+    # development snapshots of GCC prior to 3.0.
+    case $host_os in
+      aix4 | aix4.[[01]] | aix4.[[01]].*)
+      if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)'
+          echo ' yes '
+          echo '#endif'; } | ${CC} -E - | $GREP yes > /dev/null; then
+       :
+      else
+       can_build_shared=no
+      fi
+      ;;
+    esac
+    # AIX (on Power*) has no versioning support, so currently we can not hardcode correct
+    # soname into executable. Probably we can add versioning support to
+    # collect2, so additional links can be useful in future.
+    if test "$aix_use_runtimelinking" = yes; then
+      # If using run time linking (on AIX 4.2 or later) use lib<name>.so
+      # instead of lib<name>.a to let people know that these are not
+      # typical AIX shared libraries.
+      library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    else
+      # We preserve .a as extension for shared libraries through AIX4.2
+      # and later when we are not doing run time linking.
+      library_names_spec='${libname}${release}.a $libname.a'
+      soname_spec='${libname}${release}${shared_ext}$major'
+    fi
+    shlibpath_var=LIBPATH
+  fi
+  ;;
+
+amigaos*)
+  case $host_cpu in
+  powerpc)
+    # Since July 2007 AmigaOS4 officially supports .so libraries.
+    # When compiling the executable, add -use-dynld -Lsobjs: to the compileline.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    ;;
+  m68k)
+    library_names_spec='$libname.ixlibrary $libname.a'
+    # Create ${libname}_ixlibrary.a entries in /sys/libs.
+    finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; test $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done'
+    ;;
+  esac
+  ;;
+
+beos*)
+  library_names_spec='${libname}${shared_ext}'
+  dynamic_linker="$host_os ld.so"
+  shlibpath_var=LIBRARY_PATH
+  ;;
+
+bsdi[[45]]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib"
+  sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib"
+  # the default ld.so.conf also contains /usr/contrib/lib and
+  # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow
+  # libtool to hard-code these into programs
+  ;;
+
+cygwin* | mingw* | pw32* | cegcc*)
+  version_type=windows
+  shrext_cmds=".dll"
+  need_version=no
+  need_lib_prefix=no
+
+  case $GCC,$cc_basename in
+  yes,*)
+    # gcc
+    library_names_spec='$libname.dll.a'
+    # DLL is installed to $(libdir)/../bin by postinstall_cmds
+    postinstall_cmds='base_file=`basename \${file}`~
+      dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+      dldir=$destdir/`dirname \$dlpath`~
+      test -d \$dldir || mkdir -p \$dldir~
+      $install_prog $dir/$dlname \$dldir/$dlname~
+      chmod a+x \$dldir/$dlname~
+      if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then
+        eval '\''$striplib \$dldir/$dlname'\'' || exit \$?;
+      fi'
+    postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+      dlpath=$dir/\$dldll~
+       $RM \$dlpath'
+    shlibpath_overrides_runpath=yes
+
+    case $host_os in
+    cygwin*)
+      # Cygwin DLLs use 'cyg' prefix rather than 'lib'
+      soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}'
+m4_if([$1], [],[
+      sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"])
+      ;;
+    mingw* | cegcc*)
+      # MinGW DLLs use traditional 'lib' prefix
+      soname_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}'
+      ;;
+    pw32*)
+      # pw32 DLLs use 'pw' prefix rather than 'lib'
+      library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}'
+      ;;
+    esac
+    dynamic_linker='Win32 ld.exe'
+    ;;
+
+  *,cl*)
+    # Native MSVC
+    libname_spec='$name'
+    soname_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}'
+    library_names_spec='${libname}.dll.lib'
+
+    case $build_os in
+    mingw*)
+      sys_lib_search_path_spec=
+      lt_save_ifs=$IFS
+      IFS=';'
+      for lt_path in $LIB
+      do
+        IFS=$lt_save_ifs
+        # Let DOS variable expansion print the short 8.3 style file name.
+        lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"`
+        sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path"
+      done
+      IFS=$lt_save_ifs
+      # Convert to MSYS style.
+      sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([[a-zA-Z]]\\):| /\\1|g' -e 's|^ ||'`
+      ;;
+    cygwin*)
+      # Convert to unix form, then to dos form, then back to unix form
+      # but this time dos style (no spaces!) so that the unix form looks
+      # like /cygdrive/c/PROGRA~1:/cygdr...
+      sys_lib_search_path_spec=`cygpath --path --unix "$LIB"`
+      sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null`
+      sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+      ;;
+    *)
+      sys_lib_search_path_spec="$LIB"
+      if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then
+        # It is most probably a Windows format PATH.
+        sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'`
+      else
+        sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+      fi
+      # FIXME: find the short name or the path components, as spaces are
+      # common. (e.g. "Program Files" -> "PROGRA~1")
+      ;;
+    esac
+
+    # DLL is installed to $(libdir)/../bin by postinstall_cmds
+    postinstall_cmds='base_file=`basename \${file}`~
+      dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+      dldir=$destdir/`dirname \$dlpath`~
+      test -d \$dldir || mkdir -p \$dldir~
+      $install_prog $dir/$dlname \$dldir/$dlname'
+    postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+      dlpath=$dir/\$dldll~
+       $RM \$dlpath'
+    shlibpath_overrides_runpath=yes
+    dynamic_linker='Win32 link.exe'
+    ;;
+
+  *)
+    # Assume MSVC wrapper
+    library_names_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext} $libname.lib'
+    dynamic_linker='Win32 ld.exe'
+    ;;
+  esac
+  # FIXME: first we should search . and the directory the executable is in
+  shlibpath_var=PATH
+  ;;
+
+darwin* | rhapsody*)
+  dynamic_linker="$host_os dyld"
+  version_type=darwin
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${major}$shared_ext ${libname}$shared_ext'
+  soname_spec='${libname}${release}${major}$shared_ext'
+  shlibpath_overrides_runpath=yes
+  shlibpath_var=DYLD_LIBRARY_PATH
+  shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`'
+m4_if([$1], [],[
+  sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"])
+  sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib'
+  ;;
+
+dgux*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  ;;
+
+freebsd* | dragonfly*)
+  # DragonFly does not have aout.  When/if they implement a new
+  # versioning mechanism, adjust this.
+  if test -x /usr/bin/objformat; then
+    objformat=`/usr/bin/objformat`
+  else
+    case $host_os in
+    freebsd[[23]].*) objformat=aout ;;
+    *) objformat=elf ;;
+    esac
+  fi
+  version_type=freebsd-$objformat
+  case $version_type in
+    freebsd-elf*)
+      library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+      need_version=no
+      need_lib_prefix=no
+      ;;
+    freebsd-*)
+      library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix'
+      need_version=yes
+      ;;
+  esac
+  shlibpath_var=LD_LIBRARY_PATH
+  case $host_os in
+  freebsd2.*)
+    shlibpath_overrides_runpath=yes
+    ;;
+  freebsd3.[[01]]* | freebsdelf3.[[01]]*)
+    shlibpath_overrides_runpath=yes
+    hardcode_into_libs=yes
+    ;;
+  freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \
+  freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1)
+    shlibpath_overrides_runpath=no
+    hardcode_into_libs=yes
+    ;;
+  *) # from 4.6 on, and DragonFly
+    shlibpath_overrides_runpath=yes
+    hardcode_into_libs=yes
+    ;;
+  esac
+  ;;
+
+gnu*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+haiku*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  dynamic_linker="$host_os runtime_loader"
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib'
+  hardcode_into_libs=yes
+  ;;
+
+hpux9* | hpux10* | hpux11*)
+  # Give a soname corresponding to the major version so that dld.sl refuses to
+  # link against other versions.
+  version_type=sunos
+  need_lib_prefix=no
+  need_version=no
+  case $host_cpu in
+  ia64*)
+    shrext_cmds='.so'
+    hardcode_into_libs=yes
+    dynamic_linker="$host_os dld.so"
+    shlibpath_var=LD_LIBRARY_PATH
+    shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    if test "X$HPUX_IA64_MODE" = X32; then
+      sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib"
+    else
+      sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64"
+    fi
+    sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+    ;;
+  hppa*64*)
+    shrext_cmds='.sl'
+    hardcode_into_libs=yes
+    dynamic_linker="$host_os dld.sl"
+    shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH
+    shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64"
+    sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+    ;;
+  *)
+    shrext_cmds='.sl'
+    dynamic_linker="$host_os dld.sl"
+    shlibpath_var=SHLIB_PATH
+    shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    ;;
+  esac
+  # HP-UX runs *really* slowly unless shared libraries are mode 555, ...
+  postinstall_cmds='chmod 555 $lib'
+  # or fails outright, so override atomically:
+  install_override_mode=555
+  ;;
+
+interix[[3-9]]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+irix5* | irix6* | nonstopux*)
+  case $host_os in
+    nonstopux*) version_type=nonstopux ;;
+    *)
+       if test "$lt_cv_prog_gnu_ld" = yes; then
+               version_type=linux # correct to gnu/linux during the next big refactor
+       else
+               version_type=irix
+       fi ;;
+  esac
+  need_lib_prefix=no
+  need_version=no
+  soname_spec='${libname}${release}${shared_ext}$major'
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}'
+  case $host_os in
+  irix5* | nonstopux*)
+    libsuff= shlibsuff=
+    ;;
+  *)
+    case $LD in # libtool.m4 will add one of these switches to LD
+    *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ")
+      libsuff= shlibsuff= libmagic=32-bit;;
+    *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ")
+      libsuff=32 shlibsuff=N32 libmagic=N32;;
+    *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ")
+      libsuff=64 shlibsuff=64 libmagic=64-bit;;
+    *) libsuff= shlibsuff= libmagic=never-match;;
+    esac
+    ;;
+  esac
+  shlibpath_var=LD_LIBRARY${shlibsuff}_PATH
+  shlibpath_overrides_runpath=no
+  sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}"
+  sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}"
+  hardcode_into_libs=yes
+  ;;
+
+# No shared lib support for Linux oldld, aout, or coff.
+linux*oldld* | linux*aout* | linux*coff*)
+  dynamic_linker=no
+  ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+
+  # Some binutils ld are patched to set DT_RUNPATH
+  AC_CACHE_VAL([lt_cv_shlibpath_overrides_runpath],
+    [lt_cv_shlibpath_overrides_runpath=no
+    save_LDFLAGS=$LDFLAGS
+    save_libdir=$libdir
+    eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \
+        LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\""
+    AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])],
+      [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null],
+        [lt_cv_shlibpath_overrides_runpath=yes])])
+    LDFLAGS=$save_LDFLAGS
+    libdir=$save_libdir
+    ])
+  shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath
+
+  # This implies no fast_install, which is unacceptable.
+  # Some rework will be needed to allow for fast_install
+  # before this can be enabled.
+  hardcode_into_libs=yes
+
+  # Append ld.so.conf contents to the search path
+  if test -f /etc/ld.so.conf; then
+    lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[  ]*hwcap[        ]/d;s/[:,      ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '`
+    sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra"
+  fi
+
+  # We used to test for /lib/ld.so.1 and disable shared libraries on
+  # powerpc, because MkLinux only supported shared libraries with the
+  # GNU dynamic linker.  Since this was broken with cross compilers,
+  # most powerpc-linux boxes support dynamic linking these days and
+  # people can always --disable-shared, the test was removed, and we
+  # assume the GNU/Linux dynamic linker is in use.
+  dynamic_linker='GNU/Linux ld.so'
+  ;;
+
+netbsdelf*-gnu)
+  version_type=linux
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  dynamic_linker='NetBSD ld.elf_so'
+  ;;
+
+netbsd*)
+  version_type=sunos
+  need_lib_prefix=no
+  need_version=no
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+    finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+    dynamic_linker='NetBSD (a.out) ld.so'
+  else
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    dynamic_linker='NetBSD ld.elf_so'
+  fi
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  ;;
+
+newsos6)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  ;;
+
+*nto* | *qnx*)
+  version_type=qnx
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  dynamic_linker='ldqnx.so'
+  ;;
+
+openbsd*)
+  version_type=sunos
+  sys_lib_dlsearch_path_spec="/usr/lib"
+  need_lib_prefix=no
+  # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs.
+  case $host_os in
+    openbsd3.3 | openbsd3.3.*) need_version=yes ;;
+    *)                         need_version=no  ;;
+  esac
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+    case $host_os in
+      openbsd2.[[89]] | openbsd2.[[89]].*)
+       shlibpath_overrides_runpath=no
+       ;;
+      *)
+       shlibpath_overrides_runpath=yes
+       ;;
+      esac
+  else
+    shlibpath_overrides_runpath=yes
+  fi
+  ;;
+
+os2*)
+  libname_spec='$name'
+  shrext_cmds=".dll"
+  need_lib_prefix=no
+  library_names_spec='$libname${shared_ext} $libname.a'
+  dynamic_linker='OS/2 ld.exe'
+  shlibpath_var=LIBPATH
+  ;;
+
+osf3* | osf4* | osf5*)
+  version_type=osf
+  need_lib_prefix=no
+  need_version=no
+  soname_spec='${libname}${release}${shared_ext}$major'
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib"
+  sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec"
+  ;;
+
+rdos*)
+  dynamic_linker=no
+  ;;
+
+solaris*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  # ldd complains unless libraries are executable
+  postinstall_cmds='chmod +x $lib'
+  ;;
+
+sunos4*)
+  version_type=sunos
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+  finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  if test "$with_gnu_ld" = yes; then
+    need_lib_prefix=no
+  fi
+  need_version=yes
+  ;;
+
+sysv4 | sysv4.3*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  case $host_vendor in
+    sni)
+      shlibpath_overrides_runpath=no
+      need_lib_prefix=no
+      runpath_var=LD_RUN_PATH
+      ;;
+    siemens)
+      need_lib_prefix=no
+      ;;
+    motorola)
+      need_lib_prefix=no
+      need_version=no
+      shlibpath_overrides_runpath=no
+      sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib'
+      ;;
+  esac
+  ;;
+
+sysv4*MP*)
+  if test -d /usr/nec ;then
+    version_type=linux # correct to gnu/linux during the next big refactor
+    library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}'
+    soname_spec='$libname${shared_ext}.$major'
+    shlibpath_var=LD_LIBRARY_PATH
+  fi
+  ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+  version_type=freebsd-elf
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  if test "$with_gnu_ld" = yes; then
+    sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib'
+  else
+    sys_lib_search_path_spec='/usr/ccs/lib /usr/lib'
+    case $host_os in
+      sco3.2v5*)
+        sys_lib_search_path_spec="$sys_lib_search_path_spec /lib"
+       ;;
+    esac
+  fi
+  sys_lib_dlsearch_path_spec='/usr/lib'
+  ;;
+
+tpf*)
+  # TPF is a cross-target only.  Preferred cross-host = GNU/Linux.
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+uts4*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  ;;
+
+*)
+  dynamic_linker=no
+  ;;
+esac
+AC_MSG_RESULT([$dynamic_linker])
+test "$dynamic_linker" = no && can_build_shared=no
+
+variables_saved_for_relink="PATH $shlibpath_var $runpath_var"
+if test "$GCC" = yes; then
+  variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH"
+fi
+
+if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then
+  sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec"
+fi
+if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then
+  sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec"
+fi
+
+_LT_DECL([], [variables_saved_for_relink], [1],
+    [Variables whose values should be saved in libtool wrapper scripts and
+    restored at link time])
+_LT_DECL([], [need_lib_prefix], [0],
+    [Do we need the "lib" prefix for modules?])
+_LT_DECL([], [need_version], [0], [Do we need a version for libraries?])
+_LT_DECL([], [version_type], [0], [Library versioning type])
+_LT_DECL([], [runpath_var], [0],  [Shared library runtime path variable])
+_LT_DECL([], [shlibpath_var], [0],[Shared library path variable])
+_LT_DECL([], [shlibpath_overrides_runpath], [0],
+    [Is shlibpath searched before the hard-coded library search path?])
+_LT_DECL([], [libname_spec], [1], [Format of library name prefix])
+_LT_DECL([], [library_names_spec], [1],
+    [[List of archive names.  First name is the real one, the rest are links.
+    The last name is the one that the linker finds with -lNAME]])
+_LT_DECL([], [soname_spec], [1],
+    [[The coded name of the library, if different from the real name]])
+_LT_DECL([], [install_override_mode], [1],
+    [Permission mode override for installation of shared libraries])
+_LT_DECL([], [postinstall_cmds], [2],
+    [Command to use after installation of a shared archive])
+_LT_DECL([], [postuninstall_cmds], [2],
+    [Command to use after uninstallation of a shared archive])
+_LT_DECL([], [finish_cmds], [2],
+    [Commands used to finish a libtool library installation in a directory])
+_LT_DECL([], [finish_eval], [1],
+    [[As "finish_cmds", except a single script fragment to be evaled but
+    not shown]])
+_LT_DECL([], [hardcode_into_libs], [0],
+    [Whether we should hardcode library paths into libraries])
+_LT_DECL([], [sys_lib_search_path_spec], [2],
+    [Compile-time system search path for libraries])
+_LT_DECL([], [sys_lib_dlsearch_path_spec], [2],
+    [Run-time system search path for libraries])
+])# _LT_SYS_DYNAMIC_LINKER
+
+
+# _LT_PATH_TOOL_PREFIX(TOOL)
+# --------------------------
+# find a file program which can recognize shared library
+AC_DEFUN([_LT_PATH_TOOL_PREFIX],
+[m4_require([_LT_DECL_EGREP])dnl
+AC_MSG_CHECKING([for $1])
+AC_CACHE_VAL(lt_cv_path_MAGIC_CMD,
+[case $MAGIC_CMD in
+[[\\/*] |  ?:[\\/]*])
+  lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path.
+  ;;
+*)
+  lt_save_MAGIC_CMD="$MAGIC_CMD"
+  lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+dnl $ac_dummy forces splitting on constant user-supplied paths.
+dnl POSIX.2 word splitting is done only on the output of word expansions,
+dnl not every word.  This closes a longstanding sh security hole.
+  ac_dummy="m4_if([$2], , $PATH, [$2])"
+  for ac_dir in $ac_dummy; do
+    IFS="$lt_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$1; then
+      lt_cv_path_MAGIC_CMD="$ac_dir/$1"
+      if test -n "$file_magic_test_file"; then
+       case $deplibs_check_method in
+       "file_magic "*)
+         file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"`
+         MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+         if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+           $EGREP "$file_magic_regex" > /dev/null; then
+           :
+         else
+           cat <<_LT_EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such.  This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem.  Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** bug-libtool@gnu.org
+
+_LT_EOF
+         fi ;;
+       esac
+      fi
+      break
+    fi
+  done
+  IFS="$lt_save_ifs"
+  MAGIC_CMD="$lt_save_MAGIC_CMD"
+  ;;
+esac])
+MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+if test -n "$MAGIC_CMD"; then
+  AC_MSG_RESULT($MAGIC_CMD)
+else
+  AC_MSG_RESULT(no)
+fi
+_LT_DECL([], [MAGIC_CMD], [0],
+        [Used to examine libraries when file_magic_cmd begins with "file"])dnl
+])# _LT_PATH_TOOL_PREFIX
+
+# Old name:
+AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], [])
+
+
+# _LT_PATH_MAGIC
+# --------------
+# find a file program which can recognize a shared library
+m4_defun([_LT_PATH_MAGIC],
+[_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH)
+if test -z "$lt_cv_path_MAGIC_CMD"; then
+  if test -n "$ac_tool_prefix"; then
+    _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH)
+  else
+    MAGIC_CMD=:
+  fi
+fi
+])# _LT_PATH_MAGIC
+
+
+# LT_PATH_LD
+# ----------
+# find the pathname to the GNU or non-GNU linker
+AC_DEFUN([LT_PATH_LD],
+[AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_PROG_ECHO_BACKSLASH])dnl
+
+AC_ARG_WITH([gnu-ld],
+    [AS_HELP_STRING([--with-gnu-ld],
+       [assume the C compiler uses GNU ld @<:@default=no@:>@])],
+    [test "$withval" = no || with_gnu_ld=yes],
+    [with_gnu_ld=no])dnl
+
+ac_prog=ld
+if test "$GCC" = yes; then
+  # Check if gcc -print-prog-name=ld gives a path.
+  AC_MSG_CHECKING([for ld used by $CC])
+  case $host in
+  *-*-mingw*)
+    # gcc leaves a trailing carriage return which upsets mingw
+    ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+  *)
+    ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+  esac
+  case $ac_prog in
+    # Accept absolute paths.
+    [[\\/]]* | ?:[[\\/]]*)
+      re_direlt='/[[^/]][[^/]]*/\.\./'
+      # Canonicalize the pathname of ld
+      ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'`
+      while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do
+       ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"`
+      done
+      test -z "$LD" && LD="$ac_prog"
+      ;;
+  "")
+    # If it fails, then pretend we aren't using GCC.
+    ac_prog=ld
+    ;;
+  *)
+    # If it is relative, then search for the first ld in PATH.
+    with_gnu_ld=unknown
+    ;;
+  esac
+elif test "$with_gnu_ld" = yes; then
+  AC_MSG_CHECKING([for GNU ld])
+else
+  AC_MSG_CHECKING([for non-GNU ld])
+fi
+AC_CACHE_VAL(lt_cv_path_LD,
+[if test -z "$LD"; then
+  lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+  for ac_dir in $PATH; do
+    IFS="$lt_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+      lt_cv_path_LD="$ac_dir/$ac_prog"
+      # Check to see if the program is GNU ld.  I'd rather use --version,
+      # but apparently some variants of GNU ld only accept -v.
+      # Break only if it was the GNU/non-GNU ld that we prefer.
+      case `"$lt_cv_path_LD" -v 2>&1 </dev/null` in
+      *GNU* | *'with BFD'*)
+       test "$with_gnu_ld" != no && break
+       ;;
+      *)
+       test "$with_gnu_ld" != yes && break
+       ;;
+      esac
+    fi
+  done
+  IFS="$lt_save_ifs"
+else
+  lt_cv_path_LD="$LD" # Let the user override the test with a path.
+fi])
+LD="$lt_cv_path_LD"
+if test -n "$LD"; then
+  AC_MSG_RESULT($LD)
+else
+  AC_MSG_RESULT(no)
+fi
+test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH])
+_LT_PATH_LD_GNU
+AC_SUBST([LD])
+
+_LT_TAGDECL([], [LD], [1], [The linker used to build libraries])
+])# LT_PATH_LD
+
+# Old names:
+AU_ALIAS([AM_PROG_LD], [LT_PATH_LD])
+AU_ALIAS([AC_PROG_LD], [LT_PATH_LD])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_PROG_LD], [])
+dnl AC_DEFUN([AC_PROG_LD], [])
+
+
+# _LT_PATH_LD_GNU
+#- --------------
+m4_defun([_LT_PATH_LD_GNU],
+[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], lt_cv_prog_gnu_ld,
+[# I'd rather use --version here, but apparently some GNU lds only accept -v.
+case `$LD -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+  lt_cv_prog_gnu_ld=yes
+  ;;
+*)
+  lt_cv_prog_gnu_ld=no
+  ;;
+esac])
+with_gnu_ld=$lt_cv_prog_gnu_ld
+])# _LT_PATH_LD_GNU
+
+
+# _LT_CMD_RELOAD
+# --------------
+# find reload flag for linker
+#   -- PORTME Some linkers may need a different reload flag.
+m4_defun([_LT_CMD_RELOAD],
+[AC_CACHE_CHECK([for $LD option to reload object files],
+  lt_cv_ld_reload_flag,
+  [lt_cv_ld_reload_flag='-r'])
+reload_flag=$lt_cv_ld_reload_flag
+case $reload_flag in
+"" | " "*) ;;
+*) reload_flag=" $reload_flag" ;;
+esac
+reload_cmds='$LD$reload_flag -o $output$reload_objs'
+case $host_os in
+  cygwin* | mingw* | pw32* | cegcc*)
+    if test "$GCC" != yes; then
+      reload_cmds=false
+    fi
+    ;;
+  darwin*)
+    if test "$GCC" = yes; then
+      reload_cmds='$LTCC $LTCFLAGS -nostdlib ${wl}-r -o $output$reload_objs'
+    else
+      reload_cmds='$LD$reload_flag -o $output$reload_objs'
+    fi
+    ;;
+esac
+_LT_TAGDECL([], [reload_flag], [1], [How to create reloadable object files])dnl
+_LT_TAGDECL([], [reload_cmds], [2])dnl
+])# _LT_CMD_RELOAD
+
+
+# _LT_CHECK_MAGIC_METHOD
+# ----------------------
+# how to check for library dependencies
+#  -- PORTME fill in with the dynamic library characteristics
+m4_defun([_LT_CHECK_MAGIC_METHOD],
+[m4_require([_LT_DECL_EGREP])
+m4_require([_LT_DECL_OBJDUMP])
+AC_CACHE_CHECK([how to recognize dependent libraries],
+lt_cv_deplibs_check_method,
+[lt_cv_file_magic_cmd='$MAGIC_CMD'
+lt_cv_file_magic_test_file=
+lt_cv_deplibs_check_method='unknown'
+# Need to set the preceding variable on all platforms that support
+# interlibrary dependencies.
+# 'none' -- dependencies not supported.
+# `unknown' -- same as none, but documents that we really don't know.
+# 'pass_all' -- all dependencies passed with no checks.
+# 'test_compile' -- check by making test program.
+# 'file_magic [[regex]]' -- check by looking for files in library path
+# which responds to the $file_magic_cmd with a given extended regex.
+# If you have `file' or equivalent on your system and you're not sure
+# whether `pass_all' will *always* work, you probably want this one.
+
+case $host_os in
+aix[[4-9]]*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+beos*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+bsdi[[45]]*)
+  lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)'
+  lt_cv_file_magic_cmd='/usr/bin/file -L'
+  lt_cv_file_magic_test_file=/shlib/libc.so
+  ;;
+
+cygwin*)
+  # func_win32_libid is a shell function defined in ltmain.sh
+  lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+  lt_cv_file_magic_cmd='func_win32_libid'
+  ;;
+
+mingw* | pw32*)
+  # Base MSYS/MinGW do not provide the 'file' command needed by
+  # func_win32_libid shell function, so use a weaker test based on 'objdump',
+  # unless we find 'file', for example because we are cross-compiling.
+  # func_win32_libid assumes BSD nm, so disallow it if using MS dumpbin.
+  if ( test "$lt_cv_nm_interface" = "BSD nm" && file / ) >/dev/null 2>&1; then
+    lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+    lt_cv_file_magic_cmd='func_win32_libid'
+  else
+    # Keep this pattern in sync with the one in func_win32_libid.
+    lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)'
+    lt_cv_file_magic_cmd='$OBJDUMP -f'
+  fi
+  ;;
+
+cegcc*)
+  # use the weaker test based on 'objdump'. See mingw*.
+  lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?'
+  lt_cv_file_magic_cmd='$OBJDUMP -f'
+  ;;
+
+darwin* | rhapsody*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+freebsd* | dragonfly*)
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+    case $host_cpu in
+    i*86 )
+      # Not sure whether the presence of OpenBSD here was a mistake.
+      # Let's accept both of them until this is cleared up.
+      lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library'
+      lt_cv_file_magic_cmd=/usr/bin/file
+      lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*`
+      ;;
+    esac
+  else
+    lt_cv_deplibs_check_method=pass_all
+  fi
+  ;;
+
+gnu*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+haiku*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+hpux10.20* | hpux11*)
+  lt_cv_file_magic_cmd=/usr/bin/file
+  case $host_cpu in
+  ia64*)
+    lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64'
+    lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so
+    ;;
+  hppa*64*)
+    [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]']
+    lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl
+    ;;
+  *)
+    lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]]\.[[0-9]]) shared library'
+    lt_cv_file_magic_test_file=/usr/lib/libc.sl
+    ;;
+  esac
+  ;;
+
+interix[[3-9]]*)
+  # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here
+  lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$'
+  ;;
+
+irix5* | irix6* | nonstopux*)
+  case $LD in
+  *-32|*"-32 ") libmagic=32-bit;;
+  *-n32|*"-n32 ") libmagic=N32;;
+  *-64|*"-64 ") libmagic=64-bit;;
+  *) libmagic=never-match;;
+  esac
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+netbsd* | netbsdelf*-gnu)
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+    lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$'
+  else
+    lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$'
+  fi
+  ;;
+
+newos6*)
+  lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)'
+  lt_cv_file_magic_cmd=/usr/bin/file
+  lt_cv_file_magic_test_file=/usr/lib/libnls.so
+  ;;
+
+*nto* | *qnx*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+openbsd*)
+  if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+    lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$'
+  else
+    lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$'
+  fi
+  ;;
+
+osf3* | osf4* | osf5*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+rdos*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+solaris*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+sysv4 | sysv4.3*)
+  case $host_vendor in
+  motorola)
+    lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]'
+    lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*`
+    ;;
+  ncr)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  sequent)
+    lt_cv_file_magic_cmd='/bin/file'
+    lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )'
+    ;;
+  sni)
+    lt_cv_file_magic_cmd='/bin/file'
+    lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib"
+    lt_cv_file_magic_test_file=/lib/libc.so
+    ;;
+  siemens)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  pc)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  esac
+  ;;
+
+tpf*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+esac
+])
+
+file_magic_glob=
+want_nocaseglob=no
+if test "$build" = "$host"; then
+  case $host_os in
+  mingw* | pw32*)
+    if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then
+      want_nocaseglob=yes
+    else
+      file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[[\1]]\/[[\1]]\/g;/g"`
+    fi
+    ;;
+  esac
+fi
+
+file_magic_cmd=$lt_cv_file_magic_cmd
+deplibs_check_method=$lt_cv_deplibs_check_method
+test -z "$deplibs_check_method" && deplibs_check_method=unknown
+
+_LT_DECL([], [deplibs_check_method], [1],
+    [Method to check whether dependent libraries are shared objects])
+_LT_DECL([], [file_magic_cmd], [1],
+    [Command to use when deplibs_check_method = "file_magic"])
+_LT_DECL([], [file_magic_glob], [1],
+    [How to find potential files when deplibs_check_method = "file_magic"])
+_LT_DECL([], [want_nocaseglob], [1],
+    [Find potential files using nocaseglob when deplibs_check_method = "file_magic"])
+])# _LT_CHECK_MAGIC_METHOD
+
+
+# LT_PATH_NM
+# ----------
+# find the pathname to a BSD- or MS-compatible name lister
+AC_DEFUN([LT_PATH_NM],
+[AC_REQUIRE([AC_PROG_CC])dnl
+AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM,
+[if test -n "$NM"; then
+  # Let the user override the test.
+  lt_cv_path_NM="$NM"
+else
+  lt_nm_to_check="${ac_tool_prefix}nm"
+  if test -n "$ac_tool_prefix" && test "$build" = "$host"; then
+    lt_nm_to_check="$lt_nm_to_check nm"
+  fi
+  for lt_tmp_nm in $lt_nm_to_check; do
+    lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+    for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do
+      IFS="$lt_save_ifs"
+      test -z "$ac_dir" && ac_dir=.
+      tmp_nm="$ac_dir/$lt_tmp_nm"
+      if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext" ; then
+       # Check to see if the nm accepts a BSD-compat flag.
+       # Adding the `sed 1q' prevents false positives on HP-UX, which says:
+       #   nm: unknown option "B" ignored
+       # Tru64's nm complains that /dev/null is an invalid object file
+       case `"$tmp_nm" -B /dev/null 2>&1 | sed '1q'` in
+       */dev/null* | *'Invalid file or object type'*)
+         lt_cv_path_NM="$tmp_nm -B"
+         break
+         ;;
+       *)
+         case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in
+         */dev/null*)
+           lt_cv_path_NM="$tmp_nm -p"
+           break
+           ;;
+         *)
+           lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but
+           continue # so that we can try to find one that supports BSD flags
+           ;;
+         esac
+         ;;
+       esac
+      fi
+    done
+    IFS="$lt_save_ifs"
+  done
+  : ${lt_cv_path_NM=no}
+fi])
+if test "$lt_cv_path_NM" != "no"; then
+  NM="$lt_cv_path_NM"
+else
+  # Didn't find any BSD compatible name lister, look for dumpbin.
+  if test -n "$DUMPBIN"; then :
+    # Let the user override the test.
+  else
+    AC_CHECK_TOOLS(DUMPBIN, [dumpbin "link -dump"], :)
+    case `$DUMPBIN -symbols /dev/null 2>&1 | sed '1q'` in
+    *COFF*)
+      DUMPBIN="$DUMPBIN -symbols"
+      ;;
+    *)
+      DUMPBIN=:
+      ;;
+    esac
+  fi
+  AC_SUBST([DUMPBIN])
+  if test "$DUMPBIN" != ":"; then
+    NM="$DUMPBIN"
+  fi
+fi
+test -z "$NM" && NM=nm
+AC_SUBST([NM])
+_LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl
+
+AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface],
+  [lt_cv_nm_interface="BSD nm"
+  echo "int some_variable = 0;" > conftest.$ac_ext
+  (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&AS_MESSAGE_LOG_FD)
+  (eval "$ac_compile" 2>conftest.err)
+  cat conftest.err >&AS_MESSAGE_LOG_FD
+  (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD)
+  (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
+  cat conftest.err >&AS_MESSAGE_LOG_FD
+  (eval echo "\"\$as_me:$LINENO: output\"" >&AS_MESSAGE_LOG_FD)
+  cat conftest.out >&AS_MESSAGE_LOG_FD
+  if $GREP 'External.*some_variable' conftest.out > /dev/null; then
+    lt_cv_nm_interface="MS dumpbin"
+  fi
+  rm -f conftest*])
+])# LT_PATH_NM
+
+# Old names:
+AU_ALIAS([AM_PROG_NM], [LT_PATH_NM])
+AU_ALIAS([AC_PROG_NM], [LT_PATH_NM])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_PROG_NM], [])
+dnl AC_DEFUN([AC_PROG_NM], [])
+
+# _LT_CHECK_SHAREDLIB_FROM_LINKLIB
+# --------------------------------
+# how to determine the name of the shared library
+# associated with a specific link library.
+#  -- PORTME fill in with the dynamic library characteristics
+m4_defun([_LT_CHECK_SHAREDLIB_FROM_LINKLIB],
+[m4_require([_LT_DECL_EGREP])
+m4_require([_LT_DECL_OBJDUMP])
+m4_require([_LT_DECL_DLLTOOL])
+AC_CACHE_CHECK([how to associate runtime and link libraries],
+lt_cv_sharedlib_from_linklib_cmd,
+[lt_cv_sharedlib_from_linklib_cmd='unknown'
+
+case $host_os in
+cygwin* | mingw* | pw32* | cegcc*)
+  # two different shell functions defined in ltmain.sh
+  # decide which to use based on capabilities of $DLLTOOL
+  case `$DLLTOOL --help 2>&1` in
+  *--identify-strict*)
+    lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib
+    ;;
+  *)
+    lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback
+    ;;
+  esac
+  ;;
+*)
+  # fallback: assume linklib IS sharedlib
+  lt_cv_sharedlib_from_linklib_cmd="$ECHO"
+  ;;
+esac
+])
+sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd
+test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO
+
+_LT_DECL([], [sharedlib_from_linklib_cmd], [1],
+    [Command to associate shared and link libraries])
+])# _LT_CHECK_SHAREDLIB_FROM_LINKLIB
+
+
+# _LT_PATH_MANIFEST_TOOL
+# ----------------------
+# locate the manifest tool
+m4_defun([_LT_PATH_MANIFEST_TOOL],
+[AC_CHECK_TOOL(MANIFEST_TOOL, mt, :)
+test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt
+AC_CACHE_CHECK([if $MANIFEST_TOOL is a manifest tool], [lt_cv_path_mainfest_tool],
+  [lt_cv_path_mainfest_tool=no
+  echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&AS_MESSAGE_LOG_FD
+  $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out
+  cat conftest.err >&AS_MESSAGE_LOG_FD
+  if $GREP 'Manifest Tool' conftest.out > /dev/null; then
+    lt_cv_path_mainfest_tool=yes
+  fi
+  rm -f conftest*])
+if test "x$lt_cv_path_mainfest_tool" != xyes; then
+  MANIFEST_TOOL=:
+fi
+_LT_DECL([], [MANIFEST_TOOL], [1], [Manifest tool])dnl
+])# _LT_PATH_MANIFEST_TOOL
+
+
+# LT_LIB_M
+# --------
+# check for math library
+AC_DEFUN([LT_LIB_M],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+LIBM=
+case $host in
+*-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-pw32* | *-*-darwin*)
+  # These system don't have libm, or don't need it
+  ;;
+*-ncr-sysv4.3*)
+  AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM="-lmw")
+  AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm")
+  ;;
+*)
+  AC_CHECK_LIB(m, cos, LIBM="-lm")
+  ;;
+esac
+AC_SUBST([LIBM])
+])# LT_LIB_M
+
+# Old name:
+AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_CHECK_LIBM], [])
+
+
+# _LT_COMPILER_NO_RTTI([TAGNAME])
+# -------------------------------
+m4_defun([_LT_COMPILER_NO_RTTI],
+[m4_require([_LT_TAG_COMPILER])dnl
+
+_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=
+
+if test "$GCC" = yes; then
+  case $cc_basename in
+  nvcc*)
+    _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -Xcompiler -fno-builtin' ;;
+  *)
+    _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' ;;
+  esac
+
+  _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions],
+    lt_cv_prog_compiler_rtti_exceptions,
+    [-fno-rtti -fno-exceptions], [],
+    [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"])
+fi
+_LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1],
+       [Compiler flag to turn off builtin functions])
+])# _LT_COMPILER_NO_RTTI
+
+
+# _LT_CMD_GLOBAL_SYMBOLS
+# ----------------------
+m4_defun([_LT_CMD_GLOBAL_SYMBOLS],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_PROG_AWK])dnl
+AC_REQUIRE([LT_PATH_NM])dnl
+AC_REQUIRE([LT_PATH_LD])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_TAG_COMPILER])dnl
+
+# Check for command to grab the raw symbol name followed by C symbol from nm.
+AC_MSG_CHECKING([command to parse $NM output from $compiler object])
+AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe],
+[
+# These are sane defaults that work on at least a few old systems.
+# [They come from Ultrix.  What could be older than Ultrix?!! ;)]
+
+# Character class describing NM global symbol codes.
+symcode='[[BCDEGRST]]'
+
+# Regexp to match symbols that can be accessed directly from C.
+sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)'
+
+# Define system-specific variables.
+case $host_os in
+aix*)
+  symcode='[[BCDT]]'
+  ;;
+cygwin* | mingw* | pw32* | cegcc*)
+  symcode='[[ABCDGISTW]]'
+  ;;
+hpux*)
+  if test "$host_cpu" = ia64; then
+    symcode='[[ABCDEGRST]]'
+  fi
+  ;;
+irix* | nonstopux*)
+  symcode='[[BCDEGRST]]'
+  ;;
+osf*)
+  symcode='[[BCDEGQRST]]'
+  ;;
+solaris*)
+  symcode='[[BDRT]]'
+  ;;
+sco3.2v5*)
+  symcode='[[DT]]'
+  ;;
+sysv4.2uw2*)
+  symcode='[[DT]]'
+  ;;
+sysv5* | sco5v6* | unixware* | OpenUNIX*)
+  symcode='[[ABDT]]'
+  ;;
+sysv4)
+  symcode='[[DFNSTU]]'
+  ;;
+esac
+
+# If we're using GNU nm, then use its standard symbol codes.
+case `$NM -V 2>&1` in
+*GNU* | *'with BFD'*)
+  symcode='[[ABCDGIRSTW]]' ;;
+esac
+
+# Transform an extracted symbol line into a proper C declaration.
+# Some systems (esp. on ia64) link data and code symbols differently,
+# so use this general approach.
+lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'"
+
+# Transform an extracted symbol line into symbol name and symbol address
+lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\)[[ ]]*$/  {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/  {\"\2\", (void *) \&\2},/p'"
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n -e 's/^: \([[^ ]]*\)[[ ]]*$/  {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \(lib[[^ ]]*\)$/  {\"\2\", (void *) \&\2},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/  {\"lib\2\", (void *) \&\2},/p'"
+
+# Handle CRLF in mingw tool chain
+opt_cr=
+case $build_os in
+mingw*)
+  opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp
+  ;;
+esac
+
+# Try without a prefix underscore, then with it.
+for ac_symprfx in "" "_"; do
+
+  # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol.
+  symxfrm="\\1 $ac_symprfx\\2 \\2"
+
+  # Write the raw and C identifiers.
+  if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+    # Fake it for dumpbin and say T for any non-static function
+    # and D for any global variable.
+    # Also find C++ and __fastcall symbols from MSVC++,
+    # which start with @ or ?.
+    lt_cv_sys_global_symbol_pipe="$AWK ['"\
+"     {last_section=section; section=\$ 3};"\
+"     /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\
+"     /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\
+"     \$ 0!~/External *\|/{next};"\
+"     / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\
+"     {if(hide[section]) next};"\
+"     {f=0}; \$ 0~/\(\).*\|/{f=1}; {printf f ? \"T \" : \"D \"};"\
+"     {split(\$ 0, a, /\||\r/); split(a[2], s)};"\
+"     s[1]~/^[@?]/{print s[1], s[1]; next};"\
+"     s[1]~prfx {split(s[1],t,\"@\"); print t[1], substr(t[1],length(prfx))}"\
+"     ' prfx=^$ac_symprfx]"
+  else
+    lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[    ]]\($symcode$symcode*\)[[       ]][[    ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'"
+  fi
+  lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'"
+
+  # Check to see that the pipe works correctly.
+  pipe_works=no
+
+  rm -f conftest*
+  cat > conftest.$ac_ext <<_LT_EOF
+#ifdef __cplusplus
+extern "C" {
+#endif
+char nm_test_var;
+void nm_test_func(void);
+void nm_test_func(void){}
+#ifdef __cplusplus
+}
+#endif
+int main(){nm_test_var='a';nm_test_func();return(0);}
+_LT_EOF
+
+  if AC_TRY_EVAL(ac_compile); then
+    # Now try to grab the symbols.
+    nlist=conftest.nm
+    if AC_TRY_EVAL(NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) && test -s "$nlist"; then
+      # Try sorting and uniquifying the output.
+      if sort "$nlist" | uniq > "$nlist"T; then
+       mv -f "$nlist"T "$nlist"
+      else
+       rm -f "$nlist"T
+      fi
+
+      # Make sure that we snagged all the symbols we need.
+      if $GREP ' nm_test_var$' "$nlist" >/dev/null; then
+       if $GREP ' nm_test_func$' "$nlist" >/dev/null; then
+         cat <<_LT_EOF > conftest.$ac_ext
+/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests.  */
+#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE)
+/* DATA imports from DLLs on WIN32 con't be const, because runtime
+   relocations are performed -- see ld's documentation on pseudo-relocs.  */
+# define LT@&t@_DLSYM_CONST
+#elif defined(__osf__)
+/* This system does not cope well with relocations in const data.  */
+# define LT@&t@_DLSYM_CONST
+#else
+# define LT@&t@_DLSYM_CONST const
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+_LT_EOF
+         # Now generate the symbol file.
+         eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext'
+
+         cat <<_LT_EOF >> conftest.$ac_ext
+
+/* The mapping between symbol names and symbols.  */
+LT@&t@_DLSYM_CONST struct {
+  const char *name;
+  void       *address;
+}
+lt__PROGRAM__LTX_preloaded_symbols[[]] =
+{
+  { "@PROGRAM@", (void *) 0 },
+_LT_EOF
+         $SED "s/^$symcode$symcode* \(.*\) \(.*\)$/  {\"\2\", (void *) \&\2},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext
+         cat <<\_LT_EOF >> conftest.$ac_ext
+  {0, (void *) 0}
+};
+
+/* This works around a problem in FreeBSD linker */
+#ifdef FREEBSD_WORKAROUND
+static const void *lt_preloaded_setup() {
+  return lt__PROGRAM__LTX_preloaded_symbols;
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+_LT_EOF
+         # Now try linking the two files.
+         mv conftest.$ac_objext conftstm.$ac_objext
+         lt_globsym_save_LIBS=$LIBS
+         lt_globsym_save_CFLAGS=$CFLAGS
+         LIBS="conftstm.$ac_objext"
+         CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)"
+         if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext}; then
+           pipe_works=yes
+         fi
+         LIBS=$lt_globsym_save_LIBS
+         CFLAGS=$lt_globsym_save_CFLAGS
+       else
+         echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD
+       fi
+      else
+       echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD
+      fi
+    else
+      echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD
+    fi
+  else
+    echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD
+    cat conftest.$ac_ext >&5
+  fi
+  rm -rf conftest* conftst*
+
+  # Do not use the global_symbol_pipe unless it works.
+  if test "$pipe_works" = yes; then
+    break
+  else
+    lt_cv_sys_global_symbol_pipe=
+  fi
+done
+])
+if test -z "$lt_cv_sys_global_symbol_pipe"; then
+  lt_cv_sys_global_symbol_to_cdecl=
+fi
+if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then
+  AC_MSG_RESULT(failed)
+else
+  AC_MSG_RESULT(ok)
+fi
+
+# Response file support.
+if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+  nm_file_list_spec='@'
+elif $NM --help 2>/dev/null | grep '[[@]]FILE' >/dev/null; then
+  nm_file_list_spec='@'
+fi
+
+_LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1],
+    [Take the output of nm and produce a listing of raw symbols and C names])
+_LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1],
+    [Transform the output of nm in a proper C declaration])
+_LT_DECL([global_symbol_to_c_name_address],
+    [lt_cv_sys_global_symbol_to_c_name_address], [1],
+    [Transform the output of nm in a C name address pair])
+_LT_DECL([global_symbol_to_c_name_address_lib_prefix],
+    [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1],
+    [Transform the output of nm in a C name address pair when lib prefix is needed])
+_LT_DECL([], [nm_file_list_spec], [1],
+    [Specify filename containing input files for $NM])
+]) # _LT_CMD_GLOBAL_SYMBOLS
+
+
+# _LT_COMPILER_PIC([TAGNAME])
+# ---------------------------
+m4_defun([_LT_COMPILER_PIC],
+[m4_require([_LT_TAG_COMPILER])dnl
+_LT_TAGVAR(lt_prog_compiler_wl, $1)=
+_LT_TAGVAR(lt_prog_compiler_pic, $1)=
+_LT_TAGVAR(lt_prog_compiler_static, $1)=
+
+m4_if([$1], [CXX], [
+  # C++ specific cases for pic, static, wl, etc.
+  if test "$GXX" = yes; then
+    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+    _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+
+    case $host_os in
+    aix*)
+      # All AIX code is PIC.
+      if test "$host_cpu" = ia64; then
+       # AIX 5 now supports IA64 processor
+       _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+        ;;
+      m68k)
+            # FIXME: we need at least 68020 code to build shared libraries, but
+            # adding the `-m68020' flag to GCC prevents building anything better,
+            # like `-m68040'.
+            _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4'
+        ;;
+      esac
+      ;;
+
+    beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+      # PIC is the default for these OSes.
+      ;;
+    mingw* | cygwin* | os2* | pw32* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      # Although the cygwin gcc ignores -fPIC, still need this for old-style
+      # (--disable-auto-import) libraries
+      m4_if([$1], [GCJ], [],
+       [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+      ;;
+    darwin* | rhapsody*)
+      # PIC is the default on this platform
+      # Common symbols not allowed in MH_DYLIB files
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common'
+      ;;
+    *djgpp*)
+      # DJGPP does not support shared libraries at all
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+      ;;
+    haiku*)
+      # PIC is the default for Haiku.
+      # The "-static" flag exists, but is broken.
+      _LT_TAGVAR(lt_prog_compiler_static, $1)=
+      ;;
+    interix[[3-9]]*)
+      # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+      # Instead, we relocate shared libraries at runtime.
+      ;;
+    sysv4*MP*)
+      if test -d /usr/nec; then
+       _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic
+      fi
+      ;;
+    hpux*)
+      # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+      # PA HP-UX.  On IA64 HP-UX, PIC is the default but the pic flag
+      # sets the default TLS model and affects inlining.
+      case $host_cpu in
+      hppa*64*)
+       ;;
+      *)
+       _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+       ;;
+      esac
+      ;;
+    *qnx* | *nto*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+      ;;
+    *)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+      ;;
+    esac
+  else
+    case $host_os in
+      aix[[4-9]]*)
+       # All AIX code is PIC.
+       if test "$host_cpu" = ia64; then
+         # AIX 5 now supports IA64 processor
+         _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+       else
+         _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp'
+       fi
+       ;;
+      chorus*)
+       case $cc_basename in
+       cxch68*)
+         # Green Hills C++ Compiler
+         # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a"
+         ;;
+       esac
+       ;;
+      mingw* | cygwin* | os2* | pw32* | cegcc*)
+       # This hack is so that the source file can tell whether it is being
+       # built for inclusion in a dll (and should export symbols for example).
+       m4_if([$1], [GCJ], [],
+         [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+       ;;
+      dgux*)
+       case $cc_basename in
+         ec++*)
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+           ;;
+         ghcx*)
+           # Green Hills C++ Compiler
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+           ;;
+         *)
+           ;;
+       esac
+       ;;
+      freebsd* | dragonfly*)
+       # FreeBSD uses GNU C++
+       ;;
+      hpux9* | hpux10* | hpux11*)
+       case $cc_basename in
+         CC*)
+           _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+           _LT_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive'
+           if test "$host_cpu" != ia64; then
+             _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z'
+           fi
+           ;;
+         aCC*)
+           _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+           _LT_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive'
+           case $host_cpu in
+           hppa*64*|ia64*)
+             # +Z the default
+             ;;
+           *)
+             _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z'
+             ;;
+           esac
+           ;;
+         *)
+           ;;
+       esac
+       ;;
+      interix*)
+       # This is c89, which is MS Visual C++ (no shared libs)
+       # Anyone wants to do a port?
+       ;;
+      irix5* | irix6* | nonstopux*)
+       case $cc_basename in
+         CC*)
+           _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+           _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+           # CC pic flag -KPIC is the default.
+           ;;
+         *)
+           ;;
+       esac
+       ;;
+      linux* | k*bsd*-gnu | kopensolaris*-gnu)
+       case $cc_basename in
+         KCC*)
+           # KAI C++ Compiler
+           _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,'
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+           ;;
+         ecpc* )
+           # old Intel C++ for x86_64 which still supported -KPIC.
+           _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+           _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+           ;;
+         icpc* )
+           # Intel C++, used to be incompatible with GCC.
+           # ICC 10 doesn't accept -KPIC any more.
+           _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+           _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+           ;;
+         pgCC* | pgcpp*)
+           # Portland Group C++ compiler
+           _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic'
+           _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+           ;;
+         cxx*)
+           # Compaq C++
+           # Make sure the PIC flag is empty.  It appears that all Alpha
+           # Linux and Compaq Tru64 Unix objects are PIC.
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+           _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+           ;;
+         xlc* | xlC* | bgxl[[cC]]* | mpixl[[cC]]*)
+           # IBM XL 8.0, 9.0 on PPC and BlueGene
+           _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic'
+           _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink'
+           ;;
+         *)
+           case `$CC -V 2>&1 | sed 5q` in
+           *Sun\ C*)
+             # Sun C++ 5.9
+             _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+             _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+             _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+             ;;
+           esac
+           ;;
+       esac
+       ;;
+      lynxos*)
+       ;;
+      m88k*)
+       ;;
+      mvs*)
+       case $cc_basename in
+         cxx*)
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall'
+           ;;
+         *)
+           ;;
+       esac
+       ;;
+      netbsd* | netbsdelf*-gnu)
+       ;;
+      *qnx* | *nto*)
+        # QNX uses GNU C++, but need to define -shared option too, otherwise
+        # it will coredump.
+        _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+        ;;
+      osf3* | osf4* | osf5*)
+       case $cc_basename in
+         KCC*)
+           _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,'
+           ;;
+         RCC*)
+           # Rational C++ 2.4.1
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+           ;;
+         cxx*)
+           # Digital/Compaq C++
+           _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+           # Make sure the PIC flag is empty.  It appears that all Alpha
+           # Linux and Compaq Tru64 Unix objects are PIC.
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+           _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+           ;;
+         *)
+           ;;
+       esac
+       ;;
+      psos*)
+       ;;
+      solaris*)
+       case $cc_basename in
+         CC* | sunCC*)
+           # Sun C++ 4.2, 5.x and Centerline C++
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+           _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+           _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+           ;;
+         gcx*)
+           # Green Hills C++ Compiler
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+           ;;
+         *)
+           ;;
+       esac
+       ;;
+      sunos4*)
+       case $cc_basename in
+         CC*)
+           # Sun C++ 4.x
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+           _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+           ;;
+         lcc*)
+           # Lucid
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+           ;;
+         *)
+           ;;
+       esac
+       ;;
+      sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+       case $cc_basename in
+         CC*)
+           _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+           _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+           ;;
+       esac
+       ;;
+      tandem*)
+       case $cc_basename in
+         NCC*)
+           # NonStop-UX NCC 3.20
+           _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+           ;;
+         *)
+           ;;
+       esac
+       ;;
+      vxworks*)
+       ;;
+      *)
+       _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+       ;;
+    esac
+  fi
+],
+[
+  if test "$GCC" = yes; then
+    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+    _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+
+    case $host_os in
+      aix*)
+      # All AIX code is PIC.
+      if test "$host_cpu" = ia64; then
+       # AIX 5 now supports IA64 processor
+       _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+        ;;
+      m68k)
+            # FIXME: we need at least 68020 code to build shared libraries, but
+            # adding the `-m68020' flag to GCC prevents building anything better,
+            # like `-m68040'.
+            _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4'
+        ;;
+      esac
+      ;;
+
+    beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+      # PIC is the default for these OSes.
+      ;;
+
+    mingw* | cygwin* | pw32* | os2* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      # Although the cygwin gcc ignores -fPIC, still need this for old-style
+      # (--disable-auto-import) libraries
+      m4_if([$1], [GCJ], [],
+       [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+      ;;
+
+    darwin* | rhapsody*)
+      # PIC is the default on this platform
+      # Common symbols not allowed in MH_DYLIB files
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common'
+      ;;
+
+    haiku*)
+      # PIC is the default for Haiku.
+      # The "-static" flag exists, but is broken.
+      _LT_TAGVAR(lt_prog_compiler_static, $1)=
+      ;;
+
+    hpux*)
+      # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+      # PA HP-UX.  On IA64 HP-UX, PIC is the default but the pic flag
+      # sets the default TLS model and affects inlining.
+      case $host_cpu in
+      hppa*64*)
+       # +Z the default
+       ;;
+      *)
+       _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+       ;;
+      esac
+      ;;
+
+    interix[[3-9]]*)
+      # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+      # Instead, we relocate shared libraries at runtime.
+      ;;
+
+    msdosdjgpp*)
+      # Just because we use GCC doesn't mean we suddenly get shared libraries
+      # on systems that don't support them.
+      _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+      enable_shared=no
+      ;;
+
+    *nto* | *qnx*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec; then
+       _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic
+      fi
+      ;;
+
+    *)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+      ;;
+    esac
+
+    case $cc_basename in
+    nvcc*) # Cuda Compiler Driver 2.2
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Xlinker '
+      if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then
+        _LT_TAGVAR(lt_prog_compiler_pic, $1)="-Xcompiler $_LT_TAGVAR(lt_prog_compiler_pic, $1)"
+      fi
+      ;;
+    esac
+  else
+    # PORTME Check for flag to pass linker flags through the system compiler.
+    case $host_os in
+    aix*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      if test "$host_cpu" = ia64; then
+       # AIX 5 now supports IA64 processor
+       _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      else
+       _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp'
+      fi
+      ;;
+
+    mingw* | cygwin* | pw32* | os2* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      m4_if([$1], [GCJ], [],
+       [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+      ;;
+
+    hpux9* | hpux10* | hpux11*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but
+      # not for PA HP-UX.
+      case $host_cpu in
+      hppa*64*|ia64*)
+       # +Z the default
+       ;;
+      *)
+       _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z'
+       ;;
+      esac
+      # Is there a better lt_prog_compiler_static that works with the bundled CC?
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive'
+      ;;
+
+    irix5* | irix6* | nonstopux*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      # PIC (with -KPIC) is the default.
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+      ;;
+
+    linux* | k*bsd*-gnu | kopensolaris*-gnu)
+      case $cc_basename in
+      # old Intel for x86_64 which still supported -KPIC.
+      ecc*)
+       _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+       _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+       _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+        ;;
+      # icc used to be incompatible with GCC.
+      # ICC 10 doesn't accept -KPIC any more.
+      icc* | ifort*)
+       _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+       _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+       _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+        ;;
+      # Lahey Fortran 8.1.
+      lf95*)
+       _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+       _LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared'
+       _LT_TAGVAR(lt_prog_compiler_static, $1)='--static'
+       ;;
+      nagfor*)
+       # NAG Fortran compiler
+       _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,'
+       _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+       _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+       ;;
+      pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*)
+        # Portland Group compilers (*not* the Pentium gcc compiler,
+       # which looks to be a dead project)
+       _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+       _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic'
+       _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+        ;;
+      ccc*)
+        _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+        # All Alpha code is PIC.
+        _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+        ;;
+      xl* | bgxl* | bgf* | mpixl*)
+       # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene
+       _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+       _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic'
+       _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink'
+       ;;
+      *)
+       case `$CC -V 2>&1 | sed 5q` in
+       *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [[1-7]].* | *Sun*Fortran*\ 8.[[0-3]]*)
+         # Sun Fortran 8.3 passes all unrecognized flags to the linker
+         _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+         _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+         _LT_TAGVAR(lt_prog_compiler_wl, $1)=''
+         ;;
+       *Sun\ F* | *Sun*Fortran*)
+         _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+         _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+         _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+         ;;
+       *Sun\ C*)
+         # Sun C 5.9
+         _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+         _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+         _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+         ;;
+        *Intel*\ [[CF]]*Compiler*)
+         _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+         _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+         _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+         ;;
+       *Portland\ Group*)
+         _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+         _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic'
+         _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+         ;;
+       esac
+       ;;
+      esac
+      ;;
+
+    newsos6)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    *nto* | *qnx*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+      ;;
+
+    osf3* | osf4* | osf5*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      # All OSF/1 code is PIC.
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+      ;;
+
+    rdos*)
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+      ;;
+
+    solaris*)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      case $cc_basename in
+      f77* | f90* | f95* | sunf77* | sunf90* | sunf95*)
+       _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';;
+      *)
+       _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';;
+      esac
+      ;;
+
+    sunos4*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    sysv4 | sysv4.2uw2* | sysv4.3*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec ;then
+       _LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic'
+       _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      fi
+      ;;
+
+    sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    unicos*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+      ;;
+
+    uts4*)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    *)
+      _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+      ;;
+    esac
+  fi
+])
+case $host_os in
+  # For platforms which do not support PIC, -DPIC is meaningless:
+  *djgpp*)
+    _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+    ;;
+  *)
+    _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])"
+    ;;
+esac
+
+AC_CACHE_CHECK([for $compiler option to produce PIC],
+  [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)],
+  [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_prog_compiler_pic, $1)])
+_LT_TAGVAR(lt_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)
+
+#
+# Check to make sure the PIC flag actually works.
+#
+if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then
+  _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works],
+    [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)],
+    [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [],
+    [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in
+     "" | " "*) ;;
+     *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;;
+     esac],
+    [_LT_TAGVAR(lt_prog_compiler_pic, $1)=
+     _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no])
+fi
+_LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1],
+       [Additional compiler flags for building library objects])
+
+_LT_TAGDECL([wl], [lt_prog_compiler_wl], [1],
+       [How to pass a linker flag through the compiler])
+#
+# Check to make sure the static flag actually works.
+#
+wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\"
+_LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works],
+  _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1),
+  $lt_tmp_static_flag,
+  [],
+  [_LT_TAGVAR(lt_prog_compiler_static, $1)=])
+_LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1],
+       [Compiler flag to prevent dynamic linking])
+])# _LT_COMPILER_PIC
+
+
+# _LT_LINKER_SHLIBS([TAGNAME])
+# ----------------------------
+# See if the linker supports building shared libraries.
+m4_defun([_LT_LINKER_SHLIBS],
+[AC_REQUIRE([LT_PATH_LD])dnl
+AC_REQUIRE([LT_PATH_NM])dnl
+m4_require([_LT_PATH_MANIFEST_TOOL])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl
+m4_require([_LT_TAG_COMPILER])dnl
+AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries])
+m4_if([$1], [CXX], [
+  _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+  _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*']
+  case $host_os in
+  aix[[4-9]]*)
+    # If we're using GNU nm, then we don't want the "-C" option.
+    # -C means demangle to AIX nm, but means don't demangle with GNU nm
+    # Also, AIX nm treats weak defined symbols like other global defined
+    # symbols, whereas GNU nm marks them as "W".
+    if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+      _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+    else
+      _LT_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+    fi
+    ;;
+  pw32*)
+    _LT_TAGVAR(export_symbols_cmds, $1)="$ltdll_cmds"
+    ;;
+  cygwin* | mingw* | cegcc*)
+    case $cc_basename in
+    cl*)
+      _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+      ;;
+    *)
+      _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols'
+      _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname']
+      ;;
+    esac
+    ;;
+  linux* | k*bsd*-gnu | gnu*)
+    _LT_TAGVAR(link_all_deplibs, $1)=no
+    ;;
+  *)
+    _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+    ;;
+  esac
+], [
+  runpath_var=
+  _LT_TAGVAR(allow_undefined_flag, $1)=
+  _LT_TAGVAR(always_export_symbols, $1)=no
+  _LT_TAGVAR(archive_cmds, $1)=
+  _LT_TAGVAR(archive_expsym_cmds, $1)=
+  _LT_TAGVAR(compiler_needs_object, $1)=no
+  _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+  _LT_TAGVAR(export_dynamic_flag_spec, $1)=
+  _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+  _LT_TAGVAR(hardcode_automatic, $1)=no
+  _LT_TAGVAR(hardcode_direct, $1)=no
+  _LT_TAGVAR(hardcode_direct_absolute, $1)=no
+  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+  _LT_TAGVAR(hardcode_libdir_separator, $1)=
+  _LT_TAGVAR(hardcode_minus_L, $1)=no
+  _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported
+  _LT_TAGVAR(inherit_rpath, $1)=no
+  _LT_TAGVAR(link_all_deplibs, $1)=unknown
+  _LT_TAGVAR(module_cmds, $1)=
+  _LT_TAGVAR(module_expsym_cmds, $1)=
+  _LT_TAGVAR(old_archive_from_new_cmds, $1)=
+  _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)=
+  _LT_TAGVAR(thread_safe_flag_spec, $1)=
+  _LT_TAGVAR(whole_archive_flag_spec, $1)=
+  # include_expsyms should be a list of space-separated symbols to be *always*
+  # included in the symbol list
+  _LT_TAGVAR(include_expsyms, $1)=
+  # exclude_expsyms can be an extended regexp of symbols to exclude
+  # it will be wrapped by ` (' and `)$', so one must not match beginning or
+  # end of line.  Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc',
+  # as well as any symbol that contains `d'.
+  _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*']
+  # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out
+  # platforms (ab)use it in PIC code, but their linkers get confused if
+  # the symbol is explicitly referenced.  Since portable code cannot
+  # rely on this symbol name, it's probably fine to never include it in
+  # preloaded symbol tables.
+  # Exclude shared library initialization/finalization symbols.
+dnl Note also adjust exclude_expsyms for C++ above.
+  extract_expsyms_cmds=
+
+  case $host_os in
+  cygwin* | mingw* | pw32* | cegcc*)
+    # FIXME: the MSVC++ port hasn't been tested in a loooong time
+    # When not using gcc, we currently assume that we are using
+    # Microsoft Visual C++.
+    if test "$GCC" != yes; then
+      with_gnu_ld=no
+    fi
+    ;;
+  interix*)
+    # we just hope/assume this is gcc and not c89 (= MSVC++)
+    with_gnu_ld=yes
+    ;;
+  openbsd*)
+    with_gnu_ld=no
+    ;;
+  linux* | k*bsd*-gnu | gnu*)
+    _LT_TAGVAR(link_all_deplibs, $1)=no
+    ;;
+  esac
+
+  _LT_TAGVAR(ld_shlibs, $1)=yes
+
+  # On some targets, GNU ld is compatible enough with the native linker
+  # that we're better off using the native interface for both.
+  lt_use_gnu_ld_interface=no
+  if test "$with_gnu_ld" = yes; then
+    case $host_os in
+      aix*)
+       # The AIX port of GNU ld has always aspired to compatibility
+       # with the native linker.  However, as the warning in the GNU ld
+       # block says, versions before 2.19.5* couldn't really create working
+       # shared libraries, regardless of the interface used.
+       case `$LD -v 2>&1` in
+         *\ \(GNU\ Binutils\)\ 2.19.5*) ;;
+         *\ \(GNU\ Binutils\)\ 2.[[2-9]]*) ;;
+         *\ \(GNU\ Binutils\)\ [[3-9]]*) ;;
+         *)
+           lt_use_gnu_ld_interface=yes
+           ;;
+       esac
+       ;;
+      *)
+       lt_use_gnu_ld_interface=yes
+       ;;
+    esac
+  fi
+
+  if test "$lt_use_gnu_ld_interface" = yes; then
+    # If archive_cmds runs LD, not CC, wlarc should be empty
+    wlarc='${wl}'
+
+    # Set some defaults for GNU ld with shared library support. These
+    # are reset later if shared libraries are not supported. Putting them
+    # here allows them to be overridden if necessary.
+    runpath_var=LD_RUN_PATH
+    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+    # ancient GNU ld didn't support --whole-archive et. al.
+    if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then
+      _LT_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+    else
+      _LT_TAGVAR(whole_archive_flag_spec, $1)=
+    fi
+    supports_anon_versioning=no
+    case `$LD -v 2>&1` in
+      *GNU\ gold*) supports_anon_versioning=yes ;;
+      *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11
+      *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ...
+      *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ...
+      *\ 2.11.*) ;; # other 2.11 versions
+      *) supports_anon_versioning=yes ;;
+    esac
+
+    # See if GNU ld supports shared libraries.
+    case $host_os in
+    aix[[3-9]]*)
+      # On AIX/PPC, the GNU linker is very broken
+      if test "$host_cpu" != ia64; then
+       _LT_TAGVAR(ld_shlibs, $1)=no
+       cat <<_LT_EOF 1>&2
+
+*** Warning: the GNU linker, at least up to release 2.19, is reported
+*** to be unable to reliably create shared libraries on AIX.
+*** Therefore, libtool is disabling shared libraries support.  If you
+*** really care for shared libraries, you may want to install binutils
+*** 2.20 or above, or modify your PATH so that a non-GNU linker is found.
+*** You will then need to restart the configuration process.
+
+_LT_EOF
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+            _LT_TAGVAR(archive_expsym_cmds, $1)=''
+        ;;
+      m68k)
+            _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+            _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+            _LT_TAGVAR(hardcode_minus_L, $1)=yes
+        ;;
+      esac
+      ;;
+
+    beos*)
+      if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+       _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+       # Joseph Beckenbach <jrb3@best.com> says some releases of gcc
+       # support --undefined.  This deserves some investigation.  FIXME
+       _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+      else
+       _LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    cygwin* | mingw* | pw32* | cegcc*)
+      # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless,
+      # as there is no search path for DLLs.
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-all-symbols'
+      _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+      _LT_TAGVAR(always_export_symbols, $1)=no
+      _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+      _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols'
+      _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname']
+
+      if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+       # If the export-symbols file already is a .def file (1st line
+       # is EXPORTS), use it as is; otherwise, prepend...
+       _LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+         cp $export_symbols $output_objdir/$soname.def;
+       else
+         echo EXPORTS > $output_objdir/$soname.def;
+         cat $export_symbols >> $output_objdir/$soname.def;
+       fi~
+       $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+      else
+       _LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    haiku*)
+      _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      ;;
+
+    interix[[3-9]]*)
+      _LT_TAGVAR(hardcode_direct, $1)=no
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+      # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+      # Instead, shared libraries are loaded at an image base (0x10000000 by
+      # default) and relocated if they conflict, which is a slow very memory
+      # consuming and fragmenting process.  To avoid this, we pick a random,
+      # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+      # time.  Moving up from 0x10000000 also allows more sbrk(2) space.
+      _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+      _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+      ;;
+
+    gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu)
+      tmp_diet=no
+      if test "$host_os" = linux-dietlibc; then
+       case $cc_basename in
+         diet\ *) tmp_diet=yes;;       # linux-dietlibc with static linking (!diet-dyn)
+       esac
+      fi
+      if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \
+        && test "$tmp_diet" = no
+      then
+       tmp_addflag=' $pic_flag'
+       tmp_sharedflag='-shared'
+       case $cc_basename,$host_cpu in
+        pgcc*)                         # Portland Group C compiler
+         _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+         tmp_addflag=' $pic_flag'
+         ;;
+       pgf77* | pgf90* | pgf95* | pgfortran*)
+                                       # Portland Group f77 and f90 compilers
+         _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+         tmp_addflag=' $pic_flag -Mnomain' ;;
+       ecc*,ia64* | icc*,ia64*)        # Intel C compiler on ia64
+         tmp_addflag=' -i_dynamic' ;;
+       efc*,ia64* | ifort*,ia64*)      # Intel Fortran compiler on ia64
+         tmp_addflag=' -i_dynamic -nofor_main' ;;
+       ifc* | ifort*)                  # Intel Fortran compiler
+         tmp_addflag=' -nofor_main' ;;
+       lf95*)                          # Lahey Fortran 8.1
+         _LT_TAGVAR(whole_archive_flag_spec, $1)=
+         tmp_sharedflag='--shared' ;;
+       xl[[cC]]* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below)
+         tmp_sharedflag='-qmkshrobj'
+         tmp_addflag= ;;
+       nvcc*)  # Cuda Compiler Driver 2.2
+         _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+         _LT_TAGVAR(compiler_needs_object, $1)=yes
+         ;;
+       esac
+       case `$CC -V 2>&1 | sed 5q` in
+       *Sun\ C*)                       # Sun C 5.9
+         _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+         _LT_TAGVAR(compiler_needs_object, $1)=yes
+         tmp_sharedflag='-G' ;;
+       *Sun\ F*)                       # Sun Fortran 8.3
+         tmp_sharedflag='-G' ;;
+       esac
+       _LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+
+        if test "x$supports_anon_versioning" = xyes; then
+          _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~
+           cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+           echo "local: *; };" >> $output_objdir/$libname.ver~
+           $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib'
+        fi
+
+       case $cc_basename in
+       xlf* | bgf* | bgxlf* | mpixlf*)
+         # IBM XL Fortran 10.1 on PPC cannot create shared libs itself
+         _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive'
+         _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+         _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib'
+         if test "x$supports_anon_versioning" = xyes; then
+           _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~
+             cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+             echo "local: *; };" >> $output_objdir/$libname.ver~
+             $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib'
+         fi
+         ;;
+       esac
+      else
+        _LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    netbsd* | netbsdelf*-gnu)
+      if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+       _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib'
+       wlarc=
+      else
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+       _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      fi
+      ;;
+
+    solaris*)
+      if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then
+       _LT_TAGVAR(ld_shlibs, $1)=no
+       cat <<_LT_EOF 1>&2
+
+*** Warning: The releases 2.8.* of the GNU linker cannot reliably
+*** create shared libraries on Solaris systems.  Therefore, libtool
+*** is disabling shared libraries support.  We urge you to upgrade GNU
+*** binutils to release 2.9.1 or newer.  Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+      elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+       _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      else
+       _LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*)
+      case `$LD -v 2>&1` in
+        *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*)
+       _LT_TAGVAR(ld_shlibs, $1)=no
+       cat <<_LT_EOF 1>&2
+
+*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not
+*** reliably create shared libraries on SCO systems.  Therefore, libtool
+*** is disabling shared libraries support.  We urge you to upgrade GNU
+*** binutils to release 2.16.91.0.3 or newer.  Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+       ;;
+       *)
+         # For security reasons, it is highly recommended that you always
+         # use absolute paths for naming shared libraries, and exclude the
+         # DT_RUNPATH tag from executables and libraries.  But doing so
+         # requires that you compile everything twice, which is a pain.
+         if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+           _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+           _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+           _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+         else
+           _LT_TAGVAR(ld_shlibs, $1)=no
+         fi
+       ;;
+      esac
+      ;;
+
+    sunos4*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+      wlarc=
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    *)
+      if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+       _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      else
+       _LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+    esac
+
+    if test "$_LT_TAGVAR(ld_shlibs, $1)" = no; then
+      runpath_var=
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)=
+      _LT_TAGVAR(whole_archive_flag_spec, $1)=
+    fi
+  else
+    # PORTME fill in a description of your system's linker (not GNU ld)
+    case $host_os in
+    aix3*)
+      _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+      _LT_TAGVAR(always_export_symbols, $1)=yes
+      _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname'
+      # Note: this linker hardcodes the directories in LIBPATH if there
+      # are no directories specified by -L.
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then
+       # Neither direct hardcoding nor static linking is supported with a
+       # broken collect2.
+       _LT_TAGVAR(hardcode_direct, $1)=unsupported
+      fi
+      ;;
+
+    aix[[4-9]]*)
+      if test "$host_cpu" = ia64; then
+       # On IA64, the linker does run time linking by default, so we don't
+       # have to do anything special.
+       aix_use_runtimelinking=no
+       exp_sym_flag='-Bexport'
+       no_entry_flag=""
+      else
+       # If we're using GNU nm, then we don't want the "-C" option.
+       # -C means demangle to AIX nm, but means don't demangle with GNU nm
+       # Also, AIX nm treats weak defined symbols like other global
+       # defined symbols, whereas GNU nm marks them as "W".
+       if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+         _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+       else
+         _LT_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+       fi
+       aix_use_runtimelinking=no
+
+       # Test if we are trying to use run time linking or normal
+       # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+       # need to do runtime linking.
+       case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*)
+         for ld_flag in $LDFLAGS; do
+         if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then
+           aix_use_runtimelinking=yes
+           break
+         fi
+         done
+         ;;
+       esac
+
+       exp_sym_flag='-bexport'
+       no_entry_flag='-bnoentry'
+      fi
+
+      # When large executables or shared objects are built, AIX ld can
+      # have problems creating the table of contents.  If linking a library
+      # or program results in "error TOC overflow" add -mminimal-toc to
+      # CXXFLAGS/CFLAGS for g++/gcc.  In the cases where that is not
+      # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+      _LT_TAGVAR(archive_cmds, $1)=''
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      _LT_TAGVAR(file_list_spec, $1)='${wl}-f,'
+
+      if test "$GCC" = yes; then
+       case $host_os in aix4.[[012]]|aix4.[[012]].*)
+       # We only want to do this on AIX 4.2 and lower, the check
+       # below for broken collect2 doesn't work under 4.3+
+         collect2name=`${CC} -print-prog-name=collect2`
+         if test -f "$collect2name" &&
+          strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+         then
+         # We have reworked collect2
+         :
+         else
+         # We have old collect2
+         _LT_TAGVAR(hardcode_direct, $1)=unsupported
+         # It fails to find uninstalled libraries when the uninstalled
+         # path is not listed in the libpath.  Setting hardcode_minus_L
+         # to unsupported forces relinking
+         _LT_TAGVAR(hardcode_minus_L, $1)=yes
+         _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+         _LT_TAGVAR(hardcode_libdir_separator, $1)=
+         fi
+         ;;
+       esac
+       shared_flag='-shared'
+       if test "$aix_use_runtimelinking" = yes; then
+         shared_flag="$shared_flag "'${wl}-G'
+       fi
+       _LT_TAGVAR(link_all_deplibs, $1)=no
+      else
+       # not using gcc
+       if test "$host_cpu" = ia64; then
+       # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+       # chokes on -Wl,-G. The following line is correct:
+         shared_flag='-G'
+       else
+         if test "$aix_use_runtimelinking" = yes; then
+           shared_flag='${wl}-G'
+         else
+           shared_flag='${wl}-bM:SRE'
+         fi
+       fi
+      fi
+
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-bexpall'
+      # It seems that -bexpall does not export symbols beginning with
+      # underscore (_), so it is better to generate a list of symbols to export.
+      _LT_TAGVAR(always_export_symbols, $1)=yes
+      if test "$aix_use_runtimelinking" = yes; then
+       # Warning - without using the other runtime loading flags (-brtl),
+       # -berok will link without error, but may produce a broken library.
+       _LT_TAGVAR(allow_undefined_flag, $1)='-berok'
+        # Determine the default libpath from the value encoded in an
+        # empty executable.
+        _LT_SYS_MODULE_PATH_AIX([$1])
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath"
+        _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+      else
+       if test "$host_cpu" = ia64; then
+         _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib'
+         _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs"
+         _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols"
+       else
+        # Determine the default libpath from the value encoded in an
+        # empty executable.
+        _LT_SYS_MODULE_PATH_AIX([$1])
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath"
+         # Warning - without using the other run time loading flags,
+         # -berok will link without error, but may produce a broken library.
+         _LT_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok'
+         _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok'
+         if test "$with_gnu_ld" = yes; then
+           # We only use this code for GNU lds that support --whole-archive.
+           _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+         else
+           # Exported symbols can be pulled into shared objects from archives
+           _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience'
+         fi
+         _LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+         # This is similar to how AIX traditionally builds its shared libraries.
+         _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname'
+       fi
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+            _LT_TAGVAR(archive_expsym_cmds, $1)=''
+        ;;
+      m68k)
+            _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+            _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+            _LT_TAGVAR(hardcode_minus_L, $1)=yes
+        ;;
+      esac
+      ;;
+
+    bsdi[[45]]*)
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic
+      ;;
+
+    cygwin* | mingw* | pw32* | cegcc*)
+      # When not using gcc, we currently assume that we are using
+      # Microsoft Visual C++.
+      # hardcode_libdir_flag_spec is actually meaningless, as there is
+      # no search path for DLLs.
+      case $cc_basename in
+      cl*)
+       # Native MSVC
+       _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' '
+       _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+       _LT_TAGVAR(always_export_symbols, $1)=yes
+       _LT_TAGVAR(file_list_spec, $1)='@'
+       # Tell ltmain to make .lib files, not .a files.
+       libext=lib
+       # Tell ltmain to make .dll files, not .so files.
+       shrext_cmds=".dll"
+       # FIXME: Setting linknames here is a bad hack.
+       _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames='
+       _LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+           sed -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp;
+         else
+           sed -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp;
+         fi~
+         $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+         linknames='
+       # The linker will not automatically build a static lib if we build a DLL.
+       # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true'
+       _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+       _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+       _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1,DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols'
+       # Don't use ranlib
+       _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib'
+       _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~
+         lt_tool_outputfile="@TOOL_OUTPUT@"~
+         case $lt_outputfile in
+           *.exe|*.EXE) ;;
+           *)
+             lt_outputfile="$lt_outputfile.exe"
+             lt_tool_outputfile="$lt_tool_outputfile.exe"
+             ;;
+         esac~
+         if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then
+           $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+           $RM "$lt_outputfile.manifest";
+         fi'
+       ;;
+      *)
+       # Assume MSVC wrapper
+       _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' '
+       _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+       # Tell ltmain to make .lib files, not .a files.
+       libext=lib
+       # Tell ltmain to make .dll files, not .so files.
+       shrext_cmds=".dll"
+       # FIXME: Setting linknames here is a bad hack.
+       _LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames='
+       # The linker will automatically build a .lib file if we build a DLL.
+       _LT_TAGVAR(old_archive_from_new_cmds, $1)='true'
+       # FIXME: Should let the user specify the lib program.
+       _LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs'
+       _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+       ;;
+      esac
+      ;;
+
+    darwin* | rhapsody*)
+      _LT_DARWIN_LINKER_FEATURES($1)
+      ;;
+
+    dgux*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor
+    # support.  Future versions do this automatically, but an explicit c++rt0.o
+    # does not break anything, and helps significantly (at the cost of a little
+    # extra space).
+    freebsd2.2*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    # Unfortunately, older versions of FreeBSD 2 do not have this feature.
+    freebsd2.*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    # FreeBSD 3 and greater uses gcc -shared to do shared libraries.
+    freebsd* | dragonfly*)
+      _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    hpux9*)
+      if test "$GCC" = yes; then
+       _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+      else
+       _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+      fi
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+
+      # hardcode_minus_L: Not really in the search PATH,
+      # but as the default location of the library.
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+      ;;
+
+    hpux10*)
+      if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+      else
+       _LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+      fi
+      if test "$with_gnu_ld" = no; then
+       _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+       _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+       _LT_TAGVAR(hardcode_direct, $1)=yes
+       _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+       _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+       # hardcode_minus_L: Not really in the search PATH,
+       # but as the default location of the library.
+       _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      fi
+      ;;
+
+    hpux11*)
+      if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+       case $host_cpu in
+       hppa*64*)
+         _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+         ;;
+       ia64*)
+         _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+         ;;
+       *)
+         _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+         ;;
+       esac
+      else
+       case $host_cpu in
+       hppa*64*)
+         _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+         ;;
+       ia64*)
+         _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+         ;;
+       *)
+       m4_if($1, [], [
+         # Older versions of the 11.00 compiler do not understand -b yet
+         # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does)
+         _LT_LINKER_OPTION([if $CC understands -b],
+           _LT_TAGVAR(lt_cv_prog_compiler__b, $1), [-b],
+           [_LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'],
+           [_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'])],
+         [_LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'])
+         ;;
+       esac
+      fi
+      if test "$with_gnu_ld" = no; then
+       _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+       _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+       case $host_cpu in
+       hppa*64*|ia64*)
+         _LT_TAGVAR(hardcode_direct, $1)=no
+         _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+         ;;
+       *)
+         _LT_TAGVAR(hardcode_direct, $1)=yes
+         _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+         _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+
+         # hardcode_minus_L: Not really in the search PATH,
+         # but as the default location of the library.
+         _LT_TAGVAR(hardcode_minus_L, $1)=yes
+         ;;
+       esac
+      fi
+      ;;
+
+    irix5* | irix6* | nonstopux*)
+      if test "$GCC" = yes; then
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+       # Try to use the -exported_symbol ld option, if it does not
+       # work, assume that -exports_file does not work either and
+       # implicitly export all symbols.
+       # This should be the same for all languages, so no per-tag cache variable.
+       AC_CACHE_CHECK([whether the $host_os linker accepts -exported_symbol],
+         [lt_cv_irix_exported_symbol],
+         [save_LDFLAGS="$LDFLAGS"
+          LDFLAGS="$LDFLAGS -shared ${wl}-exported_symbol ${wl}foo ${wl}-update_registry ${wl}/dev/null"
+          AC_LINK_IFELSE(
+            [AC_LANG_SOURCE(
+               [AC_LANG_CASE([C], [[int foo (void) { return 0; }]],
+                             [C++], [[int foo (void) { return 0; }]],
+                             [Fortran 77], [[
+      subroutine foo
+      end]],
+                             [Fortran], [[
+      subroutine foo
+      end]])])],
+             [lt_cv_irix_exported_symbol=yes],
+             [lt_cv_irix_exported_symbol=no])
+           LDFLAGS="$save_LDFLAGS"])
+       if test "$lt_cv_irix_exported_symbol" = yes; then
+          _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations ${wl}-exports_file ${wl}$export_symbols -o $lib'
+       fi
+      else
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+       _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -exports_file $export_symbols -o $lib'
+      fi
+      _LT_TAGVAR(archive_cmds_need_lc, $1)='no'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      _LT_TAGVAR(inherit_rpath, $1)=yes
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      ;;
+
+    netbsd* | netbsdelf*-gnu)
+      if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+       _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'  # a.out
+      else
+       _LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags'      # ELF
+      fi
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    newsos6)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    *nto* | *qnx*)
+      ;;
+
+    openbsd*)
+      if test -f /usr/libexec/ld.so; then
+       _LT_TAGVAR(hardcode_direct, $1)=yes
+       _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+       _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+       if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+         _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+         _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols'
+         _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+         _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+       else
+         case $host_os in
+          openbsd[[01]].* | openbsd2.[[0-7]] | openbsd2.[[0-7]].*)
+            _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+            _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+            ;;
+          *)
+            _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+            _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+            ;;
+         esac
+       fi
+      else
+       _LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    os2*)
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+      _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~echo DATA >> $output_objdir/$libname.def~echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def'
+      _LT_TAGVAR(old_archive_from_new_cmds, $1)='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def'
+      ;;
+
+    osf3*)
+      if test "$GCC" = yes; then
+       _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*'
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+      else
+       _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*'
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+      fi
+      _LT_TAGVAR(archive_cmds_need_lc, $1)='no'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      ;;
+
+    osf4* | osf5*)     # as osf3* with the addition of -msym flag
+      if test "$GCC" = yes; then
+       _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*'
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $pic_flag $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+       _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+      else
+       _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*'
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+       _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~
+       $CC -shared${allow_undefined_flag} ${wl}-input ${wl}$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~$RM $lib.exp'
+
+       # Both c and cxx compiler support -rpath directly
+       _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir'
+      fi
+      _LT_TAGVAR(archive_cmds_need_lc, $1)='no'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      ;;
+
+    solaris*)
+      _LT_TAGVAR(no_undefined_flag, $1)=' -z defs'
+      if test "$GCC" = yes; then
+       wlarc='${wl}'
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+       _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+         $CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+      else
+       case `$CC -V 2>&1` in
+       *"Compilers 5.0"*)
+         wlarc=''
+         _LT_TAGVAR(archive_cmds, $1)='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags'
+         _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+         $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp'
+         ;;
+       *)
+         wlarc='${wl}'
+         _LT_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $compiler_flags'
+         _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+         $CC -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+         ;;
+       esac
+      fi
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      case $host_os in
+      solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+      *)
+       # The compiler driver will combine and reorder linker options,
+       # but understands `-z linker_flag'.  GCC discards it without `$wl',
+       # but is careful enough not to reorder.
+       # Supported since Solaris 2.6 (maybe 2.5.1?)
+       if test "$GCC" = yes; then
+         _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract'
+       else
+         _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract'
+       fi
+       ;;
+      esac
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      ;;
+
+    sunos4*)
+      if test "x$host_vendor" = xsequent; then
+       # Use $CC to link under sequent, because it throws in some extra .o
+       # files that make .init and .fini sections work.
+       _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+       _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags'
+      fi
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    sysv4)
+      case $host_vendor in
+       sni)
+         _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+         _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true???
+       ;;
+       siemens)
+         ## LD is ld it makes a PLAMLIB
+         ## CC just makes a GrossModule.
+         _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags'
+         _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs'
+         _LT_TAGVAR(hardcode_direct, $1)=no
+        ;;
+       motorola)
+         _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+         _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie
+       ;;
+      esac
+      runpath_var='LD_RUN_PATH'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    sysv4.3*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec; then
+       _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+       _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+       runpath_var=LD_RUN_PATH
+       hardcode_runpath_var=yes
+       _LT_TAGVAR(ld_shlibs, $1)=yes
+      fi
+      ;;
+
+    sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*)
+      _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text'
+      _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      runpath_var='LD_RUN_PATH'
+
+      if test "$GCC" = yes; then
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+       _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+       _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+       _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      fi
+      ;;
+
+    sysv5* | sco3.2v5* | sco5v6*)
+      # Note: We can NOT use -z defs as we might desire, because we do not
+      # link with -lc, and that would cause any symbols used from libc to
+      # always be unresolved, which means just about no library would
+      # ever link correctly.  If we're not using GNU ld we use -z text
+      # though, which does catch some bad symbols but isn't as heavy-handed
+      # as -z defs.
+      _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text'
+      _LT_TAGVAR(allow_undefined_flag, $1)='${wl}-z,nodefs'
+      _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R,$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Bexport'
+      runpath_var='LD_RUN_PATH'
+
+      if test "$GCC" = yes; then
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+       _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+       _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+       _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      fi
+      ;;
+
+    uts4*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    *)
+      _LT_TAGVAR(ld_shlibs, $1)=no
+      ;;
+    esac
+
+    if test x$host_vendor = xsni; then
+      case $host in
+      sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+       _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Blargedynsym'
+       ;;
+      esac
+    fi
+  fi
+])
+AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)])
+test "$_LT_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no
+
+_LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld
+
+_LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl
+_LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl
+_LT_DECL([], [extract_expsyms_cmds], [2],
+    [The commands to extract the exported symbol list from a shared archive])
+
+#
+# Do we need to explicitly link libc?
+#
+case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in
+x|xyes)
+  # Assume -lc should be added
+  _LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+
+  if test "$enable_shared" = yes && test "$GCC" = yes; then
+    case $_LT_TAGVAR(archive_cmds, $1) in
+    *'~'*)
+      # FIXME: we may have to deal with multi-command sequences.
+      ;;
+    '$CC '*)
+      # Test whether the compiler implicitly links with -lc since on some
+      # systems, -lgcc has to come before -lc. If gcc already passes -lc
+      # to ld, don't add -lc before -lgcc.
+      AC_CACHE_CHECK([whether -lc should be explicitly linked in],
+       [lt_cv_]_LT_TAGVAR(archive_cmds_need_lc, $1),
+       [$RM conftest*
+       echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+       if AC_TRY_EVAL(ac_compile) 2>conftest.err; then
+         soname=conftest
+         lib=conftest
+         libobjs=conftest.$ac_objext
+         deplibs=
+         wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1)
+         pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1)
+         compiler_flags=-v
+         linker_flags=-v
+         verstring=
+         output_objdir=.
+         libname=conftest
+         lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1)
+         _LT_TAGVAR(allow_undefined_flag, $1)=
+         if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1)
+         then
+           lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+         else
+           lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+         fi
+         _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag
+       else
+         cat conftest.err 1>&5
+       fi
+       $RM conftest*
+       ])
+      _LT_TAGVAR(archive_cmds_need_lc, $1)=$lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)
+      ;;
+    esac
+  fi
+  ;;
+esac
+
+_LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0],
+    [Whether or not to add -lc for building shared libraries])
+_LT_TAGDECL([allow_libtool_libs_with_static_runtimes],
+    [enable_shared_with_static_runtimes], [0],
+    [Whether or not to disallow shared libs when runtime libs are static])
+_LT_TAGDECL([], [export_dynamic_flag_spec], [1],
+    [Compiler flag to allow reflexive dlopens])
+_LT_TAGDECL([], [whole_archive_flag_spec], [1],
+    [Compiler flag to generate shared objects directly from archives])
+_LT_TAGDECL([], [compiler_needs_object], [1],
+    [Whether the compiler copes with passing no objects directly])
+_LT_TAGDECL([], [old_archive_from_new_cmds], [2],
+    [Create an old-style archive from a shared archive])
+_LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2],
+    [Create a temporary old-style archive to link instead of a shared archive])
+_LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive])
+_LT_TAGDECL([], [archive_expsym_cmds], [2])
+_LT_TAGDECL([], [module_cmds], [2],
+    [Commands used to build a loadable module if different from building
+    a shared archive.])
+_LT_TAGDECL([], [module_expsym_cmds], [2])
+_LT_TAGDECL([], [with_gnu_ld], [1],
+    [Whether we are building with GNU ld or not])
+_LT_TAGDECL([], [allow_undefined_flag], [1],
+    [Flag that allows shared libraries with undefined symbols to be built])
+_LT_TAGDECL([], [no_undefined_flag], [1],
+    [Flag that enforces no undefined symbols])
+_LT_TAGDECL([], [hardcode_libdir_flag_spec], [1],
+    [Flag to hardcode $libdir into a binary during linking.
+    This must work even if $libdir does not exist])
+_LT_TAGDECL([], [hardcode_libdir_separator], [1],
+    [Whether we need a single "-rpath" flag with a separated argument])
+_LT_TAGDECL([], [hardcode_direct], [0],
+    [Set to "yes" if using DIR/libNAME${shared_ext} during linking hardcodes
+    DIR into the resulting binary])
+_LT_TAGDECL([], [hardcode_direct_absolute], [0],
+    [Set to "yes" if using DIR/libNAME${shared_ext} during linking hardcodes
+    DIR into the resulting binary and the resulting library dependency is
+    "absolute", i.e impossible to change by setting ${shlibpath_var} if the
+    library is relocated])
+_LT_TAGDECL([], [hardcode_minus_L], [0],
+    [Set to "yes" if using the -LDIR flag during linking hardcodes DIR
+    into the resulting binary])
+_LT_TAGDECL([], [hardcode_shlibpath_var], [0],
+    [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR
+    into the resulting binary])
+_LT_TAGDECL([], [hardcode_automatic], [0],
+    [Set to "yes" if building a shared library automatically hardcodes DIR
+    into the library and all subsequent libraries and executables linked
+    against it])
+_LT_TAGDECL([], [inherit_rpath], [0],
+    [Set to yes if linker adds runtime paths of dependent libraries
+    to runtime path list])
+_LT_TAGDECL([], [link_all_deplibs], [0],
+    [Whether libtool must link a program against all its dependency libraries])
+_LT_TAGDECL([], [always_export_symbols], [0],
+    [Set to "yes" if exported symbols are required])
+_LT_TAGDECL([], [export_symbols_cmds], [2],
+    [The commands to list exported symbols])
+_LT_TAGDECL([], [exclude_expsyms], [1],
+    [Symbols that should not be listed in the preloaded symbols])
+_LT_TAGDECL([], [include_expsyms], [1],
+    [Symbols that must always be exported])
+_LT_TAGDECL([], [prelink_cmds], [2],
+    [Commands necessary for linking programs (against libraries) with templates])
+_LT_TAGDECL([], [postlink_cmds], [2],
+    [Commands necessary for finishing linking programs])
+_LT_TAGDECL([], [file_list_spec], [1],
+    [Specify filename containing input files])
+dnl FIXME: Not yet implemented
+dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1],
+dnl    [Compiler flag to generate thread safe objects])
+])# _LT_LINKER_SHLIBS
+
+
+# _LT_LANG_C_CONFIG([TAG])
+# ------------------------
+# Ensure that the configuration variables for a C compiler are suitably
+# defined.  These variables are subsequently used by _LT_CONFIG to write
+# the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_C_CONFIG],
+[m4_require([_LT_DECL_EGREP])dnl
+lt_save_CC="$CC"
+AC_LANG_PUSH(C)
+
+# Source file extension for C test sources.
+ac_ext=c
+
+# Object file extension for compiled C test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="int some_variable = 0;"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='int main(){return(0);}'
+
+_LT_TAG_COMPILER
+# Save the default compiler, since it gets overwritten when the other
+# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP.
+compiler_DEFAULT=$CC
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+if test -n "$compiler"; then
+  _LT_COMPILER_NO_RTTI($1)
+  _LT_COMPILER_PIC($1)
+  _LT_COMPILER_C_O($1)
+  _LT_COMPILER_FILE_LOCKS($1)
+  _LT_LINKER_SHLIBS($1)
+  _LT_SYS_DYNAMIC_LINKER($1)
+  _LT_LINKER_HARDCODE_LIBPATH($1)
+  LT_SYS_DLOPEN_SELF
+  _LT_CMD_STRIPLIB
+
+  # Report which library types will actually be built
+  AC_MSG_CHECKING([if libtool supports shared libraries])
+  AC_MSG_RESULT([$can_build_shared])
+
+  AC_MSG_CHECKING([whether to build shared libraries])
+  test "$can_build_shared" = "no" && enable_shared=no
+
+  # On AIX, shared libraries and static libraries use the same namespace, and
+  # are all built from PIC.
+  case $host_os in
+  aix3*)
+    test "$enable_shared" = yes && enable_static=no
+    if test -n "$RANLIB"; then
+      archive_cmds="$archive_cmds~\$RANLIB \$lib"
+      postinstall_cmds='$RANLIB $lib'
+    fi
+    ;;
+
+  aix[[4-9]]*)
+    if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+      test "$enable_shared" = yes && enable_static=no
+    fi
+    ;;
+  esac
+  AC_MSG_RESULT([$enable_shared])
+
+  AC_MSG_CHECKING([whether to build static libraries])
+  # Make sure either enable_shared or enable_static is yes.
+  test "$enable_shared" = yes || enable_static=yes
+  AC_MSG_RESULT([$enable_static])
+
+  _LT_CONFIG($1)
+fi
+AC_LANG_POP
+CC="$lt_save_CC"
+])# _LT_LANG_C_CONFIG
+
+
+# _LT_LANG_CXX_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for a C++ compiler are suitably
+# defined.  These variables are subsequently used by _LT_CONFIG to write
+# the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_CXX_CONFIG],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_PATH_MANIFEST_TOOL])dnl
+if test -n "$CXX" && ( test "X$CXX" != "Xno" &&
+    ( (test "X$CXX" = "Xg++" && `g++ -v >/dev/null 2>&1` ) ||
+    (test "X$CXX" != "Xg++"))) ; then
+  AC_PROG_CXXCPP
+else
+  _lt_caught_CXX_error=yes
+fi
+
+AC_LANG_PUSH(C++)
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+_LT_TAGVAR(allow_undefined_flag, $1)=
+_LT_TAGVAR(always_export_symbols, $1)=no
+_LT_TAGVAR(archive_expsym_cmds, $1)=
+_LT_TAGVAR(compiler_needs_object, $1)=no
+_LT_TAGVAR(export_dynamic_flag_spec, $1)=
+_LT_TAGVAR(hardcode_direct, $1)=no
+_LT_TAGVAR(hardcode_direct_absolute, $1)=no
+_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+_LT_TAGVAR(hardcode_libdir_separator, $1)=
+_LT_TAGVAR(hardcode_minus_L, $1)=no
+_LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported
+_LT_TAGVAR(hardcode_automatic, $1)=no
+_LT_TAGVAR(inherit_rpath, $1)=no
+_LT_TAGVAR(module_cmds, $1)=
+_LT_TAGVAR(module_expsym_cmds, $1)=
+_LT_TAGVAR(link_all_deplibs, $1)=unknown
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+_LT_TAGVAR(no_undefined_flag, $1)=
+_LT_TAGVAR(whole_archive_flag_spec, $1)=
+_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+
+# Source file extension for C++ test sources.
+ac_ext=cpp
+
+# Object file extension for compiled C++ test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# No sense in running all these tests if we already determined that
+# the CXX compiler isn't working.  Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test "$_lt_caught_CXX_error" != yes; then
+  # Code to be used in simple compile tests
+  lt_simple_compile_test_code="int some_variable = 0;"
+
+  # Code to be used in simple link tests
+  lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }'
+
+  # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+  _LT_TAG_COMPILER
+
+  # save warnings/boilerplate of simple test code
+  _LT_COMPILER_BOILERPLATE
+  _LT_LINKER_BOILERPLATE
+
+  # Allow CC to be a program name with arguments.
+  lt_save_CC=$CC
+  lt_save_CFLAGS=$CFLAGS
+  lt_save_LD=$LD
+  lt_save_GCC=$GCC
+  GCC=$GXX
+  lt_save_with_gnu_ld=$with_gnu_ld
+  lt_save_path_LD=$lt_cv_path_LD
+  if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then
+    lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx
+  else
+    $as_unset lt_cv_prog_gnu_ld
+  fi
+  if test -n "${lt_cv_path_LDCXX+set}"; then
+    lt_cv_path_LD=$lt_cv_path_LDCXX
+  else
+    $as_unset lt_cv_path_LD
+  fi
+  test -z "${LDCXX+set}" || LD=$LDCXX
+  CC=${CXX-"c++"}
+  CFLAGS=$CXXFLAGS
+  compiler=$CC
+  _LT_TAGVAR(compiler, $1)=$CC
+  _LT_CC_BASENAME([$compiler])
+
+  if test -n "$compiler"; then
+    # We don't want -fno-exception when compiling C++ code, so set the
+    # no_builtin_flag separately
+    if test "$GXX" = yes; then
+      _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin'
+    else
+      _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=
+    fi
+
+    if test "$GXX" = yes; then
+      # Set up default GNU C++ configuration
+
+      LT_PATH_LD
+
+      # Check if GNU C++ uses GNU ld as the underlying linker, since the
+      # archiving commands below assume that GNU ld is being used.
+      if test "$with_gnu_ld" = yes; then
+        _LT_TAGVAR(archive_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib'
+        _LT_TAGVAR(archive_expsym_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+        _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+
+        # If archive_cmds runs LD, not CC, wlarc should be empty
+        # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to
+        #     investigate it a little bit more. (MM)
+        wlarc='${wl}'
+
+        # ancient GNU ld didn't support --whole-archive et. al.
+        if eval "`$CC -print-prog-name=ld` --help 2>&1" |
+         $GREP 'no-whole-archive' > /dev/null; then
+          _LT_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+        else
+          _LT_TAGVAR(whole_archive_flag_spec, $1)=
+        fi
+      else
+        with_gnu_ld=no
+        wlarc=
+
+        # A generic and very simple default shared library creation
+        # command for GNU C++ for the case where it uses the native
+        # linker, instead of GNU ld.  If possible, this setting should
+        # overridden to take advantage of the native linker features on
+        # the platform it is being used on.
+        _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib'
+      fi
+
+      # Commands to make compiler produce verbose output that lists
+      # what "hidden" libraries, object files and flags are used when
+      # linking a shared library.
+      output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+
+    else
+      GXX=no
+      with_gnu_ld=no
+      wlarc=
+    fi
+
+    # PORTME: fill in a description of your system's C++ link characteristics
+    AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries])
+    _LT_TAGVAR(ld_shlibs, $1)=yes
+    case $host_os in
+      aix3*)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+      aix[[4-9]]*)
+        if test "$host_cpu" = ia64; then
+          # On IA64, the linker does run time linking by default, so we don't
+          # have to do anything special.
+          aix_use_runtimelinking=no
+          exp_sym_flag='-Bexport'
+          no_entry_flag=""
+        else
+          aix_use_runtimelinking=no
+
+          # Test if we are trying to use run time linking or normal
+          # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+          # need to do runtime linking.
+          case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*)
+           for ld_flag in $LDFLAGS; do
+             case $ld_flag in
+             *-brtl*)
+               aix_use_runtimelinking=yes
+               break
+               ;;
+             esac
+           done
+           ;;
+          esac
+
+          exp_sym_flag='-bexport'
+          no_entry_flag='-bnoentry'
+        fi
+
+        # When large executables or shared objects are built, AIX ld can
+        # have problems creating the table of contents.  If linking a library
+        # or program results in "error TOC overflow" add -mminimal-toc to
+        # CXXFLAGS/CFLAGS for g++/gcc.  In the cases where that is not
+        # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+        _LT_TAGVAR(archive_cmds, $1)=''
+        _LT_TAGVAR(hardcode_direct, $1)=yes
+        _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+        _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+        _LT_TAGVAR(link_all_deplibs, $1)=yes
+        _LT_TAGVAR(file_list_spec, $1)='${wl}-f,'
+
+        if test "$GXX" = yes; then
+          case $host_os in aix4.[[012]]|aix4.[[012]].*)
+          # We only want to do this on AIX 4.2 and lower, the check
+          # below for broken collect2 doesn't work under 4.3+
+         collect2name=`${CC} -print-prog-name=collect2`
+         if test -f "$collect2name" &&
+            strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+         then
+           # We have reworked collect2
+           :
+         else
+           # We have old collect2
+           _LT_TAGVAR(hardcode_direct, $1)=unsupported
+           # It fails to find uninstalled libraries when the uninstalled
+           # path is not listed in the libpath.  Setting hardcode_minus_L
+           # to unsupported forces relinking
+           _LT_TAGVAR(hardcode_minus_L, $1)=yes
+           _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+           _LT_TAGVAR(hardcode_libdir_separator, $1)=
+         fi
+          esac
+          shared_flag='-shared'
+         if test "$aix_use_runtimelinking" = yes; then
+           shared_flag="$shared_flag "'${wl}-G'
+         fi
+        else
+          # not using gcc
+          if test "$host_cpu" = ia64; then
+         # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+         # chokes on -Wl,-G. The following line is correct:
+         shared_flag='-G'
+          else
+           if test "$aix_use_runtimelinking" = yes; then
+             shared_flag='${wl}-G'
+           else
+             shared_flag='${wl}-bM:SRE'
+           fi
+          fi
+        fi
+
+        _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-bexpall'
+        # It seems that -bexpall does not export symbols beginning with
+        # underscore (_), so it is better to generate a list of symbols to
+       # export.
+        _LT_TAGVAR(always_export_symbols, $1)=yes
+        if test "$aix_use_runtimelinking" = yes; then
+          # Warning - without using the other runtime loading flags (-brtl),
+          # -berok will link without error, but may produce a broken library.
+          _LT_TAGVAR(allow_undefined_flag, $1)='-berok'
+          # Determine the default libpath from the value encoded in an empty
+          # executable.
+          _LT_SYS_MODULE_PATH_AIX([$1])
+          _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath"
+
+          _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+        else
+          if test "$host_cpu" = ia64; then
+           _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib'
+           _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs"
+           _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols"
+          else
+           # Determine the default libpath from the value encoded in an
+           # empty executable.
+           _LT_SYS_MODULE_PATH_AIX([$1])
+           _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath"
+           # Warning - without using the other run time loading flags,
+           # -berok will link without error, but may produce a broken library.
+           _LT_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok'
+           _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok'
+           if test "$with_gnu_ld" = yes; then
+             # We only use this code for GNU lds that support --whole-archive.
+             _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+           else
+             # Exported symbols can be pulled into shared objects from archives
+             _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience'
+           fi
+           _LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+           # This is similar to how AIX traditionally builds its shared
+           # libraries.
+           _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname'
+          fi
+        fi
+        ;;
+
+      beos*)
+       if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+         _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+         # Joseph Beckenbach <jrb3@best.com> says some releases of gcc
+         # support --undefined.  This deserves some investigation.  FIXME
+         _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+       else
+         _LT_TAGVAR(ld_shlibs, $1)=no
+       fi
+       ;;
+
+      chorus*)
+        case $cc_basename in
+          *)
+         # FIXME: insert proper C++ library support
+         _LT_TAGVAR(ld_shlibs, $1)=no
+         ;;
+        esac
+        ;;
+
+      cygwin* | mingw* | pw32* | cegcc*)
+       case $GXX,$cc_basename in
+       ,cl* | no,cl*)
+         # Native MSVC
+         # hardcode_libdir_flag_spec is actually meaningless, as there is
+         # no search path for DLLs.
+         _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' '
+         _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+         _LT_TAGVAR(always_export_symbols, $1)=yes
+         _LT_TAGVAR(file_list_spec, $1)='@'
+         # Tell ltmain to make .lib files, not .a files.
+         libext=lib
+         # Tell ltmain to make .dll files, not .so files.
+         shrext_cmds=".dll"
+         # FIXME: Setting linknames here is a bad hack.
+         _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames='
+         _LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+             $SED -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp;
+           else
+             $SED -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp;
+           fi~
+           $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+           linknames='
+         # The linker will not automatically build a static lib if we build a DLL.
+         # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true'
+         _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+         # Don't use ranlib
+         _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib'
+         _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~
+           lt_tool_outputfile="@TOOL_OUTPUT@"~
+           case $lt_outputfile in
+             *.exe|*.EXE) ;;
+             *)
+               lt_outputfile="$lt_outputfile.exe"
+               lt_tool_outputfile="$lt_tool_outputfile.exe"
+               ;;
+           esac~
+           func_to_tool_file "$lt_outputfile"~
+           if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then
+             $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+             $RM "$lt_outputfile.manifest";
+           fi'
+         ;;
+       *)
+         # g++
+         # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless,
+         # as there is no search path for DLLs.
+         _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+         _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-all-symbols'
+         _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+         _LT_TAGVAR(always_export_symbols, $1)=no
+         _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+
+         if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+           _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+           # If the export-symbols file already is a .def file (1st line
+           # is EXPORTS), use it as is; otherwise, prepend...
+           _LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+             cp $export_symbols $output_objdir/$soname.def;
+           else
+             echo EXPORTS > $output_objdir/$soname.def;
+             cat $export_symbols >> $output_objdir/$soname.def;
+           fi~
+           $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+         else
+           _LT_TAGVAR(ld_shlibs, $1)=no
+         fi
+         ;;
+       esac
+       ;;
+      darwin* | rhapsody*)
+        _LT_DARWIN_LINKER_FEATURES($1)
+       ;;
+
+      dgux*)
+        case $cc_basename in
+          ec++*)
+           # FIXME: insert proper C++ library support
+           _LT_TAGVAR(ld_shlibs, $1)=no
+           ;;
+          ghcx*)
+           # Green Hills C++ Compiler
+           # FIXME: insert proper C++ library support
+           _LT_TAGVAR(ld_shlibs, $1)=no
+           ;;
+          *)
+           # FIXME: insert proper C++ library support
+           _LT_TAGVAR(ld_shlibs, $1)=no
+           ;;
+        esac
+        ;;
+
+      freebsd2.*)
+        # C++ shared libraries reported to be fairly broken before
+       # switch to ELF
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+
+      freebsd-elf*)
+        _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+        ;;
+
+      freebsd* | dragonfly*)
+        # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF
+        # conventions
+        _LT_TAGVAR(ld_shlibs, $1)=yes
+        ;;
+
+      gnu*)
+        ;;
+
+      haiku*)
+        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+        _LT_TAGVAR(link_all_deplibs, $1)=yes
+        ;;
+
+      hpux9*)
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+        _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+        _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+        _LT_TAGVAR(hardcode_direct, $1)=yes
+        _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH,
+                                            # but as the default
+                                            # location of the library.
+
+        case $cc_basename in
+          CC*)
+            # FIXME: insert proper C++ library support
+            _LT_TAGVAR(ld_shlibs, $1)=no
+            ;;
+          aCC*)
+            _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+            # Commands to make compiler produce verbose output that lists
+            # what "hidden" libraries, object files and flags are used when
+            # linking a shared library.
+            #
+            # There doesn't appear to be a way to prevent this compiler from
+            # explicitly linking system object files so we need to strip them
+            # from the output so that they don't get included in the library
+            # dependencies.
+            output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+            ;;
+          *)
+            if test "$GXX" = yes; then
+              _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+            else
+              # FIXME: insert proper C++ library support
+              _LT_TAGVAR(ld_shlibs, $1)=no
+            fi
+            ;;
+        esac
+        ;;
+
+      hpux10*|hpux11*)
+        if test $with_gnu_ld = no; then
+         _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+         _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+          case $host_cpu in
+            hppa*64*|ia64*)
+              ;;
+            *)
+             _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+              ;;
+          esac
+        fi
+        case $host_cpu in
+          hppa*64*|ia64*)
+            _LT_TAGVAR(hardcode_direct, $1)=no
+            _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+            ;;
+          *)
+            _LT_TAGVAR(hardcode_direct, $1)=yes
+            _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+            _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH,
+                                                # but as the default
+                                                # location of the library.
+            ;;
+        esac
+
+        case $cc_basename in
+          CC*)
+           # FIXME: insert proper C++ library support
+           _LT_TAGVAR(ld_shlibs, $1)=no
+           ;;
+          aCC*)
+           case $host_cpu in
+             hppa*64*)
+               _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+               ;;
+             ia64*)
+               _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+               ;;
+             *)
+               _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+               ;;
+           esac
+           # Commands to make compiler produce verbose output that lists
+           # what "hidden" libraries, object files and flags are used when
+           # linking a shared library.
+           #
+           # There doesn't appear to be a way to prevent this compiler from
+           # explicitly linking system object files so we need to strip them
+           # from the output so that they don't get included in the library
+           # dependencies.
+           output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+           ;;
+          *)
+           if test "$GXX" = yes; then
+             if test $with_gnu_ld = no; then
+               case $host_cpu in
+                 hppa*64*)
+                   _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+                   ;;
+                 ia64*)
+                   _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+                   ;;
+                 *)
+                   _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+                   ;;
+               esac
+             fi
+           else
+             # FIXME: insert proper C++ library support
+             _LT_TAGVAR(ld_shlibs, $1)=no
+           fi
+           ;;
+        esac
+        ;;
+
+      interix[[3-9]]*)
+       _LT_TAGVAR(hardcode_direct, $1)=no
+       _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+       _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+       _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+       # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+       # Instead, shared libraries are loaded at an image base (0x10000000 by
+       # default) and relocated if they conflict, which is a slow very memory
+       # consuming and fragmenting process.  To avoid this, we pick a random,
+       # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+       # time.  Moving up from 0x10000000 also allows more sbrk(2) space.
+       _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+       _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+       ;;
+      irix5* | irix6*)
+        case $cc_basename in
+          CC*)
+           # SGI C++
+           _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+
+           # Archives containing C++ object files must be created using
+           # "CC -ar", where "CC" is the IRIX C++ compiler.  This is
+           # necessary to make sure instantiated templates are included
+           # in the archive.
+           _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs'
+           ;;
+          *)
+           if test "$GXX" = yes; then
+             if test "$with_gnu_ld" = no; then
+               _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+             else
+               _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` -o $lib'
+             fi
+           fi
+           _LT_TAGVAR(link_all_deplibs, $1)=yes
+           ;;
+        esac
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+        _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+        _LT_TAGVAR(inherit_rpath, $1)=yes
+        ;;
+
+      linux* | k*bsd*-gnu | kopensolaris*-gnu)
+        case $cc_basename in
+          KCC*)
+           # Kuck and Associates, Inc. (KAI) C++ Compiler
+
+           # KCC will only create a shared library if the output file
+           # ends with ".so" (or ".sl" for HP-UX), so rename the library
+           # to its proper name (with version) after linking.
+           _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib'
+           _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib ${wl}-retain-symbols-file,$export_symbols; mv \$templib $lib'
+           # Commands to make compiler produce verbose output that lists
+           # what "hidden" libraries, object files and flags are used when
+           # linking a shared library.
+           #
+           # There doesn't appear to be a way to prevent this compiler from
+           # explicitly linking system object files so we need to strip them
+           # from the output so that they don't get included in the library
+           # dependencies.
+           output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+
+           _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+           _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+
+           # Archives containing C++ object files must be created using
+           # "CC -Bstatic", where "CC" is the KAI C++ compiler.
+           _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs'
+           ;;
+         icpc* | ecpc* )
+           # Intel C++
+           with_gnu_ld=yes
+           # version 8.0 and above of icpc choke on multiply defined symbols
+           # if we add $predep_objects and $postdep_objects, however 7.1 and
+           # earlier do not add the objects themselves.
+           case `$CC -V 2>&1` in
+             *"Version 7."*)
+               _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib'
+               _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+               ;;
+             *)  # Version 8.0 or newer
+               tmp_idyn=
+               case $host_cpu in
+                 ia64*) tmp_idyn=' -i_dynamic';;
+               esac
+               _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+               _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+               ;;
+           esac
+           _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+           _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+           _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+           _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+           ;;
+          pgCC* | pgcpp*)
+            # Portland Group C++ compiler
+           case `$CC -V` in
+           *pgCC\ [[1-5]].* | *pgcpp\ [[1-5]].*)
+             _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~
+               rm -rf $tpldir~
+               $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~
+               compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"'
+             _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~
+               rm -rf $tpldir~
+               $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~
+               $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~
+               $RANLIB $oldlib'
+             _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~
+               rm -rf $tpldir~
+               $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~
+               $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib'
+             _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~
+               rm -rf $tpldir~
+               $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~
+               $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib'
+             ;;
+           *) # Version 6 and above use weak symbols
+             _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib'
+             _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib'
+             ;;
+           esac
+
+           _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath ${wl}$libdir'
+           _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+           _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+            ;;
+         cxx*)
+           # Compaq C++
+           _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib'
+           _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname  -o $lib ${wl}-retain-symbols-file $wl$export_symbols'
+
+           runpath_var=LD_RUN_PATH
+           _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir'
+           _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+           # Commands to make compiler produce verbose output that lists
+           # what "hidden" libraries, object files and flags are used when
+           # linking a shared library.
+           #
+           # There doesn't appear to be a way to prevent this compiler from
+           # explicitly linking system object files so we need to strip them
+           # from the output so that they don't get included in the library
+           # dependencies.
+           output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed'
+           ;;
+         xl* | mpixl* | bgxl*)
+           # IBM XL 8.0 on PPC, with GNU ld
+           _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+           _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+           _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+           if test "x$supports_anon_versioning" = xyes; then
+             _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~
+               cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+               echo "local: *; };" >> $output_objdir/$libname.ver~
+               $CC -qmkshrobj $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib'
+           fi
+           ;;
+         *)
+           case `$CC -V 2>&1 | sed 5q` in
+           *Sun\ C*)
+             # Sun C++ 5.9
+             _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs'
+             _LT_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+             _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file ${wl}$export_symbols'
+             _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+             _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+             _LT_TAGVAR(compiler_needs_object, $1)=yes
+
+             # Not sure whether something based on
+             # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1
+             # would be better.
+             output_verbose_link_cmd='func_echo_all'
+
+             # Archives containing C++ object files must be created using
+             # "CC -xar", where "CC" is the Sun C++ compiler.  This is
+             # necessary to make sure instantiated templates are included
+             # in the archive.
+             _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs'
+             ;;
+           esac
+           ;;
+       esac
+       ;;
+
+      lynxos*)
+        # FIXME: insert proper C++ library support
+       _LT_TAGVAR(ld_shlibs, $1)=no
+       ;;
+
+      m88k*)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+       ;;
+
+      mvs*)
+        case $cc_basename in
+          cxx*)
+           # FIXME: insert proper C++ library support
+           _LT_TAGVAR(ld_shlibs, $1)=no
+           ;;
+         *)
+           # FIXME: insert proper C++ library support
+           _LT_TAGVAR(ld_shlibs, $1)=no
+           ;;
+       esac
+       ;;
+
+      netbsd*)
+        if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+         _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable  -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags'
+         wlarc=
+         _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+         _LT_TAGVAR(hardcode_direct, $1)=yes
+         _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+       fi
+       # Workaround some broken pre-1.5 toolchains
+       output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"'
+       ;;
+
+      *nto* | *qnx*)
+        _LT_TAGVAR(ld_shlibs, $1)=yes
+       ;;
+
+      openbsd2*)
+        # C++ shared libraries are fairly broken
+       _LT_TAGVAR(ld_shlibs, $1)=no
+       ;;
+
+      openbsd*)
+       if test -f /usr/libexec/ld.so; then
+         _LT_TAGVAR(hardcode_direct, $1)=yes
+         _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+         _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+         _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib'
+         _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+         if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+           _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file,$export_symbols -o $lib'
+           _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+           _LT_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+         fi
+         output_verbose_link_cmd=func_echo_all
+       else
+         _LT_TAGVAR(ld_shlibs, $1)=no
+       fi
+       ;;
+
+      osf3* | osf4* | osf5*)
+        case $cc_basename in
+          KCC*)
+           # Kuck and Associates, Inc. (KAI) C++ Compiler
+
+           # KCC will only create a shared library if the output file
+           # ends with ".so" (or ".sl" for HP-UX), so rename the library
+           # to its proper name (with version) after linking.
+           _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib'
+
+           _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+           _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+           # Archives containing C++ object files must be created using
+           # the KAI C++ compiler.
+           case $host in
+             osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;;
+             *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;;
+           esac
+           ;;
+          RCC*)
+           # Rational C++ 2.4.1
+           # FIXME: insert proper C++ library support
+           _LT_TAGVAR(ld_shlibs, $1)=no
+           ;;
+          cxx*)
+           case $host in
+             osf3*)
+               _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*'
+               _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $soname `test -n "$verstring" && func_echo_all "${wl}-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+               _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+               ;;
+             *)
+               _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*'
+               _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+               _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~
+                 echo "-hidden">> $lib.exp~
+                 $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname ${wl}-input ${wl}$lib.exp  `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~
+                 $RM $lib.exp'
+               _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir'
+               ;;
+           esac
+
+           _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+           # Commands to make compiler produce verbose output that lists
+           # what "hidden" libraries, object files and flags are used when
+           # linking a shared library.
+           #
+           # There doesn't appear to be a way to prevent this compiler from
+           # explicitly linking system object files so we need to strip them
+           # from the output so that they don't get included in the library
+           # dependencies.
+           output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+           ;;
+         *)
+           if test "$GXX" = yes && test "$with_gnu_ld" = no; then
+             _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*'
+             case $host in
+               osf3*)
+                 _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+                 ;;
+               *)
+                 _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+                 ;;
+             esac
+
+             _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+             _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+             # Commands to make compiler produce verbose output that lists
+             # what "hidden" libraries, object files and flags are used when
+             # linking a shared library.
+             output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+
+           else
+             # FIXME: insert proper C++ library support
+             _LT_TAGVAR(ld_shlibs, $1)=no
+           fi
+           ;;
+        esac
+        ;;
+
+      psos*)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+
+      sunos4*)
+        case $cc_basename in
+          CC*)
+           # Sun C++ 4.x
+           # FIXME: insert proper C++ library support
+           _LT_TAGVAR(ld_shlibs, $1)=no
+           ;;
+          lcc*)
+           # Lucid
+           # FIXME: insert proper C++ library support
+           _LT_TAGVAR(ld_shlibs, $1)=no
+           ;;
+          *)
+           # FIXME: insert proper C++ library support
+           _LT_TAGVAR(ld_shlibs, $1)=no
+           ;;
+        esac
+        ;;
+
+      solaris*)
+        case $cc_basename in
+          CC* | sunCC*)
+           # Sun C++ 4.2, 5.x and Centerline C++
+            _LT_TAGVAR(archive_cmds_need_lc,$1)=yes
+           _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs'
+           _LT_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag}  -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+           _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+             $CC -G${allow_undefined_flag} ${wl}-M ${wl}$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+           _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+           _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+           case $host_os in
+             solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+             *)
+               # The compiler driver will combine and reorder linker options,
+               # but understands `-z linker_flag'.
+               # Supported since Solaris 2.6 (maybe 2.5.1?)
+               _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract'
+               ;;
+           esac
+           _LT_TAGVAR(link_all_deplibs, $1)=yes
+
+           output_verbose_link_cmd='func_echo_all'
+
+           # Archives containing C++ object files must be created using
+           # "CC -xar", where "CC" is the Sun C++ compiler.  This is
+           # necessary to make sure instantiated templates are included
+           # in the archive.
+           _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs'
+           ;;
+          gcx*)
+           # Green Hills C++ Compiler
+           _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib'
+
+           # The C++ compiler must be used to create the archive.
+           _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs'
+           ;;
+          *)
+           # GNU C++ compiler with Solaris linker
+           if test "$GXX" = yes && test "$with_gnu_ld" = no; then
+             _LT_TAGVAR(no_undefined_flag, $1)=' ${wl}-z ${wl}defs'
+             if $CC --version | $GREP -v '^2\.7' > /dev/null; then
+               _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib'
+               _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+                 $CC -shared $pic_flag -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+               # Commands to make compiler produce verbose output that lists
+               # what "hidden" libraries, object files and flags are used when
+               # linking a shared library.
+               output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+             else
+               # g++ 2.7 appears to require `-G' NOT `-shared' on this
+               # platform.
+               _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib'
+               _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+                 $CC -G -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+               # Commands to make compiler produce verbose output that lists
+               # what "hidden" libraries, object files and flags are used when
+               # linking a shared library.
+               output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+             fi
+
+             _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $wl$libdir'
+             case $host_os in
+               solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+               *)
+                 _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract'
+                 ;;
+             esac
+           fi
+           ;;
+        esac
+        ;;
+
+    sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*)
+      _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text'
+      _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      runpath_var='LD_RUN_PATH'
+
+      case $cc_basename in
+        CC*)
+         _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+         _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+         ;;
+       *)
+         _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+         _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+         ;;
+      esac
+      ;;
+
+      sysv5* | sco3.2v5* | sco5v6*)
+       # Note: We can NOT use -z defs as we might desire, because we do not
+       # link with -lc, and that would cause any symbols used from libc to
+       # always be unresolved, which means just about no library would
+       # ever link correctly.  If we're not using GNU ld we use -z text
+       # though, which does catch some bad symbols but isn't as heavy-handed
+       # as -z defs.
+       _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text'
+       _LT_TAGVAR(allow_undefined_flag, $1)='${wl}-z,nodefs'
+       _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+       _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+       _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R,$libdir'
+       _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+       _LT_TAGVAR(link_all_deplibs, $1)=yes
+       _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Bexport'
+       runpath_var='LD_RUN_PATH'
+
+       case $cc_basename in
+          CC*)
+           _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+           _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+           _LT_TAGVAR(old_archive_cmds, $1)='$CC -Tprelink_objects $oldobjs~
+             '"$_LT_TAGVAR(old_archive_cmds, $1)"
+           _LT_TAGVAR(reload_cmds, $1)='$CC -Tprelink_objects $reload_objs~
+             '"$_LT_TAGVAR(reload_cmds, $1)"
+           ;;
+         *)
+           _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+           _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+           ;;
+       esac
+      ;;
+
+      tandem*)
+        case $cc_basename in
+          NCC*)
+           # NonStop-UX NCC 3.20
+           # FIXME: insert proper C++ library support
+           _LT_TAGVAR(ld_shlibs, $1)=no
+           ;;
+          *)
+           # FIXME: insert proper C++ library support
+           _LT_TAGVAR(ld_shlibs, $1)=no
+           ;;
+        esac
+        ;;
+
+      vxworks*)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+
+      *)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+    esac
+
+    AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)])
+    test "$_LT_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no
+
+    _LT_TAGVAR(GCC, $1)="$GXX"
+    _LT_TAGVAR(LD, $1)="$LD"
+
+    ## CAVEAT EMPTOR:
+    ## There is no encapsulation within the following macros, do not change
+    ## the running order or otherwise move them around unless you know exactly
+    ## what you are doing...
+    _LT_SYS_HIDDEN_LIBDEPS($1)
+    _LT_COMPILER_PIC($1)
+    _LT_COMPILER_C_O($1)
+    _LT_COMPILER_FILE_LOCKS($1)
+    _LT_LINKER_SHLIBS($1)
+    _LT_SYS_DYNAMIC_LINKER($1)
+    _LT_LINKER_HARDCODE_LIBPATH($1)
+
+    _LT_CONFIG($1)
+  fi # test -n "$compiler"
+
+  CC=$lt_save_CC
+  CFLAGS=$lt_save_CFLAGS
+  LDCXX=$LD
+  LD=$lt_save_LD
+  GCC=$lt_save_GCC
+  with_gnu_ld=$lt_save_with_gnu_ld
+  lt_cv_path_LDCXX=$lt_cv_path_LD
+  lt_cv_path_LD=$lt_save_path_LD
+  lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld
+  lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld
+fi # test "$_lt_caught_CXX_error" != yes
+
+AC_LANG_POP
+])# _LT_LANG_CXX_CONFIG
+
+
+# _LT_FUNC_STRIPNAME_CNF
+# ----------------------
+# func_stripname_cnf prefix suffix name
+# strip PREFIX and SUFFIX off of NAME.
+# PREFIX and SUFFIX must not contain globbing or regex special
+# characters, hashes, percent signs, but SUFFIX may contain a leading
+# dot (in which case that matches only a dot).
+#
+# This function is identical to the (non-XSI) version of func_stripname,
+# except this one can be used by m4 code that may be executed by configure,
+# rather than the libtool script.
+m4_defun([_LT_FUNC_STRIPNAME_CNF],[dnl
+AC_REQUIRE([_LT_DECL_SED])
+AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])
+func_stripname_cnf ()
+{
+  case ${2} in
+  .*) func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%\\\\${2}\$%%"`;;
+  *)  func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%${2}\$%%"`;;
+  esac
+} # func_stripname_cnf
+])# _LT_FUNC_STRIPNAME_CNF
+
+# _LT_SYS_HIDDEN_LIBDEPS([TAGNAME])
+# ---------------------------------
+# Figure out "hidden" library dependencies from verbose
+# compiler output when linking a shared library.
+# Parse the compiler output and extract the necessary
+# objects, libraries and library flags.
+m4_defun([_LT_SYS_HIDDEN_LIBDEPS],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+AC_REQUIRE([_LT_FUNC_STRIPNAME_CNF])dnl
+# Dependencies to place before and after the object being linked:
+_LT_TAGVAR(predep_objects, $1)=
+_LT_TAGVAR(postdep_objects, $1)=
+_LT_TAGVAR(predeps, $1)=
+_LT_TAGVAR(postdeps, $1)=
+_LT_TAGVAR(compiler_lib_search_path, $1)=
+
+dnl we can't use the lt_simple_compile_test_code here,
+dnl because it contains code intended for an executable,
+dnl not a library.  It's possible we should let each
+dnl tag define a new lt_????_link_test_code variable,
+dnl but it's only used here...
+m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF
+int a;
+void foo (void) { a = 0; }
+_LT_EOF
+], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF
+class Foo
+{
+public:
+  Foo (void) { a = 0; }
+private:
+  int a;
+};
+_LT_EOF
+], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF
+      subroutine foo
+      implicit none
+      integer*4 a
+      a=0
+      return
+      end
+_LT_EOF
+], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF
+      subroutine foo
+      implicit none
+      integer a
+      a=0
+      return
+      end
+_LT_EOF
+], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF
+public class foo {
+  private int a;
+  public void bar (void) {
+    a = 0;
+  }
+};
+_LT_EOF
+], [$1], [GO], [cat > conftest.$ac_ext <<_LT_EOF
+package foo
+func foo() {
+}
+_LT_EOF
+])
+
+_lt_libdeps_save_CFLAGS=$CFLAGS
+case "$CC $CFLAGS " in #(
+*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;;
+*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;;
+*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;;
+esac
+
+dnl Parse the compiler output and extract the necessary
+dnl objects, libraries and library flags.
+if AC_TRY_EVAL(ac_compile); then
+  # Parse the compiler output and extract the necessary
+  # objects, libraries and library flags.
+
+  # Sentinel used to keep track of whether or not we are before
+  # the conftest object file.
+  pre_test_object_deps_done=no
+
+  for p in `eval "$output_verbose_link_cmd"`; do
+    case ${prev}${p} in
+
+    -L* | -R* | -l*)
+       # Some compilers place space between "-{L,R}" and the path.
+       # Remove the space.
+       if test $p = "-L" ||
+          test $p = "-R"; then
+        prev=$p
+        continue
+       fi
+
+       # Expand the sysroot to ease extracting the directories later.
+       if test -z "$prev"; then
+         case $p in
+         -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;;
+         -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;;
+         -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;;
+         esac
+       fi
+       case $p in
+       =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;;
+       esac
+       if test "$pre_test_object_deps_done" = no; then
+        case ${prev} in
+        -L | -R)
+          # Internal compiler library paths should come after those
+          # provided the user.  The postdeps already come after the
+          # user supplied libs so there is no need to process them.
+          if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then
+            _LT_TAGVAR(compiler_lib_search_path, $1)="${prev}${p}"
+          else
+            _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} ${prev}${p}"
+          fi
+          ;;
+        # The "-l" case would never come before the object being
+        # linked, so don't bother handling this case.
+        esac
+       else
+        if test -z "$_LT_TAGVAR(postdeps, $1)"; then
+          _LT_TAGVAR(postdeps, $1)="${prev}${p}"
+        else
+          _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} ${prev}${p}"
+        fi
+       fi
+       prev=
+       ;;
+
+    *.lto.$objext) ;; # Ignore GCC LTO objects
+    *.$objext)
+       # This assumes that the test object file only shows up
+       # once in the compiler output.
+       if test "$p" = "conftest.$objext"; then
+        pre_test_object_deps_done=yes
+        continue
+       fi
+
+       if test "$pre_test_object_deps_done" = no; then
+        if test -z "$_LT_TAGVAR(predep_objects, $1)"; then
+          _LT_TAGVAR(predep_objects, $1)="$p"
+        else
+          _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p"
+        fi
+       else
+        if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then
+          _LT_TAGVAR(postdep_objects, $1)="$p"
+        else
+          _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p"
+        fi
+       fi
+       ;;
+
+    *) ;; # Ignore the rest.
+
+    esac
+  done
+
+  # Clean up.
+  rm -f a.out a.exe
+else
+  echo "libtool.m4: error: problem compiling $1 test program"
+fi
+
+$RM -f confest.$objext
+CFLAGS=$_lt_libdeps_save_CFLAGS
+
+# PORTME: override above test on systems where it is broken
+m4_if([$1], [CXX],
+[case $host_os in
+interix[[3-9]]*)
+  # Interix 3.5 installs completely hosed .la files for C++, so rather than
+  # hack all around it, let's just trust "g++" to DTRT.
+  _LT_TAGVAR(predep_objects,$1)=
+  _LT_TAGVAR(postdep_objects,$1)=
+  _LT_TAGVAR(postdeps,$1)=
+  ;;
+
+linux*)
+  case `$CC -V 2>&1 | sed 5q` in
+  *Sun\ C*)
+    # Sun C++ 5.9
+
+    # The more standards-conforming stlport4 library is
+    # incompatible with the Cstd library. Avoid specifying
+    # it if it's in CXXFLAGS. Ignore libCrun as
+    # -library=stlport4 depends on it.
+    case " $CXX $CXXFLAGS " in
+    *" -library=stlport4 "*)
+      solaris_use_stlport4=yes
+      ;;
+    esac
+
+    if test "$solaris_use_stlport4" != yes; then
+      _LT_TAGVAR(postdeps,$1)='-library=Cstd -library=Crun'
+    fi
+    ;;
+  esac
+  ;;
+
+solaris*)
+  case $cc_basename in
+  CC* | sunCC*)
+    # The more standards-conforming stlport4 library is
+    # incompatible with the Cstd library. Avoid specifying
+    # it if it's in CXXFLAGS. Ignore libCrun as
+    # -library=stlport4 depends on it.
+    case " $CXX $CXXFLAGS " in
+    *" -library=stlport4 "*)
+      solaris_use_stlport4=yes
+      ;;
+    esac
+
+    # Adding this requires a known-good setup of shared libraries for
+    # Sun compiler versions before 5.6, else PIC objects from an old
+    # archive will be linked into the output, leading to subtle bugs.
+    if test "$solaris_use_stlport4" != yes; then
+      _LT_TAGVAR(postdeps,$1)='-library=Cstd -library=Crun'
+    fi
+    ;;
+  esac
+  ;;
+esac
+])
+
+case " $_LT_TAGVAR(postdeps, $1) " in
+*" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;;
+esac
+ _LT_TAGVAR(compiler_lib_search_dirs, $1)=
+if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then
+ _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | ${SED} -e 's! -L! !g' -e 's!^ !!'`
+fi
+_LT_TAGDECL([], [compiler_lib_search_dirs], [1],
+    [The directories searched by this compiler when creating a shared library])
+_LT_TAGDECL([], [predep_objects], [1],
+    [Dependencies to place before and after the objects being linked to
+    create a shared library])
+_LT_TAGDECL([], [postdep_objects], [1])
+_LT_TAGDECL([], [predeps], [1])
+_LT_TAGDECL([], [postdeps], [1])
+_LT_TAGDECL([], [compiler_lib_search_path], [1],
+    [The library search path used internally by the compiler when linking
+    a shared library])
+])# _LT_SYS_HIDDEN_LIBDEPS
+
+
+# _LT_LANG_F77_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for a Fortran 77 compiler are
+# suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_F77_CONFIG],
+[AC_LANG_PUSH(Fortran 77)
+if test -z "$F77" || test "X$F77" = "Xno"; then
+  _lt_disable_F77=yes
+fi
+
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+_LT_TAGVAR(allow_undefined_flag, $1)=
+_LT_TAGVAR(always_export_symbols, $1)=no
+_LT_TAGVAR(archive_expsym_cmds, $1)=
+_LT_TAGVAR(export_dynamic_flag_spec, $1)=
+_LT_TAGVAR(hardcode_direct, $1)=no
+_LT_TAGVAR(hardcode_direct_absolute, $1)=no
+_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+_LT_TAGVAR(hardcode_libdir_separator, $1)=
+_LT_TAGVAR(hardcode_minus_L, $1)=no
+_LT_TAGVAR(hardcode_automatic, $1)=no
+_LT_TAGVAR(inherit_rpath, $1)=no
+_LT_TAGVAR(module_cmds, $1)=
+_LT_TAGVAR(module_expsym_cmds, $1)=
+_LT_TAGVAR(link_all_deplibs, $1)=unknown
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+_LT_TAGVAR(no_undefined_flag, $1)=
+_LT_TAGVAR(whole_archive_flag_spec, $1)=
+_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+
+# Source file extension for f77 test sources.
+ac_ext=f
+
+# Object file extension for compiled f77 test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# No sense in running all these tests if we already determined that
+# the F77 compiler isn't working.  Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test "$_lt_disable_F77" != yes; then
+  # Code to be used in simple compile tests
+  lt_simple_compile_test_code="\
+      subroutine t
+      return
+      end
+"
+
+  # Code to be used in simple link tests
+  lt_simple_link_test_code="\
+      program t
+      end
+"
+
+  # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+  _LT_TAG_COMPILER
+
+  # save warnings/boilerplate of simple test code
+  _LT_COMPILER_BOILERPLATE
+  _LT_LINKER_BOILERPLATE
+
+  # Allow CC to be a program name with arguments.
+  lt_save_CC="$CC"
+  lt_save_GCC=$GCC
+  lt_save_CFLAGS=$CFLAGS
+  CC=${F77-"f77"}
+  CFLAGS=$FFLAGS
+  compiler=$CC
+  _LT_TAGVAR(compiler, $1)=$CC
+  _LT_CC_BASENAME([$compiler])
+  GCC=$G77
+  if test -n "$compiler"; then
+    AC_MSG_CHECKING([if libtool supports shared libraries])
+    AC_MSG_RESULT([$can_build_shared])
+
+    AC_MSG_CHECKING([whether to build shared libraries])
+    test "$can_build_shared" = "no" && enable_shared=no
+
+    # On AIX, shared libraries and static libraries use the same namespace, and
+    # are all built from PIC.
+    case $host_os in
+      aix3*)
+        test "$enable_shared" = yes && enable_static=no
+        if test -n "$RANLIB"; then
+          archive_cmds="$archive_cmds~\$RANLIB \$lib"
+          postinstall_cmds='$RANLIB $lib'
+        fi
+        ;;
+      aix[[4-9]]*)
+       if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+         test "$enable_shared" = yes && enable_static=no
+       fi
+        ;;
+    esac
+    AC_MSG_RESULT([$enable_shared])
+
+    AC_MSG_CHECKING([whether to build static libraries])
+    # Make sure either enable_shared or enable_static is yes.
+    test "$enable_shared" = yes || enable_static=yes
+    AC_MSG_RESULT([$enable_static])
+
+    _LT_TAGVAR(GCC, $1)="$G77"
+    _LT_TAGVAR(LD, $1)="$LD"
+
+    ## CAVEAT EMPTOR:
+    ## There is no encapsulation within the following macros, do not change
+    ## the running order or otherwise move them around unless you know exactly
+    ## what you are doing...
+    _LT_COMPILER_PIC($1)
+    _LT_COMPILER_C_O($1)
+    _LT_COMPILER_FILE_LOCKS($1)
+    _LT_LINKER_SHLIBS($1)
+    _LT_SYS_DYNAMIC_LINKER($1)
+    _LT_LINKER_HARDCODE_LIBPATH($1)
+
+    _LT_CONFIG($1)
+  fi # test -n "$compiler"
+
+  GCC=$lt_save_GCC
+  CC="$lt_save_CC"
+  CFLAGS="$lt_save_CFLAGS"
+fi # test "$_lt_disable_F77" != yes
+
+AC_LANG_POP
+])# _LT_LANG_F77_CONFIG
+
+
+# _LT_LANG_FC_CONFIG([TAG])
+# -------------------------
+# Ensure that the configuration variables for a Fortran compiler are
+# suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_FC_CONFIG],
+[AC_LANG_PUSH(Fortran)
+
+if test -z "$FC" || test "X$FC" = "Xno"; then
+  _lt_disable_FC=yes
+fi
+
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+_LT_TAGVAR(allow_undefined_flag, $1)=
+_LT_TAGVAR(always_export_symbols, $1)=no
+_LT_TAGVAR(archive_expsym_cmds, $1)=
+_LT_TAGVAR(export_dynamic_flag_spec, $1)=
+_LT_TAGVAR(hardcode_direct, $1)=no
+_LT_TAGVAR(hardcode_direct_absolute, $1)=no
+_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+_LT_TAGVAR(hardcode_libdir_separator, $1)=
+_LT_TAGVAR(hardcode_minus_L, $1)=no
+_LT_TAGVAR(hardcode_automatic, $1)=no
+_LT_TAGVAR(inherit_rpath, $1)=no
+_LT_TAGVAR(module_cmds, $1)=
+_LT_TAGVAR(module_expsym_cmds, $1)=
+_LT_TAGVAR(link_all_deplibs, $1)=unknown
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+_LT_TAGVAR(no_undefined_flag, $1)=
+_LT_TAGVAR(whole_archive_flag_spec, $1)=
+_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+
+# Source file extension for fc test sources.
+ac_ext=${ac_fc_srcext-f}
+
+# Object file extension for compiled fc test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# No sense in running all these tests if we already determined that
+# the FC compiler isn't working.  Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test "$_lt_disable_FC" != yes; then
+  # Code to be used in simple compile tests
+  lt_simple_compile_test_code="\
+      subroutine t
+      return
+      end
+"
+
+  # Code to be used in simple link tests
+  lt_simple_link_test_code="\
+      program t
+      end
+"
+
+  # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+  _LT_TAG_COMPILER
+
+  # save warnings/boilerplate of simple test code
+  _LT_COMPILER_BOILERPLATE
+  _LT_LINKER_BOILERPLATE
+
+  # Allow CC to be a program name with arguments.
+  lt_save_CC="$CC"
+  lt_save_GCC=$GCC
+  lt_save_CFLAGS=$CFLAGS
+  CC=${FC-"f95"}
+  CFLAGS=$FCFLAGS
+  compiler=$CC
+  GCC=$ac_cv_fc_compiler_gnu
+
+  _LT_TAGVAR(compiler, $1)=$CC
+  _LT_CC_BASENAME([$compiler])
+
+  if test -n "$compiler"; then
+    AC_MSG_CHECKING([if libtool supports shared libraries])
+    AC_MSG_RESULT([$can_build_shared])
+
+    AC_MSG_CHECKING([whether to build shared libraries])
+    test "$can_build_shared" = "no" && enable_shared=no
+
+    # On AIX, shared libraries and static libraries use the same namespace, and
+    # are all built from PIC.
+    case $host_os in
+      aix3*)
+        test "$enable_shared" = yes && enable_static=no
+        if test -n "$RANLIB"; then
+          archive_cmds="$archive_cmds~\$RANLIB \$lib"
+          postinstall_cmds='$RANLIB $lib'
+        fi
+        ;;
+      aix[[4-9]]*)
+       if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+         test "$enable_shared" = yes && enable_static=no
+       fi
+        ;;
+    esac
+    AC_MSG_RESULT([$enable_shared])
+
+    AC_MSG_CHECKING([whether to build static libraries])
+    # Make sure either enable_shared or enable_static is yes.
+    test "$enable_shared" = yes || enable_static=yes
+    AC_MSG_RESULT([$enable_static])
+
+    _LT_TAGVAR(GCC, $1)="$ac_cv_fc_compiler_gnu"
+    _LT_TAGVAR(LD, $1)="$LD"
+
+    ## CAVEAT EMPTOR:
+    ## There is no encapsulation within the following macros, do not change
+    ## the running order or otherwise move them around unless you know exactly
+    ## what you are doing...
+    _LT_SYS_HIDDEN_LIBDEPS($1)
+    _LT_COMPILER_PIC($1)
+    _LT_COMPILER_C_O($1)
+    _LT_COMPILER_FILE_LOCKS($1)
+    _LT_LINKER_SHLIBS($1)
+    _LT_SYS_DYNAMIC_LINKER($1)
+    _LT_LINKER_HARDCODE_LIBPATH($1)
+
+    _LT_CONFIG($1)
+  fi # test -n "$compiler"
+
+  GCC=$lt_save_GCC
+  CC=$lt_save_CC
+  CFLAGS=$lt_save_CFLAGS
+fi # test "$_lt_disable_FC" != yes
+
+AC_LANG_POP
+])# _LT_LANG_FC_CONFIG
+
+
+# _LT_LANG_GCJ_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for the GNU Java Compiler compiler
+# are suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_GCJ_CONFIG],
+[AC_REQUIRE([LT_PROG_GCJ])dnl
+AC_LANG_SAVE
+
+# Source file extension for Java test sources.
+ac_ext=java
+
+# Object file extension for compiled Java test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="class foo {}"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }'
+
+# ltmain only uses $CC for tagged configurations so make sure $CC is set.
+_LT_TAG_COMPILER
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+# Allow CC to be a program name with arguments.
+lt_save_CC=$CC
+lt_save_CFLAGS=$CFLAGS
+lt_save_GCC=$GCC
+GCC=yes
+CC=${GCJ-"gcj"}
+CFLAGS=$GCJFLAGS
+compiler=$CC
+_LT_TAGVAR(compiler, $1)=$CC
+_LT_TAGVAR(LD, $1)="$LD"
+_LT_CC_BASENAME([$compiler])
+
+# GCJ did not exist at the time GCC didn't implicitly link libc in.
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+
+if test -n "$compiler"; then
+  _LT_COMPILER_NO_RTTI($1)
+  _LT_COMPILER_PIC($1)
+  _LT_COMPILER_C_O($1)
+  _LT_COMPILER_FILE_LOCKS($1)
+  _LT_LINKER_SHLIBS($1)
+  _LT_LINKER_HARDCODE_LIBPATH($1)
+
+  _LT_CONFIG($1)
+fi
+
+AC_LANG_RESTORE
+
+GCC=$lt_save_GCC
+CC=$lt_save_CC
+CFLAGS=$lt_save_CFLAGS
+])# _LT_LANG_GCJ_CONFIG
+
+
+# _LT_LANG_GO_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for the GNU Go compiler
+# are suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_GO_CONFIG],
+[AC_REQUIRE([LT_PROG_GO])dnl
+AC_LANG_SAVE
+
+# Source file extension for Go test sources.
+ac_ext=go
+
+# Object file extension for compiled Go test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="package main; func main() { }"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='package main; func main() { }'
+
+# ltmain only uses $CC for tagged configurations so make sure $CC is set.
+_LT_TAG_COMPILER
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+# Allow CC to be a program name with arguments.
+lt_save_CC=$CC
+lt_save_CFLAGS=$CFLAGS
+lt_save_GCC=$GCC
+GCC=yes
+CC=${GOC-"gccgo"}
+CFLAGS=$GOFLAGS
+compiler=$CC
+_LT_TAGVAR(compiler, $1)=$CC
+_LT_TAGVAR(LD, $1)="$LD"
+_LT_CC_BASENAME([$compiler])
+
+# Go did not exist at the time GCC didn't implicitly link libc in.
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+
+if test -n "$compiler"; then
+  _LT_COMPILER_NO_RTTI($1)
+  _LT_COMPILER_PIC($1)
+  _LT_COMPILER_C_O($1)
+  _LT_COMPILER_FILE_LOCKS($1)
+  _LT_LINKER_SHLIBS($1)
+  _LT_LINKER_HARDCODE_LIBPATH($1)
+
+  _LT_CONFIG($1)
+fi
+
+AC_LANG_RESTORE
+
+GCC=$lt_save_GCC
+CC=$lt_save_CC
+CFLAGS=$lt_save_CFLAGS
+])# _LT_LANG_GO_CONFIG
+
+
+# _LT_LANG_RC_CONFIG([TAG])
+# -------------------------
+# Ensure that the configuration variables for the Windows resource compiler
+# are suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_RC_CONFIG],
+[AC_REQUIRE([LT_PROG_RC])dnl
+AC_LANG_SAVE
+
+# Source file extension for RC test sources.
+ac_ext=rc
+
+# Object file extension for compiled RC test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }'
+
+# Code to be used in simple link tests
+lt_simple_link_test_code="$lt_simple_compile_test_code"
+
+# ltmain only uses $CC for tagged configurations so make sure $CC is set.
+_LT_TAG_COMPILER
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+# Allow CC to be a program name with arguments.
+lt_save_CC="$CC"
+lt_save_CFLAGS=$CFLAGS
+lt_save_GCC=$GCC
+GCC=
+CC=${RC-"windres"}
+CFLAGS=
+compiler=$CC
+_LT_TAGVAR(compiler, $1)=$CC
+_LT_CC_BASENAME([$compiler])
+_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes
+
+if test -n "$compiler"; then
+  :
+  _LT_CONFIG($1)
+fi
+
+GCC=$lt_save_GCC
+AC_LANG_RESTORE
+CC=$lt_save_CC
+CFLAGS=$lt_save_CFLAGS
+])# _LT_LANG_RC_CONFIG
+
+
+# LT_PROG_GCJ
+# -----------
+AC_DEFUN([LT_PROG_GCJ],
+[m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ],
+  [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ],
+    [AC_CHECK_TOOL(GCJ, gcj,)
+      test "x${GCJFLAGS+set}" = xset || GCJFLAGS="-g -O2"
+      AC_SUBST(GCJFLAGS)])])[]dnl
+])
+
+# Old name:
+AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([LT_AC_PROG_GCJ], [])
+
+
+# LT_PROG_GO
+# ----------
+AC_DEFUN([LT_PROG_GO],
+[AC_CHECK_TOOL(GOC, gccgo,)
+])
+
+
+# LT_PROG_RC
+# ----------
+AC_DEFUN([LT_PROG_RC],
+[AC_CHECK_TOOL(RC, windres,)
+])
+
+# Old name:
+AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([LT_AC_PROG_RC], [])
+
+
+# _LT_DECL_EGREP
+# --------------
+# If we don't have a new enough Autoconf to choose the best grep
+# available, choose the one first in the user's PATH.
+m4_defun([_LT_DECL_EGREP],
+[AC_REQUIRE([AC_PROG_EGREP])dnl
+AC_REQUIRE([AC_PROG_FGREP])dnl
+test -z "$GREP" && GREP=grep
+_LT_DECL([], [GREP], [1], [A grep program that handles long lines])
+_LT_DECL([], [EGREP], [1], [An ERE matcher])
+_LT_DECL([], [FGREP], [1], [A literal string matcher])
+dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too
+AC_SUBST([GREP])
+])
+
+
+# _LT_DECL_OBJDUMP
+# --------------
+# If we don't have a new enough Autoconf to choose the best objdump
+# available, choose the one first in the user's PATH.
+m4_defun([_LT_DECL_OBJDUMP],
+[AC_CHECK_TOOL(OBJDUMP, objdump, false)
+test -z "$OBJDUMP" && OBJDUMP=objdump
+_LT_DECL([], [OBJDUMP], [1], [An object symbol dumper])
+AC_SUBST([OBJDUMP])
+])
+
+# _LT_DECL_DLLTOOL
+# ----------------
+# Ensure DLLTOOL variable is set.
+m4_defun([_LT_DECL_DLLTOOL],
+[AC_CHECK_TOOL(DLLTOOL, dlltool, false)
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+_LT_DECL([], [DLLTOOL], [1], [DLL creation program])
+AC_SUBST([DLLTOOL])
+])
+
+# _LT_DECL_SED
+# ------------
+# Check for a fully-functional sed program, that truncates
+# as few characters as possible.  Prefer GNU sed if found.
+m4_defun([_LT_DECL_SED],
+[AC_PROG_SED
+test -z "$SED" && SED=sed
+Xsed="$SED -e 1s/^X//"
+_LT_DECL([], [SED], [1], [A sed program that does not truncate output])
+_LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"],
+    [Sed that helps us avoid accidentally triggering echo(1) options like -n])
+])# _LT_DECL_SED
+
+m4_ifndef([AC_PROG_SED], [
+# NOTE: This macro has been submitted for inclusion into   #
+#  GNU Autoconf as AC_PROG_SED.  When it is available in   #
+#  a released version of Autoconf we should remove this    #
+#  macro and use it instead.                               #
+
+m4_defun([AC_PROG_SED],
+[AC_MSG_CHECKING([for a sed that does not truncate output])
+AC_CACHE_VAL(lt_cv_path_SED,
+[# Loop through the user's path and test for sed and gsed.
+# Then use that list of sed's as ones to test for truncation.
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for lt_ac_prog in sed gsed; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then
+        lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext"
+      fi
+    done
+  done
+done
+IFS=$as_save_IFS
+lt_ac_max=0
+lt_ac_count=0
+# Add /usr/xpg4/bin/sed as it is typically found on Solaris
+# along with /bin/sed that truncates output.
+for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do
+  test ! -f $lt_ac_sed && continue
+  cat /dev/null > conftest.in
+  lt_ac_count=0
+  echo $ECHO_N "0123456789$ECHO_C" >conftest.in
+  # Check for GNU sed and select it if it is found.
+  if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then
+    lt_cv_path_SED=$lt_ac_sed
+    break
+  fi
+  while true; do
+    cat conftest.in conftest.in >conftest.tmp
+    mv conftest.tmp conftest.in
+    cp conftest.in conftest.nl
+    echo >>conftest.nl
+    $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break
+    cmp -s conftest.out conftest.nl || break
+    # 10000 chars as input seems more than enough
+    test $lt_ac_count -gt 10 && break
+    lt_ac_count=`expr $lt_ac_count + 1`
+    if test $lt_ac_count -gt $lt_ac_max; then
+      lt_ac_max=$lt_ac_count
+      lt_cv_path_SED=$lt_ac_sed
+    fi
+  done
+done
+])
+SED=$lt_cv_path_SED
+AC_SUBST([SED])
+AC_MSG_RESULT([$SED])
+])#AC_PROG_SED
+])#m4_ifndef
+
+# Old name:
+AU_ALIAS([LT_AC_PROG_SED], [AC_PROG_SED])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([LT_AC_PROG_SED], [])
+
+
+# _LT_CHECK_SHELL_FEATURES
+# ------------------------
+# Find out whether the shell is Bourne or XSI compatible,
+# or has some other useful features.
+m4_defun([_LT_CHECK_SHELL_FEATURES],
+[AC_MSG_CHECKING([whether the shell understands some XSI constructs])
+# Try some XSI features
+xsi_shell=no
+( _lt_dummy="a/b/c"
+  test "${_lt_dummy##*/},${_lt_dummy%/*},${_lt_dummy#??}"${_lt_dummy%"$_lt_dummy"}, \
+      = c,a/b,b/c, \
+    && eval 'test $(( 1 + 1 )) -eq 2 \
+    && test "${#_lt_dummy}" -eq 5' ) >/dev/null 2>&1 \
+  && xsi_shell=yes
+AC_MSG_RESULT([$xsi_shell])
+_LT_CONFIG_LIBTOOL_INIT([xsi_shell='$xsi_shell'])
+
+AC_MSG_CHECKING([whether the shell understands "+="])
+lt_shell_append=no
+( foo=bar; set foo baz; eval "$[1]+=\$[2]" && test "$foo" = barbaz ) \
+    >/dev/null 2>&1 \
+  && lt_shell_append=yes
+AC_MSG_RESULT([$lt_shell_append])
+_LT_CONFIG_LIBTOOL_INIT([lt_shell_append='$lt_shell_append'])
+
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+  lt_unset=unset
+else
+  lt_unset=false
+fi
+_LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl
+
+# test EBCDIC or ASCII
+case `echo X|tr X '\101'` in
+ A) # ASCII based system
+    # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr
+  lt_SP2NL='tr \040 \012'
+  lt_NL2SP='tr \015\012 \040\040'
+  ;;
+ *) # EBCDIC based system
+  lt_SP2NL='tr \100 \n'
+  lt_NL2SP='tr \r\n \100\100'
+  ;;
+esac
+_LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl
+_LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl
+])# _LT_CHECK_SHELL_FEATURES
+
+
+# _LT_PROG_FUNCTION_REPLACE (FUNCNAME, REPLACEMENT-BODY)
+# ------------------------------------------------------
+# In `$cfgfile', look for function FUNCNAME delimited by `^FUNCNAME ()$' and
+# '^} FUNCNAME ', and replace its body with REPLACEMENT-BODY.
+m4_defun([_LT_PROG_FUNCTION_REPLACE],
+[dnl {
+sed -e '/^$1 ()$/,/^} # $1 /c\
+$1 ()\
+{\
+m4_bpatsubsts([$2], [$], [\\], [^\([    ]\)], [\\\1])
+} # Extended-shell $1 implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+])
+
+
+# _LT_PROG_REPLACE_SHELLFNS
+# -------------------------
+# Replace existing portable implementations of several shell functions with
+# equivalent extended shell implementations where those features are available..
+m4_defun([_LT_PROG_REPLACE_SHELLFNS],
+[if test x"$xsi_shell" = xyes; then
+  _LT_PROG_FUNCTION_REPLACE([func_dirname], [dnl
+    case ${1} in
+      */*) func_dirname_result="${1%/*}${2}" ;;
+      *  ) func_dirname_result="${3}" ;;
+    esac])
+
+  _LT_PROG_FUNCTION_REPLACE([func_basename], [dnl
+    func_basename_result="${1##*/}"])
+
+  _LT_PROG_FUNCTION_REPLACE([func_dirname_and_basename], [dnl
+    case ${1} in
+      */*) func_dirname_result="${1%/*}${2}" ;;
+      *  ) func_dirname_result="${3}" ;;
+    esac
+    func_basename_result="${1##*/}"])
+
+  _LT_PROG_FUNCTION_REPLACE([func_stripname], [dnl
+    # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are
+    # positional parameters, so assign one to ordinary parameter first.
+    func_stripname_result=${3}
+    func_stripname_result=${func_stripname_result#"${1}"}
+    func_stripname_result=${func_stripname_result%"${2}"}])
+
+  _LT_PROG_FUNCTION_REPLACE([func_split_long_opt], [dnl
+    func_split_long_opt_name=${1%%=*}
+    func_split_long_opt_arg=${1#*=}])
+
+  _LT_PROG_FUNCTION_REPLACE([func_split_short_opt], [dnl
+    func_split_short_opt_arg=${1#??}
+    func_split_short_opt_name=${1%"$func_split_short_opt_arg"}])
+
+  _LT_PROG_FUNCTION_REPLACE([func_lo2o], [dnl
+    case ${1} in
+      *.lo) func_lo2o_result=${1%.lo}.${objext} ;;
+      *)    func_lo2o_result=${1} ;;
+    esac])
+
+  _LT_PROG_FUNCTION_REPLACE([func_xform], [    func_xform_result=${1%.*}.lo])
+
+  _LT_PROG_FUNCTION_REPLACE([func_arith], [    func_arith_result=$(( $[*] ))])
+
+  _LT_PROG_FUNCTION_REPLACE([func_len], [    func_len_result=${#1}])
+fi
+
+if test x"$lt_shell_append" = xyes; then
+  _LT_PROG_FUNCTION_REPLACE([func_append], [    eval "${1}+=\\${2}"])
+
+  _LT_PROG_FUNCTION_REPLACE([func_append_quoted], [dnl
+    func_quote_for_eval "${2}"
+dnl m4 expansion turns \\\\ into \\, and then the shell eval turns that into \
+    eval "${1}+=\\\\ \\$func_quote_for_eval_result"])
+
+  # Save a `func_append' function call where possible by direct use of '+='
+  sed -e 's%func_append \([[a-zA-Z_]]\{1,\}\) "%\1+="%g' $cfgfile > $cfgfile.tmp \
+    && mv -f "$cfgfile.tmp" "$cfgfile" \
+      || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+  test 0 -eq $? || _lt_function_replace_fail=:
+else
+  # Save a `func_append' function call even when '+=' is not available
+  sed -e 's%func_append \([[a-zA-Z_]]\{1,\}\) "%\1="$\1%g' $cfgfile > $cfgfile.tmp \
+    && mv -f "$cfgfile.tmp" "$cfgfile" \
+      || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+  test 0 -eq $? || _lt_function_replace_fail=:
+fi
+
+if test x"$_lt_function_replace_fail" = x":"; then
+  AC_MSG_WARN([Unable to substitute extended shell functions in $ofile])
+fi
+])
+
+# _LT_PATH_CONVERSION_FUNCTIONS
+# -----------------------------
+# Determine which file name conversion functions should be used by
+# func_to_host_file (and, implicitly, by func_to_host_path).  These are needed
+# for certain cross-compile configurations and native mingw.
+m4_defun([_LT_PATH_CONVERSION_FUNCTIONS],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+AC_MSG_CHECKING([how to convert $build file names to $host format])
+AC_CACHE_VAL(lt_cv_to_host_file_cmd,
+[case $host in
+  *-*-mingw* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32
+        ;;
+      *-*-cygwin* )
+        lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32
+        ;;
+      * ) # otherwise, assume *nix
+        lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32
+        ;;
+    esac
+    ;;
+  *-*-cygwin* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin
+        ;;
+      *-*-cygwin* )
+        lt_cv_to_host_file_cmd=func_convert_file_noop
+        ;;
+      * ) # otherwise, assume *nix
+        lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin
+        ;;
+    esac
+    ;;
+  * ) # unhandled hosts (and "normal" native builds)
+    lt_cv_to_host_file_cmd=func_convert_file_noop
+    ;;
+esac
+])
+to_host_file_cmd=$lt_cv_to_host_file_cmd
+AC_MSG_RESULT([$lt_cv_to_host_file_cmd])
+_LT_DECL([to_host_file_cmd], [lt_cv_to_host_file_cmd],
+         [0], [convert $build file names to $host format])dnl
+
+AC_MSG_CHECKING([how to convert $build file names to toolchain format])
+AC_CACHE_VAL(lt_cv_to_tool_file_cmd,
+[#assume ordinary cross tools, or native build.
+lt_cv_to_tool_file_cmd=func_convert_file_noop
+case $host in
+  *-*-mingw* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32
+        ;;
+    esac
+    ;;
+esac
+])
+to_tool_file_cmd=$lt_cv_to_tool_file_cmd
+AC_MSG_RESULT([$lt_cv_to_tool_file_cmd])
+_LT_DECL([to_tool_file_cmd], [lt_cv_to_tool_file_cmd],
+         [0], [convert $build files to toolchain format])dnl
+])# _LT_PATH_CONVERSION_FUNCTIONS
+
+# Helper functions for option handling.                    -*- Autoconf -*-
+#
+#   Copyright (C) 2004, 2005, 2007, 2008, 2009 Free Software Foundation,
+#   Inc.
+#   Written by Gary V. Vaughan, 2004
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 7 ltoptions.m4
+
+# This is to help aclocal find these macros, as it can't see m4_define.
+AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])])
+
+
+# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME)
+# ------------------------------------------
+m4_define([_LT_MANGLE_OPTION],
+[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])])
+
+
+# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME)
+# ---------------------------------------
+# Set option OPTION-NAME for macro MACRO-NAME, and if there is a
+# matching handler defined, dispatch to it.  Other OPTION-NAMEs are
+# saved as a flag.
+m4_define([_LT_SET_OPTION],
+[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl
+m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]),
+        _LT_MANGLE_DEFUN([$1], [$2]),
+    [m4_warning([Unknown $1 option `$2'])])[]dnl
+])
+
+
+# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET])
+# ------------------------------------------------------------
+# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
+m4_define([_LT_IF_OPTION],
+[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])])
+
+
+# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET)
+# -------------------------------------------------------
+# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME
+# are set.
+m4_define([_LT_UNLESS_OPTIONS],
+[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),
+           [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option),
+                     [m4_define([$0_found])])])[]dnl
+m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3
+])[]dnl
+])
+
+
+# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST)
+# ----------------------------------------
+# OPTION-LIST is a space-separated list of Libtool options associated
+# with MACRO-NAME.  If any OPTION has a matching handler declared with
+# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about
+# the unknown option and exit.
+m4_defun([_LT_SET_OPTIONS],
+[# Set options
+m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),
+    [_LT_SET_OPTION([$1], _LT_Option)])
+
+m4_if([$1],[LT_INIT],[
+  dnl
+  dnl Simply set some default values (i.e off) if boolean options were not
+  dnl specified:
+  _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no
+  ])
+  _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no
+  ])
+  dnl
+  dnl If no reference was made to various pairs of opposing options, then
+  dnl we run the default mode handler for the pair.  For example, if neither
+  dnl `shared' nor `disable-shared' was passed, we enable building of shared
+  dnl archives by default:
+  _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED])
+  _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC])
+  _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC])
+  _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install],
+                  [_LT_ENABLE_FAST_INSTALL])
+  ])
+])# _LT_SET_OPTIONS
+
+
+
+# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME)
+# -----------------------------------------
+m4_define([_LT_MANGLE_DEFUN],
+[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])])
+
+
+# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE)
+# -----------------------------------------------
+m4_define([LT_OPTION_DEFINE],
+[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl
+])# LT_OPTION_DEFINE
+
+
+# dlopen
+# ------
+LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes
+])
+
+AU_DEFUN([AC_LIBTOOL_DLOPEN],
+[_LT_SET_OPTION([LT_INIT], [dlopen])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you
+put the `dlopen' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], [])
+
+
+# win32-dll
+# ---------
+# Declare package support for building win32 dll's.
+LT_OPTION_DEFINE([LT_INIT], [win32-dll],
+[enable_win32_dll=yes
+
+case $host in
+*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*)
+  AC_CHECK_TOOL(AS, as, false)
+  AC_CHECK_TOOL(DLLTOOL, dlltool, false)
+  AC_CHECK_TOOL(OBJDUMP, objdump, false)
+  ;;
+esac
+
+test -z "$AS" && AS=as
+_LT_DECL([], [AS],      [1], [Assembler program])dnl
+
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+_LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl
+
+test -z "$OBJDUMP" && OBJDUMP=objdump
+_LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl
+])# win32-dll
+
+AU_DEFUN([AC_LIBTOOL_WIN32_DLL],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+_LT_SET_OPTION([LT_INIT], [win32-dll])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you
+put the `win32-dll' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], [])
+
+
+# _LT_ENABLE_SHARED([DEFAULT])
+# ----------------------------
+# implement the --enable-shared flag, and supports the `shared' and
+# `disable-shared' LT_INIT options.
+# DEFAULT is either `yes' or `no'.  If omitted, it defaults to `yes'.
+m4_define([_LT_ENABLE_SHARED],
+[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl
+AC_ARG_ENABLE([shared],
+    [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@],
+       [build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])],
+    [p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_shared=yes ;;
+    no) enable_shared=no ;;
+    *)
+      enable_shared=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+       IFS="$lt_save_ifs"
+       if test "X$pkg" = "X$p"; then
+         enable_shared=yes
+       fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac],
+    [enable_shared=]_LT_ENABLE_SHARED_DEFAULT)
+
+    _LT_DECL([build_libtool_libs], [enable_shared], [0],
+       [Whether or not to build shared libraries])
+])# _LT_ENABLE_SHARED
+
+LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])])
+LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])])
+
+# Old names:
+AC_DEFUN([AC_ENABLE_SHARED],
+[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared])
+])
+
+AC_DEFUN([AC_DISABLE_SHARED],
+[_LT_SET_OPTION([LT_INIT], [disable-shared])
+])
+
+AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)])
+AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_ENABLE_SHARED], [])
+dnl AC_DEFUN([AM_DISABLE_SHARED], [])
+
+
+
+# _LT_ENABLE_STATIC([DEFAULT])
+# ----------------------------
+# implement the --enable-static flag, and support the `static' and
+# `disable-static' LT_INIT options.
+# DEFAULT is either `yes' or `no'.  If omitted, it defaults to `yes'.
+m4_define([_LT_ENABLE_STATIC],
+[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl
+AC_ARG_ENABLE([static],
+    [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@],
+       [build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])],
+    [p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_static=yes ;;
+    no) enable_static=no ;;
+    *)
+     enable_static=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+       IFS="$lt_save_ifs"
+       if test "X$pkg" = "X$p"; then
+         enable_static=yes
+       fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac],
+    [enable_static=]_LT_ENABLE_STATIC_DEFAULT)
+
+    _LT_DECL([build_old_libs], [enable_static], [0],
+       [Whether or not to build static libraries])
+])# _LT_ENABLE_STATIC
+
+LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])])
+LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])])
+
+# Old names:
+AC_DEFUN([AC_ENABLE_STATIC],
+[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static])
+])
+
+AC_DEFUN([AC_DISABLE_STATIC],
+[_LT_SET_OPTION([LT_INIT], [disable-static])
+])
+
+AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)])
+AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_ENABLE_STATIC], [])
+dnl AC_DEFUN([AM_DISABLE_STATIC], [])
+
+
+
+# _LT_ENABLE_FAST_INSTALL([DEFAULT])
+# ----------------------------------
+# implement the --enable-fast-install flag, and support the `fast-install'
+# and `disable-fast-install' LT_INIT options.
+# DEFAULT is either `yes' or `no'.  If omitted, it defaults to `yes'.
+m4_define([_LT_ENABLE_FAST_INSTALL],
+[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl
+AC_ARG_ENABLE([fast-install],
+    [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@],
+    [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])],
+    [p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_fast_install=yes ;;
+    no) enable_fast_install=no ;;
+    *)
+      enable_fast_install=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+       IFS="$lt_save_ifs"
+       if test "X$pkg" = "X$p"; then
+         enable_fast_install=yes
+       fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac],
+    [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT)
+
+_LT_DECL([fast_install], [enable_fast_install], [0],
+        [Whether or not to optimize for fast installation])dnl
+])# _LT_ENABLE_FAST_INSTALL
+
+LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])])
+LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])])
+
+# Old names:
+AU_DEFUN([AC_ENABLE_FAST_INSTALL],
+[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you put
+the `fast-install' option into LT_INIT's first parameter.])
+])
+
+AU_DEFUN([AC_DISABLE_FAST_INSTALL],
+[_LT_SET_OPTION([LT_INIT], [disable-fast-install])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you put
+the `disable-fast-install' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], [])
+dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], [])
+
+
+# _LT_WITH_PIC([MODE])
+# --------------------
+# implement the --with-pic flag, and support the `pic-only' and `no-pic'
+# LT_INIT options.
+# MODE is either `yes' or `no'.  If omitted, it defaults to `both'.
+m4_define([_LT_WITH_PIC],
+[AC_ARG_WITH([pic],
+    [AS_HELP_STRING([--with-pic@<:@=PKGS@:>@],
+       [try to use only PIC/non-PIC objects @<:@default=use both@:>@])],
+    [lt_p=${PACKAGE-default}
+    case $withval in
+    yes|no) pic_mode=$withval ;;
+    *)
+      pic_mode=default
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for lt_pkg in $withval; do
+       IFS="$lt_save_ifs"
+       if test "X$lt_pkg" = "X$lt_p"; then
+         pic_mode=yes
+       fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac],
+    [pic_mode=default])
+
+test -z "$pic_mode" && pic_mode=m4_default([$1], [default])
+
+_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl
+])# _LT_WITH_PIC
+
+LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])])
+LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])])
+
+# Old name:
+AU_DEFUN([AC_LIBTOOL_PICMODE],
+[_LT_SET_OPTION([LT_INIT], [pic-only])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you
+put the `pic-only' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_PICMODE], [])
+
+
+m4_define([_LTDL_MODE], [])
+LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive],
+                [m4_define([_LTDL_MODE], [nonrecursive])])
+LT_OPTION_DEFINE([LTDL_INIT], [recursive],
+                [m4_define([_LTDL_MODE], [recursive])])
+LT_OPTION_DEFINE([LTDL_INIT], [subproject],
+                [m4_define([_LTDL_MODE], [subproject])])
+
+m4_define([_LTDL_TYPE], [])
+LT_OPTION_DEFINE([LTDL_INIT], [installable],
+                [m4_define([_LTDL_TYPE], [installable])])
+LT_OPTION_DEFINE([LTDL_INIT], [convenience],
+                [m4_define([_LTDL_TYPE], [convenience])])
+
+# ltsugar.m4 -- libtool m4 base layer.                         -*-Autoconf-*-
+#
+# Copyright (C) 2004, 2005, 2007, 2008 Free Software Foundation, Inc.
+# Written by Gary V. Vaughan, 2004
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 6 ltsugar.m4
+
+# This is to help aclocal find these macros, as it can't see m4_define.
+AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])])
+
+
+# lt_join(SEP, ARG1, [ARG2...])
+# -----------------------------
+# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their
+# associated separator.
+# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier
+# versions in m4sugar had bugs.
+m4_define([lt_join],
+[m4_if([$#], [1], [],
+       [$#], [2], [[$2]],
+       [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])])
+m4_define([_lt_join],
+[m4_if([$#$2], [2], [],
+       [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])])
+
+
+# lt_car(LIST)
+# lt_cdr(LIST)
+# ------------
+# Manipulate m4 lists.
+# These macros are necessary as long as will still need to support
+# Autoconf-2.59 which quotes differently.
+m4_define([lt_car], [[$1]])
+m4_define([lt_cdr],
+[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])],
+       [$#], 1, [],
+       [m4_dquote(m4_shift($@))])])
+m4_define([lt_unquote], $1)
+
+
+# lt_append(MACRO-NAME, STRING, [SEPARATOR])
+# ------------------------------------------
+# Redefine MACRO-NAME to hold its former content plus `SEPARATOR'`STRING'.
+# Note that neither SEPARATOR nor STRING are expanded; they are appended
+# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked).
+# No SEPARATOR is output if MACRO-NAME was previously undefined (different
+# than defined and empty).
+#
+# This macro is needed until we can rely on Autoconf 2.62, since earlier
+# versions of m4sugar mistakenly expanded SEPARATOR but not STRING.
+m4_define([lt_append],
+[m4_define([$1],
+          m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])])
+
+
+
+# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...])
+# ----------------------------------------------------------
+# Produce a SEP delimited list of all paired combinations of elements of
+# PREFIX-LIST with SUFFIX1 through SUFFIXn.  Each element of the list
+# has the form PREFIXmINFIXSUFFIXn.
+# Needed until we can rely on m4_combine added in Autoconf 2.62.
+m4_define([lt_combine],
+[m4_if(m4_eval([$# > 3]), [1],
+       [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl
+[[m4_foreach([_Lt_prefix], [$2],
+            [m4_foreach([_Lt_suffix],
+               ]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[,
+       [_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])])
+
+
+# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ])
+# -----------------------------------------------------------------------
+# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited
+# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ.
+m4_define([lt_if_append_uniq],
+[m4_ifdef([$1],
+         [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1],
+                [lt_append([$1], [$2], [$3])$4],
+                [$5])],
+         [lt_append([$1], [$2], [$3])$4])])
+
+
+# lt_dict_add(DICT, KEY, VALUE)
+# -----------------------------
+m4_define([lt_dict_add],
+[m4_define([$1($2)], [$3])])
+
+
+# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE)
+# --------------------------------------------
+m4_define([lt_dict_add_subkey],
+[m4_define([$1($2:$3)], [$4])])
+
+
+# lt_dict_fetch(DICT, KEY, [SUBKEY])
+# ----------------------------------
+m4_define([lt_dict_fetch],
+[m4_ifval([$3],
+       m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]),
+    m4_ifdef([$1($2)], [m4_defn([$1($2)])]))])
+
+
+# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE])
+# -----------------------------------------------------------------
+m4_define([lt_if_dict_fetch],
+[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4],
+       [$5],
+    [$6])])
+
+
+# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...])
+# --------------------------------------------------------------
+m4_define([lt_dict_filter],
+[m4_if([$5], [], [],
+  [lt_join(m4_quote(m4_default([$4], [[, ]])),
+           lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]),
+                     [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl
+])
+
+# ltversion.m4 -- version numbers                      -*- Autoconf -*-
+#
+#   Copyright (C) 2004 Free Software Foundation, Inc.
+#   Written by Scott James Remnant, 2004
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# @configure_input@
+
+# serial 3337 ltversion.m4
+# This file is part of GNU Libtool
+
+m4_define([LT_PACKAGE_VERSION], [2.4.2])
+m4_define([LT_PACKAGE_REVISION], [1.3337])
+
+AC_DEFUN([LTVERSION_VERSION],
+[macro_version='2.4.2'
+macro_revision='1.3337'
+_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?])
+_LT_DECL(, macro_revision, 0)
+])
+
+# lt~obsolete.m4 -- aclocal satisfying obsolete definitions.    -*-Autoconf-*-
+#
+#   Copyright (C) 2004, 2005, 2007, 2009 Free Software Foundation, Inc.
+#   Written by Scott James Remnant, 2004.
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 5 lt~obsolete.m4
+
+# These exist entirely to fool aclocal when bootstrapping libtool.
+#
+# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN)
+# which have later been changed to m4_define as they aren't part of the
+# exported API, or moved to Autoconf or Automake where they belong.
+#
+# The trouble is, aclocal is a bit thick.  It'll see the old AC_DEFUN
+# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us
+# using a macro with the same name in our local m4/libtool.m4 it'll
+# pull the old libtool.m4 in (it doesn't see our shiny new m4_define
+# and doesn't know about Autoconf macros at all.)
+#
+# So we provide this file, which has a silly filename so it's always
+# included after everything else.  This provides aclocal with the
+# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything
+# because those macros already exist, or will be overwritten later.
+# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. 
+#
+# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here.
+# Yes, that means every name once taken will need to remain here until
+# we give up compatibility with versions before 1.7, at which point
+# we need to keep only those names which we still refer to.
+
+# This is to help aclocal find these macros, as it can't see m4_define.
+AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])])
+
+m4_ifndef([AC_LIBTOOL_LINKER_OPTION],  [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])])
+m4_ifndef([AC_PROG_EGREP],             [AC_DEFUN([AC_PROG_EGREP])])
+m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH],        [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])])
+m4_ifndef([_LT_AC_SHELL_INIT],         [AC_DEFUN([_LT_AC_SHELL_INIT])])
+m4_ifndef([_LT_AC_SYS_LIBPATH_AIX],    [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])])
+m4_ifndef([_LT_PROG_LTMAIN],           [AC_DEFUN([_LT_PROG_LTMAIN])])
+m4_ifndef([_LT_AC_TAGVAR],             [AC_DEFUN([_LT_AC_TAGVAR])])
+m4_ifndef([AC_LTDL_ENABLE_INSTALL],    [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])])
+m4_ifndef([AC_LTDL_PREOPEN],           [AC_DEFUN([AC_LTDL_PREOPEN])])
+m4_ifndef([_LT_AC_SYS_COMPILER],       [AC_DEFUN([_LT_AC_SYS_COMPILER])])
+m4_ifndef([_LT_AC_LOCK],               [AC_DEFUN([_LT_AC_LOCK])])
+m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE],        [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])])
+m4_ifndef([_LT_AC_TRY_DLOPEN_SELF],    [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])])
+m4_ifndef([AC_LIBTOOL_PROG_CC_C_O],    [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])])
+m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])])
+m4_ifndef([AC_LIBTOOL_OBJDIR],         [AC_DEFUN([AC_LIBTOOL_OBJDIR])])
+m4_ifndef([AC_LTDL_OBJDIR],            [AC_DEFUN([AC_LTDL_OBJDIR])])
+m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])])
+m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP],  [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])])
+m4_ifndef([AC_PATH_MAGIC],             [AC_DEFUN([AC_PATH_MAGIC])])
+m4_ifndef([AC_PROG_LD_GNU],            [AC_DEFUN([AC_PROG_LD_GNU])])
+m4_ifndef([AC_PROG_LD_RELOAD_FLAG],    [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])])
+m4_ifndef([AC_DEPLIBS_CHECK_METHOD],   [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])])
+m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])])
+m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])])
+m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])])
+m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])])
+m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])])
+m4_ifndef([LT_AC_PROG_EGREP],          [AC_DEFUN([LT_AC_PROG_EGREP])])
+m4_ifndef([LT_AC_PROG_SED],            [AC_DEFUN([LT_AC_PROG_SED])])
+m4_ifndef([_LT_CC_BASENAME],           [AC_DEFUN([_LT_CC_BASENAME])])
+m4_ifndef([_LT_COMPILER_BOILERPLATE],  [AC_DEFUN([_LT_COMPILER_BOILERPLATE])])
+m4_ifndef([_LT_LINKER_BOILERPLATE],    [AC_DEFUN([_LT_LINKER_BOILERPLATE])])
+m4_ifndef([_AC_PROG_LIBTOOL],          [AC_DEFUN([_AC_PROG_LIBTOOL])])
+m4_ifndef([AC_LIBTOOL_SETUP],          [AC_DEFUN([AC_LIBTOOL_SETUP])])
+m4_ifndef([_LT_AC_CHECK_DLFCN],                [AC_DEFUN([_LT_AC_CHECK_DLFCN])])
+m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER],     [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])])
+m4_ifndef([_LT_AC_TAGCONFIG],          [AC_DEFUN([_LT_AC_TAGCONFIG])])
+m4_ifndef([AC_DISABLE_FAST_INSTALL],   [AC_DEFUN([AC_DISABLE_FAST_INSTALL])])
+m4_ifndef([_LT_AC_LANG_CXX],           [AC_DEFUN([_LT_AC_LANG_CXX])])
+m4_ifndef([_LT_AC_LANG_F77],           [AC_DEFUN([_LT_AC_LANG_F77])])
+m4_ifndef([_LT_AC_LANG_GCJ],           [AC_DEFUN([_LT_AC_LANG_GCJ])])
+m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG],  [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])])
+m4_ifndef([_LT_AC_LANG_C_CONFIG],      [AC_DEFUN([_LT_AC_LANG_C_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG],        [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])])
+m4_ifndef([_LT_AC_LANG_CXX_CONFIG],    [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG],        [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])])
+m4_ifndef([_LT_AC_LANG_F77_CONFIG],    [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG],        [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])])
+m4_ifndef([_LT_AC_LANG_GCJ_CONFIG],    [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])])
+m4_ifndef([_LT_AC_LANG_RC_CONFIG],     [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])])
+m4_ifndef([AC_LIBTOOL_CONFIG],         [AC_DEFUN([AC_LIBTOOL_CONFIG])])
+m4_ifndef([_LT_AC_FILE_LTDLL_C],       [AC_DEFUN([_LT_AC_FILE_LTDLL_C])])
+m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS],        [AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])])
+m4_ifndef([_LT_AC_PROG_CXXCPP],                [AC_DEFUN([_LT_AC_PROG_CXXCPP])])
+m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS],        [AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])])
+m4_ifndef([_LT_PROG_ECHO_BACKSLASH],   [AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])])
+m4_ifndef([_LT_PROG_F77],              [AC_DEFUN([_LT_PROG_F77])])
+m4_ifndef([_LT_PROG_FC],               [AC_DEFUN([_LT_PROG_FC])])
+m4_ifndef([_LT_PROG_CXX],              [AC_DEFUN([_LT_PROG_CXX])])
+
+# Copyright (C) 2002, 2003, 2005, 2006, 2007, 2008, 2011 Free Software
+# Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 1
+
+# AM_AUTOMAKE_VERSION(VERSION)
+# ----------------------------
+# Automake X.Y traces this macro to ensure aclocal.m4 has been
+# generated from the m4 files accompanying Automake X.Y.
+# (This private macro should not be called outside this file.)
+AC_DEFUN([AM_AUTOMAKE_VERSION],
+[am__api_version='1.11'
+dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
+dnl require some minimum version.  Point them to the right macro.
+m4_if([$1], [1.11.3], [],
+      [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
+])
+
+# _AM_AUTOCONF_VERSION(VERSION)
+# -----------------------------
+# aclocal traces this macro to find the Autoconf version.
+# This is a private macro too.  Using m4_define simplifies
+# the logic in aclocal, which can simply ignore this definition.
+m4_define([_AM_AUTOCONF_VERSION], [])
+
+# AM_SET_CURRENT_AUTOMAKE_VERSION
+# -------------------------------
+# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
+# This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
+AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
+[AM_AUTOMAKE_VERSION([1.11.3])dnl
+m4_ifndef([AC_AUTOCONF_VERSION],
+  [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
+
+# AM_AUX_DIR_EXPAND                                         -*- Autoconf -*-
+
+# Copyright (C) 2001, 2003, 2005, 2011 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 1
+
+# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets
+# $ac_aux_dir to `$srcdir/foo'.  In other projects, it is set to
+# `$srcdir', `$srcdir/..', or `$srcdir/../..'.
+#
+# Of course, Automake must honor this variable whenever it calls a
+# tool from the auxiliary directory.  The problem is that $srcdir (and
+# therefore $ac_aux_dir as well) can be either absolute or relative,
+# depending on how configure is run.  This is pretty annoying, since
+# it makes $ac_aux_dir quite unusable in subdirectories: in the top
+# source directory, any form will work fine, but in subdirectories a
+# relative path needs to be adjusted first.
+#
+# $ac_aux_dir/missing
+#    fails when called from a subdirectory if $ac_aux_dir is relative
+# $top_srcdir/$ac_aux_dir/missing
+#    fails if $ac_aux_dir is absolute,
+#    fails when called from a subdirectory in a VPATH build with
+#          a relative $ac_aux_dir
+#
+# The reason of the latter failure is that $top_srcdir and $ac_aux_dir
+# are both prefixed by $srcdir.  In an in-source build this is usually
+# harmless because $srcdir is `.', but things will broke when you
+# start a VPATH build or use an absolute $srcdir.
+#
+# So we could use something similar to $top_srcdir/$ac_aux_dir/missing,
+# iff we strip the leading $srcdir from $ac_aux_dir.  That would be:
+#   am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"`
+# and then we would define $MISSING as
+#   MISSING="\${SHELL} $am_aux_dir/missing"
+# This will work as long as MISSING is not called from configure, because
+# unfortunately $(top_srcdir) has no meaning in configure.
+# However there are other variables, like CC, which are often used in
+# configure, and could therefore not use this "fixed" $ac_aux_dir.
+#
+# Another solution, used here, is to always expand $ac_aux_dir to an
+# absolute PATH.  The drawback is that using absolute paths prevent a
+# configured tree to be moved without reconfiguration.
+
+AC_DEFUN([AM_AUX_DIR_EXPAND],
+[dnl Rely on autoconf to set up CDPATH properly.
+AC_PREREQ([2.50])dnl
+# expand $ac_aux_dir to an absolute path
+am_aux_dir=`cd $ac_aux_dir && pwd`
+])
+
+# AM_CONDITIONAL                                            -*- Autoconf -*-
+
+# Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005, 2006, 2008
+# Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 9
+
+# AM_CONDITIONAL(NAME, SHELL-CONDITION)
+# -------------------------------------
+# Define a conditional.
+AC_DEFUN([AM_CONDITIONAL],
+[AC_PREREQ(2.52)dnl
+ ifelse([$1], [TRUE],  [AC_FATAL([$0: invalid condition: $1])],
+       [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl
+AC_SUBST([$1_TRUE])dnl
+AC_SUBST([$1_FALSE])dnl
+_AM_SUBST_NOTMAKE([$1_TRUE])dnl
+_AM_SUBST_NOTMAKE([$1_FALSE])dnl
+m4_define([_AM_COND_VALUE_$1], [$2])dnl
+if $2; then
+  $1_TRUE=
+  $1_FALSE='#'
+else
+  $1_TRUE='#'
+  $1_FALSE=
+fi
+AC_CONFIG_COMMANDS_PRE(
+[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then
+  AC_MSG_ERROR([[conditional "$1" was never defined.
+Usually this means the macro was only invoked conditionally.]])
+fi])])
+
+# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2009,
+# 2010, 2011 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 12
+
+# There are a few dirty hacks below to avoid letting `AC_PROG_CC' be
+# written in clear, in which case automake, when reading aclocal.m4,
+# will think it sees a *use*, and therefore will trigger all it's
+# C support machinery.  Also note that it means that autoscan, seeing
+# CC etc. in the Makefile, will ask for an AC_PROG_CC use...
+
+
+# _AM_DEPENDENCIES(NAME)
+# ----------------------
+# See how the compiler implements dependency checking.
+# NAME is "CC", "CXX", "GCJ", or "OBJC".
+# We try a few techniques and use that to set a single cache variable.
+#
+# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was
+# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular
+# dependency, and given that the user is not expected to run this macro,
+# just rely on AC_PROG_CC.
+AC_DEFUN([_AM_DEPENDENCIES],
+[AC_REQUIRE([AM_SET_DEPDIR])dnl
+AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl
+AC_REQUIRE([AM_MAKE_INCLUDE])dnl
+AC_REQUIRE([AM_DEP_TRACK])dnl
+
+ifelse([$1], CC,   [depcc="$CC"   am_compiler_list=],
+       [$1], CXX,  [depcc="$CXX"  am_compiler_list=],
+       [$1], OBJC, [depcc="$OBJC" am_compiler_list='gcc3 gcc'],
+       [$1], UPC,  [depcc="$UPC"  am_compiler_list=],
+       [$1], GCJ,  [depcc="$GCJ"  am_compiler_list='gcc3 gcc'],
+                   [depcc="$$1"   am_compiler_list=])
+
+AC_CACHE_CHECK([dependency style of $depcc],
+               [am_cv_$1_dependencies_compiler_type],
+[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+  # We make a subdir and do the tests there.  Otherwise we can end up
+  # making bogus files that we don't know about and never remove.  For
+  # instance it was reported that on HP-UX the gcc test will end up
+  # making a dummy file named `D' -- because `-MD' means `put the output
+  # in D'.
+  rm -rf conftest.dir
+  mkdir conftest.dir
+  # Copy depcomp to subdir because otherwise we won't find it if we're
+  # using a relative directory.
+  cp "$am_depcomp" conftest.dir
+  cd conftest.dir
+  # We will build objects and dependencies in a subdirectory because
+  # it helps to detect inapplicable dependency modes.  For instance
+  # both Tru64's cc and ICC support -MD to output dependencies as a
+  # side effect of compilation, but ICC will put the dependencies in
+  # the current directory while Tru64 will put them in the object
+  # directory.
+  mkdir sub
+
+  am_cv_$1_dependencies_compiler_type=none
+  if test "$am_compiler_list" = ""; then
+     am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp`
+  fi
+  am__universal=false
+  m4_case([$1], [CC],
+    [case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac],
+    [CXX],
+    [case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac])
+
+  for depmode in $am_compiler_list; do
+    # Setup a source with many dependencies, because some compilers
+    # like to wrap large dependency lists on column 80 (with \), and
+    # we should not choose a depcomp mode which is confused by this.
+    #
+    # We need to recreate these files for each test, as the compiler may
+    # overwrite some of them when testing with obscure command lines.
+    # This happens at least with the AIX C compiler.
+    : > sub/conftest.c
+    for i in 1 2 3 4 5 6; do
+      echo '#include "conftst'$i'.h"' >> sub/conftest.c
+      # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with
+      # Solaris 8's {/usr,}/bin/sh.
+      touch sub/conftst$i.h
+    done
+    echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+    # We check with `-c' and `-o' for the sake of the "dashmstdout"
+    # mode.  It turns out that the SunPro C++ compiler does not properly
+    # handle `-M -o', and we need to detect this.  Also, some Intel
+    # versions had trouble with output in subdirs
+    am__obj=sub/conftest.${OBJEXT-o}
+    am__minus_obj="-o $am__obj"
+    case $depmode in
+    gcc)
+      # This depmode causes a compiler race in universal mode.
+      test "$am__universal" = false || continue
+      ;;
+    nosideeffect)
+      # after this tag, mechanisms are not by side-effect, so they'll
+      # only be used when explicitly requested
+      if test "x$enable_dependency_tracking" = xyes; then
+       continue
+      else
+       break
+      fi
+      ;;
+    msvc7 | msvc7msys | msvisualcpp | msvcmsys)
+      # This compiler won't grok `-c -o', but also, the minuso test has
+      # not run yet.  These depmodes are late enough in the game, and
+      # so weak that their functioning should not be impacted.
+      am__obj=conftest.${OBJEXT-o}
+      am__minus_obj=
+      ;;
+    none) break ;;
+    esac
+    if depmode=$depmode \
+       source=sub/conftest.c object=$am__obj \
+       depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+       $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+         >/dev/null 2>conftest.err &&
+       grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+       ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+      # icc doesn't choke on unknown options, it will just issue warnings
+      # or remarks (even with -Werror).  So we grep stderr for any message
+      # that says an option was ignored or not supported.
+      # When given -MP, icc 7.0 and 7.1 complain thusly:
+      #   icc: Command line warning: ignoring option '-M'; no argument required
+      # The diagnosis changed in icc 8.0:
+      #   icc: Command line remark: option '-MP' not supported
+      if (grep 'ignoring option' conftest.err ||
+          grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+        am_cv_$1_dependencies_compiler_type=$depmode
+        break
+      fi
+    fi
+  done
+
+  cd ..
+  rm -rf conftest.dir
+else
+  am_cv_$1_dependencies_compiler_type=none
+fi
+])
+AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type])
+AM_CONDITIONAL([am__fastdep$1], [
+  test "x$enable_dependency_tracking" != xno \
+  && test "$am_cv_$1_dependencies_compiler_type" = gcc3])
+])
+
+
+# AM_SET_DEPDIR
+# -------------
+# Choose a directory name for dependency files.
+# This macro is AC_REQUIREd in _AM_DEPENDENCIES
+AC_DEFUN([AM_SET_DEPDIR],
+[AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl
+])
+
+
+# AM_DEP_TRACK
+# ------------
+AC_DEFUN([AM_DEP_TRACK],
+[AC_ARG_ENABLE(dependency-tracking,
+[  --disable-dependency-tracking  speeds up one-time build
+  --enable-dependency-tracking   do not reject slow dependency extractors])
+if test "x$enable_dependency_tracking" != xno; then
+  am_depcomp="$ac_aux_dir/depcomp"
+  AMDEPBACKSLASH='\'
+  am__nodep='_no'
+fi
+AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno])
+AC_SUBST([AMDEPBACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl
+AC_SUBST([am__nodep])dnl
+_AM_SUBST_NOTMAKE([am__nodep])dnl
+])
+
+# Generate code to set up dependency tracking.              -*- Autoconf -*-
+
+# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2008
+# Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+#serial 5
+
+# _AM_OUTPUT_DEPENDENCY_COMMANDS
+# ------------------------------
+AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS],
+[{
+  # Autoconf 2.62 quotes --file arguments for eval, but not when files
+  # are listed without --file.  Let's play safe and only enable the eval
+  # if we detect the quoting.
+  case $CONFIG_FILES in
+  *\'*) eval set x "$CONFIG_FILES" ;;
+  *)   set x $CONFIG_FILES ;;
+  esac
+  shift
+  for mf
+  do
+    # Strip MF so we end up with the name of the file.
+    mf=`echo "$mf" | sed -e 's/:.*$//'`
+    # Check whether this is an Automake generated Makefile or not.
+    # We used to match only the files named `Makefile.in', but
+    # some people rename them; so instead we look at the file content.
+    # Grep'ing the first line is not enough: some people post-process
+    # each Makefile.in and add a new line on top of each file to say so.
+    # Grep'ing the whole file is not good either: AIX grep has a line
+    # limit of 2048, but all sed's we know have understand at least 4000.
+    if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
+      dirpart=`AS_DIRNAME("$mf")`
+    else
+      continue
+    fi
+    # Extract the definition of DEPDIR, am__include, and am__quote
+    # from the Makefile without running `make'.
+    DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
+    test -z "$DEPDIR" && continue
+    am__include=`sed -n 's/^am__include = //p' < "$mf"`
+    test -z "am__include" && continue
+    am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
+    # When using ansi2knr, U may be empty or an underscore; expand it
+    U=`sed -n 's/^U = //p' < "$mf"`
+    # Find all dependency output files, they are included files with
+    # $(DEPDIR) in their names.  We invoke sed twice because it is the
+    # simplest approach to changing $(DEPDIR) to its actual value in the
+    # expansion.
+    for file in `sed -n "
+      s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
+        sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do
+      # Make sure the directory exists.
+      test -f "$dirpart/$file" && continue
+      fdir=`AS_DIRNAME(["$file"])`
+      AS_MKDIR_P([$dirpart/$fdir])
+      # echo "creating $dirpart/$file"
+      echo '# dummy' > "$dirpart/$file"
+    done
+  done
+}
+])# _AM_OUTPUT_DEPENDENCY_COMMANDS
+
+
+# AM_OUTPUT_DEPENDENCY_COMMANDS
+# -----------------------------
+# This macro should only be invoked once -- use via AC_REQUIRE.
+#
+# This code is only required when automatic dependency tracking
+# is enabled.  FIXME.  This creates each `.P' file that we will
+# need in order to bootstrap the dependency handling code.
+AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
+[AC_CONFIG_COMMANDS([depfiles],
+     [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS],
+     [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"])
+])
+
+# Do all the work for Automake.                             -*- Autoconf -*-
+
+# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+# 2005, 2006, 2008, 2009 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 16
+
+# This macro actually does too much.  Some checks are only needed if
+# your package does certain things.  But this isn't really a big deal.
+
+# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE])
+# AM_INIT_AUTOMAKE([OPTIONS])
+# -----------------------------------------------
+# The call with PACKAGE and VERSION arguments is the old style
+# call (pre autoconf-2.50), which is being phased out.  PACKAGE
+# and VERSION should now be passed to AC_INIT and removed from
+# the call to AM_INIT_AUTOMAKE.
+# We support both call styles for the transition.  After
+# the next Automake release, Autoconf can make the AC_INIT
+# arguments mandatory, and then we can depend on a new Autoconf
+# release and drop the old call support.
+AC_DEFUN([AM_INIT_AUTOMAKE],
+[AC_PREREQ([2.62])dnl
+dnl Autoconf wants to disallow AM_ names.  We explicitly allow
+dnl the ones we care about.
+m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl
+AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl
+AC_REQUIRE([AC_PROG_INSTALL])dnl
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+  # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+  # is not polluted with repeated "-I."
+  AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl
+  # test to see if srcdir already configured
+  if test -f $srcdir/config.status; then
+    AC_MSG_ERROR([source directory already configured; run "make distclean" there first])
+  fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+  if (cygpath --version) >/dev/null 2>/dev/null; then
+    CYGPATH_W='cygpath -w'
+  else
+    CYGPATH_W=echo
+  fi
+fi
+AC_SUBST([CYGPATH_W])
+
+# Define the identity of the package.
+dnl Distinguish between old-style and new-style calls.
+m4_ifval([$2],
+[m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl
+ AC_SUBST([PACKAGE], [$1])dnl
+ AC_SUBST([VERSION], [$2])],
+[_AM_SET_OPTIONS([$1])dnl
+dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT.
+m4_if(m4_ifdef([AC_PACKAGE_NAME], 1)m4_ifdef([AC_PACKAGE_VERSION], 1), 11,,
+  [m4_fatal([AC_INIT should be called with package and version arguments])])dnl
+ AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl
+ AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl
+
+_AM_IF_OPTION([no-define],,
+[AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package])
+ AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package])])dnl
+
+# Some tools Automake needs.
+AC_REQUIRE([AM_SANITY_CHECK])dnl
+AC_REQUIRE([AC_ARG_PROGRAM])dnl
+AM_MISSING_PROG(ACLOCAL, aclocal-${am__api_version})
+AM_MISSING_PROG(AUTOCONF, autoconf)
+AM_MISSING_PROG(AUTOMAKE, automake-${am__api_version})
+AM_MISSING_PROG(AUTOHEADER, autoheader)
+AM_MISSING_PROG(MAKEINFO, makeinfo)
+AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl
+AC_REQUIRE([AM_PROG_MKDIR_P])dnl
+# We need awk for the "check" target.  The system "awk" is bad on
+# some platforms.
+AC_REQUIRE([AC_PROG_AWK])dnl
+AC_REQUIRE([AC_PROG_MAKE_SET])dnl
+AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])],
+             [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])],
+                            [_AM_PROG_TAR([v7])])])
+_AM_IF_OPTION([no-dependencies],,
+[AC_PROVIDE_IFELSE([AC_PROG_CC],
+                 [_AM_DEPENDENCIES(CC)],
+                 [define([AC_PROG_CC],
+                         defn([AC_PROG_CC])[_AM_DEPENDENCIES(CC)])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_CXX],
+                 [_AM_DEPENDENCIES(CXX)],
+                 [define([AC_PROG_CXX],
+                         defn([AC_PROG_CXX])[_AM_DEPENDENCIES(CXX)])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_OBJC],
+                 [_AM_DEPENDENCIES(OBJC)],
+                 [define([AC_PROG_OBJC],
+                         defn([AC_PROG_OBJC])[_AM_DEPENDENCIES(OBJC)])])dnl
+])
+_AM_IF_OPTION([silent-rules], [AC_REQUIRE([AM_SILENT_RULES])])dnl
+dnl The `parallel-tests' driver may need to know about EXEEXT, so add the
+dnl `am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen.  This macro
+dnl is hooked onto _AC_COMPILER_EXEEXT early, see below.
+AC_CONFIG_COMMANDS_PRE(dnl
+[m4_provide_if([_AM_COMPILER_EXEEXT],
+  [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl
+])
+
+dnl Hook into `_AC_COMPILER_EXEEXT' early to learn its expansion.  Do not
+dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further
+dnl mangled by Autoconf and run in a shell conditional statement.
+m4_define([_AC_COMPILER_EXEEXT],
+m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])])
+
+
+# When config.status generates a header, we must update the stamp-h file.
+# This file resides in the same directory as the config header
+# that is generated.  The stamp files are numbered to have different names.
+
+# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the
+# loop where config.status creates the headers, so we can generate
+# our stamp files there.
+AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK],
+[# Compute $1's index in $config_headers.
+_am_arg=$1
+_am_stamp_count=1
+for _am_header in $config_headers :; do
+  case $_am_header in
+    $_am_arg | $_am_arg:* )
+      break ;;
+    * )
+      _am_stamp_count=`expr $_am_stamp_count + 1` ;;
+  esac
+done
+echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
+
+# Copyright (C) 2001, 2003, 2005, 2008, 2011 Free Software Foundation,
+# Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 1
+
+# AM_PROG_INSTALL_SH
+# ------------------
+# Define $install_sh.
+AC_DEFUN([AM_PROG_INSTALL_SH],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+if test x"${install_sh}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\    *)
+    install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+  *)
+    install_sh="\${SHELL} $am_aux_dir/install-sh"
+  esac
+fi
+AC_SUBST(install_sh)])
+
+# Copyright (C) 2003, 2005  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 2
+
+# Check whether the underlying file-system supports filenames
+# with a leading dot.  For instance MS-DOS doesn't.
+AC_DEFUN([AM_SET_LEADING_DOT],
+[rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+  am__leading_dot=.
+else
+  am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+AC_SUBST([am__leading_dot])])
+
+# Check to see how 'make' treats includes.                 -*- Autoconf -*-
+
+# Copyright (C) 2001, 2002, 2003, 2005, 2009  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 4
+
+# AM_MAKE_INCLUDE()
+# -----------------
+# Check to see how make treats includes.
+AC_DEFUN([AM_MAKE_INCLUDE],
+[am_make=${MAKE-make}
+cat > confinc << 'END'
+am__doit:
+       @echo this is the am__doit target
+.PHONY: am__doit
+END
+# If we don't find an include directive, just comment out the code.
+AC_MSG_CHECKING([for style of include used by $am_make])
+am__include="#"
+am__quote=
+_am_result=none
+# First try GNU make style include.
+echo "include confinc" > confmf
+# Ignore all kinds of additional output from `make'.
+case `$am_make -s -f confmf 2> /dev/null` in #(
+*the\ am__doit\ target*)
+  am__include=include
+  am__quote=
+  _am_result=GNU
+  ;;
+esac
+# Now try BSD make style include.
+if test "$am__include" = "#"; then
+   echo '.include "confinc"' > confmf
+   case `$am_make -s -f confmf 2> /dev/null` in #(
+   *the\ am__doit\ target*)
+     am__include=.include
+     am__quote="\""
+     _am_result=BSD
+     ;;
+   esac
+fi
+AC_SUBST([am__include])
+AC_SUBST([am__quote])
+AC_MSG_RESULT([$_am_result])
+rm -f confinc confmf
+])
+
+# Fake the existence of programs that GNU maintainers use.  -*- Autoconf -*-
+
+# Copyright (C) 1997, 1999, 2000, 2001, 2003, 2004, 2005, 2008
+# Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 6
+
+# AM_MISSING_PROG(NAME, PROGRAM)
+# ------------------------------
+AC_DEFUN([AM_MISSING_PROG],
+[AC_REQUIRE([AM_MISSING_HAS_RUN])
+$1=${$1-"${am_missing_run}$2"}
+AC_SUBST($1)])
+
+
+# AM_MISSING_HAS_RUN
+# ------------------
+# Define MISSING if not defined so far and test if it supports --run.
+# If it does, set am_missing_run to use it, otherwise, to nothing.
+AC_DEFUN([AM_MISSING_HAS_RUN],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([missing])dnl
+if test x"${MISSING+set}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\    *)
+    MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
+  *)
+    MISSING="\${SHELL} $am_aux_dir/missing" ;;
+  esac
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --run true"; then
+  am_missing_run="$MISSING --run "
+else
+  am_missing_run=
+  AC_MSG_WARN([`missing' script is too old or missing])
+fi
+])
+
+# Copyright (C) 2003, 2004, 2005, 2006, 2011 Free Software Foundation,
+# Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 1
+
+# AM_PROG_MKDIR_P
+# ---------------
+# Check for `mkdir -p'.
+AC_DEFUN([AM_PROG_MKDIR_P],
+[AC_PREREQ([2.60])dnl
+AC_REQUIRE([AC_PROG_MKDIR_P])dnl
+dnl Automake 1.8 to 1.9.6 used to define mkdir_p.  We now use MKDIR_P,
+dnl while keeping a definition of mkdir_p for backward compatibility.
+dnl @MKDIR_P@ is magic: AC_OUTPUT adjusts its value for each Makefile.
+dnl However we cannot define mkdir_p as $(MKDIR_P) for the sake of
+dnl Makefile.ins that do not define MKDIR_P, so we do our own
+dnl adjustment using top_builddir (which is defined more often than
+dnl MKDIR_P).
+AC_SUBST([mkdir_p], ["$MKDIR_P"])dnl
+case $mkdir_p in
+  [[\\/$]]* | ?:[[\\/]]*) ;;
+  */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;;
+esac
+])
+
+# Helper functions for option handling.                     -*- Autoconf -*-
+
+# Copyright (C) 2001, 2002, 2003, 2005, 2008, 2010 Free Software
+# Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 5
+
+# _AM_MANGLE_OPTION(NAME)
+# -----------------------
+AC_DEFUN([_AM_MANGLE_OPTION],
+[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])])
+
+# _AM_SET_OPTION(NAME)
+# --------------------
+# Set option NAME.  Presently that only means defining a flag for this option.
+AC_DEFUN([_AM_SET_OPTION],
+[m4_define(_AM_MANGLE_OPTION([$1]), 1)])
+
+# _AM_SET_OPTIONS(OPTIONS)
+# ------------------------
+# OPTIONS is a space-separated list of Automake options.
+AC_DEFUN([_AM_SET_OPTIONS],
+[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])])
+
+# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET])
+# -------------------------------------------
+# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
+AC_DEFUN([_AM_IF_OPTION],
+[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
+
+# Check to make sure that the build environment is sane.    -*- Autoconf -*-
+
+# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005, 2008
+# Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 5
+
+# AM_SANITY_CHECK
+# ---------------
+AC_DEFUN([AM_SANITY_CHECK],
+[AC_MSG_CHECKING([whether build environment is sane])
+# Just in case
+sleep 1
+echo timestamp > conftest.file
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name.  Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+  *[[\\\"\#\$\&\'\`$am_lf]]*)
+    AC_MSG_ERROR([unsafe absolute working directory name]);;
+esac
+case $srcdir in
+  *[[\\\"\#\$\&\'\`$am_lf\ \   ]]*)
+    AC_MSG_ERROR([unsafe srcdir value: `$srcdir']);;
+esac
+
+# Do `set' in a subshell so we don't clobber the current shell's
+# arguments.  Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+   set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+   if test "$[*]" = "X"; then
+      # -L didn't work.
+      set X `ls -t "$srcdir/configure" conftest.file`
+   fi
+   rm -f conftest.file
+   if test "$[*]" != "X $srcdir/configure conftest.file" \
+      && test "$[*]" != "X conftest.file $srcdir/configure"; then
+
+      # If neither matched, then we have a broken ls.  This can happen
+      # if, for instance, CONFIG_SHELL is bash and it inherits a
+      # broken ls alias from the environment.  This has actually
+      # happened.  Such a system could not be considered "sane".
+      AC_MSG_ERROR([ls -t appears to fail.  Make sure there is not a broken
+alias in your environment])
+   fi
+
+   test "$[2]" = conftest.file
+   )
+then
+   # Ok.
+   :
+else
+   AC_MSG_ERROR([newly created file is older than distributed files!
+Check your system clock])
+fi
+AC_MSG_RESULT(yes)])
+
+# Copyright (C) 2009, 2011  Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 2
+
+# AM_SILENT_RULES([DEFAULT])
+# --------------------------
+# Enable less verbose build rules; with the default set to DEFAULT
+# (`yes' being less verbose, `no' or empty being verbose).
+AC_DEFUN([AM_SILENT_RULES],
+[AC_ARG_ENABLE([silent-rules],
+[  --enable-silent-rules          less verbose build output (undo: `make V=1')
+  --disable-silent-rules         verbose build output (undo: `make V=0')])
+case $enable_silent_rules in
+yes) AM_DEFAULT_VERBOSITY=0;;
+no)  AM_DEFAULT_VERBOSITY=1;;
+*)   AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);;
+esac
+dnl
+dnl A few `make' implementations (e.g., NonStop OS and NextStep)
+dnl do not support nested variable expansions.
+dnl See automake bug#9928 and bug#10237.
+am_make=${MAKE-make}
+AC_CACHE_CHECK([whether $am_make supports nested variables],
+   [am_cv_make_support_nested_variables],
+   [if AS_ECHO([['TRUE=$(BAR$(V))
+BAR0=false
+BAR1=true
+V=1
+am__doit:
+       @$(TRUE)
+.PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then
+  am_cv_make_support_nested_variables=yes
+else
+  am_cv_make_support_nested_variables=no
+fi])
+if test $am_cv_make_support_nested_variables = yes; then
+  dnl Using `$V' instead of `$(V)' breaks IRIX make.
+  AM_V='$(V)'
+  AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)'
+else
+  AM_V=$AM_DEFAULT_VERBOSITY
+  AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY
+fi
+AC_SUBST([AM_V])dnl
+AM_SUBST_NOTMAKE([AM_V])dnl
+AC_SUBST([AM_DEFAULT_V])dnl
+AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl
+AC_SUBST([AM_DEFAULT_VERBOSITY])dnl
+AM_BACKSLASH='\'
+AC_SUBST([AM_BACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl
+])
+
+# Copyright (C) 2001, 2003, 2005, 2011 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 1
+
+# AM_PROG_INSTALL_STRIP
+# ---------------------
+# One issue with vendor `install' (even GNU) is that you can't
+# specify the program used to strip binaries.  This is especially
+# annoying in cross-compiling environments, where the build's strip
+# is unlikely to handle the host's binaries.
+# Fortunately install-sh will honor a STRIPPROG variable, so we
+# always use install-sh in `make install-strip', and initialize
+# STRIPPROG with the value of the STRIP variable (set by the user).
+AC_DEFUN([AM_PROG_INSTALL_STRIP],
+[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+# Installed binaries are usually stripped using `strip' when the user
+# run `make install-strip'.  However `strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the `STRIP' environment variable to overrule this program.
+dnl Don't test for $cross_compiling = yes, because it might be `maybe'.
+if test "$cross_compiling" != no; then
+  AC_CHECK_TOOL([STRIP], [strip], :)
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+AC_SUBST([INSTALL_STRIP_PROGRAM])])
+
+# Copyright (C) 2006, 2008, 2010 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 3
+
+# _AM_SUBST_NOTMAKE(VARIABLE)
+# ---------------------------
+# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in.
+# This macro is traced by Automake.
+AC_DEFUN([_AM_SUBST_NOTMAKE])
+
+# AM_SUBST_NOTMAKE(VARIABLE)
+# --------------------------
+# Public sister of _AM_SUBST_NOTMAKE.
+AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
+
+# Check how to create a tarball.                            -*- Autoconf -*-
+
+# Copyright (C) 2004, 2005, 2012 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# serial 2
+
+# _AM_PROG_TAR(FORMAT)
+# --------------------
+# Check how to create a tarball in format FORMAT.
+# FORMAT should be one of `v7', `ustar', or `pax'.
+#
+# Substitute a variable $(am__tar) that is a command
+# writing to stdout a FORMAT-tarball containing the directory
+# $tardir.
+#     tardir=directory && $(am__tar) > result.tar
+#
+# Substitute a variable $(am__untar) that extract such
+# a tarball read from stdin.
+#     $(am__untar) < result.tar
+AC_DEFUN([_AM_PROG_TAR],
+[# Always define AMTAR for backward compatibility.  Yes, it's still used
+# in the wild :-(  We should find a proper way to deprecate it ...
+AC_SUBST([AMTAR], ['$${TAR-tar}'])
+m4_if([$1], [v7],
+     [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'],
+     [m4_case([$1], [ustar],, [pax],,
+              [m4_fatal([Unknown tar format])])
+AC_MSG_CHECKING([how to create a $1 tar archive])
+# Loop over all known methods to create a tar archive until one works.
+_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none'
+_am_tools=${am_cv_prog_tar_$1-$_am_tools}
+# Do not fold the above two line into one, because Tru64 sh and
+# Solaris sh will not grok spaces in the rhs of `-'.
+for _am_tool in $_am_tools
+do
+  case $_am_tool in
+  gnutar)
+    for _am_tar in tar gnutar gtar;
+    do
+      AM_RUN_LOG([$_am_tar --version]) && break
+    done
+    am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"'
+    am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"'
+    am__untar="$_am_tar -xf -"
+    ;;
+  plaintar)
+    # Must skip GNU tar: if it does not support --format= it doesn't create
+    # ustar tarball either.
+    (tar --version) >/dev/null 2>&1 && continue
+    am__tar='tar chf - "$$tardir"'
+    am__tar_='tar chf - "$tardir"'
+    am__untar='tar xf -'
+    ;;
+  pax)
+    am__tar='pax -L -x $1 -w "$$tardir"'
+    am__tar_='pax -L -x $1 -w "$tardir"'
+    am__untar='pax -r'
+    ;;
+  cpio)
+    am__tar='find "$$tardir" -print | cpio -o -H $1 -L'
+    am__tar_='find "$tardir" -print | cpio -o -H $1 -L'
+    am__untar='cpio -i -H $1 -d'
+    ;;
+  none)
+    am__tar=false
+    am__tar_=false
+    am__untar=false
+    ;;
+  esac
+
+  # If the value was cached, stop now.  We just wanted to have am__tar
+  # and am__untar set.
+  test -n "${am_cv_prog_tar_$1}" && break
+
+  # tar/untar a dummy directory, and stop if the command works
+  rm -rf conftest.dir
+  mkdir conftest.dir
+  echo GrepMe > conftest.dir/file
+  AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar])
+  rm -rf conftest.dir
+  if test -s conftest.tar; then
+    AM_RUN_LOG([$am__untar <conftest.tar])
+    grep GrepMe conftest.dir/file >/dev/null 2>&1 && break
+  fi
+done
+rm -rf conftest.dir
+
+AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool])
+AC_MSG_RESULT([$am_cv_prog_tar_$1])])
+AC_SUBST([am__tar])
+AC_SUBST([am__untar])
+]) # _AM_PROG_TAR
+
diff --git a/autobuild b/autobuild
new file mode 100755 (executable)
index 0000000..b64bde9
--- /dev/null
+++ b/autobuild
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+#  autobuild
+
+libtoolize -c -f
+aclocal
+autoheader -f
+autoconf
+automake -a -c -f
+
diff --git a/cmake/Configure.cmake b/cmake/Configure.cmake
new file mode 100644 (file)
index 0000000..ca2729f
--- /dev/null
@@ -0,0 +1,134 @@
+################################################################################\r
+#\r
+# configure\r
+#\r
+################################################################################\r
+\r
+########################################\r
+# FUNCTION check_includes\r
+########################################\r
+function(check_includes files)\r
+    foreach(F ${${files}})\r
+        set(name ${F})\r
+        string(REPLACE "-" "_" name ${name})\r
+        string(REPLACE "." "_" name ${name})\r
+        string(REPLACE "/" "_" name ${name})\r
+        string(TOUPPER ${name} name)\r
+        check_include_files(${F} HAVE_${name})\r
+        file(APPEND ${AUTOCONFIG_SRC} "/* Define to 1 if you have the <${F}> header file. */\n")\r
+        file(APPEND ${AUTOCONFIG_SRC} "#cmakedefine HAVE_${name} 1\n")\r
+        file(APPEND ${AUTOCONFIG_SRC} "\n")\r
+    endforeach()\r
+endfunction(check_includes)\r
+\r
+########################################\r
+# FUNCTION check_functions\r
+########################################\r
+function(check_functions functions)\r
+    foreach(F ${${functions}})\r
+        set(name ${F})\r
+        string(TOUPPER ${name} name)\r
+        check_function_exists(${F} HAVE_${name})\r
+        file(APPEND ${AUTOCONFIG_SRC} "/* Define to 1 if you have the `${F}' function. */\n")\r
+        file(APPEND ${AUTOCONFIG_SRC} "#cmakedefine HAVE_${name} 1\n")\r
+        file(APPEND ${AUTOCONFIG_SRC} "\n")\r
+    endforeach()\r
+endfunction(check_functions)\r
+\r
+########################################\r
+\r
+file(WRITE ${AUTOCONFIG_SRC})\r
+\r
+include(CheckCSourceCompiles)\r
+include(CheckCSourceRuns)\r
+include(CheckCXXSourceCompiles)\r
+include(CheckCXXSourceRuns)\r
+include(CheckFunctionExists)\r
+include(CheckIncludeFiles)\r
+include(CheckLibraryExists)\r
+include(CheckPrototypeDefinition)\r
+include(CheckStructHasMember)\r
+include(CheckSymbolExists)\r
+include(CheckTypeSize)\r
+include(TestBigEndian)\r
+\r
+set(include_files_list\r
+    dlfcn.h\r
+    inttypes.h\r
+    memory.h\r
+    stdint.h\r
+    stdlib.h\r
+    strings.h\r
+    string.h\r
+    sys/stat.h\r
+    sys/types.h\r
+    unistd.h\r
+\r
+    openjpeg-2.0/openjpeg.h\r
+    openjpeg-2.1/openjpeg.h\r
+    openjpeg-2.2/openjpeg.h\r
+)\r
+check_includes(include_files_list)\r
+\r
+set(functions_list\r
+    fmemopen\r
+)\r
+check_functions(functions_list)\r
+\r
+test_big_endian(WORDS_BIGENDIAN)\r
+\r
+set(STDC_HEADERS 1)\r
+\r
+if (GIF_FOUND)\r
+    set(HAVE_LIBGIF 1)\r
+endif()\r
+\r
+if (JPEG_FOUND)\r
+    set(HAVE_LIBJPEG 1)\r
+endif()\r
+\r
+if (PNG_FOUND)\r
+    set(HAVE_LIBPNG 1)\r
+endif()\r
+\r
+if (TIFF_FOUND)\r
+    set(HAVE_LIBTIFF 1)\r
+endif()\r
+\r
+if (ZLIB_FOUND)\r
+    set(HAVE_LIBZ 1)\r
+endif()\r
+\r
+file(APPEND ${AUTOCONFIG_SRC} "\r
+/* Define to 1 if you have the ANSI C header files. */\r
+#cmakedefine STDC_HEADERS 1\r
+\r
+/* Define to 1 if you have giflib. */\r
+#cmakedefine HAVE_LIBGIF 1\r
+\r
+/* Define to 1 if you have libopenjp2. */\r
+#cmakedefine HAVE_LIBJP2K 1\r
+\r
+/* Define to 1 if you have jpeg. */\r
+#cmakedefine HAVE_LIBJPEG 1\r
+\r
+/* Define to 1 if you have libpng. */\r
+#cmakedefine HAVE_LIBPNG 1\r
+\r
+/* Define to 1 if you have libtiff. */\r
+#cmakedefine HAVE_LIBTIFF 1\r
+\r
+/* Define to 1 if you have libwebp. */\r
+#cmakedefine HAVE_LIBWEBP 1\r
+\r
+/* Define to 1 if you have zlib. */\r
+#cmakedefine HAVE_LIBZ 1\r
+\r
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most\r
+   significant byte first (like Motorola and SPARC, unlike Intel). */\r
+#cmakedefine WORDS_BIGENDIAN 1\r
+")\r
+\r
+########################################\r
+\r
+################################################################################\r
diff --git a/cmake/templates/LeptonicaConfig-version.cmake.in b/cmake/templates/LeptonicaConfig-version.cmake.in
new file mode 100644 (file)
index 0000000..bfc2938
--- /dev/null
@@ -0,0 +1,14 @@
+set(Leptonica_VERSION @VERSION_PLAIN@)
+set(PACKAGE_VERSION ${Leptonica_VERSION})
+
+set(PACKAGE_VERSION_EXACT False)
+set(PACKAGE_VERSION_COMPATIBLE False)
+
+if(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION)
+  set(PACKAGE_VERSION_EXACT True)
+  set(PACKAGE_VERSION_COMPATIBLE True)
+endif()
+
+if(PACKAGE_FIND_VERSION VERSION_LESS PACKAGE_VERSION)
+  set(PACKAGE_VERSION_COMPATIBLE True)
+endif()
diff --git a/cmake/templates/LeptonicaConfig.cmake.in b/cmake/templates/LeptonicaConfig.cmake.in
new file mode 100644 (file)
index 0000000..ca8721a
--- /dev/null
@@ -0,0 +1,43 @@
+# ===================================================================================
+#  The Leptonica CMake configuration file
+#
+#             ** File generated automatically, do not modify **
+#
+#  Usage from an external project:
+#    In your CMakeLists.txt, add these lines:
+#
+#    find_package(Leptonica REQUIRED)
+#    include_directories(${Leptonica_INCLUDE_DIRS})
+#    target_link_libraries(MY_TARGET_NAME ${Leptonica_LIBRARIES})
+#
+#    This file will define the following variables:
+#      - Leptonica_LIBRARIES             : The list of all imported targets for OpenCV modules.
+#      - Leptonica_INCLUDE_DIRS          : The Leptonica include directories.
+#      - Leptonica_VERSION               : The version of this Leptonica build: "@VERSION_PLAIN@"
+#      - Leptonica_VERSION_MAJOR         : Major version part of Leptonica_VERSION: "@VERSION_MAJOR@"
+#      - Leptonica_VERSION_MINOR         : Minor version part of Leptonica_VERSION: "@VERSION_MINOR@"
+#
+# ===================================================================================
+
+include(${CMAKE_CURRENT_LIST_DIR}/LeptonicaTargets.cmake)
+
+# ======================================================
+#  Version variables:
+# ======================================================
+
+SET(Leptonica_VERSION           @VERSION_PLAIN@)
+SET(Leptonica_VERSION_MAJOR     @VERSION_MAJOR@)
+SET(Leptonica_VERSION_MINOR     @VERSION_MINOR@)
+
+# ======================================================
+# Include directories to add to the user project:
+# ======================================================
+
+# Provide the include directories to the caller
+set(Leptonica_INCLUDE_DIRS @INCLUDE_DIR@)
+
+# ====================================================================
+# Link libraries:
+# ====================================================================
+
+set(Leptonica_LIBRARIES leptonica)
diff --git a/config/config.guess b/config/config.guess
new file mode 100755 (executable)
index 0000000..d622a44
--- /dev/null
@@ -0,0 +1,1530 @@
+#! /bin/sh
+# Attempt to guess a canonical system name.
+#   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+#   2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+#   2011, 2012 Free Software Foundation, Inc.
+
+timestamp='2012-02-10'
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+
+# Originally written by Per Bothner.  Please send patches (context
+# diff format) to <config-patches@gnu.org> and include a ChangeLog
+# entry.
+#
+# This script attempts to guess a canonical system name similar to
+# config.sub.  If it succeeds, it prints the system name on stdout, and
+# exits with 0.  Otherwise, it exits with 1.
+#
+# You can get the latest version of this script from:
+# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION]
+
+Output the configuration name of the system \`$me' is run on.
+
+Operation modes:
+  -h, --help         print this help, then exit
+  -t, --time-stamp   print date of last modification, then exit
+  -v, --version      print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.guess ($timestamp)
+
+Originally written by Per Bothner.
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000,
+2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
+Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+  case $1 in
+    --time-stamp | --time* | -t )
+       echo "$timestamp" ; exit ;;
+    --version | -v )
+       echo "$version" ; exit ;;
+    --help | --h* | -h )
+       echo "$usage"; exit ;;
+    -- )     # Stop option processing
+       shift; break ;;
+    - )        # Use stdin as input.
+       break ;;
+    -* )
+       echo "$me: invalid option $1$help" >&2
+       exit 1 ;;
+    * )
+       break ;;
+  esac
+done
+
+if test $# != 0; then
+  echo "$me: too many arguments$help" >&2
+  exit 1
+fi
+
+trap 'exit 1' 1 2 15
+
+# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
+# compiler to aid in system detection is discouraged as it requires
+# temporary files to be created and, as you can see below, it is a
+# headache to deal with in a portable fashion.
+
+# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+# use `HOST_CC' if defined, but it is deprecated.
+
+# Portable tmp directory creation inspired by the Autoconf team.
+
+set_cc_for_build='
+trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ;
+trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ;
+: ${TMPDIR=/tmp} ;
+ { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
+ { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } ||
+ { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } ||
+ { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ;
+dummy=$tmp/dummy ;
+tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ;
+case $CC_FOR_BUILD,$HOST_CC,$CC in
+ ,,)    echo "int x;" > $dummy.c ;
+       for c in cc gcc c89 c99 ; do
+         if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then
+            CC_FOR_BUILD="$c"; break ;
+         fi ;
+       done ;
+       if test x"$CC_FOR_BUILD" = x ; then
+         CC_FOR_BUILD=no_compiler_found ;
+       fi
+       ;;
+ ,,*)   CC_FOR_BUILD=$CC ;;
+ ,*,*)  CC_FOR_BUILD=$HOST_CC ;;
+esac ; set_cc_for_build= ;'
+
+# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
+# (ghazi@noc.rutgers.edu 1994-08-24)
+if (test -f /.attbin/uname) >/dev/null 2>&1 ; then
+       PATH=$PATH:/.attbin ; export PATH
+fi
+
+UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
+UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
+UNAME_SYSTEM=`(uname -s) 2>/dev/null`  || UNAME_SYSTEM=unknown
+UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
+
+# Note: order is significant - the case branches are not exclusive.
+
+case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
+    *:NetBSD:*:*)
+       # NetBSD (nbsd) targets should (where applicable) match one or
+       # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*,
+       # *-*-netbsdecoff* and *-*-netbsd*.  For targets that recently
+       # switched to ELF, *-*-netbsd* would select the old
+       # object file format.  This provides both forward
+       # compatibility and a consistent mechanism for selecting the
+       # object file format.
+       #
+       # Note: NetBSD doesn't particularly care about the vendor
+       # portion of the name.  We always set it to "unknown".
+       sysctl="sysctl -n hw.machine_arch"
+       UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \
+           /usr/sbin/$sysctl 2>/dev/null || echo unknown)`
+       case "${UNAME_MACHINE_ARCH}" in
+           armeb) machine=armeb-unknown ;;
+           arm*) machine=arm-unknown ;;
+           sh3el) machine=shl-unknown ;;
+           sh3eb) machine=sh-unknown ;;
+           sh5el) machine=sh5le-unknown ;;
+           *) machine=${UNAME_MACHINE_ARCH}-unknown ;;
+       esac
+       # The Operating System including object format, if it has switched
+       # to ELF recently, or will in the future.
+       case "${UNAME_MACHINE_ARCH}" in
+           arm*|i386|m68k|ns32k|sh3*|sparc|vax)
+               eval $set_cc_for_build
+               if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
+                       | grep -q __ELF__
+               then
+                   # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
+                   # Return netbsd for either.  FIX?
+                   os=netbsd
+               else
+                   os=netbsdelf
+               fi
+               ;;
+           *)
+               os=netbsd
+               ;;
+       esac
+       # The OS release
+       # Debian GNU/NetBSD machines have a different userland, and
+       # thus, need a distinct triplet. However, they do not need
+       # kernel version information, so it can be replaced with a
+       # suitable tag, in the style of linux-gnu.
+       case "${UNAME_VERSION}" in
+           Debian*)
+               release='-gnu'
+               ;;
+           *)
+               release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'`
+               ;;
+       esac
+       # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
+       # contains redundant information, the shorter form:
+       # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
+       echo "${machine}-${os}${release}"
+       exit ;;
+    *:OpenBSD:*:*)
+       UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'`
+       echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE}
+       exit ;;
+    *:ekkoBSD:*:*)
+       echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE}
+       exit ;;
+    *:SolidBSD:*:*)
+       echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE}
+       exit ;;
+    macppc:MirBSD:*:*)
+       echo powerpc-unknown-mirbsd${UNAME_RELEASE}
+       exit ;;
+    *:MirBSD:*:*)
+       echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE}
+       exit ;;
+    alpha:OSF1:*:*)
+       case $UNAME_RELEASE in
+       *4.0)
+               UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
+               ;;
+       *5.*)
+               UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'`
+               ;;
+       esac
+       # According to Compaq, /usr/sbin/psrinfo has been available on
+       # OSF/1 and Tru64 systems produced since 1995.  I hope that
+       # covers most systems running today.  This code pipes the CPU
+       # types through head -n 1, so we only detect the type of CPU 0.
+       ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^  The alpha \(.*\) processor.*$/\1/p' | head -n 1`
+       case "$ALPHA_CPU_TYPE" in
+           "EV4 (21064)")
+               UNAME_MACHINE="alpha" ;;
+           "EV4.5 (21064)")
+               UNAME_MACHINE="alpha" ;;
+           "LCA4 (21066/21068)")
+               UNAME_MACHINE="alpha" ;;
+           "EV5 (21164)")
+               UNAME_MACHINE="alphaev5" ;;
+           "EV5.6 (21164A)")
+               UNAME_MACHINE="alphaev56" ;;
+           "EV5.6 (21164PC)")
+               UNAME_MACHINE="alphapca56" ;;
+           "EV5.7 (21164PC)")
+               UNAME_MACHINE="alphapca57" ;;
+           "EV6 (21264)")
+               UNAME_MACHINE="alphaev6" ;;
+           "EV6.7 (21264A)")
+               UNAME_MACHINE="alphaev67" ;;
+           "EV6.8CB (21264C)")
+               UNAME_MACHINE="alphaev68" ;;
+           "EV6.8AL (21264B)")
+               UNAME_MACHINE="alphaev68" ;;
+           "EV6.8CX (21264D)")
+               UNAME_MACHINE="alphaev68" ;;
+           "EV6.9A (21264/EV69A)")
+               UNAME_MACHINE="alphaev69" ;;
+           "EV7 (21364)")
+               UNAME_MACHINE="alphaev7" ;;
+           "EV7.9 (21364A)")
+               UNAME_MACHINE="alphaev79" ;;
+       esac
+       # A Pn.n version is a patched version.
+       # A Vn.n version is a released version.
+       # A Tn.n version is a released field test version.
+       # A Xn.n version is an unreleased experimental baselevel.
+       # 1.2 uses "1.2" for uname -r.
+       echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+       # Reset EXIT trap before exiting to avoid spurious non-zero exit code.
+       exitcode=$?
+       trap '' 0
+       exit $exitcode ;;
+    Alpha\ *:Windows_NT*:*)
+       # How do we know it's Interix rather than the generic POSIX subsystem?
+       # Should we change UNAME_MACHINE based on the output of uname instead
+       # of the specific Alpha model?
+       echo alpha-pc-interix
+       exit ;;
+    21064:Windows_NT:50:3)
+       echo alpha-dec-winnt3.5
+       exit ;;
+    Amiga*:UNIX_System_V:4.0:*)
+       echo m68k-unknown-sysv4
+       exit ;;
+    *:[Aa]miga[Oo][Ss]:*:*)
+       echo ${UNAME_MACHINE}-unknown-amigaos
+       exit ;;
+    *:[Mm]orph[Oo][Ss]:*:*)
+       echo ${UNAME_MACHINE}-unknown-morphos
+       exit ;;
+    *:OS/390:*:*)
+       echo i370-ibm-openedition
+       exit ;;
+    *:z/VM:*:*)
+       echo s390-ibm-zvmoe
+       exit ;;
+    *:OS400:*:*)
+       echo powerpc-ibm-os400
+       exit ;;
+    arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
+       echo arm-acorn-riscix${UNAME_RELEASE}
+       exit ;;
+    arm:riscos:*:*|arm:RISCOS:*:*)
+       echo arm-unknown-riscos
+       exit ;;
+    SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
+       echo hppa1.1-hitachi-hiuxmpp
+       exit ;;
+    Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
+       # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
+       if test "`(/bin/universe) 2>/dev/null`" = att ; then
+               echo pyramid-pyramid-sysv3
+       else
+               echo pyramid-pyramid-bsd
+       fi
+       exit ;;
+    NILE*:*:*:dcosx)
+       echo pyramid-pyramid-svr4
+       exit ;;
+    DRS?6000:unix:4.0:6*)
+       echo sparc-icl-nx6
+       exit ;;
+    DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*)
+       case `/usr/bin/uname -p` in
+           sparc) echo sparc-icl-nx7; exit ;;
+       esac ;;
+    s390x:SunOS:*:*)
+       echo ${UNAME_MACHINE}-ibm-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit ;;
+    sun4H:SunOS:5.*:*)
+       echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit ;;
+    sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
+       echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit ;;
+    i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*)
+       echo i386-pc-auroraux${UNAME_RELEASE}
+       exit ;;
+    i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*)
+       eval $set_cc_for_build
+       SUN_ARCH="i386"
+       # If there is a compiler, see if it is configured for 64-bit objects.
+       # Note that the Sun cc does not turn __LP64__ into 1 like gcc does.
+       # This test works for both compilers.
+       if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then
+           if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \
+               (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \
+               grep IS_64BIT_ARCH >/dev/null
+           then
+               SUN_ARCH="x86_64"
+           fi
+       fi
+       echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit ;;
+    sun4*:SunOS:6*:*)
+       # According to config.sub, this is the proper way to canonicalize
+       # SunOS6.  Hard to guess exactly what SunOS6 will be like, but
+       # it's likely to be more like Solaris than SunOS4.
+       echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit ;;
+    sun4*:SunOS:*:*)
+       case "`/usr/bin/arch -k`" in
+           Series*|S4*)
+               UNAME_RELEASE=`uname -v`
+               ;;
+       esac
+       # Japanese Language versions have a version number like `4.1.3-JL'.
+       echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'`
+       exit ;;
+    sun3*:SunOS:*:*)
+       echo m68k-sun-sunos${UNAME_RELEASE}
+       exit ;;
+    sun*:*:4.2BSD:*)
+       UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
+       test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3
+       case "`/bin/arch`" in
+           sun3)
+               echo m68k-sun-sunos${UNAME_RELEASE}
+               ;;
+           sun4)
+               echo sparc-sun-sunos${UNAME_RELEASE}
+               ;;
+       esac
+       exit ;;
+    aushp:SunOS:*:*)
+       echo sparc-auspex-sunos${UNAME_RELEASE}
+       exit ;;
+    # The situation for MiNT is a little confusing.  The machine name
+    # can be virtually everything (everything which is not
+    # "atarist" or "atariste" at least should have a processor
+    # > m68000).  The system name ranges from "MiNT" over "FreeMiNT"
+    # to the lowercase version "mint" (or "freemint").  Finally
+    # the system name "TOS" denotes a system which is actually not
+    # MiNT.  But MiNT is downward compatible to TOS, so this should
+    # be no problem.
+    atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
+       echo m68k-atari-mint${UNAME_RELEASE}
+       exit ;;
+    atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
+       echo m68k-atari-mint${UNAME_RELEASE}
+       exit ;;
+    *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
+       echo m68k-atari-mint${UNAME_RELEASE}
+       exit ;;
+    milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
+       echo m68k-milan-mint${UNAME_RELEASE}
+       exit ;;
+    hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
+       echo m68k-hades-mint${UNAME_RELEASE}
+       exit ;;
+    *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
+       echo m68k-unknown-mint${UNAME_RELEASE}
+       exit ;;
+    m68k:machten:*:*)
+       echo m68k-apple-machten${UNAME_RELEASE}
+       exit ;;
+    powerpc:machten:*:*)
+       echo powerpc-apple-machten${UNAME_RELEASE}
+       exit ;;
+    RISC*:Mach:*:*)
+       echo mips-dec-mach_bsd4.3
+       exit ;;
+    RISC*:ULTRIX:*:*)
+       echo mips-dec-ultrix${UNAME_RELEASE}
+       exit ;;
+    VAX*:ULTRIX*:*:*)
+       echo vax-dec-ultrix${UNAME_RELEASE}
+       exit ;;
+    2020:CLIX:*:* | 2430:CLIX:*:*)
+       echo clipper-intergraph-clix${UNAME_RELEASE}
+       exit ;;
+    mips:*:*:UMIPS | mips:*:*:RISCos)
+       eval $set_cc_for_build
+       sed 's/^        //' << EOF >$dummy.c
+#ifdef __cplusplus
+#include <stdio.h>  /* for printf() prototype */
+       int main (int argc, char *argv[]) {
+#else
+       int main (argc, argv) int argc; char *argv[]; {
+#endif
+       #if defined (host_mips) && defined (MIPSEB)
+       #if defined (SYSTYPE_SYSV)
+         printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0);
+       #endif
+       #if defined (SYSTYPE_SVR4)
+         printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0);
+       #endif
+       #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
+         printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0);
+       #endif
+       #endif
+         exit (-1);
+       }
+EOF
+       $CC_FOR_BUILD -o $dummy $dummy.c &&
+         dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` &&
+         SYSTEM_NAME=`$dummy $dummyarg` &&
+           { echo "$SYSTEM_NAME"; exit; }
+       echo mips-mips-riscos${UNAME_RELEASE}
+       exit ;;
+    Motorola:PowerMAX_OS:*:*)
+       echo powerpc-motorola-powermax
+       exit ;;
+    Motorola:*:4.3:PL8-*)
+       echo powerpc-harris-powermax
+       exit ;;
+    Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
+       echo powerpc-harris-powermax
+       exit ;;
+    Night_Hawk:Power_UNIX:*:*)
+       echo powerpc-harris-powerunix
+       exit ;;
+    m88k:CX/UX:7*:*)
+       echo m88k-harris-cxux7
+       exit ;;
+    m88k:*:4*:R4*)
+       echo m88k-motorola-sysv4
+       exit ;;
+    m88k:*:3*:R3*)
+       echo m88k-motorola-sysv3
+       exit ;;
+    AViiON:dgux:*:*)
+       # DG/UX returns AViiON for all architectures
+       UNAME_PROCESSOR=`/usr/bin/uname -p`
+       if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ]
+       then
+           if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \
+              [ ${TARGET_BINARY_INTERFACE}x = x ]
+           then
+               echo m88k-dg-dgux${UNAME_RELEASE}
+           else
+               echo m88k-dg-dguxbcs${UNAME_RELEASE}
+           fi
+       else
+           echo i586-dg-dgux${UNAME_RELEASE}
+       fi
+       exit ;;
+    M88*:DolphinOS:*:*)        # DolphinOS (SVR3)
+       echo m88k-dolphin-sysv3
+       exit ;;
+    M88*:*:R3*:*)
+       # Delta 88k system running SVR3
+       echo m88k-motorola-sysv3
+       exit ;;
+    XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
+       echo m88k-tektronix-sysv3
+       exit ;;
+    Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
+       echo m68k-tektronix-bsd
+       exit ;;
+    *:IRIX*:*:*)
+       echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'`
+       exit ;;
+    ????????:AIX?:[12].1:2)   # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
+       echo romp-ibm-aix     # uname -m gives an 8 hex-code CPU id
+       exit ;;               # Note that: echo "'`uname -s`'" gives 'AIX '
+    i*86:AIX:*:*)
+       echo i386-ibm-aix
+       exit ;;
+    ia64:AIX:*:*)
+       if [ -x /usr/bin/oslevel ] ; then
+               IBM_REV=`/usr/bin/oslevel`
+       else
+               IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+       fi
+       echo ${UNAME_MACHINE}-ibm-aix${IBM_REV}
+       exit ;;
+    *:AIX:2:3)
+       if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
+               eval $set_cc_for_build
+               sed 's/^                //' << EOF >$dummy.c
+               #include <sys/systemcfg.h>
+
+               main()
+                       {
+                       if (!__power_pc())
+                               exit(1);
+                       puts("powerpc-ibm-aix3.2.5");
+                       exit(0);
+                       }
+EOF
+               if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy`
+               then
+                       echo "$SYSTEM_NAME"
+               else
+                       echo rs6000-ibm-aix3.2.5
+               fi
+       elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
+               echo rs6000-ibm-aix3.2.4
+       else
+               echo rs6000-ibm-aix3.2
+       fi
+       exit ;;
+    *:AIX:*:[4567])
+       IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'`
+       if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then
+               IBM_ARCH=rs6000
+       else
+               IBM_ARCH=powerpc
+       fi
+       if [ -x /usr/bin/oslevel ] ; then
+               IBM_REV=`/usr/bin/oslevel`
+       else
+               IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+       fi
+       echo ${IBM_ARCH}-ibm-aix${IBM_REV}
+       exit ;;
+    *:AIX:*:*)
+       echo rs6000-ibm-aix
+       exit ;;
+    ibmrt:4.4BSD:*|romp-ibm:BSD:*)
+       echo romp-ibm-bsd4.4
+       exit ;;
+    ibmrt:*BSD:*|romp-ibm:BSD:*)            # covers RT/PC BSD and
+       echo romp-ibm-bsd${UNAME_RELEASE}   # 4.3 with uname added to
+       exit ;;                             # report: romp-ibm BSD 4.3
+    *:BOSX:*:*)
+       echo rs6000-bull-bosx
+       exit ;;
+    DPX/2?00:B.O.S.:*:*)
+       echo m68k-bull-sysv3
+       exit ;;
+    9000/[34]??:4.3bsd:1.*:*)
+       echo m68k-hp-bsd
+       exit ;;
+    hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
+       echo m68k-hp-bsd4.4
+       exit ;;
+    9000/[34678]??:HP-UX:*:*)
+       HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+       case "${UNAME_MACHINE}" in
+           9000/31? )            HP_ARCH=m68000 ;;
+           9000/[34]?? )         HP_ARCH=m68k ;;
+           9000/[678][0-9][0-9])
+               if [ -x /usr/bin/getconf ]; then
+                   sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
+                   sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
+                   case "${sc_cpu_version}" in
+                     523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0
+                     528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1
+                     532)                      # CPU_PA_RISC2_0
+                       case "${sc_kernel_bits}" in
+                         32) HP_ARCH="hppa2.0n" ;;
+                         64) HP_ARCH="hppa2.0w" ;;
+                         '') HP_ARCH="hppa2.0" ;;   # HP-UX 10.20
+                       esac ;;
+                   esac
+               fi
+               if [ "${HP_ARCH}" = "" ]; then
+                   eval $set_cc_for_build
+                   sed 's/^            //' << EOF >$dummy.c
+
+               #define _HPUX_SOURCE
+               #include <stdlib.h>
+               #include <unistd.h>
+
+               int main ()
+               {
+               #if defined(_SC_KERNEL_BITS)
+                   long bits = sysconf(_SC_KERNEL_BITS);
+               #endif
+                   long cpu  = sysconf (_SC_CPU_VERSION);
+
+                   switch (cpu)
+                       {
+                       case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
+                       case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
+                       case CPU_PA_RISC2_0:
+               #if defined(_SC_KERNEL_BITS)
+                           switch (bits)
+                               {
+                               case 64: puts ("hppa2.0w"); break;
+                               case 32: puts ("hppa2.0n"); break;
+                               default: puts ("hppa2.0"); break;
+                               } break;
+               #else  /* !defined(_SC_KERNEL_BITS) */
+                           puts ("hppa2.0"); break;
+               #endif
+                       default: puts ("hppa1.0"); break;
+                       }
+                   exit (0);
+               }
+EOF
+                   (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy`
+                   test -z "$HP_ARCH" && HP_ARCH=hppa
+               fi ;;
+       esac
+       if [ ${HP_ARCH} = "hppa2.0w" ]
+       then
+           eval $set_cc_for_build
+
+           # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating
+           # 32-bit code.  hppa64-hp-hpux* has the same kernel and a compiler
+           # generating 64-bit code.  GNU and HP use different nomenclature:
+           #
+           # $ CC_FOR_BUILD=cc ./config.guess
+           # => hppa2.0w-hp-hpux11.23
+           # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess
+           # => hppa64-hp-hpux11.23
+
+           if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) |
+               grep -q __LP64__
+           then
+               HP_ARCH="hppa2.0w"
+           else
+               HP_ARCH="hppa64"
+           fi
+       fi
+       echo ${HP_ARCH}-hp-hpux${HPUX_REV}
+       exit ;;
+    ia64:HP-UX:*:*)
+       HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+       echo ia64-hp-hpux${HPUX_REV}
+       exit ;;
+    3050*:HI-UX:*:*)
+       eval $set_cc_for_build
+       sed 's/^        //' << EOF >$dummy.c
+       #include <unistd.h>
+       int
+       main ()
+       {
+         long cpu = sysconf (_SC_CPU_VERSION);
+         /* The order matters, because CPU_IS_HP_MC68K erroneously returns
+            true for CPU_PA_RISC1_0.  CPU_IS_PA_RISC returns correct
+            results, however.  */
+         if (CPU_IS_PA_RISC (cpu))
+           {
+             switch (cpu)
+               {
+                 case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
+                 case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
+                 case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
+                 default: puts ("hppa-hitachi-hiuxwe2"); break;
+               }
+           }
+         else if (CPU_IS_HP_MC68K (cpu))
+           puts ("m68k-hitachi-hiuxwe2");
+         else puts ("unknown-hitachi-hiuxwe2");
+         exit (0);
+       }
+EOF
+       $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` &&
+               { echo "$SYSTEM_NAME"; exit; }
+       echo unknown-hitachi-hiuxwe2
+       exit ;;
+    9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* )
+       echo hppa1.1-hp-bsd
+       exit ;;
+    9000/8??:4.3bsd:*:*)
+       echo hppa1.0-hp-bsd
+       exit ;;
+    *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
+       echo hppa1.0-hp-mpeix
+       exit ;;
+    hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* )
+       echo hppa1.1-hp-osf
+       exit ;;
+    hp8??:OSF1:*:*)
+       echo hppa1.0-hp-osf
+       exit ;;
+    i*86:OSF1:*:*)
+       if [ -x /usr/sbin/sysversion ] ; then
+           echo ${UNAME_MACHINE}-unknown-osf1mk
+       else
+           echo ${UNAME_MACHINE}-unknown-osf1
+       fi
+       exit ;;
+    parisc*:Lites*:*:*)
+       echo hppa1.1-hp-lites
+       exit ;;
+    C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
+       echo c1-convex-bsd
+       exit ;;
+    C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
+       if getsysinfo -f scalar_acc
+       then echo c32-convex-bsd
+       else echo c2-convex-bsd
+       fi
+       exit ;;
+    C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
+       echo c34-convex-bsd
+       exit ;;
+    C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
+       echo c38-convex-bsd
+       exit ;;
+    C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
+       echo c4-convex-bsd
+       exit ;;
+    CRAY*Y-MP:*:*:*)
+       echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+       exit ;;
+    CRAY*[A-Z]90:*:*:*)
+       echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \
+       | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
+             -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
+             -e 's/\.[^.]*$/.X/'
+       exit ;;
+    CRAY*TS:*:*:*)
+       echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+       exit ;;
+    CRAY*T3E:*:*:*)
+       echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+       exit ;;
+    CRAY*SV1:*:*:*)
+       echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+       exit ;;
+    *:UNICOS/mp:*:*)
+       echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+       exit ;;
+    F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
+       FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+       FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+       FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'`
+       echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+       exit ;;
+    5000:UNIX_System_V:4.*:*)
+       FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+       FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'`
+       echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+       exit ;;
+    i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
+       echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE}
+       exit ;;
+    sparc*:BSD/OS:*:*)
+       echo sparc-unknown-bsdi${UNAME_RELEASE}
+       exit ;;
+    *:BSD/OS:*:*)
+       echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE}
+       exit ;;
+    *:FreeBSD:*:*)
+       UNAME_PROCESSOR=`/usr/bin/uname -p`
+       case ${UNAME_PROCESSOR} in
+           amd64)
+               echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;;
+           *)
+               echo ${UNAME_PROCESSOR}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;;
+       esac
+       exit ;;
+    i*:CYGWIN*:*)
+       echo ${UNAME_MACHINE}-pc-cygwin
+       exit ;;
+    *:MINGW*:*)
+       echo ${UNAME_MACHINE}-pc-mingw32
+       exit ;;
+    i*:MSYS*:*)
+       echo ${UNAME_MACHINE}-pc-msys
+       exit ;;
+    i*:windows32*:*)
+       # uname -m includes "-pc" on this system.
+       echo ${UNAME_MACHINE}-mingw32
+       exit ;;
+    i*:PW*:*)
+       echo ${UNAME_MACHINE}-pc-pw32
+       exit ;;
+    *:Interix*:*)
+       case ${UNAME_MACHINE} in
+           x86)
+               echo i586-pc-interix${UNAME_RELEASE}
+               exit ;;
+           authenticamd | genuineintel | EM64T)
+               echo x86_64-unknown-interix${UNAME_RELEASE}
+               exit ;;
+           IA64)
+               echo ia64-unknown-interix${UNAME_RELEASE}
+               exit ;;
+       esac ;;
+    [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*)
+       echo i${UNAME_MACHINE}-pc-mks
+       exit ;;
+    8664:Windows_NT:*)
+       echo x86_64-pc-mks
+       exit ;;
+    i*:Windows_NT*:* | Pentium*:Windows_NT*:*)
+       # How do we know it's Interix rather than the generic POSIX subsystem?
+       # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we
+       # UNAME_MACHINE based on the output of uname instead of i386?
+       echo i586-pc-interix
+       exit ;;
+    i*:UWIN*:*)
+       echo ${UNAME_MACHINE}-pc-uwin
+       exit ;;
+    amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*)
+       echo x86_64-unknown-cygwin
+       exit ;;
+    p*:CYGWIN*:*)
+       echo powerpcle-unknown-cygwin
+       exit ;;
+    prep*:SunOS:5.*:*)
+       echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+       exit ;;
+    *:GNU:*:*)
+       # the GNU system
+       echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'`
+       exit ;;
+    *:GNU/*:*:*)
+       # other systems with GNU libc and userland
+       echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu
+       exit ;;
+    i*86:Minix:*:*)
+       echo ${UNAME_MACHINE}-pc-minix
+       exit ;;
+    aarch64:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    aarch64_be:Linux:*:*)
+       UNAME_MACHINE=aarch64_be
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    alpha:Linux:*:*)
+       case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in
+         EV5)   UNAME_MACHINE=alphaev5 ;;
+         EV56)  UNAME_MACHINE=alphaev56 ;;
+         PCA56) UNAME_MACHINE=alphapca56 ;;
+         PCA57) UNAME_MACHINE=alphapca56 ;;
+         EV6)   UNAME_MACHINE=alphaev6 ;;
+         EV67)  UNAME_MACHINE=alphaev67 ;;
+         EV68*) UNAME_MACHINE=alphaev68 ;;
+       esac
+       objdump --private-headers /bin/sh | grep -q ld.so.1
+       if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi
+       echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC}
+       exit ;;
+    arm*:Linux:*:*)
+       eval $set_cc_for_build
+       if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \
+           | grep -q __ARM_EABI__
+       then
+           echo ${UNAME_MACHINE}-unknown-linux-gnu
+       else
+           if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \
+               | grep -q __ARM_PCS_VFP
+           then
+               echo ${UNAME_MACHINE}-unknown-linux-gnueabi
+           else
+               echo ${UNAME_MACHINE}-unknown-linux-gnueabihf
+           fi
+       fi
+       exit ;;
+    avr32*:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    cris:Linux:*:*)
+       echo ${UNAME_MACHINE}-axis-linux-gnu
+       exit ;;
+    crisv32:Linux:*:*)
+       echo ${UNAME_MACHINE}-axis-linux-gnu
+       exit ;;
+    frv:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    hexagon:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    i*86:Linux:*:*)
+       LIBC=gnu
+       eval $set_cc_for_build
+       sed 's/^        //' << EOF >$dummy.c
+       #ifdef __dietlibc__
+       LIBC=dietlibc
+       #endif
+EOF
+       eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC'`
+       echo "${UNAME_MACHINE}-pc-linux-${LIBC}"
+       exit ;;
+    ia64:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    m32r*:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    m68*:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    mips:Linux:*:* | mips64:Linux:*:*)
+       eval $set_cc_for_build
+       sed 's/^        //' << EOF >$dummy.c
+       #undef CPU
+       #undef ${UNAME_MACHINE}
+       #undef ${UNAME_MACHINE}el
+       #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+       CPU=${UNAME_MACHINE}el
+       #else
+       #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+       CPU=${UNAME_MACHINE}
+       #else
+       CPU=
+       #endif
+       #endif
+EOF
+       eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'`
+       test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; }
+       ;;
+    or32:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    padre:Linux:*:*)
+       echo sparc-unknown-linux-gnu
+       exit ;;
+    parisc64:Linux:*:* | hppa64:Linux:*:*)
+       echo hppa64-unknown-linux-gnu
+       exit ;;
+    parisc:Linux:*:* | hppa:Linux:*:*)
+       # Look for CPU level
+       case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
+         PA7*) echo hppa1.1-unknown-linux-gnu ;;
+         PA8*) echo hppa2.0-unknown-linux-gnu ;;
+         *)    echo hppa-unknown-linux-gnu ;;
+       esac
+       exit ;;
+    ppc64:Linux:*:*)
+       echo powerpc64-unknown-linux-gnu
+       exit ;;
+    ppc:Linux:*:*)
+       echo powerpc-unknown-linux-gnu
+       exit ;;
+    s390:Linux:*:* | s390x:Linux:*:*)
+       echo ${UNAME_MACHINE}-ibm-linux
+       exit ;;
+    sh64*:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    sh*:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    sparc:Linux:*:* | sparc64:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    tile*:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    vax:Linux:*:*)
+       echo ${UNAME_MACHINE}-dec-linux-gnu
+       exit ;;
+    x86_64:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    xtensa*:Linux:*:*)
+       echo ${UNAME_MACHINE}-unknown-linux-gnu
+       exit ;;
+    i*86:DYNIX/ptx:4*:*)
+       # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
+       # earlier versions are messed up and put the nodename in both
+       # sysname and nodename.
+       echo i386-sequent-sysv4
+       exit ;;
+    i*86:UNIX_SV:4.2MP:2.*)
+       # Unixware is an offshoot of SVR4, but it has its own version
+       # number series starting with 2...
+       # I am not positive that other SVR4 systems won't match this,
+       # I just have to hope.  -- rms.
+       # Use sysv4.2uw... so that sysv4* matches it.
+       echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION}
+       exit ;;
+    i*86:OS/2:*:*)
+       # If we were able to find `uname', then EMX Unix compatibility
+       # is probably installed.
+       echo ${UNAME_MACHINE}-pc-os2-emx
+       exit ;;
+    i*86:XTS-300:*:STOP)
+       echo ${UNAME_MACHINE}-unknown-stop
+       exit ;;
+    i*86:atheos:*:*)
+       echo ${UNAME_MACHINE}-unknown-atheos
+       exit ;;
+    i*86:syllable:*:*)
+       echo ${UNAME_MACHINE}-pc-syllable
+       exit ;;
+    i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*)
+       echo i386-unknown-lynxos${UNAME_RELEASE}
+       exit ;;
+    i*86:*DOS:*:*)
+       echo ${UNAME_MACHINE}-pc-msdosdjgpp
+       exit ;;
+    i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*)
+       UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'`
+       if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
+               echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL}
+       else
+               echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL}
+       fi
+       exit ;;
+    i*86:*:5:[678]*)
+       # UnixWare 7.x, OpenUNIX and OpenServer 6.
+       case `/bin/uname -X | grep "^Machine"` in
+           *486*)           UNAME_MACHINE=i486 ;;
+           *Pentium)        UNAME_MACHINE=i586 ;;
+           *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
+       esac
+       echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
+       exit ;;
+    i*86:*:3.2:*)
+       if test -f /usr/options/cb.name; then
+               UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name`
+               echo ${UNAME_MACHINE}-pc-isc$UNAME_REL
+       elif /bin/uname -X 2>/dev/null >/dev/null ; then
+               UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')`
+               (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
+               (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
+                       && UNAME_MACHINE=i586
+               (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
+                       && UNAME_MACHINE=i686
+               (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
+                       && UNAME_MACHINE=i686
+               echo ${UNAME_MACHINE}-pc-sco$UNAME_REL
+       else
+               echo ${UNAME_MACHINE}-pc-sysv32
+       fi
+       exit ;;
+    pc:*:*:*)
+       # Left here for compatibility:
+       # uname -m prints for DJGPP always 'pc', but it prints nothing about
+       # the processor, so we play safe by assuming i586.
+       # Note: whatever this is, it MUST be the same as what config.sub
+       # prints for the "djgpp" host, or else GDB configury will decide that
+       # this is a cross-build.
+       echo i586-pc-msdosdjgpp
+       exit ;;
+    Intel:Mach:3*:*)
+       echo i386-pc-mach3
+       exit ;;
+    paragon:*:*:*)
+       echo i860-intel-osf1
+       exit ;;
+    i860:*:4.*:*) # i860-SVR4
+       if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
+         echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4
+       else # Add other i860-SVR4 vendors below as they are discovered.
+         echo i860-unknown-sysv${UNAME_RELEASE}  # Unknown i860-SVR4
+       fi
+       exit ;;
+    mini*:CTIX:SYS*5:*)
+       # "miniframe"
+       echo m68010-convergent-sysv
+       exit ;;
+    mc68k:UNIX:SYSTEM5:3.51m)
+       echo m68k-convergent-sysv
+       exit ;;
+    M680?0:D-NIX:5.3:*)
+       echo m68k-diab-dnix
+       exit ;;
+    M68*:*:R3V[5678]*:*)
+       test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;;
+    3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
+       OS_REL=''
+       test -r /etc/.relid \
+       && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+       /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+         && { echo i486-ncr-sysv4.3${OS_REL}; exit; }
+       /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+         && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;;
+    3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
+       /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+         && { echo i486-ncr-sysv4; exit; } ;;
+    NCR*:*:4.2:* | MPRAS*:*:4.2:*)
+       OS_REL='.3'
+       test -r /etc/.relid \
+           && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+       /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+           && { echo i486-ncr-sysv4.3${OS_REL}; exit; }
+       /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+           && { echo i586-ncr-sysv4.3${OS_REL}; exit; }
+       /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \
+           && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;;
+    m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
+       echo m68k-unknown-lynxos${UNAME_RELEASE}
+       exit ;;
+    mc68030:UNIX_System_V:4.*:*)
+       echo m68k-atari-sysv4
+       exit ;;
+    TSUNAMI:LynxOS:2.*:*)
+       echo sparc-unknown-lynxos${UNAME_RELEASE}
+       exit ;;
+    rs6000:LynxOS:2.*:*)
+       echo rs6000-unknown-lynxos${UNAME_RELEASE}
+       exit ;;
+    PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*)
+       echo powerpc-unknown-lynxos${UNAME_RELEASE}
+       exit ;;
+    SM[BE]S:UNIX_SV:*:*)
+       echo mips-dde-sysv${UNAME_RELEASE}
+       exit ;;
+    RM*:ReliantUNIX-*:*:*)
+       echo mips-sni-sysv4
+       exit ;;
+    RM*:SINIX-*:*:*)
+       echo mips-sni-sysv4
+       exit ;;
+    *:SINIX-*:*:*)
+       if uname -p 2>/dev/null >/dev/null ; then
+               UNAME_MACHINE=`(uname -p) 2>/dev/null`
+               echo ${UNAME_MACHINE}-sni-sysv4
+       else
+               echo ns32k-sni-sysv
+       fi
+       exit ;;
+    PENTIUM:*:4.0*:*)  # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
+                       # says <Richard.M.Bartel@ccMail.Census.GOV>
+       echo i586-unisys-sysv4
+       exit ;;
+    *:UNIX_System_V:4*:FTX*)
+       # From Gerald Hewes <hewes@openmarket.com>.
+       # How about differentiating between stratus architectures? -djm
+       echo hppa1.1-stratus-sysv4
+       exit ;;
+    *:*:*:FTX*)
+       # From seanf@swdc.stratus.com.
+       echo i860-stratus-sysv4
+       exit ;;
+    i*86:VOS:*:*)
+       # From Paul.Green@stratus.com.
+       echo ${UNAME_MACHINE}-stratus-vos
+       exit ;;
+    *:VOS:*:*)
+       # From Paul.Green@stratus.com.
+       echo hppa1.1-stratus-vos
+       exit ;;
+    mc68*:A/UX:*:*)
+       echo m68k-apple-aux${UNAME_RELEASE}
+       exit ;;
+    news*:NEWS-OS:6*:*)
+       echo mips-sony-newsos6
+       exit ;;
+    R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
+       if [ -d /usr/nec ]; then
+               echo mips-nec-sysv${UNAME_RELEASE}
+       else
+               echo mips-unknown-sysv${UNAME_RELEASE}
+       fi
+       exit ;;
+    BeBox:BeOS:*:*)    # BeOS running on hardware made by Be, PPC only.
+       echo powerpc-be-beos
+       exit ;;
+    BeMac:BeOS:*:*)    # BeOS running on Mac or Mac clone, PPC only.
+       echo powerpc-apple-beos
+       exit ;;
+    BePC:BeOS:*:*)     # BeOS running on Intel PC compatible.
+       echo i586-pc-beos
+       exit ;;
+    BePC:Haiku:*:*)    # Haiku running on Intel PC compatible.
+       echo i586-pc-haiku
+       exit ;;
+    SX-4:SUPER-UX:*:*)
+       echo sx4-nec-superux${UNAME_RELEASE}
+       exit ;;
+    SX-5:SUPER-UX:*:*)
+       echo sx5-nec-superux${UNAME_RELEASE}
+       exit ;;
+    SX-6:SUPER-UX:*:*)
+       echo sx6-nec-superux${UNAME_RELEASE}
+       exit ;;
+    SX-7:SUPER-UX:*:*)
+       echo sx7-nec-superux${UNAME_RELEASE}
+       exit ;;
+    SX-8:SUPER-UX:*:*)
+       echo sx8-nec-superux${UNAME_RELEASE}
+       exit ;;
+    SX-8R:SUPER-UX:*:*)
+       echo sx8r-nec-superux${UNAME_RELEASE}
+       exit ;;
+    Power*:Rhapsody:*:*)
+       echo powerpc-apple-rhapsody${UNAME_RELEASE}
+       exit ;;
+    *:Rhapsody:*:*)
+       echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE}
+       exit ;;
+    *:Darwin:*:*)
+       UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown
+       case $UNAME_PROCESSOR in
+           i386)
+               eval $set_cc_for_build
+               if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then
+                 if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \
+                     (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \
+                     grep IS_64BIT_ARCH >/dev/null
+                 then
+                     UNAME_PROCESSOR="x86_64"
+                 fi
+               fi ;;
+           unknown) UNAME_PROCESSOR=powerpc ;;
+       esac
+       echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE}
+       exit ;;
+    *:procnto*:*:* | *:QNX:[0123456789]*:*)
+       UNAME_PROCESSOR=`uname -p`
+       if test "$UNAME_PROCESSOR" = "x86"; then
+               UNAME_PROCESSOR=i386
+               UNAME_MACHINE=pc
+       fi
+       echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE}
+       exit ;;
+    *:QNX:*:4*)
+       echo i386-pc-qnx
+       exit ;;
+    NEO-?:NONSTOP_KERNEL:*:*)
+       echo neo-tandem-nsk${UNAME_RELEASE}
+       exit ;;
+    NSE-?:NONSTOP_KERNEL:*:*)
+       echo nse-tandem-nsk${UNAME_RELEASE}
+       exit ;;
+    NSR-?:NONSTOP_KERNEL:*:*)
+       echo nsr-tandem-nsk${UNAME_RELEASE}
+       exit ;;
+    *:NonStop-UX:*:*)
+       echo mips-compaq-nonstopux
+       exit ;;
+    BS2000:POSIX*:*:*)
+       echo bs2000-siemens-sysv
+       exit ;;
+    DS/*:UNIX_System_V:*:*)
+       echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE}
+       exit ;;
+    *:Plan9:*:*)
+       # "uname -m" is not consistent, so use $cputype instead. 386
+       # is converted to i386 for consistency with other x86
+       # operating systems.
+       if test "$cputype" = "386"; then
+           UNAME_MACHINE=i386
+       else
+           UNAME_MACHINE="$cputype"
+       fi
+       echo ${UNAME_MACHINE}-unknown-plan9
+       exit ;;
+    *:TOPS-10:*:*)
+       echo pdp10-unknown-tops10
+       exit ;;
+    *:TENEX:*:*)
+       echo pdp10-unknown-tenex
+       exit ;;
+    KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
+       echo pdp10-dec-tops20
+       exit ;;
+    XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
+       echo pdp10-xkl-tops20
+       exit ;;
+    *:TOPS-20:*:*)
+       echo pdp10-unknown-tops20
+       exit ;;
+    *:ITS:*:*)
+       echo pdp10-unknown-its
+       exit ;;
+    SEI:*:*:SEIUX)
+       echo mips-sei-seiux${UNAME_RELEASE}
+       exit ;;
+    *:DragonFly:*:*)
+       echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`
+       exit ;;
+    *:*VMS:*:*)
+       UNAME_MACHINE=`(uname -p) 2>/dev/null`
+       case "${UNAME_MACHINE}" in
+           A*) echo alpha-dec-vms ; exit ;;
+           I*) echo ia64-dec-vms ; exit ;;
+           V*) echo vax-dec-vms ; exit ;;
+       esac ;;
+    *:XENIX:*:SysV)
+       echo i386-pc-xenix
+       exit ;;
+    i*86:skyos:*:*)
+       echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//'
+       exit ;;
+    i*86:rdos:*:*)
+       echo ${UNAME_MACHINE}-pc-rdos
+       exit ;;
+    i*86:AROS:*:*)
+       echo ${UNAME_MACHINE}-pc-aros
+       exit ;;
+    x86_64:VMkernel:*:*)
+       echo ${UNAME_MACHINE}-unknown-esx
+       exit ;;
+esac
+
+#echo '(No uname command or uname output not recognized.)' 1>&2
+#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2
+
+eval $set_cc_for_build
+cat >$dummy.c <<EOF
+#ifdef _SEQUENT_
+# include <sys/types.h>
+# include <sys/utsname.h>
+#endif
+main ()
+{
+#if defined (sony)
+#if defined (MIPSEB)
+  /* BFD wants "bsd" instead of "newsos".  Perhaps BFD should be changed,
+     I don't know....  */
+  printf ("mips-sony-bsd\n"); exit (0);
+#else
+#include <sys/param.h>
+  printf ("m68k-sony-newsos%s\n",
+#ifdef NEWSOS4
+       "4"
+#else
+       ""
+#endif
+       ); exit (0);
+#endif
+#endif
+
+#if defined (__arm) && defined (__acorn) && defined (__unix)
+  printf ("arm-acorn-riscix\n"); exit (0);
+#endif
+
+#if defined (hp300) && !defined (hpux)
+  printf ("m68k-hp-bsd\n"); exit (0);
+#endif
+
+#if defined (NeXT)
+#if !defined (__ARCHITECTURE__)
+#define __ARCHITECTURE__ "m68k"
+#endif
+  int version;
+  version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`;
+  if (version < 4)
+    printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
+  else
+    printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
+  exit (0);
+#endif
+
+#if defined (MULTIMAX) || defined (n16)
+#if defined (UMAXV)
+  printf ("ns32k-encore-sysv\n"); exit (0);
+#else
+#if defined (CMU)
+  printf ("ns32k-encore-mach\n"); exit (0);
+#else
+  printf ("ns32k-encore-bsd\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (__386BSD__)
+  printf ("i386-pc-bsd\n"); exit (0);
+#endif
+
+#if defined (sequent)
+#if defined (i386)
+  printf ("i386-sequent-dynix\n"); exit (0);
+#endif
+#if defined (ns32000)
+  printf ("ns32k-sequent-dynix\n"); exit (0);
+#endif
+#endif
+
+#if defined (_SEQUENT_)
+    struct utsname un;
+
+    uname(&un);
+
+    if (strncmp(un.version, "V2", 2) == 0) {
+       printf ("i386-sequent-ptx2\n"); exit (0);
+    }
+    if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
+       printf ("i386-sequent-ptx1\n"); exit (0);
+    }
+    printf ("i386-sequent-ptx\n"); exit (0);
+
+#endif
+
+#if defined (vax)
+# if !defined (ultrix)
+#  include <sys/param.h>
+#  if defined (BSD)
+#   if BSD == 43
+      printf ("vax-dec-bsd4.3\n"); exit (0);
+#   else
+#    if BSD == 199006
+      printf ("vax-dec-bsd4.3reno\n"); exit (0);
+#    else
+      printf ("vax-dec-bsd\n"); exit (0);
+#    endif
+#   endif
+#  else
+    printf ("vax-dec-bsd\n"); exit (0);
+#  endif
+# else
+    printf ("vax-dec-ultrix\n"); exit (0);
+# endif
+#endif
+
+#if defined (alliant) && defined (i860)
+  printf ("i860-alliant-bsd\n"); exit (0);
+#endif
+
+  exit (1);
+}
+EOF
+
+$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` &&
+       { echo "$SYSTEM_NAME"; exit; }
+
+# Apollos put the system type in the environment.
+
+test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; }
+
+# Convex versions that predate uname can use getsysinfo(1)
+
+if [ -x /usr/convex/getsysinfo ]
+then
+    case `getsysinfo -f cpu_type` in
+    c1*)
+       echo c1-convex-bsd
+       exit ;;
+    c2*)
+       if getsysinfo -f scalar_acc
+       then echo c32-convex-bsd
+       else echo c2-convex-bsd
+       fi
+       exit ;;
+    c34*)
+       echo c34-convex-bsd
+       exit ;;
+    c38*)
+       echo c38-convex-bsd
+       exit ;;
+    c4*)
+       echo c4-convex-bsd
+       exit ;;
+    esac
+fi
+
+cat >&2 <<EOF
+$0: unable to guess system type
+
+This script, last modified $timestamp, has failed to recognize
+the operating system you are using. It is advised that you
+download the most up to date version of the config scripts from
+
+  http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD
+and
+  http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD
+
+If the version you run ($0) is already up to date, please
+send the following data and any information you think might be
+pertinent to <config-patches@gnu.org> in order to provide the needed
+information to handle your system.
+
+config.guess timestamp = $timestamp
+
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null`
+
+hostinfo               = `(hostinfo) 2>/dev/null`
+/bin/universe          = `(/bin/universe) 2>/dev/null`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null`
+/bin/arch              = `(/bin/arch) 2>/dev/null`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null`
+
+UNAME_MACHINE = ${UNAME_MACHINE}
+UNAME_RELEASE = ${UNAME_RELEASE}
+UNAME_SYSTEM  = ${UNAME_SYSTEM}
+UNAME_VERSION = ${UNAME_VERSION}
+EOF
+
+exit 1
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/config/config.h.in b/config/config.h.in
new file mode 100644 (file)
index 0000000..4c9dd58
--- /dev/null
@@ -0,0 +1,116 @@
+/* config/config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define if building universal (internal helper macro) */
+#undef AC_APPLE_UNIVERSAL_BUILD
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* Define to 1 if you have the `fmemopen' function. */
+#undef HAVE_FMEMOPEN
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have giflib. */
+#undef HAVE_LIBGIF
+
+/* Define to 1 if you have libopenjp2. */
+#undef HAVE_LIBJP2K
+
+/* Define to 1 if you have jpeg. */
+#undef HAVE_LIBJPEG
+
+/* Define to 1 if you have libpng. */
+#undef HAVE_LIBPNG
+
+/* Define to 1 if you have libtiff. */
+#undef HAVE_LIBTIFF
+
+/* Define to 1 if you have libwebp. */
+#undef HAVE_LIBWEBP
+
+/* Define to 1 if you have zlib. */
+#undef HAVE_LIBZ
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the <openjpeg-2.0/openjpeg.h> header file. */
+#undef HAVE_OPENJPEG_2_0_OPENJPEG_H
+
+/* Define to 1 if you have the <openjpeg-2.1/openjpeg.h> header file. */
+#undef HAVE_OPENJPEG_2_1_OPENJPEG_H
+
+/* Define to 1 if you have the <openjpeg-2.2/openjpeg.h> header file. */
+#undef HAVE_OPENJPEG_2_2_OPENJPEG_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Path to <openjpeg.h> header file. */
+#undef LIBJP2K_HEADER
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+   */
+#undef LT_OBJDIR
+
+/* Name of package */
+#undef PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Version number of package */
+#undef VERSION
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+   significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+# if defined __BIG_ENDIAN__
+#  define WORDS_BIGENDIAN 1
+# endif
+#else
+# ifndef WORDS_BIGENDIAN
+#  undef WORDS_BIGENDIAN
+# endif
+#endif
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+#undef size_t
diff --git a/config/config.h.in~ b/config/config.h.in~
new file mode 100644 (file)
index 0000000..4c9dd58
--- /dev/null
@@ -0,0 +1,116 @@
+/* config/config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define if building universal (internal helper macro) */
+#undef AC_APPLE_UNIVERSAL_BUILD
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* Define to 1 if you have the `fmemopen' function. */
+#undef HAVE_FMEMOPEN
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have giflib. */
+#undef HAVE_LIBGIF
+
+/* Define to 1 if you have libopenjp2. */
+#undef HAVE_LIBJP2K
+
+/* Define to 1 if you have jpeg. */
+#undef HAVE_LIBJPEG
+
+/* Define to 1 if you have libpng. */
+#undef HAVE_LIBPNG
+
+/* Define to 1 if you have libtiff. */
+#undef HAVE_LIBTIFF
+
+/* Define to 1 if you have libwebp. */
+#undef HAVE_LIBWEBP
+
+/* Define to 1 if you have zlib. */
+#undef HAVE_LIBZ
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the <openjpeg-2.0/openjpeg.h> header file. */
+#undef HAVE_OPENJPEG_2_0_OPENJPEG_H
+
+/* Define to 1 if you have the <openjpeg-2.1/openjpeg.h> header file. */
+#undef HAVE_OPENJPEG_2_1_OPENJPEG_H
+
+/* Define to 1 if you have the <openjpeg-2.2/openjpeg.h> header file. */
+#undef HAVE_OPENJPEG_2_2_OPENJPEG_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Path to <openjpeg.h> header file. */
+#undef LIBJP2K_HEADER
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+   */
+#undef LT_OBJDIR
+
+/* Name of package */
+#undef PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Version number of package */
+#undef VERSION
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+   significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+# if defined __BIG_ENDIAN__
+#  define WORDS_BIGENDIAN 1
+# endif
+#else
+# ifndef WORDS_BIGENDIAN
+#  undef WORDS_BIGENDIAN
+# endif
+#endif
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+#undef size_t
diff --git a/config/config.sub b/config/config.sub
new file mode 100755 (executable)
index 0000000..c894da4
--- /dev/null
@@ -0,0 +1,1773 @@
+#! /bin/sh
+# Configuration validation subroutine script.
+#   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+#   2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+#   2011, 2012 Free Software Foundation, Inc.
+
+timestamp='2012-02-10'
+
+# This file is (in principle) common to ALL GNU software.
+# The presence of a machine in this file suggests that SOME GNU software
+# can handle that machine.  It does not imply ALL GNU software can.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+
+# Please send patches to <config-patches@gnu.org>.  Submit a context
+# diff and a properly formatted GNU ChangeLog entry.
+#
+# Configuration subroutine to validate and canonicalize a configuration type.
+# Supply the specified configuration type as an argument.
+# If it is invalid, we print an error message on stderr and exit with code 1.
+# Otherwise, we print the canonical config type on stdout and succeed.
+
+# You can get the latest version of this script from:
+# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD
+
+# This file is supposed to be the same for all GNU packages
+# and recognize all the CPU types, system types and aliases
+# that are meaningful with *any* GNU software.
+# Each package is responsible for reporting which valid configurations
+# it does not support.  The user should be able to distinguish
+# a failure to support a valid configuration from a meaningless
+# configuration.
+
+# The goal of this file is to map all the various variations of a given
+# machine specification into a single specification in the form:
+#      CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or in some cases, the newer four-part form:
+#      CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+# It is wrong to echo any other type of specification.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION] CPU-MFR-OPSYS
+       $0 [OPTION] ALIAS
+
+Canonicalize a configuration name.
+
+Operation modes:
+  -h, --help         print this help, then exit
+  -t, --time-stamp   print date of last modification, then exit
+  -v, --version      print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.sub ($timestamp)
+
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000,
+2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
+Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+  case $1 in
+    --time-stamp | --time* | -t )
+       echo "$timestamp" ; exit ;;
+    --version | -v )
+       echo "$version" ; exit ;;
+    --help | --h* | -h )
+       echo "$usage"; exit ;;
+    -- )     # Stop option processing
+       shift; break ;;
+    - )        # Use stdin as input.
+       break ;;
+    -* )
+       echo "$me: invalid option $1$help"
+       exit 1 ;;
+
+    *local*)
+       # First pass through any local machine types.
+       echo $1
+       exit ;;
+
+    * )
+       break ;;
+  esac
+done
+
+case $# in
+ 0) echo "$me: missing argument$help" >&2
+    exit 1;;
+ 1) ;;
+ *) echo "$me: too many arguments$help" >&2
+    exit 1;;
+esac
+
+# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any).
+# Here we must recognize all the valid KERNEL-OS combinations.
+maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'`
+case $maybe_os in
+  nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \
+  linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \
+  knetbsd*-gnu* | netbsd*-gnu* | \
+  kopensolaris*-gnu* | \
+  storm-chaos* | os2-emx* | rtmk-nova*)
+    os=-$maybe_os
+    basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`
+    ;;
+  android-linux)
+    os=-linux-android
+    basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown
+    ;;
+  *)
+    basic_machine=`echo $1 | sed 's/-[^-]*$//'`
+    if [ $basic_machine != $1 ]
+    then os=`echo $1 | sed 's/.*-/-/'`
+    else os=; fi
+    ;;
+esac
+
+### Let's recognize common machines as not being operating systems so
+### that things like config.sub decstation-3100 work.  We also
+### recognize some manufacturers as not being operating systems, so we
+### can provide default operating systems below.
+case $os in
+       -sun*os*)
+               # Prevent following clause from handling this invalid input.
+               ;;
+       -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \
+       -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \
+       -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \
+       -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\
+       -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \
+       -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \
+       -apple | -axis | -knuth | -cray | -microblaze)
+               os=
+               basic_machine=$1
+               ;;
+       -bluegene*)
+               os=-cnk
+               ;;
+       -sim | -cisco | -oki | -wec | -winbond)
+               os=
+               basic_machine=$1
+               ;;
+       -scout)
+               ;;
+       -wrs)
+               os=-vxworks
+               basic_machine=$1
+               ;;
+       -chorusos*)
+               os=-chorusos
+               basic_machine=$1
+               ;;
+       -chorusrdb)
+               os=-chorusrdb
+               basic_machine=$1
+               ;;
+       -hiux*)
+               os=-hiuxwe2
+               ;;
+       -sco6)
+               os=-sco5v6
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco5)
+               os=-sco3.2v5
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco4)
+               os=-sco3.2v4
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco3.2.[4-9]*)
+               os=`echo $os | sed -e 's/sco3.2./sco3.2v/'`
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco3.2v[4-9]*)
+               # Don't forget version if it is 3.2v4 or newer.
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco5v6*)
+               # Don't forget version if it is 3.2v4 or newer.
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -sco*)
+               os=-sco3.2v2
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -udk*)
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -isc)
+               os=-isc2.2
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -clix*)
+               basic_machine=clipper-intergraph
+               ;;
+       -isc*)
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+               ;;
+       -lynx*)
+               os=-lynxos
+               ;;
+       -ptx*)
+               basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'`
+               ;;
+       -windowsnt*)
+               os=`echo $os | sed -e 's/windowsnt/winnt/'`
+               ;;
+       -psos*)
+               os=-psos
+               ;;
+       -mint | -mint[0-9]*)
+               basic_machine=m68k-atari
+               os=-mint
+               ;;
+esac
+
+# Decode aliases for certain CPU-COMPANY combinations.
+case $basic_machine in
+       # Recognize the basic CPU types without company name.
+       # Some are omitted here because they have special meanings below.
+       1750a | 580 \
+       | a29k \
+       | aarch64 | aarch64_be \
+       | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \
+       | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \
+       | am33_2.0 \
+       | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr | avr32 \
+        | be32 | be64 \
+       | bfin \
+       | c4x | clipper \
+       | d10v | d30v | dlx | dsp16xx \
+       | epiphany \
+       | fido | fr30 | frv \
+       | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+       | hexagon \
+       | i370 | i860 | i960 | ia64 \
+       | ip2k | iq2000 \
+       | le32 | le64 \
+       | lm32 \
+       | m32c | m32r | m32rle | m68000 | m68k | m88k \
+       | maxq | mb | microblaze | mcore | mep | metag \
+       | mips | mipsbe | mipseb | mipsel | mipsle \
+       | mips16 \
+       | mips64 | mips64el \
+       | mips64octeon | mips64octeonel \
+       | mips64orion | mips64orionel \
+       | mips64r5900 | mips64r5900el \
+       | mips64vr | mips64vrel \
+       | mips64vr4100 | mips64vr4100el \
+       | mips64vr4300 | mips64vr4300el \
+       | mips64vr5000 | mips64vr5000el \
+       | mips64vr5900 | mips64vr5900el \
+       | mipsisa32 | mipsisa32el \
+       | mipsisa32r2 | mipsisa32r2el \
+       | mipsisa64 | mipsisa64el \
+       | mipsisa64r2 | mipsisa64r2el \
+       | mipsisa64sb1 | mipsisa64sb1el \
+       | mipsisa64sr71k | mipsisa64sr71kel \
+       | mipstx39 | mipstx39el \
+       | mn10200 | mn10300 \
+       | moxie \
+       | mt \
+       | msp430 \
+       | nds32 | nds32le | nds32be \
+       | nios | nios2 \
+       | ns16k | ns32k \
+       | open8 \
+       | or32 \
+       | pdp10 | pdp11 | pj | pjl \
+       | powerpc | powerpc64 | powerpc64le | powerpcle \
+       | pyramid \
+       | rl78 | rx \
+       | score \
+       | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \
+       | sh64 | sh64le \
+       | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \
+       | sparcv8 | sparcv9 | sparcv9b | sparcv9v \
+       | spu \
+       | tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \
+       | ubicom32 \
+       | v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \
+       | we32k \
+       | x86 | xc16x | xstormy16 | xtensa \
+       | z8k | z80)
+               basic_machine=$basic_machine-unknown
+               ;;
+       c54x)
+               basic_machine=tic54x-unknown
+               ;;
+       c55x)
+               basic_machine=tic55x-unknown
+               ;;
+       c6x)
+               basic_machine=tic6x-unknown
+               ;;
+       m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | picochip)
+               basic_machine=$basic_machine-unknown
+               os=-none
+               ;;
+       m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k)
+               ;;
+       ms1)
+               basic_machine=mt-unknown
+               ;;
+
+       strongarm | thumb | xscale)
+               basic_machine=arm-unknown
+               ;;
+       xgate)
+               basic_machine=$basic_machine-unknown
+               os=-none
+               ;;
+       xscaleeb)
+               basic_machine=armeb-unknown
+               ;;
+
+       xscaleel)
+               basic_machine=armel-unknown
+               ;;
+
+       # We use `pc' rather than `unknown'
+       # because (1) that's what they normally are, and
+       # (2) the word "unknown" tends to confuse beginning users.
+       i*86 | x86_64)
+         basic_machine=$basic_machine-pc
+         ;;
+       # Object if more than one company name word.
+       *-*-*)
+               echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+               exit 1
+               ;;
+       # Recognize the basic CPU types with company name.
+       580-* \
+       | a29k-* \
+       | aarch64-* | aarch64_be-* \
+       | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \
+       | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \
+       | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \
+       | arm-*  | armbe-* | armle-* | armeb-* | armv*-* \
+       | avr-* | avr32-* \
+       | be32-* | be64-* \
+       | bfin-* | bs2000-* \
+       | c[123]* | c30-* | [cjt]90-* | c4x-* \
+       | clipper-* | craynv-* | cydra-* \
+       | d10v-* | d30v-* | dlx-* \
+       | elxsi-* \
+       | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \
+       | h8300-* | h8500-* \
+       | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \
+       | hexagon-* \
+       | i*86-* | i860-* | i960-* | ia64-* \
+       | ip2k-* | iq2000-* \
+       | le32-* | le64-* \
+       | lm32-* \
+       | m32c-* | m32r-* | m32rle-* \
+       | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \
+       | m88110-* | m88k-* | maxq-* | mcore-* | metag-* | microblaze-* \
+       | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \
+       | mips16-* \
+       | mips64-* | mips64el-* \
+       | mips64octeon-* | mips64octeonel-* \
+       | mips64orion-* | mips64orionel-* \
+       | mips64r5900-* | mips64r5900el-* \
+       | mips64vr-* | mips64vrel-* \
+       | mips64vr4100-* | mips64vr4100el-* \
+       | mips64vr4300-* | mips64vr4300el-* \
+       | mips64vr5000-* | mips64vr5000el-* \
+       | mips64vr5900-* | mips64vr5900el-* \
+       | mipsisa32-* | mipsisa32el-* \
+       | mipsisa32r2-* | mipsisa32r2el-* \
+       | mipsisa64-* | mipsisa64el-* \
+       | mipsisa64r2-* | mipsisa64r2el-* \
+       | mipsisa64sb1-* | mipsisa64sb1el-* \
+       | mipsisa64sr71k-* | mipsisa64sr71kel-* \
+       | mipstx39-* | mipstx39el-* \
+       | mmix-* \
+       | mt-* \
+       | msp430-* \
+       | nds32-* | nds32le-* | nds32be-* \
+       | nios-* | nios2-* \
+       | none-* | np1-* | ns16k-* | ns32k-* \
+       | open8-* \
+       | orion-* \
+       | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \
+       | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \
+       | pyramid-* \
+       | rl78-* | romp-* | rs6000-* | rx-* \
+       | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \
+       | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \
+       | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \
+       | sparclite-* \
+       | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx?-* \
+       | tahoe-* \
+       | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \
+       | tile*-* \
+       | tron-* \
+       | ubicom32-* \
+       | v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \
+       | vax-* \
+       | we32k-* \
+       | x86-* | x86_64-* | xc16x-* | xps100-* \
+       | xstormy16-* | xtensa*-* \
+       | ymp-* \
+       | z8k-* | z80-*)
+               ;;
+       # Recognize the basic CPU types without company name, with glob match.
+       xtensa*)
+               basic_machine=$basic_machine-unknown
+               ;;
+       # Recognize the various machine names and aliases which stand
+       # for a CPU type and a company and sometimes even an OS.
+       386bsd)
+               basic_machine=i386-unknown
+               os=-bsd
+               ;;
+       3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
+               basic_machine=m68000-att
+               ;;
+       3b*)
+               basic_machine=we32k-att
+               ;;
+       a29khif)
+               basic_machine=a29k-amd
+               os=-udi
+               ;;
+       abacus)
+               basic_machine=abacus-unknown
+               ;;
+       adobe68k)
+               basic_machine=m68010-adobe
+               os=-scout
+               ;;
+       alliant | fx80)
+               basic_machine=fx80-alliant
+               ;;
+       altos | altos3068)
+               basic_machine=m68k-altos
+               ;;
+       am29k)
+               basic_machine=a29k-none
+               os=-bsd
+               ;;
+       amd64)
+               basic_machine=x86_64-pc
+               ;;
+       amd64-*)
+               basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       amdahl)
+               basic_machine=580-amdahl
+               os=-sysv
+               ;;
+       amiga | amiga-*)
+               basic_machine=m68k-unknown
+               ;;
+       amigaos | amigados)
+               basic_machine=m68k-unknown
+               os=-amigaos
+               ;;
+       amigaunix | amix)
+               basic_machine=m68k-unknown
+               os=-sysv4
+               ;;
+       apollo68)
+               basic_machine=m68k-apollo
+               os=-sysv
+               ;;
+       apollo68bsd)
+               basic_machine=m68k-apollo
+               os=-bsd
+               ;;
+       aros)
+               basic_machine=i386-pc
+               os=-aros
+               ;;
+       aux)
+               basic_machine=m68k-apple
+               os=-aux
+               ;;
+       balance)
+               basic_machine=ns32k-sequent
+               os=-dynix
+               ;;
+       blackfin)
+               basic_machine=bfin-unknown
+               os=-linux
+               ;;
+       blackfin-*)
+               basic_machine=bfin-`echo $basic_machine | sed 's/^[^-]*-//'`
+               os=-linux
+               ;;
+       bluegene*)
+               basic_machine=powerpc-ibm
+               os=-cnk
+               ;;
+       c54x-*)
+               basic_machine=tic54x-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       c55x-*)
+               basic_machine=tic55x-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       c6x-*)
+               basic_machine=tic6x-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       c90)
+               basic_machine=c90-cray
+               os=-unicos
+               ;;
+       cegcc)
+               basic_machine=arm-unknown
+               os=-cegcc
+               ;;
+       convex-c1)
+               basic_machine=c1-convex
+               os=-bsd
+               ;;
+       convex-c2)
+               basic_machine=c2-convex
+               os=-bsd
+               ;;
+       convex-c32)
+               basic_machine=c32-convex
+               os=-bsd
+               ;;
+       convex-c34)
+               basic_machine=c34-convex
+               os=-bsd
+               ;;
+       convex-c38)
+               basic_machine=c38-convex
+               os=-bsd
+               ;;
+       cray | j90)
+               basic_machine=j90-cray
+               os=-unicos
+               ;;
+       craynv)
+               basic_machine=craynv-cray
+               os=-unicosmp
+               ;;
+       cr16 | cr16-*)
+               basic_machine=cr16-unknown
+               os=-elf
+               ;;
+       crds | unos)
+               basic_machine=m68k-crds
+               ;;
+       crisv32 | crisv32-* | etraxfs*)
+               basic_machine=crisv32-axis
+               ;;
+       cris | cris-* | etrax*)
+               basic_machine=cris-axis
+               ;;
+       crx)
+               basic_machine=crx-unknown
+               os=-elf
+               ;;
+       da30 | da30-*)
+               basic_machine=m68k-da30
+               ;;
+       decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn)
+               basic_machine=mips-dec
+               ;;
+       decsystem10* | dec10*)
+               basic_machine=pdp10-dec
+               os=-tops10
+               ;;
+       decsystem20* | dec20*)
+               basic_machine=pdp10-dec
+               os=-tops20
+               ;;
+       delta | 3300 | motorola-3300 | motorola-delta \
+             | 3300-motorola | delta-motorola)
+               basic_machine=m68k-motorola
+               ;;
+       delta88)
+               basic_machine=m88k-motorola
+               os=-sysv3
+               ;;
+       dicos)
+               basic_machine=i686-pc
+               os=-dicos
+               ;;
+       djgpp)
+               basic_machine=i586-pc
+               os=-msdosdjgpp
+               ;;
+       dpx20 | dpx20-*)
+               basic_machine=rs6000-bull
+               os=-bosx
+               ;;
+       dpx2* | dpx2*-bull)
+               basic_machine=m68k-bull
+               os=-sysv3
+               ;;
+       ebmon29k)
+               basic_machine=a29k-amd
+               os=-ebmon
+               ;;
+       elxsi)
+               basic_machine=elxsi-elxsi
+               os=-bsd
+               ;;
+       encore | umax | mmax)
+               basic_machine=ns32k-encore
+               ;;
+       es1800 | OSE68k | ose68k | ose | OSE)
+               basic_machine=m68k-ericsson
+               os=-ose
+               ;;
+       fx2800)
+               basic_machine=i860-alliant
+               ;;
+       genix)
+               basic_machine=ns32k-ns
+               ;;
+       gmicro)
+               basic_machine=tron-gmicro
+               os=-sysv
+               ;;
+       go32)
+               basic_machine=i386-pc
+               os=-go32
+               ;;
+       h3050r* | hiux*)
+               basic_machine=hppa1.1-hitachi
+               os=-hiuxwe2
+               ;;
+       h8300hms)
+               basic_machine=h8300-hitachi
+               os=-hms
+               ;;
+       h8300xray)
+               basic_machine=h8300-hitachi
+               os=-xray
+               ;;
+       h8500hms)
+               basic_machine=h8500-hitachi
+               os=-hms
+               ;;
+       harris)
+               basic_machine=m88k-harris
+               os=-sysv3
+               ;;
+       hp300-*)
+               basic_machine=m68k-hp
+               ;;
+       hp300bsd)
+               basic_machine=m68k-hp
+               os=-bsd
+               ;;
+       hp300hpux)
+               basic_machine=m68k-hp
+               os=-hpux
+               ;;
+       hp3k9[0-9][0-9] | hp9[0-9][0-9])
+               basic_machine=hppa1.0-hp
+               ;;
+       hp9k2[0-9][0-9] | hp9k31[0-9])
+               basic_machine=m68000-hp
+               ;;
+       hp9k3[2-9][0-9])
+               basic_machine=m68k-hp
+               ;;
+       hp9k6[0-9][0-9] | hp6[0-9][0-9])
+               basic_machine=hppa1.0-hp
+               ;;
+       hp9k7[0-79][0-9] | hp7[0-79][0-9])
+               basic_machine=hppa1.1-hp
+               ;;
+       hp9k78[0-9] | hp78[0-9])
+               # FIXME: really hppa2.0-hp
+               basic_machine=hppa1.1-hp
+               ;;
+       hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
+               # FIXME: really hppa2.0-hp
+               basic_machine=hppa1.1-hp
+               ;;
+       hp9k8[0-9][13679] | hp8[0-9][13679])
+               basic_machine=hppa1.1-hp
+               ;;
+       hp9k8[0-9][0-9] | hp8[0-9][0-9])
+               basic_machine=hppa1.0-hp
+               ;;
+       hppa-next)
+               os=-nextstep3
+               ;;
+       hppaosf)
+               basic_machine=hppa1.1-hp
+               os=-osf
+               ;;
+       hppro)
+               basic_machine=hppa1.1-hp
+               os=-proelf
+               ;;
+       i370-ibm* | ibm*)
+               basic_machine=i370-ibm
+               ;;
+       i*86v32)
+               basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+               os=-sysv32
+               ;;
+       i*86v4*)
+               basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+               os=-sysv4
+               ;;
+       i*86v)
+               basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+               os=-sysv
+               ;;
+       i*86sol2)
+               basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+               os=-solaris2
+               ;;
+       i386mach)
+               basic_machine=i386-mach
+               os=-mach
+               ;;
+       i386-vsta | vsta)
+               basic_machine=i386-unknown
+               os=-vsta
+               ;;
+       iris | iris4d)
+               basic_machine=mips-sgi
+               case $os in
+                   -irix*)
+                       ;;
+                   *)
+                       os=-irix4
+                       ;;
+               esac
+               ;;
+       isi68 | isi)
+               basic_machine=m68k-isi
+               os=-sysv
+               ;;
+       m68knommu)
+               basic_machine=m68k-unknown
+               os=-linux
+               ;;
+       m68knommu-*)
+               basic_machine=m68k-`echo $basic_machine | sed 's/^[^-]*-//'`
+               os=-linux
+               ;;
+       m88k-omron*)
+               basic_machine=m88k-omron
+               ;;
+       magnum | m3230)
+               basic_machine=mips-mips
+               os=-sysv
+               ;;
+       merlin)
+               basic_machine=ns32k-utek
+               os=-sysv
+               ;;
+       microblaze)
+               basic_machine=microblaze-xilinx
+               ;;
+       mingw32)
+               basic_machine=i386-pc
+               os=-mingw32
+               ;;
+       mingw32ce)
+               basic_machine=arm-unknown
+               os=-mingw32ce
+               ;;
+       miniframe)
+               basic_machine=m68000-convergent
+               ;;
+       *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*)
+               basic_machine=m68k-atari
+               os=-mint
+               ;;
+       mips3*-*)
+               basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`
+               ;;
+       mips3*)
+               basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown
+               ;;
+       monitor)
+               basic_machine=m68k-rom68k
+               os=-coff
+               ;;
+       morphos)
+               basic_machine=powerpc-unknown
+               os=-morphos
+               ;;
+       msdos)
+               basic_machine=i386-pc
+               os=-msdos
+               ;;
+       ms1-*)
+               basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'`
+               ;;
+       msys)
+               basic_machine=i386-pc
+               os=-msys
+               ;;
+       mvs)
+               basic_machine=i370-ibm
+               os=-mvs
+               ;;
+       nacl)
+               basic_machine=le32-unknown
+               os=-nacl
+               ;;
+       ncr3000)
+               basic_machine=i486-ncr
+               os=-sysv4
+               ;;
+       netbsd386)
+               basic_machine=i386-unknown
+               os=-netbsd
+               ;;
+       netwinder)
+               basic_machine=armv4l-rebel
+               os=-linux
+               ;;
+       news | news700 | news800 | news900)
+               basic_machine=m68k-sony
+               os=-newsos
+               ;;
+       news1000)
+               basic_machine=m68030-sony
+               os=-newsos
+               ;;
+       news-3600 | risc-news)
+               basic_machine=mips-sony
+               os=-newsos
+               ;;
+       necv70)
+               basic_machine=v70-nec
+               os=-sysv
+               ;;
+       next | m*-next )
+               basic_machine=m68k-next
+               case $os in
+                   -nextstep* )
+                       ;;
+                   -ns2*)
+                     os=-nextstep2
+                       ;;
+                   *)
+                     os=-nextstep3
+                       ;;
+               esac
+               ;;
+       nh3000)
+               basic_machine=m68k-harris
+               os=-cxux
+               ;;
+       nh[45]000)
+               basic_machine=m88k-harris
+               os=-cxux
+               ;;
+       nindy960)
+               basic_machine=i960-intel
+               os=-nindy
+               ;;
+       mon960)
+               basic_machine=i960-intel
+               os=-mon960
+               ;;
+       nonstopux)
+               basic_machine=mips-compaq
+               os=-nonstopux
+               ;;
+       np1)
+               basic_machine=np1-gould
+               ;;
+       neo-tandem)
+               basic_machine=neo-tandem
+               ;;
+       nse-tandem)
+               basic_machine=nse-tandem
+               ;;
+       nsr-tandem)
+               basic_machine=nsr-tandem
+               ;;
+       op50n-* | op60c-*)
+               basic_machine=hppa1.1-oki
+               os=-proelf
+               ;;
+       openrisc | openrisc-*)
+               basic_machine=or32-unknown
+               ;;
+       os400)
+               basic_machine=powerpc-ibm
+               os=-os400
+               ;;
+       OSE68000 | ose68000)
+               basic_machine=m68000-ericsson
+               os=-ose
+               ;;
+       os68k)
+               basic_machine=m68k-none
+               os=-os68k
+               ;;
+       pa-hitachi)
+               basic_machine=hppa1.1-hitachi
+               os=-hiuxwe2
+               ;;
+       paragon)
+               basic_machine=i860-intel
+               os=-osf
+               ;;
+       parisc)
+               basic_machine=hppa-unknown
+               os=-linux
+               ;;
+       parisc-*)
+               basic_machine=hppa-`echo $basic_machine | sed 's/^[^-]*-//'`
+               os=-linux
+               ;;
+       pbd)
+               basic_machine=sparc-tti
+               ;;
+       pbb)
+               basic_machine=m68k-tti
+               ;;
+       pc532 | pc532-*)
+               basic_machine=ns32k-pc532
+               ;;
+       pc98)
+               basic_machine=i386-pc
+               ;;
+       pc98-*)
+               basic_machine=i386-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       pentium | p5 | k5 | k6 | nexgen | viac3)
+               basic_machine=i586-pc
+               ;;
+       pentiumpro | p6 | 6x86 | athlon | athlon_*)
+               basic_machine=i686-pc
+               ;;
+       pentiumii | pentium2 | pentiumiii | pentium3)
+               basic_machine=i686-pc
+               ;;
+       pentium4)
+               basic_machine=i786-pc
+               ;;
+       pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+               basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       pentiumpro-* | p6-* | 6x86-* | athlon-*)
+               basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+               basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       pentium4-*)
+               basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       pn)
+               basic_machine=pn-gould
+               ;;
+       power)  basic_machine=power-ibm
+               ;;
+       ppc | ppcbe)    basic_machine=powerpc-unknown
+               ;;
+       ppc-* | ppcbe-*)
+               basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       ppcle | powerpclittle | ppc-le | powerpc-little)
+               basic_machine=powerpcle-unknown
+               ;;
+       ppcle-* | powerpclittle-*)
+               basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       ppc64)  basic_machine=powerpc64-unknown
+               ;;
+       ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       ppc64le | powerpc64little | ppc64-le | powerpc64-little)
+               basic_machine=powerpc64le-unknown
+               ;;
+       ppc64le-* | powerpc64little-*)
+               basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       ps2)
+               basic_machine=i386-ibm
+               ;;
+       pw32)
+               basic_machine=i586-unknown
+               os=-pw32
+               ;;
+       rdos)
+               basic_machine=i386-pc
+               os=-rdos
+               ;;
+       rom68k)
+               basic_machine=m68k-rom68k
+               os=-coff
+               ;;
+       rm[46]00)
+               basic_machine=mips-siemens
+               ;;
+       rtpc | rtpc-*)
+               basic_machine=romp-ibm
+               ;;
+       s390 | s390-*)
+               basic_machine=s390-ibm
+               ;;
+       s390x | s390x-*)
+               basic_machine=s390x-ibm
+               ;;
+       sa29200)
+               basic_machine=a29k-amd
+               os=-udi
+               ;;
+       sb1)
+               basic_machine=mipsisa64sb1-unknown
+               ;;
+       sb1el)
+               basic_machine=mipsisa64sb1el-unknown
+               ;;
+       sde)
+               basic_machine=mipsisa32-sde
+               os=-elf
+               ;;
+       sei)
+               basic_machine=mips-sei
+               os=-seiux
+               ;;
+       sequent)
+               basic_machine=i386-sequent
+               ;;
+       sh)
+               basic_machine=sh-hitachi
+               os=-hms
+               ;;
+       sh5el)
+               basic_machine=sh5le-unknown
+               ;;
+       sh64)
+               basic_machine=sh64-unknown
+               ;;
+       sparclite-wrs | simso-wrs)
+               basic_machine=sparclite-wrs
+               os=-vxworks
+               ;;
+       sps7)
+               basic_machine=m68k-bull
+               os=-sysv2
+               ;;
+       spur)
+               basic_machine=spur-unknown
+               ;;
+       st2000)
+               basic_machine=m68k-tandem
+               ;;
+       stratus)
+               basic_machine=i860-stratus
+               os=-sysv4
+               ;;
+       strongarm-* | thumb-*)
+               basic_machine=arm-`echo $basic_machine | sed 's/^[^-]*-//'`
+               ;;
+       sun2)
+               basic_machine=m68000-sun
+               ;;
+       sun2os3)
+               basic_machine=m68000-sun
+               os=-sunos3
+               ;;
+       sun2os4)
+               basic_machine=m68000-sun
+               os=-sunos4
+               ;;
+       sun3os3)
+               basic_machine=m68k-sun
+               os=-sunos3
+               ;;
+       sun3os4)
+               basic_machine=m68k-sun
+               os=-sunos4
+               ;;
+       sun4os3)
+               basic_machine=sparc-sun
+               os=-sunos3
+               ;;
+       sun4os4)
+               basic_machine=sparc-sun
+               os=-sunos4
+               ;;
+       sun4sol2)
+               basic_machine=sparc-sun
+               os=-solaris2
+               ;;
+       sun3 | sun3-*)
+               basic_machine=m68k-sun
+               ;;
+       sun4)
+               basic_machine=sparc-sun
+               ;;
+       sun386 | sun386i | roadrunner)
+               basic_machine=i386-sun
+               ;;
+       sv1)
+               basic_machine=sv1-cray
+               os=-unicos
+               ;;
+       symmetry)
+               basic_machine=i386-sequent
+               os=-dynix
+               ;;
+       t3e)
+               basic_machine=alphaev5-cray
+               os=-unicos
+               ;;
+       t90)
+               basic_machine=t90-cray
+               os=-unicos
+               ;;
+       tile*)
+               basic_machine=$basic_machine-unknown
+               os=-linux-gnu
+               ;;
+       tx39)
+               basic_machine=mipstx39-unknown
+               ;;
+       tx39el)
+               basic_machine=mipstx39el-unknown
+               ;;
+       toad1)
+               basic_machine=pdp10-xkl
+               os=-tops20
+               ;;
+       tower | tower-32)
+               basic_machine=m68k-ncr
+               ;;
+       tpf)
+               basic_machine=s390x-ibm
+               os=-tpf
+               ;;
+       udi29k)
+               basic_machine=a29k-amd
+               os=-udi
+               ;;
+       ultra3)
+               basic_machine=a29k-nyu
+               os=-sym1
+               ;;
+       v810 | necv810)
+               basic_machine=v810-nec
+               os=-none
+               ;;
+       vaxv)
+               basic_machine=vax-dec
+               os=-sysv
+               ;;
+       vms)
+               basic_machine=vax-dec
+               os=-vms
+               ;;
+       vpp*|vx|vx-*)
+               basic_machine=f301-fujitsu
+               ;;
+       vxworks960)
+               basic_machine=i960-wrs
+               os=-vxworks
+               ;;
+       vxworks68)
+               basic_machine=m68k-wrs
+               os=-vxworks
+               ;;
+       vxworks29k)
+               basic_machine=a29k-wrs
+               os=-vxworks
+               ;;
+       w65*)
+               basic_machine=w65-wdc
+               os=-none
+               ;;
+       w89k-*)
+               basic_machine=hppa1.1-winbond
+               os=-proelf
+               ;;
+       xbox)
+               basic_machine=i686-pc
+               os=-mingw32
+               ;;
+       xps | xps100)
+               basic_machine=xps100-honeywell
+               ;;
+       xscale-* | xscalee[bl]-*)
+               basic_machine=`echo $basic_machine | sed 's/^xscale/arm/'`
+               ;;
+       ymp)
+               basic_machine=ymp-cray
+               os=-unicos
+               ;;
+       z8k-*-coff)
+               basic_machine=z8k-unknown
+               os=-sim
+               ;;
+       z80-*-coff)
+               basic_machine=z80-unknown
+               os=-sim
+               ;;
+       none)
+               basic_machine=none-none
+               os=-none
+               ;;
+
+# Here we handle the default manufacturer of certain CPU types.  It is in
+# some cases the only manufacturer, in others, it is the most popular.
+       w89k)
+               basic_machine=hppa1.1-winbond
+               ;;
+       op50n)
+               basic_machine=hppa1.1-oki
+               ;;
+       op60c)
+               basic_machine=hppa1.1-oki
+               ;;
+       romp)
+               basic_machine=romp-ibm
+               ;;
+       mmix)
+               basic_machine=mmix-knuth
+               ;;
+       rs6000)
+               basic_machine=rs6000-ibm
+               ;;
+       vax)
+               basic_machine=vax-dec
+               ;;
+       pdp10)
+               # there are many clones, so DEC is not a safe bet
+               basic_machine=pdp10-unknown
+               ;;
+       pdp11)
+               basic_machine=pdp11-dec
+               ;;
+       we32k)
+               basic_machine=we32k-att
+               ;;
+       sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele)
+               basic_machine=sh-unknown
+               ;;
+       sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v)
+               basic_machine=sparc-sun
+               ;;
+       cydra)
+               basic_machine=cydra-cydrome
+               ;;
+       orion)
+               basic_machine=orion-highlevel
+               ;;
+       orion105)
+               basic_machine=clipper-highlevel
+               ;;
+       mac | mpw | mac-mpw)
+               basic_machine=m68k-apple
+               ;;
+       pmac | pmac-mpw)
+               basic_machine=powerpc-apple
+               ;;
+       *-unknown)
+               # Make sure to match an already-canonicalized machine name.
+               ;;
+       *)
+               echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+               exit 1
+               ;;
+esac
+
+# Here we canonicalize certain aliases for manufacturers.
+case $basic_machine in
+       *-digital*)
+               basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'`
+               ;;
+       *-commodore*)
+               basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'`
+               ;;
+       *)
+               ;;
+esac
+
+# Decode manufacturer-specific aliases for certain operating systems.
+
+if [ x"$os" != x"" ]
+then
+case $os in
+       # First match some system type aliases
+       # that might get confused with valid system types.
+       # -solaris* is a basic system type, with this one exception.
+       -auroraux)
+               os=-auroraux
+               ;;
+       -solaris1 | -solaris1.*)
+               os=`echo $os | sed -e 's|solaris1|sunos4|'`
+               ;;
+       -solaris)
+               os=-solaris2
+               ;;
+       -svr4*)
+               os=-sysv4
+               ;;
+       -unixware*)
+               os=-sysv4.2uw
+               ;;
+       -gnu/linux*)
+               os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'`
+               ;;
+       # First accept the basic system types.
+       # The portable systems comes first.
+       # Each alternative MUST END IN A *, to match a version number.
+       # -sysv* is not here because it comes later, after sysvr4.
+       -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \
+             | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\
+             | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \
+             | -sym* | -kopensolaris* \
+             | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \
+             | -aos* | -aros* \
+             | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \
+             | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \
+             | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \
+             | -openbsd* | -solidbsd* \
+             | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \
+             | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \
+             | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
+             | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
+             | -chorusos* | -chorusrdb* | -cegcc* \
+             | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
+             | -mingw32* | -linux-gnu* | -linux-android* \
+             | -linux-newlib* | -linux-uclibc* \
+             | -uxpv* | -beos* | -mpeix* | -udk* \
+             | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \
+             | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \
+             | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \
+             | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \
+             | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \
+             | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \
+             | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es*)
+       # Remember, each alternative MUST END IN *, to match a version number.
+               ;;
+       -qnx*)
+               case $basic_machine in
+                   x86-* | i*86-*)
+                       ;;
+                   *)
+                       os=-nto$os
+                       ;;
+               esac
+               ;;
+       -nto-qnx*)
+               ;;
+       -nto*)
+               os=`echo $os | sed -e 's|nto|nto-qnx|'`
+               ;;
+       -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \
+             | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \
+             | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*)
+               ;;
+       -mac*)
+               os=`echo $os | sed -e 's|mac|macos|'`
+               ;;
+       -linux-dietlibc)
+               os=-linux-dietlibc
+               ;;
+       -linux*)
+               os=`echo $os | sed -e 's|linux|linux-gnu|'`
+               ;;
+       -sunos5*)
+               os=`echo $os | sed -e 's|sunos5|solaris2|'`
+               ;;
+       -sunos6*)
+               os=`echo $os | sed -e 's|sunos6|solaris3|'`
+               ;;
+       -opened*)
+               os=-openedition
+               ;;
+       -os400*)
+               os=-os400
+               ;;
+       -wince*)
+               os=-wince
+               ;;
+       -osfrose*)
+               os=-osfrose
+               ;;
+       -osf*)
+               os=-osf
+               ;;
+       -utek*)
+               os=-bsd
+               ;;
+       -dynix*)
+               os=-bsd
+               ;;
+       -acis*)
+               os=-aos
+               ;;
+       -atheos*)
+               os=-atheos
+               ;;
+       -syllable*)
+               os=-syllable
+               ;;
+       -386bsd)
+               os=-bsd
+               ;;
+       -ctix* | -uts*)
+               os=-sysv
+               ;;
+       -nova*)
+               os=-rtmk-nova
+               ;;
+       -ns2 )
+               os=-nextstep2
+               ;;
+       -nsk*)
+               os=-nsk
+               ;;
+       # Preserve the version number of sinix5.
+       -sinix5.*)
+               os=`echo $os | sed -e 's|sinix|sysv|'`
+               ;;
+       -sinix*)
+               os=-sysv4
+               ;;
+       -tpf*)
+               os=-tpf
+               ;;
+       -triton*)
+               os=-sysv3
+               ;;
+       -oss*)
+               os=-sysv3
+               ;;
+       -svr4)
+               os=-sysv4
+               ;;
+       -svr3)
+               os=-sysv3
+               ;;
+       -sysvr4)
+               os=-sysv4
+               ;;
+       # This must come after -sysvr4.
+       -sysv*)
+               ;;
+       -ose*)
+               os=-ose
+               ;;
+       -es1800*)
+               os=-ose
+               ;;
+       -xenix)
+               os=-xenix
+               ;;
+       -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+               os=-mint
+               ;;
+       -aros*)
+               os=-aros
+               ;;
+       -kaos*)
+               os=-kaos
+               ;;
+       -zvmoe)
+               os=-zvmoe
+               ;;
+       -dicos*)
+               os=-dicos
+               ;;
+       -nacl*)
+               ;;
+       -none)
+               ;;
+       *)
+               # Get rid of the `-' at the beginning of $os.
+               os=`echo $os | sed 's/[^-]*-//'`
+               echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2
+               exit 1
+               ;;
+esac
+else
+
+# Here we handle the default operating systems that come with various machines.
+# The value should be what the vendor currently ships out the door with their
+# machine or put another way, the most popular os provided with the machine.
+
+# Note that if you're going to try to match "-MANUFACTURER" here (say,
+# "-sun"), then you have to tell the case statement up towards the top
+# that MANUFACTURER isn't an operating system.  Otherwise, code above
+# will signal an error saying that MANUFACTURER isn't an operating
+# system, and we'll never get to this point.
+
+case $basic_machine in
+       score-*)
+               os=-elf
+               ;;
+       spu-*)
+               os=-elf
+               ;;
+       *-acorn)
+               os=-riscix1.2
+               ;;
+       arm*-rebel)
+               os=-linux
+               ;;
+       arm*-semi)
+               os=-aout
+               ;;
+       c4x-* | tic4x-*)
+               os=-coff
+               ;;
+       tic54x-*)
+               os=-coff
+               ;;
+       tic55x-*)
+               os=-coff
+               ;;
+       tic6x-*)
+               os=-coff
+               ;;
+       # This must come before the *-dec entry.
+       pdp10-*)
+               os=-tops20
+               ;;
+       pdp11-*)
+               os=-none
+               ;;
+       *-dec | vax-*)
+               os=-ultrix4.2
+               ;;
+       m68*-apollo)
+               os=-domain
+               ;;
+       i386-sun)
+               os=-sunos4.0.2
+               ;;
+       m68000-sun)
+               os=-sunos3
+               ;;
+       m68*-cisco)
+               os=-aout
+               ;;
+       mep-*)
+               os=-elf
+               ;;
+       mips*-cisco)
+               os=-elf
+               ;;
+       mips*-*)
+               os=-elf
+               ;;
+       or32-*)
+               os=-coff
+               ;;
+       *-tti)  # must be before sparc entry or we get the wrong os.
+               os=-sysv3
+               ;;
+       sparc-* | *-sun)
+               os=-sunos4.1.1
+               ;;
+       *-be)
+               os=-beos
+               ;;
+       *-haiku)
+               os=-haiku
+               ;;
+       *-ibm)
+               os=-aix
+               ;;
+       *-knuth)
+               os=-mmixware
+               ;;
+       *-wec)
+               os=-proelf
+               ;;
+       *-winbond)
+               os=-proelf
+               ;;
+       *-oki)
+               os=-proelf
+               ;;
+       *-hp)
+               os=-hpux
+               ;;
+       *-hitachi)
+               os=-hiux
+               ;;
+       i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
+               os=-sysv
+               ;;
+       *-cbm)
+               os=-amigaos
+               ;;
+       *-dg)
+               os=-dgux
+               ;;
+       *-dolphin)
+               os=-sysv3
+               ;;
+       m68k-ccur)
+               os=-rtu
+               ;;
+       m88k-omron*)
+               os=-luna
+               ;;
+       *-next )
+               os=-nextstep
+               ;;
+       *-sequent)
+               os=-ptx
+               ;;
+       *-crds)
+               os=-unos
+               ;;
+       *-ns)
+               os=-genix
+               ;;
+       i370-*)
+               os=-mvs
+               ;;
+       *-next)
+               os=-nextstep3
+               ;;
+       *-gould)
+               os=-sysv
+               ;;
+       *-highlevel)
+               os=-bsd
+               ;;
+       *-encore)
+               os=-bsd
+               ;;
+       *-sgi)
+               os=-irix
+               ;;
+       *-siemens)
+               os=-sysv4
+               ;;
+       *-masscomp)
+               os=-rtu
+               ;;
+       f30[01]-fujitsu | f700-fujitsu)
+               os=-uxpv
+               ;;
+       *-rom68k)
+               os=-coff
+               ;;
+       *-*bug)
+               os=-coff
+               ;;
+       *-apple)
+               os=-macos
+               ;;
+       *-atari*)
+               os=-mint
+               ;;
+       *)
+               os=-none
+               ;;
+esac
+fi
+
+# Here we handle the case where we know the os, and the CPU type, but not the
+# manufacturer.  We pick the logical manufacturer.
+vendor=unknown
+case $basic_machine in
+       *-unknown)
+               case $os in
+                       -riscix*)
+                               vendor=acorn
+                               ;;
+                       -sunos*)
+                               vendor=sun
+                               ;;
+                       -cnk*|-aix*)
+                               vendor=ibm
+                               ;;
+                       -beos*)
+                               vendor=be
+                               ;;
+                       -hpux*)
+                               vendor=hp
+                               ;;
+                       -mpeix*)
+                               vendor=hp
+                               ;;
+                       -hiux*)
+                               vendor=hitachi
+                               ;;
+                       -unos*)
+                               vendor=crds
+                               ;;
+                       -dgux*)
+                               vendor=dg
+                               ;;
+                       -luna*)
+                               vendor=omron
+                               ;;
+                       -genix*)
+                               vendor=ns
+                               ;;
+                       -mvs* | -opened*)
+                               vendor=ibm
+                               ;;
+                       -os400*)
+                               vendor=ibm
+                               ;;
+                       -ptx*)
+                               vendor=sequent
+                               ;;
+                       -tpf*)
+                               vendor=ibm
+                               ;;
+                       -vxsim* | -vxworks* | -windiss*)
+                               vendor=wrs
+                               ;;
+                       -aux*)
+                               vendor=apple
+                               ;;
+                       -hms*)
+                               vendor=hitachi
+                               ;;
+                       -mpw* | -macos*)
+                               vendor=apple
+                               ;;
+                       -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+                               vendor=atari
+                               ;;
+                       -vos*)
+                               vendor=stratus
+                               ;;
+               esac
+               basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"`
+               ;;
+esac
+
+echo $basic_machine$os
+exit
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/config/depcomp b/config/depcomp
new file mode 100755 (executable)
index 0000000..bd0ac08
--- /dev/null
@@ -0,0 +1,688 @@
+#! /bin/sh
+# depcomp - compile a program generating dependencies as side-effects
+
+scriptversion=2011-12-04.11; # UTC
+
+# Copyright (C) 1999, 2000, 2003, 2004, 2005, 2006, 2007, 2009, 2010,
+# 2011 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Originally written by Alexandre Oliva <oliva@dcc.unicamp.br>.
+
+case $1 in
+  '')
+     echo "$0: No command.  Try \`$0 --help' for more information." 1>&2
+     exit 1;
+     ;;
+  -h | --h*)
+    cat <<\EOF
+Usage: depcomp [--help] [--version] PROGRAM [ARGS]
+
+Run PROGRAMS ARGS to compile a file, generating dependencies
+as side-effects.
+
+Environment variables:
+  depmode     Dependency tracking mode.
+  source      Source file read by `PROGRAMS ARGS'.
+  object      Object file output by `PROGRAMS ARGS'.
+  DEPDIR      directory where to store dependencies.
+  depfile     Dependency file to output.
+  tmpdepfile  Temporary file to use when outputting dependencies.
+  libtool     Whether libtool is used (yes/no).
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+    exit $?
+    ;;
+  -v | --v*)
+    echo "depcomp $scriptversion"
+    exit $?
+    ;;
+esac
+
+if test -z "$depmode" || test -z "$source" || test -z "$object"; then
+  echo "depcomp: Variables source, object and depmode must be set" 1>&2
+  exit 1
+fi
+
+# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po.
+depfile=${depfile-`echo "$object" |
+  sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`}
+tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`}
+
+rm -f "$tmpdepfile"
+
+# Some modes work just like other modes, but use different flags.  We
+# parameterize here, but still list the modes in the big case below,
+# to make depend.m4 easier to write.  Note that we *cannot* use a case
+# here, because this file can only contain one case statement.
+if test "$depmode" = hp; then
+  # HP compiler uses -M and no extra arg.
+  gccflag=-M
+  depmode=gcc
+fi
+
+if test "$depmode" = dashXmstdout; then
+   # This is just like dashmstdout with a different argument.
+   dashmflag=-xM
+   depmode=dashmstdout
+fi
+
+cygpath_u="cygpath -u -f -"
+if test "$depmode" = msvcmsys; then
+   # This is just like msvisualcpp but w/o cygpath translation.
+   # Just convert the backslash-escaped backslashes to single forward
+   # slashes to satisfy depend.m4
+   cygpath_u='sed s,\\\\,/,g'
+   depmode=msvisualcpp
+fi
+
+if test "$depmode" = msvc7msys; then
+   # This is just like msvc7 but w/o cygpath translation.
+   # Just convert the backslash-escaped backslashes to single forward
+   # slashes to satisfy depend.m4
+   cygpath_u='sed s,\\\\,/,g'
+   depmode=msvc7
+fi
+
+case "$depmode" in
+gcc3)
+## gcc 3 implements dependency tracking that does exactly what
+## we want.  Yay!  Note: for some reason libtool 1.4 doesn't like
+## it if -MD -MP comes after the -MF stuff.  Hmm.
+## Unfortunately, FreeBSD c89 acceptance of flags depends upon
+## the command line argument order; so add the flags where they
+## appear in depend2.am.  Note that the slowdown incurred here
+## affects only configure: in makefiles, %FASTDEP% shortcuts this.
+  for arg
+  do
+    case $arg in
+    -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;;
+    *)  set fnord "$@" "$arg" ;;
+    esac
+    shift # fnord
+    shift # $arg
+  done
+  "$@"
+  stat=$?
+  if test $stat -eq 0; then :
+  else
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  mv "$tmpdepfile" "$depfile"
+  ;;
+
+gcc)
+## There are various ways to get dependency output from gcc.  Here's
+## why we pick this rather obscure method:
+## - Don't want to use -MD because we'd like the dependencies to end
+##   up in a subdir.  Having to rename by hand is ugly.
+##   (We might end up doing this anyway to support other compilers.)
+## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like
+##   -MM, not -M (despite what the docs say).
+## - Using -M directly means running the compiler twice (even worse
+##   than renaming).
+  if test -z "$gccflag"; then
+    gccflag=-MD,
+  fi
+  "$@" -Wp,"$gccflag$tmpdepfile"
+  stat=$?
+  if test $stat -eq 0; then :
+  else
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
+## The second -e expression handles DOS-style file names with drive letters.
+  sed -e 's/^[^:]*: / /' \
+      -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile"
+## This next piece of magic avoids the `deleted header file' problem.
+## The problem is that when a header file which appears in a .P file
+## is deleted, the dependency causes make to die (because there is
+## typically no way to rebuild the header).  We avoid this by adding
+## dummy dependencies for each header file.  Too bad gcc doesn't do
+## this for us directly.
+  tr ' ' '
+' < "$tmpdepfile" |
+## Some versions of gcc put a space before the `:'.  On the theory
+## that the space means something, we add a space to the output as
+## well.  hp depmode also adds that space, but also prefixes the VPATH
+## to the object.  Take care to not repeat it in the output.
+## Some versions of the HPUX 10.20 sed can't process this invocation
+## correctly.  Breaking it into two sed invocations is a workaround.
+    sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \
+      | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+hp)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+sgi)
+  if test "$libtool" = yes; then
+    "$@" "-Wp,-MDupdate,$tmpdepfile"
+  else
+    "$@" -MDupdate "$tmpdepfile"
+  fi
+  stat=$?
+  if test $stat -eq 0; then :
+  else
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+
+  if test -f "$tmpdepfile"; then  # yes, the sourcefile depend on other files
+    echo "$object : \\" > "$depfile"
+
+    # Clip off the initial element (the dependent).  Don't try to be
+    # clever and replace this with sed code, as IRIX sed won't handle
+    # lines with more than a fixed number of characters (4096 in
+    # IRIX 6.2 sed, 8192 in IRIX 6.5).  We also remove comment lines;
+    # the IRIX cc adds comments like `#:fec' to the end of the
+    # dependency line.
+    tr ' ' '
+' < "$tmpdepfile" \
+    | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \
+    tr '
+' ' ' >> "$depfile"
+    echo >> "$depfile"
+
+    # The second pass generates a dummy entry for each header file.
+    tr ' ' '
+' < "$tmpdepfile" \
+   | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \
+   >> "$depfile"
+  else
+    # The sourcefile does not contain any dependencies, so just
+    # store a dummy comment line, to avoid errors with the Makefile
+    # "include basename.Plo" scheme.
+    echo "#dummy" > "$depfile"
+  fi
+  rm -f "$tmpdepfile"
+  ;;
+
+aix)
+  # The C for AIX Compiler uses -M and outputs the dependencies
+  # in a .u file.  In older versions, this file always lives in the
+  # current directory.  Also, the AIX compiler puts `$object:' at the
+  # start of each line; $object doesn't have directory information.
+  # Version 6 uses the directory in both cases.
+  dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
+  test "x$dir" = "x$object" && dir=
+  base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
+  if test "$libtool" = yes; then
+    tmpdepfile1=$dir$base.u
+    tmpdepfile2=$base.u
+    tmpdepfile3=$dir.libs/$base.u
+    "$@" -Wc,-M
+  else
+    tmpdepfile1=$dir$base.u
+    tmpdepfile2=$dir$base.u
+    tmpdepfile3=$dir$base.u
+    "$@" -M
+  fi
+  stat=$?
+
+  if test $stat -eq 0; then :
+  else
+    rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+    exit $stat
+  fi
+
+  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+  do
+    test -f "$tmpdepfile" && break
+  done
+  if test -f "$tmpdepfile"; then
+    # Each line is of the form `foo.o: dependent.h'.
+    # Do two passes, one to just change these to
+    # `$object: dependent.h' and one to simply `dependent.h:'.
+    sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
+    # That's a tab and a space in the [].
+    sed -e 's,^.*\.[a-z]*:[     ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile"
+  else
+    # The sourcefile does not contain any dependencies, so just
+    # store a dummy comment line, to avoid errors with the Makefile
+    # "include basename.Plo" scheme.
+    echo "#dummy" > "$depfile"
+  fi
+  rm -f "$tmpdepfile"
+  ;;
+
+icc)
+  # Intel's C compiler understands `-MD -MF file'.  However on
+  #    icc -MD -MF foo.d -c -o sub/foo.o sub/foo.c
+  # ICC 7.0 will fill foo.d with something like
+  #    foo.o: sub/foo.c
+  #    foo.o: sub/foo.h
+  # which is wrong.  We want:
+  #    sub/foo.o: sub/foo.c
+  #    sub/foo.o: sub/foo.h
+  #    sub/foo.c:
+  #    sub/foo.h:
+  # ICC 7.1 will output
+  #    foo.o: sub/foo.c sub/foo.h
+  # and will wrap long lines using \ :
+  #    foo.o: sub/foo.c ... \
+  #     sub/foo.h ... \
+  #     ...
+
+  "$@" -MD -MF "$tmpdepfile"
+  stat=$?
+  if test $stat -eq 0; then :
+  else
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  # Each line is of the form `foo.o: dependent.h',
+  # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'.
+  # Do two passes, one to just change these to
+  # `$object: dependent.h' and one to simply `dependent.h:'.
+  sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile"
+  # Some versions of the HPUX 10.20 sed can't process this invocation
+  # correctly.  Breaking it into two sed invocations is a workaround.
+  sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" |
+    sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+hp2)
+  # The "hp" stanza above does not work with aCC (C++) and HP's ia64
+  # compilers, which have integrated preprocessors.  The correct option
+  # to use with these is +Maked; it writes dependencies to a file named
+  # 'foo.d', which lands next to the object file, wherever that
+  # happens to be.
+  # Much of this is similar to the tru64 case; see comments there.
+  dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
+  test "x$dir" = "x$object" && dir=
+  base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
+  if test "$libtool" = yes; then
+    tmpdepfile1=$dir$base.d
+    tmpdepfile2=$dir.libs/$base.d
+    "$@" -Wc,+Maked
+  else
+    tmpdepfile1=$dir$base.d
+    tmpdepfile2=$dir$base.d
+    "$@" +Maked
+  fi
+  stat=$?
+  if test $stat -eq 0; then :
+  else
+     rm -f "$tmpdepfile1" "$tmpdepfile2"
+     exit $stat
+  fi
+
+  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2"
+  do
+    test -f "$tmpdepfile" && break
+  done
+  if test -f "$tmpdepfile"; then
+    sed -e "s,^.*\.[a-z]*:,$object:," "$tmpdepfile" > "$depfile"
+    # Add `dependent.h:' lines.
+    sed -ne '2,${
+              s/^ *//
+              s/ \\*$//
+              s/$/:/
+              p
+            }' "$tmpdepfile" >> "$depfile"
+  else
+    echo "#dummy" > "$depfile"
+  fi
+  rm -f "$tmpdepfile" "$tmpdepfile2"
+  ;;
+
+tru64)
+   # The Tru64 compiler uses -MD to generate dependencies as a side
+   # effect.  `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'.
+   # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put
+   # dependencies in `foo.d' instead, so we check for that too.
+   # Subdirectories are respected.
+   dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
+   test "x$dir" = "x$object" && dir=
+   base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
+
+   if test "$libtool" = yes; then
+      # With Tru64 cc, shared objects can also be used to make a
+      # static library.  This mechanism is used in libtool 1.4 series to
+      # handle both shared and static libraries in a single compilation.
+      # With libtool 1.4, dependencies were output in $dir.libs/$base.lo.d.
+      #
+      # With libtool 1.5 this exception was removed, and libtool now
+      # generates 2 separate objects for the 2 libraries.  These two
+      # compilations output dependencies in $dir.libs/$base.o.d and
+      # in $dir$base.o.d.  We have to check for both files, because
+      # one of the two compilations can be disabled.  We should prefer
+      # $dir$base.o.d over $dir.libs/$base.o.d because the latter is
+      # automatically cleaned when .libs/ is deleted, while ignoring
+      # the former would cause a distcleancheck panic.
+      tmpdepfile1=$dir.libs/$base.lo.d   # libtool 1.4
+      tmpdepfile2=$dir$base.o.d          # libtool 1.5
+      tmpdepfile3=$dir.libs/$base.o.d    # libtool 1.5
+      tmpdepfile4=$dir.libs/$base.d      # Compaq CCC V6.2-504
+      "$@" -Wc,-MD
+   else
+      tmpdepfile1=$dir$base.o.d
+      tmpdepfile2=$dir$base.d
+      tmpdepfile3=$dir$base.d
+      tmpdepfile4=$dir$base.d
+      "$@" -MD
+   fi
+
+   stat=$?
+   if test $stat -eq 0; then :
+   else
+      rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4"
+      exit $stat
+   fi
+
+   for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4"
+   do
+     test -f "$tmpdepfile" && break
+   done
+   if test -f "$tmpdepfile"; then
+      sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
+      # That's a tab and a space in the [].
+      sed -e 's,^.*\.[a-z]*:[   ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile"
+   else
+      echo "#dummy" > "$depfile"
+   fi
+   rm -f "$tmpdepfile"
+   ;;
+
+msvc7)
+  if test "$libtool" = yes; then
+    showIncludes=-Wc,-showIncludes
+  else
+    showIncludes=-showIncludes
+  fi
+  "$@" $showIncludes > "$tmpdepfile"
+  stat=$?
+  grep -v '^Note: including file: ' "$tmpdepfile"
+  if test "$stat" = 0; then :
+  else
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  # The first sed program below extracts the file names and escapes
+  # backslashes for cygpath.  The second sed program outputs the file
+  # name when reading, but also accumulates all include files in the
+  # hold buffer in order to output them again at the end.  This only
+  # works with sed implementations that can handle large buffers.
+  sed < "$tmpdepfile" -n '
+/^Note: including file:  *\(.*\)/ {
+  s//\1/
+  s/\\/\\\\/g
+  p
+}' | $cygpath_u | sort -u | sed -n '
+s/ /\\ /g
+s/\(.*\)/      \1 \\/p
+s/.\(.*\) \\/\1:/
+H
+$ {
+  s/.*/        /
+  G
+  p
+}' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+msvc7msys)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+#nosideeffect)
+  # This comment above is used by automake to tell side-effect
+  # dependency tracking mechanisms from slower ones.
+
+dashmstdout)
+  # Important note: in order to support this mode, a compiler *must*
+  # always write the preprocessed file to stdout, regardless of -o.
+  "$@" || exit $?
+
+  # Remove the call to Libtool.
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+
+  # Remove `-o $object'.
+  IFS=" "
+  for arg
+  do
+    case $arg in
+    -o)
+      shift
+      ;;
+    $object)
+      shift
+      ;;
+    *)
+      set fnord "$@" "$arg"
+      shift # fnord
+      shift # $arg
+      ;;
+    esac
+  done
+
+  test -z "$dashmflag" && dashmflag=-M
+  # Require at least two characters before searching for `:'
+  # in the target name.  This is to cope with DOS-style filenames:
+  # a dependency such as `c:/foo/bar' could be seen as target `c' otherwise.
+  "$@" $dashmflag |
+    sed 's:^[  ]*[^: ][^:][^:]*\:[    ]*:'"$object"'\: :' > "$tmpdepfile"
+  rm -f "$depfile"
+  cat < "$tmpdepfile" > "$depfile"
+  tr ' ' '
+' < "$tmpdepfile" | \
+## Some versions of the HPUX 10.20 sed can't process this invocation
+## correctly.  Breaking it into two sed invocations is a workaround.
+    sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+dashXmstdout)
+  # This case only exists to satisfy depend.m4.  It is never actually
+  # run, as this mode is specially recognized in the preamble.
+  exit 1
+  ;;
+
+makedepend)
+  "$@" || exit $?
+  # Remove any Libtool call
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+  # X makedepend
+  shift
+  cleared=no eat=no
+  for arg
+  do
+    case $cleared in
+    no)
+      set ""; shift
+      cleared=yes ;;
+    esac
+    if test $eat = yes; then
+      eat=no
+      continue
+    fi
+    case "$arg" in
+    -D*|-I*)
+      set fnord "$@" "$arg"; shift ;;
+    # Strip any option that makedepend may not understand.  Remove
+    # the object too, otherwise makedepend will parse it as a source file.
+    -arch)
+      eat=yes ;;
+    -*|$object)
+      ;;
+    *)
+      set fnord "$@" "$arg"; shift ;;
+    esac
+  done
+  obj_suffix=`echo "$object" | sed 's/^.*\././'`
+  touch "$tmpdepfile"
+  ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@"
+  rm -f "$depfile"
+  # makedepend may prepend the VPATH from the source file name to the object.
+  # No need to regex-escape $object, excess matching of '.' is harmless.
+  sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile"
+  sed '1,2d' "$tmpdepfile" | tr ' ' '
+' | \
+## Some versions of the HPUX 10.20 sed can't process this invocation
+## correctly.  Breaking it into two sed invocations is a workaround.
+    sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile" "$tmpdepfile".bak
+  ;;
+
+cpp)
+  # Important note: in order to support this mode, a compiler *must*
+  # always write the preprocessed file to stdout.
+  "$@" || exit $?
+
+  # Remove the call to Libtool.
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+
+  # Remove `-o $object'.
+  IFS=" "
+  for arg
+  do
+    case $arg in
+    -o)
+      shift
+      ;;
+    $object)
+      shift
+      ;;
+    *)
+      set fnord "$@" "$arg"
+      shift # fnord
+      shift # $arg
+      ;;
+    esac
+  done
+
+  "$@" -E |
+    sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
+       -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' |
+    sed '$ s: \\$::' > "$tmpdepfile"
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  cat < "$tmpdepfile" >> "$depfile"
+  sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+msvisualcpp)
+  # Important note: in order to support this mode, a compiler *must*
+  # always write the preprocessed file to stdout.
+  "$@" || exit $?
+
+  # Remove the call to Libtool.
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+
+  IFS=" "
+  for arg
+  do
+    case "$arg" in
+    -o)
+      shift
+      ;;
+    $object)
+      shift
+      ;;
+    "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI")
+       set fnord "$@"
+       shift
+       shift
+       ;;
+    *)
+       set fnord "$@" "$arg"
+       shift
+       shift
+       ;;
+    esac
+  done
+  "$@" -E 2>/dev/null |
+  sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile"
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::     \1 \\:p' >> "$depfile"
+  echo "       " >> "$depfile"
+  sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+msvcmsys)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+none)
+  exec "$@"
+  ;;
+
+*)
+  echo "Unknown depmode $depmode" 1>&2
+  exit 1
+  ;;
+esac
+
+exit 0
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/config/install-sh b/config/install-sh
new file mode 100755 (executable)
index 0000000..a9244eb
--- /dev/null
@@ -0,0 +1,527 @@
+#!/bin/sh
+# install - install a program, script, or datafile
+
+scriptversion=2011-01-19.21; # UTC
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+
+nl='
+'
+IFS=" ""       $nl"
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit=${DOITPROG-}
+if test -z "$doit"; then
+  doit_exec=exec
+else
+  doit_exec=$doit
+fi
+
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+chgrpprog=${CHGRPPROG-chgrp}
+chmodprog=${CHMODPROG-chmod}
+chownprog=${CHOWNPROG-chown}
+cmpprog=${CMPPROG-cmp}
+cpprog=${CPPROG-cp}
+mkdirprog=${MKDIRPROG-mkdir}
+mvprog=${MVPROG-mv}
+rmprog=${RMPROG-rm}
+stripprog=${STRIPPROG-strip}
+
+posix_glob='?'
+initialize_posix_glob='
+  test "$posix_glob" != "?" || {
+    if (set -f) 2>/dev/null; then
+      posix_glob=
+    else
+      posix_glob=:
+    fi
+  }
+'
+
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+chgrpcmd=
+chmodcmd=$chmodprog
+chowncmd=
+mvcmd=$mvprog
+rmcmd="$rmprog -f"
+stripcmd=
+
+src=
+dst=
+dir_arg=
+dst_arg=
+
+copy_on_change=false
+no_target_directory=
+
+usage="\
+Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+   or: $0 [OPTION]... SRCFILES... DIRECTORY
+   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+   or: $0 [OPTION]... -d DIRECTORIES...
+
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
+
+Options:
+     --help     display this help and exit.
+     --version  display version info and exit.
+
+  -c            (ignored)
+  -C            install only if different (preserve the last data modification time)
+  -d            create directories instead of installing files.
+  -g GROUP      $chgrpprog installed files to GROUP.
+  -m MODE       $chmodprog installed files to MODE.
+  -o USER       $chownprog installed files to USER.
+  -s            $stripprog installed files.
+  -t DIRECTORY  install into DIRECTORY.
+  -T            report an error if DSTFILE is a directory.
+
+Environment variables override the default commands:
+  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
+  RMPROG STRIPPROG
+"
+
+while test $# -ne 0; do
+  case $1 in
+    -c) ;;
+
+    -C) copy_on_change=true;;
+
+    -d) dir_arg=true;;
+
+    -g) chgrpcmd="$chgrpprog $2"
+       shift;;
+
+    --help) echo "$usage"; exit $?;;
+
+    -m) mode=$2
+       case $mode in
+         *' '* | *'    '* | *'
+'*       | *'*'* | *'?'* | *'['*)
+           echo "$0: invalid mode: $mode" >&2
+           exit 1;;
+       esac
+       shift;;
+
+    -o) chowncmd="$chownprog $2"
+       shift;;
+
+    -s) stripcmd=$stripprog;;
+
+    -t) dst_arg=$2
+       # Protect names problematic for `test' and other utilities.
+       case $dst_arg in
+         -* | [=\(\)!]) dst_arg=./$dst_arg;;
+       esac
+       shift;;
+
+    -T) no_target_directory=true;;
+
+    --version) echo "$0 $scriptversion"; exit $?;;
+
+    --)        shift
+       break;;
+
+    -*)        echo "$0: invalid option: $1" >&2
+       exit 1;;
+
+    *)  break;;
+  esac
+  shift
+done
+
+if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
+  # When -d is used, all remaining arguments are directories to create.
+  # When -t is used, the destination is already specified.
+  # Otherwise, the last argument is the destination.  Remove it from $@.
+  for arg
+  do
+    if test -n "$dst_arg"; then
+      # $@ is not empty: it contains at least $arg.
+      set fnord "$@" "$dst_arg"
+      shift # fnord
+    fi
+    shift # arg
+    dst_arg=$arg
+    # Protect names problematic for `test' and other utilities.
+    case $dst_arg in
+      -* | [=\(\)!]) dst_arg=./$dst_arg;;
+    esac
+  done
+fi
+
+if test $# -eq 0; then
+  if test -z "$dir_arg"; then
+    echo "$0: no input file specified." >&2
+    exit 1
+  fi
+  # It's OK to call `install-sh -d' without argument.
+  # This can happen when creating conditional directories.
+  exit 0
+fi
+
+if test -z "$dir_arg"; then
+  do_exit='(exit $ret); exit $ret'
+  trap "ret=129; $do_exit" 1
+  trap "ret=130; $do_exit" 2
+  trap "ret=141; $do_exit" 13
+  trap "ret=143; $do_exit" 15
+
+  # Set umask so as not to create temps with too-generous modes.
+  # However, 'strip' requires both read and write access to temps.
+  case $mode in
+    # Optimize common cases.
+    *644) cp_umask=133;;
+    *755) cp_umask=22;;
+
+    *[0-7])
+      if test -z "$stripcmd"; then
+       u_plus_rw=
+      else
+       u_plus_rw='% 200'
+      fi
+      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+    *)
+      if test -z "$stripcmd"; then
+       u_plus_rw=
+      else
+       u_plus_rw=,u+rw
+      fi
+      cp_umask=$mode$u_plus_rw;;
+  esac
+fi
+
+for src
+do
+  # Protect names problematic for `test' and other utilities.
+  case $src in
+    -* | [=\(\)!]) src=./$src;;
+  esac
+
+  if test -n "$dir_arg"; then
+    dst=$src
+    dstdir=$dst
+    test -d "$dstdir"
+    dstdir_status=$?
+  else
+
+    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+    # might cause directories to be created, which would be especially bad
+    # if $src (and thus $dsttmp) contains '*'.
+    if test ! -f "$src" && test ! -d "$src"; then
+      echo "$0: $src does not exist." >&2
+      exit 1
+    fi
+
+    if test -z "$dst_arg"; then
+      echo "$0: no destination specified." >&2
+      exit 1
+    fi
+    dst=$dst_arg
+
+    # If destination is a directory, append the input filename; won't work
+    # if double slashes aren't ignored.
+    if test -d "$dst"; then
+      if test -n "$no_target_directory"; then
+       echo "$0: $dst_arg: Is a directory" >&2
+       exit 1
+      fi
+      dstdir=$dst
+      dst=$dstdir/`basename "$src"`
+      dstdir_status=0
+    else
+      # Prefer dirname, but fall back on a substitute if dirname fails.
+      dstdir=`
+       (dirname "$dst") 2>/dev/null ||
+       expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+            X"$dst" : 'X\(//\)[^/]' \| \
+            X"$dst" : 'X\(//\)$' \| \
+            X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
+       echo X"$dst" |
+           sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+                  s//\1/
+                  q
+                }
+                /^X\(\/\/\)[^/].*/{
+                  s//\1/
+                  q
+                }
+                /^X\(\/\/\)$/{
+                  s//\1/
+                  q
+                }
+                /^X\(\/\).*/{
+                  s//\1/
+                  q
+                }
+                s/.*/./; q'
+      `
+
+      test -d "$dstdir"
+      dstdir_status=$?
+    fi
+  fi
+
+  obsolete_mkdir_used=false
+
+  if test $dstdir_status != 0; then
+    case $posix_mkdir in
+      '')
+       # Create intermediate dirs using mode 755 as modified by the umask.
+       # This is like FreeBSD 'install' as of 1997-10-28.
+       umask=`umask`
+       case $stripcmd.$umask in
+         # Optimize common cases.
+         *[2367][2367]) mkdir_umask=$umask;;
+         .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
+
+         *[0-7])
+           mkdir_umask=`expr $umask + 22 \
+             - $umask % 100 % 40 + $umask % 20 \
+             - $umask % 10 % 4 + $umask % 2
+           `;;
+         *) mkdir_umask=$umask,go-w;;
+       esac
+
+       # With -d, create the new directory with the user-specified mode.
+       # Otherwise, rely on $mkdir_umask.
+       if test -n "$dir_arg"; then
+         mkdir_mode=-m$mode
+       else
+         mkdir_mode=
+       fi
+
+       posix_mkdir=false
+       case $umask in
+         *[123567][0-7][0-7])
+           # POSIX mkdir -p sets u+wx bits regardless of umask, which
+           # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
+           ;;
+         *)
+           tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+           trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
+
+           if (umask $mkdir_umask &&
+               exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
+           then
+             if test -z "$dir_arg" || {
+                  # Check for POSIX incompatibilities with -m.
+                  # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+                  # other-writeable bit of parent directory when it shouldn't.
+                  # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+                  ls_ld_tmpdir=`ls -ld "$tmpdir"`
+                  case $ls_ld_tmpdir in
+                    d????-?r-*) different_mode=700;;
+                    d????-?--*) different_mode=755;;
+                    *) false;;
+                  esac &&
+                  $mkdirprog -m$different_mode -p -- "$tmpdir" && {
+                    ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
+                    test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+                  }
+                }
+             then posix_mkdir=:
+             fi
+             rmdir "$tmpdir/d" "$tmpdir"
+           else
+             # Remove any dirs left behind by ancient mkdir implementations.
+             rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
+           fi
+           trap '' 0;;
+       esac;;
+    esac
+
+    if
+      $posix_mkdir && (
+       umask $mkdir_umask &&
+       $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+      )
+    then :
+    else
+
+      # The umask is ridiculous, or mkdir does not conform to POSIX,
+      # or it failed possibly due to a race condition.  Create the
+      # directory the slow way, step by step, checking for races as we go.
+
+      case $dstdir in
+       /*) prefix='/';;
+       [-=\(\)!]*) prefix='./';;
+       *)  prefix='';;
+      esac
+
+      eval "$initialize_posix_glob"
+
+      oIFS=$IFS
+      IFS=/
+      $posix_glob set -f
+      set fnord $dstdir
+      shift
+      $posix_glob set +f
+      IFS=$oIFS
+
+      prefixes=
+
+      for d
+      do
+       test X"$d" = X && continue
+
+       prefix=$prefix$d
+       if test -d "$prefix"; then
+         prefixes=
+       else
+         if $posix_mkdir; then
+           (umask=$mkdir_umask &&
+            $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+           # Don't fail if two instances are running concurrently.
+           test -d "$prefix" || exit 1
+         else
+           case $prefix in
+             *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+             *) qprefix=$prefix;;
+           esac
+           prefixes="$prefixes '$qprefix'"
+         fi
+       fi
+       prefix=$prefix/
+      done
+
+      if test -n "$prefixes"; then
+       # Don't fail if two instances are running concurrently.
+       (umask $mkdir_umask &&
+        eval "\$doit_exec \$mkdirprog $prefixes") ||
+         test -d "$dstdir" || exit 1
+       obsolete_mkdir_used=true
+      fi
+    fi
+  fi
+
+  if test -n "$dir_arg"; then
+    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+  else
+
+    # Make a couple of temp file names in the proper directory.
+    dsttmp=$dstdir/_inst.$$_
+    rmtmp=$dstdir/_rm.$$_
+
+    # Trap to clean up those temp files at exit.
+    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+    # Copy the file name to the temp name.
+    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
+
+    # and set any options; do chmod last to preserve setuid bits.
+    #
+    # If any of these fail, we abort the whole thing.  If we want to
+    # ignore errors from any of these, just make sure not to ignore
+    # errors from the above "$doit $cpprog $src $dsttmp" command.
+    #
+    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
+    { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
+    { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+    # If -C, don't bother to copy if it wouldn't change the file.
+    if $copy_on_change &&
+       old=`LC_ALL=C ls -dlL "$dst"    2>/dev/null` &&
+       new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
+
+       eval "$initialize_posix_glob" &&
+       $posix_glob set -f &&
+       set X $old && old=:$2:$4:$5:$6 &&
+       set X $new && new=:$2:$4:$5:$6 &&
+       $posix_glob set +f &&
+
+       test "$old" = "$new" &&
+       $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
+    then
+      rm -f "$dsttmp"
+    else
+      # Rename the file to the real destination.
+      $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
+
+      # The rename failed, perhaps because mv can't rename something else
+      # to itself, or perhaps because mv is so ancient that it does not
+      # support -f.
+      {
+       # Now remove or move aside any old file at destination location.
+       # We try this two ways since rm can't unlink itself on some
+       # systems and the destination file might be busy for other
+       # reasons.  In this case, the final cleanup might fail but the new
+       # file should still install successfully.
+       {
+         test ! -f "$dst" ||
+         $doit $rmcmd -f "$dst" 2>/dev/null ||
+         { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
+           { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
+         } ||
+         { echo "$0: cannot unlink or rename $dst" >&2
+           (exit 1); exit 1
+         }
+       } &&
+
+       # Now rename the file to the real destination.
+       $doit $mvcmd "$dsttmp" "$dst"
+      }
+    fi || exit 1
+
+    trap '' 0
+  fi
+done
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/config/ltmain.sh b/config/ltmain.sh
new file mode 100644 (file)
index 0000000..c2852d8
--- /dev/null
@@ -0,0 +1,9661 @@
+
+# libtool (GNU libtool) 2.4.2
+# Written by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996
+
+# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006,
+# 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
+# This is free software; see the source for copying conditions.  There is NO
+# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# GNU Libtool is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# As a special exception to the GNU General Public License,
+# if you distribute this file as part of a program or library that
+# is built using GNU Libtool, you may include this file under the
+# same distribution terms that you use for the rest of that program.
+#
+# GNU Libtool is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Libtool; see the file COPYING.  If not, a copy
+# can be downloaded from http://www.gnu.org/licenses/gpl.html,
+# or obtained by writing to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+# Usage: $progname [OPTION]... [MODE-ARG]...
+#
+# Provide generalized library-building support services.
+#
+#       --config             show all configuration variables
+#       --debug              enable verbose shell tracing
+#   -n, --dry-run            display commands without modifying any files
+#       --features           display basic configuration information and exit
+#       --mode=MODE          use operation mode MODE
+#       --preserve-dup-deps  don't remove duplicate dependency libraries
+#       --quiet, --silent    don't print informational messages
+#       --no-quiet, --no-silent
+#                            print informational messages (default)
+#       --no-warn            don't display warning messages
+#       --tag=TAG            use configuration variables from tag TAG
+#   -v, --verbose            print more informational messages than default
+#       --no-verbose         don't print the extra informational messages
+#       --version            print version information
+#   -h, --help, --help-all   print short, long, or detailed help message
+#
+# MODE must be one of the following:
+#
+#         clean              remove files from the build directory
+#         compile            compile a source file into a libtool object
+#         execute            automatically set library path, then run a program
+#         finish             complete the installation of libtool libraries
+#         install            install libraries or executables
+#         link               create a library or an executable
+#         uninstall          remove libraries from an installed directory
+#
+# MODE-ARGS vary depending on the MODE.  When passed as first option,
+# `--mode=MODE' may be abbreviated as `MODE' or a unique abbreviation of that.
+# Try `$progname --help --mode=MODE' for a more detailed description of MODE.
+#
+# When reporting a bug, please describe a test case to reproduce it and
+# include the following information:
+#
+#         host-triplet:        $host
+#         shell:               $SHELL
+#         compiler:            $LTCC
+#         compiler flags:              $LTCFLAGS
+#         linker:              $LD (gnu? $with_gnu_ld)
+#         $progname:   (GNU libtool) 2.4.2 Debian-2.4.2-1ubuntu1
+#         automake:    $automake_version
+#         autoconf:    $autoconf_version
+#
+# Report bugs to <bug-libtool@gnu.org>.
+# GNU libtool home page: <http://www.gnu.org/software/libtool/>.
+# General help using GNU software: <http://www.gnu.org/gethelp/>.
+
+PROGRAM=libtool
+PACKAGE=libtool
+VERSION="2.4.2 Debian-2.4.2-1ubuntu1"
+TIMESTAMP=""
+package_revision=1.3337
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+  eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+
+# NLS nuisances: We save the old values to restore during execute mode.
+lt_user_locale=
+lt_safe_locale=
+for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES
+do
+  eval "if test \"\${$lt_var+set}\" = set; then
+          save_$lt_var=\$$lt_var
+          $lt_var=C
+         export $lt_var
+         lt_user_locale=\"$lt_var=\\\$save_\$lt_var; \$lt_user_locale\"
+         lt_safe_locale=\"$lt_var=C; \$lt_safe_locale\"
+       fi"
+done
+LC_ALL=C
+LANGUAGE=C
+export LANGUAGE LC_ALL
+
+$lt_unset CDPATH
+
+
+# Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh
+# is ksh but when the shell is invoked as "sh" and the current value of
+# the _XPG environment variable is not equal to 1 (one), the special
+# positional parameter $0, within a function call, is the name of the
+# function.
+progpath="$0"
+
+
+
+: ${CP="cp -f"}
+test "${ECHO+set}" = set || ECHO=${as_echo-'printf %s\n'}
+: ${MAKE="make"}
+: ${MKDIR="mkdir"}
+: ${MV="mv -f"}
+: ${RM="rm -f"}
+: ${SHELL="${CONFIG_SHELL-/bin/sh}"}
+: ${Xsed="$SED -e 1s/^X//"}
+
+# Global variables:
+EXIT_SUCCESS=0
+EXIT_FAILURE=1
+EXIT_MISMATCH=63  # $? = 63 is used to indicate version mismatch to missing.
+EXIT_SKIP=77     # $? = 77 is used to indicate a skipped test to automake.
+
+exit_status=$EXIT_SUCCESS
+
+# Make sure IFS has a sensible default
+lt_nl='
+'
+IFS="  $lt_nl"
+
+dirname="s,/[^/]*$,,"
+basename="s,^.*/,,"
+
+# func_dirname file append nondir_replacement
+# Compute the dirname of FILE.  If nonempty, add APPEND to the result,
+# otherwise set result to NONDIR_REPLACEMENT.
+func_dirname ()
+{
+    func_dirname_result=`$ECHO "${1}" | $SED "$dirname"`
+    if test "X$func_dirname_result" = "X${1}"; then
+      func_dirname_result="${3}"
+    else
+      func_dirname_result="$func_dirname_result${2}"
+    fi
+} # func_dirname may be replaced by extended shell implementation
+
+
+# func_basename file
+func_basename ()
+{
+    func_basename_result=`$ECHO "${1}" | $SED "$basename"`
+} # func_basename may be replaced by extended shell implementation
+
+
+# func_dirname_and_basename file append nondir_replacement
+# perform func_basename and func_dirname in a single function
+# call:
+#   dirname:  Compute the dirname of FILE.  If nonempty,
+#             add APPEND to the result, otherwise set result
+#             to NONDIR_REPLACEMENT.
+#             value returned in "$func_dirname_result"
+#   basename: Compute filename of FILE.
+#             value retuned in "$func_basename_result"
+# Implementation must be kept synchronized with func_dirname
+# and func_basename. For efficiency, we do not delegate to
+# those functions but instead duplicate the functionality here.
+func_dirname_and_basename ()
+{
+    # Extract subdirectory from the argument.
+    func_dirname_result=`$ECHO "${1}" | $SED -e "$dirname"`
+    if test "X$func_dirname_result" = "X${1}"; then
+      func_dirname_result="${3}"
+    else
+      func_dirname_result="$func_dirname_result${2}"
+    fi
+    func_basename_result=`$ECHO "${1}" | $SED -e "$basename"`
+} # func_dirname_and_basename may be replaced by extended shell implementation
+
+
+# func_stripname prefix suffix name
+# strip PREFIX and SUFFIX off of NAME.
+# PREFIX and SUFFIX must not contain globbing or regex special
+# characters, hashes, percent signs, but SUFFIX may contain a leading
+# dot (in which case that matches only a dot).
+# func_strip_suffix prefix name
+func_stripname ()
+{
+    case ${2} in
+      .*) func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%\\\\${2}\$%%"`;;
+      *)  func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%${2}\$%%"`;;
+    esac
+} # func_stripname may be replaced by extended shell implementation
+
+
+# These SED scripts presuppose an absolute path with a trailing slash.
+pathcar='s,^/\([^/]*\).*$,\1,'
+pathcdr='s,^/[^/]*,,'
+removedotparts=':dotsl
+               s@/\./@/@g
+               t dotsl
+               s,/\.$,/,'
+collapseslashes='s@/\{1,\}@/@g'
+finalslash='s,/*$,/,'
+
+# func_normal_abspath PATH
+# Remove doubled-up and trailing slashes, "." path components,
+# and cancel out any ".." path components in PATH after making
+# it an absolute path.
+#             value returned in "$func_normal_abspath_result"
+func_normal_abspath ()
+{
+  # Start from root dir and reassemble the path.
+  func_normal_abspath_result=
+  func_normal_abspath_tpath=$1
+  func_normal_abspath_altnamespace=
+  case $func_normal_abspath_tpath in
+    "")
+      # Empty path, that just means $cwd.
+      func_stripname '' '/' "`pwd`"
+      func_normal_abspath_result=$func_stripname_result
+      return
+    ;;
+    # The next three entries are used to spot a run of precisely
+    # two leading slashes without using negated character classes;
+    # we take advantage of case's first-match behaviour.
+    ///*)
+      # Unusual form of absolute path, do nothing.
+    ;;
+    //*)
+      # Not necessarily an ordinary path; POSIX reserves leading '//'
+      # and for example Cygwin uses it to access remote file shares
+      # over CIFS/SMB, so we conserve a leading double slash if found.
+      func_normal_abspath_altnamespace=/
+    ;;
+    /*)
+      # Absolute path, do nothing.
+    ;;
+    *)
+      # Relative path, prepend $cwd.
+      func_normal_abspath_tpath=`pwd`/$func_normal_abspath_tpath
+    ;;
+  esac
+  # Cancel out all the simple stuff to save iterations.  We also want
+  # the path to end with a slash for ease of parsing, so make sure
+  # there is one (and only one) here.
+  func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \
+        -e "$removedotparts" -e "$collapseslashes" -e "$finalslash"`
+  while :; do
+    # Processed it all yet?
+    if test "$func_normal_abspath_tpath" = / ; then
+      # If we ascended to the root using ".." the result may be empty now.
+      if test -z "$func_normal_abspath_result" ; then
+        func_normal_abspath_result=/
+      fi
+      break
+    fi
+    func_normal_abspath_tcomponent=`$ECHO "$func_normal_abspath_tpath" | $SED \
+        -e "$pathcar"`
+    func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \
+        -e "$pathcdr"`
+    # Figure out what to do with it
+    case $func_normal_abspath_tcomponent in
+      "")
+        # Trailing empty path component, ignore it.
+      ;;
+      ..)
+        # Parent dir; strip last assembled component from result.
+        func_dirname "$func_normal_abspath_result"
+        func_normal_abspath_result=$func_dirname_result
+      ;;
+      *)
+        # Actual path component, append it.
+        func_normal_abspath_result=$func_normal_abspath_result/$func_normal_abspath_tcomponent
+      ;;
+    esac
+  done
+  # Restore leading double-slash if one was found on entry.
+  func_normal_abspath_result=$func_normal_abspath_altnamespace$func_normal_abspath_result
+}
+
+# func_relative_path SRCDIR DSTDIR
+# generates a relative path from SRCDIR to DSTDIR, with a trailing
+# slash if non-empty, suitable for immediately appending a filename
+# without needing to append a separator.
+#             value returned in "$func_relative_path_result"
+func_relative_path ()
+{
+  func_relative_path_result=
+  func_normal_abspath "$1"
+  func_relative_path_tlibdir=$func_normal_abspath_result
+  func_normal_abspath "$2"
+  func_relative_path_tbindir=$func_normal_abspath_result
+
+  # Ascend the tree starting from libdir
+  while :; do
+    # check if we have found a prefix of bindir
+    case $func_relative_path_tbindir in
+      $func_relative_path_tlibdir)
+        # found an exact match
+        func_relative_path_tcancelled=
+        break
+        ;;
+      $func_relative_path_tlibdir*)
+        # found a matching prefix
+        func_stripname "$func_relative_path_tlibdir" '' "$func_relative_path_tbindir"
+        func_relative_path_tcancelled=$func_stripname_result
+        if test -z "$func_relative_path_result"; then
+          func_relative_path_result=.
+        fi
+        break
+        ;;
+      *)
+        func_dirname $func_relative_path_tlibdir
+        func_relative_path_tlibdir=${func_dirname_result}
+        if test "x$func_relative_path_tlibdir" = x ; then
+          # Have to descend all the way to the root!
+          func_relative_path_result=../$func_relative_path_result
+          func_relative_path_tcancelled=$func_relative_path_tbindir
+          break
+        fi
+        func_relative_path_result=../$func_relative_path_result
+        ;;
+    esac
+  done
+
+  # Now calculate path; take care to avoid doubling-up slashes.
+  func_stripname '' '/' "$func_relative_path_result"
+  func_relative_path_result=$func_stripname_result
+  func_stripname '/' '/' "$func_relative_path_tcancelled"
+  if test "x$func_stripname_result" != x ; then
+    func_relative_path_result=${func_relative_path_result}/${func_stripname_result}
+  fi
+
+  # Normalisation. If bindir is libdir, return empty string,
+  # else relative path ending with a slash; either way, target
+  # file name can be directly appended.
+  if test ! -z "$func_relative_path_result"; then
+    func_stripname './' '' "$func_relative_path_result/"
+    func_relative_path_result=$func_stripname_result
+  fi
+}
+
+# The name of this program:
+func_dirname_and_basename "$progpath"
+progname=$func_basename_result
+
+# Make sure we have an absolute path for reexecution:
+case $progpath in
+  [\\/]*|[A-Za-z]:\\*) ;;
+  *[\\/]*)
+     progdir=$func_dirname_result
+     progdir=`cd "$progdir" && pwd`
+     progpath="$progdir/$progname"
+     ;;
+  *)
+     save_IFS="$IFS"
+     IFS=${PATH_SEPARATOR-:}
+     for progdir in $PATH; do
+       IFS="$save_IFS"
+       test -x "$progdir/$progname" && break
+     done
+     IFS="$save_IFS"
+     test -n "$progdir" || progdir=`pwd`
+     progpath="$progdir/$progname"
+     ;;
+esac
+
+# Sed substitution that helps us do robust quoting.  It backslashifies
+# metacharacters that are still active within double-quoted strings.
+Xsed="${SED}"' -e 1s/^X//'
+sed_quote_subst='s/\([`"$\\]\)/\\\1/g'
+
+# Same as above, but do not quote variable references.
+double_quote_subst='s/\(["`\\]\)/\\\1/g'
+
+# Sed substitution that turns a string into a regex matching for the
+# string literally.
+sed_make_literal_regex='s,[].[^$\\*\/],\\&,g'
+
+# Sed substitution that converts a w32 file name or path
+# which contains forward slashes, into one that contains
+# (escaped) backslashes.  A very naive implementation.
+lt_sed_naive_backslashify='s|\\\\*|\\|g;s|/|\\|g;s|\\|\\\\|g'
+
+# Re-`\' parameter expansions in output of double_quote_subst that were
+# `\'-ed in input to the same.  If an odd number of `\' preceded a '$'
+# in input to double_quote_subst, that '$' was protected from expansion.
+# Since each input `\' is now two `\'s, look for any number of runs of
+# four `\'s followed by two `\'s and then a '$'.  `\' that '$'.
+bs='\\'
+bs2='\\\\'
+bs4='\\\\\\\\'
+dollar='\$'
+sed_double_backslash="\
+  s/$bs4/&\\
+/g
+  s/^$bs2$dollar/$bs&/
+  s/\\([^$bs]\\)$bs2$dollar/\\1$bs2$bs$dollar/g
+  s/\n//g"
+
+# Standard options:
+opt_dry_run=false
+opt_help=false
+opt_quiet=false
+opt_verbose=false
+opt_warning=:
+
+# func_echo arg...
+# Echo program name prefixed message, along with the current mode
+# name if it has been set yet.
+func_echo ()
+{
+    $ECHO "$progname: ${opt_mode+$opt_mode: }$*"
+}
+
+# func_verbose arg...
+# Echo program name prefixed message in verbose mode only.
+func_verbose ()
+{
+    $opt_verbose && func_echo ${1+"$@"}
+
+    # A bug in bash halts the script if the last line of a function
+    # fails when set -e is in force, so we need another command to
+    # work around that:
+    :
+}
+
+# func_echo_all arg...
+# Invoke $ECHO with all args, space-separated.
+func_echo_all ()
+{
+    $ECHO "$*"
+}
+
+# func_error arg...
+# Echo program name prefixed message to standard error.
+func_error ()
+{
+    $ECHO "$progname: ${opt_mode+$opt_mode: }"${1+"$@"} 1>&2
+}
+
+# func_warning arg...
+# Echo program name prefixed warning message to standard error.
+func_warning ()
+{
+    $opt_warning && $ECHO "$progname: ${opt_mode+$opt_mode: }warning: "${1+"$@"} 1>&2
+
+    # bash bug again:
+    :
+}
+
+# func_fatal_error arg...
+# Echo program name prefixed message to standard error, and exit.
+func_fatal_error ()
+{
+    func_error ${1+"$@"}
+    exit $EXIT_FAILURE
+}
+
+# func_fatal_help arg...
+# Echo program name prefixed message to standard error, followed by
+# a help hint, and exit.
+func_fatal_help ()
+{
+    func_error ${1+"$@"}
+    func_fatal_error "$help"
+}
+help="Try \`$progname --help' for more information."  ## default
+
+
+# func_grep expression filename
+# Check whether EXPRESSION matches any line of FILENAME, without output.
+func_grep ()
+{
+    $GREP "$1" "$2" >/dev/null 2>&1
+}
+
+
+# func_mkdir_p directory-path
+# Make sure the entire path to DIRECTORY-PATH is available.
+func_mkdir_p ()
+{
+    my_directory_path="$1"
+    my_dir_list=
+
+    if test -n "$my_directory_path" && test "$opt_dry_run" != ":"; then
+
+      # Protect directory names starting with `-'
+      case $my_directory_path in
+        -*) my_directory_path="./$my_directory_path" ;;
+      esac
+
+      # While some portion of DIR does not yet exist...
+      while test ! -d "$my_directory_path"; do
+        # ...make a list in topmost first order.  Use a colon delimited
+       # list incase some portion of path contains whitespace.
+        my_dir_list="$my_directory_path:$my_dir_list"
+
+        # If the last portion added has no slash in it, the list is done
+        case $my_directory_path in */*) ;; *) break ;; esac
+
+        # ...otherwise throw away the child directory and loop
+        my_directory_path=`$ECHO "$my_directory_path" | $SED -e "$dirname"`
+      done
+      my_dir_list=`$ECHO "$my_dir_list" | $SED 's,:*$,,'`
+
+      save_mkdir_p_IFS="$IFS"; IFS=':'
+      for my_dir in $my_dir_list; do
+       IFS="$save_mkdir_p_IFS"
+        # mkdir can fail with a `File exist' error if two processes
+        # try to create one of the directories concurrently.  Don't
+        # stop in that case!
+        $MKDIR "$my_dir" 2>/dev/null || :
+      done
+      IFS="$save_mkdir_p_IFS"
+
+      # Bail out if we (or some other process) failed to create a directory.
+      test -d "$my_directory_path" || \
+        func_fatal_error "Failed to create \`$1'"
+    fi
+}
+
+
+# func_mktempdir [string]
+# Make a temporary directory that won't clash with other running
+# libtool processes, and avoids race conditions if possible.  If
+# given, STRING is the basename for that directory.
+func_mktempdir ()
+{
+    my_template="${TMPDIR-/tmp}/${1-$progname}"
+
+    if test "$opt_dry_run" = ":"; then
+      # Return a directory name, but don't create it in dry-run mode
+      my_tmpdir="${my_template}-$$"
+    else
+
+      # If mktemp works, use that first and foremost
+      my_tmpdir=`mktemp -d "${my_template}-XXXXXXXX" 2>/dev/null`
+
+      if test ! -d "$my_tmpdir"; then
+        # Failing that, at least try and use $RANDOM to avoid a race
+        my_tmpdir="${my_template}-${RANDOM-0}$$"
+
+        save_mktempdir_umask=`umask`
+        umask 0077
+        $MKDIR "$my_tmpdir"
+        umask $save_mktempdir_umask
+      fi
+
+      # If we're not in dry-run mode, bomb out on failure
+      test -d "$my_tmpdir" || \
+        func_fatal_error "cannot create temporary directory \`$my_tmpdir'"
+    fi
+
+    $ECHO "$my_tmpdir"
+}
+
+
+# func_quote_for_eval arg
+# Aesthetically quote ARG to be evaled later.
+# This function returns two values: FUNC_QUOTE_FOR_EVAL_RESULT
+# is double-quoted, suitable for a subsequent eval, whereas
+# FUNC_QUOTE_FOR_EVAL_UNQUOTED_RESULT has merely all characters
+# which are still active within double quotes backslashified.
+func_quote_for_eval ()
+{
+    case $1 in
+      *[\\\`\"\$]*)
+       func_quote_for_eval_unquoted_result=`$ECHO "$1" | $SED "$sed_quote_subst"` ;;
+      *)
+        func_quote_for_eval_unquoted_result="$1" ;;
+    esac
+
+    case $func_quote_for_eval_unquoted_result in
+      # Double-quote args containing shell metacharacters to delay
+      # word splitting, command substitution and and variable
+      # expansion for a subsequent eval.
+      # Many Bourne shells cannot handle close brackets correctly
+      # in scan sets, so we specify it separately.
+      *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \    ]*|*]*|"")
+        func_quote_for_eval_result="\"$func_quote_for_eval_unquoted_result\""
+        ;;
+      *)
+        func_quote_for_eval_result="$func_quote_for_eval_unquoted_result"
+    esac
+}
+
+
+# func_quote_for_expand arg
+# Aesthetically quote ARG to be evaled later; same as above,
+# but do not quote variable references.
+func_quote_for_expand ()
+{
+    case $1 in
+      *[\\\`\"]*)
+       my_arg=`$ECHO "$1" | $SED \
+           -e "$double_quote_subst" -e "$sed_double_backslash"` ;;
+      *)
+        my_arg="$1" ;;
+    esac
+
+    case $my_arg in
+      # Double-quote args containing shell metacharacters to delay
+      # word splitting and command substitution for a subsequent eval.
+      # Many Bourne shells cannot handle close brackets correctly
+      # in scan sets, so we specify it separately.
+      *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \    ]*|*]*|"")
+        my_arg="\"$my_arg\""
+        ;;
+    esac
+
+    func_quote_for_expand_result="$my_arg"
+}
+
+
+# func_show_eval cmd [fail_exp]
+# Unless opt_silent is true, then output CMD.  Then, if opt_dryrun is
+# not true, evaluate CMD.  If the evaluation of CMD fails, and FAIL_EXP
+# is given, then evaluate it.
+func_show_eval ()
+{
+    my_cmd="$1"
+    my_fail_exp="${2-:}"
+
+    ${opt_silent-false} || {
+      func_quote_for_expand "$my_cmd"
+      eval "func_echo $func_quote_for_expand_result"
+    }
+
+    if ${opt_dry_run-false}; then :; else
+      eval "$my_cmd"
+      my_status=$?
+      if test "$my_status" -eq 0; then :; else
+       eval "(exit $my_status); $my_fail_exp"
+      fi
+    fi
+}
+
+
+# func_show_eval_locale cmd [fail_exp]
+# Unless opt_silent is true, then output CMD.  Then, if opt_dryrun is
+# not true, evaluate CMD.  If the evaluation of CMD fails, and FAIL_EXP
+# is given, then evaluate it.  Use the saved locale for evaluation.
+func_show_eval_locale ()
+{
+    my_cmd="$1"
+    my_fail_exp="${2-:}"
+
+    ${opt_silent-false} || {
+      func_quote_for_expand "$my_cmd"
+      eval "func_echo $func_quote_for_expand_result"
+    }
+
+    if ${opt_dry_run-false}; then :; else
+      eval "$lt_user_locale
+           $my_cmd"
+      my_status=$?
+      eval "$lt_safe_locale"
+      if test "$my_status" -eq 0; then :; else
+       eval "(exit $my_status); $my_fail_exp"
+      fi
+    fi
+}
+
+# func_tr_sh
+# Turn $1 into a string suitable for a shell variable name.
+# Result is stored in $func_tr_sh_result.  All characters
+# not in the set a-zA-Z0-9_ are replaced with '_'. Further,
+# if $1 begins with a digit, a '_' is prepended as well.
+func_tr_sh ()
+{
+  case $1 in
+  [0-9]* | *[!a-zA-Z0-9_]*)
+    func_tr_sh_result=`$ECHO "$1" | $SED 's/^\([0-9]\)/_\1/; s/[^a-zA-Z0-9_]/_/g'`
+    ;;
+  * )
+    func_tr_sh_result=$1
+    ;;
+  esac
+}
+
+
+# func_version
+# Echo version message to standard output and exit.
+func_version ()
+{
+    $opt_debug
+
+    $SED -n '/(C)/!b go
+       :more
+       /\./!{
+         N
+         s/\n# / /
+         b more
+       }
+       :go
+       /^# '$PROGRAM' (GNU /,/# warranty; / {
+        s/^# //
+       s/^# *$//
+        s/\((C)\)[ 0-9,-]*\( [1-9][0-9]*\)/\1\2/
+        p
+     }' < "$progpath"
+     exit $?
+}
+
+# func_usage
+# Echo short help message to standard output and exit.
+func_usage ()
+{
+    $opt_debug
+
+    $SED -n '/^# Usage:/,/^#  *.*--help/ {
+        s/^# //
+       s/^# *$//
+       s/\$progname/'$progname'/
+       p
+    }' < "$progpath"
+    echo
+    $ECHO "run \`$progname --help | more' for full usage"
+    exit $?
+}
+
+# func_help [NOEXIT]
+# Echo long help message to standard output and exit,
+# unless 'noexit' is passed as argument.
+func_help ()
+{
+    $opt_debug
+
+    $SED -n '/^# Usage:/,/# Report bugs to/ {
+       :print
+        s/^# //
+       s/^# *$//
+       s*\$progname*'$progname'*
+       s*\$host*'"$host"'*
+       s*\$SHELL*'"$SHELL"'*
+       s*\$LTCC*'"$LTCC"'*
+       s*\$LTCFLAGS*'"$LTCFLAGS"'*
+       s*\$LD*'"$LD"'*
+       s/\$with_gnu_ld/'"$with_gnu_ld"'/
+       s/\$automake_version/'"`(${AUTOMAKE-automake} --version) 2>/dev/null |$SED 1q`"'/
+       s/\$autoconf_version/'"`(${AUTOCONF-autoconf} --version) 2>/dev/null |$SED 1q`"'/
+       p
+       d
+     }
+     /^# .* home page:/b print
+     /^# General help using/b print
+     ' < "$progpath"
+    ret=$?
+    if test -z "$1"; then
+      exit $ret
+    fi
+}
+
+# func_missing_arg argname
+# Echo program name prefixed message to standard error and set global
+# exit_cmd.
+func_missing_arg ()
+{
+    $opt_debug
+
+    func_error "missing argument for $1."
+    exit_cmd=exit
+}
+
+
+# func_split_short_opt shortopt
+# Set func_split_short_opt_name and func_split_short_opt_arg shell
+# variables after splitting SHORTOPT after the 2nd character.
+func_split_short_opt ()
+{
+    my_sed_short_opt='1s/^\(..\).*$/\1/;q'
+    my_sed_short_rest='1s/^..\(.*\)$/\1/;q'
+
+    func_split_short_opt_name=`$ECHO "$1" | $SED "$my_sed_short_opt"`
+    func_split_short_opt_arg=`$ECHO "$1" | $SED "$my_sed_short_rest"`
+} # func_split_short_opt may be replaced by extended shell implementation
+
+
+# func_split_long_opt longopt
+# Set func_split_long_opt_name and func_split_long_opt_arg shell
+# variables after splitting LONGOPT at the `=' sign.
+func_split_long_opt ()
+{
+    my_sed_long_opt='1s/^\(--[^=]*\)=.*/\1/;q'
+    my_sed_long_arg='1s/^--[^=]*=//'
+
+    func_split_long_opt_name=`$ECHO "$1" | $SED "$my_sed_long_opt"`
+    func_split_long_opt_arg=`$ECHO "$1" | $SED "$my_sed_long_arg"`
+} # func_split_long_opt may be replaced by extended shell implementation
+
+exit_cmd=:
+
+
+
+
+
+magic="%%%MAGIC variable%%%"
+magic_exe="%%%MAGIC EXE variable%%%"
+
+# Global variables.
+nonopt=
+preserve_args=
+lo2o="s/\\.lo\$/.${objext}/"
+o2lo="s/\\.${objext}\$/.lo/"
+extracted_archives=
+extracted_serial=0
+
+# If this variable is set in any of the actions, the command in it
+# will be execed at the end.  This prevents here-documents from being
+# left over by shells.
+exec_cmd=
+
+# func_append var value
+# Append VALUE to the end of shell variable VAR.
+func_append ()
+{
+    eval "${1}=\$${1}\${2}"
+} # func_append may be replaced by extended shell implementation
+
+# func_append_quoted var value
+# Quote VALUE and append to the end of shell variable VAR, separated
+# by a space.
+func_append_quoted ()
+{
+    func_quote_for_eval "${2}"
+    eval "${1}=\$${1}\\ \$func_quote_for_eval_result"
+} # func_append_quoted may be replaced by extended shell implementation
+
+
+# func_arith arithmetic-term...
+func_arith ()
+{
+    func_arith_result=`expr "${@}"`
+} # func_arith may be replaced by extended shell implementation
+
+
+# func_len string
+# STRING may not start with a hyphen.
+func_len ()
+{
+    func_len_result=`expr "${1}" : ".*" 2>/dev/null || echo $max_cmd_len`
+} # func_len may be replaced by extended shell implementation
+
+
+# func_lo2o object
+func_lo2o ()
+{
+    func_lo2o_result=`$ECHO "${1}" | $SED "$lo2o"`
+} # func_lo2o may be replaced by extended shell implementation
+
+
+# func_xform libobj-or-source
+func_xform ()
+{
+    func_xform_result=`$ECHO "${1}" | $SED 's/\.[^.]*$/.lo/'`
+} # func_xform may be replaced by extended shell implementation
+
+
+# func_fatal_configuration arg...
+# Echo program name prefixed message to standard error, followed by
+# a configuration failure hint, and exit.
+func_fatal_configuration ()
+{
+    func_error ${1+"$@"}
+    func_error "See the $PACKAGE documentation for more information."
+    func_fatal_error "Fatal configuration error."
+}
+
+
+# func_config
+# Display the configuration for all the tags in this script.
+func_config ()
+{
+    re_begincf='^# ### BEGIN LIBTOOL'
+    re_endcf='^# ### END LIBTOOL'
+
+    # Default configuration.
+    $SED "1,/$re_begincf CONFIG/d;/$re_endcf CONFIG/,\$d" < "$progpath"
+
+    # Now print the configurations for the tags.
+    for tagname in $taglist; do
+      $SED -n "/$re_begincf TAG CONFIG: $tagname\$/,/$re_endcf TAG CONFIG: $tagname\$/p" < "$progpath"
+    done
+
+    exit $?
+}
+
+# func_features
+# Display the features supported by this script.
+func_features ()
+{
+    echo "host: $host"
+    if test "$build_libtool_libs" = yes; then
+      echo "enable shared libraries"
+    else
+      echo "disable shared libraries"
+    fi
+    if test "$build_old_libs" = yes; then
+      echo "enable static libraries"
+    else
+      echo "disable static libraries"
+    fi
+
+    exit $?
+}
+
+# func_enable_tag tagname
+# Verify that TAGNAME is valid, and either flag an error and exit, or
+# enable the TAGNAME tag.  We also add TAGNAME to the global $taglist
+# variable here.
+func_enable_tag ()
+{
+  # Global variable:
+  tagname="$1"
+
+  re_begincf="^# ### BEGIN LIBTOOL TAG CONFIG: $tagname\$"
+  re_endcf="^# ### END LIBTOOL TAG CONFIG: $tagname\$"
+  sed_extractcf="/$re_begincf/,/$re_endcf/p"
+
+  # Validate tagname.
+  case $tagname in
+    *[!-_A-Za-z0-9,/]*)
+      func_fatal_error "invalid tag name: $tagname"
+      ;;
+  esac
+
+  # Don't test for the "default" C tag, as we know it's
+  # there but not specially marked.
+  case $tagname in
+    CC) ;;
+    *)
+      if $GREP "$re_begincf" "$progpath" >/dev/null 2>&1; then
+       taglist="$taglist $tagname"
+
+       # Evaluate the configuration.  Be careful to quote the path
+       # and the sed script, to avoid splitting on whitespace, but
+       # also don't use non-portable quotes within backquotes within
+       # quotes we have to do it in 2 steps:
+       extractedcf=`$SED -n -e "$sed_extractcf" < "$progpath"`
+       eval "$extractedcf"
+      else
+       func_error "ignoring unknown tag $tagname"
+      fi
+      ;;
+  esac
+}
+
+# func_check_version_match
+# Ensure that we are using m4 macros, and libtool script from the same
+# release of libtool.
+func_check_version_match ()
+{
+  if test "$package_revision" != "$macro_revision"; then
+    if test "$VERSION" != "$macro_version"; then
+      if test -z "$macro_version"; then
+        cat >&2 <<_LT_EOF
+$progname: Version mismatch error.  This is $PACKAGE $VERSION, but the
+$progname: definition of this LT_INIT comes from an older release.
+$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION
+$progname: and run autoconf again.
+_LT_EOF
+      else
+        cat >&2 <<_LT_EOF
+$progname: Version mismatch error.  This is $PACKAGE $VERSION, but the
+$progname: definition of this LT_INIT comes from $PACKAGE $macro_version.
+$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION
+$progname: and run autoconf again.
+_LT_EOF
+      fi
+    else
+      cat >&2 <<_LT_EOF
+$progname: Version mismatch error.  This is $PACKAGE $VERSION, revision $package_revision,
+$progname: but the definition of this LT_INIT comes from revision $macro_revision.
+$progname: You should recreate aclocal.m4 with macros from revision $package_revision
+$progname: of $PACKAGE $VERSION and run autoconf again.
+_LT_EOF
+    fi
+
+    exit $EXIT_MISMATCH
+  fi
+}
+
+
+# Shorthand for --mode=foo, only valid as the first argument
+case $1 in
+clean|clea|cle|cl)
+  shift; set dummy --mode clean ${1+"$@"}; shift
+  ;;
+compile|compil|compi|comp|com|co|c)
+  shift; set dummy --mode compile ${1+"$@"}; shift
+  ;;
+execute|execut|execu|exec|exe|ex|e)
+  shift; set dummy --mode execute ${1+"$@"}; shift
+  ;;
+finish|finis|fini|fin|fi|f)
+  shift; set dummy --mode finish ${1+"$@"}; shift
+  ;;
+install|instal|insta|inst|ins|in|i)
+  shift; set dummy --mode install ${1+"$@"}; shift
+  ;;
+link|lin|li|l)
+  shift; set dummy --mode link ${1+"$@"}; shift
+  ;;
+uninstall|uninstal|uninsta|uninst|unins|unin|uni|un|u)
+  shift; set dummy --mode uninstall ${1+"$@"}; shift
+  ;;
+esac
+
+
+
+# Option defaults:
+opt_debug=:
+opt_dry_run=false
+opt_config=false
+opt_preserve_dup_deps=false
+opt_features=false
+opt_finish=false
+opt_help=false
+opt_help_all=false
+opt_silent=:
+opt_warning=:
+opt_verbose=:
+opt_silent=false
+opt_verbose=false
+
+
+# Parse options once, thoroughly.  This comes as soon as possible in the
+# script to make things like `--version' happen as quickly as we can.
+{
+  # this just eases exit handling
+  while test $# -gt 0; do
+    opt="$1"
+    shift
+    case $opt in
+      --debug|-x)      opt_debug='set -x'
+                       func_echo "enabling shell trace mode"
+                       $opt_debug
+                       ;;
+      --dry-run|--dryrun|-n)
+                       opt_dry_run=:
+                       ;;
+      --config)
+                       opt_config=:
+func_config
+                       ;;
+      --dlopen|-dlopen)
+                       optarg="$1"
+                       opt_dlopen="${opt_dlopen+$opt_dlopen
+}$optarg"
+                       shift
+                       ;;
+      --preserve-dup-deps)
+                       opt_preserve_dup_deps=:
+                       ;;
+      --features)
+                       opt_features=:
+func_features
+                       ;;
+      --finish)
+                       opt_finish=:
+set dummy --mode finish ${1+"$@"}; shift
+                       ;;
+      --help)
+                       opt_help=:
+                       ;;
+      --help-all)
+                       opt_help_all=:
+opt_help=': help-all'
+                       ;;
+      --mode)
+                       test $# = 0 && func_missing_arg $opt && break
+                       optarg="$1"
+                       opt_mode="$optarg"
+case $optarg in
+  # Valid mode arguments:
+  clean|compile|execute|finish|install|link|relink|uninstall) ;;
+
+  # Catch anything else as an error
+  *) func_error "invalid argument for $opt"
+     exit_cmd=exit
+     break
+     ;;
+esac
+                       shift
+                       ;;
+      --no-silent|--no-quiet)
+                       opt_silent=false
+func_append preserve_args " $opt"
+                       ;;
+      --no-warning|--no-warn)
+                       opt_warning=false
+func_append preserve_args " $opt"
+                       ;;
+      --no-verbose)
+                       opt_verbose=false
+func_append preserve_args " $opt"
+                       ;;
+      --silent|--quiet)
+                       opt_silent=:
+func_append preserve_args " $opt"
+        opt_verbose=false
+                       ;;
+      --verbose|-v)
+                       opt_verbose=:
+func_append preserve_args " $opt"
+opt_silent=false
+                       ;;
+      --tag)
+                       test $# = 0 && func_missing_arg $opt && break
+                       optarg="$1"
+                       opt_tag="$optarg"
+func_append preserve_args " $opt $optarg"
+func_enable_tag "$optarg"
+                       shift
+                       ;;
+
+      -\?|-h)          func_usage                              ;;
+      --help)          func_help                               ;;
+      --version)       func_version                            ;;
+
+      # Separate optargs to long options:
+      --*=*)
+                       func_split_long_opt "$opt"
+                       set dummy "$func_split_long_opt_name" "$func_split_long_opt_arg" ${1+"$@"}
+                       shift
+                       ;;
+
+      # Separate non-argument short options:
+      -\?*|-h*|-n*|-v*)
+                       func_split_short_opt "$opt"
+                       set dummy "$func_split_short_opt_name" "-$func_split_short_opt_arg" ${1+"$@"}
+                       shift
+                       ;;
+
+      --)              break                                   ;;
+      -*)              func_fatal_help "unrecognized option \`$opt'" ;;
+      *)               set dummy "$opt" ${1+"$@"};     shift; break  ;;
+    esac
+  done
+
+  # Validate options:
+
+  # save first non-option argument
+  if test "$#" -gt 0; then
+    nonopt="$opt"
+    shift
+  fi
+
+  # preserve --debug
+  test "$opt_debug" = : || func_append preserve_args " --debug"
+
+  case $host in
+    *cygwin* | *mingw* | *pw32* | *cegcc*)
+      # don't eliminate duplications in $postdeps and $predeps
+      opt_duplicate_compiler_generated_deps=:
+      ;;
+    *)
+      opt_duplicate_compiler_generated_deps=$opt_preserve_dup_deps
+      ;;
+  esac
+
+  $opt_help || {
+    # Sanity checks first:
+    func_check_version_match
+
+    if test "$build_libtool_libs" != yes && test "$build_old_libs" != yes; then
+      func_fatal_configuration "not configured to build any kind of library"
+    fi
+
+    # Darwin sucks
+    eval std_shrext=\"$shrext_cmds\"
+
+    # Only execute mode is allowed to have -dlopen flags.
+    if test -n "$opt_dlopen" && test "$opt_mode" != execute; then
+      func_error "unrecognized option \`-dlopen'"
+      $ECHO "$help" 1>&2
+      exit $EXIT_FAILURE
+    fi
+
+    # Change the help message to a mode-specific one.
+    generic_help="$help"
+    help="Try \`$progname --help --mode=$opt_mode' for more information."
+  }
+
+
+  # Bail if the options were screwed
+  $exit_cmd $EXIT_FAILURE
+}
+
+
+
+
+## ----------- ##
+##    Main.    ##
+## ----------- ##
+
+# func_lalib_p file
+# True iff FILE is a libtool `.la' library or `.lo' object file.
+# This function is only a basic sanity check; it will hardly flush out
+# determined imposters.
+func_lalib_p ()
+{
+    test -f "$1" &&
+      $SED -e 4q "$1" 2>/dev/null \
+        | $GREP "^# Generated by .*$PACKAGE" > /dev/null 2>&1
+}
+
+# func_lalib_unsafe_p file
+# True iff FILE is a libtool `.la' library or `.lo' object file.
+# This function implements the same check as func_lalib_p without
+# resorting to external programs.  To this end, it redirects stdin and
+# closes it afterwards, without saving the original file descriptor.
+# As a safety measure, use it only where a negative result would be
+# fatal anyway.  Works if `file' does not exist.
+func_lalib_unsafe_p ()
+{
+    lalib_p=no
+    if test -f "$1" && test -r "$1" && exec 5<&0 <"$1"; then
+       for lalib_p_l in 1 2 3 4
+       do
+           read lalib_p_line
+           case "$lalib_p_line" in
+               \#\ Generated\ by\ *$PACKAGE* ) lalib_p=yes; break;;
+           esac
+       done
+       exec 0<&5 5<&-
+    fi
+    test "$lalib_p" = yes
+}
+
+# func_ltwrapper_script_p file
+# True iff FILE is a libtool wrapper script
+# This function is only a basic sanity check; it will hardly flush out
+# determined imposters.
+func_ltwrapper_script_p ()
+{
+    func_lalib_p "$1"
+}
+
+# func_ltwrapper_executable_p file
+# True iff FILE is a libtool wrapper executable
+# This function is only a basic sanity check; it will hardly flush out
+# determined imposters.
+func_ltwrapper_executable_p ()
+{
+    func_ltwrapper_exec_suffix=
+    case $1 in
+    *.exe) ;;
+    *) func_ltwrapper_exec_suffix=.exe ;;
+    esac
+    $GREP "$magic_exe" "$1$func_ltwrapper_exec_suffix" >/dev/null 2>&1
+}
+
+# func_ltwrapper_scriptname file
+# Assumes file is an ltwrapper_executable
+# uses $file to determine the appropriate filename for a
+# temporary ltwrapper_script.
+func_ltwrapper_scriptname ()
+{
+    func_dirname_and_basename "$1" "" "."
+    func_stripname '' '.exe' "$func_basename_result"
+    func_ltwrapper_scriptname_result="$func_dirname_result/$objdir/${func_stripname_result}_ltshwrapper"
+}
+
+# func_ltwrapper_p file
+# True iff FILE is a libtool wrapper script or wrapper executable
+# This function is only a basic sanity check; it will hardly flush out
+# determined imposters.
+func_ltwrapper_p ()
+{
+    func_ltwrapper_script_p "$1" || func_ltwrapper_executable_p "$1"
+}
+
+
+# func_execute_cmds commands fail_cmd
+# Execute tilde-delimited COMMANDS.
+# If FAIL_CMD is given, eval that upon failure.
+# FAIL_CMD may read-access the current command in variable CMD!
+func_execute_cmds ()
+{
+    $opt_debug
+    save_ifs=$IFS; IFS='~'
+    for cmd in $1; do
+      IFS=$save_ifs
+      eval cmd=\"$cmd\"
+      func_show_eval "$cmd" "${2-:}"
+    done
+    IFS=$save_ifs
+}
+
+
+# func_source file
+# Source FILE, adding directory component if necessary.
+# Note that it is not necessary on cygwin/mingw to append a dot to
+# FILE even if both FILE and FILE.exe exist: automatic-append-.exe
+# behavior happens only for exec(3), not for open(2)!  Also, sourcing
+# `FILE.' does not work on cygwin managed mounts.
+func_source ()
+{
+    $opt_debug
+    case $1 in
+    */* | *\\*)        . "$1" ;;
+    *)         . "./$1" ;;
+    esac
+}
+
+
+# func_resolve_sysroot PATH
+# Replace a leading = in PATH with a sysroot.  Store the result into
+# func_resolve_sysroot_result
+func_resolve_sysroot ()
+{
+  func_resolve_sysroot_result=$1
+  case $func_resolve_sysroot_result in
+  =*)
+    func_stripname '=' '' "$func_resolve_sysroot_result"
+    func_resolve_sysroot_result=$lt_sysroot$func_stripname_result
+    ;;
+  esac
+}
+
+# func_replace_sysroot PATH
+# If PATH begins with the sysroot, replace it with = and
+# store the result into func_replace_sysroot_result.
+func_replace_sysroot ()
+{
+  case "$lt_sysroot:$1" in
+  ?*:"$lt_sysroot"*)
+    func_stripname "$lt_sysroot" '' "$1"
+    func_replace_sysroot_result="=$func_stripname_result"
+    ;;
+  *)
+    # Including no sysroot.
+    func_replace_sysroot_result=$1
+    ;;
+  esac
+}
+
+# func_infer_tag arg
+# Infer tagged configuration to use if any are available and
+# if one wasn't chosen via the "--tag" command line option.
+# Only attempt this if the compiler in the base compile
+# command doesn't match the default compiler.
+# arg is usually of the form 'gcc ...'
+func_infer_tag ()
+{
+    $opt_debug
+    if test -n "$available_tags" && test -z "$tagname"; then
+      CC_quoted=
+      for arg in $CC; do
+       func_append_quoted CC_quoted "$arg"
+      done
+      CC_expanded=`func_echo_all $CC`
+      CC_quoted_expanded=`func_echo_all $CC_quoted`
+      case $@ in
+      # Blanks in the command may have been stripped by the calling shell,
+      # but not from the CC environment variable when configure was run.
+      " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \
+      " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) ;;
+      # Blanks at the start of $base_compile will cause this to fail
+      # if we don't check for them as well.
+      *)
+       for z in $available_tags; do
+         if $GREP "^# ### BEGIN LIBTOOL TAG CONFIG: $z$" < "$progpath" > /dev/null; then
+           # Evaluate the configuration.
+           eval "`${SED} -n -e '/^# ### BEGIN LIBTOOL TAG CONFIG: '$z'$/,/^# ### END LIBTOOL TAG CONFIG: '$z'$/p' < $progpath`"
+           CC_quoted=
+           for arg in $CC; do
+             # Double-quote args containing other shell metacharacters.
+             func_append_quoted CC_quoted "$arg"
+           done
+           CC_expanded=`func_echo_all $CC`
+           CC_quoted_expanded=`func_echo_all $CC_quoted`
+           case "$@ " in
+           " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \
+           " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*)
+             # The compiler in the base compile command matches
+             # the one in the tagged configuration.
+             # Assume this is the tagged configuration we want.
+             tagname=$z
+             break
+             ;;
+           esac
+         fi
+       done
+       # If $tagname still isn't set, then no tagged configuration
+       # was found and let the user know that the "--tag" command
+       # line option must be used.
+       if test -z "$tagname"; then
+         func_echo "unable to infer tagged configuration"
+         func_fatal_error "specify a tag with \`--tag'"
+#      else
+#        func_verbose "using $tagname tagged configuration"
+       fi
+       ;;
+      esac
+    fi
+}
+
+
+
+# func_write_libtool_object output_name pic_name nonpic_name
+# Create a libtool object file (analogous to a ".la" file),
+# but don't create it if we're doing a dry run.
+func_write_libtool_object ()
+{
+    write_libobj=${1}
+    if test "$build_libtool_libs" = yes; then
+      write_lobj=\'${2}\'
+    else
+      write_lobj=none
+    fi
+
+    if test "$build_old_libs" = yes; then
+      write_oldobj=\'${3}\'
+    else
+      write_oldobj=none
+    fi
+
+    $opt_dry_run || {
+      cat >${write_libobj}T <<EOF
+# $write_libobj - a libtool object file
+# Generated by $PROGRAM (GNU $PACKAGE$TIMESTAMP) $VERSION
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# Name of the PIC object.
+pic_object=$write_lobj
+
+# Name of the non-PIC object
+non_pic_object=$write_oldobj
+
+EOF
+      $MV "${write_libobj}T" "${write_libobj}"
+    }
+}
+
+
+##################################################
+# FILE NAME AND PATH CONVERSION HELPER FUNCTIONS #
+##################################################
+
+# func_convert_core_file_wine_to_w32 ARG
+# Helper function used by file name conversion functions when $build is *nix,
+# and $host is mingw, cygwin, or some other w32 environment. Relies on a
+# correctly configured wine environment available, with the winepath program
+# in $build's $PATH.
+#
+# ARG is the $build file name to be converted to w32 format.
+# Result is available in $func_convert_core_file_wine_to_w32_result, and will
+# be empty on error (or when ARG is empty)
+func_convert_core_file_wine_to_w32 ()
+{
+  $opt_debug
+  func_convert_core_file_wine_to_w32_result="$1"
+  if test -n "$1"; then
+    # Unfortunately, winepath does not exit with a non-zero error code, so we
+    # are forced to check the contents of stdout. On the other hand, if the
+    # command is not found, the shell will set an exit code of 127 and print
+    # *an error message* to stdout. So we must check for both error code of
+    # zero AND non-empty stdout, which explains the odd construction:
+    func_convert_core_file_wine_to_w32_tmp=`winepath -w "$1" 2>/dev/null`
+    if test "$?" -eq 0 && test -n "${func_convert_core_file_wine_to_w32_tmp}"; then
+      func_convert_core_file_wine_to_w32_result=`$ECHO "$func_convert_core_file_wine_to_w32_tmp" |
+        $SED -e "$lt_sed_naive_backslashify"`
+    else
+      func_convert_core_file_wine_to_w32_result=
+    fi
+  fi
+}
+# end: func_convert_core_file_wine_to_w32
+
+
+# func_convert_core_path_wine_to_w32 ARG
+# Helper function used by path conversion functions when $build is *nix, and
+# $host is mingw, cygwin, or some other w32 environment. Relies on a correctly
+# configured wine environment available, with the winepath program in $build's
+# $PATH. Assumes ARG has no leading or trailing path separator characters.
+#
+# ARG is path to be converted from $build format to win32.
+# Result is available in $func_convert_core_path_wine_to_w32_result.
+# Unconvertible file (directory) names in ARG are skipped; if no directory names
+# are convertible, then the result may be empty.
+func_convert_core_path_wine_to_w32 ()
+{
+  $opt_debug
+  # unfortunately, winepath doesn't convert paths, only file names
+  func_convert_core_path_wine_to_w32_result=""
+  if test -n "$1"; then
+    oldIFS=$IFS
+    IFS=:
+    for func_convert_core_path_wine_to_w32_f in $1; do
+      IFS=$oldIFS
+      func_convert_core_file_wine_to_w32 "$func_convert_core_path_wine_to_w32_f"
+      if test -n "$func_convert_core_file_wine_to_w32_result" ; then
+        if test -z "$func_convert_core_path_wine_to_w32_result"; then
+          func_convert_core_path_wine_to_w32_result="$func_convert_core_file_wine_to_w32_result"
+        else
+          func_append func_convert_core_path_wine_to_w32_result ";$func_convert_core_file_wine_to_w32_result"
+        fi
+      fi
+    done
+    IFS=$oldIFS
+  fi
+}
+# end: func_convert_core_path_wine_to_w32
+
+
+# func_cygpath ARGS...
+# Wrapper around calling the cygpath program via LT_CYGPATH. This is used when
+# when (1) $build is *nix and Cygwin is hosted via a wine environment; or (2)
+# $build is MSYS and $host is Cygwin, or (3) $build is Cygwin. In case (1) or
+# (2), returns the Cygwin file name or path in func_cygpath_result (input
+# file name or path is assumed to be in w32 format, as previously converted
+# from $build's *nix or MSYS format). In case (3), returns the w32 file name
+# or path in func_cygpath_result (input file name or path is assumed to be in
+# Cygwin format). Returns an empty string on error.
+#
+# ARGS are passed to cygpath, with the last one being the file name or path to
+# be converted.
+#
+# Specify the absolute *nix (or w32) name to cygpath in the LT_CYGPATH
+# environment variable; do not put it in $PATH.
+func_cygpath ()
+{
+  $opt_debug
+  if test -n "$LT_CYGPATH" && test -f "$LT_CYGPATH"; then
+    func_cygpath_result=`$LT_CYGPATH "$@" 2>/dev/null`
+    if test "$?" -ne 0; then
+      # on failure, ensure result is empty
+      func_cygpath_result=
+    fi
+  else
+    func_cygpath_result=
+    func_error "LT_CYGPATH is empty or specifies non-existent file: \`$LT_CYGPATH'"
+  fi
+}
+#end: func_cygpath
+
+
+# func_convert_core_msys_to_w32 ARG
+# Convert file name or path ARG from MSYS format to w32 format.  Return
+# result in func_convert_core_msys_to_w32_result.
+func_convert_core_msys_to_w32 ()
+{
+  $opt_debug
+  # awkward: cmd appends spaces to result
+  func_convert_core_msys_to_w32_result=`( cmd //c echo "$1" ) 2>/dev/null |
+    $SED -e 's/[ ]*$//' -e "$lt_sed_naive_backslashify"`
+}
+#end: func_convert_core_msys_to_w32
+
+
+# func_convert_file_check ARG1 ARG2
+# Verify that ARG1 (a file name in $build format) was converted to $host
+# format in ARG2. Otherwise, emit an error message, but continue (resetting
+# func_to_host_file_result to ARG1).
+func_convert_file_check ()
+{
+  $opt_debug
+  if test -z "$2" && test -n "$1" ; then
+    func_error "Could not determine host file name corresponding to"
+    func_error "  \`$1'"
+    func_error "Continuing, but uninstalled executables may not work."
+    # Fallback:
+    func_to_host_file_result="$1"
+  fi
+}
+# end func_convert_file_check
+
+
+# func_convert_path_check FROM_PATHSEP TO_PATHSEP FROM_PATH TO_PATH
+# Verify that FROM_PATH (a path in $build format) was converted to $host
+# format in TO_PATH. Otherwise, emit an error message, but continue, resetting
+# func_to_host_file_result to a simplistic fallback value (see below).
+func_convert_path_check ()
+{
+  $opt_debug
+  if test -z "$4" && test -n "$3"; then
+    func_error "Could not determine the host path corresponding to"
+    func_error "  \`$3'"
+    func_error "Continuing, but uninstalled executables may not work."
+    # Fallback.  This is a deliberately simplistic "conversion" and
+    # should not be "improved".  See libtool.info.
+    if test "x$1" != "x$2"; then
+      lt_replace_pathsep_chars="s|$1|$2|g"
+      func_to_host_path_result=`echo "$3" |
+        $SED -e "$lt_replace_pathsep_chars"`
+    else
+      func_to_host_path_result="$3"
+    fi
+  fi
+}
+# end func_convert_path_check
+
+
+# func_convert_path_front_back_pathsep FRONTPAT BACKPAT REPL ORIG
+# Modifies func_to_host_path_result by prepending REPL if ORIG matches FRONTPAT
+# and appending REPL if ORIG matches BACKPAT.
+func_convert_path_front_back_pathsep ()
+{
+  $opt_debug
+  case $4 in
+  $1 ) func_to_host_path_result="$3$func_to_host_path_result"
+    ;;
+  esac
+  case $4 in
+  $2 ) func_append func_to_host_path_result "$3"
+    ;;
+  esac
+}
+# end func_convert_path_front_back_pathsep
+
+
+##################################################
+# $build to $host FILE NAME CONVERSION FUNCTIONS #
+##################################################
+# invoked via `$to_host_file_cmd ARG'
+#
+# In each case, ARG is the path to be converted from $build to $host format.
+# Result will be available in $func_to_host_file_result.
+
+
+# func_to_host_file ARG
+# Converts the file name ARG from $build format to $host format. Return result
+# in func_to_host_file_result.
+func_to_host_file ()
+{
+  $opt_debug
+  $to_host_file_cmd "$1"
+}
+# end func_to_host_file
+
+
+# func_to_tool_file ARG LAZY
+# converts the file name ARG from $build format to toolchain format. Return
+# result in func_to_tool_file_result.  If the conversion in use is listed
+# in (the comma separated) LAZY, no conversion takes place.
+func_to_tool_file ()
+{
+  $opt_debug
+  case ,$2, in
+    *,"$to_tool_file_cmd",*)
+      func_to_tool_file_result=$1
+      ;;
+    *)
+      $to_tool_file_cmd "$1"
+      func_to_tool_file_result=$func_to_host_file_result
+      ;;
+  esac
+}
+# end func_to_tool_file
+
+
+# func_convert_file_noop ARG
+# Copy ARG to func_to_host_file_result.
+func_convert_file_noop ()
+{
+  func_to_host_file_result="$1"
+}
+# end func_convert_file_noop
+
+
+# func_convert_file_msys_to_w32 ARG
+# Convert file name ARG from (mingw) MSYS to (mingw) w32 format; automatic
+# conversion to w32 is not available inside the cwrapper.  Returns result in
+# func_to_host_file_result.
+func_convert_file_msys_to_w32 ()
+{
+  $opt_debug
+  func_to_host_file_result="$1"
+  if test -n "$1"; then
+    func_convert_core_msys_to_w32 "$1"
+    func_to_host_file_result="$func_convert_core_msys_to_w32_result"
+  fi
+  func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_msys_to_w32
+
+
+# func_convert_file_cygwin_to_w32 ARG
+# Convert file name ARG from Cygwin to w32 format.  Returns result in
+# func_to_host_file_result.
+func_convert_file_cygwin_to_w32 ()
+{
+  $opt_debug
+  func_to_host_file_result="$1"
+  if test -n "$1"; then
+    # because $build is cygwin, we call "the" cygpath in $PATH; no need to use
+    # LT_CYGPATH in this case.
+    func_to_host_file_result=`cygpath -m "$1"`
+  fi
+  func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_cygwin_to_w32
+
+
+# func_convert_file_nix_to_w32 ARG
+# Convert file name ARG from *nix to w32 format.  Requires a wine environment
+# and a working winepath. Returns result in func_to_host_file_result.
+func_convert_file_nix_to_w32 ()
+{
+  $opt_debug
+  func_to_host_file_result="$1"
+  if test -n "$1"; then
+    func_convert_core_file_wine_to_w32 "$1"
+    func_to_host_file_result="$func_convert_core_file_wine_to_w32_result"
+  fi
+  func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_nix_to_w32
+
+
+# func_convert_file_msys_to_cygwin ARG
+# Convert file name ARG from MSYS to Cygwin format.  Requires LT_CYGPATH set.
+# Returns result in func_to_host_file_result.
+func_convert_file_msys_to_cygwin ()
+{
+  $opt_debug
+  func_to_host_file_result="$1"
+  if test -n "$1"; then
+    func_convert_core_msys_to_w32 "$1"
+    func_cygpath -u "$func_convert_core_msys_to_w32_result"
+    func_to_host_file_result="$func_cygpath_result"
+  fi
+  func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_msys_to_cygwin
+
+
+# func_convert_file_nix_to_cygwin ARG
+# Convert file name ARG from *nix to Cygwin format.  Requires Cygwin installed
+# in a wine environment, working winepath, and LT_CYGPATH set.  Returns result
+# in func_to_host_file_result.
+func_convert_file_nix_to_cygwin ()
+{
+  $opt_debug
+  func_to_host_file_result="$1"
+  if test -n "$1"; then
+    # convert from *nix to w32, then use cygpath to convert from w32 to cygwin.
+    func_convert_core_file_wine_to_w32 "$1"
+    func_cygpath -u "$func_convert_core_file_wine_to_w32_result"
+    func_to_host_file_result="$func_cygpath_result"
+  fi
+  func_convert_file_check "$1" "$func_to_host_file_result"
+}
+# end func_convert_file_nix_to_cygwin
+
+
+#############################################
+# $build to $host PATH CONVERSION FUNCTIONS #
+#############################################
+# invoked via `$to_host_path_cmd ARG'
+#
+# In each case, ARG is the path to be converted from $build to $host format.
+# The result will be available in $func_to_host_path_result.
+#
+# Path separators are also converted from $build format to $host format.  If
+# ARG begins or ends with a path separator character, it is preserved (but
+# converted to $host format) on output.
+#
+# All path conversion functions are named using the following convention:
+#   file name conversion function    : func_convert_file_X_to_Y ()
+#   path conversion function         : func_convert_path_X_to_Y ()
+# where, for any given $build/$host combination the 'X_to_Y' value is the
+# same.  If conversion functions are added for new $build/$host combinations,
+# the two new functions must follow this pattern, or func_init_to_host_path_cmd
+# will break.
+
+
+# func_init_to_host_path_cmd
+# Ensures that function "pointer" variable $to_host_path_cmd is set to the
+# appropriate value, based on the value of $to_host_file_cmd.
+to_host_path_cmd=
+func_init_to_host_path_cmd ()
+{
+  $opt_debug
+  if test -z "$to_host_path_cmd"; then
+    func_stripname 'func_convert_file_' '' "$to_host_file_cmd"
+    to_host_path_cmd="func_convert_path_${func_stripname_result}"
+  fi
+}
+
+
+# func_to_host_path ARG
+# Converts the path ARG from $build format to $host format. Return result
+# in func_to_host_path_result.
+func_to_host_path ()
+{
+  $opt_debug
+  func_init_to_host_path_cmd
+  $to_host_path_cmd "$1"
+}
+# end func_to_host_path
+
+
+# func_convert_path_noop ARG
+# Copy ARG to func_to_host_path_result.
+func_convert_path_noop ()
+{
+  func_to_host_path_result="$1"
+}
+# end func_convert_path_noop
+
+
+# func_convert_path_msys_to_w32 ARG
+# Convert path ARG from (mingw) MSYS to (mingw) w32 format; automatic
+# conversion to w32 is not available inside the cwrapper.  Returns result in
+# func_to_host_path_result.
+func_convert_path_msys_to_w32 ()
+{
+  $opt_debug
+  func_to_host_path_result="$1"
+  if test -n "$1"; then
+    # Remove leading and trailing path separator characters from ARG.  MSYS
+    # behavior is inconsistent here; cygpath turns them into '.;' and ';.';
+    # and winepath ignores them completely.
+    func_stripname : : "$1"
+    func_to_host_path_tmp1=$func_stripname_result
+    func_convert_core_msys_to_w32 "$func_to_host_path_tmp1"
+    func_to_host_path_result="$func_convert_core_msys_to_w32_result"
+    func_convert_path_check : ";" \
+      "$func_to_host_path_tmp1" "$func_to_host_path_result"
+    func_convert_path_front_back_pathsep ":*" "*:" ";" "$1"
+  fi
+}
+# end func_convert_path_msys_to_w32
+
+
+# func_convert_path_cygwin_to_w32 ARG
+# Convert path ARG from Cygwin to w32 format.  Returns result in
+# func_to_host_file_result.
+func_convert_path_cygwin_to_w32 ()
+{
+  $opt_debug
+  func_to_host_path_result="$1"
+  if test -n "$1"; then
+    # See func_convert_path_msys_to_w32:
+    func_stripname : : "$1"
+    func_to_host_path_tmp1=$func_stripname_result
+    func_to_host_path_result=`cygpath -m -p "$func_to_host_path_tmp1"`
+    func_convert_path_check : ";" \
+      "$func_to_host_path_tmp1" "$func_to_host_path_result"
+    func_convert_path_front_back_pathsep ":*" "*:" ";" "$1"
+  fi
+}
+# end func_convert_path_cygwin_to_w32
+
+
+# func_convert_path_nix_to_w32 ARG
+# Convert path ARG from *nix to w32 format.  Requires a wine environment and
+# a working winepath.  Returns result in func_to_host_file_result.
+func_convert_path_nix_to_w32 ()
+{
+  $opt_debug
+  func_to_host_path_result="$1"
+  if test -n "$1"; then
+    # See func_convert_path_msys_to_w32:
+    func_stripname : : "$1"
+    func_to_host_path_tmp1=$func_stripname_result
+    func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1"
+    func_to_host_path_result="$func_convert_core_path_wine_to_w32_result"
+    func_convert_path_check : ";" \
+      "$func_to_host_path_tmp1" "$func_to_host_path_result"
+    func_convert_path_front_back_pathsep ":*" "*:" ";" "$1"
+  fi
+}
+# end func_convert_path_nix_to_w32
+
+
+# func_convert_path_msys_to_cygwin ARG
+# Convert path ARG from MSYS to Cygwin format.  Requires LT_CYGPATH set.
+# Returns result in func_to_host_file_result.
+func_convert_path_msys_to_cygwin ()
+{
+  $opt_debug
+  func_to_host_path_result="$1"
+  if test -n "$1"; then
+    # See func_convert_path_msys_to_w32:
+    func_stripname : : "$1"
+    func_to_host_path_tmp1=$func_stripname_result
+    func_convert_core_msys_to_w32 "$func_to_host_path_tmp1"
+    func_cygpath -u -p "$func_convert_core_msys_to_w32_result"
+    func_to_host_path_result="$func_cygpath_result"
+    func_convert_path_check : : \
+      "$func_to_host_path_tmp1" "$func_to_host_path_result"
+    func_convert_path_front_back_pathsep ":*" "*:" : "$1"
+  fi
+}
+# end func_convert_path_msys_to_cygwin
+
+
+# func_convert_path_nix_to_cygwin ARG
+# Convert path ARG from *nix to Cygwin format.  Requires Cygwin installed in a
+# a wine environment, working winepath, and LT_CYGPATH set.  Returns result in
+# func_to_host_file_result.
+func_convert_path_nix_to_cygwin ()
+{
+  $opt_debug
+  func_to_host_path_result="$1"
+  if test -n "$1"; then
+    # Remove leading and trailing path separator characters from
+    # ARG. msys behavior is inconsistent here, cygpath turns them
+    # into '.;' and ';.', and winepath ignores them completely.
+    func_stripname : : "$1"
+    func_to_host_path_tmp1=$func_stripname_result
+    func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1"
+    func_cygpath -u -p "$func_convert_core_path_wine_to_w32_result"
+    func_to_host_path_result="$func_cygpath_result"
+    func_convert_path_check : : \
+      "$func_to_host_path_tmp1" "$func_to_host_path_result"
+    func_convert_path_front_back_pathsep ":*" "*:" : "$1"
+  fi
+}
+# end func_convert_path_nix_to_cygwin
+
+
+# func_mode_compile arg...
+func_mode_compile ()
+{
+    $opt_debug
+    # Get the compilation command and the source file.
+    base_compile=
+    srcfile="$nonopt"  #  always keep a non-empty value in "srcfile"
+    suppress_opt=yes
+    suppress_output=
+    arg_mode=normal
+    libobj=
+    later=
+    pie_flag=
+
+    for arg
+    do
+      case $arg_mode in
+      arg  )
+       # do not "continue".  Instead, add this to base_compile
+       lastarg="$arg"
+       arg_mode=normal
+       ;;
+
+      target )
+       libobj="$arg"
+       arg_mode=normal
+       continue
+       ;;
+
+      normal )
+       # Accept any command-line options.
+       case $arg in
+       -o)
+         test -n "$libobj" && \
+           func_fatal_error "you cannot specify \`-o' more than once"
+         arg_mode=target
+         continue
+         ;;
+
+       -pie | -fpie | -fPIE)
+          func_append pie_flag " $arg"
+         continue
+         ;;
+
+       -shared | -static | -prefer-pic | -prefer-non-pic)
+         func_append later " $arg"
+         continue
+         ;;
+
+       -no-suppress)
+         suppress_opt=no
+         continue
+         ;;
+
+       -Xcompiler)
+         arg_mode=arg  #  the next one goes into the "base_compile" arg list
+         continue      #  The current "srcfile" will either be retained or
+         ;;            #  replaced later.  I would guess that would be a bug.
+
+       -Wc,*)
+         func_stripname '-Wc,' '' "$arg"
+         args=$func_stripname_result
+         lastarg=
+         save_ifs="$IFS"; IFS=','
+         for arg in $args; do
+           IFS="$save_ifs"
+           func_append_quoted lastarg "$arg"
+         done
+         IFS="$save_ifs"
+         func_stripname ' ' '' "$lastarg"
+         lastarg=$func_stripname_result
+
+         # Add the arguments to base_compile.
+         func_append base_compile " $lastarg"
+         continue
+         ;;
+
+       *)
+         # Accept the current argument as the source file.
+         # The previous "srcfile" becomes the current argument.
+         #
+         lastarg="$srcfile"
+         srcfile="$arg"
+         ;;
+       esac  #  case $arg
+       ;;
+      esac    #  case $arg_mode
+
+      # Aesthetically quote the previous argument.
+      func_append_quoted base_compile "$lastarg"
+    done # for arg
+
+    case $arg_mode in
+    arg)
+      func_fatal_error "you must specify an argument for -Xcompile"
+      ;;
+    target)
+      func_fatal_error "you must specify a target with \`-o'"
+      ;;
+    *)
+      # Get the name of the library object.
+      test -z "$libobj" && {
+       func_basename "$srcfile"
+       libobj="$func_basename_result"
+      }
+      ;;
+    esac
+
+    # Recognize several different file suffixes.
+    # If the user specifies -o file.o, it is replaced with file.lo
+    case $libobj in
+    *.[cCFSifmso] | \
+    *.ada | *.adb | *.ads | *.asm | \
+    *.c++ | *.cc | *.ii | *.class | *.cpp | *.cxx | \
+    *.[fF][09]? | *.for | *.java | *.go | *.obj | *.sx | *.cu | *.cup)
+      func_xform "$libobj"
+      libobj=$func_xform_result
+      ;;
+    esac
+
+    case $libobj in
+    *.lo) func_lo2o "$libobj"; obj=$func_lo2o_result ;;
+    *)
+      func_fatal_error "cannot determine name of library object from \`$libobj'"
+      ;;
+    esac
+
+    func_infer_tag $base_compile
+
+    for arg in $later; do
+      case $arg in
+      -shared)
+       test "$build_libtool_libs" != yes && \
+         func_fatal_configuration "can not build a shared library"
+       build_old_libs=no
+       continue
+       ;;
+
+      -static)
+       build_libtool_libs=no
+       build_old_libs=yes
+       continue
+       ;;
+
+      -prefer-pic)
+       pic_mode=yes
+       continue
+       ;;
+
+      -prefer-non-pic)
+       pic_mode=no
+       continue
+       ;;
+      esac
+    done
+
+    func_quote_for_eval "$libobj"
+    test "X$libobj" != "X$func_quote_for_eval_result" \
+      && $ECHO "X$libobj" | $GREP '[]~#^*{};<>?"'"'"'   &()|`$[]' \
+      && func_warning "libobj name \`$libobj' may not contain shell special characters."
+    func_dirname_and_basename "$obj" "/" ""
+    objname="$func_basename_result"
+    xdir="$func_dirname_result"
+    lobj=${xdir}$objdir/$objname
+
+    test -z "$base_compile" && \
+      func_fatal_help "you must specify a compilation command"
+
+    # Delete any leftover library objects.
+    if test "$build_old_libs" = yes; then
+      removelist="$obj $lobj $libobj ${libobj}T"
+    else
+      removelist="$lobj $libobj ${libobj}T"
+    fi
+
+    # On Cygwin there's no "real" PIC flag so we must build both object types
+    case $host_os in
+    cygwin* | mingw* | pw32* | os2* | cegcc*)
+      pic_mode=default
+      ;;
+    esac
+    if test "$pic_mode" = no && test "$deplibs_check_method" != pass_all; then
+      # non-PIC code in shared libraries is not supported
+      pic_mode=default
+    fi
+
+    # Calculate the filename of the output object if compiler does
+    # not support -o with -c
+    if test "$compiler_c_o" = no; then
+      output_obj=`$ECHO "$srcfile" | $SED 's%^.*/%%; s%\.[^.]*$%%'`.${objext}
+      lockfile="$output_obj.lock"
+    else
+      output_obj=
+      need_locks=no
+      lockfile=
+    fi
+
+    # Lock this critical section if it is needed
+    # We use this script file to make the link, it avoids creating a new file
+    if test "$need_locks" = yes; then
+      until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do
+       func_echo "Waiting for $lockfile to be removed"
+       sleep 2
+      done
+    elif test "$need_locks" = warn; then
+      if test -f "$lockfile"; then
+       $ECHO "\
+*** ERROR, $lockfile exists and contains:
+`cat $lockfile 2>/dev/null`
+
+This indicates that another process is trying to use the same
+temporary object file, and libtool could not work around it because
+your compiler does not support \`-c' and \`-o' together.  If you
+repeat this compilation, it may succeed, by chance, but you had better
+avoid parallel builds (make -j) in this platform, or get a better
+compiler."
+
+       $opt_dry_run || $RM $removelist
+       exit $EXIT_FAILURE
+      fi
+      func_append removelist " $output_obj"
+      $ECHO "$srcfile" > "$lockfile"
+    fi
+
+    $opt_dry_run || $RM $removelist
+    func_append removelist " $lockfile"
+    trap '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' 1 2 15
+
+    func_to_tool_file "$srcfile" func_convert_file_msys_to_w32
+    srcfile=$func_to_tool_file_result
+    func_quote_for_eval "$srcfile"
+    qsrcfile=$func_quote_for_eval_result
+
+    # Only build a PIC object if we are building libtool libraries.
+    if test "$build_libtool_libs" = yes; then
+      # Without this assignment, base_compile gets emptied.
+      fbsd_hideous_sh_bug=$base_compile
+
+      if test "$pic_mode" != no; then
+       command="$base_compile $qsrcfile $pic_flag"
+      else
+       # Don't build PIC code
+       command="$base_compile $qsrcfile"
+      fi
+
+      func_mkdir_p "$xdir$objdir"
+
+      if test -z "$output_obj"; then
+       # Place PIC objects in $objdir
+       func_append command " -o $lobj"
+      fi
+
+      func_show_eval_locale "$command" \
+          'test -n "$output_obj" && $RM $removelist; exit $EXIT_FAILURE'
+
+      if test "$need_locks" = warn &&
+        test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then
+       $ECHO "\
+*** ERROR, $lockfile contains:
+`cat $lockfile 2>/dev/null`
+
+but it should contain:
+$srcfile
+
+This indicates that another process is trying to use the same
+temporary object file, and libtool could not work around it because
+your compiler does not support \`-c' and \`-o' together.  If you
+repeat this compilation, it may succeed, by chance, but you had better
+avoid parallel builds (make -j) in this platform, or get a better
+compiler."
+
+       $opt_dry_run || $RM $removelist
+       exit $EXIT_FAILURE
+      fi
+
+      # Just move the object if needed, then go on to compile the next one
+      if test -n "$output_obj" && test "X$output_obj" != "X$lobj"; then
+       func_show_eval '$MV "$output_obj" "$lobj"' \
+         'error=$?; $opt_dry_run || $RM $removelist; exit $error'
+      fi
+
+      # Allow error messages only from the first compilation.
+      if test "$suppress_opt" = yes; then
+       suppress_output=' >/dev/null 2>&1'
+      fi
+    fi
+
+    # Only build a position-dependent object if we build old libraries.
+    if test "$build_old_libs" = yes; then
+      if test "$pic_mode" != yes; then
+       # Don't build PIC code
+       command="$base_compile $qsrcfile$pie_flag"
+      else
+       command="$base_compile $qsrcfile $pic_flag"
+      fi
+      if test "$compiler_c_o" = yes; then
+       func_append command " -o $obj"
+      fi
+
+      # Suppress compiler output if we already did a PIC compilation.
+      func_append command "$suppress_output"
+      func_show_eval_locale "$command" \
+        '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE'
+
+      if test "$need_locks" = warn &&
+        test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then
+       $ECHO "\
+*** ERROR, $lockfile contains:
+`cat $lockfile 2>/dev/null`
+
+but it should contain:
+$srcfile
+
+This indicates that another process is trying to use the same
+temporary object file, and libtool could not work around it because
+your compiler does not support \`-c' and \`-o' together.  If you
+repeat this compilation, it may succeed, by chance, but you had better
+avoid parallel builds (make -j) in this platform, or get a better
+compiler."
+
+       $opt_dry_run || $RM $removelist
+       exit $EXIT_FAILURE
+      fi
+
+      # Just move the object if needed
+      if test -n "$output_obj" && test "X$output_obj" != "X$obj"; then
+       func_show_eval '$MV "$output_obj" "$obj"' \
+         'error=$?; $opt_dry_run || $RM $removelist; exit $error'
+      fi
+    fi
+
+    $opt_dry_run || {
+      func_write_libtool_object "$libobj" "$objdir/$objname" "$objname"
+
+      # Unlock the critical section if it was locked
+      if test "$need_locks" != no; then
+       removelist=$lockfile
+        $RM "$lockfile"
+      fi
+    }
+
+    exit $EXIT_SUCCESS
+}
+
+$opt_help || {
+  test "$opt_mode" = compile && func_mode_compile ${1+"$@"}
+}
+
+func_mode_help ()
+{
+    # We need to display help for each of the modes.
+    case $opt_mode in
+      "")
+        # Generic help is extracted from the usage comments
+        # at the start of this file.
+        func_help
+        ;;
+
+      clean)
+        $ECHO \
+"Usage: $progname [OPTION]... --mode=clean RM [RM-OPTION]... FILE...
+
+Remove files from the build directory.
+
+RM is the name of the program to use to delete files associated with each FILE
+(typically \`/bin/rm').  RM-OPTIONS are options (such as \`-f') to be passed
+to RM.
+
+If FILE is a libtool library, object or program, all the files associated
+with it are deleted. Otherwise, only FILE itself is deleted using RM."
+        ;;
+
+      compile)
+      $ECHO \
+"Usage: $progname [OPTION]... --mode=compile COMPILE-COMMAND... SOURCEFILE
+
+Compile a source file into a libtool library object.
+
+This mode accepts the following additional options:
+
+  -o OUTPUT-FILE    set the output file name to OUTPUT-FILE
+  -no-suppress      do not suppress compiler output for multiple passes
+  -prefer-pic       try to build PIC objects only
+  -prefer-non-pic   try to build non-PIC objects only
+  -shared           do not build a \`.o' file suitable for static linking
+  -static           only build a \`.o' file suitable for static linking
+  -Wc,FLAG          pass FLAG directly to the compiler
+
+COMPILE-COMMAND is a command to be used in creating a \`standard' object file
+from the given SOURCEFILE.
+
+The output file name is determined by removing the directory component from
+SOURCEFILE, then substituting the C source code suffix \`.c' with the
+library object suffix, \`.lo'."
+        ;;
+
+      execute)
+        $ECHO \
+"Usage: $progname [OPTION]... --mode=execute COMMAND [ARGS]...
+
+Automatically set library path, then run a program.
+
+This mode accepts the following additional options:
+
+  -dlopen FILE      add the directory containing FILE to the library path
+
+This mode sets the library path environment variable according to \`-dlopen'
+flags.
+
+If any of the ARGS are libtool executable wrappers, then they are translated
+into their corresponding uninstalled binary, and any of their required library
+directories are added to the library path.
+
+Then, COMMAND is executed, with ARGS as arguments."
+        ;;
+
+      finish)
+        $ECHO \
+"Usage: $progname [OPTION]... --mode=finish [LIBDIR]...
+
+Complete the installation of libtool libraries.
+
+Each LIBDIR is a directory that contains libtool libraries.
+
+The commands that this mode executes may require superuser privileges.  Use
+the \`--dry-run' option if you just want to see what would be executed."
+        ;;
+
+      install)
+        $ECHO \
+"Usage: $progname [OPTION]... --mode=install INSTALL-COMMAND...
+
+Install executables or libraries.
+
+INSTALL-COMMAND is the installation command.  The first component should be
+either the \`install' or \`cp' program.
+
+The following components of INSTALL-COMMAND are treated specially:
+
+  -inst-prefix-dir PREFIX-DIR  Use PREFIX-DIR as a staging area for installation
+
+The rest of the components are interpreted as arguments to that command (only
+BSD-compatible install options are recognized)."
+        ;;
+
+      link)
+        $ECHO \
+"Usage: $progname [OPTION]... --mode=link LINK-COMMAND...
+
+Link object files or libraries together to form another library, or to
+create an executable program.
+
+LINK-COMMAND is a command using the C compiler that you would use to create
+a program from several object files.
+
+The following components of LINK-COMMAND are treated specially:
+
+  -all-static       do not do any dynamic linking at all
+  -avoid-version    do not add a version suffix if possible
+  -bindir BINDIR    specify path to binaries directory (for systems where
+                    libraries must be found in the PATH setting at runtime)
+  -dlopen FILE      \`-dlpreopen' FILE if it cannot be dlopened at runtime
+  -dlpreopen FILE   link in FILE and add its symbols to lt_preloaded_symbols
+  -export-dynamic   allow symbols from OUTPUT-FILE to be resolved with dlsym(3)
+  -export-symbols SYMFILE
+                    try to export only the symbols listed in SYMFILE
+  -export-symbols-regex REGEX
+                    try to export only the symbols matching REGEX
+  -LLIBDIR          search LIBDIR for required installed libraries
+  -lNAME            OUTPUT-FILE requires the installed library libNAME
+  -module           build a library that can dlopened
+  -no-fast-install  disable the fast-install mode
+  -no-install       link a not-installable executable
+  -no-undefined     declare that a library does not refer to external symbols
+  -o OUTPUT-FILE    create OUTPUT-FILE from the specified objects
+  -objectlist FILE  Use a list of object files found in FILE to specify objects
+  -precious-files-regex REGEX
+                    don't remove output files matching REGEX
+  -release RELEASE  specify package release information
+  -rpath LIBDIR     the created library will eventually be installed in LIBDIR
+  -R[ ]LIBDIR       add LIBDIR to the runtime path of programs and libraries
+  -shared           only do dynamic linking of libtool libraries
+  -shrext SUFFIX    override the standard shared library file extension
+  -static           do not do any dynamic linking of uninstalled libtool libraries
+  -static-libtool-libs
+                    do not do any dynamic linking of libtool libraries
+  -version-info CURRENT[:REVISION[:AGE]]
+                    specify library version info [each variable defaults to 0]
+  -weak LIBNAME     declare that the target provides the LIBNAME interface
+  -Wc,FLAG
+  -Xcompiler FLAG   pass linker-specific FLAG directly to the compiler
+  -Wl,FLAG
+  -Xlinker FLAG     pass linker-specific FLAG directly to the linker
+  -XCClinker FLAG   pass link-specific FLAG to the compiler driver (CC)
+
+All other options (arguments beginning with \`-') are ignored.
+
+Every other argument is treated as a filename.  Files ending in \`.la' are
+treated as uninstalled libtool libraries, other files are standard or library
+object files.
+
+If the OUTPUT-FILE ends in \`.la', then a libtool library is created,
+only library objects (\`.lo' files) may be specified, and \`-rpath' is
+required, except when creating a convenience library.
+
+If OUTPUT-FILE ends in \`.a' or \`.lib', then a standard library is created
+using \`ar' and \`ranlib', or on Windows using \`lib'.
+
+If OUTPUT-FILE ends in \`.lo' or \`.${objext}', then a reloadable object file
+is created, otherwise an executable program is created."
+        ;;
+
+      uninstall)
+        $ECHO \
+"Usage: $progname [OPTION]... --mode=uninstall RM [RM-OPTION]... FILE...
+
+Remove libraries from an installation directory.
+
+RM is the name of the program to use to delete files associated with each FILE
+(typically \`/bin/rm').  RM-OPTIONS are options (such as \`-f') to be passed
+to RM.
+
+If FILE is a libtool library, all the files associated with it are deleted.
+Otherwise, only FILE itself is deleted using RM."
+        ;;
+
+      *)
+        func_fatal_help "invalid operation mode \`$opt_mode'"
+        ;;
+    esac
+
+    echo
+    $ECHO "Try \`$progname --help' for more information about other modes."
+}
+
+# Now that we've collected a possible --mode arg, show help if necessary
+if $opt_help; then
+  if test "$opt_help" = :; then
+    func_mode_help
+  else
+    {
+      func_help noexit
+      for opt_mode in compile link execute install finish uninstall clean; do
+       func_mode_help
+      done
+    } | sed -n '1p; 2,$s/^Usage:/  or: /p'
+    {
+      func_help noexit
+      for opt_mode in compile link execute install finish uninstall clean; do
+       echo
+       func_mode_help
+      done
+    } |
+    sed '1d
+      /^When reporting/,/^Report/{
+       H
+       d
+      }
+      $x
+      /information about other modes/d
+      /more detailed .*MODE/d
+      s/^Usage:.*--mode=\([^ ]*\) .*/Description of \1 mode:/'
+  fi
+  exit $?
+fi
+
+
+# func_mode_execute arg...
+func_mode_execute ()
+{
+    $opt_debug
+    # The first argument is the command name.
+    cmd="$nonopt"
+    test -z "$cmd" && \
+      func_fatal_help "you must specify a COMMAND"
+
+    # Handle -dlopen flags immediately.
+    for file in $opt_dlopen; do
+      test -f "$file" \
+       || func_fatal_help "\`$file' is not a file"
+
+      dir=
+      case $file in
+      *.la)
+       func_resolve_sysroot "$file"
+       file=$func_resolve_sysroot_result
+
+       # Check to see that this really is a libtool archive.
+       func_lalib_unsafe_p "$file" \
+         || func_fatal_help "\`$lib' is not a valid libtool archive"
+
+       # Read the libtool library.
+       dlname=
+       library_names=
+       func_source "$file"
+
+       # Skip this library if it cannot be dlopened.
+       if test -z "$dlname"; then
+         # Warn if it was a shared library.
+         test -n "$library_names" && \
+           func_warning "\`$file' was not linked with \`-export-dynamic'"
+         continue
+       fi
+
+       func_dirname "$file" "" "."
+       dir="$func_dirname_result"
+
+       if test -f "$dir/$objdir/$dlname"; then
+         func_append dir "/$objdir"
+       else
+         if test ! -f "$dir/$dlname"; then
+           func_fatal_error "cannot find \`$dlname' in \`$dir' or \`$dir/$objdir'"
+         fi
+       fi
+       ;;
+
+      *.lo)
+       # Just add the directory containing the .lo file.
+       func_dirname "$file" "" "."
+       dir="$func_dirname_result"
+       ;;
+
+      *)
+       func_warning "\`-dlopen' is ignored for non-libtool libraries and objects"
+       continue
+       ;;
+      esac
+
+      # Get the absolute pathname.
+      absdir=`cd "$dir" && pwd`
+      test -n "$absdir" && dir="$absdir"
+
+      # Now add the directory to shlibpath_var.
+      if eval "test -z \"\$$shlibpath_var\""; then
+       eval "$shlibpath_var=\"\$dir\""
+      else
+       eval "$shlibpath_var=\"\$dir:\$$shlibpath_var\""
+      fi
+    done
+
+    # This variable tells wrapper scripts just to set shlibpath_var
+    # rather than running their programs.
+    libtool_execute_magic="$magic"
+
+    # Check if any of the arguments is a wrapper script.
+    args=
+    for file
+    do
+      case $file in
+      -* | *.la | *.lo ) ;;
+      *)
+       # Do a test to see if this is really a libtool program.
+       if func_ltwrapper_script_p "$file"; then
+         func_source "$file"
+         # Transform arg to wrapped name.
+         file="$progdir/$program"
+       elif func_ltwrapper_executable_p "$file"; then
+         func_ltwrapper_scriptname "$file"
+         func_source "$func_ltwrapper_scriptname_result"
+         # Transform arg to wrapped name.
+         file="$progdir/$program"
+       fi
+       ;;
+      esac
+      # Quote arguments (to preserve shell metacharacters).
+      func_append_quoted args "$file"
+    done
+
+    if test "X$opt_dry_run" = Xfalse; then
+      if test -n "$shlibpath_var"; then
+       # Export the shlibpath_var.
+       eval "export $shlibpath_var"
+      fi
+
+      # Restore saved environment variables
+      for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES
+      do
+       eval "if test \"\${save_$lt_var+set}\" = set; then
+                $lt_var=\$save_$lt_var; export $lt_var
+             else
+               $lt_unset $lt_var
+             fi"
+      done
+
+      # Now prepare to actually exec the command.
+      exec_cmd="\$cmd$args"
+    else
+      # Display what would be done.
+      if test -n "$shlibpath_var"; then
+       eval "\$ECHO \"\$shlibpath_var=\$$shlibpath_var\""
+       echo "export $shlibpath_var"
+      fi
+      $ECHO "$cmd$args"
+      exit $EXIT_SUCCESS
+    fi
+}
+
+test "$opt_mode" = execute && func_mode_execute ${1+"$@"}
+
+
+# func_mode_finish arg...
+func_mode_finish ()
+{
+    $opt_debug
+    libs=
+    libdirs=
+    admincmds=
+
+    for opt in "$nonopt" ${1+"$@"}
+    do
+      if test -d "$opt"; then
+       func_append libdirs " $opt"
+
+      elif test -f "$opt"; then
+       if func_lalib_unsafe_p "$opt"; then
+         func_append libs " $opt"
+       else
+         func_warning "\`$opt' is not a valid libtool archive"
+       fi
+
+      else
+       func_fatal_error "invalid argument \`$opt'"
+      fi
+    done
+
+    if test -n "$libs"; then
+      if test -n "$lt_sysroot"; then
+        sysroot_regex=`$ECHO "$lt_sysroot" | $SED "$sed_make_literal_regex"`
+        sysroot_cmd="s/\([ ']\)$sysroot_regex/\1/g;"
+      else
+        sysroot_cmd=
+      fi
+
+      # Remove sysroot references
+      if $opt_dry_run; then
+        for lib in $libs; do
+          echo "removing references to $lt_sysroot and \`=' prefixes from $lib"
+        done
+      else
+        tmpdir=`func_mktempdir`
+        for lib in $libs; do
+         sed -e "${sysroot_cmd} s/\([ ']-[LR]\)=/\1/g; s/\([ ']\)=/\1/g" $lib \
+           > $tmpdir/tmp-la
+         mv -f $tmpdir/tmp-la $lib
+       done
+        ${RM}r "$tmpdir"
+      fi
+    fi
+
+    if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then
+      for libdir in $libdirs; do
+       if test -n "$finish_cmds"; then
+         # Do each command in the finish commands.
+         func_execute_cmds "$finish_cmds" 'admincmds="$admincmds
+'"$cmd"'"'
+       fi
+       if test -n "$finish_eval"; then
+         # Do the single finish_eval.
+         eval cmds=\"$finish_eval\"
+         $opt_dry_run || eval "$cmds" || func_append admincmds "
+       $cmds"
+       fi
+      done
+    fi
+
+    # Exit here if they wanted silent mode.
+    $opt_silent && exit $EXIT_SUCCESS
+
+    if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then
+      echo "----------------------------------------------------------------------"
+      echo "Libraries have been installed in:"
+      for libdir in $libdirs; do
+       $ECHO "   $libdir"
+      done
+      echo
+      echo "If you ever happen to want to link against installed libraries"
+      echo "in a given directory, LIBDIR, you must either use libtool, and"
+      echo "specify the full pathname of the library, or use the \`-LLIBDIR'"
+      echo "flag during linking and do at least one of the following:"
+      if test -n "$shlibpath_var"; then
+       echo "   - add LIBDIR to the \`$shlibpath_var' environment variable"
+       echo "     during execution"
+      fi
+      if test -n "$runpath_var"; then
+       echo "   - add LIBDIR to the \`$runpath_var' environment variable"
+       echo "     during linking"
+      fi
+      if test -n "$hardcode_libdir_flag_spec"; then
+       libdir=LIBDIR
+       eval flag=\"$hardcode_libdir_flag_spec\"
+
+       $ECHO "   - use the \`$flag' linker flag"
+      fi
+      if test -n "$admincmds"; then
+       $ECHO "   - have your system administrator run these commands:$admincmds"
+      fi
+      if test -f /etc/ld.so.conf; then
+       echo "   - have your system administrator add LIBDIR to \`/etc/ld.so.conf'"
+      fi
+      echo
+
+      echo "See any operating system documentation about shared libraries for"
+      case $host in
+       solaris2.[6789]|solaris2.1[0-9])
+         echo "more information, such as the ld(1), crle(1) and ld.so(8) manual"
+         echo "pages."
+         ;;
+       *)
+         echo "more information, such as the ld(1) and ld.so(8) manual pages."
+         ;;
+      esac
+      echo "----------------------------------------------------------------------"
+    fi
+    exit $EXIT_SUCCESS
+}
+
+test "$opt_mode" = finish && func_mode_finish ${1+"$@"}
+
+
+# func_mode_install arg...
+func_mode_install ()
+{
+    $opt_debug
+    # There may be an optional sh(1) argument at the beginning of
+    # install_prog (especially on Windows NT).
+    if test "$nonopt" = "$SHELL" || test "$nonopt" = /bin/sh ||
+       # Allow the use of GNU shtool's install command.
+       case $nonopt in *shtool*) :;; *) false;; esac; then
+      # Aesthetically quote it.
+      func_quote_for_eval "$nonopt"
+      install_prog="$func_quote_for_eval_result "
+      arg=$1
+      shift
+    else
+      install_prog=
+      arg=$nonopt
+    fi
+
+    # The real first argument should be the name of the installation program.
+    # Aesthetically quote it.
+    func_quote_for_eval "$arg"
+    func_append install_prog "$func_quote_for_eval_result"
+    install_shared_prog=$install_prog
+    case " $install_prog " in
+      *[\\\ /]cp\ *) install_cp=: ;;
+      *) install_cp=false ;;
+    esac
+
+    # We need to accept at least all the BSD install flags.
+    dest=
+    files=
+    opts=
+    prev=
+    install_type=
+    isdir=no
+    stripme=
+    no_mode=:
+    for arg
+    do
+      arg2=
+      if test -n "$dest"; then
+       func_append files " $dest"
+       dest=$arg
+       continue
+      fi
+
+      case $arg in
+      -d) isdir=yes ;;
+      -f)
+       if $install_cp; then :; else
+         prev=$arg
+       fi
+       ;;
+      -g | -m | -o)
+       prev=$arg
+       ;;
+      -s)
+       stripme=" -s"
+       continue
+       ;;
+      -*)
+       ;;
+      *)
+       # If the previous option needed an argument, then skip it.
+       if test -n "$prev"; then
+         if test "x$prev" = x-m && test -n "$install_override_mode"; then
+           arg2=$install_override_mode
+           no_mode=false
+         fi
+         prev=
+       else
+         dest=$arg
+         continue
+       fi
+       ;;
+      esac
+
+      # Aesthetically quote the argument.
+      func_quote_for_eval "$arg"
+      func_append install_prog " $func_quote_for_eval_result"
+      if test -n "$arg2"; then
+       func_quote_for_eval "$arg2"
+      fi
+      func_append install_shared_prog " $func_quote_for_eval_result"
+    done
+
+    test -z "$install_prog" && \
+      func_fatal_help "you must specify an install program"
+
+    test -n "$prev" && \
+      func_fatal_help "the \`$prev' option requires an argument"
+
+    if test -n "$install_override_mode" && $no_mode; then
+      if $install_cp; then :; else
+       func_quote_for_eval "$install_override_mode"
+       func_append install_shared_prog " -m $func_quote_for_eval_result"
+      fi
+    fi
+
+    if test -z "$files"; then
+      if test -z "$dest"; then
+       func_fatal_help "no file or destination specified"
+      else
+       func_fatal_help "you must specify a destination"
+      fi
+    fi
+
+    # Strip any trailing slash from the destination.
+    func_stripname '' '/' "$dest"
+    dest=$func_stripname_result
+
+    # Check to see that the destination is a directory.
+    test -d "$dest" && isdir=yes
+    if test "$isdir" = yes; then
+      destdir="$dest"
+      destname=
+    else
+      func_dirname_and_basename "$dest" "" "."
+      destdir="$func_dirname_result"
+      destname="$func_basename_result"
+
+      # Not a directory, so check to see that there is only one file specified.
+      set dummy $files; shift
+      test "$#" -gt 1 && \
+       func_fatal_help "\`$dest' is not a directory"
+    fi
+    case $destdir in
+    [\\/]* | [A-Za-z]:[\\/]*) ;;
+    *)
+      for file in $files; do
+       case $file in
+       *.lo) ;;
+       *)
+         func_fatal_help "\`$destdir' must be an absolute directory name"
+         ;;
+       esac
+      done
+      ;;
+    esac
+
+    # This variable tells wrapper scripts just to set variables rather
+    # than running their programs.
+    libtool_install_magic="$magic"
+
+    staticlibs=
+    future_libdirs=
+    current_libdirs=
+    for file in $files; do
+
+      # Do each installation.
+      case $file in
+      *.$libext)
+       # Do the static libraries later.
+       func_append staticlibs " $file"
+       ;;
+
+      *.la)
+       func_resolve_sysroot "$file"
+       file=$func_resolve_sysroot_result
+
+       # Check to see that this really is a libtool archive.
+       func_lalib_unsafe_p "$file" \
+         || func_fatal_help "\`$file' is not a valid libtool archive"
+
+       library_names=
+       old_library=
+       relink_command=
+       func_source "$file"
+
+       # Add the libdir to current_libdirs if it is the destination.
+       if test "X$destdir" = "X$libdir"; then
+         case "$current_libdirs " in
+         *" $libdir "*) ;;
+         *) func_append current_libdirs " $libdir" ;;
+         esac
+       else
+         # Note the libdir as a future libdir.
+         case "$future_libdirs " in
+         *" $libdir "*) ;;
+         *) func_append future_libdirs " $libdir" ;;
+         esac
+       fi
+
+       func_dirname "$file" "/" ""
+       dir="$func_dirname_result"
+       func_append dir "$objdir"
+
+       if test -n "$relink_command"; then
+         # Determine the prefix the user has applied to our future dir.
+         inst_prefix_dir=`$ECHO "$destdir" | $SED -e "s%$libdir\$%%"`
+
+         # Don't allow the user to place us outside of our expected
+         # location b/c this prevents finding dependent libraries that
+         # are installed to the same prefix.
+         # At present, this check doesn't affect windows .dll's that
+         # are installed into $libdir/../bin (currently, that works fine)
+         # but it's something to keep an eye on.
+         test "$inst_prefix_dir" = "$destdir" && \
+           func_fatal_error "error: cannot install \`$file' to a directory not ending in $libdir"
+
+         if test -n "$inst_prefix_dir"; then
+           # Stick the inst_prefix_dir data into the link command.
+           relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%-inst-prefix-dir $inst_prefix_dir%"`
+         else
+           relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%%"`
+         fi
+
+         func_warning "relinking \`$file'"
+         func_show_eval "$relink_command" \
+           'func_fatal_error "error: relink \`$file'\'' with the above command before installing it"'
+       fi
+
+       # See the names of the shared library.
+       set dummy $library_names; shift
+       if test -n "$1"; then
+         realname="$1"
+         shift
+
+         srcname="$realname"
+         test -n "$relink_command" && srcname="$realname"T
+
+         # Install the shared library and build the symlinks.
+         func_show_eval "$install_shared_prog $dir/$srcname $destdir/$realname" \
+             'exit $?'
+         tstripme="$stripme"
+         case $host_os in
+         cygwin* | mingw* | pw32* | cegcc*)
+           case $realname in
+           *.dll.a)
+             tstripme=""
+             ;;
+           esac
+           ;;
+         esac
+         if test -n "$tstripme" && test -n "$striplib"; then
+           func_show_eval "$striplib $destdir/$realname" 'exit $?'
+         fi
+
+         if test "$#" -gt 0; then
+           # Delete the old symlinks, and create new ones.
+           # Try `ln -sf' first, because the `ln' binary might depend on
+           # the symlink we replace!  Solaris /bin/ln does not understand -f,
+           # so we also need to try rm && ln -s.
+           for linkname
+           do
+             test "$linkname" != "$realname" \
+               && func_show_eval "(cd $destdir && { $LN_S -f $realname $linkname || { $RM $linkname && $LN_S $realname $linkname; }; })"
+           done
+         fi
+
+         # Do each command in the postinstall commands.
+         lib="$destdir/$realname"
+         func_execute_cmds "$postinstall_cmds" 'exit $?'
+       fi
+
+       # Install the pseudo-library for information purposes.
+       func_basename "$file"
+       name="$func_basename_result"
+       instname="$dir/$name"i
+       func_show_eval "$install_prog $instname $destdir/$name" 'exit $?'
+
+       # Maybe install the static library, too.
+       test -n "$old_library" && func_append staticlibs " $dir/$old_library"
+       ;;
+
+      *.lo)
+       # Install (i.e. copy) a libtool object.
+
+       # Figure out destination file name, if it wasn't already specified.
+       if test -n "$destname"; then
+         destfile="$destdir/$destname"
+       else
+         func_basename "$file"
+         destfile="$func_basename_result"
+         destfile="$destdir/$destfile"
+       fi
+
+       # Deduce the name of the destination old-style object file.
+       case $destfile in
+       *.lo)
+         func_lo2o "$destfile"
+         staticdest=$func_lo2o_result
+         ;;
+       *.$objext)
+         staticdest="$destfile"
+         destfile=
+         ;;
+       *)
+         func_fatal_help "cannot copy a libtool object to \`$destfile'"
+         ;;
+       esac
+
+       # Install the libtool object if requested.
+       test -n "$destfile" && \
+         func_show_eval "$install_prog $file $destfile" 'exit $?'
+
+       # Install the old object if enabled.
+       if test "$build_old_libs" = yes; then
+         # Deduce the name of the old-style object file.
+         func_lo2o "$file"
+         staticobj=$func_lo2o_result
+         func_show_eval "$install_prog \$staticobj \$staticdest" 'exit $?'
+       fi
+       exit $EXIT_SUCCESS
+       ;;
+
+      *)
+       # Figure out destination file name, if it wasn't already specified.
+       if test -n "$destname"; then
+         destfile="$destdir/$destname"
+       else
+         func_basename "$file"
+         destfile="$func_basename_result"
+         destfile="$destdir/$destfile"
+       fi
+
+       # If the file is missing, and there is a .exe on the end, strip it
+       # because it is most likely a libtool script we actually want to
+       # install
+       stripped_ext=""
+       case $file in
+         *.exe)
+           if test ! -f "$file"; then
+             func_stripname '' '.exe' "$file"
+             file=$func_stripname_result
+             stripped_ext=".exe"
+           fi
+           ;;
+       esac
+
+       # Do a test to see if this is really a libtool program.
+       case $host in
+       *cygwin* | *mingw*)
+           if func_ltwrapper_executable_p "$file"; then
+             func_ltwrapper_scriptname "$file"
+             wrapper=$func_ltwrapper_scriptname_result
+           else
+             func_stripname '' '.exe' "$file"
+             wrapper=$func_stripname_result
+           fi
+           ;;
+       *)
+           wrapper=$file
+           ;;
+       esac
+       if func_ltwrapper_script_p "$wrapper"; then
+         notinst_deplibs=
+         relink_command=
+
+         func_source "$wrapper"
+
+         # Check the variables that should have been set.
+         test -z "$generated_by_libtool_version" && \
+           func_fatal_error "invalid libtool wrapper script \`$wrapper'"
+
+         finalize=yes
+         for lib in $notinst_deplibs; do
+           # Check to see that each library is installed.
+           libdir=
+           if test -f "$lib"; then
+             func_source "$lib"
+           fi
+           libfile="$libdir/"`$ECHO "$lib" | $SED 's%^.*/%%g'` ### testsuite: skip nested quoting test
+           if test -n "$libdir" && test ! -f "$libfile"; then
+             func_warning "\`$lib' has not been installed in \`$libdir'"
+             finalize=no
+           fi
+         done
+
+         relink_command=
+         func_source "$wrapper"
+
+         outputname=
+         if test "$fast_install" = no && test -n "$relink_command"; then
+           $opt_dry_run || {
+             if test "$finalize" = yes; then
+               tmpdir=`func_mktempdir`
+               func_basename "$file$stripped_ext"
+               file="$func_basename_result"
+               outputname="$tmpdir/$file"
+               # Replace the output file specification.
+               relink_command=`$ECHO "$relink_command" | $SED 's%@OUTPUT@%'"$outputname"'%g'`
+
+               $opt_silent || {
+                 func_quote_for_expand "$relink_command"
+                 eval "func_echo $func_quote_for_expand_result"
+               }
+               if eval "$relink_command"; then :
+                 else
+                 func_error "error: relink \`$file' with the above command before installing it"
+                 $opt_dry_run || ${RM}r "$tmpdir"
+                 continue
+               fi
+               file="$outputname"
+             else
+               func_warning "cannot relink \`$file'"
+             fi
+           }
+         else
+           # Install the binary that we compiled earlier.
+           file=`$ECHO "$file$stripped_ext" | $SED "s%\([^/]*\)$%$objdir/\1%"`
+         fi
+       fi
+
+       # remove .exe since cygwin /usr/bin/install will append another
+       # one anyway
+       case $install_prog,$host in
+       */usr/bin/install*,*cygwin*)
+         case $file:$destfile in
+         *.exe:*.exe)
+           # this is ok
+           ;;
+         *.exe:*)
+           destfile=$destfile.exe
+           ;;
+         *:*.exe)
+           func_stripname '' '.exe' "$destfile"
+           destfile=$func_stripname_result
+           ;;
+         esac
+         ;;
+       esac
+       func_show_eval "$install_prog\$stripme \$file \$destfile" 'exit $?'
+       $opt_dry_run || if test -n "$outputname"; then
+         ${RM}r "$tmpdir"
+       fi
+       ;;
+      esac
+    done
+
+    for file in $staticlibs; do
+      func_basename "$file"
+      name="$func_basename_result"
+
+      # Set up the ranlib parameters.
+      oldlib="$destdir/$name"
+      func_to_tool_file "$oldlib" func_convert_file_msys_to_w32
+      tool_oldlib=$func_to_tool_file_result
+
+      func_show_eval "$install_prog \$file \$oldlib" 'exit $?'
+
+      if test -n "$stripme" && test -n "$old_striplib"; then
+       func_show_eval "$old_striplib $tool_oldlib" 'exit $?'
+      fi
+
+      # Do each command in the postinstall commands.
+      func_execute_cmds "$old_postinstall_cmds" 'exit $?'
+    done
+
+    test -n "$future_libdirs" && \
+      func_warning "remember to run \`$progname --finish$future_libdirs'"
+
+    if test -n "$current_libdirs"; then
+      # Maybe just do a dry run.
+      $opt_dry_run && current_libdirs=" -n$current_libdirs"
+      exec_cmd='$SHELL $progpath $preserve_args --finish$current_libdirs'
+    else
+      exit $EXIT_SUCCESS
+    fi
+}
+
+test "$opt_mode" = install && func_mode_install ${1+"$@"}
+
+
+# func_generate_dlsyms outputname originator pic_p
+# Extract symbols from dlprefiles and create ${outputname}S.o with
+# a dlpreopen symbol table.
+func_generate_dlsyms ()
+{
+    $opt_debug
+    my_outputname="$1"
+    my_originator="$2"
+    my_pic_p="${3-no}"
+    my_prefix=`$ECHO "$my_originator" | sed 's%[^a-zA-Z0-9]%_%g'`
+    my_dlsyms=
+
+    if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then
+      if test -n "$NM" && test -n "$global_symbol_pipe"; then
+       my_dlsyms="${my_outputname}S.c"
+      else
+       func_error "not configured to extract global symbols from dlpreopened files"
+      fi
+    fi
+
+    if test -n "$my_dlsyms"; then
+      case $my_dlsyms in
+      "") ;;
+      *.c)
+       # Discover the nlist of each of the dlfiles.
+       nlist="$output_objdir/${my_outputname}.nm"
+
+       func_show_eval "$RM $nlist ${nlist}S ${nlist}T"
+
+       # Parse the name list into a source file.
+       func_verbose "creating $output_objdir/$my_dlsyms"
+
+       $opt_dry_run || $ECHO > "$output_objdir/$my_dlsyms" "\
+/* $my_dlsyms - symbol resolution table for \`$my_outputname' dlsym emulation. */
+/* Generated by $PROGRAM (GNU $PACKAGE$TIMESTAMP) $VERSION */
+
+#ifdef __cplusplus
+extern \"C\" {
+#endif
+
+#if defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4)) || (__GNUC__ > 4))
+#pragma GCC diagnostic ignored \"-Wstrict-prototypes\"
+#endif
+
+/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests.  */
+#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE)
+/* DATA imports from DLLs on WIN32 con't be const, because runtime
+   relocations are performed -- see ld's documentation on pseudo-relocs.  */
+# define LT_DLSYM_CONST
+#elif defined(__osf__)
+/* This system does not cope well with relocations in const data.  */
+# define LT_DLSYM_CONST
+#else
+# define LT_DLSYM_CONST const
+#endif
+
+/* External symbol declarations for the compiler. */\
+"
+
+       if test "$dlself" = yes; then
+         func_verbose "generating symbol list for \`$output'"
+
+         $opt_dry_run || echo ': @PROGRAM@ ' > "$nlist"
+
+         # Add our own program objects to the symbol list.
+         progfiles=`$ECHO "$objs$old_deplibs" | $SP2NL | $SED "$lo2o" | $NL2SP`
+         for progfile in $progfiles; do
+           func_to_tool_file "$progfile" func_convert_file_msys_to_w32
+           func_verbose "extracting global C symbols from \`$func_to_tool_file_result'"
+           $opt_dry_run || eval "$NM $func_to_tool_file_result | $global_symbol_pipe >> '$nlist'"
+         done
+
+         if test -n "$exclude_expsyms"; then
+           $opt_dry_run || {
+             eval '$EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T'
+             eval '$MV "$nlist"T "$nlist"'
+           }
+         fi
+
+         if test -n "$export_symbols_regex"; then
+           $opt_dry_run || {
+             eval '$EGREP -e "$export_symbols_regex" "$nlist" > "$nlist"T'
+             eval '$MV "$nlist"T "$nlist"'
+           }
+         fi
+
+         # Prepare the list of exported symbols
+         if test -z "$export_symbols"; then
+           export_symbols="$output_objdir/$outputname.exp"
+           $opt_dry_run || {
+             $RM $export_symbols
+             eval "${SED} -n -e '/^: @PROGRAM@ $/d' -e 's/^.* \(.*\)$/\1/p' "'< "$nlist" > "$export_symbols"'
+             case $host in
+             *cygwin* | *mingw* | *cegcc* )
+                eval "echo EXPORTS "'> "$output_objdir/$outputname.def"'
+                eval 'cat "$export_symbols" >> "$output_objdir/$outputname.def"'
+               ;;
+             esac
+           }
+         else
+           $opt_dry_run || {
+             eval "${SED} -e 's/\([].[*^$]\)/\\\\\1/g' -e 's/^/ /' -e 's/$/$/'"' < "$export_symbols" > "$output_objdir/$outputname.exp"'
+             eval '$GREP -f "$output_objdir/$outputname.exp" < "$nlist" > "$nlist"T'
+             eval '$MV "$nlist"T "$nlist"'
+             case $host in
+               *cygwin* | *mingw* | *cegcc* )
+                 eval "echo EXPORTS "'> "$output_objdir/$outputname.def"'
+                 eval 'cat "$nlist" >> "$output_objdir/$outputname.def"'
+                 ;;
+             esac
+           }
+         fi
+       fi
+
+       for dlprefile in $dlprefiles; do
+         func_verbose "extracting global C symbols from \`$dlprefile'"
+         func_basename "$dlprefile"
+         name="$func_basename_result"
+          case $host in
+           *cygwin* | *mingw* | *cegcc* )
+             # if an import library, we need to obtain dlname
+             if func_win32_import_lib_p "$dlprefile"; then
+               func_tr_sh "$dlprefile"
+               eval "curr_lafile=\$libfile_$func_tr_sh_result"
+               dlprefile_dlbasename=""
+               if test -n "$curr_lafile" && func_lalib_p "$curr_lafile"; then
+                 # Use subshell, to avoid clobbering current variable values
+                 dlprefile_dlname=`source "$curr_lafile" && echo "$dlname"`
+                 if test -n "$dlprefile_dlname" ; then
+                   func_basename "$dlprefile_dlname"
+                   dlprefile_dlbasename="$func_basename_result"
+                 else
+                   # no lafile. user explicitly requested -dlpreopen <import library>.
+                   $sharedlib_from_linklib_cmd "$dlprefile"
+                   dlprefile_dlbasename=$sharedlib_from_linklib_result
+                 fi
+               fi
+               $opt_dry_run || {
+                 if test -n "$dlprefile_dlbasename" ; then
+                   eval '$ECHO ": $dlprefile_dlbasename" >> "$nlist"'
+                 else
+                   func_warning "Could not compute DLL name from $name"
+                   eval '$ECHO ": $name " >> "$nlist"'
+                 fi
+                 func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32
+                 eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe |
+                   $SED -e '/I __imp/d' -e 's/I __nm_/D /;s/_nm__//' >> '$nlist'"
+               }
+             else # not an import lib
+               $opt_dry_run || {
+                 eval '$ECHO ": $name " >> "$nlist"'
+                 func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32
+                 eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'"
+               }
+             fi
+           ;;
+           *)
+             $opt_dry_run || {
+               eval '$ECHO ": $name " >> "$nlist"'
+               func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32
+               eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'"
+             }
+           ;;
+          esac
+       done
+
+       $opt_dry_run || {
+         # Make sure we have at least an empty file.
+         test -f "$nlist" || : > "$nlist"
+
+         if test -n "$exclude_expsyms"; then
+           $EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T
+           $MV "$nlist"T "$nlist"
+         fi
+
+         # Try sorting and uniquifying the output.
+         if $GREP -v "^: " < "$nlist" |
+             if sort -k 3 </dev/null >/dev/null 2>&1; then
+               sort -k 3
+             else
+               sort +2
+             fi |
+             uniq > "$nlist"S; then
+           :
+         else
+           $GREP -v "^: " < "$nlist" > "$nlist"S
+         fi
+
+         if test -f "$nlist"S; then
+           eval "$global_symbol_to_cdecl"' < "$nlist"S >> "$output_objdir/$my_dlsyms"'
+         else
+           echo '/* NONE */' >> "$output_objdir/$my_dlsyms"
+         fi
+
+         echo >> "$output_objdir/$my_dlsyms" "\
+
+/* The mapping between symbol names and symbols.  */
+typedef struct {
+  const char *name;
+  void *address;
+} lt_dlsymlist;
+extern LT_DLSYM_CONST lt_dlsymlist
+lt_${my_prefix}_LTX_preloaded_symbols[];
+LT_DLSYM_CONST lt_dlsymlist
+lt_${my_prefix}_LTX_preloaded_symbols[] =
+{\
+  { \"$my_originator\", (void *) 0 },"
+
+         case $need_lib_prefix in
+         no)
+           eval "$global_symbol_to_c_name_address" < "$nlist" >> "$output_objdir/$my_dlsyms"
+           ;;
+         *)
+           eval "$global_symbol_to_c_name_address_lib_prefix" < "$nlist" >> "$output_objdir/$my_dlsyms"
+           ;;
+         esac
+         echo >> "$output_objdir/$my_dlsyms" "\
+  {0, (void *) 0}
+};
+
+/* This works around a problem in FreeBSD linker */
+#ifdef FREEBSD_WORKAROUND
+static const void *lt_preloaded_setup() {
+  return lt_${my_prefix}_LTX_preloaded_symbols;
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif\
+"
+       } # !$opt_dry_run
+
+       pic_flag_for_symtable=
+       case "$compile_command " in
+       *" -static "*) ;;
+       *)
+         case $host in
+         # compiling the symbol table file with pic_flag works around
+         # a FreeBSD bug that causes programs to crash when -lm is
+         # linked before any other PIC object.  But we must not use
+         # pic_flag when linking with -static.  The problem exists in
+         # FreeBSD 2.2.6 and is fixed in FreeBSD 3.1.
+         *-*-freebsd2.*|*-*-freebsd3.0*|*-*-freebsdelf3.0*)
+           pic_flag_for_symtable=" $pic_flag -DFREEBSD_WORKAROUND" ;;
+         *-*-hpux*)
+           pic_flag_for_symtable=" $pic_flag"  ;;
+         *)
+           if test "X$my_pic_p" != Xno; then
+             pic_flag_for_symtable=" $pic_flag"
+           fi
+           ;;
+         esac
+         ;;
+       esac
+       symtab_cflags=
+       for arg in $LTCFLAGS; do
+         case $arg in
+         -pie | -fpie | -fPIE) ;;
+         *) func_append symtab_cflags " $arg" ;;
+         esac
+       done
+
+       # Now compile the dynamic symbol file.
+       func_show_eval '(cd $output_objdir && $LTCC$symtab_cflags -c$no_builtin_flag$pic_flag_for_symtable "$my_dlsyms")' 'exit $?'
+
+       # Clean up the generated files.
+       func_show_eval '$RM "$output_objdir/$my_dlsyms" "$nlist" "${nlist}S" "${nlist}T"'
+
+       # Transform the symbol file into the correct name.
+       symfileobj="$output_objdir/${my_outputname}S.$objext"
+       case $host in
+       *cygwin* | *mingw* | *cegcc* )
+         if test -f "$output_objdir/$my_outputname.def"; then
+           compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"`
+           finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"`
+         else
+           compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"`
+           finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"`
+         fi
+         ;;
+       *)
+         compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"`
+         finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"`
+         ;;
+       esac
+       ;;
+      *)
+       func_fatal_error "unknown suffix for \`$my_dlsyms'"
+       ;;
+      esac
+    else
+      # We keep going just in case the user didn't refer to
+      # lt_preloaded_symbols.  The linker will fail if global_symbol_pipe
+      # really was required.
+
+      # Nullify the symbol file.
+      compile_command=`$ECHO "$compile_command" | $SED "s% @SYMFILE@%%"`
+      finalize_command=`$ECHO "$finalize_command" | $SED "s% @SYMFILE@%%"`
+    fi
+}
+
+# func_win32_libid arg
+# return the library type of file 'arg'
+#
+# Need a lot of goo to handle *both* DLLs and import libs
+# Has to be a shell function in order to 'eat' the argument
+# that is supplied when $file_magic_command is called.
+# Despite the name, also deal with 64 bit binaries.
+func_win32_libid ()
+{
+  $opt_debug
+  win32_libid_type="unknown"
+  win32_fileres=`file -L $1 2>/dev/null`
+  case $win32_fileres in
+  *ar\ archive\ import\ library*) # definitely import
+    win32_libid_type="x86 archive import"
+    ;;
+  *ar\ archive*) # could be an import, or static
+    # Keep the egrep pattern in sync with the one in _LT_CHECK_MAGIC_METHOD.
+    if eval $OBJDUMP -f $1 | $SED -e '10q' 2>/dev/null |
+       $EGREP 'file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' >/dev/null; then
+      func_to_tool_file "$1" func_convert_file_msys_to_w32
+      win32_nmres=`eval $NM -f posix -A \"$func_to_tool_file_result\" |
+       $SED -n -e '
+           1,100{
+               / I /{
+                   s,.*,import,
+                   p
+                   q
+               }
+           }'`
+      case $win32_nmres in
+      import*)  win32_libid_type="x86 archive import";;
+      *)        win32_libid_type="x86 archive static";;
+      esac
+    fi
+    ;;
+  *DLL*)
+    win32_libid_type="x86 DLL"
+    ;;
+  *executable*) # but shell scripts are "executable" too...
+    case $win32_fileres in
+    *MS\ Windows\ PE\ Intel*)
+      win32_libid_type="x86 DLL"
+      ;;
+    esac
+    ;;
+  esac
+  $ECHO "$win32_libid_type"
+}
+
+# func_cygming_dll_for_implib ARG
+#
+# Platform-specific function to extract the
+# name of the DLL associated with the specified
+# import library ARG.
+# Invoked by eval'ing the libtool variable
+#    $sharedlib_from_linklib_cmd
+# Result is available in the variable
+#    $sharedlib_from_linklib_result
+func_cygming_dll_for_implib ()
+{
+  $opt_debug
+  sharedlib_from_linklib_result=`$DLLTOOL --identify-strict --identify "$1"`
+}
+
+# func_cygming_dll_for_implib_fallback_core SECTION_NAME LIBNAMEs
+#
+# The is the core of a fallback implementation of a
+# platform-specific function to extract the name of the
+# DLL associated with the specified import library LIBNAME.
+#
+# SECTION_NAME is either .idata$6 or .idata$7, depending
+# on the platform and compiler that created the implib.
+#
+# Echos the name of the DLL associated with the
+# specified import library.
+func_cygming_dll_for_implib_fallback_core ()
+{
+  $opt_debug
+  match_literal=`$ECHO "$1" | $SED "$sed_make_literal_regex"`
+  $OBJDUMP -s --section "$1" "$2" 2>/dev/null |
+    $SED '/^Contents of section '"$match_literal"':/{
+      # Place marker at beginning of archive member dllname section
+      s/.*/====MARK====/
+      p
+      d
+    }
+    # These lines can sometimes be longer than 43 characters, but
+    # are always uninteresting
+    /:[         ]*file format pe[i]\{,1\}-/d
+    /^In archive [^:]*:/d
+    # Ensure marker is printed
+    /^====MARK====/p
+    # Remove all lines with less than 43 characters
+    /^.\{43\}/!d
+    # From remaining lines, remove first 43 characters
+    s/^.\{43\}//' |
+    $SED -n '
+      # Join marker and all lines until next marker into a single line
+      /^====MARK====/ b para
+      H
+      $ b para
+      b
+      :para
+      x
+      s/\n//g
+      # Remove the marker
+      s/^====MARK====//
+      # Remove trailing dots and whitespace
+      s/[\. \t]*$//
+      # Print
+      /./p' |
+    # we now have a list, one entry per line, of the stringified
+    # contents of the appropriate section of all members of the
+    # archive which possess that section. Heuristic: eliminate
+    # all those which have a first or second character that is
+    # a '.' (that is, objdump's representation of an unprintable
+    # character.) This should work for all archives with less than
+    # 0x302f exports -- but will fail for DLLs whose name actually
+    # begins with a literal '.' or a single character followed by
+    # a '.'.
+    #
+    # Of those that remain, print the first one.
+    $SED -e '/^\./d;/^.\./d;q'
+}
+
+# func_cygming_gnu_implib_p ARG
+# This predicate returns with zero status (TRUE) if
+# ARG is a GNU/binutils-style import library. Returns
+# with nonzero status (FALSE) otherwise.
+func_cygming_gnu_implib_p ()
+{
+  $opt_debug
+  func_to_tool_file "$1" func_convert_file_msys_to_w32
+  func_cygming_gnu_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $EGREP ' (_head_[A-Za-z0-9_]+_[ad]l*|[A-Za-z0-9_]+_[ad]l*_iname)$'`
+  test -n "$func_cygming_gnu_implib_tmp"
+}
+
+# func_cygming_ms_implib_p ARG
+# This predicate returns with zero status (TRUE) if
+# ARG is an MS-style import library. Returns
+# with nonzero status (FALSE) otherwise.
+func_cygming_ms_implib_p ()
+{
+  $opt_debug
+  func_to_tool_file "$1" func_convert_file_msys_to_w32
+  func_cygming_ms_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $GREP '_NULL_IMPORT_DESCRIPTOR'`
+  test -n "$func_cygming_ms_implib_tmp"
+}
+
+# func_cygming_dll_for_implib_fallback ARG
+# Platform-specific function to extract the
+# name of the DLL associated with the specified
+# import library ARG.
+#
+# This fallback implementation is for use when $DLLTOOL
+# does not support the --identify-strict option.
+# Invoked by eval'ing the libtool variable
+#    $sharedlib_from_linklib_cmd
+# Result is available in the variable
+#    $sharedlib_from_linklib_result
+func_cygming_dll_for_implib_fallback ()
+{
+  $opt_debug
+  if func_cygming_gnu_implib_p "$1" ; then
+    # binutils import library
+    sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$7' "$1"`
+  elif func_cygming_ms_implib_p "$1" ; then
+    # ms-generated import library
+    sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$6' "$1"`
+  else
+    # unknown
+    sharedlib_from_linklib_result=""
+  fi
+}
+
+
+# func_extract_an_archive dir oldlib
+func_extract_an_archive ()
+{
+    $opt_debug
+    f_ex_an_ar_dir="$1"; shift
+    f_ex_an_ar_oldlib="$1"
+    if test "$lock_old_archive_extraction" = yes; then
+      lockfile=$f_ex_an_ar_oldlib.lock
+      until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do
+       func_echo "Waiting for $lockfile to be removed"
+       sleep 2
+      done
+    fi
+    func_show_eval "(cd \$f_ex_an_ar_dir && $AR x \"\$f_ex_an_ar_oldlib\")" \
+                  'stat=$?; rm -f "$lockfile"; exit $stat'
+    if test "$lock_old_archive_extraction" = yes; then
+      $opt_dry_run || rm -f "$lockfile"
+    fi
+    if ($AR t "$f_ex_an_ar_oldlib" | sort | sort -uc >/dev/null 2>&1); then
+     :
+    else
+      func_fatal_error "object name conflicts in archive: $f_ex_an_ar_dir/$f_ex_an_ar_oldlib"
+    fi
+}
+
+
+# func_extract_archives gentop oldlib ...
+func_extract_archives ()
+{
+    $opt_debug
+    my_gentop="$1"; shift
+    my_oldlibs=${1+"$@"}
+    my_oldobjs=""
+    my_xlib=""
+    my_xabs=""
+    my_xdir=""
+
+    for my_xlib in $my_oldlibs; do
+      # Extract the objects.
+      case $my_xlib in
+       [\\/]* | [A-Za-z]:[\\/]*) my_xabs="$my_xlib" ;;
+       *) my_xabs=`pwd`"/$my_xlib" ;;
+      esac
+      func_basename "$my_xlib"
+      my_xlib="$func_basename_result"
+      my_xlib_u=$my_xlib
+      while :; do
+        case " $extracted_archives " in
+       *" $my_xlib_u "*)
+         func_arith $extracted_serial + 1
+         extracted_serial=$func_arith_result
+         my_xlib_u=lt$extracted_serial-$my_xlib ;;
+       *) break ;;
+       esac
+      done
+      extracted_archives="$extracted_archives $my_xlib_u"
+      my_xdir="$my_gentop/$my_xlib_u"
+
+      func_mkdir_p "$my_xdir"
+
+      case $host in
+      *-darwin*)
+       func_verbose "Extracting $my_xabs"
+       # Do not bother doing anything if just a dry run
+       $opt_dry_run || {
+         darwin_orig_dir=`pwd`
+         cd $my_xdir || exit $?
+         darwin_archive=$my_xabs
+         darwin_curdir=`pwd`
+         darwin_base_archive=`basename "$darwin_archive"`
+         darwin_arches=`$LIPO -info "$darwin_archive" 2>/dev/null | $GREP Architectures 2>/dev/null || true`
+         if test -n "$darwin_arches"; then
+           darwin_arches=`$ECHO "$darwin_arches" | $SED -e 's/.*are://'`
+           darwin_arch=
+           func_verbose "$darwin_base_archive has multiple architectures $darwin_arches"
+           for darwin_arch in  $darwin_arches ; do
+             func_mkdir_p "unfat-$$/${darwin_base_archive}-${darwin_arch}"
+             $LIPO -thin $darwin_arch -output "unfat-$$/${darwin_base_archive}-${darwin_arch}/${darwin_base_archive}" "${darwin_archive}"
+             cd "unfat-$$/${darwin_base_archive}-${darwin_arch}"
+             func_extract_an_archive "`pwd`" "${darwin_base_archive}"
+             cd "$darwin_curdir"
+             $RM "unfat-$$/${darwin_base_archive}-${darwin_arch}/${darwin_base_archive}"
+           done # $darwin_arches
+            ## Okay now we've a bunch of thin objects, gotta fatten them up :)
+           darwin_filelist=`find unfat-$$ -type f -name \*.o -print -o -name \*.lo -print | $SED -e "$basename" | sort -u`
+           darwin_file=
+           darwin_files=
+           for darwin_file in $darwin_filelist; do
+             darwin_files=`find unfat-$$ -name $darwin_file -print | sort | $NL2SP`
+             $LIPO -create -output "$darwin_file" $darwin_files
+           done # $darwin_filelist
+           $RM -rf unfat-$$
+           cd "$darwin_orig_dir"
+         else
+           cd $darwin_orig_dir
+           func_extract_an_archive "$my_xdir" "$my_xabs"
+         fi # $darwin_arches
+       } # !$opt_dry_run
+       ;;
+      *)
+        func_extract_an_archive "$my_xdir" "$my_xabs"
+       ;;
+      esac
+      my_oldobjs="$my_oldobjs "`find $my_xdir -name \*.$objext -print -o -name \*.lo -print | sort | $NL2SP`
+    done
+
+    func_extract_archives_result="$my_oldobjs"
+}
+
+
+# func_emit_wrapper [arg=no]
+#
+# Emit a libtool wrapper script on stdout.
+# Don't directly open a file because we may want to
+# incorporate the script contents within a cygwin/mingw
+# wrapper executable.  Must ONLY be called from within
+# func_mode_link because it depends on a number of variables
+# set therein.
+#
+# ARG is the value that the WRAPPER_SCRIPT_BELONGS_IN_OBJDIR
+# variable will take.  If 'yes', then the emitted script
+# will assume that the directory in which it is stored is
+# the $objdir directory.  This is a cygwin/mingw-specific
+# behavior.
+func_emit_wrapper ()
+{
+       func_emit_wrapper_arg1=${1-no}
+
+       $ECHO "\
+#! $SHELL
+
+# $output - temporary wrapper script for $objdir/$outputname
+# Generated by $PROGRAM (GNU $PACKAGE$TIMESTAMP) $VERSION
+#
+# The $output program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting.  It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='$sed_quote_subst'
+
+# Be Bourne compatible
+if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then
+  emulate sh
+  NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on \${1+\"\$@\"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '\${1+\"\$@\"}'='\"\$@\"'
+  setopt NO_GLOB_SUBST
+else
+  case \`(set -o) 2>/dev/null\` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=\"$relink_command\"
+
+# This environment variable determines our operation mode.
+if test \"\$libtool_install_magic\" = \"$magic\"; then
+  # install mode needs the following variables:
+  generated_by_libtool_version='$macro_version'
+  notinst_deplibs='$notinst_deplibs'
+else
+  # When we are sourced in execute mode, \$file and \$ECHO are already set.
+  if test \"\$libtool_execute_magic\" != \"$magic\"; then
+    file=\"\$0\""
+
+    qECHO=`$ECHO "$ECHO" | $SED "$sed_quote_subst"`
+    $ECHO "\
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+  eval 'cat <<_LTECHO_EOF
+\$1
+_LTECHO_EOF'
+}
+    ECHO=\"$qECHO\"
+  fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ which is used only on
+# windows platforms, and (c) all begin with the string "--lt-"
+# (application programs are unlikely to have options which match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's $0 value, followed by "$@".
+lt_option_debug=
+func_parse_lt_options ()
+{
+  lt_script_arg0=\$0
+  shift
+  for lt_opt
+  do
+    case \"\$lt_opt\" in
+    --lt-debug) lt_option_debug=1 ;;
+    --lt-dump-script)
+        lt_dump_D=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%/[^/]*$%%'\`
+        test \"X\$lt_dump_D\" = \"X\$lt_script_arg0\" && lt_dump_D=.
+        lt_dump_F=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%^.*/%%'\`
+        cat \"\$lt_dump_D/\$lt_dump_F\"
+        exit 0
+      ;;
+    --lt-*)
+        \$ECHO \"Unrecognized --lt- option: '\$lt_opt'\" 1>&2
+        exit 1
+      ;;
+    esac
+  done
+
+  # Print the debug banner immediately:
+  if test -n \"\$lt_option_debug\"; then
+    echo \"${outputname}:${output}:\${LINENO}: libtool wrapper (GNU $PACKAGE$TIMESTAMP) $VERSION\" 1>&2
+  fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+  lt_dump_args_N=1;
+  for lt_arg
+  do
+    \$ECHO \"${outputname}:${output}:\${LINENO}: newargv[\$lt_dump_args_N]: \$lt_arg\"
+    lt_dump_args_N=\`expr \$lt_dump_args_N + 1\`
+  done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+"
+  case $host in
+  # Backslashes separate directories on plain windows
+  *-*-mingw | *-*-os2* | *-cegcc*)
+    $ECHO "\
+      if test -n \"\$lt_option_debug\"; then
+        \$ECHO \"${outputname}:${output}:\${LINENO}: newargv[0]: \$progdir\\\\\$program\" 1>&2
+        func_lt_dump_args \${1+\"\$@\"} 1>&2
+      fi
+      exec \"\$progdir\\\\\$program\" \${1+\"\$@\"}
+"
+    ;;
+
+  *)
+    $ECHO "\
+      if test -n \"\$lt_option_debug\"; then
+        \$ECHO \"${outputname}:${output}:\${LINENO}: newargv[0]: \$progdir/\$program\" 1>&2
+        func_lt_dump_args \${1+\"\$@\"} 1>&2
+      fi
+      exec \"\$progdir/\$program\" \${1+\"\$@\"}
+"
+    ;;
+  esac
+  $ECHO "\
+      \$ECHO \"\$0: cannot exec \$program \$*\" 1>&2
+      exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from \$@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+  case \" \$* \" in
+  *\\ --lt-*)
+    for lt_wr_arg
+    do
+      case \$lt_wr_arg in
+      --lt-*) ;;
+      *) set x \"\$@\" \"\$lt_wr_arg\"; shift;;
+      esac
+      shift
+    done ;;
+  esac
+  func_exec_program_core \${1+\"\$@\"}
+}
+
+  # Parse options
+  func_parse_lt_options \"\$0\" \${1+\"\$@\"}
+
+  # Find the directory that this script lives in.
+  thisdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*$%%'\`
+  test \"x\$thisdir\" = \"x\$file\" && thisdir=.
+
+  # Follow symbolic links until we get to the real thisdir.
+  file=\`ls -ld \"\$file\" | $SED -n 's/.*-> //p'\`
+  while test -n \"\$file\"; do
+    destdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*\$%%'\`
+
+    # If there was a directory component, then change thisdir.
+    if test \"x\$destdir\" != \"x\$file\"; then
+      case \"\$destdir\" in
+      [\\\\/]* | [A-Za-z]:[\\\\/]*) thisdir=\"\$destdir\" ;;
+      *) thisdir=\"\$thisdir/\$destdir\" ;;
+      esac
+    fi
+
+    file=\`\$ECHO \"\$file\" | $SED 's%^.*/%%'\`
+    file=\`ls -ld \"\$thisdir/\$file\" | $SED -n 's/.*-> //p'\`
+  done
+
+  # Usually 'no', except on cygwin/mingw when embedded into
+  # the cwrapper.
+  WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=$func_emit_wrapper_arg1
+  if test \"\$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR\" = \"yes\"; then
+    # special case for '.'
+    if test \"\$thisdir\" = \".\"; then
+      thisdir=\`pwd\`
+    fi
+    # remove .libs from thisdir
+    case \"\$thisdir\" in
+    *[\\\\/]$objdir ) thisdir=\`\$ECHO \"\$thisdir\" | $SED 's%[\\\\/][^\\\\/]*$%%'\` ;;
+    $objdir )   thisdir=. ;;
+    esac
+  fi
+
+  # Try to get the absolute directory name.
+  absdir=\`cd \"\$thisdir\" && pwd\`
+  test -n \"\$absdir\" && thisdir=\"\$absdir\"
+"
+
+       if test "$fast_install" = yes; then
+         $ECHO "\
+  program=lt-'$outputname'$exeext
+  progdir=\"\$thisdir/$objdir\"
+
+  if test ! -f \"\$progdir/\$program\" ||
+     { file=\`ls -1dt \"\$progdir/\$program\" \"\$progdir/../\$program\" 2>/dev/null | ${SED} 1q\`; \\
+       test \"X\$file\" != \"X\$progdir/\$program\"; }; then
+
+    file=\"\$\$-\$program\"
+
+    if test ! -d \"\$progdir\"; then
+      $MKDIR \"\$progdir\"
+    else
+      $RM \"\$progdir/\$file\"
+    fi"
+
+         $ECHO "\
+
+    # relink executable if necessary
+    if test -n \"\$relink_command\"; then
+      if relink_command_output=\`eval \$relink_command 2>&1\`; then :
+      else
+       $ECHO \"\$relink_command_output\" >&2
+       $RM \"\$progdir/\$file\"
+       exit 1
+      fi
+    fi
+
+    $MV \"\$progdir/\$file\" \"\$progdir/\$program\" 2>/dev/null ||
+    { $RM \"\$progdir/\$program\";
+      $MV \"\$progdir/\$file\" \"\$progdir/\$program\"; }
+    $RM \"\$progdir/\$file\"
+  fi"
+       else
+         $ECHO "\
+  program='$outputname'
+  progdir=\"\$thisdir/$objdir\"
+"
+       fi
+
+       $ECHO "\
+
+  if test -f \"\$progdir/\$program\"; then"
+
+       # fixup the dll searchpath if we need to.
+       #
+       # Fix the DLL searchpath if we need to.  Do this before prepending
+       # to shlibpath, because on Windows, both are PATH and uninstalled
+       # libraries must come first.
+       if test -n "$dllsearchpath"; then
+         $ECHO "\
+    # Add the dll search path components to the executable PATH
+    PATH=$dllsearchpath:\$PATH
+"
+       fi
+
+       # Export our shlibpath_var if we have one.
+       if test "$shlibpath_overrides_runpath" = yes && test -n "$shlibpath_var" && test -n "$temp_rpath"; then
+         $ECHO "\
+    # Add our own library path to $shlibpath_var
+    $shlibpath_var=\"$temp_rpath\$$shlibpath_var\"
+
+    # Some systems cannot cope with colon-terminated $shlibpath_var
+    # The second colon is a workaround for a bug in BeOS R4 sed
+    $shlibpath_var=\`\$ECHO \"\$$shlibpath_var\" | $SED 's/::*\$//'\`
+
+    export $shlibpath_var
+"
+       fi
+
+       $ECHO "\
+    if test \"\$libtool_execute_magic\" != \"$magic\"; then
+      # Run the actual program with our arguments.
+      func_exec_program \${1+\"\$@\"}
+    fi
+  else
+    # The program doesn't exist.
+    \$ECHO \"\$0: error: \\\`\$progdir/\$program' does not exist\" 1>&2
+    \$ECHO \"This script is just a wrapper for \$program.\" 1>&2
+    \$ECHO \"See the $PACKAGE documentation for more information.\" 1>&2
+    exit 1
+  fi
+fi\
+"
+}
+
+
+# func_emit_cwrapperexe_src
+# emit the source code for a wrapper executable on stdout
+# Must ONLY be called from within func_mode_link because
+# it depends on a number of variable set therein.
+func_emit_cwrapperexe_src ()
+{
+       cat <<EOF
+
+/* $cwrappersource - temporary wrapper executable for $objdir/$outputname
+   Generated by $PROGRAM (GNU $PACKAGE$TIMESTAMP) $VERSION
+
+   The $output program cannot be directly executed until all the libtool
+   libraries that it depends on are installed.
+
+   This wrapper executable should never be moved out of the build directory.
+   If it is, it will not operate correctly.
+*/
+EOF
+           cat <<"EOF"
+#ifdef _MSC_VER
+# define _CRT_SECURE_NO_DEPRECATE 1
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef _MSC_VER
+# include <direct.h>
+# include <process.h>
+# include <io.h>
+#else
+# include <unistd.h>
+# include <stdint.h>
+# ifdef __CYGWIN__
+#  include <io.h>
+# endif
+#endif
+#include <malloc.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+/* declarations of non-ANSI functions */
+#if defined(__MINGW32__)
+# ifdef __STRICT_ANSI__
+int _putenv (const char *);
+# endif
+#elif defined(__CYGWIN__)
+# ifdef __STRICT_ANSI__
+char *realpath (const char *, char *);
+int putenv (char *);
+int setenv (const char *, const char *, int);
+# endif
+/* #elif defined (other platforms) ... */
+#endif
+
+/* portability defines, excluding path handling macros */
+#if defined(_MSC_VER)
+# define setmode _setmode
+# define stat    _stat
+# define chmod   _chmod
+# define getcwd  _getcwd
+# define putenv  _putenv
+# define S_IXUSR _S_IEXEC
+# ifndef _INTPTR_T_DEFINED
+#  define _INTPTR_T_DEFINED
+#  define intptr_t int
+# endif
+#elif defined(__MINGW32__)
+# define setmode _setmode
+# define stat    _stat
+# define chmod   _chmod
+# define getcwd  _getcwd
+# define putenv  _putenv
+#elif defined(__CYGWIN__)
+# define HAVE_SETENV
+# define FOPEN_WB "wb"
+/* #elif defined (other platforms) ... */
+#endif
+
+#if defined(PATH_MAX)
+# define LT_PATHMAX PATH_MAX
+#elif defined(MAXPATHLEN)
+# define LT_PATHMAX MAXPATHLEN
+#else
+# define LT_PATHMAX 1024
+#endif
+
+#ifndef S_IXOTH
+# define S_IXOTH 0
+#endif
+#ifndef S_IXGRP
+# define S_IXGRP 0
+#endif
+
+/* path handling portability macros */
+#ifndef DIR_SEPARATOR
+# define DIR_SEPARATOR '/'
+# define PATH_SEPARATOR ':'
+#endif
+
+#if defined (_WIN32) || defined (__MSDOS__) || defined (__DJGPP__) || \
+  defined (__OS2__)
+# define HAVE_DOS_BASED_FILE_SYSTEM
+# define FOPEN_WB "wb"
+# ifndef DIR_SEPARATOR_2
+#  define DIR_SEPARATOR_2 '\\'
+# endif
+# ifndef PATH_SEPARATOR_2
+#  define PATH_SEPARATOR_2 ';'
+# endif
+#endif
+
+#ifndef DIR_SEPARATOR_2
+# define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR)
+#else /* DIR_SEPARATOR_2 */
+# define IS_DIR_SEPARATOR(ch) \
+       (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2))
+#endif /* DIR_SEPARATOR_2 */
+
+#ifndef PATH_SEPARATOR_2
+# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR)
+#else /* PATH_SEPARATOR_2 */
+# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR_2)
+#endif /* PATH_SEPARATOR_2 */
+
+#ifndef FOPEN_WB
+# define FOPEN_WB "w"
+#endif
+#ifndef _O_BINARY
+# define _O_BINARY 0
+#endif
+
+#define XMALLOC(type, num)      ((type *) xmalloc ((num) * sizeof(type)))
+#define XFREE(stale) do { \
+  if (stale) { free ((void *) stale); stale = 0; } \
+} while (0)
+
+#if defined(LT_DEBUGWRAPPER)
+static int lt_debug = 1;
+#else
+static int lt_debug = 0;
+#endif
+
+const char *program_name = "libtool-wrapper"; /* in case xstrdup fails */
+
+void *xmalloc (size_t num);
+char *xstrdup (const char *string);
+const char *base_name (const char *name);
+char *find_executable (const char *wrapper);
+char *chase_symlinks (const char *pathspec);
+int make_executable (const char *path);
+int check_executable (const char *path);
+char *strendzap (char *str, const char *pat);
+void lt_debugprintf (const char *file, int line, const char *fmt, ...);
+void lt_fatal (const char *file, int line, const char *message, ...);
+static const char *nonnull (const char *s);
+static const char *nonempty (const char *s);
+void lt_setenv (const char *name, const char *value);
+char *lt_extend_str (const char *orig_value, const char *add, int to_end);
+void lt_update_exe_path (const char *name, const char *value);
+void lt_update_lib_path (const char *name, const char *value);
+char **prepare_spawn (char **argv);
+void lt_dump_script (FILE *f);
+EOF
+
+           cat <<EOF
+volatile const char * MAGIC_EXE = "$magic_exe";
+const char * LIB_PATH_VARNAME = "$shlibpath_var";
+EOF
+
+           if test "$shlibpath_overrides_runpath" = yes && test -n "$shlibpath_var" && test -n "$temp_rpath"; then
+              func_to_host_path "$temp_rpath"
+             cat <<EOF
+const char * LIB_PATH_VALUE   = "$func_to_host_path_result";
+EOF
+           else
+             cat <<"EOF"
+const char * LIB_PATH_VALUE   = "";
+EOF
+           fi
+
+           if test -n "$dllsearchpath"; then
+              func_to_host_path "$dllsearchpath:"
+             cat <<EOF
+const char * EXE_PATH_VARNAME = "PATH";
+const char * EXE_PATH_VALUE   = "$func_to_host_path_result";
+EOF
+           else
+             cat <<"EOF"
+const char * EXE_PATH_VARNAME = "";
+const char * EXE_PATH_VALUE   = "";
+EOF
+           fi
+
+           if test "$fast_install" = yes; then
+             cat <<EOF
+const char * TARGET_PROGRAM_NAME = "lt-$outputname"; /* hopefully, no .exe */
+EOF
+           else
+             cat <<EOF
+const char * TARGET_PROGRAM_NAME = "$outputname"; /* hopefully, no .exe */
+EOF
+           fi
+
+
+           cat <<"EOF"
+
+#define LTWRAPPER_OPTION_PREFIX         "--lt-"
+
+static const char *ltwrapper_option_prefix = LTWRAPPER_OPTION_PREFIX;
+static const char *dumpscript_opt       = LTWRAPPER_OPTION_PREFIX "dump-script";
+static const char *debug_opt            = LTWRAPPER_OPTION_PREFIX "debug";
+
+int
+main (int argc, char *argv[])
+{
+  char **newargz;
+  int  newargc;
+  char *tmp_pathspec;
+  char *actual_cwrapper_path;
+  char *actual_cwrapper_name;
+  char *target_name;
+  char *lt_argv_zero;
+  intptr_t rval = 127;
+
+  int i;
+
+  program_name = (char *) xstrdup (base_name (argv[0]));
+  newargz = XMALLOC (char *, argc + 1);
+
+  /* very simple arg parsing; don't want to rely on getopt
+   * also, copy all non cwrapper options to newargz, except
+   * argz[0], which is handled differently
+   */
+  newargc=0;
+  for (i = 1; i < argc; i++)
+    {
+      if (strcmp (argv[i], dumpscript_opt) == 0)
+       {
+EOF
+           case "$host" in
+             *mingw* | *cygwin* )
+               # make stdout use "unix" line endings
+               echo "          setmode(1,_O_BINARY);"
+               ;;
+             esac
+
+           cat <<"EOF"
+         lt_dump_script (stdout);
+         return 0;
+       }
+      if (strcmp (argv[i], debug_opt) == 0)
+       {
+          lt_debug = 1;
+          continue;
+       }
+      if (strcmp (argv[i], ltwrapper_option_prefix) == 0)
+        {
+          /* however, if there is an option in the LTWRAPPER_OPTION_PREFIX
+             namespace, but it is not one of the ones we know about and
+             have already dealt with, above (inluding dump-script), then
+             report an error. Otherwise, targets might begin to believe
+             they are allowed to use options in the LTWRAPPER_OPTION_PREFIX
+             namespace. The first time any user complains about this, we'll
+             need to make LTWRAPPER_OPTION_PREFIX a configure-time option
+             or a configure.ac-settable value.
+           */
+          lt_fatal (__FILE__, __LINE__,
+                   "unrecognized %s option: '%s'",
+                    ltwrapper_option_prefix, argv[i]);
+        }
+      /* otherwise ... */
+      newargz[++newargc] = xstrdup (argv[i]);
+    }
+  newargz[++newargc] = NULL;
+
+EOF
+           cat <<EOF
+  /* The GNU banner must be the first non-error debug message */
+  lt_debugprintf (__FILE__, __LINE__, "libtool wrapper (GNU $PACKAGE$TIMESTAMP) $VERSION\n");
+EOF
+           cat <<"EOF"
+  lt_debugprintf (__FILE__, __LINE__, "(main) argv[0]: %s\n", argv[0]);
+  lt_debugprintf (__FILE__, __LINE__, "(main) program_name: %s\n", program_name);
+
+  tmp_pathspec = find_executable (argv[0]);
+  if (tmp_pathspec == NULL)
+    lt_fatal (__FILE__, __LINE__, "couldn't find %s", argv[0]);
+  lt_debugprintf (__FILE__, __LINE__,
+                  "(main) found exe (before symlink chase) at: %s\n",
+                 tmp_pathspec);
+
+  actual_cwrapper_path = chase_symlinks (tmp_pathspec);
+  lt_debugprintf (__FILE__, __LINE__,
+                  "(main) found exe (after symlink chase) at: %s\n",
+                 actual_cwrapper_path);
+  XFREE (tmp_pathspec);
+
+  actual_cwrapper_name = xstrdup (base_name (actual_cwrapper_path));
+  strendzap (actual_cwrapper_path, actual_cwrapper_name);
+
+  /* wrapper name transforms */
+  strendzap (actual_cwrapper_name, ".exe");
+  tmp_pathspec = lt_extend_str (actual_cwrapper_name, ".exe", 1);
+  XFREE (actual_cwrapper_name);
+  actual_cwrapper_name = tmp_pathspec;
+  tmp_pathspec = 0;
+
+  /* target_name transforms -- use actual target program name; might have lt- prefix */
+  target_name = xstrdup (base_name (TARGET_PROGRAM_NAME));
+  strendzap (target_name, ".exe");
+  tmp_pathspec = lt_extend_str (target_name, ".exe", 1);
+  XFREE (target_name);
+  target_name = tmp_pathspec;
+  tmp_pathspec = 0;
+
+  lt_debugprintf (__FILE__, __LINE__,
+                 "(main) libtool target name: %s\n",
+                 target_name);
+EOF
+
+           cat <<EOF
+  newargz[0] =
+    XMALLOC (char, (strlen (actual_cwrapper_path) +
+                   strlen ("$objdir") + 1 + strlen (actual_cwrapper_name) + 1));
+  strcpy (newargz[0], actual_cwrapper_path);
+  strcat (newargz[0], "$objdir");
+  strcat (newargz[0], "/");
+EOF
+
+           cat <<"EOF"
+  /* stop here, and copy so we don't have to do this twice */
+  tmp_pathspec = xstrdup (newargz[0]);
+
+  /* do NOT want the lt- prefix here, so use actual_cwrapper_name */
+  strcat (newargz[0], actual_cwrapper_name);
+
+  /* DO want the lt- prefix here if it exists, so use target_name */
+  lt_argv_zero = lt_extend_str (tmp_pathspec, target_name, 1);
+  XFREE (tmp_pathspec);
+  tmp_pathspec = NULL;
+EOF
+
+           case $host_os in
+             mingw*)
+           cat <<"EOF"
+  {
+    char* p;
+    while ((p = strchr (newargz[0], '\\')) != NULL)
+      {
+       *p = '/';
+      }
+    while ((p = strchr (lt_argv_zero, '\\')) != NULL)
+      {
+       *p = '/';
+      }
+  }
+EOF
+           ;;
+           esac
+
+           cat <<"EOF"
+  XFREE (target_name);
+  XFREE (actual_cwrapper_path);
+  XFREE (actual_cwrapper_name);
+
+  lt_setenv ("BIN_SH", "xpg4"); /* for Tru64 */
+  lt_setenv ("DUALCASE", "1");  /* for MSK sh */
+  /* Update the DLL searchpath.  EXE_PATH_VALUE ($dllsearchpath) must
+     be prepended before (that is, appear after) LIB_PATH_VALUE ($temp_rpath)
+     because on Windows, both *_VARNAMEs are PATH but uninstalled
+     libraries must come first. */
+  lt_update_exe_path (EXE_PATH_VARNAME, EXE_PATH_VALUE);
+  lt_update_lib_path (LIB_PATH_VARNAME, LIB_PATH_VALUE);
+
+  lt_debugprintf (__FILE__, __LINE__, "(main) lt_argv_zero: %s\n",
+                 nonnull (lt_argv_zero));
+  for (i = 0; i < newargc; i++)
+    {
+      lt_debugprintf (__FILE__, __LINE__, "(main) newargz[%d]: %s\n",
+                     i, nonnull (newargz[i]));
+    }
+
+EOF
+
+           case $host_os in
+             mingw*)
+               cat <<"EOF"
+  /* execv doesn't actually work on mingw as expected on unix */
+  newargz = prepare_spawn (newargz);
+  rval = _spawnv (_P_WAIT, lt_argv_zero, (const char * const *) newargz);
+  if (rval == -1)
+    {
+      /* failed to start process */
+      lt_debugprintf (__FILE__, __LINE__,
+                     "(main) failed to launch target \"%s\": %s\n",
+                     lt_argv_zero, nonnull (strerror (errno)));
+      return 127;
+    }
+  return rval;
+EOF
+               ;;
+             *)
+               cat <<"EOF"
+  execv (lt_argv_zero, newargz);
+  return rval; /* =127, but avoids unused variable warning */
+EOF
+               ;;
+           esac
+
+           cat <<"EOF"
+}
+
+void *
+xmalloc (size_t num)
+{
+  void *p = (void *) malloc (num);
+  if (!p)
+    lt_fatal (__FILE__, __LINE__, "memory exhausted");
+
+  return p;
+}
+
+char *
+xstrdup (const char *string)
+{
+  return string ? strcpy ((char *) xmalloc (strlen (string) + 1),
+                         string) : NULL;
+}
+
+const char *
+base_name (const char *name)
+{
+  const char *base;
+
+#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
+  /* Skip over the disk name in MSDOS pathnames. */
+  if (isalpha ((unsigned char) name[0]) && name[1] == ':')
+    name += 2;
+#endif
+
+  for (base = name; *name; name++)
+    if (IS_DIR_SEPARATOR (*name))
+      base = name + 1;
+  return base;
+}
+
+int
+check_executable (const char *path)
+{
+  struct stat st;
+
+  lt_debugprintf (__FILE__, __LINE__, "(check_executable): %s\n",
+                  nonempty (path));
+  if ((!path) || (!*path))
+    return 0;
+
+  if ((stat (path, &st) >= 0)
+      && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
+    return 1;
+  else
+    return 0;
+}
+
+int
+make_executable (const char *path)
+{
+  int rval = 0;
+  struct stat st;
+
+  lt_debugprintf (__FILE__, __LINE__, "(make_executable): %s\n",
+                  nonempty (path));
+  if ((!path) || (!*path))
+    return 0;
+
+  if (stat (path, &st) >= 0)
+    {
+      rval = chmod (path, st.st_mode | S_IXOTH | S_IXGRP | S_IXUSR);
+    }
+  return rval;
+}
+
+/* Searches for the full path of the wrapper.  Returns
+   newly allocated full path name if found, NULL otherwise
+   Does not chase symlinks, even on platforms that support them.
+*/
+char *
+find_executable (const char *wrapper)
+{
+  int has_slash = 0;
+  const char *p;
+  const char *p_next;
+  /* static buffer for getcwd */
+  char tmp[LT_PATHMAX + 1];
+  int tmp_len;
+  char *concat_name;
+
+  lt_debugprintf (__FILE__, __LINE__, "(find_executable): %s\n",
+                  nonempty (wrapper));
+
+  if ((wrapper == NULL) || (*wrapper == '\0'))
+    return NULL;
+
+  /* Absolute path? */
+#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
+  if (isalpha ((unsigned char) wrapper[0]) && wrapper[1] == ':')
+    {
+      concat_name = xstrdup (wrapper);
+      if (check_executable (concat_name))
+       return concat_name;
+      XFREE (concat_name);
+    }
+  else
+    {
+#endif
+      if (IS_DIR_SEPARATOR (wrapper[0]))
+       {
+         concat_name = xstrdup (wrapper);
+         if (check_executable (concat_name))
+           return concat_name;
+         XFREE (concat_name);
+       }
+#if defined (HAVE_DOS_BASED_FILE_SYSTEM)
+    }
+#endif
+
+  for (p = wrapper; *p; p++)
+    if (*p == '/')
+      {
+       has_slash = 1;
+       break;
+      }
+  if (!has_slash)
+    {
+      /* no slashes; search PATH */
+      const char *path = getenv ("PATH");
+      if (path != NULL)
+       {
+         for (p = path; *p; p = p_next)
+           {
+             const char *q;
+             size_t p_len;
+             for (q = p; *q; q++)
+               if (IS_PATH_SEPARATOR (*q))
+                 break;
+             p_len = q - p;
+             p_next = (*q == '\0' ? q : q + 1);
+             if (p_len == 0)
+               {
+                 /* empty path: current directory */
+                 if (getcwd (tmp, LT_PATHMAX) == NULL)
+                   lt_fatal (__FILE__, __LINE__, "getcwd failed: %s",
+                              nonnull (strerror (errno)));
+                 tmp_len = strlen (tmp);
+                 concat_name =
+                   XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1);
+                 memcpy (concat_name, tmp, tmp_len);
+                 concat_name[tmp_len] = '/';
+                 strcpy (concat_name + tmp_len + 1, wrapper);
+               }
+             else
+               {
+                 concat_name =
+                   XMALLOC (char, p_len + 1 + strlen (wrapper) + 1);
+                 memcpy (concat_name, p, p_len);
+                 concat_name[p_len] = '/';
+                 strcpy (concat_name + p_len + 1, wrapper);
+               }
+             if (check_executable (concat_name))
+               return concat_name;
+             XFREE (concat_name);
+           }
+       }
+      /* not found in PATH; assume curdir */
+    }
+  /* Relative path | not found in path: prepend cwd */
+  if (getcwd (tmp, LT_PATHMAX) == NULL)
+    lt_fatal (__FILE__, __LINE__, "getcwd failed: %s",
+              nonnull (strerror (errno)));
+  tmp_len = strlen (tmp);
+  concat_name = XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1);
+  memcpy (concat_name, tmp, tmp_len);
+  concat_name[tmp_len] = '/';
+  strcpy (concat_name + tmp_len + 1, wrapper);
+
+  if (check_executable (concat_name))
+    return concat_name;
+  XFREE (concat_name);
+  return NULL;
+}
+
+char *
+chase_symlinks (const char *pathspec)
+{
+#ifndef S_ISLNK
+  return xstrdup (pathspec);
+#else
+  char buf[LT_PATHMAX];
+  struct stat s;
+  char *tmp_pathspec = xstrdup (pathspec);
+  char *p;
+  int has_symlinks = 0;
+  while (strlen (tmp_pathspec) && !has_symlinks)
+    {
+      lt_debugprintf (__FILE__, __LINE__,
+                     "checking path component for symlinks: %s\n",
+                     tmp_pathspec);
+      if (lstat (tmp_pathspec, &s) == 0)
+       {
+         if (S_ISLNK (s.st_mode) != 0)
+           {
+             has_symlinks = 1;
+             break;
+           }
+
+         /* search backwards for last DIR_SEPARATOR */
+         p = tmp_pathspec + strlen (tmp_pathspec) - 1;
+         while ((p > tmp_pathspec) && (!IS_DIR_SEPARATOR (*p)))
+           p--;
+         if ((p == tmp_pathspec) && (!IS_DIR_SEPARATOR (*p)))
+           {
+             /* no more DIR_SEPARATORS left */
+             break;
+           }
+         *p = '\0';
+       }
+      else
+       {
+         lt_fatal (__FILE__, __LINE__,
+                   "error accessing file \"%s\": %s",
+                   tmp_pathspec, nonnull (strerror (errno)));
+       }
+    }
+  XFREE (tmp_pathspec);
+
+  if (!has_symlinks)
+    {
+      return xstrdup (pathspec);
+    }
+
+  tmp_pathspec = realpath (pathspec, buf);
+  if (tmp_pathspec == 0)
+    {
+      lt_fatal (__FILE__, __LINE__,
+               "could not follow symlinks for %s", pathspec);
+    }
+  return xstrdup (tmp_pathspec);
+#endif
+}
+
+char *
+strendzap (char *str, const char *pat)
+{
+  size_t len, patlen;
+
+  assert (str != NULL);
+  assert (pat != NULL);
+
+  len = strlen (str);
+  patlen = strlen (pat);
+
+  if (patlen <= len)
+    {
+      str += len - patlen;
+      if (strcmp (str, pat) == 0)
+       *str = '\0';
+    }
+  return str;
+}
+
+void
+lt_debugprintf (const char *file, int line, const char *fmt, ...)
+{
+  va_list args;
+  if (lt_debug)
+    {
+      (void) fprintf (stderr, "%s:%s:%d: ", program_name, file, line);
+      va_start (args, fmt);
+      (void) vfprintf (stderr, fmt, args);
+      va_end (args);
+    }
+}
+
+static void
+lt_error_core (int exit_status, const char *file,
+              int line, const char *mode,
+              const char *message, va_list ap)
+{
+  fprintf (stderr, "%s:%s:%d: %s: ", program_name, file, line, mode);
+  vfprintf (stderr, message, ap);
+  fprintf (stderr, ".\n");
+
+  if (exit_status >= 0)
+    exit (exit_status);
+}
+
+void
+lt_fatal (const char *file, int line, const char *message, ...)
+{
+  va_list ap;
+  va_start (ap, message);
+  lt_error_core (EXIT_FAILURE, file, line, "FATAL", message, ap);
+  va_end (ap);
+}
+
+static const char *
+nonnull (const char *s)
+{
+  return s ? s : "(null)";
+}
+
+static const char *
+nonempty (const char *s)
+{
+  return (s && !*s) ? "(empty)" : nonnull (s);
+}
+
+void
+lt_setenv (const char *name, const char *value)
+{
+  lt_debugprintf (__FILE__, __LINE__,
+                 "(lt_setenv) setting '%s' to '%s'\n",
+                  nonnull (name), nonnull (value));
+  {
+#ifdef HAVE_SETENV
+    /* always make a copy, for consistency with !HAVE_SETENV */
+    char *str = xstrdup (value);
+    setenv (name, str, 1);
+#else
+    int len = strlen (name) + 1 + strlen (value) + 1;
+    char *str = XMALLOC (char, len);
+    sprintf (str, "%s=%s", name, value);
+    if (putenv (str) != EXIT_SUCCESS)
+      {
+        XFREE (str);
+      }
+#endif
+  }
+}
+
+char *
+lt_extend_str (const char *orig_value, const char *add, int to_end)
+{
+  char *new_value;
+  if (orig_value && *orig_value)
+    {
+      int orig_value_len = strlen (orig_value);
+      int add_len = strlen (add);
+      new_value = XMALLOC (char, add_len + orig_value_len + 1);
+      if (to_end)
+        {
+          strcpy (new_value, orig_value);
+          strcpy (new_value + orig_value_len, add);
+        }
+      else
+        {
+          strcpy (new_value, add);
+          strcpy (new_value + add_len, orig_value);
+        }
+    }
+  else
+    {
+      new_value = xstrdup (add);
+    }
+  return new_value;
+}
+
+void
+lt_update_exe_path (const char *name, const char *value)
+{
+  lt_debugprintf (__FILE__, __LINE__,
+                 "(lt_update_exe_path) modifying '%s' by prepending '%s'\n",
+                  nonnull (name), nonnull (value));
+
+  if (name && *name && value && *value)
+    {
+      char *new_value = lt_extend_str (getenv (name), value, 0);
+      /* some systems can't cope with a ':'-terminated path #' */
+      int len = strlen (new_value);
+      while (((len = strlen (new_value)) > 0) && IS_PATH_SEPARATOR (new_value[len-1]))
+        {
+          new_value[len-1] = '\0';
+        }
+      lt_setenv (name, new_value);
+      XFREE (new_value);
+    }
+}
+
+void
+lt_update_lib_path (const char *name, const char *value)
+{
+  lt_debugprintf (__FILE__, __LINE__,
+                 "(lt_update_lib_path) modifying '%s' by prepending '%s'\n",
+                  nonnull (name), nonnull (value));
+
+  if (name && *name && value && *value)
+    {
+      char *new_value = lt_extend_str (getenv (name), value, 0);
+      lt_setenv (name, new_value);
+      XFREE (new_value);
+    }
+}
+
+EOF
+           case $host_os in
+             mingw*)
+               cat <<"EOF"
+
+/* Prepares an argument vector before calling spawn().
+   Note that spawn() does not by itself call the command interpreter
+     (getenv ("COMSPEC") != NULL ? getenv ("COMSPEC") :
+      ({ OSVERSIONINFO v; v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+         GetVersionEx(&v);
+         v.dwPlatformId == VER_PLATFORM_WIN32_NT;
+      }) ? "cmd.exe" : "command.com").
+   Instead it simply concatenates the arguments, separated by ' ', and calls
+   CreateProcess().  We must quote the arguments since Win32 CreateProcess()
+   interprets characters like ' ', '\t', '\\', '"' (but not '<' and '>') in a
+   special way:
+   - Space and tab are interpreted as delimiters. They are not treated as
+     delimiters if they are surrounded by double quotes: "...".
+   - Unescaped double quotes are removed from the input. Their only effect is
+     that within double quotes, space and tab are treated like normal
+     characters.
+   - Backslashes not followed by double quotes are not special.
+   - But 2*n+1 backslashes followed by a double quote become
+     n backslashes followed by a double quote (n >= 0):
+       \" -> "
+       \\\" -> \"
+       \\\\\" -> \\"
+ */
+#define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
+#define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
+char **
+prepare_spawn (char **argv)
+{
+  size_t argc;
+  char **new_argv;
+  size_t i;
+
+  /* Count number of arguments.  */
+  for (argc = 0; argv[argc] != NULL; argc++)
+    ;
+
+  /* Allocate new argument vector.  */
+  new_argv = XMALLOC (char *, argc + 1);
+
+  /* Put quoted arguments into the new argument vector.  */
+  for (i = 0; i < argc; i++)
+    {
+      const char *string = argv[i];
+
+      if (string[0] == '\0')
+       new_argv[i] = xstrdup ("\"\"");
+      else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL)
+       {
+         int quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL);
+         size_t length;
+         unsigned int backslashes;
+         const char *s;
+         char *quoted_string;
+         char *p;
+
+         length = 0;
+         backslashes = 0;
+         if (quote_around)
+           length++;
+         for (s = string; *s != '\0'; s++)
+           {
+             char c = *s;
+             if (c == '"')
+               length += backslashes + 1;
+             length++;
+             if (c == '\\')
+               backslashes++;
+             else
+               backslashes = 0;
+           }
+         if (quote_around)
+           length += backslashes + 1;
+
+         quoted_string = XMALLOC (char, length + 1);
+
+         p = quoted_string;
+         backslashes = 0;
+         if (quote_around)
+           *p++ = '"';
+         for (s = string; *s != '\0'; s++)
+           {
+             char c = *s;
+             if (c == '"')
+               {
+                 unsigned int j;
+                 for (j = backslashes + 1; j > 0; j--)
+                   *p++ = '\\';
+               }
+             *p++ = c;
+             if (c == '\\')
+               backslashes++;
+             else
+               backslashes = 0;
+           }
+         if (quote_around)
+           {
+             unsigned int j;
+             for (j = backslashes; j > 0; j--)
+               *p++ = '\\';
+             *p++ = '"';
+           }
+         *p = '\0';
+
+         new_argv[i] = quoted_string;
+       }
+      else
+       new_argv[i] = (char *) string;
+    }
+  new_argv[argc] = NULL;
+
+  return new_argv;
+}
+EOF
+               ;;
+           esac
+
+            cat <<"EOF"
+void lt_dump_script (FILE* f)
+{
+EOF
+           func_emit_wrapper yes |
+             $SED -n -e '
+s/^\(.\{79\}\)\(..*\)/\1\
+\2/
+h
+s/\([\\"]\)/\\\1/g
+s/$/\\n/
+s/\([^\n]*\).*/  fputs ("\1", f);/p
+g
+D'
+            cat <<"EOF"
+}
+EOF
+}
+# end: func_emit_cwrapperexe_src
+
+# func_win32_import_lib_p ARG
+# True if ARG is an import lib, as indicated by $file_magic_cmd
+func_win32_import_lib_p ()
+{
+    $opt_debug
+    case `eval $file_magic_cmd \"\$1\" 2>/dev/null | $SED -e 10q` in
+    *import*) : ;;
+    *) false ;;
+    esac
+}
+
+# func_mode_link arg...
+func_mode_link ()
+{
+    $opt_debug
+    case $host in
+    *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*)
+      # It is impossible to link a dll without this setting, and
+      # we shouldn't force the makefile maintainer to figure out
+      # which system we are compiling for in order to pass an extra
+      # flag for every libtool invocation.
+      # allow_undefined=no
+
+      # FIXME: Unfortunately, there are problems with the above when trying
+      # to make a dll which has undefined symbols, in which case not
+      # even a static library is built.  For now, we need to specify
+      # -no-undefined on the libtool link line when we can be certain
+      # that all symbols are satisfied, otherwise we get a static library.
+      allow_undefined=yes
+      ;;
+    *)
+      allow_undefined=yes
+      ;;
+    esac
+    libtool_args=$nonopt
+    base_compile="$nonopt $@"
+    compile_command=$nonopt
+    finalize_command=$nonopt
+
+    compile_rpath=
+    finalize_rpath=
+    compile_shlibpath=
+    finalize_shlibpath=
+    convenience=
+    old_convenience=
+    deplibs=
+    old_deplibs=
+    compiler_flags=
+    linker_flags=
+    dllsearchpath=
+    lib_search_path=`pwd`
+    inst_prefix_dir=
+    new_inherited_linker_flags=
+
+    avoid_version=no
+    bindir=
+    dlfiles=
+    dlprefiles=
+    dlself=no
+    export_dynamic=no
+    export_symbols=
+    export_symbols_regex=
+    generated=
+    libobjs=
+    ltlibs=
+    module=no
+    no_install=no
+    objs=
+    non_pic_objects=
+    precious_files_regex=
+    prefer_static_libs=no
+    preload=no
+    prev=
+    prevarg=
+    release=
+    rpath=
+    xrpath=
+    perm_rpath=
+    temp_rpath=
+    thread_safe=no
+    vinfo=
+    vinfo_number=no
+    weak_libs=
+    single_module="${wl}-single_module"
+    func_infer_tag $base_compile
+
+    # We need to know -static, to get the right output filenames.
+    for arg
+    do
+      case $arg in
+      -shared)
+       test "$build_libtool_libs" != yes && \
+         func_fatal_configuration "can not build a shared library"
+       build_old_libs=no
+       break
+       ;;
+      -all-static | -static | -static-libtool-libs)
+       case $arg in
+       -all-static)
+         if test "$build_libtool_libs" = yes && test -z "$link_static_flag"; then
+           func_warning "complete static linking is impossible in this configuration"
+         fi
+         if test -n "$link_static_flag"; then
+           dlopen_self=$dlopen_self_static
+         fi
+         prefer_static_libs=yes
+         ;;
+       -static)
+         if test -z "$pic_flag" && test -n "$link_static_flag"; then
+           dlopen_self=$dlopen_self_static
+         fi
+         prefer_static_libs=built
+         ;;
+       -static-libtool-libs)
+         if test -z "$pic_flag" && test -n "$link_static_flag"; then
+           dlopen_self=$dlopen_self_static
+         fi
+         prefer_static_libs=yes
+         ;;
+       esac
+       build_libtool_libs=no
+       build_old_libs=yes
+       break
+       ;;
+      esac
+    done
+
+    # See if our shared archives depend on static archives.
+    test -n "$old_archive_from_new_cmds" && build_old_libs=yes
+
+    # Go through the arguments, transforming them on the way.
+    while test "$#" -gt 0; do
+      arg="$1"
+      shift
+      func_quote_for_eval "$arg"
+      qarg=$func_quote_for_eval_unquoted_result
+      func_append libtool_args " $func_quote_for_eval_result"
+
+      # If the previous option needs an argument, assign it.
+      if test -n "$prev"; then
+       case $prev in
+       output)
+         func_append compile_command " @OUTPUT@"
+         func_append finalize_command " @OUTPUT@"
+         ;;
+       esac
+
+       case $prev in
+       bindir)
+         bindir="$arg"
+         prev=
+         continue
+         ;;
+       dlfiles|dlprefiles)
+         if test "$preload" = no; then
+           # Add the symbol object into the linking commands.
+           func_append compile_command " @SYMFILE@"
+           func_append finalize_command " @SYMFILE@"
+           preload=yes
+         fi
+         case $arg in
+         *.la | *.lo) ;;  # We handle these cases below.
+         force)
+           if test "$dlself" = no; then
+             dlself=needless
+             export_dynamic=yes
+           fi
+           prev=
+           continue
+           ;;
+         self)
+           if test "$prev" = dlprefiles; then
+             dlself=yes
+           elif test "$prev" = dlfiles && test "$dlopen_self" != yes; then
+             dlself=yes
+           else
+             dlself=needless
+             export_dynamic=yes
+           fi
+           prev=
+           continue
+           ;;
+         *)
+           if test "$prev" = dlfiles; then
+             func_append dlfiles " $arg"
+           else
+             func_append dlprefiles " $arg"
+           fi
+           prev=
+           continue
+           ;;
+         esac
+         ;;
+       expsyms)
+         export_symbols="$arg"
+         test -f "$arg" \
+           || func_fatal_error "symbol file \`$arg' does not exist"
+         prev=
+         continue
+         ;;
+       expsyms_regex)
+         export_symbols_regex="$arg"
+         prev=
+         continue
+         ;;
+       framework)
+         case $host in
+           *-*-darwin*)
+             case "$deplibs " in
+               *" $qarg.ltframework "*) ;;
+               *) func_append deplibs " $qarg.ltframework" # this is fixed later
+                  ;;
+             esac
+             ;;
+         esac
+         prev=
+         continue
+         ;;
+       inst_prefix)
+         inst_prefix_dir="$arg"
+         prev=
+         continue
+         ;;
+       objectlist)
+         if test -f "$arg"; then
+           save_arg=$arg
+           moreargs=
+           for fil in `cat "$save_arg"`
+           do
+#            func_append moreargs " $fil"
+             arg=$fil
+             # A libtool-controlled object.
+
+             # Check to see that this really is a libtool object.
+             if func_lalib_unsafe_p "$arg"; then
+               pic_object=
+               non_pic_object=
+
+               # Read the .lo file
+               func_source "$arg"
+
+               if test -z "$pic_object" ||
+                  test -z "$non_pic_object" ||
+                  test "$pic_object" = none &&
+                  test "$non_pic_object" = none; then
+                 func_fatal_error "cannot find name of object for \`$arg'"
+               fi
+
+               # Extract subdirectory from the argument.
+               func_dirname "$arg" "/" ""
+               xdir="$func_dirname_result"
+
+               if test "$pic_object" != none; then
+                 # Prepend the subdirectory the object is found in.
+                 pic_object="$xdir$pic_object"
+
+                 if test "$prev" = dlfiles; then
+                   if test "$build_libtool_libs" = yes && test "$dlopen_support" = yes; then
+                     func_append dlfiles " $pic_object"
+                     prev=
+                     continue
+                   else
+                     # If libtool objects are unsupported, then we need to preload.
+                     prev=dlprefiles
+                   fi
+                 fi
+
+                 # CHECK ME:  I think I busted this.  -Ossama
+                 if test "$prev" = dlprefiles; then
+                   # Preload the old-style object.
+                   func_append dlprefiles " $pic_object"
+                   prev=
+                 fi
+
+                 # A PIC object.
+                 func_append libobjs " $pic_object"
+                 arg="$pic_object"
+               fi
+
+               # Non-PIC object.
+               if test "$non_pic_object" != none; then
+                 # Prepend the subdirectory the object is found in.
+                 non_pic_object="$xdir$non_pic_object"
+
+                 # A standard non-PIC object
+                 func_append non_pic_objects " $non_pic_object"
+                 if test -z "$pic_object" || test "$pic_object" = none ; then
+                   arg="$non_pic_object"
+                 fi
+               else
+                 # If the PIC object exists, use it instead.
+                 # $xdir was prepended to $pic_object above.
+                 non_pic_object="$pic_object"
+                 func_append non_pic_objects " $non_pic_object"
+               fi
+             else
+               # Only an error if not doing a dry-run.
+               if $opt_dry_run; then
+                 # Extract subdirectory from the argument.
+                 func_dirname "$arg" "/" ""
+                 xdir="$func_dirname_result"
+
+                 func_lo2o "$arg"
+                 pic_object=$xdir$objdir/$func_lo2o_result
+                 non_pic_object=$xdir$func_lo2o_result
+                 func_append libobjs " $pic_object"
+                 func_append non_pic_objects " $non_pic_object"
+               else
+                 func_fatal_error "\`$arg' is not a valid libtool object"
+               fi
+             fi
+           done
+         else
+           func_fatal_error "link input file \`$arg' does not exist"
+         fi
+         arg=$save_arg
+         prev=
+         continue
+         ;;
+       precious_regex)
+         precious_files_regex="$arg"
+         prev=
+         continue
+         ;;
+       release)
+         release="-$arg"
+         prev=
+         continue
+         ;;
+       rpath | xrpath)
+         # We need an absolute path.
+         case $arg in
+         [\\/]* | [A-Za-z]:[\\/]*) ;;
+         *)
+           func_fatal_error "only absolute run-paths are allowed"
+           ;;
+         esac
+         if test "$prev" = rpath; then
+           case "$rpath " in
+           *" $arg "*) ;;
+           *) func_append rpath " $arg" ;;
+           esac
+         else
+           case "$xrpath " in
+           *" $arg "*) ;;
+           *) func_append xrpath " $arg" ;;
+           esac
+         fi
+         prev=
+         continue
+         ;;
+       shrext)
+         shrext_cmds="$arg"
+         prev=
+         continue
+         ;;
+       weak)
+         func_append weak_libs " $arg"
+         prev=
+         continue
+         ;;
+       xcclinker)
+         func_append linker_flags " $qarg"
+         func_append compiler_flags " $qarg"
+         prev=
+         func_append compile_command " $qarg"
+         func_append finalize_command " $qarg"
+         continue
+         ;;
+       xcompiler)
+         func_append compiler_flags " $qarg"
+         prev=
+         func_append compile_command " $qarg"
+         func_append finalize_command " $qarg"
+         continue
+         ;;
+       xlinker)
+         func_append linker_flags " $qarg"
+         func_append compiler_flags " $wl$qarg"
+         prev=
+         func_append compile_command " $wl$qarg"
+         func_append finalize_command " $wl$qarg"
+         continue
+         ;;
+       *)
+         eval "$prev=\"\$arg\""
+         prev=
+         continue
+         ;;
+       esac
+      fi # test -n "$prev"
+
+      prevarg="$arg"
+
+      case $arg in
+      -all-static)
+       if test -n "$link_static_flag"; then
+         # See comment for -static flag below, for more details.
+         func_append compile_command " $link_static_flag"
+         func_append finalize_command " $link_static_flag"
+       fi
+       continue
+       ;;
+
+      -allow-undefined)
+       # FIXME: remove this flag sometime in the future.
+       func_fatal_error "\`-allow-undefined' must not be used because it is the default"
+       ;;
+
+      -avoid-version)
+       avoid_version=yes
+       continue
+       ;;
+
+      -bindir)
+       prev=bindir
+       continue
+       ;;
+
+      -dlopen)
+       prev=dlfiles
+       continue
+       ;;
+
+      -dlpreopen)
+       prev=dlprefiles
+       continue
+       ;;
+
+      -export-dynamic)
+       export_dynamic=yes
+       continue
+       ;;
+
+      -export-symbols | -export-symbols-regex)
+       if test -n "$export_symbols" || test -n "$export_symbols_regex"; then
+         func_fatal_error "more than one -exported-symbols argument is not allowed"
+       fi
+       if test "X$arg" = "X-export-symbols"; then
+         prev=expsyms
+       else
+         prev=expsyms_regex
+       fi
+       continue
+       ;;
+
+      -framework)
+       prev=framework
+       continue
+       ;;
+
+      -inst-prefix-dir)
+       prev=inst_prefix
+       continue
+       ;;
+
+      # The native IRIX linker understands -LANG:*, -LIST:* and -LNO:*
+      # so, if we see these flags be careful not to treat them like -L
+      -L[A-Z][A-Z]*:*)
+       case $with_gcc/$host in
+       no/*-*-irix* | /*-*-irix*)
+         func_append compile_command " $arg"
+         func_append finalize_command " $arg"
+         ;;
+       esac
+       continue
+       ;;
+
+      -L*)
+       func_stripname "-L" '' "$arg"
+       if test -z "$func_stripname_result"; then
+         if test "$#" -gt 0; then
+           func_fatal_error "require no space between \`-L' and \`$1'"
+         else
+           func_fatal_error "need path for \`-L' option"
+         fi
+       fi
+       func_resolve_sysroot "$func_stripname_result"
+       dir=$func_resolve_sysroot_result
+       # We need an absolute path.
+       case $dir in
+       [\\/]* | [A-Za-z]:[\\/]*) ;;
+       *)
+         absdir=`cd "$dir" && pwd`
+         test -z "$absdir" && \
+           func_fatal_error "cannot determine absolute directory name of \`$dir'"
+         dir="$absdir"
+         ;;
+       esac
+       case "$deplibs " in
+       *" -L$dir "* | *" $arg "*)
+         # Will only happen for absolute or sysroot arguments
+         ;;
+       *)
+         # Preserve sysroot, but never include relative directories
+         case $dir in
+           [\\/]* | [A-Za-z]:[\\/]* | =*) func_append deplibs " $arg" ;;
+           *) func_append deplibs " -L$dir" ;;
+         esac
+         func_append lib_search_path " $dir"
+         ;;
+       esac
+       case $host in
+       *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*)
+         testbindir=`$ECHO "$dir" | $SED 's*/lib$*/bin*'`
+         case :$dllsearchpath: in
+         *":$dir:"*) ;;
+         ::) dllsearchpath=$dir;;
+         *) func_append dllsearchpath ":$dir";;
+         esac
+         case :$dllsearchpath: in
+         *":$testbindir:"*) ;;
+         ::) dllsearchpath=$testbindir;;
+         *) func_append dllsearchpath ":$testbindir";;
+         esac
+         ;;
+       esac
+       continue
+       ;;
+
+      -l*)
+       if test "X$arg" = "X-lc" || test "X$arg" = "X-lm"; then
+         case $host in
+         *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-beos* | *-cegcc* | *-*-haiku*)
+           # These systems don't actually have a C or math library (as such)
+           continue
+           ;;
+         *-*-os2*)
+           # These systems don't actually have a C library (as such)
+           test "X$arg" = "X-lc" && continue
+           ;;
+         *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*)
+           # Do not include libc due to us having libc/libc_r.
+           test "X$arg" = "X-lc" && continue
+           ;;
+         *-*-rhapsody* | *-*-darwin1.[012])
+           # Rhapsody C and math libraries are in the System framework
+           func_append deplibs " System.ltframework"
+           continue
+           ;;
+         *-*-sco3.2v5* | *-*-sco5v6*)
+           # Causes problems with __ctype
+           test "X$arg" = "X-lc" && continue
+           ;;
+         *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*)
+           # Compiler inserts libc in the correct place for threads to work
+           test "X$arg" = "X-lc" && continue
+           ;;
+         esac
+       elif test "X$arg" = "X-lc_r"; then
+        case $host in
+        *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*)
+          # Do not include libc_r directly, use -pthread flag.
+          continue
+          ;;
+        esac
+       fi
+       func_append deplibs " $arg"
+       continue
+       ;;
+
+      -module)
+       module=yes
+       continue
+       ;;
+
+      # Tru64 UNIX uses -model [arg] to determine the layout of C++
+      # classes, name mangling, and exception handling.
+      # Darwin uses the -arch flag to determine output architecture.
+      -model|-arch|-isysroot|--sysroot)
+       func_append compiler_flags " $arg"
+       func_append compile_command " $arg"
+       func_append finalize_command " $arg"
+       prev=xcompiler
+       continue
+       ;;
+
+      -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \
+      |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*)
+       func_append compiler_flags " $arg"
+       func_append compile_command " $arg"
+       func_append finalize_command " $arg"
+       case "$new_inherited_linker_flags " in
+           *" $arg "*) ;;
+           * ) func_append new_inherited_linker_flags " $arg" ;;
+       esac
+       continue
+       ;;
+
+      -multi_module)
+       single_module="${wl}-multi_module"
+       continue
+       ;;
+
+      -no-fast-install)
+       fast_install=no
+       continue
+       ;;
+
+      -no-install)
+       case $host in
+       *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-darwin* | *-cegcc*)
+         # The PATH hackery in wrapper scripts is required on Windows
+         # and Darwin in order for the loader to find any dlls it needs.
+         func_warning "\`-no-install' is ignored for $host"
+         func_warning "assuming \`-no-fast-install' instead"
+         fast_install=no
+         ;;
+       *) no_install=yes ;;
+       esac
+       continue
+       ;;
+
+      -no-undefined)
+       allow_undefined=no
+       continue
+       ;;
+
+      -objectlist)
+       prev=objectlist
+       continue
+       ;;
+
+      -o) prev=output ;;
+
+      -precious-files-regex)
+       prev=precious_regex
+       continue
+       ;;
+
+      -release)
+       prev=release
+       continue
+       ;;
+
+      -rpath)
+       prev=rpath
+       continue
+       ;;
+
+      -R)
+       prev=xrpath
+       continue
+       ;;
+
+      -R*)
+       func_stripname '-R' '' "$arg"
+       dir=$func_stripname_result
+       # We need an absolute path.
+       case $dir in
+       [\\/]* | [A-Za-z]:[\\/]*) ;;
+       =*)
+         func_stripname '=' '' "$dir"
+         dir=$lt_sysroot$func_stripname_result
+         ;;
+       *)
+         func_fatal_error "only absolute run-paths are allowed"
+         ;;
+       esac
+       case "$xrpath " in
+       *" $dir "*) ;;
+       *) func_append xrpath " $dir" ;;
+       esac
+       continue
+       ;;
+
+      -shared)
+       # The effects of -shared are defined in a previous loop.
+       continue
+       ;;
+
+      -shrext)
+       prev=shrext
+       continue
+       ;;
+
+      -static | -static-libtool-libs)
+       # The effects of -static are defined in a previous loop.
+       # We used to do the same as -all-static on platforms that
+       # didn't have a PIC flag, but the assumption that the effects
+       # would be equivalent was wrong.  It would break on at least
+       # Digital Unix and AIX.
+       continue
+       ;;
+
+      -thread-safe)
+       thread_safe=yes
+       continue
+       ;;
+
+      -version-info)
+       prev=vinfo
+       continue
+       ;;
+
+      -version-number)
+       prev=vinfo
+       vinfo_number=yes
+       continue
+       ;;
+
+      -weak)
+        prev=weak
+       continue
+       ;;
+
+      -Wc,*)
+       func_stripname '-Wc,' '' "$arg"
+       args=$func_stripname_result
+       arg=
+       save_ifs="$IFS"; IFS=','
+       for flag in $args; do
+         IFS="$save_ifs"
+          func_quote_for_eval "$flag"
+         func_append arg " $func_quote_for_eval_result"
+         func_append compiler_flags " $func_quote_for_eval_result"
+       done
+       IFS="$save_ifs"
+       func_stripname ' ' '' "$arg"
+       arg=$func_stripname_result
+       ;;
+
+      -Wl,*)
+       func_stripname '-Wl,' '' "$arg"
+       args=$func_stripname_result
+       arg=
+       save_ifs="$IFS"; IFS=','
+       for flag in $args; do
+         IFS="$save_ifs"
+          func_quote_for_eval "$flag"
+         func_append arg " $wl$func_quote_for_eval_result"
+         func_append compiler_flags " $wl$func_quote_for_eval_result"
+         func_append linker_flags " $func_quote_for_eval_result"
+       done
+       IFS="$save_ifs"
+       func_stripname ' ' '' "$arg"
+       arg=$func_stripname_result
+       ;;
+
+      -Xcompiler)
+       prev=xcompiler
+       continue
+       ;;
+
+      -Xlinker)
+       prev=xlinker
+       continue
+       ;;
+
+      -XCClinker)
+       prev=xcclinker
+       continue
+       ;;
+
+      # -msg_* for osf cc
+      -msg_*)
+       func_quote_for_eval "$arg"
+       arg="$func_quote_for_eval_result"
+       ;;
+
+      # Flags to be passed through unchanged, with rationale:
+      # -64, -mips[0-9]      enable 64-bit mode for the SGI compiler
+      # -r[0-9][0-9]*        specify processor for the SGI compiler
+      # -xarch=*, -xtarget=* enable 64-bit mode for the Sun compiler
+      # +DA*, +DD*           enable 64-bit mode for the HP compiler
+      # -q*                  compiler args for the IBM compiler
+      # -m*, -t[45]*, -txscale* architecture-specific flags for GCC
+      # -F/path              path to uninstalled frameworks, gcc on darwin
+      # -p, -pg, --coverage, -fprofile-*  profiling flags for GCC
+      # @file                GCC response files
+      # -tp=*                Portland pgcc target processor selection
+      # --sysroot=*          for sysroot support
+      # -O*, -flto*, -fwhopr*, -fuse-linker-plugin GCC link-time optimization
+      -64|-mips[0-9]|-r[0-9][0-9]*|-xarch=*|-xtarget=*|+DA*|+DD*|-q*|-m*| \
+      -t[45]*|-txscale*|-p|-pg|--coverage|-fprofile-*|-F*|@*|-tp=*|--sysroot=*| \
+      -O*|-flto*|-fwhopr*|-fuse-linker-plugin)
+        func_quote_for_eval "$arg"
+       arg="$func_quote_for_eval_result"
+        func_append compile_command " $arg"
+        func_append finalize_command " $arg"
+        func_append compiler_flags " $arg"
+        continue
+        ;;
+
+      # Some other compiler flag.
+      -* | +*)
+        func_quote_for_eval "$arg"
+       arg="$func_quote_for_eval_result"
+       ;;
+
+      *.$objext)
+       # A standard object.
+       func_append objs " $arg"
+       ;;
+
+      *.lo)
+       # A libtool-controlled object.
+
+       # Check to see that this really is a libtool object.
+       if func_lalib_unsafe_p "$arg"; then
+         pic_object=
+         non_pic_object=
+
+         # Read the .lo file
+         func_source "$arg"
+
+         if test -z "$pic_object" ||
+            test -z "$non_pic_object" ||
+            test "$pic_object" = none &&
+            test "$non_pic_object" = none; then
+           func_fatal_error "cannot find name of object for \`$arg'"
+         fi
+
+         # Extract subdirectory from the argument.
+         func_dirname "$arg" "/" ""
+         xdir="$func_dirname_result"
+
+         if test "$pic_object" != none; then
+           # Prepend the subdirectory the object is found in.
+           pic_object="$xdir$pic_object"
+
+           if test "$prev" = dlfiles; then
+             if test "$build_libtool_libs" = yes && test "$dlopen_support" = yes; then
+               func_append dlfiles " $pic_object"
+               prev=
+               continue
+             else
+               # If libtool objects are unsupported, then we need to preload.
+               prev=dlprefiles
+             fi
+           fi
+
+           # CHECK ME:  I think I busted this.  -Ossama
+           if test "$prev" = dlprefiles; then
+             # Preload the old-style object.
+             func_append dlprefiles " $pic_object"
+             prev=
+           fi
+
+           # A PIC object.
+           func_append libobjs " $pic_object"
+           arg="$pic_object"
+         fi
+
+         # Non-PIC object.
+         if test "$non_pic_object" != none; then
+           # Prepend the subdirectory the object is found in.
+           non_pic_object="$xdir$non_pic_object"
+
+           # A standard non-PIC object
+           func_append non_pic_objects " $non_pic_object"
+           if test -z "$pic_object" || test "$pic_object" = none ; then
+             arg="$non_pic_object"
+           fi
+         else
+           # If the PIC object exists, use it instead.
+           # $xdir was prepended to $pic_object above.
+           non_pic_object="$pic_object"
+           func_append non_pic_objects " $non_pic_object"
+         fi
+       else
+         # Only an error if not doing a dry-run.
+         if $opt_dry_run; then
+           # Extract subdirectory from the argument.
+           func_dirname "$arg" "/" ""
+           xdir="$func_dirname_result"
+
+           func_lo2o "$arg"
+           pic_object=$xdir$objdir/$func_lo2o_result
+           non_pic_object=$xdir$func_lo2o_result
+           func_append libobjs " $pic_object"
+           func_append non_pic_objects " $non_pic_object"
+         else
+           func_fatal_error "\`$arg' is not a valid libtool object"
+         fi
+       fi
+       ;;
+
+      *.$libext)
+       # An archive.
+       func_append deplibs " $arg"
+       func_append old_deplibs " $arg"
+       continue
+       ;;
+
+      *.la)
+       # A libtool-controlled library.
+
+       func_resolve_sysroot "$arg"
+       if test "$prev" = dlfiles; then
+         # This library was specified with -dlopen.
+         func_append dlfiles " $func_resolve_sysroot_result"
+         prev=
+       elif test "$prev" = dlprefiles; then
+         # The library was specified with -dlpreopen.
+         func_append dlprefiles " $func_resolve_sysroot_result"
+         prev=
+       else
+         func_append deplibs " $func_resolve_sysroot_result"
+       fi
+       continue
+       ;;
+
+      # Some other compiler argument.
+      *)
+       # Unknown arguments in both finalize_command and compile_command need
+       # to be aesthetically quoted because they are evaled later.
+       func_quote_for_eval "$arg"
+       arg="$func_quote_for_eval_result"
+       ;;
+      esac # arg
+
+      # Now actually substitute the argument into the commands.
+      if test -n "$arg"; then
+       func_append compile_command " $arg"
+       func_append finalize_command " $arg"
+      fi
+    done # argument parsing loop
+
+    test -n "$prev" && \
+      func_fatal_help "the \`$prevarg' option requires an argument"
+
+    if test "$export_dynamic" = yes && test -n "$export_dynamic_flag_spec"; then
+      eval arg=\"$export_dynamic_flag_spec\"
+      func_append compile_command " $arg"
+      func_append finalize_command " $arg"
+    fi
+
+    oldlibs=
+    # calculate the name of the file, without its directory
+    func_basename "$output"
+    outputname="$func_basename_result"
+    libobjs_save="$libobjs"
+
+    if test -n "$shlibpath_var"; then
+      # get the directories listed in $shlibpath_var
+      eval shlib_search_path=\`\$ECHO \"\${$shlibpath_var}\" \| \$SED \'s/:/ /g\'\`
+    else
+      shlib_search_path=
+    fi
+    eval sys_lib_search_path=\"$sys_lib_search_path_spec\"
+    eval sys_lib_dlsearch_path=\"$sys_lib_dlsearch_path_spec\"
+
+    func_dirname "$output" "/" ""
+    output_objdir="$func_dirname_result$objdir"
+    func_to_tool_file "$output_objdir/"
+    tool_output_objdir=$func_to_tool_file_result
+    # Create the object directory.
+    func_mkdir_p "$output_objdir"
+
+    # Determine the type of output
+    case $output in
+    "")
+      func_fatal_help "you must specify an output file"
+      ;;
+    *.$libext) linkmode=oldlib ;;
+    *.lo | *.$objext) linkmode=obj ;;
+    *.la) linkmode=lib ;;
+    *) linkmode=prog ;; # Anything else should be a program.
+    esac
+
+    specialdeplibs=
+
+    libs=
+    # Find all interdependent deplibs by searching for libraries
+    # that are linked more than once (e.g. -la -lb -la)
+    for deplib in $deplibs; do
+      if $opt_preserve_dup_deps ; then
+       case "$libs " in
+       *" $deplib "*) func_append specialdeplibs " $deplib" ;;
+       esac
+      fi
+      func_append libs " $deplib"
+    done
+
+    if test "$linkmode" = lib; then
+      libs="$predeps $libs $compiler_lib_search_path $postdeps"
+
+      # Compute libraries that are listed more than once in $predeps
+      # $postdeps and mark them as special (i.e., whose duplicates are
+      # not to be eliminated).
+      pre_post_deps=
+      if $opt_duplicate_compiler_generated_deps; then
+       for pre_post_dep in $predeps $postdeps; do
+         case "$pre_post_deps " in
+         *" $pre_post_dep "*) func_append specialdeplibs " $pre_post_deps" ;;
+         esac
+         func_append pre_post_deps " $pre_post_dep"
+       done
+      fi
+      pre_post_deps=
+    fi
+
+    deplibs=
+    newdependency_libs=
+    newlib_search_path=
+    need_relink=no # whether we're linking any uninstalled libtool libraries
+    notinst_deplibs= # not-installed libtool libraries
+    notinst_path= # paths that contain not-installed libtool libraries
+
+    case $linkmode in
+    lib)
+       passes="conv dlpreopen link"
+       for file in $dlfiles $dlprefiles; do
+         case $file in
+         *.la) ;;
+         *)
+           func_fatal_help "libraries can \`-dlopen' only libtool libraries: $file"
+           ;;
+         esac
+       done
+       ;;
+    prog)
+       compile_deplibs=
+       finalize_deplibs=
+       alldeplibs=no
+       newdlfiles=
+       newdlprefiles=
+       passes="conv scan dlopen dlpreopen link"
+       ;;
+    *)  passes="conv"
+       ;;
+    esac
+
+    for pass in $passes; do
+      # The preopen pass in lib mode reverses $deplibs; put it back here
+      # so that -L comes before libs that need it for instance...
+      if test "$linkmode,$pass" = "lib,link"; then
+       ## FIXME: Find the place where the list is rebuilt in the wrong
+       ##        order, and fix it there properly
+        tmp_deplibs=
+       for deplib in $deplibs; do
+         tmp_deplibs="$deplib $tmp_deplibs"
+       done
+       deplibs="$tmp_deplibs"
+      fi
+
+      if test "$linkmode,$pass" = "lib,link" ||
+        test "$linkmode,$pass" = "prog,scan"; then
+       libs="$deplibs"
+       deplibs=
+      fi
+      if test "$linkmode" = prog; then
+       case $pass in
+       dlopen) libs="$dlfiles" ;;
+       dlpreopen) libs="$dlprefiles" ;;
+       link)
+         libs="$deplibs %DEPLIBS%"
+         test "X$link_all_deplibs" != Xno && libs="$libs $dependency_libs"
+         ;;
+       esac
+      fi
+      if test "$linkmode,$pass" = "lib,dlpreopen"; then
+       # Collect and forward deplibs of preopened libtool libs
+       for lib in $dlprefiles; do
+         # Ignore non-libtool-libs
+         dependency_libs=
+         func_resolve_sysroot "$lib"
+         case $lib in
+         *.la) func_source "$func_resolve_sysroot_result" ;;
+         esac
+
+         # Collect preopened libtool deplibs, except any this library
+         # has declared as weak libs
+         for deplib in $dependency_libs; do
+           func_basename "$deplib"
+            deplib_base=$func_basename_result
+           case " $weak_libs " in
+           *" $deplib_base "*) ;;
+           *) func_append deplibs " $deplib" ;;
+           esac
+         done
+       done
+       libs="$dlprefiles"
+      fi
+      if test "$pass" = dlopen; then
+       # Collect dlpreopened libraries
+       save_deplibs="$deplibs"
+       deplibs=
+      fi
+
+      for deplib in $libs; do
+       lib=
+       found=no
+       case $deplib in
+       -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \
+        |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*)
+         if test "$linkmode,$pass" = "prog,link"; then
+           compile_deplibs="$deplib $compile_deplibs"
+           finalize_deplibs="$deplib $finalize_deplibs"
+         else
+           func_append compiler_flags " $deplib"
+           if test "$linkmode" = lib ; then
+               case "$new_inherited_linker_flags " in
+                   *" $deplib "*) ;;
+                   * ) func_append new_inherited_linker_flags " $deplib" ;;
+               esac
+           fi
+         fi
+         continue
+         ;;
+       -l*)
+         if test "$linkmode" != lib && test "$linkmode" != prog; then
+           func_warning "\`-l' is ignored for archives/objects"
+           continue
+         fi
+         func_stripname '-l' '' "$deplib"
+         name=$func_stripname_result
+         if test "$linkmode" = lib; then
+           searchdirs="$newlib_search_path $lib_search_path $compiler_lib_search_dirs $sys_lib_search_path $shlib_search_path"
+         else
+           searchdirs="$newlib_search_path $lib_search_path $sys_lib_search_path $shlib_search_path"
+         fi
+         for searchdir in $searchdirs; do
+           for search_ext in .la $std_shrext .so .a; do
+             # Search the libtool library
+             lib="$searchdir/lib${name}${search_ext}"
+             if test -f "$lib"; then
+               if test "$search_ext" = ".la"; then
+                 found=yes
+               else
+                 found=no
+               fi
+               break 2
+             fi
+           done
+         done
+         if test "$found" != yes; then
+           # deplib doesn't seem to be a libtool library
+           if test "$linkmode,$pass" = "prog,link"; then
+             compile_deplibs="$deplib $compile_deplibs"
+             finalize_deplibs="$deplib $finalize_deplibs"
+           else
+             deplibs="$deplib $deplibs"
+             test "$linkmode" = lib && newdependency_libs="$deplib $newdependency_libs"
+           fi
+           continue
+         else # deplib is a libtool library
+           # If $allow_libtool_libs_with_static_runtimes && $deplib is a stdlib,
+           # We need to do some special things here, and not later.
+           if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then
+             case " $predeps $postdeps " in
+             *" $deplib "*)
+               if func_lalib_p "$lib"; then
+                 library_names=
+                 old_library=
+                 func_source "$lib"
+                 for l in $old_library $library_names; do
+                   ll="$l"
+                 done
+                 if test "X$ll" = "X$old_library" ; then # only static version available
+                   found=no
+                   func_dirname "$lib" "" "."
+                   ladir="$func_dirname_result"
+                   lib=$ladir/$old_library
+                   if test "$linkmode,$pass" = "prog,link"; then
+                     compile_deplibs="$deplib $compile_deplibs"
+                     finalize_deplibs="$deplib $finalize_deplibs"
+                   else
+                     deplibs="$deplib $deplibs"
+                     test "$linkmode" = lib && newdependency_libs="$deplib $newdependency_libs"
+                   fi
+                   continue
+                 fi
+               fi
+               ;;
+             *) ;;
+             esac
+           fi
+         fi
+         ;; # -l
+       *.ltframework)
+         if test "$linkmode,$pass" = "prog,link"; then
+           compile_deplibs="$deplib $compile_deplibs"
+           finalize_deplibs="$deplib $finalize_deplibs"
+         else
+           deplibs="$deplib $deplibs"
+           if test "$linkmode" = lib ; then
+               case "$new_inherited_linker_flags " in
+                   *" $deplib "*) ;;
+                   * ) func_append new_inherited_linker_flags " $deplib" ;;
+               esac
+           fi
+         fi
+         continue
+         ;;
+       -L*)
+         case $linkmode in
+         lib)
+           deplibs="$deplib $deplibs"
+           test "$pass" = conv && continue
+           newdependency_libs="$deplib $newdependency_libs"
+           func_stripname '-L' '' "$deplib"
+           func_resolve_sysroot "$func_stripname_result"
+           func_append newlib_search_path " $func_resolve_sysroot_result"
+           ;;
+         prog)
+           if test "$pass" = conv; then
+             deplibs="$deplib $deplibs"
+             continue
+           fi
+           if test "$pass" = scan; then
+             deplibs="$deplib $deplibs"
+           else
+             compile_deplibs="$deplib $compile_deplibs"
+             finalize_deplibs="$deplib $finalize_deplibs"
+           fi
+           func_stripname '-L' '' "$deplib"
+           func_resolve_sysroot "$func_stripname_result"
+           func_append newlib_search_path " $func_resolve_sysroot_result"
+           ;;
+         *)
+           func_warning "\`-L' is ignored for archives/objects"
+           ;;
+         esac # linkmode
+         continue
+         ;; # -L
+       -R*)
+         if test "$pass" = link; then
+           func_stripname '-R' '' "$deplib"
+           func_resolve_sysroot "$func_stripname_result"
+           dir=$func_resolve_sysroot_result
+           # Make sure the xrpath contains only unique directories.
+           case "$xrpath " in
+           *" $dir "*) ;;
+           *) func_append xrpath " $dir" ;;
+           esac
+         fi
+         deplibs="$deplib $deplibs"
+         continue
+         ;;
+       *.la)
+         func_resolve_sysroot "$deplib"
+         lib=$func_resolve_sysroot_result
+         ;;
+       *.$libext)
+         if test "$pass" = conv; then
+           deplibs="$deplib $deplibs"
+           continue
+         fi
+         case $linkmode in
+         lib)
+           # Linking convenience modules into shared libraries is allowed,
+           # but linking other static libraries is non-portable.
+           case " $dlpreconveniencelibs " in
+           *" $deplib "*) ;;
+           *)
+             valid_a_lib=no
+             case $deplibs_check_method in
+               match_pattern*)
+                 set dummy $deplibs_check_method; shift
+                 match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"`
+                 if eval "\$ECHO \"$deplib\"" 2>/dev/null | $SED 10q \
+                   | $EGREP "$match_pattern_regex" > /dev/null; then
+                   valid_a_lib=yes
+                 fi
+               ;;
+               pass_all)
+                 valid_a_lib=yes
+               ;;
+             esac
+             if test "$valid_a_lib" != yes; then
+               echo
+               $ECHO "*** Warning: Trying to link with static lib archive $deplib."
+               echo "*** I have the capability to make that library automatically link in when"
+               echo "*** you link to this library.  But I can only do this if you have a"
+               echo "*** shared version of the library, which you do not appear to have"
+               echo "*** because the file extensions .$libext of this argument makes me believe"
+               echo "*** that it is just a static archive that I should not use here."
+             else
+               echo
+               $ECHO "*** Warning: Linking the shared library $output against the"
+               $ECHO "*** static library $deplib is not portable!"
+               deplibs="$deplib $deplibs"
+             fi
+             ;;
+           esac
+           continue
+           ;;
+         prog)
+           if test "$pass" != link; then
+             deplibs="$deplib $deplibs"
+           else
+             compile_deplibs="$deplib $compile_deplibs"
+             finalize_deplibs="$deplib $finalize_deplibs"
+           fi
+           continue
+           ;;
+         esac # linkmode
+         ;; # *.$libext
+       *.lo | *.$objext)
+         if test "$pass" = conv; then
+           deplibs="$deplib $deplibs"
+         elif test "$linkmode" = prog; then
+           if test "$pass" = dlpreopen || test "$dlopen_support" != yes || test "$build_libtool_libs" = no; then
+             # If there is no dlopen support or we're linking statically,
+             # we need to preload.
+             func_append newdlprefiles " $deplib"
+             compile_deplibs="$deplib $compile_deplibs"
+             finalize_deplibs="$deplib $finalize_deplibs"
+           else
+             func_append newdlfiles " $deplib"
+           fi
+         fi
+         continue
+         ;;
+       %DEPLIBS%)
+         alldeplibs=yes
+         continue
+         ;;
+       esac # case $deplib
+
+       if test "$found" = yes || test -f "$lib"; then :
+       else
+         func_fatal_error "cannot find the library \`$lib' or unhandled argument \`$deplib'"
+       fi
+
+       # Check to see that this really is a libtool archive.
+       func_lalib_unsafe_p "$lib" \
+         || func_fatal_error "\`$lib' is not a valid libtool archive"
+
+       func_dirname "$lib" "" "."
+       ladir="$func_dirname_result"
+
+       dlname=
+       dlopen=
+       dlpreopen=
+       libdir=
+       library_names=
+       old_library=
+       inherited_linker_flags=
+       # If the library was installed with an old release of libtool,
+       # it will not redefine variables installed, or shouldnotlink
+       installed=yes
+       shouldnotlink=no
+       avoidtemprpath=
+
+
+       # Read the .la file
+       func_source "$lib"
+
+       # Convert "-framework foo" to "foo.ltframework"
+       if test -n "$inherited_linker_flags"; then
+         tmp_inherited_linker_flags=`$ECHO "$inherited_linker_flags" | $SED 's/-framework \([^ $]*\)/\1.ltframework/g'`
+         for tmp_inherited_linker_flag in $tmp_inherited_linker_flags; do
+           case " $new_inherited_linker_flags " in
+             *" $tmp_inherited_linker_flag "*) ;;
+             *) func_append new_inherited_linker_flags " $tmp_inherited_linker_flag";;
+           esac
+         done
+       fi
+       dependency_libs=`$ECHO " $dependency_libs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+       if test "$linkmode,$pass" = "lib,link" ||
+          test "$linkmode,$pass" = "prog,scan" ||
+          { test "$linkmode" != prog && test "$linkmode" != lib; }; then
+         test -n "$dlopen" && func_append dlfiles " $dlopen"
+         test -n "$dlpreopen" && func_append dlprefiles " $dlpreopen"
+       fi
+
+       if test "$pass" = conv; then
+         # Only check for convenience libraries
+         deplibs="$lib $deplibs"
+         if test -z "$libdir"; then
+           if test -z "$old_library"; then
+             func_fatal_error "cannot find name of link library for \`$lib'"
+           fi
+           # It is a libtool convenience library, so add in its objects.
+           func_append convenience " $ladir/$objdir/$old_library"
+           func_append old_convenience " $ladir/$objdir/$old_library"
+           tmp_libs=
+           for deplib in $dependency_libs; do
+             deplibs="$deplib $deplibs"
+             if $opt_preserve_dup_deps ; then
+               case "$tmp_libs " in
+               *" $deplib "*) func_append specialdeplibs " $deplib" ;;
+               esac
+             fi
+             func_append tmp_libs " $deplib"
+           done
+         elif test "$linkmode" != prog && test "$linkmode" != lib; then
+           func_fatal_error "\`$lib' is not a convenience library"
+         fi
+         continue
+       fi # $pass = conv
+
+
+       # Get the name of the library we link against.
+       linklib=
+       if test -n "$old_library" &&
+          { test "$prefer_static_libs" = yes ||
+            test "$prefer_static_libs,$installed" = "built,no"; }; then
+         linklib=$old_library
+       else
+         for l in $old_library $library_names; do
+           linklib="$l"
+         done
+       fi
+       if test -z "$linklib"; then
+         func_fatal_error "cannot find name of link library for \`$lib'"
+       fi
+
+       # This library was specified with -dlopen.
+       if test "$pass" = dlopen; then
+         if test -z "$libdir"; then
+           func_fatal_error "cannot -dlopen a convenience library: \`$lib'"
+         fi
+         if test -z "$dlname" ||
+            test "$dlopen_support" != yes ||
+            test "$build_libtool_libs" = no; then
+           # If there is no dlname, no dlopen support or we're linking
+           # statically, we need to preload.  We also need to preload any
+           # dependent libraries so libltdl's deplib preloader doesn't
+           # bomb out in the load deplibs phase.
+           func_append dlprefiles " $lib $dependency_libs"
+         else
+           func_append newdlfiles " $lib"
+         fi
+         continue
+       fi # $pass = dlopen
+
+       # We need an absolute path.
+       case $ladir in
+       [\\/]* | [A-Za-z]:[\\/]*) abs_ladir="$ladir" ;;
+       *)
+         abs_ladir=`cd "$ladir" && pwd`
+         if test -z "$abs_ladir"; then
+           func_warning "cannot determine absolute directory name of \`$ladir'"
+           func_warning "passing it literally to the linker, although it might fail"
+           abs_ladir="$ladir"
+         fi
+         ;;
+       esac
+       func_basename "$lib"
+       laname="$func_basename_result"
+
+       # Find the relevant object directory and library name.
+       if test "X$installed" = Xyes; then
+         if test ! -f "$lt_sysroot$libdir/$linklib" && test -f "$abs_ladir/$linklib"; then
+           func_warning "library \`$lib' was moved."
+           dir="$ladir"
+           absdir="$abs_ladir"
+           libdir="$abs_ladir"
+         else
+           dir="$lt_sysroot$libdir"
+           absdir="$lt_sysroot$libdir"
+         fi
+         test "X$hardcode_automatic" = Xyes && avoidtemprpath=yes
+       else
+         if test ! -f "$ladir/$objdir/$linklib" && test -f "$abs_ladir/$linklib"; then
+           dir="$ladir"
+           absdir="$abs_ladir"
+           # Remove this search path later
+           func_append notinst_path " $abs_ladir"
+         else
+           dir="$ladir/$objdir"
+           absdir="$abs_ladir/$objdir"
+           # Remove this search path later
+           func_append notinst_path " $abs_ladir"
+         fi
+       fi # $installed = yes
+       func_stripname 'lib' '.la' "$laname"
+       name=$func_stripname_result
+
+       # This library was specified with -dlpreopen.
+       if test "$pass" = dlpreopen; then
+         if test -z "$libdir" && test "$linkmode" = prog; then
+           func_fatal_error "only libraries may -dlpreopen a convenience library: \`$lib'"
+         fi
+         case "$host" in
+           # special handling for platforms with PE-DLLs.
+           *cygwin* | *mingw* | *cegcc* )
+             # Linker will automatically link against shared library if both
+             # static and shared are present.  Therefore, ensure we extract
+             # symbols from the import library if a shared library is present
+             # (otherwise, the dlopen module name will be incorrect).  We do
+             # this by putting the import library name into $newdlprefiles.
+             # We recover the dlopen module name by 'saving' the la file
+             # name in a special purpose variable, and (later) extracting the
+             # dlname from the la file.
+             if test -n "$dlname"; then
+               func_tr_sh "$dir/$linklib"
+               eval "libfile_$func_tr_sh_result=\$abs_ladir/\$laname"
+               func_append newdlprefiles " $dir/$linklib"
+             else
+               func_append newdlprefiles " $dir/$old_library"
+               # Keep a list of preopened convenience libraries to check
+               # that they are being used correctly in the link pass.
+               test -z "$libdir" && \
+                 func_append dlpreconveniencelibs " $dir/$old_library"
+             fi
+           ;;
+           * )
+             # Prefer using a static library (so that no silly _DYNAMIC symbols
+             # are required to link).
+             if test -n "$old_library"; then
+               func_append newdlprefiles " $dir/$old_library"
+               # Keep a list of preopened convenience libraries to check
+               # that they are being used correctly in the link pass.
+               test -z "$libdir" && \
+                 func_append dlpreconveniencelibs " $dir/$old_library"
+             # Otherwise, use the dlname, so that lt_dlopen finds it.
+             elif test -n "$dlname"; then
+               func_append newdlprefiles " $dir/$dlname"
+             else
+               func_append newdlprefiles " $dir/$linklib"
+             fi
+           ;;
+         esac
+       fi # $pass = dlpreopen
+
+       if test -z "$libdir"; then
+         # Link the convenience library
+         if test "$linkmode" = lib; then
+           deplibs="$dir/$old_library $deplibs"
+         elif test "$linkmode,$pass" = "prog,link"; then
+           compile_deplibs="$dir/$old_library $compile_deplibs"
+           finalize_deplibs="$dir/$old_library $finalize_deplibs"
+         else
+           deplibs="$lib $deplibs" # used for prog,scan pass
+         fi
+         continue
+       fi
+
+
+       if test "$linkmode" = prog && test "$pass" != link; then
+         func_append newlib_search_path " $ladir"
+         deplibs="$lib $deplibs"
+
+         linkalldeplibs=no
+         if test "$link_all_deplibs" != no || test -z "$library_names" ||
+            test "$build_libtool_libs" = no; then
+           linkalldeplibs=yes
+         fi
+
+         tmp_libs=
+         for deplib in $dependency_libs; do
+           case $deplib in
+           -L*) func_stripname '-L' '' "$deplib"
+                func_resolve_sysroot "$func_stripname_result"
+                func_append newlib_search_path " $func_resolve_sysroot_result"
+                ;;
+           esac
+           # Need to link against all dependency_libs?
+           if test "$linkalldeplibs" = yes; then
+             deplibs="$deplib $deplibs"
+           else
+             # Need to hardcode shared library paths
+             # or/and link against static libraries
+             newdependency_libs="$deplib $newdependency_libs"
+           fi
+           if $opt_preserve_dup_deps ; then
+             case "$tmp_libs " in
+             *" $deplib "*) func_append specialdeplibs " $deplib" ;;
+             esac
+           fi
+           func_append tmp_libs " $deplib"
+         done # for deplib
+         continue
+       fi # $linkmode = prog...
+
+       if test "$linkmode,$pass" = "prog,link"; then
+         if test -n "$library_names" &&
+            { { test "$prefer_static_libs" = no ||
+                test "$prefer_static_libs,$installed" = "built,yes"; } ||
+              test -z "$old_library"; }; then
+           # We need to hardcode the library path
+           if test -n "$shlibpath_var" && test -z "$avoidtemprpath" ; then
+             # Make sure the rpath contains only unique directories.
+             case "$temp_rpath:" in
+             *"$absdir:"*) ;;
+             *) func_append temp_rpath "$absdir:" ;;
+             esac
+           fi
+
+           # Hardcode the library path.
+           # Skip directories that are in the system default run-time
+           # search path.
+           case " $sys_lib_dlsearch_path " in
+           *" $absdir "*) ;;
+           *)
+             case "$compile_rpath " in
+             *" $absdir "*) ;;
+             *) func_append compile_rpath " $absdir" ;;
+             esac
+             ;;
+           esac
+           case " $sys_lib_dlsearch_path " in
+           *" $libdir "*) ;;
+           *)
+             case "$finalize_rpath " in
+             *" $libdir "*) ;;
+             *) func_append finalize_rpath " $libdir" ;;
+             esac
+             ;;
+           esac
+         fi # $linkmode,$pass = prog,link...
+
+         if test "$alldeplibs" = yes &&
+            { test "$deplibs_check_method" = pass_all ||
+              { test "$build_libtool_libs" = yes &&
+                test -n "$library_names"; }; }; then
+           # We only need to search for static libraries
+           continue
+         fi
+       fi
+
+       link_static=no # Whether the deplib will be linked statically
+       use_static_libs=$prefer_static_libs
+       if test "$use_static_libs" = built && test "$installed" = yes; then
+         use_static_libs=no
+       fi
+       if test -n "$library_names" &&
+          { test "$use_static_libs" = no || test -z "$old_library"; }; then
+         case $host in
+         *cygwin* | *mingw* | *cegcc*)
+             # No point in relinking DLLs because paths are not encoded
+             func_append notinst_deplibs " $lib"
+             need_relink=no
+           ;;
+         *)
+           if test "$installed" = no; then
+             func_append notinst_deplibs " $lib"
+             need_relink=yes
+           fi
+           ;;
+         esac
+         # This is a shared library
+
+         # Warn about portability, can't link against -module's on some
+         # systems (darwin).  Don't bleat about dlopened modules though!
+         dlopenmodule=""
+         for dlpremoduletest in $dlprefiles; do
+           if test "X$dlpremoduletest" = "X$lib"; then
+             dlopenmodule="$dlpremoduletest"
+             break
+           fi
+         done
+         if test -z "$dlopenmodule" && test "$shouldnotlink" = yes && test "$pass" = link; then
+           echo
+           if test "$linkmode" = prog; then
+             $ECHO "*** Warning: Linking the executable $output against the loadable module"
+           else
+             $ECHO "*** Warning: Linking the shared library $output against the loadable module"
+           fi
+           $ECHO "*** $linklib is not portable!"
+         fi
+         if test "$linkmode" = lib &&
+            test "$hardcode_into_libs" = yes; then
+           # Hardcode the library path.
+           # Skip directories that are in the system default run-time
+           # search path.
+           case " $sys_lib_dlsearch_path " in
+           *" $absdir "*) ;;
+           *)
+             case "$compile_rpath " in
+             *" $absdir "*) ;;
+             *) func_append compile_rpath " $absdir" ;;
+             esac
+             ;;
+           esac
+           case " $sys_lib_dlsearch_path " in
+           *" $libdir "*) ;;
+           *)
+             case "$finalize_rpath " in
+             *" $libdir "*) ;;
+             *) func_append finalize_rpath " $libdir" ;;
+             esac
+             ;;
+           esac
+         fi
+
+         if test -n "$old_archive_from_expsyms_cmds"; then
+           # figure out the soname
+           set dummy $library_names
+           shift
+           realname="$1"
+           shift
+           libname=`eval "\\$ECHO \"$libname_spec\""`
+           # use dlname if we got it. it's perfectly good, no?
+           if test -n "$dlname"; then
+             soname="$dlname"
+           elif test -n "$soname_spec"; then
+             # bleh windows
+             case $host in
+             *cygwin* | mingw* | *cegcc*)
+               func_arith $current - $age
+               major=$func_arith_result
+               versuffix="-$major"
+               ;;
+             esac
+             eval soname=\"$soname_spec\"
+           else
+             soname="$realname"
+           fi
+
+           # Make a new name for the extract_expsyms_cmds to use
+           soroot="$soname"
+           func_basename "$soroot"
+           soname="$func_basename_result"
+           func_stripname 'lib' '.dll' "$soname"
+           newlib=libimp-$func_stripname_result.a
+
+           # If the library has no export list, then create one now
+           if test -f "$output_objdir/$soname-def"; then :
+           else
+             func_verbose "extracting exported symbol list from \`$soname'"
+             func_execute_cmds "$extract_expsyms_cmds" 'exit $?'
+           fi
+
+           # Create $newlib
+           if test -f "$output_objdir/$newlib"; then :; else
+             func_verbose "generating import library for \`$soname'"
+             func_execute_cmds "$old_archive_from_expsyms_cmds" 'exit $?'
+           fi
+           # make sure the library variables are pointing to the new library
+           dir=$output_objdir
+           linklib=$newlib
+         fi # test -n "$old_archive_from_expsyms_cmds"
+
+         if test "$linkmode" = prog || test "$opt_mode" != relink; then
+           add_shlibpath=
+           add_dir=
+           add=
+           lib_linked=yes
+           case $hardcode_action in
+           immediate | unsupported)
+             if test "$hardcode_direct" = no; then
+               add="$dir/$linklib"
+               case $host in
+                 *-*-sco3.2v5.0.[024]*) add_dir="-L$dir" ;;
+                 *-*-sysv4*uw2*) add_dir="-L$dir" ;;
+                 *-*-sysv5OpenUNIX* | *-*-sysv5UnixWare7.[01].[10]* | \
+                   *-*-unixware7*) add_dir="-L$dir" ;;
+                 *-*-darwin* )
+                   # if the lib is a (non-dlopened) module then we can not
+                   # link against it, someone is ignoring the earlier warnings
+                   if /usr/bin/file -L $add 2> /dev/null |
+                        $GREP ": [^:]* bundle" >/dev/null ; then
+                     if test "X$dlopenmodule" != "X$lib"; then
+                       $ECHO "*** Warning: lib $linklib is a module, not a shared library"
+                       if test -z "$old_library" ; then
+                         echo
+                         echo "*** And there doesn't seem to be a static archive available"
+                         echo "*** The link will probably fail, sorry"
+                       else
+                         add="$dir/$old_library"
+                       fi
+                     elif test -n "$old_library"; then
+                       add="$dir/$old_library"
+                     fi
+                   fi
+               esac
+             elif test "$hardcode_minus_L" = no; then
+               case $host in
+               *-*-sunos*) add_shlibpath="$dir" ;;
+               esac
+               add_dir="-L$dir"
+               add="-l$name"
+             elif test "$hardcode_shlibpath_var" = no; then
+               add_shlibpath="$dir"
+               add="-l$name"
+             else
+               lib_linked=no
+             fi
+             ;;
+           relink)
+             if test "$hardcode_direct" = yes &&
+                test "$hardcode_direct_absolute" = no; then
+               add="$dir/$linklib"
+             elif test "$hardcode_minus_L" = yes; then
+               add_dir="-L$absdir"
+               # Try looking first in the location we're being installed to.
+               if test -n "$inst_prefix_dir"; then
+                 case $libdir in
+                   [\\/]*)
+                     func_append add_dir " -L$inst_prefix_dir$libdir"
+                     ;;
+                 esac
+               fi
+               add="-l$name"
+             elif test "$hardcode_shlibpath_var" = yes; then
+               add_shlibpath="$dir"
+               add="-l$name"
+             else
+               lib_linked=no
+             fi
+             ;;
+           *) lib_linked=no ;;
+           esac
+
+           if test "$lib_linked" != yes; then
+             func_fatal_configuration "unsupported hardcode properties"
+           fi
+
+           if test -n "$add_shlibpath"; then
+             case :$compile_shlibpath: in
+             *":$add_shlibpath:"*) ;;
+             *) func_append compile_shlibpath "$add_shlibpath:" ;;
+             esac
+           fi
+           if test "$linkmode" = prog; then
+             test -n "$add_dir" && compile_deplibs="$add_dir $compile_deplibs"
+             test -n "$add" && compile_deplibs="$add $compile_deplibs"
+           else
+             test -n "$add_dir" && deplibs="$add_dir $deplibs"
+             test -n "$add" && deplibs="$add $deplibs"
+             if test "$hardcode_direct" != yes &&
+                test "$hardcode_minus_L" != yes &&
+                test "$hardcode_shlibpath_var" = yes; then
+               case :$finalize_shlibpath: in
+               *":$libdir:"*) ;;
+               *) func_append finalize_shlibpath "$libdir:" ;;
+               esac
+             fi
+           fi
+         fi
+
+         if test "$linkmode" = prog || test "$opt_mode" = relink; then
+           add_shlibpath=
+           add_dir=
+           add=
+           # Finalize command for both is simple: just hardcode it.
+           if test "$hardcode_direct" = yes &&
+              test "$hardcode_direct_absolute" = no; then
+             add="$libdir/$linklib"
+           elif test "$hardcode_minus_L" = yes; then
+             add_dir="-L$libdir"
+             add="-l$name"
+           elif test "$hardcode_shlibpath_var" = yes; then
+             case :$finalize_shlibpath: in
+             *":$libdir:"*) ;;
+             *) func_append finalize_shlibpath "$libdir:" ;;
+             esac
+             add="-l$name"
+           elif test "$hardcode_automatic" = yes; then
+             if test -n "$inst_prefix_dir" &&
+                test -f "$inst_prefix_dir$libdir/$linklib" ; then
+               add="$inst_prefix_dir$libdir/$linklib"
+             else
+               add="$libdir/$linklib"
+             fi
+           else
+             # We cannot seem to hardcode it, guess we'll fake it.
+             add_dir="-L$libdir"
+             # Try looking first in the location we're being installed to.
+             if test -n "$inst_prefix_dir"; then
+               case $libdir in
+                 [\\/]*)
+                   func_append add_dir " -L$inst_prefix_dir$libdir"
+                   ;;
+               esac
+             fi
+             add="-l$name"
+           fi
+
+           if test "$linkmode" = prog; then
+             test -n "$add_dir" && finalize_deplibs="$add_dir $finalize_deplibs"
+             test -n "$add" && finalize_deplibs="$add $finalize_deplibs"
+           else
+             test -n "$add_dir" && deplibs="$add_dir $deplibs"
+             test -n "$add" && deplibs="$add $deplibs"
+           fi
+         fi
+       elif test "$linkmode" = prog; then
+         # Here we assume that one of hardcode_direct or hardcode_minus_L
+         # is not unsupported.  This is valid on all known static and
+         # shared platforms.
+         if test "$hardcode_direct" != unsupported; then
+           test -n "$old_library" && linklib="$old_library"
+           compile_deplibs="$dir/$linklib $compile_deplibs"
+           finalize_deplibs="$dir/$linklib $finalize_deplibs"
+         else
+           compile_deplibs="-l$name -L$dir $compile_deplibs"
+           finalize_deplibs="-l$name -L$dir $finalize_deplibs"
+         fi
+       elif test "$build_libtool_libs" = yes; then
+         # Not a shared library
+         if test "$deplibs_check_method" != pass_all; then
+           # We're trying link a shared library against a static one
+           # but the system doesn't support it.
+
+           # Just print a warning and add the library to dependency_libs so
+           # that the program can be linked against the static library.
+           echo
+           $ECHO "*** Warning: This system can not link to static lib archive $lib."
+           echo "*** I have the capability to make that library automatically link in when"
+           echo "*** you link to this library.  But I can only do this if you have a"
+           echo "*** shared version of the library, which you do not appear to have."
+           if test "$module" = yes; then
+             echo "*** But as you try to build a module library, libtool will still create "
+             echo "*** a static module, that should work as long as the dlopening application"
+             echo "*** is linked with the -dlopen flag to resolve symbols at runtime."
+             if test -z "$global_symbol_pipe"; then
+               echo
+               echo "*** However, this would only work if libtool was able to extract symbol"
+               echo "*** lists from a program, using \`nm' or equivalent, but libtool could"
+               echo "*** not find such a program.  So, this module is probably useless."
+               echo "*** \`nm' from GNU binutils and a full rebuild may help."
+             fi
+             if test "$build_old_libs" = no; then
+               build_libtool_libs=module
+               build_old_libs=yes
+             else
+               build_libtool_libs=no
+             fi
+           fi
+         else
+           deplibs="$dir/$old_library $deplibs"
+           link_static=yes
+         fi
+       fi # link shared/static library?
+
+       if test "$linkmode" = lib; then
+         if test -n "$dependency_libs" &&
+            { test "$hardcode_into_libs" != yes ||
+              test "$build_old_libs" = yes ||
+              test "$link_static" = yes; }; then
+           # Extract -R from dependency_libs
+           temp_deplibs=
+           for libdir in $dependency_libs; do
+             case $libdir in
+             -R*) func_stripname '-R' '' "$libdir"
+                  temp_xrpath=$func_stripname_result
+                  case " $xrpath " in
+                  *" $temp_xrpath "*) ;;
+                  *) func_append xrpath " $temp_xrpath";;
+                  esac;;
+             *) func_append temp_deplibs " $libdir";;
+             esac
+           done
+           dependency_libs="$temp_deplibs"
+         fi
+
+         func_append newlib_search_path " $absdir"
+         # Link against this library
+         test "$link_static" = no && newdependency_libs="$abs_ladir/$laname $newdependency_libs"
+         # ... and its dependency_libs
+         tmp_libs=
+         for deplib in $dependency_libs; do
+           newdependency_libs="$deplib $newdependency_libs"
+           case $deplib in
+              -L*) func_stripname '-L' '' "$deplib"
+                   func_resolve_sysroot "$func_stripname_result";;
+              *) func_resolve_sysroot "$deplib" ;;
+            esac
+           if $opt_preserve_dup_deps ; then
+             case "$tmp_libs " in
+             *" $func_resolve_sysroot_result "*)
+                func_append specialdeplibs " $func_resolve_sysroot_result" ;;
+             esac
+           fi
+           func_append tmp_libs " $func_resolve_sysroot_result"
+         done
+
+         if test "$link_all_deplibs" != no; then
+           # Add the search paths of all dependency libraries
+           for deplib in $dependency_libs; do
+             path=
+             case $deplib in
+             -L*) path="$deplib" ;;
+             *.la)
+               func_resolve_sysroot "$deplib"
+               deplib=$func_resolve_sysroot_result
+               func_dirname "$deplib" "" "."
+               dir=$func_dirname_result
+               # We need an absolute path.
+               case $dir in
+               [\\/]* | [A-Za-z]:[\\/]*) absdir="$dir" ;;
+               *)
+                 absdir=`cd "$dir" && pwd`
+                 if test -z "$absdir"; then
+                   func_warning "cannot determine absolute directory name of \`$dir'"
+                   absdir="$dir"
+                 fi
+                 ;;
+               esac
+               if $GREP "^installed=no" $deplib > /dev/null; then
+               case $host in
+               *-*-darwin*)
+                 depdepl=
+                 eval deplibrary_names=`${SED} -n -e 's/^library_names=\(.*\)$/\1/p' $deplib`
+                 if test -n "$deplibrary_names" ; then
+                   for tmp in $deplibrary_names ; do
+                     depdepl=$tmp
+                   done
+                   if test -f "$absdir/$objdir/$depdepl" ; then
+                     depdepl="$absdir/$objdir/$depdepl"
+                     darwin_install_name=`${OTOOL} -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'`
+                      if test -z "$darwin_install_name"; then
+                          darwin_install_name=`${OTOOL64} -L $depdepl  | awk '{if (NR == 2) {print $1;exit}}'`
+                      fi
+                     func_append compiler_flags " ${wl}-dylib_file ${wl}${darwin_install_name}:${depdepl}"
+                     func_append linker_flags " -dylib_file ${darwin_install_name}:${depdepl}"
+                     path=
+                   fi
+                 fi
+                 ;;
+               *)
+                 path="-L$absdir/$objdir"
+                 ;;
+               esac
+               else
+                 eval libdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $deplib`
+                 test -z "$libdir" && \
+                   func_fatal_error "\`$deplib' is not a valid libtool archive"
+                 test "$absdir" != "$libdir" && \
+                   func_warning "\`$deplib' seems to be moved"
+
+                 path="-L$absdir"
+               fi
+               ;;
+             esac
+             case " $deplibs " in
+             *" $path "*) ;;
+             *) deplibs="$path $deplibs" ;;
+             esac
+           done
+         fi # link_all_deplibs != no
+       fi # linkmode = lib
+      done # for deplib in $libs
+      if test "$pass" = link; then
+       if test "$linkmode" = "prog"; then
+         compile_deplibs="$new_inherited_linker_flags $compile_deplibs"
+         finalize_deplibs="$new_inherited_linker_flags $finalize_deplibs"
+       else
+         compiler_flags="$compiler_flags "`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+       fi
+      fi
+      dependency_libs="$newdependency_libs"
+      if test "$pass" = dlpreopen; then
+       # Link the dlpreopened libraries before other libraries
+       for deplib in $save_deplibs; do
+         deplibs="$deplib $deplibs"
+       done
+      fi
+      if test "$pass" != dlopen; then
+       if test "$pass" != conv; then
+         # Make sure lib_search_path contains only unique directories.
+         lib_search_path=
+         for dir in $newlib_search_path; do
+           case "$lib_search_path " in
+           *" $dir "*) ;;
+           *) func_append lib_search_path " $dir" ;;
+           esac
+         done
+         newlib_search_path=
+       fi
+
+       if test "$linkmode,$pass" != "prog,link"; then
+         vars="deplibs"
+       else
+         vars="compile_deplibs finalize_deplibs"
+       fi
+       for var in $vars dependency_libs; do
+         # Add libraries to $var in reverse order
+         eval tmp_libs=\"\$$var\"
+         new_libs=
+         for deplib in $tmp_libs; do
+           # FIXME: Pedantically, this is the right thing to do, so
+           #        that some nasty dependency loop isn't accidentally
+           #        broken:
+           #new_libs="$deplib $new_libs"
+           # Pragmatically, this seems to cause very few problems in
+           # practice:
+           case $deplib in
+           -L*) new_libs="$deplib $new_libs" ;;
+           -R*) ;;
+           *)
+             # And here is the reason: when a library appears more
+             # than once as an explicit dependence of a library, or
+             # is implicitly linked in more than once by the
+             # compiler, it is considered special, and multiple
+             # occurrences thereof are not removed.  Compare this
+             # with having the same library being listed as a
+             # dependency of multiple other libraries: in this case,
+             # we know (pedantically, we assume) the library does not
+             # need to be listed more than once, so we keep only the
+             # last copy.  This is not always right, but it is rare
+             # enough that we require users that really mean to play
+             # such unportable linking tricks to link the library
+             # using -Wl,-lname, so that libtool does not consider it
+             # for duplicate removal.
+             case " $specialdeplibs " in
+             *" $deplib "*) new_libs="$deplib $new_libs" ;;
+             *)
+               case " $new_libs " in
+               *" $deplib "*) ;;
+               *) new_libs="$deplib $new_libs" ;;
+               esac
+               ;;
+             esac
+             ;;
+           esac
+         done
+         tmp_libs=
+         for deplib in $new_libs; do
+           case $deplib in
+           -L*)
+             case " $tmp_libs " in
+             *" $deplib "*) ;;
+             *) func_append tmp_libs " $deplib" ;;
+             esac
+             ;;
+           *) func_append tmp_libs " $deplib" ;;
+           esac
+         done
+         eval $var=\"$tmp_libs\"
+       done # for var
+      fi
+      # Last step: remove runtime libs from dependency_libs
+      # (they stay in deplibs)
+      tmp_libs=
+      for i in $dependency_libs ; do
+       case " $predeps $postdeps $compiler_lib_search_path " in
+       *" $i "*)
+         i=""
+         ;;
+       esac
+       if test -n "$i" ; then
+         func_append tmp_libs " $i"
+       fi
+      done
+      dependency_libs=$tmp_libs
+    done # for pass
+    if test "$linkmode" = prog; then
+      dlfiles="$newdlfiles"
+    fi
+    if test "$linkmode" = prog || test "$linkmode" = lib; then
+      dlprefiles="$newdlprefiles"
+    fi
+
+    case $linkmode in
+    oldlib)
+      if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then
+       func_warning "\`-dlopen' is ignored for archives"
+      fi
+
+      case " $deplibs" in
+      *\ -l* | *\ -L*)
+       func_warning "\`-l' and \`-L' are ignored for archives" ;;
+      esac
+
+      test -n "$rpath" && \
+       func_warning "\`-rpath' is ignored for archives"
+
+      test -n "$xrpath" && \
+       func_warning "\`-R' is ignored for archives"
+
+      test -n "$vinfo" && \
+       func_warning "\`-version-info/-version-number' is ignored for archives"
+
+      test -n "$release" && \
+       func_warning "\`-release' is ignored for archives"
+
+      test -n "$export_symbols$export_symbols_regex" && \
+       func_warning "\`-export-symbols' is ignored for archives"
+
+      # Now set the variables for building old libraries.
+      build_libtool_libs=no
+      oldlibs="$output"
+      func_append objs "$old_deplibs"
+      ;;
+
+    lib)
+      # Make sure we only generate libraries of the form `libNAME.la'.
+      case $outputname in
+      lib*)
+       func_stripname 'lib' '.la' "$outputname"
+       name=$func_stripname_result
+       eval shared_ext=\"$shrext_cmds\"
+       eval libname=\"$libname_spec\"
+       ;;
+      *)
+       test "$module" = no && \
+         func_fatal_help "libtool library \`$output' must begin with \`lib'"
+
+       if test "$need_lib_prefix" != no; then
+         # Add the "lib" prefix for modules if required
+         func_stripname '' '.la' "$outputname"
+         name=$func_stripname_result
+         eval shared_ext=\"$shrext_cmds\"
+         eval libname=\"$libname_spec\"
+       else
+         func_stripname '' '.la' "$outputname"
+         libname=$func_stripname_result
+       fi
+       ;;
+      esac
+
+      if test -n "$objs"; then
+       if test "$deplibs_check_method" != pass_all; then
+         func_fatal_error "cannot build libtool library \`$output' from non-libtool objects on this host:$objs"
+       else
+         echo
+         $ECHO "*** Warning: Linking the shared library $output against the non-libtool"
+         $ECHO "*** objects $objs is not portable!"
+         func_append libobjs " $objs"
+       fi
+      fi
+
+      test "$dlself" != no && \
+       func_warning "\`-dlopen self' is ignored for libtool libraries"
+
+      set dummy $rpath
+      shift
+      test "$#" -gt 1 && \
+       func_warning "ignoring multiple \`-rpath's for a libtool library"
+
+      install_libdir="$1"
+
+      oldlibs=
+      if test -z "$rpath"; then
+       if test "$build_libtool_libs" = yes; then
+         # Building a libtool convenience library.
+         # Some compilers have problems with a `.al' extension so
+         # convenience libraries should have the same extension an
+         # archive normally would.
+         oldlibs="$output_objdir/$libname.$libext $oldlibs"
+         build_libtool_libs=convenience
+         build_old_libs=yes
+       fi
+
+       test -n "$vinfo" && \
+         func_warning "\`-version-info/-version-number' is ignored for convenience libraries"
+
+       test -n "$release" && \
+         func_warning "\`-release' is ignored for convenience libraries"
+      else
+
+       # Parse the version information argument.
+       save_ifs="$IFS"; IFS=':'
+       set dummy $vinfo 0 0 0
+       shift
+       IFS="$save_ifs"
+
+       test -n "$7" && \
+         func_fatal_help "too many parameters to \`-version-info'"
+
+       # convert absolute version numbers to libtool ages
+       # this retains compatibility with .la files and attempts
+       # to make the code below a bit more comprehensible
+
+       case $vinfo_number in
+       yes)
+         number_major="$1"
+         number_minor="$2"
+         number_revision="$3"
+         #
+         # There are really only two kinds -- those that
+         # use the current revision as the major version
+         # and those that subtract age and use age as
+         # a minor version.  But, then there is irix
+         # which has an extra 1 added just for fun
+         #
+         case $version_type in
+         # correct linux to gnu/linux during the next big refactor
+         darwin|linux|osf|windows|none)
+           func_arith $number_major + $number_minor
+           current=$func_arith_result
+           age="$number_minor"
+           revision="$number_revision"
+           ;;
+         freebsd-aout|freebsd-elf|qnx|sunos)
+           current="$number_major"
+           revision="$number_minor"
+           age="0"
+           ;;
+         irix|nonstopux)
+           func_arith $number_major + $number_minor
+           current=$func_arith_result
+           age="$number_minor"
+           revision="$number_minor"
+           lt_irix_increment=no
+           ;;
+         *)
+           func_fatal_configuration "$modename: unknown library version type \`$version_type'"
+           ;;
+         esac
+         ;;
+       no)
+         current="$1"
+         revision="$2"
+         age="$3"
+         ;;
+       esac
+
+       # Check that each of the things are valid numbers.
+       case $current in
+       0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;;
+       *)
+         func_error "CURRENT \`$current' must be a nonnegative integer"
+         func_fatal_error "\`$vinfo' is not valid version information"
+         ;;
+       esac
+
+       case $revision in
+       0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;;
+       *)
+         func_error "REVISION \`$revision' must be a nonnegative integer"
+         func_fatal_error "\`$vinfo' is not valid version information"
+         ;;
+       esac
+
+       case $age in
+       0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;;
+       *)
+         func_error "AGE \`$age' must be a nonnegative integer"
+         func_fatal_error "\`$vinfo' is not valid version information"
+         ;;
+       esac
+
+       if test "$age" -gt "$current"; then
+         func_error "AGE \`$age' is greater than the current interface number \`$current'"
+         func_fatal_error "\`$vinfo' is not valid version information"
+       fi
+
+       # Calculate the version variables.
+       major=
+       versuffix=
+       verstring=
+       case $version_type in
+       none) ;;
+
+       darwin)
+         # Like Linux, but with the current version available in
+         # verstring for coding it into the library header
+         func_arith $current - $age
+         major=.$func_arith_result
+         versuffix="$major.$age.$revision"
+         # Darwin ld doesn't like 0 for these options...
+         func_arith $current + 1
+         minor_current=$func_arith_result
+         xlcverstring="${wl}-compatibility_version ${wl}$minor_current ${wl}-current_version ${wl}$minor_current.$revision"
+         verstring="-compatibility_version $minor_current -current_version $minor_current.$revision"
+         ;;
+
+       freebsd-aout)
+         major=".$current"
+         versuffix=".$current.$revision";
+         ;;
+
+       freebsd-elf)
+         major=".$current"
+         versuffix=".$current"
+         ;;
+
+       irix | nonstopux)
+         if test "X$lt_irix_increment" = "Xno"; then
+           func_arith $current - $age
+         else
+           func_arith $current - $age + 1
+         fi
+         major=$func_arith_result
+
+         case $version_type in
+           nonstopux) verstring_prefix=nonstopux ;;
+           *)         verstring_prefix=sgi ;;
+         esac
+         verstring="$verstring_prefix$major.$revision"
+
+         # Add in all the interfaces that we are compatible with.
+         loop=$revision
+         while test "$loop" -ne 0; do
+           func_arith $revision - $loop
+           iface=$func_arith_result
+           func_arith $loop - 1
+           loop=$func_arith_result
+           verstring="$verstring_prefix$major.$iface:$verstring"
+         done
+
+         # Before this point, $major must not contain `.'.
+         major=.$major
+         versuffix="$major.$revision"
+         ;;
+
+       linux) # correct to gnu/linux during the next big refactor
+         func_arith $current - $age
+         major=.$func_arith_result
+         versuffix="$major.$age.$revision"
+         ;;
+
+       osf)
+         func_arith $current - $age
+         major=.$func_arith_result
+         versuffix=".$current.$age.$revision"
+         verstring="$current.$age.$revision"
+
+         # Add in all the interfaces that we are compatible with.
+         loop=$age
+         while test "$loop" -ne 0; do
+           func_arith $current - $loop
+           iface=$func_arith_result
+           func_arith $loop - 1
+           loop=$func_arith_result
+           verstring="$verstring:${iface}.0"
+         done
+
+         # Make executables depend on our current version.
+         func_append verstring ":${current}.0"
+         ;;
+
+       qnx)
+         major=".$current"
+         versuffix=".$current"
+         ;;
+
+       sunos)
+         major=".$current"
+         versuffix=".$current.$revision"
+         ;;
+
+       windows)
+         # Use '-' rather than '.', since we only want one
+         # extension on DOS 8.3 filesystems.
+         func_arith $current - $age
+         major=$func_arith_result
+         versuffix="-$major"
+         ;;
+
+       *)
+         func_fatal_configuration "unknown library version type \`$version_type'"
+         ;;
+       esac
+
+       # Clear the version info if we defaulted, and they specified a release.
+       if test -z "$vinfo" && test -n "$release"; then
+         major=
+         case $version_type in
+         darwin)
+           # we can't check for "0.0" in archive_cmds due to quoting
+           # problems, so we reset it completely
+           verstring=
+           ;;
+         *)
+           verstring="0.0"
+           ;;
+         esac
+         if test "$need_version" = no; then
+           versuffix=
+         else
+           versuffix=".0.0"
+         fi
+       fi
+
+       # Remove version info from name if versioning should be avoided
+       if test "$avoid_version" = yes && test "$need_version" = no; then
+         major=
+         versuffix=
+         verstring=""
+       fi
+
+       # Check to see if the archive will have undefined symbols.
+       if test "$allow_undefined" = yes; then
+         if test "$allow_undefined_flag" = unsupported; then
+           func_warning "undefined symbols not allowed in $host shared libraries"
+           build_libtool_libs=no
+           build_old_libs=yes
+         fi
+       else
+         # Don't allow undefined symbols.
+         allow_undefined_flag="$no_undefined_flag"
+       fi
+
+      fi
+
+      func_generate_dlsyms "$libname" "$libname" "yes"
+      func_append libobjs " $symfileobj"
+      test "X$libobjs" = "X " && libobjs=
+
+      if test "$opt_mode" != relink; then
+       # Remove our outputs, but don't remove object files since they
+       # may have been created when compiling PIC objects.
+       removelist=
+       tempremovelist=`$ECHO "$output_objdir/*"`
+       for p in $tempremovelist; do
+         case $p in
+           *.$objext | *.gcno)
+              ;;
+           $output_objdir/$outputname | $output_objdir/$libname.* | $output_objdir/${libname}${release}.*)
+              if test "X$precious_files_regex" != "X"; then
+                if $ECHO "$p" | $EGREP -e "$precious_files_regex" >/dev/null 2>&1
+                then
+                  continue
+                fi
+              fi
+              func_append removelist " $p"
+              ;;
+           *) ;;
+         esac
+       done
+       test -n "$removelist" && \
+         func_show_eval "${RM}r \$removelist"
+      fi
+
+      # Now set the variables for building old libraries.
+      if test "$build_old_libs" = yes && test "$build_libtool_libs" != convenience ; then
+       func_append oldlibs " $output_objdir/$libname.$libext"
+
+       # Transform .lo files to .o files.
+       oldobjs="$objs "`$ECHO "$libobjs" | $SP2NL | $SED "/\.${libext}$/d; $lo2o" | $NL2SP`
+      fi
+
+      # Eliminate all temporary directories.
+      #for path in $notinst_path; do
+      #        lib_search_path=`$ECHO "$lib_search_path " | $SED "s% $path % %g"`
+      #        deplibs=`$ECHO "$deplibs " | $SED "s% -L$path % %g"`
+      #        dependency_libs=`$ECHO "$dependency_libs " | $SED "s% -L$path % %g"`
+      #done
+
+      if test -n "$xrpath"; then
+       # If the user specified any rpath flags, then add them.
+       temp_xrpath=
+       for libdir in $xrpath; do
+         func_replace_sysroot "$libdir"
+         func_append temp_xrpath " -R$func_replace_sysroot_result"
+         case "$finalize_rpath " in
+         *" $libdir "*) ;;
+         *) func_append finalize_rpath " $libdir" ;;
+         esac
+       done
+       if test "$hardcode_into_libs" != yes || test "$build_old_libs" = yes; then
+         dependency_libs="$temp_xrpath $dependency_libs"
+       fi
+      fi
+
+      # Make sure dlfiles contains only unique files that won't be dlpreopened
+      old_dlfiles="$dlfiles"
+      dlfiles=
+      for lib in $old_dlfiles; do
+       case " $dlprefiles $dlfiles " in
+       *" $lib "*) ;;
+       *) func_append dlfiles " $lib" ;;
+       esac
+      done
+
+      # Make sure dlprefiles contains only unique files
+      old_dlprefiles="$dlprefiles"
+      dlprefiles=
+      for lib in $old_dlprefiles; do
+       case "$dlprefiles " in
+       *" $lib "*) ;;
+       *) func_append dlprefiles " $lib" ;;
+       esac
+      done
+
+      if test "$build_libtool_libs" = yes; then
+       if test -n "$rpath"; then
+         case $host in
+         *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-beos* | *-cegcc* | *-*-haiku*)
+           # these systems don't actually have a c library (as such)!
+           ;;
+         *-*-rhapsody* | *-*-darwin1.[012])
+           # Rhapsody C library is in the System framework
+           func_append deplibs " System.ltframework"
+           ;;
+         *-*-netbsd*)
+           # Don't link with libc until the a.out ld.so is fixed.
+           ;;
+         *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*)
+           # Do not include libc due to us having libc/libc_r.
+           ;;
+         *-*-sco3.2v5* | *-*-sco5v6*)
+           # Causes problems with __ctype
+           ;;
+         *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*)
+           # Compiler inserts libc in the correct place for threads to work
+           ;;
+         *)
+           # Add libc to deplibs on all other systems if necessary.
+           if test "$build_libtool_need_lc" = "yes"; then
+             func_append deplibs " -lc"
+           fi
+           ;;
+         esac
+       fi
+
+       # Transform deplibs into only deplibs that can be linked in shared.
+       name_save=$name
+       libname_save=$libname
+       release_save=$release
+       versuffix_save=$versuffix
+       major_save=$major
+       # I'm not sure if I'm treating the release correctly.  I think
+       # release should show up in the -l (ie -lgmp5) so we don't want to
+       # add it in twice.  Is that correct?
+       release=""
+       versuffix=""
+       major=""
+       newdeplibs=
+       droppeddeps=no
+       case $deplibs_check_method in
+       pass_all)
+         # Don't check for shared/static.  Everything works.
+         # This might be a little naive.  We might want to check
+         # whether the library exists or not.  But this is on
+         # osf3 & osf4 and I'm not really sure... Just
+         # implementing what was already the behavior.
+         newdeplibs=$deplibs
+         ;;
+       test_compile)
+         # This code stresses the "libraries are programs" paradigm to its
+         # limits. Maybe even breaks it.  We compile a program, linking it
+         # against the deplibs as a proxy for the library.  Then we can check
+         # whether they linked in statically or dynamically with ldd.
+         $opt_dry_run || $RM conftest.c
+         cat > conftest.c <<EOF
+         int main() { return 0; }
+EOF
+         $opt_dry_run || $RM conftest
+         if $LTCC $LTCFLAGS -o conftest conftest.c $deplibs; then
+           ldd_output=`ldd conftest`
+           for i in $deplibs; do
+             case $i in
+             -l*)
+               func_stripname -l '' "$i"
+               name=$func_stripname_result
+               if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then
+                 case " $predeps $postdeps " in
+                 *" $i "*)
+                   func_append newdeplibs " $i"
+                   i=""
+                   ;;
+                 esac
+               fi
+               if test -n "$i" ; then
+                 libname=`eval "\\$ECHO \"$libname_spec\""`
+                 deplib_matches=`eval "\\$ECHO \"$library_names_spec\""`
+                 set dummy $deplib_matches; shift
+                 deplib_match=$1
+                 if test `expr "$ldd_output" : ".*$deplib_match"` -ne 0 ; then
+                   func_append newdeplibs " $i"
+                 else
+                   droppeddeps=yes
+                   echo
+                   $ECHO "*** Warning: dynamic linker does not accept needed library $i."
+                   echo "*** I have the capability to make that library automatically link in when"
+                   echo "*** you link to this library.  But I can only do this if you have a"
+                   echo "*** shared version of the library, which I believe you do not have"
+                   echo "*** because a test_compile did reveal that the linker did not use it for"
+                   echo "*** its dynamic dependency list that programs get resolved with at runtime."
+                 fi
+               fi
+               ;;
+             *)
+               func_append newdeplibs " $i"
+               ;;
+             esac
+           done
+         else
+           # Error occurred in the first compile.  Let's try to salvage
+           # the situation: Compile a separate program for each library.
+           for i in $deplibs; do
+             case $i in
+             -l*)
+               func_stripname -l '' "$i"
+               name=$func_stripname_result
+               $opt_dry_run || $RM conftest
+               if $LTCC $LTCFLAGS -o conftest conftest.c $i; then
+                 ldd_output=`ldd conftest`
+                 if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then
+                   case " $predeps $postdeps " in
+                   *" $i "*)
+                     func_append newdeplibs " $i"
+                     i=""
+                     ;;
+                   esac
+                 fi
+                 if test -n "$i" ; then
+                   libname=`eval "\\$ECHO \"$libname_spec\""`
+                   deplib_matches=`eval "\\$ECHO \"$library_names_spec\""`
+                   set dummy $deplib_matches; shift
+                   deplib_match=$1
+                   if test `expr "$ldd_output" : ".*$deplib_match"` -ne 0 ; then
+                     func_append newdeplibs " $i"
+                   else
+                     droppeddeps=yes
+                     echo
+                     $ECHO "*** Warning: dynamic linker does not accept needed library $i."
+                     echo "*** I have the capability to make that library automatically link in when"
+                     echo "*** you link to this library.  But I can only do this if you have a"
+                     echo "*** shared version of the library, which you do not appear to have"
+                     echo "*** because a test_compile did reveal that the linker did not use this one"
+                     echo "*** as a dynamic dependency that programs can get resolved with at runtime."
+                   fi
+                 fi
+               else
+                 droppeddeps=yes
+                 echo
+                 $ECHO "*** Warning!  Library $i is needed by this library but I was not able to"
+                 echo "*** make it link in!  You will probably need to install it or some"
+                 echo "*** library that it depends on before this library will be fully"
+                 echo "*** functional.  Installing it before continuing would be even better."
+               fi
+               ;;
+             *)
+               func_append newdeplibs " $i"
+               ;;
+             esac
+           done
+         fi
+         ;;
+       file_magic*)
+         set dummy $deplibs_check_method; shift
+         file_magic_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"`
+         for a_deplib in $deplibs; do
+           case $a_deplib in
+           -l*)
+             func_stripname -l '' "$a_deplib"
+             name=$func_stripname_result
+             if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then
+               case " $predeps $postdeps " in
+               *" $a_deplib "*)
+                 func_append newdeplibs " $a_deplib"
+                 a_deplib=""
+                 ;;
+               esac
+             fi
+             if test -n "$a_deplib" ; then
+               libname=`eval "\\$ECHO \"$libname_spec\""`
+               if test -n "$file_magic_glob"; then
+                 libnameglob=`func_echo_all "$libname" | $SED -e $file_magic_glob`
+               else
+                 libnameglob=$libname
+               fi
+               test "$want_nocaseglob" = yes && nocaseglob=`shopt -p nocaseglob`
+               for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do
+                 if test "$want_nocaseglob" = yes; then
+                   shopt -s nocaseglob
+                   potential_libs=`ls $i/$libnameglob[.-]* 2>/dev/null`
+                   $nocaseglob
+                 else
+                   potential_libs=`ls $i/$libnameglob[.-]* 2>/dev/null`
+                 fi
+                 for potent_lib in $potential_libs; do
+                     # Follow soft links.
+                     if ls -lLd "$potent_lib" 2>/dev/null |
+                        $GREP " -> " >/dev/null; then
+                       continue
+                     fi
+                     # The statement above tries to avoid entering an
+                     # endless loop below, in case of cyclic links.
+                     # We might still enter an endless loop, since a link
+                     # loop can be closed while we follow links,
+                     # but so what?
+                     potlib="$potent_lib"
+                     while test -h "$potlib" 2>/dev/null; do
+                       potliblink=`ls -ld $potlib | ${SED} 's/.* -> //'`
+                       case $potliblink in
+                       [\\/]* | [A-Za-z]:[\\/]*) potlib="$potliblink";;
+                       *) potlib=`$ECHO "$potlib" | $SED 's,[^/]*$,,'`"$potliblink";;
+                       esac
+                     done
+                     if eval $file_magic_cmd \"\$potlib\" 2>/dev/null |
+                        $SED -e 10q |
+                        $EGREP "$file_magic_regex" > /dev/null; then
+                       func_append newdeplibs " $a_deplib"
+                       a_deplib=""
+                       break 2
+                     fi
+                 done
+               done
+             fi
+             if test -n "$a_deplib" ; then
+               droppeddeps=yes
+               echo
+               $ECHO "*** Warning: linker path does not have real file for library $a_deplib."
+               echo "*** I have the capability to make that library automatically link in when"
+               echo "*** you link to this library.  But I can only do this if you have a"
+               echo "*** shared version of the library, which you do not appear to have"
+               echo "*** because I did check the linker path looking for a file starting"
+               if test -z "$potlib" ; then
+                 $ECHO "*** with $libname but no candidates were found. (...for file magic test)"
+               else
+                 $ECHO "*** with $libname and none of the candidates passed a file format test"
+                 $ECHO "*** using a file magic. Last file checked: $potlib"
+               fi
+             fi
+             ;;
+           *)
+             # Add a -L argument.
+             func_append newdeplibs " $a_deplib"
+             ;;
+           esac
+         done # Gone through all deplibs.
+         ;;
+       match_pattern*)
+         set dummy $deplibs_check_method; shift
+         match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"`
+         for a_deplib in $deplibs; do
+           case $a_deplib in
+           -l*)
+             func_stripname -l '' "$a_deplib"
+             name=$func_stripname_result
+             if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then
+               case " $predeps $postdeps " in
+               *" $a_deplib "*)
+                 func_append newdeplibs " $a_deplib"
+                 a_deplib=""
+                 ;;
+               esac
+             fi
+             if test -n "$a_deplib" ; then
+               libname=`eval "\\$ECHO \"$libname_spec\""`
+               for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do
+                 potential_libs=`ls $i/$libname[.-]* 2>/dev/null`
+                 for potent_lib in $potential_libs; do
+                   potlib="$potent_lib" # see symlink-check above in file_magic test
+                   if eval "\$ECHO \"$potent_lib\"" 2>/dev/null | $SED 10q | \
+                      $EGREP "$match_pattern_regex" > /dev/null; then
+                     func_append newdeplibs " $a_deplib"
+                     a_deplib=""
+                     break 2
+                   fi
+                 done
+               done
+             fi
+             if test -n "$a_deplib" ; then
+               droppeddeps=yes
+               echo
+               $ECHO "*** Warning: linker path does not have real file for library $a_deplib."
+               echo "*** I have the capability to make that library automatically link in when"
+               echo "*** you link to this library.  But I can only do this if you have a"
+               echo "*** shared version of the library, which you do not appear to have"
+               echo "*** because I did check the linker path looking for a file starting"
+               if test -z "$potlib" ; then
+                 $ECHO "*** with $libname but no candidates were found. (...for regex pattern test)"
+               else
+                 $ECHO "*** with $libname and none of the candidates passed a file format test"
+                 $ECHO "*** using a regex pattern. Last file checked: $potlib"
+               fi
+             fi
+             ;;
+           *)
+             # Add a -L argument.
+             func_append newdeplibs " $a_deplib"
+             ;;
+           esac
+         done # Gone through all deplibs.
+         ;;
+       none | unknown | *)
+         newdeplibs=""
+         tmp_deplibs=`$ECHO " $deplibs" | $SED 's/ -lc$//; s/ -[LR][^ ]*//g'`
+         if test "X$allow_libtool_libs_with_static_runtimes" = "Xyes" ; then
+           for i in $predeps $postdeps ; do
+             # can't use Xsed below, because $i might contain '/'
+             tmp_deplibs=`$ECHO " $tmp_deplibs" | $SED "s,$i,,"`
+           done
+         fi
+         case $tmp_deplibs in
+         *[!\  \ ]*)
+           echo
+           if test "X$deplibs_check_method" = "Xnone"; then
+             echo "*** Warning: inter-library dependencies are not supported in this platform."
+           else
+             echo "*** Warning: inter-library dependencies are not known to be supported."
+           fi
+           echo "*** All declared inter-library dependencies are being dropped."
+           droppeddeps=yes
+           ;;
+         esac
+         ;;
+       esac
+       versuffix=$versuffix_save
+       major=$major_save
+       release=$release_save
+       libname=$libname_save
+       name=$name_save
+
+       case $host in
+       *-*-rhapsody* | *-*-darwin1.[012])
+         # On Rhapsody replace the C library with the System framework
+         newdeplibs=`$ECHO " $newdeplibs" | $SED 's/ -lc / System.ltframework /'`
+         ;;
+       esac
+
+       if test "$droppeddeps" = yes; then
+         if test "$module" = yes; then
+           echo
+           echo "*** Warning: libtool could not satisfy all declared inter-library"
+           $ECHO "*** dependencies of module $libname.  Therefore, libtool will create"
+           echo "*** a static module, that should work as long as the dlopening"
+           echo "*** application is linked with the -dlopen flag."
+           if test -z "$global_symbol_pipe"; then
+             echo
+             echo "*** However, this would only work if libtool was able to extract symbol"
+             echo "*** lists from a program, using \`nm' or equivalent, but libtool could"
+             echo "*** not find such a program.  So, this module is probably useless."
+             echo "*** \`nm' from GNU binutils and a full rebuild may help."
+           fi
+           if test "$build_old_libs" = no; then
+             oldlibs="$output_objdir/$libname.$libext"
+             build_libtool_libs=module
+             build_old_libs=yes
+           else
+             build_libtool_libs=no
+           fi
+         else
+           echo "*** The inter-library dependencies that have been dropped here will be"
+           echo "*** automatically added whenever a program is linked with this library"
+           echo "*** or is declared to -dlopen it."
+
+           if test "$allow_undefined" = no; then
+             echo
+             echo "*** Since this library must not contain undefined symbols,"
+             echo "*** because either the platform does not support them or"
+             echo "*** it was explicitly requested with -no-undefined,"
+             echo "*** libtool will only create a static version of it."
+             if test "$build_old_libs" = no; then
+               oldlibs="$output_objdir/$libname.$libext"
+               build_libtool_libs=module
+               build_old_libs=yes
+             else
+               build_libtool_libs=no
+             fi
+           fi
+         fi
+       fi
+       # Done checking deplibs!
+       deplibs=$newdeplibs
+      fi
+      # Time to change all our "foo.ltframework" stuff back to "-framework foo"
+      case $host in
+       *-*-darwin*)
+         newdeplibs=`$ECHO " $newdeplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+         new_inherited_linker_flags=`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+         deplibs=`$ECHO " $deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+         ;;
+      esac
+
+      # move library search paths that coincide with paths to not yet
+      # installed libraries to the beginning of the library search list
+      new_libs=
+      for path in $notinst_path; do
+       case " $new_libs " in
+       *" -L$path/$objdir "*) ;;
+       *)
+         case " $deplibs " in
+         *" -L$path/$objdir "*)
+           func_append new_libs " -L$path/$objdir" ;;
+         esac
+         ;;
+       esac
+      done
+      for deplib in $deplibs; do
+       case $deplib in
+       -L*)
+         case " $new_libs " in
+         *" $deplib "*) ;;
+         *) func_append new_libs " $deplib" ;;
+         esac
+         ;;
+       *) func_append new_libs " $deplib" ;;
+       esac
+      done
+      deplibs="$new_libs"
+
+      # All the library-specific variables (install_libdir is set above).
+      library_names=
+      old_library=
+      dlname=
+
+      # Test again, we may have decided not to build it any more
+      if test "$build_libtool_libs" = yes; then
+       # Remove ${wl} instances when linking with ld.
+       # FIXME: should test the right _cmds variable.
+       case $archive_cmds in
+         *\$LD\ *) wl= ;;
+        esac
+       if test "$hardcode_into_libs" = yes; then
+         # Hardcode the library paths
+         hardcode_libdirs=
+         dep_rpath=
+         rpath="$finalize_rpath"
+         test "$opt_mode" != relink && rpath="$compile_rpath$rpath"
+         for libdir in $rpath; do
+           if test -n "$hardcode_libdir_flag_spec"; then
+             if test -n "$hardcode_libdir_separator"; then
+               func_replace_sysroot "$libdir"
+               libdir=$func_replace_sysroot_result
+               if test -z "$hardcode_libdirs"; then
+                 hardcode_libdirs="$libdir"
+               else
+                 # Just accumulate the unique libdirs.
+                 case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in
+                 *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*)
+                   ;;
+                 *)
+                   func_append hardcode_libdirs "$hardcode_libdir_separator$libdir"
+                   ;;
+                 esac
+               fi
+             else
+               eval flag=\"$hardcode_libdir_flag_spec\"
+               func_append dep_rpath " $flag"
+             fi
+           elif test -n "$runpath_var"; then
+             case "$perm_rpath " in
+             *" $libdir "*) ;;
+             *) func_append perm_rpath " $libdir" ;;
+             esac
+           fi
+         done
+         # Substitute the hardcoded libdirs into the rpath.
+         if test -n "$hardcode_libdir_separator" &&
+            test -n "$hardcode_libdirs"; then
+           libdir="$hardcode_libdirs"
+           eval "dep_rpath=\"$hardcode_libdir_flag_spec\""
+         fi
+         if test -n "$runpath_var" && test -n "$perm_rpath"; then
+           # We should set the runpath_var.
+           rpath=
+           for dir in $perm_rpath; do
+             func_append rpath "$dir:"
+           done
+           eval "$runpath_var='$rpath\$$runpath_var'; export $runpath_var"
+         fi
+         test -n "$dep_rpath" && deplibs="$dep_rpath $deplibs"
+       fi
+
+       shlibpath="$finalize_shlibpath"
+       test "$opt_mode" != relink && shlibpath="$compile_shlibpath$shlibpath"
+       if test -n "$shlibpath"; then
+         eval "$shlibpath_var='$shlibpath\$$shlibpath_var'; export $shlibpath_var"
+       fi
+
+       # Get the real and link names of the library.
+       eval shared_ext=\"$shrext_cmds\"
+       eval library_names=\"$library_names_spec\"
+       set dummy $library_names
+       shift
+       realname="$1"
+       shift
+
+       if test -n "$soname_spec"; then
+         eval soname=\"$soname_spec\"
+       else
+         soname="$realname"
+       fi
+       if test -z "$dlname"; then
+         dlname=$soname
+       fi
+
+       lib="$output_objdir/$realname"
+       linknames=
+       for link
+       do
+         func_append linknames " $link"
+       done
+
+       # Use standard objects if they are pic
+       test -z "$pic_flag" && libobjs=`$ECHO "$libobjs" | $SP2NL | $SED "$lo2o" | $NL2SP`
+       test "X$libobjs" = "X " && libobjs=
+
+       delfiles=
+       if test -n "$export_symbols" && test -n "$include_expsyms"; then
+         $opt_dry_run || cp "$export_symbols" "$output_objdir/$libname.uexp"
+         export_symbols="$output_objdir/$libname.uexp"
+         func_append delfiles " $export_symbols"
+       fi
+
+       orig_export_symbols=
+       case $host_os in
+       cygwin* | mingw* | cegcc*)
+         if test -n "$export_symbols" && test -z "$export_symbols_regex"; then
+           # exporting using user supplied symfile
+           if test "x`$SED 1q $export_symbols`" != xEXPORTS; then
+             # and it's NOT already a .def file. Must figure out
+             # which of the given symbols are data symbols and tag
+             # them as such. So, trigger use of export_symbols_cmds.
+             # export_symbols gets reassigned inside the "prepare
+             # the list of exported symbols" if statement, so the
+             # include_expsyms logic still works.
+             orig_export_symbols="$export_symbols"
+             export_symbols=
+             always_export_symbols=yes
+           fi
+         fi
+         ;;
+       esac
+
+       # Prepare the list of exported symbols
+       if test -z "$export_symbols"; then
+         if test "$always_export_symbols" = yes || test -n "$export_symbols_regex"; then
+           func_verbose "generating symbol list for \`$libname.la'"
+           export_symbols="$output_objdir/$libname.exp"
+           $opt_dry_run || $RM $export_symbols
+           cmds=$export_symbols_cmds
+           save_ifs="$IFS"; IFS='~'
+           for cmd1 in $cmds; do
+             IFS="$save_ifs"
+             # Take the normal branch if the nm_file_list_spec branch
+             # doesn't work or if tool conversion is not needed.
+             case $nm_file_list_spec~$to_tool_file_cmd in
+               *~func_convert_file_noop | *~func_convert_file_msys_to_w32 | ~*)
+                 try_normal_branch=yes
+                 eval cmd=\"$cmd1\"
+                 func_len " $cmd"
+                 len=$func_len_result
+                 ;;
+               *)
+                 try_normal_branch=no
+                 ;;
+             esac
+             if test "$try_normal_branch" = yes \
+                && { test "$len" -lt "$max_cmd_len" \
+                     || test "$max_cmd_len" -le -1; }
+             then
+               func_show_eval "$cmd" 'exit $?'
+               skipped_export=false
+             elif test -n "$nm_file_list_spec"; then
+               func_basename "$output"
+               output_la=$func_basename_result
+               save_libobjs=$libobjs
+               save_output=$output
+               output=${output_objdir}/${output_la}.nm
+               func_to_tool_file "$output"
+               libobjs=$nm_file_list_spec$func_to_tool_file_result
+               func_append delfiles " $output"
+               func_verbose "creating $NM input file list: $output"
+               for obj in $save_libobjs; do
+                 func_to_tool_file "$obj"
+                 $ECHO "$func_to_tool_file_result"
+               done > "$output"
+               eval cmd=\"$cmd1\"
+               func_show_eval "$cmd" 'exit $?'
+               output=$save_output
+               libobjs=$save_libobjs
+               skipped_export=false
+             else
+               # The command line is too long to execute in one step.
+               func_verbose "using reloadable object file for export list..."
+               skipped_export=:
+               # Break out early, otherwise skipped_export may be
+               # set to false by a later but shorter cmd.
+               break
+             fi
+           done
+           IFS="$save_ifs"
+           if test -n "$export_symbols_regex" && test "X$skipped_export" != "X:"; then
+             func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"'
+             func_show_eval '$MV "${export_symbols}T" "$export_symbols"'
+           fi
+         fi
+       fi
+
+       if test -n "$export_symbols" && test -n "$include_expsyms"; then
+         tmp_export_symbols="$export_symbols"
+         test -n "$orig_export_symbols" && tmp_export_symbols="$orig_export_symbols"
+         $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"'
+       fi
+
+       if test "X$skipped_export" != "X:" && test -n "$orig_export_symbols"; then
+         # The given exports_symbols file has to be filtered, so filter it.
+         func_verbose "filter symbol list for \`$libname.la' to tag DATA exports"
+         # FIXME: $output_objdir/$libname.filter potentially contains lots of
+         # 's' commands which not all seds can handle. GNU sed should be fine
+         # though. Also, the filter scales superlinearly with the number of
+         # global variables. join(1) would be nice here, but unfortunately
+         # isn't a blessed tool.
+         $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter
+         func_append delfiles " $export_symbols $output_objdir/$libname.filter"
+         export_symbols=$output_objdir/$libname.def
+         $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols
+       fi
+
+       tmp_deplibs=
+       for test_deplib in $deplibs; do
+         case " $convenience " in
+         *" $test_deplib "*) ;;
+         *)
+           func_append tmp_deplibs " $test_deplib"
+           ;;
+         esac
+       done
+       deplibs="$tmp_deplibs"
+
+       if test -n "$convenience"; then
+         if test -n "$whole_archive_flag_spec" &&
+           test "$compiler_needs_object" = yes &&
+           test -z "$libobjs"; then
+           # extract the archives, so we have objects to list.
+           # TODO: could optimize this to just extract one archive.
+           whole_archive_flag_spec=
+         fi
+         if test -n "$whole_archive_flag_spec"; then
+           save_libobjs=$libobjs
+           eval libobjs=\"\$libobjs $whole_archive_flag_spec\"
+           test "X$libobjs" = "X " && libobjs=
+         else
+           gentop="$output_objdir/${outputname}x"
+           func_append generated " $gentop"
+
+           func_extract_archives $gentop $convenience
+           func_append libobjs " $func_extract_archives_result"
+           test "X$libobjs" = "X " && libobjs=
+         fi
+       fi
+
+       if test "$thread_safe" = yes && test -n "$thread_safe_flag_spec"; then
+         eval flag=\"$thread_safe_flag_spec\"
+         func_append linker_flags " $flag"
+       fi
+
+       # Make a backup of the uninstalled library when relinking
+       if test "$opt_mode" = relink; then
+         $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}U && $MV $realname ${realname}U)' || exit $?
+       fi
+
+       # Do each of the archive commands.
+       if test "$module" = yes && test -n "$module_cmds" ; then
+         if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then
+           eval test_cmds=\"$module_expsym_cmds\"
+           cmds=$module_expsym_cmds
+         else
+           eval test_cmds=\"$module_cmds\"
+           cmds=$module_cmds
+         fi
+       else
+         if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then
+           eval test_cmds=\"$archive_expsym_cmds\"
+           cmds=$archive_expsym_cmds
+         else
+           eval test_cmds=\"$archive_cmds\"
+           cmds=$archive_cmds
+         fi
+       fi
+
+       if test "X$skipped_export" != "X:" &&
+          func_len " $test_cmds" &&
+          len=$func_len_result &&
+          test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then
+         :
+       else
+         # The command line is too long to link in one step, link piecewise
+         # or, if using GNU ld and skipped_export is not :, use a linker
+         # script.
+
+         # Save the value of $output and $libobjs because we want to
+         # use them later.  If we have whole_archive_flag_spec, we
+         # want to use save_libobjs as it was before
+         # whole_archive_flag_spec was expanded, because we can't
+         # assume the linker understands whole_archive_flag_spec.
+         # This may have to be revisited, in case too many
+         # convenience libraries get linked in and end up exceeding
+         # the spec.
+         if test -z "$convenience" || test -z "$whole_archive_flag_spec"; then
+           save_libobjs=$libobjs
+         fi
+         save_output=$output
+         func_basename "$output"
+         output_la=$func_basename_result
+
+         # Clear the reloadable object creation command queue and
+         # initialize k to one.
+         test_cmds=
+         concat_cmds=
+         objlist=
+         last_robj=
+         k=1
+
+         if test -n "$save_libobjs" && test "X$skipped_export" != "X:" && test "$with_gnu_ld" = yes; then
+           output=${output_objdir}/${output_la}.lnkscript
+           func_verbose "creating GNU ld script: $output"
+           echo 'INPUT (' > $output
+           for obj in $save_libobjs
+           do
+             func_to_tool_file "$obj"
+             $ECHO "$func_to_tool_file_result" >> $output
+           done
+           echo ')' >> $output
+           func_append delfiles " $output"
+           func_to_tool_file "$output"
+           output=$func_to_tool_file_result
+         elif test -n "$save_libobjs" && test "X$skipped_export" != "X:" && test "X$file_list_spec" != X; then
+           output=${output_objdir}/${output_la}.lnk
+           func_verbose "creating linker input file list: $output"
+           : > $output
+           set x $save_libobjs
+           shift
+           firstobj=
+           if test "$compiler_needs_object" = yes; then
+             firstobj="$1 "
+             shift
+           fi
+           for obj
+           do
+             func_to_tool_file "$obj"
+             $ECHO "$func_to_tool_file_result" >> $output
+           done
+           func_append delfiles " $output"
+           func_to_tool_file "$output"
+           output=$firstobj\"$file_list_spec$func_to_tool_file_result\"
+         else
+           if test -n "$save_libobjs"; then
+             func_verbose "creating reloadable object files..."
+             output=$output_objdir/$output_la-${k}.$objext
+             eval test_cmds=\"$reload_cmds\"
+             func_len " $test_cmds"
+             len0=$func_len_result
+             len=$len0
+
+             # Loop over the list of objects to be linked.
+             for obj in $save_libobjs
+             do
+               func_len " $obj"
+               func_arith $len + $func_len_result
+               len=$func_arith_result
+               if test "X$objlist" = X ||
+                  test "$len" -lt "$max_cmd_len"; then
+                 func_append objlist " $obj"
+               else
+                 # The command $test_cmds is almost too long, add a
+                 # command to the queue.
+                 if test "$k" -eq 1 ; then
+                   # The first file doesn't have a previous command to add.
+                   reload_objs=$objlist
+                   eval concat_cmds=\"$reload_cmds\"
+                 else
+                   # All subsequent reloadable object files will link in
+                   # the last one created.
+                   reload_objs="$objlist $last_robj"
+                   eval concat_cmds=\"\$concat_cmds~$reload_cmds~\$RM $last_robj\"
+                 fi
+                 last_robj=$output_objdir/$output_la-${k}.$objext
+                 func_arith $k + 1
+                 k=$func_arith_result
+                 output=$output_objdir/$output_la-${k}.$objext
+                 objlist=" $obj"
+                 func_len " $last_robj"
+                 func_arith $len0 + $func_len_result
+                 len=$func_arith_result
+               fi
+             done
+             # Handle the remaining objects by creating one last
+             # reloadable object file.  All subsequent reloadable object
+             # files will link in the last one created.
+             test -z "$concat_cmds" || concat_cmds=$concat_cmds~
+             reload_objs="$objlist $last_robj"
+             eval concat_cmds=\"\${concat_cmds}$reload_cmds\"
+             if test -n "$last_robj"; then
+               eval concat_cmds=\"\${concat_cmds}~\$RM $last_robj\"
+             fi
+             func_append delfiles " $output"
+
+           else
+             output=
+           fi
+
+           if ${skipped_export-false}; then
+             func_verbose "generating symbol list for \`$libname.la'"
+             export_symbols="$output_objdir/$libname.exp"
+             $opt_dry_run || $RM $export_symbols
+             libobjs=$output
+             # Append the command to create the export file.
+             test -z "$concat_cmds" || concat_cmds=$concat_cmds~
+             eval concat_cmds=\"\$concat_cmds$export_symbols_cmds\"
+             if test -n "$last_robj"; then
+               eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\"
+             fi
+           fi
+
+           test -n "$save_libobjs" &&
+             func_verbose "creating a temporary reloadable object file: $output"
+
+           # Loop through the commands generated above and execute them.
+           save_ifs="$IFS"; IFS='~'
+           for cmd in $concat_cmds; do
+             IFS="$save_ifs"
+             $opt_silent || {
+                 func_quote_for_expand "$cmd"
+                 eval "func_echo $func_quote_for_expand_result"
+             }
+             $opt_dry_run || eval "$cmd" || {
+               lt_exit=$?
+
+               # Restore the uninstalled library and exit
+               if test "$opt_mode" = relink; then
+                 ( cd "$output_objdir" && \
+                   $RM "${realname}T" && \
+                   $MV "${realname}U" "$realname" )
+               fi
+
+               exit $lt_exit
+             }
+           done
+           IFS="$save_ifs"
+
+           if test -n "$export_symbols_regex" && ${skipped_export-false}; then
+             func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"'
+             func_show_eval '$MV "${export_symbols}T" "$export_symbols"'
+           fi
+         fi
+
+          if ${skipped_export-false}; then
+           if test -n "$export_symbols" && test -n "$include_expsyms"; then
+             tmp_export_symbols="$export_symbols"
+             test -n "$orig_export_symbols" && tmp_export_symbols="$orig_export_symbols"
+             $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"'
+           fi
+
+           if test -n "$orig_export_symbols"; then
+             # The given exports_symbols file has to be filtered, so filter it.
+             func_verbose "filter symbol list for \`$libname.la' to tag DATA exports"
+             # FIXME: $output_objdir/$libname.filter potentially contains lots of
+             # 's' commands which not all seds can handle. GNU sed should be fine
+             # though. Also, the filter scales superlinearly with the number of
+             # global variables. join(1) would be nice here, but unfortunately
+             # isn't a blessed tool.
+             $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter
+             func_append delfiles " $export_symbols $output_objdir/$libname.filter"
+             export_symbols=$output_objdir/$libname.def
+             $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols
+           fi
+         fi
+
+         libobjs=$output
+         # Restore the value of output.
+         output=$save_output
+
+         if test -n "$convenience" && test -n "$whole_archive_flag_spec"; then
+           eval libobjs=\"\$libobjs $whole_archive_flag_spec\"
+           test "X$libobjs" = "X " && libobjs=
+         fi
+         # Expand the library linking commands again to reset the
+         # value of $libobjs for piecewise linking.
+
+         # Do each of the archive commands.
+         if test "$module" = yes && test -n "$module_cmds" ; then
+           if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then
+             cmds=$module_expsym_cmds
+           else
+             cmds=$module_cmds
+           fi
+         else
+           if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then
+             cmds=$archive_expsym_cmds
+           else
+             cmds=$archive_cmds
+           fi
+         fi
+       fi
+
+       if test -n "$delfiles"; then
+         # Append the command to remove temporary files to $cmds.
+         eval cmds=\"\$cmds~\$RM $delfiles\"
+       fi
+
+       # Add any objects from preloaded convenience libraries
+       if test -n "$dlprefiles"; then
+         gentop="$output_objdir/${outputname}x"
+         func_append generated " $gentop"
+
+         func_extract_archives $gentop $dlprefiles
+         func_append libobjs " $func_extract_archives_result"
+         test "X$libobjs" = "X " && libobjs=
+       fi
+
+       save_ifs="$IFS"; IFS='~'
+       for cmd in $cmds; do
+         IFS="$save_ifs"
+         eval cmd=\"$cmd\"
+         $opt_silent || {
+           func_quote_for_expand "$cmd"
+           eval "func_echo $func_quote_for_expand_result"
+         }
+         $opt_dry_run || eval "$cmd" || {
+           lt_exit=$?
+
+           # Restore the uninstalled library and exit
+           if test "$opt_mode" = relink; then
+             ( cd "$output_objdir" && \
+               $RM "${realname}T" && \
+               $MV "${realname}U" "$realname" )
+           fi
+
+           exit $lt_exit
+         }
+       done
+       IFS="$save_ifs"
+
+       # Restore the uninstalled library and exit
+       if test "$opt_mode" = relink; then
+         $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}T && $MV $realname ${realname}T && $MV ${realname}U $realname)' || exit $?
+
+         if test -n "$convenience"; then
+           if test -z "$whole_archive_flag_spec"; then
+             func_show_eval '${RM}r "$gentop"'
+           fi
+         fi
+
+         exit $EXIT_SUCCESS
+       fi
+
+       # Create links to the real library.
+       for linkname in $linknames; do
+         if test "$realname" != "$linkname"; then
+           func_show_eval '(cd "$output_objdir" && $RM "$linkname" && $LN_S "$realname" "$linkname")' 'exit $?'
+         fi
+       done
+
+       # If -module or -export-dynamic was specified, set the dlname.
+       if test "$module" = yes || test "$export_dynamic" = yes; then
+         # On all known operating systems, these are identical.
+         dlname="$soname"
+       fi
+      fi
+      ;;
+
+    obj)
+      if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then
+       func_warning "\`-dlopen' is ignored for objects"
+      fi
+
+      case " $deplibs" in
+      *\ -l* | *\ -L*)
+       func_warning "\`-l' and \`-L' are ignored for objects" ;;
+      esac
+
+      test -n "$rpath" && \
+       func_warning "\`-rpath' is ignored for objects"
+
+      test -n "$xrpath" && \
+       func_warning "\`-R' is ignored for objects"
+
+      test -n "$vinfo" && \
+       func_warning "\`-version-info' is ignored for objects"
+
+      test -n "$release" && \
+       func_warning "\`-release' is ignored for objects"
+
+      case $output in
+      *.lo)
+       test -n "$objs$old_deplibs" && \
+         func_fatal_error "cannot build library object \`$output' from non-libtool objects"
+
+       libobj=$output
+       func_lo2o "$libobj"
+       obj=$func_lo2o_result
+       ;;
+      *)
+       libobj=
+       obj="$output"
+       ;;
+      esac
+
+      # Delete the old objects.
+      $opt_dry_run || $RM $obj $libobj
+
+      # Objects from convenience libraries.  This assumes
+      # single-version convenience libraries.  Whenever we create
+      # different ones for PIC/non-PIC, this we'll have to duplicate
+      # the extraction.
+      reload_conv_objs=
+      gentop=
+      # reload_cmds runs $LD directly, so let us get rid of
+      # -Wl from whole_archive_flag_spec and hope we can get by with
+      # turning comma into space..
+      wl=
+
+      if test -n "$convenience"; then
+       if test -n "$whole_archive_flag_spec"; then
+         eval tmp_whole_archive_flags=\"$whole_archive_flag_spec\"
+         reload_conv_objs=$reload_objs\ `$ECHO "$tmp_whole_archive_flags" | $SED 's|,| |g'`
+       else
+         gentop="$output_objdir/${obj}x"
+         func_append generated " $gentop"
+
+         func_extract_archives $gentop $convenience
+         reload_conv_objs="$reload_objs $func_extract_archives_result"
+       fi
+      fi
+
+      # If we're not building shared, we need to use non_pic_objs
+      test "$build_libtool_libs" != yes && libobjs="$non_pic_objects"
+
+      # Create the old-style object.
+      reload_objs="$objs$old_deplibs "`$ECHO "$libobjs" | $SP2NL | $SED "/\.${libext}$/d; /\.lib$/d; $lo2o" | $NL2SP`" $reload_conv_objs" ### testsuite: skip nested quoting test
+
+      output="$obj"
+      func_execute_cmds "$reload_cmds" 'exit $?'
+
+      # Exit if we aren't doing a library object file.
+      if test -z "$libobj"; then
+       if test -n "$gentop"; then
+         func_show_eval '${RM}r "$gentop"'
+       fi
+
+       exit $EXIT_SUCCESS
+      fi
+
+      if test "$build_libtool_libs" != yes; then
+       if test -n "$gentop"; then
+         func_show_eval '${RM}r "$gentop"'
+       fi
+
+       # Create an invalid libtool object if no PIC, so that we don't
+       # accidentally link it into a program.
+       # $show "echo timestamp > $libobj"
+       # $opt_dry_run || eval "echo timestamp > $libobj" || exit $?
+       exit $EXIT_SUCCESS
+      fi
+
+      if test -n "$pic_flag" || test "$pic_mode" != default; then
+       # Only do commands if we really have different PIC objects.
+       reload_objs="$libobjs $reload_conv_objs"
+       output="$libobj"
+       func_execute_cmds "$reload_cmds" 'exit $?'
+      fi
+
+      if test -n "$gentop"; then
+       func_show_eval '${RM}r "$gentop"'
+      fi
+
+      exit $EXIT_SUCCESS
+      ;;
+
+    prog)
+      case $host in
+       *cygwin*) func_stripname '' '.exe' "$output"
+                 output=$func_stripname_result.exe;;
+      esac
+      test -n "$vinfo" && \
+       func_warning "\`-version-info' is ignored for programs"
+
+      test -n "$release" && \
+       func_warning "\`-release' is ignored for programs"
+
+      test "$preload" = yes \
+        && test "$dlopen_support" = unknown \
+       && test "$dlopen_self" = unknown \
+       && test "$dlopen_self_static" = unknown && \
+         func_warning "\`LT_INIT([dlopen])' not used. Assuming no dlopen support."
+
+      case $host in
+      *-*-rhapsody* | *-*-darwin1.[012])
+       # On Rhapsody replace the C library is the System framework
+       compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's/ -lc / System.ltframework /'`
+       finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's/ -lc / System.ltframework /'`
+       ;;
+      esac
+
+      case $host in
+      *-*-darwin*)
+       # Don't allow lazy linking, it breaks C++ global constructors
+       # But is supposedly fixed on 10.4 or later (yay!).
+       if test "$tagname" = CXX ; then
+         case ${MACOSX_DEPLOYMENT_TARGET-10.0} in
+           10.[0123])
+             func_append compile_command " ${wl}-bind_at_load"
+             func_append finalize_command " ${wl}-bind_at_load"
+           ;;
+         esac
+       fi
+       # Time to change all our "foo.ltframework" stuff back to "-framework foo"
+       compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+       finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'`
+       ;;
+      esac
+
+
+      # move library search paths that coincide with paths to not yet
+      # installed libraries to the beginning of the library search list
+      new_libs=
+      for path in $notinst_path; do
+       case " $new_libs " in
+       *" -L$path/$objdir "*) ;;
+       *)
+         case " $compile_deplibs " in
+         *" -L$path/$objdir "*)
+           func_append new_libs " -L$path/$objdir" ;;
+         esac
+         ;;
+       esac
+      done
+      for deplib in $compile_deplibs; do
+       case $deplib in
+       -L*)
+         case " $new_libs " in
+         *" $deplib "*) ;;
+         *) func_append new_libs " $deplib" ;;
+         esac
+         ;;
+       *) func_append new_libs " $deplib" ;;
+       esac
+      done
+      compile_deplibs="$new_libs"
+
+
+      func_append compile_command " $compile_deplibs"
+      func_append finalize_command " $finalize_deplibs"
+
+      if test -n "$rpath$xrpath"; then
+       # If the user specified any rpath flags, then add them.
+       for libdir in $rpath $xrpath; do
+         # This is the magic to use -rpath.
+         case "$finalize_rpath " in
+         *" $libdir "*) ;;
+         *) func_append finalize_rpath " $libdir" ;;
+         esac
+       done
+      fi
+
+      # Now hardcode the library paths
+      rpath=
+      hardcode_libdirs=
+      for libdir in $compile_rpath $finalize_rpath; do
+       if test -n "$hardcode_libdir_flag_spec"; then
+         if test -n "$hardcode_libdir_separator"; then
+           if test -z "$hardcode_libdirs"; then
+             hardcode_libdirs="$libdir"
+           else
+             # Just accumulate the unique libdirs.
+             case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in
+             *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*)
+               ;;
+             *)
+               func_append hardcode_libdirs "$hardcode_libdir_separator$libdir"
+               ;;
+             esac
+           fi
+         else
+           eval flag=\"$hardcode_libdir_flag_spec\"
+           func_append rpath " $flag"
+         fi
+       elif test -n "$runpath_var"; then
+         case "$perm_rpath " in
+         *" $libdir "*) ;;
+         *) func_append perm_rpath " $libdir" ;;
+         esac
+       fi
+       case $host in
+       *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*)
+         testbindir=`${ECHO} "$libdir" | ${SED} -e 's*/lib$*/bin*'`
+         case :$dllsearchpath: in
+         *":$libdir:"*) ;;
+         ::) dllsearchpath=$libdir;;
+         *) func_append dllsearchpath ":$libdir";;
+         esac
+         case :$dllsearchpath: in
+         *":$testbindir:"*) ;;
+         ::) dllsearchpath=$testbindir;;
+         *) func_append dllsearchpath ":$testbindir";;
+         esac
+         ;;
+       esac
+      done
+      # Substitute the hardcoded libdirs into the rpath.
+      if test -n "$hardcode_libdir_separator" &&
+        test -n "$hardcode_libdirs"; then
+       libdir="$hardcode_libdirs"
+       eval rpath=\" $hardcode_libdir_flag_spec\"
+      fi
+      compile_rpath="$rpath"
+
+      rpath=
+      hardcode_libdirs=
+      for libdir in $finalize_rpath; do
+       if test -n "$hardcode_libdir_flag_spec"; then
+         if test -n "$hardcode_libdir_separator"; then
+           if test -z "$hardcode_libdirs"; then
+             hardcode_libdirs="$libdir"
+           else
+             # Just accumulate the unique libdirs.
+             case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in
+             *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*)
+               ;;
+             *)
+               func_append hardcode_libdirs "$hardcode_libdir_separator$libdir"
+               ;;
+             esac
+           fi
+         else
+           eval flag=\"$hardcode_libdir_flag_spec\"
+           func_append rpath " $flag"
+         fi
+       elif test -n "$runpath_var"; then
+         case "$finalize_perm_rpath " in
+         *" $libdir "*) ;;
+         *) func_append finalize_perm_rpath " $libdir" ;;
+         esac
+       fi
+      done
+      # Substitute the hardcoded libdirs into the rpath.
+      if test -n "$hardcode_libdir_separator" &&
+        test -n "$hardcode_libdirs"; then
+       libdir="$hardcode_libdirs"
+       eval rpath=\" $hardcode_libdir_flag_spec\"
+      fi
+      finalize_rpath="$rpath"
+
+      if test -n "$libobjs" && test "$build_old_libs" = yes; then
+       # Transform all the library objects into standard objects.
+       compile_command=`$ECHO "$compile_command" | $SP2NL | $SED "$lo2o" | $NL2SP`
+       finalize_command=`$ECHO "$finalize_command" | $SP2NL | $SED "$lo2o" | $NL2SP`
+      fi
+
+      func_generate_dlsyms "$outputname" "@PROGRAM@" "no"
+
+      # template prelinking step
+      if test -n "$prelink_cmds"; then
+       func_execute_cmds "$prelink_cmds" 'exit $?'
+      fi
+
+      wrappers_required=yes
+      case $host in
+      *cegcc* | *mingw32ce*)
+        # Disable wrappers for cegcc and mingw32ce hosts, we are cross compiling anyway.
+        wrappers_required=no
+        ;;
+      *cygwin* | *mingw* )
+        if test "$build_libtool_libs" != yes; then
+          wrappers_required=no
+        fi
+        ;;
+      *)
+        if test "$need_relink" = no || test "$build_libtool_libs" != yes; then
+          wrappers_required=no
+        fi
+        ;;
+      esac
+      if test "$wrappers_required" = no; then
+       # Replace the output file specification.
+       compile_command=`$ECHO "$compile_command" | $SED 's%@OUTPUT@%'"$output"'%g'`
+       link_command="$compile_command$compile_rpath"
+
+       # We have no uninstalled library dependencies, so finalize right now.
+       exit_status=0
+       func_show_eval "$link_command" 'exit_status=$?'
+
+       if test -n "$postlink_cmds"; then
+         func_to_tool_file "$output"
+         postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'`
+         func_execute_cmds "$postlink_cmds" 'exit $?'
+       fi
+
+       # Delete the generated files.
+       if test -f "$output_objdir/${outputname}S.${objext}"; then
+         func_show_eval '$RM "$output_objdir/${outputname}S.${objext}"'
+       fi
+
+       exit $exit_status
+      fi
+
+      if test -n "$compile_shlibpath$finalize_shlibpath"; then
+       compile_command="$shlibpath_var=\"$compile_shlibpath$finalize_shlibpath\$$shlibpath_var\" $compile_command"
+      fi
+      if test -n "$finalize_shlibpath"; then
+       finalize_command="$shlibpath_var=\"$finalize_shlibpath\$$shlibpath_var\" $finalize_command"
+      fi
+
+      compile_var=
+      finalize_var=
+      if test -n "$runpath_var"; then
+       if test -n "$perm_rpath"; then
+         # We should set the runpath_var.
+         rpath=
+         for dir in $perm_rpath; do
+           func_append rpath "$dir:"
+         done
+         compile_var="$runpath_var=\"$rpath\$$runpath_var\" "
+       fi
+       if test -n "$finalize_perm_rpath"; then
+         # We should set the runpath_var.
+         rpath=
+         for dir in $finalize_perm_rpath; do
+           func_append rpath "$dir:"
+         done
+         finalize_var="$runpath_var=\"$rpath\$$runpath_var\" "
+       fi
+      fi
+
+      if test "$no_install" = yes; then
+       # We don't need to create a wrapper script.
+       link_command="$compile_var$compile_command$compile_rpath"
+       # Replace the output file specification.
+       link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output"'%g'`
+       # Delete the old output file.
+       $opt_dry_run || $RM $output
+       # Link the executable and exit
+       func_show_eval "$link_command" 'exit $?'
+
+       if test -n "$postlink_cmds"; then
+         func_to_tool_file "$output"
+         postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'`
+         func_execute_cmds "$postlink_cmds" 'exit $?'
+       fi
+
+       exit $EXIT_SUCCESS
+      fi
+
+      if test "$hardcode_action" = relink; then
+       # Fast installation is not supported
+       link_command="$compile_var$compile_command$compile_rpath"
+       relink_command="$finalize_var$finalize_command$finalize_rpath"
+
+       func_warning "this platform does not like uninstalled shared libraries"
+       func_warning "\`$output' will be relinked during installation"
+      else
+       if test "$fast_install" != no; then
+         link_command="$finalize_var$compile_command$finalize_rpath"
+         if test "$fast_install" = yes; then
+           relink_command=`$ECHO "$compile_var$compile_command$compile_rpath" | $SED 's%@OUTPUT@%\$progdir/\$file%g'`
+         else
+           # fast_install is set to needless
+           relink_command=
+         fi
+       else
+         link_command="$compile_var$compile_command$compile_rpath"
+         relink_command="$finalize_var$finalize_command$finalize_rpath"
+       fi
+      fi
+
+      # Replace the output file specification.
+      link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output_objdir/$outputname"'%g'`
+
+      # Delete the old output files.
+      $opt_dry_run || $RM $output $output_objdir/$outputname $output_objdir/lt-$outputname
+
+      func_show_eval "$link_command" 'exit $?'
+
+      if test -n "$postlink_cmds"; then
+       func_to_tool_file "$output_objdir/$outputname"
+       postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output_objdir/$outputname"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'`
+       func_execute_cmds "$postlink_cmds" 'exit $?'
+      fi
+
+      # Now create the wrapper script.
+      func_verbose "creating $output"
+
+      # Quote the relink command for shipping.
+      if test -n "$relink_command"; then
+       # Preserve any variables that may affect compiler behavior
+       for var in $variables_saved_for_relink; do
+         if eval test -z \"\${$var+set}\"; then
+           relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command"
+         elif eval var_value=\$$var; test -z "$var_value"; then
+           relink_command="$var=; export $var; $relink_command"
+         else
+           func_quote_for_eval "$var_value"
+           relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command"
+         fi
+       done
+       relink_command="(cd `pwd`; $relink_command)"
+       relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"`
+      fi
+
+      # Only actually do things if not in dry run mode.
+      $opt_dry_run || {
+       # win32 will think the script is a binary if it has
+       # a .exe suffix, so we strip it off here.
+       case $output in
+         *.exe) func_stripname '' '.exe' "$output"
+                output=$func_stripname_result ;;
+       esac
+       # test for cygwin because mv fails w/o .exe extensions
+       case $host in
+         *cygwin*)
+           exeext=.exe
+           func_stripname '' '.exe' "$outputname"
+           outputname=$func_stripname_result ;;
+         *) exeext= ;;
+       esac
+       case $host in
+         *cygwin* | *mingw* )
+           func_dirname_and_basename "$output" "" "."
+           output_name=$func_basename_result
+           output_path=$func_dirname_result
+           cwrappersource="$output_path/$objdir/lt-$output_name.c"
+           cwrapper="$output_path/$output_name.exe"
+           $RM $cwrappersource $cwrapper
+           trap "$RM $cwrappersource $cwrapper; exit $EXIT_FAILURE" 1 2 15
+
+           func_emit_cwrapperexe_src > $cwrappersource
+
+           # The wrapper executable is built using the $host compiler,
+           # because it contains $host paths and files. If cross-
+           # compiling, it, like the target executable, must be
+           # executed on the $host or under an emulation environment.
+           $opt_dry_run || {
+             $LTCC $LTCFLAGS -o $cwrapper $cwrappersource
+             $STRIP $cwrapper
+           }
+
+           # Now, create the wrapper script for func_source use:
+           func_ltwrapper_scriptname $cwrapper
+           $RM $func_ltwrapper_scriptname_result
+           trap "$RM $func_ltwrapper_scriptname_result; exit $EXIT_FAILURE" 1 2 15
+           $opt_dry_run || {
+             # note: this script will not be executed, so do not chmod.
+             if test "x$build" = "x$host" ; then
+               $cwrapper --lt-dump-script > $func_ltwrapper_scriptname_result
+             else
+               func_emit_wrapper no > $func_ltwrapper_scriptname_result
+             fi
+           }
+         ;;
+         * )
+           $RM $output
+           trap "$RM $output; exit $EXIT_FAILURE" 1 2 15
+
+           func_emit_wrapper no > $output
+           chmod +x $output
+         ;;
+       esac
+      }
+      exit $EXIT_SUCCESS
+      ;;
+    esac
+
+    # See if we need to build an old-fashioned archive.
+    for oldlib in $oldlibs; do
+
+      if test "$build_libtool_libs" = convenience; then
+       oldobjs="$libobjs_save $symfileobj"
+       addlibs="$convenience"
+       build_libtool_libs=no
+      else
+       if test "$build_libtool_libs" = module; then
+         oldobjs="$libobjs_save"
+         build_libtool_libs=no
+       else
+         oldobjs="$old_deplibs $non_pic_objects"
+         if test "$preload" = yes && test -f "$symfileobj"; then
+           func_append oldobjs " $symfileobj"
+         fi
+       fi
+       addlibs="$old_convenience"
+      fi
+
+      if test -n "$addlibs"; then
+       gentop="$output_objdir/${outputname}x"
+       func_append generated " $gentop"
+
+       func_extract_archives $gentop $addlibs
+       func_append oldobjs " $func_extract_archives_result"
+      fi
+
+      # Do each command in the archive commands.
+      if test -n "$old_archive_from_new_cmds" && test "$build_libtool_libs" = yes; then
+       cmds=$old_archive_from_new_cmds
+      else
+
+       # Add any objects from preloaded convenience libraries
+       if test -n "$dlprefiles"; then
+         gentop="$output_objdir/${outputname}x"
+         func_append generated " $gentop"
+
+         func_extract_archives $gentop $dlprefiles
+         func_append oldobjs " $func_extract_archives_result"
+       fi
+
+       # POSIX demands no paths to be encoded in archives.  We have
+       # to avoid creating archives with duplicate basenames if we
+       # might have to extract them afterwards, e.g., when creating a
+       # static archive out of a convenience library, or when linking
+       # the entirety of a libtool archive into another (currently
+       # not supported by libtool).
+       if (for obj in $oldobjs
+           do
+             func_basename "$obj"
+             $ECHO "$func_basename_result"
+           done | sort | sort -uc >/dev/null 2>&1); then
+         :
+       else
+         echo "copying selected object files to avoid basename conflicts..."
+         gentop="$output_objdir/${outputname}x"
+         func_append generated " $gentop"
+         func_mkdir_p "$gentop"
+         save_oldobjs=$oldobjs
+         oldobjs=
+         counter=1
+         for obj in $save_oldobjs
+         do
+           func_basename "$obj"
+           objbase="$func_basename_result"
+           case " $oldobjs " in
+           " ") oldobjs=$obj ;;
+           *[\ /]"$objbase "*)
+             while :; do
+               # Make sure we don't pick an alternate name that also
+               # overlaps.
+               newobj=lt$counter-$objbase
+               func_arith $counter + 1
+               counter=$func_arith_result
+               case " $oldobjs " in
+               *[\ /]"$newobj "*) ;;
+               *) if test ! -f "$gentop/$newobj"; then break; fi ;;
+               esac
+             done
+             func_show_eval "ln $obj $gentop/$newobj || cp $obj $gentop/$newobj"
+             func_append oldobjs " $gentop/$newobj"
+             ;;
+           *) func_append oldobjs " $obj" ;;
+           esac
+         done
+       fi
+       func_to_tool_file "$oldlib" func_convert_file_msys_to_w32
+       tool_oldlib=$func_to_tool_file_result
+       eval cmds=\"$old_archive_cmds\"
+
+       func_len " $cmds"
+       len=$func_len_result
+       if test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then
+         cmds=$old_archive_cmds
+       elif test -n "$archiver_list_spec"; then
+         func_verbose "using command file archive linking..."
+         for obj in $oldobjs
+         do
+           func_to_tool_file "$obj"
+           $ECHO "$func_to_tool_file_result"
+         done > $output_objdir/$libname.libcmd
+         func_to_tool_file "$output_objdir/$libname.libcmd"
+         oldobjs=" $archiver_list_spec$func_to_tool_file_result"
+         cmds=$old_archive_cmds
+       else
+         # the command line is too long to link in one step, link in parts
+         func_verbose "using piecewise archive linking..."
+         save_RANLIB=$RANLIB
+         RANLIB=:
+         objlist=
+         concat_cmds=
+         save_oldobjs=$oldobjs
+         oldobjs=
+         # Is there a better way of finding the last object in the list?
+         for obj in $save_oldobjs
+         do
+           last_oldobj=$obj
+         done
+         eval test_cmds=\"$old_archive_cmds\"
+         func_len " $test_cmds"
+         len0=$func_len_result
+         len=$len0
+         for obj in $save_oldobjs
+         do
+           func_len " $obj"
+           func_arith $len + $func_len_result
+           len=$func_arith_result
+           func_append objlist " $obj"
+           if test "$len" -lt "$max_cmd_len"; then
+             :
+           else
+             # the above command should be used before it gets too long
+             oldobjs=$objlist
+             if test "$obj" = "$last_oldobj" ; then
+               RANLIB=$save_RANLIB
+             fi
+             test -z "$concat_cmds" || concat_cmds=$concat_cmds~
+             eval concat_cmds=\"\${concat_cmds}$old_archive_cmds\"
+             objlist=
+             len=$len0
+           fi
+         done
+         RANLIB=$save_RANLIB
+         oldobjs=$objlist
+         if test "X$oldobjs" = "X" ; then
+           eval cmds=\"\$concat_cmds\"
+         else
+           eval cmds=\"\$concat_cmds~\$old_archive_cmds\"
+         fi
+       fi
+      fi
+      func_execute_cmds "$cmds" 'exit $?'
+    done
+
+    test -n "$generated" && \
+      func_show_eval "${RM}r$generated"
+
+    # Now create the libtool archive.
+    case $output in
+    *.la)
+      old_library=
+      test "$build_old_libs" = yes && old_library="$libname.$libext"
+      func_verbose "creating $output"
+
+      # Preserve any variables that may affect compiler behavior
+      for var in $variables_saved_for_relink; do
+       if eval test -z \"\${$var+set}\"; then
+         relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command"
+       elif eval var_value=\$$var; test -z "$var_value"; then
+         relink_command="$var=; export $var; $relink_command"
+       else
+         func_quote_for_eval "$var_value"
+         relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command"
+       fi
+      done
+      # Quote the link command for shipping.
+      relink_command="(cd `pwd`; $SHELL $progpath $preserve_args --mode=relink $libtool_args @inst_prefix_dir@)"
+      relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"`
+      if test "$hardcode_automatic" = yes ; then
+       relink_command=
+      fi
+
+      # Only create the output if not a dry run.
+      $opt_dry_run || {
+       for installed in no yes; do
+         if test "$installed" = yes; then
+           if test -z "$install_libdir"; then
+             break
+           fi
+           output="$output_objdir/$outputname"i
+           # Replace all uninstalled libtool libraries with the installed ones
+           newdependency_libs=
+           for deplib in $dependency_libs; do
+             case $deplib in
+             *.la)
+               func_basename "$deplib"
+               name="$func_basename_result"
+               func_resolve_sysroot "$deplib"
+               eval libdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $func_resolve_sysroot_result`
+               test -z "$libdir" && \
+                 func_fatal_error "\`$deplib' is not a valid libtool archive"
+               func_append newdependency_libs " ${lt_sysroot:+=}$libdir/$name"
+               ;;
+             -L*)
+               func_stripname -L '' "$deplib"
+               func_replace_sysroot "$func_stripname_result"
+               func_append newdependency_libs " -L$func_replace_sysroot_result"
+               ;;
+             -R*)
+               func_stripname -R '' "$deplib"
+               func_replace_sysroot "$func_stripname_result"
+               func_append newdependency_libs " -R$func_replace_sysroot_result"
+               ;;
+             *) func_append newdependency_libs " $deplib" ;;
+             esac
+           done
+           dependency_libs="$newdependency_libs"
+           newdlfiles=
+
+           for lib in $dlfiles; do
+             case $lib in
+             *.la)
+               func_basename "$lib"
+               name="$func_basename_result"
+               eval libdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $lib`
+               test -z "$libdir" && \
+                 func_fatal_error "\`$lib' is not a valid libtool archive"
+               func_append newdlfiles " ${lt_sysroot:+=}$libdir/$name"
+               ;;
+             *) func_append newdlfiles " $lib" ;;
+             esac
+           done
+           dlfiles="$newdlfiles"
+           newdlprefiles=
+           for lib in $dlprefiles; do
+             case $lib in
+             *.la)
+               # Only pass preopened files to the pseudo-archive (for
+               # eventual linking with the app. that links it) if we
+               # didn't already link the preopened objects directly into
+               # the library:
+               func_basename "$lib"
+               name="$func_basename_result"
+               eval libdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $lib`
+               test -z "$libdir" && \
+                 func_fatal_error "\`$lib' is not a valid libtool archive"
+               func_append newdlprefiles " ${lt_sysroot:+=}$libdir/$name"
+               ;;
+             esac
+           done
+           dlprefiles="$newdlprefiles"
+         else
+           newdlfiles=
+           for lib in $dlfiles; do
+             case $lib in
+               [\\/]* | [A-Za-z]:[\\/]*) abs="$lib" ;;
+               *) abs=`pwd`"/$lib" ;;
+             esac
+             func_append newdlfiles " $abs"
+           done
+           dlfiles="$newdlfiles"
+           newdlprefiles=
+           for lib in $dlprefiles; do
+             case $lib in
+               [\\/]* | [A-Za-z]:[\\/]*) abs="$lib" ;;
+               *) abs=`pwd`"/$lib" ;;
+             esac
+             func_append newdlprefiles " $abs"
+           done
+           dlprefiles="$newdlprefiles"
+         fi
+         $RM $output
+         # place dlname in correct position for cygwin
+         # In fact, it would be nice if we could use this code for all target
+         # systems that can't hard-code library paths into their executables
+         # and that have no shared library path variable independent of PATH,
+         # but it turns out we can't easily determine that from inspecting
+         # libtool variables, so we have to hard-code the OSs to which it
+         # applies here; at the moment, that means platforms that use the PE
+         # object format with DLL files.  See the long comment at the top of
+         # tests/bindir.at for full details.
+         tdlname=$dlname
+         case $host,$output,$installed,$module,$dlname in
+           *cygwin*,*lai,yes,no,*.dll | *mingw*,*lai,yes,no,*.dll | *cegcc*,*lai,yes,no,*.dll)
+             # If a -bindir argument was supplied, place the dll there.
+             if test "x$bindir" != x ;
+             then
+               func_relative_path "$install_libdir" "$bindir"
+               tdlname=$func_relative_path_result$dlname
+             else
+               # Otherwise fall back on heuristic.
+               tdlname=../bin/$dlname
+             fi
+             ;;
+         esac
+         $ECHO > $output "\
+# $outputname - a libtool library file
+# Generated by $PROGRAM (GNU $PACKAGE$TIMESTAMP) $VERSION
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname='$tdlname'
+
+# Names of this library.
+library_names='$library_names'
+
+# The name of the static archive.
+old_library='$old_library'
+
+# Linker flags that can not go in dependency_libs.
+inherited_linker_flags='$new_inherited_linker_flags'
+
+# Libraries that this one depends upon.
+dependency_libs='$dependency_libs'
+
+# Names of additional weak libraries provided by this library
+weak_library_names='$weak_libs'
+
+# Version information for $libname.
+current=$current
+age=$age
+revision=$revision
+
+# Is this an already installed library?
+installed=$installed
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=$module
+
+# Files to dlopen/dlpreopen
+dlopen='$dlfiles'
+dlpreopen='$dlprefiles'
+
+# Directory that this library needs to be installed in:
+libdir='$install_libdir'"
+         if test "$installed" = no && test "$need_relink" = yes; then
+           $ECHO >> $output "\
+relink_command=\"$relink_command\""
+         fi
+       done
+      }
+
+      # Do a symbolic link so that the libtool archive can be found in
+      # LD_LIBRARY_PATH before the program is installed.
+      func_show_eval '( cd "$output_objdir" && $RM "$outputname" && $LN_S "../$outputname" "$outputname" )' 'exit $?'
+      ;;
+    esac
+    exit $EXIT_SUCCESS
+}
+
+{ test "$opt_mode" = link || test "$opt_mode" = relink; } &&
+    func_mode_link ${1+"$@"}
+
+
+# func_mode_uninstall arg...
+func_mode_uninstall ()
+{
+    $opt_debug
+    RM="$nonopt"
+    files=
+    rmforce=
+    exit_status=0
+
+    # This variable tells wrapper scripts just to set variables rather
+    # than running their programs.
+    libtool_install_magic="$magic"
+
+    for arg
+    do
+      case $arg in
+      -f) func_append RM " $arg"; rmforce=yes ;;
+      -*) func_append RM " $arg" ;;
+      *) func_append files " $arg" ;;
+      esac
+    done
+
+    test -z "$RM" && \
+      func_fatal_help "you must specify an RM program"
+
+    rmdirs=
+
+    for file in $files; do
+      func_dirname "$file" "" "."
+      dir="$func_dirname_result"
+      if test "X$dir" = X.; then
+       odir="$objdir"
+      else
+       odir="$dir/$objdir"
+      fi
+      func_basename "$file"
+      name="$func_basename_result"
+      test "$opt_mode" = uninstall && odir="$dir"
+
+      # Remember odir for removal later, being careful to avoid duplicates
+      if test "$opt_mode" = clean; then
+       case " $rmdirs " in
+         *" $odir "*) ;;
+         *) func_append rmdirs " $odir" ;;
+       esac
+      fi
+
+      # Don't error if the file doesn't exist and rm -f was used.
+      if { test -L "$file"; } >/dev/null 2>&1 ||
+        { test -h "$file"; } >/dev/null 2>&1 ||
+        test -f "$file"; then
+       :
+      elif test -d "$file"; then
+       exit_status=1
+       continue
+      elif test "$rmforce" = yes; then
+       continue
+      fi
+
+      rmfiles="$file"
+
+      case $name in
+      *.la)
+       # Possibly a libtool archive, so verify it.
+       if func_lalib_p "$file"; then
+         func_source $dir/$name
+
+         # Delete the libtool libraries and symlinks.
+         for n in $library_names; do
+           func_append rmfiles " $odir/$n"
+         done
+         test -n "$old_library" && func_append rmfiles " $odir/$old_library"
+
+         case "$opt_mode" in
+         clean)
+           case " $library_names " in
+           *" $dlname "*) ;;
+           *) test -n "$dlname" && func_append rmfiles " $odir/$dlname" ;;
+           esac
+           test -n "$libdir" && func_append rmfiles " $odir/$name $odir/${name}i"
+           ;;
+         uninstall)
+           if test -n "$library_names"; then
+             # Do each command in the postuninstall commands.
+             func_execute_cmds "$postuninstall_cmds" 'test "$rmforce" = yes || exit_status=1'
+           fi
+
+           if test -n "$old_library"; then
+             # Do each command in the old_postuninstall commands.
+             func_execute_cmds "$old_postuninstall_cmds" 'test "$rmforce" = yes || exit_status=1'
+           fi
+           # FIXME: should reinstall the best remaining shared library.
+           ;;
+         esac
+       fi
+       ;;
+
+      *.lo)
+       # Possibly a libtool object, so verify it.
+       if func_lalib_p "$file"; then
+
+         # Read the .lo file
+         func_source $dir/$name
+
+         # Add PIC object to the list of files to remove.
+         if test -n "$pic_object" &&
+            test "$pic_object" != none; then
+           func_append rmfiles " $dir/$pic_object"
+         fi
+
+         # Add non-PIC object to the list of files to remove.
+         if test -n "$non_pic_object" &&
+            test "$non_pic_object" != none; then
+           func_append rmfiles " $dir/$non_pic_object"
+         fi
+       fi
+       ;;
+
+      *)
+       if test "$opt_mode" = clean ; then
+         noexename=$name
+         case $file in
+         *.exe)
+           func_stripname '' '.exe' "$file"
+           file=$func_stripname_result
+           func_stripname '' '.exe' "$name"
+           noexename=$func_stripname_result
+           # $file with .exe has already been added to rmfiles,
+           # add $file without .exe
+           func_append rmfiles " $file"
+           ;;
+         esac
+         # Do a test to see if this is a libtool program.
+         if func_ltwrapper_p "$file"; then
+           if func_ltwrapper_executable_p "$file"; then
+             func_ltwrapper_scriptname "$file"
+             relink_command=
+             func_source $func_ltwrapper_scriptname_result
+             func_append rmfiles " $func_ltwrapper_scriptname_result"
+           else
+             relink_command=
+             func_source $dir/$noexename
+           fi
+
+           # note $name still contains .exe if it was in $file originally
+           # as does the version of $file that was added into $rmfiles
+           func_append rmfiles " $odir/$name $odir/${name}S.${objext}"
+           if test "$fast_install" = yes && test -n "$relink_command"; then
+             func_append rmfiles " $odir/lt-$name"
+           fi
+           if test "X$noexename" != "X$name" ; then
+             func_append rmfiles " $odir/lt-${noexename}.c"
+           fi
+         fi
+       fi
+       ;;
+      esac
+      func_show_eval "$RM $rmfiles" 'exit_status=1'
+    done
+
+    # Try to remove the ${objdir}s in the directories where we deleted files
+    for dir in $rmdirs; do
+      if test -d "$dir"; then
+       func_show_eval "rmdir $dir >/dev/null 2>&1"
+      fi
+    done
+
+    exit $exit_status
+}
+
+{ test "$opt_mode" = uninstall || test "$opt_mode" = clean; } &&
+    func_mode_uninstall ${1+"$@"}
+
+test -z "$opt_mode" && {
+  help="$generic_help"
+  func_fatal_help "you must specify a MODE"
+}
+
+test -z "$exec_cmd" && \
+  func_fatal_help "invalid operation mode \`$opt_mode'"
+
+if test -n "$exec_cmd"; then
+  eval exec "$exec_cmd"
+  exit $EXIT_FAILURE
+fi
+
+exit $exit_status
+
+
+# The TAGs below are defined such that we never get into a situation
+# in which we disable both kinds of libraries.  Given conflicting
+# choices, we go for a static library, that is the most portable,
+# since we can't tell whether shared libraries were disabled because
+# the user asked for that or because the platform doesn't support
+# them.  This is particularly important on AIX, because we don't
+# support having both static and shared libraries enabled at the same
+# time on that platform, so we default to a shared-only configuration.
+# If a disable-shared tag is given, we'll fallback to a static-only
+# configuration.  But we'll never go from static-only to shared-only.
+
+# ### BEGIN LIBTOOL TAG CONFIG: disable-shared
+build_libtool_libs=no
+build_old_libs=yes
+# ### END LIBTOOL TAG CONFIG: disable-shared
+
+# ### BEGIN LIBTOOL TAG CONFIG: disable-static
+build_old_libs=`case $build_libtool_libs in yes) echo no;; *) echo yes;; esac`
+# ### END LIBTOOL TAG CONFIG: disable-static
+
+# Local Variables:
+# mode:shell-script
+# sh-indentation:2
+# End:
+# vi:sw=2
+
diff --git a/config/missing b/config/missing
new file mode 100755 (executable)
index 0000000..86a8fc3
--- /dev/null
@@ -0,0 +1,331 @@
+#! /bin/sh
+# Common stub for a few missing GNU programs while installing.
+
+scriptversion=2012-01-06.13; # UTC
+
+# Copyright (C) 1996, 1997, 1999, 2000, 2002, 2003, 2004, 2005, 2006,
+# 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
+# Originally by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+if test $# -eq 0; then
+  echo 1>&2 "Try \`$0 --help' for more information"
+  exit 1
+fi
+
+run=:
+sed_output='s/.* --output[ =]\([^ ]*\).*/\1/p'
+sed_minuso='s/.* -o \([^ ]*\).*/\1/p'
+
+# In the cases where this matters, `missing' is being run in the
+# srcdir already.
+if test -f configure.ac; then
+  configure_ac=configure.ac
+else
+  configure_ac=configure.in
+fi
+
+msg="missing on your system"
+
+case $1 in
+--run)
+  # Try to run requested program, and just exit if it succeeds.
+  run=
+  shift
+  "$@" && exit 0
+  # Exit code 63 means version mismatch.  This often happens
+  # when the user try to use an ancient version of a tool on
+  # a file that requires a minimum version.  In this case we
+  # we should proceed has if the program had been absent, or
+  # if --run hadn't been passed.
+  if test $? = 63; then
+    run=:
+    msg="probably too old"
+  fi
+  ;;
+
+  -h|--h|--he|--hel|--help)
+    echo "\
+$0 [OPTION]... PROGRAM [ARGUMENT]...
+
+Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an
+error status if there is no known handling for PROGRAM.
+
+Options:
+  -h, --help      display this help and exit
+  -v, --version   output version information and exit
+  --run           try to run the given command, and emulate it if it fails
+
+Supported PROGRAM values:
+  aclocal      touch file \`aclocal.m4'
+  autoconf     touch file \`configure'
+  autoheader   touch file \`config.h.in'
+  autom4te     touch the output file, or create a stub one
+  automake     touch all \`Makefile.in' files
+  bison        create \`y.tab.[ch]', if possible, from existing .[ch]
+  flex         create \`lex.yy.c', if possible, from existing .c
+  help2man     touch the output file
+  lex          create \`lex.yy.c', if possible, from existing .c
+  makeinfo     touch the output file
+  yacc         create \`y.tab.[ch]', if possible, from existing .[ch]
+
+Version suffixes to PROGRAM as well as the prefixes \`gnu-', \`gnu', and
+\`g' are ignored when checking the name.
+
+Send bug reports to <bug-automake@gnu.org>."
+    exit $?
+    ;;
+
+  -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
+    echo "missing $scriptversion (GNU Automake)"
+    exit $?
+    ;;
+
+  -*)
+    echo 1>&2 "$0: Unknown \`$1' option"
+    echo 1>&2 "Try \`$0 --help' for more information"
+    exit 1
+    ;;
+
+esac
+
+# normalize program name to check for.
+program=`echo "$1" | sed '
+  s/^gnu-//; t
+  s/^gnu//; t
+  s/^g//; t'`
+
+# Now exit if we have it, but it failed.  Also exit now if we
+# don't have it and --version was passed (most likely to detect
+# the program).  This is about non-GNU programs, so use $1 not
+# $program.
+case $1 in
+  lex*|yacc*)
+    # Not GNU programs, they don't have --version.
+    ;;
+
+  *)
+    if test -z "$run" && ($1 --version) > /dev/null 2>&1; then
+       # We have it, but it failed.
+       exit 1
+    elif test "x$2" = "x--version" || test "x$2" = "x--help"; then
+       # Could not run --version or --help.  This is probably someone
+       # running `$TOOL --version' or `$TOOL --help' to check whether
+       # $TOOL exists and not knowing $TOOL uses missing.
+       exit 1
+    fi
+    ;;
+esac
+
+# If it does not exist, or fails to run (possibly an outdated version),
+# try to emulate it.
+case $program in
+  aclocal*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+         you modified \`acinclude.m4' or \`${configure_ac}'.  You might want
+         to install the \`Automake' and \`Perl' packages.  Grab them from
+         any GNU archive site."
+    touch aclocal.m4
+    ;;
+
+  autoconf*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+         you modified \`${configure_ac}'.  You might want to install the
+         \`Autoconf' and \`GNU m4' packages.  Grab them from any GNU
+         archive site."
+    touch configure
+    ;;
+
+  autoheader*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+         you modified \`acconfig.h' or \`${configure_ac}'.  You might want
+         to install the \`Autoconf' and \`GNU m4' packages.  Grab them
+         from any GNU archive site."
+    files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}`
+    test -z "$files" && files="config.h"
+    touch_files=
+    for f in $files; do
+      case $f in
+      *:*) touch_files="$touch_files "`echo "$f" |
+                                      sed -e 's/^[^:]*://' -e 's/:.*//'`;;
+      *) touch_files="$touch_files $f.in";;
+      esac
+    done
+    touch $touch_files
+    ;;
+
+  automake*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+         you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'.
+         You might want to install the \`Automake' and \`Perl' packages.
+         Grab them from any GNU archive site."
+    find . -type f -name Makefile.am -print |
+          sed 's/\.am$/.in/' |
+          while read f; do touch "$f"; done
+    ;;
+
+  autom4te*)
+    echo 1>&2 "\
+WARNING: \`$1' is needed, but is $msg.
+         You might have modified some files without having the
+         proper tools for further handling them.
+         You can get \`$1' as part of \`Autoconf' from any GNU
+         archive site."
+
+    file=`echo "$*" | sed -n "$sed_output"`
+    test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
+    if test -f "$file"; then
+       touch $file
+    else
+       test -z "$file" || exec >$file
+       echo "#! /bin/sh"
+       echo "# Created by GNU Automake missing as a replacement of"
+       echo "#  $ $@"
+       echo "exit 0"
+       chmod +x $file
+       exit 1
+    fi
+    ;;
+
+  bison*|yacc*)
+    echo 1>&2 "\
+WARNING: \`$1' $msg.  You should only need it if
+         you modified a \`.y' file.  You may need the \`Bison' package
+         in order for those modifications to take effect.  You can get
+         \`Bison' from any GNU archive site."
+    rm -f y.tab.c y.tab.h
+    if test $# -ne 1; then
+        eval LASTARG=\${$#}
+       case $LASTARG in
+       *.y)
+           SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'`
+           if test -f "$SRCFILE"; then
+                cp "$SRCFILE" y.tab.c
+           fi
+           SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'`
+           if test -f "$SRCFILE"; then
+                cp "$SRCFILE" y.tab.h
+           fi
+         ;;
+       esac
+    fi
+    if test ! -f y.tab.h; then
+       echo >y.tab.h
+    fi
+    if test ! -f y.tab.c; then
+       echo 'main() { return 0; }' >y.tab.c
+    fi
+    ;;
+
+  lex*|flex*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+         you modified a \`.l' file.  You may need the \`Flex' package
+         in order for those modifications to take effect.  You can get
+         \`Flex' from any GNU archive site."
+    rm -f lex.yy.c
+    if test $# -ne 1; then
+        eval LASTARG=\${$#}
+       case $LASTARG in
+       *.l)
+           SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'`
+           if test -f "$SRCFILE"; then
+                cp "$SRCFILE" lex.yy.c
+           fi
+         ;;
+       esac
+    fi
+    if test ! -f lex.yy.c; then
+       echo 'main() { return 0; }' >lex.yy.c
+    fi
+    ;;
+
+  help2man*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+        you modified a dependency of a manual page.  You may need the
+        \`Help2man' package in order for those modifications to take
+        effect.  You can get \`Help2man' from any GNU archive site."
+
+    file=`echo "$*" | sed -n "$sed_output"`
+    test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
+    if test -f "$file"; then
+       touch $file
+    else
+       test -z "$file" || exec >$file
+       echo ".ab help2man is required to generate this page"
+       exit $?
+    fi
+    ;;
+
+  makeinfo*)
+    echo 1>&2 "\
+WARNING: \`$1' is $msg.  You should only need it if
+         you modified a \`.texi' or \`.texinfo' file, or any other file
+         indirectly affecting the aspect of the manual.  The spurious
+         call might also be the consequence of using a buggy \`make' (AIX,
+         DU, IRIX).  You might want to install the \`Texinfo' package or
+         the \`GNU make' package.  Grab either from any GNU archive site."
+    # The file to touch is that specified with -o ...
+    file=`echo "$*" | sed -n "$sed_output"`
+    test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
+    if test -z "$file"; then
+      # ... or it is the one specified with @setfilename ...
+      infile=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'`
+      file=`sed -n '
+       /^@setfilename/{
+         s/.* \([^ ]*\) *$/\1/
+         p
+         q
+       }' $infile`
+      # ... or it is derived from the source name (dir/f.texi becomes f.info)
+      test -z "$file" && file=`echo "$infile" | sed 's,.*/,,;s,.[^.]*$,,'`.info
+    fi
+    # If the file does not exist, the user really needs makeinfo;
+    # let's fail without touching anything.
+    test -f $file || exit 1
+    touch $file
+    ;;
+
+  *)
+    echo 1>&2 "\
+WARNING: \`$1' is needed, and is $msg.
+         You might have modified some files without having the
+         proper tools for further handling them.  Check the \`README' file,
+         it often tells you about the needed prerequisites for installing
+         this package.  You may also peek at any GNU archive site, in case
+         some other package would contain this missing \`$1' program."
+    exit 1
+    ;;
+esac
+
+exit 0
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..2922d41
--- /dev/null
+++ b/configure
@@ -0,0 +1,15458 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.68 for leptonica 1.73.
+#
+#
+# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
+# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software
+# Foundation, Inc.
+#
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+       expr "X$arg" : "X\\(.*\\)$as_nl";
+       arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""       $as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+if test "x$CONFIG_SHELL" = x; then
+  as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '\${1+\"\$@\"}'='\"\$@\"'
+  setopt NO_GLOB_SUBST
+else
+  case \`(set -o) 2>/dev/null\` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+"
+  as_required="as_fn_return () { (exit \$1); }
+as_fn_success () { as_fn_return 0; }
+as_fn_failure () { as_fn_return 1; }
+as_fn_ret_success () { return 0; }
+as_fn_ret_failure () { return 1; }
+
+exitcode=0
+as_fn_success || { exitcode=1; echo as_fn_success failed.; }
+as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
+as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
+as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
+if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
+
+else
+  exitcode=1; echo positional parameters were not saved.
+fi
+test x\$exitcode = x0 || exit 1"
+  as_suggested="  as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
+  as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
+  eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
+  test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
+
+  test -n \"\${ZSH_VERSION+set}\${BASH_VERSION+set}\" || (
+    ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+    ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO
+    ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO
+    PATH=/empty FPATH=/empty; export PATH FPATH
+    test \"X\`printf %s \$ECHO\`\" = \"X\$ECHO\" \\
+      || test \"X\`print -r -- \$ECHO\`\" = \"X\$ECHO\" ) || exit 1
+test \$(( 1 + 1 )) = 2 || exit 1"
+  if (eval "$as_required") 2>/dev/null; then :
+  as_have_required=yes
+else
+  as_have_required=no
+fi
+  if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
+
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  as_found=:
+  case $as_dir in #(
+        /*)
+          for as_base in sh bash ksh sh5; do
+            # Try only shells that exist, to save several forks.
+            as_shell=$as_dir/$as_base
+            if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+                   { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  CONFIG_SHELL=$as_shell as_have_required=yes
+                  if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  break 2
+fi
+fi
+          done;;
+       esac
+  as_found=false
+done
+$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
+             { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
+  CONFIG_SHELL=$SHELL as_have_required=yes
+fi; }
+IFS=$as_save_IFS
+
+
+      if test "x$CONFIG_SHELL" != x; then :
+  # We cannot yet assume a decent shell, so we have to provide a
+       # neutralization value for shells without unset; and this also
+       # works around shells that cannot unset nonexistent variables.
+       # Preserve -v and -x to the replacement shell.
+       BASH_ENV=/dev/null
+       ENV=/dev/null
+       (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+       export CONFIG_SHELL
+       case $- in # ((((
+         *v*x* | *x*v* ) as_opts=-vx ;;
+         *v* ) as_opts=-v ;;
+         *x* ) as_opts=-x ;;
+         * ) as_opts= ;;
+       esac
+       exec "$CONFIG_SHELL" $as_opts "$as_myself" ${1+"$@"}
+fi
+
+    if test x$as_have_required = xno; then :
+  $as_echo "$0: This script requires a shell more modern than all"
+  $as_echo "$0: the shells that I found on your system."
+  if test x${ZSH_VERSION+set} = xset ; then
+    $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
+    $as_echo "$0: be upgraded to zsh 4.3.4 or later."
+  else
+    $as_echo "$0: Please tell bug-autoconf@gnu.org about your system,
+$0: including any error possibly output before this
+$0: message. Then install a modern shell, or manually run
+$0: the script under such a shell if you do have one."
+  fi
+  exit 1
+fi
+fi
+fi
+SHELL=${CONFIG_SHELL-/bin/sh}
+export SHELL
+# Unset more variables known to interfere with behavior of common tools.
+CLICOLOR_FORCE= GREP_OPTIONS=
+unset CLICOLOR_FORCE GREP_OPTIONS
+
+## --------------------- ##
+## M4sh Shell Functions. ##
+## --------------------- ##
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_dir" : 'X\(//\)[^/]' \| \
+        X"$as_dir" : 'X\(//\)$' \| \
+        X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+
+  as_lineno_1=$LINENO as_lineno_1a=$LINENO
+  as_lineno_2=$LINENO as_lineno_2a=$LINENO
+  eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
+  test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
+  # Blame Lee E. McMahon (1931-1989) for sed's syntax.  :-)
+  sed -n '
+    p
+    /[$]LINENO/=
+  ' <$as_myself |
+    sed '
+      s/[$]LINENO.*/&-/
+      t lineno
+      b
+      :lineno
+      N
+      :loop
+      s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+      t loop
+      s/-\n.*//
+    ' >$as_me.lineno &&
+  chmod +x "$as_me.lineno" ||
+    { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
+
+  # Don't try to exec as it changes $[0], causing all sort of problems
+  # (the dirname of $[0] is not the place where we might find the
+  # original and so on.  Autoconf is especially sensitive to this).
+  . "./$as_me.lineno"
+  # Exit status is that of the last command.
+  exit
+}
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='        ';;     # ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='        ';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -p'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -p'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -p'
+  fi
+else
+  as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+if test -x / >/dev/null 2>&1; then
+  as_test_x='test -x'
+else
+  if ls -dL / >/dev/null 2>&1; then
+    as_ls_L_option=L
+  else
+    as_ls_L_option=
+  fi
+  as_test_x='
+    eval sh -c '\''
+      if test -d "$1"; then
+       test -d "$1/.";
+      else
+       case $1 in #(
+       -*)set "./$1";;
+       esac;
+       case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #((
+       ???[sx]*):;;*)false;;esac;fi
+    '\'' sh
+  '
+fi
+as_executable_p=$as_test_x
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+
+test -n "$DJDIR" || exec 7<&0 </dev/null
+exec 6>&1
+
+# Name of the host.
+# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_clean_files=
+ac_config_libobj_dir=.
+LIBOBJS=
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+
+# Identity of this package.
+PACKAGE_NAME='leptonica'
+PACKAGE_TARNAME='leptonica'
+PACKAGE_VERSION='1.73'
+PACKAGE_STRING='leptonica 1.73'
+PACKAGE_BUGREPORT=''
+PACKAGE_URL=''
+
+ac_unique_file="src/adaptmap.c"
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stdio.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif
+#ifdef HAVE_STRING_H
+# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_subst_vars='am__EXEEXT_FALSE
+am__EXEEXT_TRUE
+LTLIBOBJS
+LIBOBJS
+ENDIANNESS
+APPLE_UNIVERSAL_BUILD
+AM_BACKSLASH
+AM_DEFAULT_VERBOSITY
+AM_DEFAULT_V
+AM_V
+GDI_LIBS
+HAVE_LIBJP2K_FALSE
+HAVE_LIBJP2K_TRUE
+LIBJP2K_LIBS
+HAVE_LIBWEBP_FALSE
+HAVE_LIBWEBP_TRUE
+LIBWEBP_LIBS
+LIBTIFF_LIBS
+HAVE_LIBGIF_FALSE
+HAVE_LIBGIF_TRUE
+GIFLIB_LIBS
+JPEG_LIBS
+LIBPNG_LIBS
+ZLIB_LIBS
+LIBM
+ENABLE_PROGRAMS_FALSE
+ENABLE_PROGRAMS_TRUE
+am__fastdepCC_FALSE
+am__fastdepCC_TRUE
+CCDEPMODE
+am__nodep
+AMDEPBACKSLASH
+AMDEP_FALSE
+AMDEP_TRUE
+am__quote
+am__include
+DEPDIR
+am__untar
+am__tar
+AMTAR
+am__leading_dot
+SET_MAKE
+mkdir_p
+MKDIR_P
+INSTALL_STRIP_PROGRAM
+install_sh
+MAKEINFO
+AUTOHEADER
+AUTOMAKE
+AUTOCONF
+ACLOCAL
+VERSION
+PACKAGE
+CYGPATH_W
+am__isrc
+INSTALL_DATA
+INSTALL_SCRIPT
+INSTALL_PROGRAM
+CPP
+OTOOL64
+OTOOL
+LIPO
+NMEDIT
+DSYMUTIL
+MANIFEST_TOOL
+AWK
+RANLIB
+STRIP
+ac_ct_AR
+AR
+DLLTOOL
+OBJDUMP
+LN_S
+NM
+ac_ct_DUMPBIN
+DUMPBIN
+LD
+FGREP
+EGREP
+GREP
+SED
+OBJEXT
+EXEEXT
+ac_ct_CC
+CPPFLAGS
+LDFLAGS
+CFLAGS
+CC
+host_os
+host_vendor
+host_cpu
+host
+build_os
+build_vendor
+build_cpu
+build
+LIBTOOL
+target_alias
+host_alias
+build_alias
+LIBS
+ECHO_T
+ECHO_N
+ECHO_C
+DEFS
+mandir
+localedir
+libdir
+psdir
+pdfdir
+dvidir
+htmldir
+infodir
+docdir
+oldincludedir
+includedir
+localstatedir
+sharedstatedir
+sysconfdir
+datadir
+datarootdir
+libexecdir
+sbindir
+bindir
+program_transform_name
+prefix
+exec_prefix
+PACKAGE_URL
+PACKAGE_BUGREPORT
+PACKAGE_STRING
+PACKAGE_VERSION
+PACKAGE_TARNAME
+PACKAGE_NAME
+PATH_SEPARATOR
+SHELL'
+ac_subst_files=''
+ac_user_opts='
+enable_option_checking
+enable_shared
+enable_static
+with_pic
+enable_fast_install
+with_gnu_ld
+with_sysroot
+enable_libtool_lock
+enable_dependency_tracking
+with_zlib
+with_libpng
+with_jpeg
+with_giflib
+with_libtiff
+with_libwebp
+with_libopenjpeg
+enable_programs
+enable_silent_rules
+'
+      ac_precious_vars='build_alias
+host_alias
+target_alias
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+CPP'
+
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+ac_unrecognized_opts=
+ac_unrecognized_sep=
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
+
+ac_prev=
+ac_dashdash=
+for ac_option
+do
+  # If the previous option needs an argument, assign it.
+  if test -n "$ac_prev"; then
+    eval $ac_prev=\$ac_option
+    ac_prev=
+    continue
+  fi
+
+  case $ac_option in
+  *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+  *=)   ac_optarg= ;;
+  *)    ac_optarg=yes ;;
+  esac
+
+  # Accept the important Cygnus configure options, so we can diagnose typos.
+
+  case $ac_dashdash$ac_option in
+  --)
+    ac_dashdash=yes ;;
+
+  -bindir | --bindir | --bindi | --bind | --bin | --bi)
+    ac_prev=bindir ;;
+  -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+    bindir=$ac_optarg ;;
+
+  -build | --build | --buil | --bui | --bu)
+    ac_prev=build_alias ;;
+  -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+    build_alias=$ac_optarg ;;
+
+  -cache-file | --cache-file | --cache-fil | --cache-fi \
+  | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+    ac_prev=cache_file ;;
+  -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+  | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+    cache_file=$ac_optarg ;;
+
+  --config-cache | -C)
+    cache_file=config.cache ;;
+
+  -datadir | --datadir | --datadi | --datad)
+    ac_prev=datadir ;;
+  -datadir=* | --datadir=* | --datadi=* | --datad=*)
+    datadir=$ac_optarg ;;
+
+  -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+  | --dataroo | --dataro | --datar)
+    ac_prev=datarootdir ;;
+  -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+  | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+    datarootdir=$ac_optarg ;;
+
+  -disable-* | --disable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
+        ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=no ;;
+
+  -docdir | --docdir | --docdi | --doc | --do)
+    ac_prev=docdir ;;
+  -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+    docdir=$ac_optarg ;;
+
+  -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+    ac_prev=dvidir ;;
+  -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+    dvidir=$ac_optarg ;;
+
+  -enable-* | --enable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
+        ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=\$ac_optarg ;;
+
+  -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+  | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+  | --exec | --exe | --ex)
+    ac_prev=exec_prefix ;;
+  -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+  | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+  | --exec=* | --exe=* | --ex=*)
+    exec_prefix=$ac_optarg ;;
+
+  -gas | --gas | --ga | --g)
+    # Obsolete; use --with-gas.
+    with_gas=yes ;;
+
+  -help | --help | --hel | --he | -h)
+    ac_init_help=long ;;
+  -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+    ac_init_help=recursive ;;
+  -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+    ac_init_help=short ;;
+
+  -host | --host | --hos | --ho)
+    ac_prev=host_alias ;;
+  -host=* | --host=* | --hos=* | --ho=*)
+    host_alias=$ac_optarg ;;
+
+  -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+    ac_prev=htmldir ;;
+  -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+  | --ht=*)
+    htmldir=$ac_optarg ;;
+
+  -includedir | --includedir | --includedi | --included | --include \
+  | --includ | --inclu | --incl | --inc)
+    ac_prev=includedir ;;
+  -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+  | --includ=* | --inclu=* | --incl=* | --inc=*)
+    includedir=$ac_optarg ;;
+
+  -infodir | --infodir | --infodi | --infod | --info | --inf)
+    ac_prev=infodir ;;
+  -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+    infodir=$ac_optarg ;;
+
+  -libdir | --libdir | --libdi | --libd)
+    ac_prev=libdir ;;
+  -libdir=* | --libdir=* | --libdi=* | --libd=*)
+    libdir=$ac_optarg ;;
+
+  -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+  | --libexe | --libex | --libe)
+    ac_prev=libexecdir ;;
+  -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+  | --libexe=* | --libex=* | --libe=*)
+    libexecdir=$ac_optarg ;;
+
+  -localedir | --localedir | --localedi | --localed | --locale)
+    ac_prev=localedir ;;
+  -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+    localedir=$ac_optarg ;;
+
+  -localstatedir | --localstatedir | --localstatedi | --localstated \
+  | --localstate | --localstat | --localsta | --localst | --locals)
+    ac_prev=localstatedir ;;
+  -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
+    localstatedir=$ac_optarg ;;
+
+  -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+    ac_prev=mandir ;;
+  -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+    mandir=$ac_optarg ;;
+
+  -nfp | --nfp | --nf)
+    # Obsolete; use --without-fp.
+    with_fp=no ;;
+
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c | -n)
+    no_create=yes ;;
+
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+    no_recursion=yes ;;
+
+  -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+  | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+  | --oldin | --oldi | --old | --ol | --o)
+    ac_prev=oldincludedir ;;
+  -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+  | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+  | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+    oldincludedir=$ac_optarg ;;
+
+  -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+    ac_prev=prefix ;;
+  -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+    prefix=$ac_optarg ;;
+
+  -program-prefix | --program-prefix | --program-prefi | --program-pref \
+  | --program-pre | --program-pr | --program-p)
+    ac_prev=program_prefix ;;
+  -program-prefix=* | --program-prefix=* | --program-prefi=* \
+  | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+    program_prefix=$ac_optarg ;;
+
+  -program-suffix | --program-suffix | --program-suffi | --program-suff \
+  | --program-suf | --program-su | --program-s)
+    ac_prev=program_suffix ;;
+  -program-suffix=* | --program-suffix=* | --program-suffi=* \
+  | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+    program_suffix=$ac_optarg ;;
+
+  -program-transform-name | --program-transform-name \
+  | --program-transform-nam | --program-transform-na \
+  | --program-transform-n | --program-transform- \
+  | --program-transform | --program-transfor \
+  | --program-transfo | --program-transf \
+  | --program-trans | --program-tran \
+  | --progr-tra | --program-tr | --program-t)
+    ac_prev=program_transform_name ;;
+  -program-transform-name=* | --program-transform-name=* \
+  | --program-transform-nam=* | --program-transform-na=* \
+  | --program-transform-n=* | --program-transform-=* \
+  | --program-transform=* | --program-transfor=* \
+  | --program-transfo=* | --program-transf=* \
+  | --program-trans=* | --program-tran=* \
+  | --progr-tra=* | --program-tr=* | --program-t=*)
+    program_transform_name=$ac_optarg ;;
+
+  -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+    ac_prev=pdfdir ;;
+  -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+    pdfdir=$ac_optarg ;;
+
+  -psdir | --psdir | --psdi | --psd | --ps)
+    ac_prev=psdir ;;
+  -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+    psdir=$ac_optarg ;;
+
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil)
+    silent=yes ;;
+
+  -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+    ac_prev=sbindir ;;
+  -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+  | --sbi=* | --sb=*)
+    sbindir=$ac_optarg ;;
+
+  -sharedstatedir | --sharedstatedir | --sharedstatedi \
+  | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+  | --sharedst | --shareds | --shared | --share | --shar \
+  | --sha | --sh)
+    ac_prev=sharedstatedir ;;
+  -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+  | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+  | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+  | --sha=* | --sh=*)
+    sharedstatedir=$ac_optarg ;;
+
+  -site | --site | --sit)
+    ac_prev=site ;;
+  -site=* | --site=* | --sit=*)
+    site=$ac_optarg ;;
+
+  -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+    ac_prev=srcdir ;;
+  -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+    srcdir=$ac_optarg ;;
+
+  -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+  | --syscon | --sysco | --sysc | --sys | --sy)
+    ac_prev=sysconfdir ;;
+  -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+  | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+    sysconfdir=$ac_optarg ;;
+
+  -target | --target | --targe | --targ | --tar | --ta | --t)
+    ac_prev=target_alias ;;
+  -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+    target_alias=$ac_optarg ;;
+
+  -v | -verbose | --verbose | --verbos | --verbo | --verb)
+    verbose=yes ;;
+
+  -version | --version | --versio | --versi | --vers | -V)
+    ac_init_version=: ;;
+
+  -with-* | --with-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
+        ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=\$ac_optarg ;;
+
+  -without-* | --without-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
+        ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=no ;;
+
+  --x)
+    # Obsolete; use --with-x.
+    with_x=yes ;;
+
+  -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+  | --x-incl | --x-inc | --x-in | --x-i)
+    ac_prev=x_includes ;;
+  -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+  | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+    x_includes=$ac_optarg ;;
+
+  -x-libraries | --x-libraries | --x-librarie | --x-librari \
+  | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+    ac_prev=x_libraries ;;
+  -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+  | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+    x_libraries=$ac_optarg ;;
+
+  -*) as_fn_error $? "unrecognized option: \`$ac_option'
+Try \`$0 --help' for more information"
+    ;;
+
+  *=*)
+    ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+    # Reject names that are not valid shell variable names.
+    case $ac_envvar in #(
+      '' | [0-9]* | *[!_$as_cr_alnum]* )
+      as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
+    esac
+    eval $ac_envvar=\$ac_optarg
+    export $ac_envvar ;;
+
+  *)
+    # FIXME: should be removed in autoconf 3.0.
+    $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+    expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+    : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
+    ;;
+
+  esac
+done
+
+if test -n "$ac_prev"; then
+  ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+  as_fn_error $? "missing argument to $ac_option"
+fi
+
+if test -n "$ac_unrecognized_opts"; then
+  case $enable_option_checking in
+    no) ;;
+    fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
+    *)     $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
+  esac
+fi
+
+# Check all directory arguments for consistency.
+for ac_var in  exec_prefix prefix bindir sbindir libexecdir datarootdir \
+               datadir sysconfdir sharedstatedir localstatedir includedir \
+               oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
+               libdir localedir mandir
+do
+  eval ac_val=\$$ac_var
+  # Remove trailing slashes.
+  case $ac_val in
+    */ )
+      ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
+      eval $ac_var=\$ac_val;;
+  esac
+  # Be sure to have absolute directory names.
+  case $ac_val in
+    [\\/$]* | ?:[\\/]* )  continue;;
+    NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
+  esac
+  as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+  if test "x$build_alias" = x; then
+    cross_compiling=maybe
+    $as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host.
+    If a cross compiler is detected then cross compile mode will be used" >&2
+  elif test "x$build_alias" != "x$host_alias"; then
+    cross_compiling=yes
+  fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+ac_pwd=`pwd` && test -n "$ac_pwd" &&
+ac_ls_di=`ls -di .` &&
+ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
+  as_fn_error $? "working directory cannot be determined"
+test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
+  as_fn_error $? "pwd does not report name of working directory"
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+  ac_srcdir_defaulted=yes
+  # Try the directory containing this script, then the parent directory.
+  ac_confdir=`$as_dirname -- "$as_myself" ||
+$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_myself" : 'X\(//\)[^/]' \| \
+        X"$as_myself" : 'X\(//\)$' \| \
+        X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_myself" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+  srcdir=$ac_confdir
+  if test ! -r "$srcdir/$ac_unique_file"; then
+    srcdir=..
+  fi
+else
+  ac_srcdir_defaulted=no
+fi
+if test ! -r "$srcdir/$ac_unique_file"; then
+  test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
+  as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
+fi
+ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
+ac_abs_confdir=`(
+       cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
+       pwd)`
+# When building in place, set srcdir=.
+if test "$ac_abs_confdir" = "$ac_pwd"; then
+  srcdir=.
+fi
+# Remove unnecessary trailing slashes from srcdir.
+# Double slashes in file names in object file debugging info
+# mess up M-x gdb in Emacs.
+case $srcdir in
+*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
+esac
+for ac_var in $ac_precious_vars; do
+  eval ac_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_env_${ac_var}_value=\$${ac_var}
+  eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_cv_env_${ac_var}_value=\$${ac_var}
+done
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+  # Omit some internal or obsolete options to make the list less imposing.
+  # This message is too long to be a string in the A/UX 3.1 sh.
+  cat <<_ACEOF
+\`configure' configures leptonica 1.73 to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE.  See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+  -h, --help              display this help and exit
+      --help=short        display options specific to this package
+      --help=recursive    display the short help of all the included packages
+  -V, --version           display version information and exit
+  -q, --quiet, --silent   do not print \`checking ...' messages
+      --cache-file=FILE   cache test results in FILE [disabled]
+  -C, --config-cache      alias for \`--cache-file=config.cache'
+  -n, --no-create         do not create output files
+      --srcdir=DIR        find the sources in DIR [configure dir or \`..']
+
+Installation directories:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                          [$ac_default_prefix]
+  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
+                          [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc.  You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --oldincludedir=DIR     C header files for non-gcc [/usr/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --docdir=DIR            documentation root [DATAROOTDIR/doc/leptonica]
+  --htmldir=DIR           html documentation [DOCDIR]
+  --dvidir=DIR            dvi documentation [DOCDIR]
+  --pdfdir=DIR            pdf documentation [DOCDIR]
+  --psdir=DIR             ps documentation [DOCDIR]
+_ACEOF
+
+  cat <<\_ACEOF
+
+Program names:
+  --program-prefix=PREFIX            prepend PREFIX to installed program names
+  --program-suffix=SUFFIX            append SUFFIX to installed program names
+  --program-transform-name=PROGRAM   run sed PROGRAM on installed program names
+
+System types:
+  --build=BUILD     configure for building on BUILD [guessed]
+  --host=HOST       cross-compile to build programs to run on HOST [BUILD]
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+  case $ac_init_help in
+     short | recursive ) echo "Configuration of leptonica 1.73:";;
+   esac
+  cat <<\_ACEOF
+
+Optional Features:
+  --disable-option-checking  ignore unrecognized --enable/--with options
+  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
+  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
+  --enable-shared[=PKGS]  build shared libraries [default=yes]
+  --enable-static[=PKGS]  build static libraries [default=yes]
+  --enable-fast-install[=PKGS]
+                          optimize for fast installation [default=yes]
+  --disable-libtool-lock  avoid locking (might break parallel builds)
+  --disable-dependency-tracking  speeds up one-time build
+  --enable-dependency-tracking   do not reject slow dependency extractors
+  --disable-programs      do not build additional programs
+  --enable-silent-rules          less verbose build output (undo: `make V=1')
+  --disable-silent-rules         verbose build output (undo: `make V=0')
+
+Optional Packages:
+  --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
+  --without-PACKAGE       do not use PACKAGE (same as --with-PACKAGE=no)
+  --with-pic[=PKGS]       try to use only PIC/non-PIC objects [default=use
+                          both]
+  --with-gnu-ld           assume the C compiler uses GNU ld [default=no]
+  --with-sysroot=DIR Search for dependent libraries within DIR
+                        (or the compiler's sysroot if not specified).
+  --without-zlib          do not include zlib support
+  --without-libpng        do not include libpng support
+  --without-jpeg          do not include jpeg support
+  --without-giflib        do not include giflib support
+  --without-libtiff       do not include libtiff support
+  --without-libwebp       do not include libwebp support
+  --without-libopenjpeg   do not include libopenjpeg support
+
+Some influential environment variables:
+  CC          C compiler command
+  CFLAGS      C compiler flags
+  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a
+              nonstandard directory <lib dir>
+  LIBS        libraries to pass to the linker, e.g. -l<library>
+  CPPFLAGS    (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
+              you have headers in a nonstandard directory <include dir>
+  CPP         C preprocessor
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to the package provider.
+_ACEOF
+ac_status=$?
+fi
+
+if test "$ac_init_help" = "recursive"; then
+  # If there are subdirs, report their specific --help.
+  for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+    test -d "$ac_dir" ||
+      { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
+      continue
+    ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+    cd "$ac_dir" || { ac_status=$?; continue; }
+    # Check for guested configure.
+    if test -f "$ac_srcdir/configure.gnu"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure.gnu" --help=recursive
+    elif test -f "$ac_srcdir/configure"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure" --help=recursive
+    else
+      $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+    fi || ac_status=$?
+    cd "$ac_pwd" || { ac_status=$?; break; }
+  done
+fi
+
+test -n "$ac_init_help" && exit $ac_status
+if $ac_init_version; then
+  cat <<\_ACEOF
+leptonica configure 1.73
+generated by GNU Autoconf 2.68
+
+Copyright (C) 2010 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+  exit
+fi
+
+## ------------------------ ##
+## Autoconf initialization. ##
+## ------------------------ ##
+
+# ac_fn_c_try_compile LINENO
+# --------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext
+  if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+        test -z "$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_compile
+
+# ac_fn_c_try_link LINENO
+# -----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_link ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext conftest$ac_exeext
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+        test -z "$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest$ac_exeext && {
+        test "$cross_compiling" = yes ||
+        $as_test_x conftest$ac_exeext
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=1
+fi
+  # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+  # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+  # interfere with the next link command; also delete a directory that is
+  # left behind by Apple's compiler.  We do this before executing the actions.
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_link
+
+# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists and can be compiled using the include files in
+# INCLUDES, setting the cache variable VAR accordingly.
+ac_fn_c_check_header_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_compile
+
+# ac_fn_c_try_cpp LINENO
+# ----------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_cpp ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } > conftest.i && {
+        test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+    ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_cpp
+
+# ac_fn_c_try_run LINENO
+# ----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
+# that executables *can* be run.
+ac_fn_c_try_run ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: program exited with status $ac_status" >&5
+       $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=$ac_status
+fi
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_run
+
+# ac_fn_c_check_func LINENO FUNC VAR
+# ----------------------------------
+# Tests whether FUNC exists, setting the cache variable VAR accordingly
+ac_fn_c_check_func ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
+   For example, HP-UX 11i <limits.h> declares gettimeofday.  */
+#define $2 innocuous_$2
+
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char $2 (); below.
+    Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+    <limits.h> exists even on freestanding compilers.  */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $2
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $2 ();
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined __stub_$2 || defined __stub___$2
+choke me
+#endif
+
+int
+main ()
+{
+return $2 ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_func
+
+# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists, giving a warning if it cannot be compiled using
+# the include files in INCLUDES and setting the cache variable VAR
+# accordingly.
+ac_fn_c_check_header_mongrel ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if eval \${$3+:} false; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+fi
+eval ac_res=\$$3
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+else
+  # Is the header compilable?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5
+$as_echo_n "checking $2 usability... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_header_compiler=yes
+else
+  ac_header_compiler=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5
+$as_echo "$ac_header_compiler" >&6; }
+
+# Is the header present?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5
+$as_echo_n "checking $2 presence... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <$2>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  ac_header_preproc=yes
+else
+  ac_header_preproc=no
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
+$as_echo "$ac_header_preproc" >&6; }
+
+# So?  What about this header?
+case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #((
+  yes:no: )
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5
+$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+    ;;
+  no:yes:* )
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5
+$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2:     check for missing prerequisite headers?" >&5
+$as_echo "$as_me: WARNING: $2:     check for missing prerequisite headers?" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5
+$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&5
+$as_echo "$as_me: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+    ;;
+esac
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  eval "$3=\$ac_header_compiler"
+fi
+eval ac_res=\$$3
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_mongrel
+
+# ac_fn_c_check_type LINENO TYPE VAR INCLUDES
+# -------------------------------------------
+# Tests whether TYPE exists after having included INCLUDES, setting cache
+# variable VAR accordingly.
+ac_fn_c_check_type ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  eval "$3=no"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+if (sizeof ($2))
+        return 0;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main ()
+{
+if (sizeof (($2)))
+           return 0;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+  eval "$3=yes"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_type
+cat >config.log <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by leptonica $as_me 1.73, which was
+generated by GNU Autoconf 2.68.  Invocation command line was
+
+  $ $0 $@
+
+_ACEOF
+exec 5>>config.log
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null     || echo unknown`
+
+/bin/arch              = `(/bin/arch) 2>/dev/null              || echo unknown`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null       || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+/usr/bin/hostinfo      = `(/usr/bin/hostinfo) 2>/dev/null      || echo unknown`
+/bin/machine           = `(/bin/machine) 2>/dev/null           || echo unknown`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null       || echo unknown`
+/bin/universe          = `(/bin/universe) 2>/dev/null          || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    $as_echo "PATH: $as_dir"
+  done
+IFS=$as_save_IFS
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+  for ac_arg
+  do
+    case $ac_arg in
+    -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+    -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+    | -silent | --silent | --silen | --sile | --sil)
+      continue ;;
+    *\'*)
+      ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    case $ac_pass in
+    1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
+    2)
+      as_fn_append ac_configure_args1 " '$ac_arg'"
+      if test $ac_must_keep_next = true; then
+       ac_must_keep_next=false # Got value, back to normal.
+      else
+       case $ac_arg in
+         *=* | --config-cache | -C | -disable-* | --disable-* \
+         | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+         | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+         | -with-* | --with-* | -without-* | --without-* | --x)
+           case "$ac_configure_args0 " in
+             "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+           esac
+           ;;
+         -* ) ac_must_keep_next=true ;;
+       esac
+      fi
+      as_fn_append ac_configure_args " '$ac_arg'"
+      ;;
+    esac
+  done
+done
+{ ac_configure_args0=; unset ac_configure_args0;}
+{ ac_configure_args1=; unset ac_configure_args1;}
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log.  We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Use '\'' to represent an apostrophe within the trap.
+# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
+trap 'exit_status=$?
+  # Save into config.log some information that might help in debugging.
+  {
+    echo
+
+    $as_echo "## ---------------- ##
+## Cache variables. ##
+## ---------------- ##"
+    echo
+    # The following way of writing the cache mishandles newlines in values,
+(
+  for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+  (set) 2>&1 |
+    case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      sed -n \
+       "s/'\''/'\''\\\\'\'''\''/g;
+         s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
+      ;; #(
+    *)
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+)
+    echo
+
+    $as_echo "## ----------------- ##
+## Output variables. ##
+## ----------------- ##"
+    echo
+    for ac_var in $ac_subst_vars
+    do
+      eval ac_val=\$$ac_var
+      case $ac_val in
+      *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+      esac
+      $as_echo "$ac_var='\''$ac_val'\''"
+    done | sort
+    echo
+
+    if test -n "$ac_subst_files"; then
+      $as_echo "## ------------------- ##
+## File substitutions. ##
+## ------------------- ##"
+      echo
+      for ac_var in $ac_subst_files
+      do
+       eval ac_val=\$$ac_var
+       case $ac_val in
+       *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+       esac
+       $as_echo "$ac_var='\''$ac_val'\''"
+      done | sort
+      echo
+    fi
+
+    if test -s confdefs.h; then
+      $as_echo "## ----------- ##
+## confdefs.h. ##
+## ----------- ##"
+      echo
+      cat confdefs.h
+      echo
+    fi
+    test "$ac_signal" != 0 &&
+      $as_echo "$as_me: caught signal $ac_signal"
+    $as_echo "$as_me: exit $exit_status"
+  } >&5
+  rm -f core *.core core.conftest.* &&
+    rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
+    exit $exit_status
+' 0
+for ac_signal in 1 2 13 15; do
+  trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -f -r conftest* confdefs.h
+
+$as_echo "/* confdefs.h */" > confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_URL "$PACKAGE_URL"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer an explicitly selected file to automatically selected ones.
+ac_site_file1=NONE
+ac_site_file2=NONE
+if test -n "$CONFIG_SITE"; then
+  # We do not want a PATH search for config.site.
+  case $CONFIG_SITE in #((
+    -*)  ac_site_file1=./$CONFIG_SITE;;
+    */*) ac_site_file1=$CONFIG_SITE;;
+    *)   ac_site_file1=./$CONFIG_SITE;;
+  esac
+elif test "x$prefix" != xNONE; then
+  ac_site_file1=$prefix/share/config.site
+  ac_site_file2=$prefix/etc/config.site
+else
+  ac_site_file1=$ac_default_prefix/share/config.site
+  ac_site_file2=$ac_default_prefix/etc/config.site
+fi
+for ac_site_file in "$ac_site_file1" "$ac_site_file2"
+do
+  test "x$ac_site_file" = xNONE && continue
+  if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
+$as_echo "$as_me: loading site script $ac_site_file" >&6;}
+    sed 's/^/| /' "$ac_site_file" >&5
+    . "$ac_site_file" \
+      || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "failed to load site script $ac_site_file
+See \`config.log' for more details" "$LINENO" 5; }
+  fi
+done
+
+if test -r "$cache_file"; then
+  # Some versions of bash will fail to source /dev/null (special files
+  # actually), so we avoid doing that.  DJGPP emulates it as a regular file.
+  if test /dev/null != "$cache_file" && test -f "$cache_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
+$as_echo "$as_me: loading cache $cache_file" >&6;}
+    case $cache_file in
+      [\\/]* | ?:[\\/]* ) . "$cache_file";;
+      *)                      . "./$cache_file";;
+    esac
+  fi
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
+$as_echo "$as_me: creating cache $cache_file" >&6;}
+  >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+  eval ac_old_set=\$ac_cv_env_${ac_var}_set
+  eval ac_new_set=\$ac_env_${ac_var}_set
+  eval ac_old_val=\$ac_cv_env_${ac_var}_value
+  eval ac_new_val=\$ac_env_${ac_var}_value
+  case $ac_old_set,$ac_new_set in
+    set,)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,set)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,);;
+    *)
+      if test "x$ac_old_val" != "x$ac_new_val"; then
+       # differences in whitespace do not lead to failure.
+       ac_old_val_w=`echo x $ac_old_val`
+       ac_new_val_w=`echo x $ac_new_val`
+       if test "$ac_old_val_w" != "$ac_new_val_w"; then
+         { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
+$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+         ac_cache_corrupted=:
+       else
+         { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
+$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
+         eval $ac_var=\$ac_old_val
+       fi
+       { $as_echo "$as_me:${as_lineno-$LINENO}:   former value:  \`$ac_old_val'" >&5
+$as_echo "$as_me:   former value:  \`$ac_old_val'" >&2;}
+       { $as_echo "$as_me:${as_lineno-$LINENO}:   current value: \`$ac_new_val'" >&5
+$as_echo "$as_me:   current value: \`$ac_new_val'" >&2;}
+      fi;;
+  esac
+  # Pass precious variables to config.status.
+  if test "$ac_new_set" = set; then
+    case $ac_new_val in
+    *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+    *) ac_arg=$ac_var=$ac_new_val ;;
+    esac
+    case " $ac_configure_args " in
+      *" '$ac_arg' "*) ;; # Avoid dups.  Use of quotes ensures accuracy.
+      *) as_fn_append ac_configure_args " '$ac_arg'" ;;
+    esac
+  fi
+done
+if $ac_cache_corrupted; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+  as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
+fi
+## -------------------- ##
+## Main body of script. ##
+## -------------------- ##
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ac_aux_dir=
+for ac_dir in config "$srcdir"/config; do
+  if test -f "$ac_dir/install-sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install-sh -c"
+    break
+  elif test -f "$ac_dir/install.sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install.sh -c"
+    break
+  elif test -f "$ac_dir/shtool"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/shtool install -c"
+    break
+  fi
+done
+if test -z "$ac_aux_dir"; then
+  as_fn_error $? "cannot find install-sh, install.sh, or shtool in config \"$srcdir\"/config" "$LINENO" 5
+fi
+
+# These three variables are undocumented and unsupported,
+# and are intended to be withdrawn in a future Autoconf release.
+# They can cause serious problems if a builder's source tree is in a directory
+# whose full name contains unusual characters.
+ac_config_guess="$SHELL $ac_aux_dir/config.guess"  # Please don't use this var.
+ac_config_sub="$SHELL $ac_aux_dir/config.sub"  # Please don't use this var.
+ac_configure="$SHELL $ac_aux_dir/configure"  # Please don't use this var.
+
+
+ac_config_headers="$ac_config_headers config_auto.h:config/config.h.in"
+
+
+
+
+case `pwd` in
+  *\ * | *\    *)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&5
+$as_echo "$as_me: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&2;} ;;
+esac
+
+
+
+macro_version='2.4.2'
+macro_revision='1.3337'
+
+
+
+
+
+
+
+
+
+
+
+
+
+ltmain="$ac_aux_dir/ltmain.sh"
+
+# Make sure we can run config.sub.
+$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 ||
+  as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5
+$as_echo_n "checking build system type... " >&6; }
+if ${ac_cv_build+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_build_alias=$build_alias
+test "x$ac_build_alias" = x &&
+  ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"`
+test "x$ac_build_alias" = x &&
+  as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5
+ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` ||
+  as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
+$as_echo "$ac_cv_build" >&6; }
+case $ac_cv_build in
+*-*-*) ;;
+*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;;
+esac
+build=$ac_cv_build
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_build
+shift
+build_cpu=$1
+build_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+build_os=$*
+IFS=$ac_save_IFS
+case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5
+$as_echo_n "checking host system type... " >&6; }
+if ${ac_cv_host+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test "x$host_alias" = x; then
+  ac_cv_host=$ac_cv_build
+else
+  ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` ||
+    as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5
+$as_echo "$ac_cv_host" >&6; }
+case $ac_cv_host in
+*-*-*) ;;
+*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;;
+esac
+host=$ac_cv_host
+ac_save_IFS=$IFS; IFS='-'
+set x $ac_cv_host
+shift
+host_cpu=$1
+host_vendor=$2
+shift; shift
+# Remember, the first character of IFS is used to create $*,
+# except with old shells:
+host_os=$*
+IFS=$ac_save_IFS
+case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac
+
+
+# Backslashify metacharacters that are still active within
+# double-quoted strings.
+sed_quote_subst='s/\(["`$\\]\)/\\\1/g'
+
+# Same as above, but do not quote variable references.
+double_quote_subst='s/\(["`\\]\)/\\\1/g'
+
+# Sed substitution to delay expansion of an escaped shell variable in a
+# double_quote_subst'ed string.
+delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g'
+
+# Sed substitution to delay expansion of an escaped single quote.
+delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g'
+
+# Sed substitution to avoid accidental globbing in evaled expressions
+no_glob_subst='s/\*/\\\*/g'
+
+ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to print strings" >&5
+$as_echo_n "checking how to print strings... " >&6; }
+# Test print first, because it will be a builtin if present.
+if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \
+   test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then
+  ECHO='print -r --'
+elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then
+  ECHO='printf %s\n'
+else
+  # Use this function as a fallback that always works.
+  func_fallback_echo ()
+  {
+    eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+  }
+  ECHO='func_fallback_echo'
+fi
+
+# func_echo_all arg...
+# Invoke $ECHO with all args, space-separated.
+func_echo_all ()
+{
+    $ECHO ""
+}
+
+case "$ECHO" in
+  printf*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: printf" >&5
+$as_echo "printf" >&6; } ;;
+  print*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: print -r" >&5
+$as_echo "print -r" >&6; } ;;
+  *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: cat" >&5
+$as_echo "cat" >&6; } ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="${ac_tool_prefix}gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+  ac_ct_CC=$CC
+  # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_CC="gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+else
+  CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+          if test -n "$ac_tool_prefix"; then
+    # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="${ac_tool_prefix}cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  fi
+fi
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+       ac_prog_rejected=yes
+       continue
+     fi
+    ac_cv_prog_CC="cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# != 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+  fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+  if test -n "$ac_tool_prefix"; then
+  for ac_prog in cl.exe
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$CC" && break
+  done
+fi
+if test -z "$CC"; then
+  ac_ct_CC=$CC
+  for ac_prog in cl.exe
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_CC="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_CC" && break
+done
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+fi
+
+fi
+
+
+test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "no acceptable C compiler found in \$PATH
+See \`config.log' for more details" "$LINENO" 5; }
+
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+  { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    sed '10a\
+... rest of stderr output deleted ...
+         10q' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+  fi
+  rm -f conftest.er1 conftest.err
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+done
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
+$as_echo_n "checking whether the C compiler works... " >&6; }
+ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+
+# The possible output files:
+ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
+
+ac_rmfiles=
+for ac_file in $ac_files
+do
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    * ) ac_rmfiles="$ac_rmfiles $ac_file";;
+  esac
+done
+rm -f $ac_rmfiles
+
+if { { ac_try="$ac_link_default"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link_default") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
+# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
+# in a Makefile.  We should not override ac_cv_exeext if it was cached,
+# so that the user can short-circuit this test for compilers unknown to
+# Autoconf.
+for ac_file in $ac_files ''
+do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
+       ;;
+    [ab].out )
+       # We found the default executable, but exeext='' is most
+       # certainly right.
+       break;;
+    *.* )
+       if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
+       then :; else
+          ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+       fi
+       # We set ac_cv_exeext here because the later test for it is not
+       # safe: cross compilers may not add the suffix if given an `-o'
+       # argument, so we may need to know it at that point already.
+       # Even if this section looks crufty: it has the advantage of
+       # actually working.
+       break;;
+    * )
+       break;;
+  esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
+
+else
+  ac_file=''
+fi
+if test -z "$ac_file"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+$as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "C compiler cannot create executables
+See \`config.log' for more details" "$LINENO" 5; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
+$as_echo_n "checking for C compiler default output file name... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
+$as_echo "$ac_file" >&6; }
+ac_exeext=$ac_cv_exeext
+
+rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
+$as_echo_n "checking for suffix of executables... " >&6; }
+if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'.  For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+         break;;
+    * ) break;;
+  esac
+done
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest conftest$ac_cv_exeext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
+$as_echo "$ac_cv_exeext" >&6; }
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdio.h>
+int
+main ()
+{
+FILE *f = fopen ("conftest.out", "w");
+ return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files="$ac_clean_files conftest.out"
+# Check that the compiler produces executables we can run.  If not, either
+# the compiler is broken, or we cross compile.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
+$as_echo_n "checking whether we are cross compiling... " >&6; }
+if test "$cross_compiling" != yes; then
+  { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+  if { ac_try='./conftest$ac_cv_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then
+    cross_compiling=no
+  else
+    if test "$cross_compiling" = maybe; then
+       cross_compiling=yes
+    else
+       { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details" "$LINENO" 5; }
+    fi
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
+$as_echo "$cross_compiling" >&6; }
+
+rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
+$as_echo_n "checking for suffix of object files... " >&6; }
+if ${ac_cv_objext+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  for ac_file in conftest.o conftest.obj conftest.*; do
+  test -f "$ac_file" || continue;
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
+    *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+       break;;
+  esac
+done
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of object files: cannot compile
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
+$as_echo "$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+if ${ac_cv_c_compiler_gnu+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_compiler_gnu=yes
+else
+  ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+  GCC=yes
+else
+  GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+$as_echo_n "checking whether $CC accepts -g... " >&6; }
+if ${ac_cv_prog_cc_g+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_c_werror_flag=$ac_c_werror_flag
+   ac_c_werror_flag=yes
+   ac_cv_prog_cc_g=no
+   CFLAGS="-g"
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+else
+  CFLAGS=""
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+  ac_c_werror_flag=$ac_save_c_werror_flag
+        CFLAGS="-g"
+        cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+   ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+$as_echo "$ac_cv_prog_cc_g" >&6; }
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+if ${ac_cv_prog_cc_c89+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+     char **p;
+     int i;
+{
+  return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+  char *s;
+  va_list v;
+  va_start (v,p);
+  s = g (p, va_arg (v,int));
+  va_end (v);
+  return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default.  It has
+   function prototypes and stuff, but not '\xHH' hex character constants.
+   These don't provoke an error unfortunately, instead are silently treated
+   as 'x'.  The following induces an error, until -std is added to get
+   proper ANSI mode.  Curiously '\x00'!='x' always comes out true, for an
+   array size at least.  It's necessary to write '\x00'==0 to get something
+   that's true only with -std.  */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+   inside strings and character constants.  */
+#define FOO(x) 'x'
+int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0]  ||  f (e, argv, 1) != argv[1];
+  ;
+  return 0;
+}
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
+       -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+  CC="$ac_save_CC $ac_arg"
+  if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_c89=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext
+  test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c89" in
+  x)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+  xno)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+  *)
+    CC="$CC $ac_cv_prog_cc_c89"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c89" != xno; then :
+
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5
+$as_echo_n "checking for a sed that does not truncate output... " >&6; }
+if ${ac_cv_path_SED+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+            ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/
+     for ac_i in 1 2 3 4 5 6 7; do
+       ac_script="$ac_script$as_nl$ac_script"
+     done
+     echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed
+     { ac_script=; unset ac_script;}
+     if test -z "$SED"; then
+  ac_path_SED_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in sed gsed; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_SED="$as_dir/$ac_prog$ac_exec_ext"
+      { test -f "$ac_path_SED" && $as_test_x "$ac_path_SED"; } || continue
+# Check for GNU ac_path_SED and select it if it is found.
+  # Check for GNU $ac_path_SED
+case `"$ac_path_SED" --version 2>&1` in
+*GNU*)
+  ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo '' >> "conftest.nl"
+    "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_SED_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_SED="$ac_path_SED"
+      ac_path_SED_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_SED_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_SED"; then
+    as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5
+  fi
+else
+  ac_cv_path_SED=$SED
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5
+$as_echo "$ac_cv_path_SED" >&6; }
+ SED="$ac_cv_path_SED"
+  rm -f conftest.sed
+
+test -z "$SED" && SED=sed
+Xsed="$SED -e 1s/^X//"
+
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
+$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
+if ${ac_cv_path_GREP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -z "$GREP"; then
+  ac_path_GREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in grep ggrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
+      { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue
+# Check for GNU ac_path_GREP and select it if it is found.
+  # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'GREP' >> "conftest.nl"
+    "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_GREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_GREP="$ac_path_GREP"
+      ac_path_GREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_GREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_GREP"; then
+    as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_GREP=$GREP
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
+$as_echo "$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+$as_echo_n "checking for egrep... " >&6; }
+if ${ac_cv_path_EGREP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+   then ac_cv_path_EGREP="$GREP -E"
+   else
+     if test -z "$EGREP"; then
+  ac_path_EGREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in egrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
+      { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+  # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'EGREP' >> "conftest.nl"
+    "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_EGREP="$ac_path_EGREP"
+      ac_path_EGREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_EGREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_EGREP"; then
+    as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_EGREP=$EGREP
+fi
+
+   fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+$as_echo "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for fgrep" >&5
+$as_echo_n "checking for fgrep... " >&6; }
+if ${ac_cv_path_FGREP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if echo 'ab*c' | $GREP -F 'ab*c' >/dev/null 2>&1
+   then ac_cv_path_FGREP="$GREP -F"
+   else
+     if test -z "$FGREP"; then
+  ac_path_FGREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in fgrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_FGREP="$as_dir/$ac_prog$ac_exec_ext"
+      { test -f "$ac_path_FGREP" && $as_test_x "$ac_path_FGREP"; } || continue
+# Check for GNU ac_path_FGREP and select it if it is found.
+  # Check for GNU $ac_path_FGREP
+case `"$ac_path_FGREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'FGREP' >> "conftest.nl"
+    "$ac_path_FGREP" FGREP < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_FGREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_FGREP="$ac_path_FGREP"
+      ac_path_FGREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_FGREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_FGREP"; then
+    as_fn_error $? "no acceptable fgrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_FGREP=$FGREP
+fi
+
+   fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_FGREP" >&5
+$as_echo "$ac_cv_path_FGREP" >&6; }
+ FGREP="$ac_cv_path_FGREP"
+
+
+test -z "$GREP" && GREP=grep
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Check whether --with-gnu-ld was given.
+if test "${with_gnu_ld+set}" = set; then :
+  withval=$with_gnu_ld; test "$withval" = no || with_gnu_ld=yes
+else
+  with_gnu_ld=no
+fi
+
+ac_prog=ld
+if test "$GCC" = yes; then
+  # Check if gcc -print-prog-name=ld gives a path.
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5
+$as_echo_n "checking for ld used by $CC... " >&6; }
+  case $host in
+  *-*-mingw*)
+    # gcc leaves a trailing carriage return which upsets mingw
+    ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+  *)
+    ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+  esac
+  case $ac_prog in
+    # Accept absolute paths.
+    [\\/]* | ?:[\\/]*)
+      re_direlt='/[^/][^/]*/\.\./'
+      # Canonicalize the pathname of ld
+      ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'`
+      while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do
+       ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"`
+      done
+      test -z "$LD" && LD="$ac_prog"
+      ;;
+  "")
+    # If it fails, then pretend we aren't using GCC.
+    ac_prog=ld
+    ;;
+  *)
+    # If it is relative, then search for the first ld in PATH.
+    with_gnu_ld=unknown
+    ;;
+  esac
+elif test "$with_gnu_ld" = yes; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5
+$as_echo_n "checking for GNU ld... " >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5
+$as_echo_n "checking for non-GNU ld... " >&6; }
+fi
+if ${lt_cv_path_LD+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -z "$LD"; then
+  lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+  for ac_dir in $PATH; do
+    IFS="$lt_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+      lt_cv_path_LD="$ac_dir/$ac_prog"
+      # Check to see if the program is GNU ld.  I'd rather use --version,
+      # but apparently some variants of GNU ld only accept -v.
+      # Break only if it was the GNU/non-GNU ld that we prefer.
+      case `"$lt_cv_path_LD" -v 2>&1 </dev/null` in
+      *GNU* | *'with BFD'*)
+       test "$with_gnu_ld" != no && break
+       ;;
+      *)
+       test "$with_gnu_ld" != yes && break
+       ;;
+      esac
+    fi
+  done
+  IFS="$lt_save_ifs"
+else
+  lt_cv_path_LD="$LD" # Let the user override the test with a path.
+fi
+fi
+
+LD="$lt_cv_path_LD"
+if test -n "$LD"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LD" >&5
+$as_echo "$LD" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5
+$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; }
+if ${lt_cv_prog_gnu_ld+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  # I'd rather use --version here, but apparently some GNU lds only accept -v.
+case `$LD -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+  lt_cv_prog_gnu_ld=yes
+  ;;
+*)
+  lt_cv_prog_gnu_ld=no
+  ;;
+esac
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_gnu_ld" >&5
+$as_echo "$lt_cv_prog_gnu_ld" >&6; }
+with_gnu_ld=$lt_cv_prog_gnu_ld
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for BSD- or MS-compatible name lister (nm)" >&5
+$as_echo_n "checking for BSD- or MS-compatible name lister (nm)... " >&6; }
+if ${lt_cv_path_NM+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$NM"; then
+  # Let the user override the test.
+  lt_cv_path_NM="$NM"
+else
+  lt_nm_to_check="${ac_tool_prefix}nm"
+  if test -n "$ac_tool_prefix" && test "$build" = "$host"; then
+    lt_nm_to_check="$lt_nm_to_check nm"
+  fi
+  for lt_tmp_nm in $lt_nm_to_check; do
+    lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+    for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do
+      IFS="$lt_save_ifs"
+      test -z "$ac_dir" && ac_dir=.
+      tmp_nm="$ac_dir/$lt_tmp_nm"
+      if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext" ; then
+       # Check to see if the nm accepts a BSD-compat flag.
+       # Adding the `sed 1q' prevents false positives on HP-UX, which says:
+       #   nm: unknown option "B" ignored
+       # Tru64's nm complains that /dev/null is an invalid object file
+       case `"$tmp_nm" -B /dev/null 2>&1 | sed '1q'` in
+       */dev/null* | *'Invalid file or object type'*)
+         lt_cv_path_NM="$tmp_nm -B"
+         break
+         ;;
+       *)
+         case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in
+         */dev/null*)
+           lt_cv_path_NM="$tmp_nm -p"
+           break
+           ;;
+         *)
+           lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but
+           continue # so that we can try to find one that supports BSD flags
+           ;;
+         esac
+         ;;
+       esac
+      fi
+    done
+    IFS="$lt_save_ifs"
+  done
+  : ${lt_cv_path_NM=no}
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_NM" >&5
+$as_echo "$lt_cv_path_NM" >&6; }
+if test "$lt_cv_path_NM" != "no"; then
+  NM="$lt_cv_path_NM"
+else
+  # Didn't find any BSD compatible name lister, look for dumpbin.
+  if test -n "$DUMPBIN"; then :
+    # Let the user override the test.
+  else
+    if test -n "$ac_tool_prefix"; then
+  for ac_prog in dumpbin "link -dump"
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_DUMPBIN+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$DUMPBIN"; then
+  ac_cv_prog_DUMPBIN="$DUMPBIN" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_DUMPBIN="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+DUMPBIN=$ac_cv_prog_DUMPBIN
+if test -n "$DUMPBIN"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DUMPBIN" >&5
+$as_echo "$DUMPBIN" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$DUMPBIN" && break
+  done
+fi
+if test -z "$DUMPBIN"; then
+  ac_ct_DUMPBIN=$DUMPBIN
+  for ac_prog in dumpbin "link -dump"
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_DUMPBIN+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_DUMPBIN"; then
+  ac_cv_prog_ac_ct_DUMPBIN="$ac_ct_DUMPBIN" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_DUMPBIN="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_DUMPBIN=$ac_cv_prog_ac_ct_DUMPBIN
+if test -n "$ac_ct_DUMPBIN"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DUMPBIN" >&5
+$as_echo "$ac_ct_DUMPBIN" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_DUMPBIN" && break
+done
+
+  if test "x$ac_ct_DUMPBIN" = x; then
+    DUMPBIN=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    DUMPBIN=$ac_ct_DUMPBIN
+  fi
+fi
+
+    case `$DUMPBIN -symbols /dev/null 2>&1 | sed '1q'` in
+    *COFF*)
+      DUMPBIN="$DUMPBIN -symbols"
+      ;;
+    *)
+      DUMPBIN=:
+      ;;
+    esac
+  fi
+
+  if test "$DUMPBIN" != ":"; then
+    NM="$DUMPBIN"
+  fi
+fi
+test -z "$NM" && NM=nm
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the name lister ($NM) interface" >&5
+$as_echo_n "checking the name lister ($NM) interface... " >&6; }
+if ${lt_cv_nm_interface+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_nm_interface="BSD nm"
+  echo "int some_variable = 0;" > conftest.$ac_ext
+  (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&5)
+  (eval "$ac_compile" 2>conftest.err)
+  cat conftest.err >&5
+  (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&5)
+  (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
+  cat conftest.err >&5
+  (eval echo "\"\$as_me:$LINENO: output\"" >&5)
+  cat conftest.out >&5
+  if $GREP 'External.*some_variable' conftest.out > /dev/null; then
+    lt_cv_nm_interface="MS dumpbin"
+  fi
+  rm -f conftest*
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_nm_interface" >&5
+$as_echo "$lt_cv_nm_interface" >&6; }
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5
+$as_echo_n "checking whether ln -s works... " >&6; }
+LN_S=$as_ln_s
+if test "$LN_S" = "ln -s"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5
+$as_echo "no, using $LN_S" >&6; }
+fi
+
+# find the maximum length of command line arguments
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the maximum length of command line arguments" >&5
+$as_echo_n "checking the maximum length of command line arguments... " >&6; }
+if ${lt_cv_sys_max_cmd_len+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+    i=0
+  teststring="ABCD"
+
+  case $build_os in
+  msdosdjgpp*)
+    # On DJGPP, this test can blow up pretty badly due to problems in libc
+    # (any single argument exceeding 2000 bytes causes a buffer overrun
+    # during glob expansion).  Even if it were fixed, the result of this
+    # check would be larger than it should be.
+    lt_cv_sys_max_cmd_len=12288;    # 12K is about right
+    ;;
+
+  gnu*)
+    # Under GNU Hurd, this test is not required because there is
+    # no limit to the length of command line arguments.
+    # Libtool will interpret -1 as no limit whatsoever
+    lt_cv_sys_max_cmd_len=-1;
+    ;;
+
+  cygwin* | mingw* | cegcc*)
+    # On Win9x/ME, this test blows up -- it succeeds, but takes
+    # about 5 minutes as the teststring grows exponentially.
+    # Worse, since 9x/ME are not pre-emptively multitasking,
+    # you end up with a "frozen" computer, even though with patience
+    # the test eventually succeeds (with a max line length of 256k).
+    # Instead, let's just punt: use the minimum linelength reported by
+    # all of the supported platforms: 8192 (on NT/2K/XP).
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  mint*)
+    # On MiNT this can take a long time and run out of memory.
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  amigaos*)
+    # On AmigaOS with pdksh, this test takes hours, literally.
+    # So we just punt and use a minimum line length of 8192.
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  netbsd* | freebsd* | openbsd* | darwin* | dragonfly*)
+    # This has been around since 386BSD, at least.  Likely further.
+    if test -x /sbin/sysctl; then
+      lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax`
+    elif test -x /usr/sbin/sysctl; then
+      lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax`
+    else
+      lt_cv_sys_max_cmd_len=65536      # usable default for all BSDs
+    fi
+    # And add a safety zone
+    lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+    lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+    ;;
+
+  interix*)
+    # We know the value 262144 and hardcode it with a safety zone (like BSD)
+    lt_cv_sys_max_cmd_len=196608
+    ;;
+
+  os2*)
+    # The test takes a long time on OS/2.
+    lt_cv_sys_max_cmd_len=8192
+    ;;
+
+  osf*)
+    # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure
+    # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not
+    # nice to cause kernel panics so lets avoid the loop below.
+    # First set a reasonable default.
+    lt_cv_sys_max_cmd_len=16384
+    #
+    if test -x /sbin/sysconfig; then
+      case `/sbin/sysconfig -q proc exec_disable_arg_limit` in
+        *1*) lt_cv_sys_max_cmd_len=-1 ;;
+      esac
+    fi
+    ;;
+  sco3.2v5*)
+    lt_cv_sys_max_cmd_len=102400
+    ;;
+  sysv5* | sco5v6* | sysv4.2uw2*)
+    kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null`
+    if test -n "$kargmax"; then
+      lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[         ]//'`
+    else
+      lt_cv_sys_max_cmd_len=32768
+    fi
+    ;;
+  *)
+    lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null`
+    if test -n "$lt_cv_sys_max_cmd_len"; then
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+    else
+      # Make teststring a little bigger before we do anything with it.
+      # a 1K string should be a reasonable start.
+      for i in 1 2 3 4 5 6 7 8 ; do
+        teststring=$teststring$teststring
+      done
+      SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}}
+      # If test is not a shell built-in, we'll probably end up computing a
+      # maximum length that is only half of the actual maximum length, but
+      # we can't tell.
+      while { test "X"`env echo "$teststring$teststring" 2>/dev/null` \
+                = "X$teststring$teststring"; } >/dev/null 2>&1 &&
+             test $i != 17 # 1/2 MB should be enough
+      do
+        i=`expr $i + 1`
+        teststring=$teststring$teststring
+      done
+      # Only check the string length outside the loop.
+      lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1`
+      teststring=
+      # Add a significant safety factor because C++ compilers can tack on
+      # massive amounts of additional arguments before passing them to the
+      # linker.  It appears as though 1/2 is a usable value.
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2`
+    fi
+    ;;
+  esac
+
+fi
+
+if test -n $lt_cv_sys_max_cmd_len ; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sys_max_cmd_len" >&5
+$as_echo "$lt_cv_sys_max_cmd_len" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5
+$as_echo "none" >&6; }
+fi
+max_cmd_len=$lt_cv_sys_max_cmd_len
+
+
+
+
+
+
+: ${CP="cp -f"}
+: ${MV="mv -f"}
+: ${RM="rm -f"}
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the shell understands some XSI constructs" >&5
+$as_echo_n "checking whether the shell understands some XSI constructs... " >&6; }
+# Try some XSI features
+xsi_shell=no
+( _lt_dummy="a/b/c"
+  test "${_lt_dummy##*/},${_lt_dummy%/*},${_lt_dummy#??}"${_lt_dummy%"$_lt_dummy"}, \
+      = c,a/b,b/c, \
+    && eval 'test $(( 1 + 1 )) -eq 2 \
+    && test "${#_lt_dummy}" -eq 5' ) >/dev/null 2>&1 \
+  && xsi_shell=yes
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $xsi_shell" >&5
+$as_echo "$xsi_shell" >&6; }
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the shell understands \"+=\"" >&5
+$as_echo_n "checking whether the shell understands \"+=\"... " >&6; }
+lt_shell_append=no
+( foo=bar; set foo baz; eval "$1+=\$2" && test "$foo" = barbaz ) \
+    >/dev/null 2>&1 \
+  && lt_shell_append=yes
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_shell_append" >&5
+$as_echo "$lt_shell_append" >&6; }
+
+
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+  lt_unset=unset
+else
+  lt_unset=false
+fi
+
+
+
+
+
+# test EBCDIC or ASCII
+case `echo X|tr X '\101'` in
+ A) # ASCII based system
+    # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr
+  lt_SP2NL='tr \040 \012'
+  lt_NL2SP='tr \015\012 \040\040'
+  ;;
+ *) # EBCDIC based system
+  lt_SP2NL='tr \100 \n'
+  lt_NL2SP='tr \r\n \100\100'
+  ;;
+esac
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to $host format" >&5
+$as_echo_n "checking how to convert $build file names to $host format... " >&6; }
+if ${lt_cv_to_host_file_cmd+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $host in
+  *-*-mingw* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32
+        ;;
+      *-*-cygwin* )
+        lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32
+        ;;
+      * ) # otherwise, assume *nix
+        lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32
+        ;;
+    esac
+    ;;
+  *-*-cygwin* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin
+        ;;
+      *-*-cygwin* )
+        lt_cv_to_host_file_cmd=func_convert_file_noop
+        ;;
+      * ) # otherwise, assume *nix
+        lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin
+        ;;
+    esac
+    ;;
+  * ) # unhandled hosts (and "normal" native builds)
+    lt_cv_to_host_file_cmd=func_convert_file_noop
+    ;;
+esac
+
+fi
+
+to_host_file_cmd=$lt_cv_to_host_file_cmd
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_host_file_cmd" >&5
+$as_echo "$lt_cv_to_host_file_cmd" >&6; }
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to toolchain format" >&5
+$as_echo_n "checking how to convert $build file names to toolchain format... " >&6; }
+if ${lt_cv_to_tool_file_cmd+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  #assume ordinary cross tools, or native build.
+lt_cv_to_tool_file_cmd=func_convert_file_noop
+case $host in
+  *-*-mingw* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32
+        ;;
+    esac
+    ;;
+esac
+
+fi
+
+to_tool_file_cmd=$lt_cv_to_tool_file_cmd
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_tool_file_cmd" >&5
+$as_echo "$lt_cv_to_tool_file_cmd" >&6; }
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $LD option to reload object files" >&5
+$as_echo_n "checking for $LD option to reload object files... " >&6; }
+if ${lt_cv_ld_reload_flag+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_ld_reload_flag='-r'
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_reload_flag" >&5
+$as_echo "$lt_cv_ld_reload_flag" >&6; }
+reload_flag=$lt_cv_ld_reload_flag
+case $reload_flag in
+"" | " "*) ;;
+*) reload_flag=" $reload_flag" ;;
+esac
+reload_cmds='$LD$reload_flag -o $output$reload_objs'
+case $host_os in
+  cygwin* | mingw* | pw32* | cegcc*)
+    if test "$GCC" != yes; then
+      reload_cmds=false
+    fi
+    ;;
+  darwin*)
+    if test "$GCC" = yes; then
+      reload_cmds='$LTCC $LTCFLAGS -nostdlib ${wl}-r -o $output$reload_objs'
+    else
+      reload_cmds='$LD$reload_flag -o $output$reload_objs'
+    fi
+    ;;
+esac
+
+
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args.
+set dummy ${ac_tool_prefix}objdump; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_OBJDUMP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$OBJDUMP"; then
+  ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+OBJDUMP=$ac_cv_prog_OBJDUMP
+if test -n "$OBJDUMP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5
+$as_echo "$OBJDUMP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OBJDUMP"; then
+  ac_ct_OBJDUMP=$OBJDUMP
+  # Extract the first word of "objdump", so it can be a program name with args.
+set dummy objdump; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_OBJDUMP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_OBJDUMP"; then
+  ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_OBJDUMP="objdump"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP
+if test -n "$ac_ct_OBJDUMP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5
+$as_echo "$ac_ct_OBJDUMP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_OBJDUMP" = x; then
+    OBJDUMP="false"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    OBJDUMP=$ac_ct_OBJDUMP
+  fi
+else
+  OBJDUMP="$ac_cv_prog_OBJDUMP"
+fi
+
+test -z "$OBJDUMP" && OBJDUMP=objdump
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to recognize dependent libraries" >&5
+$as_echo_n "checking how to recognize dependent libraries... " >&6; }
+if ${lt_cv_deplibs_check_method+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_file_magic_cmd='$MAGIC_CMD'
+lt_cv_file_magic_test_file=
+lt_cv_deplibs_check_method='unknown'
+# Need to set the preceding variable on all platforms that support
+# interlibrary dependencies.
+# 'none' -- dependencies not supported.
+# `unknown' -- same as none, but documents that we really don't know.
+# 'pass_all' -- all dependencies passed with no checks.
+# 'test_compile' -- check by making test program.
+# 'file_magic [[regex]]' -- check by looking for files in library path
+# which responds to the $file_magic_cmd with a given extended regex.
+# If you have `file' or equivalent on your system and you're not sure
+# whether `pass_all' will *always* work, you probably want this one.
+
+case $host_os in
+aix[4-9]*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+beos*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+bsdi[45]*)
+  lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)'
+  lt_cv_file_magic_cmd='/usr/bin/file -L'
+  lt_cv_file_magic_test_file=/shlib/libc.so
+  ;;
+
+cygwin*)
+  # func_win32_libid is a shell function defined in ltmain.sh
+  lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+  lt_cv_file_magic_cmd='func_win32_libid'
+  ;;
+
+mingw* | pw32*)
+  # Base MSYS/MinGW do not provide the 'file' command needed by
+  # func_win32_libid shell function, so use a weaker test based on 'objdump',
+  # unless we find 'file', for example because we are cross-compiling.
+  # func_win32_libid assumes BSD nm, so disallow it if using MS dumpbin.
+  if ( test "$lt_cv_nm_interface" = "BSD nm" && file / ) >/dev/null 2>&1; then
+    lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+    lt_cv_file_magic_cmd='func_win32_libid'
+  else
+    # Keep this pattern in sync with the one in func_win32_libid.
+    lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)'
+    lt_cv_file_magic_cmd='$OBJDUMP -f'
+  fi
+  ;;
+
+cegcc*)
+  # use the weaker test based on 'objdump'. See mingw*.
+  lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?'
+  lt_cv_file_magic_cmd='$OBJDUMP -f'
+  ;;
+
+darwin* | rhapsody*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+freebsd* | dragonfly*)
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+    case $host_cpu in
+    i*86 )
+      # Not sure whether the presence of OpenBSD here was a mistake.
+      # Let's accept both of them until this is cleared up.
+      lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library'
+      lt_cv_file_magic_cmd=/usr/bin/file
+      lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*`
+      ;;
+    esac
+  else
+    lt_cv_deplibs_check_method=pass_all
+  fi
+  ;;
+
+gnu*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+haiku*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+hpux10.20* | hpux11*)
+  lt_cv_file_magic_cmd=/usr/bin/file
+  case $host_cpu in
+  ia64*)
+    lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64'
+    lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so
+    ;;
+  hppa*64*)
+    lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]'
+    lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl
+    ;;
+  *)
+    lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9]\.[0-9]) shared library'
+    lt_cv_file_magic_test_file=/usr/lib/libc.sl
+    ;;
+  esac
+  ;;
+
+interix[3-9]*)
+  # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here
+  lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$'
+  ;;
+
+irix5* | irix6* | nonstopux*)
+  case $LD in
+  *-32|*"-32 ") libmagic=32-bit;;
+  *-n32|*"-n32 ") libmagic=N32;;
+  *-64|*"-64 ") libmagic=64-bit;;
+  *) libmagic=never-match;;
+  esac
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+netbsd* | netbsdelf*-gnu)
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+    lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$'
+  else
+    lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$'
+  fi
+  ;;
+
+newos6*)
+  lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)'
+  lt_cv_file_magic_cmd=/usr/bin/file
+  lt_cv_file_magic_test_file=/usr/lib/libnls.so
+  ;;
+
+*nto* | *qnx*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+openbsd*)
+  if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+    lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$'
+  else
+    lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$'
+  fi
+  ;;
+
+osf3* | osf4* | osf5*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+rdos*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+solaris*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+sysv4 | sysv4.3*)
+  case $host_vendor in
+  motorola)
+    lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]'
+    lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*`
+    ;;
+  ncr)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  sequent)
+    lt_cv_file_magic_cmd='/bin/file'
+    lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )'
+    ;;
+  sni)
+    lt_cv_file_magic_cmd='/bin/file'
+    lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib"
+    lt_cv_file_magic_test_file=/lib/libc.so
+    ;;
+  siemens)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  pc)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  esac
+  ;;
+
+tpf*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+esac
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_deplibs_check_method" >&5
+$as_echo "$lt_cv_deplibs_check_method" >&6; }
+
+file_magic_glob=
+want_nocaseglob=no
+if test "$build" = "$host"; then
+  case $host_os in
+  mingw* | pw32*)
+    if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then
+      want_nocaseglob=yes
+    else
+      file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[\1]\/[\1]\/g;/g"`
+    fi
+    ;;
+  esac
+fi
+
+file_magic_cmd=$lt_cv_file_magic_cmd
+deplibs_check_method=$lt_cv_deplibs_check_method
+test -z "$deplibs_check_method" && deplibs_check_method=unknown
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}dlltool", so it can be a program name with args.
+set dummy ${ac_tool_prefix}dlltool; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_DLLTOOL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$DLLTOOL"; then
+  ac_cv_prog_DLLTOOL="$DLLTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_DLLTOOL="${ac_tool_prefix}dlltool"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+DLLTOOL=$ac_cv_prog_DLLTOOL
+if test -n "$DLLTOOL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DLLTOOL" >&5
+$as_echo "$DLLTOOL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_DLLTOOL"; then
+  ac_ct_DLLTOOL=$DLLTOOL
+  # Extract the first word of "dlltool", so it can be a program name with args.
+set dummy dlltool; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_DLLTOOL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_DLLTOOL"; then
+  ac_cv_prog_ac_ct_DLLTOOL="$ac_ct_DLLTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_DLLTOOL="dlltool"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_DLLTOOL=$ac_cv_prog_ac_ct_DLLTOOL
+if test -n "$ac_ct_DLLTOOL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DLLTOOL" >&5
+$as_echo "$ac_ct_DLLTOOL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_DLLTOOL" = x; then
+    DLLTOOL="false"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    DLLTOOL=$ac_ct_DLLTOOL
+  fi
+else
+  DLLTOOL="$ac_cv_prog_DLLTOOL"
+fi
+
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to associate runtime and link libraries" >&5
+$as_echo_n "checking how to associate runtime and link libraries... " >&6; }
+if ${lt_cv_sharedlib_from_linklib_cmd+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_sharedlib_from_linklib_cmd='unknown'
+
+case $host_os in
+cygwin* | mingw* | pw32* | cegcc*)
+  # two different shell functions defined in ltmain.sh
+  # decide which to use based on capabilities of $DLLTOOL
+  case `$DLLTOOL --help 2>&1` in
+  *--identify-strict*)
+    lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib
+    ;;
+  *)
+    lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback
+    ;;
+  esac
+  ;;
+*)
+  # fallback: assume linklib IS sharedlib
+  lt_cv_sharedlib_from_linklib_cmd="$ECHO"
+  ;;
+esac
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sharedlib_from_linklib_cmd" >&5
+$as_echo "$lt_cv_sharedlib_from_linklib_cmd" >&6; }
+sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd
+test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO
+
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+  for ac_prog in ar
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_AR+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$AR"; then
+  ac_cv_prog_AR="$AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_AR="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+AR=$ac_cv_prog_AR
+if test -n "$AR"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5
+$as_echo "$AR" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$AR" && break
+  done
+fi
+if test -z "$AR"; then
+  ac_ct_AR=$AR
+  for ac_prog in ar
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_AR+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_AR"; then
+  ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_AR="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_AR=$ac_cv_prog_ac_ct_AR
+if test -n "$ac_ct_AR"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5
+$as_echo "$ac_ct_AR" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_AR" && break
+done
+
+  if test "x$ac_ct_AR" = x; then
+    AR="false"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    AR=$ac_ct_AR
+  fi
+fi
+
+: ${AR=ar}
+: ${AR_FLAGS=cru}
+
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for archiver @FILE support" >&5
+$as_echo_n "checking for archiver @FILE support... " >&6; }
+if ${lt_cv_ar_at_file+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_ar_at_file=no
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  echo conftest.$ac_objext > conftest.lst
+      lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&5'
+      { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5
+  (eval $lt_ar_try) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+      if test "$ac_status" -eq 0; then
+       # Ensure the archiver fails upon bogus file names.
+       rm -f conftest.$ac_objext libconftest.a
+       { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5
+  (eval $lt_ar_try) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+       if test "$ac_status" -ne 0; then
+          lt_cv_ar_at_file=@
+        fi
+      fi
+      rm -f conftest.* libconftest.a
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ar_at_file" >&5
+$as_echo "$lt_cv_ar_at_file" >&6; }
+
+if test "x$lt_cv_ar_at_file" = xno; then
+  archiver_list_spec=
+else
+  archiver_list_spec=$lt_cv_ar_at_file
+fi
+
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args.
+set dummy ${ac_tool_prefix}strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_STRIP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$STRIP"; then
+  ac_cv_prog_STRIP="$STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_STRIP="${ac_tool_prefix}strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+STRIP=$ac_cv_prog_STRIP
+if test -n "$STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5
+$as_echo "$STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_STRIP"; then
+  ac_ct_STRIP=$STRIP
+  # Extract the first word of "strip", so it can be a program name with args.
+set dummy strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_STRIP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_STRIP"; then
+  ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_STRIP="strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP
+if test -n "$ac_ct_STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5
+$as_echo "$ac_ct_STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_STRIP" = x; then
+    STRIP=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    STRIP=$ac_ct_STRIP
+  fi
+else
+  STRIP="$ac_cv_prog_STRIP"
+fi
+
+test -z "$STRIP" && STRIP=:
+
+
+
+
+
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args.
+set dummy ${ac_tool_prefix}ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_RANLIB+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$RANLIB"; then
+  ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+RANLIB=$ac_cv_prog_RANLIB
+if test -n "$RANLIB"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5
+$as_echo "$RANLIB" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_RANLIB"; then
+  ac_ct_RANLIB=$RANLIB
+  # Extract the first word of "ranlib", so it can be a program name with args.
+set dummy ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_RANLIB+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_RANLIB"; then
+  ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_RANLIB="ranlib"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB
+if test -n "$ac_ct_RANLIB"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5
+$as_echo "$ac_ct_RANLIB" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_RANLIB" = x; then
+    RANLIB=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    RANLIB=$ac_ct_RANLIB
+  fi
+else
+  RANLIB="$ac_cv_prog_RANLIB"
+fi
+
+test -z "$RANLIB" && RANLIB=:
+
+
+
+
+
+
+# Determine commands to create old-style static archives.
+old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs'
+old_postinstall_cmds='chmod 644 $oldlib'
+old_postuninstall_cmds=
+
+if test -n "$RANLIB"; then
+  case $host_os in
+  openbsd*)
+    old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib"
+    ;;
+  *)
+    old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib"
+    ;;
+  esac
+  old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib"
+fi
+
+case $host_os in
+  darwin*)
+    lock_old_archive_extraction=yes ;;
+  *)
+    lock_old_archive_extraction=no ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+for ac_prog in gawk mawk nawk awk
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_AWK+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$AWK"; then
+  ac_cv_prog_AWK="$AWK" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_AWK="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+AWK=$ac_cv_prog_AWK
+if test -n "$AWK"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5
+$as_echo "$AWK" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$AWK" && break
+done
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+
+
+# Check for command to grab the raw symbol name followed by C symbol from nm.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking command to parse $NM output from $compiler object" >&5
+$as_echo_n "checking command to parse $NM output from $compiler object... " >&6; }
+if ${lt_cv_sys_global_symbol_pipe+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+
+# These are sane defaults that work on at least a few old systems.
+# [They come from Ultrix.  What could be older than Ultrix?!! ;)]
+
+# Character class describing NM global symbol codes.
+symcode='[BCDEGRST]'
+
+# Regexp to match symbols that can be accessed directly from C.
+sympat='\([_A-Za-z][_A-Za-z0-9]*\)'
+
+# Define system-specific variables.
+case $host_os in
+aix*)
+  symcode='[BCDT]'
+  ;;
+cygwin* | mingw* | pw32* | cegcc*)
+  symcode='[ABCDGISTW]'
+  ;;
+hpux*)
+  if test "$host_cpu" = ia64; then
+    symcode='[ABCDEGRST]'
+  fi
+  ;;
+irix* | nonstopux*)
+  symcode='[BCDEGRST]'
+  ;;
+osf*)
+  symcode='[BCDEGQRST]'
+  ;;
+solaris*)
+  symcode='[BDRT]'
+  ;;
+sco3.2v5*)
+  symcode='[DT]'
+  ;;
+sysv4.2uw2*)
+  symcode='[DT]'
+  ;;
+sysv5* | sco5v6* | unixware* | OpenUNIX*)
+  symcode='[ABDT]'
+  ;;
+sysv4)
+  symcode='[DFNSTU]'
+  ;;
+esac
+
+# If we're using GNU nm, then use its standard symbol codes.
+case `$NM -V 2>&1` in
+*GNU* | *'with BFD'*)
+  symcode='[ABCDGIRSTW]' ;;
+esac
+
+# Transform an extracted symbol line into a proper C declaration.
+# Some systems (esp. on ia64) link data and code symbols differently,
+# so use this general approach.
+lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'"
+
+# Transform an extracted symbol line into symbol name and symbol address
+lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([^ ]*\)[ ]*$/  {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([^ ]*\) \([^ ]*\)$/  {\"\2\", (void *) \&\2},/p'"
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n -e 's/^: \([^ ]*\)[ ]*$/  {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([^ ]*\) \(lib[^ ]*\)$/  {\"\2\", (void *) \&\2},/p' -e 's/^$symcode* \([^ ]*\) \([^ ]*\)$/  {\"lib\2\", (void *) \&\2},/p'"
+
+# Handle CRLF in mingw tool chain
+opt_cr=
+case $build_os in
+mingw*)
+  opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp
+  ;;
+esac
+
+# Try without a prefix underscore, then with it.
+for ac_symprfx in "" "_"; do
+
+  # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol.
+  symxfrm="\\1 $ac_symprfx\\2 \\2"
+
+  # Write the raw and C identifiers.
+  if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+    # Fake it for dumpbin and say T for any non-static function
+    # and D for any global variable.
+    # Also find C++ and __fastcall symbols from MSVC++,
+    # which start with @ or ?.
+    lt_cv_sys_global_symbol_pipe="$AWK '"\
+"     {last_section=section; section=\$ 3};"\
+"     /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\
+"     /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\
+"     \$ 0!~/External *\|/{next};"\
+"     / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\
+"     {if(hide[section]) next};"\
+"     {f=0}; \$ 0~/\(\).*\|/{f=1}; {printf f ? \"T \" : \"D \"};"\
+"     {split(\$ 0, a, /\||\r/); split(a[2], s)};"\
+"     s[1]~/^[@?]/{print s[1], s[1]; next};"\
+"     s[1]~prfx {split(s[1],t,\"@\"); print t[1], substr(t[1],length(prfx))}"\
+"     ' prfx=^$ac_symprfx"
+  else
+    lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[     ]\($symcode$symcode*\)[         ][      ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'"
+  fi
+  lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'"
+
+  # Check to see that the pipe works correctly.
+  pipe_works=no
+
+  rm -f conftest*
+  cat > conftest.$ac_ext <<_LT_EOF
+#ifdef __cplusplus
+extern "C" {
+#endif
+char nm_test_var;
+void nm_test_func(void);
+void nm_test_func(void){}
+#ifdef __cplusplus
+}
+#endif
+int main(){nm_test_var='a';nm_test_func();return(0);}
+_LT_EOF
+
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+    # Now try to grab the symbols.
+    nlist=conftest.nm
+    if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist\""; } >&5
+  (eval $NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && test -s "$nlist"; then
+      # Try sorting and uniquifying the output.
+      if sort "$nlist" | uniq > "$nlist"T; then
+       mv -f "$nlist"T "$nlist"
+      else
+       rm -f "$nlist"T
+      fi
+
+      # Make sure that we snagged all the symbols we need.
+      if $GREP ' nm_test_var$' "$nlist" >/dev/null; then
+       if $GREP ' nm_test_func$' "$nlist" >/dev/null; then
+         cat <<_LT_EOF > conftest.$ac_ext
+/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests.  */
+#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE)
+/* DATA imports from DLLs on WIN32 con't be const, because runtime
+   relocations are performed -- see ld's documentation on pseudo-relocs.  */
+# define LT_DLSYM_CONST
+#elif defined(__osf__)
+/* This system does not cope well with relocations in const data.  */
+# define LT_DLSYM_CONST
+#else
+# define LT_DLSYM_CONST const
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+_LT_EOF
+         # Now generate the symbol file.
+         eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext'
+
+         cat <<_LT_EOF >> conftest.$ac_ext
+
+/* The mapping between symbol names and symbols.  */
+LT_DLSYM_CONST struct {
+  const char *name;
+  void       *address;
+}
+lt__PROGRAM__LTX_preloaded_symbols[] =
+{
+  { "@PROGRAM@", (void *) 0 },
+_LT_EOF
+         $SED "s/^$symcode$symcode* \(.*\) \(.*\)$/  {\"\2\", (void *) \&\2},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext
+         cat <<\_LT_EOF >> conftest.$ac_ext
+  {0, (void *) 0}
+};
+
+/* This works around a problem in FreeBSD linker */
+#ifdef FREEBSD_WORKAROUND
+static const void *lt_preloaded_setup() {
+  return lt__PROGRAM__LTX_preloaded_symbols;
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+_LT_EOF
+         # Now try linking the two files.
+         mv conftest.$ac_objext conftstm.$ac_objext
+         lt_globsym_save_LIBS=$LIBS
+         lt_globsym_save_CFLAGS=$CFLAGS
+         LIBS="conftstm.$ac_objext"
+         CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag"
+         if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5
+  (eval $ac_link) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && test -s conftest${ac_exeext}; then
+           pipe_works=yes
+         fi
+         LIBS=$lt_globsym_save_LIBS
+         CFLAGS=$lt_globsym_save_CFLAGS
+       else
+         echo "cannot find nm_test_func in $nlist" >&5
+       fi
+      else
+       echo "cannot find nm_test_var in $nlist" >&5
+      fi
+    else
+      echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5
+    fi
+  else
+    echo "$progname: failed program was:" >&5
+    cat conftest.$ac_ext >&5
+  fi
+  rm -rf conftest* conftst*
+
+  # Do not use the global_symbol_pipe unless it works.
+  if test "$pipe_works" = yes; then
+    break
+  else
+    lt_cv_sys_global_symbol_pipe=
+  fi
+done
+
+fi
+
+if test -z "$lt_cv_sys_global_symbol_pipe"; then
+  lt_cv_sys_global_symbol_to_cdecl=
+fi
+if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5
+$as_echo "failed" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: ok" >&5
+$as_echo "ok" >&6; }
+fi
+
+# Response file support.
+if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+  nm_file_list_spec='@'
+elif $NM --help 2>/dev/null | grep '[@]FILE' >/dev/null; then
+  nm_file_list_spec='@'
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sysroot" >&5
+$as_echo_n "checking for sysroot... " >&6; }
+
+# Check whether --with-sysroot was given.
+if test "${with_sysroot+set}" = set; then :
+  withval=$with_sysroot;
+else
+  with_sysroot=no
+fi
+
+
+lt_sysroot=
+case ${with_sysroot} in #(
+ yes)
+   if test "$GCC" = yes; then
+     lt_sysroot=`$CC --print-sysroot 2>/dev/null`
+   fi
+   ;; #(
+ /*)
+   lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"`
+   ;; #(
+ no|'')
+   ;; #(
+ *)
+   { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${with_sysroot}" >&5
+$as_echo "${with_sysroot}" >&6; }
+   as_fn_error $? "The sysroot must be an absolute path." "$LINENO" 5
+   ;;
+esac
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${lt_sysroot:-no}" >&5
+$as_echo "${lt_sysroot:-no}" >&6; }
+
+
+
+
+
+# Check whether --enable-libtool-lock was given.
+if test "${enable_libtool_lock+set}" = set; then :
+  enableval=$enable_libtool_lock;
+fi
+
+test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes
+
+# Some flags need to be propagated to the compiler or linker for good
+# libtool support.
+case $host in
+ia64-*-hpux*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+    case `/usr/bin/file conftest.$ac_objext` in
+      *ELF-32*)
+       HPUX_IA64_MODE="32"
+       ;;
+      *ELF-64*)
+       HPUX_IA64_MODE="64"
+       ;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+*-*-irix6*)
+  # Find out which ABI we are using.
+  echo '#line '$LINENO' "configure"' > conftest.$ac_ext
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+    if test "$lt_cv_prog_gnu_ld" = yes; then
+      case `/usr/bin/file conftest.$ac_objext` in
+       *32-bit*)
+         LD="${LD-ld} -melf32bsmip"
+         ;;
+       *N32*)
+         LD="${LD-ld} -melf32bmipn32"
+         ;;
+       *64-bit*)
+         LD="${LD-ld} -melf64bmip"
+       ;;
+      esac
+    else
+      case `/usr/bin/file conftest.$ac_objext` in
+       *32-bit*)
+         LD="${LD-ld} -32"
+         ;;
+       *N32*)
+         LD="${LD-ld} -n32"
+         ;;
+       *64-bit*)
+         LD="${LD-ld} -64"
+         ;;
+      esac
+    fi
+  fi
+  rm -rf conftest*
+  ;;
+
+x86_64-*kfreebsd*-gnu|x86_64-*linux*|ppc*-*linux*|powerpc*-*linux*| \
+s390*-*linux*|s390*-*tpf*|sparc*-*linux*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+    case `/usr/bin/file conftest.o` in
+      *32-bit*)
+       case $host in
+         x86_64-*kfreebsd*-gnu)
+           LD="${LD-ld} -m elf_i386_fbsd"
+           ;;
+         x86_64-*linux*)
+           LD="${LD-ld} -m elf_i386"
+           ;;
+         ppc64-*linux*|powerpc64-*linux*)
+           LD="${LD-ld} -m elf32ppclinux"
+           ;;
+         s390x-*linux*)
+           LD="${LD-ld} -m elf_s390"
+           ;;
+         sparc64-*linux*)
+           LD="${LD-ld} -m elf32_sparc"
+           ;;
+       esac
+       ;;
+      *64-bit*)
+       case $host in
+         x86_64-*kfreebsd*-gnu)
+           LD="${LD-ld} -m elf_x86_64_fbsd"
+           ;;
+         x86_64-*linux*)
+           LD="${LD-ld} -m elf_x86_64"
+           ;;
+         ppc*-*linux*|powerpc*-*linux*)
+           LD="${LD-ld} -m elf64ppc"
+           ;;
+         s390*-*linux*|s390*-*tpf*)
+           LD="${LD-ld} -m elf64_s390"
+           ;;
+         sparc*-*linux*)
+           LD="${LD-ld} -m elf64_sparc"
+           ;;
+       esac
+       ;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+
+*-*-sco3.2v5*)
+  # On SCO OpenServer 5, we need -belf to get full-featured binaries.
+  SAVE_CFLAGS="$CFLAGS"
+  CFLAGS="$CFLAGS -belf"
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler needs -belf" >&5
+$as_echo_n "checking whether the C compiler needs -belf... " >&6; }
+if ${lt_cv_cc_needs_belf+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+     cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  lt_cv_cc_needs_belf=yes
+else
+  lt_cv_cc_needs_belf=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+     ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_cc_needs_belf" >&5
+$as_echo "$lt_cv_cc_needs_belf" >&6; }
+  if test x"$lt_cv_cc_needs_belf" != x"yes"; then
+    # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf
+    CFLAGS="$SAVE_CFLAGS"
+  fi
+  ;;
+*-*solaris*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+    case `/usr/bin/file conftest.o` in
+    *64-bit*)
+      case $lt_cv_prog_gnu_ld in
+      yes*)
+        case $host in
+        i?86-*-solaris*)
+          LD="${LD-ld} -m elf_x86_64"
+          ;;
+        sparc*-*-solaris*)
+          LD="${LD-ld} -m elf64_sparc"
+          ;;
+        esac
+        # GNU ld 2.21 introduced _sol2 emulations.  Use them if available.
+        if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then
+          LD="${LD-ld}_sol2"
+        fi
+        ;;
+      *)
+       if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then
+         LD="${LD-ld} -64"
+       fi
+       ;;
+      esac
+      ;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+esac
+
+need_locks="$enable_libtool_lock"
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}mt", so it can be a program name with args.
+set dummy ${ac_tool_prefix}mt; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_MANIFEST_TOOL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$MANIFEST_TOOL"; then
+  ac_cv_prog_MANIFEST_TOOL="$MANIFEST_TOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_MANIFEST_TOOL="${ac_tool_prefix}mt"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+MANIFEST_TOOL=$ac_cv_prog_MANIFEST_TOOL
+if test -n "$MANIFEST_TOOL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MANIFEST_TOOL" >&5
+$as_echo "$MANIFEST_TOOL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_MANIFEST_TOOL"; then
+  ac_ct_MANIFEST_TOOL=$MANIFEST_TOOL
+  # Extract the first word of "mt", so it can be a program name with args.
+set dummy mt; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_MANIFEST_TOOL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_MANIFEST_TOOL"; then
+  ac_cv_prog_ac_ct_MANIFEST_TOOL="$ac_ct_MANIFEST_TOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_MANIFEST_TOOL="mt"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_MANIFEST_TOOL=$ac_cv_prog_ac_ct_MANIFEST_TOOL
+if test -n "$ac_ct_MANIFEST_TOOL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_MANIFEST_TOOL" >&5
+$as_echo "$ac_ct_MANIFEST_TOOL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_MANIFEST_TOOL" = x; then
+    MANIFEST_TOOL=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    MANIFEST_TOOL=$ac_ct_MANIFEST_TOOL
+  fi
+else
+  MANIFEST_TOOL="$ac_cv_prog_MANIFEST_TOOL"
+fi
+
+test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $MANIFEST_TOOL is a manifest tool" >&5
+$as_echo_n "checking if $MANIFEST_TOOL is a manifest tool... " >&6; }
+if ${lt_cv_path_mainfest_tool+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_path_mainfest_tool=no
+  echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&5
+  $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out
+  cat conftest.err >&5
+  if $GREP 'Manifest Tool' conftest.out > /dev/null; then
+    lt_cv_path_mainfest_tool=yes
+  fi
+  rm -f conftest*
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_mainfest_tool" >&5
+$as_echo "$lt_cv_path_mainfest_tool" >&6; }
+if test "x$lt_cv_path_mainfest_tool" != xyes; then
+  MANIFEST_TOOL=:
+fi
+
+
+
+
+
+
+  case $host_os in
+    rhapsody* | darwin*)
+    if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args.
+set dummy ${ac_tool_prefix}dsymutil; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_DSYMUTIL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$DSYMUTIL"; then
+  ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+DSYMUTIL=$ac_cv_prog_DSYMUTIL
+if test -n "$DSYMUTIL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DSYMUTIL" >&5
+$as_echo "$DSYMUTIL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_DSYMUTIL"; then
+  ac_ct_DSYMUTIL=$DSYMUTIL
+  # Extract the first word of "dsymutil", so it can be a program name with args.
+set dummy dsymutil; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_DSYMUTIL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_DSYMUTIL"; then
+  ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_DSYMUTIL="dsymutil"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL
+if test -n "$ac_ct_DSYMUTIL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DSYMUTIL" >&5
+$as_echo "$ac_ct_DSYMUTIL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_DSYMUTIL" = x; then
+    DSYMUTIL=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    DSYMUTIL=$ac_ct_DSYMUTIL
+  fi
+else
+  DSYMUTIL="$ac_cv_prog_DSYMUTIL"
+fi
+
+    if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args.
+set dummy ${ac_tool_prefix}nmedit; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_NMEDIT+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$NMEDIT"; then
+  ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+NMEDIT=$ac_cv_prog_NMEDIT
+if test -n "$NMEDIT"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NMEDIT" >&5
+$as_echo "$NMEDIT" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_NMEDIT"; then
+  ac_ct_NMEDIT=$NMEDIT
+  # Extract the first word of "nmedit", so it can be a program name with args.
+set dummy nmedit; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_NMEDIT+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_NMEDIT"; then
+  ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_NMEDIT="nmedit"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT
+if test -n "$ac_ct_NMEDIT"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NMEDIT" >&5
+$as_echo "$ac_ct_NMEDIT" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_NMEDIT" = x; then
+    NMEDIT=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    NMEDIT=$ac_ct_NMEDIT
+  fi
+else
+  NMEDIT="$ac_cv_prog_NMEDIT"
+fi
+
+    if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}lipo", so it can be a program name with args.
+set dummy ${ac_tool_prefix}lipo; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_LIPO+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$LIPO"; then
+  ac_cv_prog_LIPO="$LIPO" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_LIPO="${ac_tool_prefix}lipo"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+LIPO=$ac_cv_prog_LIPO
+if test -n "$LIPO"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LIPO" >&5
+$as_echo "$LIPO" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_LIPO"; then
+  ac_ct_LIPO=$LIPO
+  # Extract the first word of "lipo", so it can be a program name with args.
+set dummy lipo; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_LIPO+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_LIPO"; then
+  ac_cv_prog_ac_ct_LIPO="$ac_ct_LIPO" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_LIPO="lipo"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_LIPO=$ac_cv_prog_ac_ct_LIPO
+if test -n "$ac_ct_LIPO"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_LIPO" >&5
+$as_echo "$ac_ct_LIPO" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_LIPO" = x; then
+    LIPO=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    LIPO=$ac_ct_LIPO
+  fi
+else
+  LIPO="$ac_cv_prog_LIPO"
+fi
+
+    if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}otool", so it can be a program name with args.
+set dummy ${ac_tool_prefix}otool; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_OTOOL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$OTOOL"; then
+  ac_cv_prog_OTOOL="$OTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_OTOOL="${ac_tool_prefix}otool"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+OTOOL=$ac_cv_prog_OTOOL
+if test -n "$OTOOL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL" >&5
+$as_echo "$OTOOL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OTOOL"; then
+  ac_ct_OTOOL=$OTOOL
+  # Extract the first word of "otool", so it can be a program name with args.
+set dummy otool; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_OTOOL+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_OTOOL"; then
+  ac_cv_prog_ac_ct_OTOOL="$ac_ct_OTOOL" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_OTOOL="otool"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OTOOL=$ac_cv_prog_ac_ct_OTOOL
+if test -n "$ac_ct_OTOOL"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL" >&5
+$as_echo "$ac_ct_OTOOL" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_OTOOL" = x; then
+    OTOOL=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    OTOOL=$ac_ct_OTOOL
+  fi
+else
+  OTOOL="$ac_cv_prog_OTOOL"
+fi
+
+    if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}otool64", so it can be a program name with args.
+set dummy ${ac_tool_prefix}otool64; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_OTOOL64+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$OTOOL64"; then
+  ac_cv_prog_OTOOL64="$OTOOL64" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_OTOOL64="${ac_tool_prefix}otool64"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+OTOOL64=$ac_cv_prog_OTOOL64
+if test -n "$OTOOL64"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL64" >&5
+$as_echo "$OTOOL64" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_OTOOL64"; then
+  ac_ct_OTOOL64=$OTOOL64
+  # Extract the first word of "otool64", so it can be a program name with args.
+set dummy otool64; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_OTOOL64+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_OTOOL64"; then
+  ac_cv_prog_ac_ct_OTOOL64="$ac_ct_OTOOL64" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_OTOOL64="otool64"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_OTOOL64=$ac_cv_prog_ac_ct_OTOOL64
+if test -n "$ac_ct_OTOOL64"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL64" >&5
+$as_echo "$ac_ct_OTOOL64" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_OTOOL64" = x; then
+    OTOOL64=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    OTOOL64=$ac_ct_OTOOL64
+  fi
+else
+  OTOOL64="$ac_cv_prog_OTOOL64"
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -single_module linker flag" >&5
+$as_echo_n "checking for -single_module linker flag... " >&6; }
+if ${lt_cv_apple_cc_single_mod+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_apple_cc_single_mod=no
+      if test -z "${LT_MULTI_MODULE}"; then
+       # By default we will add the -single_module flag. You can override
+       # by either setting the environment variable LT_MULTI_MODULE
+       # non-empty at configure time, or by adding -multi_module to the
+       # link flags.
+       rm -rf libconftest.dylib*
+       echo "int foo(void){return 1;}" > conftest.c
+       echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+-dynamiclib -Wl,-single_module conftest.c" >&5
+       $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+         -dynamiclib -Wl,-single_module conftest.c 2>conftest.err
+        _lt_result=$?
+       # If there is a non-empty error log, and "single_module"
+       # appears in it, assume the flag caused a linker warning
+        if test -s conftest.err && $GREP single_module conftest.err; then
+         cat conftest.err >&5
+       # Otherwise, if the output was created with a 0 exit code from
+       # the compiler, it worked.
+       elif test -f libconftest.dylib && test $_lt_result -eq 0; then
+         lt_cv_apple_cc_single_mod=yes
+       else
+         cat conftest.err >&5
+       fi
+       rm -rf libconftest.dylib*
+       rm -f conftest.*
+      fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_apple_cc_single_mod" >&5
+$as_echo "$lt_cv_apple_cc_single_mod" >&6; }
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -exported_symbols_list linker flag" >&5
+$as_echo_n "checking for -exported_symbols_list linker flag... " >&6; }
+if ${lt_cv_ld_exported_symbols_list+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_ld_exported_symbols_list=no
+      save_LDFLAGS=$LDFLAGS
+      echo "_main" > conftest.sym
+      LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym"
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  lt_cv_ld_exported_symbols_list=yes
+else
+  lt_cv_ld_exported_symbols_list=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+       LDFLAGS="$save_LDFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_exported_symbols_list" >&5
+$as_echo "$lt_cv_ld_exported_symbols_list" >&6; }
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -force_load linker flag" >&5
+$as_echo_n "checking for -force_load linker flag... " >&6; }
+if ${lt_cv_ld_force_load+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_ld_force_load=no
+      cat > conftest.c << _LT_EOF
+int forced_loaded() { return 2;}
+_LT_EOF
+      echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5
+      $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5
+      echo "$AR cru libconftest.a conftest.o" >&5
+      $AR cru libconftest.a conftest.o 2>&5
+      echo "$RANLIB libconftest.a" >&5
+      $RANLIB libconftest.a 2>&5
+      cat > conftest.c << _LT_EOF
+int main() { return 0;}
+_LT_EOF
+      echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&5
+      $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err
+      _lt_result=$?
+      if test -s conftest.err && $GREP force_load conftest.err; then
+       cat conftest.err >&5
+      elif test -f conftest && test $_lt_result -eq 0 && $GREP forced_load conftest >/dev/null 2>&1 ; then
+       lt_cv_ld_force_load=yes
+      else
+       cat conftest.err >&5
+      fi
+        rm -f conftest.err libconftest.a conftest conftest.c
+        rm -rf conftest.dSYM
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_force_load" >&5
+$as_echo "$lt_cv_ld_force_load" >&6; }
+    case $host_os in
+    rhapsody* | darwin1.[012])
+      _lt_dar_allow_undefined='${wl}-undefined ${wl}suppress' ;;
+    darwin1.*)
+      _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;;
+    darwin*) # darwin 5.x on
+      # if running on 10.5 or later, the deployment target defaults
+      # to the OS version, if on x86, and 10.4, the deployment
+      # target defaults to 10.4. Don't you love it?
+      case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in
+       10.0,*86*-darwin8*|10.0,*-darwin[91]*)
+         _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;;
+       10.[012]*)
+         _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;;
+       10.*)
+         _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;;
+      esac
+    ;;
+  esac
+    if test "$lt_cv_apple_cc_single_mod" = "yes"; then
+      _lt_dar_single_mod='$single_module'
+    fi
+    if test "$lt_cv_ld_exported_symbols_list" = "yes"; then
+      _lt_dar_export_syms=' ${wl}-exported_symbols_list,$output_objdir/${libname}-symbols.expsym'
+    else
+      _lt_dar_export_syms='~$NMEDIT -s $output_objdir/${libname}-symbols.expsym ${lib}'
+    fi
+    if test "$DSYMUTIL" != ":" && test "$lt_cv_ld_force_load" = "no"; then
+      _lt_dsymutil='~$DSYMUTIL $lib || :'
+    else
+      _lt_dsymutil=
+    fi
+    ;;
+  esac
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+$as_echo_n "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+  CPP=
+fi
+if test -z "$CPP"; then
+  if ${ac_cv_prog_CPP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+      # Double quotes because CPP needs to be expanded
+    for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+    do
+      ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+                    Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+  break
+fi
+
+    done
+    ac_cv_prog_CPP=$CPP
+
+fi
+  CPP=$ac_cv_prog_CPP
+else
+  ac_cv_prog_CPP=$CPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+$as_echo "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+                    Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
+$as_echo_n "checking for ANSI C header files... " >&6; }
+if ${ac_cv_header_stdc+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_header_stdc=yes
+else
+  ac_cv_header_stdc=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+  # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "memchr" >/dev/null 2>&1; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "free" >/dev/null 2>&1; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+  if test "$cross_compiling" = yes; then :
+  :
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ctype.h>
+#include <stdlib.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+                  (('a' <= (c) && (c) <= 'i') \
+                    || ('j' <= (c) && (c) <= 'r') \
+                    || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+  int i;
+  for (i = 0; i < 256; i++)
+    if (XOR (islower (i), ISLOWER (i))
+       || toupper (i) != TOUPPER (i))
+      return 2;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
+$as_echo "$ac_cv_header_stdc" >&6; }
+if test $ac_cv_header_stdc = yes; then
+
+$as_echo "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+                 inttypes.h stdint.h unistd.h
+do :
+  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
+"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+for ac_header in dlfcn.h
+do :
+  ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default
+"
+if test "x$ac_cv_header_dlfcn_h" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_DLFCN_H 1
+_ACEOF
+
+fi
+
+done
+
+
+
+
+
+# Set options
+
+
+
+        enable_dlopen=no
+
+
+  enable_win32_dll=no
+
+
+            # Check whether --enable-shared was given.
+if test "${enable_shared+set}" = set; then :
+  enableval=$enable_shared; p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_shared=yes ;;
+    no) enable_shared=no ;;
+    *)
+      enable_shared=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+       IFS="$lt_save_ifs"
+       if test "X$pkg" = "X$p"; then
+         enable_shared=yes
+       fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac
+else
+  enable_shared=yes
+fi
+
+
+
+
+
+
+
+
+
+  # Check whether --enable-static was given.
+if test "${enable_static+set}" = set; then :
+  enableval=$enable_static; p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_static=yes ;;
+    no) enable_static=no ;;
+    *)
+     enable_static=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+       IFS="$lt_save_ifs"
+       if test "X$pkg" = "X$p"; then
+         enable_static=yes
+       fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac
+else
+  enable_static=yes
+fi
+
+
+
+
+
+
+
+
+
+
+# Check whether --with-pic was given.
+if test "${with_pic+set}" = set; then :
+  withval=$with_pic; lt_p=${PACKAGE-default}
+    case $withval in
+    yes|no) pic_mode=$withval ;;
+    *)
+      pic_mode=default
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for lt_pkg in $withval; do
+       IFS="$lt_save_ifs"
+       if test "X$lt_pkg" = "X$lt_p"; then
+         pic_mode=yes
+       fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac
+else
+  pic_mode=default
+fi
+
+
+test -z "$pic_mode" && pic_mode=default
+
+
+
+
+
+
+
+  # Check whether --enable-fast-install was given.
+if test "${enable_fast_install+set}" = set; then :
+  enableval=$enable_fast_install; p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_fast_install=yes ;;
+    no) enable_fast_install=no ;;
+    *)
+      enable_fast_install=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+       IFS="$lt_save_ifs"
+       if test "X$pkg" = "X$p"; then
+         enable_fast_install=yes
+       fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac
+else
+  enable_fast_install=yes
+fi
+
+
+
+
+
+
+
+
+
+
+
+# This can be used to rebuild libtool when needed
+LIBTOOL_DEPS="$ltmain"
+
+# Always use our own libtool.
+LIBTOOL='$(SHELL) $(top_builddir)/libtool'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+test -z "$LN_S" && LN_S="ln -s"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+if test -n "${ZSH_VERSION+set}" ; then
+   setopt NO_GLOB_SUBST
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for objdir" >&5
+$as_echo_n "checking for objdir... " >&6; }
+if ${lt_cv_objdir+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  rm -f .libs 2>/dev/null
+mkdir .libs 2>/dev/null
+if test -d .libs; then
+  lt_cv_objdir=.libs
+else
+  # MS-DOS does not allow filenames that begin with a dot.
+  lt_cv_objdir=_libs
+fi
+rmdir .libs 2>/dev/null
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_objdir" >&5
+$as_echo "$lt_cv_objdir" >&6; }
+objdir=$lt_cv_objdir
+
+
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define LT_OBJDIR "$lt_cv_objdir/"
+_ACEOF
+
+
+
+
+case $host_os in
+aix3*)
+  # AIX sometimes has problems with the GCC collect2 program.  For some
+  # reason, if we set the COLLECT_NAMES environment variable, the problems
+  # vanish in a puff of smoke.
+  if test "X${COLLECT_NAMES+set}" != Xset; then
+    COLLECT_NAMES=
+    export COLLECT_NAMES
+  fi
+  ;;
+esac
+
+# Global variables:
+ofile=libtool
+can_build_shared=yes
+
+# All known linkers require a `.a' archive for static linking (except MSVC,
+# which needs '.lib').
+libext=a
+
+with_gnu_ld="$lt_cv_prog_gnu_ld"
+
+old_CC="$CC"
+old_CFLAGS="$CFLAGS"
+
+# Set sane defaults for various variables
+test -z "$CC" && CC=cc
+test -z "$LTCC" && LTCC=$CC
+test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS
+test -z "$LD" && LD=ld
+test -z "$ac_objext" && ac_objext=o
+
+for cc_temp in $compiler""; do
+  case $cc_temp in
+    compile | *[\\/]compile | ccache | *[\\/]ccache ) ;;
+    distcc | *[\\/]distcc | purify | *[\\/]purify ) ;;
+    \-*) ;;
+    *) break;;
+  esac
+done
+cc_basename=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"`
+
+
+# Only perform the check for file, if the check method requires it
+test -z "$MAGIC_CMD" && MAGIC_CMD=file
+case $deplibs_check_method in
+file_magic*)
+  if test "$file_magic_cmd" = '$MAGIC_CMD'; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${ac_tool_prefix}file" >&5
+$as_echo_n "checking for ${ac_tool_prefix}file... " >&6; }
+if ${lt_cv_path_MAGIC_CMD+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $MAGIC_CMD in
+[\\/*] |  ?:[\\/]*)
+  lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path.
+  ;;
+*)
+  lt_save_MAGIC_CMD="$MAGIC_CMD"
+  lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+  ac_dummy="/usr/bin$PATH_SEPARATOR$PATH"
+  for ac_dir in $ac_dummy; do
+    IFS="$lt_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/${ac_tool_prefix}file; then
+      lt_cv_path_MAGIC_CMD="$ac_dir/${ac_tool_prefix}file"
+      if test -n "$file_magic_test_file"; then
+       case $deplibs_check_method in
+       "file_magic "*)
+         file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"`
+         MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+         if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+           $EGREP "$file_magic_regex" > /dev/null; then
+           :
+         else
+           cat <<_LT_EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such.  This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem.  Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** bug-libtool@gnu.org
+
+_LT_EOF
+         fi ;;
+       esac
+      fi
+      break
+    fi
+  done
+  IFS="$lt_save_ifs"
+  MAGIC_CMD="$lt_save_MAGIC_CMD"
+  ;;
+esac
+fi
+
+MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+if test -n "$MAGIC_CMD"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5
+$as_echo "$MAGIC_CMD" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+
+
+if test -z "$lt_cv_path_MAGIC_CMD"; then
+  if test -n "$ac_tool_prefix"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for file" >&5
+$as_echo_n "checking for file... " >&6; }
+if ${lt_cv_path_MAGIC_CMD+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $MAGIC_CMD in
+[\\/*] |  ?:[\\/]*)
+  lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path.
+  ;;
+*)
+  lt_save_MAGIC_CMD="$MAGIC_CMD"
+  lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+  ac_dummy="/usr/bin$PATH_SEPARATOR$PATH"
+  for ac_dir in $ac_dummy; do
+    IFS="$lt_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/file; then
+      lt_cv_path_MAGIC_CMD="$ac_dir/file"
+      if test -n "$file_magic_test_file"; then
+       case $deplibs_check_method in
+       "file_magic "*)
+         file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"`
+         MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+         if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+           $EGREP "$file_magic_regex" > /dev/null; then
+           :
+         else
+           cat <<_LT_EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such.  This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem.  Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** bug-libtool@gnu.org
+
+_LT_EOF
+         fi ;;
+       esac
+      fi
+      break
+    fi
+  done
+  IFS="$lt_save_ifs"
+  MAGIC_CMD="$lt_save_MAGIC_CMD"
+  ;;
+esac
+fi
+
+MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+if test -n "$MAGIC_CMD"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5
+$as_echo "$MAGIC_CMD" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  else
+    MAGIC_CMD=:
+  fi
+fi
+
+  fi
+  ;;
+esac
+
+# Use C for the default configuration in the libtool script
+
+lt_save_CC="$CC"
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+# Source file extension for C test sources.
+ac_ext=c
+
+# Object file extension for compiled C test sources.
+objext=o
+objext=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="int some_variable = 0;"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='int main(){return(0);}'
+
+
+
+
+
+
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+
+# Save the default compiler, since it gets overwritten when the other
+# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP.
+compiler_DEFAULT=$CC
+
+# save warnings/boilerplate of simple test code
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_compile_test_code" >conftest.$ac_ext
+eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_compiler_boilerplate=`cat conftest.err`
+$RM conftest*
+
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_link_test_code" >conftest.$ac_ext
+eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_linker_boilerplate=`cat conftest.err`
+$RM -r conftest*
+
+
+if test -n "$compiler"; then
+
+lt_prog_compiler_no_builtin_flag=
+
+if test "$GCC" = yes; then
+  case $cc_basename in
+  nvcc*)
+    lt_prog_compiler_no_builtin_flag=' -Xcompiler -fno-builtin' ;;
+  *)
+    lt_prog_compiler_no_builtin_flag=' -fno-builtin' ;;
+  esac
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -fno-rtti -fno-exceptions" >&5
+$as_echo_n "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; }
+if ${lt_cv_prog_compiler_rtti_exceptions+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_rtti_exceptions=no
+   ac_outfile=conftest.$ac_objext
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+   lt_compiler_flag="-fno-rtti -fno-exceptions"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   # The option is referenced via a variable to avoid confusing sed.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+   (eval "$lt_compile" 2>conftest.err)
+   ac_status=$?
+   cat conftest.err >&5
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   if (exit $ac_status) && test -s "$ac_outfile"; then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings other than the usual output.
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+     $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+     if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+       lt_cv_prog_compiler_rtti_exceptions=yes
+     fi
+   fi
+   $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_rtti_exceptions" >&5
+$as_echo "$lt_cv_prog_compiler_rtti_exceptions" >&6; }
+
+if test x"$lt_cv_prog_compiler_rtti_exceptions" = xyes; then
+    lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions"
+else
+    :
+fi
+
+fi
+
+
+
+
+
+
+  lt_prog_compiler_wl=
+lt_prog_compiler_pic=
+lt_prog_compiler_static=
+
+
+  if test "$GCC" = yes; then
+    lt_prog_compiler_wl='-Wl,'
+    lt_prog_compiler_static='-static'
+
+    case $host_os in
+      aix*)
+      # All AIX code is PIC.
+      if test "$host_cpu" = ia64; then
+       # AIX 5 now supports IA64 processor
+       lt_prog_compiler_static='-Bstatic'
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            lt_prog_compiler_pic='-fPIC'
+        ;;
+      m68k)
+            # FIXME: we need at least 68020 code to build shared libraries, but
+            # adding the `-m68020' flag to GCC prevents building anything better,
+            # like `-m68040'.
+            lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4'
+        ;;
+      esac
+      ;;
+
+    beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+      # PIC is the default for these OSes.
+      ;;
+
+    mingw* | cygwin* | pw32* | os2* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      # Although the cygwin gcc ignores -fPIC, still need this for old-style
+      # (--disable-auto-import) libraries
+      lt_prog_compiler_pic='-DDLL_EXPORT'
+      ;;
+
+    darwin* | rhapsody*)
+      # PIC is the default on this platform
+      # Common symbols not allowed in MH_DYLIB files
+      lt_prog_compiler_pic='-fno-common'
+      ;;
+
+    haiku*)
+      # PIC is the default for Haiku.
+      # The "-static" flag exists, but is broken.
+      lt_prog_compiler_static=
+      ;;
+
+    hpux*)
+      # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+      # PA HP-UX.  On IA64 HP-UX, PIC is the default but the pic flag
+      # sets the default TLS model and affects inlining.
+      case $host_cpu in
+      hppa*64*)
+       # +Z the default
+       ;;
+      *)
+       lt_prog_compiler_pic='-fPIC'
+       ;;
+      esac
+      ;;
+
+    interix[3-9]*)
+      # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+      # Instead, we relocate shared libraries at runtime.
+      ;;
+
+    msdosdjgpp*)
+      # Just because we use GCC doesn't mean we suddenly get shared libraries
+      # on systems that don't support them.
+      lt_prog_compiler_can_build_shared=no
+      enable_shared=no
+      ;;
+
+    *nto* | *qnx*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      lt_prog_compiler_pic='-fPIC -shared'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec; then
+       lt_prog_compiler_pic=-Kconform_pic
+      fi
+      ;;
+
+    *)
+      lt_prog_compiler_pic='-fPIC'
+      ;;
+    esac
+
+    case $cc_basename in
+    nvcc*) # Cuda Compiler Driver 2.2
+      lt_prog_compiler_wl='-Xlinker '
+      if test -n "$lt_prog_compiler_pic"; then
+        lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic"
+      fi
+      ;;
+    esac
+  else
+    # PORTME Check for flag to pass linker flags through the system compiler.
+    case $host_os in
+    aix*)
+      lt_prog_compiler_wl='-Wl,'
+      if test "$host_cpu" = ia64; then
+       # AIX 5 now supports IA64 processor
+       lt_prog_compiler_static='-Bstatic'
+      else
+       lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp'
+      fi
+      ;;
+
+    mingw* | cygwin* | pw32* | os2* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      lt_prog_compiler_pic='-DDLL_EXPORT'
+      ;;
+
+    hpux9* | hpux10* | hpux11*)
+      lt_prog_compiler_wl='-Wl,'
+      # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but
+      # not for PA HP-UX.
+      case $host_cpu in
+      hppa*64*|ia64*)
+       # +Z the default
+       ;;
+      *)
+       lt_prog_compiler_pic='+Z'
+       ;;
+      esac
+      # Is there a better lt_prog_compiler_static that works with the bundled CC?
+      lt_prog_compiler_static='${wl}-a ${wl}archive'
+      ;;
+
+    irix5* | irix6* | nonstopux*)
+      lt_prog_compiler_wl='-Wl,'
+      # PIC (with -KPIC) is the default.
+      lt_prog_compiler_static='-non_shared'
+      ;;
+
+    linux* | k*bsd*-gnu | kopensolaris*-gnu)
+      case $cc_basename in
+      # old Intel for x86_64 which still supported -KPIC.
+      ecc*)
+       lt_prog_compiler_wl='-Wl,'
+       lt_prog_compiler_pic='-KPIC'
+       lt_prog_compiler_static='-static'
+        ;;
+      # icc used to be incompatible with GCC.
+      # ICC 10 doesn't accept -KPIC any more.
+      icc* | ifort*)
+       lt_prog_compiler_wl='-Wl,'
+       lt_prog_compiler_pic='-fPIC'
+       lt_prog_compiler_static='-static'
+        ;;
+      # Lahey Fortran 8.1.
+      lf95*)
+       lt_prog_compiler_wl='-Wl,'
+       lt_prog_compiler_pic='--shared'
+       lt_prog_compiler_static='--static'
+       ;;
+      nagfor*)
+       # NAG Fortran compiler
+       lt_prog_compiler_wl='-Wl,-Wl,,'
+       lt_prog_compiler_pic='-PIC'
+       lt_prog_compiler_static='-Bstatic'
+       ;;
+      pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*)
+        # Portland Group compilers (*not* the Pentium gcc compiler,
+       # which looks to be a dead project)
+       lt_prog_compiler_wl='-Wl,'
+       lt_prog_compiler_pic='-fpic'
+       lt_prog_compiler_static='-Bstatic'
+        ;;
+      ccc*)
+        lt_prog_compiler_wl='-Wl,'
+        # All Alpha code is PIC.
+        lt_prog_compiler_static='-non_shared'
+        ;;
+      xl* | bgxl* | bgf* | mpixl*)
+       # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene
+       lt_prog_compiler_wl='-Wl,'
+       lt_prog_compiler_pic='-qpic'
+       lt_prog_compiler_static='-qstaticlink'
+       ;;
+      *)
+       case `$CC -V 2>&1 | sed 5q` in
+       *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*)
+         # Sun Fortran 8.3 passes all unrecognized flags to the linker
+         lt_prog_compiler_pic='-KPIC'
+         lt_prog_compiler_static='-Bstatic'
+         lt_prog_compiler_wl=''
+         ;;
+       *Sun\ F* | *Sun*Fortran*)
+         lt_prog_compiler_pic='-KPIC'
+         lt_prog_compiler_static='-Bstatic'
+         lt_prog_compiler_wl='-Qoption ld '
+         ;;
+       *Sun\ C*)
+         # Sun C 5.9
+         lt_prog_compiler_pic='-KPIC'
+         lt_prog_compiler_static='-Bstatic'
+         lt_prog_compiler_wl='-Wl,'
+         ;;
+        *Intel*\ [CF]*Compiler*)
+         lt_prog_compiler_wl='-Wl,'
+         lt_prog_compiler_pic='-fPIC'
+         lt_prog_compiler_static='-static'
+         ;;
+       *Portland\ Group*)
+         lt_prog_compiler_wl='-Wl,'
+         lt_prog_compiler_pic='-fpic'
+         lt_prog_compiler_static='-Bstatic'
+         ;;
+       esac
+       ;;
+      esac
+      ;;
+
+    newsos6)
+      lt_prog_compiler_pic='-KPIC'
+      lt_prog_compiler_static='-Bstatic'
+      ;;
+
+    *nto* | *qnx*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      lt_prog_compiler_pic='-fPIC -shared'
+      ;;
+
+    osf3* | osf4* | osf5*)
+      lt_prog_compiler_wl='-Wl,'
+      # All OSF/1 code is PIC.
+      lt_prog_compiler_static='-non_shared'
+      ;;
+
+    rdos*)
+      lt_prog_compiler_static='-non_shared'
+      ;;
+
+    solaris*)
+      lt_prog_compiler_pic='-KPIC'
+      lt_prog_compiler_static='-Bstatic'
+      case $cc_basename in
+      f77* | f90* | f95* | sunf77* | sunf90* | sunf95*)
+       lt_prog_compiler_wl='-Qoption ld ';;
+      *)
+       lt_prog_compiler_wl='-Wl,';;
+      esac
+      ;;
+
+    sunos4*)
+      lt_prog_compiler_wl='-Qoption ld '
+      lt_prog_compiler_pic='-PIC'
+      lt_prog_compiler_static='-Bstatic'
+      ;;
+
+    sysv4 | sysv4.2uw2* | sysv4.3*)
+      lt_prog_compiler_wl='-Wl,'
+      lt_prog_compiler_pic='-KPIC'
+      lt_prog_compiler_static='-Bstatic'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec ;then
+       lt_prog_compiler_pic='-Kconform_pic'
+       lt_prog_compiler_static='-Bstatic'
+      fi
+      ;;
+
+    sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+      lt_prog_compiler_wl='-Wl,'
+      lt_prog_compiler_pic='-KPIC'
+      lt_prog_compiler_static='-Bstatic'
+      ;;
+
+    unicos*)
+      lt_prog_compiler_wl='-Wl,'
+      lt_prog_compiler_can_build_shared=no
+      ;;
+
+    uts4*)
+      lt_prog_compiler_pic='-pic'
+      lt_prog_compiler_static='-Bstatic'
+      ;;
+
+    *)
+      lt_prog_compiler_can_build_shared=no
+      ;;
+    esac
+  fi
+
+case $host_os in
+  # For platforms which do not support PIC, -DPIC is meaningless:
+  *djgpp*)
+    lt_prog_compiler_pic=
+    ;;
+  *)
+    lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC"
+    ;;
+esac
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5
+$as_echo_n "checking for $compiler option to produce PIC... " >&6; }
+if ${lt_cv_prog_compiler_pic+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_pic=$lt_prog_compiler_pic
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5
+$as_echo "$lt_cv_prog_compiler_pic" >&6; }
+lt_prog_compiler_pic=$lt_cv_prog_compiler_pic
+
+#
+# Check to make sure the PIC flag actually works.
+#
+if test -n "$lt_prog_compiler_pic"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5
+$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; }
+if ${lt_cv_prog_compiler_pic_works+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_pic_works=no
+   ac_outfile=conftest.$ac_objext
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+   lt_compiler_flag="$lt_prog_compiler_pic -DPIC"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   # The option is referenced via a variable to avoid confusing sed.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+   (eval "$lt_compile" 2>conftest.err)
+   ac_status=$?
+   cat conftest.err >&5
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   if (exit $ac_status) && test -s "$ac_outfile"; then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings other than the usual output.
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+     $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+     if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+       lt_cv_prog_compiler_pic_works=yes
+     fi
+   fi
+   $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5
+$as_echo "$lt_cv_prog_compiler_pic_works" >&6; }
+
+if test x"$lt_cv_prog_compiler_pic_works" = xyes; then
+    case $lt_prog_compiler_pic in
+     "" | " "*) ;;
+     *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;;
+     esac
+else
+    lt_prog_compiler_pic=
+     lt_prog_compiler_can_build_shared=no
+fi
+
+fi
+
+
+
+
+
+
+
+
+
+
+
+#
+# Check to make sure the static flag actually works.
+#
+wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5
+$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; }
+if ${lt_cv_prog_compiler_static_works+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_static_works=no
+   save_LDFLAGS="$LDFLAGS"
+   LDFLAGS="$LDFLAGS $lt_tmp_static_flag"
+   echo "$lt_simple_link_test_code" > conftest.$ac_ext
+   if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+     # The linker can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     if test -s conftest.err; then
+       # Append any errors to the config.log.
+       cat conftest.err 1>&5
+       $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+       $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+       if diff conftest.exp conftest.er2 >/dev/null; then
+         lt_cv_prog_compiler_static_works=yes
+       fi
+     else
+       lt_cv_prog_compiler_static_works=yes
+     fi
+   fi
+   $RM -r conftest*
+   LDFLAGS="$save_LDFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5
+$as_echo "$lt_cv_prog_compiler_static_works" >&6; }
+
+if test x"$lt_cv_prog_compiler_static_works" = xyes; then
+    :
+else
+    lt_prog_compiler_static=
+fi
+
+
+
+
+
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5
+$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; }
+if ${lt_cv_prog_compiler_c_o+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_c_o=no
+   $RM -r conftest 2>/dev/null
+   mkdir conftest
+   cd conftest
+   mkdir out
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+   lt_compiler_flag="-o out/conftest2.$ac_objext"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+   (eval "$lt_compile" 2>out/conftest.err)
+   ac_status=$?
+   cat out/conftest.err >&5
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   if (exit $ac_status) && test -s out/conftest2.$ac_objext
+   then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+     $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+     if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+       lt_cv_prog_compiler_c_o=yes
+     fi
+   fi
+   chmod u+w . 2>&5
+   $RM conftest*
+   # SGI C++ compiler will create directory out/ii_files/ for
+   # template instantiation
+   test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+   $RM out/* && rmdir out
+   cd ..
+   $RM -r conftest
+   $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5
+$as_echo "$lt_cv_prog_compiler_c_o" >&6; }
+
+
+
+
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5
+$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; }
+if ${lt_cv_prog_compiler_c_o+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler_c_o=no
+   $RM -r conftest 2>/dev/null
+   mkdir conftest
+   cd conftest
+   mkdir out
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+   lt_compiler_flag="-o out/conftest2.$ac_objext"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5)
+   (eval "$lt_compile" 2>out/conftest.err)
+   ac_status=$?
+   cat out/conftest.err >&5
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   if (exit $ac_status) && test -s out/conftest2.$ac_objext
+   then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+     $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+     if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+       lt_cv_prog_compiler_c_o=yes
+     fi
+   fi
+   chmod u+w . 2>&5
+   $RM conftest*
+   # SGI C++ compiler will create directory out/ii_files/ for
+   # template instantiation
+   test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+   $RM out/* && rmdir out
+   cd ..
+   $RM -r conftest
+   $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5
+$as_echo "$lt_cv_prog_compiler_c_o" >&6; }
+
+
+
+
+hard_links="nottested"
+if test "$lt_cv_prog_compiler_c_o" = no && test "$need_locks" != no; then
+  # do not overwrite the value of need_locks provided by the user
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5
+$as_echo_n "checking if we can lock with hard links... " >&6; }
+  hard_links=yes
+  $RM conftest*
+  ln conftest.a conftest.b 2>/dev/null && hard_links=no
+  touch conftest.a
+  ln conftest.a conftest.b 2>&5 || hard_links=no
+  ln conftest.a conftest.b 2>/dev/null && hard_links=no
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5
+$as_echo "$hard_links" >&6; }
+  if test "$hard_links" = no; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&5
+$as_echo "$as_me: WARNING: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" >&2;}
+    need_locks=warn
+  fi
+else
+  need_locks=no
+fi
+
+
+
+
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5
+$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; }
+
+  runpath_var=
+  allow_undefined_flag=
+  always_export_symbols=no
+  archive_cmds=
+  archive_expsym_cmds=
+  compiler_needs_object=no
+  enable_shared_with_static_runtimes=no
+  export_dynamic_flag_spec=
+  export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+  hardcode_automatic=no
+  hardcode_direct=no
+  hardcode_direct_absolute=no
+  hardcode_libdir_flag_spec=
+  hardcode_libdir_separator=
+  hardcode_minus_L=no
+  hardcode_shlibpath_var=unsupported
+  inherit_rpath=no
+  link_all_deplibs=unknown
+  module_cmds=
+  module_expsym_cmds=
+  old_archive_from_new_cmds=
+  old_archive_from_expsyms_cmds=
+  thread_safe_flag_spec=
+  whole_archive_flag_spec=
+  # include_expsyms should be a list of space-separated symbols to be *always*
+  # included in the symbol list
+  include_expsyms=
+  # exclude_expsyms can be an extended regexp of symbols to exclude
+  # it will be wrapped by ` (' and `)$', so one must not match beginning or
+  # end of line.  Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc',
+  # as well as any symbol that contains `d'.
+  exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'
+  # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out
+  # platforms (ab)use it in PIC code, but their linkers get confused if
+  # the symbol is explicitly referenced.  Since portable code cannot
+  # rely on this symbol name, it's probably fine to never include it in
+  # preloaded symbol tables.
+  # Exclude shared library initialization/finalization symbols.
+  extract_expsyms_cmds=
+
+  case $host_os in
+  cygwin* | mingw* | pw32* | cegcc*)
+    # FIXME: the MSVC++ port hasn't been tested in a loooong time
+    # When not using gcc, we currently assume that we are using
+    # Microsoft Visual C++.
+    if test "$GCC" != yes; then
+      with_gnu_ld=no
+    fi
+    ;;
+  interix*)
+    # we just hope/assume this is gcc and not c89 (= MSVC++)
+    with_gnu_ld=yes
+    ;;
+  openbsd*)
+    with_gnu_ld=no
+    ;;
+  linux* | k*bsd*-gnu | gnu*)
+    link_all_deplibs=no
+    ;;
+  esac
+
+  ld_shlibs=yes
+
+  # On some targets, GNU ld is compatible enough with the native linker
+  # that we're better off using the native interface for both.
+  lt_use_gnu_ld_interface=no
+  if test "$with_gnu_ld" = yes; then
+    case $host_os in
+      aix*)
+       # The AIX port of GNU ld has always aspired to compatibility
+       # with the native linker.  However, as the warning in the GNU ld
+       # block says, versions before 2.19.5* couldn't really create working
+       # shared libraries, regardless of the interface used.
+       case `$LD -v 2>&1` in
+         *\ \(GNU\ Binutils\)\ 2.19.5*) ;;
+         *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;;
+         *\ \(GNU\ Binutils\)\ [3-9]*) ;;
+         *)
+           lt_use_gnu_ld_interface=yes
+           ;;
+       esac
+       ;;
+      *)
+       lt_use_gnu_ld_interface=yes
+       ;;
+    esac
+  fi
+
+  if test "$lt_use_gnu_ld_interface" = yes; then
+    # If archive_cmds runs LD, not CC, wlarc should be empty
+    wlarc='${wl}'
+
+    # Set some defaults for GNU ld with shared library support. These
+    # are reset later if shared libraries are not supported. Putting them
+    # here allows them to be overridden if necessary.
+    runpath_var=LD_RUN_PATH
+    hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+    export_dynamic_flag_spec='${wl}--export-dynamic'
+    # ancient GNU ld didn't support --whole-archive et. al.
+    if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then
+      whole_archive_flag_spec="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+    else
+      whole_archive_flag_spec=
+    fi
+    supports_anon_versioning=no
+    case `$LD -v 2>&1` in
+      *GNU\ gold*) supports_anon_versioning=yes ;;
+      *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11
+      *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ...
+      *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ...
+      *\ 2.11.*) ;; # other 2.11 versions
+      *) supports_anon_versioning=yes ;;
+    esac
+
+    # See if GNU ld supports shared libraries.
+    case $host_os in
+    aix[3-9]*)
+      # On AIX/PPC, the GNU linker is very broken
+      if test "$host_cpu" != ia64; then
+       ld_shlibs=no
+       cat <<_LT_EOF 1>&2
+
+*** Warning: the GNU linker, at least up to release 2.19, is reported
+*** to be unable to reliably create shared libraries on AIX.
+*** Therefore, libtool is disabling shared libraries support.  If you
+*** really care for shared libraries, you may want to install binutils
+*** 2.20 or above, or modify your PATH so that a non-GNU linker is found.
+*** You will then need to restart the configuration process.
+
+_LT_EOF
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+            archive_expsym_cmds=''
+        ;;
+      m68k)
+            archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+            hardcode_libdir_flag_spec='-L$libdir'
+            hardcode_minus_L=yes
+        ;;
+      esac
+      ;;
+
+    beos*)
+      if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+       allow_undefined_flag=unsupported
+       # Joseph Beckenbach <jrb3@best.com> says some releases of gcc
+       # support --undefined.  This deserves some investigation.  FIXME
+       archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+      else
+       ld_shlibs=no
+      fi
+      ;;
+
+    cygwin* | mingw* | pw32* | cegcc*)
+      # _LT_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless,
+      # as there is no search path for DLLs.
+      hardcode_libdir_flag_spec='-L$libdir'
+      export_dynamic_flag_spec='${wl}--export-all-symbols'
+      allow_undefined_flag=unsupported
+      always_export_symbols=no
+      enable_shared_with_static_runtimes=yes
+      export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols'
+      exclude_expsyms='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'
+
+      if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+        archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+       # If the export-symbols file already is a .def file (1st line
+       # is EXPORTS), use it as is; otherwise, prepend...
+       archive_expsym_cmds='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+         cp $export_symbols $output_objdir/$soname.def;
+       else
+         echo EXPORTS > $output_objdir/$soname.def;
+         cat $export_symbols >> $output_objdir/$soname.def;
+       fi~
+       $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+      else
+       ld_shlibs=no
+      fi
+      ;;
+
+    haiku*)
+      archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+      link_all_deplibs=yes
+      ;;
+
+    interix[3-9]*)
+      hardcode_direct=no
+      hardcode_shlibpath_var=no
+      hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+      export_dynamic_flag_spec='${wl}-E'
+      # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+      # Instead, shared libraries are loaded at an image base (0x10000000 by
+      # default) and relocated if they conflict, which is a slow very memory
+      # consuming and fragmenting process.  To avoid this, we pick a random,
+      # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+      # time.  Moving up from 0x10000000 also allows more sbrk(2) space.
+      archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+      archive_expsym_cmds='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+      ;;
+
+    gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu)
+      tmp_diet=no
+      if test "$host_os" = linux-dietlibc; then
+       case $cc_basename in
+         diet\ *) tmp_diet=yes;;       # linux-dietlibc with static linking (!diet-dyn)
+       esac
+      fi
+      if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \
+        && test "$tmp_diet" = no
+      then
+       tmp_addflag=' $pic_flag'
+       tmp_sharedflag='-shared'
+       case $cc_basename,$host_cpu in
+        pgcc*)                         # Portland Group C compiler
+         whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+         tmp_addflag=' $pic_flag'
+         ;;
+       pgf77* | pgf90* | pgf95* | pgfortran*)
+                                       # Portland Group f77 and f90 compilers
+         whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+         tmp_addflag=' $pic_flag -Mnomain' ;;
+       ecc*,ia64* | icc*,ia64*)        # Intel C compiler on ia64
+         tmp_addflag=' -i_dynamic' ;;
+       efc*,ia64* | ifort*,ia64*)      # Intel Fortran compiler on ia64
+         tmp_addflag=' -i_dynamic -nofor_main' ;;
+       ifc* | ifort*)                  # Intel Fortran compiler
+         tmp_addflag=' -nofor_main' ;;
+       lf95*)                          # Lahey Fortran 8.1
+         whole_archive_flag_spec=
+         tmp_sharedflag='--shared' ;;
+       xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below)
+         tmp_sharedflag='-qmkshrobj'
+         tmp_addflag= ;;
+       nvcc*)  # Cuda Compiler Driver 2.2
+         whole_archive_flag_spec='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+         compiler_needs_object=yes
+         ;;
+       esac
+       case `$CC -V 2>&1 | sed 5q` in
+       *Sun\ C*)                       # Sun C 5.9
+         whole_archive_flag_spec='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+         compiler_needs_object=yes
+         tmp_sharedflag='-G' ;;
+       *Sun\ F*)                       # Sun Fortran 8.3
+         tmp_sharedflag='-G' ;;
+       esac
+       archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+
+        if test "x$supports_anon_versioning" = xyes; then
+          archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~
+           cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+           echo "local: *; };" >> $output_objdir/$libname.ver~
+           $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib'
+        fi
+
+       case $cc_basename in
+       xlf* | bgf* | bgxlf* | mpixlf*)
+         # IBM XL Fortran 10.1 on PPC cannot create shared libs itself
+         whole_archive_flag_spec='--whole-archive$convenience --no-whole-archive'
+         hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+         archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib'
+         if test "x$supports_anon_versioning" = xyes; then
+           archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~
+             cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+             echo "local: *; };" >> $output_objdir/$libname.ver~
+             $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib'
+         fi
+         ;;
+       esac
+      else
+        ld_shlibs=no
+      fi
+      ;;
+
+    netbsd* | netbsdelf*-gnu)
+      if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+       archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib'
+       wlarc=
+      else
+       archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+       archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      fi
+      ;;
+
+    solaris*)
+      if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then
+       ld_shlibs=no
+       cat <<_LT_EOF 1>&2
+
+*** Warning: The releases 2.8.* of the GNU linker cannot reliably
+*** create shared libraries on Solaris systems.  Therefore, libtool
+*** is disabling shared libraries support.  We urge you to upgrade GNU
+*** binutils to release 2.9.1 or newer.  Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+      elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+       archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+       archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      else
+       ld_shlibs=no
+      fi
+      ;;
+
+    sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*)
+      case `$LD -v 2>&1` in
+        *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*)
+       ld_shlibs=no
+       cat <<_LT_EOF 1>&2
+
+*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not
+*** reliably create shared libraries on SCO systems.  Therefore, libtool
+*** is disabling shared libraries support.  We urge you to upgrade GNU
+*** binutils to release 2.16.91.0.3 or newer.  Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+       ;;
+       *)
+         # For security reasons, it is highly recommended that you always
+         # use absolute paths for naming shared libraries, and exclude the
+         # DT_RUNPATH tag from executables and libraries.  But doing so
+         # requires that you compile everything twice, which is a pain.
+         if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+           hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+           archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+           archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+         else
+           ld_shlibs=no
+         fi
+       ;;
+      esac
+      ;;
+
+    sunos4*)
+      archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+      wlarc=
+      hardcode_direct=yes
+      hardcode_shlibpath_var=no
+      ;;
+
+    *)
+      if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+       archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+       archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      else
+       ld_shlibs=no
+      fi
+      ;;
+    esac
+
+    if test "$ld_shlibs" = no; then
+      runpath_var=
+      hardcode_libdir_flag_spec=
+      export_dynamic_flag_spec=
+      whole_archive_flag_spec=
+    fi
+  else
+    # PORTME fill in a description of your system's linker (not GNU ld)
+    case $host_os in
+    aix3*)
+      allow_undefined_flag=unsupported
+      always_export_symbols=yes
+      archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname'
+      # Note: this linker hardcodes the directories in LIBPATH if there
+      # are no directories specified by -L.
+      hardcode_minus_L=yes
+      if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then
+       # Neither direct hardcoding nor static linking is supported with a
+       # broken collect2.
+       hardcode_direct=unsupported
+      fi
+      ;;
+
+    aix[4-9]*)
+      if test "$host_cpu" = ia64; then
+       # On IA64, the linker does run time linking by default, so we don't
+       # have to do anything special.
+       aix_use_runtimelinking=no
+       exp_sym_flag='-Bexport'
+       no_entry_flag=""
+      else
+       # If we're using GNU nm, then we don't want the "-C" option.
+       # -C means demangle to AIX nm, but means don't demangle with GNU nm
+       # Also, AIX nm treats weak defined symbols like other global
+       # defined symbols, whereas GNU nm marks them as "W".
+       if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+         export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+       else
+         export_symbols_cmds='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && (substr(\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+       fi
+       aix_use_runtimelinking=no
+
+       # Test if we are trying to use run time linking or normal
+       # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+       # need to do runtime linking.
+       case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*)
+         for ld_flag in $LDFLAGS; do
+         if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then
+           aix_use_runtimelinking=yes
+           break
+         fi
+         done
+         ;;
+       esac
+
+       exp_sym_flag='-bexport'
+       no_entry_flag='-bnoentry'
+      fi
+
+      # When large executables or shared objects are built, AIX ld can
+      # have problems creating the table of contents.  If linking a library
+      # or program results in "error TOC overflow" add -mminimal-toc to
+      # CXXFLAGS/CFLAGS for g++/gcc.  In the cases where that is not
+      # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+      archive_cmds=''
+      hardcode_direct=yes
+      hardcode_direct_absolute=yes
+      hardcode_libdir_separator=':'
+      link_all_deplibs=yes
+      file_list_spec='${wl}-f,'
+
+      if test "$GCC" = yes; then
+       case $host_os in aix4.[012]|aix4.[012].*)
+       # We only want to do this on AIX 4.2 and lower, the check
+       # below for broken collect2 doesn't work under 4.3+
+         collect2name=`${CC} -print-prog-name=collect2`
+         if test -f "$collect2name" &&
+          strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+         then
+         # We have reworked collect2
+         :
+         else
+         # We have old collect2
+         hardcode_direct=unsupported
+         # It fails to find uninstalled libraries when the uninstalled
+         # path is not listed in the libpath.  Setting hardcode_minus_L
+         # to unsupported forces relinking
+         hardcode_minus_L=yes
+         hardcode_libdir_flag_spec='-L$libdir'
+         hardcode_libdir_separator=
+         fi
+         ;;
+       esac
+       shared_flag='-shared'
+       if test "$aix_use_runtimelinking" = yes; then
+         shared_flag="$shared_flag "'${wl}-G'
+       fi
+       link_all_deplibs=no
+      else
+       # not using gcc
+       if test "$host_cpu" = ia64; then
+       # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+       # chokes on -Wl,-G. The following line is correct:
+         shared_flag='-G'
+       else
+         if test "$aix_use_runtimelinking" = yes; then
+           shared_flag='${wl}-G'
+         else
+           shared_flag='${wl}-bM:SRE'
+         fi
+       fi
+      fi
+
+      export_dynamic_flag_spec='${wl}-bexpall'
+      # It seems that -bexpall does not export symbols beginning with
+      # underscore (_), so it is better to generate a list of symbols to export.
+      always_export_symbols=yes
+      if test "$aix_use_runtimelinking" = yes; then
+       # Warning - without using the other runtime loading flags (-brtl),
+       # -berok will link without error, but may produce a broken library.
+       allow_undefined_flag='-berok'
+        # Determine the default libpath from the value encoded in an
+        # empty executable.
+        if test "${lt_cv_aix_libpath+set}" = set; then
+  aix_libpath=$lt_cv_aix_libpath
+else
+  if ${lt_cv_aix_libpath_+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+
+  lt_aix_libpath_sed='
+      /Import File Strings/,/^$/ {
+         /^0/ {
+             s/^0  *\([^ ]*\) *$/\1/
+             p
+         }
+      }'
+  lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  # Check for a 64-bit object if we didn't find anything.
+  if test -z "$lt_cv_aix_libpath_"; then
+    lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+  if test -z "$lt_cv_aix_libpath_"; then
+    lt_cv_aix_libpath_="/usr/lib:/lib"
+  fi
+
+fi
+
+  aix_libpath=$lt_cv_aix_libpath_
+fi
+
+        hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath"
+        archive_expsym_cmds='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+      else
+       if test "$host_cpu" = ia64; then
+         hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib'
+         allow_undefined_flag="-z nodefs"
+         archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols"
+       else
+        # Determine the default libpath from the value encoded in an
+        # empty executable.
+        if test "${lt_cv_aix_libpath+set}" = set; then
+  aix_libpath=$lt_cv_aix_libpath
+else
+  if ${lt_cv_aix_libpath_+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+
+  lt_aix_libpath_sed='
+      /Import File Strings/,/^$/ {
+         /^0/ {
+             s/^0  *\([^ ]*\) *$/\1/
+             p
+         }
+      }'
+  lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  # Check for a 64-bit object if we didn't find anything.
+  if test -z "$lt_cv_aix_libpath_"; then
+    lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+  if test -z "$lt_cv_aix_libpath_"; then
+    lt_cv_aix_libpath_="/usr/lib:/lib"
+  fi
+
+fi
+
+  aix_libpath=$lt_cv_aix_libpath_
+fi
+
+        hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath"
+         # Warning - without using the other run time loading flags,
+         # -berok will link without error, but may produce a broken library.
+         no_undefined_flag=' ${wl}-bernotok'
+         allow_undefined_flag=' ${wl}-berok'
+         if test "$with_gnu_ld" = yes; then
+           # We only use this code for GNU lds that support --whole-archive.
+           whole_archive_flag_spec='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+         else
+           # Exported symbols can be pulled into shared objects from archives
+           whole_archive_flag_spec='$convenience'
+         fi
+         archive_cmds_need_lc=yes
+         # This is similar to how AIX traditionally builds its shared libraries.
+         archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname'
+       fi
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+            archive_expsym_cmds=''
+        ;;
+      m68k)
+            archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+            hardcode_libdir_flag_spec='-L$libdir'
+            hardcode_minus_L=yes
+        ;;
+      esac
+      ;;
+
+    bsdi[45]*)
+      export_dynamic_flag_spec=-rdynamic
+      ;;
+
+    cygwin* | mingw* | pw32* | cegcc*)
+      # When not using gcc, we currently assume that we are using
+      # Microsoft Visual C++.
+      # hardcode_libdir_flag_spec is actually meaningless, as there is
+      # no search path for DLLs.
+      case $cc_basename in
+      cl*)
+       # Native MSVC
+       hardcode_libdir_flag_spec=' '
+       allow_undefined_flag=unsupported
+       always_export_symbols=yes
+       file_list_spec='@'
+       # Tell ltmain to make .lib files, not .a files.
+       libext=lib
+       # Tell ltmain to make .dll files, not .so files.
+       shrext_cmds=".dll"
+       # FIXME: Setting linknames here is a bad hack.
+       archive_cmds='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames='
+       archive_expsym_cmds='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+           sed -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp;
+         else
+           sed -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp;
+         fi~
+         $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+         linknames='
+       # The linker will not automatically build a static lib if we build a DLL.
+       # _LT_TAGVAR(old_archive_from_new_cmds, )='true'
+       enable_shared_with_static_runtimes=yes
+       exclude_expsyms='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+       export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols'
+       # Don't use ranlib
+       old_postinstall_cmds='chmod 644 $oldlib'
+       postlink_cmds='lt_outputfile="@OUTPUT@"~
+         lt_tool_outputfile="@TOOL_OUTPUT@"~
+         case $lt_outputfile in
+           *.exe|*.EXE) ;;
+           *)
+             lt_outputfile="$lt_outputfile.exe"
+             lt_tool_outputfile="$lt_tool_outputfile.exe"
+             ;;
+         esac~
+         if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then
+           $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+           $RM "$lt_outputfile.manifest";
+         fi'
+       ;;
+      *)
+       # Assume MSVC wrapper
+       hardcode_libdir_flag_spec=' '
+       allow_undefined_flag=unsupported
+       # Tell ltmain to make .lib files, not .a files.
+       libext=lib
+       # Tell ltmain to make .dll files, not .so files.
+       shrext_cmds=".dll"
+       # FIXME: Setting linknames here is a bad hack.
+       archive_cmds='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames='
+       # The linker will automatically build a .lib file if we build a DLL.
+       old_archive_from_new_cmds='true'
+       # FIXME: Should let the user specify the lib program.
+       old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs'
+       enable_shared_with_static_runtimes=yes
+       ;;
+      esac
+      ;;
+
+    darwin* | rhapsody*)
+
+
+  archive_cmds_need_lc=no
+  hardcode_direct=no
+  hardcode_automatic=yes
+  hardcode_shlibpath_var=unsupported
+  if test "$lt_cv_ld_force_load" = "yes"; then
+    whole_archive_flag_spec='`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience ${wl}-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`'
+
+  else
+    whole_archive_flag_spec=''
+  fi
+  link_all_deplibs=yes
+  allow_undefined_flag="$_lt_dar_allow_undefined"
+  case $cc_basename in
+     ifort*) _lt_dar_can_shared=yes ;;
+     *) _lt_dar_can_shared=$GCC ;;
+  esac
+  if test "$_lt_dar_can_shared" = "yes"; then
+    output_verbose_link_cmd=func_echo_all
+    archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}"
+    module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}"
+    archive_expsym_cmds="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}"
+    module_expsym_cmds="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}"
+
+  else
+  ld_shlibs=no
+  fi
+
+      ;;
+
+    dgux*)
+      archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_libdir_flag_spec='-L$libdir'
+      hardcode_shlibpath_var=no
+      ;;
+
+    # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor
+    # support.  Future versions do this automatically, but an explicit c++rt0.o
+    # does not break anything, and helps significantly (at the cost of a little
+    # extra space).
+    freebsd2.2*)
+      archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o'
+      hardcode_libdir_flag_spec='-R$libdir'
+      hardcode_direct=yes
+      hardcode_shlibpath_var=no
+      ;;
+
+    # Unfortunately, older versions of FreeBSD 2 do not have this feature.
+    freebsd2.*)
+      archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_direct=yes
+      hardcode_minus_L=yes
+      hardcode_shlibpath_var=no
+      ;;
+
+    # FreeBSD 3 and greater uses gcc -shared to do shared libraries.
+    freebsd* | dragonfly*)
+      archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+      hardcode_libdir_flag_spec='-R$libdir'
+      hardcode_direct=yes
+      hardcode_shlibpath_var=no
+      ;;
+
+    hpux9*)
+      if test "$GCC" = yes; then
+       archive_cmds='$RM $output_objdir/$soname~$CC -shared $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+      else
+       archive_cmds='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+      fi
+      hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+      hardcode_libdir_separator=:
+      hardcode_direct=yes
+
+      # hardcode_minus_L: Not really in the search PATH,
+      # but as the default location of the library.
+      hardcode_minus_L=yes
+      export_dynamic_flag_spec='${wl}-E'
+      ;;
+
+    hpux10*)
+      if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+       archive_cmds='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+      else
+       archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+      fi
+      if test "$with_gnu_ld" = no; then
+       hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+       hardcode_libdir_separator=:
+       hardcode_direct=yes
+       hardcode_direct_absolute=yes
+       export_dynamic_flag_spec='${wl}-E'
+       # hardcode_minus_L: Not really in the search PATH,
+       # but as the default location of the library.
+       hardcode_minus_L=yes
+      fi
+      ;;
+
+    hpux11*)
+      if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+       case $host_cpu in
+       hppa*64*)
+         archive_cmds='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+         ;;
+       ia64*)
+         archive_cmds='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+         ;;
+       *)
+         archive_cmds='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+         ;;
+       esac
+      else
+       case $host_cpu in
+       hppa*64*)
+         archive_cmds='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+         ;;
+       ia64*)
+         archive_cmds='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+         ;;
+       *)
+
+         # Older versions of the 11.00 compiler do not understand -b yet
+         # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does)
+         { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC understands -b" >&5
+$as_echo_n "checking if $CC understands -b... " >&6; }
+if ${lt_cv_prog_compiler__b+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_prog_compiler__b=no
+   save_LDFLAGS="$LDFLAGS"
+   LDFLAGS="$LDFLAGS -b"
+   echo "$lt_simple_link_test_code" > conftest.$ac_ext
+   if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+     # The linker can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     if test -s conftest.err; then
+       # Append any errors to the config.log.
+       cat conftest.err 1>&5
+       $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+       $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+       if diff conftest.exp conftest.er2 >/dev/null; then
+         lt_cv_prog_compiler__b=yes
+       fi
+     else
+       lt_cv_prog_compiler__b=yes
+     fi
+   fi
+   $RM -r conftest*
+   LDFLAGS="$save_LDFLAGS"
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler__b" >&5
+$as_echo "$lt_cv_prog_compiler__b" >&6; }
+
+if test x"$lt_cv_prog_compiler__b" = xyes; then
+    archive_cmds='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+else
+    archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+fi
+
+         ;;
+       esac
+      fi
+      if test "$with_gnu_ld" = no; then
+       hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+       hardcode_libdir_separator=:
+
+       case $host_cpu in
+       hppa*64*|ia64*)
+         hardcode_direct=no
+         hardcode_shlibpath_var=no
+         ;;
+       *)
+         hardcode_direct=yes
+         hardcode_direct_absolute=yes
+         export_dynamic_flag_spec='${wl}-E'
+
+         # hardcode_minus_L: Not really in the search PATH,
+         # but as the default location of the library.
+         hardcode_minus_L=yes
+         ;;
+       esac
+      fi
+      ;;
+
+    irix5* | irix6* | nonstopux*)
+      if test "$GCC" = yes; then
+       archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+       # Try to use the -exported_symbol ld option, if it does not
+       # work, assume that -exports_file does not work either and
+       # implicitly export all symbols.
+       # This should be the same for all languages, so no per-tag cache variable.
+       { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5
+$as_echo_n "checking whether the $host_os linker accepts -exported_symbol... " >&6; }
+if ${lt_cv_irix_exported_symbol+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  save_LDFLAGS="$LDFLAGS"
+          LDFLAGS="$LDFLAGS -shared ${wl}-exported_symbol ${wl}foo ${wl}-update_registry ${wl}/dev/null"
+          cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+int foo (void) { return 0; }
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  lt_cv_irix_exported_symbol=yes
+else
+  lt_cv_irix_exported_symbol=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+           LDFLAGS="$save_LDFLAGS"
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5
+$as_echo "$lt_cv_irix_exported_symbol" >&6; }
+       if test "$lt_cv_irix_exported_symbol" = yes; then
+          archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations ${wl}-exports_file ${wl}$export_symbols -o $lib'
+       fi
+      else
+       archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+       archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -exports_file $export_symbols -o $lib'
+      fi
+      archive_cmds_need_lc='no'
+      hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+      hardcode_libdir_separator=:
+      inherit_rpath=yes
+      link_all_deplibs=yes
+      ;;
+
+    netbsd* | netbsdelf*-gnu)
+      if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+       archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'  # a.out
+      else
+       archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags'      # ELF
+      fi
+      hardcode_libdir_flag_spec='-R$libdir'
+      hardcode_direct=yes
+      hardcode_shlibpath_var=no
+      ;;
+
+    newsos6)
+      archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_direct=yes
+      hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+      hardcode_libdir_separator=:
+      hardcode_shlibpath_var=no
+      ;;
+
+    *nto* | *qnx*)
+      ;;
+
+    openbsd*)
+      if test -f /usr/libexec/ld.so; then
+       hardcode_direct=yes
+       hardcode_shlibpath_var=no
+       hardcode_direct_absolute=yes
+       if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+         archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+         archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols'
+         hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+         export_dynamic_flag_spec='${wl}-E'
+       else
+         case $host_os in
+          openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*)
+            archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+            hardcode_libdir_flag_spec='-R$libdir'
+            ;;
+          *)
+            archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+            hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+            ;;
+         esac
+       fi
+      else
+       ld_shlibs=no
+      fi
+      ;;
+
+    os2*)
+      hardcode_libdir_flag_spec='-L$libdir'
+      hardcode_minus_L=yes
+      allow_undefined_flag=unsupported
+      archive_cmds='$ECHO "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~echo DATA >> $output_objdir/$libname.def~echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def'
+      old_archive_from_new_cmds='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def'
+      ;;
+
+    osf3*)
+      if test "$GCC" = yes; then
+       allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*'
+       archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+      else
+       allow_undefined_flag=' -expect_unresolved \*'
+       archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+      fi
+      archive_cmds_need_lc='no'
+      hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+      hardcode_libdir_separator=:
+      ;;
+
+    osf4* | osf5*)     # as osf3* with the addition of -msym flag
+      if test "$GCC" = yes; then
+       allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*'
+       archive_cmds='$CC -shared${allow_undefined_flag} $pic_flag $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+       hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+      else
+       allow_undefined_flag=' -expect_unresolved \*'
+       archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+       archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~
+       $CC -shared${allow_undefined_flag} ${wl}-input ${wl}$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~$RM $lib.exp'
+
+       # Both c and cxx compiler support -rpath directly
+       hardcode_libdir_flag_spec='-rpath $libdir'
+      fi
+      archive_cmds_need_lc='no'
+      hardcode_libdir_separator=:
+      ;;
+
+    solaris*)
+      no_undefined_flag=' -z defs'
+      if test "$GCC" = yes; then
+       wlarc='${wl}'
+       archive_cmds='$CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+       archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+         $CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+      else
+       case `$CC -V 2>&1` in
+       *"Compilers 5.0"*)
+         wlarc=''
+         archive_cmds='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags'
+         archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+         $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp'
+         ;;
+       *)
+         wlarc='${wl}'
+         archive_cmds='$CC -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $compiler_flags'
+         archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+         $CC -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+         ;;
+       esac
+      fi
+      hardcode_libdir_flag_spec='-R$libdir'
+      hardcode_shlibpath_var=no
+      case $host_os in
+      solaris2.[0-5] | solaris2.[0-5].*) ;;
+      *)
+       # The compiler driver will combine and reorder linker options,
+       # but understands `-z linker_flag'.  GCC discards it without `$wl',
+       # but is careful enough not to reorder.
+       # Supported since Solaris 2.6 (maybe 2.5.1?)
+       if test "$GCC" = yes; then
+         whole_archive_flag_spec='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract'
+       else
+         whole_archive_flag_spec='-z allextract$convenience -z defaultextract'
+       fi
+       ;;
+      esac
+      link_all_deplibs=yes
+      ;;
+
+    sunos4*)
+      if test "x$host_vendor" = xsequent; then
+       # Use $CC to link under sequent, because it throws in some extra .o
+       # files that make .init and .fini sections work.
+       archive_cmds='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+       archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags'
+      fi
+      hardcode_libdir_flag_spec='-L$libdir'
+      hardcode_direct=yes
+      hardcode_minus_L=yes
+      hardcode_shlibpath_var=no
+      ;;
+
+    sysv4)
+      case $host_vendor in
+       sni)
+         archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+         hardcode_direct=yes # is this really true???
+       ;;
+       siemens)
+         ## LD is ld it makes a PLAMLIB
+         ## CC just makes a GrossModule.
+         archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags'
+         reload_cmds='$CC -r -o $output$reload_objs'
+         hardcode_direct=no
+        ;;
+       motorola)
+         archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+         hardcode_direct=no #Motorola manual says yes, but my tests say they lie
+       ;;
+      esac
+      runpath_var='LD_RUN_PATH'
+      hardcode_shlibpath_var=no
+      ;;
+
+    sysv4.3*)
+      archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_shlibpath_var=no
+      export_dynamic_flag_spec='-Bexport'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec; then
+       archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+       hardcode_shlibpath_var=no
+       runpath_var=LD_RUN_PATH
+       hardcode_runpath_var=yes
+       ld_shlibs=yes
+      fi
+      ;;
+
+    sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*)
+      no_undefined_flag='${wl}-z,text'
+      archive_cmds_need_lc=no
+      hardcode_shlibpath_var=no
+      runpath_var='LD_RUN_PATH'
+
+      if test "$GCC" = yes; then
+       archive_cmds='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+       archive_expsym_cmds='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+       archive_cmds='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+       archive_expsym_cmds='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      fi
+      ;;
+
+    sysv5* | sco3.2v5* | sco5v6*)
+      # Note: We can NOT use -z defs as we might desire, because we do not
+      # link with -lc, and that would cause any symbols used from libc to
+      # always be unresolved, which means just about no library would
+      # ever link correctly.  If we're not using GNU ld we use -z text
+      # though, which does catch some bad symbols but isn't as heavy-handed
+      # as -z defs.
+      no_undefined_flag='${wl}-z,text'
+      allow_undefined_flag='${wl}-z,nodefs'
+      archive_cmds_need_lc=no
+      hardcode_shlibpath_var=no
+      hardcode_libdir_flag_spec='${wl}-R,$libdir'
+      hardcode_libdir_separator=':'
+      link_all_deplibs=yes
+      export_dynamic_flag_spec='${wl}-Bexport'
+      runpath_var='LD_RUN_PATH'
+
+      if test "$GCC" = yes; then
+       archive_cmds='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+       archive_expsym_cmds='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+       archive_cmds='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+       archive_expsym_cmds='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      fi
+      ;;
+
+    uts4*)
+      archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      hardcode_libdir_flag_spec='-L$libdir'
+      hardcode_shlibpath_var=no
+      ;;
+
+    *)
+      ld_shlibs=no
+      ;;
+    esac
+
+    if test x$host_vendor = xsni; then
+      case $host in
+      sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+       export_dynamic_flag_spec='${wl}-Blargedynsym'
+       ;;
+      esac
+    fi
+  fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs" >&5
+$as_echo "$ld_shlibs" >&6; }
+test "$ld_shlibs" = no && can_build_shared=no
+
+with_gnu_ld=$with_gnu_ld
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#
+# Do we need to explicitly link libc?
+#
+case "x$archive_cmds_need_lc" in
+x|xyes)
+  # Assume -lc should be added
+  archive_cmds_need_lc=yes
+
+  if test "$enable_shared" = yes && test "$GCC" = yes; then
+    case $archive_cmds in
+    *'~'*)
+      # FIXME: we may have to deal with multi-command sequences.
+      ;;
+    '$CC '*)
+      # Test whether the compiler implicitly links with -lc since on some
+      # systems, -lgcc has to come before -lc. If gcc already passes -lc
+      # to ld, don't add -lc before -lgcc.
+      { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5
+$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; }
+if ${lt_cv_archive_cmds_need_lc+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  $RM conftest*
+       echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+       if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5
+  (eval $ac_compile) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } 2>conftest.err; then
+         soname=conftest
+         lib=conftest
+         libobjs=conftest.$ac_objext
+         deplibs=
+         wl=$lt_prog_compiler_wl
+         pic_flag=$lt_prog_compiler_pic
+         compiler_flags=-v
+         linker_flags=-v
+         verstring=
+         output_objdir=.
+         libname=conftest
+         lt_save_allow_undefined_flag=$allow_undefined_flag
+         allow_undefined_flag=
+         if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5
+  (eval $archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+         then
+           lt_cv_archive_cmds_need_lc=no
+         else
+           lt_cv_archive_cmds_need_lc=yes
+         fi
+         allow_undefined_flag=$lt_save_allow_undefined_flag
+       else
+         cat conftest.err 1>&5
+       fi
+       $RM conftest*
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc" >&5
+$as_echo "$lt_cv_archive_cmds_need_lc" >&6; }
+      archive_cmds_need_lc=$lt_cv_archive_cmds_need_lc
+      ;;
+    esac
+  fi
+  ;;
+esac
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5
+$as_echo_n "checking dynamic linker characteristics... " >&6; }
+
+if test "$GCC" = yes; then
+  case $host_os in
+    darwin*) lt_awk_arg="/^libraries:/,/LR/" ;;
+    *) lt_awk_arg="/^libraries:/" ;;
+  esac
+  case $host_os in
+    mingw* | cegcc*) lt_sed_strip_eq="s,=\([A-Za-z]:\),\1,g" ;;
+    *) lt_sed_strip_eq="s,=/,/,g" ;;
+  esac
+  lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq`
+  case $lt_search_path_spec in
+  *\;*)
+    # if the path contains ";" then we assume it to be the separator
+    # otherwise default to the standard path separator (i.e. ":") - it is
+    # assumed that no part of a normal pathname contains ";" but that should
+    # okay in the real world where ";" in dirpaths is itself problematic.
+    lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'`
+    ;;
+  *)
+    lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"`
+    ;;
+  esac
+  # Ok, now we have the path, separated by spaces, we can step through it
+  # and add multilib dir if necessary.
+  lt_tmp_lt_search_path_spec=
+  lt_multi_os_dir=`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null`
+  for lt_sys_path in $lt_search_path_spec; do
+    if test -d "$lt_sys_path/$lt_multi_os_dir"; then
+      lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path/$lt_multi_os_dir"
+    else
+      test -d "$lt_sys_path" && \
+       lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path"
+    fi
+  done
+  lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk '
+BEGIN {RS=" "; FS="/|\n";} {
+  lt_foo="";
+  lt_count=0;
+  for (lt_i = NF; lt_i > 0; lt_i--) {
+    if ($lt_i != "" && $lt_i != ".") {
+      if ($lt_i == "..") {
+        lt_count++;
+      } else {
+        if (lt_count == 0) {
+          lt_foo="/" $lt_i lt_foo;
+        } else {
+          lt_count--;
+        }
+      }
+    }
+  }
+  if (lt_foo != "") { lt_freq[lt_foo]++; }
+  if (lt_freq[lt_foo] == 1) { print lt_foo; }
+}'`
+  # AWK program above erroneously prepends '/' to C:/dos/paths
+  # for these hosts.
+  case $host_os in
+    mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\
+      $SED 's,/\([A-Za-z]:\),\1,g'` ;;
+  esac
+  sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP`
+else
+  sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
+fi
+library_names_spec=
+libname_spec='lib$name'
+soname_spec=
+shrext_cmds=".so"
+postinstall_cmds=
+postuninstall_cmds=
+finish_cmds=
+finish_eval=
+shlibpath_var=
+shlibpath_overrides_runpath=unknown
+version_type=none
+dynamic_linker="$host_os ld.so"
+sys_lib_dlsearch_path_spec="/lib /usr/lib"
+need_lib_prefix=unknown
+hardcode_into_libs=no
+
+# when you set need_version to no, make sure it does not cause -set_version
+# flags to be left without arguments
+need_version=unknown
+
+case $host_os in
+aix3*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a'
+  shlibpath_var=LIBPATH
+
+  # AIX 3 has no versioning support, so we append a major version to the name.
+  soname_spec='${libname}${release}${shared_ext}$major'
+  ;;
+
+aix[4-9]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  hardcode_into_libs=yes
+  if test "$host_cpu" = ia64; then
+    # AIX 5 supports IA64
+    library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}'
+    shlibpath_var=LD_LIBRARY_PATH
+  else
+    # With GCC up to 2.95.x, collect2 would create an import file
+    # for dependence libraries.  The import file would start with
+    # the line `#! .'.  This would cause the generated library to
+    # depend on `.', always an invalid library.  This was fixed in
+    # development snapshots of GCC prior to 3.0.
+    case $host_os in
+      aix4 | aix4.[01] | aix4.[01].*)
+      if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)'
+          echo ' yes '
+          echo '#endif'; } | ${CC} -E - | $GREP yes > /dev/null; then
+       :
+      else
+       can_build_shared=no
+      fi
+      ;;
+    esac
+    # AIX (on Power*) has no versioning support, so currently we can not hardcode correct
+    # soname into executable. Probably we can add versioning support to
+    # collect2, so additional links can be useful in future.
+    if test "$aix_use_runtimelinking" = yes; then
+      # If using run time linking (on AIX 4.2 or later) use lib<name>.so
+      # instead of lib<name>.a to let people know that these are not
+      # typical AIX shared libraries.
+      library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    else
+      # We preserve .a as extension for shared libraries through AIX4.2
+      # and later when we are not doing run time linking.
+      library_names_spec='${libname}${release}.a $libname.a'
+      soname_spec='${libname}${release}${shared_ext}$major'
+    fi
+    shlibpath_var=LIBPATH
+  fi
+  ;;
+
+amigaos*)
+  case $host_cpu in
+  powerpc)
+    # Since July 2007 AmigaOS4 officially supports .so libraries.
+    # When compiling the executable, add -use-dynld -Lsobjs: to the compileline.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    ;;
+  m68k)
+    library_names_spec='$libname.ixlibrary $libname.a'
+    # Create ${libname}_ixlibrary.a entries in /sys/libs.
+    finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done'
+    ;;
+  esac
+  ;;
+
+beos*)
+  library_names_spec='${libname}${shared_ext}'
+  dynamic_linker="$host_os ld.so"
+  shlibpath_var=LIBRARY_PATH
+  ;;
+
+bsdi[45]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib"
+  sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib"
+  # the default ld.so.conf also contains /usr/contrib/lib and
+  # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow
+  # libtool to hard-code these into programs
+  ;;
+
+cygwin* | mingw* | pw32* | cegcc*)
+  version_type=windows
+  shrext_cmds=".dll"
+  need_version=no
+  need_lib_prefix=no
+
+  case $GCC,$cc_basename in
+  yes,*)
+    # gcc
+    library_names_spec='$libname.dll.a'
+    # DLL is installed to $(libdir)/../bin by postinstall_cmds
+    postinstall_cmds='base_file=`basename \${file}`~
+      dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+      dldir=$destdir/`dirname \$dlpath`~
+      test -d \$dldir || mkdir -p \$dldir~
+      $install_prog $dir/$dlname \$dldir/$dlname~
+      chmod a+x \$dldir/$dlname~
+      if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then
+        eval '\''$striplib \$dldir/$dlname'\'' || exit \$?;
+      fi'
+    postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+      dlpath=$dir/\$dldll~
+       $RM \$dlpath'
+    shlibpath_overrides_runpath=yes
+
+    case $host_os in
+    cygwin*)
+      # Cygwin DLLs use 'cyg' prefix rather than 'lib'
+      soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+
+      sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"
+      ;;
+    mingw* | cegcc*)
+      # MinGW DLLs use traditional 'lib' prefix
+      soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+      ;;
+    pw32*)
+      # pw32 DLLs use 'pw' prefix rather than 'lib'
+      library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+      ;;
+    esac
+    dynamic_linker='Win32 ld.exe'
+    ;;
+
+  *,cl*)
+    # Native MSVC
+    libname_spec='$name'
+    soname_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext}'
+    library_names_spec='${libname}.dll.lib'
+
+    case $build_os in
+    mingw*)
+      sys_lib_search_path_spec=
+      lt_save_ifs=$IFS
+      IFS=';'
+      for lt_path in $LIB
+      do
+        IFS=$lt_save_ifs
+        # Let DOS variable expansion print the short 8.3 style file name.
+        lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"`
+        sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path"
+      done
+      IFS=$lt_save_ifs
+      # Convert to MSYS style.
+      sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'`
+      ;;
+    cygwin*)
+      # Convert to unix form, then to dos form, then back to unix form
+      # but this time dos style (no spaces!) so that the unix form looks
+      # like /cygdrive/c/PROGRA~1:/cygdr...
+      sys_lib_search_path_spec=`cygpath --path --unix "$LIB"`
+      sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null`
+      sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+      ;;
+    *)
+      sys_lib_search_path_spec="$LIB"
+      if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then
+        # It is most probably a Windows format PATH.
+        sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'`
+      else
+        sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+      fi
+      # FIXME: find the short name or the path components, as spaces are
+      # common. (e.g. "Program Files" -> "PROGRA~1")
+      ;;
+    esac
+
+    # DLL is installed to $(libdir)/../bin by postinstall_cmds
+    postinstall_cmds='base_file=`basename \${file}`~
+      dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+      dldir=$destdir/`dirname \$dlpath`~
+      test -d \$dldir || mkdir -p \$dldir~
+      $install_prog $dir/$dlname \$dldir/$dlname'
+    postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+      dlpath=$dir/\$dldll~
+       $RM \$dlpath'
+    shlibpath_overrides_runpath=yes
+    dynamic_linker='Win32 link.exe'
+    ;;
+
+  *)
+    # Assume MSVC wrapper
+    library_names_spec='${libname}`echo ${release} | $SED -e 's/[.]/-/g'`${versuffix}${shared_ext} $libname.lib'
+    dynamic_linker='Win32 ld.exe'
+    ;;
+  esac
+  # FIXME: first we should search . and the directory the executable is in
+  shlibpath_var=PATH
+  ;;
+
+darwin* | rhapsody*)
+  dynamic_linker="$host_os dyld"
+  version_type=darwin
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${major}$shared_ext ${libname}$shared_ext'
+  soname_spec='${libname}${release}${major}$shared_ext'
+  shlibpath_overrides_runpath=yes
+  shlibpath_var=DYLD_LIBRARY_PATH
+  shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`'
+
+  sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"
+  sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib'
+  ;;
+
+dgux*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  ;;
+
+freebsd* | dragonfly*)
+  # DragonFly does not have aout.  When/if they implement a new
+  # versioning mechanism, adjust this.
+  if test -x /usr/bin/objformat; then
+    objformat=`/usr/bin/objformat`
+  else
+    case $host_os in
+    freebsd[23].*) objformat=aout ;;
+    *) objformat=elf ;;
+    esac
+  fi
+  version_type=freebsd-$objformat
+  case $version_type in
+    freebsd-elf*)
+      library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+      need_version=no
+      need_lib_prefix=no
+      ;;
+    freebsd-*)
+      library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix'
+      need_version=yes
+      ;;
+  esac
+  shlibpath_var=LD_LIBRARY_PATH
+  case $host_os in
+  freebsd2.*)
+    shlibpath_overrides_runpath=yes
+    ;;
+  freebsd3.[01]* | freebsdelf3.[01]*)
+    shlibpath_overrides_runpath=yes
+    hardcode_into_libs=yes
+    ;;
+  freebsd3.[2-9]* | freebsdelf3.[2-9]* | \
+  freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1)
+    shlibpath_overrides_runpath=no
+    hardcode_into_libs=yes
+    ;;
+  *) # from 4.6 on, and DragonFly
+    shlibpath_overrides_runpath=yes
+    hardcode_into_libs=yes
+    ;;
+  esac
+  ;;
+
+gnu*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+haiku*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  dynamic_linker="$host_os runtime_loader"
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib'
+  hardcode_into_libs=yes
+  ;;
+
+hpux9* | hpux10* | hpux11*)
+  # Give a soname corresponding to the major version so that dld.sl refuses to
+  # link against other versions.
+  version_type=sunos
+  need_lib_prefix=no
+  need_version=no
+  case $host_cpu in
+  ia64*)
+    shrext_cmds='.so'
+    hardcode_into_libs=yes
+    dynamic_linker="$host_os dld.so"
+    shlibpath_var=LD_LIBRARY_PATH
+    shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    if test "X$HPUX_IA64_MODE" = X32; then
+      sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib"
+    else
+      sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64"
+    fi
+    sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+    ;;
+  hppa*64*)
+    shrext_cmds='.sl'
+    hardcode_into_libs=yes
+    dynamic_linker="$host_os dld.sl"
+    shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH
+    shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64"
+    sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+    ;;
+  *)
+    shrext_cmds='.sl'
+    dynamic_linker="$host_os dld.sl"
+    shlibpath_var=SHLIB_PATH
+    shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    ;;
+  esac
+  # HP-UX runs *really* slowly unless shared libraries are mode 555, ...
+  postinstall_cmds='chmod 555 $lib'
+  # or fails outright, so override atomically:
+  install_override_mode=555
+  ;;
+
+interix[3-9]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+irix5* | irix6* | nonstopux*)
+  case $host_os in
+    nonstopux*) version_type=nonstopux ;;
+    *)
+       if test "$lt_cv_prog_gnu_ld" = yes; then
+               version_type=linux # correct to gnu/linux during the next big refactor
+       else
+               version_type=irix
+       fi ;;
+  esac
+  need_lib_prefix=no
+  need_version=no
+  soname_spec='${libname}${release}${shared_ext}$major'
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}'
+  case $host_os in
+  irix5* | nonstopux*)
+    libsuff= shlibsuff=
+    ;;
+  *)
+    case $LD in # libtool.m4 will add one of these switches to LD
+    *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ")
+      libsuff= shlibsuff= libmagic=32-bit;;
+    *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ")
+      libsuff=32 shlibsuff=N32 libmagic=N32;;
+    *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ")
+      libsuff=64 shlibsuff=64 libmagic=64-bit;;
+    *) libsuff= shlibsuff= libmagic=never-match;;
+    esac
+    ;;
+  esac
+  shlibpath_var=LD_LIBRARY${shlibsuff}_PATH
+  shlibpath_overrides_runpath=no
+  sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}"
+  sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}"
+  hardcode_into_libs=yes
+  ;;
+
+# No shared lib support for Linux oldld, aout, or coff.
+linux*oldld* | linux*aout* | linux*coff*)
+  dynamic_linker=no
+  ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+
+  # Some binutils ld are patched to set DT_RUNPATH
+  if ${lt_cv_shlibpath_overrides_runpath+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  lt_cv_shlibpath_overrides_runpath=no
+    save_LDFLAGS=$LDFLAGS
+    save_libdir=$libdir
+    eval "libdir=/foo; wl=\"$lt_prog_compiler_wl\"; \
+        LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec\""
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  if  ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then :
+  lt_cv_shlibpath_overrides_runpath=yes
+fi
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+    LDFLAGS=$save_LDFLAGS
+    libdir=$save_libdir
+
+fi
+
+  shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath
+
+  # This implies no fast_install, which is unacceptable.
+  # Some rework will be needed to allow for fast_install
+  # before this can be enabled.
+  hardcode_into_libs=yes
+
+  # Append ld.so.conf contents to the search path
+  if test -f /etc/ld.so.conf; then
+    lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[      ]*hwcap[        ]/d;s/[:,      ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '`
+    sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra"
+  fi
+
+  # We used to test for /lib/ld.so.1 and disable shared libraries on
+  # powerpc, because MkLinux only supported shared libraries with the
+  # GNU dynamic linker.  Since this was broken with cross compilers,
+  # most powerpc-linux boxes support dynamic linking these days and
+  # people can always --disable-shared, the test was removed, and we
+  # assume the GNU/Linux dynamic linker is in use.
+  dynamic_linker='GNU/Linux ld.so'
+  ;;
+
+netbsdelf*-gnu)
+  version_type=linux
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  dynamic_linker='NetBSD ld.elf_so'
+  ;;
+
+netbsd*)
+  version_type=sunos
+  need_lib_prefix=no
+  need_version=no
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+    finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+    dynamic_linker='NetBSD (a.out) ld.so'
+  else
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    dynamic_linker='NetBSD ld.elf_so'
+  fi
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  ;;
+
+newsos6)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  ;;
+
+*nto* | *qnx*)
+  version_type=qnx
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  dynamic_linker='ldqnx.so'
+  ;;
+
+openbsd*)
+  version_type=sunos
+  sys_lib_dlsearch_path_spec="/usr/lib"
+  need_lib_prefix=no
+  # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs.
+  case $host_os in
+    openbsd3.3 | openbsd3.3.*) need_version=yes ;;
+    *)                         need_version=no  ;;
+  esac
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+    case $host_os in
+      openbsd2.[89] | openbsd2.[89].*)
+       shlibpath_overrides_runpath=no
+       ;;
+      *)
+       shlibpath_overrides_runpath=yes
+       ;;
+      esac
+  else
+    shlibpath_overrides_runpath=yes
+  fi
+  ;;
+
+os2*)
+  libname_spec='$name'
+  shrext_cmds=".dll"
+  need_lib_prefix=no
+  library_names_spec='$libname${shared_ext} $libname.a'
+  dynamic_linker='OS/2 ld.exe'
+  shlibpath_var=LIBPATH
+  ;;
+
+osf3* | osf4* | osf5*)
+  version_type=osf
+  need_lib_prefix=no
+  need_version=no
+  soname_spec='${libname}${release}${shared_ext}$major'
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib"
+  sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec"
+  ;;
+
+rdos*)
+  dynamic_linker=no
+  ;;
+
+solaris*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  # ldd complains unless libraries are executable
+  postinstall_cmds='chmod +x $lib'
+  ;;
+
+sunos4*)
+  version_type=sunos
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+  finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  if test "$with_gnu_ld" = yes; then
+    need_lib_prefix=no
+  fi
+  need_version=yes
+  ;;
+
+sysv4 | sysv4.3*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  case $host_vendor in
+    sni)
+      shlibpath_overrides_runpath=no
+      need_lib_prefix=no
+      runpath_var=LD_RUN_PATH
+      ;;
+    siemens)
+      need_lib_prefix=no
+      ;;
+    motorola)
+      need_lib_prefix=no
+      need_version=no
+      shlibpath_overrides_runpath=no
+      sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib'
+      ;;
+  esac
+  ;;
+
+sysv4*MP*)
+  if test -d /usr/nec ;then
+    version_type=linux # correct to gnu/linux during the next big refactor
+    library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}'
+    soname_spec='$libname${shared_ext}.$major'
+    shlibpath_var=LD_LIBRARY_PATH
+  fi
+  ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+  version_type=freebsd-elf
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  if test "$with_gnu_ld" = yes; then
+    sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib'
+  else
+    sys_lib_search_path_spec='/usr/ccs/lib /usr/lib'
+    case $host_os in
+      sco3.2v5*)
+        sys_lib_search_path_spec="$sys_lib_search_path_spec /lib"
+       ;;
+    esac
+  fi
+  sys_lib_dlsearch_path_spec='/usr/lib'
+  ;;
+
+tpf*)
+  # TPF is a cross-target only.  Preferred cross-host = GNU/Linux.
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+uts4*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  ;;
+
+*)
+  dynamic_linker=no
+  ;;
+esac
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5
+$as_echo "$dynamic_linker" >&6; }
+test "$dynamic_linker" = no && can_build_shared=no
+
+variables_saved_for_relink="PATH $shlibpath_var $runpath_var"
+if test "$GCC" = yes; then
+  variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH"
+fi
+
+if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then
+  sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec"
+fi
+if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then
+  sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec"
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5
+$as_echo_n "checking how to hardcode library paths into programs... " >&6; }
+hardcode_action=
+if test -n "$hardcode_libdir_flag_spec" ||
+   test -n "$runpath_var" ||
+   test "X$hardcode_automatic" = "Xyes" ; then
+
+  # We can hardcode non-existent directories.
+  if test "$hardcode_direct" != no &&
+     # If the only mechanism to avoid hardcoding is shlibpath_var, we
+     # have to relink, otherwise we might link with an installed library
+     # when we should be linking with a yet-to-be-installed one
+     ## test "$_LT_TAGVAR(hardcode_shlibpath_var, )" != no &&
+     test "$hardcode_minus_L" != no; then
+    # Linking always hardcodes the temporary library directory.
+    hardcode_action=relink
+  else
+    # We can link without hardcoding, and we can hardcode nonexisting dirs.
+    hardcode_action=immediate
+  fi
+else
+  # We cannot hardcode anything, or else we can only hardcode existing
+  # directories.
+  hardcode_action=unsupported
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action" >&5
+$as_echo "$hardcode_action" >&6; }
+
+if test "$hardcode_action" = relink ||
+   test "$inherit_rpath" = yes; then
+  # Fast installation is not supported
+  enable_fast_install=no
+elif test "$shlibpath_overrides_runpath" = yes ||
+     test "$enable_shared" = no; then
+  # Fast installation is not necessary
+  enable_fast_install=needless
+fi
+
+
+
+
+
+
+  if test "x$enable_dlopen" != xyes; then
+  enable_dlopen=unknown
+  enable_dlopen_self=unknown
+  enable_dlopen_self_static=unknown
+else
+  lt_cv_dlopen=no
+  lt_cv_dlopen_libs=
+
+  case $host_os in
+  beos*)
+    lt_cv_dlopen="load_add_on"
+    lt_cv_dlopen_libs=
+    lt_cv_dlopen_self=yes
+    ;;
+
+  mingw* | pw32* | cegcc*)
+    lt_cv_dlopen="LoadLibrary"
+    lt_cv_dlopen_libs=
+    ;;
+
+  cygwin*)
+    lt_cv_dlopen="dlopen"
+    lt_cv_dlopen_libs=
+    ;;
+
+  darwin*)
+  # if libdl is installed we need to link against it
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+$as_echo_n "checking for dlopen in -ldl... " >&6; }
+if ${ac_cv_lib_dl_dlopen+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_dl_dlopen=yes
+else
+  ac_cv_lib_dl_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+$as_echo "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes; then :
+  lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"
+else
+
+    lt_cv_dlopen="dyld"
+    lt_cv_dlopen_libs=
+    lt_cv_dlopen_self=yes
+
+fi
+
+    ;;
+
+  *)
+    ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load"
+if test "x$ac_cv_func_shl_load" = xyes; then :
+  lt_cv_dlopen="shl_load"
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5
+$as_echo_n "checking for shl_load in -ldld... " >&6; }
+if ${ac_cv_lib_dld_shl_load+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldld  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char shl_load ();
+int
+main ()
+{
+return shl_load ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_dld_shl_load=yes
+else
+  ac_cv_lib_dld_shl_load=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5
+$as_echo "$ac_cv_lib_dld_shl_load" >&6; }
+if test "x$ac_cv_lib_dld_shl_load" = xyes; then :
+  lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-ldld"
+else
+  ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen"
+if test "x$ac_cv_func_dlopen" = xyes; then :
+  lt_cv_dlopen="dlopen"
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5
+$as_echo_n "checking for dlopen in -ldl... " >&6; }
+if ${ac_cv_lib_dl_dlopen+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_dl_dlopen=yes
+else
+  ac_cv_lib_dl_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5
+$as_echo "$ac_cv_lib_dl_dlopen" >&6; }
+if test "x$ac_cv_lib_dl_dlopen" = xyes; then :
+  lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5
+$as_echo_n "checking for dlopen in -lsvld... " >&6; }
+if ${ac_cv_lib_svld_dlopen+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsvld  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlopen ();
+int
+main ()
+{
+return dlopen ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_svld_dlopen=yes
+else
+  ac_cv_lib_svld_dlopen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5
+$as_echo "$ac_cv_lib_svld_dlopen" >&6; }
+if test "x$ac_cv_lib_svld_dlopen" = xyes; then :
+  lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld"
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5
+$as_echo_n "checking for dld_link in -ldld... " >&6; }
+if ${ac_cv_lib_dld_dld_link+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldld  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dld_link ();
+int
+main ()
+{
+return dld_link ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_dld_dld_link=yes
+else
+  ac_cv_lib_dld_dld_link=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5
+$as_echo "$ac_cv_lib_dld_dld_link" >&6; }
+if test "x$ac_cv_lib_dld_dld_link" = xyes; then :
+  lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-ldld"
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+    ;;
+  esac
+
+  if test "x$lt_cv_dlopen" != xno; then
+    enable_dlopen=yes
+  else
+    enable_dlopen=no
+  fi
+
+  case $lt_cv_dlopen in
+  dlopen)
+    save_CPPFLAGS="$CPPFLAGS"
+    test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H"
+
+    save_LDFLAGS="$LDFLAGS"
+    wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\"
+
+    save_LIBS="$LIBS"
+    LIBS="$lt_cv_dlopen_libs $LIBS"
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a program can dlopen itself" >&5
+$as_echo_n "checking whether a program can dlopen itself... " >&6; }
+if ${lt_cv_dlopen_self+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+         if test "$cross_compiling" = yes; then :
+  lt_cv_dlopen_self=cross
+else
+  lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+  lt_status=$lt_dlunknown
+  cat > conftest.$ac_ext <<_LT_EOF
+#line $LINENO "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+#  define LT_DLGLOBAL          RTLD_GLOBAL
+#else
+#  ifdef DL_GLOBAL
+#    define LT_DLGLOBAL                DL_GLOBAL
+#  else
+#    define LT_DLGLOBAL                0
+#  endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+   find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+#  ifdef RTLD_LAZY
+#    define LT_DLLAZY_OR_NOW           RTLD_LAZY
+#  else
+#    ifdef DL_LAZY
+#      define LT_DLLAZY_OR_NOW         DL_LAZY
+#    else
+#      ifdef RTLD_NOW
+#        define LT_DLLAZY_OR_NOW       RTLD_NOW
+#      else
+#        ifdef DL_NOW
+#          define LT_DLLAZY_OR_NOW     DL_NOW
+#        else
+#          define LT_DLLAZY_OR_NOW     0
+#        endif
+#      endif
+#    endif
+#  endif
+#endif
+
+/* When -fvisbility=hidden is used, assume the code has been annotated
+   correspondingly for the symbols needed.  */
+#if defined(__GNUC__) && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3))
+int fnord () __attribute__((visibility("default")));
+#endif
+
+int fnord () { return 42; }
+int main ()
+{
+  void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+  int status = $lt_dlunknown;
+
+  if (self)
+    {
+      if (dlsym (self,"fnord"))       status = $lt_dlno_uscore;
+      else
+        {
+         if (dlsym( self,"_fnord"))  status = $lt_dlneed_uscore;
+          else puts (dlerror ());
+       }
+      /* dlclose (self); */
+    }
+  else
+    puts (dlerror ());
+
+  return status;
+}
+_LT_EOF
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5
+  (eval $ac_link) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && test -s conftest${ac_exeext} 2>/dev/null; then
+    (./conftest; exit; ) >&5 2>/dev/null
+    lt_status=$?
+    case x$lt_status in
+      x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;;
+      x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;;
+      x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;;
+    esac
+  else :
+    # compilation failed
+    lt_cv_dlopen_self=no
+  fi
+fi
+rm -fr conftest*
+
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self" >&5
+$as_echo "$lt_cv_dlopen_self" >&6; }
+
+    if test "x$lt_cv_dlopen_self" = xyes; then
+      wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a statically linked program can dlopen itself" >&5
+$as_echo_n "checking whether a statically linked program can dlopen itself... " >&6; }
+if ${lt_cv_dlopen_self_static+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+         if test "$cross_compiling" = yes; then :
+  lt_cv_dlopen_self_static=cross
+else
+  lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+  lt_status=$lt_dlunknown
+  cat > conftest.$ac_ext <<_LT_EOF
+#line $LINENO "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+#  define LT_DLGLOBAL          RTLD_GLOBAL
+#else
+#  ifdef DL_GLOBAL
+#    define LT_DLGLOBAL                DL_GLOBAL
+#  else
+#    define LT_DLGLOBAL                0
+#  endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+   find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+#  ifdef RTLD_LAZY
+#    define LT_DLLAZY_OR_NOW           RTLD_LAZY
+#  else
+#    ifdef DL_LAZY
+#      define LT_DLLAZY_OR_NOW         DL_LAZY
+#    else
+#      ifdef RTLD_NOW
+#        define LT_DLLAZY_OR_NOW       RTLD_NOW
+#      else
+#        ifdef DL_NOW
+#          define LT_DLLAZY_OR_NOW     DL_NOW
+#        else
+#          define LT_DLLAZY_OR_NOW     0
+#        endif
+#      endif
+#    endif
+#  endif
+#endif
+
+/* When -fvisbility=hidden is used, assume the code has been annotated
+   correspondingly for the symbols needed.  */
+#if defined(__GNUC__) && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3))
+int fnord () __attribute__((visibility("default")));
+#endif
+
+int fnord () { return 42; }
+int main ()
+{
+  void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+  int status = $lt_dlunknown;
+
+  if (self)
+    {
+      if (dlsym (self,"fnord"))       status = $lt_dlno_uscore;
+      else
+        {
+         if (dlsym( self,"_fnord"))  status = $lt_dlneed_uscore;
+          else puts (dlerror ());
+       }
+      /* dlclose (self); */
+    }
+  else
+    puts (dlerror ());
+
+  return status;
+}
+_LT_EOF
+  if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5
+  (eval $ac_link) 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && test -s conftest${ac_exeext} 2>/dev/null; then
+    (./conftest; exit; ) >&5 2>/dev/null
+    lt_status=$?
+    case x$lt_status in
+      x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;;
+      x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;;
+      x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;;
+    esac
+  else :
+    # compilation failed
+    lt_cv_dlopen_self_static=no
+  fi
+fi
+rm -fr conftest*
+
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self_static" >&5
+$as_echo "$lt_cv_dlopen_self_static" >&6; }
+    fi
+
+    CPPFLAGS="$save_CPPFLAGS"
+    LDFLAGS="$save_LDFLAGS"
+    LIBS="$save_LIBS"
+    ;;
+  esac
+
+  case $lt_cv_dlopen_self in
+  yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;;
+  *) enable_dlopen_self=unknown ;;
+  esac
+
+  case $lt_cv_dlopen_self_static in
+  yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;;
+  *) enable_dlopen_self_static=unknown ;;
+  esac
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+striplib=
+old_striplib=
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether stripping libraries is possible" >&5
+$as_echo_n "checking whether stripping libraries is possible... " >&6; }
+if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then
+  test -z "$old_striplib" && old_striplib="$STRIP --strip-debug"
+  test -z "$striplib" && striplib="$STRIP --strip-unneeded"
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+# FIXME - insert some real tests, host_os isn't really good enough
+  case $host_os in
+  darwin*)
+    if test -n "$STRIP" ; then
+      striplib="$STRIP -x"
+      old_striplib="$STRIP -S"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+    else
+      { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+    fi
+    ;;
+  *)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+    ;;
+  esac
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+  # Report which library types will actually be built
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5
+$as_echo_n "checking if libtool supports shared libraries... " >&6; }
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5
+$as_echo "$can_build_shared" >&6; }
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5
+$as_echo_n "checking whether to build shared libraries... " >&6; }
+  test "$can_build_shared" = "no" && enable_shared=no
+
+  # On AIX, shared libraries and static libraries use the same namespace, and
+  # are all built from PIC.
+  case $host_os in
+  aix3*)
+    test "$enable_shared" = yes && enable_static=no
+    if test -n "$RANLIB"; then
+      archive_cmds="$archive_cmds~\$RANLIB \$lib"
+      postinstall_cmds='$RANLIB $lib'
+    fi
+    ;;
+
+  aix[4-9]*)
+    if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+      test "$enable_shared" = yes && enable_static=no
+    fi
+    ;;
+  esac
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5
+$as_echo "$enable_shared" >&6; }
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5
+$as_echo_n "checking whether to build static libraries... " >&6; }
+  # Make sure either enable_shared or enable_static is yes.
+  test "$enable_shared" = yes || enable_static=yes
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5
+$as_echo "$enable_static" >&6; }
+
+
+
+
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+CC="$lt_save_CC"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+        ac_config_commands="$ac_config_commands libtool"
+
+
+
+
+# Only expand once:
+
+
+
+am__api_version='1.11'
+
+# Find a good install program.  We prefer a C program (faster),
+# so one script is as good as another.  But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AmigaOS /C/install, which installs bootblocks on floppy discs
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# OS/2's system install, which has a completely different semantic
+# ./install, which can be erroneously created by make from ./install.sh.
+# Reject install programs that cannot install multiple files.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5
+$as_echo_n "checking for a BSD-compatible install... " >&6; }
+if test -z "$INSTALL"; then
+if ${ac_cv_path_install+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    # Account for people who put trailing slashes in PATH elements.
+case $as_dir/ in #((
+  ./ | .// | /[cC]/* | \
+  /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \
+  ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \
+  /usr/ucb/* ) ;;
+  *)
+    # OSF1 and SCO ODT 3.0 have their own names for install.
+    # Don't use installbsd from OSF since it installs stuff as root
+    # by default.
+    for ac_prog in ginstall scoinst install; do
+      for ac_exec_ext in '' $ac_executable_extensions; do
+       if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then
+         if test $ac_prog = install &&
+           grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+           # AIX install.  It has an incompatible calling convention.
+           :
+         elif test $ac_prog = install &&
+           grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+           # program-specific install script used by HP pwplus--don't use.
+           :
+         else
+           rm -rf conftest.one conftest.two conftest.dir
+           echo one > conftest.one
+           echo two > conftest.two
+           mkdir conftest.dir
+           if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" &&
+             test -s conftest.one && test -s conftest.two &&
+             test -s conftest.dir/conftest.one &&
+             test -s conftest.dir/conftest.two
+           then
+             ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c"
+             break 3
+           fi
+         fi
+       fi
+      done
+    done
+    ;;
+esac
+
+  done
+IFS=$as_save_IFS
+
+rm -rf conftest.one conftest.two conftest.dir
+
+fi
+  if test "${ac_cv_path_install+set}" = set; then
+    INSTALL=$ac_cv_path_install
+  else
+    # As a last resort, use the slow shell script.  Don't cache a
+    # value for INSTALL within a source directory, because that will
+    # break other packages using the cache if that directory is
+    # removed, or if the value is a relative name.
+    INSTALL=$ac_install_sh
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5
+$as_echo "$INSTALL" >&6; }
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5
+$as_echo_n "checking whether build environment is sane... " >&6; }
+# Just in case
+sleep 1
+echo timestamp > conftest.file
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name.  Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+  *[\\\"\#\$\&\'\`$am_lf]*)
+    as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;;
+esac
+case $srcdir in
+  *[\\\"\#\$\&\'\`$am_lf\ \    ]*)
+    as_fn_error $? "unsafe srcdir value: \`$srcdir'" "$LINENO" 5;;
+esac
+
+# Do `set' in a subshell so we don't clobber the current shell's
+# arguments.  Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+   set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+   if test "$*" = "X"; then
+      # -L didn't work.
+      set X `ls -t "$srcdir/configure" conftest.file`
+   fi
+   rm -f conftest.file
+   if test "$*" != "X $srcdir/configure conftest.file" \
+      && test "$*" != "X conftest.file $srcdir/configure"; then
+
+      # If neither matched, then we have a broken ls.  This can happen
+      # if, for instance, CONFIG_SHELL is bash and it inherits a
+      # broken ls alias from the environment.  This has actually
+      # happened.  Such a system could not be considered "sane".
+      as_fn_error $? "ls -t appears to fail.  Make sure there is not a broken
+alias in your environment" "$LINENO" 5
+   fi
+
+   test "$2" = conftest.file
+   )
+then
+   # Ok.
+   :
+else
+   as_fn_error $? "newly created file is older than distributed files!
+Check your system clock" "$LINENO" 5
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+test "$program_prefix" != NONE &&
+  program_transform_name="s&^&$program_prefix&;$program_transform_name"
+# Use a double $ so make ignores it.
+test "$program_suffix" != NONE &&
+  program_transform_name="s&\$&$program_suffix&;$program_transform_name"
+# Double any \ or $.
+# By default was `s,x,x', remove it if useless.
+ac_script='s/[\\$]/&&/g;s/;s,x,x,$//'
+program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"`
+
+# expand $ac_aux_dir to an absolute path
+am_aux_dir=`cd $ac_aux_dir && pwd`
+
+if test x"${MISSING+set}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\    *)
+    MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
+  *)
+    MISSING="\${SHELL} $am_aux_dir/missing" ;;
+  esac
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --run true"; then
+  am_missing_run="$MISSING --run "
+else
+  am_missing_run=
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \`missing' script is too old or missing" >&5
+$as_echo "$as_me: WARNING: \`missing' script is too old or missing" >&2;}
+fi
+
+if test x"${install_sh}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\    *)
+    install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+  *)
+    install_sh="\${SHELL} $am_aux_dir/install-sh"
+  esac
+fi
+
+# Installed binaries are usually stripped using `strip' when the user
+# run `make install-strip'.  However `strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the `STRIP' environment variable to overrule this program.
+if test "$cross_compiling" != no; then
+  if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args.
+set dummy ${ac_tool_prefix}strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_STRIP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$STRIP"; then
+  ac_cv_prog_STRIP="$STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_STRIP="${ac_tool_prefix}strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+STRIP=$ac_cv_prog_STRIP
+if test -n "$STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5
+$as_echo "$STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_STRIP"; then
+  ac_ct_STRIP=$STRIP
+  # Extract the first word of "strip", so it can be a program name with args.
+set dummy strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_STRIP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_STRIP"; then
+  ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_STRIP="strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP
+if test -n "$ac_ct_STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5
+$as_echo "$ac_ct_STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_STRIP" = x; then
+    STRIP=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    STRIP=$ac_ct_STRIP
+  fi
+else
+  STRIP="$ac_cv_prog_STRIP"
+fi
+
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5
+$as_echo_n "checking for a thread-safe mkdir -p... " >&6; }
+if test -z "$MKDIR_P"; then
+  if ${ac_cv_path_mkdir+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in mkdir gmkdir; do
+        for ac_exec_ext in '' $ac_executable_extensions; do
+          { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; } || continue
+          case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #(
+            'mkdir (GNU coreutils) '* | \
+            'mkdir (coreutils) '* | \
+            'mkdir (fileutils) '4.1*)
+              ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext
+              break 3;;
+          esac
+        done
+       done
+  done
+IFS=$as_save_IFS
+
+fi
+
+  test -d ./--version && rmdir ./--version
+  if test "${ac_cv_path_mkdir+set}" = set; then
+    MKDIR_P="$ac_cv_path_mkdir -p"
+  else
+    # As a last resort, use the slow shell script.  Don't cache a
+    # value for MKDIR_P within a source directory, because that will
+    # break other packages using the cache if that directory is
+    # removed, or if the value is a relative name.
+    MKDIR_P="$ac_install_sh -d"
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5
+$as_echo "$MKDIR_P" >&6; }
+
+mkdir_p="$MKDIR_P"
+case $mkdir_p in
+  [\\/$]* | ?:[\\/]*) ;;
+  */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;;
+esac
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5
+$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
+set x ${MAKE-make}
+ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
+if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat >conftest.make <<\_ACEOF
+SHELL = /bin/sh
+all:
+       @echo '@@@%%%=$(MAKE)=@@@%%%'
+_ACEOF
+# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
+case `${MAKE-make} -f conftest.make 2>/dev/null` in
+  *@@@%%%=?*=@@@%%%*)
+    eval ac_cv_prog_make_${ac_make}_set=yes;;
+  *)
+    eval ac_cv_prog_make_${ac_make}_set=no;;
+esac
+rm -f conftest.make
+fi
+if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+  SET_MAKE=
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+  SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+  am__leading_dot=.
+else
+  am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+
+DEPDIR="${am__leading_dot}deps"
+
+ac_config_commands="$ac_config_commands depfiles"
+
+
+am_make=${MAKE-make}
+cat > confinc << 'END'
+am__doit:
+       @echo this is the am__doit target
+.PHONY: am__doit
+END
+# If we don't find an include directive, just comment out the code.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for style of include used by $am_make" >&5
+$as_echo_n "checking for style of include used by $am_make... " >&6; }
+am__include="#"
+am__quote=
+_am_result=none
+# First try GNU make style include.
+echo "include confinc" > confmf
+# Ignore all kinds of additional output from `make'.
+case `$am_make -s -f confmf 2> /dev/null` in #(
+*the\ am__doit\ target*)
+  am__include=include
+  am__quote=
+  _am_result=GNU
+  ;;
+esac
+# Now try BSD make style include.
+if test "$am__include" = "#"; then
+   echo '.include "confinc"' > confmf
+   case `$am_make -s -f confmf 2> /dev/null` in #(
+   *the\ am__doit\ target*)
+     am__include=.include
+     am__quote="\""
+     _am_result=BSD
+     ;;
+   esac
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $_am_result" >&5
+$as_echo "$_am_result" >&6; }
+rm -f confinc confmf
+
+# Check whether --enable-dependency-tracking was given.
+if test "${enable_dependency_tracking+set}" = set; then :
+  enableval=$enable_dependency_tracking;
+fi
+
+if test "x$enable_dependency_tracking" != xno; then
+  am_depcomp="$ac_aux_dir/depcomp"
+  AMDEPBACKSLASH='\'
+  am__nodep='_no'
+fi
+ if test "x$enable_dependency_tracking" != xno; then
+  AMDEP_TRUE=
+  AMDEP_FALSE='#'
+else
+  AMDEP_TRUE='#'
+  AMDEP_FALSE=
+fi
+
+
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+  # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+  # is not polluted with repeated "-I."
+  am__isrc=' -I$(srcdir)'
+  # test to see if srcdir already configured
+  if test -f $srcdir/config.status; then
+    as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5
+  fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+  if (cygpath --version) >/dev/null 2>/dev/null; then
+    CYGPATH_W='cygpath -w'
+  else
+    CYGPATH_W=echo
+  fi
+fi
+
+
+# Define the identity of the package.
+ PACKAGE='leptonica'
+ VERSION='1.73'
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE "$PACKAGE"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define VERSION "$VERSION"
+_ACEOF
+
+# Some tools Automake needs.
+
+ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"}
+
+
+AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"}
+
+
+AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"}
+
+
+AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"}
+
+
+MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"}
+
+# We need awk for the "check" target.  The system "awk" is bad on
+# some platforms.
+# Always define AMTAR for backward compatibility.  Yes, it's still used
+# in the wild :-(  We should find a proper way to deprecate it ...
+AMTAR='$${TAR-tar}'
+
+am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'
+
+
+
+
+depcc="$CC"   am_compiler_list=
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5
+$as_echo_n "checking dependency style of $depcc... " >&6; }
+if ${am_cv_CC_dependencies_compiler_type+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+  # We make a subdir and do the tests there.  Otherwise we can end up
+  # making bogus files that we don't know about and never remove.  For
+  # instance it was reported that on HP-UX the gcc test will end up
+  # making a dummy file named `D' -- because `-MD' means `put the output
+  # in D'.
+  rm -rf conftest.dir
+  mkdir conftest.dir
+  # Copy depcomp to subdir because otherwise we won't find it if we're
+  # using a relative directory.
+  cp "$am_depcomp" conftest.dir
+  cd conftest.dir
+  # We will build objects and dependencies in a subdirectory because
+  # it helps to detect inapplicable dependency modes.  For instance
+  # both Tru64's cc and ICC support -MD to output dependencies as a
+  # side effect of compilation, but ICC will put the dependencies in
+  # the current directory while Tru64 will put them in the object
+  # directory.
+  mkdir sub
+
+  am_cv_CC_dependencies_compiler_type=none
+  if test "$am_compiler_list" = ""; then
+     am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp`
+  fi
+  am__universal=false
+  case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac
+
+  for depmode in $am_compiler_list; do
+    # Setup a source with many dependencies, because some compilers
+    # like to wrap large dependency lists on column 80 (with \), and
+    # we should not choose a depcomp mode which is confused by this.
+    #
+    # We need to recreate these files for each test, as the compiler may
+    # overwrite some of them when testing with obscure command lines.
+    # This happens at least with the AIX C compiler.
+    : > sub/conftest.c
+    for i in 1 2 3 4 5 6; do
+      echo '#include "conftst'$i'.h"' >> sub/conftest.c
+      # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with
+      # Solaris 8's {/usr,}/bin/sh.
+      touch sub/conftst$i.h
+    done
+    echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+    # We check with `-c' and `-o' for the sake of the "dashmstdout"
+    # mode.  It turns out that the SunPro C++ compiler does not properly
+    # handle `-M -o', and we need to detect this.  Also, some Intel
+    # versions had trouble with output in subdirs
+    am__obj=sub/conftest.${OBJEXT-o}
+    am__minus_obj="-o $am__obj"
+    case $depmode in
+    gcc)
+      # This depmode causes a compiler race in universal mode.
+      test "$am__universal" = false || continue
+      ;;
+    nosideeffect)
+      # after this tag, mechanisms are not by side-effect, so they'll
+      # only be used when explicitly requested
+      if test "x$enable_dependency_tracking" = xyes; then
+       continue
+      else
+       break
+      fi
+      ;;
+    msvc7 | msvc7msys | msvisualcpp | msvcmsys)
+      # This compiler won't grok `-c -o', but also, the minuso test has
+      # not run yet.  These depmodes are late enough in the game, and
+      # so weak that their functioning should not be impacted.
+      am__obj=conftest.${OBJEXT-o}
+      am__minus_obj=
+      ;;
+    none) break ;;
+    esac
+    if depmode=$depmode \
+       source=sub/conftest.c object=$am__obj \
+       depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+       $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+         >/dev/null 2>conftest.err &&
+       grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+       ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+      # icc doesn't choke on unknown options, it will just issue warnings
+      # or remarks (even with -Werror).  So we grep stderr for any message
+      # that says an option was ignored or not supported.
+      # When given -MP, icc 7.0 and 7.1 complain thusly:
+      #   icc: Command line warning: ignoring option '-M'; no argument required
+      # The diagnosis changed in icc 8.0:
+      #   icc: Command line remark: option '-MP' not supported
+      if (grep 'ignoring option' conftest.err ||
+          grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+        am_cv_CC_dependencies_compiler_type=$depmode
+        break
+      fi
+    fi
+  done
+
+  cd ..
+  rm -rf conftest.dir
+else
+  am_cv_CC_dependencies_compiler_type=none
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5
+$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; }
+CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type
+
+ if
+  test "x$enable_dependency_tracking" != xno \
+  && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then
+  am__fastdepCC_TRUE=
+  am__fastdepCC_FALSE='#'
+else
+  am__fastdepCC_TRUE='#'
+  am__fastdepCC_FALSE=
+fi
+
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+# Checks for programs.
+for ac_prog in gawk mawk nawk awk
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_AWK+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$AWK"; then
+  ac_cv_prog_AWK="$AWK" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_AWK="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+AWK=$ac_cv_prog_AWK
+if test -n "$AWK"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5
+$as_echo "$AWK" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$AWK" && break
+done
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="${ac_tool_prefix}gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+  ac_ct_CC=$CC
+  # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_CC="gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+else
+  CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+          if test -n "$ac_tool_prefix"; then
+    # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="${ac_tool_prefix}cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  fi
+fi
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+       ac_prog_rejected=yes
+       continue
+     fi
+    ac_cv_prog_CC="cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# != 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+  fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+  if test -n "$ac_tool_prefix"; then
+  for ac_prog in cl.exe
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$CC" && break
+  done
+fi
+if test -z "$CC"; then
+  ac_ct_CC=$CC
+  for ac_prog in cl.exe
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+    ac_cv_prog_ac_ct_CC="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_CC" && break
+done
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+fi
+
+fi
+
+
+test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "no acceptable C compiler found in \$PATH
+See \`config.log' for more details" "$LINENO" 5; }
+
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+  { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    sed '10a\
+... rest of stderr output deleted ...
+         10q' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+  fi
+  rm -f conftest.er1 conftest.err
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+done
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+if ${ac_cv_c_compiler_gnu+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_compiler_gnu=yes
+else
+  ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+  GCC=yes
+else
+  GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+$as_echo_n "checking whether $CC accepts -g... " >&6; }
+if ${ac_cv_prog_cc_g+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_c_werror_flag=$ac_c_werror_flag
+   ac_c_werror_flag=yes
+   ac_cv_prog_cc_g=no
+   CFLAGS="-g"
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+else
+  CFLAGS=""
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+  ac_c_werror_flag=$ac_save_c_werror_flag
+        CFLAGS="-g"
+        cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+   ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+$as_echo "$ac_cv_prog_cc_g" >&6; }
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+if ${ac_cv_prog_cc_c89+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+     char **p;
+     int i;
+{
+  return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+  char *s;
+  va_list v;
+  va_start (v,p);
+  s = g (p, va_arg (v,int));
+  va_end (v);
+  return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default.  It has
+   function prototypes and stuff, but not '\xHH' hex character constants.
+   These don't provoke an error unfortunately, instead are silently treated
+   as 'x'.  The following induces an error, until -std is added to get
+   proper ANSI mode.  Curiously '\x00'!='x' always comes out true, for an
+   array size at least.  It's necessary to write '\x00'==0 to get something
+   that's true only with -std.  */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+   inside strings and character constants.  */
+#define FOO(x) 'x'
+int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0]  ||  f (e, argv, 1) != argv[1];
+  ;
+  return 0;
+}
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
+       -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+  CC="$ac_save_CC $ac_arg"
+  if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_c89=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext
+  test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c89" in
+  x)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+  xno)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+  *)
+    CC="$CC $ac_cv_prog_cc_c89"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c89" != xno; then :
+
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+$as_echo_n "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+  CPP=
+fi
+if test -z "$CPP"; then
+  if ${ac_cv_prog_CPP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+      # Double quotes because CPP needs to be expanded
+    for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+    do
+      ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+                    Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+  break
+fi
+
+    done
+    ac_cv_prog_CPP=$CPP
+
+fi
+  CPP=$ac_cv_prog_CPP
+else
+  ac_cv_prog_CPP=$CPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+$as_echo "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+                    Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5
+$as_echo_n "checking whether ln -s works... " >&6; }
+LN_S=$as_ln_s
+if test "$LN_S" = "ln -s"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5
+$as_echo "no, using $LN_S" >&6; }
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5
+$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
+set x ${MAKE-make}
+ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
+if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat >conftest.make <<\_ACEOF
+SHELL = /bin/sh
+all:
+       @echo '@@@%%%=$(MAKE)=@@@%%%'
+_ACEOF
+# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
+case `${MAKE-make} -f conftest.make 2>/dev/null` in
+  *@@@%%%=?*=@@@%%%*)
+    eval ac_cv_prog_make_${ac_make}_set=yes;;
+  *)
+    eval ac_cv_prog_make_${ac_make}_set=no;;
+esac
+rm -f conftest.make
+fi
+if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+  SET_MAKE=
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+  SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+
+# Checks for arguments.
+
+# Check whether --with-zlib was given.
+if test "${with_zlib+set}" = set; then :
+  withval=$with_zlib;
+fi
+
+
+# Check whether --with-libpng was given.
+if test "${with_libpng+set}" = set; then :
+  withval=$with_libpng;
+fi
+
+
+# Check whether --with-jpeg was given.
+if test "${with_jpeg+set}" = set; then :
+  withval=$with_jpeg;
+fi
+
+
+# Check whether --with-giflib was given.
+if test "${with_giflib+set}" = set; then :
+  withval=$with_giflib;
+fi
+
+
+# Check whether --with-libtiff was given.
+if test "${with_libtiff+set}" = set; then :
+  withval=$with_libtiff;
+fi
+
+
+# Check whether --with-libwebp was given.
+if test "${with_libwebp+set}" = set; then :
+  withval=$with_libwebp;
+fi
+
+
+# Check whether --with-libopenjpeg was given.
+if test "${with_libopenjpeg+set}" = set; then :
+  withval=$with_libopenjpeg;
+fi
+
+
+# Check whether --enable-programs was given.
+if test "${enable_programs+set}" = set; then :
+  enableval=$enable_programs;
+fi
+
+ if test "x$enable_programs" != xno; then
+  ENABLE_PROGRAMS_TRUE=
+  ENABLE_PROGRAMS_FALSE='#'
+else
+  ENABLE_PROGRAMS_TRUE='#'
+  ENABLE_PROGRAMS_FALSE=
+fi
+
+
+# Checks for libraries.
+LIBM=
+case $host in
+*-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-pw32* | *-*-darwin*)
+  # These system don't have libm, or don't need it
+  ;;
+*-ncr-sysv4.3*)
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for _mwvalidcheckl in -lmw" >&5
+$as_echo_n "checking for _mwvalidcheckl in -lmw... " >&6; }
+if ${ac_cv_lib_mw__mwvalidcheckl+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lmw  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char _mwvalidcheckl ();
+int
+main ()
+{
+return _mwvalidcheckl ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_mw__mwvalidcheckl=yes
+else
+  ac_cv_lib_mw__mwvalidcheckl=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_mw__mwvalidcheckl" >&5
+$as_echo "$ac_cv_lib_mw__mwvalidcheckl" >&6; }
+if test "x$ac_cv_lib_mw__mwvalidcheckl" = xyes; then :
+  LIBM="-lmw"
+fi
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for cos in -lm" >&5
+$as_echo_n "checking for cos in -lm... " >&6; }
+if ${ac_cv_lib_m_cos+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lm  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char cos ();
+int
+main ()
+{
+return cos ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_m_cos=yes
+else
+  ac_cv_lib_m_cos=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_m_cos" >&5
+$as_echo "$ac_cv_lib_m_cos" >&6; }
+if test "x$ac_cv_lib_m_cos" = xyes; then :
+  LIBM="$LIBM -lm"
+fi
+
+  ;;
+*)
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for cos in -lm" >&5
+$as_echo_n "checking for cos in -lm... " >&6; }
+if ${ac_cv_lib_m_cos+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lm  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char cos ();
+int
+main ()
+{
+return cos ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_m_cos=yes
+else
+  ac_cv_lib_m_cos=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_m_cos" >&5
+$as_echo "$ac_cv_lib_m_cos" >&6; }
+if test "x$ac_cv_lib_m_cos" = xyes; then :
+  LIBM="-lm"
+fi
+
+  ;;
+esac
+
+
+
+if test "x$with_zlib" != xno; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for deflate in -lz" >&5
+$as_echo_n "checking for deflate in -lz... " >&6; }
+if ${ac_cv_lib_z_deflate+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lz  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char deflate ();
+int
+main ()
+{
+return deflate ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_z_deflate=yes
+else
+  ac_cv_lib_z_deflate=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_z_deflate" >&5
+$as_echo "$ac_cv_lib_z_deflate" >&6; }
+if test "x$ac_cv_lib_z_deflate" = xyes; then :
+
+$as_echo "#define HAVE_LIBZ 1" >>confdefs.h
+ ZLIB_LIBS=-lz
+
+else
+  if test "x$with_zlib" = xyes; then :
+  as_fn_error $? "zlib support requested but library not found" "$LINENO" 5
+fi
+
+fi
+
+
+fi
+
+if test "x$with_libpng" != xno; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for png_read_png in -lpng" >&5
+$as_echo_n "checking for png_read_png in -lpng... " >&6; }
+if ${ac_cv_lib_png_png_read_png+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpng ${LIBM} ${ZLIB_LIBS}
+   $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char png_read_png ();
+int
+main ()
+{
+return png_read_png ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_png_png_read_png=yes
+else
+  ac_cv_lib_png_png_read_png=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_png_png_read_png" >&5
+$as_echo "$ac_cv_lib_png_png_read_png" >&6; }
+if test "x$ac_cv_lib_png_png_read_png" = xyes; then :
+
+$as_echo "#define HAVE_LIBPNG 1" >>confdefs.h
+ LIBPNG_LIBS=-lpng
+
+else
+  if test "x$with_libpng" = xyes; then :
+  as_fn_error $? "libpng support requested but library not found" "$LINENO" 5
+fi
+fi
+
+
+fi
+
+if test "x$with_jpeg" != xno; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for jpeg_read_scanlines in -ljpeg" >&5
+$as_echo_n "checking for jpeg_read_scanlines in -ljpeg... " >&6; }
+if ${ac_cv_lib_jpeg_jpeg_read_scanlines+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ljpeg  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char jpeg_read_scanlines ();
+int
+main ()
+{
+return jpeg_read_scanlines ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_jpeg_jpeg_read_scanlines=yes
+else
+  ac_cv_lib_jpeg_jpeg_read_scanlines=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_jpeg_jpeg_read_scanlines" >&5
+$as_echo "$ac_cv_lib_jpeg_jpeg_read_scanlines" >&6; }
+if test "x$ac_cv_lib_jpeg_jpeg_read_scanlines" = xyes; then :
+
+$as_echo "#define HAVE_LIBJPEG 1" >>confdefs.h
+ JPEG_LIBS=-ljpeg
+
+else
+  if test "x$with_jpeg" = xyes; then :
+  as_fn_error $? "jpeg support requested but library not found" "$LINENO" 5
+fi
+
+fi
+
+
+fi
+
+if test "x$with_giflib" != xno; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for DGifOpenFileHandle in -lgif" >&5
+$as_echo_n "checking for DGifOpenFileHandle in -lgif... " >&6; }
+if ${ac_cv_lib_gif_DGifOpenFileHandle+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lgif  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char DGifOpenFileHandle ();
+int
+main ()
+{
+return DGifOpenFileHandle ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_gif_DGifOpenFileHandle=yes
+else
+  ac_cv_lib_gif_DGifOpenFileHandle=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_gif_DGifOpenFileHandle" >&5
+$as_echo "$ac_cv_lib_gif_DGifOpenFileHandle" >&6; }
+if test "x$ac_cv_lib_gif_DGifOpenFileHandle" = xyes; then :
+
+$as_echo "#define HAVE_LIBGIF 1" >>confdefs.h
+ GIFLIB_LIBS=-lgif
+
+else
+  if test "x$with_giflib" = xyes; then :
+  as_fn_error $? "giflib support requested but library not found" "$LINENO" 5
+fi
+
+fi
+
+
+fi
+
+ if test "x$ac_cv_lib_gif_DGifOpenFileHandle" = xyes; then
+  HAVE_LIBGIF_TRUE=
+  HAVE_LIBGIF_FALSE='#'
+else
+  HAVE_LIBGIF_TRUE='#'
+  HAVE_LIBGIF_FALSE=
+fi
+
+
+if test "x$with_libtiff" != xno; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for TIFFOpen in -ltiff" >&5
+$as_echo_n "checking for TIFFOpen in -ltiff... " >&6; }
+if ${ac_cv_lib_tiff_TIFFOpen+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ltiff ${LIBM} ${ZLIB_LIBS} ${JPEG_LIBS}
+   $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char TIFFOpen ();
+int
+main ()
+{
+return TIFFOpen ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_tiff_TIFFOpen=yes
+else
+  ac_cv_lib_tiff_TIFFOpen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_tiff_TIFFOpen" >&5
+$as_echo "$ac_cv_lib_tiff_TIFFOpen" >&6; }
+if test "x$ac_cv_lib_tiff_TIFFOpen" = xyes; then :
+
+$as_echo "#define HAVE_LIBTIFF 1" >>confdefs.h
+ LIBTIFF_LIBS=-ltiff
+
+else
+  if test "x$with_libtiff" = xyes; then :
+  as_fn_error $? "libtiff support requested but library not found" "$LINENO" 5
+fi
+fi
+
+
+fi
+
+if test "x$with_libwebp" != xno; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for WebPGetInfo in -lwebp" >&5
+$as_echo_n "checking for WebPGetInfo in -lwebp... " >&6; }
+if ${ac_cv_lib_webp_WebPGetInfo+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lwebp ${LIBM}
+   $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char WebPGetInfo ();
+int
+main ()
+{
+return WebPGetInfo ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_webp_WebPGetInfo=yes
+else
+  ac_cv_lib_webp_WebPGetInfo=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_webp_WebPGetInfo" >&5
+$as_echo "$ac_cv_lib_webp_WebPGetInfo" >&6; }
+if test "x$ac_cv_lib_webp_WebPGetInfo" = xyes; then :
+
+$as_echo "#define HAVE_LIBWEBP 1" >>confdefs.h
+ LIBWEBP_LIBS=-lwebp
+
+else
+  if test "x$with_libwebp" = xyes; then :
+  as_fn_error $? "libwebp support requested but library not found" "$LINENO" 5
+fi
+fi
+
+
+fi
+
+ if test "x$ac_cv_lib_webp_WebPGetInfo" = xyes; then
+  HAVE_LIBWEBP_TRUE=
+  HAVE_LIBWEBP_FALSE='#'
+else
+  HAVE_LIBWEBP_TRUE='#'
+  HAVE_LIBWEBP_FALSE=
+fi
+
+
+if test "x$with_libopenjpeg" != xno; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for opj_create_decompress in -lopenjp2" >&5
+$as_echo_n "checking for opj_create_decompress in -lopenjp2... " >&6; }
+if ${ac_cv_lib_openjp2_opj_create_decompress+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lopenjp2  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char opj_create_decompress ();
+int
+main ()
+{
+return opj_create_decompress ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_openjp2_opj_create_decompress=yes
+else
+  ac_cv_lib_openjp2_opj_create_decompress=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_openjp2_opj_create_decompress" >&5
+$as_echo "$ac_cv_lib_openjp2_opj_create_decompress" >&6; }
+if test "x$ac_cv_lib_openjp2_opj_create_decompress" = xyes; then :
+
+
+$as_echo "#define HAVE_LIBJP2K 1" >>confdefs.h
+ LIBJP2K_LIBS=-lopenjp2
+
+      for ac_header in openjpeg-2.2/openjpeg.h openjpeg-2.1/openjpeg.h openjpeg-2.0/openjpeg.h
+do :
+  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define LIBJP2K_HEADER <$ac_header>
+_ACEOF
+ break
+fi
+
+done
+
+
+else
+  if test "x$with_libopenjpeg" = xyes; then :
+  as_fn_error $? "libopenjp2 support requested but library not found" "$LINENO" 5
+fi
+
+fi
+
+
+fi
+
+ if test "x$ac_cv_lib_openjp2_opj_create_decompress" = xyes; then
+  HAVE_LIBJP2K_TRUE=
+  HAVE_LIBJP2K_FALSE='#'
+else
+  HAVE_LIBJP2K_TRUE='#'
+  HAVE_LIBJP2K_FALSE=
+fi
+
+
+case "$host_os" in
+  mingw32*)
+  GDI_LIBS=-lgdi32
+
+  CPPFLAGS="${CPPFLAGS} -D__USE_MINGW_ANSI_STDIO" ;;
+esac
+
+# Enable less verbose output when building.
+# Check whether --enable-silent-rules was given.
+if test "${enable_silent_rules+set}" = set; then :
+  enableval=$enable_silent_rules;
+fi
+
+case $enable_silent_rules in
+yes) AM_DEFAULT_VERBOSITY=0;;
+no)  AM_DEFAULT_VERBOSITY=1;;
+*)   AM_DEFAULT_VERBOSITY=0;;
+esac
+am_make=${MAKE-make}
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5
+$as_echo_n "checking whether $am_make supports nested variables... " >&6; }
+if ${am_cv_make_support_nested_variables+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if $as_echo 'TRUE=$(BAR$(V))
+BAR0=false
+BAR1=true
+V=1
+am__doit:
+       @$(TRUE)
+.PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then
+  am_cv_make_support_nested_variables=yes
+else
+  am_cv_make_support_nested_variables=no
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5
+$as_echo "$am_cv_make_support_nested_variables" >&6; }
+if test $am_cv_make_support_nested_variables = yes; then
+    AM_V='$(V)'
+  AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)'
+else
+  AM_V=$AM_DEFAULT_VERBOSITY
+  AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY
+fi
+AM_BACKSLASH='\'
+
+
+# Checks for typedefs, structures, and compiler characteristics.
+ac_fn_c_check_type "$LINENO" "size_t" "ac_cv_type_size_t" "$ac_includes_default"
+if test "x$ac_cv_type_size_t" = xyes; then :
+
+else
+
+cat >>confdefs.h <<_ACEOF
+#define size_t unsigned int
+_ACEOF
+
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5
+$as_echo_n "checking whether byte ordering is bigendian... " >&6; }
+if ${ac_cv_c_bigendian+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_c_bigendian=unknown
+    # See if we're dealing with a universal compiler.
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifndef __APPLE_CC__
+              not a universal capable compiler
+            #endif
+            typedef int dummy;
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+       # Check for potential -arch flags.  It is not universal unless
+       # there are at least two -arch flags with different values.
+       ac_arch=
+       ac_prev=
+       for ac_word in $CC $CFLAGS $CPPFLAGS $LDFLAGS; do
+        if test -n "$ac_prev"; then
+          case $ac_word in
+            i?86 | x86_64 | ppc | ppc64)
+              if test -z "$ac_arch" || test "$ac_arch" = "$ac_word"; then
+                ac_arch=$ac_word
+              else
+                ac_cv_c_bigendian=universal
+                break
+              fi
+              ;;
+          esac
+          ac_prev=
+        elif test "x$ac_word" = "x-arch"; then
+          ac_prev=arch
+        fi
+       done
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+    if test $ac_cv_c_bigendian = unknown; then
+      # See if sys/param.h defines the BYTE_ORDER macro.
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <sys/types.h>
+            #include <sys/param.h>
+
+int
+main ()
+{
+#if ! (defined BYTE_ORDER && defined BIG_ENDIAN \
+                    && defined LITTLE_ENDIAN && BYTE_ORDER && BIG_ENDIAN \
+                    && LITTLE_ENDIAN)
+             bogus endian macros
+            #endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  # It does; now see whether it defined to BIG_ENDIAN or not.
+        cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <sys/types.h>
+               #include <sys/param.h>
+
+int
+main ()
+{
+#if BYTE_ORDER != BIG_ENDIAN
+                not big endian
+               #endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_c_bigendian=yes
+else
+  ac_cv_c_bigendian=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+    fi
+    if test $ac_cv_c_bigendian = unknown; then
+      # See if <limits.h> defines _LITTLE_ENDIAN or _BIG_ENDIAN (e.g., Solaris).
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <limits.h>
+
+int
+main ()
+{
+#if ! (defined _LITTLE_ENDIAN || defined _BIG_ENDIAN)
+             bogus endian macros
+            #endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  # It does; now see whether it defined to _BIG_ENDIAN or not.
+        cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <limits.h>
+
+int
+main ()
+{
+#ifndef _BIG_ENDIAN
+                not big endian
+               #endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_c_bigendian=yes
+else
+  ac_cv_c_bigendian=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+    fi
+    if test $ac_cv_c_bigendian = unknown; then
+      # Compile a test program.
+      if test "$cross_compiling" = yes; then :
+  # Try to guess by grepping values from an object file.
+        cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+short int ascii_mm[] =
+                 { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 };
+               short int ascii_ii[] =
+                 { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 };
+               int use_ascii (int i) {
+                 return ascii_mm[i] + ascii_ii[i];
+               }
+               short int ebcdic_ii[] =
+                 { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 };
+               short int ebcdic_mm[] =
+                 { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 };
+               int use_ebcdic (int i) {
+                 return ebcdic_mm[i] + ebcdic_ii[i];
+               }
+               extern int foo;
+
+int
+main ()
+{
+return use_ascii (foo) == use_ebcdic (foo);
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  if grep BIGenDianSyS conftest.$ac_objext >/dev/null; then
+             ac_cv_c_bigendian=yes
+           fi
+           if grep LiTTleEnDian conftest.$ac_objext >/dev/null ; then
+             if test "$ac_cv_c_bigendian" = unknown; then
+               ac_cv_c_bigendian=no
+             else
+               # finding both strings is unlikely to happen, but who knows?
+               ac_cv_c_bigendian=unknown
+             fi
+           fi
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$ac_includes_default
+int
+main ()
+{
+
+            /* Are we little or big endian?  From Harbison&Steele.  */
+            union
+            {
+              long int l;
+              char c[sizeof (long int)];
+            } u;
+            u.l = 1;
+            return u.c[sizeof (long int) - 1] == 1;
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+  ac_cv_c_bigendian=no
+else
+  ac_cv_c_bigendian=yes
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+    fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_bigendian" >&5
+$as_echo "$ac_cv_c_bigendian" >&6; }
+ case $ac_cv_c_bigendian in #(
+   yes)
+     $as_echo "#define WORDS_BIGENDIAN 1" >>confdefs.h
+;; #(
+   no)
+      ;; #(
+   universal)
+
+$as_echo "#define AC_APPLE_UNIVERSAL_BUILD 1" >>confdefs.h
+
+     ;; #(
+   *)
+     as_fn_error $? "unknown endianness
+ presetting ac_cv_c_bigendian=no (or yes) will help" "$LINENO" 5 ;;
+ esac
+
+
+APPLE_UNIVERSAL_BUILD=0
+
+ENDIANNESS=L_LITTLE_ENDIAN
+
+
+case "$ac_cv_c_bigendian" in
+  yes) ENDIANNESS=L_BIG_ENDIAN
+ ;;
+  universal) APPLE_UNIVERSAL_BUILD=1
+ ;;
+esac
+
+# Add the -Wl and --as-needed flags to gcc compiler
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wl,--as-needed" >&5
+$as_echo_n "checking whether compiler supports -Wl,--as-needed... " >&6; }
+OLD_LDFLAGS="${LDFLAGS}"
+LDFLAGS="-Wl,--as-needed ${LDFLAGS}"
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+  LDFLAGS="${OLD_LDFLAGS}"; { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+
+# Checks for library functions.
+for ac_func in fmemopen
+do :
+  ac_fn_c_check_func "$LINENO" "fmemopen" "ac_cv_func_fmemopen"
+if test "x$ac_cv_func_fmemopen" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_FMEMOPEN 1
+_ACEOF
+
+fi
+done
+
+
+ac_config_files="$ac_config_files Makefile src/endianness.h src/Makefile prog/Makefile lept.pc"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems.  If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+  for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+
+  (set) 2>&1 |
+    case $as_nl`(ac_space=' '; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      # `set' does not quote correctly, so add quotes: double-quote
+      # substitution turns \\\\ into \\, and sed turns \\ into \.
+      sed -n \
+       "s/'/'\\\\''/g;
+         s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+      ;; #(
+    *)
+      # `set' quotes correctly as required by POSIX, so do not add quotes.
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+) |
+  sed '
+     /^ac_cv_env_/b end
+     t clear
+     :clear
+     s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+     t end
+     s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+     :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+  if test -w "$cache_file"; then
+    if test "x$cache_file" != "x/dev/null"; then
+      { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+$as_echo "$as_me: updating cache $cache_file" >&6;}
+      if test ! -f "$cache_file" || test -h "$cache_file"; then
+       cat confcache >"$cache_file"
+      else
+        case $cache_file in #(
+        */* | ?:*)
+         mv -f confcache "$cache_file"$$ &&
+         mv -f "$cache_file"$$ "$cache_file" ;; #(
+        *)
+         mv -f confcache "$cache_file" ;;
+       esac
+      fi
+    fi
+  else
+    { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+  fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+DEFS=-DHAVE_CONFIG_H
+
+ac_libobjs=
+ac_ltlibobjs=
+U=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+  # 1. Remove the extension, and $U if already installed.
+  ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+  ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
+  # 2. Prepend LIBOBJDIR.  When used with automake>=1.10 LIBOBJDIR
+  #    will be set to the directory where LIBOBJS objects are built.
+  as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+  as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then
+  as_fn_error $? "conditional \"AMDEP\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then
+  as_fn_error $? "conditional \"am__fastdepCC\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+ if test -n "$EXEEXT"; then
+  am__EXEEXT_TRUE=
+  am__EXEEXT_FALSE='#'
+else
+  am__EXEEXT_TRUE='#'
+  am__EXEEXT_FALSE=
+fi
+
+if test -z "${ENABLE_PROGRAMS_TRUE}" && test -z "${ENABLE_PROGRAMS_FALSE}"; then
+  as_fn_error $? "conditional \"ENABLE_PROGRAMS\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${HAVE_LIBGIF_TRUE}" && test -z "${HAVE_LIBGIF_FALSE}"; then
+  as_fn_error $? "conditional \"HAVE_LIBGIF\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${HAVE_LIBWEBP_TRUE}" && test -z "${HAVE_LIBWEBP_FALSE}"; then
+  as_fn_error $? "conditional \"HAVE_LIBWEBP\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${HAVE_LIBJP2K_TRUE}" && test -z "${HAVE_LIBJP2K_FALSE}"; then
+  as_fn_error $? "conditional \"HAVE_LIBJP2K\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+
+
+: "${CONFIG_STATUS=./config.status}"
+ac_write_fail=0
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
+as_write_fail=0
+cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+       expr "X$arg" : "X\\(.*\\)$as_nl";
+       arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""       $as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='        ';;     # ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='        ';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -p'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -p'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -p'
+  fi
+else
+  as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_dir" : 'X\(//\)[^/]' \| \
+        X"$as_dir" : 'X\(//\)$' \| \
+        X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+if test -x / >/dev/null 2>&1; then
+  as_test_x='test -x'
+else
+  if ls -dL / >/dev/null 2>&1; then
+    as_ls_L_option=L
+  else
+    as_ls_L_option=
+  fi
+  as_test_x='
+    eval sh -c '\''
+      if test -d "$1"; then
+       test -d "$1/.";
+      else
+       case $1 in #(
+       -*)set "./$1";;
+       esac;
+       case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #((
+       ???[sx]*):;;*)false;;esac;fi
+    '\'' sh
+  '
+fi
+as_executable_p=$as_test_x
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+## ----------------------------------- ##
+## Main body of $CONFIG_STATUS script. ##
+## ----------------------------------- ##
+_ASEOF
+test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# Save the log message, to keep $0 and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.
+ac_log="
+This file was extended by leptonica $as_me 1.73, which was
+generated by GNU Autoconf 2.68.  Invocation command line was
+
+  CONFIG_FILES    = $CONFIG_FILES
+  CONFIG_HEADERS  = $CONFIG_HEADERS
+  CONFIG_LINKS    = $CONFIG_LINKS
+  CONFIG_COMMANDS = $CONFIG_COMMANDS
+  $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+case $ac_config_files in *"
+"*) set x $ac_config_files; shift; ac_config_files=$*;;
+esac
+
+case $ac_config_headers in *"
+"*) set x $ac_config_headers; shift; ac_config_headers=$*;;
+esac
+
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+# Files that config.status was made for.
+config_files="$ac_config_files"
+config_headers="$ac_config_headers"
+config_commands="$ac_config_commands"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ac_cs_usage="\
+\`$as_me' instantiates files and other configuration actions
+from templates according to the current configuration.  Unless the files
+and actions are specified as TAGs, all are instantiated by default.
+
+Usage: $0 [OPTION]... [TAG]...
+
+  -h, --help       print this help, then exit
+  -V, --version    print version number and configuration settings, then exit
+      --config     print configuration, then exit
+  -q, --quiet, --silent
+                   do not print progress messages
+  -d, --debug      don't remove temporary files
+      --recheck    update $as_me by reconfiguring in the same conditions
+      --file=FILE[:TEMPLATE]
+                   instantiate the configuration file FILE
+      --header=FILE[:TEMPLATE]
+                   instantiate the configuration header FILE
+
+Configuration files:
+$config_files
+
+Configuration headers:
+$config_headers
+
+Configuration commands:
+$config_commands
+
+Report bugs to the package provider."
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ac_cs_version="\\
+leptonica config.status 1.73
+configured by $0, generated by GNU Autoconf 2.68,
+  with options \\"\$ac_cs_config\\"
+
+Copyright (C) 2010 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+INSTALL='$INSTALL'
+MKDIR_P='$MKDIR_P'
+AWK='$AWK'
+test -n "\$AWK" || AWK=awk
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# The default lists apply if the user does not specify any file.
+ac_need_defaults=:
+while test $# != 0
+do
+  case $1 in
+  --*=?*)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+    ac_shift=:
+    ;;
+  --*=)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=
+    ac_shift=:
+    ;;
+  *)
+    ac_option=$1
+    ac_optarg=$2
+    ac_shift=shift
+    ;;
+  esac
+
+  case $ac_option in
+  # Handling of the options.
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    ac_cs_recheck=: ;;
+  --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+    $as_echo "$ac_cs_version"; exit ;;
+  --config | --confi | --conf | --con | --co | --c )
+    $as_echo "$ac_cs_config"; exit ;;
+  --debug | --debu | --deb | --de | --d | -d )
+    debug=: ;;
+  --file | --fil | --fi | --f )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    '') as_fn_error $? "missing file argument" ;;
+    esac
+    as_fn_append CONFIG_FILES " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --header | --heade | --head | --hea )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    as_fn_append CONFIG_HEADERS " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --he | --h)
+    # Conflict between --help and --header
+    as_fn_error $? "ambiguous option: \`$1'
+Try \`$0 --help' for more information.";;
+  --help | --hel | -h )
+    $as_echo "$ac_cs_usage"; exit ;;
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil | --si | --s)
+    ac_cs_silent=: ;;
+
+  # This is an error.
+  -*) as_fn_error $? "unrecognized option: \`$1'
+Try \`$0 --help' for more information." ;;
+
+  *) as_fn_append ac_config_targets " $1"
+     ac_need_defaults=false ;;
+
+  esac
+  shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+  exec 6>/dev/null
+  ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+if \$ac_cs_recheck; then
+  set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+  shift
+  \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
+  CONFIG_SHELL='$SHELL'
+  export CONFIG_SHELL
+  exec "\$@"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+exec 5>>config.log
+{
+  echo
+  sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+  $as_echo "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+#
+# INIT-COMMANDS
+#
+
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+sed_quote_subst='$sed_quote_subst'
+double_quote_subst='$double_quote_subst'
+delay_variable_subst='$delay_variable_subst'
+macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`'
+macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`'
+enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`'
+enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`'
+pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`'
+enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`'
+SHELL='`$ECHO "$SHELL" | $SED "$delay_single_quote_subst"`'
+ECHO='`$ECHO "$ECHO" | $SED "$delay_single_quote_subst"`'
+PATH_SEPARATOR='`$ECHO "$PATH_SEPARATOR" | $SED "$delay_single_quote_subst"`'
+host_alias='`$ECHO "$host_alias" | $SED "$delay_single_quote_subst"`'
+host='`$ECHO "$host" | $SED "$delay_single_quote_subst"`'
+host_os='`$ECHO "$host_os" | $SED "$delay_single_quote_subst"`'
+build_alias='`$ECHO "$build_alias" | $SED "$delay_single_quote_subst"`'
+build='`$ECHO "$build" | $SED "$delay_single_quote_subst"`'
+build_os='`$ECHO "$build_os" | $SED "$delay_single_quote_subst"`'
+SED='`$ECHO "$SED" | $SED "$delay_single_quote_subst"`'
+Xsed='`$ECHO "$Xsed" | $SED "$delay_single_quote_subst"`'
+GREP='`$ECHO "$GREP" | $SED "$delay_single_quote_subst"`'
+EGREP='`$ECHO "$EGREP" | $SED "$delay_single_quote_subst"`'
+FGREP='`$ECHO "$FGREP" | $SED "$delay_single_quote_subst"`'
+LD='`$ECHO "$LD" | $SED "$delay_single_quote_subst"`'
+NM='`$ECHO "$NM" | $SED "$delay_single_quote_subst"`'
+LN_S='`$ECHO "$LN_S" | $SED "$delay_single_quote_subst"`'
+max_cmd_len='`$ECHO "$max_cmd_len" | $SED "$delay_single_quote_subst"`'
+ac_objext='`$ECHO "$ac_objext" | $SED "$delay_single_quote_subst"`'
+exeext='`$ECHO "$exeext" | $SED "$delay_single_quote_subst"`'
+lt_unset='`$ECHO "$lt_unset" | $SED "$delay_single_quote_subst"`'
+lt_SP2NL='`$ECHO "$lt_SP2NL" | $SED "$delay_single_quote_subst"`'
+lt_NL2SP='`$ECHO "$lt_NL2SP" | $SED "$delay_single_quote_subst"`'
+lt_cv_to_host_file_cmd='`$ECHO "$lt_cv_to_host_file_cmd" | $SED "$delay_single_quote_subst"`'
+lt_cv_to_tool_file_cmd='`$ECHO "$lt_cv_to_tool_file_cmd" | $SED "$delay_single_quote_subst"`'
+reload_flag='`$ECHO "$reload_flag" | $SED "$delay_single_quote_subst"`'
+reload_cmds='`$ECHO "$reload_cmds" | $SED "$delay_single_quote_subst"`'
+OBJDUMP='`$ECHO "$OBJDUMP" | $SED "$delay_single_quote_subst"`'
+deplibs_check_method='`$ECHO "$deplibs_check_method" | $SED "$delay_single_quote_subst"`'
+file_magic_cmd='`$ECHO "$file_magic_cmd" | $SED "$delay_single_quote_subst"`'
+file_magic_glob='`$ECHO "$file_magic_glob" | $SED "$delay_single_quote_subst"`'
+want_nocaseglob='`$ECHO "$want_nocaseglob" | $SED "$delay_single_quote_subst"`'
+DLLTOOL='`$ECHO "$DLLTOOL" | $SED "$delay_single_quote_subst"`'
+sharedlib_from_linklib_cmd='`$ECHO "$sharedlib_from_linklib_cmd" | $SED "$delay_single_quote_subst"`'
+AR='`$ECHO "$AR" | $SED "$delay_single_quote_subst"`'
+AR_FLAGS='`$ECHO "$AR_FLAGS" | $SED "$delay_single_quote_subst"`'
+archiver_list_spec='`$ECHO "$archiver_list_spec" | $SED "$delay_single_quote_subst"`'
+STRIP='`$ECHO "$STRIP" | $SED "$delay_single_quote_subst"`'
+RANLIB='`$ECHO "$RANLIB" | $SED "$delay_single_quote_subst"`'
+old_postinstall_cmds='`$ECHO "$old_postinstall_cmds" | $SED "$delay_single_quote_subst"`'
+old_postuninstall_cmds='`$ECHO "$old_postuninstall_cmds" | $SED "$delay_single_quote_subst"`'
+old_archive_cmds='`$ECHO "$old_archive_cmds" | $SED "$delay_single_quote_subst"`'
+lock_old_archive_extraction='`$ECHO "$lock_old_archive_extraction" | $SED "$delay_single_quote_subst"`'
+CC='`$ECHO "$CC" | $SED "$delay_single_quote_subst"`'
+CFLAGS='`$ECHO "$CFLAGS" | $SED "$delay_single_quote_subst"`'
+compiler='`$ECHO "$compiler" | $SED "$delay_single_quote_subst"`'
+GCC='`$ECHO "$GCC" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_pipe='`$ECHO "$lt_cv_sys_global_symbol_pipe" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_cdecl='`$ECHO "$lt_cv_sys_global_symbol_to_cdecl" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_c_name_address='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address" | $SED "$delay_single_quote_subst"`'
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address_lib_prefix" | $SED "$delay_single_quote_subst"`'
+nm_file_list_spec='`$ECHO "$nm_file_list_spec" | $SED "$delay_single_quote_subst"`'
+lt_sysroot='`$ECHO "$lt_sysroot" | $SED "$delay_single_quote_subst"`'
+objdir='`$ECHO "$objdir" | $SED "$delay_single_quote_subst"`'
+MAGIC_CMD='`$ECHO "$MAGIC_CMD" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_no_builtin_flag='`$ECHO "$lt_prog_compiler_no_builtin_flag" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_pic='`$ECHO "$lt_prog_compiler_pic" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_wl='`$ECHO "$lt_prog_compiler_wl" | $SED "$delay_single_quote_subst"`'
+lt_prog_compiler_static='`$ECHO "$lt_prog_compiler_static" | $SED "$delay_single_quote_subst"`'
+lt_cv_prog_compiler_c_o='`$ECHO "$lt_cv_prog_compiler_c_o" | $SED "$delay_single_quote_subst"`'
+need_locks='`$ECHO "$need_locks" | $SED "$delay_single_quote_subst"`'
+MANIFEST_TOOL='`$ECHO "$MANIFEST_TOOL" | $SED "$delay_single_quote_subst"`'
+DSYMUTIL='`$ECHO "$DSYMUTIL" | $SED "$delay_single_quote_subst"`'
+NMEDIT='`$ECHO "$NMEDIT" | $SED "$delay_single_quote_subst"`'
+LIPO='`$ECHO "$LIPO" | $SED "$delay_single_quote_subst"`'
+OTOOL='`$ECHO "$OTOOL" | $SED "$delay_single_quote_subst"`'
+OTOOL64='`$ECHO "$OTOOL64" | $SED "$delay_single_quote_subst"`'
+libext='`$ECHO "$libext" | $SED "$delay_single_quote_subst"`'
+shrext_cmds='`$ECHO "$shrext_cmds" | $SED "$delay_single_quote_subst"`'
+extract_expsyms_cmds='`$ECHO "$extract_expsyms_cmds" | $SED "$delay_single_quote_subst"`'
+archive_cmds_need_lc='`$ECHO "$archive_cmds_need_lc" | $SED "$delay_single_quote_subst"`'
+enable_shared_with_static_runtimes='`$ECHO "$enable_shared_with_static_runtimes" | $SED "$delay_single_quote_subst"`'
+export_dynamic_flag_spec='`$ECHO "$export_dynamic_flag_spec" | $SED "$delay_single_quote_subst"`'
+whole_archive_flag_spec='`$ECHO "$whole_archive_flag_spec" | $SED "$delay_single_quote_subst"`'
+compiler_needs_object='`$ECHO "$compiler_needs_object" | $SED "$delay_single_quote_subst"`'
+old_archive_from_new_cmds='`$ECHO "$old_archive_from_new_cmds" | $SED "$delay_single_quote_subst"`'
+old_archive_from_expsyms_cmds='`$ECHO "$old_archive_from_expsyms_cmds" | $SED "$delay_single_quote_subst"`'
+archive_cmds='`$ECHO "$archive_cmds" | $SED "$delay_single_quote_subst"`'
+archive_expsym_cmds='`$ECHO "$archive_expsym_cmds" | $SED "$delay_single_quote_subst"`'
+module_cmds='`$ECHO "$module_cmds" | $SED "$delay_single_quote_subst"`'
+module_expsym_cmds='`$ECHO "$module_expsym_cmds" | $SED "$delay_single_quote_subst"`'
+with_gnu_ld='`$ECHO "$with_gnu_ld" | $SED "$delay_single_quote_subst"`'
+allow_undefined_flag='`$ECHO "$allow_undefined_flag" | $SED "$delay_single_quote_subst"`'
+no_undefined_flag='`$ECHO "$no_undefined_flag" | $SED "$delay_single_quote_subst"`'
+hardcode_libdir_flag_spec='`$ECHO "$hardcode_libdir_flag_spec" | $SED "$delay_single_quote_subst"`'
+hardcode_libdir_separator='`$ECHO "$hardcode_libdir_separator" | $SED "$delay_single_quote_subst"`'
+hardcode_direct='`$ECHO "$hardcode_direct" | $SED "$delay_single_quote_subst"`'
+hardcode_direct_absolute='`$ECHO "$hardcode_direct_absolute" | $SED "$delay_single_quote_subst"`'
+hardcode_minus_L='`$ECHO "$hardcode_minus_L" | $SED "$delay_single_quote_subst"`'
+hardcode_shlibpath_var='`$ECHO "$hardcode_shlibpath_var" | $SED "$delay_single_quote_subst"`'
+hardcode_automatic='`$ECHO "$hardcode_automatic" | $SED "$delay_single_quote_subst"`'
+inherit_rpath='`$ECHO "$inherit_rpath" | $SED "$delay_single_quote_subst"`'
+link_all_deplibs='`$ECHO "$link_all_deplibs" | $SED "$delay_single_quote_subst"`'
+always_export_symbols='`$ECHO "$always_export_symbols" | $SED "$delay_single_quote_subst"`'
+export_symbols_cmds='`$ECHO "$export_symbols_cmds" | $SED "$delay_single_quote_subst"`'
+exclude_expsyms='`$ECHO "$exclude_expsyms" | $SED "$delay_single_quote_subst"`'
+include_expsyms='`$ECHO "$include_expsyms" | $SED "$delay_single_quote_subst"`'
+prelink_cmds='`$ECHO "$prelink_cmds" | $SED "$delay_single_quote_subst"`'
+postlink_cmds='`$ECHO "$postlink_cmds" | $SED "$delay_single_quote_subst"`'
+file_list_spec='`$ECHO "$file_list_spec" | $SED "$delay_single_quote_subst"`'
+variables_saved_for_relink='`$ECHO "$variables_saved_for_relink" | $SED "$delay_single_quote_subst"`'
+need_lib_prefix='`$ECHO "$need_lib_prefix" | $SED "$delay_single_quote_subst"`'
+need_version='`$ECHO "$need_version" | $SED "$delay_single_quote_subst"`'
+version_type='`$ECHO "$version_type" | $SED "$delay_single_quote_subst"`'
+runpath_var='`$ECHO "$runpath_var" | $SED "$delay_single_quote_subst"`'
+shlibpath_var='`$ECHO "$shlibpath_var" | $SED "$delay_single_quote_subst"`'
+shlibpath_overrides_runpath='`$ECHO "$shlibpath_overrides_runpath" | $SED "$delay_single_quote_subst"`'
+libname_spec='`$ECHO "$libname_spec" | $SED "$delay_single_quote_subst"`'
+library_names_spec='`$ECHO "$library_names_spec" | $SED "$delay_single_quote_subst"`'
+soname_spec='`$ECHO "$soname_spec" | $SED "$delay_single_quote_subst"`'
+install_override_mode='`$ECHO "$install_override_mode" | $SED "$delay_single_quote_subst"`'
+postinstall_cmds='`$ECHO "$postinstall_cmds" | $SED "$delay_single_quote_subst"`'
+postuninstall_cmds='`$ECHO "$postuninstall_cmds" | $SED "$delay_single_quote_subst"`'
+finish_cmds='`$ECHO "$finish_cmds" | $SED "$delay_single_quote_subst"`'
+finish_eval='`$ECHO "$finish_eval" | $SED "$delay_single_quote_subst"`'
+hardcode_into_libs='`$ECHO "$hardcode_into_libs" | $SED "$delay_single_quote_subst"`'
+sys_lib_search_path_spec='`$ECHO "$sys_lib_search_path_spec" | $SED "$delay_single_quote_subst"`'
+sys_lib_dlsearch_path_spec='`$ECHO "$sys_lib_dlsearch_path_spec" | $SED "$delay_single_quote_subst"`'
+hardcode_action='`$ECHO "$hardcode_action" | $SED "$delay_single_quote_subst"`'
+enable_dlopen='`$ECHO "$enable_dlopen" | $SED "$delay_single_quote_subst"`'
+enable_dlopen_self='`$ECHO "$enable_dlopen_self" | $SED "$delay_single_quote_subst"`'
+enable_dlopen_self_static='`$ECHO "$enable_dlopen_self_static" | $SED "$delay_single_quote_subst"`'
+old_striplib='`$ECHO "$old_striplib" | $SED "$delay_single_quote_subst"`'
+striplib='`$ECHO "$striplib" | $SED "$delay_single_quote_subst"`'
+
+LTCC='$LTCC'
+LTCFLAGS='$LTCFLAGS'
+compiler='$compiler_DEFAULT'
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+  eval 'cat <<_LTECHO_EOF
+\$1
+_LTECHO_EOF'
+}
+
+# Quote evaled strings.
+for var in SHELL \
+ECHO \
+PATH_SEPARATOR \
+SED \
+GREP \
+EGREP \
+FGREP \
+LD \
+NM \
+LN_S \
+lt_SP2NL \
+lt_NL2SP \
+reload_flag \
+OBJDUMP \
+deplibs_check_method \
+file_magic_cmd \
+file_magic_glob \
+want_nocaseglob \
+DLLTOOL \
+sharedlib_from_linklib_cmd \
+AR \
+AR_FLAGS \
+archiver_list_spec \
+STRIP \
+RANLIB \
+CC \
+CFLAGS \
+compiler \
+lt_cv_sys_global_symbol_pipe \
+lt_cv_sys_global_symbol_to_cdecl \
+lt_cv_sys_global_symbol_to_c_name_address \
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix \
+nm_file_list_spec \
+lt_prog_compiler_no_builtin_flag \
+lt_prog_compiler_pic \
+lt_prog_compiler_wl \
+lt_prog_compiler_static \
+lt_cv_prog_compiler_c_o \
+need_locks \
+MANIFEST_TOOL \
+DSYMUTIL \
+NMEDIT \
+LIPO \
+OTOOL \
+OTOOL64 \
+shrext_cmds \
+export_dynamic_flag_spec \
+whole_archive_flag_spec \
+compiler_needs_object \
+with_gnu_ld \
+allow_undefined_flag \
+no_undefined_flag \
+hardcode_libdir_flag_spec \
+hardcode_libdir_separator \
+exclude_expsyms \
+include_expsyms \
+file_list_spec \
+variables_saved_for_relink \
+libname_spec \
+library_names_spec \
+soname_spec \
+install_override_mode \
+finish_eval \
+old_striplib \
+striplib; do
+    case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+    *[\\\\\\\`\\"\\\$]*)
+      eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\""
+      ;;
+    *)
+      eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+      ;;
+    esac
+done
+
+# Double-quote double-evaled strings.
+for var in reload_cmds \
+old_postinstall_cmds \
+old_postuninstall_cmds \
+old_archive_cmds \
+extract_expsyms_cmds \
+old_archive_from_new_cmds \
+old_archive_from_expsyms_cmds \
+archive_cmds \
+archive_expsym_cmds \
+module_cmds \
+module_expsym_cmds \
+export_symbols_cmds \
+prelink_cmds \
+postlink_cmds \
+postinstall_cmds \
+postuninstall_cmds \
+finish_cmds \
+sys_lib_search_path_spec \
+sys_lib_dlsearch_path_spec; do
+    case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+    *[\\\\\\\`\\"\\\$]*)
+      eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\""
+      ;;
+    *)
+      eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+      ;;
+    esac
+done
+
+ac_aux_dir='$ac_aux_dir'
+xsi_shell='$xsi_shell'
+lt_shell_append='$lt_shell_append'
+
+# See if we are running on zsh, and set the options which allow our
+# commands through without removal of \ escapes INIT.
+if test -n "\${ZSH_VERSION+set}" ; then
+   setopt NO_GLOB_SUBST
+fi
+
+
+    PACKAGE='$PACKAGE'
+    VERSION='$VERSION'
+    TIMESTAMP='$TIMESTAMP'
+    RM='$RM'
+    ofile='$ofile'
+
+
+
+AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+  case $ac_config_target in
+    "config_auto.h") CONFIG_HEADERS="$CONFIG_HEADERS config_auto.h:config/config.h.in" ;;
+    "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;;
+    "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;;
+    "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+    "src/endianness.h") CONFIG_FILES="$CONFIG_FILES src/endianness.h" ;;
+    "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;;
+    "prog/Makefile") CONFIG_FILES="$CONFIG_FILES prog/Makefile" ;;
+    "lept.pc") CONFIG_FILES="$CONFIG_FILES lept.pc" ;;
+
+  *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+  esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used.  Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+  test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+  test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers
+  test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands
+fi
+
+# Have a temporary directory for convenience.  Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+  tmp= ac_tmp=
+  trap 'exit_status=$?
+  : "${ac_tmp:=$tmp}"
+  { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+' 0
+  trap 'as_fn_exit 1' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+  tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+  test -d "$tmp"
+}  ||
+{
+  tmp=./conf$$-$RANDOM
+  (umask 077 && mkdir "$tmp")
+} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+ac_tmp=$tmp
+
+# Set up the scripts for CONFIG_FILES section.
+# No need to generate them if there are no CONFIG_FILES.
+# This happens for instance with `./config.status config.h'.
+if test -n "$CONFIG_FILES"; then
+
+
+ac_cr=`echo X | tr X '\015'`
+# On cygwin, bash can eat \r inside `` if the user requested igncr.
+# But we know of no other shell where ac_cr would be empty at this
+# point, so we can use a bashism as a fallback.
+if test "x$ac_cr" = x; then
+  eval ac_cr=\$\'\\r\'
+fi
+ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
+if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
+  ac_cs_awk_cr='\\r'
+else
+  ac_cs_awk_cr=$ac_cr
+fi
+
+echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+_ACEOF
+
+
+{
+  echo "cat >conf$$subs.awk <<_ACEOF" &&
+  echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
+  echo "_ACEOF"
+} >conf$$subs.sh ||
+  as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+  . ./conf$$subs.sh ||
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+
+  ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
+  if test $ac_delim_n = $ac_delim_num; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+rm -f conf$$subs.sh
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+_ACEOF
+sed -n '
+h
+s/^/S["/; s/!.*/"]=/
+p
+g
+s/^[^!]*!//
+:repl
+t repl
+s/'"$ac_delim"'$//
+t delim
+:nl
+h
+s/\(.\{148\}\)..*/\1/
+t more1
+s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
+p
+n
+b repl
+:more1
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t nl
+:delim
+h
+s/\(.\{148\}\)..*/\1/
+t more2
+s/["\\]/\\&/g; s/^/"/; s/$/"/
+p
+b
+:more2
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t delim
+' <conf$$subs.awk | sed '
+/^[^""]/{
+  N
+  s/\n//
+}
+' >>$CONFIG_STATUS || ac_write_fail=1
+rm -f conf$$subs.awk
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACAWK
+cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+  for (key in S) S_is_set[key] = 1
+  FS = "\a"
+
+}
+{
+  line = $ 0
+  nfields = split(line, field, "@")
+  substed = 0
+  len = length(field[1])
+  for (i = 2; i < nfields; i++) {
+    key = field[i]
+    keylen = length(key)
+    if (S_is_set[key]) {
+      value = S[key]
+      line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
+      len += length(value) + length(field[++i])
+      substed = 1
+    } else
+      len += 1 + keylen
+  }
+
+  print line
+}
+
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
+  sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
+else
+  cat
+fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+  || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
+_ACEOF
+
+# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
+# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[         ]*VPATH[        ]*=[    ]*/{
+h
+s///
+s/^/:/
+s/[     ]*$/:/
+s/:\$(srcdir):/:/g
+s/:\${srcdir}:/:/g
+s/:@srcdir@:/:/g
+s/^:*//
+s/:*$//
+x
+s/\(=[  ]*\).*/\1/
+G
+s/\n//
+s/^[^=]*=[      ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+fi # test -n "$CONFIG_FILES"
+
+# Set up the scripts for CONFIG_HEADERS section.
+# No need to generate them if there are no CONFIG_HEADERS.
+# This happens for instance with `./config.status Makefile'.
+if test -n "$CONFIG_HEADERS"; then
+cat >"$ac_tmp/defines.awk" <<\_ACAWK ||
+BEGIN {
+_ACEOF
+
+# Transform confdefs.h into an awk script `defines.awk', embedded as
+# here-document in config.status, that substitutes the proper values into
+# config.h.in to produce config.h.
+
+# Create a delimiter string that does not exist in confdefs.h, to ease
+# handling of long lines.
+ac_delim='%!_!# '
+for ac_last_try in false false :; do
+  ac_tt=`sed -n "/$ac_delim/p" confdefs.h`
+  if test -z "$ac_tt"; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+
+# For the awk script, D is an array of macro values keyed by name,
+# likewise P contains macro parameters if any.  Preserve backslash
+# newline sequences.
+
+ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]*
+sed -n '
+s/.\{148\}/&'"$ac_delim"'/g
+t rset
+:rset
+s/^[    ]*#[    ]*define[       ][      ]*/ /
+t def
+d
+:def
+s/\\$//
+t bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[    ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3"/p
+s/^ \('"$ac_word_re"'\)[        ]*\(.*\)/D["\1"]=" \2"/p
+d
+:bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[    ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3\\\\\\n"\\/p
+t cont
+s/^ \('"$ac_word_re"'\)[        ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p
+t cont
+d
+:cont
+n
+s/.\{148\}/&'"$ac_delim"'/g
+t clear
+:clear
+s/\\$//
+t bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/"/p
+d
+:bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p
+b cont
+' <confdefs.h | sed '
+s/'"$ac_delim"'/"\\\
+"/g' >>$CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  for (key in D) D_is_set[key] = 1
+  FS = "\a"
+}
+/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ {
+  line = \$ 0
+  split(line, arg, " ")
+  if (arg[1] == "#") {
+    defundef = arg[2]
+    mac1 = arg[3]
+  } else {
+    defundef = substr(arg[1], 2)
+    mac1 = arg[2]
+  }
+  split(mac1, mac2, "(") #)
+  macro = mac2[1]
+  prefix = substr(line, 1, index(line, defundef) - 1)
+  if (D_is_set[macro]) {
+    # Preserve the white space surrounding the "#".
+    print prefix "define", macro P[macro] D[macro]
+    next
+  } else {
+    # Replace #undef with comments.  This is necessary, for example,
+    # in the case of _POSIX_SOURCE, which is predefined and required
+    # on some systems where configure will not decide to define it.
+    if (defundef == "undef") {
+      print "/*", prefix defundef, macro, "*/"
+      next
+    }
+  }
+}
+{ print }
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+  as_fn_error $? "could not setup config headers machinery" "$LINENO" 5
+fi # test -n "$CONFIG_HEADERS"
+
+
+eval set X "  :F $CONFIG_FILES  :H $CONFIG_HEADERS    :C $CONFIG_COMMANDS"
+shift
+for ac_tag
+do
+  case $ac_tag in
+  :[FHLC]) ac_mode=$ac_tag; continue;;
+  esac
+  case $ac_mode$ac_tag in
+  :[FHL]*:*);;
+  :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+  :[FH]-) ac_tag=-:-;;
+  :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+  esac
+  ac_save_IFS=$IFS
+  IFS=:
+  set x $ac_tag
+  IFS=$ac_save_IFS
+  shift
+  ac_file=$1
+  shift
+
+  case $ac_mode in
+  :L) ac_source=$1;;
+  :[FH])
+    ac_file_inputs=
+    for ac_f
+    do
+      case $ac_f in
+      -) ac_f="$ac_tmp/stdin";;
+      *) # Look for the file first in the build tree, then in the source tree
+        # (if the path is not absolute).  The absolute path cannot be DOS-style,
+        # because $ac_f cannot contain `:'.
+        test -f "$ac_f" ||
+          case $ac_f in
+          [\\/$]*) false;;
+          *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+          esac ||
+          as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+      esac
+      case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+      as_fn_append ac_file_inputs " '$ac_f'"
+    done
+
+    # Let's still pretend it is `configure' which instantiates (i.e., don't
+    # use $as_me), people would be surprised to read:
+    #    /* config.h.  Generated by config.status.  */
+    configure_input='Generated from '`
+         $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+       `' by configure.'
+    if test x"$ac_file" != x-; then
+      configure_input="$ac_file.  $configure_input"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+$as_echo "$as_me: creating $ac_file" >&6;}
+    fi
+    # Neutralize special characters interpreted by sed in replacement strings.
+    case $configure_input in #(
+    *\&* | *\|* | *\\* )
+       ac_sed_conf_input=`$as_echo "$configure_input" |
+       sed 's/[\\\\&|]/\\\\&/g'`;; #(
+    *) ac_sed_conf_input=$configure_input;;
+    esac
+
+    case $ac_tag in
+    *:-:* | *:-) cat >"$ac_tmp/stdin" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+    esac
+    ;;
+  esac
+
+  ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$ac_file" : 'X\(//\)[^/]' \| \
+        X"$ac_file" : 'X\(//\)$' \| \
+        X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$ac_file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+  as_dir="$ac_dir"; as_fn_mkdir_p
+  ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+  case $ac_mode in
+  :F)
+  #
+  # CONFIG_FILE
+  #
+
+  case $INSTALL in
+  [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+  *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;;
+  esac
+  ac_MKDIR_P=$MKDIR_P
+  case $MKDIR_P in
+  [\\/$]* | ?:[\\/]* ) ;;
+  */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;;
+  esac
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+ac_sed_dataroot='
+/datarootdir/ {
+  p
+  q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p'
+case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  ac_datarootdir_hack='
+  s&@datadir@&$datadir&g
+  s&@docdir@&$docdir&g
+  s&@infodir@&$infodir&g
+  s&@localedir@&$localedir&g
+  s&@mandir@&$mandir&g
+  s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_sed_extra="$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s|@configure_input@|$ac_sed_conf_input|;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@top_build_prefix@&$ac_top_build_prefix&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+s&@INSTALL@&$ac_INSTALL&;t t
+s&@MKDIR_P@&$ac_MKDIR_P&;t t
+$ac_datarootdir_hack
+"
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
+  >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+  { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
+  { ac_out=`sed -n '/^[         ]*datarootdir[  ]*:*=/p' \
+      "$ac_tmp/out"`; test -z "$ac_out"; } &&
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&5
+$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&2;}
+
+  rm -f "$ac_tmp/stdin"
+  case $ac_file in
+  -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
+  *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+  esac \
+  || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ ;;
+  :H)
+  #
+  # CONFIG_HEADER
+  #
+  if test x"$ac_file" != x-; then
+    {
+      $as_echo "/* $configure_input  */" \
+      && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs"
+    } >"$ac_tmp/config.h" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+    if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then
+      { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5
+$as_echo "$as_me: $ac_file is unchanged" >&6;}
+    else
+      rm -f "$ac_file"
+      mv "$ac_tmp/config.h" "$ac_file" \
+       || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+    fi
+  else
+    $as_echo "/* $configure_input  */" \
+      && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \
+      || as_fn_error $? "could not create -" "$LINENO" 5
+  fi
+# Compute "$ac_file"'s index in $config_headers.
+_am_arg="$ac_file"
+_am_stamp_count=1
+for _am_header in $config_headers :; do
+  case $_am_header in
+    $_am_arg | $_am_arg:* )
+      break ;;
+    * )
+      _am_stamp_count=`expr $_am_stamp_count + 1` ;;
+  esac
+done
+echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" ||
+$as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$_am_arg" : 'X\(//\)[^/]' \| \
+        X"$_am_arg" : 'X\(//\)$' \| \
+        X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$_am_arg" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`/stamp-h$_am_stamp_count
+ ;;
+
+  :C)  { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5
+$as_echo "$as_me: executing $ac_file commands" >&6;}
+ ;;
+  esac
+
+
+  case $ac_file$ac_mode in
+    "libtool":C)
+
+    # See if we are running on zsh, and set the options which allow our
+    # commands through without removal of \ escapes.
+    if test -n "${ZSH_VERSION+set}" ; then
+      setopt NO_GLOB_SUBST
+    fi
+
+    cfgfile="${ofile}T"
+    trap "$RM \"$cfgfile\"; exit 1" 1 2 15
+    $RM "$cfgfile"
+
+    cat <<_LT_EOF >> "$cfgfile"
+#! $SHELL
+
+# `$ECHO "$ofile" | sed 's%^.*/%%'` - Provide generalized library-building support services.
+# Generated automatically by $as_me ($PACKAGE$TIMESTAMP) $VERSION
+# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+# NOTE: Changes made to this file will be lost: look at ltmain.sh.
+#
+#   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
+#                 2006, 2007, 2008, 2009, 2010, 2011 Free Software
+#                 Foundation, Inc.
+#   Written by Gordon Matzigkeit, 1996
+#
+#   This file is part of GNU Libtool.
+#
+# GNU Libtool is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+#
+# As a special exception to the GNU General Public License,
+# if you distribute this file as part of a program or library that
+# is built using GNU Libtool, you may include this file under the
+# same distribution terms that you use for the rest of that program.
+#
+# GNU Libtool is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Libtool; see the file COPYING.  If not, a copy
+# can be downloaded from http://www.gnu.org/licenses/gpl.html, or
+# obtained by writing to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+
+# The names of the tagged configurations supported by this script.
+available_tags=""
+
+# ### BEGIN LIBTOOL CONFIG
+
+# Which release of libtool.m4 was used?
+macro_version=$macro_version
+macro_revision=$macro_revision
+
+# Whether or not to build shared libraries.
+build_libtool_libs=$enable_shared
+
+# Whether or not to build static libraries.
+build_old_libs=$enable_static
+
+# What type of objects to build.
+pic_mode=$pic_mode
+
+# Whether or not to optimize for fast installation.
+fast_install=$enable_fast_install
+
+# Shell to use when invoking shell scripts.
+SHELL=$lt_SHELL
+
+# An echo program that protects backslashes.
+ECHO=$lt_ECHO
+
+# The PATH separator for the build system.
+PATH_SEPARATOR=$lt_PATH_SEPARATOR
+
+# The host system.
+host_alias=$host_alias
+host=$host
+host_os=$host_os
+
+# The build system.
+build_alias=$build_alias
+build=$build
+build_os=$build_os
+
+# A sed program that does not truncate output.
+SED=$lt_SED
+
+# Sed that helps us avoid accidentally triggering echo(1) options like -n.
+Xsed="\$SED -e 1s/^X//"
+
+# A grep program that handles long lines.
+GREP=$lt_GREP
+
+# An ERE matcher.
+EGREP=$lt_EGREP
+
+# A literal string matcher.
+FGREP=$lt_FGREP
+
+# A BSD- or MS-compatible name lister.
+NM=$lt_NM
+
+# Whether we need soft or hard links.
+LN_S=$lt_LN_S
+
+# What is the maximum length of a command?
+max_cmd_len=$max_cmd_len
+
+# Object file suffix (normally "o").
+objext=$ac_objext
+
+# Executable file suffix (normally "").
+exeext=$exeext
+
+# whether the shell understands "unset".
+lt_unset=$lt_unset
+
+# turn spaces into newlines.
+SP2NL=$lt_lt_SP2NL
+
+# turn newlines into spaces.
+NL2SP=$lt_lt_NL2SP
+
+# convert \$build file names to \$host format.
+to_host_file_cmd=$lt_cv_to_host_file_cmd
+
+# convert \$build files to toolchain format.
+to_tool_file_cmd=$lt_cv_to_tool_file_cmd
+
+# An object symbol dumper.
+OBJDUMP=$lt_OBJDUMP
+
+# Method to check whether dependent libraries are shared objects.
+deplibs_check_method=$lt_deplibs_check_method
+
+# Command to use when deplibs_check_method = "file_magic".
+file_magic_cmd=$lt_file_magic_cmd
+
+# How to find potential files when deplibs_check_method = "file_magic".
+file_magic_glob=$lt_file_magic_glob
+
+# Find potential files using nocaseglob when deplibs_check_method = "file_magic".
+want_nocaseglob=$lt_want_nocaseglob
+
+# DLL creation program.
+DLLTOOL=$lt_DLLTOOL
+
+# Command to associate shared and link libraries.
+sharedlib_from_linklib_cmd=$lt_sharedlib_from_linklib_cmd
+
+# The archiver.
+AR=$lt_AR
+
+# Flags to create an archive.
+AR_FLAGS=$lt_AR_FLAGS
+
+# How to feed a file listing to the archiver.
+archiver_list_spec=$lt_archiver_list_spec
+
+# A symbol stripping program.
+STRIP=$lt_STRIP
+
+# Commands used to install an old-style archive.
+RANLIB=$lt_RANLIB
+old_postinstall_cmds=$lt_old_postinstall_cmds
+old_postuninstall_cmds=$lt_old_postuninstall_cmds
+
+# Whether to use a lock for old archive extraction.
+lock_old_archive_extraction=$lock_old_archive_extraction
+
+# A C compiler.
+LTCC=$lt_CC
+
+# LTCC compiler flags.
+LTCFLAGS=$lt_CFLAGS
+
+# Take the output of nm and produce a listing of raw symbols and C names.
+global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe
+
+# Transform the output of nm in a proper C declaration.
+global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl
+
+# Transform the output of nm in a C name address pair.
+global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address
+
+# Transform the output of nm in a C name address pair when lib prefix is needed.
+global_symbol_to_c_name_address_lib_prefix=$lt_lt_cv_sys_global_symbol_to_c_name_address_lib_prefix
+
+# Specify filename containing input files for \$NM.
+nm_file_list_spec=$lt_nm_file_list_spec
+
+# The root where to search for dependent libraries,and in which our libraries should be installed.
+lt_sysroot=$lt_sysroot
+
+# The name of the directory that contains temporary libtool files.
+objdir=$objdir
+
+# Used to examine libraries when file_magic_cmd begins with "file".
+MAGIC_CMD=$MAGIC_CMD
+
+# Must we lock files when doing compilation?
+need_locks=$lt_need_locks
+
+# Manifest tool.
+MANIFEST_TOOL=$lt_MANIFEST_TOOL
+
+# Tool to manipulate archived DWARF debug symbol files on Mac OS X.
+DSYMUTIL=$lt_DSYMUTIL
+
+# Tool to change global to local symbols on Mac OS X.
+NMEDIT=$lt_NMEDIT
+
+# Tool to manipulate fat objects and archives on Mac OS X.
+LIPO=$lt_LIPO
+
+# ldd/readelf like tool for Mach-O binaries on Mac OS X.
+OTOOL=$lt_OTOOL
+
+# ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4.
+OTOOL64=$lt_OTOOL64
+
+# Old archive suffix (normally "a").
+libext=$libext
+
+# Shared library suffix (normally ".so").
+shrext_cmds=$lt_shrext_cmds
+
+# The commands to extract the exported symbol list from a shared archive.
+extract_expsyms_cmds=$lt_extract_expsyms_cmds
+
+# Variables whose values should be saved in libtool wrapper scripts and
+# restored at link time.
+variables_saved_for_relink=$lt_variables_saved_for_relink
+
+# Do we need the "lib" prefix for modules?
+need_lib_prefix=$need_lib_prefix
+
+# Do we need a version for libraries?
+need_version=$need_version
+
+# Library versioning type.
+version_type=$version_type
+
+# Shared library runtime path variable.
+runpath_var=$runpath_var
+
+# Shared library path variable.
+shlibpath_var=$shlibpath_var
+
+# Is shlibpath searched before the hard-coded library search path?
+shlibpath_overrides_runpath=$shlibpath_overrides_runpath
+
+# Format of library name prefix.
+libname_spec=$lt_libname_spec
+
+# List of archive names.  First name is the real one, the rest are links.
+# The last name is the one that the linker finds with -lNAME
+library_names_spec=$lt_library_names_spec
+
+# The coded name of the library, if different from the real name.
+soname_spec=$lt_soname_spec
+
+# Permission mode override for installation of shared libraries.
+install_override_mode=$lt_install_override_mode
+
+# Command to use after installation of a shared archive.
+postinstall_cmds=$lt_postinstall_cmds
+
+# Command to use after uninstallation of a shared archive.
+postuninstall_cmds=$lt_postuninstall_cmds
+
+# Commands used to finish a libtool library installation in a directory.
+finish_cmds=$lt_finish_cmds
+
+# As "finish_cmds", except a single script fragment to be evaled but
+# not shown.
+finish_eval=$lt_finish_eval
+
+# Whether we should hardcode library paths into libraries.
+hardcode_into_libs=$hardcode_into_libs
+
+# Compile-time system search path for libraries.
+sys_lib_search_path_spec=$lt_sys_lib_search_path_spec
+
+# Run-time system search path for libraries.
+sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec
+
+# Whether dlopen is supported.
+dlopen_support=$enable_dlopen
+
+# Whether dlopen of programs is supported.
+dlopen_self=$enable_dlopen_self
+
+# Whether dlopen of statically linked programs is supported.
+dlopen_self_static=$enable_dlopen_self_static
+
+# Commands to strip libraries.
+old_striplib=$lt_old_striplib
+striplib=$lt_striplib
+
+
+# The linker used to build libraries.
+LD=$lt_LD
+
+# How to create reloadable object files.
+reload_flag=$lt_reload_flag
+reload_cmds=$lt_reload_cmds
+
+# Commands used to build an old-style archive.
+old_archive_cmds=$lt_old_archive_cmds
+
+# A language specific compiler.
+CC=$lt_compiler
+
+# Is the compiler the GNU compiler?
+with_gcc=$GCC
+
+# Compiler flag to turn off builtin functions.
+no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag
+
+# Additional compiler flags for building library objects.
+pic_flag=$lt_lt_prog_compiler_pic
+
+# How to pass a linker flag through the compiler.
+wl=$lt_lt_prog_compiler_wl
+
+# Compiler flag to prevent dynamic linking.
+link_static_flag=$lt_lt_prog_compiler_static
+
+# Does compiler simultaneously support -c and -o options?
+compiler_c_o=$lt_lt_cv_prog_compiler_c_o
+
+# Whether or not to add -lc for building shared libraries.
+build_libtool_need_lc=$archive_cmds_need_lc
+
+# Whether or not to disallow shared libs when runtime libs are static.
+allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes
+
+# Compiler flag to allow reflexive dlopens.
+export_dynamic_flag_spec=$lt_export_dynamic_flag_spec
+
+# Compiler flag to generate shared objects directly from archives.
+whole_archive_flag_spec=$lt_whole_archive_flag_spec
+
+# Whether the compiler copes with passing no objects directly.
+compiler_needs_object=$lt_compiler_needs_object
+
+# Create an old-style archive from a shared archive.
+old_archive_from_new_cmds=$lt_old_archive_from_new_cmds
+
+# Create a temporary old-style archive to link instead of a shared archive.
+old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds
+
+# Commands used to build a shared archive.
+archive_cmds=$lt_archive_cmds
+archive_expsym_cmds=$lt_archive_expsym_cmds
+
+# Commands used to build a loadable module if different from building
+# a shared archive.
+module_cmds=$lt_module_cmds
+module_expsym_cmds=$lt_module_expsym_cmds
+
+# Whether we are building with GNU ld or not.
+with_gnu_ld=$lt_with_gnu_ld
+
+# Flag that allows shared libraries with undefined symbols to be built.
+allow_undefined_flag=$lt_allow_undefined_flag
+
+# Flag that enforces no undefined symbols.
+no_undefined_flag=$lt_no_undefined_flag
+
+# Flag to hardcode \$libdir into a binary during linking.
+# This must work even if \$libdir does not exist
+hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec
+
+# Whether we need a single "-rpath" flag with a separated argument.
+hardcode_libdir_separator=$lt_hardcode_libdir_separator
+
+# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes
+# DIR into the resulting binary.
+hardcode_direct=$hardcode_direct
+
+# Set to "yes" if using DIR/libNAME\${shared_ext} during linking hardcodes
+# DIR into the resulting binary and the resulting library dependency is
+# "absolute",i.e impossible to change by setting \${shlibpath_var} if the
+# library is relocated.
+hardcode_direct_absolute=$hardcode_direct_absolute
+
+# Set to "yes" if using the -LDIR flag during linking hardcodes DIR
+# into the resulting binary.
+hardcode_minus_L=$hardcode_minus_L
+
+# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR
+# into the resulting binary.
+hardcode_shlibpath_var=$hardcode_shlibpath_var
+
+# Set to "yes" if building a shared library automatically hardcodes DIR
+# into the library and all subsequent libraries and executables linked
+# against it.
+hardcode_automatic=$hardcode_automatic
+
+# Set to yes if linker adds runtime paths of dependent libraries
+# to runtime path list.
+inherit_rpath=$inherit_rpath
+
+# Whether libtool must link a program against all its dependency libraries.
+link_all_deplibs=$link_all_deplibs
+
+# Set to "yes" if exported symbols are required.
+always_export_symbols=$always_export_symbols
+
+# The commands to list exported symbols.
+export_symbols_cmds=$lt_export_symbols_cmds
+
+# Symbols that should not be listed in the preloaded symbols.
+exclude_expsyms=$lt_exclude_expsyms
+
+# Symbols that must always be exported.
+include_expsyms=$lt_include_expsyms
+
+# Commands necessary for linking programs (against libraries) with templates.
+prelink_cmds=$lt_prelink_cmds
+
+# Commands necessary for finishing linking programs.
+postlink_cmds=$lt_postlink_cmds
+
+# Specify filename containing input files.
+file_list_spec=$lt_file_list_spec
+
+# How to hardcode a shared library path into an executable.
+hardcode_action=$hardcode_action
+
+# ### END LIBTOOL CONFIG
+
+_LT_EOF
+
+  case $host_os in
+  aix3*)
+    cat <<\_LT_EOF >> "$cfgfile"
+# AIX sometimes has problems with the GCC collect2 program.  For some
+# reason, if we set the COLLECT_NAMES environment variable, the problems
+# vanish in a puff of smoke.
+if test "X${COLLECT_NAMES+set}" != Xset; then
+  COLLECT_NAMES=
+  export COLLECT_NAMES
+fi
+_LT_EOF
+    ;;
+  esac
+
+
+ltmain="$ac_aux_dir/ltmain.sh"
+
+
+  # We use sed instead of cat because bash on DJGPP gets confused if
+  # if finds mixed CR/LF and LF-only lines.  Since sed operates in
+  # text mode, it properly converts lines to CR/LF.  This bash problem
+  # is reportedly fixed, but why not run on old versions too?
+  sed '$q' "$ltmain" >> "$cfgfile" \
+     || (rm -f "$cfgfile"; exit 1)
+
+  if test x"$xsi_shell" = xyes; then
+  sed -e '/^func_dirname ()$/,/^} # func_dirname /c\
+func_dirname ()\
+{\
+\    case ${1} in\
+\      */*) func_dirname_result="${1%/*}${2}" ;;\
+\      *  ) func_dirname_result="${3}" ;;\
+\    esac\
+} # Extended-shell func_dirname implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_basename ()$/,/^} # func_basename /c\
+func_basename ()\
+{\
+\    func_basename_result="${1##*/}"\
+} # Extended-shell func_basename implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_dirname_and_basename ()$/,/^} # func_dirname_and_basename /c\
+func_dirname_and_basename ()\
+{\
+\    case ${1} in\
+\      */*) func_dirname_result="${1%/*}${2}" ;;\
+\      *  ) func_dirname_result="${3}" ;;\
+\    esac\
+\    func_basename_result="${1##*/}"\
+} # Extended-shell func_dirname_and_basename implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_stripname ()$/,/^} # func_stripname /c\
+func_stripname ()\
+{\
+\    # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are\
+\    # positional parameters, so assign one to ordinary parameter first.\
+\    func_stripname_result=${3}\
+\    func_stripname_result=${func_stripname_result#"${1}"}\
+\    func_stripname_result=${func_stripname_result%"${2}"}\
+} # Extended-shell func_stripname implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_split_long_opt ()$/,/^} # func_split_long_opt /c\
+func_split_long_opt ()\
+{\
+\    func_split_long_opt_name=${1%%=*}\
+\    func_split_long_opt_arg=${1#*=}\
+} # Extended-shell func_split_long_opt implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_split_short_opt ()$/,/^} # func_split_short_opt /c\
+func_split_short_opt ()\
+{\
+\    func_split_short_opt_arg=${1#??}\
+\    func_split_short_opt_name=${1%"$func_split_short_opt_arg"}\
+} # Extended-shell func_split_short_opt implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_lo2o ()$/,/^} # func_lo2o /c\
+func_lo2o ()\
+{\
+\    case ${1} in\
+\      *.lo) func_lo2o_result=${1%.lo}.${objext} ;;\
+\      *)    func_lo2o_result=${1} ;;\
+\    esac\
+} # Extended-shell func_lo2o implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_xform ()$/,/^} # func_xform /c\
+func_xform ()\
+{\
+    func_xform_result=${1%.*}.lo\
+} # Extended-shell func_xform implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_arith ()$/,/^} # func_arith /c\
+func_arith ()\
+{\
+    func_arith_result=$(( $* ))\
+} # Extended-shell func_arith implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_len ()$/,/^} # func_len /c\
+func_len ()\
+{\
+    func_len_result=${#1}\
+} # Extended-shell func_len implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+fi
+
+if test x"$lt_shell_append" = xyes; then
+  sed -e '/^func_append ()$/,/^} # func_append /c\
+func_append ()\
+{\
+    eval "${1}+=\\${2}"\
+} # Extended-shell func_append implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  sed -e '/^func_append_quoted ()$/,/^} # func_append_quoted /c\
+func_append_quoted ()\
+{\
+\    func_quote_for_eval "${2}"\
+\    eval "${1}+=\\\\ \\$func_quote_for_eval_result"\
+} # Extended-shell func_append_quoted implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+
+
+  # Save a `func_append' function call where possible by direct use of '+='
+  sed -e 's%func_append \([a-zA-Z_]\{1,\}\) "%\1+="%g' $cfgfile > $cfgfile.tmp \
+    && mv -f "$cfgfile.tmp" "$cfgfile" \
+      || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+  test 0 -eq $? || _lt_function_replace_fail=:
+else
+  # Save a `func_append' function call even when '+=' is not available
+  sed -e 's%func_append \([a-zA-Z_]\{1,\}\) "%\1="$\1%g' $cfgfile > $cfgfile.tmp \
+    && mv -f "$cfgfile.tmp" "$cfgfile" \
+      || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+  test 0 -eq $? || _lt_function_replace_fail=:
+fi
+
+if test x"$_lt_function_replace_fail" = x":"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Unable to substitute extended shell functions in $ofile" >&5
+$as_echo "$as_me: WARNING: Unable to substitute extended shell functions in $ofile" >&2;}
+fi
+
+
+   mv -f "$cfgfile" "$ofile" ||
+    (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile")
+  chmod +x "$ofile"
+
+ ;;
+    "depfiles":C) test x"$AMDEP_TRUE" != x"" || {
+  # Autoconf 2.62 quotes --file arguments for eval, but not when files
+  # are listed without --file.  Let's play safe and only enable the eval
+  # if we detect the quoting.
+  case $CONFIG_FILES in
+  *\'*) eval set x "$CONFIG_FILES" ;;
+  *)   set x $CONFIG_FILES ;;
+  esac
+  shift
+  for mf
+  do
+    # Strip MF so we end up with the name of the file.
+    mf=`echo "$mf" | sed -e 's/:.*$//'`
+    # Check whether this is an Automake generated Makefile or not.
+    # We used to match only the files named `Makefile.in', but
+    # some people rename them; so instead we look at the file content.
+    # Grep'ing the first line is not enough: some people post-process
+    # each Makefile.in and add a new line on top of each file to say so.
+    # Grep'ing the whole file is not good either: AIX grep has a line
+    # limit of 2048, but all sed's we know have understand at least 4000.
+    if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
+      dirpart=`$as_dirname -- "$mf" ||
+$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$mf" : 'X\(//\)[^/]' \| \
+        X"$mf" : 'X\(//\)$' \| \
+        X"$mf" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$mf" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+    else
+      continue
+    fi
+    # Extract the definition of DEPDIR, am__include, and am__quote
+    # from the Makefile without running `make'.
+    DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
+    test -z "$DEPDIR" && continue
+    am__include=`sed -n 's/^am__include = //p' < "$mf"`
+    test -z "am__include" && continue
+    am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
+    # When using ansi2knr, U may be empty or an underscore; expand it
+    U=`sed -n 's/^U = //p' < "$mf"`
+    # Find all dependency output files, they are included files with
+    # $(DEPDIR) in their names.  We invoke sed twice because it is the
+    # simplest approach to changing $(DEPDIR) to its actual value in the
+    # expansion.
+    for file in `sed -n "
+      s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
+        sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do
+      # Make sure the directory exists.
+      test -f "$dirpart/$file" && continue
+      fdir=`$as_dirname -- "$file" ||
+$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$file" : 'X\(//\)[^/]' \| \
+        X"$file" : 'X\(//\)$' \| \
+        X"$file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+      as_dir=$dirpart/$fdir; as_fn_mkdir_p
+      # echo "creating $dirpart/$file"
+      echo '# dummy' > "$dirpart/$file"
+    done
+  done
+}
+ ;;
+
+  esac
+done # for ac_tag
+
+
+as_fn_exit 0
+_ACEOF
+ac_clean_files=$ac_clean_files_save
+
+test $ac_write_fail = 0 ||
+  as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded.  So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status.  When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+  ac_cs_success=:
+  ac_config_status_args=
+  test "$silent" = yes &&
+    ac_config_status_args="$ac_config_status_args --quiet"
+  exec 5>/dev/null
+  $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+  exec 5>>config.log
+  # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+  # would make configure fail if this is the last instruction.
+  $ac_cs_success || as_fn_exit 1
+fi
+if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+fi
+
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..5c11d53
--- /dev/null
@@ -0,0 +1,135 @@
+AC_PREREQ([2.50])
+AC_INIT([leptonica], [1.73])
+AC_CONFIG_AUX_DIR([config])
+AC_CONFIG_HEADERS([config_auto.h:config/config.h.in])
+AC_CONFIG_SRCDIR([src/adaptmap.c])
+
+AC_CONFIG_MACRO_DIR([m4])
+LT_INIT
+
+AM_INIT_AUTOMAKE
+AC_LANG(C)
+AC_CANONICAL_HOST
+
+# Checks for programs.
+AC_PROG_AWK
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+
+# Checks for arguments.
+AC_ARG_WITH([zlib], AS_HELP_STRING([--without-zlib], [do not include zlib support]))
+AC_ARG_WITH([libpng], AS_HELP_STRING([--without-libpng], [do not include libpng support]))
+AC_ARG_WITH([jpeg], AS_HELP_STRING([--without-jpeg], [do not include jpeg support]))
+AC_ARG_WITH([giflib], AS_HELP_STRING([--without-giflib], [do not include giflib support]))
+AC_ARG_WITH([libtiff], AS_HELP_STRING([--without-libtiff], [do not include libtiff support]))
+AC_ARG_WITH([libwebp], AS_HELP_STRING([--without-libwebp], [do not include libwebp support]))
+AC_ARG_WITH([libopenjpeg], AS_HELP_STRING([--without-libopenjpeg], [do not include libopenjpeg support]))
+
+AC_ARG_ENABLE([programs], AS_HELP_STRING([--disable-programs], [do not build additional programs]))
+AM_CONDITIONAL([ENABLE_PROGRAMS], [test "x$enable_programs" != xno])
+
+# Checks for libraries.
+LT_LIB_M
+
+AS_IF([test "x$with_zlib" != xno],
+  AC_CHECK_LIB([z], [deflate],
+    AC_DEFINE([HAVE_LIBZ], 1, [Define to 1 if you have zlib.]) AC_SUBST([ZLIB_LIBS], [-lz]),
+    AS_IF([test "x$with_zlib" = xyes], AC_MSG_ERROR([zlib support requested but library not found]))
+  )
+)
+
+AS_IF([test "x$with_libpng" != xno],
+  AC_CHECK_LIB([png], [png_read_png],
+    AC_DEFINE([HAVE_LIBPNG], 1, [Define to 1 if you have libpng.]) AC_SUBST([LIBPNG_LIBS], [-lpng]),
+    AS_IF([test "x$with_libpng" = xyes], AC_MSG_ERROR([libpng support requested but library not found])),
+    ${LIBM} ${ZLIB_LIBS}
+  )
+)
+
+AS_IF([test "x$with_jpeg" != xno],
+  AC_CHECK_LIB([jpeg], [jpeg_read_scanlines],
+    AC_DEFINE([HAVE_LIBJPEG], 1, [Define to 1 if you have jpeg.]) AC_SUBST([JPEG_LIBS], [-ljpeg]),
+    AS_IF([test "x$with_jpeg" = xyes], AC_MSG_ERROR([jpeg support requested but library not found]))
+  )
+)
+
+AS_IF([test "x$with_giflib" != xno],
+  AC_CHECK_LIB([gif], [DGifOpenFileHandle],
+    AC_DEFINE([HAVE_LIBGIF], 1, [Define to 1 if you have giflib.]) AC_SUBST([GIFLIB_LIBS], [-lgif]),
+    AS_IF([test "x$with_giflib" = xyes], AC_MSG_ERROR([giflib support requested but library not found]))
+  )
+)
+
+AM_CONDITIONAL([HAVE_LIBGIF], [test "x$ac_cv_lib_gif_DGifOpenFileHandle" = xyes])
+
+AS_IF([test "x$with_libtiff" != xno],
+  AC_CHECK_LIB([tiff], [TIFFOpen],
+    AC_DEFINE([HAVE_LIBTIFF], 1, [Define to 1 if you have libtiff.]) AC_SUBST([LIBTIFF_LIBS], [-ltiff]),
+    AS_IF([test "x$with_libtiff" = xyes], AC_MSG_ERROR([libtiff support requested but library not found])),
+    ${LIBM} ${ZLIB_LIBS} ${JPEG_LIBS}
+  )
+)
+
+AS_IF([test "x$with_libwebp" != xno],
+  AC_CHECK_LIB([webp], [WebPGetInfo],
+    AC_DEFINE([HAVE_LIBWEBP], 1, [Define to 1 if you have libwebp.]) AC_SUBST([LIBWEBP_LIBS], [-lwebp]),
+    AS_IF([test "x$with_libwebp" = xyes], AC_MSG_ERROR([libwebp support requested but library not found])),
+    ${LIBM}
+  )
+)
+
+AM_CONDITIONAL([HAVE_LIBWEBP], [test "x$ac_cv_lib_webp_WebPGetInfo" = xyes])
+
+AS_IF([test "x$with_libopenjpeg" != xno],
+  AC_CHECK_LIB([openjp2], [opj_create_decompress],
+    [
+      AC_DEFINE([HAVE_LIBJP2K], 1, [Define to 1 if you have libopenjp2.]) AC_SUBST([LIBJP2K_LIBS], [-lopenjp2])
+      [AC_CHECK_HEADERS([openjpeg-2.2/openjpeg.h openjpeg-2.1/openjpeg.h openjpeg-2.0/openjpeg.h],
+       AC_DEFINE_UNQUOTED([LIBJP2K_HEADER], [<$ac_header>], [Path to <openjpeg.h> header file.]) break)]
+    ],
+    AS_IF([test "x$with_libopenjpeg" = xyes], AC_MSG_ERROR([libopenjp2 support requested but library not found]))
+  )
+)
+
+AM_CONDITIONAL([HAVE_LIBJP2K], [test "x$ac_cv_lib_openjp2_opj_create_decompress" = xyes])
+
+case "$host_os" in
+  mingw32*)
+  AC_SUBST([GDI_LIBS], [-lgdi32])
+  CPPFLAGS="${CPPFLAGS} -D__USE_MINGW_ANSI_STDIO" ;;
+esac
+
+# Enable less verbose output when building.
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_TYPE_SIZE_T
+AC_C_BIGENDIAN
+
+AC_SUBST([APPLE_UNIVERSAL_BUILD], [0])
+AC_SUBST([ENDIANNESS], [L_LITTLE_ENDIAN])
+
+case "$ac_cv_c_bigendian" in
+  yes) AC_SUBST([ENDIANNESS], [L_BIG_ENDIAN]) ;;
+  universal) AC_SUBST([APPLE_UNIVERSAL_BUILD], [1]) ;;
+esac
+
+# Add the -Wl and --as-needed flags to gcc compiler
+AC_MSG_CHECKING([whether compiler supports -Wl,--as-needed])
+OLD_LDFLAGS="${LDFLAGS}"
+LDFLAGS="-Wl,--as-needed ${LDFLAGS}"
+
+AC_LINK_IFELSE([AC_LANG_PROGRAM()],
+  AC_MSG_RESULT([yes]),
+  LDFLAGS="${OLD_LDFLAGS}"; AC_MSG_RESULT([no])
+)
+
+# Checks for library functions.
+AC_CHECK_FUNCS([fmemopen])
+
+AC_CONFIG_FILES([Makefile src/endianness.h src/Makefile lept.pc])
+AC_OUTPUT
diff --git a/endiantest.c b/endiantest.c
new file mode 100644 (file)
index 0000000..e45f27f
--- /dev/null
@@ -0,0 +1,50 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *    endiantest.c
+ *
+ *    This test was contributed by Bill Janssen.  When used with the
+ *    gnu compiler, it allows efficient computation of the endian
+ *    flag as part of the normal compilation process.  As a result,
+ *    it is not necessary to set this flag either manually or
+ *    through the configure Makefile generator.
+ */
+
+#include <stdio.h>
+
+int main()
+{
+/* fprintf(stderr, "doing the test\n"); */
+    long v = 0x04030201;
+    if (*((unsigned char *)(&v)) == 0x04)
+        printf("L_BIG_ENDIAN\n");
+    else
+        printf("L_LITTLE_ENDIAN\n");
+    return 0;
+}
+
+
diff --git a/lept.pc.in b/lept.pc.in
new file mode 100644 (file)
index 0000000..c1b9492
--- /dev/null
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@/leptonica
+
+Name: leptonica
+Description: An open source C library for efficient image processing and image analysis operations
+Version: @VERSION@
+Libs: -L${libdir} -llept
+Libs.private: @ZLIB_LIBS@ @LIBPNG_LIBS@ @JPEG_LIBS@ @GIFLIB_LIBS@ @LIBTIFF_LIBS@ @LIBWEBP_LIBS@
+Cflags: -I${includedir}
+
diff --git a/leptonica-license.txt b/leptonica-license.txt
new file mode 100644 (file)
index 0000000..da7520b
--- /dev/null
@@ -0,0 +1,26 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
diff --git a/leptonica.manifest b/leptonica.manifest
new file mode 100644 (file)
index 0000000..86dbb26
--- /dev/null
@@ -0,0 +1,5 @@
+<manifest>
+    <request>
+        <domain name="_" />
+    </request>
+</manifest>
diff --git a/make-for-auto b/make-for-auto
new file mode 100755 (executable)
index 0000000..b8ac88b
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# make-for-auto
+#
+# Run this to make with autoconf if you've previously run make-for-local
+
+mv src/makefile src/makefile.static
+mv prog/makefile prog/makefile.static
diff --git a/make-for-local b/make-for-local
new file mode 100755 (executable)
index 0000000..e196831
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# make-for-local
+#
+# Run this first if you want to use these static makefiles
+
+cp src/makefile.static src/makefile
+cp prog/makefile.static prog/makefile
diff --git a/packaging/leptonica.spec b/packaging/leptonica.spec
new file mode 100644 (file)
index 0000000..81df55a
--- /dev/null
@@ -0,0 +1,153 @@
+Name:    leptonica
+Version: 1.73
+Release: 2%{?dist}
+Summary: C library for efficient image processing and image analysis operations
+
+License: BSD-2-Clause
+URL:     http://code.google.com/p/leptonica/
+Source0: http://www.leptonica.com/source/%{name}-%{version}.tar.gz
+
+BuildRequires: libjpeg-devel
+BuildRequires: libtiff-devel
+BuildRequires: libpng-devel
+BuildRequires: zlib-devel
+BuildRequires: giflib-devel
+#BuildRequires: libwebp-devel
+BuildRequires: automake
+BuildRequires: autoconf
+BuildRequires: libtool
+
+%description
+The library supports many operations that are useful on
+ * Document images
+ * Natural images
+
+Fundamental image processing and image analysis operations
+ * Rasterop (aka bitblt)
+ * Affine transforms (scaling, translation, rotation, shear)
+   on images of arbitrary pixel depth
+ * Projective and bi-linear transforms
+ * Binary and gray scale morphology, rank order filters, and
+   convolution
+ * Seed-fill and connected components
+ * Image transformations with changes in pixel depth, both at
+   the same scale and with scale change
+ * Pixelwise masking, blending, enhancement, arithmetic ops,
+   etc.
+
+
+%package devel
+Summary:        Development files for %{name}
+Requires:       %{name}%{?_isa} = %{version}-%{release}
+
+%description devel
+The %{name}-devel package contains header files for
+developing applications that use %{name}.
+
+
+%prep
+%setup -q
+
+
+%build
+autoreconf -ivf
+%configure --disable-static --disable-rpath --program-prefix=leptonica-
+make %{?_smp_mflags}
+
+
+%install
+%make_install
+rm -f %{buildroot}%{_libdir}/*.la
+rm -rf %{buildroot}%{_bindir}
+install -Dpm 0644 lept.pc %{buildroot}/%{_libdir}/pkgconfig
+
+
+%post -p /sbin/ldconfig
+%postun -p /sbin/ldconfig
+
+
+%files
+%manifest leptonica.manifest
+%license leptonica-license.txt
+%doc README.html version-notes.html
+%{_libdir}/liblept.so.5*
+
+%files devel
+%{_includedir}/%{name}
+%{_libdir}/liblept.so
+%{_libdir}/pkgconfig/lept.pc
+
+
+%changelog
+* Thu Feb 04 2016 Fedora Release Engineering <releng@fedoraproject.org> - 1.73-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild
+
+* Tue Jan 26 2016 Sandro Mani <manisandro@gmail.com> - 1.73-1
+- Update to 1.73
+
+* Mon Dec 28 2015 Igor Gnatenko <i.gnatenko.brain@gmail.com> - 1.72-3
+- Rebuilt for libwebp soname bump
+
+* Wed Jun 17 2015 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.72-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild
+
+* Mon Apr 27 2015 Sandro Mani <manisandro@gmail.com> - 1.72-1
+- Update to 1.72
+
+* Sun Aug 17 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.71-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild
+
+* Tue Aug 05 2014 Sandro Mani <manisandro@gmail.com> - 1.71-1
+- Update to 1.71
+
+* Sat Jun 07 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.69-12
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild
+
+* Fri Jan 03 2014 Kalev Lember <kalevlember@gmail.com> - 1.69-11
+- Rebuilt for libwebp soname bump
+
+* Sat Aug 03 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.69-10
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild
+
+* Fri Mar 08 2013 Ding-Yi Chen <dchen at redhat.com> - 1.69-9
+- Fixed Bug 904805 - [PATCH] Provide pkg-config file
+
+
+* Fri Mar 08 2013 Ding-Yi Chen <dchen at redhat.com> - 1.69-8
+- Rebuild to resolves #914124
+
+* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 1.69-7
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild
+
+* Thu Jan 24 2013 Ding-Yi Chen <dchen at redhat.com> - 1.69-6
+- Rebuild for dependency libwebp-0.2.1-1
+
+* Fri Jan 18 2013 Adam Tkac <atkac redhat com> - 1.69-5
+- rebuild due to "jpeg8-ABI" feature drop
+
+* Fri Dec 28 2012 Richard W.M. Jones <rjones@redhat.com> - 1.69-4
+- Rebuild, see
+  http://lists.fedoraproject.org/pipermail/devel/2012-December/175685.html
+
+* Fri Dec 21 2012 Adam Tkac <atkac redhat com> - 1.69-3
+- rebuild against new libjpeg
+
+* Thu Aug 02 2012 Ding-Yi Chen <dchen at redhat.com> - 1.69-2
+- Fixed issues addressed in Review Request comment #8.
+
+* Wed Jul 25 2012 Ding-Yi Chen <dchen at redhat.com> - 1.69-1
+- Upstream update to 1.69
+- Add program-prefix in configure.
+
+* Wed Jun 20 2012 Ding-Yi Chen <dchen at redhat.com> - 1.68-4
+- Remove util package and its binary files.
+
+* Mon Jun 11 2012 Ding-Yi Chen <dchen at redhat.com> - 1.68-3
+- Split the binary into util package
+
+* Wed May 09 2012 Ding-Yi Chen <dchen at redhat.com> - 1.68-2
+- Add zlib.h to fix the koji build
+
+* Wed May 09 2012 Ding-Yi Chen <dchen at redhat.com> - 1.68-1
+- Initial import.
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..56ddc58
--- /dev/null
@@ -0,0 +1,112 @@
+#
+# leptonica
+#
+
+################################################################################
+
+########################################
+# SHARED LIBRARY leptonica
+########################################
+
+set(leptonica_src
+    adaptmap.c affine.c
+    affinecompose.c arrayaccess.c
+    bardecode.c baseline.c bbuffer.c
+    bilateral.c bilinear.c binarize.c
+    binexpand.c binreduce.c
+    blend.c bmf.c bmpio.c bmpiostub.c bootnumgen1.c bootnumgen2.c
+    boxbasic.c boxfunc1.c boxfunc2.c boxfunc3.c boxfunc4.c
+    bytearray.c ccbord.c ccthin.c classapp.c
+    colorcontent.c coloring.c
+    colormap.c colormorph.c
+    colorquant1.c colorquant2.c
+    colorseg.c colorspace.c
+    compare.c conncomp.c convertfiles.c
+    convolve.c correlscore.c
+    dewarp1.c dewarp2.c dewarp3.c dewarp4.c
+    dnabasic.c dwacomb.2.c dwacomblow.2.c
+    edge.c encoding.c enhance.c
+    fhmtauto.c fhmtgen.1.c fhmtgenlow.1.c
+    finditalic.c flipdetect.c fliphmtgen.c
+    fmorphauto.c fmorphgen.1.c fmorphgenlow.1.c
+    fpix1.c fpix2.c gifio.c gifiostub.c
+    gplot.c graphics.c graymorph.c
+    grayquant.c grayquantlow.c heap.c jbclass.c
+    jp2kheader.c jp2kheaderstub.c
+    jp2kio.c jp2kiostub.c jpegio.c jpegiostub.c
+    kernel.c leptwin.c libversions.c list.c map.c maze.c
+    morph.c morphapp.c morphdwa.c morphseq.c
+    numabasic.c numafunc1.c numafunc2.c
+    pageseg.c paintcmap.c
+    parseprotos.c partition.c
+    pdfio1.c pdfio1stub.c pdfio2.c pdfio2stub.c
+    pix1.c pix2.c pix3.c pix4.c pix5.c
+    pixabasic.c pixacc.c pixafunc1.c pixafunc2.c
+    pixalloc.c pixarith.c pixcomp.c pixconv.c
+    pixlabel.c pixtiling.c pngio.c pngiostub.c
+    pnmio.c pnmiostub.c projective.c
+    psio1.c psio1stub.c psio2.c psio2stub.c
+    ptabasic.c ptafunc1.c ptra.c
+    quadtree.c queue.c rank.c rbtree.c
+    readbarcode.c readfile.c
+    recogbasic.c recogdid.c recogident.c
+    recogtrain.c regutils.c
+    rop.c ropiplow.c roplow.c
+    rotate.c rotateam.c rotateamlow.c
+    rotateorth.c rotateshear.c
+    runlength.c sarray.c
+    scale.c scalelow.c
+    seedfill.c seedfilllow.c
+    sel1.c sel2.c selgen.c
+    shear.c skew.c spixio.c
+    stack.c stringcode.c sudoku.c textops.c
+    tiffio.c tiffiostub.c
+    utils.c viewfiles.c
+    warper.c watershed.c
+    webpio.c webpiostub.c
+    writefile.c zlibmem.c zlibmemstub.c
+)
+if (WIN32)
+set_source_files_properties(${leptonica_src} PROPERTIES LANGUAGE CXX)
+endif()
+
+set(leptonica_hdr
+    allheaders.h alltypes.h
+    array.h arrayaccess.h bbuffer.h bilateral.h
+    bmf.h bmfdata.h bmp.h ccbord.h
+    dewarp.h endianness.h environ.h
+    gplot.h heap.h imageio.h jbclass.h
+    leptwin.h list.h morph.h pix.h
+    ptra.h queue.h rbtree.h readbarcode.h
+    recog.h regutils.h stack.h
+    stringcode.h sudoku.h watershed.h
+)
+
+add_library                     (leptonica ${LIBRARY_TYPE} ${leptonica_src} ${leptonica_hdr})
+set_target_properties           (leptonica PROPERTIES OUTPUT_NAME lept${VERSION_MAJOR}${VERSION_MINOR})
+set_target_properties           (leptonica PROPERTIES DEBUG_OUTPUT_NAME lept${VERSION_MAJOR}${VERSION_MINOR}d)
+if (NOT STATIC)
+    target_compile_definitions  (leptonica PRIVATE -DLIBLEPT_EXPORTS)
+endif()
+if (GIF_LIBRARY)
+    target_link_libraries       (leptonica ${GIF_LIBRARY})
+endif()
+if (JPEG_LIBRARY)
+    target_link_libraries       (leptonica ${JPEG_LIBRARY})
+endif()
+if (PNG_LIBRARY)
+    target_link_libraries       (leptonica ${PNG_LIBRARY})
+endif()
+if (TIFF_LIBRARY)
+    target_link_libraries       (leptonica ${TIFF_LIBRARY})
+endif()
+if (ZLIB_LIBRARY)
+    target_link_libraries       (leptonica ${ZLIB_LIBRARY})
+endif()
+if (UNIX)
+    target_link_libraries       (leptonica m)
+endif()
+export(TARGETS leptonica FILE ${CMAKE_BINARY_DIR}/LeptonicaTargets.cmake)
+
+################################################################################
+
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644 (file)
index 0000000..a9aaed6
--- /dev/null
@@ -0,0 +1,91 @@
+AM_CFLAGS = $(DEBUG_FLAGS)
+
+lib_LTLIBRARIES = liblept.la
+liblept_la_LIBADD = $(LIBM) $(ZLIB_LIBS) $(LIBPNG_LIBS) $(JPEG_LIBS) $(GIFLIB_LIBS) $(LIBTIFF_LIBS) $(LIBWEBP_LIBS) $(LIBJP2K_LIBS) $(GDI_LIBS)
+
+liblept_la_LDFLAGS = -no-undefined -version-info 5:0:0
+
+liblept_la_SOURCES = adaptmap.c affine.c                        \
+ affinecompose.c arrayaccess.c                                  \
+ bardecode.c baseline.c bbuffer.c                               \
+ bilateral.c bilinear.c binarize.c                              \
+ binexpand.c binreduce.c                                        \
+ blend.c bmf.c bmpio.c bmpiostub.c                              \
+ bootnumgen1.c bootnumgen2.c                                    \
+ boxbasic.c boxfunc1.c boxfunc2.c boxfunc3.c boxfunc4.c         \
+ bytearray.c ccbord.c ccthin.c classapp.c                       \
+ colorcontent.c coloring.c                                      \
+ colormap.c colormorph.c                                       \
+ colorquant1.c colorquant2.c                                    \
+ colorseg.c colorspace.c                                        \
+ compare.c conncomp.c convertfiles.c                            \
+ convolve.c correlscore.c                                       \
+ dewarp1.c dewarp2.c dewarp3.c dewarp4.c                        \
+ dnabasic.c dwacomb.2.c dwacomblow.2.c                          \
+ edge.c encoding.c enhance.c                                    \
+ fhmtauto.c fhmtgen.1.c fhmtgenlow.1.c                         \
+ finditalic.c flipdetect.c fliphmtgen.c                         \
+ fmorphauto.c fmorphgen.1.c fmorphgenlow.1.c                    \
+ fpix1.c fpix2.c gifio.c gifiostub.c                            \
+ gplot.c graphics.c graymorph.c                                 \
+ grayquant.c grayquantlow.c heap.c jbclass.c                    \
+ jp2kheader.c jp2kheaderstub.c                                  \
+ jp2kio.c jp2kiostub.c jpegio.c jpegiostub.c                    \
+ kernel.c leptwin.c libversions.c list.c map.c maze.c           \
+ morph.c morphapp.c morphdwa.c morphseq.c                       \
+ numabasic.c numafunc1.c numafunc2.c                            \
+ pageseg.c paintcmap.c                                          \
+ parseprotos.c partition.c                                      \
+ pdfio1.c pdfio1stub.c pdfio2.c pdfio2stub.c                    \
+ pix1.c pix2.c pix3.c pix4.c pix5.c                             \
+ pixabasic.c pixacc.c pixafunc1.c pixafunc2.c                   \
+ pixalloc.c pixarith.c pixcomp.c pixconv.c                     \
+ pixlabel.c pixtiling.c pngio.c pngiostub.c                     \
+ pnmio.c pnmiostub.c projective.c                              \
+ psio1.c psio1stub.c psio2.c psio2stub.c                        \
+ ptabasic.c ptafunc1.c ptra.c                                  \
+ quadtree.c queue.c rank.c rbtree.c                             \
+ readbarcode.c readfile.c                                       \
+ recogbasic.c recogdid.c recogident.c                           \
+ recogtrain.c regutils.c                                        \
+ rop.c ropiplow.c roplow.c                                      \
+ rotate.c rotateam.c rotateamlow.c                              \
+ rotateorth.c rotateshear.c                                     \
+ runlength.c sarray.c                                           \
+ scale.c scalelow.c                                            \
+ seedfill.c seedfilllow.c                                       \
+ sel1.c sel2.c selgen.c                                         \
+ shear.c skew.c        spixio.c                                        \
+ stack.c stringcode.c sudoku.c textops.c                        \
+ tiffio.c tiffiostub.c                                                 \
+ utils.c viewfiles.c                                            \
+ warper.c watershed.c                                           \
+ webpio.c webpiostub.c                                         \
+ writefile.c zlibmem.c zlibmemstub.c
+
+pkginclude_HEADERS = allheaders.h alltypes.h                    \
+ array.h arrayaccess.h bbuffer.h bilateral.h                    \
+ bmf.h bmfdata.h bmp.h ccbord.h                                 \
+ dewarp.h endianness.h environ.h                               \
+ gplot.h heap.h imageio.h jbclass.h                             \
+ leptwin.h list.h                                              \
+ morph.h pix.h ptra.h queue.h rbtree.h                          \
+ readbarcode.h recog.h regutils.h stack.h                       \
+ stringcode.h sudoku.h watershed.h
+
+noinst_PROGRAMS = xtractprotos
+LDADD = liblept.la
+
+EXTRA_DIST = arrayaccess.h.vc                                   \
+ endiantest.c endianness.h.dist                                        \
+ hmttemplate1.txt hmttemplate2.txt                              \
+ leptonica-license.txt                                         \
+ morphtemplate1.txt morphtemplate2.txt                          \
+ stringtemplate1.txt stringtemplate2.txt
+
+allheaders: $(liblept_la_SOURCES)
+       @test -x xtractprotos || echo "First run 'make xtractprotos'"
+       ./xtractprotos -prestring=LEPT_DLL -protos=inline $(liblept_la_SOURCES)
+
+dist-hook: 
+       cp "$(distdir)"/endianness.h{.dist,}
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644 (file)
index 0000000..f511f30
--- /dev/null
@@ -0,0 +1,897 @@
+# Makefile.in generated by automake 1.11.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software
+# Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+noinst_PROGRAMS = xtractprotos$(EXEEXT)
+subdir = src
+DIST_COMMON = $(pkginclude_HEADERS) $(srcdir)/Makefile.am \
+       $(srcdir)/Makefile.in $(srcdir)/endianness.h.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+       $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config_auto.h
+CONFIG_CLEAN_FILES = endianness.h
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+  test -z "$$files" \
+    || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+    || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+         $(am__cd) "$$dir" && rm -f $$files; }; \
+  }
+am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(pkgincludedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+liblept_la_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+       $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+       $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+       $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+       $(am__DEPENDENCIES_1)
+am_liblept_la_OBJECTS = adaptmap.lo affine.lo affinecompose.lo \
+       arrayaccess.lo bardecode.lo baseline.lo bbuffer.lo \
+       bilateral.lo bilinear.lo binarize.lo binexpand.lo binreduce.lo \
+       blend.lo bmf.lo bmpio.lo bmpiostub.lo bootnumgen1.lo \
+       bootnumgen2.lo boxbasic.lo boxfunc1.lo boxfunc2.lo boxfunc3.lo \
+       boxfunc4.lo bytearray.lo ccbord.lo ccthin.lo classapp.lo \
+       colorcontent.lo coloring.lo colormap.lo colormorph.lo \
+       colorquant1.lo colorquant2.lo colorseg.lo colorspace.lo \
+       compare.lo conncomp.lo convertfiles.lo convolve.lo \
+       correlscore.lo dewarp1.lo dewarp2.lo dewarp3.lo dewarp4.lo \
+       dnabasic.lo dwacomb.2.lo dwacomblow.2.lo edge.lo encoding.lo \
+       enhance.lo fhmtauto.lo fhmtgen.1.lo fhmtgenlow.1.lo \
+       finditalic.lo flipdetect.lo fliphmtgen.lo fmorphauto.lo \
+       fmorphgen.1.lo fmorphgenlow.1.lo fpix1.lo fpix2.lo gifio.lo \
+       gifiostub.lo gplot.lo graphics.lo graymorph.lo grayquant.lo \
+       grayquantlow.lo heap.lo jbclass.lo jp2kheader.lo \
+       jp2kheaderstub.lo jp2kio.lo jp2kiostub.lo jpegio.lo \
+       jpegiostub.lo kernel.lo leptwin.lo libversions.lo list.lo \
+       map.lo maze.lo morph.lo morphapp.lo morphdwa.lo morphseq.lo \
+       numabasic.lo numafunc1.lo numafunc2.lo pageseg.lo paintcmap.lo \
+       parseprotos.lo partition.lo pdfio1.lo pdfio1stub.lo pdfio2.lo \
+       pdfio2stub.lo pix1.lo pix2.lo pix3.lo pix4.lo pix5.lo \
+       pixabasic.lo pixacc.lo pixafunc1.lo pixafunc2.lo pixalloc.lo \
+       pixarith.lo pixcomp.lo pixconv.lo pixlabel.lo pixtiling.lo \
+       pngio.lo pngiostub.lo pnmio.lo pnmiostub.lo projective.lo \
+       psio1.lo psio1stub.lo psio2.lo psio2stub.lo ptabasic.lo \
+       ptafunc1.lo ptra.lo quadtree.lo queue.lo rank.lo rbtree.lo \
+       readbarcode.lo readfile.lo recogbasic.lo recogdid.lo \
+       recogident.lo recogtrain.lo regutils.lo rop.lo ropiplow.lo \
+       roplow.lo rotate.lo rotateam.lo rotateamlow.lo rotateorth.lo \
+       rotateshear.lo runlength.lo sarray.lo scale.lo scalelow.lo \
+       seedfill.lo seedfilllow.lo sel1.lo sel2.lo selgen.lo shear.lo \
+       skew.lo spixio.lo stack.lo stringcode.lo sudoku.lo textops.lo \
+       tiffio.lo tiffiostub.lo utils.lo viewfiles.lo warper.lo \
+       watershed.lo webpio.lo webpiostub.lo writefile.lo zlibmem.lo \
+       zlibmemstub.lo
+liblept_la_OBJECTS = $(am_liblept_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+liblept_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+       $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+       $(liblept_la_LDFLAGS) $(LDFLAGS) -o $@
+PROGRAMS = $(noinst_PROGRAMS)
+xtractprotos_SOURCES = xtractprotos.c
+xtractprotos_OBJECTS = xtractprotos.$(OBJEXT)
+xtractprotos_LDADD = $(LDADD)
+xtractprotos_DEPENDENCIES = liblept.la
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+       $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+       $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+       $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+       $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo "  CC    " $@;
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+       $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+       $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(liblept_la_SOURCES) xtractprotos.c
+DIST_SOURCES = $(liblept_la_SOURCES) xtractprotos.c
+HEADERS = $(pkginclude_HEADERS)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPLE_UNIVERSAL_BUILD = @APPLE_UNIVERSAL_BUILD@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ENDIANNESS = @ENDIANNESS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GDI_LIBS = @GDI_LIBS@
+GIFLIB_LIBS = @GIFLIB_LIBS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+JPEG_LIBS = @JPEG_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBJP2K_LIBS = @LIBJP2K_LIBS@
+LIBM = @LIBM@
+LIBOBJS = @LIBOBJS@
+LIBPNG_LIBS = @LIBPNG_LIBS@
+LIBS = @LIBS@
+LIBTIFF_LIBS = @LIBTIFF_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBWEBP_LIBS = @LIBWEBP_LIBS@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+ZLIB_LIBS = @ZLIB_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CFLAGS = $(DEBUG_FLAGS)
+lib_LTLIBRARIES = liblept.la
+liblept_la_LIBADD = $(LIBM) $(ZLIB_LIBS) $(LIBPNG_LIBS) $(JPEG_LIBS) $(GIFLIB_LIBS) $(LIBTIFF_LIBS) $(LIBWEBP_LIBS) $(LIBJP2K_LIBS) $(GDI_LIBS)
+liblept_la_LDFLAGS = -no-undefined -version-info 5:0:0
+liblept_la_SOURCES = adaptmap.c affine.c                        \
+ affinecompose.c arrayaccess.c                                  \
+ bardecode.c baseline.c bbuffer.c                               \
+ bilateral.c bilinear.c binarize.c                              \
+ binexpand.c binreduce.c                                        \
+ blend.c bmf.c bmpio.c bmpiostub.c                              \
+ bootnumgen1.c bootnumgen2.c                                    \
+ boxbasic.c boxfunc1.c boxfunc2.c boxfunc3.c boxfunc4.c         \
+ bytearray.c ccbord.c ccthin.c classapp.c                       \
+ colorcontent.c coloring.c                                      \
+ colormap.c colormorph.c                                       \
+ colorquant1.c colorquant2.c                                    \
+ colorseg.c colorspace.c                                        \
+ compare.c conncomp.c convertfiles.c                            \
+ convolve.c correlscore.c                                       \
+ dewarp1.c dewarp2.c dewarp3.c dewarp4.c                        \
+ dnabasic.c dwacomb.2.c dwacomblow.2.c                          \
+ edge.c encoding.c enhance.c                                    \
+ fhmtauto.c fhmtgen.1.c fhmtgenlow.1.c                         \
+ finditalic.c flipdetect.c fliphmtgen.c                         \
+ fmorphauto.c fmorphgen.1.c fmorphgenlow.1.c                    \
+ fpix1.c fpix2.c gifio.c gifiostub.c                            \
+ gplot.c graphics.c graymorph.c                                 \
+ grayquant.c grayquantlow.c heap.c jbclass.c                    \
+ jp2kheader.c jp2kheaderstub.c                                  \
+ jp2kio.c jp2kiostub.c jpegio.c jpegiostub.c                    \
+ kernel.c leptwin.c libversions.c list.c map.c maze.c           \
+ morph.c morphapp.c morphdwa.c morphseq.c                       \
+ numabasic.c numafunc1.c numafunc2.c                            \
+ pageseg.c paintcmap.c                                          \
+ parseprotos.c partition.c                                      \
+ pdfio1.c pdfio1stub.c pdfio2.c pdfio2stub.c                    \
+ pix1.c pix2.c pix3.c pix4.c pix5.c                             \
+ pixabasic.c pixacc.c pixafunc1.c pixafunc2.c                   \
+ pixalloc.c pixarith.c pixcomp.c pixconv.c                     \
+ pixlabel.c pixtiling.c pngio.c pngiostub.c                     \
+ pnmio.c pnmiostub.c projective.c                              \
+ psio1.c psio1stub.c psio2.c psio2stub.c                        \
+ ptabasic.c ptafunc1.c ptra.c                                  \
+ quadtree.c queue.c rank.c rbtree.c                             \
+ readbarcode.c readfile.c                                       \
+ recogbasic.c recogdid.c recogident.c                           \
+ recogtrain.c regutils.c                                        \
+ rop.c ropiplow.c roplow.c                                      \
+ rotate.c rotateam.c rotateamlow.c                              \
+ rotateorth.c rotateshear.c                                     \
+ runlength.c sarray.c                                           \
+ scale.c scalelow.c                                            \
+ seedfill.c seedfilllow.c                                       \
+ sel1.c sel2.c selgen.c                                         \
+ shear.c skew.c        spixio.c                                        \
+ stack.c stringcode.c sudoku.c textops.c                        \
+ tiffio.c tiffiostub.c                                                 \
+ utils.c viewfiles.c                                            \
+ warper.c watershed.c                                           \
+ webpio.c webpiostub.c                                         \
+ writefile.c zlibmem.c zlibmemstub.c
+
+pkginclude_HEADERS = allheaders.h alltypes.h                    \
+ array.h arrayaccess.h bbuffer.h bilateral.h                    \
+ bmf.h bmfdata.h bmp.h ccbord.h                                 \
+ dewarp.h endianness.h environ.h                               \
+ gplot.h heap.h imageio.h jbclass.h                             \
+ leptwin.h list.h                                              \
+ morph.h pix.h ptra.h queue.h rbtree.h                          \
+ readbarcode.h recog.h regutils.h stack.h                       \
+ stringcode.h sudoku.h watershed.h
+
+LDADD = liblept.la
+EXTRA_DIST = arrayaccess.h.vc                                   \
+ endiantest.c endianness.h.dist                                        \
+ hmttemplate1.txt hmttemplate2.txt                              \
+ leptonica-license.txt                                         \
+ morphtemplate1.txt morphtemplate2.txt                          \
+ stringtemplate1.txt stringtemplate2.txt
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+       @for dep in $?; do \
+         case '$(am__configure_deps)' in \
+           *$$dep*) \
+             ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+               && { if test -f $@; then exit 0; else break; fi; }; \
+             exit 1;; \
+         esac; \
+       done; \
+       echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \
+       $(am__cd) $(top_srcdir) && \
+         $(AUTOMAKE) --gnu src/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+       @case '$?' in \
+         *config.status*) \
+           cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+         *) \
+           echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+           cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+       esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+endianness.h: $(top_builddir)/config.status $(srcdir)/endianness.h.in
+       cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+       @$(NORMAL_INSTALL)
+       test -z "$(libdir)" || $(MKDIR_P) "$(DESTDIR)$(libdir)"
+       @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+       list2=; for p in $$list; do \
+         if test -f $$p; then \
+           list2="$$list2 $$p"; \
+         else :; fi; \
+       done; \
+       test -z "$$list2" || { \
+         echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+         $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+       }
+
+uninstall-libLTLIBRARIES:
+       @$(NORMAL_UNINSTALL)
+       @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+       for p in $$list; do \
+         $(am__strip_dir) \
+         echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+         $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+       done
+
+clean-libLTLIBRARIES:
+       -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+       @list='$(lib_LTLIBRARIES)'; for p in $$list; do \
+         dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \
+         test "$$dir" != "$$p" || dir=.; \
+         echo "rm -f \"$${dir}/so_locations\""; \
+         rm -f "$${dir}/so_locations"; \
+       done
+liblept.la: $(liblept_la_OBJECTS) $(liblept_la_DEPENDENCIES) $(EXTRA_liblept_la_DEPENDENCIES) 
+       $(AM_V_CCLD)$(liblept_la_LINK) -rpath $(libdir) $(liblept_la_OBJECTS) $(liblept_la_LIBADD) $(LIBS)
+
+clean-noinstPROGRAMS:
+       @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \
+       echo " rm -f" $$list; \
+       rm -f $$list || exit $$?; \
+       test -n "$(EXEEXT)" || exit 0; \
+       list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+       echo " rm -f" $$list; \
+       rm -f $$list
+xtractprotos$(EXEEXT): $(xtractprotos_OBJECTS) $(xtractprotos_DEPENDENCIES) $(EXTRA_xtractprotos_DEPENDENCIES) 
+       @rm -f xtractprotos$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(xtractprotos_OBJECTS) $(xtractprotos_LDADD) $(LIBS)
+
+mostlyclean-compile:
+       -rm -f *.$(OBJEXT)
+
+distclean-compile:
+       -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adaptmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/affine.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/affinecompose.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/arrayaccess.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bardecode.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/baseline.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bbuffer.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bilateral.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bilinear.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/binarize.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/binexpand.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/binreduce.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/blend.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bmf.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bmpio.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bmpiostub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bootnumgen1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bootnumgen2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/boxbasic.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/boxfunc1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/boxfunc2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/boxfunc3.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/boxfunc4.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bytearray.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ccbord.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ccthin.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/classapp.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colorcontent.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/coloring.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colormap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colormorph.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colorquant1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colorquant2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colorseg.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/colorspace.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/compare.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conncomp.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/convertfiles.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/convolve.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/correlscore.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dewarp1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dewarp2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dewarp3.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dewarp4.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dnabasic.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dwacomb.2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dwacomblow.2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/edge.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/encoding.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/enhance.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fhmtauto.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fhmtgen.1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fhmtgenlow.1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/finditalic.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/flipdetect.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fliphmtgen.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fmorphauto.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fmorphgen.1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fmorphgenlow.1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fpix1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fpix2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gifio.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gifiostub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gplot.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/graphics.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/graymorph.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/grayquant.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/grayquantlow.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/heap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jbclass.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jp2kheader.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jp2kheaderstub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jp2kio.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jp2kiostub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jpegio.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jpegiostub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/kernel.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/leptwin.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libversions.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/list.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/map.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/maze.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/morph.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/morphapp.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/morphdwa.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/morphseq.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/numabasic.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/numafunc1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/numafunc2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pageseg.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/paintcmap.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parseprotos.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/partition.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pdfio1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pdfio1stub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pdfio2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pdfio2stub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pix1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pix2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pix3.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pix4.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pix5.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pixabasic.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pixacc.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pixafunc1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pixafunc2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pixalloc.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pixarith.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pixcomp.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pixconv.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pixlabel.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pixtiling.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pngio.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pngiostub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pnmio.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pnmiostub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/projective.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psio1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psio1stub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psio2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psio2stub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ptabasic.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ptafunc1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ptra.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quadtree.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/queue.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rank.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rbtree.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/readbarcode.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/readfile.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/recogbasic.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/recogdid.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/recogident.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/recogtrain.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/regutils.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rop.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ropiplow.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/roplow.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rotate.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rotateam.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rotateamlow.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rotateorth.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rotateshear.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/runlength.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sarray.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/scale.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/scalelow.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/seedfill.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/seedfilllow.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sel1.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sel2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/selgen.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/shear.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/skew.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/spixio.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stack.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stringcode.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sudoku.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/textops.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tiffio.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tiffiostub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/viewfiles.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/warper.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/watershed.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/webpio.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/webpiostub.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/writefile.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xtractprotos.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zlibmem.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/zlibmemstub.Plo@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+       -rm -f *.lo
+
+clean-libtool:
+       -rm -rf .libs _libs
+install-pkgincludeHEADERS: $(pkginclude_HEADERS)
+       @$(NORMAL_INSTALL)
+       test -z "$(pkgincludedir)" || $(MKDIR_P) "$(DESTDIR)$(pkgincludedir)"
+       @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \
+       for p in $$list; do \
+         if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+         echo "$$d$$p"; \
+       done | $(am__base_list) | \
+       while read files; do \
+         echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkgincludedir)'"; \
+         $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkgincludedir)" || exit $$?; \
+       done
+
+uninstall-pkgincludeHEADERS:
+       @$(NORMAL_UNINSTALL)
+       @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \
+       files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+       dir='$(DESTDIR)$(pkgincludedir)'; $(am__uninstall_files_from_dir)
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+       list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+       unique=`for i in $$list; do \
+           if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+         done | \
+         $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+             END { if (nonempty) { for (i in files) print i; }; }'`; \
+       mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+               $(TAGS_FILES) $(LISP)
+       set x; \
+       here=`pwd`; \
+       list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+       unique=`for i in $$list; do \
+           if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+         done | \
+         $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+             END { if (nonempty) { for (i in files) print i; }; }'`; \
+       shift; \
+       if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+         test -n "$$unique" || unique=$$empty_fix; \
+         if test $$# -gt 0; then \
+           $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+             "$$@" $$unique; \
+         else \
+           $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+             $$unique; \
+         fi; \
+       fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+               $(TAGS_FILES) $(LISP)
+       list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+       unique=`for i in $$list; do \
+           if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+         done | \
+         $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+             END { if (nonempty) { for (i in files) print i; }; }'`; \
+       test -z "$(CTAGS_ARGS)$$unique" \
+         || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+            $$unique
+
+GTAGS:
+       here=`$(am__cd) $(top_builddir) && pwd` \
+         && $(am__cd) $(top_srcdir) \
+         && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+       -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+       @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       list='$(DISTFILES)'; \
+         dist_files=`for file in $$list; do echo $$file; done | \
+         sed -e "s|^$$srcdirstrip/||;t" \
+             -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+       case $$dist_files in \
+         */*) $(MKDIR_P) `echo "$$dist_files" | \
+                          sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+                          sort -u` ;; \
+       esac; \
+       for file in $$dist_files; do \
+         if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+         if test -d $$d/$$file; then \
+           dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+           if test -d "$(distdir)/$$file"; then \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+             cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+         else \
+           test -f "$(distdir)/$$file" \
+           || cp -p $$d/$$file "$(distdir)/$$file" \
+           || exit 1; \
+         fi; \
+       done
+       $(MAKE) $(AM_MAKEFLAGS) \
+         top_distdir="$(top_distdir)" distdir="$(distdir)" \
+         dist-hook
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(PROGRAMS) $(HEADERS)
+installdirs:
+       for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(pkgincludedir)"; do \
+         test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+       done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+       @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+       if test -z '$(STRIP)'; then \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+             install; \
+       else \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+           "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+       fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+       -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+       -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+       @echo "This command is intended for maintainers to use"
+       @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+       clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+       -rm -rf ./$(DEPDIR)
+       -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+       distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkgincludeHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+       -rm -rf ./$(DEPDIR)
+       -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+       mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES uninstall-pkgincludeHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+       clean-libLTLIBRARIES clean-libtool clean-noinstPROGRAMS ctags \
+       dist-hook distclean distclean-compile distclean-generic \
+       distclean-libtool distclean-tags distdir dvi dvi-am html \
+       html-am info info-am install install-am install-data \
+       install-data-am install-dvi install-dvi-am install-exec \
+       install-exec-am install-html install-html-am install-info \
+       install-info-am install-libLTLIBRARIES install-man install-pdf \
+       install-pdf-am install-pkgincludeHEADERS install-ps \
+       install-ps-am install-strip installcheck installcheck-am \
+       installdirs maintainer-clean maintainer-clean-generic \
+       mostlyclean mostlyclean-compile mostlyclean-generic \
+       mostlyclean-libtool pdf pdf-am ps ps-am tags uninstall \
+       uninstall-am uninstall-libLTLIBRARIES \
+       uninstall-pkgincludeHEADERS
+
+
+allheaders: $(liblept_la_SOURCES)
+       @test -x xtractprotos || echo "First run 'make xtractprotos'"
+       ./xtractprotos -prestring=LEPT_DLL -protos=inline $(liblept_la_SOURCES)
+
+dist-hook: 
+       cp "$(distdir)"/endianness.h{.dist,}
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/adaptmap.c b/src/adaptmap.c
new file mode 100644 (file)
index 0000000..8fad074
--- /dev/null
@@ -0,0 +1,2872 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  adaptmap.c
+ *
+ *  ===================================================================
+ *  Image binarization algorithms are found in:
+ *     grayquant.c:   standard, simple, general grayscale quantization
+ *     adaptmap.c:    local adaptive; mostly gray-to-gray in preparation
+ *                    for binarization
+ *     binarize.c:    special binarization methods, locally adaptive.
+ *  ===================================================================
+ *
+ *      Clean background to white using background normalization
+ *          PIX       *pixCleanBackgroundToWhite()
+ *
+ *      Adaptive background normalization (top-level functions)
+ *          PIX       *pixBackgroundNormSimple()     8 and 32 bpp
+ *          PIX       *pixBackgroundNorm()           8 and 32 bpp
+ *          PIX       *pixBackgroundNormMorph()      8 and 32 bpp
+ *
+ *      Arrays of inverted background values for normalization (16 bpp)
+ *          l_int32    pixBackgroundNormGrayArray()   8 bpp input
+ *          l_int32    pixBackgroundNormRGBArrays()   32 bpp input
+ *          l_int32    pixBackgroundNormGrayArrayMorph()   8 bpp input
+ *          l_int32    pixBackgroundNormRGBArraysMorph()   32 bpp input
+ *
+ *      Measurement of local background
+ *          l_int32    pixGetBackgroundGrayMap()        8 bpp
+ *          l_int32    pixGetBackgroundRGBMap()         32 bpp
+ *          l_int32    pixGetBackgroundGrayMapMorph()   8 bpp
+ *          l_int32    pixGetBackgroundRGBMapMorph()    32 bpp
+ *          l_int32    pixFillMapHoles()
+ *          PIX       *pixExtendByReplication()         8 bpp
+ *          l_int32    pixSmoothConnectedRegions()      8 bpp
+ *
+ *      Measurement of local foreground
+ *          l_int32    pixGetForegroundGrayMap()        8 bpp
+ *
+ *      Generate inverted background map for each component
+ *          PIX       *pixGetInvBackgroundMap()   16 bpp
+ *
+ *      Apply inverse background map to image
+ *          PIX       *pixApplyInvBackgroundGrayMap()   8 bpp
+ *          PIX       *pixApplyInvBackgroundRGBMap()    32 bpp
+ *
+ *      Apply variable map
+ *          PIX       *pixApplyVariableGrayMap()        8 bpp
+ *
+ *      Non-adaptive (global) mapping
+ *          PIX       *pixGlobalNormRGB()               32 bpp or cmapped
+ *          PIX       *pixGlobalNormNoSatRGB()          32 bpp
+ *
+ *      Adaptive threshold spread normalization
+ *          l_int32    pixThresholdSpreadNorm()         8 bpp
+ *
+ *      Adaptive background normalization (flexible adaptaption)
+ *          PIX       *pixBackgroundNormFlex()          8 bpp
+ *
+ *      Adaptive contrast normalization
+ *          PIX             *pixContrastNorm()          8 bpp
+ *          l_int32          pixMinMaxTiles()
+ *          l_int32          pixSetLowContrast()
+ *          PIX             *pixLinearTRCTiled()
+ *          static l_int32  *iaaGetLinearTRC()
+ *
+ *  Background normalization is done by generating a reduced map (or set
+ *  of maps) representing the estimated background value of the
+ *  input image, and using this to shift the pixel values so that
+ *  this background value is set to some constant value.
+ *
+ *  Specifically, normalization has 3 steps:
+ *    (1) Generate a background map at a reduced scale.
+ *    (2) Make the array of inverted background values by inverting
+ *        the map.  The result is an array of local multiplicative factors.
+ *    (3) Apply this inverse background map to the image
+ *
+ *  The inverse background arrays can be generated in two different ways here:
+ *    (1) Remove the 'foreground' pixels and average over the remaining
+ *        pixels in each tile.  Propagate values into tiles where
+ *        values have not been assigned, either because there was not
+ *        enough background in the tile or because the tile is covered
+ *        by a foreground region described by an image mask.
+ *        After the background map is made, the inverse map is generated by
+ *        smoothing over some number of adjacent tiles
+ *        (block convolution) and then inverting.
+ *    (2) Remove the foreground pixels using a morphological closing
+ *        on a subsampled version of the image.  Propagate values
+ *        into pixels covered by an optional image mask.  Invert the
+ *        background map without preconditioning by convolutional smoothing.
+ *
+ *  Note: Several of these functions make an implicit assumption about RGB
+ *        component ordering.
+ *
+ *  Other methods for adaptively normalizing the image are also given here.
+ *
+ *  (1) pixThresholdSpreadNorm() computes a local threshold over the image
+ *      and normalizes the input pixel values so that this computed threshold
+ *      is a constant across the entire image.
+ *
+ *  (2) pixContrastNorm() computes and applies a local TRC so that the
+ *      local dynamic range is expanded to the full 8 bits, where the
+ *      darkest pixels are mapped to 0 and the lightest to 255.  This is
+ *      useful for improving the appearance of pages with very light
+ *      foreground or very dark background, and where the local TRC
+ *      function doesn't change rapidly with position.
+ */
+
+#include "allheaders.h"
+
+    /* Default input parameters for pixBackgroundNormSimple()
+     * Note:
+     *    (1) mincount must never exceed the tile area (width * height)
+     *    (2) bgval must be sufficiently below 255 to avoid accidental
+     *        saturation; otherwise it should be large to avoid
+     *        shrinking the dynamic range
+     *    (3) results should otherwise not be sensitive to these values
+     */
+static const l_int32  DEFAULT_TILE_WIDTH = 10;
+static const l_int32  DEFAULT_TILE_HEIGHT = 15;
+static const l_int32  DEFAULT_FG_THRESHOLD = 60;
+static const l_int32  DEFAULT_MIN_COUNT = 40;
+static const l_int32  DEFAULT_BG_VAL = 200;
+static const l_int32  DEFAULT_X_SMOOTH_SIZE = 2;
+static const l_int32  DEFAULT_Y_SMOOTH_SIZE = 1;
+
+static l_int32 *iaaGetLinearTRC(l_int32 **iaa, l_int32 diff);
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_GLOBAL    0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*------------------------------------------------------------------*
+ *      Clean background to white using background normalization    *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixCleanBackgroundToWhite()
+ *
+ *      Input:  pixs (8 bpp grayscale or 32 bpp rgb)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null)
+ *              pixg (<optional> 8 bpp grayscale version; can be null)
+ *              gamma (gamma correction; must be > 0.0; typically ~1.0)
+ *              blackval (dark value to set to black (0))
+ *              whiteval (light value to set to white (255))
+ *      Return: pixd (8 bpp or 32 bpp rgb), or null on error
+ *
+ *  Notes:
+ *    (1) This is a simplified interface for cleaning an image.
+ *        For comparison, see pixAdaptThresholdToBinaryGen().
+ *    (2) The suggested default values for the input parameters are:
+ *          gamma:    1.0  (reduce this to increase the contrast; e.g.,
+ *                          for light text)
+ *          blackval   70  (a bit more than 60)
+ *          whiteval  190  (a bit less than 200)
+ */
+PIX *
+pixCleanBackgroundToWhite(PIX       *pixs,
+                          PIX       *pixim,
+                          PIX       *pixg,
+                          l_float32  gamma,
+                          l_int32    blackval,
+                          l_int32    whiteval)
+{
+l_int32  d;
+PIX     *pixd;
+
+    PROCNAME("pixCleanBackgroundToWhite");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("depth not 8 or 32", procName, NULL);
+
+    pixd = pixBackgroundNormSimple(pixs, pixim, pixg);
+    pixGammaTRC(pixd, pixd, gamma, blackval, whiteval);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                Adaptive background normalization                 *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixBackgroundNormSimple()
+ *
+ *      Input:  pixs (8 bpp grayscale or 32 bpp rgb)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null)
+ *              pixg (<optional> 8 bpp grayscale version; can be null)
+ *      Return: pixd (8 bpp or 32 bpp rgb), or null on error
+ *
+ *  Notes:
+ *    (1) This is a simplified interface to pixBackgroundNorm(),
+ *        where seven parameters are defaulted.
+ *    (2) The input image is either grayscale or rgb.
+ *    (3) See pixBackgroundNorm() for usage and function.
+ */
+PIX *
+pixBackgroundNormSimple(PIX  *pixs,
+                        PIX  *pixim,
+                        PIX  *pixg)
+{
+    return pixBackgroundNorm(pixs, pixim, pixg,
+                             DEFAULT_TILE_WIDTH, DEFAULT_TILE_HEIGHT,
+                             DEFAULT_FG_THRESHOLD, DEFAULT_MIN_COUNT,
+                             DEFAULT_BG_VAL, DEFAULT_X_SMOOTH_SIZE,
+                             DEFAULT_Y_SMOOTH_SIZE);
+}
+
+
+/*!
+ *  pixBackgroundNorm()
+ *
+ *      Input:  pixs (8 bpp grayscale or 32 bpp rgb)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null)
+ *              pixg (<optional> 8 bpp grayscale version; can be null)
+ *              sx, sy (tile size in pixels)
+ *              thresh (threshold for determining foreground)
+ *              mincount (min threshold on counts in a tile)
+ *              bgval (target bg val; typ. > 128)
+ *              smoothx (half-width of block convolution kernel width)
+ *              smoothy (half-width of block convolution kernel height)
+ *      Return: pixd (8 bpp or 32 bpp rgb), or null on error
+ *
+ *  Notes:
+ *    (1) This is a top-level interface for normalizing the image intensity
+ *        by mapping the image so that the background is near the input
+ *        value 'bgval'.
+ *    (2) The input image is either grayscale or rgb.
+ *    (3) For each component in the input image, the background value
+ *        in each tile is estimated using the values in the tile that
+ *        are not part of the foreground, where the foreground is
+ *        determined by the input 'thresh' argument.
+ *    (4) An optional binary mask can be specified, with the foreground
+ *        pixels typically over image regions.  The resulting background
+ *        map values will be determined by surrounding pixels that are
+ *        not under the mask foreground.  The origin (0,0) of this mask
+ *        is assumed to be aligned with the origin of the input image.
+ *        This binary mask must not fully cover pixs, because then there
+ *        will be no pixels in the input image available to compute
+ *        the background.
+ *    (5) An optional grayscale version of the input pixs can be supplied.
+ *        The only reason to do this is if the input is RGB and this
+ *        grayscale version can be used elsewhere.  If the input is RGB
+ *        and this is not supplied, it is made internally using only
+ *        the green component, and destroyed after use.
+ *    (6) The dimensions of the pixel tile (sx, sy) give the amount by
+ *        by which the map is reduced in size from the input image.
+ *    (7) The threshold is used to binarize the input image, in order to
+ *        locate the foreground components.  If this is set too low,
+ *        some actual foreground may be used to determine the maps;
+ *        if set too high, there may not be enough background
+ *        to determine the map values accurately.  Typically, it's
+ *        better to err by setting the threshold too high.
+ *    (8) A 'mincount' threshold is a minimum count of pixels in a
+ *        tile for which a background reading is made, in order for that
+ *        pixel in the map to be valid.  This number should perhaps be
+ *        at least 1/3 the size of the tile.
+ *    (9) A 'bgval' target background value for the normalized image.  This
+ *        should be at least 128.  If set too close to 255, some
+ *        clipping will occur in the result.
+ *    (10) Two factors, 'smoothx' and 'smoothy', are input for smoothing
+ *        the map.  Each low-pass filter kernel dimension is
+ *        is 2 * (smoothing factor) + 1, so a
+ *        value of 0 means no smoothing. A value of 1 or 2 is recommended.
+ */
+PIX *
+pixBackgroundNorm(PIX     *pixs,
+                  PIX     *pixim,
+                  PIX     *pixg,
+                  l_int32  sx,
+                  l_int32  sy,
+                  l_int32  thresh,
+                  l_int32  mincount,
+                  l_int32  bgval,
+                  l_int32  smoothx,
+                  l_int32  smoothy)
+{
+l_int32  d, allfg;
+PIX     *pixm, *pixmi, *pixd;
+PIX     *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi;
+
+    PROCNAME("pixBackgroundNorm");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+    if (sx < 4 || sy < 4)
+        return (PIX *)ERROR_PTR("sx and sy must be >= 4", procName, NULL);
+    if (mincount > sx * sy) {
+        L_WARNING("mincount too large for tile size\n", procName);
+        mincount = (sx * sy) / 3;
+    }
+
+        /* If pixim exists, verify that it is not all foreground. */
+    if (pixim) {
+        pixInvert(pixim, pixim);
+        pixZero(pixim, &allfg);
+        pixInvert(pixim, pixim);
+        if (allfg)
+            return (PIX *)ERROR_PTR("pixim all foreground", procName, NULL);
+    }
+
+    pixd = NULL;
+    if (d == 8) {
+        pixm = NULL;
+        pixGetBackgroundGrayMap(pixs, pixim, sx, sy, thresh, mincount, &pixm);
+        if (!pixm) {
+            L_WARNING("map not made; return a copy of the source\n", procName);
+            return pixCopy(NULL, pixs);
+        }
+
+        pixmi = pixGetInvBackgroundMap(pixm, bgval, smoothx, smoothy);
+        if (!pixmi)
+            ERROR_PTR("pixmi not made", procName, NULL);
+        else
+            pixd = pixApplyInvBackgroundGrayMap(pixs, pixmi, sx, sy);
+
+        pixDestroy(&pixm);
+        pixDestroy(&pixmi);
+    }
+    else {
+        pixmr = pixmg = pixmb = NULL;
+        pixGetBackgroundRGBMap(pixs, pixim, pixg, sx, sy, thresh,
+                               mincount, &pixmr, &pixmg, &pixmb);
+        if (!pixmr || !pixmg || !pixmb) {
+            pixDestroy(&pixmr);
+            pixDestroy(&pixmg);
+            pixDestroy(&pixmb);
+            L_WARNING("map not made; return a copy of the source\n", procName);
+            return pixCopy(NULL, pixs);
+        }
+
+        pixmri = pixGetInvBackgroundMap(pixmr, bgval, smoothx, smoothy);
+        pixmgi = pixGetInvBackgroundMap(pixmg, bgval, smoothx, smoothy);
+        pixmbi = pixGetInvBackgroundMap(pixmb, bgval, smoothx, smoothy);
+        if (!pixmri || !pixmgi || !pixmbi)
+            ERROR_PTR("not all pixm*i are made", procName, NULL);
+        else
+            pixd = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi,
+                                               sx, sy);
+
+        pixDestroy(&pixmr);
+        pixDestroy(&pixmg);
+        pixDestroy(&pixmb);
+        pixDestroy(&pixmri);
+        pixDestroy(&pixmgi);
+        pixDestroy(&pixmbi);
+    }
+
+    if (!pixd)
+        ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixBackgroundNormMorph()
+ *
+ *      Input:  pixs (8 bpp grayscale or 32 bpp rgb)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null)
+ *              reduction (at which morph closings are done; between 2 and 16)
+ *              size (of square Sel for the closing; use an odd number)
+ *              bgval (target bg val; typ. > 128)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *    (1) This is a top-level interface for normalizing the image intensity
+ *        by mapping the image so that the background is near the input
+ *        value 'bgval'.
+ *    (2) The input image is either grayscale or rgb.
+ *    (3) For each component in the input image, the background value
+ *        is estimated using a grayscale closing; hence the 'Morph'
+ *        in the function name.
+ *    (4) An optional binary mask can be specified, with the foreground
+ *        pixels typically over image regions.  The resulting background
+ *        map values will be determined by surrounding pixels that are
+ *        not under the mask foreground.  The origin (0,0) of this mask
+ *        is assumed to be aligned with the origin of the input image.
+ *        This binary mask must not fully cover pixs, because then there
+ *        will be no pixels in the input image available to compute
+ *        the background.
+ *    (5) The map is computed at reduced size (given by 'reduction')
+ *        from the input pixs and optional pixim.  At this scale,
+ *        pixs is closed to remove the background, using a square Sel
+ *        of odd dimension.  The product of reduction * size should be
+ *        large enough to remove most of the text foreground.
+ *    (6) No convolutional smoothing needs to be done on the map before
+ *        inverting it.
+ *    (7) A 'bgval' target background value for the normalized image.  This
+ *        should be at least 128.  If set too close to 255, some
+ *        clipping will occur in the result.
+ */
+PIX *
+pixBackgroundNormMorph(PIX     *pixs,
+                       PIX     *pixim,
+                       l_int32  reduction,
+                       l_int32  size,
+                       l_int32  bgval)
+{
+l_int32    d, allfg;
+PIX       *pixm, *pixmi, *pixd;
+PIX       *pixmr, *pixmg, *pixmb, *pixmri, *pixmgi, *pixmbi;
+
+    PROCNAME("pixBackgroundNormMorph");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+    if (reduction < 2 || reduction > 16)
+        return (PIX *)ERROR_PTR("reduction must be between 2 and 16",
+                                procName, NULL);
+
+        /* If pixim exists, verify that it is not all foreground. */
+    if (pixim) {
+        pixInvert(pixim, pixim);
+        pixZero(pixim, &allfg);
+        pixInvert(pixim, pixim);
+        if (allfg)
+            return (PIX *)ERROR_PTR("pixim all foreground", procName, NULL);
+    }
+
+    pixd = NULL;
+    if (d == 8) {
+        pixGetBackgroundGrayMapMorph(pixs, pixim, reduction, size, &pixm);
+        if (!pixm)
+            return (PIX *)ERROR_PTR("pixm not made", procName, NULL);
+        pixmi = pixGetInvBackgroundMap(pixm, bgval, 0, 0);
+        if (!pixmi)
+            ERROR_PTR("pixmi not made", procName, NULL);
+        else
+            pixd = pixApplyInvBackgroundGrayMap(pixs, pixmi,
+                                                reduction, reduction);
+        pixDestroy(&pixm);
+        pixDestroy(&pixmi);
+    }
+    else {  /* d == 32 */
+        pixmr = pixmg = pixmb = NULL;
+        pixGetBackgroundRGBMapMorph(pixs, pixim, reduction, size,
+                                    &pixmr, &pixmg, &pixmb);
+        if (!pixmr || !pixmg || !pixmb) {
+            pixDestroy(&pixmr);
+            pixDestroy(&pixmg);
+            pixDestroy(&pixmb);
+            return (PIX *)ERROR_PTR("not all pixm*", procName, NULL);
+        }
+
+        pixmri = pixGetInvBackgroundMap(pixmr, bgval, 0, 0);
+        pixmgi = pixGetInvBackgroundMap(pixmg, bgval, 0, 0);
+        pixmbi = pixGetInvBackgroundMap(pixmb, bgval, 0, 0);
+        if (!pixmri || !pixmgi || !pixmbi)
+            ERROR_PTR("not all pixm*i are made", procName, NULL);
+        else
+            pixd = pixApplyInvBackgroundRGBMap(pixs, pixmri, pixmgi, pixmbi,
+                                               reduction, reduction);
+
+        pixDestroy(&pixmr);
+        pixDestroy(&pixmg);
+        pixDestroy(&pixmb);
+        pixDestroy(&pixmri);
+        pixDestroy(&pixmgi);
+        pixDestroy(&pixmbi);
+    }
+
+    if (!pixd)
+        ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------------------*
+ *      Arrays of inverted background values for normalization             *
+ *-------------------------------------------------------------------------*
+ *  Notes for these four functions:                                        *
+ *      (1) They are useful if you need to save the actual mapping array.  *
+ *      (2) They could be used in the top-level functions but are          *
+ *          not because their use makes those functions less clear.        *
+ *      (3) Each component in the input pixs generates a 16 bpp pix array. *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  pixBackgroundNormGrayArray()
+ *
+ *      Input:  pixs (8 bpp grayscale)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null)
+ *              sx, sy (tile size in pixels)
+ *              thresh (threshold for determining foreground)
+ *              mincount (min threshold on counts in a tile)
+ *              bgval (target bg val; typ. > 128)
+ *              smoothx (half-width of block convolution kernel width)
+ *              smoothy (half-width of block convolution kernel height)
+ *              &pixd (<return> 16 bpp array of inverted background value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *    (1) See notes in pixBackgroundNorm().
+ *    (2) This returns a 16 bpp pix that can be used by
+ *        pixApplyInvBackgroundGrayMap() to generate a normalized version
+ *        of the input pixs.
+ */
+l_int32
+pixBackgroundNormGrayArray(PIX     *pixs,
+                           PIX     *pixim,
+                           l_int32  sx,
+                           l_int32  sy,
+                           l_int32  thresh,
+                           l_int32  mincount,
+                           l_int32  bgval,
+                           l_int32  smoothx,
+                           l_int32  smoothy,
+                           PIX    **ppixd)
+{
+l_int32  allfg;
+PIX     *pixm;
+
+    PROCNAME("pixBackgroundNormGrayArray");
+
+    if (!ppixd)
+        return ERROR_INT("&pixd not defined", procName, 1);
+    *ppixd = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (pixGetColormap(pixs))
+        return ERROR_INT("pixs is colormapped", procName, 1);
+    if (pixim && pixGetDepth(pixim) != 1)
+        return ERROR_INT("pixim not 1 bpp", procName, 1);
+    if (sx < 4 || sy < 4)
+        return ERROR_INT("sx and sy must be >= 4", procName, 1);
+    if (mincount > sx * sy) {
+        L_WARNING("mincount too large for tile size\n", procName);
+        mincount = (sx * sy) / 3;
+    }
+
+        /* If pixim exists, verify that it is not all foreground. */
+    if (pixim) {
+        pixInvert(pixim, pixim);
+        pixZero(pixim, &allfg);
+        pixInvert(pixim, pixim);
+        if (allfg)
+            return ERROR_INT("pixim all foreground", procName, 1);
+    }
+
+    pixGetBackgroundGrayMap(pixs, pixim, sx, sy, thresh, mincount, &pixm);
+    if (!pixm)
+        return ERROR_INT("pixm not made", procName, 1);
+    *ppixd = pixGetInvBackgroundMap(pixm, bgval, smoothx, smoothy);
+    pixCopyResolution(*ppixd, pixs);
+    pixDestroy(&pixm);
+    return 0;
+}
+
+
+/*!
+ *  pixBackgroundNormRGBArrays()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null)
+ *              pixg (<optional> 8 bpp grayscale version; can be null)
+ *              sx, sy (tile size in pixels)
+ *              thresh (threshold for determining foreground)
+ *              mincount (min threshold on counts in a tile)
+ *              bgval (target bg val; typ. > 128)
+ *              smoothx (half-width of block convolution kernel width)
+ *              smoothy (half-width of block convolution kernel height)
+ *              &pixr (<return> 16 bpp array of inverted R background value)
+ *              &pixg (<return> 16 bpp array of inverted G background value)
+ *              &pixb (<return> 16 bpp array of inverted B background value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *    (1) See notes in pixBackgroundNorm().
+ *    (2) This returns a set of three 16 bpp pix that can be used by
+ *        pixApplyInvBackgroundGrayMap() to generate a normalized version
+ *        of each component of the input pixs.
+ */
+l_int32
+pixBackgroundNormRGBArrays(PIX     *pixs,
+                           PIX     *pixim,
+                           PIX     *pixg,
+                           l_int32  sx,
+                           l_int32  sy,
+                           l_int32  thresh,
+                           l_int32  mincount,
+                           l_int32  bgval,
+                           l_int32  smoothx,
+                           l_int32  smoothy,
+                           PIX    **ppixr,
+                           PIX    **ppixg,
+                           PIX    **ppixb)
+{
+l_int32  allfg;
+PIX     *pixmr, *pixmg, *pixmb;
+
+    PROCNAME("pixBackgroundNormRGBArrays");
+
+    if (!ppixr || !ppixg || !ppixb)
+        return ERROR_INT("&pixr, &pixg, &pixb not all defined", procName, 1);
+    *ppixr = *ppixg = *ppixb = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not 32 bpp", procName, 1);
+    if (pixim && pixGetDepth(pixim) != 1)
+        return ERROR_INT("pixim not 1 bpp", procName, 1);
+    if (sx < 4 || sy < 4)
+        return ERROR_INT("sx and sy must be >= 4", procName, 1);
+    if (mincount > sx * sy) {
+        L_WARNING("mincount too large for tile size\n", procName);
+        mincount = (sx * sy) / 3;
+    }
+
+        /* If pixim exists, verify that it is not all foreground. */
+    if (pixim) {
+        pixInvert(pixim, pixim);
+        pixZero(pixim, &allfg);
+        pixInvert(pixim, pixim);
+        if (allfg)
+            return ERROR_INT("pixim all foreground", procName, 1);
+    }
+
+    pixGetBackgroundRGBMap(pixs, pixim, pixg, sx, sy, thresh, mincount,
+                           &pixmr, &pixmg, &pixmb);
+    if (!pixmr || !pixmg || !pixmb) {
+        pixDestroy(&pixmr);
+        pixDestroy(&pixmg);
+        pixDestroy(&pixmb);
+        return ERROR_INT("not all pixm* made", procName, 1);
+    }
+
+    *ppixr = pixGetInvBackgroundMap(pixmr, bgval, smoothx, smoothy);
+    *ppixg = pixGetInvBackgroundMap(pixmg, bgval, smoothx, smoothy);
+    *ppixb = pixGetInvBackgroundMap(pixmb, bgval, smoothx, smoothy);
+    pixDestroy(&pixmr);
+    pixDestroy(&pixmg);
+    pixDestroy(&pixmb);
+    return 0;
+}
+
+
+/*!
+ *  pixBackgroundNormGrayArrayMorph()
+ *
+ *      Input:  pixs (8 bpp grayscale)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null)
+ *              reduction (at which morph closings are done; between 2 and 16)
+ *              size (of square Sel for the closing; use an odd number)
+ *              bgval (target bg val; typ. > 128)
+ *              &pixd (<return> 16 bpp array of inverted background value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *    (1) See notes in pixBackgroundNormMorph().
+ *    (2) This returns a 16 bpp pix that can be used by
+ *        pixApplyInvBackgroundGrayMap() to generate a normalized version
+ *        of the input pixs.
+ */
+l_int32
+pixBackgroundNormGrayArrayMorph(PIX     *pixs,
+                                PIX     *pixim,
+                                l_int32  reduction,
+                                l_int32  size,
+                                l_int32  bgval,
+                                PIX    **ppixd)
+{
+l_int32  allfg;
+PIX     *pixm;
+
+    PROCNAME("pixBackgroundNormGrayArrayMorph");
+
+    if (!ppixd)
+        return ERROR_INT("&pixd not defined", procName, 1);
+    *ppixd = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not 8 bpp", procName, 1);
+    if (pixim && pixGetDepth(pixim) != 1)
+        return ERROR_INT("pixim not 1 bpp", procName, 1);
+    if (reduction < 2 || reduction > 16)
+        return ERROR_INT("reduction must be between 2 and 16", procName, 1);
+
+        /* If pixim exists, verify that it is not all foreground. */
+    if (pixim) {
+        pixInvert(pixim, pixim);
+        pixZero(pixim, &allfg);
+        pixInvert(pixim, pixim);
+        if (allfg)
+            return ERROR_INT("pixim all foreground", procName, 1);
+    }
+
+    pixGetBackgroundGrayMapMorph(pixs, pixim, reduction, size, &pixm);
+    if (!pixm)
+        return ERROR_INT("pixm not made", procName, 1);
+    *ppixd = pixGetInvBackgroundMap(pixm, bgval, 0, 0);
+    pixCopyResolution(*ppixd, pixs);
+    pixDestroy(&pixm);
+    return 0;
+}
+
+
+/*!
+ *  pixBackgroundNormRGBArraysMorph()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null)
+ *              reduction (at which morph closings are done; between 2 and 16)
+ *              size (of square Sel for the closing; use an odd number)
+ *              bgval (target bg val; typ. > 128)
+ *              &pixr (<return> 16 bpp array of inverted R background value)
+ *              &pixg (<return> 16 bpp array of inverted G background value)
+ *              &pixb (<return> 16 bpp array of inverted B background value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *    (1) See notes in pixBackgroundNormMorph().
+ *    (2) This returns a set of three 16 bpp pix that can be used by
+ *        pixApplyInvBackgroundGrayMap() to generate a normalized version
+ *        of each component of the input pixs.
+ */
+l_int32
+pixBackgroundNormRGBArraysMorph(PIX     *pixs,
+                                PIX     *pixim,
+                                l_int32  reduction,
+                                l_int32  size,
+                                l_int32  bgval,
+                                PIX    **ppixr,
+                                PIX    **ppixg,
+                                PIX    **ppixb)
+{
+l_int32  allfg;
+PIX     *pixmr, *pixmg, *pixmb;
+
+    PROCNAME("pixBackgroundNormRGBArraysMorph");
+
+    if (!ppixr || !ppixg || !ppixb)
+        return ERROR_INT("&pixr, &pixg, &pixb not all defined", procName, 1);
+    *ppixr = *ppixg = *ppixb = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not 32 bpp", procName, 1);
+    if (pixim && pixGetDepth(pixim) != 1)
+        return ERROR_INT("pixim not 1 bpp", procName, 1);
+    if (reduction < 2 || reduction > 16)
+        return ERROR_INT("reduction must be between 2 and 16", procName, 1);
+
+        /* If pixim exists, verify that it is not all foreground. */
+    if (pixim) {
+        pixInvert(pixim, pixim);
+        pixZero(pixim, &allfg);
+        pixInvert(pixim, pixim);
+        if (allfg)
+            return ERROR_INT("pixim all foreground", procName, 1);
+    }
+
+    pixGetBackgroundRGBMapMorph(pixs, pixim, reduction, size,
+                                &pixmr, &pixmg, &pixmb);
+    if (!pixmr || !pixmg || !pixmb) {
+        pixDestroy(&pixmr);
+        pixDestroy(&pixmg);
+        pixDestroy(&pixmb);
+        return ERROR_INT("not all pixm* made", procName, 1);
+    }
+
+    *ppixr = pixGetInvBackgroundMap(pixmr, bgval, 0, 0);
+    *ppixg = pixGetInvBackgroundMap(pixmg, bgval, 0, 0);
+    *ppixb = pixGetInvBackgroundMap(pixmb, bgval, 0, 0);
+    pixDestroy(&pixmr);
+    pixDestroy(&pixmg);
+    pixDestroy(&pixmb);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *                 Measurement of local background                  *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixGetBackgroundGrayMap()
+ *
+ *      Input:  pixs (8 bpp grayscale; not cmapped)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null; it
+ *                     should not have all foreground pixels)
+ *              sx, sy (tile size in pixels)
+ *              thresh (threshold for determining foreground)
+ *              mincount (min threshold on counts in a tile)
+ *              &pixd (<return> 8 bpp grayscale map)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The background is measured in regions that don't have
+ *          images.  It is then propagated into the image regions,
+ *          and finally smoothed in each image region.
+ */
+l_int32
+pixGetBackgroundGrayMap(PIX     *pixs,
+                        PIX     *pixim,
+                        l_int32  sx,
+                        l_int32  sy,
+                        l_int32  thresh,
+                        l_int32  mincount,
+                        PIX    **ppixd)
+{
+l_int32    w, h, wd, hd, wim, him, wpls, wplim, wpld, wplf;
+l_int32    xim, yim, delx, nx, ny, i, j, k, m;
+l_int32    count, sum, val8;
+l_int32    empty, fgpixels;
+l_uint32  *datas, *dataim, *datad, *dataf, *lines, *lineim, *lined, *linef;
+l_float32  scalex, scaley;
+PIX       *pixd, *piximi, *pixb, *pixf, *pixims;
+
+    PROCNAME("pixGetBackgroundGrayMap");
+
+    if (!ppixd)
+        return ERROR_INT("&pixd not defined", procName, 1);
+    *ppixd = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (pixGetColormap(pixs))
+        return ERROR_INT("pixs is colormapped", procName, 1);
+    if (pixim && pixGetDepth(pixim) != 1)
+        return ERROR_INT("pixim not 1 bpp", procName, 1);
+    if (sx < 4 || sy < 4)
+        return ERROR_INT("sx and sy must be >= 4", procName, 1);
+    if (mincount > sx * sy) {
+        L_WARNING("mincount too large for tile size\n", procName);
+        mincount = (sx * sy) / 3;
+    }
+
+        /* Evaluate the 'image' mask, pixim, and make sure
+         * it is not all fg. */
+    fgpixels = 0;  /* boolean for existence of fg pixels in the image mask. */
+    if (pixim) {
+        piximi = pixInvert(NULL, pixim);  /* set non-'image' pixels to 1 */
+        pixZero(piximi, &empty);
+        pixDestroy(&piximi);
+        if (empty)
+            return ERROR_INT("pixim all fg; no background", procName, 1);
+        pixZero(pixim, &empty);
+        if (!empty)  /* there are fg pixels in pixim */
+            fgpixels = 1;
+    }
+
+        /* Generate the foreground mask, pixf, which is at
+         * full resolution.  These pixels will be ignored when
+         * computing the background values. */
+    pixb = pixThresholdToBinary(pixs, thresh);
+    pixf = pixMorphSequence(pixb, "d7.1 + d1.7", 0);
+    pixDestroy(&pixb);
+
+
+    /* ------------- Set up the output map pixd --------------- */
+        /* Generate pixd, which is reduced by the factors (sx, sy). */
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    wd = (w + sx - 1) / sx;
+    hd = (h + sy - 1) / sy;
+    pixd = pixCreate(wd, hd, 8);
+
+        /* Note: we only compute map values in tiles that are complete.
+         * In general, tiles at right and bottom edges will not be
+         * complete, and we must fill them in later. */
+    nx = w / sx;
+    ny = h / sy;
+    wpls = pixGetWpl(pixs);
+    datas = pixGetData(pixs);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    wplf = pixGetWpl(pixf);
+    dataf = pixGetData(pixf);
+    for (i = 0; i < ny; i++) {
+        lines = datas + sy * i * wpls;
+        linef = dataf + sy * i * wplf;
+        lined = datad + i * wpld;
+        for (j = 0; j < nx; j++) {
+            delx = j * sx;
+            sum = 0;
+            count = 0;
+            for (k = 0; k < sy; k++) {
+                for (m = 0; m < sx; m++) {
+                    if (GET_DATA_BIT(linef + k * wplf, delx + m) == 0) {
+                        sum += GET_DATA_BYTE(lines + k * wpls, delx + m);
+                        count++;
+                    }
+                }
+            }
+            if (count >= mincount) {
+                val8 = sum / count;
+                SET_DATA_BYTE(lined, j, val8);
+            }
+        }
+    }
+    pixDestroy(&pixf);
+
+        /* If there is an optional mask with fg pixels, erase the previous
+         * calculation for the corresponding map pixels, setting the
+         * map values to 0.   Then, when all the map holes are filled,
+         * these erased pixels will be set by the surrounding map values.
+         *
+         * The calculation here is relatively efficient: for each pixel
+         * in pixd (which corresponds to a tile of mask pixels in pixim)
+         * we look only at the pixel in pixim that is at the center
+         * of the tile.  If the mask pixel is ON, we reset the map
+         * pixel in pixd to 0, so that it can later be filled in. */
+    pixims = NULL;
+    if (pixim && fgpixels) {
+        wim = pixGetWidth(pixim);
+        him = pixGetHeight(pixim);
+        dataim = pixGetData(pixim);
+        wplim = pixGetWpl(pixim);
+        for (i = 0; i < ny; i++) {
+            yim = i * sy + sy / 2;
+            if (yim >= him)
+                break;
+            lineim = dataim + yim * wplim;
+            for (j = 0; j < nx; j++) {
+                xim = j * sx + sx / 2;
+                if (xim >= wim)
+                    break;
+                if (GET_DATA_BIT(lineim, xim))
+                    pixSetPixel(pixd, j, i, 0);
+            }
+        }
+    }
+
+        /* Fill all the holes in the map. */
+    if (pixFillMapHoles(pixd, nx, ny, L_FILL_BLACK)) {
+        pixDestroy(&pixd);
+        L_WARNING("can't make the map\n", procName);
+        return 1;
+    }
+
+        /* Finally, for each connected region corresponding to the
+         * 'image' mask, reset all pixels to their average value.
+         * Each of these components represents an image (or part of one)
+         * in the input, and this smooths the background values
+         * in each of these regions. */
+    if (pixim && fgpixels) {
+        scalex = 1. / (l_float32)sx;
+        scaley = 1. / (l_float32)sy;
+        pixims = pixScaleBySampling(pixim, scalex, scaley);
+        pixSmoothConnectedRegions(pixd, pixims, 2);
+        pixDestroy(&pixims);
+    }
+
+    *ppixd = pixd;
+    pixCopyResolution(*ppixd, pixs);
+    return 0;
+}
+
+
+/*!
+ *  pixGetBackgroundRGBMap()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null; it
+ *                     should not have all foreground pixels)
+ *              pixg (<optional> 8 bpp grayscale version; can be null)
+ *              sx, sy (tile size in pixels)
+ *              thresh (threshold for determining foreground)
+ *              mincount (min threshold on counts in a tile)
+ *              &pixmr, &pixmg, &pixmb (<return> rgb maps)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If pixg, which is a grayscale version of pixs, is provided,
+ *          use this internally to generate the foreground mask.
+ *          Otherwise, a grayscale version of pixs will be generated
+ *          from the green component only, used, and destroyed.
+ */
+l_int32
+pixGetBackgroundRGBMap(PIX     *pixs,
+                       PIX     *pixim,
+                       PIX     *pixg,
+                       l_int32  sx,
+                       l_int32  sy,
+                       l_int32  thresh,
+                       l_int32  mincount,
+                       PIX    **ppixmr,
+                       PIX    **ppixmg,
+                       PIX    **ppixmb)
+{
+l_int32    w, h, wm, hm, wim, him, wpls, wplim, wplf;
+l_int32    xim, yim, delx, nx, ny, i, j, k, m;
+l_int32    count, rsum, gsum, bsum, rval, gval, bval;
+l_int32    empty, fgpixels;
+l_uint32   pixel;
+l_uint32  *datas, *dataim, *dataf, *lines, *lineim, *linef;
+l_float32  scalex, scaley;
+PIX       *piximi, *pixgc, *pixb, *pixf, *pixims;
+PIX       *pixmr, *pixmg, *pixmb;
+
+    PROCNAME("pixGetBackgroundRGBMap");
+
+    if (!ppixmr || !ppixmg || !ppixmb)
+        return ERROR_INT("&pixm* not all defined", procName, 1);
+    *ppixmr = *ppixmg = *ppixmb = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not 32 bpp", procName, 1);
+    if (pixim && pixGetDepth(pixim) != 1)
+        return ERROR_INT("pixim not 1 bpp", procName, 1);
+    if (sx < 4 || sy < 4)
+        return ERROR_INT("sx and sy must be >= 4", procName, 1);
+    if (mincount > sx * sy) {
+        L_WARNING("mincount too large for tile size\n", procName);
+        mincount = (sx * sy) / 3;
+    }
+
+        /* Evaluate the mask pixim and make sure it is not all foreground */
+    fgpixels = 0;  /* boolean for existence of fg mask pixels */
+    if (pixim) {
+        piximi = pixInvert(NULL, pixim);  /* set non-'image' pixels to 1 */
+        pixZero(piximi, &empty);
+        pixDestroy(&piximi);
+        if (empty)
+            return ERROR_INT("pixim all fg; no background", procName, 1);
+        pixZero(pixim, &empty);
+        if (!empty)  /* there are fg pixels in pixim */
+            fgpixels = 1;
+    }
+
+        /* Generate the foreground mask.  These pixels will be
+         * ignored when computing the background values. */
+    if (pixg)  /* use the input grayscale version if it is provided */
+        pixgc = pixClone(pixg);
+    else
+        pixgc = pixConvertRGBToGrayFast(pixs);
+    pixb = pixThresholdToBinary(pixgc, thresh);
+    pixf = pixMorphSequence(pixb, "d7.1 + d1.7", 0);
+    pixDestroy(&pixgc);
+    pixDestroy(&pixb);
+
+        /* Generate the output mask images */
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    wm = (w + sx - 1) / sx;
+    hm = (h + sy - 1) / sy;
+    pixmr = pixCreate(wm, hm, 8);
+    pixmg = pixCreate(wm, hm, 8);
+    pixmb = pixCreate(wm, hm, 8);
+
+    /* ------------- Set up the mapping images --------------- */
+        /* Note: we only compute map values in tiles that are complete.
+         * In general, tiles at right and bottom edges will not be
+         * complete, and we must fill them in later. */
+    nx = w / sx;
+    ny = h / sy;
+    wpls = pixGetWpl(pixs);
+    datas = pixGetData(pixs);
+    wplf = pixGetWpl(pixf);
+    dataf = pixGetData(pixf);
+    for (i = 0; i < ny; i++) {
+        lines = datas + sy * i * wpls;
+        linef = dataf + sy * i * wplf;
+        for (j = 0; j < nx; j++) {
+            delx = j * sx;
+            rsum = gsum = bsum = 0;
+            count = 0;
+            for (k = 0; k < sy; k++) {
+                for (m = 0; m < sx; m++) {
+                    if (GET_DATA_BIT(linef + k * wplf, delx + m) == 0) {
+                        pixel = *(lines + k * wpls + delx + m);
+                        rsum += (pixel >> 24);
+                        gsum += ((pixel >> 16) & 0xff);
+                        bsum += ((pixel >> 8) & 0xff);
+                        count++;
+                    }
+                }
+            }
+            if (count >= mincount) {
+                rval = rsum / count;
+                gval = gsum / count;
+                bval = bsum / count;
+                pixSetPixel(pixmr, j, i, rval);
+                pixSetPixel(pixmg, j, i, gval);
+                pixSetPixel(pixmb, j, i, bval);
+            }
+        }
+    }
+    pixDestroy(&pixf);
+
+        /* If there is an optional mask with fg pixels, erase the previous
+         * calculation for the corresponding map pixels, setting the
+         * map values in each of the 3 color maps to 0.   Then, when
+         * all the map holes are filled, these erased pixels will
+         * be set by the surrounding map values. */
+    if (pixim) {
+        wim = pixGetWidth(pixim);
+        him = pixGetHeight(pixim);
+        dataim = pixGetData(pixim);
+        wplim = pixGetWpl(pixim);
+        for (i = 0; i < ny; i++) {
+            yim = i * sy + sy / 2;
+            if (yim >= him)
+                break;
+            lineim = dataim + yim * wplim;
+            for (j = 0; j < nx; j++) {
+                xim = j * sx + sx / 2;
+                if (xim >= wim)
+                    break;
+                if (GET_DATA_BIT(lineim, xim)) {
+                    pixSetPixel(pixmr, j, i, 0);
+                    pixSetPixel(pixmg, j, i, 0);
+                    pixSetPixel(pixmb, j, i, 0);
+                }
+            }
+        }
+    }
+
+    /* ----------------- Now fill in the holes ----------------------- */
+    if (pixFillMapHoles(pixmr, nx, ny, L_FILL_BLACK) ||
+        pixFillMapHoles(pixmg, nx, ny, L_FILL_BLACK) ||
+        pixFillMapHoles(pixmb, nx, ny, L_FILL_BLACK)) {
+        pixDestroy(&pixmr);
+        pixDestroy(&pixmg);
+        pixDestroy(&pixmb);
+        L_WARNING("can't make the maps\n", procName);
+        return 1;
+    }
+
+        /* Finally, for each connected region corresponding to the
+         * fg mask, reset all pixels to their average value. */
+    if (pixim && fgpixels) {
+        scalex = 1. / (l_float32)sx;
+        scaley = 1. / (l_float32)sy;
+        pixims = pixScaleBySampling(pixim, scalex, scaley);
+        pixSmoothConnectedRegions(pixmr, pixims, 2);
+        pixSmoothConnectedRegions(pixmg, pixims, 2);
+        pixSmoothConnectedRegions(pixmb, pixims, 2);
+        pixDestroy(&pixims);
+    }
+
+    *ppixmr = pixmr;
+    *ppixmg = pixmg;
+    *ppixmb = pixmb;
+    pixCopyResolution(*ppixmr, pixs);
+    pixCopyResolution(*ppixmg, pixs);
+    pixCopyResolution(*ppixmb, pixs);
+    return 0;
+}
+
+
+/*!
+ *  pixGetBackgroundGrayMapMorph()
+ *
+ *      Input:  pixs (8 bpp grayscale; not cmapped)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null; it
+ *                     should not have all foreground pixels)
+ *              reduction (factor at which closing is performed)
+ *              size (of square Sel for the closing; use an odd number)
+ *              &pixm (<return> grayscale map)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixGetBackgroundGrayMapMorph(PIX     *pixs,
+                             PIX     *pixim,
+                             l_int32  reduction,
+                             l_int32  size,
+                             PIX    **ppixm)
+{
+l_int32    nx, ny, empty, fgpixels;
+l_float32  scale;
+PIX       *pixm, *pixt1, *pixt2, *pixt3, *pixims;
+
+    PROCNAME("pixGetBackgroundGrayMapMorph");
+
+    if (!ppixm)
+        return ERROR_INT("&pixm not defined", procName, 1);
+    *ppixm = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (pixGetColormap(pixs))
+        return ERROR_INT("pixs is colormapped", procName, 1);
+    if (pixim && pixGetDepth(pixim) != 1)
+        return ERROR_INT("pixim not 1 bpp", procName, 1);
+
+        /* Evaluate the mask pixim and make sure it is not all foreground. */
+    fgpixels = 0;  /* boolean for existence of fg mask pixels */
+    if (pixim) {
+        pixInvert(pixim, pixim);  /* set background pixels to 1 */
+        pixZero(pixim, &empty);
+        if (empty)
+            return ERROR_INT("pixim all fg; no background", procName, 1);
+        pixInvert(pixim, pixim);  /* revert to original mask */
+        pixZero(pixim, &empty);
+        if (!empty)  /* there are fg pixels in pixim */
+            fgpixels = 1;
+    }
+
+        /* Downscale as requested and do the closing to get the background. */
+    scale = 1. / (l_float32)reduction;
+    pixt1 = pixScaleBySampling(pixs, scale, scale);
+    pixt2 = pixCloseGray(pixt1, size, size);
+    pixt3 = pixExtendByReplication(pixt2, 1, 1);
+
+        /* Downscale the image mask, if any, and remove it from the
+         * background.  These pixels will be filled in (twice). */
+    pixims = NULL;
+    if (pixim) {
+        pixims = pixScale(pixim, scale, scale);
+        pixm = pixConvertTo8(pixims, FALSE);
+        pixAnd(pixm, pixm, pixt3);
+    }
+    else
+        pixm = pixClone(pixt3);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+
+        /* Fill all the holes in the map. */
+    nx = pixGetWidth(pixs) / reduction;
+    ny = pixGetHeight(pixs) / reduction;
+    if (pixFillMapHoles(pixm, nx, ny, L_FILL_BLACK)) {
+        pixDestroy(&pixm);
+        L_WARNING("can't make the map\n", procName);
+        return 1;
+    }
+
+        /* Finally, for each connected region corresponding to the
+         * fg mask, reset all pixels to their average value. */
+    if (pixim && fgpixels) {
+        pixSmoothConnectedRegions(pixm, pixims, 2);
+        pixDestroy(&pixims);
+    }
+
+    *ppixm = pixm;
+    pixCopyResolution(*ppixm, pixs);
+    return 0;
+}
+
+
+/*!
+ *  pixGetBackgroundRGBMapMorph()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null; it
+ *                     should not have all foreground pixels)
+ *              reduction (factor at which closing is performed)
+ *              size (of square Sel for the closing; use an odd number)
+ *              &pixmr (<return> red component map)
+ *              &pixmg (<return> green component map)
+ *              &pixmb (<return> blue component map)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixGetBackgroundRGBMapMorph(PIX     *pixs,
+                            PIX     *pixim,
+                            l_int32  reduction,
+                            l_int32  size,
+                            PIX    **ppixmr,
+                            PIX    **ppixmg,
+                            PIX    **ppixmb)
+{
+l_int32    nx, ny, empty, fgpixels;
+l_float32  scale;
+PIX       *pixm, *pixmr, *pixmg, *pixmb, *pixt1, *pixt2, *pixt3, *pixims;
+
+    PROCNAME("pixGetBackgroundRGBMapMorph");
+
+    if (!ppixmr || !ppixmg || !ppixmb)
+        return ERROR_INT("&pixm* not all defined", procName, 1);
+    *ppixmr = *ppixmg = *ppixmb = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not 32 bpp", procName, 1);
+    if (pixim && pixGetDepth(pixim) != 1)
+        return ERROR_INT("pixim not 1 bpp", procName, 1);
+
+        /* Generate an 8 bpp version of the image mask, if it exists */
+    scale = 1. / (l_float32)reduction;
+    pixm = NULL;
+    if (pixim) {
+        pixims = pixScale(pixim, scale, scale);
+        pixm = pixConvertTo8(pixims, FALSE);
+    }
+
+        /* Evaluate the mask pixim and make sure it is not all foreground. */
+    fgpixels = 0;  /* boolean for existence of fg mask pixels */
+    if (pixim) {
+        pixInvert(pixim, pixim);  /* set background pixels to 1 */
+        pixZero(pixim, &empty);
+        if (empty)
+            return ERROR_INT("pixim all fg; no background", procName, 1);
+        pixInvert(pixim, pixim);  /* revert to original mask */
+        pixZero(pixim, &empty);
+        if (!empty)  /* there are fg pixels in pixim */
+            fgpixels = 1;
+    }
+
+        /* Downscale as requested and do the closing to get the background.
+         * Then remove the image mask pixels from the background.  They
+         * will be filled in (twice) later.  Do this for all 3 components. */
+    pixt1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_RED);
+    pixt2 = pixCloseGray(pixt1, size, size);
+    pixt3 = pixExtendByReplication(pixt2, 1, 1);
+    if (pixim)
+        pixmr = pixAnd(NULL, pixm, pixt3);
+    else
+        pixmr = pixClone(pixt3);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+
+    pixt1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_GREEN);
+    pixt2 = pixCloseGray(pixt1, size, size);
+    pixt3 = pixExtendByReplication(pixt2, 1, 1);
+    if (pixim)
+        pixmg = pixAnd(NULL, pixm, pixt3);
+    else
+        pixmg = pixClone(pixt3);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+
+    pixt1 = pixScaleRGBToGrayFast(pixs, reduction, COLOR_BLUE);
+    pixt2 = pixCloseGray(pixt1, size, size);
+    pixt3 = pixExtendByReplication(pixt2, 1, 1);
+    if (pixim)
+        pixmb = pixAnd(NULL, pixm, pixt3);
+    else
+        pixmb = pixClone(pixt3);
+    pixDestroy(&pixm);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+
+        /* Fill all the holes in the three maps. */
+    nx = pixGetWidth(pixs) / reduction;
+    ny = pixGetHeight(pixs) / reduction;
+    if (pixFillMapHoles(pixmr, nx, ny, L_FILL_BLACK) ||
+        pixFillMapHoles(pixmg, nx, ny, L_FILL_BLACK) ||
+        pixFillMapHoles(pixmb, nx, ny, L_FILL_BLACK)) {
+        pixDestroy(&pixmr);
+        pixDestroy(&pixmg);
+        pixDestroy(&pixmb);
+        L_WARNING("can't make the maps\n", procName);
+        return 1;
+    }
+
+        /* Finally, for each connected region corresponding to the
+         * fg mask in each component, reset all pixels to their
+         * average value. */
+    if (pixim && fgpixels) {
+        pixSmoothConnectedRegions(pixmr, pixims, 2);
+        pixSmoothConnectedRegions(pixmg, pixims, 2);
+        pixSmoothConnectedRegions(pixmb, pixims, 2);
+        pixDestroy(&pixims);
+    }
+
+    *ppixmr = pixmr;
+    *ppixmg = pixmg;
+    *ppixmb = pixmb;
+    pixCopyResolution(*ppixmr, pixs);
+    pixCopyResolution(*ppixmg, pixs);
+    pixCopyResolution(*ppixmb, pixs);
+    return 0;
+}
+
+
+/*!
+ *  pixFillMapHoles()
+ *
+ *      Input:  pix (8 bpp; a map, with one pixel for each tile in
+ *              a larger image)
+ *              nx (number of horizontal pixel tiles that are entirely
+ *                  covered with pixels in the original source image)
+ *              ny (ditto for the number of vertical pixel tiles)
+ *              filltype (L_FILL_WHITE or L_FILL_BLACK)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place operation on pix (the map).  pix is
+ *          typically a low-resolution version of some other image
+ *          from which it was derived, where each pixel in pix
+ *          corresponds to a rectangular tile (say, m x n) of pixels
+ *          in the larger image.  All we need to know about the larger
+ *          image is whether or not the rightmost column and bottommost
+ *          row of pixels in pix correspond to tiles that are
+ *          only partially covered by pixels in the larger image.
+ *      (2) Typically, some number of pixels in the input map are
+ *          not known, and their values must be determined by near
+ *          pixels that are known.  These unknown pixels are the 'holes'.
+ *          They can take on only two values, 0 and 255, and the
+ *          instruction about which to fill is given by the filltype flag.
+ *      (3) The "holes" can come from two sources.  The first is when there
+ *          are not enough foreground or background pixels in a tile;
+ *          the second is when a tile is at least partially covered
+ *          by an image mask.  If we're filling holes in a fg mask,
+ *          the holes are initialized to black (0) and use L_FILL_BLACK.
+ *          For filling holes in a bg mask, initialize the holes to
+ *          white (255) and use L_FILL_WHITE.
+ *      (4) If w is the map width, nx = w or nx = w - 1; ditto for h and ny.
+ */
+l_int32
+pixFillMapHoles(PIX     *pix,
+                l_int32  nx,
+                l_int32  ny,
+                l_int32  filltype)
+{
+l_int32   w, h, y, nmiss, goodcol, i, j, found, ival, valtest;
+l_uint32  val, lastval;
+NUMA     *na;  /* indicates if there is any data in the column */
+PIX      *pixt;
+
+    PROCNAME("pixFillMapHoles");
+
+    if (!pix || pixGetDepth(pix) != 8)
+        return ERROR_INT("pix not defined or not 8 bpp", procName, 1);
+    if (pixGetColormap(pix))
+        return ERROR_INT("pix is colormapped", procName, 1);
+
+    /* ------------- Fill holes in the mapping image columns ----------- */
+    pixGetDimensions(pix, &w, &h, NULL);
+    na = numaCreate(0);  /* holds flag for which columns have data */
+    nmiss = 0;
+    valtest = (filltype == L_FILL_WHITE) ? 255 : 0;
+    for (j = 0; j < nx; j++) {  /* do it by columns */
+        found = FALSE;
+        for (i = 0; i < ny; i++) {
+            pixGetPixel(pix, j, i, &val);
+            if (val != valtest) {
+                y = i;
+                found = TRUE;
+                break;
+            }
+        }
+        if (found == FALSE) {
+            numaAddNumber(na, 0);  /* no data in the column */
+            nmiss++;
+        }
+        else {
+            numaAddNumber(na, 1);  /* data in the column */
+            for (i = y - 1; i >= 0; i--)  /* replicate upwards to top */
+                pixSetPixel(pix, j, i, val);
+            pixGetPixel(pix, j, 0, &lastval);
+            for (i = 1; i < h; i++) {  /* set going down to bottom */
+                pixGetPixel(pix, j, i, &val);
+                if (val == valtest)
+                    pixSetPixel(pix, j, i, lastval);
+                else
+                    lastval = val;
+            }
+        }
+    }
+    numaAddNumber(na, 0);  /* last column */
+
+    if (nmiss == nx) {  /* no data in any column! */
+        numaDestroy(&na);
+        L_WARNING("no bg found; no data in any column\n", procName);
+        return 1;
+    }
+
+    /* ---------- Fill in missing columns by replication ----------- */
+    if (nmiss > 0) {  /* replicate columns */
+        pixt = pixCopy(NULL, pix);
+            /* Find the first good column */
+        goodcol = 0;
+        for (j = 0; j < w; j++) {
+            numaGetIValue(na, j, &ival);
+            if (ival == 1) {
+                goodcol = j;
+                break;
+            }
+        }
+        if (goodcol > 0) {  /* copy cols backward */
+            for (j = goodcol - 1; j >= 0; j--) {
+                pixRasterop(pix, j, 0, 1, h, PIX_SRC, pixt, j + 1, 0);
+                pixRasterop(pixt, j, 0, 1, h, PIX_SRC, pix, j, 0);
+            }
+        }
+        for (j = goodcol + 1; j < w; j++) {   /* copy cols forward */
+            numaGetIValue(na, j, &ival);
+            if (ival == 0) {
+                    /* Copy the column to the left of j */
+                pixRasterop(pix, j, 0, 1, h, PIX_SRC, pixt, j - 1, 0);
+                pixRasterop(pixt, j, 0, 1, h, PIX_SRC, pix, j, 0);
+            }
+        }
+        pixDestroy(&pixt);
+    }
+    if (w > nx) {  /* replicate the last column */
+        for (i = 0; i < h; i++) {
+            pixGetPixel(pix, w - 2, i, &val);
+            pixSetPixel(pix, w - 1, i, val);
+        }
+    }
+
+    numaDestroy(&na);
+    return 0;
+}
+
+
+/*!
+ *  pixExtendByReplication()
+ *
+ *      Input:  pixs (8 bpp)
+ *              addw (number of extra pixels horizontally to add)
+ *              addh (number of extra pixels vertically to add)
+ *      Return: pixd (extended with replicated pixel values), or null on error
+ *
+ *  Notes:
+ *      (1) The pixel values are extended to the left and down, as required.
+ */
+PIX *
+pixExtendByReplication(PIX     *pixs,
+                       l_int32  addw,
+                       l_int32  addh)
+{
+l_int32   w, h, i, j;
+l_uint32  val;
+PIX      *pixd;
+
+    PROCNAME("pixExtendByReplication");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+
+    if (addw == 0 && addh == 0)
+        return pixCopy(NULL, pixs);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w + addw, h + addh, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0);
+
+    if (addw > 0) {
+        for (i = 0; i < h; i++) {
+            pixGetPixel(pixd, w - 1, i, &val);
+            for (j = 0; j < addw; j++)
+                pixSetPixel(pixd, w + j, i, val);
+        }
+    }
+
+    if (addh > 0) {
+        for (j = 0; j < w + addw; j++) {
+            pixGetPixel(pixd, j, h - 1, &val);
+            for (i = 0; i < addh; i++)
+                pixSetPixel(pixd, j, h + i, val);
+        }
+    }
+
+    pixCopyResolution(pixd, pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixSmoothConnectedRegions()
+ *
+ *      Input:  pixs (8 bpp grayscale; no colormap)
+ *              pixm (<optional> 1 bpp; if null, this is a no-op)
+ *              factor (subsampling factor for getting average; >= 1)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The pixels in pixs corresponding to those in each
+ *          8-connected region in the mask are set to the average value.
+ *      (2) This is required for adaptive mapping to avoid the
+ *          generation of stripes in the background map, due to
+ *          variations in the pixel values near the edges of mask regions.
+ *      (3) This function is optimized for background smoothing, where
+ *          there are a relatively small number of components.  It will
+ *          be inefficient if used where there are many small components.
+ */
+l_int32
+pixSmoothConnectedRegions(PIX     *pixs,
+                          PIX     *pixm,
+                          l_int32  factor)
+{
+l_int32    empty, i, n, x, y;
+l_float32  aveval;
+BOXA      *boxa;
+PIX       *pixmc;
+PIXA      *pixa;
+
+    PROCNAME("pixSmoothConnectedRegions");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (pixGetColormap(pixs))
+        return ERROR_INT("pixs has colormap", procName, 1);
+    if (!pixm) {
+        L_INFO("pixm not defined\n", procName);
+        return 0;
+    }
+    if (pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+    pixZero(pixm, &empty);
+    if (empty) {
+        L_INFO("pixm has no fg pixels; nothing to do\n", procName);
+        return 0;
+    }
+
+    boxa = pixConnComp(pixm, &pixa, 8);
+    n = boxaGetCount(boxa);
+    for (i = 0; i < n; i++) {
+        if ((pixmc = pixaGetPix(pixa, i, L_CLONE)) == NULL) {
+            L_WARNING("missing pixmc!\n", procName);
+            continue;
+        }
+        boxaGetBoxGeometry(boxa, i, &x, &y, NULL, NULL);
+        pixGetAverageMasked(pixs, pixmc, x, y, factor, L_MEAN_ABSVAL, &aveval);
+        pixPaintThroughMask(pixs, pixmc, x, y, (l_int32)aveval);
+        pixDestroy(&pixmc);
+    }
+
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixa);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *                 Measurement of local foreground                  *
+ *------------------------------------------------------------------*/
+#if 0    /* Not working properly: do not use */
+
+/*!
+ *  pixGetForegroundGrayMap()
+ *
+ *      Input:  pixs (8 bpp)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null)
+ *              sx, sy (src tile size, in pixels)
+ *              thresh (threshold for determining foreground)
+ *              &pixd (<return> 8 bpp grayscale map)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Each (sx, sy) tile of pixs gets mapped to one pixel in pixd.
+ *      (2) pixd is the estimate of the fg (darkest) value within each tile.
+ *      (3) All pixels in pixd that are in 'image' regions, as specified
+ *          by pixim, are given the background value 0.
+ *      (4) For pixels in pixd that can't directly be given a fg value,
+ *          the value is inferred by propagating from neighboring pixels.
+ *      (5) In practice, pixd can be used to normalize the fg, and
+ *          it can be done after background normalization.
+ *      (6) The overall procedure is:
+ *            - reduce 2x by sampling
+ *            - paint all 'image' pixels white, so that they don't
+ *              participate in the Min reduction
+ *            - do a further (sx, sy) Min reduction -- think of
+ *              it as a large opening followed by subsampling by the
+ *              reduction factors
+ *            - threshold the result to identify fg, and set the
+ *              bg pixels to 255 (these are 'holes')
+ *            - fill holes by propagation from fg values
+ *            - replicatively expand by 2x, arriving at the final
+ *              resolution of pixd
+ *            - smooth with a 17x17 kernel
+ *            - paint the 'image' regions black
+ */
+l_int32
+pixGetForegroundGrayMap(PIX     *pixs,
+                        PIX     *pixim,
+                        l_int32  sx,
+                        l_int32  sy,
+                        l_int32  thresh,
+                        PIX    **ppixd)
+{
+l_int32  w, h, d, wd, hd;
+l_int32  empty, fgpixels;
+PIX     *pixd, *piximi, *pixim2, *pixims, *pixs2, *pixb, *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixGetForegroundGrayMap");
+
+    if (!ppixd)
+        return ERROR_INT("&pixd not defined", procName, 1);
+    *ppixd = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return ERROR_INT("pixs not 8 bpp", procName, 1);
+    if (pixim && pixGetDepth(pixim) != 1)
+        return ERROR_INT("pixim not 1 bpp", procName, 1);
+    if (sx < 2 || sy < 2)
+        return ERROR_INT("sx and sy must be >= 2", procName, 1);
+
+        /* Generate pixd, which is reduced by the factors (sx, sy). */
+    wd = (w + sx - 1) / sx;
+    hd = (h + sy - 1) / sy;
+    pixd = pixCreate(wd, hd, 8);
+    *ppixd = pixd;
+
+        /* Evaluate the 'image' mask, pixim.  If it is all fg,
+         * the output pixd has all pixels with value 0. */
+    fgpixels = 0;  /* boolean for existence of fg pixels in the image mask. */
+    if (pixim) {
+        piximi = pixInvert(NULL, pixim);  /* set non-image pixels to 1 */
+        pixZero(piximi, &empty);
+        pixDestroy(&piximi);
+        if (empty)  /* all 'image'; return with all pixels set to 0 */
+            return 0;
+        pixZero(pixim, &empty);
+        if (!empty)  /* there are fg pixels in pixim */
+            fgpixels = 1;
+    }
+
+        /* 2x subsampling; paint white through 'image' mask. */
+    pixs2 = pixScaleBySampling(pixs, 0.5, 0.5);
+    if (pixim && fgpixels) {
+        pixim2 = pixReduceBinary2(pixim, NULL);
+        pixPaintThroughMask(pixs2, pixim2, 0, 0, 255);
+        pixDestroy(&pixim2);
+    }
+
+        /* Min (erosion) downscaling; total reduction (4 sx, 4 sy). */
+    pixt1 = pixScaleGrayMinMax(pixs2, sx, sy, L_CHOOSE_MIN);
+
+/*    pixDisplay(pixt1, 300, 200); */
+
+        /* Threshold to identify fg; paint bg pixels to white. */
+    pixb = pixThresholdToBinary(pixt1, thresh);  /* fg pixels */
+    pixInvert(pixb, pixb);
+    pixPaintThroughMask(pixt1, pixb, 0, 0, 255);
+    pixDestroy(&pixb);
+
+        /* Replicative expansion by 2x to (sx, sy). */
+    pixt2 = pixExpandReplicate(pixt1, 2);
+
+/*    pixDisplay(pixt2, 500, 200); */
+
+        /* Fill holes in the fg by propagation */
+    pixFillMapHoles(pixt2, w / sx, h / sy, L_FILL_WHITE);
+
+/*    pixDisplay(pixt2, 700, 200); */
+
+        /* Smooth with 17x17 kernel. */
+    pixt3 = pixBlockconv(pixt2, 8, 8);
+    pixRasterop(pixd, 0, 0, wd, hd, PIX_SRC, pixt3, 0, 0);
+
+        /* Paint the image parts black. */
+    pixims = pixScaleBySampling(pixim, 1. / sx, 1. / sy);
+    pixPaintThroughMask(pixd, pixims, 0, 0, 0);
+
+    pixDestroy(&pixs2);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+    return 0;
+}
+#endif   /* Not working properly: do not use */
+
+
+/*------------------------------------------------------------------*
+ *                  Generate inverted background map                *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixGetInvBackgroundMap()
+ *
+ *      Input:  pixs (8 bpp grayscale; no colormap)
+ *              bgval (target bg val; typ. > 128)
+ *              smoothx (half-width of block convolution kernel width)
+ *              smoothy (half-width of block convolution kernel height)
+ *      Return: pixd (16 bpp), or null on error
+ *
+ *  Note:
+ *     - bgval should typically be > 120 and < 240
+ *     - pixd is a normalization image; the original image is
+ *       multiplied by pixd and the result is divided by 256.
+ */
+PIX *
+pixGetInvBackgroundMap(PIX     *pixs,
+                       l_int32  bgval,
+                       l_int32  smoothx,
+                       l_int32  smoothy)
+{
+l_int32    w, h, wplsm, wpld, i, j;
+l_int32    val, val16;
+l_uint32  *datasm, *datad, *linesm, *lined;
+PIX       *pixsm, *pixd;
+
+    PROCNAME("pixGetInvBackgroundMap");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (w < 5 || h < 5)
+        return (PIX *)ERROR_PTR("w and h must be >= 5", procName, NULL);
+
+        /* smooth the map image */
+    pixsm = pixBlockconv(pixs, smoothx, smoothy);
+    datasm = pixGetData(pixsm);
+    wplsm = pixGetWpl(pixsm);
+
+        /* invert the map image, scaling up to preserve dynamic range */
+    pixd = pixCreate(w, h, 16);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        linesm = datasm + i * wplsm;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(linesm, j);
+            if (val > 0)
+                val16 = (256 * bgval) / val;
+            else {  /* shouldn't happen */
+                L_WARNING("smoothed bg has 0 pixel!\n", procName);
+                val16 = bgval / 2;
+            }
+            SET_DATA_TWO_BYTES(lined, j, val16);
+        }
+    }
+
+    pixDestroy(&pixsm);
+    pixCopyResolution(pixd, pixs);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                    Apply background map to image                 *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixApplyInvBackgroundGrayMap()
+ *
+ *      Input:  pixs (8 bpp grayscale; no colormap)
+ *              pixm (16 bpp, inverse background map)
+ *              sx (tile width in pixels)
+ *              sy (tile height in pixels)
+ *      Return: pixd (8 bpp), or null on error
+ */
+PIX *
+pixApplyInvBackgroundGrayMap(PIX     *pixs,
+                             PIX     *pixm,
+                             l_int32  sx,
+                             l_int32  sy)
+{
+l_int32    w, h, wm, hm, wpls, wpld, i, j, k, m, xoff, yoff;
+l_int32    vals, vald;
+l_uint32   val16;
+l_uint32  *datas, *datad, *lines, *lined, *flines, *flined;
+PIX       *pixd;
+
+    PROCNAME("pixApplyInvBackgroundGrayMap");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
+    if (!pixm || pixGetDepth(pixm) != 16)
+        return (PIX *)ERROR_PTR("pixm undefined or not 16 bpp", procName, NULL);
+    if (sx == 0 || sy == 0)
+        return (PIX *)ERROR_PTR("invalid sx and/or sy", procName, NULL);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixGetDimensions(pixm, &wm, &hm, NULL);
+    pixd = pixCreateTemplate(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < hm; i++) {
+        lines = datas + sy * i * wpls;
+        lined = datad + sy * i * wpld;
+        yoff = sy * i;
+        for (j = 0; j < wm; j++) {
+            pixGetPixel(pixm, j, i, &val16);
+            xoff = sx * j;
+            for (k = 0; k < sy && yoff + k < h; k++) {
+                flines = lines + k * wpls;
+                flined = lined + k * wpld;
+                for (m = 0; m < sx && xoff + m < w; m++) {
+                    vals = GET_DATA_BYTE(flines, xoff + m);
+                    vald = (vals * val16) / 256;
+                    vald = L_MIN(vald, 255);
+                    SET_DATA_BYTE(flined, xoff + m, vald);
+                }
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixApplyInvBackgroundRGBMap()
+ *
+ *      Input:  pixs (32 bpp rbg)
+ *              pixmr (16 bpp, red inverse background map)
+ *              pixmg (16 bpp, green inverse background map)
+ *              pixmb (16 bpp, blue inverse background map)
+ *              sx (tile width in pixels)
+ *              sy (tile height in pixels)
+ *      Return: pixd (32 bpp rbg), or null on error
+ */
+PIX *
+pixApplyInvBackgroundRGBMap(PIX     *pixs,
+                            PIX     *pixmr,
+                            PIX     *pixmg,
+                            PIX     *pixmb,
+                            l_int32  sx,
+                            l_int32  sy)
+{
+l_int32    w, h, wm, hm, wpls, wpld, i, j, k, m, xoff, yoff;
+l_int32    rvald, gvald, bvald;
+l_uint32   vals;
+l_uint32   rval16, gval16, bval16;
+l_uint32  *datas, *datad, *lines, *lined, *flines, *flined;
+PIX       *pixd;
+
+    PROCNAME("pixApplyInvBackgroundRGBMap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (!pixmr || !pixmg || !pixmb)
+        return (PIX *)ERROR_PTR("pix maps not all defined", procName, NULL);
+    if (pixGetDepth(pixmr) != 16 || pixGetDepth(pixmg) != 16 ||
+        pixGetDepth(pixmb) != 16)
+        return (PIX *)ERROR_PTR("pix maps not all 16 bpp", procName, NULL);
+    if (sx == 0 || sy == 0)
+        return (PIX *)ERROR_PTR("invalid sx and/or sy", procName, NULL);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    wm = pixGetWidth(pixmr);
+    hm = pixGetHeight(pixmr);
+    pixd = pixCreateTemplate(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < hm; i++) {
+        lines = datas + sy * i * wpls;
+        lined = datad + sy * i * wpld;
+        yoff = sy * i;
+        for (j = 0; j < wm; j++) {
+            pixGetPixel(pixmr, j, i, &rval16);
+            pixGetPixel(pixmg, j, i, &gval16);
+            pixGetPixel(pixmb, j, i, &bval16);
+            xoff = sx * j;
+            for (k = 0; k < sy && yoff + k < h; k++) {
+                flines = lines + k * wpls;
+                flined = lined + k * wpld;
+                for (m = 0; m < sx && xoff + m < w; m++) {
+                    vals = *(flines + xoff + m);
+                    rvald = ((vals >> 24) * rval16) / 256;
+                    rvald = L_MIN(rvald, 255);
+                    gvald = (((vals >> 16) & 0xff) * gval16) / 256;
+                    gvald = L_MIN(gvald, 255);
+                    bvald = (((vals >> 8) & 0xff) * bval16) / 256;
+                    bvald = L_MIN(bvald, 255);
+                    composeRGBPixel(rvald, gvald, bvald, flined + xoff + m);
+                }
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                         Apply variable map                       *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixApplyVariableGrayMap()
+ *
+ *      Input:  pixs (8 bpp)
+ *              pixg (8 bpp, variable map)
+ *              target (typ. 128 for threshold)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) Suppose you have an image that you want to transform based
+ *          on some photometric measurement at each point, such as the
+ *          threshold value for binarization.  Representing the photometric
+ *          measurement as an image pixg, you can threshold in input image
+ *          using pixVarThresholdToBinary().  Alternatively, you can map
+ *          the input image pointwise so that the threshold over the
+ *          entire image becomes a constant, such as 128.  For example,
+ *          if a pixel in pixg is 150 and the target is 128, the
+ *          corresponding pixel in pixs is mapped linearly to a value
+ *          (128/150) of the input value.  If the resulting mapped image
+ *          pixd were then thresholded at 128, you would obtain the
+ *          same result as a direct binarization using pixg with
+ *          pixVarThresholdToBinary().
+ *      (2) The sizes of pixs and pixg must be equal.
+ */
+PIX *
+pixApplyVariableGrayMap(PIX     *pixs,
+                        PIX     *pixg,
+                        l_int32  target)
+{
+l_int32    i, j, w, h, d, wpls, wplg, wpld, vals, valg, vald;
+l_uint8   *lut;
+l_uint32  *datas, *datag, *datad, *lines, *lineg, *lined;
+l_float32  fval;
+PIX       *pixd;
+
+    PROCNAME("pixApplyVariableGrayMap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!pixg)
+        return (PIX *)ERROR_PTR("pixg not defined", procName, NULL);
+    if (!pixSizesEqual(pixs, pixg))
+        return (PIX *)ERROR_PTR("pix sizes not equal", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("depth not 8 bpp", procName, NULL);
+
+        /* Generate a LUT for the mapping if the image is large enough
+         * to warrant the overhead.  The LUT is of size 2^16.  For the
+         * index to the table, get the MSB from pixs and the LSB from pixg.
+         * Note: this LUT is bigger than the typical 32K L1 cache, so
+         * we expect cache misses.  L2 latencies are about 5ns.  But
+         * division is slooooow.  For large images, this function is about
+         * 4x faster when using the LUT.  C'est la vie.  */
+    lut = NULL;
+    if (w * h > 100000) {  /* more pixels than 2^16 */
+        if ((lut = (l_uint8 *)LEPT_CALLOC(0x10000, sizeof(l_uint8))) == NULL)
+            return (PIX *)ERROR_PTR("lut not made", procName, NULL);
+        for (i = 0; i < 256; i++) {
+            for (j = 0; j < 256; j++) {
+                fval = (l_float32)(i * target) / (j + 0.5);
+                lut[(i << 8) + j] = L_MIN(255, (l_int32)(fval + 0.5));
+            }
+        }
+    }
+
+    pixd = pixCreateNoInit(w, h, 8);
+    pixCopyResolution(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datag = pixGetData(pixg);
+    wplg = pixGetWpl(pixg);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lineg = datag + i * wplg;
+        lined = datad + i * wpld;
+        if (lut) {
+            for (j = 0; j < w; j++) {
+                vals = GET_DATA_BYTE(lines, j);
+                valg = GET_DATA_BYTE(lineg, j);
+                vald = lut[(vals << 8) + valg];
+                SET_DATA_BYTE(lined, j, vald);
+            }
+        }
+        else {
+            for (j = 0; j < w; j++) {
+                vals = GET_DATA_BYTE(lines, j);
+                valg = GET_DATA_BYTE(lineg, j);
+                fval = (l_float32)(vals * target) / (valg + 0.5);
+                vald = L_MIN(255, (l_int32)(fval + 0.5));
+                SET_DATA_BYTE(lined, j, vald);
+            }
+        }
+    }
+
+    if (lut) LEPT_FREE(lut);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                  Non-adaptive (global) mapping                   *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixGlobalNormRGB()
+ *
+ *      Input:  pixd (<optional> null, existing or equal to pixs)
+ *              pixs (32 bpp rgb, or colormapped)
+ *              rval, gval, bval (pixel values in pixs that are
+ *                                linearly mapped to mapval)
+ *              mapval (use 255 for mapping to white)
+ *      Return: pixd (32 bpp rgb or colormapped), or null on error
+ *
+ *  Notes:
+ *    (1) The value of pixd determines if the results are written to a
+ *        new pix (use NULL), in-place to pixs (use pixs), or to some
+ *        other existing pix.
+ *    (2) This does a global normalization of an image where the
+ *        r,g,b color components are not balanced.  Thus, white in pixs is
+ *        represented by a set of r,g,b values that are not all 255.
+ *    (3) The input values (rval, gval, bval) should be chosen to
+ *        represent the gray color (mapval, mapval, mapval) in src.
+ *        Thus, this function will map (rval, gval, bval) to that gray color.
+ *    (4) Typically, mapval = 255, so that (rval, gval, bval)
+ *        corresponds to the white point of src.  In that case, these
+ *        parameters should be chosen so that few pixels have higher values.
+ *    (5) In all cases, we do a linear TRC separately on each of the
+ *        components, saturating at 255.
+ *    (6) If the input pix is 8 bpp without a colormap, you can get
+ *        this functionality with mapval = 255 by calling:
+ *            pixGammaTRC(pixd, pixs, 1.0, 0, bgval);
+ *        where bgval is the value you want to be mapped to 255.
+ *        Or more generally, if you want bgval to be mapped to mapval:
+ *            pixGammaTRC(pixd, pixs, 1.0, 0, 255 * bgval / mapval);
+ */
+PIX *
+pixGlobalNormRGB(PIX     *pixd,
+                 PIX     *pixs,
+                 l_int32  rval,
+                 l_int32  gval,
+                 l_int32  bval,
+                 l_int32  mapval)
+{
+l_int32    w, h, d, i, j, ncolors, rv, gv, bv, wpl;
+l_int32   *rarray, *garray, *barray;
+l_uint32  *data, *line;
+NUMA      *nar, *nag, *nab;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixGlobalNormRGB");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    cmap = pixGetColormap(pixs);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (!cmap && d != 32)
+        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
+    if (mapval <= 0) {
+        L_WARNING("mapval must be > 0; setting to 255\n", procName);
+        mapval = 255;
+    }
+
+        /* Prepare pixd to be a copy of pixs */
+    if ((pixd = pixCopy(pixd, pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+        /* Generate the TRC maps for each component.  Make sure the
+         * upper range for each color is greater than zero. */
+    nar = numaGammaTRC(1.0, 0, L_MAX(1, 255 * rval / mapval));
+    nag = numaGammaTRC(1.0, 0, L_MAX(1, 255 * gval / mapval));
+    nab = numaGammaTRC(1.0, 0, L_MAX(1, 255 * bval / mapval));
+    if (!nar || !nag || !nab)
+        return (PIX *)ERROR_PTR("trc maps not all made", procName, pixd);
+
+        /* Extract copies of the internal arrays */
+    rarray = numaGetIArray(nar);
+    garray = numaGetIArray(nag);
+    barray = numaGetIArray(nab);
+    if (!rarray || !garray || !barray)
+        return (PIX *)ERROR_PTR("*arrays not all made", procName, pixd);
+
+    if (cmap) {
+        ncolors = pixcmapGetCount(cmap);
+        for (i = 0; i < ncolors; i++) {
+            pixcmapGetColor(cmap, i, &rv, &gv, &bv);
+            pixcmapResetColor(cmap, i, rarray[rv], garray[gv], barray[bv]);
+        }
+    }
+    else {
+        data = pixGetData(pixd);
+        wpl = pixGetWpl(pixd);
+        for (i = 0; i < h; i++) {
+            line = data + i * wpl;
+            for (j = 0; j < w; j++) {
+                extractRGBValues(line[j], &rv, &gv, &bv);
+                composeRGBPixel(rarray[rv], garray[gv], barray[bv], line + j);
+            }
+        }
+    }
+
+    numaDestroy(&nar);
+    numaDestroy(&nag);
+    numaDestroy(&nab);
+    LEPT_FREE(rarray);
+    LEPT_FREE(garray);
+    LEPT_FREE(barray);
+    return pixd;
+}
+
+
+/*!
+ *  pixGlobalNormNoSatRGB()
+ *
+ *      Input:  pixd (<optional> null, existing or equal to pixs)
+ *              pixs (32 bpp rgb)
+ *              rval, gval, bval (pixel values in pixs that are
+ *                                linearly mapped to mapval; but see below)
+ *              factor (subsampling factor; integer >= 1)
+ *              rank (between 0.0 and 1.0; typ. use a value near 1.0)
+ *      Return: pixd (32 bpp rgb), or null on error
+ *
+ *  Notes:
+ *    (1) This is a version of pixGlobalNormRGB(), where the output
+ *        intensity is scaled back so that a controlled fraction of
+ *        pixel components is allowed to saturate.  See comments in
+ *        pixGlobalNormRGB().
+ *    (2) The value of pixd determines if the results are written to a
+ *        new pix (use NULL), in-place to pixs (use pixs), or to some
+ *        other existing pix.
+ *    (3) This does a global normalization of an image where the
+ *        r,g,b color components are not balanced.  Thus, white in pixs is
+ *        represented by a set of r,g,b values that are not all 255.
+ *    (4) The input values (rval, gval, bval) can be chosen to be the
+ *        color that, after normalization, becomes white background.
+ *        For images that are mostly background, the closer these values
+ *        are to the median component values, the closer the resulting
+ *        background will be to gray, becoming white at the brightest places.
+ *    (5) The mapval used in pixGlobalNormRGB() is computed here to
+ *        avoid saturation of any component in the image (save for a
+ *        fraction of the pixels given by the input rank value).
+ */
+PIX *
+pixGlobalNormNoSatRGB(PIX       *pixd,
+                      PIX       *pixs,
+                      l_int32    rval,
+                      l_int32    gval,
+                      l_int32    bval,
+                      l_int32    factor,
+                      l_float32  rank)
+{
+l_int32    mapval;
+l_float32  rankrval, rankgval, rankbval;
+l_float32  rfract, gfract, bfract, maxfract;
+
+    PROCNAME("pixGlobalNormNoSatRGB");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (factor < 1)
+        return (PIX *)ERROR_PTR("sampling factor < 1", procName, NULL);
+    if (rank < 0.0 || rank > 1.0)
+        return (PIX *)ERROR_PTR("rank not in [0.0 ... 1.0]", procName, NULL);
+    if (rval <= 0 || gval <= 0 || bval <= 0)
+        return (PIX *)ERROR_PTR("invalid estim. color values", procName, NULL);
+
+        /* The max value for each component may be larger than the
+         * input estimated background value.  In that case, mapping
+         * for those pixels would saturate.  To prevent saturation,
+         * we compute the fraction for each component by which we
+         * would oversaturate.  Then take the max of these, and
+         * reduce, uniformly over all components, the output intensity
+         * by this value.  Then no component will saturate.
+         * In practice, if rank < 1.0, a fraction of pixels
+         * may have a component saturate.  By keeping rank close to 1.0,
+         * that fraction can be made arbitrarily small. */
+    pixGetRankValueMaskedRGB(pixs, NULL, 0, 0, factor, rank, &rankrval,
+                             &rankgval, &rankbval);
+    rfract = rankrval / (l_float32)rval;
+    gfract = rankgval / (l_float32)gval;
+    bfract = rankbval / (l_float32)bval;
+    maxfract = L_MAX(rfract, gfract);
+    maxfract = L_MAX(maxfract, bfract);
+#if  DEBUG_GLOBAL
+    fprintf(stderr, "rankrval = %7.2f, rankgval = %7.2f, rankbval = %7.2f\n",
+            rankrval, rankgval, rankbval);
+    fprintf(stderr, "rfract = %7.4f, gfract = %7.4f, bfract = %7.4f\n",
+            rfract, gfract, bfract);
+#endif  /* DEBUG_GLOBAL */
+
+    mapval = (l_int32)(255. / maxfract);
+    pixd = pixGlobalNormRGB(pixd, pixs, rval, gval, bval, mapval);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *              Adaptive threshold spread normalization             *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixThresholdSpreadNorm()
+ *
+ *      Input:  pixs (8 bpp grayscale; not colormapped)
+ *              filtertype (L_SOBEL_EDGE or L_TWO_SIDED_EDGE);
+ *              edgethresh (threshold on magnitude of edge filter; typ 10-20)
+ *              smoothx, smoothy (half-width of convolution kernel applied to
+ *                                spread threshold: use 0 for no smoothing)
+ *              gamma (gamma correction; typ. about 0.7)
+ *              minval  (input value that gives 0 for output; typ. -25)
+ *              maxval  (input value that gives 255 for output; typ. 255)
+ *              targetthresh (target threshold for normalization)
+ *              &pixth (<optional return> computed local threshold value)
+ *              &pixb (<optional return> thresholded normalized image)
+ *              &pixd (<optional return> normalized image)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The basis of this approach is the use of seed spreading
+ *          on a (possibly) sparse set of estimates for the local threshold.
+ *          The resulting dense estimates are smoothed by convolution
+ *          and used to either threshold the input image or normalize it
+ *          with a local transformation that linearly maps the pixels so
+ *          that the local threshold estimate becomes constant over the
+ *          resulting image.  This approach is one of several that
+ *          have been suggested (and implemented) by Ray Smith.
+ *      (2) You can use either the Sobel or TwoSided edge filters.
+ *          The results appear to be similar, using typical values
+ *          of edgethresh in the rang 10-20.
+ *      (3) To skip the trc enhancement, use gamma = 1.0, minval = 0
+ *          and maxval = 255.
+ *      (4) For the normalized image pixd, each pixel is linearly mapped
+ *          in such a way that the local threshold is equal to targetthresh.
+ *      (5) The full width and height of the convolution kernel
+ *          are (2 * smoothx + 1) and (2 * smoothy + 1).
+ *      (6) This function can be used with the pixtiling utility if the
+ *          images are too large.  See pixOtsuAdaptiveThreshold() for
+ *          an example of this.
+ */
+l_int32
+pixThresholdSpreadNorm(PIX       *pixs,
+                       l_int32    filtertype,
+                       l_int32    edgethresh,
+                       l_int32    smoothx,
+                       l_int32    smoothy,
+                       l_float32  gamma,
+                       l_int32    minval,
+                       l_int32    maxval,
+                       l_int32    targetthresh,
+                       PIX      **ppixth,
+                       PIX      **ppixb,
+                       PIX      **ppixd)
+{
+PIX  *pixe, *pixet, *pixsd, *pixg1, *pixg2, *pixth;
+
+    PROCNAME("pixThresholdSpreadNorm");
+
+    if (ppixth) *ppixth = NULL;
+    if (ppixb) *ppixb = NULL;
+    if (ppixd) *ppixd = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (pixGetColormap(pixs))
+        return ERROR_INT("pixs is colormapped", procName, 1);
+    if (!ppixth && !ppixb && !ppixd)
+        return ERROR_INT("no output requested", procName, 1);
+    if (filtertype != L_SOBEL_EDGE && filtertype != L_TWO_SIDED_EDGE)
+        return ERROR_INT("invalid filter type", procName, 1);
+
+        /* Get the thresholded edge pixels.  These are the ones
+         * that have values in pixs near the local optimal fg/bg threshold. */
+    if (filtertype == L_SOBEL_EDGE)
+        pixe = pixSobelEdgeFilter(pixs, L_VERTICAL_EDGES);
+    else  /* L_TWO_SIDED_EDGE */
+        pixe = pixTwoSidedEdgeFilter(pixs, L_VERTICAL_EDGES);
+    pixet = pixThresholdToBinary(pixe, edgethresh);
+    pixInvert(pixet, pixet);
+
+        /* Build a seed image whose only nonzero values are those
+         * values of pixs corresponding to pixels in the fg of pixet. */
+    pixsd = pixCreateTemplate(pixs);
+    pixCombineMasked(pixsd, pixs, pixet);
+
+        /* Spread the seed and optionally smooth to reduce noise */
+    pixg1 = pixSeedspread(pixsd, 4);
+    pixg2 = pixBlockconv(pixg1, smoothx, smoothy);
+
+        /* Optionally do a gamma enhancement */
+    pixth = pixGammaTRC(NULL, pixg2, gamma, minval, maxval);
+
+        /* Do the mapping and thresholding */
+    if (ppixd) {
+        *ppixd = pixApplyVariableGrayMap(pixs, pixth, targetthresh);
+        if (ppixb)
+            *ppixb = pixThresholdToBinary(*ppixd, targetthresh);
+    }
+    else if (ppixb)
+        *ppixb = pixVarThresholdToBinary(pixs, pixth);
+
+    if (ppixth)
+        *ppixth = pixth;
+    else
+        pixDestroy(&pixth);
+
+    pixDestroy(&pixe);
+    pixDestroy(&pixet);
+    pixDestroy(&pixsd);
+    pixDestroy(&pixg1);
+    pixDestroy(&pixg2);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *      Adaptive background normalization (flexible adaptaption)    *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixBackgroundNormFlex()
+ *
+ *      Input:  pixs (8 bpp grayscale; not colormapped)
+ *              sx, sy (desired tile dimensions; actual size may vary; use
+ *                      values between 3 and 10)
+ *              smoothx, smoothy (half-width of convolution kernel applied to
+ *                                threshold array: use values between 1 and 3)
+ *              delta (difference parameter in basin filling; use 0
+ *                     to skip)
+ *      Return: pixd (8 bpp, background-normalized), or null on error)
+ *
+ *  Notes:
+ *      (1) This does adaptation flexibly to a quickly varying background.
+ *          For that reason, all input parameters should be small.
+ *      (2) sx and sy give the tile size; they should be in [5 - 7].
+ *      (3) The full width and height of the convolution kernel
+ *          are (2 * smoothx + 1) and (2 * smoothy + 1).  They
+ *          should be in [1 - 2].
+ *      (4) Basin filling is used to fill the large fg regions.  The
+ *          parameter @delta measures the height that the black
+ *          background is raised from the local minima.  By raising
+ *          the background, it is possible to threshold the large
+ *          fg regions to foreground.  If @delta is too large,
+ *          bg regions will be lifted, causing thickening of
+ *          the fg regions.  Use 0 to skip.
+ */
+PIX *
+pixBackgroundNormFlex(PIX     *pixs,
+                      l_int32  sx,
+                      l_int32  sy,
+                      l_int32  smoothx,
+                      l_int32  smoothy,
+                      l_int32  delta)
+{
+l_float32  scalex, scaley;
+PIX       *pixt, *pixsd, *pixmin, *pixbg, *pixbgi, *pixd;
+
+    PROCNAME("pixBackgroundNormFlex");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs is colormapped", procName, NULL);
+    if (sx < 3 || sy < 3)
+        return (PIX *)ERROR_PTR("sx and/or sy less than 3", procName, NULL);
+    if (sx > 10 || sy > 10)
+        return (PIX *)ERROR_PTR("sx and/or sy exceed 10", procName, NULL);
+    if (smoothx < 1 || smoothy < 1)
+        return (PIX *)ERROR_PTR("smooth params less than 1", procName, NULL);
+    if (smoothx > 3 || smoothy > 3)
+        return (PIX *)ERROR_PTR("smooth params exceed 3", procName, NULL);
+
+        /* Generate the bg estimate using smoothed average with subsampling */
+    scalex = 1. / (l_float32)sx;
+    scaley = 1. / (l_float32)sy;
+    pixt = pixScaleSmooth(pixs, scalex, scaley);
+
+        /* Do basin filling on the bg estimate if requested */
+    if (delta <= 0)
+        pixsd = pixClone(pixt);
+    else {
+        pixLocalExtrema(pixt, 0, 0, &pixmin, NULL);
+        pixsd = pixSeedfillGrayBasin(pixmin, pixt, delta, 4);
+        pixDestroy(&pixmin);
+    }
+    pixbg = pixExtendByReplication(pixsd, 1, 1);
+
+        /* Map the bg to 200 */
+    pixbgi = pixGetInvBackgroundMap(pixbg, 200, smoothx, smoothy);
+    pixd = pixApplyInvBackgroundGrayMap(pixs, pixbgi, sx, sy);
+
+    pixDestroy(&pixt);
+    pixDestroy(&pixsd);
+    pixDestroy(&pixbg);
+    pixDestroy(&pixbgi);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                    Adaptive contrast normalization               *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixContrastNorm()
+ *
+ *      Input:  pixd (<optional> 8 bpp; null or equal to pixs)
+ *              pixs (8 bpp grayscale; not colormapped)
+ *              sx, sy (tile dimensions)
+ *              mindiff (minimum difference to accept as valid)
+ *              smoothx, smoothy (half-width of convolution kernel applied to
+ *                                min and max arrays: use 0 for no smoothing)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) This function adaptively attempts to expand the contrast
+ *          to the full dynamic range in each tile.  If the contrast in
+ *          a tile is smaller than @mindiff, it uses the min and max
+ *          pixel values from neighboring tiles.  It also can use
+ *          convolution to smooth the min and max values from
+ *          neighboring tiles.  After all that processing, it is
+ *          possible that the actual pixel values in the tile are outside
+ *          the computed [min ... max] range for local contrast
+ *          normalization.  Such pixels are taken to be at either 0
+ *          (if below the min) or 255 (if above the max).
+ *      (2) pixd can be equal to pixs (in-place operation) or
+ *          null (makes a new pixd).
+ *      (3) sx and sy give the tile size; they are typically at least 20.
+ *      (4) mindiff is used to eliminate results for tiles where it is
+ *          likely that either fg or bg is missing.  A value around 50
+ *          or more is reasonable.
+ *      (5) The full width and height of the convolution kernel
+ *          are (2 * smoothx + 1) and (2 * smoothy + 1).  Some smoothing
+ *          is typically useful, and we limit the smoothing half-widths
+ *          to the range from 0 to 8.
+ *      (6) A linear TRC (gamma = 1.0) is applied to increase the contrast
+ *          in each tile.  The result can subsequently be globally corrected,
+ *          by applying pixGammaTRC() with arbitrary values of gamma
+ *          and the 0 and 255 points of the mapping.
+ */
+PIX *
+pixContrastNorm(PIX       *pixd,
+                PIX       *pixs,
+                l_int32    sx,
+                l_int32    sy,
+                l_int32    mindiff,
+                l_int32    smoothx,
+                l_int32    smoothy)
+{
+PIX  *pixmin, *pixmax;
+
+    PROCNAME("pixContrastNorm");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, pixd);
+    if (pixd && pixd != pixs)
+        return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs is colormapped", procName, pixd);
+    if (sx < 5 || sy < 5)
+        return (PIX *)ERROR_PTR("sx and/or sy less than 5", procName, pixd);
+    if (smoothx < 0 || smoothy < 0)
+        return (PIX *)ERROR_PTR("smooth params less than 0", procName, pixd);
+    if (smoothx > 8 || smoothy > 8)
+        return (PIX *)ERROR_PTR("smooth params exceed 8", procName, pixd);
+
+        /* Get the min and max pixel values in each tile, and represent
+         * each value as a pixel in pixmin and pixmax, respectively. */
+    pixMinMaxTiles(pixs, sx, sy, mindiff, smoothx, smoothy, &pixmin, &pixmax);
+
+        /* For each tile, do a linear expansion of the dynamic range
+         * of pixels so that the min value is mapped to 0 and the
+         * max value is mapped to 255.  */
+    pixd = pixLinearTRCTiled(pixd, pixs, sx, sy, pixmin, pixmax);
+
+    pixDestroy(&pixmin);
+    pixDestroy(&pixmax);
+    return pixd;
+}
+
+
+/*!
+ *  pixMinMaxTiles()
+ *
+ *      Input:  pixs (8 bpp grayscale; not colormapped)
+ *              sx, sy (tile dimensions)
+ *              mindiff (minimum difference to accept as valid)
+ *              smoothx, smoothy (half-width of convolution kernel applied to
+ *                                min and max arrays: use 0 for no smoothing)
+ *              &pixmin (<return> tiled minima)
+ *              &pixmax (<return> tiled maxima)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This computes filtered and smoothed values for the min and
+ *          max pixel values in each tile of the image.
+ *      (2) See pixContrastNorm() for usage.
+ */
+l_int32
+pixMinMaxTiles(PIX     *pixs,
+               l_int32  sx,
+               l_int32  sy,
+               l_int32  mindiff,
+               l_int32  smoothx,
+               l_int32  smoothy,
+               PIX    **ppixmin,
+               PIX    **ppixmax)
+{
+l_int32  w, h;
+PIX     *pixmin1, *pixmax1, *pixmin2, *pixmax2;
+
+    PROCNAME("pixMinMaxTiles");
+
+    if (ppixmin) *ppixmin = NULL;
+    if (ppixmax) *ppixmax = NULL;
+    if (!ppixmin || !ppixmax)
+        return ERROR_INT("&pixmin or &pixmax undefined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs undefined or not 8 bpp", procName, 1);
+    if (pixGetColormap(pixs))
+        return ERROR_INT("pixs is colormapped", procName, 1);
+    if (sx < 5 || sy < 5)
+        return ERROR_INT("sx and/or sy less than 3", procName, 1);
+    if (smoothx < 0 || smoothy < 0)
+        return ERROR_INT("smooth params less than 0", procName, 1);
+    if (smoothx > 5 || smoothy > 5)
+        return ERROR_INT("smooth params exceed 5", procName, 1);
+
+        /* Get the min and max values in each tile */
+    pixmin1 = pixScaleGrayMinMax(pixs, sx, sy, L_CHOOSE_MIN);
+    pixmax1 = pixScaleGrayMinMax(pixs, sx, sy, L_CHOOSE_MAX);
+
+    pixmin2 = pixExtendByReplication(pixmin1, 1, 1);
+    pixmax2 = pixExtendByReplication(pixmax1, 1, 1);
+    pixDestroy(&pixmin1);
+    pixDestroy(&pixmax1);
+
+        /* Make sure no value is 0 */
+    pixAddConstantGray(pixmin2, 1);
+    pixAddConstantGray(pixmax2, 1);
+
+        /* Generate holes where the contrast is too small */
+    pixSetLowContrast(pixmin2, pixmax2, mindiff);
+
+        /* Fill the holes (0 values) */
+    pixGetDimensions(pixmin2, &w, &h, NULL);
+    pixFillMapHoles(pixmin2, w, h, L_FILL_BLACK);
+    pixFillMapHoles(pixmax2, w, h, L_FILL_BLACK);
+
+        /* Smooth if requested */
+    if (smoothx > 0 || smoothy > 0) {
+        smoothx = L_MIN(smoothx, (w - 1) / 2);
+        smoothy = L_MIN(smoothy, (h - 1) / 2);
+        *ppixmin = pixBlockconv(pixmin2, smoothx, smoothy);
+        *ppixmax = pixBlockconv(pixmax2, smoothx, smoothy);
+    }
+    else {
+        *ppixmin = pixClone(pixmin2);
+        *ppixmax = pixClone(pixmax2);
+    }
+    pixCopyResolution(*ppixmin, pixs);
+    pixCopyResolution(*ppixmax, pixs);
+    pixDestroy(&pixmin2);
+    pixDestroy(&pixmax2);
+
+    return 0;
+}
+
+
+/*!
+ *  pixSetLowContrast()
+ *
+ *      Input:  pixs1 (8 bpp)
+ *              pixs2 (8 bpp)
+ *              mindiff (minimum difference to accept as valid)
+ *      Return: 0 if OK; 1 if no pixel diffs are large enough, or on error
+ *
+ *  Notes:
+ *      (1) This compares corresponding pixels in pixs1 and pixs2.
+ *          When they differ by less than @mindiff, set the pixel
+ *          values to 0 in each.  Each pixel typically represents a tile
+ *          in a larger image, and a very small difference between
+ *          the min and max in the tile indicates that the min and max
+ *          values are not to be trusted.
+ *      (2) If contrast (pixel difference) detection is expected to fail,
+ *          caller should check return value.
+ */
+l_int32
+pixSetLowContrast(PIX     *pixs1,
+                  PIX     *pixs2,
+                  l_int32  mindiff)
+{
+l_int32    i, j, w, h, d, wpl, val1, val2, found;
+l_uint32  *data1, *data2, *line1, *line2;
+
+    PROCNAME("pixSetLowContrast");
+
+    if (!pixs1 || !pixs2)
+        return ERROR_INT("pixs1 and pixs2 not both defined", procName, 1);
+    if (pixSizesEqual(pixs1, pixs2) == 0)
+        return ERROR_INT("pixs1 and pixs2 not equal size", procName, 1);
+    pixGetDimensions(pixs1, &w, &h, &d);
+    if (d != 8)
+        return ERROR_INT("depth not 8 bpp", procName, 1);
+    if (mindiff > 254) return 0;
+
+    data1 = pixGetData(pixs1);
+    data2 = pixGetData(pixs2);
+    wpl = pixGetWpl(pixs1);
+    found = 0;  /* init to not finding any diffs >= mindiff */
+    for (i = 0; i < h; i++) {
+        line1 = data1 + i * wpl;
+        line2 = data2 + i * wpl;
+        for (j = 0; j < w; j++) {
+            val1 = GET_DATA_BYTE(line1, j);
+            val2 = GET_DATA_BYTE(line2, j);
+            if (L_ABS(val1 - val2) >= mindiff) {
+                found = 1;
+                break;
+            }
+        }
+        if (found) break;
+    }
+    if (!found) {
+        L_WARNING("no pixel pair diffs as large as mindiff\n", procName);
+        pixClearAll(pixs1);
+        pixClearAll(pixs2);
+        return 1;
+    }
+
+    for (i = 0; i < h; i++) {
+        line1 = data1 + i * wpl;
+        line2 = data2 + i * wpl;
+        for (j = 0; j < w; j++) {
+            val1 = GET_DATA_BYTE(line1, j);
+            val2 = GET_DATA_BYTE(line2, j);
+            if (L_ABS(val1 - val2) < mindiff) {
+                SET_DATA_BYTE(line1, j, 0);
+                SET_DATA_BYTE(line2, j, 0);
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixLinearTRCTiled()
+ *
+ *      Input:  pixd (<optional> 8 bpp)
+ *              pixs (8 bpp, not colormapped)
+ *              sx, sy (tile dimensions)
+ *              pixmin (pix of min values in tiles)
+ *              pixmax (pix of max values in tiles)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) pixd can be equal to pixs (in-place operation) or
+ *          null (makes a new pixd).
+ *      (2) sx and sy give the tile size; they are typically at least 20.
+ *      (3) pixmin and pixmax are generated by pixMinMaxTiles()
+ *      (4) For each tile, this does a linear expansion of the dynamic
+ *          range so that the min value in the tile becomes 0 and the
+ *          max value in the tile becomes 255.
+ *      (5) The LUTs that do the mapping are generated as needed
+ *          and stored for reuse in an integer array within the ptr array iaa[].
+ */
+PIX *
+pixLinearTRCTiled(PIX       *pixd,
+                  PIX       *pixs,
+                  l_int32    sx,
+                  l_int32    sy,
+                  PIX       *pixmin,
+                  PIX       *pixmax)
+{
+l_int32    i, j, k, m, w, h, wt, ht, wpl, wplt, xoff, yoff;
+l_int32    minval, maxval, val, sval;
+l_int32   *ia;
+l_int32  **iaa;
+l_uint32  *data, *datamin, *datamax, *line, *tline, *linemin, *linemax;
+
+    PROCNAME("pixLinearTRCTiled");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, pixd);
+    if (pixd && pixd != pixs)
+        return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs is colormapped", procName, pixd);
+    if (!pixmin || !pixmax)
+        return (PIX *)ERROR_PTR("pixmin & pixmax not defined", procName, pixd);
+    if (sx < 5 || sy < 5)
+        return (PIX *)ERROR_PTR("sx and/or sy less than 5", procName, pixd);
+
+    pixd = pixCopy(pixd, pixs);
+    iaa = (l_int32 **)LEPT_CALLOC(256, sizeof(l_int32 *));
+    pixGetDimensions(pixd, &w, &h, NULL);
+
+    data = pixGetData(pixd);
+    wpl = pixGetWpl(pixd);
+    datamin = pixGetData(pixmin);
+    datamax = pixGetData(pixmax);
+    wplt = pixGetWpl(pixmin);
+    pixGetDimensions(pixmin, &wt, &ht, NULL);
+    for (i = 0; i < ht; i++) {
+        line = data + sy * i * wpl;
+        linemin = datamin + i * wplt;
+        linemax = datamax + i * wplt;
+        yoff = sy * i;
+        for (j = 0; j < wt; j++) {
+            xoff = sx * j;
+            minval = GET_DATA_BYTE(linemin, j);
+            maxval = GET_DATA_BYTE(linemax, j);
+            if (maxval == minval) {  /* this is bad */
+/*                fprintf(stderr, "should't happen! i,j = %d,%d, minval = %d\n",
+                        i, j, minval); */
+                continue;
+            }
+            ia = iaaGetLinearTRC(iaa, maxval - minval);
+            for (k = 0; k < sy && yoff + k < h; k++) {
+                tline = line + k * wpl;
+                for (m = 0; m < sx && xoff + m < w; m++) {
+                    val = GET_DATA_BYTE(tline, xoff + m);
+                    sval = val - minval;
+                    sval = L_MAX(0, sval);
+                    SET_DATA_BYTE(tline, xoff + m, ia[sval]);
+                }
+            }
+        }
+    }
+
+    for (i = 0; i < 256; i++)
+        if (iaa[i]) LEPT_FREE(iaa[i]);
+    LEPT_FREE(iaa);
+    return pixd;
+}
+
+
+/*!
+ *  iaaGetLinearTRC()
+ *
+ *      Input:  iaa (bare array of ptrs to l_int32)
+ *              diff (between min and max pixel values that are
+ *                    to be mapped to 0 and 255)
+ *      Return: ia (LUT with input (val - minval) and output a
+ *                  value between 0 and 255)
+ */
+static l_int32 *
+iaaGetLinearTRC(l_int32  **iaa,
+                l_int32    diff)
+{
+l_int32    i;
+l_int32   *ia;
+l_float32  factor;
+
+    PROCNAME("iaaGetLinearTRC");
+
+    if (!iaa)
+        return (l_int32 *)ERROR_PTR("iaa not defined", procName, NULL);
+
+    if (iaa[diff] != NULL)  /* already have it */
+       return iaa[diff];
+
+    if ((ia = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return (l_int32 *)ERROR_PTR("ia not made", procName, NULL);
+    iaa[diff] = ia;
+    if (diff == 0) {  /* shouldn't happen */
+        for (i = 0; i < 256; i++)
+            ia[i] = 128;
+    }
+    else {
+        factor = 255. / (l_float32)diff;
+        for (i = 0; i < diff + 1; i++)
+            ia[i] = (l_int32)(factor * i + 0.5);
+        for (i = diff + 1; i < 256; i++)
+            ia[i] = 255;
+    }
+
+    return ia;
+}
diff --git a/src/affine.c b/src/affine.c
new file mode 100644 (file)
index 0000000..c4805f3
--- /dev/null
@@ -0,0 +1,1559 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  affine.c
+ *
+ *      Affine (3 pt) image transformation using a sampled
+ *      (to nearest integer) transform on each dest point
+ *           PIX        *pixAffineSampledPta()
+ *           PIX        *pixAffineSampled()
+ *
+ *      Affine (3 pt) image transformation using interpolation
+ *      (or area mapping) for anti-aliasing images that are
+ *      2, 4, or 8 bpp gray, or colormapped, or 32 bpp RGB
+ *           PIX        *pixAffinePta()
+ *           PIX        *pixAffine()
+ *           PIX        *pixAffinePtaColor()
+ *           PIX        *pixAffineColor()
+ *           PIX        *pixAffinePtaGray()
+ *           PIX        *pixAffineGray()
+ *
+ *      Affine transform including alpha (blend) component
+ *           PIX        *pixAffinePtaWithAlpha()
+ *
+ *      Affine coordinate transformation
+ *           l_int32     getAffineXformCoeffs()
+ *           l_int32     affineInvertXform()
+ *           l_int32     affineXformSampledPt()
+ *           l_int32     affineXformPt()
+ *
+ *      Interpolation helper functions
+ *           l_int32     linearInterpolatePixelGray()
+ *           l_int32     linearInterpolatePixelColor()
+ *
+ *      Gauss-jordan linear equation solver
+ *           l_int32     gaussjordan()
+ *
+ *      Affine image transformation using a sequence of
+ *      shear/scale/translation operations
+ *           PIX        *pixAffineSequential()
+ *
+ *      One can define a coordinate space by the location of the origin,
+ *      the orientation of x and y axes, and the unit scaling along
+ *      each axis.  An affine transform is a general linear
+ *      transformation from one coordinate space to another.
+ *
+ *      For the general case, we can define the affine transform using
+ *      two sets of three (noncollinear) points in a plane.  One set
+ *      corresponds to the input (src) coordinate space; the other to the
+ *      transformed (dest) coordinate space.  Each point in the
+ *      src corresponds to one of the points in the dest.  With two
+ *      sets of three points, we get a set of 6 equations in 6 unknowns
+ *      that specifies the mapping between the coordinate spaces.
+ *      The interface here allows you to specify either the corresponding
+ *      sets of 3 points, or the transform itself (as a vector of 6
+ *      coefficients).
+ *
+ *      Given the transform as a vector of 6 coefficients, we can compute
+ *      both a a pointwise affine coordinate transformation and an
+ *      affine image transformation.
+ *
+ *      To compute the coordinate transform, we need the coordinate
+ *      value (x',y') in the transformed space for any point (x,y)
+ *      in the original space.  To derive this transform from the
+ *      three corresponding points, it is convenient to express the affine
+ *      coordinate transformation using an LU decomposition of
+ *      a set of six linear equations that express the six coordinates
+ *      of the three points in the transformed space as a function of
+ *      the six coordinates in the original space.  Once we have
+ *      this transform matrix , we can transform an image by
+ *      finding, for each destination pixel, the pixel (or pixels)
+ *      in the source that give rise to it.
+ *
+ *      This 'pointwise' transformation can be done either by sampling
+ *      and picking a single pixel in the src to replicate into the dest,
+ *      or by interpolating (or averaging) over four src pixels to
+ *      determine the value of the dest pixel.  The first method is
+ *      implemented by pixAffineSampled() and the second method by
+ *      pixAffine().  The interpolated method can only be used for
+ *      images with more than 1 bpp, but for these, the image quality
+ *      is significantly better than the sampled method, due to
+ *      the 'antialiasing' effect of weighting the src pixels.
+ *
+ *      Interpolation works well when there is relatively little scaling,
+ *      or if there is image expansion in general.  However, if there
+ *      is significant image reduction, one should apply a low-pass
+ *      filter before subsampling to avoid aliasing the high frequencies.
+ *
+ *      A typical application might be to align two images, which
+ *      may be scaled, rotated and translated versions of each other.
+ *      Through some pre-processing, three corresponding points are
+ *      located in each of the two images.  One of the images is
+ *      then to be (affine) transformed to align with the other.
+ *      As mentioned, the standard way to do this is to use three
+ *      sets of points, compute the 6 transformation coefficients
+ *      from these points that describe the linear transformation,
+ *
+ *          x' = ax + by + c
+ *          y' = dx + ey + f
+ *
+ *      and use this in a pointwise manner to transform the image.
+ *
+ *      N.B.  Be sure to see the comment in getAffineXformCoeffs(),
+ *      regarding using the inverse of the affine transform for points
+ *      to transform images.
+ *
+ *      There is another way to do this transformation; namely,
+ *      by doing a sequence of simple affine transforms, without
+ *      computing directly the affine coordinate transformation.
+ *      We have at our disposal (1) translations (using rasterop),
+ *      (2) horizontal and vertical shear about any horizontal and vertical
+ *      line, respectively, and (3) non-isotropic scaling by two
+ *      arbitrary x and y scaling factors.  We also have rotation
+ *      about an arbitrary point, but this is equivalent to a set
+ *      of three shears so we do not need to use it.
+ *
+ *      Why might we do this?  For binary images, it is usually
+ *      more efficient to do such transformations by a sequence
+ *      of word parallel operations.  Shear and translation can be
+ *      done in-place and word parallel; arbitrary scaling is
+ *      mostly pixel-wise.
+ *
+ *      Suppose that we are tranforming image 1 to correspond to image 2.
+ *      We have a set of three points, describing the coordinate space
+ *      embedded in image 1, and we need to transform image 1 until
+ *      those three points exactly correspond to the new coordinate space
+ *      defined by the second set of three points.  In our image
+ *      matching application, the latter set of three points was
+ *      found to be the corresponding points in image 2.
+ *
+ *      The most elegant way I can think of to do such a sequential
+ *      implementation is to imagine that we're going to transform
+ *      BOTH images until they're aligned.  (We don't really want
+ *      to transform both, because in fact we may only have one image
+ *      that is undergoing a general affine transformation.)
+ *
+ *      Choose the 3 corresponding points as follows:
+ *         - The 1st point is an origin
+ *         - The 2nd point gives the orientation and scaling of the
+ *           "x" axis with respect to the origin
+ *         - The 3rd point does likewise for the "y" axis.
+ *      These "axes" must not be collinear; otherwise they are
+ *      arbitrary (although some strange things will happen if
+ *      the handedness sweeping through the minimum angle between
+ *      the axes is opposite).
+ *
+ *      An important constraint is that we have shear operations
+ *      about an arbitrary horizontal or vertical line, but always
+ *      parallel to the x or y axis.  If we continue to pretend that
+ *      we have an unprimed coordinate space embedded in image 1 and
+ *      a primed coordinate space embedded in image 2, we imagine
+ *      (a) transforming image 1 by horizontal and vertical shears about
+ *      point 1 to align points 3 and 2 along the y and x axes,
+ *      respectively, and (b) transforming image 2 by horizontal and
+ *      vertical shears about point 1' to align points 3' and 2' along
+ *      the y and x axes.  Then we scale image 1 so that the distances
+ *      from 1 to 2 and from 1 to 3 are equal to the distances in
+ *      image 2 from 1' to 2' and from 1' to 3'.  This scaling operation
+ *      leaves the true image origin, at (0,0) invariant, and will in
+ *      general translate point 1.  The original points 1 and 1' will
+ *      typically not coincide in any event, so we must translate
+ *      the origin of image 1, at its current point 1, to the origin
+ *      of image 2 at 1'.  The images should now be aligned.  But
+ *      because we never really transformed image 2 (and image 2 may
+ *      not even exist), we now perform  on image 1 the reverse of
+ *      the shear transforms that we imagined doing on image 2;
+ *      namely, the negative vertical shear followed by the negative
+ *      horizontal shear.  Image 1 should now have its transformed
+ *      unprimed coordinates aligned with the original primed
+ *      coordinates.  In all this, it is only necessary to keep track
+ *      of the shear angles and translations of points during the shears.
+ *      What has been accomplished is a general affine transformation
+ *      on image 1.
+ *
+ *      Having described all this, if you are going to use an
+ *      affine transformation in an application, this is what you
+ *      need to know:
+ *
+ *          (1) You should NEVER use the sequential method, because
+ *              the image quality for 1 bpp text is much poorer
+ *              (even though it is about 2x faster than the pointwise sampled
+ *              method), and for images with depth greater than 1, it is
+ *              nearly 20x slower than the pointwise sampled method
+ *              and over 10x slower than the pointwise interpolated method!
+ *              The sequential method is given here for purely
+ *              pedagogical reasons.
+ *
+ *          (2) For 1 bpp images, use the pointwise sampled function
+ *              pixAffineSampled().  For all other images, the best
+ *              quality results result from using the pointwise
+ *              interpolated function pixAffinePta() or pixAffine();
+ *              the cost is less than a doubling of the computation time
+ *              with respect to the sampled function.  If you use
+ *              interpolation on colormapped images, the colormap will
+ *              be removed, resulting in either a grayscale or color
+ *              image, depending on the values in the colormap.
+ *              If you want to retain the colormap, use pixAffineSampled().
+ *
+ *      Typical relative timing of pointwise transforms (sampled = 1.0):
+ *      8 bpp:   sampled        1.0
+ *               interpolated   1.6
+ *      32 bpp:  sampled        1.0
+ *               interpolated   1.8
+ *      Additionally, the computation time/pixel is nearly the same
+ *      for 8 bpp and 32 bpp, for both sampled and interpolated.
+ */
+
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+extern l_float32  AlphaMaskBorderVals[2];
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG     0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*-------------------------------------------------------------*
+ *               Sampled affine image transformation           *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixAffineSampledPta()
+ *
+ *      Input:  pixs (all depths)
+ *              ptad  (3 pts of final coordinate space)
+ *              ptas  (3 pts of initial coordinate space)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Brings in either black or white pixels from the boundary.
+ *      (2) Retains colormap, which you can do for a sampled transform..
+ *      (3) The 3 points must not be collinear.
+ *      (4) The order of the 3 points is arbitrary; however, to compare
+ *          with the sequential transform they must be in these locations
+ *          and in this order: origin, x-axis, y-axis.
+ *      (5) For 1 bpp images, this has much better quality results
+ *          than pixAffineSequential(), particularly for text.
+ *          It is about 3x slower, but does not require additional
+ *          border pixels.  The poor quality of pixAffineSequential()
+ *          is due to repeated quantized transforms.  It is strongly
+ *          recommended that pixAffineSampled() be used for 1 bpp images.
+ *      (6) For 8 or 32 bpp, much better quality is obtained by the
+ *          somewhat slower pixAffinePta().  See that function
+ *          for relative timings between sampled and interpolated.
+ *      (7) To repeat, use of the sequential transform,
+ *          pixAffineSequential(), for any images, is discouraged.
+ */
+PIX *
+pixAffineSampledPta(PIX     *pixs,
+                    PTA     *ptad,
+                    PTA     *ptas,
+                    l_int32  incolor)
+{
+l_float32  *vc;
+PIX        *pixd;
+
+    PROCNAME("pixAffineSampledPta");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+    if (ptaGetCount(ptas) != 3)
+        return (PIX *)ERROR_PTR("ptas count not 3", procName, NULL);
+    if (ptaGetCount(ptad) != 3)
+        return (PIX *)ERROR_PTR("ptad count not 3", procName, NULL);
+
+        /* Get backwards transform from dest to src, and apply it */
+    getAffineXformCoeffs(ptad, ptas, &vc);
+    pixd = pixAffineSampled(pixs, vc, incolor);
+    LEPT_FREE(vc);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixAffineSampled()
+ *
+ *      Input:  pixs (all depths)
+ *              vc  (vector of 6 coefficients for affine transformation)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Brings in either black or white pixels from the boundary.
+ *      (2) Retains colormap, which you can do for a sampled transform..
+ *      (3) For 8 or 32 bpp, much better quality is obtained by the
+ *          somewhat slower pixAffine().  See that function
+ *          for relative timings between sampled and interpolated.
+ */
+PIX *
+pixAffineSampled(PIX        *pixs,
+                 l_float32  *vc,
+                 l_int32     incolor)
+{
+l_int32     i, j, w, h, d, x, y, wpls, wpld, color, cmapindex;
+l_uint32    val;
+l_uint32   *datas, *datad, *lines, *lined;
+PIX        *pixd;
+PIXCMAP    *cmap;
+
+    PROCNAME("pixAffineSampled");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!vc)
+        return (PIX *)ERROR_PTR("vc not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("depth not 1, 2, 4, 8 or 16", procName, NULL);
+
+        /* Init all dest pixels to color to be brought in from outside */
+    pixd = pixCreateTemplate(pixs);
+    if ((cmap = pixGetColormap(pixs)) != NULL) {
+        if (incolor == L_BRING_IN_WHITE)
+            color = 1;
+        else
+            color = 0;
+        pixcmapAddBlackOrWhite(cmap, color, &cmapindex);
+        pixSetAllArbitrary(pixd, cmapindex);
+    } else {
+        if ((d == 1 && incolor == L_BRING_IN_WHITE) ||
+            (d > 1 && incolor == L_BRING_IN_BLACK)) {
+            pixClearAll(pixd);
+        } else {
+            pixSetAll(pixd);
+        }
+    }
+
+        /* Scan over the dest pixels */
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            affineXformSampledPt(vc, j, i, &x, &y);
+            if (x < 0 || y < 0 || x >=w || y >= h)
+                continue;
+            lines = datas + y * wpls;
+            if (d == 1) {
+                val = GET_DATA_BIT(lines, x);
+                SET_DATA_BIT_VAL(lined, j, val);
+            } else if (d == 8) {
+                val = GET_DATA_BYTE(lines, x);
+                SET_DATA_BYTE(lined, j, val);
+            } else if (d == 32) {
+                lined[j] = lines[x];
+            } else if (d == 2) {
+                val = GET_DATA_DIBIT(lines, x);
+                SET_DATA_DIBIT(lined, j, val);
+            } else if (d == 4) {
+                val = GET_DATA_QBIT(lines, x);
+                SET_DATA_QBIT(lined, j, val);
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *               Interpolated affine image transformation              *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixAffinePta()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              ptad  (3 pts of final coordinate space)
+ *              ptas  (3 pts of initial coordinate space)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Brings in either black or white pixels from the boundary
+ *      (2) Removes any existing colormap, if necessary, before transforming
+ */
+PIX *
+pixAffinePta(PIX     *pixs,
+             PTA     *ptad,
+             PTA     *ptas,
+             l_int32  incolor)
+{
+l_int32   d;
+l_uint32  colorval;
+PIX      *pixt1, *pixt2, *pixd;
+
+    PROCNAME("pixAffinePta");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+    if (ptaGetCount(ptas) != 3)
+        return (PIX *)ERROR_PTR("ptas count not 3", procName, NULL);
+    if (ptaGetCount(ptad) != 3)
+        return (PIX *)ERROR_PTR("ptad count not 3", procName, NULL);
+
+    if (pixGetDepth(pixs) == 1)
+        return pixAffineSampledPta(pixs, ptad, ptas, incolor);
+
+        /* Remove cmap if it exists, and unpack to 8 bpp if necessary */
+    pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    d = pixGetDepth(pixt1);
+    if (d < 8)
+        pixt2 = pixConvertTo8(pixt1, FALSE);
+    else
+        pixt2 = pixClone(pixt1);
+    d = pixGetDepth(pixt2);
+
+        /* Compute actual color to bring in from edges */
+    colorval = 0;
+    if (incolor == L_BRING_IN_WHITE) {
+        if (d == 8)
+            colorval = 255;
+        else  /* d == 32 */
+            colorval = 0xffffff00;
+    }
+
+    if (d == 8)
+        pixd = pixAffinePtaGray(pixt2, ptad, ptas, colorval);
+    else  /* d == 32 */
+        pixd = pixAffinePtaColor(pixt2, ptad, ptas, colorval);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    return pixd;
+}
+
+
+/*!
+ *  pixAffine()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              vc  (vector of 6 coefficients for affine transformation)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Brings in either black or white pixels from the boundary
+ *      (2) Removes any existing colormap, if necessary, before transforming
+ */
+PIX *
+pixAffine(PIX        *pixs,
+          l_float32  *vc,
+          l_int32     incolor)
+{
+l_int32   d;
+l_uint32  colorval;
+PIX      *pixt1, *pixt2, *pixd;
+
+    PROCNAME("pixAffine");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!vc)
+        return (PIX *)ERROR_PTR("vc not defined", procName, NULL);
+
+    if (pixGetDepth(pixs) == 1)
+        return pixAffineSampled(pixs, vc, incolor);
+
+        /* Remove cmap if it exists, and unpack to 8 bpp if necessary */
+    pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    d = pixGetDepth(pixt1);
+    if (d < 8)
+        pixt2 = pixConvertTo8(pixt1, FALSE);
+    else
+        pixt2 = pixClone(pixt1);
+    d = pixGetDepth(pixt2);
+
+        /* Compute actual color to bring in from edges */
+    colorval = 0;
+    if (incolor == L_BRING_IN_WHITE) {
+        if (d == 8)
+            colorval = 255;
+        else  /* d == 32 */
+            colorval = 0xffffff00;
+    }
+
+    if (d == 8)
+        pixd = pixAffineGray(pixt2, vc, colorval);
+    else  /* d == 32 */
+        pixd = pixAffineColor(pixt2, vc, colorval);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    return pixd;
+}
+
+
+/*!
+ *  pixAffinePtaColor()
+ *
+ *      Input:  pixs (32 bpp)
+ *              ptad  (3 pts of final coordinate space)
+ *              ptas  (3 pts of initial coordinate space)
+ *              colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixAffinePtaColor(PIX      *pixs,
+                  PTA      *ptad,
+                  PTA      *ptas,
+                  l_uint32  colorval)
+{
+l_float32  *vc;
+PIX        *pixd;
+
+    PROCNAME("pixAffinePtaColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL);
+    if (ptaGetCount(ptas) != 3)
+        return (PIX *)ERROR_PTR("ptas count not 3", procName, NULL);
+    if (ptaGetCount(ptad) != 3)
+        return (PIX *)ERROR_PTR("ptad count not 3", procName, NULL);
+
+        /* Get backwards transform from dest to src, and apply it */
+    getAffineXformCoeffs(ptad, ptas, &vc);
+    pixd = pixAffineColor(pixs, vc, colorval);
+    LEPT_FREE(vc);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixAffineColor()
+ *
+ *      Input:  pixs (32 bpp)
+ *              vc  (vector of 6 coefficients for affine transformation)
+ *              colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixAffineColor(PIX        *pixs,
+               l_float32  *vc,
+               l_uint32    colorval)
+{
+l_int32    i, j, w, h, d, wpls, wpld;
+l_uint32   val;
+l_uint32  *datas, *datad, *lined;
+l_float32  x, y;
+PIX       *pix1, *pix2, *pixd;
+
+    PROCNAME("pixAffineColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32)
+        return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL);
+    if (!vc)
+        return (PIX *)ERROR_PTR("vc not defined", procName, NULL);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreateTemplate(pixs);
+    pixSetAllArbitrary(pixd, colorval);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* Iterate over destination pixels */
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+                /* Compute float src pixel location corresponding to (i,j) */
+            affineXformPt(vc, j, i, &x, &y);
+            linearInterpolatePixelColor(datas, wpls, w, h, x, y, colorval,
+                                        &val);
+            *(lined + j) = val;
+        }
+    }
+
+        /* If rgba, transform the pixs alpha channel and insert in pixd */
+    if (pixGetSpp(pixs) == 4) {
+        pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+        pix2 = pixAffineGray(pix1, vc, 255);  /* bring in opaque */
+        pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixAffinePtaGray()
+ *
+ *      Input:  pixs (8 bpp)
+ *              ptad  (3 pts of final coordinate space)
+ *              ptas  (3 pts of initial coordinate space)
+ *              grayval (0 to bring in BLACK, 255 for WHITE)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixAffinePtaGray(PIX     *pixs,
+                 PTA     *ptad,
+                 PTA     *ptas,
+                 l_uint8  grayval)
+{
+l_float32  *vc;
+PIX        *pixd;
+
+    PROCNAME("pixAffinePtaGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs must be 8 bpp", procName, NULL);
+    if (ptaGetCount(ptas) != 3)
+        return (PIX *)ERROR_PTR("ptas count not 3", procName, NULL);
+    if (ptaGetCount(ptad) != 3)
+        return (PIX *)ERROR_PTR("ptad count not 3", procName, NULL);
+
+        /* Get backwards transform from dest to src, and apply it */
+    getAffineXformCoeffs(ptad, ptas, &vc);
+    pixd = pixAffineGray(pixs, vc, grayval);
+    LEPT_FREE(vc);
+
+    return pixd;
+}
+
+
+
+/*!
+ *  pixAffineGray()
+ *
+ *      Input:  pixs (8 bpp)
+ *              vc  (vector of 6 coefficients for affine transformation)
+ *              grayval (0 to bring in BLACK, 255 for WHITE)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixAffineGray(PIX        *pixs,
+              l_float32  *vc,
+              l_uint8     grayval)
+{
+l_int32    i, j, w, h, wpls, wpld, val;
+l_uint32  *datas, *datad, *lined;
+l_float32  x, y;
+PIX       *pixd;
+
+    PROCNAME("pixAffineGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs must be 8 bpp", procName, NULL);
+    if (!vc)
+        return (PIX *)ERROR_PTR("vc not defined", procName, NULL);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreateTemplate(pixs);
+    pixSetAllArbitrary(pixd, grayval);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* Iterate over destination pixels */
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+                /* Compute float src pixel location corresponding to (i,j) */
+            affineXformPt(vc, j, i, &x, &y);
+            linearInterpolatePixelGray(datas, wpls, w, h, x, y, grayval, &val);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *            Affine transform including alpha (blend) component             *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixAffinePtaWithAlpha()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              ptad  (3 pts of final coordinate space)
+ *              ptas  (3 pts of initial coordinate space)
+ *              pixg (<optional> 8 bpp, can be null)
+ *              fract (between 0.0 and 1.0, with 0.0 fully transparent
+ *                     and 1.0 fully opaque)
+ *              border (of pixels added to capture transformed source pixels)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The alpha channel is transformed separately from pixs,
+ *          and aligns with it, being fully transparent outside the
+ *          boundary of the transformed pixs.  For pixels that are fully
+ *          transparent, a blending function like pixBlendWithGrayMask()
+ *          will give zero weight to corresponding pixels in pixs.
+ *      (2) If pixg is NULL, it is generated as an alpha layer that is
+ *          partially opaque, using @fract.  Otherwise, it is cropped
+ *          to pixs if required and @fract is ignored.  The alpha channel
+ *          in pixs is never used.
+ *      (3) Colormaps are removed.
+ *      (4) When pixs is transformed, it doesn't matter what color is brought
+ *          in because the alpha channel will be transparent (0) there.
+ *      (5) To avoid losing source pixels in the destination, it may be
+ *          necessary to add a border to the source pix before doing
+ *          the affine transformation.  This can be any non-negative number.
+ *      (6) The input @ptad and @ptas are in a coordinate space before
+ *          the border is added.  Internally, we compensate for this
+ *          before doing the affine transform on the image after the border
+ *          is added.
+ *      (7) The default setting for the border values in the alpha channel
+ *          is 0 (transparent) for the outermost ring of pixels and
+ *          (0.5 * fract * 255) for the second ring.  When blended over
+ *          a second image, this
+ *          (a) shrinks the visible image to make a clean overlap edge
+ *              with an image below, and
+ *          (b) softens the edges by weakening the aliasing there.
+ *          Use l_setAlphaMaskBorder() to change these values.
+ */
+PIX *
+pixAffinePtaWithAlpha(PIX       *pixs,
+                      PTA       *ptad,
+                      PTA       *ptas,
+                      PIX       *pixg,
+                      l_float32  fract,
+                      l_int32    border)
+{
+l_int32  ws, hs, d;
+PIX     *pixd, *pixb1, *pixb2, *pixg2, *pixga;
+PTA     *ptad2, *ptas2;
+
+    PROCNAME("pixAffinePtaWithAlpha");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &ws, &hs, &d);
+    if (d != 32 && pixGetColormap(pixs) == NULL)
+        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
+    if (pixg && pixGetDepth(pixg) != 8) {
+        L_WARNING("pixg not 8 bpp; using @fract transparent alpha\n", procName);
+        pixg = NULL;
+    }
+    if (!pixg && (fract < 0.0 || fract > 1.0)) {
+        L_WARNING("invalid fract; using 1.0 (fully transparent)\n", procName);
+        fract = 1.0;
+    }
+    if (!pixg && fract == 0.0)
+        L_WARNING("fully opaque alpha; image will not be blended\n", procName);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+
+        /* Add border; the color doesn't matter */
+    pixb1 = pixAddBorder(pixs, border, 0);
+
+        /* Transform the ptr arrays to work on the bordered image */
+    ptad2 = ptaTransform(ptad, border, border, 1.0, 1.0);
+    ptas2 = ptaTransform(ptas, border, border, 1.0, 1.0);
+
+        /* Do separate affine transform of rgb channels of pixs and of pixg */
+    pixd = pixAffinePtaColor(pixb1, ptad2, ptas2, 0);
+    if (!pixg) {
+        pixg2 = pixCreate(ws, hs, 8);
+        if (fract == 1.0)
+            pixSetAll(pixg2);
+        else
+            pixSetAllArbitrary(pixg2, (l_int32)(255.0 * fract));
+    } else {
+        pixg2 = pixResizeToMatch(pixg, NULL, ws, hs);
+    }
+    if (ws > 10 && hs > 10) {  /* see note 7 */
+        pixSetBorderRingVal(pixg2, 1,
+                            (l_int32)(255.0 * fract * AlphaMaskBorderVals[0]));
+        pixSetBorderRingVal(pixg2, 2,
+                            (l_int32)(255.0 * fract * AlphaMaskBorderVals[1]));
+
+    }
+    pixb2 = pixAddBorder(pixg2, border, 0);  /* must be black border */
+    pixga = pixAffinePtaGray(pixb2, ptad2, ptas2, 0);
+    pixSetRGBComponent(pixd, pixga, L_ALPHA_CHANNEL);
+    pixSetSpp(pixd, 4);
+
+    pixDestroy(&pixg2);
+    pixDestroy(&pixb1);
+    pixDestroy(&pixb2);
+    pixDestroy(&pixga);
+    ptaDestroy(&ptad2);
+    ptaDestroy(&ptas2);
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ *                 Affine coordinate transformation            *
+ *-------------------------------------------------------------*/
+/*!
+ *  getAffineXformCoeffs()
+ *
+ *      Input:  ptas  (source 3 points; unprimed)
+ *              ptad  (transformed 3 points; primed)
+ *              &vc   (<return> vector of coefficients of transform)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  We have a set of six equations, describing the affine
+ *  transformation that takes 3 points (ptas) into 3 other
+ *  points (ptad).  These equations are:
+ *
+ *          x1' = c[0]*x1 + c[1]*y1 + c[2]
+ *          y1' = c[3]*x1 + c[4]*y1 + c[5]
+ *          x2' = c[0]*x2 + c[1]*y2 + c[2]
+ *          y2' = c[3]*x2 + c[4]*y2 + c[5]
+ *          x3' = c[0]*x3 + c[1]*y3 + c[2]
+ *          y3' = c[3]*x3 + c[4]*y3 + c[5]
+ *
+ *  This can be represented as
+ *
+ *          AC = B
+ *
+ *  where B and C are column vectors
+ *
+ *          B = [ x1' y1' x2' y2' x3' y3' ]
+ *          C = [ c[0] c[1] c[2] c[3] c[4] c[5] c[6] ]
+ *
+ *  and A is the 6x6 matrix
+ *
+ *          x1   y1   1   0    0    0
+ *           0    0   0   x1   y1   1
+ *          x2   y2   1   0    0    0
+ *           0    0   0   x2   y2   1
+ *          x3   y3   1   0    0    0
+ *           0    0   0   x3   y3   1
+ *
+ *  These six equations are solved here for the coefficients C.
+ *
+ *  These six coefficients can then be used to find the dest
+ *  point (x',y') corresponding to any src point (x,y), according
+ *  to the equations
+ *
+ *           x' = c[0]x + c[1]y + c[2]
+ *           y' = c[3]x + c[4]y + c[5]
+ *
+ *  that are implemented in affineXformPt().
+ *
+ *  !!!!!!!!!!!!!!!!!!   Very important   !!!!!!!!!!!!!!!!!!!!!!
+ *
+ *  When the affine transform is composed from a set of simple
+ *  operations such as translation, scaling and rotation,
+ *  it is built in a form to convert from the un-transformed src
+ *  point to the transformed dest point.  However, when an
+ *  affine transform is used on images, it is used in an inverted
+ *  way: it converts from the transformed dest point to the
+ *  un-transformed src point.  So, for example, if you transform
+ *  a boxa using transform A, to transform an image in the same
+ *  way you must use the inverse of A.
+ *
+ *  For example, if you transform a boxa with a 3x3 affine matrix
+ *  'mat', the analogous image transformation must use 'matinv':
+ *
+ *     boxad = boxaAffineTransform(boxas, mat);
+ *     affineInvertXform(mat, &matinv);
+ *     pixd = pixAffine(pixs, matinv, L_BRING_IN_WHITE);
+ *
+ *  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ */
+l_int32
+getAffineXformCoeffs(PTA         *ptas,
+                     PTA         *ptad,
+                     l_float32  **pvc)
+{
+l_int32     i;
+l_float32   x1, y1, x2, y2, x3, y3;
+l_float32  *b;   /* rhs vector of primed coords X'; coeffs returned in *pvc */
+l_float32  *a[6];  /* 6x6 matrix A  */
+
+    PROCNAME("getAffineXformCoeffs");
+
+    if (!ptas)
+        return ERROR_INT("ptas not defined", procName, 1);
+    if (!ptad)
+        return ERROR_INT("ptad not defined", procName, 1);
+    if (!pvc)
+        return ERROR_INT("&vc not defined", procName, 1);
+
+    if ((b = (l_float32 *)LEPT_CALLOC(6, sizeof(l_float32))) == NULL)
+        return ERROR_INT("b not made", procName, 1);
+    *pvc = b;
+
+    ptaGetPt(ptas, 0, &x1, &y1);
+    ptaGetPt(ptas, 1, &x2, &y2);
+    ptaGetPt(ptas, 2, &x3, &y3);
+    ptaGetPt(ptad, 0, &b[0], &b[1]);
+    ptaGetPt(ptad, 1, &b[2], &b[3]);
+    ptaGetPt(ptad, 2, &b[4], &b[5]);
+
+    for (i = 0; i < 6; i++)
+        if ((a[i] = (l_float32 *)LEPT_CALLOC(6, sizeof(l_float32))) == NULL)
+            return ERROR_INT("a[i] not made", procName, 1);
+
+    a[0][0] = x1;
+    a[0][1] = y1;
+    a[0][2] = 1.;
+    a[1][3] = x1;
+    a[1][4] = y1;
+    a[1][5] = 1.;
+    a[2][0] = x2;
+    a[2][1] = y2;
+    a[2][2] = 1.;
+    a[3][3] = x2;
+    a[3][4] = y2;
+    a[3][5] = 1.;
+    a[4][0] = x3;
+    a[4][1] = y3;
+    a[4][2] = 1.;
+    a[5][3] = x3;
+    a[5][4] = y3;
+    a[5][5] = 1.;
+
+    gaussjordan(a, b, 6);
+
+    for (i = 0; i < 6; i++)
+        LEPT_FREE(a[i]);
+
+    return 0;
+}
+
+
+/*!
+ *  affineInvertXform()
+ *
+ *      Input:  vc (vector of 6 coefficients)
+ *              *vci (<return> inverted transform)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The 6 affine transform coefficients are the first
+ *          two rows of a 3x3 matrix where the last row has
+ *          only a 1 in the third column.  We invert this
+ *          using gaussjordan(), and select the first 2 rows
+ *          as the coefficients of the inverse affine transform.
+ *      (2) Alternatively, we can find the inverse transform
+ *          coefficients by inverting the 2x2 submatrix,
+ *          and treating the top 2 coefficients in the 3rd column as
+ *          a RHS vector for that 2x2 submatrix.  Then the
+ *          6 inverted transform coefficients are composed of
+ *          the inverted 2x2 submatrix and the negative of the
+ *          transformed RHS vector.  Why is this so?  We have
+ *             Y = AX + R  (2 equations in 6 unknowns)
+ *          Then
+ *             X = A'Y - A'R
+ *          Gauss-jordan solves
+ *             AF = R
+ *          and puts the solution for F, which is A'R,
+ *          into the input R vector.
+ *
+ */
+l_int32
+affineInvertXform(l_float32   *vc,
+                  l_float32  **pvci)
+{
+l_int32     i;
+l_float32  *vci;
+l_float32  *a[3];
+l_float32   b[3] = {1.0, 1.0, 1.0};   /* anything; results ignored */
+
+    PROCNAME("affineInvertXform");
+
+    if (!pvci)
+        return ERROR_INT("&vci not defined", procName, 1);
+    *pvci = NULL;
+    if (!vc)
+        return ERROR_INT("vc not defined", procName, 1);
+
+    for (i = 0; i < 3; i++)
+        a[i] = (l_float32 *)LEPT_CALLOC(3, sizeof(l_float32));
+    a[0][0] = vc[0];
+    a[0][1] = vc[1];
+    a[0][2] = vc[2];
+    a[1][0] = vc[3];
+    a[1][1] = vc[4];
+    a[1][2] = vc[5];
+    a[2][2] = 1.0;
+    gaussjordan(a, b, 3);  /* now matrix a contains the inverse */
+    vci = (l_float32 *)LEPT_CALLOC(6, sizeof(l_float32));
+    *pvci = vci;
+    vci[0] = a[0][0];
+    vci[1] = a[0][1];
+    vci[2] = a[0][2];
+    vci[3] = a[1][0];
+    vci[4] = a[1][1];
+    vci[5] = a[1][2];
+
+#if 0
+        /* Alternative version, inverting a 2x2 matrix */
+    for (i = 0; i < 2; i++)
+        a[i] = (l_float32 *)LEPT_CALLOC(2, sizeof(l_float32));
+    a[0][0] = vc[0];
+    a[0][1] = vc[1];
+    a[1][0] = vc[3];
+    a[1][1] = vc[4];
+    b[0] = vc[2];
+    b[1] = vc[5];
+    gaussjordan(a, b, 2);  /* now matrix a contains the inverse */
+    vci = (l_float32 *)LEPT_CALLOC(6, sizeof(l_float32));
+    *pvci = vci;
+    vci[0] = a[0][0];
+    vci[1] = a[0][1];
+    vci[2] = -b[0];   /* note sign */
+    vci[3] = a[1][0];
+    vci[4] = a[1][1];
+    vci[5] = -b[1];   /* note sign */
+#endif
+
+    return 0;
+}
+
+
+/*!
+ *  affineXformSampledPt()
+ *
+ *      Input:  vc (vector of 6 coefficients)
+ *              (x, y)  (initial point)
+ *              (&xp, &yp)   (<return> transformed point)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This finds the nearest pixel coordinates of the transformed point.
+ *      (2) It does not check ptrs for returned data!
+ */
+l_int32
+affineXformSampledPt(l_float32  *vc,
+                     l_int32     x,
+                     l_int32     y,
+                     l_int32    *pxp,
+                     l_int32    *pyp)
+{
+    PROCNAME("affineXformSampledPt");
+
+    if (!vc)
+        return ERROR_INT("vc not defined", procName, 1);
+
+    *pxp = (l_int32)(vc[0] * x + vc[1] * y + vc[2] + 0.5);
+    *pyp = (l_int32)(vc[3] * x + vc[4] * y + vc[5] + 0.5);
+    return 0;
+}
+
+
+/*!
+ *  affineXformPt()
+ *
+ *      Input:  vc (vector of 6 coefficients)
+ *              (x, y)  (initial point)
+ *              (&xp, &yp)   (<return> transformed point)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This computes the floating point location of the transformed point.
+ *      (2) It does not check ptrs for returned data!
+ */
+l_int32
+affineXformPt(l_float32  *vc,
+              l_int32     x,
+              l_int32     y,
+              l_float32  *pxp,
+              l_float32  *pyp)
+{
+    PROCNAME("affineXformPt");
+
+    if (!vc)
+        return ERROR_INT("vc not defined", procName, 1);
+
+    *pxp = vc[0] * x + vc[1] * y + vc[2];
+    *pyp = vc[3] * x + vc[4] * y + vc[5];
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *                 Interpolation helper functions              *
+ *-------------------------------------------------------------*/
+/*!
+ *  linearInterpolatePixelColor()
+ *
+ *      Input:  datas (ptr to beginning of image data)
+ *              wpls (32-bit word/line for this data array)
+ *              w, h (of image)
+ *              x, y (floating pt location for evaluation)
+ *              colorval (color brought in from the outside when the
+ *                        input x,y location is outside the image;
+ *                        in 0xrrggbb00 format))
+ *              &val (<return> interpolated color value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a standard linear interpolation function.  It is
+ *          equivalent to area weighting on each component, and
+ *          avoids "jaggies" when rendering sharp edges.
+ */
+l_int32
+linearInterpolatePixelColor(l_uint32  *datas,
+                            l_int32    wpls,
+                            l_int32    w,
+                            l_int32    h,
+                            l_float32  x,
+                            l_float32  y,
+                            l_uint32   colorval,
+                            l_uint32  *pval)
+{
+l_int32    xpm, ypm, xp, xp2, yp, xf, yf;
+l_int32    rval, gval, bval;
+l_uint32   word00, word01, word10, word11;
+l_uint32  *lines;
+
+    PROCNAME("linearInterpolatePixelColor");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = colorval;
+    if (!datas)
+        return ERROR_INT("datas not defined", procName, 1);
+
+        /* Skip if off the edge */
+    if (x < 0.0 || y < 0.0 || x >= w || y >= h)
+        return 0;
+
+    xpm = (l_int32)(16.0 * x);
+    ypm = (l_int32)(16.0 * y);
+    xp = xpm >> 4;
+    xp2 = xp + 1 < w ? xp + 1 : xp;
+    yp = ypm >> 4;
+    if (yp + 1 >= h) wpls = 0;
+    xf = xpm & 0x0f;
+    yf = ypm & 0x0f;
+
+#if  DEBUG
+    if (xf < 0 || yf < 0)
+        fprintf(stderr, "xp = %d, yp = %d, xf = %d, yf = %d\n", xp, yp, xf, yf);
+#endif  /* DEBUG */
+
+        /* Do area weighting (eqiv. to linear interpolation) */
+    lines = datas + yp * wpls;
+    word00 = *(lines + xp);
+    word10 = *(lines + xp2);
+    word01 = *(lines + wpls + xp);
+    word11 = *(lines + wpls + xp2);
+    rval = ((16 - xf) * (16 - yf) * ((word00 >> L_RED_SHIFT) & 0xff) +
+        xf * (16 - yf) * ((word10 >> L_RED_SHIFT) & 0xff) +
+        (16 - xf) * yf * ((word01 >> L_RED_SHIFT) & 0xff) +
+        xf * yf * ((word11 >> L_RED_SHIFT) & 0xff)) / 256;
+    gval = ((16 - xf) * (16 - yf) * ((word00 >> L_GREEN_SHIFT) & 0xff) +
+        xf * (16 - yf) * ((word10 >> L_GREEN_SHIFT) & 0xff) +
+        (16 - xf) * yf * ((word01 >> L_GREEN_SHIFT) & 0xff) +
+        xf * yf * ((word11 >> L_GREEN_SHIFT) & 0xff)) / 256;
+    bval = ((16 - xf) * (16 - yf) * ((word00 >> L_BLUE_SHIFT) & 0xff) +
+        xf * (16 - yf) * ((word10 >> L_BLUE_SHIFT) & 0xff) +
+        (16 - xf) * yf * ((word01 >> L_BLUE_SHIFT) & 0xff) +
+        xf * yf * ((word11 >> L_BLUE_SHIFT) & 0xff)) / 256;
+    *pval = (rval << L_RED_SHIFT) | (gval << L_GREEN_SHIFT) |
+          (bval << L_BLUE_SHIFT);
+    return 0;
+}
+
+
+/*!
+ *  linearInterpolatePixelGray()
+ *
+ *      Input:  datas (ptr to beginning of image data)
+ *              wpls (32-bit word/line for this data array)
+ *              w, h (of image)
+ *              x, y (floating pt location for evaluation)
+ *              grayval (color brought in from the outside when the
+ *                       input x,y location is outside the image)
+ *              &val (<return> interpolated gray value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a standard linear interpolation function.  It is
+ *          equivalent to area weighting on each component, and
+ *          avoids "jaggies" when rendering sharp edges.
+ */
+l_int32
+linearInterpolatePixelGray(l_uint32  *datas,
+                           l_int32    wpls,
+                           l_int32    w,
+                           l_int32    h,
+                           l_float32  x,
+                           l_float32  y,
+                           l_int32    grayval,
+                           l_int32   *pval)
+{
+l_int32    xpm, ypm, xp, xp2, yp, xf, yf, v00, v10, v01, v11;
+l_uint32  *lines;
+
+    PROCNAME("linearInterpolatePixelGray");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = grayval;
+    if (!datas)
+        return ERROR_INT("datas not defined", procName, 1);
+
+        /* Skip if really off the edge */
+    if (x < 0.0 || y < 0.0 || x >= w || y >= h)
+        return 0;
+
+    xpm = (l_int32)(16.0 * x);
+    ypm = (l_int32)(16.0 * y);
+    xp = xpm >> 4;
+    xp2 = xp + 1 < w ? xp + 1 : xp;
+    yp = ypm >> 4;
+    if (yp + 1 >= h) wpls = 0;
+    xf = xpm & 0x0f;
+    yf = ypm & 0x0f;
+
+#if  DEBUG
+    if (xf < 0 || yf < 0)
+        fprintf(stderr, "xp = %d, yp = %d, xf = %d, yf = %d\n", xp, yp, xf, yf);
+#endif  /* DEBUG */
+
+        /* Interpolate by area weighting. */
+    lines = datas + yp * wpls;
+    v00 = (16 - xf) * (16 - yf) * GET_DATA_BYTE(lines, xp);
+    v10 = xf * (16 - yf) * GET_DATA_BYTE(lines, xp2);
+    v01 = (16 - xf) * yf * GET_DATA_BYTE(lines + wpls, xp);
+    v11 = xf * yf * GET_DATA_BYTE(lines + wpls, xp2);
+    *pval = (v00 + v01 + v10 + v11) / 256;
+    return 0;
+}
+
+
+
+/*-------------------------------------------------------------*
+ *               Gauss-jordan linear equation solver           *
+ *-------------------------------------------------------------*/
+#define  SWAP(a,b)   {temp = (a); (a) = (b); (b) = temp;}
+
+/*!
+ *  gaussjordan()
+ *
+ *      Input:  a  (n x n matrix)
+ *              b  (n x 1 right-hand side column vector)
+ *              n  (dimension)
+ *      Return: 0 if ok, 1 on error
+ *
+ *  Notes:
+ *      (1) There are two side-effects:
+ *          * The matrix a is transformed to its inverse A
+ *          * The rhs vector b is transformed to the solution x
+ *            of the linear equation ax = b
+ *      (2) The inverse A can then be used to solve the same equation with
+ *          different rhs vectors c by multiplication: x = Ac
+ *      (3) Adapted from "Numerical Recipes in C, Second Edition", 1992,
+ *          pp. 36-41 (gauss-jordan elimination)
+ */
+l_int32
+gaussjordan(l_float32  **a,
+            l_float32   *b,
+            l_int32      n)
+{
+l_int32    i, icol, irow, j, k, col, row;
+l_int32   *indexc, *indexr, *ipiv;
+l_float32  maxval, val, pivinv, temp;
+
+    PROCNAME("gaussjordan");
+
+    if (!a)
+        return ERROR_INT("a not defined", procName, 1);
+    if (!b)
+        return ERROR_INT("b not defined", procName, 1);
+
+    if ((indexc = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32))) == NULL)
+        return ERROR_INT("indexc not made", procName, 1);
+    if ((indexr = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32))) == NULL)
+        return ERROR_INT("indexr not made", procName, 1);
+    if ((ipiv = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32))) == NULL)
+        return ERROR_INT("ipiv not made", procName, 1);
+
+    for (i = 0; i < n; i++) {
+        maxval = 0.0;
+        for (j = 0; j < n; j++) {
+            if (ipiv[j] != 1) {
+                for (k = 0; k < n; k++) {
+                    if (ipiv[k] == 0) {
+                        if (fabs(a[j][k]) >= maxval) {
+                            maxval = fabs(a[j][k]);
+                            irow = j;
+                            icol = k;
+                        }
+                    } else if (ipiv[k] > 1) {
+                        return ERROR_INT("singular matrix", procName, 1);
+                    }
+                }
+            }
+        }
+        ++(ipiv[icol]);
+
+        if (irow != icol) {
+            for (col = 0; col < n; col++)
+                SWAP(a[irow][col], a[icol][col]);
+            SWAP(b[irow], b[icol]);
+        }
+
+        indexr[i] = irow;
+        indexc[i] = icol;
+        if (a[icol][icol] == 0.0)
+            return ERROR_INT("singular matrix", procName, 1);
+        pivinv = 1.0 / a[icol][icol];
+        a[icol][icol] = 1.0;
+        for (col = 0; col < n; col++)
+            a[icol][col] *= pivinv;
+        b[icol] *= pivinv;
+
+        for (row = 0; row < n; row++) {
+            if (row != icol) {
+                val = a[row][icol];
+                a[row][icol] = 0.0;
+                for (col = 0; col < n; col++)
+                    a[row][col] -= a[icol][col] * val;
+                b[row] -= b[icol] * val;
+            }
+        }
+    }
+
+    for (col = n - 1; col >= 0; col--) {
+        if (indexr[col] != indexc[col]) {
+            for (k = 0; k < n; k++)
+                SWAP(a[k][indexr[col]], a[k][indexc[col]]);
+        }
+    }
+
+    LEPT_FREE(indexr);
+    LEPT_FREE(indexc);
+    LEPT_FREE(ipiv);
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *              Sequential affine image transformation         *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixAffineSequential()
+ *
+ *      Input:  pixs
+ *              ptad  (3 pts of final coordinate space)
+ *              ptas  (3 pts of initial coordinate space)
+ *              bw    (pixels of additional border width during computation)
+ *              bh    (pixels of additional border height during computation)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The 3 pts must not be collinear.
+ *      (2) The 3 pts must be given in this order:
+ *           - origin
+ *           - a location along the x-axis
+ *           - a location along the y-axis.
+ *      (3) You must guess how much border must be added so that no
+ *          pixels are lost in the transformations from src to
+ *          dest coordinate space.  (This can be calculated but it
+ *          is a lot of work!)  For coordinate spaces that are nearly
+ *          at right angles, on a 300 ppi scanned page, the addition
+ *          of 1000 pixels on each side is usually sufficient.
+ *      (4) This is here for pedagogical reasons.  It is about 3x faster
+ *          on 1 bpp images than pixAffineSampled(), but the results
+ *          on text are much inferior.
+ */
+PIX *
+pixAffineSequential(PIX     *pixs,
+                    PTA     *ptad,
+                    PTA     *ptas,
+                    l_int32  bw,
+                    l_int32  bh)
+{
+l_int32    x1, y1, x2, y2, x3, y3;    /* ptas */
+l_int32    x1p, y1p, x2p, y2p, x3p, y3p;   /* ptad */
+l_int32    x1sc, y1sc;  /* scaled origin */
+l_float32  x2s, x2sp, scalex, scaley;
+l_float32  th3, th3p, ph2, ph2p;
+l_float32  rad2deg;
+PIX       *pixt1, *pixt2, *pixd;
+
+    PROCNAME("pixAffineSequential");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+
+    if (ptaGetCount(ptas) != 3)
+        return (PIX *)ERROR_PTR("ptas count not 3", procName, NULL);
+    if (ptaGetCount(ptad) != 3)
+        return (PIX *)ERROR_PTR("ptad count not 3", procName, NULL);
+    ptaGetIPt(ptas, 0, &x1, &y1);
+    ptaGetIPt(ptas, 1, &x2, &y2);
+    ptaGetIPt(ptas, 2, &x3, &y3);
+    ptaGetIPt(ptad, 0, &x1p, &y1p);
+    ptaGetIPt(ptad, 1, &x2p, &y2p);
+    ptaGetIPt(ptad, 2, &x3p, &y3p);
+
+    rad2deg = 180. / 3.1415926535;
+
+    if (y1 == y3)
+        return (PIX *)ERROR_PTR("y1 == y3!", procName, NULL);
+    if (y1p == y3p)
+        return (PIX *)ERROR_PTR("y1p == y3p!", procName, NULL);
+
+    if (bw != 0 || bh != 0) {
+            /* resize all points and add border to pixs */
+        x1 = x1 + bw;
+        y1 = y1 + bh;
+        x2 = x2 + bw;
+        y2 = y2 + bh;
+        x3 = x3 + bw;
+        y3 = y3 + bh;
+        x1p = x1p + bw;
+        y1p = y1p + bh;
+        x2p = x2p + bw;
+        y2p = y2p + bh;
+        x3p = x3p + bw;
+        y3p = y3p + bh;
+
+        if ((pixt1 = pixAddBorderGeneral(pixs, bw, bw, bh, bh, 0)) == NULL)
+            return (PIX *)ERROR_PTR("pixt1 not made", procName, NULL);
+    } else {
+        pixt1 = pixCopy(NULL, pixs);
+    }
+
+    /*-------------------------------------------------------------*
+        The horizontal shear is done to move the 3rd point to the
+        y axis.  This moves the 2nd point either towards or away
+        from the y axis, depending on whether it is above or below
+        the x axis.  That motion must be computed so that we know
+        the angle of vertical shear to use to get the 2nd point
+        on the x axis.  We must also know the x coordinate of the
+        2nd point in order to compute how much scaling is required
+        to match points on the axis.
+     *-------------------------------------------------------------*/
+
+        /* Shear angles required to put src points on x and y axes */
+    th3 = atan2((l_float64)(x1 - x3), (l_float64)(y1 - y3));
+    x2s = (l_float32)(x2 - ((l_float32)(y1 - y2) * (x3 - x1)) / (y1 - y3));
+    if (x2s == (l_float32)x1)
+        return (PIX *)ERROR_PTR("x2s == x1!", procName, NULL);
+    ph2 = atan2((l_float64)(y1 - y2), (l_float64)(x2s - x1));
+
+        /* Shear angles required to put dest points on x and y axes.
+         * Use the negative of these values to instead move the
+         * src points from the axes to the actual dest position.
+         * These values are also needed to scale the image. */
+    th3p = atan2((l_float64)(x1p - x3p), (l_float64)(y1p - y3p));
+    x2sp = (l_float32)(x2p - ((l_float32)(y1p - y2p) * (x3p - x1p)) / (y1p - y3p));
+    if (x2sp == (l_float32)x1p)
+        return (PIX *)ERROR_PTR("x2sp == x1p!", procName, NULL);
+    ph2p = atan2((l_float64)(y1p - y2p), (l_float64)(x2sp - x1p));
+
+        /* Shear image to first put src point 3 on the y axis,
+         * and then to put src point 2 on the x axis */
+    pixHShearIP(pixt1, y1, th3, L_BRING_IN_WHITE);
+    pixVShearIP(pixt1, x1, ph2, L_BRING_IN_WHITE);
+
+        /* Scale image to match dest scale.  The dest scale
+         * is calculated above from the angles th3p and ph2p
+         * that would be required to move the dest points to
+         * the x and y axes. */
+    scalex = (l_float32)(x2sp - x1p) / (x2s - x1);
+    scaley = (l_float32)(y3p - y1p) / (y3 - y1);
+    if ((pixt2 = pixScale(pixt1, scalex, scaley)) == NULL)
+        return (PIX *)ERROR_PTR("pixt2 not made", procName, NULL);
+
+#if  DEBUG
+    fprintf(stderr, "th3 = %5.1f deg, ph2 = %5.1f deg\n",
+            rad2deg * th3, rad2deg * ph2);
+    fprintf(stderr, "th3' = %5.1f deg, ph2' = %5.1f deg\n",
+            rad2deg * th3p, rad2deg * ph2p);
+    fprintf(stderr, "scalex = %6.3f, scaley = %6.3f\n", scalex, scaley);
+#endif  /* DEBUG */
+
+    /*-------------------------------------------------------------*
+        Scaling moves the 1st src point, which is the origin.
+        It must now be moved again to coincide with the origin
+        (1st point) of the dest.  After this is done, the 2nd
+        and 3rd points must be sheared back to the original
+        positions of the 2nd and 3rd dest points.  We use the
+        negative of the angles that were previously computed
+        for shearing those points in the dest image to x and y
+        axes, and take the shears in reverse order as well.
+     *-------------------------------------------------------------*/
+        /* Shift image to match dest origin. */
+    x1sc = (l_int32)(scalex * x1 + 0.5);   /* x comp of origin after scaling */
+    y1sc = (l_int32)(scaley * y1 + 0.5);   /* y comp of origin after scaling */
+    pixRasteropIP(pixt2, x1p - x1sc, y1p - y1sc, L_BRING_IN_WHITE);
+
+        /* Shear image to take points 2 and 3 off the axis and
+         * put them in the original dest position */
+    pixVShearIP(pixt2, x1p, -ph2p, L_BRING_IN_WHITE);
+    pixHShearIP(pixt2, y1p, -th3p, L_BRING_IN_WHITE);
+
+    if (bw != 0 || bh != 0) {
+        if ((pixd = pixRemoveBorderGeneral(pixt2, bw, bw, bh, bh)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    } else {
+        pixd = pixClone(pixt2);
+    }
+
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    return pixd;
+}
diff --git a/src/affinecompose.c b/src/affinecompose.c
new file mode 100644 (file)
index 0000000..07ec85b
--- /dev/null
@@ -0,0 +1,658 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  affinecompose.c
+ *
+ *      Composable coordinate transforms
+ *           l_float32   *createMatrix2dTranslate()
+ *           l_float32   *createMatrix2dScale()
+ *           l_float32   *createMatrix2dRotate()
+ *
+ *      Special coordinate transforms on pta
+ *           PTA         *ptaTranslate()
+ *           PTA         *ptaScale()
+ *           PTA         *ptaRotate()
+ *
+ *      Special coordinate transforms on boxa
+ *           BOXA        *boxaTranslate()
+ *           BOXA        *boxaScale()
+ *           BOXA        *boxaRotate()
+ *
+ *      General coordinate transform on pta and boxa
+ *           PTA         *ptaAffineTransform()
+ *           BOXA        *boxaAffineTransform()
+ *
+ *      Matrix operations
+ *           l_int32      l_productMatVec()
+ *           l_int32      l_productMat2()
+ *           l_int32      l_productMat3()
+ *           l_int32      l_productMat4()
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+
+/*-------------------------------------------------------------*
+ *                Composable coordinate transforms             *
+ *-------------------------------------------------------------*/
+/*!
+ *  createMatrix2dTranslate()
+ *
+ *      Input:  transx  (x component of translation wrt. the origin)
+ *              transy  (y component of translation wrt. the origin)
+ *      Return: 3x3 transform matrix, or null on error
+ *
+ *  Notes;
+ *      (1) The translation is equivalent to:
+ *             v' = Av
+ *          where v and v' are 1x3 column vectors in the form
+ *             v = [x, y, 1]^    (^ denotes transpose)
+ *          and the affine tranlation matrix is
+ *             A = [ 1   0   tx
+ *                   0   1   ty
+ *                   0   0    1  ]
+ *
+ *      (2) We consider translation as with respect to a fixed origin.
+ *          In a clipping operation, the origin moves and the points
+ *          are fixed, and you use (-tx, -ty) where (tx, ty) is the
+ *          translation vector of the origin.
+ */
+l_float32 *
+createMatrix2dTranslate(l_float32  transx,
+                        l_float32  transy)
+{
+l_float32  *mat;
+
+    PROCNAME("createMatrix2dTranslate");
+
+    if ((mat = (l_float32 *)LEPT_CALLOC(9, sizeof(l_float32))) == NULL)
+        return (l_float32 *)ERROR_PTR("mat not made", procName, NULL);
+
+    mat[0] = mat[4] = mat[8] = 1;
+    mat[2] = transx;
+    mat[5] = transy;
+    return mat;
+}
+
+
+/*!
+ *  createMatrix2dScale()
+ *
+ *      Input:  scalex  (horizontal scale factor)
+ *              scaley  (vertical scale factor)
+ *      Return: 3x3 transform matrix, or null on error
+ *
+ *  Notes;
+ *      (1) The scaling is equivalent to:
+ *             v' = Av
+ *          where v and v' are 1x3 column vectors in the form
+ *             v = [x, y, 1]^    (^ denotes transpose)
+ *          and the affine scaling matrix is
+ *             A = [ sx  0    0
+ *                   0   sy   0
+ *                   0   0    1  ]
+ *
+ *      (2) We consider scaling as with respect to a fixed origin.
+ *          In other words, the origin is the only point that doesn't
+ *          move in the scaling transform.
+ */
+l_float32 *
+createMatrix2dScale(l_float32  scalex,
+                    l_float32  scaley)
+{
+l_float32  *mat;
+
+    PROCNAME("createMatrix2dScale");
+
+    if ((mat = (l_float32 *)LEPT_CALLOC(9, sizeof(l_float32))) == NULL)
+        return (l_float32 *)ERROR_PTR("mat not made", procName, NULL);
+
+    mat[0] = scalex;
+    mat[4] = scaley;
+    mat[8] = 1;
+    return mat;
+}
+
+
+/*!
+ *  createMatrix2dRotate()
+ *
+ *      Input:  xc, yc  (location of center of rotation)
+ *              angle  (rotation in radians; clockwise is positive)
+ *      Return: 3x3 transform matrix, or null on error
+ *
+ *  Notes;
+ *      (1) The rotation is equivalent to:
+ *             v' = Av
+ *          where v and v' are 1x3 column vectors in the form
+ *             v = [x, y, 1]^    (^ denotes transpose)
+ *          and the affine rotation matrix is
+ *             A = [ cosa   -sina    xc*(1-cosa) + yc*sina
+ *                   sina    cosa    yc*(1-cosa) - xc*sina
+ *                     0       0                 1         ]
+ *
+ *          If the rotation is about the origin, (xc, yc) = (0, 0) and
+ *          this simplifies to
+ *             A = [ cosa   -sina    0
+ *                   sina    cosa    0
+ *                     0       0     1 ]
+ *
+ *          These relations follow from the following equations, which
+ *          you can convince yourself are correct as follows.  Draw a
+ *          circle centered on (xc,yc) and passing through (x,y), with
+ *          (x',y') on the arc at an angle 'a' clockwise from (x,y).
+ *          [ Hint: cos(a + b) = cosa * cosb - sina * sinb
+ *                  sin(a + b) = sina * cosb + cosa * sinb ]
+ *
+ *            x' - xc =  (x - xc) * cosa - (y - yc) * sina
+ *            y' - yc =  (x - xc) * sina + (y - yc) * cosa
+ */
+l_float32 *
+createMatrix2dRotate(l_float32  xc,
+                     l_float32  yc,
+                     l_float32  angle)
+{
+l_float32   sina, cosa;
+l_float32  *mat;
+
+    PROCNAME("createMatrix2dRotate");
+
+    if ((mat = (l_float32 *)LEPT_CALLOC(9, sizeof(l_float32))) == NULL)
+        return (l_float32 *)ERROR_PTR("mat not made", procName, NULL);
+
+    sina = sin(angle);
+    cosa = cos(angle);
+    mat[0] = mat[4] = cosa;
+    mat[1] = -sina;
+    mat[2] = xc * (1.0 - cosa) + yc * sina;
+    mat[3] = sina;
+    mat[5] = yc * (1.0 - cosa) - xc * sina;
+    mat[8] = 1;
+    return mat;
+}
+
+
+
+/*-------------------------------------------------------------*
+ *            Special coordinate transforms on pta             *
+ *-------------------------------------------------------------*/
+/*!
+ *  ptaTranslate()
+ *
+ *      Input:  ptas (for initial points)
+ *              transx  (x component of translation wrt. the origin)
+ *              transy  (y component of translation wrt. the origin)
+ *      Return: ptad  (translated points), or null on error
+ *
+ *  Notes;
+ *      (1) See createMatrix2dTranslate() for details of transform.
+ */
+PTA *
+ptaTranslate(PTA       *ptas,
+             l_float32  transx,
+             l_float32  transy)
+{
+l_int32    i, npts;
+l_float32  x, y;
+PTA       *ptad;
+
+    PROCNAME("ptaTranslate");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+
+    npts = ptaGetCount(ptas);
+    if ((ptad = ptaCreate(npts)) == NULL)
+        return (PTA *)ERROR_PTR("ptad not made", procName, NULL);
+    for (i = 0; i < npts; i++) {
+        ptaGetPt(ptas, i, &x, &y);
+        ptaAddPt(ptad, x + transx, y + transy);
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  ptaScale()
+ *
+ *      Input:  ptas (for initial points)
+ *              scalex  (horizontal scale factor)
+ *              scaley  (vertical scale factor)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes;
+ *      (1) See createMatrix2dScale() for details of transform.
+ */
+PTA *
+ptaScale(PTA       *ptas,
+         l_float32  scalex,
+         l_float32  scaley)
+{
+l_int32    i, npts;
+l_float32  x, y;
+PTA       *ptad;
+
+    PROCNAME("ptaScale");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+
+    npts = ptaGetCount(ptas);
+    if ((ptad = ptaCreate(npts)) == NULL)
+        return (PTA *)ERROR_PTR("ptad not made", procName, NULL);
+    for (i = 0; i < npts; i++) {
+        ptaGetPt(ptas, i, &x, &y);
+        ptaAddPt(ptad, scalex * x, scaley * y);
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  ptaRotate()
+ *
+ *      Input:  ptas (for initial points)
+ *              (xc, yc)  (location of center of rotation)
+ *              angle  (rotation in radians; clockwise is positive)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes;
+ *      (1) See createMatrix2dScale() for details of transform.
+ *      (2) This transform can be thought of as composed of the
+ *          sum of two parts:
+ *          (a) an (x,y)-dependent rotation about the origin:
+ *              xr = x * cosa - y * sina
+ *              yr = x * sina + y * cosa
+ *          (b) an (x,y)-independent translation that depends on the
+ *              rotation center and the angle:
+ *              xt = xc - xc * cosa + yc * sina
+ *              yt = yc - xc * sina - yc * cosa
+ *          The translation part (xt,yt) is equal to the difference
+ *          between the center (xc,yc) and the location of the
+ *          center after it is rotated about the origin.
+ */
+PTA *
+ptaRotate(PTA       *ptas,
+          l_float32  xc,
+          l_float32  yc,
+          l_float32  angle)
+{
+l_int32    i, npts;
+l_float32  x, y, xp, yp, sina, cosa;
+PTA       *ptad;
+
+    PROCNAME("ptaRotate");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+
+    npts = ptaGetCount(ptas);
+    if ((ptad = ptaCreate(npts)) == NULL)
+        return (PTA *)ERROR_PTR("ptad not made", procName, NULL);
+    sina = sin(angle);
+    cosa = cos(angle);
+    for (i = 0; i < npts; i++) {
+        ptaGetPt(ptas, i, &x, &y);
+        xp = xc + (x - xc) * cosa - (y - yc) * sina;
+        yp = yc + (x - xc) * sina + (y - yc) * cosa;
+        ptaAddPt(ptad, xp, yp);
+    }
+
+    return ptad;
+}
+
+
+/*-------------------------------------------------------------*
+ *            Special coordinate transforms on boxa            *
+ *-------------------------------------------------------------*/
+/*!
+ *  boxaTranslate()
+ *
+ *      Input:  boxas
+ *              transx  (x component of translation wrt. the origin)
+ *              transy  (y component of translation wrt. the origin)
+ *      Return: boxad  (translated boxas), or null on error
+ *
+ *  Notes;
+ *      (1) See createMatrix2dTranslate() for details of transform.
+ */
+BOXA *
+boxaTranslate(BOXA       *boxas,
+              l_float32  transx,
+              l_float32  transy)
+{
+PTA   *ptas, *ptad;
+BOXA  *boxad;
+
+    PROCNAME("boxaTranslate");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+
+    ptas = boxaConvertToPta(boxas, 4);
+    ptad = ptaTranslate(ptas, transx, transy);
+    boxad = ptaConvertToBoxa(ptad, 4);
+    ptaDestroy(&ptas);
+    ptaDestroy(&ptad);
+    return boxad;
+}
+
+
+/*!
+ *  boxaScale()
+ *
+ *      Input:  boxas
+ *              scalex  (horizontal scale factor)
+ *              scaley  (vertical scale factor)
+ *      Return: boxad  (scaled boxas), or null on error
+ *
+ *  Notes;
+ *      (1) See createMatrix2dScale() for details of transform.
+ */
+BOXA *
+boxaScale(BOXA      *boxas,
+          l_float32  scalex,
+          l_float32  scaley)
+{
+PTA   *ptas, *ptad;
+BOXA  *boxad;
+
+    PROCNAME("boxaScale");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+
+    ptas = boxaConvertToPta(boxas, 4);
+    ptad = ptaScale(ptas, scalex, scaley);
+    boxad = ptaConvertToBoxa(ptad, 4);
+    ptaDestroy(&ptas);
+    ptaDestroy(&ptad);
+    return boxad;
+}
+
+
+/*!
+ *  boxaRotate()
+ *
+ *      Input:  boxas
+ *              (xc, yc)  (location of center of rotation)
+ *              angle  (rotation in radians; clockwise is positive)
+ *      Return: boxad  (scaled boxas), or null on error
+ *
+ *  Notes;
+ *      (1) See createMatrix2dRotate() for details of transform.
+ */
+BOXA *
+boxaRotate(BOXA      *boxas,
+           l_float32  xc,
+           l_float32  yc,
+           l_float32  angle)
+{
+PTA   *ptas, *ptad;
+BOXA  *boxad;
+
+    PROCNAME("boxaRotate");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+
+    ptas = boxaConvertToPta(boxas, 4);
+    ptad = ptaRotate(ptas, xc, yc, angle);
+    boxad = ptaConvertToBoxa(ptad, 4);
+    ptaDestroy(&ptas);
+    ptaDestroy(&ptad);
+    return boxad;
+}
+
+
+/*-------------------------------------------------------------*
+ *            General affine coordinate transform              *
+ *-------------------------------------------------------------*/
+/*!
+ *  ptaAffineTransform()
+ *
+ *      Input:  ptas (for initial points)
+ *              mat  (3x3 transform matrix; canonical form)
+ *      Return: ptad  (transformed points), or null on error
+ */
+PTA *
+ptaAffineTransform(PTA        *ptas,
+                   l_float32  *mat)
+{
+l_int32    i, npts;
+l_float32  vecs[3], vecd[3];
+PTA       *ptad;
+
+    PROCNAME("ptaAffineTransform");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!mat)
+        return (PTA *)ERROR_PTR("transform not defined", procName, NULL);
+
+    vecs[2] = 1;
+    npts = ptaGetCount(ptas);
+    if ((ptad = ptaCreate(npts)) == NULL)
+        return (PTA *)ERROR_PTR("ptad not made", procName, NULL);
+    for (i = 0; i < npts; i++) {
+        ptaGetPt(ptas, i, &vecs[0], &vecs[1]);
+        l_productMatVec(mat, vecs, vecd, 3);
+        ptaAddPt(ptad, vecd[0], vecd[1]);
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  boxaAffineTransform()
+ *
+ *      Input:  boxas
+ *              mat  (3x3 transform matrix; canonical form)
+ *      Return: boxad  (transformed boxas), or null on error
+ */
+BOXA *
+boxaAffineTransform(BOXA       *boxas,
+                    l_float32  *mat)
+{
+PTA   *ptas, *ptad;
+BOXA  *boxad;
+
+    PROCNAME("boxaAffineTransform");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (!mat)
+        return (BOXA *)ERROR_PTR("transform not defined", procName, NULL);
+
+    ptas = boxaConvertToPta(boxas, 4);
+    ptad = ptaAffineTransform(ptas, mat);
+    boxad = ptaConvertToBoxa(ptad, 4);
+    ptaDestroy(&ptas);
+    ptaDestroy(&ptad);
+    return boxad;
+}
+
+
+/*-------------------------------------------------------------*
+ *                      Matrix operations                      *
+ *-------------------------------------------------------------*/
+/*!
+ *  l_productMatVec()
+ *
+ *      Input:  mat  (square matrix, as a 1-dimensional @size^2 array)
+ *              vecs (input column vector of length @size)
+ *              vecd (result column vector)
+ *              size (matrix is @size x @size; vectors are length @size)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_productMatVec(l_float32  *mat,
+                l_float32  *vecs,
+                l_float32  *vecd,
+                l_int32     size)
+{
+l_int32  i, j;
+
+    PROCNAME("l_productMatVec");
+
+    if (!mat)
+        return ERROR_INT("matrix not defined", procName, 1);
+    if (!vecs)
+        return ERROR_INT("input vector not defined", procName, 1);
+    if (!vecd)
+        return ERROR_INT("result vector not defined", procName, 1);
+
+    for (i = 0; i < size; i++) {
+        vecd[i] = 0;
+        for (j = 0; j < size; j++) {
+            vecd[i] += mat[size * i + j] * vecs[j];
+        }
+    }
+    return 0;
+}
+
+
+/*!
+ *  l_productMat2()
+ *
+ *      Input:  mat1  (square matrix, as a 1-dimensional size^2 array)
+ *              mat2  (square matrix, as a 1-dimensional size^2 array)
+ *              matd  (square matrix; product stored here)
+ *              size (of matrices)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_productMat2(l_float32  *mat1,
+              l_float32  *mat2,
+              l_float32  *matd,
+              l_int32     size)
+{
+l_int32  i, j, k, index;
+
+    PROCNAME("l_productMat2");
+
+    if (!mat1)
+        return ERROR_INT("matrix 1 not defined", procName, 1);
+    if (!mat2)
+        return ERROR_INT("matrix 2 not defined", procName, 1);
+    if (!matd)
+        return ERROR_INT("result matrix not defined", procName, 1);
+
+    for (i = 0; i < size; i++) {
+        for (j = 0; j < size; j++) {
+            index = size * i + j;
+            matd[index] = 0;
+            for (k = 0; k < size; k++)
+                 matd[index] += mat1[size * i + k] * mat2[size * k + j];
+        }
+    }
+    return 0;
+}
+
+
+/*!
+ *  l_productMat3()
+ *
+ *      Input:  mat1  (square matrix, as a 1-dimensional size^2 array)
+ *              mat2  (square matrix, as a 1-dimensional size^2 array)
+ *              mat3  (square matrix, as a 1-dimensional size^2 array)
+ *              matd  (square matrix; product stored here)
+ *              size  (of matrices)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_productMat3(l_float32  *mat1,
+              l_float32  *mat2,
+              l_float32  *mat3,
+              l_float32  *matd,
+              l_int32     size)
+{
+l_float32  *matt;
+
+    PROCNAME("l_productMat3");
+
+    if (!mat1)
+        return ERROR_INT("matrix 1 not defined", procName, 1);
+    if (!mat2)
+        return ERROR_INT("matrix 2 not defined", procName, 1);
+    if (!mat3)
+        return ERROR_INT("matrix 3 not defined", procName, 1);
+    if (!matd)
+        return ERROR_INT("result matrix not defined", procName, 1);
+
+    if ((matt = (l_float32 *)LEPT_CALLOC(size * size, sizeof(l_float32))) == NULL)
+        return ERROR_INT("matt not made", procName, 1);
+    l_productMat2(mat1, mat2, matt, size);
+    l_productMat2(matt, mat3, matd, size);
+    LEPT_FREE(matt);
+    return 0;
+}
+
+
+/*!
+ *  l_productMat4()
+ *
+ *      Input:  mat1  (square matrix, as a 1-dimensional size^2 array)
+ *              mat2  (square matrix, as a 1-dimensional size^2 array)
+ *              mat3  (square matrix, as a 1-dimensional size^2 array)
+ *              mat4  (square matrix, as a 1-dimensional size^2 array)
+ *              matd  (square matrix; product stored here)
+ *              size  (of matrices)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_productMat4(l_float32  *mat1,
+              l_float32  *mat2,
+              l_float32  *mat3,
+              l_float32  *mat4,
+              l_float32  *matd,
+              l_int32     size)
+{
+l_float32  *matt;
+
+    PROCNAME("l_productMat4");
+
+    if (!mat1)
+        return ERROR_INT("matrix 1 not defined", procName, 1);
+    if (!mat2)
+        return ERROR_INT("matrix 2 not defined", procName, 1);
+    if (!mat3)
+        return ERROR_INT("matrix 3 not defined", procName, 1);
+    if (!matd)
+        return ERROR_INT("result matrix not defined", procName, 1);
+
+    if ((matt = (l_float32 *)LEPT_CALLOC(size * size, sizeof(l_float32))) == NULL)
+        return ERROR_INT("matt not made", procName, 1);
+    l_productMat3(mat1, mat2, mat3, matt, size);
+    l_productMat2(matt, mat4, matd, size);
+    LEPT_FREE(matt);
+    return 0;
+}
diff --git a/src/allheaders.h b/src/allheaders.h
new file mode 100644 (file)
index 0000000..8ec6a24
--- /dev/null
@@ -0,0 +1,2636 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_ALLHEADERS_H
+#define  LEPTONICA_ALLHEADERS_H
+
+
+#define LIBLEPT_MAJOR_VERSION   1
+#define LIBLEPT_MINOR_VERSION   73
+
+#include "alltypes.h"
+
+#ifndef NO_PROTOS
+/*
+ *  These prototypes were autogen'd by xtractprotos, v. 1.5
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif  /* __cplusplus */
+
+LEPT_DLL extern PIX * pixCleanBackgroundToWhite ( PIX *pixs, PIX *pixim, PIX *pixg, l_float32 gamma, l_int32 blackval, l_int32 whiteval );
+LEPT_DLL extern PIX * pixBackgroundNormSimple ( PIX *pixs, PIX *pixim, PIX *pixg );
+LEPT_DLL extern PIX * pixBackgroundNorm ( PIX *pixs, PIX *pixim, PIX *pixg, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, l_int32 bgval, l_int32 smoothx, l_int32 smoothy );
+LEPT_DLL extern PIX * pixBackgroundNormMorph ( PIX *pixs, PIX *pixim, l_int32 reduction, l_int32 size, l_int32 bgval );
+LEPT_DLL extern l_int32 pixBackgroundNormGrayArray ( PIX *pixs, PIX *pixim, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, l_int32 bgval, l_int32 smoothx, l_int32 smoothy, PIX **ppixd );
+LEPT_DLL extern l_int32 pixBackgroundNormRGBArrays ( PIX *pixs, PIX *pixim, PIX *pixg, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, l_int32 bgval, l_int32 smoothx, l_int32 smoothy, PIX **ppixr, PIX **ppixg, PIX **ppixb );
+LEPT_DLL extern l_int32 pixBackgroundNormGrayArrayMorph ( PIX *pixs, PIX *pixim, l_int32 reduction, l_int32 size, l_int32 bgval, PIX **ppixd );
+LEPT_DLL extern l_int32 pixBackgroundNormRGBArraysMorph ( PIX *pixs, PIX *pixim, l_int32 reduction, l_int32 size, l_int32 bgval, PIX **ppixr, PIX **ppixg, PIX **ppixb );
+LEPT_DLL extern l_int32 pixGetBackgroundGrayMap ( PIX *pixs, PIX *pixim, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, PIX **ppixd );
+LEPT_DLL extern l_int32 pixGetBackgroundRGBMap ( PIX *pixs, PIX *pixim, PIX *pixg, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, PIX **ppixmr, PIX **ppixmg, PIX **ppixmb );
+LEPT_DLL extern l_int32 pixGetBackgroundGrayMapMorph ( PIX *pixs, PIX *pixim, l_int32 reduction, l_int32 size, PIX **ppixm );
+LEPT_DLL extern l_int32 pixGetBackgroundRGBMapMorph ( PIX *pixs, PIX *pixim, l_int32 reduction, l_int32 size, PIX **ppixmr, PIX **ppixmg, PIX **ppixmb );
+LEPT_DLL extern l_int32 pixFillMapHoles ( PIX *pix, l_int32 nx, l_int32 ny, l_int32 filltype );
+LEPT_DLL extern PIX * pixExtendByReplication ( PIX *pixs, l_int32 addw, l_int32 addh );
+LEPT_DLL extern l_int32 pixSmoothConnectedRegions ( PIX *pixs, PIX *pixm, l_int32 factor );
+LEPT_DLL extern PIX * pixGetInvBackgroundMap ( PIX *pixs, l_int32 bgval, l_int32 smoothx, l_int32 smoothy );
+LEPT_DLL extern PIX * pixApplyInvBackgroundGrayMap ( PIX *pixs, PIX *pixm, l_int32 sx, l_int32 sy );
+LEPT_DLL extern PIX * pixApplyInvBackgroundRGBMap ( PIX *pixs, PIX *pixmr, PIX *pixmg, PIX *pixmb, l_int32 sx, l_int32 sy );
+LEPT_DLL extern PIX * pixApplyVariableGrayMap ( PIX *pixs, PIX *pixg, l_int32 target );
+LEPT_DLL extern PIX * pixGlobalNormRGB ( PIX *pixd, PIX *pixs, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 mapval );
+LEPT_DLL extern PIX * pixGlobalNormNoSatRGB ( PIX *pixd, PIX *pixs, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 factor, l_float32 rank );
+LEPT_DLL extern l_int32 pixThresholdSpreadNorm ( PIX *pixs, l_int32 filtertype, l_int32 edgethresh, l_int32 smoothx, l_int32 smoothy, l_float32 gamma, l_int32 minval, l_int32 maxval, l_int32 targetthresh, PIX **ppixth, PIX **ppixb, PIX **ppixd );
+LEPT_DLL extern PIX * pixBackgroundNormFlex ( PIX *pixs, l_int32 sx, l_int32 sy, l_int32 smoothx, l_int32 smoothy, l_int32 delta );
+LEPT_DLL extern PIX * pixContrastNorm ( PIX *pixd, PIX *pixs, l_int32 sx, l_int32 sy, l_int32 mindiff, l_int32 smoothx, l_int32 smoothy );
+LEPT_DLL extern l_int32 pixMinMaxTiles ( PIX *pixs, l_int32 sx, l_int32 sy, l_int32 mindiff, l_int32 smoothx, l_int32 smoothy, PIX **ppixmin, PIX **ppixmax );
+LEPT_DLL extern l_int32 pixSetLowContrast ( PIX *pixs1, PIX *pixs2, l_int32 mindiff );
+LEPT_DLL extern PIX * pixLinearTRCTiled ( PIX *pixd, PIX *pixs, l_int32 sx, l_int32 sy, PIX *pixmin, PIX *pixmax );
+LEPT_DLL extern PIX * pixAffineSampledPta ( PIX *pixs, PTA *ptad, PTA *ptas, l_int32 incolor );
+LEPT_DLL extern PIX * pixAffineSampled ( PIX *pixs, l_float32 *vc, l_int32 incolor );
+LEPT_DLL extern PIX * pixAffinePta ( PIX *pixs, PTA *ptad, PTA *ptas, l_int32 incolor );
+LEPT_DLL extern PIX * pixAffine ( PIX *pixs, l_float32 *vc, l_int32 incolor );
+LEPT_DLL extern PIX * pixAffinePtaColor ( PIX *pixs, PTA *ptad, PTA *ptas, l_uint32 colorval );
+LEPT_DLL extern PIX * pixAffineColor ( PIX *pixs, l_float32 *vc, l_uint32 colorval );
+LEPT_DLL extern PIX * pixAffinePtaGray ( PIX *pixs, PTA *ptad, PTA *ptas, l_uint8 grayval );
+LEPT_DLL extern PIX * pixAffineGray ( PIX *pixs, l_float32 *vc, l_uint8 grayval );
+LEPT_DLL extern PIX * pixAffinePtaWithAlpha ( PIX *pixs, PTA *ptad, PTA *ptas, PIX *pixg, l_float32 fract, l_int32 border );
+LEPT_DLL extern l_int32 getAffineXformCoeffs ( PTA *ptas, PTA *ptad, l_float32 **pvc );
+LEPT_DLL extern l_int32 affineInvertXform ( l_float32 *vc, l_float32 **pvci );
+LEPT_DLL extern l_int32 affineXformSampledPt ( l_float32 *vc, l_int32 x, l_int32 y, l_int32 *pxp, l_int32 *pyp );
+LEPT_DLL extern l_int32 affineXformPt ( l_float32 *vc, l_int32 x, l_int32 y, l_float32 *pxp, l_float32 *pyp );
+LEPT_DLL extern l_int32 linearInterpolatePixelColor ( l_uint32 *datas, l_int32 wpls, l_int32 w, l_int32 h, l_float32 x, l_float32 y, l_uint32 colorval, l_uint32 *pval );
+LEPT_DLL extern l_int32 linearInterpolatePixelGray ( l_uint32 *datas, l_int32 wpls, l_int32 w, l_int32 h, l_float32 x, l_float32 y, l_int32 grayval, l_int32 *pval );
+LEPT_DLL extern l_int32 gaussjordan ( l_float32 **a, l_float32 *b, l_int32 n );
+LEPT_DLL extern PIX * pixAffineSequential ( PIX *pixs, PTA *ptad, PTA *ptas, l_int32 bw, l_int32 bh );
+LEPT_DLL extern l_float32 * createMatrix2dTranslate ( l_float32 transx, l_float32 transy );
+LEPT_DLL extern l_float32 * createMatrix2dScale ( l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern l_float32 * createMatrix2dRotate ( l_float32 xc, l_float32 yc, l_float32 angle );
+LEPT_DLL extern PTA * ptaTranslate ( PTA *ptas, l_float32 transx, l_float32 transy );
+LEPT_DLL extern PTA * ptaScale ( PTA *ptas, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PTA * ptaRotate ( PTA *ptas, l_float32 xc, l_float32 yc, l_float32 angle );
+LEPT_DLL extern BOXA * boxaTranslate ( BOXA *boxas, l_float32 transx, l_float32 transy );
+LEPT_DLL extern BOXA * boxaScale ( BOXA *boxas, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern BOXA * boxaRotate ( BOXA *boxas, l_float32 xc, l_float32 yc, l_float32 angle );
+LEPT_DLL extern PTA * ptaAffineTransform ( PTA *ptas, l_float32 *mat );
+LEPT_DLL extern BOXA * boxaAffineTransform ( BOXA *boxas, l_float32 *mat );
+LEPT_DLL extern l_int32 l_productMatVec ( l_float32 *mat, l_float32 *vecs, l_float32 *vecd, l_int32 size );
+LEPT_DLL extern l_int32 l_productMat2 ( l_float32 *mat1, l_float32 *mat2, l_float32 *matd, l_int32 size );
+LEPT_DLL extern l_int32 l_productMat3 ( l_float32 *mat1, l_float32 *mat2, l_float32 *mat3, l_float32 *matd, l_int32 size );
+LEPT_DLL extern l_int32 l_productMat4 ( l_float32 *mat1, l_float32 *mat2, l_float32 *mat3, l_float32 *mat4, l_float32 *matd, l_int32 size );
+LEPT_DLL extern l_int32 l_getDataBit ( void *line, l_int32 n );
+LEPT_DLL extern void l_setDataBit ( void *line, l_int32 n );
+LEPT_DLL extern void l_clearDataBit ( void *line, l_int32 n );
+LEPT_DLL extern void l_setDataBitVal ( void *line, l_int32 n, l_int32 val );
+LEPT_DLL extern l_int32 l_getDataDibit ( void *line, l_int32 n );
+LEPT_DLL extern void l_setDataDibit ( void *line, l_int32 n, l_int32 val );
+LEPT_DLL extern void l_clearDataDibit ( void *line, l_int32 n );
+LEPT_DLL extern l_int32 l_getDataQbit ( void *line, l_int32 n );
+LEPT_DLL extern void l_setDataQbit ( void *line, l_int32 n, l_int32 val );
+LEPT_DLL extern void l_clearDataQbit ( void *line, l_int32 n );
+LEPT_DLL extern l_int32 l_getDataByte ( void *line, l_int32 n );
+LEPT_DLL extern void l_setDataByte ( void *line, l_int32 n, l_int32 val );
+LEPT_DLL extern l_int32 l_getDataTwoBytes ( void *line, l_int32 n );
+LEPT_DLL extern void l_setDataTwoBytes ( void *line, l_int32 n, l_int32 val );
+LEPT_DLL extern l_int32 l_getDataFourBytes ( void *line, l_int32 n );
+LEPT_DLL extern void l_setDataFourBytes ( void *line, l_int32 n, l_int32 val );
+LEPT_DLL extern char * barcodeDispatchDecoder ( char *barstr, l_int32 format, l_int32 debugflag );
+LEPT_DLL extern l_int32 barcodeFormatIsSupported ( l_int32 format );
+LEPT_DLL extern NUMA * pixFindBaselines ( PIX *pixs, PTA **ppta, l_int32 debug );
+LEPT_DLL extern PIX * pixDeskewLocal ( PIX *pixs, l_int32 nslices, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta );
+LEPT_DLL extern l_int32 pixGetLocalSkewTransform ( PIX *pixs, l_int32 nslices, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, PTA **pptas, PTA **pptad );
+LEPT_DLL extern NUMA * pixGetLocalSkewAngles ( PIX *pixs, l_int32 nslices, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, l_float32 *pa, l_float32 *pb );
+LEPT_DLL extern L_BBUFFER * bbufferCreate ( l_uint8 *indata, l_int32 nalloc );
+LEPT_DLL extern void bbufferDestroy ( L_BBUFFER **pbb );
+LEPT_DLL extern l_uint8 * bbufferDestroyAndSaveData ( L_BBUFFER **pbb, size_t *pnbytes );
+LEPT_DLL extern l_int32 bbufferRead ( L_BBUFFER *bb, l_uint8 *src, l_int32 nbytes );
+LEPT_DLL extern l_int32 bbufferReadStream ( L_BBUFFER *bb, FILE *fp, l_int32 nbytes );
+LEPT_DLL extern l_int32 bbufferExtendArray ( L_BBUFFER *bb, l_int32 nbytes );
+LEPT_DLL extern l_int32 bbufferWrite ( L_BBUFFER *bb, l_uint8 *dest, size_t nbytes, size_t *pnout );
+LEPT_DLL extern l_int32 bbufferWriteStream ( L_BBUFFER *bb, FILE *fp, size_t nbytes, size_t *pnout );
+LEPT_DLL extern PIX * pixBilateral ( PIX *pixs, l_float32 spatial_stdev, l_float32 range_stdev, l_int32 ncomps, l_int32 reduction );
+LEPT_DLL extern PIX * pixBilateralGray ( PIX *pixs, l_float32 spatial_stdev, l_float32 range_stdev, l_int32 ncomps, l_int32 reduction );
+LEPT_DLL extern PIX * pixBilateralExact ( PIX *pixs, L_KERNEL *spatial_kel, L_KERNEL *range_kel );
+LEPT_DLL extern PIX * pixBilateralGrayExact ( PIX *pixs, L_KERNEL *spatial_kel, L_KERNEL *range_kel );
+LEPT_DLL extern PIX* pixBlockBilateralExact ( PIX *pixs, l_float32 spatial_stdev, l_float32 range_stdev );
+LEPT_DLL extern L_KERNEL * makeRangeKernel ( l_float32 range_stdev );
+LEPT_DLL extern PIX * pixBilinearSampledPta ( PIX *pixs, PTA *ptad, PTA *ptas, l_int32 incolor );
+LEPT_DLL extern PIX * pixBilinearSampled ( PIX *pixs, l_float32 *vc, l_int32 incolor );
+LEPT_DLL extern PIX * pixBilinearPta ( PIX *pixs, PTA *ptad, PTA *ptas, l_int32 incolor );
+LEPT_DLL extern PIX * pixBilinear ( PIX *pixs, l_float32 *vc, l_int32 incolor );
+LEPT_DLL extern PIX * pixBilinearPtaColor ( PIX *pixs, PTA *ptad, PTA *ptas, l_uint32 colorval );
+LEPT_DLL extern PIX * pixBilinearColor ( PIX *pixs, l_float32 *vc, l_uint32 colorval );
+LEPT_DLL extern PIX * pixBilinearPtaGray ( PIX *pixs, PTA *ptad, PTA *ptas, l_uint8 grayval );
+LEPT_DLL extern PIX * pixBilinearGray ( PIX *pixs, l_float32 *vc, l_uint8 grayval );
+LEPT_DLL extern PIX * pixBilinearPtaWithAlpha ( PIX *pixs, PTA *ptad, PTA *ptas, PIX *pixg, l_float32 fract, l_int32 border );
+LEPT_DLL extern l_int32 getBilinearXformCoeffs ( PTA *ptas, PTA *ptad, l_float32 **pvc );
+LEPT_DLL extern l_int32 bilinearXformSampledPt ( l_float32 *vc, l_int32 x, l_int32 y, l_int32 *pxp, l_int32 *pyp );
+LEPT_DLL extern l_int32 bilinearXformPt ( l_float32 *vc, l_int32 x, l_int32 y, l_float32 *pxp, l_float32 *pyp );
+LEPT_DLL extern l_int32 pixOtsuAdaptiveThreshold ( PIX *pixs, l_int32 sx, l_int32 sy, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, PIX **ppixth, PIX **ppixd );
+LEPT_DLL extern PIX * pixOtsuThreshOnBackgroundNorm ( PIX *pixs, PIX *pixim, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, l_int32 bgval, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, l_int32 *pthresh );
+LEPT_DLL extern PIX * pixMaskedThreshOnBackgroundNorm ( PIX *pixs, PIX *pixim, l_int32 sx, l_int32 sy, l_int32 thresh, l_int32 mincount, l_int32 smoothx, l_int32 smoothy, l_float32 scorefract, l_int32 *pthresh );
+LEPT_DLL extern l_int32 pixSauvolaBinarizeTiled ( PIX *pixs, l_int32 whsize, l_float32 factor, l_int32 nx, l_int32 ny, PIX **ppixth, PIX **ppixd );
+LEPT_DLL extern l_int32 pixSauvolaBinarize ( PIX *pixs, l_int32 whsize, l_float32 factor, l_int32 addborder, PIX **ppixm, PIX **ppixsd, PIX **ppixth, PIX **ppixd );
+LEPT_DLL extern PIX * pixSauvolaGetThreshold ( PIX *pixm, PIX *pixms, l_float32 factor, PIX **ppixsd );
+LEPT_DLL extern PIX * pixApplyLocalThreshold ( PIX *pixs, PIX *pixth, l_int32 redfactor );
+LEPT_DLL extern l_int32 pixThresholdByConnComp ( PIX *pixs, PIX *pixm, l_int32 start, l_int32 end, l_int32 incr, l_float32 thresh48, l_float32 threshdiff, l_int32 *pglobthresh, PIX **ppixd, l_int32 debugflag );
+LEPT_DLL extern PIX * pixExpandBinaryReplicate ( PIX *pixs, l_int32 factor );
+LEPT_DLL extern PIX * pixExpandBinaryPower2 ( PIX *pixs, l_int32 factor );
+LEPT_DLL extern PIX * pixReduceBinary2 ( PIX *pixs, l_uint8 *intab );
+LEPT_DLL extern PIX * pixReduceRankBinaryCascade ( PIX *pixs, l_int32 level1, l_int32 level2, l_int32 level3, l_int32 level4 );
+LEPT_DLL extern PIX * pixReduceRankBinary2 ( PIX *pixs, l_int32 level, l_uint8 *intab );
+LEPT_DLL extern l_uint8 * makeSubsampleTab2x ( void );
+LEPT_DLL extern PIX * pixBlend ( PIX *pixs1, PIX *pixs2, l_int32 x, l_int32 y, l_float32 fract );
+LEPT_DLL extern PIX * pixBlendMask ( PIX *pixd, PIX *pixs1, PIX *pixs2, l_int32 x, l_int32 y, l_float32 fract, l_int32 type );
+LEPT_DLL extern PIX * pixBlendGray ( PIX *pixd, PIX *pixs1, PIX *pixs2, l_int32 x, l_int32 y, l_float32 fract, l_int32 type, l_int32 transparent, l_uint32 transpix );
+LEPT_DLL extern PIX * pixBlendGrayInverse ( PIX *pixd, PIX *pixs1, PIX *pixs2, l_int32 x, l_int32 y, l_float32 fract );
+LEPT_DLL extern PIX * pixBlendColor ( PIX *pixd, PIX *pixs1, PIX *pixs2, l_int32 x, l_int32 y, l_float32 fract, l_int32 transparent, l_uint32 transpix );
+LEPT_DLL extern PIX * pixBlendColorByChannel ( PIX *pixd, PIX *pixs1, PIX *pixs2, l_int32 x, l_int32 y, l_float32 rfract, l_float32 gfract, l_float32 bfract, l_int32 transparent, l_uint32 transpix );
+LEPT_DLL extern PIX * pixBlendGrayAdapt ( PIX *pixd, PIX *pixs1, PIX *pixs2, l_int32 x, l_int32 y, l_float32 fract, l_int32 shift );
+LEPT_DLL extern PIX * pixFadeWithGray ( PIX *pixs, PIX *pixb, l_float32 factor, l_int32 type );
+LEPT_DLL extern PIX * pixBlendHardLight ( PIX *pixd, PIX *pixs1, PIX *pixs2, l_int32 x, l_int32 y, l_float32 fract );
+LEPT_DLL extern l_int32 pixBlendCmap ( PIX *pixs, PIX *pixb, l_int32 x, l_int32 y, l_int32 sindex );
+LEPT_DLL extern PIX * pixBlendWithGrayMask ( PIX *pixs1, PIX *pixs2, PIX *pixg, l_int32 x, l_int32 y );
+LEPT_DLL extern PIX * pixBlendBackgroundToColor ( PIX *pixd, PIX *pixs, BOX *box, l_uint32 color, l_float32 gamma, l_int32 minval, l_int32 maxval );
+LEPT_DLL extern PIX * pixMultiplyByColor ( PIX *pixd, PIX *pixs, BOX *box, l_uint32 color );
+LEPT_DLL extern PIX * pixAlphaBlendUniform ( PIX *pixs, l_uint32 color );
+LEPT_DLL extern PIX * pixAddAlphaToBlend ( PIX *pixs, l_float32 fract, l_int32 invert );
+LEPT_DLL extern PIX * pixSetAlphaOverWhite ( PIX *pixs );
+LEPT_DLL extern L_BMF * bmfCreate ( const char *dir, l_int32 fontsize );
+LEPT_DLL extern void bmfDestroy ( L_BMF **pbmf );
+LEPT_DLL extern PIX * bmfGetPix ( L_BMF *bmf, char chr );
+LEPT_DLL extern l_int32 bmfGetWidth ( L_BMF *bmf, char chr, l_int32 *pw );
+LEPT_DLL extern l_int32 bmfGetBaseline ( L_BMF *bmf, char chr, l_int32 *pbaseline );
+LEPT_DLL extern PIXA * pixaGetFont ( const char *dir, l_int32 fontsize, l_int32 *pbl0, l_int32 *pbl1, l_int32 *pbl2 );
+LEPT_DLL extern l_int32 pixaSaveFont ( const char *indir, const char *outdir, l_int32 fontsize );
+LEPT_DLL extern PIXA * pixaGenerateFontFromFile ( const char *dir, l_int32 fontsize, l_int32 *pbl0, l_int32 *pbl1, l_int32 *pbl2 );
+LEPT_DLL extern PIXA * pixaGenerateFontFromString ( l_int32 fontsize, l_int32 *pbl0, l_int32 *pbl1, l_int32 *pbl2 );
+LEPT_DLL extern PIXA * pixaGenerateFont ( PIX *pixs, l_int32 fontsize, l_int32 *pbl0, l_int32 *pbl1, l_int32 *pbl2 );
+LEPT_DLL extern PIX * pixReadStreamBmp ( FILE *fp );
+LEPT_DLL extern l_int32 pixWriteStreamBmp ( FILE *fp, PIX *pix );
+LEPT_DLL extern PIX * pixReadMemBmp ( const l_uint8 *cdata, size_t size );
+LEPT_DLL extern l_int32 pixWriteMemBmp ( l_uint8 **pdata, size_t *psize, PIX *pix );
+LEPT_DLL extern void * l_bootnum_gen1 ( void );
+LEPT_DLL extern void * l_bootnum_gen2 ( void );
+LEPT_DLL extern BOX * boxCreate ( l_int32 x, l_int32 y, l_int32 w, l_int32 h );
+LEPT_DLL extern BOX * boxCreateValid ( l_int32 x, l_int32 y, l_int32 w, l_int32 h );
+LEPT_DLL extern BOX * boxCopy ( BOX *box );
+LEPT_DLL extern BOX * boxClone ( BOX *box );
+LEPT_DLL extern void boxDestroy ( BOX **pbox );
+LEPT_DLL extern l_int32 boxGetGeometry ( BOX *box, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_int32 boxSetGeometry ( BOX *box, l_int32 x, l_int32 y, l_int32 w, l_int32 h );
+LEPT_DLL extern l_int32 boxGetSideLocation ( BOX *box, l_int32 side, l_int32 *ploc );
+LEPT_DLL extern l_int32 boxGetRefcount ( BOX *box );
+LEPT_DLL extern l_int32 boxChangeRefcount ( BOX *box, l_int32 delta );
+LEPT_DLL extern l_int32 boxIsValid ( BOX *box, l_int32 *pvalid );
+LEPT_DLL extern BOXA * boxaCreate ( l_int32 n );
+LEPT_DLL extern BOXA * boxaCopy ( BOXA *boxa, l_int32 copyflag );
+LEPT_DLL extern void boxaDestroy ( BOXA **pboxa );
+LEPT_DLL extern l_int32 boxaAddBox ( BOXA *boxa, BOX *box, l_int32 copyflag );
+LEPT_DLL extern l_int32 boxaExtendArray ( BOXA *boxa );
+LEPT_DLL extern l_int32 boxaExtendArrayToSize ( BOXA *boxa, l_int32 size );
+LEPT_DLL extern l_int32 boxaGetCount ( BOXA *boxa );
+LEPT_DLL extern l_int32 boxaGetValidCount ( BOXA *boxa );
+LEPT_DLL extern BOX * boxaGetBox ( BOXA *boxa, l_int32 index, l_int32 accessflag );
+LEPT_DLL extern BOX * boxaGetValidBox ( BOXA *boxa, l_int32 index, l_int32 accessflag );
+LEPT_DLL extern l_int32 boxaGetBoxGeometry ( BOXA *boxa, l_int32 index, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_int32 boxaIsFull ( BOXA *boxa, l_int32 *pfull );
+LEPT_DLL extern l_int32 boxaReplaceBox ( BOXA *boxa, l_int32 index, BOX *box );
+LEPT_DLL extern l_int32 boxaInsertBox ( BOXA *boxa, l_int32 index, BOX *box );
+LEPT_DLL extern l_int32 boxaRemoveBox ( BOXA *boxa, l_int32 index );
+LEPT_DLL extern l_int32 boxaRemoveBoxAndSave ( BOXA *boxa, l_int32 index, BOX **pbox );
+LEPT_DLL extern BOXA * boxaSaveValid ( BOXA *boxas, l_int32 copyflag );
+LEPT_DLL extern l_int32 boxaInitFull ( BOXA *boxa, BOX *box );
+LEPT_DLL extern l_int32 boxaClear ( BOXA *boxa );
+LEPT_DLL extern BOXAA * boxaaCreate ( l_int32 n );
+LEPT_DLL extern BOXAA * boxaaCopy ( BOXAA *baas, l_int32 copyflag );
+LEPT_DLL extern void boxaaDestroy ( BOXAA **pbaa );
+LEPT_DLL extern l_int32 boxaaAddBoxa ( BOXAA *baa, BOXA *ba, l_int32 copyflag );
+LEPT_DLL extern l_int32 boxaaExtendArray ( BOXAA *baa );
+LEPT_DLL extern l_int32 boxaaExtendArrayToSize ( BOXAA *baa, l_int32 size );
+LEPT_DLL extern l_int32 boxaaGetCount ( BOXAA *baa );
+LEPT_DLL extern l_int32 boxaaGetBoxCount ( BOXAA *baa );
+LEPT_DLL extern BOXA * boxaaGetBoxa ( BOXAA *baa, l_int32 index, l_int32 accessflag );
+LEPT_DLL extern BOX * boxaaGetBox ( BOXAA *baa, l_int32 iboxa, l_int32 ibox, l_int32 accessflag );
+LEPT_DLL extern l_int32 boxaaInitFull ( BOXAA *baa, BOXA *boxa );
+LEPT_DLL extern l_int32 boxaaExtendWithInit ( BOXAA *baa, l_int32 maxindex, BOXA *boxa );
+LEPT_DLL extern l_int32 boxaaReplaceBoxa ( BOXAA *baa, l_int32 index, BOXA *boxa );
+LEPT_DLL extern l_int32 boxaaInsertBoxa ( BOXAA *baa, l_int32 index, BOXA *boxa );
+LEPT_DLL extern l_int32 boxaaRemoveBoxa ( BOXAA *baa, l_int32 index );
+LEPT_DLL extern l_int32 boxaaAddBox ( BOXAA *baa, l_int32 index, BOX *box, l_int32 accessflag );
+LEPT_DLL extern BOXAA * boxaaReadFromFiles ( const char *dirname, const char *substr, l_int32 first, l_int32 nfiles );
+LEPT_DLL extern BOXAA * boxaaRead ( const char *filename );
+LEPT_DLL extern BOXAA * boxaaReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 boxaaWrite ( const char *filename, BOXAA *baa );
+LEPT_DLL extern l_int32 boxaaWriteStream ( FILE *fp, BOXAA *baa );
+LEPT_DLL extern BOXA * boxaRead ( const char *filename );
+LEPT_DLL extern BOXA * boxaReadStream ( FILE *fp );
+LEPT_DLL extern BOXA * boxaReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_int32 boxaWrite ( const char *filename, BOXA *boxa );
+LEPT_DLL extern l_int32 boxaWriteStream ( FILE *fp, BOXA *boxa );
+LEPT_DLL extern l_int32 boxaWriteMem ( l_uint8 **pdata, size_t *psize, BOXA *boxa );
+LEPT_DLL extern l_int32 boxPrintStreamInfo ( FILE *fp, BOX *box );
+LEPT_DLL extern l_int32 boxContains ( BOX *box1, BOX *box2, l_int32 *presult );
+LEPT_DLL extern l_int32 boxIntersects ( BOX *box1, BOX *box2, l_int32 *presult );
+LEPT_DLL extern BOXA * boxaContainedInBox ( BOXA *boxas, BOX *box );
+LEPT_DLL extern BOXA * boxaIntersectsBox ( BOXA *boxas, BOX *box );
+LEPT_DLL extern BOXA * boxaClipToBox ( BOXA *boxas, BOX *box );
+LEPT_DLL extern BOXA * boxaCombineOverlaps ( BOXA *boxas );
+LEPT_DLL extern BOX * boxOverlapRegion ( BOX *box1, BOX *box2 );
+LEPT_DLL extern BOX * boxBoundingRegion ( BOX *box1, BOX *box2 );
+LEPT_DLL extern l_int32 boxOverlapFraction ( BOX *box1, BOX *box2, l_float32 *pfract );
+LEPT_DLL extern l_int32 boxOverlapArea ( BOX *box1, BOX *box2, l_int32 *parea );
+LEPT_DLL extern BOXA * boxaHandleOverlaps ( BOXA *boxas, l_int32 op, l_int32 range, l_float32 min_overlap, l_float32 max_ratio, NUMA **pnamap );
+LEPT_DLL extern l_int32 boxSeparationDistance ( BOX *box1, BOX *box2, l_int32 *ph_sep, l_int32 *pv_sep );
+LEPT_DLL extern l_int32 boxContainsPt ( BOX *box, l_float32 x, l_float32 y, l_int32 *pcontains );
+LEPT_DLL extern BOX * boxaGetNearestToPt ( BOXA *boxa, l_int32 x, l_int32 y );
+LEPT_DLL extern l_int32 boxGetCenter ( BOX *box, l_float32 *pcx, l_float32 *pcy );
+LEPT_DLL extern l_int32 boxIntersectByLine ( BOX *box, l_int32 x, l_int32 y, l_float32 slope, l_int32 *px1, l_int32 *py1, l_int32 *px2, l_int32 *py2, l_int32 *pn );
+LEPT_DLL extern BOX * boxClipToRectangle ( BOX *box, l_int32 wi, l_int32 hi );
+LEPT_DLL extern l_int32 boxClipToRectangleParams ( BOX *box, l_int32 w, l_int32 h, l_int32 *pxstart, l_int32 *pystart, l_int32 *pxend, l_int32 *pyend, l_int32 *pbw, l_int32 *pbh );
+LEPT_DLL extern BOX * boxRelocateOneSide ( BOX *boxd, BOX *boxs, l_int32 loc, l_int32 sideflag );
+LEPT_DLL extern BOX * boxAdjustSides ( BOX *boxd, BOX *boxs, l_int32 delleft, l_int32 delright, l_int32 deltop, l_int32 delbot );
+LEPT_DLL extern BOXA * boxaSetSide ( BOXA *boxad, BOXA *boxas, l_int32 side, l_int32 val, l_int32 thresh );
+LEPT_DLL extern BOXA * boxaAdjustWidthToTarget ( BOXA *boxad, BOXA *boxas, l_int32 sides, l_int32 target, l_int32 thresh );
+LEPT_DLL extern BOXA * boxaAdjustHeightToTarget ( BOXA *boxad, BOXA *boxas, l_int32 sides, l_int32 target, l_int32 thresh );
+LEPT_DLL extern l_int32 boxEqual ( BOX *box1, BOX *box2, l_int32 *psame );
+LEPT_DLL extern l_int32 boxaEqual ( BOXA *boxa1, BOXA *boxa2, l_int32 maxdist, NUMA **pnaindex, l_int32 *psame );
+LEPT_DLL extern l_int32 boxSimilar ( BOX *box1, BOX *box2, l_int32 leftdiff, l_int32 rightdiff, l_int32 topdiff, l_int32 botdiff, l_int32 *psimilar );
+LEPT_DLL extern l_int32 boxaSimilar ( BOXA *boxa1, BOXA *boxa2, l_int32 leftdiff, l_int32 rightdiff, l_int32 topdiff, l_int32 botdiff, l_int32 debug, l_int32 *psimilar, NUMA **pnasim );
+LEPT_DLL extern l_int32 boxaJoin ( BOXA *boxad, BOXA *boxas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern l_int32 boxaaJoin ( BOXAA *baad, BOXAA *baas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern l_int32 boxaSplitEvenOdd ( BOXA *boxa, l_int32 fillflag, BOXA **pboxae, BOXA **pboxao );
+LEPT_DLL extern BOXA * boxaMergeEvenOdd ( BOXA *boxae, BOXA *boxao, l_int32 fillflag );
+LEPT_DLL extern BOXA * boxaTransform ( BOXA *boxas, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern BOX * boxTransform ( BOX *box, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern BOXA * boxaTransformOrdered ( BOXA *boxas, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 order );
+LEPT_DLL extern BOX * boxTransformOrdered ( BOX *boxs, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 order );
+LEPT_DLL extern BOXA * boxaRotateOrth ( BOXA *boxas, l_int32 w, l_int32 h, l_int32 rotation );
+LEPT_DLL extern BOX * boxRotateOrth ( BOX *box, l_int32 w, l_int32 h, l_int32 rotation );
+LEPT_DLL extern BOXA * boxaSort ( BOXA *boxas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex );
+LEPT_DLL extern BOXA * boxaBinSort ( BOXA *boxas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex );
+LEPT_DLL extern BOXA * boxaSortByIndex ( BOXA *boxas, NUMA *naindex );
+LEPT_DLL extern BOXAA * boxaSort2d ( BOXA *boxas, NUMAA **pnaad, l_int32 delta1, l_int32 delta2, l_int32 minh1 );
+LEPT_DLL extern BOXAA * boxaSort2dByIndex ( BOXA *boxas, NUMAA *naa );
+LEPT_DLL extern l_int32 boxaExtractAsNuma ( BOXA *boxa, NUMA **pnal, NUMA **pnat, NUMA **pnar, NUMA **pnab, NUMA **pnaw, NUMA **pnah, l_int32 keepinvalid );
+LEPT_DLL extern l_int32 boxaExtractAsPta ( BOXA *boxa, PTA **pptal, PTA **pptat, PTA **pptar, PTA **pptab, PTA **pptaw, PTA **pptah, l_int32 keepinvalid );
+LEPT_DLL extern BOX * boxaGetRankSize ( BOXA *boxa, l_float32 fract );
+LEPT_DLL extern BOX * boxaGetMedian ( BOXA *boxa );
+LEPT_DLL extern l_int32 boxaGetAverageSize ( BOXA *boxa, l_float32 *pw, l_float32 *ph );
+LEPT_DLL extern l_int32 boxaaGetExtent ( BOXAA *baa, l_int32 *pw, l_int32 *ph, BOX **pbox, BOXA **pboxa );
+LEPT_DLL extern BOXA * boxaaFlattenToBoxa ( BOXAA *baa, NUMA **pnaindex, l_int32 copyflag );
+LEPT_DLL extern BOXA * boxaaFlattenAligned ( BOXAA *baa, l_int32 num, BOX *fillerbox, l_int32 copyflag );
+LEPT_DLL extern BOXAA * boxaEncapsulateAligned ( BOXA *boxa, l_int32 num, l_int32 copyflag );
+LEPT_DLL extern l_int32 boxaaAlignBox ( BOXAA *baa, BOX *box, l_int32 delta, l_int32 *pindex );
+LEPT_DLL extern PIX * pixMaskConnComp ( PIX *pixs, l_int32 connectivity, BOXA **pboxa );
+LEPT_DLL extern PIX * pixMaskBoxa ( PIX *pixd, PIX *pixs, BOXA *boxa, l_int32 op );
+LEPT_DLL extern PIX * pixPaintBoxa ( PIX *pixs, BOXA *boxa, l_uint32 val );
+LEPT_DLL extern PIX * pixSetBlackOrWhiteBoxa ( PIX *pixs, BOXA *boxa, l_int32 op );
+LEPT_DLL extern PIX * pixPaintBoxaRandom ( PIX *pixs, BOXA *boxa );
+LEPT_DLL extern PIX * pixBlendBoxaRandom ( PIX *pixs, BOXA *boxa, l_float32 fract );
+LEPT_DLL extern PIX * pixDrawBoxa ( PIX *pixs, BOXA *boxa, l_int32 width, l_uint32 val );
+LEPT_DLL extern PIX * pixDrawBoxaRandom ( PIX *pixs, BOXA *boxa, l_int32 width );
+LEPT_DLL extern PIX * boxaaDisplay ( BOXAA *baa, l_int32 linewba, l_int32 linewb, l_uint32 colorba, l_uint32 colorb, l_int32 w, l_int32 h );
+LEPT_DLL extern BOXA * pixSplitIntoBoxa ( PIX *pixs, l_int32 minsum, l_int32 skipdist, l_int32 delta, l_int32 maxbg, l_int32 maxcomps, l_int32 remainder );
+LEPT_DLL extern BOXA * pixSplitComponentIntoBoxa ( PIX *pix, BOX *box, l_int32 minsum, l_int32 skipdist, l_int32 delta, l_int32 maxbg, l_int32 maxcomps, l_int32 remainder );
+LEPT_DLL extern BOXA * makeMosaicStrips ( l_int32 w, l_int32 h, l_int32 direction, l_int32 size );
+LEPT_DLL extern l_int32 boxaCompareRegions ( BOXA *boxa1, BOXA *boxa2, l_int32 areathresh, l_int32 *pnsame, l_float32 *pdiffarea, l_float32 *pdiffxor, PIX **ppixdb );
+LEPT_DLL extern BOX * pixSelectLargeULComp ( PIX *pixs, l_float32 areaslop, l_int32 yslop, l_int32 connectivity );
+LEPT_DLL extern BOX * boxaSelectLargeULBox ( BOXA *boxas, l_float32 areaslop, l_int32 yslop );
+LEPT_DLL extern BOXA * boxaSelectRange ( BOXA *boxas, l_int32 first, l_int32 last, l_int32 copyflag );
+LEPT_DLL extern BOXAA * boxaaSelectRange ( BOXAA *baas, l_int32 first, l_int32 last, l_int32 copyflag );
+LEPT_DLL extern BOXA * boxaSelectBySize ( BOXA *boxas, l_int32 width, l_int32 height, l_int32 type, l_int32 relation, l_int32 *pchanged );
+LEPT_DLL extern NUMA * boxaMakeSizeIndicator ( BOXA *boxa, l_int32 width, l_int32 height, l_int32 type, l_int32 relation );
+LEPT_DLL extern BOXA * boxaSelectByArea ( BOXA *boxas, l_int32 area, l_int32 relation, l_int32 *pchanged );
+LEPT_DLL extern NUMA * boxaMakeAreaIndicator ( BOXA *boxa, l_int32 area, l_int32 relation );
+LEPT_DLL extern BOXA * boxaSelectWithIndicator ( BOXA *boxas, NUMA *na, l_int32 *pchanged );
+LEPT_DLL extern BOXA * boxaPermutePseudorandom ( BOXA *boxas );
+LEPT_DLL extern BOXA * boxaPermuteRandom ( BOXA *boxad, BOXA *boxas );
+LEPT_DLL extern l_int32 boxaSwapBoxes ( BOXA *boxa, l_int32 i, l_int32 j );
+LEPT_DLL extern PTA * boxaConvertToPta ( BOXA *boxa, l_int32 ncorners );
+LEPT_DLL extern BOXA * ptaConvertToBoxa ( PTA *pta, l_int32 ncorners );
+LEPT_DLL extern PTA * boxConvertToPta ( BOX *box, l_int32 ncorners );
+LEPT_DLL extern BOX * ptaConvertToBox ( PTA *pta );
+LEPT_DLL extern BOXA * boxaSmoothSequenceLS ( BOXA *boxas, l_float32 factor, l_int32 subflag, l_int32 maxdiff, l_int32 debug );
+LEPT_DLL extern BOXA * boxaSmoothSequenceMedian ( BOXA *boxas, l_int32 halfwin, l_int32 subflag, l_int32 maxdiff, l_int32 debug );
+LEPT_DLL extern BOXA * boxaLinearFit ( BOXA *boxas, l_float32 factor, l_int32 debug );
+LEPT_DLL extern BOXA * boxaWindowedMedian ( BOXA *boxas, l_int32 halfwin, l_int32 debug );
+LEPT_DLL extern BOXA * boxaModifyWithBoxa ( BOXA *boxas, BOXA *boxam, l_int32 subflag, l_int32 maxdiff );
+LEPT_DLL extern BOXA * boxaConstrainSize ( BOXA *boxas, l_int32 width, l_int32 widthflag, l_int32 height, l_int32 heightflag );
+LEPT_DLL extern BOXA * boxaReconcileEvenOddHeight ( BOXA *boxas, l_int32 sides, l_int32 delh, l_int32 op, l_float32 factor );
+LEPT_DLL extern BOXA * boxaReconcilePairWidth ( BOXA *boxas, l_int32 delw, l_int32 op, l_float32 factor, NUMA *na );
+LEPT_DLL extern l_int32 boxaPlotSides ( BOXA *boxa, const char *plotname, NUMA **pnal, NUMA **pnat, NUMA **pnar, NUMA **pnab, l_int32 outformat );
+LEPT_DLL extern BOXA * boxaFillSequence ( BOXA *boxas, l_int32 useflag, l_int32 debug );
+LEPT_DLL extern l_int32 boxaGetExtent ( BOXA *boxa, l_int32 *pw, l_int32 *ph, BOX **pbox );
+LEPT_DLL extern l_int32 boxaGetCoverage ( BOXA *boxa, l_int32 wc, l_int32 hc, l_int32 exactflag, l_float32 *pfract );
+LEPT_DLL extern l_int32 boxaaSizeRange ( BOXAA *baa, l_int32 *pminw, l_int32 *pminh, l_int32 *pmaxw, l_int32 *pmaxh );
+LEPT_DLL extern l_int32 boxaSizeRange ( BOXA *boxa, l_int32 *pminw, l_int32 *pminh, l_int32 *pmaxw, l_int32 *pmaxh );
+LEPT_DLL extern l_int32 boxaLocationRange ( BOXA *boxa, l_int32 *pminx, l_int32 *pminy, l_int32 *pmaxx, l_int32 *pmaxy );
+LEPT_DLL extern l_int32 boxaGetArea ( BOXA *boxa, l_int32 *parea );
+LEPT_DLL extern PIX * boxaDisplayTiled ( BOXA *boxas, PIXA *pixa, l_int32 maxwidth, l_int32 linewidth, l_float32 scalefactor, l_int32 background, l_int32 spacing, l_int32 border, const char *fontdir );
+LEPT_DLL extern L_BYTEA * l_byteaCreate ( size_t nbytes );
+LEPT_DLL extern L_BYTEA * l_byteaInitFromMem ( l_uint8 *data, size_t size );
+LEPT_DLL extern L_BYTEA * l_byteaInitFromFile ( const char *fname );
+LEPT_DLL extern L_BYTEA * l_byteaInitFromStream ( FILE *fp );
+LEPT_DLL extern L_BYTEA * l_byteaCopy ( L_BYTEA *bas, l_int32 copyflag );
+LEPT_DLL extern void l_byteaDestroy ( L_BYTEA **pba );
+LEPT_DLL extern size_t l_byteaGetSize ( L_BYTEA *ba );
+LEPT_DLL extern l_uint8 * l_byteaGetData ( L_BYTEA *ba, size_t *psize );
+LEPT_DLL extern l_uint8 * l_byteaCopyData ( L_BYTEA *ba, size_t *psize );
+LEPT_DLL extern l_int32 l_byteaAppendData ( L_BYTEA *ba, l_uint8 *newdata, size_t newbytes );
+LEPT_DLL extern l_int32 l_byteaAppendString ( L_BYTEA *ba, char *str );
+LEPT_DLL extern l_int32 l_byteaJoin ( L_BYTEA *ba1, L_BYTEA **pba2 );
+LEPT_DLL extern l_int32 l_byteaSplit ( L_BYTEA *ba1, size_t splitloc, L_BYTEA **pba2 );
+LEPT_DLL extern l_int32 l_byteaFindEachSequence ( L_BYTEA *ba, l_uint8 *sequence, l_int32 seqlen, L_DNA **pda );
+LEPT_DLL extern l_int32 l_byteaWrite ( const char *fname, L_BYTEA *ba, size_t startloc, size_t endloc );
+LEPT_DLL extern l_int32 l_byteaWriteStream ( FILE *fp, L_BYTEA *ba, size_t startloc, size_t endloc );
+LEPT_DLL extern CCBORDA * ccbaCreate ( PIX *pixs, l_int32 n );
+LEPT_DLL extern void ccbaDestroy ( CCBORDA **pccba );
+LEPT_DLL extern CCBORD * ccbCreate ( PIX *pixs );
+LEPT_DLL extern void ccbDestroy ( CCBORD **pccb );
+LEPT_DLL extern l_int32 ccbaAddCcb ( CCBORDA *ccba, CCBORD *ccb );
+LEPT_DLL extern l_int32 ccbaGetCount ( CCBORDA *ccba );
+LEPT_DLL extern CCBORD * ccbaGetCcb ( CCBORDA *ccba, l_int32 index );
+LEPT_DLL extern CCBORDA * pixGetAllCCBorders ( PIX *pixs );
+LEPT_DLL extern CCBORD * pixGetCCBorders ( PIX *pixs, BOX *box );
+LEPT_DLL extern PTAA * pixGetOuterBordersPtaa ( PIX *pixs );
+LEPT_DLL extern PTA * pixGetOuterBorderPta ( PIX *pixs, BOX *box );
+LEPT_DLL extern l_int32 pixGetOuterBorder ( CCBORD *ccb, PIX *pixs, BOX *box );
+LEPT_DLL extern l_int32 pixGetHoleBorder ( CCBORD *ccb, PIX *pixs, BOX *box, l_int32 xs, l_int32 ys );
+LEPT_DLL extern l_int32 findNextBorderPixel ( l_int32 w, l_int32 h, l_uint32 *data, l_int32 wpl, l_int32 px, l_int32 py, l_int32 *pqpos, l_int32 *pnpx, l_int32 *pnpy );
+LEPT_DLL extern void locateOutsideSeedPixel ( l_int32 fpx, l_int32 fpy, l_int32 spx, l_int32 spy, l_int32 *pxs, l_int32 *pys );
+LEPT_DLL extern l_int32 ccbaGenerateGlobalLocs ( CCBORDA *ccba );
+LEPT_DLL extern l_int32 ccbaGenerateStepChains ( CCBORDA *ccba );
+LEPT_DLL extern l_int32 ccbaStepChainsToPixCoords ( CCBORDA *ccba, l_int32 coordtype );
+LEPT_DLL extern l_int32 ccbaGenerateSPGlobalLocs ( CCBORDA *ccba, l_int32 ptsflag );
+LEPT_DLL extern l_int32 ccbaGenerateSinglePath ( CCBORDA *ccba );
+LEPT_DLL extern PTA * getCutPathForHole ( PIX *pix, PTA *pta, BOX *boxinner, l_int32 *pdir, l_int32 *plen );
+LEPT_DLL extern PIX * ccbaDisplayBorder ( CCBORDA *ccba );
+LEPT_DLL extern PIX * ccbaDisplaySPBorder ( CCBORDA *ccba );
+LEPT_DLL extern PIX * ccbaDisplayImage1 ( CCBORDA *ccba );
+LEPT_DLL extern PIX * ccbaDisplayImage2 ( CCBORDA *ccba );
+LEPT_DLL extern l_int32 ccbaWrite ( const char *filename, CCBORDA *ccba );
+LEPT_DLL extern l_int32 ccbaWriteStream ( FILE *fp, CCBORDA *ccba );
+LEPT_DLL extern CCBORDA * ccbaRead ( const char *filename );
+LEPT_DLL extern CCBORDA * ccbaReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 ccbaWriteSVG ( const char *filename, CCBORDA *ccba );
+LEPT_DLL extern char * ccbaWriteSVGString ( const char *filename, CCBORDA *ccba );
+LEPT_DLL extern PIX * pixThin ( PIX *pixs, l_int32 type, l_int32 connectivity, l_int32 maxiters );
+LEPT_DLL extern PIX * pixThinGeneral ( PIX *pixs, l_int32 type, SELA *sela, l_int32 maxiters );
+LEPT_DLL extern PIX * pixThinExamples ( PIX *pixs, l_int32 type, l_int32 index, l_int32 maxiters, const char *selfile );
+LEPT_DLL extern l_int32 jbCorrelation ( const char *dirin, l_float32 thresh, l_float32 weight, l_int32 components, const char *rootname, l_int32 firstpage, l_int32 npages, l_int32 renderflag );
+LEPT_DLL extern l_int32 jbRankHaus ( const char *dirin, l_int32 size, l_float32 rank, l_int32 components, const char *rootname, l_int32 firstpage, l_int32 npages, l_int32 renderflag );
+LEPT_DLL extern JBCLASSER * jbWordsInTextlines ( const char *dirin, l_int32 reduction, l_int32 maxwidth, l_int32 maxheight, l_float32 thresh, l_float32 weight, NUMA **pnatl, l_int32 firstpage, l_int32 npages );
+LEPT_DLL extern l_int32 pixGetWordsInTextlines ( PIX *pixs, l_int32 reduction, l_int32 minwidth, l_int32 minheight, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxad, PIXA **ppixad, NUMA **pnai );
+LEPT_DLL extern l_int32 pixGetWordBoxesInTextlines ( PIX *pixs, l_int32 reduction, l_int32 minwidth, l_int32 minheight, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxad, NUMA **pnai );
+LEPT_DLL extern NUMAA * boxaExtractSortedPattern ( BOXA *boxa, NUMA *na );
+LEPT_DLL extern l_int32 numaaCompareImagesByBoxes ( NUMAA *naa1, NUMAA *naa2, l_int32 nperline, l_int32 nreq, l_int32 maxshiftx, l_int32 maxshifty, l_int32 delx, l_int32 dely, l_int32 *psame, l_int32 debugflag );
+LEPT_DLL extern l_int32 pixColorContent ( PIX *pixs, l_int32 rwhite, l_int32 gwhite, l_int32 bwhite, l_int32 mingray, PIX **ppixr, PIX **ppixg, PIX **ppixb );
+LEPT_DLL extern PIX * pixColorMagnitude ( PIX *pixs, l_int32 rwhite, l_int32 gwhite, l_int32 bwhite, l_int32 type );
+LEPT_DLL extern PIX * pixMaskOverColorPixels ( PIX *pixs, l_int32 threshdiff, l_int32 mindist );
+LEPT_DLL extern PIX * pixMaskOverColorRange ( PIX *pixs, l_int32 rmin, l_int32 rmax, l_int32 gmin, l_int32 gmax, l_int32 bmin, l_int32 bmax );
+LEPT_DLL extern l_int32 pixColorFraction ( PIX *pixs, l_int32 darkthresh, l_int32 lightthresh, l_int32 diffthresh, l_int32 factor, l_float32 *ppixfract, l_float32 *pcolorfract );
+LEPT_DLL extern l_int32 pixNumSignificantGrayColors ( PIX *pixs, l_int32 darkthresh, l_int32 lightthresh, l_float32 minfract, l_int32 factor, l_int32 *pncolors );
+LEPT_DLL extern l_int32 pixColorsForQuantization ( PIX *pixs, l_int32 thresh, l_int32 *pncolors, l_int32 *piscolor, l_int32 debug );
+LEPT_DLL extern l_int32 pixNumColors ( PIX *pixs, l_int32 factor, l_int32 *pncolors );
+LEPT_DLL extern l_int32 pixGetMostPopulatedColors ( PIX *pixs, l_int32 sigbits, l_int32 factor, l_int32 ncolors, l_uint32 **parray, PIXCMAP **pcmap );
+LEPT_DLL extern PIX * pixSimpleColorQuantize ( PIX *pixs, l_int32 sigbits, l_int32 factor, l_int32 ncolors );
+LEPT_DLL extern NUMA * pixGetRGBHistogram ( PIX *pixs, l_int32 sigbits, l_int32 factor );
+LEPT_DLL extern l_int32 makeRGBIndexTables ( l_uint32 **prtab, l_uint32 **pgtab, l_uint32 **pbtab, l_int32 sigbits );
+LEPT_DLL extern l_int32 getRGBFromIndex ( l_uint32 index, l_int32 sigbits, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern l_int32 pixHasHighlightRed ( PIX *pixs, l_int32 factor, l_float32 fract, l_float32 fthresh, l_int32 *phasred, l_float32 *pratio, PIX **ppixdb );
+LEPT_DLL extern PIX * pixColorGrayRegions ( PIX *pixs, BOXA *boxa, l_int32 type, l_int32 thresh, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_int32 pixColorGray ( PIX *pixs, BOX *box, l_int32 type, l_int32 thresh, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern PIX * pixColorGrayMasked ( PIX *pixs, PIX *pixm, l_int32 type, l_int32 thresh, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern PIX * pixSnapColor ( PIX *pixd, PIX *pixs, l_uint32 srcval, l_uint32 dstval, l_int32 diff );
+LEPT_DLL extern PIX * pixSnapColorCmap ( PIX *pixd, PIX *pixs, l_uint32 srcval, l_uint32 dstval, l_int32 diff );
+LEPT_DLL extern PIX * pixLinearMapToTargetColor ( PIX *pixd, PIX *pixs, l_uint32 srcval, l_uint32 dstval );
+LEPT_DLL extern l_int32 pixelLinearMapToTargetColor ( l_uint32 scolor, l_uint32 srcmap, l_uint32 dstmap, l_uint32 *pdcolor );
+LEPT_DLL extern PIX * pixShiftByComponent ( PIX *pixd, PIX *pixs, l_uint32 srcval, l_uint32 dstval );
+LEPT_DLL extern l_int32 pixelShiftByComponent ( l_int32 rval, l_int32 gval, l_int32 bval, l_uint32 srcval, l_uint32 dstval, l_uint32 *ppixel );
+LEPT_DLL extern l_int32 pixelFractionalShift ( l_int32 rval, l_int32 gval, l_int32 bval, l_float32 fraction, l_uint32 *ppixel );
+LEPT_DLL extern PIXCMAP * pixcmapCreate ( l_int32 depth );
+LEPT_DLL extern PIXCMAP * pixcmapCreateRandom ( l_int32 depth, l_int32 hasblack, l_int32 haswhite );
+LEPT_DLL extern PIXCMAP * pixcmapCreateLinear ( l_int32 d, l_int32 nlevels );
+LEPT_DLL extern PIXCMAP * pixcmapCopy ( PIXCMAP *cmaps );
+LEPT_DLL extern void pixcmapDestroy ( PIXCMAP **pcmap );
+LEPT_DLL extern l_int32 pixcmapAddColor ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_int32 pixcmapAddRGBA ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 aval );
+LEPT_DLL extern l_int32 pixcmapAddNewColor ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *pindex );
+LEPT_DLL extern l_int32 pixcmapAddNearestColor ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *pindex );
+LEPT_DLL extern l_int32 pixcmapUsableColor ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *pusable );
+LEPT_DLL extern l_int32 pixcmapAddBlackOrWhite ( PIXCMAP *cmap, l_int32 color, l_int32 *pindex );
+LEPT_DLL extern l_int32 pixcmapSetBlackAndWhite ( PIXCMAP *cmap, l_int32 setblack, l_int32 setwhite );
+LEPT_DLL extern l_int32 pixcmapGetCount ( PIXCMAP *cmap );
+LEPT_DLL extern l_int32 pixcmapGetFreeCount ( PIXCMAP *cmap );
+LEPT_DLL extern l_int32 pixcmapGetDepth ( PIXCMAP *cmap );
+LEPT_DLL extern l_int32 pixcmapGetMinDepth ( PIXCMAP *cmap, l_int32 *pmindepth );
+LEPT_DLL extern l_int32 pixcmapClear ( PIXCMAP *cmap );
+LEPT_DLL extern l_int32 pixcmapGetColor ( PIXCMAP *cmap, l_int32 index, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern l_int32 pixcmapGetColor32 ( PIXCMAP *cmap, l_int32 index, l_uint32 *pval32 );
+LEPT_DLL extern l_int32 pixcmapGetRGBA ( PIXCMAP *cmap, l_int32 index, l_int32 *prval, l_int32 *pgval, l_int32 *pbval, l_int32 *paval );
+LEPT_DLL extern l_int32 pixcmapGetRGBA32 ( PIXCMAP *cmap, l_int32 index, l_uint32 *pval32 );
+LEPT_DLL extern l_int32 pixcmapResetColor ( PIXCMAP *cmap, l_int32 index, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_int32 pixcmapSetAlpha ( PIXCMAP *cmap, l_int32 index, l_int32 aval );
+LEPT_DLL extern l_int32 pixcmapGetIndex ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *pindex );
+LEPT_DLL extern l_int32 pixcmapHasColor ( PIXCMAP *cmap, l_int32 *pcolor );
+LEPT_DLL extern l_int32 pixcmapIsOpaque ( PIXCMAP *cmap, l_int32 *popaque );
+LEPT_DLL extern l_int32 pixcmapIsBlackAndWhite ( PIXCMAP *cmap, l_int32 *pblackwhite );
+LEPT_DLL extern l_int32 pixcmapCountGrayColors ( PIXCMAP *cmap, l_int32 *pngray );
+LEPT_DLL extern l_int32 pixcmapGetRankIntensity ( PIXCMAP *cmap, l_float32 rankval, l_int32 *pindex );
+LEPT_DLL extern l_int32 pixcmapGetNearestIndex ( PIXCMAP *cmap, l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *pindex );
+LEPT_DLL extern l_int32 pixcmapGetNearestGrayIndex ( PIXCMAP *cmap, l_int32 val, l_int32 *pindex );
+LEPT_DLL extern l_int32 pixcmapGetComponentRange ( PIXCMAP *cmap, l_int32 color, l_int32 *pminval, l_int32 *pmaxval );
+LEPT_DLL extern l_int32 pixcmapGetExtremeValue ( PIXCMAP *cmap, l_int32 type, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern PIXCMAP * pixcmapGrayToColor ( l_uint32 color );
+LEPT_DLL extern PIXCMAP * pixcmapColorToGray ( PIXCMAP *cmaps, l_float32 rwt, l_float32 gwt, l_float32 bwt );
+LEPT_DLL extern PIXCMAP * pixcmapRead ( const char *filename );
+LEPT_DLL extern PIXCMAP * pixcmapReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 pixcmapWrite ( const char *filename, PIXCMAP *cmap );
+LEPT_DLL extern l_int32 pixcmapWriteStream ( FILE *fp, PIXCMAP *cmap );
+LEPT_DLL extern l_int32 pixcmapToArrays ( PIXCMAP *cmap, l_int32 **prmap, l_int32 **pgmap, l_int32 **pbmap, l_int32 **pamap );
+LEPT_DLL extern l_int32 pixcmapToRGBTable ( PIXCMAP *cmap, l_uint32 **ptab, l_int32 *pncolors );
+LEPT_DLL extern l_int32 pixcmapSerializeToMemory ( PIXCMAP *cmap, l_int32 cpc, l_int32 *pncolors, l_uint8 **pdata );
+LEPT_DLL extern PIXCMAP * pixcmapDeserializeFromMemory ( l_uint8 *data, l_int32 cpc, l_int32 ncolors );
+LEPT_DLL extern char * pixcmapConvertToHex ( l_uint8 *data, l_int32 ncolors );
+LEPT_DLL extern l_int32 pixcmapGammaTRC ( PIXCMAP *cmap, l_float32 gamma, l_int32 minval, l_int32 maxval );
+LEPT_DLL extern l_int32 pixcmapContrastTRC ( PIXCMAP *cmap, l_float32 factor );
+LEPT_DLL extern l_int32 pixcmapShiftIntensity ( PIXCMAP *cmap, l_float32 fraction );
+LEPT_DLL extern l_int32 pixcmapShiftByComponent ( PIXCMAP *cmap, l_uint32 srcval, l_uint32 dstval );
+LEPT_DLL extern PIX * pixColorMorph ( PIX *pixs, l_int32 type, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixOctreeColorQuant ( PIX *pixs, l_int32 colors, l_int32 ditherflag );
+LEPT_DLL extern PIX * pixOctreeColorQuantGeneral ( PIX *pixs, l_int32 colors, l_int32 ditherflag, l_float32 validthresh, l_float32 colorthresh );
+LEPT_DLL extern l_int32 makeRGBToIndexTables ( l_uint32 **prtab, l_uint32 **pgtab, l_uint32 **pbtab, l_int32 cqlevels );
+LEPT_DLL extern void getOctcubeIndexFromRGB ( l_int32 rval, l_int32 gval, l_int32 bval, l_uint32 *rtab, l_uint32 *gtab, l_uint32 *btab, l_uint32 *pindex );
+LEPT_DLL extern PIX * pixOctreeQuantByPopulation ( PIX *pixs, l_int32 level, l_int32 ditherflag );
+LEPT_DLL extern PIX * pixOctreeQuantNumColors ( PIX *pixs, l_int32 maxcolors, l_int32 subsample );
+LEPT_DLL extern PIX * pixOctcubeQuantMixedWithGray ( PIX *pixs, l_int32 depth, l_int32 graylevels, l_int32 delta );
+LEPT_DLL extern PIX * pixFixedOctcubeQuant256 ( PIX *pixs, l_int32 ditherflag );
+LEPT_DLL extern PIX * pixFewColorsOctcubeQuant1 ( PIX *pixs, l_int32 level );
+LEPT_DLL extern PIX * pixFewColorsOctcubeQuant2 ( PIX *pixs, l_int32 level, NUMA *na, l_int32 ncolors, l_int32 *pnerrors );
+LEPT_DLL extern PIX * pixFewColorsOctcubeQuantMixed ( PIX *pixs, l_int32 level, l_int32 darkthresh, l_int32 lightthresh, l_int32 diffthresh, l_float32 minfract, l_int32 maxspan );
+LEPT_DLL extern PIX * pixFixedOctcubeQuantGenRGB ( PIX *pixs, l_int32 level );
+LEPT_DLL extern PIX * pixQuantFromCmap ( PIX *pixs, PIXCMAP *cmap, l_int32 mindepth, l_int32 level, l_int32 metric );
+LEPT_DLL extern PIX * pixOctcubeQuantFromCmap ( PIX *pixs, PIXCMAP *cmap, l_int32 mindepth, l_int32 level, l_int32 metric );
+LEPT_DLL extern NUMA * pixOctcubeHistogram ( PIX *pixs, l_int32 level, l_int32 *pncolors );
+LEPT_DLL extern l_int32 * pixcmapToOctcubeLUT ( PIXCMAP *cmap, l_int32 level, l_int32 metric );
+LEPT_DLL extern l_int32 pixRemoveUnusedColors ( PIX *pixs );
+LEPT_DLL extern l_int32 pixNumberOccupiedOctcubes ( PIX *pix, l_int32 level, l_int32 mincount, l_float32 minfract, l_int32 *pncolors );
+LEPT_DLL extern PIX * pixMedianCutQuant ( PIX *pixs, l_int32 ditherflag );
+LEPT_DLL extern PIX * pixMedianCutQuantGeneral ( PIX *pixs, l_int32 ditherflag, l_int32 outdepth, l_int32 maxcolors, l_int32 sigbits, l_int32 maxsub, l_int32 checkbw );
+LEPT_DLL extern PIX * pixMedianCutQuantMixed ( PIX *pixs, l_int32 ncolor, l_int32 ngray, l_int32 darkthresh, l_int32 lightthresh, l_int32 diffthresh );
+LEPT_DLL extern PIX * pixFewColorsMedianCutQuantMixed ( PIX *pixs, l_int32 ncolor, l_int32 ngray, l_int32 maxncolors, l_int32 darkthresh, l_int32 lightthresh, l_int32 diffthresh );
+LEPT_DLL extern l_int32 * pixMedianCutHisto ( PIX *pixs, l_int32 sigbits, l_int32 subsample );
+LEPT_DLL extern PIX * pixColorSegment ( PIX *pixs, l_int32 maxdist, l_int32 maxcolors, l_int32 selsize, l_int32 finalcolors );
+LEPT_DLL extern PIX * pixColorSegmentCluster ( PIX *pixs, l_int32 maxdist, l_int32 maxcolors );
+LEPT_DLL extern l_int32 pixAssignToNearestColor ( PIX *pixd, PIX *pixs, PIX *pixm, l_int32 level, l_int32 *countarray );
+LEPT_DLL extern l_int32 pixColorSegmentClean ( PIX *pixs, l_int32 selsize, l_int32 *countarray );
+LEPT_DLL extern l_int32 pixColorSegmentRemoveColors ( PIX *pixd, PIX *pixs, l_int32 finalcolors );
+LEPT_DLL extern PIX * pixConvertRGBToHSV ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern PIX * pixConvertHSVToRGB ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern l_int32 convertRGBToHSV ( l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *phval, l_int32 *psval, l_int32 *pvval );
+LEPT_DLL extern l_int32 convertHSVToRGB ( l_int32 hval, l_int32 sval, l_int32 vval, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern l_int32 pixcmapConvertRGBToHSV ( PIXCMAP *cmap );
+LEPT_DLL extern l_int32 pixcmapConvertHSVToRGB ( PIXCMAP *cmap );
+LEPT_DLL extern PIX * pixConvertRGBToHue ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvertRGBToSaturation ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvertRGBToValue ( PIX *pixs );
+LEPT_DLL extern PIX * pixMakeRangeMaskHS ( PIX *pixs, l_int32 huecenter, l_int32 huehw, l_int32 satcenter, l_int32 sathw, l_int32 regionflag );
+LEPT_DLL extern PIX * pixMakeRangeMaskHV ( PIX *pixs, l_int32 huecenter, l_int32 huehw, l_int32 valcenter, l_int32 valhw, l_int32 regionflag );
+LEPT_DLL extern PIX * pixMakeRangeMaskSV ( PIX *pixs, l_int32 satcenter, l_int32 sathw, l_int32 valcenter, l_int32 valhw, l_int32 regionflag );
+LEPT_DLL extern PIX * pixMakeHistoHS ( PIX *pixs, l_int32 factor, NUMA **pnahue, NUMA **pnasat );
+LEPT_DLL extern PIX * pixMakeHistoHV ( PIX *pixs, l_int32 factor, NUMA **pnahue, NUMA **pnaval );
+LEPT_DLL extern PIX * pixMakeHistoSV ( PIX *pixs, l_int32 factor, NUMA **pnasat, NUMA **pnaval );
+LEPT_DLL extern l_int32 pixFindHistoPeaksHSV ( PIX *pixs, l_int32 type, l_int32 width, l_int32 height, l_int32 npeaks, l_float32 erasefactor, PTA **ppta, NUMA **pnatot, PIXA **ppixa );
+LEPT_DLL extern PIX * displayHSVColorRange ( l_int32 hval, l_int32 sval, l_int32 vval, l_int32 huehw, l_int32 sathw, l_int32 nsamp, l_int32 factor );
+LEPT_DLL extern PIX * pixConvertRGBToYUV ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern PIX * pixConvertYUVToRGB ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern l_int32 convertRGBToYUV ( l_int32 rval, l_int32 gval, l_int32 bval, l_int32 *pyval, l_int32 *puval, l_int32 *pvval );
+LEPT_DLL extern l_int32 convertYUVToRGB ( l_int32 yval, l_int32 uval, l_int32 vval, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern l_int32 pixcmapConvertRGBToYUV ( PIXCMAP *cmap );
+LEPT_DLL extern l_int32 pixcmapConvertYUVToRGB ( PIXCMAP *cmap );
+LEPT_DLL extern FPIXA * pixConvertRGBToXYZ ( PIX *pixs );
+LEPT_DLL extern PIX * fpixaConvertXYZToRGB ( FPIXA *fpixa );
+LEPT_DLL extern l_int32 convertRGBToXYZ ( l_int32 rval, l_int32 gval, l_int32 bval, l_float32 *pfxval, l_float32 *pfyval, l_float32 *pfzval );
+LEPT_DLL extern l_int32 convertXYZToRGB ( l_float32 fxval, l_float32 fyval, l_float32 fzval, l_int32 blackout, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern FPIXA * fpixaConvertXYZToLAB ( FPIXA *fpixas );
+LEPT_DLL extern FPIXA * fpixaConvertLABToXYZ ( FPIXA *fpixas );
+LEPT_DLL extern l_int32 convertXYZToLAB ( l_float32 xval, l_float32 yval, l_float32 zval, l_float32 *plval, l_float32 *paval, l_float32 *pbval );
+LEPT_DLL extern l_int32 convertLABToXYZ ( l_float32 lval, l_float32 aval, l_float32 bval, l_float32 *pxval, l_float32 *pyval, l_float32 *pzval );
+LEPT_DLL extern FPIXA * pixConvertRGBToLAB ( PIX *pixs );
+LEPT_DLL extern PIX * fpixaConvertLABToRGB ( FPIXA *fpixa );
+LEPT_DLL extern l_int32 convertRGBToLAB ( l_int32 rval, l_int32 gval, l_int32 bval, l_float32 *pflval, l_float32 *pfaval, l_float32 *pfbval );
+LEPT_DLL extern l_int32 convertLABToRGB ( l_float32 flval, l_float32 faval, l_float32 fbval, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern l_int32 pixEqual ( PIX *pix1, PIX *pix2, l_int32 *psame );
+LEPT_DLL extern l_int32 pixEqualWithAlpha ( PIX *pix1, PIX *pix2, l_int32 use_alpha, l_int32 *psame );
+LEPT_DLL extern l_int32 pixEqualWithCmap ( PIX *pix1, PIX *pix2, l_int32 *psame );
+LEPT_DLL extern l_int32 pixUsesCmapColor ( PIX *pixs, l_int32 *pcolor );
+LEPT_DLL extern l_int32 pixCorrelationBinary ( PIX *pix1, PIX *pix2, l_float32 *pval );
+LEPT_DLL extern PIX * pixDisplayDiffBinary ( PIX *pix1, PIX *pix2 );
+LEPT_DLL extern l_int32 pixCompareBinary ( PIX *pix1, PIX *pix2, l_int32 comptype, l_float32 *pfract, PIX **ppixdiff );
+LEPT_DLL extern l_int32 pixCompareGrayOrRGB ( PIX *pix1, PIX *pix2, l_int32 comptype, l_int32 plottype, l_int32 *psame, l_float32 *pdiff, l_float32 *prmsdiff, PIX **ppixdiff );
+LEPT_DLL extern l_int32 pixCompareGray ( PIX *pix1, PIX *pix2, l_int32 comptype, l_int32 plottype, l_int32 *psame, l_float32 *pdiff, l_float32 *prmsdiff, PIX **ppixdiff );
+LEPT_DLL extern l_int32 pixCompareRGB ( PIX *pix1, PIX *pix2, l_int32 comptype, l_int32 plottype, l_int32 *psame, l_float32 *pdiff, l_float32 *prmsdiff, PIX **ppixdiff );
+LEPT_DLL extern l_int32 pixCompareTiled ( PIX *pix1, PIX *pix2, l_int32 sx, l_int32 sy, l_int32 type, PIX **ppixdiff );
+LEPT_DLL extern NUMA * pixCompareRankDifference ( PIX *pix1, PIX *pix2, l_int32 factor );
+LEPT_DLL extern l_int32 pixTestForSimilarity ( PIX *pix1, PIX *pix2, l_int32 factor, l_int32 mindiff, l_float32 maxfract, l_float32 maxave, l_int32 *psimilar, l_int32 printstats );
+LEPT_DLL extern l_int32 pixGetDifferenceStats ( PIX *pix1, PIX *pix2, l_int32 factor, l_int32 mindiff, l_float32 *pfractdiff, l_float32 *pavediff, l_int32 printstats );
+LEPT_DLL extern NUMA * pixGetDifferenceHistogram ( PIX *pix1, PIX *pix2, l_int32 factor );
+LEPT_DLL extern l_int32 pixGetPerceptualDiff ( PIX *pixs1, PIX *pixs2, l_int32 sampling, l_int32 dilation, l_int32 mindiff, l_float32 *pfract, PIX **ppixdiff1, PIX **ppixdiff2 );
+LEPT_DLL extern l_int32 pixGetPSNR ( PIX *pix1, PIX *pix2, l_int32 factor, l_float32 *ppsnr );
+LEPT_DLL extern l_int32 pixaComparePhotoRegionsByHisto ( PIXA *pixa, l_float32 minratio, l_float32 textthresh, l_int32 factor, l_int32 nx, l_int32 ny, l_float32 simthresh, NUMA **pnai, l_float32 **pscores, PIX **ppixd );
+LEPT_DLL extern l_int32 pixComparePhotoRegionsByHisto ( PIX *pix1, PIX *pix2, BOX *box1, BOX *box2, l_float32 minratio, l_int32 factor, l_int32 nx, l_int32 ny, l_float32 *pscore, l_int32 debugflag );
+LEPT_DLL extern l_int32 pixGenPhotoHistos ( PIX *pixs, BOX *box, l_int32 factor, l_float32 thresh, l_int32 nx, l_int32 ny, NUMAA **pnaa, l_int32 *pw, l_int32 *ph, l_int32 debugflag );
+LEPT_DLL extern PIX * pixPadToCenterCentroid ( PIX *pixs, l_int32 factor );
+LEPT_DLL extern l_int32 pixCentroid8 ( PIX *pixs, l_int32 factor, l_float32 *pcx, l_float32 *pcy );
+LEPT_DLL extern l_int32 pixDecideIfPhotoImage ( PIX *pix, l_int32 factor, l_int32 nx, l_int32 ny, l_float32 thresh, NUMAA **pnaa, PIXA *pixadebug );
+LEPT_DLL extern l_int32 compareTilesByHisto ( NUMAA *naa1, NUMAA *naa2, l_float32 minratio, l_int32 w1, l_int32 h1, l_int32 w2, l_int32 h2, l_float32 *pscore, PIXA *pixadebug );
+LEPT_DLL extern l_int32 pixCompareGrayByHisto ( PIX *pix1, PIX *pix2, BOX *box1, BOX *box2, l_float32 minratio, l_int32 maxgray, l_int32 factor, l_int32 nx, l_int32 ny, l_float32 *pscore, l_int32 debugflag );
+LEPT_DLL extern l_int32 pixCropAlignedToCentroid ( PIX *pix1, PIX *pix2, l_int32 factor, BOX **pbox1, BOX **pbox2 );
+LEPT_DLL extern l_uint8 * l_compressGrayHistograms ( NUMAA *naa, l_int32 w, l_int32 h, size_t *psize );
+LEPT_DLL extern NUMAA * l_uncompressGrayHistograms ( l_uint8 *bytea, size_t size, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_int32 pixCompareWithTranslation ( PIX *pix1, PIX *pix2, l_int32 thresh, l_int32 *pdelx, l_int32 *pdely, l_float32 *pscore, l_int32 debugflag );
+LEPT_DLL extern l_int32 pixBestCorrelation ( PIX *pix1, PIX *pix2, l_int32 area1, l_int32 area2, l_int32 etransx, l_int32 etransy, l_int32 maxshift, l_int32 *tab8, l_int32 *pdelx, l_int32 *pdely, l_float32 *pscore, l_int32 debugflag );
+LEPT_DLL extern BOXA * pixConnComp ( PIX *pixs, PIXA **ppixa, l_int32 connectivity );
+LEPT_DLL extern BOXA * pixConnCompPixa ( PIX *pixs, PIXA **ppixa, l_int32 connectivity );
+LEPT_DLL extern BOXA * pixConnCompBB ( PIX *pixs, l_int32 connectivity );
+LEPT_DLL extern l_int32 pixCountConnComp ( PIX *pixs, l_int32 connectivity, l_int32 *pcount );
+LEPT_DLL extern l_int32 nextOnPixelInRaster ( PIX *pixs, l_int32 xstart, l_int32 ystart, l_int32 *px, l_int32 *py );
+LEPT_DLL extern l_int32 nextOnPixelInRasterLow ( l_uint32 *data, l_int32 w, l_int32 h, l_int32 wpl, l_int32 xstart, l_int32 ystart, l_int32 *px, l_int32 *py );
+LEPT_DLL extern BOX * pixSeedfillBB ( PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y, l_int32 connectivity );
+LEPT_DLL extern BOX * pixSeedfill4BB ( PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y );
+LEPT_DLL extern BOX * pixSeedfill8BB ( PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y );
+LEPT_DLL extern l_int32 pixSeedfill ( PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y, l_int32 connectivity );
+LEPT_DLL extern l_int32 pixSeedfill4 ( PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y );
+LEPT_DLL extern l_int32 pixSeedfill8 ( PIX *pixs, L_STACK *stack, l_int32 x, l_int32 y );
+LEPT_DLL extern l_int32 convertFilesTo1bpp ( const char *dirin, const char *substr, l_int32 upscaling, l_int32 thresh, l_int32 firstpage, l_int32 npages, const char *dirout, l_int32 outformat );
+LEPT_DLL extern PIX * pixBlockconv ( PIX *pix, l_int32 wc, l_int32 hc );
+LEPT_DLL extern PIX * pixBlockconvGray ( PIX *pixs, PIX *pixacc, l_int32 wc, l_int32 hc );
+LEPT_DLL extern PIX * pixBlockconvAccum ( PIX *pixs );
+LEPT_DLL extern PIX * pixBlockconvGrayUnnormalized ( PIX *pixs, l_int32 wc, l_int32 hc );
+LEPT_DLL extern PIX * pixBlockconvTiled ( PIX *pix, l_int32 wc, l_int32 hc, l_int32 nx, l_int32 ny );
+LEPT_DLL extern PIX * pixBlockconvGrayTile ( PIX *pixs, PIX *pixacc, l_int32 wc, l_int32 hc );
+LEPT_DLL extern l_int32 pixWindowedStats ( PIX *pixs, l_int32 wc, l_int32 hc, l_int32 hasborder, PIX **ppixm, PIX **ppixms, FPIX **pfpixv, FPIX **pfpixrv );
+LEPT_DLL extern PIX * pixWindowedMean ( PIX *pixs, l_int32 wc, l_int32 hc, l_int32 hasborder, l_int32 normflag );
+LEPT_DLL extern PIX * pixWindowedMeanSquare ( PIX *pixs, l_int32 wc, l_int32 hc, l_int32 hasborder );
+LEPT_DLL extern l_int32 pixWindowedVariance ( PIX *pixm, PIX *pixms, FPIX **pfpixv, FPIX **pfpixrv );
+LEPT_DLL extern DPIX * pixMeanSquareAccum ( PIX *pixs );
+LEPT_DLL extern PIX * pixBlockrank ( PIX *pixs, PIX *pixacc, l_int32 wc, l_int32 hc, l_float32 rank );
+LEPT_DLL extern PIX * pixBlocksum ( PIX *pixs, PIX *pixacc, l_int32 wc, l_int32 hc );
+LEPT_DLL extern PIX * pixCensusTransform ( PIX *pixs, l_int32 halfsize, PIX *pixacc );
+LEPT_DLL extern PIX * pixConvolve ( PIX *pixs, L_KERNEL *kel, l_int32 outdepth, l_int32 normflag );
+LEPT_DLL extern PIX * pixConvolveSep ( PIX *pixs, L_KERNEL *kelx, L_KERNEL *kely, l_int32 outdepth, l_int32 normflag );
+LEPT_DLL extern PIX * pixConvolveRGB ( PIX *pixs, L_KERNEL *kel );
+LEPT_DLL extern PIX * pixConvolveRGBSep ( PIX *pixs, L_KERNEL *kelx, L_KERNEL *kely );
+LEPT_DLL extern FPIX * fpixConvolve ( FPIX *fpixs, L_KERNEL *kel, l_int32 normflag );
+LEPT_DLL extern FPIX * fpixConvolveSep ( FPIX *fpixs, L_KERNEL *kelx, L_KERNEL *kely, l_int32 normflag );
+LEPT_DLL extern PIX * pixConvolveWithBias ( PIX *pixs, L_KERNEL *kel1, L_KERNEL *kel2, l_int32 force8, l_int32 *pbias );
+LEPT_DLL extern void l_setConvolveSampling ( l_int32 xfact, l_int32 yfact );
+LEPT_DLL extern PIX * pixAddGaussianNoise ( PIX *pixs, l_float32 stdev );
+LEPT_DLL extern l_float32 gaussDistribSampling (  );
+LEPT_DLL extern l_int32 pixCorrelationScore ( PIX *pix1, PIX *pix2, l_int32 area1, l_int32 area2, l_float32 delx, l_float32 dely, l_int32 maxdiffw, l_int32 maxdiffh, l_int32 *tab, l_float32 *pscore );
+LEPT_DLL extern l_int32 pixCorrelationScoreThresholded ( PIX *pix1, PIX *pix2, l_int32 area1, l_int32 area2, l_float32 delx, l_float32 dely, l_int32 maxdiffw, l_int32 maxdiffh, l_int32 *tab, l_int32 *downcount, l_float32 score_threshold );
+LEPT_DLL extern l_int32 pixCorrelationScoreSimple ( PIX *pix1, PIX *pix2, l_int32 area1, l_int32 area2, l_float32 delx, l_float32 dely, l_int32 maxdiffw, l_int32 maxdiffh, l_int32 *tab, l_float32 *pscore );
+LEPT_DLL extern l_int32 pixCorrelationScoreShifted ( PIX *pix1, PIX *pix2, l_int32 area1, l_int32 area2, l_int32 delx, l_int32 dely, l_int32 *tab, l_float32 *pscore );
+LEPT_DLL extern L_DEWARP * dewarpCreate ( PIX *pixs, l_int32 pageno );
+LEPT_DLL extern L_DEWARP * dewarpCreateRef ( l_int32 pageno, l_int32 refpage );
+LEPT_DLL extern void dewarpDestroy ( L_DEWARP **pdew );
+LEPT_DLL extern L_DEWARPA * dewarpaCreate ( l_int32 nptrs, l_int32 sampling, l_int32 redfactor, l_int32 minlines, l_int32 maxdist );
+LEPT_DLL extern L_DEWARPA * dewarpaCreateFromPixacomp ( PIXAC *pixac, l_int32 useboth, l_int32 sampling, l_int32 minlines, l_int32 maxdist );
+LEPT_DLL extern void dewarpaDestroy ( L_DEWARPA **pdewa );
+LEPT_DLL extern l_int32 dewarpaDestroyDewarp ( L_DEWARPA *dewa, l_int32 pageno );
+LEPT_DLL extern l_int32 dewarpaInsertDewarp ( L_DEWARPA *dewa, L_DEWARP *dew );
+LEPT_DLL extern L_DEWARP * dewarpaGetDewarp ( L_DEWARPA *dewa, l_int32 index );
+LEPT_DLL extern l_int32 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 );
+LEPT_DLL extern l_int32 dewarpaUseBothArrays ( L_DEWARPA *dewa, l_int32 useboth );
+LEPT_DLL extern l_int32 dewarpaSetMaxDistance ( L_DEWARPA *dewa, l_int32 maxdist );
+LEPT_DLL extern L_DEWARP * dewarpRead ( const char *filename );
+LEPT_DLL extern L_DEWARP * dewarpReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 dewarpWrite ( const char *filename, L_DEWARP *dew );
+LEPT_DLL extern l_int32 dewarpWriteStream ( FILE *fp, L_DEWARP *dew );
+LEPT_DLL extern L_DEWARPA * dewarpaRead ( const char *filename );
+LEPT_DLL extern L_DEWARPA * dewarpaReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 dewarpaWrite ( const char *filename, L_DEWARPA *dewa );
+LEPT_DLL extern l_int32 dewarpaWriteStream ( FILE *fp, L_DEWARPA *dewa );
+LEPT_DLL extern l_int32 dewarpBuildPageModel ( L_DEWARP *dew, const char *debugfile );
+LEPT_DLL extern l_int32 dewarpFindVertDisparity ( L_DEWARP *dew, PTAA *ptaa, l_int32 rotflag );
+LEPT_DLL extern l_int32 dewarpFindHorizDisparity ( L_DEWARP *dew, PTAA *ptaa );
+LEPT_DLL extern PTAA * dewarpGetTextlineCenters ( PIX *pixs, l_int32 debugflag );
+LEPT_DLL extern PTAA * dewarpRemoveShortLines ( PIX *pixs, PTAA *ptaas, l_float32 fract, l_int32 debugflag );
+LEPT_DLL extern l_int32 dewarpBuildLineModel ( L_DEWARP *dew, l_int32 opensize, const char *debugfile );
+LEPT_DLL extern l_int32 dewarpaModelStatus ( L_DEWARPA *dewa, l_int32 pageno, l_int32 *pvsuccess, l_int32 *phsuccess );
+LEPT_DLL extern l_int32 dewarpaApplyDisparity ( L_DEWARPA *dewa, l_int32 pageno, PIX *pixs, l_int32 grayin, l_int32 x, l_int32 y, PIX **ppixd, const char *debugfile );
+LEPT_DLL extern l_int32 dewarpaApplyDisparityBoxa ( L_DEWARPA *dewa, l_int32 pageno, PIX *pixs, BOXA *boxas, l_int32 mapdir, l_int32 x, l_int32 y, BOXA **pboxad, const char *debugfile );
+LEPT_DLL extern l_int32 dewarpMinimize ( L_DEWARP *dew );
+LEPT_DLL extern l_int32 dewarpPopulateFullRes ( L_DEWARP *dew, PIX *pix, l_int32 x, l_int32 y );
+LEPT_DLL extern l_int32 dewarpSinglePage ( PIX *pixs, l_int32 thresh, l_int32 adaptive, l_int32 use_both, PIX **ppixd, L_DEWARPA **pdewa, l_int32 debug );
+LEPT_DLL extern l_int32 dewarpSinglePageInit ( PIX *pixs, l_int32 thresh, l_int32 adaptive, l_int32 use_both, PIX **ppixb, L_DEWARPA **pdewa );
+LEPT_DLL extern l_int32 dewarpSinglePageRun ( PIX *pixs, PIX *pixb, L_DEWARPA *dewa, PIX **ppixd, l_int32 debug );
+LEPT_DLL extern l_int32 dewarpaListPages ( L_DEWARPA *dewa );
+LEPT_DLL extern l_int32 dewarpaSetValidModels ( L_DEWARPA *dewa, l_int32 notests, l_int32 debug );
+LEPT_DLL extern l_int32 dewarpaInsertRefModels ( L_DEWARPA *dewa, l_int32 notests, l_int32 debug );
+LEPT_DLL extern l_int32 dewarpaStripRefModels ( L_DEWARPA *dewa );
+LEPT_DLL extern l_int32 dewarpaRestoreModels ( L_DEWARPA *dewa );
+LEPT_DLL extern l_int32 dewarpaInfo ( FILE *fp, L_DEWARPA *dewa );
+LEPT_DLL extern l_int32 dewarpaModelStats ( L_DEWARPA *dewa, l_int32 *pnnone, l_int32 *pnvsuccess, l_int32 *pnvvalid, l_int32 *pnhsuccess, l_int32 *pnhvalid, l_int32 *pnref );
+LEPT_DLL extern l_int32 dewarpaShowArrays ( L_DEWARPA *dewa, l_float32 scalefact, l_int32 first, l_int32 last );
+LEPT_DLL extern l_int32 dewarpDebug ( L_DEWARP *dew, const char *subdirs, l_int32 index );
+LEPT_DLL extern l_int32 dewarpShowResults ( L_DEWARPA *dewa, SARRAY *sa, BOXA *boxa, l_int32 firstpage, l_int32 lastpage, const char *pdfout );
+LEPT_DLL extern L_DNA * l_dnaCreate ( l_int32 n );
+LEPT_DLL extern L_DNA * l_dnaCreateFromIArray ( l_int32 *iarray, l_int32 size );
+LEPT_DLL extern L_DNA * l_dnaCreateFromDArray ( l_float64 *darray, l_int32 size, l_int32 copyflag );
+LEPT_DLL extern L_DNA * l_dnaMakeSequence ( l_float64 startval, l_float64 increment, l_int32 size );
+LEPT_DLL extern void l_dnaDestroy ( L_DNA **pda );
+LEPT_DLL extern L_DNA * l_dnaCopy ( L_DNA *da );
+LEPT_DLL extern L_DNA * l_dnaClone ( L_DNA *da );
+LEPT_DLL extern l_int32 l_dnaEmpty ( L_DNA *da );
+LEPT_DLL extern l_int32 l_dnaAddNumber ( L_DNA *da, l_float64 val );
+LEPT_DLL extern l_int32 l_dnaInsertNumber ( L_DNA *da, l_int32 index, l_float64 val );
+LEPT_DLL extern l_int32 l_dnaRemoveNumber ( L_DNA *da, l_int32 index );
+LEPT_DLL extern l_int32 l_dnaReplaceNumber ( L_DNA *da, l_int32 index, l_float64 val );
+LEPT_DLL extern l_int32 l_dnaGetCount ( L_DNA *da );
+LEPT_DLL extern l_int32 l_dnaSetCount ( L_DNA *da, l_int32 newcount );
+LEPT_DLL extern l_int32 l_dnaGetDValue ( L_DNA *da, l_int32 index, l_float64 *pval );
+LEPT_DLL extern l_int32 l_dnaGetIValue ( L_DNA *da, l_int32 index, l_int32 *pival );
+LEPT_DLL extern l_int32 l_dnaSetValue ( L_DNA *da, l_int32 index, l_float64 val );
+LEPT_DLL extern l_int32 l_dnaShiftValue ( L_DNA *da, l_int32 index, l_float64 diff );
+LEPT_DLL extern l_int32 * l_dnaGetIArray ( L_DNA *da );
+LEPT_DLL extern l_float64 * l_dnaGetDArray ( L_DNA *da, l_int32 copyflag );
+LEPT_DLL extern l_int32 l_dnaGetRefcount ( L_DNA *da );
+LEPT_DLL extern l_int32 l_dnaChangeRefcount ( L_DNA *da, l_int32 delta );
+LEPT_DLL extern l_int32 l_dnaGetParameters ( L_DNA *da, l_float64 *pstartx, l_float64 *pdelx );
+LEPT_DLL extern l_int32 l_dnaSetParameters ( L_DNA *da, l_float64 startx, l_float64 delx );
+LEPT_DLL extern l_int32 l_dnaCopyParameters ( L_DNA *dad, L_DNA *das );
+LEPT_DLL extern L_DNA * l_dnaRead ( const char *filename );
+LEPT_DLL extern L_DNA * l_dnaReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 l_dnaWrite ( const char *filename, L_DNA *da );
+LEPT_DLL extern l_int32 l_dnaWriteStream ( FILE *fp, L_DNA *da );
+LEPT_DLL extern L_DNAA * l_dnaaCreate ( l_int32 n );
+LEPT_DLL extern L_DNAA * l_dnaaCreateFull ( l_int32 nptr, l_int32 n );
+LEPT_DLL extern l_int32 l_dnaaTruncate ( L_DNAA *daa );
+LEPT_DLL extern void l_dnaaDestroy ( L_DNAA **pdaa );
+LEPT_DLL extern l_int32 l_dnaaAddDna ( L_DNAA *daa, L_DNA *da, l_int32 copyflag );
+LEPT_DLL extern l_int32 l_dnaaGetCount ( L_DNAA *daa );
+LEPT_DLL extern l_int32 l_dnaaGetDnaCount ( L_DNAA *daa, l_int32 index );
+LEPT_DLL extern l_int32 l_dnaaGetNumberCount ( L_DNAA *daa );
+LEPT_DLL extern L_DNA * l_dnaaGetDna ( L_DNAA *daa, l_int32 index, l_int32 accessflag );
+LEPT_DLL extern l_int32 l_dnaaReplaceDna ( L_DNAA *daa, l_int32 index, L_DNA *da );
+LEPT_DLL extern l_int32 l_dnaaGetValue ( L_DNAA *daa, l_int32 i, l_int32 j, l_float64 *pval );
+LEPT_DLL extern l_int32 l_dnaaAddNumber ( L_DNAA *daa, l_int32 index, l_float64 val );
+LEPT_DLL extern L_DNAA * l_dnaaRead ( const char *filename );
+LEPT_DLL extern L_DNAA * l_dnaaReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 l_dnaaWrite ( const char *filename, L_DNAA *daa );
+LEPT_DLL extern l_int32 l_dnaaWriteStream ( FILE *fp, L_DNAA *daa );
+LEPT_DLL extern L_DNAHASH * l_dnaHashCreate ( l_int32 nbuckets, l_int32 initsize );
+LEPT_DLL extern void l_dnaHashDestroy ( L_DNAHASH **pdahash );
+LEPT_DLL extern l_int32 l_dnaHashGetCount ( L_DNAHASH *dahash );
+LEPT_DLL extern l_int32 l_dnaHashGetTotalCount ( L_DNAHASH *dahash );
+LEPT_DLL extern L_DNA * l_dnaHashGetDna ( L_DNAHASH *dahash, l_uint64 key, l_int32 copyflag );
+LEPT_DLL extern l_int32 l_dnaHashAdd ( L_DNAHASH *dahash, l_uint64 key, l_float64 value );
+LEPT_DLL extern L_DNAHASH * l_dnaHashCreateFromDna ( L_DNA *da );
+LEPT_DLL extern l_int32 l_dnaRemoveDupsByHash ( L_DNA *das, L_DNA **pdad, L_DNAHASH **pdahash );
+LEPT_DLL extern l_int32 l_dnaMakeHistoByHash ( L_DNA *das, L_DNAHASH **pdahash, L_DNA **pdav, L_DNA **pdac );
+LEPT_DLL extern L_DNA * l_dnaIntersectionByHash ( L_DNA *da1, L_DNA *da2 );
+LEPT_DLL extern l_int32 l_dnaFindValByHash ( L_DNA *da, L_DNAHASH *dahash, l_float64 val, l_int32 *pindex );
+LEPT_DLL extern L_DNA * l_dnaMakeDelta ( L_DNA *das );
+LEPT_DLL extern NUMA * l_dnaConvertToNuma ( L_DNA *da );
+LEPT_DLL extern L_DNA * numaConvertToDna ( NUMA *na );
+LEPT_DLL extern l_int32 l_dnaJoin ( L_DNA *dad, L_DNA *das, l_int32 istart, l_int32 iend );
+LEPT_DLL extern PIX * pixMorphDwa_2 ( PIX *pixd, PIX *pixs, l_int32 operation, char *selname );
+LEPT_DLL extern PIX * pixFMorphopGen_2 ( PIX *pixd, PIX *pixs, l_int32 operation, char *selname );
+LEPT_DLL extern l_int32 fmorphopgen_low_2 ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_int32 index );
+LEPT_DLL extern PIX * pixSobelEdgeFilter ( PIX *pixs, l_int32 orientflag );
+LEPT_DLL extern PIX * pixTwoSidedEdgeFilter ( PIX *pixs, l_int32 orientflag );
+LEPT_DLL extern l_int32 pixMeasureEdgeSmoothness ( PIX *pixs, l_int32 side, l_int32 minjump, l_int32 minreversal, l_float32 *pjpl, l_float32 *pjspl, l_float32 *prpl, const char *debugfile );
+LEPT_DLL extern NUMA * pixGetEdgeProfile ( PIX *pixs, l_int32 side, const char *debugfile );
+LEPT_DLL extern l_int32 pixGetLastOffPixelInRun ( PIX *pixs, l_int32 x, l_int32 y, l_int32 direction, l_int32 *ploc );
+LEPT_DLL extern l_int32 pixGetLastOnPixelInRun ( PIX *pixs, l_int32 x, l_int32 y, l_int32 direction, l_int32 *ploc );
+LEPT_DLL extern char * encodeBase64 ( l_uint8 *inarray, l_int32 insize, l_int32 *poutsize );
+LEPT_DLL extern l_uint8 * decodeBase64 ( const char *inarray, l_int32 insize, l_int32 *poutsize );
+LEPT_DLL extern char * encodeAscii85 ( l_uint8 *inarray, l_int32 insize, l_int32 *poutsize );
+LEPT_DLL extern l_uint8 * decodeAscii85 ( char *inarray, l_int32 insize, l_int32 *poutsize );
+LEPT_DLL extern char * reformatPacked64 ( char *inarray, l_int32 insize, l_int32 leadspace, l_int32 linechars, l_int32 addquotes, l_int32 *poutsize );
+LEPT_DLL extern PIX * pixGammaTRC ( PIX *pixd, PIX *pixs, l_float32 gamma, l_int32 minval, l_int32 maxval );
+LEPT_DLL extern PIX * pixGammaTRCMasked ( PIX *pixd, PIX *pixs, PIX *pixm, l_float32 gamma, l_int32 minval, l_int32 maxval );
+LEPT_DLL extern PIX * pixGammaTRCWithAlpha ( PIX *pixd, PIX *pixs, l_float32 gamma, l_int32 minval, l_int32 maxval );
+LEPT_DLL extern NUMA * numaGammaTRC ( l_float32 gamma, l_int32 minval, l_int32 maxval );
+LEPT_DLL extern PIX * pixContrastTRC ( PIX *pixd, PIX *pixs, l_float32 factor );
+LEPT_DLL extern PIX * pixContrastTRCMasked ( PIX *pixd, PIX *pixs, PIX *pixm, l_float32 factor );
+LEPT_DLL extern NUMA * numaContrastTRC ( l_float32 factor );
+LEPT_DLL extern PIX * pixEqualizeTRC ( PIX *pixd, PIX *pixs, l_float32 fract, l_int32 factor );
+LEPT_DLL extern NUMA * numaEqualizeTRC ( PIX *pix, l_float32 fract, l_int32 factor );
+LEPT_DLL extern l_int32 pixTRCMap ( PIX *pixs, PIX *pixm, NUMA *na );
+LEPT_DLL extern PIX * pixUnsharpMasking ( PIX *pixs, l_int32 halfwidth, l_float32 fract );
+LEPT_DLL extern PIX * pixUnsharpMaskingGray ( PIX *pixs, l_int32 halfwidth, l_float32 fract );
+LEPT_DLL extern PIX * pixUnsharpMaskingFast ( PIX *pixs, l_int32 halfwidth, l_float32 fract, l_int32 direction );
+LEPT_DLL extern PIX * pixUnsharpMaskingGrayFast ( PIX *pixs, l_int32 halfwidth, l_float32 fract, l_int32 direction );
+LEPT_DLL extern PIX * pixUnsharpMaskingGray1D ( PIX *pixs, l_int32 halfwidth, l_float32 fract, l_int32 direction );
+LEPT_DLL extern PIX * pixUnsharpMaskingGray2D ( PIX *pixs, l_int32 halfwidth, l_float32 fract );
+LEPT_DLL extern PIX * pixModifyHue ( PIX *pixd, PIX *pixs, l_float32 fract );
+LEPT_DLL extern PIX * pixModifySaturation ( PIX *pixd, PIX *pixs, l_float32 fract );
+LEPT_DLL extern l_int32 pixMeasureSaturation ( PIX *pixs, l_int32 factor, l_float32 *psat );
+LEPT_DLL extern PIX * pixModifyBrightness ( PIX *pixd, PIX *pixs, l_float32 fract );
+LEPT_DLL extern PIX * pixColorShiftRGB ( PIX *pixs, l_float32 rfract, l_float32 gfract, l_float32 bfract );
+LEPT_DLL extern PIX * pixMultConstantColor ( PIX *pixs, l_float32 rfact, l_float32 gfact, l_float32 bfact );
+LEPT_DLL extern PIX * pixMultMatrixColor ( PIX *pixs, L_KERNEL *kel );
+LEPT_DLL extern PIX * pixHalfEdgeByBandpass ( PIX *pixs, l_int32 sm1h, l_int32 sm1v, l_int32 sm2h, l_int32 sm2v );
+LEPT_DLL extern l_int32 fhmtautogen ( SELA *sela, l_int32 fileindex, const char *filename );
+LEPT_DLL extern l_int32 fhmtautogen1 ( SELA *sela, l_int32 fileindex, const char *filename );
+LEPT_DLL extern l_int32 fhmtautogen2 ( SELA *sela, l_int32 fileindex, const char *filename );
+LEPT_DLL extern PIX * pixHMTDwa_1 ( PIX *pixd, PIX *pixs, const char *selname );
+LEPT_DLL extern PIX * pixFHMTGen_1 ( PIX *pixd, PIX *pixs, const char *selname );
+LEPT_DLL extern l_int32 fhmtgen_low_1 ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_int32 index );
+LEPT_DLL extern l_int32 pixItalicWords ( PIX *pixs, BOXA *boxaw, PIX *pixw, BOXA **pboxa, l_int32 debugflag );
+LEPT_DLL extern l_int32 pixOrientDetect ( PIX *pixs, l_float32 *pupconf, l_float32 *pleftconf, l_int32 mincount, l_int32 debug );
+LEPT_DLL extern l_int32 makeOrientDecision ( l_float32 upconf, l_float32 leftconf, l_float32 minupconf, l_float32 minratio, l_int32 *porient, l_int32 debug );
+LEPT_DLL extern l_int32 pixUpDownDetect ( PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 debug );
+LEPT_DLL extern l_int32 pixUpDownDetectGeneral ( PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 npixels, l_int32 debug );
+LEPT_DLL extern l_int32 pixOrientDetectDwa ( PIX *pixs, l_float32 *pupconf, l_float32 *pleftconf, l_int32 mincount, l_int32 debug );
+LEPT_DLL extern l_int32 pixUpDownDetectDwa ( PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 debug );
+LEPT_DLL extern l_int32 pixUpDownDetectGeneralDwa ( PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 npixels, l_int32 debug );
+LEPT_DLL extern l_int32 pixMirrorDetect ( PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 debug );
+LEPT_DLL extern l_int32 pixMirrorDetectDwa ( PIX *pixs, l_float32 *pconf, l_int32 mincount, l_int32 debug );
+LEPT_DLL extern PIX * pixFlipFHMTGen ( PIX *pixd, PIX *pixs, char *selname );
+LEPT_DLL extern l_int32 fmorphautogen ( SELA *sela, l_int32 fileindex, const char *filename );
+LEPT_DLL extern l_int32 fmorphautogen1 ( SELA *sela, l_int32 fileindex, const char *filename );
+LEPT_DLL extern l_int32 fmorphautogen2 ( SELA *sela, l_int32 fileindex, const char *filename );
+LEPT_DLL extern PIX * pixMorphDwa_1 ( PIX *pixd, PIX *pixs, l_int32 operation, char *selname );
+LEPT_DLL extern PIX * pixFMorphopGen_1 ( PIX *pixd, PIX *pixs, l_int32 operation, char *selname );
+LEPT_DLL extern l_int32 fmorphopgen_low_1 ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_int32 index );
+LEPT_DLL extern FPIX * fpixCreate ( l_int32 width, l_int32 height );
+LEPT_DLL extern FPIX * fpixCreateTemplate ( FPIX *fpixs );
+LEPT_DLL extern FPIX * fpixClone ( FPIX *fpix );
+LEPT_DLL extern FPIX * fpixCopy ( FPIX *fpixd, FPIX *fpixs );
+LEPT_DLL extern l_int32 fpixResizeImageData ( FPIX *fpixd, FPIX *fpixs );
+LEPT_DLL extern void fpixDestroy ( FPIX **pfpix );
+LEPT_DLL extern l_int32 fpixGetDimensions ( FPIX *fpix, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_int32 fpixSetDimensions ( FPIX *fpix, l_int32 w, l_int32 h );
+LEPT_DLL extern l_int32 fpixGetWpl ( FPIX *fpix );
+LEPT_DLL extern l_int32 fpixSetWpl ( FPIX *fpix, l_int32 wpl );
+LEPT_DLL extern l_int32 fpixGetRefcount ( FPIX *fpix );
+LEPT_DLL extern l_int32 fpixChangeRefcount ( FPIX *fpix, l_int32 delta );
+LEPT_DLL extern l_int32 fpixGetResolution ( FPIX *fpix, l_int32 *pxres, l_int32 *pyres );
+LEPT_DLL extern l_int32 fpixSetResolution ( FPIX *fpix, l_int32 xres, l_int32 yres );
+LEPT_DLL extern l_int32 fpixCopyResolution ( FPIX *fpixd, FPIX *fpixs );
+LEPT_DLL extern l_float32 * fpixGetData ( FPIX *fpix );
+LEPT_DLL extern l_int32 fpixSetData ( FPIX *fpix, l_float32 *data );
+LEPT_DLL extern l_int32 fpixGetPixel ( FPIX *fpix, l_int32 x, l_int32 y, l_float32 *pval );
+LEPT_DLL extern l_int32 fpixSetPixel ( FPIX *fpix, l_int32 x, l_int32 y, l_float32 val );
+LEPT_DLL extern FPIXA * fpixaCreate ( l_int32 n );
+LEPT_DLL extern FPIXA * fpixaCopy ( FPIXA *fpixa, l_int32 copyflag );
+LEPT_DLL extern void fpixaDestroy ( FPIXA **pfpixa );
+LEPT_DLL extern l_int32 fpixaAddFPix ( FPIXA *fpixa, FPIX *fpix, l_int32 copyflag );
+LEPT_DLL extern l_int32 fpixaGetCount ( FPIXA *fpixa );
+LEPT_DLL extern l_int32 fpixaChangeRefcount ( FPIXA *fpixa, l_int32 delta );
+LEPT_DLL extern FPIX * fpixaGetFPix ( FPIXA *fpixa, l_int32 index, l_int32 accesstype );
+LEPT_DLL extern l_int32 fpixaGetFPixDimensions ( FPIXA *fpixa, l_int32 index, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_float32 * fpixaGetData ( FPIXA *fpixa, l_int32 index );
+LEPT_DLL extern l_int32 fpixaGetPixel ( FPIXA *fpixa, l_int32 index, l_int32 x, l_int32 y, l_float32 *pval );
+LEPT_DLL extern l_int32 fpixaSetPixel ( FPIXA *fpixa, l_int32 index, l_int32 x, l_int32 y, l_float32 val );
+LEPT_DLL extern DPIX * dpixCreate ( l_int32 width, l_int32 height );
+LEPT_DLL extern DPIX * dpixCreateTemplate ( DPIX *dpixs );
+LEPT_DLL extern DPIX * dpixClone ( DPIX *dpix );
+LEPT_DLL extern DPIX * dpixCopy ( DPIX *dpixd, DPIX *dpixs );
+LEPT_DLL extern l_int32 dpixResizeImageData ( DPIX *dpixd, DPIX *dpixs );
+LEPT_DLL extern void dpixDestroy ( DPIX **pdpix );
+LEPT_DLL extern l_int32 dpixGetDimensions ( DPIX *dpix, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_int32 dpixSetDimensions ( DPIX *dpix, l_int32 w, l_int32 h );
+LEPT_DLL extern l_int32 dpixGetWpl ( DPIX *dpix );
+LEPT_DLL extern l_int32 dpixSetWpl ( DPIX *dpix, l_int32 wpl );
+LEPT_DLL extern l_int32 dpixGetRefcount ( DPIX *dpix );
+LEPT_DLL extern l_int32 dpixChangeRefcount ( DPIX *dpix, l_int32 delta );
+LEPT_DLL extern l_int32 dpixGetResolution ( DPIX *dpix, l_int32 *pxres, l_int32 *pyres );
+LEPT_DLL extern l_int32 dpixSetResolution ( DPIX *dpix, l_int32 xres, l_int32 yres );
+LEPT_DLL extern l_int32 dpixCopyResolution ( DPIX *dpixd, DPIX *dpixs );
+LEPT_DLL extern l_float64 * dpixGetData ( DPIX *dpix );
+LEPT_DLL extern l_int32 dpixSetData ( DPIX *dpix, l_float64 *data );
+LEPT_DLL extern l_int32 dpixGetPixel ( DPIX *dpix, l_int32 x, l_int32 y, l_float64 *pval );
+LEPT_DLL extern l_int32 dpixSetPixel ( DPIX *dpix, l_int32 x, l_int32 y, l_float64 val );
+LEPT_DLL extern FPIX * fpixRead ( const char *filename );
+LEPT_DLL extern FPIX * fpixReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 fpixWrite ( const char *filename, FPIX *fpix );
+LEPT_DLL extern l_int32 fpixWriteStream ( FILE *fp, FPIX *fpix );
+LEPT_DLL extern FPIX * fpixEndianByteSwap ( FPIX *fpixd, FPIX *fpixs );
+LEPT_DLL extern DPIX * dpixRead ( const char *filename );
+LEPT_DLL extern DPIX * dpixReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 dpixWrite ( const char *filename, DPIX *dpix );
+LEPT_DLL extern l_int32 dpixWriteStream ( FILE *fp, DPIX *dpix );
+LEPT_DLL extern DPIX * dpixEndianByteSwap ( DPIX *dpixd, DPIX *dpixs );
+LEPT_DLL extern l_int32 fpixPrintStream ( FILE *fp, FPIX *fpix, l_int32 factor );
+LEPT_DLL extern FPIX * pixConvertToFPix ( PIX *pixs, l_int32 ncomps );
+LEPT_DLL extern DPIX * pixConvertToDPix ( PIX *pixs, l_int32 ncomps );
+LEPT_DLL extern PIX * fpixConvertToPix ( FPIX *fpixs, l_int32 outdepth, l_int32 negvals, l_int32 errorflag );
+LEPT_DLL extern PIX * fpixDisplayMaxDynamicRange ( FPIX *fpixs );
+LEPT_DLL extern DPIX * fpixConvertToDPix ( FPIX *fpix );
+LEPT_DLL extern PIX * dpixConvertToPix ( DPIX *dpixs, l_int32 outdepth, l_int32 negvals, l_int32 errorflag );
+LEPT_DLL extern FPIX * dpixConvertToFPix ( DPIX *dpix );
+LEPT_DLL extern l_int32 fpixGetMin ( FPIX *fpix, l_float32 *pminval, l_int32 *pxminloc, l_int32 *pyminloc );
+LEPT_DLL extern l_int32 fpixGetMax ( FPIX *fpix, l_float32 *pmaxval, l_int32 *pxmaxloc, l_int32 *pymaxloc );
+LEPT_DLL extern l_int32 dpixGetMin ( DPIX *dpix, l_float64 *pminval, l_int32 *pxminloc, l_int32 *pyminloc );
+LEPT_DLL extern l_int32 dpixGetMax ( DPIX *dpix, l_float64 *pmaxval, l_int32 *pxmaxloc, l_int32 *pymaxloc );
+LEPT_DLL extern FPIX * fpixScaleByInteger ( FPIX *fpixs, l_int32 factor );
+LEPT_DLL extern DPIX * dpixScaleByInteger ( DPIX *dpixs, l_int32 factor );
+LEPT_DLL extern FPIX * fpixLinearCombination ( FPIX *fpixd, FPIX *fpixs1, FPIX *fpixs2, l_float32 a, l_float32 b );
+LEPT_DLL extern l_int32 fpixAddMultConstant ( FPIX *fpix, l_float32 addc, l_float32 multc );
+LEPT_DLL extern DPIX * dpixLinearCombination ( DPIX *dpixd, DPIX *dpixs1, DPIX *dpixs2, l_float32 a, l_float32 b );
+LEPT_DLL extern l_int32 dpixAddMultConstant ( DPIX *dpix, l_float64 addc, l_float64 multc );
+LEPT_DLL extern l_int32 fpixSetAllArbitrary ( FPIX *fpix, l_float32 inval );
+LEPT_DLL extern l_int32 dpixSetAllArbitrary ( DPIX *dpix, l_float64 inval );
+LEPT_DLL extern FPIX * fpixAddBorder ( FPIX *fpixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot );
+LEPT_DLL extern FPIX * fpixRemoveBorder ( FPIX *fpixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot );
+LEPT_DLL extern FPIX * fpixAddMirroredBorder ( FPIX *fpixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot );
+LEPT_DLL extern FPIX * fpixAddContinuedBorder ( FPIX *fpixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot );
+LEPT_DLL extern FPIX * fpixAddSlopeBorder ( FPIX *fpixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot );
+LEPT_DLL extern l_int32 fpixRasterop ( FPIX *fpixd, l_int32 dx, l_int32 dy, l_int32 dw, l_int32 dh, FPIX *fpixs, l_int32 sx, l_int32 sy );
+LEPT_DLL extern FPIX * fpixRotateOrth ( FPIX *fpixs, l_int32 quads );
+LEPT_DLL extern FPIX * fpixRotate180 ( FPIX *fpixd, FPIX *fpixs );
+LEPT_DLL extern FPIX * fpixRotate90 ( FPIX *fpixs, l_int32 direction );
+LEPT_DLL extern FPIX * fpixFlipLR ( FPIX *fpixd, FPIX *fpixs );
+LEPT_DLL extern FPIX * fpixFlipTB ( FPIX *fpixd, FPIX *fpixs );
+LEPT_DLL extern FPIX * fpixAffinePta ( FPIX *fpixs, PTA *ptad, PTA *ptas, l_int32 border, l_float32 inval );
+LEPT_DLL extern FPIX * fpixAffine ( FPIX *fpixs, l_float32 *vc, l_float32 inval );
+LEPT_DLL extern FPIX * fpixProjectivePta ( FPIX *fpixs, PTA *ptad, PTA *ptas, l_int32 border, l_float32 inval );
+LEPT_DLL extern FPIX * fpixProjective ( FPIX *fpixs, l_float32 *vc, l_float32 inval );
+LEPT_DLL extern l_int32 linearInterpolatePixelFloat ( l_float32 *datas, l_int32 w, l_int32 h, l_float32 x, l_float32 y, l_float32 inval, l_float32 *pval );
+LEPT_DLL extern PIX * fpixThresholdToPix ( FPIX *fpix, l_float32 thresh );
+LEPT_DLL extern FPIX * pixComponentFunction ( PIX *pix, l_float32 rnum, l_float32 gnum, l_float32 bnum, l_float32 rdenom, l_float32 gdenom, l_float32 bdenom );
+LEPT_DLL extern PIX * pixReadStreamGif ( FILE *fp );
+LEPT_DLL extern l_int32 pixWriteStreamGif ( FILE *fp, PIX *pix );
+LEPT_DLL extern PIX * pixReadMemGif ( const l_uint8 *cdata, size_t size );
+LEPT_DLL extern l_int32 pixWriteMemGif ( l_uint8 **pdata, size_t *psize, PIX *pix );
+LEPT_DLL extern GPLOT * gplotCreate ( const char *rootname, l_int32 outformat, const char *title, const char *xlabel, const char *ylabel );
+LEPT_DLL extern void gplotDestroy ( GPLOT **pgplot );
+LEPT_DLL extern l_int32 gplotAddPlot ( GPLOT *gplot, NUMA *nax, NUMA *nay, l_int32 plotstyle, const char *plottitle );
+LEPT_DLL extern l_int32 gplotSetScaling ( GPLOT *gplot, l_int32 scaling );
+LEPT_DLL extern l_int32 gplotMakeOutput ( GPLOT *gplot );
+LEPT_DLL extern l_int32 gplotGenCommandFile ( GPLOT *gplot );
+LEPT_DLL extern l_int32 gplotGenDataFiles ( GPLOT *gplot );
+LEPT_DLL extern l_int32 gplotSimple1 ( NUMA *na, l_int32 outformat, const char *outroot, const char *title );
+LEPT_DLL extern l_int32 gplotSimple2 ( NUMA *na1, NUMA *na2, l_int32 outformat, const char *outroot, const char *title );
+LEPT_DLL extern l_int32 gplotSimpleN ( NUMAA *naa, l_int32 outformat, const char *outroot, const char *title );
+LEPT_DLL extern l_int32 gplotSimpleXY1 ( NUMA *nax, NUMA *nay, l_int32 plotstyle, l_int32 outformat, const char *outroot, const char *title );
+LEPT_DLL extern l_int32 gplotSimpleXY2 ( NUMA *nax, NUMA *nay1, NUMA *nay2, l_int32 plotstyle, l_int32 outformat, const char *outroot, const char *title );
+LEPT_DLL extern l_int32 gplotSimpleXYN ( NUMA *nax, NUMAA *naay, l_int32 plotstyle, l_int32 outformat, const char *outroot, const char *title );
+LEPT_DLL extern GPLOT * gplotRead ( const char *filename );
+LEPT_DLL extern l_int32 gplotWrite ( const char *filename, GPLOT *gplot );
+LEPT_DLL extern PTA * generatePtaLine ( l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2 );
+LEPT_DLL extern PTA * generatePtaWideLine ( l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2, l_int32 width );
+LEPT_DLL extern PTA * generatePtaBox ( BOX *box, l_int32 width );
+LEPT_DLL extern PTA * generatePtaBoxa ( BOXA *boxa, l_int32 width, l_int32 removedups );
+LEPT_DLL extern PTA * generatePtaHashBox ( BOX *box, l_int32 spacing, l_int32 width, l_int32 orient, l_int32 outline );
+LEPT_DLL extern PTA * generatePtaHashBoxa ( BOXA *boxa, l_int32 spacing, l_int32 width, l_int32 orient, l_int32 outline, l_int32 removedups );
+LEPT_DLL extern PTAA * generatePtaaBoxa ( BOXA *boxa );
+LEPT_DLL extern PTAA * generatePtaaHashBoxa ( BOXA *boxa, l_int32 spacing, l_int32 width, l_int32 orient, l_int32 outline );
+LEPT_DLL extern PTA * generatePtaPolyline ( PTA *ptas, l_int32 width, l_int32 closeflag, l_int32 removedups );
+LEPT_DLL extern PTA * generatePtaGrid ( l_int32 w, l_int32 h, l_int32 nx, l_int32 ny, l_int32 width );
+LEPT_DLL extern PTA * convertPtaLineTo4cc ( PTA *ptas );
+LEPT_DLL extern PTA * generatePtaFilledCircle ( l_int32 radius );
+LEPT_DLL extern PTA * generatePtaFilledSquare ( l_int32 side );
+LEPT_DLL extern PTA * generatePtaLineFromPt ( l_int32 x, l_int32 y, l_float64 length, l_float64 radang );
+LEPT_DLL extern l_int32 locatePtRadially ( l_int32 xr, l_int32 yr, l_float64 dist, l_float64 radang, l_float64 *px, l_float64 *py );
+LEPT_DLL extern l_int32 pixRenderPlotFromNuma ( PIX **ppix, NUMA *na, l_int32 plotloc, l_int32 linewidth, l_int32 max, l_uint32 color );
+LEPT_DLL extern PTA * makePlotPtaFromNuma ( NUMA *na, l_int32 size, l_int32 plotloc, l_int32 linewidth, l_int32 max );
+LEPT_DLL extern l_int32 pixRenderPlotFromNumaGen ( PIX **ppix, NUMA *na, l_int32 orient, l_int32 linewidth, l_int32 refpos, l_int32 max, l_int32 drawref, l_uint32 color );
+LEPT_DLL extern PTA * makePlotPtaFromNumaGen ( NUMA *na, l_int32 orient, l_int32 linewidth, l_int32 refpos, l_int32 max, l_int32 drawref );
+LEPT_DLL extern l_int32 pixRenderPta ( PIX *pix, PTA *pta, l_int32 op );
+LEPT_DLL extern l_int32 pixRenderPtaArb ( PIX *pix, PTA *pta, l_uint8 rval, l_uint8 gval, l_uint8 bval );
+LEPT_DLL extern l_int32 pixRenderPtaBlend ( PIX *pix, PTA *pta, l_uint8 rval, l_uint8 gval, l_uint8 bval, l_float32 fract );
+LEPT_DLL extern l_int32 pixRenderLine ( PIX *pix, l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2, l_int32 width, l_int32 op );
+LEPT_DLL extern l_int32 pixRenderLineArb ( PIX *pix, l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval );
+LEPT_DLL extern l_int32 pixRenderLineBlend ( PIX *pix, l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval, l_float32 fract );
+LEPT_DLL extern l_int32 pixRenderBox ( PIX *pix, BOX *box, l_int32 width, l_int32 op );
+LEPT_DLL extern l_int32 pixRenderBoxArb ( PIX *pix, BOX *box, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval );
+LEPT_DLL extern l_int32 pixRenderBoxBlend ( PIX *pix, BOX *box, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval, l_float32 fract );
+LEPT_DLL extern l_int32 pixRenderBoxa ( PIX *pix, BOXA *boxa, l_int32 width, l_int32 op );
+LEPT_DLL extern l_int32 pixRenderBoxaArb ( PIX *pix, BOXA *boxa, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval );
+LEPT_DLL extern l_int32 pixRenderBoxaBlend ( PIX *pix, BOXA *boxa, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval, l_float32 fract, l_int32 removedups );
+LEPT_DLL extern l_int32 pixRenderHashBox ( PIX *pix, BOX *box, l_int32 spacing, l_int32 width, l_int32 orient, l_int32 outline, l_int32 op );
+LEPT_DLL extern l_int32 pixRenderHashBoxArb ( PIX *pix, BOX *box, l_int32 spacing, l_int32 width, l_int32 orient, l_int32 outline, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_int32 pixRenderHashBoxBlend ( PIX *pix, BOX *box, l_int32 spacing, l_int32 width, l_int32 orient, l_int32 outline, l_int32 rval, l_int32 gval, l_int32 bval, l_float32 fract );
+LEPT_DLL extern l_int32 pixRenderHashBoxa ( PIX *pix, BOXA *boxa, l_int32 spacing, l_int32 width, l_int32 orient, l_int32 outline, l_int32 op );
+LEPT_DLL extern l_int32 pixRenderHashBoxaArb ( PIX *pix, BOXA *boxa, l_int32 spacing, l_int32 width, l_int32 orient, l_int32 outline, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_int32 pixRenderHashBoxaBlend ( PIX *pix, BOXA *boxa, l_int32 spacing, l_int32 width, l_int32 orient, l_int32 outline, l_int32 rval, l_int32 gval, l_int32 bval, l_float32 fract );
+LEPT_DLL extern l_int32 pixRenderPolyline ( PIX *pix, PTA *ptas, l_int32 width, l_int32 op, l_int32 closeflag );
+LEPT_DLL extern l_int32 pixRenderPolylineArb ( PIX *pix, PTA *ptas, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval, l_int32 closeflag );
+LEPT_DLL extern l_int32 pixRenderPolylineBlend ( PIX *pix, PTA *ptas, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval, l_float32 fract, l_int32 closeflag, l_int32 removedups );
+LEPT_DLL extern l_int32 pixRenderGridArb ( PIX *pix, l_int32 nx, l_int32 ny, l_int32 width, l_uint8 rval, l_uint8 gval, l_uint8 bval );
+LEPT_DLL extern PIX * pixRenderRandomCmapPtaa ( PIX *pix, PTAA *ptaa, l_int32 polyflag, l_int32 width, l_int32 closeflag );
+LEPT_DLL extern PIX * pixRenderPolygon ( PTA *ptas, l_int32 width, l_int32 *pxmin, l_int32 *pymin );
+LEPT_DLL extern PIX * pixFillPolygon ( PIX *pixs, PTA *pta, l_int32 xmin, l_int32 ymin );
+LEPT_DLL extern PIX * pixRenderContours ( PIX *pixs, l_int32 startval, l_int32 incr, l_int32 outdepth );
+LEPT_DLL extern PIX * fpixAutoRenderContours ( FPIX *fpix, l_int32 ncontours );
+LEPT_DLL extern PIX * fpixRenderContours ( FPIX *fpixs, l_float32 incr, l_float32 proxim );
+LEPT_DLL extern PTA * pixGeneratePtaBoundary ( PIX *pixs, l_int32 width );
+LEPT_DLL extern PIX * pixErodeGray ( PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixDilateGray ( PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixOpenGray ( PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixCloseGray ( PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixErodeGray3 ( PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixDilateGray3 ( PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixOpenGray3 ( PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixCloseGray3 ( PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixDitherToBinary ( PIX *pixs );
+LEPT_DLL extern PIX * pixDitherToBinarySpec ( PIX *pixs, l_int32 lowerclip, l_int32 upperclip );
+LEPT_DLL extern PIX * pixThresholdToBinary ( PIX *pixs, l_int32 thresh );
+LEPT_DLL extern PIX * pixVarThresholdToBinary ( PIX *pixs, PIX *pixg );
+LEPT_DLL extern PIX * pixAdaptThresholdToBinary ( PIX *pixs, PIX *pixm, l_float32 gamma );
+LEPT_DLL extern PIX * pixAdaptThresholdToBinaryGen ( PIX *pixs, PIX *pixm, l_float32 gamma, l_int32 blackval, l_int32 whiteval, l_int32 thresh );
+LEPT_DLL extern PIX * pixDitherToBinaryLUT ( PIX *pixs, l_int32 lowerclip, l_int32 upperclip );
+LEPT_DLL extern PIX * pixGenerateMaskByValue ( PIX *pixs, l_int32 val, l_int32 usecmap );
+LEPT_DLL extern PIX * pixGenerateMaskByBand ( PIX *pixs, l_int32 lower, l_int32 upper, l_int32 inband, l_int32 usecmap );
+LEPT_DLL extern PIX * pixDitherTo2bpp ( PIX *pixs, l_int32 cmapflag );
+LEPT_DLL extern PIX * pixDitherTo2bppSpec ( PIX *pixs, l_int32 lowerclip, l_int32 upperclip, l_int32 cmapflag );
+LEPT_DLL extern PIX * pixThresholdTo2bpp ( PIX *pixs, l_int32 nlevels, l_int32 cmapflag );
+LEPT_DLL extern PIX * pixThresholdTo4bpp ( PIX *pixs, l_int32 nlevels, l_int32 cmapflag );
+LEPT_DLL extern PIX * pixThresholdOn8bpp ( PIX *pixs, l_int32 nlevels, l_int32 cmapflag );
+LEPT_DLL extern PIX * pixThresholdGrayArb ( PIX *pixs, const char *edgevals, l_int32 outdepth, l_int32 use_average, l_int32 setblack, l_int32 setwhite );
+LEPT_DLL extern l_int32 * makeGrayQuantIndexTable ( l_int32 nlevels );
+LEPT_DLL extern l_int32 * makeGrayQuantTargetTable ( l_int32 nlevels, l_int32 depth );
+LEPT_DLL extern l_int32 makeGrayQuantTableArb ( NUMA *na, l_int32 outdepth, l_int32 **ptab, PIXCMAP **pcmap );
+LEPT_DLL extern l_int32 makeGrayQuantColormapArb ( PIX *pixs, l_int32 *tab, l_int32 outdepth, PIXCMAP **pcmap );
+LEPT_DLL extern PIX * pixGenerateMaskByBand32 ( PIX *pixs, l_uint32 refval, l_int32 delm, l_int32 delp, l_float32 fractm, l_float32 fractp );
+LEPT_DLL extern PIX * pixGenerateMaskByDiscr32 ( PIX *pixs, l_uint32 refval1, l_uint32 refval2, l_int32 distflag );
+LEPT_DLL extern PIX * pixGrayQuantFromHisto ( PIX *pixd, PIX *pixs, PIX *pixm, l_float32 minfract, l_int32 maxsize );
+LEPT_DLL extern PIX * pixGrayQuantFromCmap ( PIX *pixs, PIXCMAP *cmap, l_int32 mindepth );
+LEPT_DLL extern void ditherToBinaryLow ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_uint32 *bufs1, l_uint32 *bufs2, l_int32 lowerclip, l_int32 upperclip );
+LEPT_DLL extern void ditherToBinaryLineLow ( l_uint32 *lined, l_int32 w, l_uint32 *bufs1, l_uint32 *bufs2, l_int32 lowerclip, l_int32 upperclip, l_int32 lastlineflag );
+LEPT_DLL extern void thresholdToBinaryLow ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 d, l_int32 wpls, l_int32 thresh );
+LEPT_DLL extern void thresholdToBinaryLineLow ( l_uint32 *lined, l_int32 w, l_uint32 *lines, l_int32 d, l_int32 thresh );
+LEPT_DLL extern void ditherToBinaryLUTLow ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_uint32 *bufs1, l_uint32 *bufs2, l_int32 *tabval, l_int32 *tab38, l_int32 *tab14 );
+LEPT_DLL extern void ditherToBinaryLineLUTLow ( l_uint32 *lined, l_int32 w, l_uint32 *bufs1, l_uint32 *bufs2, l_int32 *tabval, l_int32 *tab38, l_int32 *tab14, l_int32 lastlineflag );
+LEPT_DLL extern l_int32 make8To1DitherTables ( l_int32 **ptabval, l_int32 **ptab38, l_int32 **ptab14, l_int32 lowerclip, l_int32 upperclip );
+LEPT_DLL extern void ditherTo2bppLow ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_uint32 *bufs1, l_uint32 *bufs2, l_int32 *tabval, l_int32 *tab38, l_int32 *tab14 );
+LEPT_DLL extern void ditherTo2bppLineLow ( l_uint32 *lined, l_int32 w, l_uint32 *bufs1, l_uint32 *bufs2, l_int32 *tabval, l_int32 *tab38, l_int32 *tab14, l_int32 lastlineflag );
+LEPT_DLL extern l_int32 make8To2DitherTables ( l_int32 **ptabval, l_int32 **ptab38, l_int32 **ptab14, l_int32 cliptoblack, l_int32 cliptowhite );
+LEPT_DLL extern void thresholdTo2bppLow ( l_uint32 *datad, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_int32 *tab );
+LEPT_DLL extern void thresholdTo4bppLow ( l_uint32 *datad, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_int32 *tab );
+LEPT_DLL extern L_HEAP * lheapCreate ( l_int32 nalloc, l_int32 direction );
+LEPT_DLL extern void lheapDestroy ( L_HEAP **plh, l_int32 freeflag );
+LEPT_DLL extern l_int32 lheapAdd ( L_HEAP *lh, void *item );
+LEPT_DLL extern void * lheapRemove ( L_HEAP *lh );
+LEPT_DLL extern l_int32 lheapGetCount ( L_HEAP *lh );
+LEPT_DLL extern l_int32 lheapSwapUp ( L_HEAP *lh, l_int32 index );
+LEPT_DLL extern l_int32 lheapSwapDown ( L_HEAP *lh );
+LEPT_DLL extern l_int32 lheapSort ( L_HEAP *lh );
+LEPT_DLL extern l_int32 lheapSortStrictOrder ( L_HEAP *lh );
+LEPT_DLL extern l_int32 lheapPrint ( FILE *fp, L_HEAP *lh );
+LEPT_DLL extern JBCLASSER * jbRankHausInit ( l_int32 components, l_int32 maxwidth, l_int32 maxheight, l_int32 size, l_float32 rank );
+LEPT_DLL extern JBCLASSER * jbCorrelationInit ( l_int32 components, l_int32 maxwidth, l_int32 maxheight, l_float32 thresh, l_float32 weightfactor );
+LEPT_DLL extern JBCLASSER * jbCorrelationInitWithoutComponents ( l_int32 components, l_int32 maxwidth, l_int32 maxheight, l_float32 thresh, l_float32 weightfactor );
+LEPT_DLL extern l_int32 jbAddPages ( JBCLASSER *classer, SARRAY *safiles );
+LEPT_DLL extern l_int32 jbAddPage ( JBCLASSER *classer, PIX *pixs );
+LEPT_DLL extern l_int32 jbAddPageComponents ( JBCLASSER *classer, PIX *pixs, BOXA *boxas, PIXA *pixas );
+LEPT_DLL extern l_int32 jbClassifyRankHaus ( JBCLASSER *classer, BOXA *boxa, PIXA *pixas );
+LEPT_DLL extern l_int32 pixHaustest ( PIX *pix1, PIX *pix2, PIX *pix3, PIX *pix4, l_float32 delx, l_float32 dely, l_int32 maxdiffw, l_int32 maxdiffh );
+LEPT_DLL extern l_int32 pixRankHaustest ( PIX *pix1, PIX *pix2, PIX *pix3, PIX *pix4, l_float32 delx, l_float32 dely, l_int32 maxdiffw, l_int32 maxdiffh, l_int32 area1, l_int32 area3, l_float32 rank, l_int32 *tab8 );
+LEPT_DLL extern l_int32 jbClassifyCorrelation ( JBCLASSER *classer, BOXA *boxa, PIXA *pixas );
+LEPT_DLL extern l_int32 jbGetComponents ( PIX *pixs, l_int32 components, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxad, PIXA **ppixad );
+LEPT_DLL extern l_int32 pixWordMaskByDilation ( PIX *pixs, l_int32 maxdil, PIX **ppixm, l_int32 *psize );
+LEPT_DLL extern l_int32 pixWordBoxesByDilation ( PIX *pixs, l_int32 maxdil, l_int32 minwidth, l_int32 minheight, l_int32 maxwidth, l_int32 maxheight, BOXA **pboxa, l_int32 *psize );
+LEPT_DLL extern PIXA * jbAccumulateComposites ( PIXAA *pixaa, NUMA **pna, PTA **pptat );
+LEPT_DLL extern PIXA * jbTemplatesFromComposites ( PIXA *pixac, NUMA *na );
+LEPT_DLL extern JBCLASSER * jbClasserCreate ( l_int32 method, l_int32 components );
+LEPT_DLL extern void jbClasserDestroy ( JBCLASSER **pclasser );
+LEPT_DLL extern JBDATA * jbDataSave ( JBCLASSER *classer );
+LEPT_DLL extern void jbDataDestroy ( JBDATA **pdata );
+LEPT_DLL extern l_int32 jbDataWrite ( const char *rootout, JBDATA *jbdata );
+LEPT_DLL extern JBDATA * jbDataRead ( const char *rootname );
+LEPT_DLL extern PIXA * jbDataRender ( JBDATA *data, l_int32 debugflag );
+LEPT_DLL extern l_int32 jbGetULCorners ( JBCLASSER *classer, PIX *pixs, BOXA *boxa );
+LEPT_DLL extern l_int32 jbGetLLCorners ( JBCLASSER *classer );
+LEPT_DLL extern l_int32 readHeaderJp2k ( const char *filename, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp );
+LEPT_DLL extern l_int32 freadHeaderJp2k ( FILE *fp, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp );
+LEPT_DLL extern l_int32 readHeaderMemJp2k ( const l_uint8 *data, size_t size, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp );
+LEPT_DLL extern l_int32 fgetJp2kResolution ( FILE *fp, l_int32 *pxres, l_int32 *pyres );
+LEPT_DLL extern PIX * pixReadJp2k ( const char *filename, l_uint32 reduction, BOX *box, l_int32 hint, l_int32 debug );
+LEPT_DLL extern PIX * pixReadStreamJp2k ( FILE *fp, l_uint32 reduction, BOX *box, l_int32 hint, l_int32 debug );
+LEPT_DLL extern l_int32 pixWriteJp2k ( const char *filename, PIX *pix, l_int32 quality, l_int32 nlevels, l_int32 hint, l_int32 debug );
+LEPT_DLL extern l_int32 pixWriteStreamJp2k ( FILE *fp, PIX *pix, l_int32 quality, l_int32 nlevels, l_int32 hint, l_int32 debug );
+LEPT_DLL extern PIX * pixReadMemJp2k ( const l_uint8 *data, size_t size, l_uint32 reduction, BOX *box, l_int32 hint, l_int32 debug );
+LEPT_DLL extern l_int32 pixWriteMemJp2k ( l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 quality, l_int32 nlevels, l_int32 hint, l_int32 debug );
+LEPT_DLL extern PIX * pixReadJpeg ( const char *filename, l_int32 cmapflag, l_int32 reduction, l_int32 *pnwarn, l_int32 hint );
+LEPT_DLL extern PIX * pixReadStreamJpeg ( FILE *fp, l_int32 cmapflag, l_int32 reduction, l_int32 *pnwarn, l_int32 hint );
+LEPT_DLL extern l_int32 readHeaderJpeg ( const char *filename, l_int32 *pw, l_int32 *ph, l_int32 *pspp, l_int32 *pycck, l_int32 *pcmyk );
+LEPT_DLL extern l_int32 freadHeaderJpeg ( FILE *fp, l_int32 *pw, l_int32 *ph, l_int32 *pspp, l_int32 *pycck, l_int32 *pcmyk );
+LEPT_DLL extern l_int32 fgetJpegResolution ( FILE *fp, l_int32 *pxres, l_int32 *pyres );
+LEPT_DLL extern l_int32 fgetJpegComment ( FILE *fp, l_uint8 **pcomment );
+LEPT_DLL extern l_int32 pixWriteJpeg ( const char *filename, PIX *pix, l_int32 quality, l_int32 progressive );
+LEPT_DLL extern l_int32 pixWriteStreamJpeg ( FILE *fp, PIX *pixs, l_int32 quality, l_int32 progressive );
+LEPT_DLL extern PIX * pixReadMemJpeg ( const l_uint8 *data, size_t size, l_int32 cmflag, l_int32 reduction, l_int32 *pnwarn, l_int32 hint );
+LEPT_DLL extern l_int32 readHeaderMemJpeg ( const l_uint8 *data, size_t size, l_int32 *pw, l_int32 *ph, l_int32 *pspp, l_int32 *pycck, l_int32 *pcmyk );
+LEPT_DLL extern l_int32 pixWriteMemJpeg ( l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 quality, l_int32 progressive );
+LEPT_DLL extern l_int32 pixSetChromaSampling ( PIX *pix, l_int32 sampling );
+LEPT_DLL extern L_KERNEL * kernelCreate ( l_int32 height, l_int32 width );
+LEPT_DLL extern void kernelDestroy ( L_KERNEL **pkel );
+LEPT_DLL extern L_KERNEL * kernelCopy ( L_KERNEL *kels );
+LEPT_DLL extern l_int32 kernelGetElement ( L_KERNEL *kel, l_int32 row, l_int32 col, l_float32 *pval );
+LEPT_DLL extern l_int32 kernelSetElement ( L_KERNEL *kel, l_int32 row, l_int32 col, l_float32 val );
+LEPT_DLL extern l_int32 kernelGetParameters ( L_KERNEL *kel, l_int32 *psy, l_int32 *psx, l_int32 *pcy, l_int32 *pcx );
+LEPT_DLL extern l_int32 kernelSetOrigin ( L_KERNEL *kel, l_int32 cy, l_int32 cx );
+LEPT_DLL extern l_int32 kernelGetSum ( L_KERNEL *kel, l_float32 *psum );
+LEPT_DLL extern l_int32 kernelGetMinMax ( L_KERNEL *kel, l_float32 *pmin, l_float32 *pmax );
+LEPT_DLL extern L_KERNEL * kernelNormalize ( L_KERNEL *kels, l_float32 normsum );
+LEPT_DLL extern L_KERNEL * kernelInvert ( L_KERNEL *kels );
+LEPT_DLL extern l_float32 ** create2dFloatArray ( l_int32 sy, l_int32 sx );
+LEPT_DLL extern L_KERNEL * kernelRead ( const char *fname );
+LEPT_DLL extern L_KERNEL * kernelReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 kernelWrite ( const char *fname, L_KERNEL *kel );
+LEPT_DLL extern l_int32 kernelWriteStream ( FILE *fp, L_KERNEL *kel );
+LEPT_DLL extern L_KERNEL * kernelCreateFromString ( l_int32 h, l_int32 w, l_int32 cy, l_int32 cx, const char *kdata );
+LEPT_DLL extern L_KERNEL * kernelCreateFromFile ( const char *filename );
+LEPT_DLL extern L_KERNEL * kernelCreateFromPix ( PIX *pix, l_int32 cy, l_int32 cx );
+LEPT_DLL extern PIX * kernelDisplayInPix ( L_KERNEL *kel, l_int32 size, l_int32 gthick );
+LEPT_DLL extern NUMA * parseStringForNumbers ( const char *str, const char *seps );
+LEPT_DLL extern L_KERNEL * makeFlatKernel ( l_int32 height, l_int32 width, l_int32 cy, l_int32 cx );
+LEPT_DLL extern L_KERNEL * makeGaussianKernel ( l_int32 halfheight, l_int32 halfwidth, l_float32 stdev, l_float32 max );
+LEPT_DLL extern l_int32 makeGaussianKernelSep ( l_int32 halfheight, l_int32 halfwidth, l_float32 stdev, l_float32 max, L_KERNEL **pkelx, L_KERNEL **pkely );
+LEPT_DLL extern L_KERNEL * makeDoGKernel ( l_int32 halfheight, l_int32 halfwidth, l_float32 stdev, l_float32 ratio );
+LEPT_DLL extern char * getImagelibVersions (  );
+LEPT_DLL extern void listDestroy ( DLLIST **phead );
+LEPT_DLL extern l_int32 listAddToHead ( DLLIST **phead, void *data );
+LEPT_DLL extern l_int32 listAddToTail ( DLLIST **phead, DLLIST **ptail, void *data );
+LEPT_DLL extern l_int32 listInsertBefore ( DLLIST **phead, DLLIST *elem, void *data );
+LEPT_DLL extern l_int32 listInsertAfter ( DLLIST **phead, DLLIST *elem, void *data );
+LEPT_DLL extern void * listRemoveElement ( DLLIST **phead, DLLIST *elem );
+LEPT_DLL extern void * listRemoveFromHead ( DLLIST **phead );
+LEPT_DLL extern void * listRemoveFromTail ( DLLIST **phead, DLLIST **ptail );
+LEPT_DLL extern DLLIST * listFindElement ( DLLIST *head, void *data );
+LEPT_DLL extern DLLIST * listFindTail ( DLLIST *head );
+LEPT_DLL extern l_int32 listGetCount ( DLLIST *head );
+LEPT_DLL extern l_int32 listReverse ( DLLIST **phead );
+LEPT_DLL extern l_int32 listJoin ( DLLIST **phead1, DLLIST **phead2 );
+LEPT_DLL extern L_AMAP * l_amapCreate ( l_int32 keytype );
+LEPT_DLL extern RB_TYPE * l_amapFind ( L_AMAP *m, RB_TYPE key );
+LEPT_DLL extern void l_amapInsert ( L_AMAP *m, RB_TYPE key, RB_TYPE value );
+LEPT_DLL extern void l_amapDelete ( L_AMAP *m, RB_TYPE key );
+LEPT_DLL extern void l_amapDestroy ( L_AMAP **pm );
+LEPT_DLL extern L_AMAP_NODE * l_amapGetFirst ( L_AMAP *m );
+LEPT_DLL extern L_AMAP_NODE * l_amapGetNext ( L_AMAP_NODE *n );
+LEPT_DLL extern L_AMAP_NODE * l_amapGetLast ( L_AMAP *m );
+LEPT_DLL extern L_AMAP_NODE * l_amapGetPrev ( L_AMAP_NODE *n );
+LEPT_DLL extern l_int32 l_amapSize ( L_AMAP *m );
+LEPT_DLL extern L_ASET * l_asetCreate ( l_int32 keytype );
+LEPT_DLL extern RB_TYPE * l_asetFind ( L_ASET *s, RB_TYPE key );
+LEPT_DLL extern void l_asetInsert ( L_ASET *s, RB_TYPE key );
+LEPT_DLL extern void l_asetDelete ( L_ASET *s, RB_TYPE key );
+LEPT_DLL extern void l_asetDestroy ( L_ASET **ps );
+LEPT_DLL extern L_ASET_NODE * l_asetGetFirst ( L_ASET *s );
+LEPT_DLL extern L_ASET_NODE * l_asetGetNext ( L_ASET_NODE *n );
+LEPT_DLL extern L_ASET_NODE * l_asetGetLast ( L_ASET *s );
+LEPT_DLL extern L_ASET_NODE * l_asetGetPrev ( L_ASET_NODE *n );
+LEPT_DLL extern l_int32 l_asetSize ( L_ASET *s );
+LEPT_DLL extern PIX * generateBinaryMaze ( l_int32 w, l_int32 h, l_int32 xi, l_int32 yi, l_float32 wallps, l_float32 ranis );
+LEPT_DLL extern PTA * pixSearchBinaryMaze ( PIX *pixs, l_int32 xi, l_int32 yi, l_int32 xf, l_int32 yf, PIX **ppixd );
+LEPT_DLL extern PTA * pixSearchGrayMaze ( PIX *pixs, l_int32 xi, l_int32 yi, l_int32 xf, l_int32 yf, PIX **ppixd );
+LEPT_DLL extern l_int32 pixFindLargestRectangle ( PIX *pixs, l_int32 polarity, BOX **pbox, const char *debugfile );
+LEPT_DLL extern PIX * pixDilate ( PIX *pixd, PIX *pixs, SEL *sel );
+LEPT_DLL extern PIX * pixErode ( PIX *pixd, PIX *pixs, SEL *sel );
+LEPT_DLL extern PIX * pixHMT ( PIX *pixd, PIX *pixs, SEL *sel );
+LEPT_DLL extern PIX * pixOpen ( PIX *pixd, PIX *pixs, SEL *sel );
+LEPT_DLL extern PIX * pixClose ( PIX *pixd, PIX *pixs, SEL *sel );
+LEPT_DLL extern PIX * pixCloseSafe ( PIX *pixd, PIX *pixs, SEL *sel );
+LEPT_DLL extern PIX * pixOpenGeneralized ( PIX *pixd, PIX *pixs, SEL *sel );
+LEPT_DLL extern PIX * pixCloseGeneralized ( PIX *pixd, PIX *pixs, SEL *sel );
+LEPT_DLL extern PIX * pixDilateBrick ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixErodeBrick ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixOpenBrick ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixCloseBrick ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixCloseSafeBrick ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern l_int32 selectComposableSels ( l_int32 size, l_int32 direction, SEL **psel1, SEL **psel2 );
+LEPT_DLL extern l_int32 selectComposableSizes ( l_int32 size, l_int32 *pfactor1, l_int32 *pfactor2 );
+LEPT_DLL extern PIX * pixDilateCompBrick ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixErodeCompBrick ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixOpenCompBrick ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixCloseCompBrick ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixCloseSafeCompBrick ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern void resetMorphBoundaryCondition ( l_int32 bc );
+LEPT_DLL extern l_uint32 getMorphBorderPixelColor ( l_int32 type, l_int32 depth );
+LEPT_DLL extern PIX * pixExtractBoundary ( PIX *pixs, l_int32 type );
+LEPT_DLL extern PIX * pixMorphSequenceMasked ( PIX *pixs, PIX *pixm, const char *sequence, l_int32 dispsep );
+LEPT_DLL extern PIX * pixMorphSequenceByComponent ( PIX *pixs, const char *sequence, l_int32 connectivity, l_int32 minw, l_int32 minh, BOXA **pboxa );
+LEPT_DLL extern PIXA * pixaMorphSequenceByComponent ( PIXA *pixas, const char *sequence, l_int32 minw, l_int32 minh );
+LEPT_DLL extern PIX * pixMorphSequenceByRegion ( PIX *pixs, PIX *pixm, const char *sequence, l_int32 connectivity, l_int32 minw, l_int32 minh, BOXA **pboxa );
+LEPT_DLL extern PIXA * pixaMorphSequenceByRegion ( PIX *pixs, PIXA *pixam, const char *sequence, l_int32 minw, l_int32 minh );
+LEPT_DLL extern PIX * pixUnionOfMorphOps ( PIX *pixs, SELA *sela, l_int32 type );
+LEPT_DLL extern PIX * pixIntersectionOfMorphOps ( PIX *pixs, SELA *sela, l_int32 type );
+LEPT_DLL extern PIX * pixSelectiveConnCompFill ( PIX *pixs, l_int32 connectivity, l_int32 minw, l_int32 minh );
+LEPT_DLL extern l_int32 pixRemoveMatchedPattern ( PIX *pixs, PIX *pixp, PIX *pixe, l_int32 x0, l_int32 y0, l_int32 dsize );
+LEPT_DLL extern PIX * pixDisplayMatchedPattern ( PIX *pixs, PIX *pixp, PIX *pixe, l_int32 x0, l_int32 y0, l_uint32 color, l_float32 scale, l_int32 nlevels );
+LEPT_DLL extern PIXA * pixaExtendIterative ( PIXA *pixas, l_int32 type, l_int32 niters, SEL *sel, l_int32 include );
+LEPT_DLL extern PIX * pixSeedfillMorph ( PIX *pixs, PIX *pixm, l_int32 maxiters, l_int32 connectivity );
+LEPT_DLL extern NUMA * pixRunHistogramMorph ( PIX *pixs, l_int32 runtype, l_int32 direction, l_int32 maxsize );
+LEPT_DLL extern PIX * pixTophat ( PIX *pixs, l_int32 hsize, l_int32 vsize, l_int32 type );
+LEPT_DLL extern PIX * pixHDome ( PIX *pixs, l_int32 height, l_int32 connectivity );
+LEPT_DLL extern PIX * pixFastTophat ( PIX *pixs, l_int32 xsize, l_int32 ysize, l_int32 type );
+LEPT_DLL extern PIX * pixMorphGradient ( PIX *pixs, l_int32 hsize, l_int32 vsize, l_int32 smoothing );
+LEPT_DLL extern PTA * pixaCentroids ( PIXA *pixa );
+LEPT_DLL extern l_int32 pixCentroid ( PIX *pix, l_int32 *centtab, l_int32 *sumtab, l_float32 *pxave, l_float32 *pyave );
+LEPT_DLL extern PIX * pixDilateBrickDwa ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixErodeBrickDwa ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixOpenBrickDwa ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixCloseBrickDwa ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixDilateCompBrickDwa ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixErodeCompBrickDwa ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixOpenCompBrickDwa ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixCloseCompBrickDwa ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixDilateCompBrickExtendDwa ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixErodeCompBrickExtendDwa ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixOpenCompBrickExtendDwa ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern PIX * pixCloseCompBrickExtendDwa ( PIX *pixd, PIX *pixs, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern l_int32 getExtendedCompositeParameters ( l_int32 size, l_int32 *pn, l_int32 *pextra, l_int32 *pactualsize );
+LEPT_DLL extern PIX * pixMorphSequence ( PIX *pixs, const char *sequence, l_int32 dispsep );
+LEPT_DLL extern PIX * pixMorphCompSequence ( PIX *pixs, const char *sequence, l_int32 dispsep );
+LEPT_DLL extern PIX * pixMorphSequenceDwa ( PIX *pixs, const char *sequence, l_int32 dispsep );
+LEPT_DLL extern PIX * pixMorphCompSequenceDwa ( PIX *pixs, const char *sequence, l_int32 dispsep );
+LEPT_DLL extern l_int32 morphSequenceVerify ( SARRAY *sa );
+LEPT_DLL extern PIX * pixGrayMorphSequence ( PIX *pixs, const char *sequence, l_int32 dispsep, l_int32 dispy );
+LEPT_DLL extern PIX * pixColorMorphSequence ( PIX *pixs, const char *sequence, l_int32 dispsep, l_int32 dispy );
+LEPT_DLL extern NUMA * numaCreate ( l_int32 n );
+LEPT_DLL extern NUMA * numaCreateFromIArray ( l_int32 *iarray, l_int32 size );
+LEPT_DLL extern NUMA * numaCreateFromFArray ( l_float32 *farray, l_int32 size, l_int32 copyflag );
+LEPT_DLL extern NUMA * numaCreateFromString ( const char *str );
+LEPT_DLL extern void numaDestroy ( NUMA **pna );
+LEPT_DLL extern NUMA * numaCopy ( NUMA *na );
+LEPT_DLL extern NUMA * numaClone ( NUMA *na );
+LEPT_DLL extern l_int32 numaEmpty ( NUMA *na );
+LEPT_DLL extern l_int32 numaAddNumber ( NUMA *na, l_float32 val );
+LEPT_DLL extern l_int32 numaInsertNumber ( NUMA *na, l_int32 index, l_float32 val );
+LEPT_DLL extern l_int32 numaRemoveNumber ( NUMA *na, l_int32 index );
+LEPT_DLL extern l_int32 numaReplaceNumber ( NUMA *na, l_int32 index, l_float32 val );
+LEPT_DLL extern l_int32 numaGetCount ( NUMA *na );
+LEPT_DLL extern l_int32 numaSetCount ( NUMA *na, l_int32 newcount );
+LEPT_DLL extern l_int32 numaGetFValue ( NUMA *na, l_int32 index, l_float32 *pval );
+LEPT_DLL extern l_int32 numaGetIValue ( NUMA *na, l_int32 index, l_int32 *pival );
+LEPT_DLL extern l_int32 numaSetValue ( NUMA *na, l_int32 index, l_float32 val );
+LEPT_DLL extern l_int32 numaShiftValue ( NUMA *na, l_int32 index, l_float32 diff );
+LEPT_DLL extern l_int32 * numaGetIArray ( NUMA *na );
+LEPT_DLL extern l_float32 * numaGetFArray ( NUMA *na, l_int32 copyflag );
+LEPT_DLL extern l_int32 numaGetRefcount ( NUMA *na );
+LEPT_DLL extern l_int32 numaChangeRefcount ( NUMA *na, l_int32 delta );
+LEPT_DLL extern l_int32 numaGetParameters ( NUMA *na, l_float32 *pstartx, l_float32 *pdelx );
+LEPT_DLL extern l_int32 numaSetParameters ( NUMA *na, l_float32 startx, l_float32 delx );
+LEPT_DLL extern l_int32 numaCopyParameters ( NUMA *nad, NUMA *nas );
+LEPT_DLL extern SARRAY * numaConvertToSarray ( NUMA *na, l_int32 size1, l_int32 size2, l_int32 addzeros, l_int32 type );
+LEPT_DLL extern NUMA * numaRead ( const char *filename );
+LEPT_DLL extern NUMA * numaReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 numaWrite ( const char *filename, NUMA *na );
+LEPT_DLL extern l_int32 numaWriteStream ( FILE *fp, NUMA *na );
+LEPT_DLL extern NUMAA * numaaCreate ( l_int32 n );
+LEPT_DLL extern NUMAA * numaaCreateFull ( l_int32 nptr, l_int32 n );
+LEPT_DLL extern l_int32 numaaTruncate ( NUMAA *naa );
+LEPT_DLL extern void numaaDestroy ( NUMAA **pnaa );
+LEPT_DLL extern l_int32 numaaAddNuma ( NUMAA *naa, NUMA *na, l_int32 copyflag );
+LEPT_DLL extern l_int32 numaaGetCount ( NUMAA *naa );
+LEPT_DLL extern l_int32 numaaGetNumaCount ( NUMAA *naa, l_int32 index );
+LEPT_DLL extern l_int32 numaaGetNumberCount ( NUMAA *naa );
+LEPT_DLL extern NUMA ** numaaGetPtrArray ( NUMAA *naa );
+LEPT_DLL extern NUMA * numaaGetNuma ( NUMAA *naa, l_int32 index, l_int32 accessflag );
+LEPT_DLL extern l_int32 numaaReplaceNuma ( NUMAA *naa, l_int32 index, NUMA *na );
+LEPT_DLL extern l_int32 numaaGetValue ( NUMAA *naa, l_int32 i, l_int32 j, l_float32 *pfval, l_int32 *pival );
+LEPT_DLL extern l_int32 numaaAddNumber ( NUMAA *naa, l_int32 index, l_float32 val );
+LEPT_DLL extern NUMAA * numaaRead ( const char *filename );
+LEPT_DLL extern NUMAA * numaaReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 numaaWrite ( const char *filename, NUMAA *naa );
+LEPT_DLL extern l_int32 numaaWriteStream ( FILE *fp, NUMAA *naa );
+LEPT_DLL extern NUMA * numaArithOp ( NUMA *nad, NUMA *na1, NUMA *na2, l_int32 op );
+LEPT_DLL extern NUMA * numaLogicalOp ( NUMA *nad, NUMA *na1, NUMA *na2, l_int32 op );
+LEPT_DLL extern NUMA * numaInvert ( NUMA *nad, NUMA *nas );
+LEPT_DLL extern l_int32 numaSimilar ( NUMA *na1, NUMA *na2, l_float32 maxdiff, l_int32 *psimilar );
+LEPT_DLL extern l_int32 numaAddToNumber ( NUMA *na, l_int32 index, l_float32 val );
+LEPT_DLL extern l_int32 numaGetMin ( NUMA *na, l_float32 *pminval, l_int32 *piminloc );
+LEPT_DLL extern l_int32 numaGetMax ( NUMA *na, l_float32 *pmaxval, l_int32 *pimaxloc );
+LEPT_DLL extern l_int32 numaGetSum ( NUMA *na, l_float32 *psum );
+LEPT_DLL extern NUMA * numaGetPartialSums ( NUMA *na );
+LEPT_DLL extern l_int32 numaGetSumOnInterval ( NUMA *na, l_int32 first, l_int32 last, l_float32 *psum );
+LEPT_DLL extern l_int32 numaHasOnlyIntegers ( NUMA *na, l_int32 maxsamples, l_int32 *pallints );
+LEPT_DLL extern NUMA * numaSubsample ( NUMA *nas, l_int32 subfactor );
+LEPT_DLL extern NUMA * numaMakeDelta ( NUMA *nas );
+LEPT_DLL extern NUMA * numaMakeSequence ( l_float32 startval, l_float32 increment, l_int32 size );
+LEPT_DLL extern NUMA * numaMakeConstant ( l_float32 val, l_int32 size );
+LEPT_DLL extern NUMA * numaMakeAbsValue ( NUMA *nad, NUMA *nas );
+LEPT_DLL extern NUMA * numaAddBorder ( NUMA *nas, l_int32 left, l_int32 right, l_float32 val );
+LEPT_DLL extern NUMA * numaAddSpecifiedBorder ( NUMA *nas, l_int32 left, l_int32 right, l_int32 type );
+LEPT_DLL extern NUMA * numaRemoveBorder ( NUMA *nas, l_int32 left, l_int32 right );
+LEPT_DLL extern l_int32 numaGetNonzeroRange ( NUMA *na, l_float32 eps, l_int32 *pfirst, l_int32 *plast );
+LEPT_DLL extern l_int32 numaGetCountRelativeToZero ( NUMA *na, l_int32 type, l_int32 *pcount );
+LEPT_DLL extern NUMA * numaClipToInterval ( NUMA *nas, l_int32 first, l_int32 last );
+LEPT_DLL extern NUMA * numaMakeThresholdIndicator ( NUMA *nas, l_float32 thresh, l_int32 type );
+LEPT_DLL extern NUMA * numaUniformSampling ( NUMA *nas, l_int32 nsamp );
+LEPT_DLL extern NUMA * numaReverse ( NUMA *nad, NUMA *nas );
+LEPT_DLL extern NUMA * numaLowPassIntervals ( NUMA *nas, l_float32 thresh, l_float32 maxn );
+LEPT_DLL extern NUMA * numaThresholdEdges ( NUMA *nas, l_float32 thresh1, l_float32 thresh2, l_float32 maxn );
+LEPT_DLL extern l_int32 numaGetSpanValues ( NUMA *na, l_int32 span, l_int32 *pstart, l_int32 *pend );
+LEPT_DLL extern l_int32 numaGetEdgeValues ( NUMA *na, l_int32 edge, l_int32 *pstart, l_int32 *pend, l_int32 *psign );
+LEPT_DLL extern l_int32 numaInterpolateEqxVal ( l_float32 startx, l_float32 deltax, NUMA *nay, l_int32 type, l_float32 xval, l_float32 *pyval );
+LEPT_DLL extern l_int32 numaInterpolateArbxVal ( NUMA *nax, NUMA *nay, l_int32 type, l_float32 xval, l_float32 *pyval );
+LEPT_DLL extern l_int32 numaInterpolateEqxInterval ( l_float32 startx, l_float32 deltax, NUMA *nasy, l_int32 type, l_float32 x0, l_float32 x1, l_int32 npts, NUMA **pnax, NUMA **pnay );
+LEPT_DLL extern l_int32 numaInterpolateArbxInterval ( NUMA *nax, NUMA *nay, l_int32 type, l_float32 x0, l_float32 x1, l_int32 npts, NUMA **pnadx, NUMA **pnady );
+LEPT_DLL extern l_int32 numaFitMax ( NUMA *na, l_float32 *pmaxval, NUMA *naloc, l_float32 *pmaxloc );
+LEPT_DLL extern l_int32 numaDifferentiateInterval ( NUMA *nax, NUMA *nay, l_float32 x0, l_float32 x1, l_int32 npts, NUMA **pnadx, NUMA **pnady );
+LEPT_DLL extern l_int32 numaIntegrateInterval ( NUMA *nax, NUMA *nay, l_float32 x0, l_float32 x1, l_int32 npts, l_float32 *psum );
+LEPT_DLL extern l_int32 numaSortGeneral ( NUMA *na, NUMA **pnasort, NUMA **pnaindex, NUMA **pnainvert, l_int32 sortorder, l_int32 sorttype );
+LEPT_DLL extern NUMA * numaSortAutoSelect ( NUMA *nas, l_int32 sortorder );
+LEPT_DLL extern NUMA * numaSortIndexAutoSelect ( NUMA *nas, l_int32 sortorder );
+LEPT_DLL extern l_int32 numaChooseSortType ( NUMA *nas );
+LEPT_DLL extern NUMA * numaSort ( NUMA *naout, NUMA *nain, l_int32 sortorder );
+LEPT_DLL extern NUMA * numaBinSort ( NUMA *nas, l_int32 sortorder );
+LEPT_DLL extern NUMA * numaGetSortIndex ( NUMA *na, l_int32 sortorder );
+LEPT_DLL extern NUMA * numaGetBinSortIndex ( NUMA *nas, l_int32 sortorder );
+LEPT_DLL extern NUMA * numaSortByIndex ( NUMA *nas, NUMA *naindex );
+LEPT_DLL extern l_int32 numaIsSorted ( NUMA *nas, l_int32 sortorder, l_int32 *psorted );
+LEPT_DLL extern l_int32 numaSortPair ( NUMA *nax, NUMA *nay, l_int32 sortorder, NUMA **pnasx, NUMA **pnasy );
+LEPT_DLL extern NUMA * numaInvertMap ( NUMA *nas );
+LEPT_DLL extern NUMA * numaPseudorandomSequence ( l_int32 size, l_int32 seed );
+LEPT_DLL extern NUMA * numaRandomPermutation ( NUMA *nas, l_int32 seed );
+LEPT_DLL extern l_int32 numaGetRankValue ( NUMA *na, l_float32 fract, NUMA *nasort, l_int32 usebins, l_float32 *pval );
+LEPT_DLL extern l_int32 numaGetMedian ( NUMA *na, l_float32 *pval );
+LEPT_DLL extern l_int32 numaGetBinnedMedian ( NUMA *na, l_int32 *pval );
+LEPT_DLL extern l_int32 numaGetMode ( NUMA *na, l_float32 *pval, l_int32 *pcount );
+LEPT_DLL extern l_int32 numaGetMedianVariation ( NUMA *na, l_float32 *pmedval, l_float32 *pmedvar );
+LEPT_DLL extern l_int32 numaJoin ( NUMA *nad, NUMA *nas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern l_int32 numaaJoin ( NUMAA *naad, NUMAA *naas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern NUMA * numaaFlattenToNuma ( NUMAA *naa );
+LEPT_DLL extern NUMA * numaUnionByAset ( NUMA *na1, NUMA *na2 );
+LEPT_DLL extern NUMA * numaRemoveDupsByAset ( NUMA *nas );
+LEPT_DLL extern NUMA * numaIntersectionByAset ( NUMA *na1, NUMA *na2 );
+LEPT_DLL extern L_ASET * l_asetCreateFromNuma ( NUMA *na );
+LEPT_DLL extern NUMA * numaErode ( NUMA *nas, l_int32 size );
+LEPT_DLL extern NUMA * numaDilate ( NUMA *nas, l_int32 size );
+LEPT_DLL extern NUMA * numaOpen ( NUMA *nas, l_int32 size );
+LEPT_DLL extern NUMA * numaClose ( NUMA *nas, l_int32 size );
+LEPT_DLL extern NUMA * numaTransform ( NUMA *nas, l_float32 shift, l_float32 scale );
+LEPT_DLL extern l_int32 numaSimpleStats ( NUMA *na, l_int32 first, l_int32 last, l_float32 *pmean, l_float32 *pvar, l_float32 *prvar );
+LEPT_DLL extern l_int32 numaWindowedStats ( NUMA *nas, l_int32 wc, NUMA **pnam, NUMA **pnams, NUMA **pnav, NUMA **pnarv );
+LEPT_DLL extern NUMA * numaWindowedMean ( NUMA *nas, l_int32 wc );
+LEPT_DLL extern NUMA * numaWindowedMeanSquare ( NUMA *nas, l_int32 wc );
+LEPT_DLL extern l_int32 numaWindowedVariance ( NUMA *nam, NUMA *nams, NUMA **pnav, NUMA **pnarv );
+LEPT_DLL extern NUMA * numaWindowedMedian ( NUMA *nas, l_int32 halfwin );
+LEPT_DLL extern NUMA * numaConvertToInt ( NUMA *nas );
+LEPT_DLL extern NUMA * numaMakeHistogram ( NUMA *na, l_int32 maxbins, l_int32 *pbinsize, l_int32 *pbinstart );
+LEPT_DLL extern NUMA * numaMakeHistogramAuto ( NUMA *na, l_int32 maxbins );
+LEPT_DLL extern NUMA * numaMakeHistogramClipped ( NUMA *na, l_float32 binsize, l_float32 maxsize );
+LEPT_DLL extern NUMA * numaRebinHistogram ( NUMA *nas, l_int32 newsize );
+LEPT_DLL extern NUMA * numaNormalizeHistogram ( NUMA *nas, l_float32 tsum );
+LEPT_DLL extern l_int32 numaGetStatsUsingHistogram ( NUMA *na, l_int32 maxbins, l_float32 *pmin, l_float32 *pmax, l_float32 *pmean, l_float32 *pvariance, l_float32 *pmedian, l_float32 rank, l_float32 *prval, NUMA **phisto );
+LEPT_DLL extern l_int32 numaGetHistogramStats ( NUMA *nahisto, l_float32 startx, l_float32 deltax, l_float32 *pxmean, l_float32 *pxmedian, l_float32 *pxmode, l_float32 *pxvariance );
+LEPT_DLL extern l_int32 numaGetHistogramStatsOnInterval ( NUMA *nahisto, l_float32 startx, l_float32 deltax, l_int32 ifirst, l_int32 ilast, l_float32 *pxmean, l_float32 *pxmedian, l_float32 *pxmode, l_float32 *pxvariance );
+LEPT_DLL extern l_int32 numaMakeRankFromHistogram ( l_float32 startx, l_float32 deltax, NUMA *nasy, l_int32 npts, NUMA **pnax, NUMA **pnay );
+LEPT_DLL extern l_int32 numaHistogramGetRankFromVal ( NUMA *na, l_float32 rval, l_float32 *prank );
+LEPT_DLL extern l_int32 numaHistogramGetValFromRank ( NUMA *na, l_float32 rank, l_float32 *prval );
+LEPT_DLL extern l_int32 numaDiscretizeRankAndIntensity ( NUMA *na, l_int32 nbins, NUMA **pnarbin, NUMA **pnam, NUMA **pnar, NUMA **pnabb );
+LEPT_DLL extern l_int32 numaGetRankBinValues ( NUMA *na, l_int32 nbins, NUMA **pnarbin, NUMA **pnam );
+LEPT_DLL extern l_int32 numaSplitDistribution ( NUMA *na, l_float32 scorefract, l_int32 *psplitindex, l_float32 *pave1, l_float32 *pave2, l_float32 *pnum1, l_float32 *pnum2, NUMA **pnascore );
+LEPT_DLL extern l_int32 grayHistogramsToEMD ( NUMAA *naa1, NUMAA *naa2, NUMA **pnad );
+LEPT_DLL extern l_int32 numaEarthMoverDistance ( NUMA *na1, NUMA *na2, l_float32 *pdist );
+LEPT_DLL extern l_int32 grayInterHistogramStats ( NUMAA *naa, l_int32 wc, NUMA **pnam, NUMA **pnams, NUMA **pnav, NUMA **pnarv );
+LEPT_DLL extern NUMA * numaFindPeaks ( NUMA *nas, l_int32 nmax, l_float32 fract1, l_float32 fract2 );
+LEPT_DLL extern NUMA * numaFindExtrema ( NUMA *nas, l_float32 delta );
+LEPT_DLL extern l_int32 numaCountReversals ( NUMA *nas, l_float32 minreversal, l_int32 *pnr, l_float32 *pnrpl );
+LEPT_DLL extern l_int32 numaSelectCrossingThreshold ( NUMA *nax, NUMA *nay, l_float32 estthresh, l_float32 *pbestthresh );
+LEPT_DLL extern NUMA * numaCrossingsByThreshold ( NUMA *nax, NUMA *nay, l_float32 thresh );
+LEPT_DLL extern NUMA * numaCrossingsByPeaks ( NUMA *nax, NUMA *nay, l_float32 delta );
+LEPT_DLL extern l_int32 numaEvalBestHaarParameters ( NUMA *nas, l_float32 relweight, l_int32 nwidth, l_int32 nshift, l_float32 minwidth, l_float32 maxwidth, l_float32 *pbestwidth, l_float32 *pbestshift, l_float32 *pbestscore );
+LEPT_DLL extern l_int32 numaEvalHaarSum ( NUMA *nas, l_float32 width, l_float32 shift, l_float32 relweight, l_float32 *pscore );
+LEPT_DLL extern NUMA * genConstrainedNumaInRange ( l_int32 first, l_int32 last, l_int32 nmax, l_int32 use_pairs );
+LEPT_DLL extern l_int32 pixGetRegionsBinary ( PIX *pixs, PIX **ppixhm, PIX **ppixtm, PIX **ppixtb, l_int32 debug );
+LEPT_DLL extern PIX * pixGenHalftoneMask ( PIX *pixs, PIX **ppixtext, l_int32 *phtfound, l_int32 debug );
+LEPT_DLL extern PIX * pixGenTextlineMask ( PIX *pixs, PIX **ppixvws, l_int32 *ptlfound, l_int32 debug );
+LEPT_DLL extern PIX * pixGenTextblockMask ( PIX *pixs, PIX *pixvws, l_int32 debug );
+LEPT_DLL extern BOX * pixFindPageForeground ( PIX *pixs, l_int32 threshold, l_int32 mindist, l_int32 erasedist, l_int32 pagenum, l_int32 showmorph, l_int32 display, const char *pdfdir );
+LEPT_DLL extern l_int32 pixSplitIntoCharacters ( PIX *pixs, l_int32 minw, l_int32 minh, BOXA **pboxa, PIXA **ppixa, PIX **ppixdebug );
+LEPT_DLL extern BOXA * pixSplitComponentWithProfile ( PIX *pixs, l_int32 delta, l_int32 mindel, PIX **ppixdebug );
+LEPT_DLL extern PIXA * pixExtractTextlines ( PIX *pixs, l_int32 maxw, l_int32 maxh, l_int32 minw, l_int32 minh );
+LEPT_DLL extern l_int32 pixDecideIfText ( PIX *pixs, BOX *box, l_int32 *pistext, PIXA *pixadb );
+LEPT_DLL extern l_int32 pixFindThreshFgExtent ( PIX *pixs, l_int32 thresh, l_int32 *ptop, l_int32 *pbot );
+LEPT_DLL extern l_int32 pixSetSelectCmap ( PIX *pixs, BOX *box, l_int32 sindex, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_int32 pixColorGrayRegionsCmap ( PIX *pixs, BOXA *boxa, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_int32 pixColorGrayCmap ( PIX *pixs, BOX *box, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_int32 pixColorGrayMaskedCmap ( PIX *pixs, PIX *pixm, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_int32 addColorizedGrayToCmap ( PIXCMAP *cmap, l_int32 type, l_int32 rval, l_int32 gval, l_int32 bval, NUMA **pna );
+LEPT_DLL extern l_int32 pixSetSelectMaskedCmap ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 sindex, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_int32 pixSetMaskedCmap ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern char * parseForProtos ( const char *filein, const char *prestring );
+LEPT_DLL extern BOXA * boxaGetWhiteblocks ( BOXA *boxas, BOX *box, l_int32 sortflag, l_int32 maxboxes, l_float32 maxoverlap, l_int32 maxperim, l_float32 fract, l_int32 maxpops );
+LEPT_DLL extern BOXA * boxaPruneSortedOnOverlap ( BOXA *boxas, l_float32 maxoverlap );
+LEPT_DLL extern l_int32 convertFilesToPdf ( const char *dirname, const char *substr, l_int32 res, l_float32 scalefactor, l_int32 type, l_int32 quality, const char *title, const char *fileout );
+LEPT_DLL extern l_int32 saConvertFilesToPdf ( SARRAY *sa, l_int32 res, l_float32 scalefactor, l_int32 type, l_int32 quality, const char *title, const char *fileout );
+LEPT_DLL extern l_int32 saConvertFilesToPdfData ( SARRAY *sa, l_int32 res, l_float32 scalefactor, l_int32 type, l_int32 quality, const char *title, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_int32 selectDefaultPdfEncoding ( PIX *pix, l_int32 *ptype );
+LEPT_DLL extern l_int32 convertUnscaledFilesToPdf ( const char *dirname, const char *substr, const char *title, const char *fileout );
+LEPT_DLL extern l_int32 saConvertUnscaledFilesToPdf ( SARRAY *sa, const char *title, const char *fileout );
+LEPT_DLL extern l_int32 saConvertUnscaledFilesToPdfData ( SARRAY *sa, const char *title, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_int32 convertUnscaledToPdfData ( const char *fname, const char *title, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_int32 pixaConvertToPdf ( PIXA *pixa, l_int32 res, l_float32 scalefactor, l_int32 type, l_int32 quality, const char *title, const char *fileout );
+LEPT_DLL extern l_int32 pixaConvertToPdfData ( PIXA *pixa, l_int32 res, l_float32 scalefactor, l_int32 type, l_int32 quality, const char *title, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_int32 convertToPdf ( const char *filein, l_int32 type, l_int32 quality, const char *fileout, l_int32 x, l_int32 y, l_int32 res, const char *title, L_PDF_DATA **plpd, l_int32 position );
+LEPT_DLL extern l_int32 convertImageDataToPdf ( l_uint8 *imdata, size_t size, l_int32 type, l_int32 quality, const char *fileout, l_int32 x, l_int32 y, l_int32 res, const char *title, L_PDF_DATA **plpd, l_int32 position );
+LEPT_DLL extern l_int32 convertToPdfData ( const char *filein, l_int32 type, l_int32 quality, l_uint8 **pdata, size_t *pnbytes, l_int32 x, l_int32 y, l_int32 res, const char *title, L_PDF_DATA **plpd, l_int32 position );
+LEPT_DLL extern l_int32 convertImageDataToPdfData ( l_uint8 *imdata, size_t size, l_int32 type, l_int32 quality, l_uint8 **pdata, size_t *pnbytes, l_int32 x, l_int32 y, l_int32 res, const char *title, L_PDF_DATA **plpd, l_int32 position );
+LEPT_DLL extern l_int32 pixConvertToPdf ( PIX *pix, l_int32 type, l_int32 quality, const char *fileout, l_int32 x, l_int32 y, l_int32 res, const char *title, L_PDF_DATA **plpd, l_int32 position );
+LEPT_DLL extern l_int32 pixWriteStreamPdf ( FILE *fp, PIX *pix, l_int32 res, const char *title );
+LEPT_DLL extern l_int32 pixWriteMemPdf ( l_uint8 **pdata, size_t *pnbytes, PIX *pix, l_int32 res, const char *title );
+LEPT_DLL extern l_int32 convertSegmentedFilesToPdf ( const char *dirname, const char *substr, l_int32 res, l_int32 type, l_int32 thresh, BOXAA *baa, l_int32 quality, l_float32 scalefactor, const char *title, const char *fileout );
+LEPT_DLL extern BOXAA * convertNumberedMasksToBoxaa ( const char *dirname, const char *substr, l_int32 numpre, l_int32 numpost );
+LEPT_DLL extern l_int32 convertToPdfSegmented ( const char *filein, l_int32 res, l_int32 type, l_int32 thresh, BOXA *boxa, l_int32 quality, l_float32 scalefactor, const char *title, const char *fileout );
+LEPT_DLL extern l_int32 pixConvertToPdfSegmented ( PIX *pixs, l_int32 res, l_int32 type, l_int32 thresh, BOXA *boxa, l_int32 quality, l_float32 scalefactor, const char *title, const char *fileout );
+LEPT_DLL extern l_int32 convertToPdfDataSegmented ( const char *filein, l_int32 res, l_int32 type, l_int32 thresh, BOXA *boxa, l_int32 quality, l_float32 scalefactor, const char *title, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_int32 pixConvertToPdfDataSegmented ( PIX *pixs, l_int32 res, l_int32 type, l_int32 thresh, BOXA *boxa, l_int32 quality, l_float32 scalefactor, const char *title, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_int32 concatenatePdf ( const char *dirname, const char *substr, const char *fileout );
+LEPT_DLL extern l_int32 saConcatenatePdf ( SARRAY *sa, const char *fileout );
+LEPT_DLL extern l_int32 ptraConcatenatePdf ( L_PTRA *pa, const char *fileout );
+LEPT_DLL extern l_int32 concatenatePdfToData ( const char *dirname, const char *substr, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_int32 saConcatenatePdfToData ( SARRAY *sa, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_int32 pixConvertToPdfData ( PIX *pix, l_int32 type, l_int32 quality, l_uint8 **pdata, size_t *pnbytes, l_int32 x, l_int32 y, l_int32 res, const char *title, L_PDF_DATA **plpd, l_int32 position );
+LEPT_DLL extern l_int32 ptraConcatenatePdfToData ( L_PTRA *pa_data, SARRAY *sa, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_int32 l_generateCIDataForPdf ( const char *fname, PIX *pix, l_int32 quality, L_COMP_DATA **pcid );
+LEPT_DLL extern L_COMP_DATA * l_generateFlateDataPdf ( const char *fname, PIX *pixs );
+LEPT_DLL extern L_COMP_DATA * l_generateJpegData ( const char *fname, l_int32 ascii85flag );
+LEPT_DLL extern l_int32 l_generateCIData ( const char *fname, l_int32 type, l_int32 quality, l_int32 ascii85, L_COMP_DATA **pcid );
+LEPT_DLL extern l_int32 pixGenerateCIData ( PIX *pixs, l_int32 type, l_int32 quality, l_int32 ascii85, L_COMP_DATA **pcid );
+LEPT_DLL extern L_COMP_DATA * l_generateFlateData ( const char *fname, l_int32 ascii85flag );
+LEPT_DLL extern L_COMP_DATA * l_generateG4Data ( const char *fname, l_int32 ascii85flag );
+LEPT_DLL extern l_int32 cidConvertToPdfData ( L_COMP_DATA *cid, const char *title, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern void l_CIDataDestroy ( L_COMP_DATA **pcid );
+LEPT_DLL extern void l_pdfSetG4ImageMask ( l_int32 flag );
+LEPT_DLL extern void l_pdfSetDateAndVersion ( l_int32 flag );
+LEPT_DLL extern void setPixMemoryManager ( void * (  ( *allocator ) ( size_t ) ), void  (  ( *deallocator ) ( void * ) ) );
+LEPT_DLL extern PIX * pixCreate ( l_int32 width, l_int32 height, l_int32 depth );
+LEPT_DLL extern PIX * pixCreateNoInit ( l_int32 width, l_int32 height, l_int32 depth );
+LEPT_DLL extern PIX * pixCreateTemplate ( PIX *pixs );
+LEPT_DLL extern PIX * pixCreateTemplateNoInit ( PIX *pixs );
+LEPT_DLL extern PIX * pixCreateHeader ( l_int32 width, l_int32 height, l_int32 depth );
+LEPT_DLL extern PIX * pixClone ( PIX *pixs );
+LEPT_DLL extern void pixDestroy ( PIX **ppix );
+LEPT_DLL extern PIX * pixCopy ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern l_int32 pixResizeImageData ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern l_int32 pixCopyColormap ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern l_int32 pixSizesEqual ( PIX *pix1, PIX *pix2 );
+LEPT_DLL extern l_int32 pixTransferAllData ( PIX *pixd, PIX **ppixs, l_int32 copytext, l_int32 copyformat );
+LEPT_DLL extern l_int32 pixSwapAndDestroy ( PIX **ppixd, PIX **ppixs );
+LEPT_DLL extern l_int32 pixGetWidth ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetWidth ( PIX *pix, l_int32 width );
+LEPT_DLL extern l_int32 pixGetHeight ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetHeight ( PIX *pix, l_int32 height );
+LEPT_DLL extern l_int32 pixGetDepth ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetDepth ( PIX *pix, l_int32 depth );
+LEPT_DLL extern l_int32 pixGetDimensions ( PIX *pix, l_int32 *pw, l_int32 *ph, l_int32 *pd );
+LEPT_DLL extern l_int32 pixSetDimensions ( PIX *pix, l_int32 w, l_int32 h, l_int32 d );
+LEPT_DLL extern l_int32 pixCopyDimensions ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern l_int32 pixGetSpp ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetSpp ( PIX *pix, l_int32 spp );
+LEPT_DLL extern l_int32 pixCopySpp ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern l_int32 pixGetWpl ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetWpl ( PIX *pix, l_int32 wpl );
+LEPT_DLL extern l_int32 pixGetRefcount ( PIX *pix );
+LEPT_DLL extern l_int32 pixChangeRefcount ( PIX *pix, l_int32 delta );
+LEPT_DLL extern l_int32 pixGetXRes ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetXRes ( PIX *pix, l_int32 res );
+LEPT_DLL extern l_int32 pixGetYRes ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetYRes ( PIX *pix, l_int32 res );
+LEPT_DLL extern l_int32 pixGetResolution ( PIX *pix, l_int32 *pxres, l_int32 *pyres );
+LEPT_DLL extern l_int32 pixSetResolution ( PIX *pix, l_int32 xres, l_int32 yres );
+LEPT_DLL extern l_int32 pixCopyResolution ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern l_int32 pixScaleResolution ( PIX *pix, l_float32 xscale, l_float32 yscale );
+LEPT_DLL extern l_int32 pixGetInputFormat ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetInputFormat ( PIX *pix, l_int32 informat );
+LEPT_DLL extern l_int32 pixCopyInputFormat ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern l_int32 pixSetSpecial ( PIX *pix, l_int32 special );
+LEPT_DLL extern char * pixGetText ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetText ( PIX *pix, const char *textstring );
+LEPT_DLL extern l_int32 pixAddText ( PIX *pix, const char *textstring );
+LEPT_DLL extern l_int32 pixCopyText ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern PIXCMAP * pixGetColormap ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetColormap ( PIX *pix, PIXCMAP *colormap );
+LEPT_DLL extern l_int32 pixDestroyColormap ( PIX *pix );
+LEPT_DLL extern l_uint32 * pixGetData ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetData ( PIX *pix, l_uint32 *data );
+LEPT_DLL extern l_uint32 * pixExtractData ( PIX *pixs );
+LEPT_DLL extern l_int32 pixFreeData ( PIX *pix );
+LEPT_DLL extern void ** pixGetLinePtrs ( PIX *pix, l_int32 *psize );
+LEPT_DLL extern l_int32 pixPrintStreamInfo ( FILE *fp, PIX *pix, const char *text );
+LEPT_DLL extern l_int32 pixGetPixel ( PIX *pix, l_int32 x, l_int32 y, l_uint32 *pval );
+LEPT_DLL extern l_int32 pixSetPixel ( PIX *pix, l_int32 x, l_int32 y, l_uint32 val );
+LEPT_DLL extern l_int32 pixGetRGBPixel ( PIX *pix, l_int32 x, l_int32 y, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern l_int32 pixSetRGBPixel ( PIX *pix, l_int32 x, l_int32 y, l_int32 rval, l_int32 gval, l_int32 bval );
+LEPT_DLL extern l_int32 pixGetRandomPixel ( PIX *pix, l_uint32 *pval, l_int32 *px, l_int32 *py );
+LEPT_DLL extern l_int32 pixClearPixel ( PIX *pix, l_int32 x, l_int32 y );
+LEPT_DLL extern l_int32 pixFlipPixel ( PIX *pix, l_int32 x, l_int32 y );
+LEPT_DLL extern void setPixelLow ( l_uint32 *line, l_int32 x, l_int32 depth, l_uint32 val );
+LEPT_DLL extern l_int32 pixGetBlackOrWhiteVal ( PIX *pixs, l_int32 op, l_uint32 *pval );
+LEPT_DLL extern l_int32 pixClearAll ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetAll ( PIX *pix );
+LEPT_DLL extern l_int32 pixSetAllGray ( PIX *pix, l_int32 grayval );
+LEPT_DLL extern l_int32 pixSetAllArbitrary ( PIX *pix, l_uint32 val );
+LEPT_DLL extern l_int32 pixSetBlackOrWhite ( PIX *pixs, l_int32 op );
+LEPT_DLL extern l_int32 pixSetComponentArbitrary ( PIX *pix, l_int32 comp, l_int32 val );
+LEPT_DLL extern l_int32 pixClearInRect ( PIX *pix, BOX *box );
+LEPT_DLL extern l_int32 pixSetInRect ( PIX *pix, BOX *box );
+LEPT_DLL extern l_int32 pixSetInRectArbitrary ( PIX *pix, BOX *box, l_uint32 val );
+LEPT_DLL extern l_int32 pixBlendInRect ( PIX *pixs, BOX *box, l_uint32 val, l_float32 fract );
+LEPT_DLL extern l_int32 pixSetPadBits ( PIX *pix, l_int32 val );
+LEPT_DLL extern l_int32 pixSetPadBitsBand ( PIX *pix, l_int32 by, l_int32 bh, l_int32 val );
+LEPT_DLL extern l_int32 pixSetOrClearBorder ( PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot, l_int32 op );
+LEPT_DLL extern l_int32 pixSetBorderVal ( PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot, l_uint32 val );
+LEPT_DLL extern l_int32 pixSetBorderRingVal ( PIX *pixs, l_int32 dist, l_uint32 val );
+LEPT_DLL extern l_int32 pixSetMirroredBorder ( PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot );
+LEPT_DLL extern PIX * pixCopyBorder ( PIX *pixd, PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot );
+LEPT_DLL extern PIX * pixAddBorder ( PIX *pixs, l_int32 npix, l_uint32 val );
+LEPT_DLL extern PIX * pixAddBlackOrWhiteBorder ( PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot, l_int32 op );
+LEPT_DLL extern PIX * pixAddBorderGeneral ( PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot, l_uint32 val );
+LEPT_DLL extern PIX * pixRemoveBorder ( PIX *pixs, l_int32 npix );
+LEPT_DLL extern PIX * pixRemoveBorderGeneral ( PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot );
+LEPT_DLL extern PIX * pixRemoveBorderToSize ( PIX *pixs, l_int32 wd, l_int32 hd );
+LEPT_DLL extern PIX * pixAddMirroredBorder ( PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot );
+LEPT_DLL extern PIX * pixAddRepeatedBorder ( PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot );
+LEPT_DLL extern PIX * pixAddMixedBorder ( PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot );
+LEPT_DLL extern PIX * pixAddContinuedBorder ( PIX *pixs, l_int32 left, l_int32 right, l_int32 top, l_int32 bot );
+LEPT_DLL extern l_int32 pixShiftAndTransferAlpha ( PIX *pixd, PIX *pixs, l_float32 shiftx, l_float32 shifty );
+LEPT_DLL extern PIX * pixDisplayLayersRGBA ( PIX *pixs, l_uint32 val, l_int32 maxw );
+LEPT_DLL extern PIX * pixCreateRGBImage ( PIX *pixr, PIX *pixg, PIX *pixb );
+LEPT_DLL extern PIX * pixGetRGBComponent ( PIX *pixs, l_int32 comp );
+LEPT_DLL extern l_int32 pixSetRGBComponent ( PIX *pixd, PIX *pixs, l_int32 comp );
+LEPT_DLL extern PIX * pixGetRGBComponentCmap ( PIX *pixs, l_int32 comp );
+LEPT_DLL extern l_int32 pixCopyRGBComponent ( PIX *pixd, PIX *pixs, l_int32 comp );
+LEPT_DLL extern l_int32 composeRGBPixel ( l_int32 rval, l_int32 gval, l_int32 bval, l_uint32 *ppixel );
+LEPT_DLL extern l_int32 composeRGBAPixel ( l_int32 rval, l_int32 gval, l_int32 bval, l_int32 aval, l_uint32 *ppixel );
+LEPT_DLL extern void extractRGBValues ( l_uint32 pixel, l_int32 *prval, l_int32 *pgval, l_int32 *pbval );
+LEPT_DLL extern void extractRGBAValues ( l_uint32 pixel, l_int32 *prval, l_int32 *pgval, l_int32 *pbval, l_int32 *paval );
+LEPT_DLL extern l_int32 extractMinMaxComponent ( l_uint32 pixel, l_int32 type );
+LEPT_DLL extern l_int32 pixGetRGBLine ( PIX *pixs, l_int32 row, l_uint8 *bufr, l_uint8 *bufg, l_uint8 *bufb );
+LEPT_DLL extern PIX * pixEndianByteSwapNew ( PIX *pixs );
+LEPT_DLL extern l_int32 pixEndianByteSwap ( PIX *pixs );
+LEPT_DLL extern l_int32 lineEndianByteSwap ( l_uint32 *datad, l_uint32 *datas, l_int32 wpl );
+LEPT_DLL extern PIX * pixEndianTwoByteSwapNew ( PIX *pixs );
+LEPT_DLL extern l_int32 pixEndianTwoByteSwap ( PIX *pixs );
+LEPT_DLL extern l_int32 pixGetRasterData ( PIX *pixs, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_int32 pixAlphaIsOpaque ( PIX *pix, l_int32 *popaque );
+LEPT_DLL extern l_uint8 ** pixSetupByteProcessing ( PIX *pix, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_int32 pixCleanupByteProcessing ( PIX *pix, l_uint8 **lineptrs );
+LEPT_DLL extern void l_setAlphaMaskBorder ( l_float32 val1, l_float32 val2 );
+LEPT_DLL extern l_int32 pixSetMasked ( PIX *pixd, PIX *pixm, l_uint32 val );
+LEPT_DLL extern l_int32 pixSetMaskedGeneral ( PIX *pixd, PIX *pixm, l_uint32 val, l_int32 x, l_int32 y );
+LEPT_DLL extern l_int32 pixCombineMasked ( PIX *pixd, PIX *pixs, PIX *pixm );
+LEPT_DLL extern l_int32 pixCombineMaskedGeneral ( PIX *pixd, PIX *pixs, PIX *pixm, l_int32 x, l_int32 y );
+LEPT_DLL extern l_int32 pixPaintThroughMask ( PIX *pixd, PIX *pixm, l_int32 x, l_int32 y, l_uint32 val );
+LEPT_DLL extern l_int32 pixPaintSelfThroughMask ( PIX *pixd, PIX *pixm, l_int32 x, l_int32 y, l_int32 searchdir, l_int32 mindist, l_int32 tilesize, l_int32 ntiles, l_int32 distblend );
+LEPT_DLL extern PIX * pixMakeMaskFromLUT ( PIX *pixs, l_int32 *tab );
+LEPT_DLL extern PIX * pixSetUnderTransparency ( PIX *pixs, l_uint32 val, l_int32 debug );
+LEPT_DLL extern PIX * pixMakeAlphaFromMask ( PIX *pixs, l_int32 dist, BOX **pbox );
+LEPT_DLL extern l_int32 pixGetColorNearMaskBoundary ( PIX *pixs, PIX *pixm, BOX *box, l_int32 dist, l_uint32 *pval, l_int32 debug );
+LEPT_DLL extern PIX * pixInvert ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern PIX * pixOr ( PIX *pixd, PIX *pixs1, PIX *pixs2 );
+LEPT_DLL extern PIX * pixAnd ( PIX *pixd, PIX *pixs1, PIX *pixs2 );
+LEPT_DLL extern PIX * pixXor ( PIX *pixd, PIX *pixs1, PIX *pixs2 );
+LEPT_DLL extern PIX * pixSubtract ( PIX *pixd, PIX *pixs1, PIX *pixs2 );
+LEPT_DLL extern l_int32 pixZero ( PIX *pix, l_int32 *pempty );
+LEPT_DLL extern l_int32 pixForegroundFraction ( PIX *pix, l_float32 *pfract );
+LEPT_DLL extern NUMA * pixaCountPixels ( PIXA *pixa );
+LEPT_DLL extern l_int32 pixCountPixels ( PIX *pix, l_int32 *pcount, l_int32 *tab8 );
+LEPT_DLL extern NUMA * pixCountByRow ( PIX *pix, BOX *box );
+LEPT_DLL extern NUMA * pixCountByColumn ( PIX *pix, BOX *box );
+LEPT_DLL extern NUMA * pixCountPixelsByRow ( PIX *pix, l_int32 *tab8 );
+LEPT_DLL extern NUMA * pixCountPixelsByColumn ( PIX *pix );
+LEPT_DLL extern l_int32 pixCountPixelsInRow ( PIX *pix, l_int32 row, l_int32 *pcount, l_int32 *tab8 );
+LEPT_DLL extern NUMA * pixGetMomentByColumn ( PIX *pix, l_int32 order );
+LEPT_DLL extern l_int32 pixThresholdPixelSum ( PIX *pix, l_int32 thresh, l_int32 *pabove, l_int32 *tab8 );
+LEPT_DLL extern l_int32 * makePixelSumTab8 ( void );
+LEPT_DLL extern l_int32 * makePixelCentroidTab8 ( void );
+LEPT_DLL extern NUMA * pixAverageByRow ( PIX *pix, BOX *box, l_int32 type );
+LEPT_DLL extern NUMA * pixAverageByColumn ( PIX *pix, BOX *box, l_int32 type );
+LEPT_DLL extern l_int32 pixAverageInRect ( PIX *pix, BOX *box, l_float32 *pave );
+LEPT_DLL extern NUMA * pixVarianceByRow ( PIX *pix, BOX *box );
+LEPT_DLL extern NUMA * pixVarianceByColumn ( PIX *pix, BOX *box );
+LEPT_DLL extern l_int32 pixVarianceInRect ( PIX *pix, BOX *box, l_float32 *prootvar );
+LEPT_DLL extern NUMA * pixAbsDiffByRow ( PIX *pix, BOX *box );
+LEPT_DLL extern NUMA * pixAbsDiffByColumn ( PIX *pix, BOX *box );
+LEPT_DLL extern l_int32 pixAbsDiffInRect ( PIX *pix, BOX *box, l_int32 dir, l_float32 *pabsdiff );
+LEPT_DLL extern l_int32 pixAbsDiffOnLine ( PIX *pix, l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2, l_float32 *pabsdiff );
+LEPT_DLL extern l_int32 pixCountArbInRect ( PIX *pixs, BOX *box, l_int32 val, l_int32 factor, l_int32 *pcount );
+LEPT_DLL extern PIX * pixMirroredTiling ( PIX *pixs, l_int32 w, l_int32 h );
+LEPT_DLL extern l_int32 pixFindRepCloseTile ( PIX *pixs, BOX *box, l_int32 searchdir, l_int32 mindist, l_int32 tsize, l_int32 ntiles, BOX **pboxtile, l_int32 debug );
+LEPT_DLL extern NUMA * pixGetGrayHistogram ( PIX *pixs, l_int32 factor );
+LEPT_DLL extern NUMA * pixGetGrayHistogramMasked ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 factor );
+LEPT_DLL extern NUMA * pixGetGrayHistogramInRect ( PIX *pixs, BOX *box, l_int32 factor );
+LEPT_DLL extern NUMAA * pixGetGrayHistogramTiled ( PIX *pixs, l_int32 factor, l_int32 nx, l_int32 ny );
+LEPT_DLL extern l_int32 pixGetColorHistogram ( PIX *pixs, l_int32 factor, NUMA **pnar, NUMA **pnag, NUMA **pnab );
+LEPT_DLL extern l_int32 pixGetColorHistogramMasked ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 factor, NUMA **pnar, NUMA **pnag, NUMA **pnab );
+LEPT_DLL extern NUMA * pixGetCmapHistogram ( PIX *pixs, l_int32 factor );
+LEPT_DLL extern NUMA * pixGetCmapHistogramMasked ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 factor );
+LEPT_DLL extern NUMA * pixGetCmapHistogramInRect ( PIX *pixs, BOX *box, l_int32 factor );
+LEPT_DLL extern l_int32 pixGetRankValue ( PIX *pixs, l_int32 factor, l_float32 rank, l_uint32 *pvalue );
+LEPT_DLL extern l_int32 pixGetRankValueMaskedRGB ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 factor, l_float32 rank, l_float32 *prval, l_float32 *pgval, l_float32 *pbval );
+LEPT_DLL extern l_int32 pixGetRankValueMasked ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 factor, l_float32 rank, l_float32 *pval, NUMA **pna );
+LEPT_DLL extern l_int32 pixGetAverageValue ( PIX *pixs, l_int32 factor, l_int32 type, l_uint32 *pvalue );
+LEPT_DLL extern l_int32 pixGetAverageMaskedRGB ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 factor, l_int32 type, l_float32 *prval, l_float32 *pgval, l_float32 *pbval );
+LEPT_DLL extern l_int32 pixGetAverageMasked ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_int32 factor, l_int32 type, l_float32 *pval );
+LEPT_DLL extern l_int32 pixGetAverageTiledRGB ( PIX *pixs, l_int32 sx, l_int32 sy, l_int32 type, PIX **ppixr, PIX **ppixg, PIX **ppixb );
+LEPT_DLL extern PIX * pixGetAverageTiled ( PIX *pixs, l_int32 sx, l_int32 sy, l_int32 type );
+LEPT_DLL extern l_int32 pixRowStats ( PIX *pixs, BOX *box, NUMA **pnamean, NUMA **pnamedian, NUMA **pnamode, NUMA **pnamodecount, NUMA **pnavar, NUMA **pnarootvar );
+LEPT_DLL extern l_int32 pixColumnStats ( PIX *pixs, BOX *box, NUMA **pnamean, NUMA **pnamedian, NUMA **pnamode, NUMA **pnamodecount, NUMA **pnavar, NUMA **pnarootvar );
+LEPT_DLL extern l_int32 pixGetComponentRange ( PIX *pixs, l_int32 factor, l_int32 color, l_int32 *pminval, l_int32 *pmaxval );
+LEPT_DLL extern l_int32 pixGetExtremeValue ( PIX *pixs, l_int32 factor, l_int32 type, l_int32 *prval, l_int32 *pgval, l_int32 *pbval, l_int32 *pgrayval );
+LEPT_DLL extern l_int32 pixGetMaxValueInRect ( PIX *pixs, BOX *box, l_uint32 *pmaxval, l_int32 *pxmax, l_int32 *pymax );
+LEPT_DLL extern l_int32 pixGetBinnedComponentRange ( PIX *pixs, l_int32 nbins, l_int32 factor, l_int32 color, l_int32 *pminval, l_int32 *pmaxval, l_uint32 **pcarray, l_int32 fontsize );
+LEPT_DLL extern l_int32 pixGetRankColorArray ( PIX *pixs, l_int32 nbins, l_int32 type, l_int32 factor, l_uint32 **pcarray, l_int32 debugflag, l_int32 fontsize );
+LEPT_DLL extern l_int32 pixGetBinnedColor ( PIX *pixs, PIX *pixg, l_int32 factor, l_int32 nbins, NUMA *nalut, l_uint32 **pcarray, l_int32 debugflag );
+LEPT_DLL extern PIX * pixDisplayColorArray ( l_uint32 *carray, l_int32 ncolors, l_int32 side, l_int32 ncols, l_int32 fontsize );
+LEPT_DLL extern PIX * pixRankBinByStrip ( PIX *pixs, l_int32 direction, l_int32 size, l_int32 nbins, l_int32 type );
+LEPT_DLL extern PIX * pixaGetAlignedStats ( PIXA *pixa, l_int32 type, l_int32 nbins, l_int32 thresh );
+LEPT_DLL extern l_int32 pixaExtractColumnFromEachPix ( PIXA *pixa, l_int32 col, PIX *pixd );
+LEPT_DLL extern l_int32 pixGetRowStats ( PIX *pixs, l_int32 type, l_int32 nbins, l_int32 thresh, l_float32 *colvect );
+LEPT_DLL extern l_int32 pixGetColumnStats ( PIX *pixs, l_int32 type, l_int32 nbins, l_int32 thresh, l_float32 *rowvect );
+LEPT_DLL extern l_int32 pixSetPixelColumn ( PIX *pix, l_int32 col, l_float32 *colvect );
+LEPT_DLL extern l_int32 pixThresholdForFgBg ( PIX *pixs, l_int32 factor, l_int32 thresh, l_int32 *pfgval, l_int32 *pbgval );
+LEPT_DLL extern l_int32 pixSplitDistributionFgBg ( PIX *pixs, l_float32 scorefract, l_int32 factor, l_int32 *pthresh, l_int32 *pfgval, l_int32 *pbgval, l_int32 debugflag );
+LEPT_DLL extern l_int32 pixaFindDimensions ( PIXA *pixa, NUMA **pnaw, NUMA **pnah );
+LEPT_DLL extern l_int32 pixFindAreaPerimRatio ( PIX *pixs, l_int32 *tab, l_float32 *pfract );
+LEPT_DLL extern NUMA * pixaFindPerimToAreaRatio ( PIXA *pixa );
+LEPT_DLL extern l_int32 pixFindPerimToAreaRatio ( PIX *pixs, l_int32 *tab, l_float32 *pfract );
+LEPT_DLL extern NUMA * pixaFindPerimSizeRatio ( PIXA *pixa );
+LEPT_DLL extern l_int32 pixFindPerimSizeRatio ( PIX *pixs, l_int32 *tab, l_float32 *pratio );
+LEPT_DLL extern NUMA * pixaFindAreaFraction ( PIXA *pixa );
+LEPT_DLL extern l_int32 pixFindAreaFraction ( PIX *pixs, l_int32 *tab, l_float32 *pfract );
+LEPT_DLL extern NUMA * pixaFindAreaFractionMasked ( PIXA *pixa, PIX *pixm, l_int32 debug );
+LEPT_DLL extern l_int32 pixFindAreaFractionMasked ( PIX *pixs, BOX *box, PIX *pixm, l_int32 *tab, l_float32 *pfract );
+LEPT_DLL extern NUMA * pixaFindWidthHeightRatio ( PIXA *pixa );
+LEPT_DLL extern NUMA * pixaFindWidthHeightProduct ( PIXA *pixa );
+LEPT_DLL extern l_int32 pixFindOverlapFraction ( PIX *pixs1, PIX *pixs2, l_int32 x2, l_int32 y2, l_int32 *tab, l_float32 *pratio, l_int32 *pnoverlap );
+LEPT_DLL extern BOXA * pixFindRectangleComps ( PIX *pixs, l_int32 dist, l_int32 minw, l_int32 minh );
+LEPT_DLL extern l_int32 pixConformsToRectangle ( PIX *pixs, BOX *box, l_int32 dist, l_int32 *pconforms );
+LEPT_DLL extern PIXA * pixClipRectangles ( PIX *pixs, BOXA *boxa );
+LEPT_DLL extern PIX * pixClipRectangle ( PIX *pixs, BOX *box, BOX **pboxc );
+LEPT_DLL extern PIX * pixClipMasked ( PIX *pixs, PIX *pixm, l_int32 x, l_int32 y, l_uint32 outval );
+LEPT_DLL extern l_int32 pixCropToMatch ( PIX *pixs1, PIX *pixs2, PIX **ppixd1, PIX **ppixd2 );
+LEPT_DLL extern PIX * pixCropToSize ( PIX *pixs, l_int32 w, l_int32 h );
+LEPT_DLL extern PIX * pixResizeToMatch ( PIX *pixs, PIX *pixt, l_int32 w, l_int32 h );
+LEPT_DLL extern PIX * pixMakeFrameMask ( l_int32 w, l_int32 h, l_float32 hf1, l_float32 hf2, l_float32 vf1, l_float32 vf2 );
+LEPT_DLL extern l_int32 pixFractionFgInMask ( PIX *pix1, PIX *pix2, l_float32 *pfract );
+LEPT_DLL extern l_int32 pixClipToForeground ( PIX *pixs, PIX **ppixd, BOX **pbox );
+LEPT_DLL extern l_int32 pixTestClipToForeground ( PIX *pixs, l_int32 *pcanclip );
+LEPT_DLL extern l_int32 pixClipBoxToForeground ( PIX *pixs, BOX *boxs, PIX **ppixd, BOX **pboxd );
+LEPT_DLL extern l_int32 pixScanForForeground ( PIX *pixs, BOX *box, l_int32 scanflag, l_int32 *ploc );
+LEPT_DLL extern l_int32 pixClipBoxToEdges ( PIX *pixs, BOX *boxs, l_int32 lowthresh, l_int32 highthresh, l_int32 maxwidth, l_int32 factor, PIX **ppixd, BOX **pboxd );
+LEPT_DLL extern l_int32 pixScanForEdge ( PIX *pixs, BOX *box, l_int32 lowthresh, l_int32 highthresh, l_int32 maxwidth, l_int32 factor, l_int32 scanflag, l_int32 *ploc );
+LEPT_DLL extern NUMA * pixExtractOnLine ( PIX *pixs, l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2, l_int32 factor );
+LEPT_DLL extern l_float32 pixAverageOnLine ( PIX *pixs, l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2, l_int32 factor );
+LEPT_DLL extern NUMA * pixAverageIntensityProfile ( PIX *pixs, l_float32 fract, l_int32 dir, l_int32 first, l_int32 last, l_int32 factor1, l_int32 factor2 );
+LEPT_DLL extern NUMA * pixReversalProfile ( PIX *pixs, l_float32 fract, l_int32 dir, l_int32 first, l_int32 last, l_int32 minreversal, l_int32 factor1, l_int32 factor2 );
+LEPT_DLL extern l_int32 pixWindowedVarianceOnLine ( PIX *pixs, l_int32 dir, l_int32 loc, l_int32 c1, l_int32 c2, l_int32 size, NUMA **pnad );
+LEPT_DLL extern l_int32 pixMinMaxNearLine ( PIX *pixs, l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2, l_int32 dist, l_int32 direction, NUMA **pnamin, NUMA **pnamax, l_float32 *pminave, l_float32 *pmaxave );
+LEPT_DLL extern PIX * pixRankRowTransform ( PIX *pixs );
+LEPT_DLL extern PIX * pixRankColumnTransform ( PIX *pixs );
+LEPT_DLL extern PIXA * pixaCreate ( l_int32 n );
+LEPT_DLL extern PIXA * pixaCreateFromPix ( PIX *pixs, l_int32 n, l_int32 cellw, l_int32 cellh );
+LEPT_DLL extern PIXA * pixaCreateFromBoxa ( PIX *pixs, BOXA *boxa, l_int32 *pcropwarn );
+LEPT_DLL extern PIXA * pixaSplitPix ( PIX *pixs, l_int32 nx, l_int32 ny, l_int32 borderwidth, l_uint32 bordercolor );
+LEPT_DLL extern void pixaDestroy ( PIXA **ppixa );
+LEPT_DLL extern PIXA * pixaCopy ( PIXA *pixa, l_int32 copyflag );
+LEPT_DLL extern l_int32 pixaAddPix ( PIXA *pixa, PIX *pix, l_int32 copyflag );
+LEPT_DLL extern l_int32 pixaAddBox ( PIXA *pixa, BOX *box, l_int32 copyflag );
+LEPT_DLL extern l_int32 pixaExtendArrayToSize ( PIXA *pixa, l_int32 size );
+LEPT_DLL extern l_int32 pixaGetCount ( PIXA *pixa );
+LEPT_DLL extern l_int32 pixaChangeRefcount ( PIXA *pixa, l_int32 delta );
+LEPT_DLL extern PIX * pixaGetPix ( PIXA *pixa, l_int32 index, l_int32 accesstype );
+LEPT_DLL extern l_int32 pixaGetPixDimensions ( PIXA *pixa, l_int32 index, l_int32 *pw, l_int32 *ph, l_int32 *pd );
+LEPT_DLL extern BOXA * pixaGetBoxa ( PIXA *pixa, l_int32 accesstype );
+LEPT_DLL extern l_int32 pixaGetBoxaCount ( PIXA *pixa );
+LEPT_DLL extern BOX * pixaGetBox ( PIXA *pixa, l_int32 index, l_int32 accesstype );
+LEPT_DLL extern l_int32 pixaGetBoxGeometry ( PIXA *pixa, l_int32 index, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_int32 pixaSetBoxa ( PIXA *pixa, BOXA *boxa, l_int32 accesstype );
+LEPT_DLL extern PIX ** pixaGetPixArray ( PIXA *pixa );
+LEPT_DLL extern l_int32 pixaVerifyDepth ( PIXA *pixa, l_int32 *pmaxdepth );
+LEPT_DLL extern l_int32 pixaIsFull ( PIXA *pixa, l_int32 *pfullpa, l_int32 *pfullba );
+LEPT_DLL extern l_int32 pixaCountText ( PIXA *pixa, l_int32 *pntext );
+LEPT_DLL extern void *** pixaGetLinePtrs ( PIXA *pixa, l_int32 *psize );
+LEPT_DLL extern l_int32 pixaWriteStreamInfo ( FILE *fp, PIXA *pixa );
+LEPT_DLL extern l_int32 pixaReplacePix ( PIXA *pixa, l_int32 index, PIX *pix, BOX *box );
+LEPT_DLL extern l_int32 pixaInsertPix ( PIXA *pixa, l_int32 index, PIX *pixs, BOX *box );
+LEPT_DLL extern l_int32 pixaRemovePix ( PIXA *pixa, l_int32 index );
+LEPT_DLL extern l_int32 pixaRemovePixAndSave ( PIXA *pixa, l_int32 index, PIX **ppix, BOX **pbox );
+LEPT_DLL extern l_int32 pixaInitFull ( PIXA *pixa, PIX *pix, BOX *box );
+LEPT_DLL extern l_int32 pixaClear ( PIXA *pixa );
+LEPT_DLL extern l_int32 pixaJoin ( PIXA *pixad, PIXA *pixas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern l_int32 pixaaJoin ( PIXAA *paad, PIXAA *paas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern PIXAA * pixaaCreate ( l_int32 n );
+LEPT_DLL extern PIXAA * pixaaCreateFromPixa ( PIXA *pixa, l_int32 n, l_int32 type, l_int32 copyflag );
+LEPT_DLL extern void pixaaDestroy ( PIXAA **ppaa );
+LEPT_DLL extern l_int32 pixaaAddPixa ( PIXAA *paa, PIXA *pixa, l_int32 copyflag );
+LEPT_DLL extern l_int32 pixaaExtendArray ( PIXAA *paa );
+LEPT_DLL extern l_int32 pixaaAddPix ( PIXAA *paa, l_int32 index, PIX *pix, BOX *box, l_int32 copyflag );
+LEPT_DLL extern l_int32 pixaaAddBox ( PIXAA *paa, BOX *box, l_int32 copyflag );
+LEPT_DLL extern l_int32 pixaaGetCount ( PIXAA *paa, NUMA **pna );
+LEPT_DLL extern PIXA * pixaaGetPixa ( PIXAA *paa, l_int32 index, l_int32 accesstype );
+LEPT_DLL extern BOXA * pixaaGetBoxa ( PIXAA *paa, l_int32 accesstype );
+LEPT_DLL extern PIX * pixaaGetPix ( PIXAA *paa, l_int32 index, l_int32 ipix, l_int32 accessflag );
+LEPT_DLL extern l_int32 pixaaVerifyDepth ( PIXAA *paa, l_int32 *pmaxdepth );
+LEPT_DLL extern l_int32 pixaaIsFull ( PIXAA *paa, l_int32 *pfull );
+LEPT_DLL extern l_int32 pixaaInitFull ( PIXAA *paa, PIXA *pixa );
+LEPT_DLL extern l_int32 pixaaReplacePixa ( PIXAA *paa, l_int32 index, PIXA *pixa );
+LEPT_DLL extern l_int32 pixaaClear ( PIXAA *paa );
+LEPT_DLL extern l_int32 pixaaTruncate ( PIXAA *paa );
+LEPT_DLL extern PIXA * pixaRead ( const char *filename );
+LEPT_DLL extern PIXA * pixaReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 pixaWrite ( const char *filename, PIXA *pixa );
+LEPT_DLL extern l_int32 pixaWriteStream ( FILE *fp, PIXA *pixa );
+LEPT_DLL extern PIXAA * pixaaReadFromFiles ( const char *dirname, const char *substr, l_int32 first, l_int32 nfiles );
+LEPT_DLL extern PIXAA * pixaaRead ( const char *filename );
+LEPT_DLL extern PIXAA * pixaaReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 pixaaWrite ( const char *filename, PIXAA *paa );
+LEPT_DLL extern l_int32 pixaaWriteStream ( FILE *fp, PIXAA *paa );
+LEPT_DLL extern PIXACC * pixaccCreate ( l_int32 w, l_int32 h, l_int32 negflag );
+LEPT_DLL extern PIXACC * pixaccCreateFromPix ( PIX *pix, l_int32 negflag );
+LEPT_DLL extern void pixaccDestroy ( PIXACC **ppixacc );
+LEPT_DLL extern PIX * pixaccFinal ( PIXACC *pixacc, l_int32 outdepth );
+LEPT_DLL extern PIX * pixaccGetPix ( PIXACC *pixacc );
+LEPT_DLL extern l_int32 pixaccGetOffset ( PIXACC *pixacc );
+LEPT_DLL extern l_int32 pixaccAdd ( PIXACC *pixacc, PIX *pix );
+LEPT_DLL extern l_int32 pixaccSubtract ( PIXACC *pixacc, PIX *pix );
+LEPT_DLL extern l_int32 pixaccMultConst ( PIXACC *pixacc, l_float32 factor );
+LEPT_DLL extern l_int32 pixaccMultConstAccumulate ( PIXACC *pixacc, PIX *pix, l_float32 factor );
+LEPT_DLL extern PIX * pixSelectBySize ( PIX *pixs, l_int32 width, l_int32 height, l_int32 connectivity, l_int32 type, l_int32 relation, l_int32 *pchanged );
+LEPT_DLL extern PIXA * pixaSelectBySize ( PIXA *pixas, l_int32 width, l_int32 height, l_int32 type, l_int32 relation, l_int32 *pchanged );
+LEPT_DLL extern NUMA * pixaMakeSizeIndicator ( PIXA *pixa, l_int32 width, l_int32 height, l_int32 type, l_int32 relation );
+LEPT_DLL extern PIX * pixSelectByPerimToAreaRatio ( PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged );
+LEPT_DLL extern PIXA * pixaSelectByPerimToAreaRatio ( PIXA *pixas, l_float32 thresh, l_int32 type, l_int32 *pchanged );
+LEPT_DLL extern PIX * pixSelectByPerimSizeRatio ( PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged );
+LEPT_DLL extern PIXA * pixaSelectByPerimSizeRatio ( PIXA *pixas, l_float32 thresh, l_int32 type, l_int32 *pchanged );
+LEPT_DLL extern PIX * pixSelectByAreaFraction ( PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged );
+LEPT_DLL extern PIXA * pixaSelectByAreaFraction ( PIXA *pixas, l_float32 thresh, l_int32 type, l_int32 *pchanged );
+LEPT_DLL extern PIX * pixSelectByWidthHeightRatio ( PIX *pixs, l_float32 thresh, l_int32 connectivity, l_int32 type, l_int32 *pchanged );
+LEPT_DLL extern PIXA * pixaSelectByWidthHeightRatio ( PIXA *pixas, l_float32 thresh, l_int32 type, l_int32 *pchanged );
+LEPT_DLL extern PIXA * pixaSelectWithIndicator ( PIXA *pixas, NUMA *na, l_int32 *pchanged );
+LEPT_DLL extern l_int32 pixRemoveWithIndicator ( PIX *pixs, PIXA *pixa, NUMA *na );
+LEPT_DLL extern l_int32 pixAddWithIndicator ( PIX *pixs, PIXA *pixa, NUMA *na );
+LEPT_DLL extern PIXA * pixaSelectWithString ( PIXA *pixas, const char *str, l_int32 *perror );
+LEPT_DLL extern PIX * pixaRenderComponent ( PIX *pixs, PIXA *pixa, l_int32 index );
+LEPT_DLL extern PIXA * pixaSort ( PIXA *pixas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex, l_int32 copyflag );
+LEPT_DLL extern PIXA * pixaBinSort ( PIXA *pixas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex, l_int32 copyflag );
+LEPT_DLL extern PIXA * pixaSortByIndex ( PIXA *pixas, NUMA *naindex, l_int32 copyflag );
+LEPT_DLL extern PIXAA * pixaSort2dByIndex ( PIXA *pixas, NUMAA *naa, l_int32 copyflag );
+LEPT_DLL extern PIXA * pixaSelectRange ( PIXA *pixas, l_int32 first, l_int32 last, l_int32 copyflag );
+LEPT_DLL extern PIXAA * pixaaSelectRange ( PIXAA *paas, l_int32 first, l_int32 last, l_int32 copyflag );
+LEPT_DLL extern PIXAA * pixaaScaleToSize ( PIXAA *paas, l_int32 wd, l_int32 hd );
+LEPT_DLL extern PIXAA * pixaaScaleToSizeVar ( PIXAA *paas, NUMA *nawd, NUMA *nahd );
+LEPT_DLL extern PIXA * pixaScaleToSize ( PIXA *pixas, l_int32 wd, l_int32 hd );
+LEPT_DLL extern PIXA * pixaAddBorderGeneral ( PIXA *pixad, PIXA *pixas, l_int32 left, l_int32 right, l_int32 top, l_int32 bot, l_uint32 val );
+LEPT_DLL extern PIXA * pixaaFlattenToPixa ( PIXAA *paa, NUMA **pnaindex, l_int32 copyflag );
+LEPT_DLL extern l_int32 pixaaSizeRange ( PIXAA *paa, l_int32 *pminw, l_int32 *pminh, l_int32 *pmaxw, l_int32 *pmaxh );
+LEPT_DLL extern l_int32 pixaSizeRange ( PIXA *pixa, l_int32 *pminw, l_int32 *pminh, l_int32 *pmaxw, l_int32 *pmaxh );
+LEPT_DLL extern PIXA * pixaClipToPix ( PIXA *pixas, PIX *pixs );
+LEPT_DLL extern l_int32 pixaGetRenderingDepth ( PIXA *pixa, l_int32 *pdepth );
+LEPT_DLL extern l_int32 pixaHasColor ( PIXA *pixa, l_int32 *phascolor );
+LEPT_DLL extern l_int32 pixaAnyColormaps ( PIXA *pixa, l_int32 *phascmap );
+LEPT_DLL extern l_int32 pixaGetDepthInfo ( PIXA *pixa, l_int32 *pmaxdepth, l_int32 *psame );
+LEPT_DLL extern PIXA * pixaConvertToSameDepth ( PIXA *pixas );
+LEPT_DLL extern l_int32 pixaEqual ( PIXA *pixa1, PIXA *pixa2, l_int32 maxdist, NUMA **pnaindex, l_int32 *psame );
+LEPT_DLL extern PIXA * pixaRotateOrth ( PIXA *pixas, l_int32 rotation );
+LEPT_DLL extern PIX * pixaDisplay ( PIXA *pixa, l_int32 w, l_int32 h );
+LEPT_DLL extern PIX * pixaDisplayOnColor ( PIXA *pixa, l_int32 w, l_int32 h, l_uint32 bgcolor );
+LEPT_DLL extern PIX * pixaDisplayRandomCmap ( PIXA *pixa, l_int32 w, l_int32 h );
+LEPT_DLL extern PIX * pixaDisplayLinearly ( PIXA *pixas, l_int32 direction, l_float32 scalefactor, l_int32 background, l_int32 spacing, l_int32 border, BOXA **pboxa );
+LEPT_DLL extern PIX * pixaDisplayOnLattice ( PIXA *pixa, l_int32 cellw, l_int32 cellh, l_int32 *pncols, BOXA **pboxa );
+LEPT_DLL extern PIX * pixaDisplayUnsplit ( PIXA *pixa, l_int32 nx, l_int32 ny, l_int32 borderwidth, l_uint32 bordercolor );
+LEPT_DLL extern PIX * pixaDisplayTiled ( PIXA *pixa, l_int32 maxwidth, l_int32 background, l_int32 spacing );
+LEPT_DLL extern PIX * pixaDisplayTiledInRows ( PIXA *pixa, l_int32 outdepth, l_int32 maxwidth, l_float32 scalefactor, l_int32 background, l_int32 spacing, l_int32 border );
+LEPT_DLL extern PIX * pixaDisplayTiledAndScaled ( PIXA *pixa, l_int32 outdepth, l_int32 tilewidth, l_int32 ncols, l_int32 background, l_int32 spacing, l_int32 border );
+LEPT_DLL extern PIX * pixaDisplayTiledWithText ( PIXA *pixa, l_int32 maxwidth, l_float32 scalefactor, l_int32 spacing, l_int32 border, l_int32 fontsize, l_uint32 textcolor );
+LEPT_DLL extern PIX * pixaDisplayTiledByIndex ( PIXA *pixa, NUMA *na, l_int32 width, l_int32 spacing, l_int32 border, l_int32 fontsize, l_uint32 textcolor );
+LEPT_DLL extern PIX * pixaaDisplay ( PIXAA *paa, l_int32 w, l_int32 h );
+LEPT_DLL extern PIX * pixaaDisplayByPixa ( PIXAA *paa, l_int32 xspace, l_int32 yspace, l_int32 maxw );
+LEPT_DLL extern PIXA * pixaaDisplayTiledAndScaled ( PIXAA *paa, l_int32 outdepth, l_int32 tilewidth, l_int32 ncols, l_int32 background, l_int32 spacing, l_int32 border );
+LEPT_DLL extern PIXA * pixaConvertTo1 ( PIXA *pixas, l_int32 thresh );
+LEPT_DLL extern PIXA * pixaConvertTo8 ( PIXA *pixas, l_int32 cmapflag );
+LEPT_DLL extern PIXA * pixaConvertTo8Color ( PIXA *pixas, l_int32 dither );
+LEPT_DLL extern PIXA * pixaConvertTo32 ( PIXA *pixas );
+LEPT_DLL extern l_int32 convertToNUpFiles ( const char *dir, const char *substr, l_int32 nx, l_int32 ny, l_int32 tw, l_int32 spacing, l_int32 border, l_int32 fontsize, const char *outdir );
+LEPT_DLL extern PIXA * convertToNUpPixa ( const char *dir, const char *substr, l_int32 nx, l_int32 ny, l_int32 tw, l_int32 spacing, l_int32 border, l_int32 fontsize );
+LEPT_DLL extern l_int32 pmsCreate ( size_t minsize, size_t smallest, NUMA *numalloc, const char *logfile );
+LEPT_DLL extern void pmsDestroy (  );
+LEPT_DLL extern void * pmsCustomAlloc ( size_t nbytes );
+LEPT_DLL extern void pmsCustomDealloc ( void *data );
+LEPT_DLL extern void * pmsGetAlloc ( size_t nbytes );
+LEPT_DLL extern l_int32 pmsGetLevelForAlloc ( size_t nbytes, l_int32 *plevel );
+LEPT_DLL extern l_int32 pmsGetLevelForDealloc ( void *data, l_int32 *plevel );
+LEPT_DLL extern void pmsLogInfo (  );
+LEPT_DLL extern l_int32 pixAddConstantGray ( PIX *pixs, l_int32 val );
+LEPT_DLL extern l_int32 pixMultConstantGray ( PIX *pixs, l_float32 val );
+LEPT_DLL extern PIX * pixAddGray ( PIX *pixd, PIX *pixs1, PIX *pixs2 );
+LEPT_DLL extern PIX * pixSubtractGray ( PIX *pixd, PIX *pixs1, PIX *pixs2 );
+LEPT_DLL extern PIX * pixThresholdToValue ( PIX *pixd, PIX *pixs, l_int32 threshval, l_int32 setval );
+LEPT_DLL extern PIX * pixInitAccumulate ( l_int32 w, l_int32 h, l_uint32 offset );
+LEPT_DLL extern PIX * pixFinalAccumulate ( PIX *pixs, l_uint32 offset, l_int32 depth );
+LEPT_DLL extern PIX * pixFinalAccumulateThreshold ( PIX *pixs, l_uint32 offset, l_uint32 threshold );
+LEPT_DLL extern l_int32 pixAccumulate ( PIX *pixd, PIX *pixs, l_int32 op );
+LEPT_DLL extern l_int32 pixMultConstAccumulate ( PIX *pixs, l_float32 factor, l_uint32 offset );
+LEPT_DLL extern PIX * pixAbsDifference ( PIX *pixs1, PIX *pixs2 );
+LEPT_DLL extern PIX * pixAddRGB ( PIX *pixs1, PIX *pixs2 );
+LEPT_DLL extern PIX * pixMinOrMax ( PIX *pixd, PIX *pixs1, PIX *pixs2, l_int32 type );
+LEPT_DLL extern PIX * pixMaxDynamicRange ( PIX *pixs, l_int32 type );
+LEPT_DLL extern l_float32 * makeLogBase2Tab ( void );
+LEPT_DLL extern l_float32 getLogBase2 ( l_int32 val, l_float32 *logtab );
+LEPT_DLL extern PIXC * pixcompCreateFromPix ( PIX *pix, l_int32 comptype );
+LEPT_DLL extern PIXC * pixcompCreateFromString ( l_uint8 *data, size_t size, l_int32 copyflag );
+LEPT_DLL extern PIXC * pixcompCreateFromFile ( const char *filename, l_int32 comptype );
+LEPT_DLL extern void pixcompDestroy ( PIXC **ppixc );
+LEPT_DLL extern l_int32 pixcompGetDimensions ( PIXC *pixc, l_int32 *pw, l_int32 *ph, l_int32 *pd );
+LEPT_DLL extern l_int32 pixcompDetermineFormat ( l_int32 comptype, l_int32 d, l_int32 cmapflag, l_int32 *pformat );
+LEPT_DLL extern PIX * pixCreateFromPixcomp ( PIXC *pixc );
+LEPT_DLL extern PIXAC * pixacompCreate ( l_int32 n );
+LEPT_DLL extern PIXAC * pixacompCreateWithInit ( l_int32 n, l_int32 offset, PIX *pix, l_int32 comptype );
+LEPT_DLL extern PIXAC * pixacompCreateFromPixa ( PIXA *pixa, l_int32 comptype, l_int32 accesstype );
+LEPT_DLL extern PIXAC * pixacompCreateFromFiles ( const char *dirname, const char *substr, l_int32 comptype );
+LEPT_DLL extern PIXAC * pixacompCreateFromSA ( SARRAY *sa, l_int32 comptype );
+LEPT_DLL extern void pixacompDestroy ( PIXAC **ppixac );
+LEPT_DLL extern l_int32 pixacompAddPix ( PIXAC *pixac, PIX *pix, l_int32 comptype );
+LEPT_DLL extern l_int32 pixacompAddPixcomp ( PIXAC *pixac, PIXC *pixc );
+LEPT_DLL extern l_int32 pixacompReplacePix ( PIXAC *pixac, l_int32 index, PIX *pix, l_int32 comptype );
+LEPT_DLL extern l_int32 pixacompReplacePixcomp ( PIXAC *pixac, l_int32 index, PIXC *pixc );
+LEPT_DLL extern l_int32 pixacompAddBox ( PIXAC *pixac, BOX *box, l_int32 copyflag );
+LEPT_DLL extern l_int32 pixacompGetCount ( PIXAC *pixac );
+LEPT_DLL extern PIXC * pixacompGetPixcomp ( PIXAC *pixac, l_int32 index );
+LEPT_DLL extern PIX * pixacompGetPix ( PIXAC *pixac, l_int32 index );
+LEPT_DLL extern l_int32 pixacompGetPixDimensions ( PIXAC *pixac, l_int32 index, l_int32 *pw, l_int32 *ph, l_int32 *pd );
+LEPT_DLL extern BOXA * pixacompGetBoxa ( PIXAC *pixac, l_int32 accesstype );
+LEPT_DLL extern l_int32 pixacompGetBoxaCount ( PIXAC *pixac );
+LEPT_DLL extern BOX * pixacompGetBox ( PIXAC *pixac, l_int32 index, l_int32 accesstype );
+LEPT_DLL extern l_int32 pixacompGetBoxGeometry ( PIXAC *pixac, l_int32 index, l_int32 *px, l_int32 *py, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern l_int32 pixacompGetOffset ( PIXAC *pixac );
+LEPT_DLL extern l_int32 pixacompSetOffset ( PIXAC *pixac, l_int32 offset );
+LEPT_DLL extern PIXA * pixaCreateFromPixacomp ( PIXAC *pixac, l_int32 accesstype );
+LEPT_DLL extern PIXAC * pixacompRead ( const char *filename );
+LEPT_DLL extern PIXAC * pixacompReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 pixacompWrite ( const char *filename, PIXAC *pixac );
+LEPT_DLL extern l_int32 pixacompWriteStream ( FILE *fp, PIXAC *pixac );
+LEPT_DLL extern l_int32 pixacompConvertToPdf ( PIXAC *pixac, l_int32 res, l_float32 scalefactor, l_int32 type, l_int32 quality, const char *title, const char *fileout );
+LEPT_DLL extern l_int32 pixacompConvertToPdfData ( PIXAC *pixac, l_int32 res, l_float32 scalefactor, l_int32 type, l_int32 quality, const char *title, l_uint8 **pdata, size_t *pnbytes );
+LEPT_DLL extern l_int32 pixacompWriteStreamInfo ( FILE *fp, PIXAC *pixac, const char *text );
+LEPT_DLL extern l_int32 pixcompWriteStreamInfo ( FILE *fp, PIXC *pixc, const char *text );
+LEPT_DLL extern PIX * pixacompDisplayTiledAndScaled ( PIXAC *pixac, l_int32 outdepth, l_int32 tilewidth, l_int32 ncols, l_int32 background, l_int32 spacing, l_int32 border );
+LEPT_DLL extern PIX * pixThreshold8 ( PIX *pixs, l_int32 d, l_int32 nlevels, l_int32 cmapflag );
+LEPT_DLL extern PIX * pixRemoveColormapGeneral ( PIX *pixs, l_int32 type, l_int32 ifnocmap );
+LEPT_DLL extern PIX * pixRemoveColormap ( PIX *pixs, l_int32 type );
+LEPT_DLL extern l_int32 pixAddGrayColormap8 ( PIX *pixs );
+LEPT_DLL extern PIX * pixAddMinimalGrayColormap8 ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvertRGBToLuminance ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvertRGBToGray ( PIX *pixs, l_float32 rwt, l_float32 gwt, l_float32 bwt );
+LEPT_DLL extern PIX * pixConvertRGBToGrayFast ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvertRGBToGrayMinMax ( PIX *pixs, l_int32 type );
+LEPT_DLL extern PIX * pixConvertRGBToGraySatBoost ( PIX *pixs, l_int32 refval );
+LEPT_DLL extern PIX * pixConvertGrayToColormap ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvertGrayToColormap8 ( PIX *pixs, l_int32 mindepth );
+LEPT_DLL extern PIX * pixColorizeGray ( PIX *pixs, l_uint32 color, l_int32 cmapflag );
+LEPT_DLL extern PIX * pixConvertRGBToColormap ( PIX *pixs, l_int32 ditherflag );
+LEPT_DLL extern l_int32 pixQuantizeIfFewColors ( PIX *pixs, l_int32 maxcolors, l_int32 mingraycolors, l_int32 octlevel, PIX **ppixd );
+LEPT_DLL extern PIX * pixConvert16To8 ( PIX *pixs, l_int32 type );
+LEPT_DLL extern PIX * pixConvertGrayToFalseColor ( PIX *pixs, l_float32 gamma );
+LEPT_DLL extern PIX * pixUnpackBinary ( PIX *pixs, l_int32 depth, l_int32 invert );
+LEPT_DLL extern PIX * pixConvert1To16 ( PIX *pixd, PIX *pixs, l_uint16 val0, l_uint16 val1 );
+LEPT_DLL extern PIX * pixConvert1To32 ( PIX *pixd, PIX *pixs, l_uint32 val0, l_uint32 val1 );
+LEPT_DLL extern PIX * pixConvert1To2Cmap ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvert1To2 ( PIX *pixd, PIX *pixs, l_int32 val0, l_int32 val1 );
+LEPT_DLL extern PIX * pixConvert1To4Cmap ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvert1To4 ( PIX *pixd, PIX *pixs, l_int32 val0, l_int32 val1 );
+LEPT_DLL extern PIX * pixConvert1To8Cmap ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvert1To8 ( PIX *pixd, PIX *pixs, l_uint8 val0, l_uint8 val1 );
+LEPT_DLL extern PIX * pixConvert2To8 ( PIX *pixs, l_uint8 val0, l_uint8 val1, l_uint8 val2, l_uint8 val3, l_int32 cmapflag );
+LEPT_DLL extern PIX * pixConvert4To8 ( PIX *pixs, l_int32 cmapflag );
+LEPT_DLL extern PIX * pixConvert8To16 ( PIX *pixs, l_int32 leftshift );
+LEPT_DLL extern PIX * pixConvertTo1 ( PIX *pixs, l_int32 threshold );
+LEPT_DLL extern PIX * pixConvertTo1BySampling ( PIX *pixs, l_int32 factor, l_int32 threshold );
+LEPT_DLL extern PIX * pixConvertTo8 ( PIX *pixs, l_int32 cmapflag );
+LEPT_DLL extern PIX * pixConvertTo8BySampling ( PIX *pixs, l_int32 factor, l_int32 cmapflag );
+LEPT_DLL extern PIX * pixConvertTo8Color ( PIX *pixs, l_int32 dither );
+LEPT_DLL extern PIX * pixConvertTo16 ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvertTo32 ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvertTo32BySampling ( PIX *pixs, l_int32 factor );
+LEPT_DLL extern PIX * pixConvert8To32 ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvertTo8Or32 ( PIX *pixs, l_int32 copyflag, l_int32 warnflag );
+LEPT_DLL extern PIX * pixConvert24To32 ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvert32To24 ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvert32To16 ( PIX *pixs, l_int32 type );
+LEPT_DLL extern PIX * pixConvert32To8 ( PIX *pixs, l_int32 type16, l_int32 type8 );
+LEPT_DLL extern PIX * pixRemoveAlpha ( PIX *pixs );
+LEPT_DLL extern PIX * pixAddAlphaTo1bpp ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern PIX * pixConvertLossless ( PIX *pixs, l_int32 d );
+LEPT_DLL extern PIX * pixConvertForPSWrap ( PIX *pixs );
+LEPT_DLL extern PIX * pixConvertToSubpixelRGB ( PIX *pixs, l_float32 scalex, l_float32 scaley, l_int32 order );
+LEPT_DLL extern PIX * pixConvertGrayToSubpixelRGB ( PIX *pixs, l_float32 scalex, l_float32 scaley, l_int32 order );
+LEPT_DLL extern PIX * pixConvertColorToSubpixelRGB ( PIX *pixs, l_float32 scalex, l_float32 scaley, l_int32 order );
+LEPT_DLL extern PIX * pixConnCompTransform ( PIX *pixs, l_int32 connect, l_int32 depth );
+LEPT_DLL extern PIX * pixConnCompAreaTransform ( PIX *pixs, l_int32 connect );
+LEPT_DLL extern l_int32 pixConnCompIncrInit ( PIX *pixs, l_int32 conn, PIX **ppixd, PTAA **pptaa, l_int32 *pncc );
+LEPT_DLL extern l_int32 pixConnCompIncrAdd ( PIX *pixs, PTAA *ptaa, l_int32 *pncc, l_float32 x, l_float32 y, l_int32 debug );
+LEPT_DLL extern l_int32 pixGetSortedNeighborValues ( PIX *pixs, l_int32 x, l_int32 y, l_int32 conn, l_int32 **pneigh, l_int32 *pnvals );
+LEPT_DLL extern PIX * pixLocToColorTransform ( PIX *pixs );
+LEPT_DLL extern PIXTILING * pixTilingCreate ( PIX *pixs, l_int32 nx, l_int32 ny, l_int32 w, l_int32 h, l_int32 xoverlap, l_int32 yoverlap );
+LEPT_DLL extern void pixTilingDestroy ( PIXTILING **ppt );
+LEPT_DLL extern l_int32 pixTilingGetCount ( PIXTILING *pt, l_int32 *pnx, l_int32 *pny );
+LEPT_DLL extern l_int32 pixTilingGetSize ( PIXTILING *pt, l_int32 *pw, l_int32 *ph );
+LEPT_DLL extern PIX * pixTilingGetTile ( PIXTILING *pt, l_int32 i, l_int32 j );
+LEPT_DLL extern l_int32 pixTilingNoStripOnPaint ( PIXTILING *pt );
+LEPT_DLL extern l_int32 pixTilingPaintTile ( PIX *pixd, l_int32 i, l_int32 j, PIX *pixs, PIXTILING *pt );
+LEPT_DLL extern PIX * pixReadStreamPng ( FILE *fp );
+LEPT_DLL extern l_int32 readHeaderPng ( const char *filename, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_int32 freadHeaderPng ( FILE *fp, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_int32 readHeaderMemPng ( const l_uint8 *data, size_t size, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_int32 fgetPngResolution ( FILE *fp, l_int32 *pxres, l_int32 *pyres );
+LEPT_DLL extern l_int32 isPngInterlaced ( const char *filename, l_int32 *pinterlaced );
+LEPT_DLL extern l_int32 fgetPngColormapInfo ( FILE *fp, PIXCMAP **pcmap, l_int32 *ptransparency );
+LEPT_DLL extern l_int32 pixWritePng ( const char *filename, PIX *pix, l_float32 gamma );
+LEPT_DLL extern l_int32 pixWriteStreamPng ( FILE *fp, PIX *pix, l_float32 gamma );
+LEPT_DLL extern l_int32 pixSetZlibCompression ( PIX *pix, l_int32 compval );
+LEPT_DLL extern void l_pngSetReadStrip16To8 ( l_int32 flag );
+LEPT_DLL extern PIX * pixReadMemPng ( const l_uint8 *cdata, size_t size );
+LEPT_DLL extern l_int32 pixWriteMemPng ( l_uint8 **pdata, size_t *psize, PIX *pix, l_float32 gamma );
+LEPT_DLL extern PIX * pixReadStreamPnm ( FILE *fp );
+LEPT_DLL extern l_int32 readHeaderPnm ( const char *filename, l_int32 *pw, l_int32 *ph, l_int32 *pd, l_int32 *ptype, l_int32 *pbps, l_int32 *pspp );
+LEPT_DLL extern l_int32 freadHeaderPnm ( FILE *fp, l_int32 *pw, l_int32 *ph, l_int32 *pd, l_int32 *ptype, l_int32 *pbps, l_int32 *pspp );
+LEPT_DLL extern l_int32 pixWriteStreamPnm ( FILE *fp, PIX *pix );
+LEPT_DLL extern l_int32 pixWriteStreamAsciiPnm ( FILE *fp, PIX *pix );
+LEPT_DLL extern PIX * pixReadMemPnm ( const l_uint8 *cdata, size_t size );
+LEPT_DLL extern l_int32 readHeaderMemPnm ( const l_uint8 *cdata, size_t size, l_int32 *pw, l_int32 *ph, l_int32 *pd, l_int32 *ptype, l_int32 *pbps, l_int32 *pspp );
+LEPT_DLL extern l_int32 pixWriteMemPnm ( l_uint8 **pdata, size_t *psize, PIX *pix );
+LEPT_DLL extern PIX * pixProjectiveSampledPta ( PIX *pixs, PTA *ptad, PTA *ptas, l_int32 incolor );
+LEPT_DLL extern PIX * pixProjectiveSampled ( PIX *pixs, l_float32 *vc, l_int32 incolor );
+LEPT_DLL extern PIX * pixProjectivePta ( PIX *pixs, PTA *ptad, PTA *ptas, l_int32 incolor );
+LEPT_DLL extern PIX * pixProjective ( PIX *pixs, l_float32 *vc, l_int32 incolor );
+LEPT_DLL extern PIX * pixProjectivePtaColor ( PIX *pixs, PTA *ptad, PTA *ptas, l_uint32 colorval );
+LEPT_DLL extern PIX * pixProjectiveColor ( PIX *pixs, l_float32 *vc, l_uint32 colorval );
+LEPT_DLL extern PIX * pixProjectivePtaGray ( PIX *pixs, PTA *ptad, PTA *ptas, l_uint8 grayval );
+LEPT_DLL extern PIX * pixProjectiveGray ( PIX *pixs, l_float32 *vc, l_uint8 grayval );
+LEPT_DLL extern PIX * pixProjectivePtaWithAlpha ( PIX *pixs, PTA *ptad, PTA *ptas, PIX *pixg, l_float32 fract, l_int32 border );
+LEPT_DLL extern l_int32 getProjectiveXformCoeffs ( PTA *ptas, PTA *ptad, l_float32 **pvc );
+LEPT_DLL extern l_int32 projectiveXformSampledPt ( l_float32 *vc, l_int32 x, l_int32 y, l_int32 *pxp, l_int32 *pyp );
+LEPT_DLL extern l_int32 projectiveXformPt ( l_float32 *vc, l_int32 x, l_int32 y, l_float32 *pxp, l_float32 *pyp );
+LEPT_DLL extern l_int32 convertFilesToPS ( const char *dirin, const char *substr, l_int32 res, const char *fileout );
+LEPT_DLL extern l_int32 sarrayConvertFilesToPS ( SARRAY *sa, l_int32 res, const char *fileout );
+LEPT_DLL extern l_int32 convertFilesFittedToPS ( const char *dirin, const char *substr, l_float32 xpts, l_float32 ypts, const char *fileout );
+LEPT_DLL extern l_int32 sarrayConvertFilesFittedToPS ( SARRAY *sa, l_float32 xpts, l_float32 ypts, const char *fileout );
+LEPT_DLL extern l_int32 writeImageCompressedToPSFile ( const char *filein, const char *fileout, l_int32 res, l_int32 *pfirstfile, l_int32 *pindex );
+LEPT_DLL extern l_int32 convertSegmentedPagesToPS ( const char *pagedir, const char *pagestr, l_int32 page_numpre, const char *maskdir, const char *maskstr, l_int32 mask_numpre, l_int32 numpost, l_int32 maxnum, l_float32 textscale, l_float32 imagescale, l_int32 threshold, const char *fileout );
+LEPT_DLL extern l_int32 pixWriteSegmentedPageToPS ( PIX *pixs, PIX *pixm, l_float32 textscale, l_float32 imagescale, l_int32 threshold, l_int32 pageno, const char *fileout );
+LEPT_DLL extern l_int32 pixWriteMixedToPS ( PIX *pixb, PIX *pixc, l_float32 scale, l_int32 pageno, const char *fileout );
+LEPT_DLL extern l_int32 convertToPSEmbed ( const char *filein, const char *fileout, l_int32 level );
+LEPT_DLL extern l_int32 pixaWriteCompressedToPS ( PIXA *pixa, const char *fileout, l_int32 res, l_int32 level );
+LEPT_DLL extern l_int32 pixWritePSEmbed ( const char *filein, const char *fileout );
+LEPT_DLL extern l_int32 pixWriteStreamPS ( FILE *fp, PIX *pix, BOX *box, l_int32 res, l_float32 scale );
+LEPT_DLL extern char * pixWriteStringPS ( PIX *pixs, BOX *box, l_int32 res, l_float32 scale );
+LEPT_DLL extern char * generateUncompressedPS ( char *hexdata, l_int32 w, l_int32 h, l_int32 d, l_int32 psbpl, l_int32 bps, l_float32 xpt, l_float32 ypt, l_float32 wpt, l_float32 hpt, l_int32 boxflag );
+LEPT_DLL extern void getScaledParametersPS ( BOX *box, l_int32 wpix, l_int32 hpix, l_int32 res, l_float32 scale, l_float32 *pxpt, l_float32 *pypt, l_float32 *pwpt, l_float32 *phpt );
+LEPT_DLL extern void convertByteToHexAscii ( l_uint8 byteval, char *pnib1, char *pnib2 );
+LEPT_DLL extern l_int32 convertJpegToPSEmbed ( const char *filein, const char *fileout );
+LEPT_DLL extern l_int32 convertJpegToPS ( const char *filein, const char *fileout, const char *operation, l_int32 x, l_int32 y, l_int32 res, l_float32 scale, l_int32 pageno, l_int32 endpage );
+LEPT_DLL extern l_int32 convertJpegToPSString ( const char *filein, char **poutstr, l_int32 *pnbytes, l_int32 x, l_int32 y, l_int32 res, l_float32 scale, l_int32 pageno, l_int32 endpage );
+LEPT_DLL extern char * generateJpegPS ( const char *filein, L_COMP_DATA *cid, l_float32 xpt, l_float32 ypt, l_float32 wpt, l_float32 hpt, l_int32 pageno, l_int32 endpage );
+LEPT_DLL extern l_int32 convertG4ToPSEmbed ( const char *filein, const char *fileout );
+LEPT_DLL extern l_int32 convertG4ToPS ( const char *filein, const char *fileout, const char *operation, l_int32 x, l_int32 y, l_int32 res, l_float32 scale, l_int32 pageno, l_int32 maskflag, l_int32 endpage );
+LEPT_DLL extern l_int32 convertG4ToPSString ( const char *filein, char **poutstr, l_int32 *pnbytes, l_int32 x, l_int32 y, l_int32 res, l_float32 scale, l_int32 pageno, l_int32 maskflag, l_int32 endpage );
+LEPT_DLL extern char * generateG4PS ( const char *filein, L_COMP_DATA *cid, l_float32 xpt, l_float32 ypt, l_float32 wpt, l_float32 hpt, l_int32 maskflag, l_int32 pageno, l_int32 endpage );
+LEPT_DLL extern l_int32 convertTiffMultipageToPS ( const char *filein, const char *fileout, const char *tempfile, l_float32 fillfract );
+LEPT_DLL extern l_int32 convertFlateToPSEmbed ( const char *filein, const char *fileout );
+LEPT_DLL extern l_int32 convertFlateToPS ( const char *filein, const char *fileout, const char *operation, l_int32 x, l_int32 y, l_int32 res, l_float32 scale, l_int32 pageno, l_int32 endpage );
+LEPT_DLL extern l_int32 convertFlateToPSString ( const char *filein, char **poutstr, l_int32 *pnbytes, l_int32 x, l_int32 y, l_int32 res, l_float32 scale, l_int32 pageno, l_int32 endpage );
+LEPT_DLL extern char * generateFlatePS ( const char *filein, L_COMP_DATA *cid, l_float32 xpt, l_float32 ypt, l_float32 wpt, l_float32 hpt, l_int32 pageno, l_int32 endpage );
+LEPT_DLL extern l_int32 pixWriteMemPS ( l_uint8 **pdata, size_t *psize, PIX *pix, BOX *box, l_int32 res, l_float32 scale );
+LEPT_DLL extern l_int32 getResLetterPage ( l_int32 w, l_int32 h, l_float32 fillfract );
+LEPT_DLL extern l_int32 getResA4Page ( l_int32 w, l_int32 h, l_float32 fillfract );
+LEPT_DLL extern void l_psWriteBoundingBox ( l_int32 flag );
+LEPT_DLL extern PTA * ptaCreate ( l_int32 n );
+LEPT_DLL extern PTA * ptaCreateFromNuma ( NUMA *nax, NUMA *nay );
+LEPT_DLL extern void ptaDestroy ( PTA **ppta );
+LEPT_DLL extern PTA * ptaCopy ( PTA *pta );
+LEPT_DLL extern PTA * ptaCopyRange ( PTA *ptas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern PTA * ptaClone ( PTA *pta );
+LEPT_DLL extern l_int32 ptaEmpty ( PTA *pta );
+LEPT_DLL extern l_int32 ptaAddPt ( PTA *pta, l_float32 x, l_float32 y );
+LEPT_DLL extern l_int32 ptaInsertPt ( PTA *pta, l_int32 index, l_int32 x, l_int32 y );
+LEPT_DLL extern l_int32 ptaRemovePt ( PTA *pta, l_int32 index );
+LEPT_DLL extern l_int32 ptaGetRefcount ( PTA *pta );
+LEPT_DLL extern l_int32 ptaChangeRefcount ( PTA *pta, l_int32 delta );
+LEPT_DLL extern l_int32 ptaGetCount ( PTA *pta );
+LEPT_DLL extern l_int32 ptaGetPt ( PTA *pta, l_int32 index, l_float32 *px, l_float32 *py );
+LEPT_DLL extern l_int32 ptaGetIPt ( PTA *pta, l_int32 index, l_int32 *px, l_int32 *py );
+LEPT_DLL extern l_int32 ptaSetPt ( PTA *pta, l_int32 index, l_float32 x, l_float32 y );
+LEPT_DLL extern l_int32 ptaGetArrays ( PTA *pta, NUMA **pnax, NUMA **pnay );
+LEPT_DLL extern PTA * ptaRead ( const char *filename );
+LEPT_DLL extern PTA * ptaReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 ptaWrite ( const char *filename, PTA *pta, l_int32 type );
+LEPT_DLL extern l_int32 ptaWriteStream ( FILE *fp, PTA *pta, l_int32 type );
+LEPT_DLL extern PTAA * ptaaCreate ( l_int32 n );
+LEPT_DLL extern void ptaaDestroy ( PTAA **pptaa );
+LEPT_DLL extern l_int32 ptaaAddPta ( PTAA *ptaa, PTA *pta, l_int32 copyflag );
+LEPT_DLL extern l_int32 ptaaGetCount ( PTAA *ptaa );
+LEPT_DLL extern PTA * ptaaGetPta ( PTAA *ptaa, l_int32 index, l_int32 accessflag );
+LEPT_DLL extern l_int32 ptaaGetPt ( PTAA *ptaa, l_int32 ipta, l_int32 jpt, l_float32 *px, l_float32 *py );
+LEPT_DLL extern l_int32 ptaaInitFull ( PTAA *ptaa, PTA *pta );
+LEPT_DLL extern l_int32 ptaaReplacePta ( PTAA *ptaa, l_int32 index, PTA *pta );
+LEPT_DLL extern l_int32 ptaaAddPt ( PTAA *ptaa, l_int32 ipta, l_float32 x, l_float32 y );
+LEPT_DLL extern l_int32 ptaaTruncate ( PTAA *ptaa );
+LEPT_DLL extern PTAA * ptaaRead ( const char *filename );
+LEPT_DLL extern PTAA * ptaaReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 ptaaWrite ( const char *filename, PTAA *ptaa, l_int32 type );
+LEPT_DLL extern l_int32 ptaaWriteStream ( FILE *fp, PTAA *ptaa, l_int32 type );
+LEPT_DLL extern PTA * ptaSubsample ( PTA *ptas, l_int32 subfactor );
+LEPT_DLL extern l_int32 ptaJoin ( PTA *ptad, PTA *ptas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern l_int32 ptaaJoin ( PTAA *ptaad, PTAA *ptaas, l_int32 istart, l_int32 iend );
+LEPT_DLL extern PTA * ptaReverse ( PTA *ptas, l_int32 type );
+LEPT_DLL extern PTA * ptaTranspose ( PTA *ptas );
+LEPT_DLL extern PTA * ptaCyclicPerm ( PTA *ptas, l_int32 xs, l_int32 ys );
+LEPT_DLL extern PTA * ptaSort ( PTA *ptas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex );
+LEPT_DLL extern l_int32 ptaGetSortIndex ( PTA *ptas, l_int32 sorttype, l_int32 sortorder, NUMA **pnaindex );
+LEPT_DLL extern PTA * ptaSortByIndex ( PTA *ptas, NUMA *naindex );
+LEPT_DLL extern PTAA * ptaaSortByIndex ( PTAA *ptaas, NUMA *naindex );
+LEPT_DLL extern PTA * ptaUnionByAset ( PTA *pta1, PTA *pta2 );
+LEPT_DLL extern PTA * ptaRemoveDupsByAset ( PTA *ptas );
+LEPT_DLL extern PTA * ptaIntersectionByAset ( PTA *pta1, PTA *pta2 );
+LEPT_DLL extern L_ASET * l_asetCreateFromPta ( PTA *pta );
+LEPT_DLL extern PTA * ptaUnionByHash ( PTA *pta1, PTA *pta2 );
+LEPT_DLL extern l_int32 ptaRemoveDupsByHash ( PTA *ptas, PTA **pptad, L_DNAHASH **pdahash );
+LEPT_DLL extern PTA * ptaIntersectionByHash ( PTA *pta1, PTA *pta2 );
+LEPT_DLL extern l_int32 ptaFindPtByHash ( PTA *pta, L_DNAHASH *dahash, l_int32 x, l_int32 y, l_int32 *pindex );
+LEPT_DLL extern L_DNAHASH * l_dnaHashCreateFromPta ( PTA *pta );
+LEPT_DLL extern BOX * ptaGetBoundingRegion ( PTA *pta );
+LEPT_DLL extern l_int32 ptaGetRange ( PTA *pta, l_float32 *pminx, l_float32 *pmaxx, l_float32 *pminy, l_float32 *pmaxy );
+LEPT_DLL extern PTA * ptaGetInsideBox ( PTA *ptas, BOX *box );
+LEPT_DLL extern PTA * pixFindCornerPixels ( PIX *pixs );
+LEPT_DLL extern l_int32 ptaContainsPt ( PTA *pta, l_int32 x, l_int32 y );
+LEPT_DLL extern l_int32 ptaTestIntersection ( PTA *pta1, PTA *pta2 );
+LEPT_DLL extern PTA * ptaTransform ( PTA *ptas, l_int32 shiftx, l_int32 shifty, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern l_int32 ptaPtInsidePolygon ( PTA *pta, l_float32 x, l_float32 y, l_int32 *pinside );
+LEPT_DLL extern l_float32 l_angleBetweenVectors ( l_float32 x1, l_float32 y1, l_float32 x2, l_float32 y2 );
+LEPT_DLL extern l_int32 ptaGetLinearLSF ( PTA *pta, l_float32 *pa, l_float32 *pb, NUMA **pnafit );
+LEPT_DLL extern l_int32 ptaGetQuadraticLSF ( PTA *pta, l_float32 *pa, l_float32 *pb, l_float32 *pc, NUMA **pnafit );
+LEPT_DLL extern l_int32 ptaGetCubicLSF ( PTA *pta, l_float32 *pa, l_float32 *pb, l_float32 *pc, l_float32 *pd, NUMA **pnafit );
+LEPT_DLL extern l_int32 ptaGetQuarticLSF ( PTA *pta, l_float32 *pa, l_float32 *pb, l_float32 *pc, l_float32 *pd, l_float32 *pe, NUMA **pnafit );
+LEPT_DLL extern l_int32 ptaNoisyLinearLSF ( PTA *pta, l_float32 factor, PTA **pptad, l_float32 *pa, l_float32 *pb, l_float32 *pmederr, NUMA **pnafit );
+LEPT_DLL extern l_int32 ptaNoisyQuadraticLSF ( PTA *pta, l_float32 factor, PTA **pptad, l_float32 *pa, l_float32 *pb, l_float32 *pc, l_float32 *pmederr, NUMA **pnafit );
+LEPT_DLL extern l_int32 applyLinearFit ( l_float32 a, l_float32 b, l_float32 x, l_float32 *py );
+LEPT_DLL extern l_int32 applyQuadraticFit ( l_float32 a, l_float32 b, l_float32 c, l_float32 x, l_float32 *py );
+LEPT_DLL extern l_int32 applyCubicFit ( l_float32 a, l_float32 b, l_float32 c, l_float32 d, l_float32 x, l_float32 *py );
+LEPT_DLL extern l_int32 applyQuarticFit ( l_float32 a, l_float32 b, l_float32 c, l_float32 d, l_float32 e, l_float32 x, l_float32 *py );
+LEPT_DLL extern l_int32 pixPlotAlongPta ( PIX *pixs, PTA *pta, l_int32 outformat, const char *title );
+LEPT_DLL extern PTA * ptaGetPixelsFromPix ( PIX *pixs, BOX *box );
+LEPT_DLL extern PIX * pixGenerateFromPta ( PTA *pta, l_int32 w, l_int32 h );
+LEPT_DLL extern PTA * ptaGetBoundaryPixels ( PIX *pixs, l_int32 type );
+LEPT_DLL extern PTAA * ptaaGetBoundaryPixels ( PIX *pixs, l_int32 type, l_int32 connectivity, BOXA **pboxa, PIXA **ppixa );
+LEPT_DLL extern PTAA * ptaaIndexLabelledPixels ( PIX *pixs, l_int32 *pncc );
+LEPT_DLL extern PTA * ptaGetNeighborPixLocs ( PIX *pixs, l_int32 x, l_int32 y, l_int32 conn );
+LEPT_DLL extern PIX * pixDisplayPta ( PIX *pixd, PIX *pixs, PTA *pta );
+LEPT_DLL extern PIX * pixDisplayPtaaPattern ( PIX *pixd, PIX *pixs, PTAA *ptaa, PIX *pixp, l_int32 cx, l_int32 cy );
+LEPT_DLL extern PIX * pixDisplayPtaPattern ( PIX *pixd, PIX *pixs, PTA *pta, PIX *pixp, l_int32 cx, l_int32 cy, l_uint32 color );
+LEPT_DLL extern PTA * ptaReplicatePattern ( PTA *ptas, PIX *pixp, PTA *ptap, l_int32 cx, l_int32 cy, l_int32 w, l_int32 h );
+LEPT_DLL extern PIX * pixDisplayPtaa ( PIX *pixs, PTAA *ptaa );
+LEPT_DLL extern L_PTRA * ptraCreate ( l_int32 n );
+LEPT_DLL extern void ptraDestroy ( L_PTRA **ppa, l_int32 freeflag, l_int32 warnflag );
+LEPT_DLL extern l_int32 ptraAdd ( L_PTRA *pa, void *item );
+LEPT_DLL extern l_int32 ptraInsert ( L_PTRA *pa, l_int32 index, void *item, l_int32 shiftflag );
+LEPT_DLL extern void * ptraRemove ( L_PTRA *pa, l_int32 index, l_int32 flag );
+LEPT_DLL extern void * ptraRemoveLast ( L_PTRA *pa );
+LEPT_DLL extern void * ptraReplace ( L_PTRA *pa, l_int32 index, void *item, l_int32 freeflag );
+LEPT_DLL extern l_int32 ptraSwap ( L_PTRA *pa, l_int32 index1, l_int32 index2 );
+LEPT_DLL extern l_int32 ptraCompactArray ( L_PTRA *pa );
+LEPT_DLL extern l_int32 ptraReverse ( L_PTRA *pa );
+LEPT_DLL extern l_int32 ptraJoin ( L_PTRA *pa1, L_PTRA *pa2 );
+LEPT_DLL extern l_int32 ptraGetMaxIndex ( L_PTRA *pa, l_int32 *pmaxindex );
+LEPT_DLL extern l_int32 ptraGetActualCount ( L_PTRA *pa, l_int32 *pcount );
+LEPT_DLL extern void * ptraGetPtrToItem ( L_PTRA *pa, l_int32 index );
+LEPT_DLL extern L_PTRAA * ptraaCreate ( l_int32 n );
+LEPT_DLL extern void ptraaDestroy ( L_PTRAA **ppaa, l_int32 freeflag, l_int32 warnflag );
+LEPT_DLL extern l_int32 ptraaGetSize ( L_PTRAA *paa, l_int32 *psize );
+LEPT_DLL extern l_int32 ptraaInsertPtra ( L_PTRAA *paa, l_int32 index, L_PTRA *pa );
+LEPT_DLL extern L_PTRA * ptraaGetPtra ( L_PTRAA *paa, l_int32 index, l_int32 accessflag );
+LEPT_DLL extern L_PTRA * ptraaFlattenToPtra ( L_PTRAA *paa );
+LEPT_DLL extern l_int32 pixQuadtreeMean ( PIX *pixs, l_int32 nlevels, PIX *pix_ma, FPIXA **pfpixa );
+LEPT_DLL extern l_int32 pixQuadtreeVariance ( PIX *pixs, l_int32 nlevels, PIX *pix_ma, DPIX *dpix_msa, FPIXA **pfpixa_v, FPIXA **pfpixa_rv );
+LEPT_DLL extern l_int32 pixMeanInRectangle ( PIX *pixs, BOX *box, PIX *pixma, l_float32 *pval );
+LEPT_DLL extern l_int32 pixVarianceInRectangle ( PIX *pixs, BOX *box, PIX *pix_ma, DPIX *dpix_msa, l_float32 *pvar, l_float32 *prvar );
+LEPT_DLL extern BOXAA * boxaaQuadtreeRegions ( l_int32 w, l_int32 h, l_int32 nlevels );
+LEPT_DLL extern l_int32 quadtreeGetParent ( FPIXA *fpixa, l_int32 level, l_int32 x, l_int32 y, l_float32 *pval );
+LEPT_DLL extern l_int32 quadtreeGetChildren ( FPIXA *fpixa, l_int32 level, l_int32 x, l_int32 y, l_float32 *pval00, l_float32 *pval10, l_float32 *pval01, l_float32 *pval11 );
+LEPT_DLL extern l_int32 quadtreeMaxLevels ( l_int32 w, l_int32 h );
+LEPT_DLL extern PIX * fpixaDisplayQuadtree ( FPIXA *fpixa, l_int32 factor, const char *fontdir );
+LEPT_DLL extern L_QUEUE * lqueueCreate ( l_int32 nalloc );
+LEPT_DLL extern void lqueueDestroy ( L_QUEUE **plq, l_int32 freeflag );
+LEPT_DLL extern l_int32 lqueueAdd ( L_QUEUE *lq, void *item );
+LEPT_DLL extern void * lqueueRemove ( L_QUEUE *lq );
+LEPT_DLL extern l_int32 lqueueGetCount ( L_QUEUE *lq );
+LEPT_DLL extern l_int32 lqueuePrint ( FILE *fp, L_QUEUE *lq );
+LEPT_DLL extern PIX * pixRankFilter ( PIX *pixs, l_int32 wf, l_int32 hf, l_float32 rank );
+LEPT_DLL extern PIX * pixRankFilterRGB ( PIX *pixs, l_int32 wf, l_int32 hf, l_float32 rank );
+LEPT_DLL extern PIX * pixRankFilterGray ( PIX *pixs, l_int32 wf, l_int32 hf, l_float32 rank );
+LEPT_DLL extern PIX * pixMedianFilter ( PIX *pixs, l_int32 wf, l_int32 hf );
+LEPT_DLL extern PIX * pixRankFilterWithScaling ( PIX *pixs, l_int32 wf, l_int32 hf, l_float32 rank, l_float32 scalefactor );
+LEPT_DLL extern L_RBTREE * l_rbtreeCreate ( l_int32 keytype );
+LEPT_DLL extern RB_TYPE * l_rbtreeLookup ( L_RBTREE *t, RB_TYPE key );
+LEPT_DLL extern void l_rbtreeInsert ( L_RBTREE *t, RB_TYPE key, RB_TYPE value );
+LEPT_DLL extern void l_rbtreeDelete ( L_RBTREE *t, RB_TYPE key );
+LEPT_DLL extern void l_rbtreeDestroy ( L_RBTREE **pt );
+LEPT_DLL extern L_RBTREE_NODE * l_rbtreeGetFirst ( L_RBTREE *t );
+LEPT_DLL extern L_RBTREE_NODE * l_rbtreeGetNext ( L_RBTREE_NODE *n );
+LEPT_DLL extern L_RBTREE_NODE * l_rbtreeGetLast ( L_RBTREE *t );
+LEPT_DLL extern L_RBTREE_NODE * l_rbtreeGetPrev ( L_RBTREE_NODE *n );
+LEPT_DLL extern l_int32 l_rbtreeGetCount ( L_RBTREE *t );
+LEPT_DLL extern void l_rbtreePrint ( FILE *fp, L_RBTREE *t );
+LEPT_DLL extern l_int32 l_compareKeys ( l_int32 keytype, RB_TYPE left, RB_TYPE right );
+LEPT_DLL extern SARRAY * pixProcessBarcodes ( PIX *pixs, l_int32 format, l_int32 method, SARRAY **psaw, l_int32 debugflag );
+LEPT_DLL extern PIXA * pixExtractBarcodes ( PIX *pixs, l_int32 debugflag );
+LEPT_DLL extern SARRAY * pixReadBarcodes ( PIXA *pixa, l_int32 format, l_int32 method, SARRAY **psaw, l_int32 debugflag );
+LEPT_DLL extern NUMA * pixReadBarcodeWidths ( PIX *pixs, l_int32 method, l_int32 debugflag );
+LEPT_DLL extern BOXA * pixLocateBarcodes ( PIX *pixs, l_int32 thresh, PIX **ppixb, PIX **ppixm );
+LEPT_DLL extern PIX * pixDeskewBarcode ( PIX *pixs, PIX *pixb, BOX *box, l_int32 margin, l_int32 threshold, l_float32 *pangle, l_float32 *pconf );
+LEPT_DLL extern NUMA * pixExtractBarcodeWidths1 ( PIX *pixs, l_float32 thresh, l_float32 binfract, NUMA **pnaehist, NUMA **pnaohist, l_int32 debugflag );
+LEPT_DLL extern NUMA * pixExtractBarcodeWidths2 ( PIX *pixs, l_float32 thresh, l_float32 *pwidth, NUMA **pnac, l_int32 debugflag );
+LEPT_DLL extern NUMA * pixExtractBarcodeCrossings ( PIX *pixs, l_float32 thresh, l_int32 debugflag );
+LEPT_DLL extern NUMA * numaQuantizeCrossingsByWidth ( NUMA *nas, l_float32 binfract, NUMA **pnaehist, NUMA **pnaohist, l_int32 debugflag );
+LEPT_DLL extern NUMA * numaQuantizeCrossingsByWindow ( NUMA *nas, l_float32 ratio, l_float32 *pwidth, l_float32 *pfirstloc, NUMA **pnac, l_int32 debugflag );
+LEPT_DLL extern PIXA * pixaReadFiles ( const char *dirname, const char *substr );
+LEPT_DLL extern PIXA * pixaReadFilesSA ( SARRAY *sa );
+LEPT_DLL extern PIX * pixRead ( const char *filename );
+LEPT_DLL extern PIX * pixReadWithHint ( const char *filename, l_int32 hint );
+LEPT_DLL extern PIX * pixReadIndexed ( SARRAY *sa, l_int32 index );
+LEPT_DLL extern PIX * pixReadStream ( FILE *fp, l_int32 hint );
+LEPT_DLL extern l_int32 pixReadHeader ( const char *filename, l_int32 *pformat, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_int32 findFileFormat ( const char *filename, l_int32 *pformat );
+LEPT_DLL extern l_int32 findFileFormatStream ( FILE *fp, l_int32 *pformat );
+LEPT_DLL extern l_int32 findFileFormatBuffer ( const l_uint8 *buf, l_int32 *pformat );
+LEPT_DLL extern l_int32 fileFormatIsTiff ( FILE *fp );
+LEPT_DLL extern PIX * pixReadMem ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_int32 pixReadHeaderMem ( const l_uint8 *data, size_t size, l_int32 *pformat, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_int32 ioFormatTest ( const char *filename );
+LEPT_DLL extern L_RECOGA * recogaCreateFromRecog ( L_RECOG *recog );
+LEPT_DLL extern L_RECOGA * recogaCreateFromPixaa ( PIXAA *paa, l_int32 scalew, l_int32 scaleh, l_int32 templ_type, l_int32 threshold, l_int32 maxyshift );
+LEPT_DLL extern L_RECOGA * recogaCreate ( l_int32 n );
+LEPT_DLL extern void recogaDestroy ( L_RECOGA **precoga );
+LEPT_DLL extern l_int32 recogaAddRecog ( L_RECOGA *recoga, L_RECOG *recog );
+LEPT_DLL extern l_int32 recogReplaceInRecoga ( L_RECOG **precog1, L_RECOG *recog2 );
+LEPT_DLL extern L_RECOG * recogaGetRecog ( L_RECOGA *recoga, l_int32 index );
+LEPT_DLL extern l_int32 recogaGetCount ( L_RECOGA *recoga );
+LEPT_DLL extern l_int32 recogGetCount ( L_RECOG *recog );
+LEPT_DLL extern l_int32 recogGetIndex ( L_RECOG *recog, l_int32 *pindex );
+LEPT_DLL extern L_RECOGA * recogGetParent ( L_RECOG *recog );
+LEPT_DLL extern l_int32 recogSetBootflag ( L_RECOG *recog );
+LEPT_DLL extern L_RECOG * recogCreateFromRecog ( L_RECOG *recs, l_int32 scalew, l_int32 scaleh, l_int32 templ_type, l_int32 threshold, l_int32 maxyshift );
+LEPT_DLL extern L_RECOG * recogCreateFromPixa ( PIXA *pixa, l_int32 scalew, l_int32 scaleh, l_int32 templ_type, l_int32 threshold, l_int32 maxyshift );
+LEPT_DLL extern L_RECOG * recogCreate ( l_int32 scalew, l_int32 scaleh, l_int32 templ_type, l_int32 threshold, l_int32 maxyshift );
+LEPT_DLL extern void recogDestroy ( L_RECOG **precog );
+LEPT_DLL extern l_int32 recogAppend ( L_RECOG *recog1, L_RECOG *recog2 );
+LEPT_DLL extern l_int32 recogGetClassIndex ( L_RECOG *recog, l_int32 val, char *text, l_int32 *pindex );
+LEPT_DLL extern l_int32 recogStringToIndex ( L_RECOG *recog, char *text, l_int32 *pindex );
+LEPT_DLL extern l_int32 recogGetClassString ( L_RECOG *recog, l_int32 index, char **pcharstr );
+LEPT_DLL extern l_int32 l_convertCharstrToInt ( const char *str, l_int32 *pval );
+LEPT_DLL extern L_RECOGA * recogaRead ( const char *filename );
+LEPT_DLL extern L_RECOGA * recogaReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 recogaWrite ( const char *filename, L_RECOGA *recoga );
+LEPT_DLL extern l_int32 recogaWriteStream ( FILE *fp, L_RECOGA *recoga, const char *filename );
+LEPT_DLL extern l_int32 recogaWritePixaa ( const char *filename, L_RECOGA *recoga );
+LEPT_DLL extern L_RECOG * recogRead ( const char *filename );
+LEPT_DLL extern L_RECOG * recogReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 recogWrite ( const char *filename, L_RECOG *recog );
+LEPT_DLL extern l_int32 recogWriteStream ( FILE *fp, L_RECOG *recog, const char *filename );
+LEPT_DLL extern l_int32 recogWritePixa ( const char *filename, L_RECOG *recog );
+LEPT_DLL extern l_int32 recogDecode ( L_RECOG *recog, PIX *pixs, l_int32 nlevels, PIX **ppixdb );
+LEPT_DLL extern l_int32 recogMakeDecodingArrays ( L_RECOG *recog, PIX *pixs, l_int32 debug );
+LEPT_DLL extern l_int32 recogRunViterbi ( L_RECOG *recog, PIX **ppixdb );
+LEPT_DLL extern l_int32 recogCreateDid ( L_RECOG *recog, PIX *pixs );
+LEPT_DLL extern l_int32 recogDestroyDid ( L_RECOG *recog );
+LEPT_DLL extern l_int32 recogDidExists ( L_RECOG *recog );
+LEPT_DLL extern L_RDID * recogGetDid ( L_RECOG *recog );
+LEPT_DLL extern l_int32 recogSetChannelParams ( L_RECOG *recog, l_int32 nlevels );
+LEPT_DLL extern l_int32 recogaIdentifyMultiple ( L_RECOGA *recoga, PIX *pixs, l_int32 nitems, l_int32 minw, l_int32 minh, BOXA **pboxa, PIXA **ppixa, PIX **ppixdb, l_int32 debugsplit );
+LEPT_DLL extern l_int32 recogSplitIntoCharacters ( L_RECOG *recog, PIX *pixs, l_int32 minw, l_int32 minh, BOXA **pboxa, PIXA **ppixa, NUMA **pnaid, l_int32 debug );
+LEPT_DLL extern l_int32 recogCorrelationBestRow ( L_RECOG *recog, PIX *pixs, BOXA **pboxa, NUMA **pnascore, NUMA **pnaindex, SARRAY **psachar, l_int32 debug );
+LEPT_DLL extern l_int32 recogCorrelationBestChar ( L_RECOG *recog, PIX *pixs, BOX **pbox, l_float32 *pscore, l_int32 *pindex, char **pcharstr, PIX **ppixdb );
+LEPT_DLL extern l_int32 recogaIdentifyPixa ( L_RECOGA *recoga, PIXA *pixa, NUMA *naid, PIX **ppixdb );
+LEPT_DLL extern l_int32 recogIdentifyPixa ( L_RECOG *recog, PIXA *pixa, NUMA *naid, PIX **ppixdb );
+LEPT_DLL extern l_int32 recogIdentifyPix ( L_RECOG *recog, PIX *pixs, PIX **ppixdb );
+LEPT_DLL extern l_int32 recogSkipIdentify ( L_RECOG *recog );
+LEPT_DLL extern void rchaDestroy ( L_RCHA **prcha );
+LEPT_DLL extern void rchDestroy ( L_RCH **prch );
+LEPT_DLL extern l_int32 rchaExtract ( L_RCHA *rcha, NUMA **pnaindex, NUMA **pnascore, SARRAY **psatext, NUMA **pnasample, NUMA **pnaxloc, NUMA **pnayloc, NUMA **pnawidth );
+LEPT_DLL extern l_int32 rchExtract ( L_RCH *rch, l_int32 *pindex, l_float32 *pscore, char **ptext, l_int32 *psample, l_int32 *pxloc, l_int32 *pyloc, l_int32 *pwidth );
+LEPT_DLL extern PIX * recogProcessToIdentify ( L_RECOG *recog, PIX *pixs, l_int32 pad );
+LEPT_DLL extern PIX * recogPreSplittingFilter ( L_RECOG *recog, PIX *pixs, l_float32 maxasp, l_float32 minaf, l_int32 debug );
+LEPT_DLL extern l_int32 recogSplittingFilter ( L_RECOG *recog, PIX *pixs, l_float32 maxasp, l_float32 minaf, l_int32 *premove, l_int32 debug );
+LEPT_DLL extern SARRAY * recogaExtractNumbers ( L_RECOGA *recoga, BOXA *boxas, l_float32 scorethresh, l_int32 spacethresh, BOXAA **pbaa, NUMAA **pnaa );
+LEPT_DLL extern l_int32 recogSetTemplateType ( L_RECOG *recog, l_int32 templ_type );
+LEPT_DLL extern l_int32 recogSetScaling ( L_RECOG *recog, l_int32 scalew, l_int32 scaleh );
+LEPT_DLL extern l_int32 recogTrainLabelled ( L_RECOG *recog, PIX *pixs, BOX *box, char *text, l_int32 multflag, l_int32 debug );
+LEPT_DLL extern l_int32 recogProcessMultLabelled ( L_RECOG *recog, PIX *pixs, BOX *box, char *text, PIXA **ppixa, l_int32 debug );
+LEPT_DLL extern l_int32 recogProcessSingleLabelled ( L_RECOG *recog, PIX *pixs, BOX *box, char *text, PIXA **ppixa );
+LEPT_DLL extern l_int32 recogAddSamples ( L_RECOG *recog, PIXA *pixa, l_int32 classindex, l_int32 debug );
+LEPT_DLL extern PIX * recogScaleCharacter ( L_RECOG *recog, PIX *pixs );
+LEPT_DLL extern l_int32 recogAverageSamples ( L_RECOG *recog, l_int32 debug );
+LEPT_DLL extern l_int32 pixaAccumulateSamples ( PIXA *pixa, PTA *pta, PIX **ppixd, l_float32 *px, l_float32 *py );
+LEPT_DLL extern l_int32 recogTrainingFinished ( L_RECOG *recog, l_int32 debug );
+LEPT_DLL extern l_int32 recogRemoveOutliers ( L_RECOG *recog, l_float32 targetscore, l_float32 minfract, l_int32 debug );
+LEPT_DLL extern l_int32 recogaTrainingDone ( L_RECOGA *recoga, l_int32 *pdone );
+LEPT_DLL extern l_int32 recogaFinishAveraging ( L_RECOGA *recoga );
+LEPT_DLL extern l_int32 recogTrainUnlabelled ( L_RECOG *recog, L_RECOG *recogboot, PIX *pixs, BOX *box, l_int32 singlechar, l_float32 minscore, l_int32 debug );
+LEPT_DLL extern l_int32 recogPadTrainingSet ( L_RECOG **precog, l_int32 debug );
+LEPT_DLL extern l_int32 recogBestCorrelForPadding ( L_RECOG *recog, L_RECOGA *recoga, NUMA **pnaset, NUMA **pnaindex, NUMA **pnascore, NUMA **pnasum, PIXA *pixadb );
+LEPT_DLL extern l_int32 recogCorrelAverages ( L_RECOG *recog1, L_RECOG *recog2, NUMA **pnaindex, NUMA **pnascore, PIXA *pixadb );
+LEPT_DLL extern l_int32 recogSetPadParams ( L_RECOG *recog, const char *bootdir, const char *bootpattern, const char *bootpath, l_int32 boot_iters, l_int32 type, l_int32 min_nopad, l_int32 max_afterpad, l_int32 min_samples );
+LEPT_DLL extern l_int32 recogaShowContent ( FILE *fp, L_RECOGA *recoga, l_int32 display );
+LEPT_DLL extern l_int32 recogShowContent ( FILE *fp, L_RECOG *recog, l_int32 display );
+LEPT_DLL extern l_int32 recogDebugAverages ( L_RECOG *recog, l_int32 debug );
+LEPT_DLL extern l_int32 recogShowAverageTemplates ( L_RECOG *recog );
+LEPT_DLL extern l_int32 recogShowMatchesInRange ( L_RECOG *recog, PIXA *pixa, l_float32 minscore, l_float32 maxscore, l_int32 display );
+LEPT_DLL extern PIX * recogShowMatch ( L_RECOG *recog, PIX *pix1, PIX *pix2, BOX *box, l_int32 index, l_float32 score );
+LEPT_DLL extern l_int32 recogResetBmf ( L_RECOG *recog, l_int32 size );
+LEPT_DLL extern l_int32 regTestSetup ( l_int32 argc, char **argv, L_REGPARAMS **prp );
+LEPT_DLL extern l_int32 regTestCleanup ( L_REGPARAMS *rp );
+LEPT_DLL extern l_int32 regTestCompareValues ( L_REGPARAMS *rp, l_float32 val1, l_float32 val2, l_float32 delta );
+LEPT_DLL extern l_int32 regTestCompareStrings ( L_REGPARAMS *rp, l_uint8 *string1, size_t bytes1, l_uint8 *string2, size_t bytes2 );
+LEPT_DLL extern l_int32 regTestComparePix ( L_REGPARAMS *rp, PIX *pix1, PIX *pix2 );
+LEPT_DLL extern l_int32 regTestCompareSimilarPix ( L_REGPARAMS *rp, PIX *pix1, PIX *pix2, l_int32 mindiff, l_float32 maxfract, l_int32 printstats );
+LEPT_DLL extern l_int32 regTestCheckFile ( L_REGPARAMS *rp, const char *localname );
+LEPT_DLL extern l_int32 regTestCompareFiles ( L_REGPARAMS *rp, l_int32 index1, l_int32 index2 );
+LEPT_DLL extern l_int32 regTestWritePixAndCheck ( L_REGPARAMS *rp, PIX *pix, l_int32 format );
+LEPT_DLL extern l_int32 pixRasterop ( PIX *pixd, l_int32 dx, l_int32 dy, l_int32 dw, l_int32 dh, l_int32 op, PIX *pixs, l_int32 sx, l_int32 sy );
+LEPT_DLL extern l_int32 pixRasteropVip ( PIX *pixd, l_int32 bx, l_int32 bw, l_int32 vshift, l_int32 incolor );
+LEPT_DLL extern l_int32 pixRasteropHip ( PIX *pixd, l_int32 by, l_int32 bh, l_int32 hshift, l_int32 incolor );
+LEPT_DLL extern PIX * pixTranslate ( PIX *pixd, PIX *pixs, l_int32 hshift, l_int32 vshift, l_int32 incolor );
+LEPT_DLL extern l_int32 pixRasteropIP ( PIX *pixd, l_int32 hshift, l_int32 vshift, l_int32 incolor );
+LEPT_DLL extern l_int32 pixRasteropFullImage ( PIX *pixd, PIX *pixs, l_int32 op );
+LEPT_DLL extern void rasteropVipLow ( l_uint32 *data, l_int32 pixw, l_int32 pixh, l_int32 depth, l_int32 wpl, l_int32 x, l_int32 w, l_int32 shift );
+LEPT_DLL extern void rasteropHipLow ( l_uint32 *data, l_int32 pixh, l_int32 depth, l_int32 wpl, l_int32 y, l_int32 h, l_int32 shift );
+LEPT_DLL extern void shiftDataHorizontalLow ( l_uint32 *datad, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_int32 shift );
+LEPT_DLL extern void rasteropUniLow ( l_uint32 *datad, l_int32 dpixw, l_int32 dpixh, l_int32 depth, l_int32 dwpl, l_int32 dx, l_int32 dy, l_int32 dw, l_int32 dh, l_int32 op );
+LEPT_DLL extern void rasteropLow ( l_uint32 *datad, l_int32 dpixw, l_int32 dpixh, l_int32 depth, l_int32 dwpl, l_int32 dx, l_int32 dy, l_int32 dw, l_int32 dh, l_int32 op, l_uint32 *datas, l_int32 spixw, l_int32 spixh, l_int32 swpl, l_int32 sx, l_int32 sy );
+LEPT_DLL extern PIX * pixRotate ( PIX *pixs, l_float32 angle, l_int32 type, l_int32 incolor, l_int32 width, l_int32 height );
+LEPT_DLL extern PIX * pixEmbedForRotation ( PIX *pixs, l_float32 angle, l_int32 incolor, l_int32 width, l_int32 height );
+LEPT_DLL extern PIX * pixRotateBySampling ( PIX *pixs, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 incolor );
+LEPT_DLL extern PIX * pixRotateBinaryNice ( PIX *pixs, l_float32 angle, l_int32 incolor );
+LEPT_DLL extern PIX * pixRotateWithAlpha ( PIX *pixs, l_float32 angle, PIX *pixg, l_float32 fract );
+LEPT_DLL extern PIX * pixRotateAM ( PIX *pixs, l_float32 angle, l_int32 incolor );
+LEPT_DLL extern PIX * pixRotateAMColor ( PIX *pixs, l_float32 angle, l_uint32 colorval );
+LEPT_DLL extern PIX * pixRotateAMGray ( PIX *pixs, l_float32 angle, l_uint8 grayval );
+LEPT_DLL extern PIX * pixRotateAMCorner ( PIX *pixs, l_float32 angle, l_int32 incolor );
+LEPT_DLL extern PIX * pixRotateAMColorCorner ( PIX *pixs, l_float32 angle, l_uint32 fillval );
+LEPT_DLL extern PIX * pixRotateAMGrayCorner ( PIX *pixs, l_float32 angle, l_uint8 grayval );
+LEPT_DLL extern PIX * pixRotateAMColorFast ( PIX *pixs, l_float32 angle, l_uint32 colorval );
+LEPT_DLL extern void rotateAMColorLow ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_float32 angle, l_uint32 colorval );
+LEPT_DLL extern void rotateAMGrayLow ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_float32 angle, l_uint8 grayval );
+LEPT_DLL extern void rotateAMColorCornerLow ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_float32 angle, l_uint32 colorval );
+LEPT_DLL extern void rotateAMGrayCornerLow ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_float32 angle, l_uint8 grayval );
+LEPT_DLL extern void rotateAMColorFastLow ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_float32 angle, l_uint32 colorval );
+LEPT_DLL extern PIX * pixRotateOrth ( PIX *pixs, l_int32 quads );
+LEPT_DLL extern PIX * pixRotate180 ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern PIX * pixRotate90 ( PIX *pixs, l_int32 direction );
+LEPT_DLL extern PIX * pixFlipLR ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern PIX * pixFlipTB ( PIX *pixd, PIX *pixs );
+LEPT_DLL extern PIX * pixRotateShear ( PIX *pixs, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 incolor );
+LEPT_DLL extern PIX * pixRotate2Shear ( PIX *pixs, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 incolor );
+LEPT_DLL extern PIX * pixRotate3Shear ( PIX *pixs, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 incolor );
+LEPT_DLL extern l_int32 pixRotateShearIP ( PIX *pixs, l_int32 xcen, l_int32 ycen, l_float32 angle, l_int32 incolor );
+LEPT_DLL extern PIX * pixRotateShearCenter ( PIX *pixs, l_float32 angle, l_int32 incolor );
+LEPT_DLL extern l_int32 pixRotateShearCenterIP ( PIX *pixs, l_float32 angle, l_int32 incolor );
+LEPT_DLL extern PIX * pixStrokeWidthTransform ( PIX *pixs, l_int32 color, l_int32 depth, l_int32 nangles );
+LEPT_DLL extern PIX * pixRunlengthTransform ( PIX *pixs, l_int32 color, l_int32 direction, l_int32 depth );
+LEPT_DLL extern l_int32 pixFindHorizontalRuns ( PIX *pix, l_int32 y, l_int32 *xstart, l_int32 *xend, l_int32 *pn );
+LEPT_DLL extern l_int32 pixFindVerticalRuns ( PIX *pix, l_int32 x, l_int32 *ystart, l_int32 *yend, l_int32 *pn );
+LEPT_DLL extern NUMA * pixFindMaxRuns ( PIX *pix, l_int32 direction, NUMA **pnastart );
+LEPT_DLL extern l_int32 pixFindMaxHorizontalRunOnLine ( PIX *pix, l_int32 y, l_int32 *pxstart, l_int32 *psize );
+LEPT_DLL extern l_int32 pixFindMaxVerticalRunOnLine ( PIX *pix, l_int32 x, l_int32 *pystart, l_int32 *psize );
+LEPT_DLL extern l_int32 runlengthMembershipOnLine ( l_int32 *buffer, l_int32 size, l_int32 depth, l_int32 *start, l_int32 *end, l_int32 n );
+LEPT_DLL extern l_int32 * makeMSBitLocTab ( l_int32 bitval );
+LEPT_DLL extern SARRAY * sarrayCreate ( l_int32 n );
+LEPT_DLL extern SARRAY * sarrayCreateInitialized ( l_int32 n, char *initstr );
+LEPT_DLL extern SARRAY * sarrayCreateWordsFromString ( const char *string );
+LEPT_DLL extern SARRAY * sarrayCreateLinesFromString ( const char *string, l_int32 blankflag );
+LEPT_DLL extern void sarrayDestroy ( SARRAY **psa );
+LEPT_DLL extern SARRAY * sarrayCopy ( SARRAY *sa );
+LEPT_DLL extern SARRAY * sarrayClone ( SARRAY *sa );
+LEPT_DLL extern l_int32 sarrayAddString ( SARRAY *sa, char *string, l_int32 copyflag );
+LEPT_DLL extern char * sarrayRemoveString ( SARRAY *sa, l_int32 index );
+LEPT_DLL extern l_int32 sarrayReplaceString ( SARRAY *sa, l_int32 index, char *newstr, l_int32 copyflag );
+LEPT_DLL extern l_int32 sarrayClear ( SARRAY *sa );
+LEPT_DLL extern l_int32 sarrayGetCount ( SARRAY *sa );
+LEPT_DLL extern char ** sarrayGetArray ( SARRAY *sa, l_int32 *pnalloc, l_int32 *pn );
+LEPT_DLL extern char * sarrayGetString ( SARRAY *sa, l_int32 index, l_int32 copyflag );
+LEPT_DLL extern l_int32 sarrayGetRefcount ( SARRAY *sa );
+LEPT_DLL extern l_int32 sarrayChangeRefcount ( SARRAY *sa, l_int32 delta );
+LEPT_DLL extern char * sarrayToString ( SARRAY *sa, l_int32 addnlflag );
+LEPT_DLL extern char * sarrayToStringRange ( SARRAY *sa, l_int32 first, l_int32 nstrings, l_int32 addnlflag );
+LEPT_DLL extern l_int32 sarrayJoin ( SARRAY *sa1, SARRAY *sa2 );
+LEPT_DLL extern l_int32 sarrayAppendRange ( SARRAY *sa1, SARRAY *sa2, l_int32 start, l_int32 end );
+LEPT_DLL extern l_int32 sarrayPadToSameSize ( SARRAY *sa1, SARRAY *sa2, char *padstring );
+LEPT_DLL extern SARRAY * sarrayConvertWordsToLines ( SARRAY *sa, l_int32 linesize );
+LEPT_DLL extern l_int32 sarraySplitString ( SARRAY *sa, const char *str, const char *separators );
+LEPT_DLL extern SARRAY * sarraySelectBySubstring ( SARRAY *sain, const char *substr );
+LEPT_DLL extern SARRAY * sarraySelectByRange ( SARRAY *sain, l_int32 first, l_int32 last );
+LEPT_DLL extern l_int32 sarrayParseRange ( SARRAY *sa, l_int32 start, l_int32 *pactualstart, l_int32 *pend, l_int32 *pnewstart, const char *substr, l_int32 loc );
+LEPT_DLL extern SARRAY * sarraySort ( SARRAY *saout, SARRAY *sain, l_int32 sortorder );
+LEPT_DLL extern SARRAY * sarraySortByIndex ( SARRAY *sain, NUMA *naindex );
+LEPT_DLL extern l_int32 stringCompareLexical ( const char *str1, const char *str2 );
+LEPT_DLL extern SARRAY * sarrayUnionByAset ( SARRAY *sa1, SARRAY *sa2 );
+LEPT_DLL extern SARRAY * sarrayRemoveDupsByAset ( SARRAY *sas );
+LEPT_DLL extern SARRAY * sarrayIntersectionByAset ( SARRAY *sa1, SARRAY *sa2 );
+LEPT_DLL extern L_ASET * l_asetCreateFromSarray ( SARRAY *sa );
+LEPT_DLL extern l_int32 sarrayRemoveDupsByHash ( SARRAY *sas, SARRAY **psad, L_DNAHASH **pdahash );
+LEPT_DLL extern SARRAY * sarrayIntersectionByHash ( SARRAY *sa1, SARRAY *sa2 );
+LEPT_DLL extern l_int32 sarrayFindStringByHash ( SARRAY *sa, L_DNAHASH *dahash, const char *str, l_int32 *pindex );
+LEPT_DLL extern L_DNAHASH * l_dnaHashCreateFromSarray ( SARRAY *sa );
+LEPT_DLL extern SARRAY * sarrayRead ( const char *filename );
+LEPT_DLL extern SARRAY * sarrayReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 sarrayWrite ( const char *filename, SARRAY *sa );
+LEPT_DLL extern l_int32 sarrayWriteStream ( FILE *fp, SARRAY *sa );
+LEPT_DLL extern l_int32 sarrayAppend ( const char *filename, SARRAY *sa );
+LEPT_DLL extern SARRAY * getNumberedPathnamesInDirectory ( const char *dirname, const char *substr, l_int32 numpre, l_int32 numpost, l_int32 maxnum );
+LEPT_DLL extern SARRAY * getSortedPathnamesInDirectory ( const char *dirname, const char *substr, l_int32 first, l_int32 nfiles );
+LEPT_DLL extern SARRAY * convertSortedToNumberedPathnames ( SARRAY *sa, l_int32 numpre, l_int32 numpost, l_int32 maxnum );
+LEPT_DLL extern SARRAY * getFilenamesInDirectory ( const char *dirname );
+LEPT_DLL extern PIX * pixScale ( PIX *pixs, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PIX * pixScaleToSize ( PIX *pixs, l_int32 wd, l_int32 hd );
+LEPT_DLL extern PIX * pixScaleGeneral ( PIX *pixs, l_float32 scalex, l_float32 scaley, l_float32 sharpfract, l_int32 sharpwidth );
+LEPT_DLL extern PIX * pixScaleLI ( PIX *pixs, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PIX * pixScaleColorLI ( PIX *pixs, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PIX * pixScaleColor2xLI ( PIX *pixs );
+LEPT_DLL extern PIX * pixScaleColor4xLI ( PIX *pixs );
+LEPT_DLL extern PIX * pixScaleGrayLI ( PIX *pixs, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PIX * pixScaleGray2xLI ( PIX *pixs );
+LEPT_DLL extern PIX * pixScaleGray4xLI ( PIX *pixs );
+LEPT_DLL extern PIX * pixScaleBySampling ( PIX *pixs, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PIX * pixScaleBySamplingToSize ( PIX *pixs, l_int32 wd, l_int32 hd );
+LEPT_DLL extern PIX * pixScaleByIntSampling ( PIX *pixs, l_int32 factor );
+LEPT_DLL extern PIX * pixScaleRGBToGrayFast ( PIX *pixs, l_int32 factor, l_int32 color );
+LEPT_DLL extern PIX * pixScaleRGBToBinaryFast ( PIX *pixs, l_int32 factor, l_int32 thresh );
+LEPT_DLL extern PIX * pixScaleGrayToBinaryFast ( PIX *pixs, l_int32 factor, l_int32 thresh );
+LEPT_DLL extern PIX * pixScaleSmooth ( PIX *pix, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PIX * pixScaleRGBToGray2 ( PIX *pixs, l_float32 rwt, l_float32 gwt, l_float32 bwt );
+LEPT_DLL extern PIX * pixScaleAreaMap ( PIX *pix, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PIX * pixScaleAreaMap2 ( PIX *pix );
+LEPT_DLL extern PIX * pixScaleBinary ( PIX *pixs, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PIX * pixScaleToGray ( PIX *pixs, l_float32 scalefactor );
+LEPT_DLL extern PIX * pixScaleToGrayFast ( PIX *pixs, l_float32 scalefactor );
+LEPT_DLL extern PIX * pixScaleToGray2 ( PIX *pixs );
+LEPT_DLL extern PIX * pixScaleToGray3 ( PIX *pixs );
+LEPT_DLL extern PIX * pixScaleToGray4 ( PIX *pixs );
+LEPT_DLL extern PIX * pixScaleToGray6 ( PIX *pixs );
+LEPT_DLL extern PIX * pixScaleToGray8 ( PIX *pixs );
+LEPT_DLL extern PIX * pixScaleToGray16 ( PIX *pixs );
+LEPT_DLL extern PIX * pixScaleToGrayMipmap ( PIX *pixs, l_float32 scalefactor );
+LEPT_DLL extern PIX * pixScaleMipmap ( PIX *pixs1, PIX *pixs2, l_float32 scale );
+LEPT_DLL extern PIX * pixExpandReplicate ( PIX *pixs, l_int32 factor );
+LEPT_DLL extern PIX * pixScaleGray2xLIThresh ( PIX *pixs, l_int32 thresh );
+LEPT_DLL extern PIX * pixScaleGray2xLIDither ( PIX *pixs );
+LEPT_DLL extern PIX * pixScaleGray4xLIThresh ( PIX *pixs, l_int32 thresh );
+LEPT_DLL extern PIX * pixScaleGray4xLIDither ( PIX *pixs );
+LEPT_DLL extern PIX * pixScaleGrayMinMax ( PIX *pixs, l_int32 xfact, l_int32 yfact, l_int32 type );
+LEPT_DLL extern PIX * pixScaleGrayMinMax2 ( PIX *pixs, l_int32 type );
+LEPT_DLL extern PIX * pixScaleGrayRankCascade ( PIX *pixs, l_int32 level1, l_int32 level2, l_int32 level3, l_int32 level4 );
+LEPT_DLL extern PIX * pixScaleGrayRank2 ( PIX *pixs, l_int32 rank );
+LEPT_DLL extern l_int32 pixScaleAndTransferAlpha ( PIX *pixd, PIX *pixs, l_float32 scalex, l_float32 scaley );
+LEPT_DLL extern PIX * pixScaleWithAlpha ( PIX *pixs, l_float32 scalex, l_float32 scaley, PIX *pixg, l_float32 fract );
+LEPT_DLL extern void scaleColorLILow ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 ws, l_int32 hs, l_int32 wpls );
+LEPT_DLL extern void scaleGrayLILow ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 ws, l_int32 hs, l_int32 wpls );
+LEPT_DLL extern void scaleColor2xLILow ( l_uint32 *datad, l_int32 wpld, l_uint32 *datas, l_int32 ws, l_int32 hs, l_int32 wpls );
+LEPT_DLL extern void scaleColor2xLILineLow ( l_uint32 *lined, l_int32 wpld, l_uint32 *lines, l_int32 ws, l_int32 wpls, l_int32 lastlineflag );
+LEPT_DLL extern void scaleGray2xLILow ( l_uint32 *datad, l_int32 wpld, l_uint32 *datas, l_int32 ws, l_int32 hs, l_int32 wpls );
+LEPT_DLL extern void scaleGray2xLILineLow ( l_uint32 *lined, l_int32 wpld, l_uint32 *lines, l_int32 ws, l_int32 wpls, l_int32 lastlineflag );
+LEPT_DLL extern void scaleGray4xLILow ( l_uint32 *datad, l_int32 wpld, l_uint32 *datas, l_int32 ws, l_int32 hs, l_int32 wpls );
+LEPT_DLL extern void scaleGray4xLILineLow ( l_uint32 *lined, l_int32 wpld, l_uint32 *lines, l_int32 ws, l_int32 wpls, l_int32 lastlineflag );
+LEPT_DLL extern l_int32 scaleBySamplingLow ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 ws, l_int32 hs, l_int32 d, l_int32 wpls );
+LEPT_DLL extern l_int32 scaleSmoothLow ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 ws, l_int32 hs, l_int32 d, l_int32 wpls, l_int32 size );
+LEPT_DLL extern void scaleRGBToGray2Low ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_float32 rwt, l_float32 gwt, l_float32 bwt );
+LEPT_DLL extern void scaleColorAreaMapLow ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 ws, l_int32 hs, l_int32 wpls );
+LEPT_DLL extern void scaleGrayAreaMapLow ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 ws, l_int32 hs, l_int32 wpls );
+LEPT_DLL extern void scaleAreaMapLow2 ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 d, l_int32 wpls );
+LEPT_DLL extern l_int32 scaleBinaryLow ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 ws, l_int32 hs, l_int32 wpls );
+LEPT_DLL extern void scaleToGray2Low ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_uint32 *sumtab, l_uint8 *valtab );
+LEPT_DLL extern l_uint32 * makeSumTabSG2 ( void );
+LEPT_DLL extern l_uint8 * makeValTabSG2 ( void );
+LEPT_DLL extern void scaleToGray3Low ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_uint32 *sumtab, l_uint8 *valtab );
+LEPT_DLL extern l_uint32 * makeSumTabSG3 ( void );
+LEPT_DLL extern l_uint8 * makeValTabSG3 ( void );
+LEPT_DLL extern void scaleToGray4Low ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_uint32 *sumtab, l_uint8 *valtab );
+LEPT_DLL extern l_uint32 * makeSumTabSG4 ( void );
+LEPT_DLL extern l_uint8 * makeValTabSG4 ( void );
+LEPT_DLL extern void scaleToGray6Low ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_int32 *tab8, l_uint8 *valtab );
+LEPT_DLL extern l_uint8 * makeValTabSG6 ( void );
+LEPT_DLL extern void scaleToGray8Low ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_int32 *tab8, l_uint8 *valtab );
+LEPT_DLL extern l_uint8 * makeValTabSG8 ( void );
+LEPT_DLL extern void scaleToGray16Low ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas, l_int32 wpls, l_int32 *tab8 );
+LEPT_DLL extern l_int32 scaleMipmapLow ( l_uint32 *datad, l_int32 wd, l_int32 hd, l_int32 wpld, l_uint32 *datas1, l_int32 wpls1, l_uint32 *datas2, l_int32 wpls2, l_float32 red );
+LEPT_DLL extern PIX * pixSeedfillBinary ( PIX *pixd, PIX *pixs, PIX *pixm, l_int32 connectivity );
+LEPT_DLL extern PIX * pixSeedfillBinaryRestricted ( PIX *pixd, PIX *pixs, PIX *pixm, l_int32 connectivity, l_int32 xmax, l_int32 ymax );
+LEPT_DLL extern PIX * pixHolesByFilling ( PIX *pixs, l_int32 connectivity );
+LEPT_DLL extern PIX * pixFillClosedBorders ( PIX *pixs, l_int32 connectivity );
+LEPT_DLL extern PIX * pixExtractBorderConnComps ( PIX *pixs, l_int32 connectivity );
+LEPT_DLL extern PIX * pixRemoveBorderConnComps ( PIX *pixs, l_int32 connectivity );
+LEPT_DLL extern PIX * pixFillBgFromBorder ( PIX *pixs, l_int32 connectivity );
+LEPT_DLL extern PIX * pixFillHolesToBoundingRect ( PIX *pixs, l_int32 minsize, l_float32 maxhfract, l_float32 minfgfract );
+LEPT_DLL extern l_int32 pixSeedfillGray ( PIX *pixs, PIX *pixm, l_int32 connectivity );
+LEPT_DLL extern l_int32 pixSeedfillGrayInv ( PIX *pixs, PIX *pixm, l_int32 connectivity );
+LEPT_DLL extern l_int32 pixSeedfillGraySimple ( PIX *pixs, PIX *pixm, l_int32 connectivity );
+LEPT_DLL extern l_int32 pixSeedfillGrayInvSimple ( PIX *pixs, PIX *pixm, l_int32 connectivity );
+LEPT_DLL extern PIX * pixSeedfillGrayBasin ( PIX *pixb, PIX *pixm, l_int32 delta, l_int32 connectivity );
+LEPT_DLL extern PIX * pixDistanceFunction ( PIX *pixs, l_int32 connectivity, l_int32 outdepth, l_int32 boundcond );
+LEPT_DLL extern PIX * pixSeedspread ( PIX *pixs, l_int32 connectivity );
+LEPT_DLL extern l_int32 pixLocalExtrema ( PIX *pixs, l_int32 maxmin, l_int32 minmax, PIX **ppixmin, PIX **ppixmax );
+LEPT_DLL extern l_int32 pixSelectedLocalExtrema ( PIX *pixs, l_int32 mindist, PIX **ppixmin, PIX **ppixmax );
+LEPT_DLL extern PIX * pixFindEqualValues ( PIX *pixs1, PIX *pixs2 );
+LEPT_DLL extern l_int32 pixSelectMinInConnComp ( PIX *pixs, PIX *pixm, PTA **ppta, NUMA **pnav );
+LEPT_DLL extern PIX * pixRemoveSeededComponents ( PIX *pixd, PIX *pixs, PIX *pixm, l_int32 connectivity, l_int32 bordersize );
+LEPT_DLL extern void seedfillBinaryLow ( l_uint32 *datas, l_int32 hs, l_int32 wpls, l_uint32 *datam, l_int32 hm, l_int32 wplm, l_int32 connectivity );
+LEPT_DLL extern void seedfillGrayLow ( l_uint32 *datas, l_int32 w, l_int32 h, l_int32 wpls, l_uint32 *datam, l_int32 wplm, l_int32 connectivity );
+LEPT_DLL extern void seedfillGrayInvLow ( l_uint32 *datas, l_int32 w, l_int32 h, l_int32 wpls, l_uint32 *datam, l_int32 wplm, l_int32 connectivity );
+LEPT_DLL extern void seedfillGrayLowSimple ( l_uint32 *datas, l_int32 w, l_int32 h, l_int32 wpls, l_uint32 *datam, l_int32 wplm, l_int32 connectivity );
+LEPT_DLL extern void seedfillGrayInvLowSimple ( l_uint32 *datas, l_int32 w, l_int32 h, l_int32 wpls, l_uint32 *datam, l_int32 wplm, l_int32 connectivity );
+LEPT_DLL extern void distanceFunctionLow ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 d, l_int32 wpld, l_int32 connectivity );
+LEPT_DLL extern void seedspreadLow ( l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpld, l_uint32 *datat, l_int32 wplt, l_int32 connectivity );
+LEPT_DLL extern SELA * selaCreate ( l_int32 n );
+LEPT_DLL extern void selaDestroy ( SELA **psela );
+LEPT_DLL extern SEL * selCreate ( l_int32 height, l_int32 width, const char *name );
+LEPT_DLL extern void selDestroy ( SEL **psel );
+LEPT_DLL extern SEL * selCopy ( SEL *sel );
+LEPT_DLL extern SEL * selCreateBrick ( l_int32 h, l_int32 w, l_int32 cy, l_int32 cx, l_int32 type );
+LEPT_DLL extern SEL * selCreateComb ( l_int32 factor1, l_int32 factor2, l_int32 direction );
+LEPT_DLL extern l_int32 ** create2dIntArray ( l_int32 sy, l_int32 sx );
+LEPT_DLL extern l_int32 selaAddSel ( SELA *sela, SEL *sel, const char *selname, l_int32 copyflag );
+LEPT_DLL extern l_int32 selaGetCount ( SELA *sela );
+LEPT_DLL extern SEL * selaGetSel ( SELA *sela, l_int32 i );
+LEPT_DLL extern char * selGetName ( SEL *sel );
+LEPT_DLL extern l_int32 selSetName ( SEL *sel, const char *name );
+LEPT_DLL extern l_int32 selaFindSelByName ( SELA *sela, const char *name, l_int32 *pindex, SEL **psel );
+LEPT_DLL extern l_int32 selGetElement ( SEL *sel, l_int32 row, l_int32 col, l_int32 *ptype );
+LEPT_DLL extern l_int32 selSetElement ( SEL *sel, l_int32 row, l_int32 col, l_int32 type );
+LEPT_DLL extern l_int32 selGetParameters ( SEL *sel, l_int32 *psy, l_int32 *psx, l_int32 *pcy, l_int32 *pcx );
+LEPT_DLL extern l_int32 selSetOrigin ( SEL *sel, l_int32 cy, l_int32 cx );
+LEPT_DLL extern l_int32 selGetTypeAtOrigin ( SEL *sel, l_int32 *ptype );
+LEPT_DLL extern char * selaGetBrickName ( SELA *sela, l_int32 hsize, l_int32 vsize );
+LEPT_DLL extern char * selaGetCombName ( SELA *sela, l_int32 size, l_int32 direction );
+LEPT_DLL extern l_int32 getCompositeParameters ( l_int32 size, l_int32 *psize1, l_int32 *psize2, char **pnameh1, char **pnameh2, char **pnamev1, char **pnamev2 );
+LEPT_DLL extern SARRAY * selaGetSelnames ( SELA *sela );
+LEPT_DLL extern l_int32 selFindMaxTranslations ( SEL *sel, l_int32 *pxp, l_int32 *pyp, l_int32 *pxn, l_int32 *pyn );
+LEPT_DLL extern SEL * selRotateOrth ( SEL *sel, l_int32 quads );
+LEPT_DLL extern SELA * selaRead ( const char *fname );
+LEPT_DLL extern SELA * selaReadStream ( FILE *fp );
+LEPT_DLL extern SEL * selRead ( const char *fname );
+LEPT_DLL extern SEL * selReadStream ( FILE *fp );
+LEPT_DLL extern l_int32 selaWrite ( const char *fname, SELA *sela );
+LEPT_DLL extern l_int32 selaWriteStream ( FILE *fp, SELA *sela );
+LEPT_DLL extern l_int32 selWrite ( const char *fname, SEL *sel );
+LEPT_DLL extern l_int32 selWriteStream ( FILE *fp, SEL *sel );
+LEPT_DLL extern SEL * selCreateFromString ( const char *text, l_int32 h, l_int32 w, const char *name );
+LEPT_DLL extern char * selPrintToString ( SEL *sel );
+LEPT_DLL extern SELA * selaCreateFromFile ( const char *filename );
+LEPT_DLL extern SEL * selCreateFromPta ( PTA *pta, l_int32 cy, l_int32 cx, const char *name );
+LEPT_DLL extern SEL * selCreateFromPix ( PIX *pix, l_int32 cy, l_int32 cx, const char *name );
+LEPT_DLL extern SEL * selReadFromColorImage ( const char *pathname );
+LEPT_DLL extern SEL * selCreateFromColorPix ( PIX *pixs, char *selname );
+LEPT_DLL extern PIX * selDisplayInPix ( SEL *sel, l_int32 size, l_int32 gthick );
+LEPT_DLL extern PIX * selaDisplayInPix ( SELA *sela, l_int32 size, l_int32 gthick, l_int32 spacing, l_int32 ncols );
+LEPT_DLL extern SELA * selaAddBasic ( SELA *sela );
+LEPT_DLL extern SELA * selaAddHitMiss ( SELA *sela );
+LEPT_DLL extern SELA * selaAddDwaLinear ( SELA *sela );
+LEPT_DLL extern SELA * selaAddDwaCombs ( SELA *sela );
+LEPT_DLL extern SELA * selaAddCrossJunctions ( SELA *sela, l_float32 hlsize, l_float32 mdist, l_int32 norient, l_int32 debugflag );
+LEPT_DLL extern SELA * selaAddTJunctions ( SELA *sela, l_float32 hlsize, l_float32 mdist, l_int32 norient, l_int32 debugflag );
+LEPT_DLL extern SEL * pixGenerateSelWithRuns ( PIX *pixs, l_int32 nhlines, l_int32 nvlines, l_int32 distance, l_int32 minlength, l_int32 toppix, l_int32 botpix, l_int32 leftpix, l_int32 rightpix, PIX **ppixe );
+LEPT_DLL extern SEL * pixGenerateSelRandom ( PIX *pixs, l_float32 hitfract, l_float32 missfract, l_int32 distance, l_int32 toppix, l_int32 botpix, l_int32 leftpix, l_int32 rightpix, PIX **ppixe );
+LEPT_DLL extern SEL * pixGenerateSelBoundary ( PIX *pixs, l_int32 hitdist, l_int32 missdist, l_int32 hitskip, l_int32 missskip, l_int32 topflag, l_int32 botflag, l_int32 leftflag, l_int32 rightflag, PIX **ppixe );
+LEPT_DLL extern NUMA * pixGetRunCentersOnLine ( PIX *pixs, l_int32 x, l_int32 y, l_int32 minlength );
+LEPT_DLL extern NUMA * pixGetRunsOnLine ( PIX *pixs, l_int32 x1, l_int32 y1, l_int32 x2, l_int32 y2 );
+LEPT_DLL extern PTA * pixSubsampleBoundaryPixels ( PIX *pixs, l_int32 skip );
+LEPT_DLL extern l_int32 adjacentOnPixelInRaster ( PIX *pixs, l_int32 x, l_int32 y, l_int32 *pxa, l_int32 *pya );
+LEPT_DLL extern PIX * pixDisplayHitMissSel ( PIX *pixs, SEL *sel, l_int32 scalefactor, l_uint32 hitcolor, l_uint32 misscolor );
+LEPT_DLL extern PIX * pixHShear ( PIX *pixd, PIX *pixs, l_int32 yloc, l_float32 radang, l_int32 incolor );
+LEPT_DLL extern PIX * pixVShear ( PIX *pixd, PIX *pixs, l_int32 xloc, l_float32 radang, l_int32 incolor );
+LEPT_DLL extern PIX * pixHShearCorner ( PIX *pixd, PIX *pixs, l_float32 radang, l_int32 incolor );
+LEPT_DLL extern PIX * pixVShearCorner ( PIX *pixd, PIX *pixs, l_float32 radang, l_int32 incolor );
+LEPT_DLL extern PIX * pixHShearCenter ( PIX *pixd, PIX *pixs, l_float32 radang, l_int32 incolor );
+LEPT_DLL extern PIX * pixVShearCenter ( PIX *pixd, PIX *pixs, l_float32 radang, l_int32 incolor );
+LEPT_DLL extern l_int32 pixHShearIP ( PIX *pixs, l_int32 yloc, l_float32 radang, l_int32 incolor );
+LEPT_DLL extern l_int32 pixVShearIP ( PIX *pixs, l_int32 xloc, l_float32 radang, l_int32 incolor );
+LEPT_DLL extern PIX * pixHShearLI ( PIX *pixs, l_int32 yloc, l_float32 radang, l_int32 incolor );
+LEPT_DLL extern PIX * pixVShearLI ( PIX *pixs, l_int32 xloc, l_float32 radang, l_int32 incolor );
+LEPT_DLL extern PIX * pixDeskew ( PIX *pixs, l_int32 redsearch );
+LEPT_DLL extern PIX * pixFindSkewAndDeskew ( PIX *pixs, l_int32 redsearch, l_float32 *pangle, l_float32 *pconf );
+LEPT_DLL extern PIX * pixDeskewGeneral ( PIX *pixs, l_int32 redsweep, l_float32 sweeprange, l_float32 sweepdelta, l_int32 redsearch, l_int32 thresh, l_float32 *pangle, l_float32 *pconf );
+LEPT_DLL extern l_int32 pixFindSkew ( PIX *pixs, l_float32 *pangle, l_float32 *pconf );
+LEPT_DLL extern l_int32 pixFindSkewSweep ( PIX *pixs, l_float32 *pangle, l_int32 reduction, l_float32 sweeprange, l_float32 sweepdelta );
+LEPT_DLL extern l_int32 pixFindSkewSweepAndSearch ( PIX *pixs, l_float32 *pangle, l_float32 *pconf, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta );
+LEPT_DLL extern l_int32 pixFindSkewSweepAndSearchScore ( PIX *pixs, l_float32 *pangle, l_float32 *pconf, l_float32 *pendscore, l_int32 redsweep, l_int32 redsearch, l_float32 sweepcenter, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta );
+LEPT_DLL extern l_int32 pixFindSkewSweepAndSearchScorePivot ( PIX *pixs, l_float32 *pangle, l_float32 *pconf, l_float32 *pendscore, l_int32 redsweep, l_int32 redsearch, l_float32 sweepcenter, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, l_int32 pivot );
+LEPT_DLL extern l_int32 pixFindSkewOrthogonalRange ( PIX *pixs, l_float32 *pangle, l_float32 *pconf, l_int32 redsweep, l_int32 redsearch, l_float32 sweeprange, l_float32 sweepdelta, l_float32 minbsdelta, l_float32 confprior );
+LEPT_DLL extern l_int32 pixFindDifferentialSquareSum ( PIX *pixs, l_float32 *psum );
+LEPT_DLL extern l_int32 pixFindNormalizedSquareSum ( PIX *pixs, l_float32 *phratio, l_float32 *pvratio, l_float32 *pfract );
+LEPT_DLL extern PIX * pixReadStreamSpix ( FILE *fp );
+LEPT_DLL extern l_int32 readHeaderSpix ( const char *filename, l_int32 *pwidth, l_int32 *pheight, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_int32 freadHeaderSpix ( FILE *fp, l_int32 *pwidth, l_int32 *pheight, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_int32 sreadHeaderSpix ( const l_uint32 *data, l_int32 *pwidth, l_int32 *pheight, l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap );
+LEPT_DLL extern l_int32 pixWriteStreamSpix ( FILE *fp, PIX *pix );
+LEPT_DLL extern PIX * pixReadMemSpix ( const l_uint8 *data, size_t size );
+LEPT_DLL extern l_int32 pixWriteMemSpix ( l_uint8 **pdata, size_t *psize, PIX *pix );
+LEPT_DLL extern l_int32 pixSerializeToMemory ( PIX *pixs, l_uint32 **pdata, size_t *pnbytes );
+LEPT_DLL extern PIX * pixDeserializeFromMemory ( const l_uint32 *data, size_t nbytes );
+LEPT_DLL extern L_STACK * lstackCreate ( l_int32 nalloc );
+LEPT_DLL extern void lstackDestroy ( L_STACK **plstack, l_int32 freeflag );
+LEPT_DLL extern l_int32 lstackAdd ( L_STACK *lstack, void *item );
+LEPT_DLL extern void * lstackRemove ( L_STACK *lstack );
+LEPT_DLL extern l_int32 lstackGetCount ( L_STACK *lstack );
+LEPT_DLL extern l_int32 lstackPrint ( FILE *fp, L_STACK *lstack );
+LEPT_DLL extern L_STRCODE * strcodeCreate ( l_int32 fileno );
+LEPT_DLL extern l_int32 strcodeCreateFromFile ( const char *filein, l_int32 fileno, const char *outdir );
+LEPT_DLL extern l_int32 strcodeGenerate ( L_STRCODE *strcode, const char *filein, const char *type );
+LEPT_DLL extern l_int32 strcodeFinalize ( L_STRCODE **pstrcode, const char *outdir );
+LEPT_DLL extern l_int32 l_getStructnameFromFile ( const char *filename, char **psn );
+LEPT_DLL extern l_int32 * sudokuReadFile ( const char *filename );
+LEPT_DLL extern l_int32 * sudokuReadString ( const char *str );
+LEPT_DLL extern L_SUDOKU * sudokuCreate ( l_int32 *array );
+LEPT_DLL extern void sudokuDestroy ( L_SUDOKU **psud );
+LEPT_DLL extern l_int32 sudokuSolve ( L_SUDOKU *sud );
+LEPT_DLL extern l_int32 sudokuTestUniqueness ( l_int32 *array, l_int32 *punique );
+LEPT_DLL extern L_SUDOKU * sudokuGenerate ( l_int32 *array, l_int32 seed, l_int32 minelems, l_int32 maxtries );
+LEPT_DLL extern l_int32 sudokuOutput ( L_SUDOKU *sud, l_int32 arraytype );
+LEPT_DLL extern PIX * pixAddSingleTextblock ( PIX *pixs, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 location, l_int32 *poverflow );
+LEPT_DLL extern PIX * pixAddTextlines ( PIX *pixs, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 location );
+LEPT_DLL extern l_int32 pixSetTextblock ( PIX *pixs, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 x0, l_int32 y0, l_int32 wtext, l_int32 firstindent, l_int32 *poverflow );
+LEPT_DLL extern l_int32 pixSetTextline ( PIX *pixs, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 x0, l_int32 y0, l_int32 *pwidth, l_int32 *poverflow );
+LEPT_DLL extern PIXA * pixaAddTextNumber ( PIXA *pixas, L_BMF *bmf, NUMA *na, l_uint32 val, l_int32 location );
+LEPT_DLL extern PIXA * pixaAddTextlines ( PIXA *pixas, L_BMF *bmf, SARRAY *sa, l_uint32 val, l_int32 location );
+LEPT_DLL extern l_int32 pixaAddPixWithText ( PIXA *pixa, PIX *pixs, l_int32 reduction, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 location );
+LEPT_DLL extern SARRAY * bmfGetLineStrings ( L_BMF *bmf, const char *textstr, l_int32 maxw, l_int32 firstindent, l_int32 *ph );
+LEPT_DLL extern NUMA * bmfGetWordWidths ( L_BMF *bmf, const char *textstr, SARRAY *sa );
+LEPT_DLL extern l_int32 bmfGetStringWidth ( L_BMF *bmf, const char *textstr, l_int32 *pw );
+LEPT_DLL extern SARRAY * splitStringToParagraphs ( char *textstr, l_int32 splitflag );
+LEPT_DLL extern PIX * pixReadTiff ( const char *filename, l_int32 n );
+LEPT_DLL extern PIX * pixReadStreamTiff ( FILE *fp, l_int32 n );
+LEPT_DLL extern l_int32 pixWriteTiff ( const char *filename, PIX *pix, l_int32 comptype, const char *modestring );
+LEPT_DLL extern l_int32 pixWriteTiffCustom ( const char *filename, PIX *pix, l_int32 comptype, const char *modestring, NUMA *natags, SARRAY *savals, SARRAY *satypes, NUMA *nasizes );
+LEPT_DLL extern l_int32 pixWriteStreamTiff ( FILE *fp, PIX *pix, l_int32 comptype );
+LEPT_DLL extern PIXA * pixaReadMultipageTiff ( const char *filename );
+LEPT_DLL extern l_int32 writeMultipageTiff ( const char *dirin, const char *substr, const char *fileout );
+LEPT_DLL extern l_int32 writeMultipageTiffSA ( SARRAY *sa, const char *fileout );
+LEPT_DLL extern l_int32 fprintTiffInfo ( FILE *fpout, const char *tiffile );
+LEPT_DLL extern l_int32 tiffGetCount ( FILE *fp, l_int32 *pn );
+LEPT_DLL extern l_int32 getTiffResolution ( FILE *fp, l_int32 *pxres, l_int32 *pyres );
+LEPT_DLL extern l_int32 readHeaderTiff ( const char *filename, l_int32 n, l_int32 *pwidth, l_int32 *pheight, l_int32 *pbps, l_int32 *pspp, l_int32 *pres, l_int32 *pcmap, l_int32 *pformat );
+LEPT_DLL extern l_int32 freadHeaderTiff ( FILE *fp, l_int32 n, l_int32 *pwidth, l_int32 *pheight, l_int32 *pbps, l_int32 *pspp, l_int32 *pres, l_int32 *pcmap, l_int32 *pformat );
+LEPT_DLL extern l_int32 readHeaderMemTiff ( const l_uint8 *cdata, size_t size, l_int32 n, l_int32 *pwidth, l_int32 *pheight, l_int32 *pbps, l_int32 *pspp, l_int32 *pres, l_int32 *pcmap, l_int32 *pformat );
+LEPT_DLL extern l_int32 findTiffCompression ( FILE *fp, l_int32 *pcomptype );
+LEPT_DLL extern l_int32 extractG4DataFromFile ( const char *filein, l_uint8 **pdata, size_t *pnbytes, l_int32 *pw, l_int32 *ph, l_int32 *pminisblack );
+LEPT_DLL extern PIX * pixReadMemTiff ( const l_uint8 *cdata, size_t size, l_int32 n );
+LEPT_DLL extern l_int32 pixWriteMemTiff ( l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 comptype );
+LEPT_DLL extern l_int32 pixWriteMemTiffCustom ( l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 comptype, NUMA *natags, SARRAY *savals, SARRAY *satypes, NUMA *nasizes );
+LEPT_DLL extern l_int32 setMsgSeverity ( l_int32 newsev );
+LEPT_DLL extern l_int32 returnErrorInt ( const char *msg, const char *procname, l_int32 ival );
+LEPT_DLL extern l_float32 returnErrorFloat ( const char *msg, const char *procname, l_float32 fval );
+LEPT_DLL extern void * returnErrorPtr ( const char *msg, const char *procname, void *pval );
+LEPT_DLL extern char * stringNew ( const char *src );
+LEPT_DLL extern l_int32 stringCopy ( char *dest, const char *src, l_int32 n );
+LEPT_DLL extern l_int32 stringReplace ( char **pdest, const char *src );
+LEPT_DLL extern l_int32 stringLength ( const char *src, size_t size );
+LEPT_DLL extern l_int32 stringCat ( char *dest, size_t size, const char *src );
+LEPT_DLL extern char * stringConcatNew ( const char *first, ... );
+LEPT_DLL extern char * stringJoin ( const char *src1, const char *src2 );
+LEPT_DLL extern l_int32 stringJoinIP ( char **psrc1, const char *src2 );
+LEPT_DLL extern char * stringReverse ( const char *src );
+LEPT_DLL extern char * strtokSafe ( char *cstr, const char *seps, char **psaveptr );
+LEPT_DLL extern l_int32 stringSplitOnToken ( char *cstr, const char *seps, char **phead, char **ptail );
+LEPT_DLL extern char * stringRemoveChars ( const char *src, const char *remchars );
+LEPT_DLL extern l_int32 stringFindSubstr ( const char *src, const char *sub, l_int32 *ploc );
+LEPT_DLL extern char * stringReplaceSubstr ( const char *src, const char *sub1, const char *sub2, l_int32 *pfound, l_int32 *ploc );
+LEPT_DLL extern char * stringReplaceEachSubstr ( const char *src, const char *sub1, const char *sub2, l_int32 *pcount );
+LEPT_DLL extern L_DNA * arrayFindEachSequence ( const l_uint8 *data, size_t datalen, const l_uint8 *sequence, size_t seqlen );
+LEPT_DLL extern l_int32 arrayFindSequence ( const l_uint8 *data, size_t datalen, const l_uint8 *sequence, size_t seqlen, l_int32 *poffset, l_int32 *pfound );
+LEPT_DLL extern void * reallocNew ( void **pindata, l_int32 oldsize, l_int32 newsize );
+LEPT_DLL extern l_uint8 * l_binaryRead ( const char *filename, size_t *pnbytes );
+LEPT_DLL extern l_uint8 * l_binaryReadStream ( FILE *fp, size_t *pnbytes );
+LEPT_DLL extern l_uint8 * l_binaryReadSelect ( const char *filename, size_t start, size_t nbytes, size_t *pnread );
+LEPT_DLL extern l_uint8 * l_binaryReadSelectStream ( FILE *fp, size_t start, size_t nbytes, size_t *pnread );
+LEPT_DLL extern l_int32 l_binaryWrite ( const char *filename, const char *operation, void *data, size_t nbytes );
+LEPT_DLL extern size_t nbytesInFile ( const char *filename );
+LEPT_DLL extern size_t fnbytesInFile ( FILE *fp );
+LEPT_DLL extern l_uint8 * l_binaryCopy ( l_uint8 *datas, size_t size );
+LEPT_DLL extern l_int32 fileCopy ( const char *srcfile, const char *newfile );
+LEPT_DLL extern l_int32 fileConcatenate ( const char *srcfile, const char *destfile );
+LEPT_DLL extern l_int32 fileAppendString ( const char *filename, const char *str );
+LEPT_DLL extern l_int32 filesAreIdentical ( const char *fname1, const char *fname2, l_int32 *psame );
+LEPT_DLL extern l_uint16 convertOnLittleEnd16 ( l_uint16 shortin );
+LEPT_DLL extern l_uint16 convertOnBigEnd16 ( l_uint16 shortin );
+LEPT_DLL extern l_uint32 convertOnLittleEnd32 ( l_uint32 wordin );
+LEPT_DLL extern l_uint32 convertOnBigEnd32 ( l_uint32 wordin );
+LEPT_DLL extern FILE * fopenReadStream ( const char *filename );
+LEPT_DLL extern FILE * fopenWriteStream ( const char *filename, const char *modestring );
+LEPT_DLL extern FILE * lept_fopen ( const char *filename, const char *mode );
+LEPT_DLL extern l_int32 lept_fclose ( FILE *fp );
+LEPT_DLL extern void * lept_calloc ( size_t nmemb, size_t size );
+LEPT_DLL extern void lept_free ( void *ptr );
+LEPT_DLL extern l_int32 lept_mkdir ( const char *subdir );
+LEPT_DLL extern l_int32 lept_rmdir ( const char *subdir );
+LEPT_DLL extern void lept_direxists ( const char *dir, l_int32 *pexists );
+LEPT_DLL extern l_int32 lept_rm_match ( const char *subdir, const char *substr );
+LEPT_DLL extern l_int32 lept_rm ( const char *subdir, const char *tail );
+LEPT_DLL extern l_int32 lept_rmfile ( const char *filepath );
+LEPT_DLL extern l_int32 lept_mv ( const char *srcfile, const char *newdir, const char *newtail, char **pnewpath );
+LEPT_DLL extern l_int32 lept_cp ( const char *srcfile, const char *newdir, const char *newtail, char **pnewpath );
+LEPT_DLL extern l_int32 splitPathAtDirectory ( const char *pathname, char **pdir, char **ptail );
+LEPT_DLL extern l_int32 splitPathAtExtension ( const char *pathname, char **pbasename, char **pextension );
+LEPT_DLL extern char * pathJoin ( const char *dir, const char *fname );
+LEPT_DLL extern char * appendSubdirs ( const char *basedir, const char *subdirs );
+LEPT_DLL extern l_int32 convertSepCharsInPath ( char *path, l_int32 type );
+LEPT_DLL extern char * genPathname ( const char *dir, const char *fname );
+LEPT_DLL extern l_int32 makeTempDirname ( char *result, size_t nbytes, const char *subdir );
+LEPT_DLL extern l_int32 modifyTrailingSlash ( char *path, size_t nbytes, l_int32 flag );
+LEPT_DLL extern char * genTempFilename ( const char *dir, const char *tail, l_int32 usetime, l_int32 usepid );
+LEPT_DLL extern l_int32 extractNumberFromFilename ( const char *fname, l_int32 numpre, l_int32 numpost );
+LEPT_DLL extern l_int32 fileCorruptByDeletion ( const char *filein, l_float32 loc, l_float32 size, const char *fileout );
+LEPT_DLL extern l_int32 fileCorruptByMutation ( const char *filein, l_float32 loc, l_float32 size, const char *fileout );
+LEPT_DLL extern l_int32 genRandomIntegerInRange ( l_int32 range, l_int32 seed, l_int32 *pval );
+LEPT_DLL extern l_int32 lept_roundftoi ( l_float32 fval );
+LEPT_DLL extern l_int32 l_hashStringToUint64 ( const char *str, l_uint64 *phash );
+LEPT_DLL extern l_int32 l_hashPtToUint64 ( l_int32 x, l_int32 y, l_uint64 *phash );
+LEPT_DLL extern l_int32 l_hashPtToUint64Fast ( l_int32 nbuckets, l_int32 x, l_int32 y, l_uint64 *phash );
+LEPT_DLL extern l_int32 l_hashFloat64ToUint64 ( l_int32 nbuckets, l_float64 val, l_uint64 *phash );
+LEPT_DLL extern l_int32 findNextLargerPrime ( l_int32 start, l_uint32 *pprime );
+LEPT_DLL extern l_int32 lept_isPrime ( l_uint64 n, l_int32 *pis_prime, l_uint32 *pfactor );
+LEPT_DLL extern l_uint32 convertBinaryToGrayCode ( l_uint32 val );
+LEPT_DLL extern l_uint32 convertGrayCodeToBinary ( l_uint32 val );
+LEPT_DLL extern char * getLeptonicaVersion (  );
+LEPT_DLL extern void startTimer ( void );
+LEPT_DLL extern l_float32 stopTimer ( void );
+LEPT_DLL extern L_TIMER startTimerNested ( void );
+LEPT_DLL extern l_float32 stopTimerNested ( L_TIMER rusage_start );
+LEPT_DLL extern void l_getCurrentTime ( l_int32 *sec, l_int32 *usec );
+LEPT_DLL extern L_WALLTIMER * startWallTimer ( void );
+LEPT_DLL extern l_float32 stopWallTimer ( L_WALLTIMER **ptimer );
+LEPT_DLL extern char * l_getFormattedDate (  );
+LEPT_DLL extern l_int32 pixHtmlViewer ( const char *dirin, const char *dirout, const char *rootname, l_int32 thumbwidth, l_int32 viewwidth, l_int32 copyorig );
+LEPT_DLL extern PIX * pixSimpleCaptcha ( PIX *pixs, l_int32 border, l_int32 nterms, l_uint32 seed, l_uint32 color, l_int32 cmapflag );
+LEPT_DLL extern PIX * pixRandomHarmonicWarp ( PIX *pixs, l_float32 xmag, l_float32 ymag, l_float32 xfreq, l_float32 yfreq, l_int32 nx, l_int32 ny, l_uint32 seed, l_int32 grayval );
+LEPT_DLL extern PIX * pixWarpStereoscopic ( PIX *pixs, l_int32 zbend, l_int32 zshiftt, l_int32 zshiftb, l_int32 ybendt, l_int32 ybendb, l_int32 redleft );
+LEPT_DLL extern PIX * pixStretchHorizontal ( PIX *pixs, l_int32 dir, l_int32 type, l_int32 hmax, l_int32 operation, l_int32 incolor );
+LEPT_DLL extern PIX * pixStretchHorizontalSampled ( PIX *pixs, l_int32 dir, l_int32 type, l_int32 hmax, l_int32 incolor );
+LEPT_DLL extern PIX * pixStretchHorizontalLI ( PIX *pixs, l_int32 dir, l_int32 type, l_int32 hmax, l_int32 incolor );
+LEPT_DLL extern PIX * pixQuadraticVShear ( PIX *pixs, l_int32 dir, l_int32 vmaxt, l_int32 vmaxb, l_int32 operation, l_int32 incolor );
+LEPT_DLL extern PIX * pixQuadraticVShearSampled ( PIX *pixs, l_int32 dir, l_int32 vmaxt, l_int32 vmaxb, l_int32 incolor );
+LEPT_DLL extern PIX * pixQuadraticVShearLI ( PIX *pixs, l_int32 dir, l_int32 vmaxt, l_int32 vmaxb, l_int32 incolor );
+LEPT_DLL extern PIX * pixStereoFromPair ( PIX *pix1, PIX *pix2, l_float32 rwt, l_float32 gwt, l_float32 bwt );
+LEPT_DLL extern L_WSHED * wshedCreate ( PIX *pixs, PIX *pixm, l_int32 mindepth, l_int32 debugflag );
+LEPT_DLL extern void wshedDestroy ( L_WSHED **pwshed );
+LEPT_DLL extern l_int32 wshedApply ( L_WSHED *wshed );
+LEPT_DLL extern l_int32 wshedBasins ( L_WSHED *wshed, PIXA **ppixa, NUMA **pnalevels );
+LEPT_DLL extern PIX * wshedRenderFill ( L_WSHED *wshed );
+LEPT_DLL extern PIX * wshedRenderColors ( L_WSHED *wshed );
+LEPT_DLL extern PIX * pixReadStreamWebP ( FILE *fp );
+LEPT_DLL extern PIX * pixReadMemWebP ( const l_uint8 *filedata, size_t filesize );
+LEPT_DLL extern l_int32 readHeaderWebP ( const char *filename, l_int32 *pw, l_int32 *ph, l_int32 *pspp );
+LEPT_DLL extern l_int32 readHeaderMemWebP ( const l_uint8 *data, size_t size, l_int32 *pw, l_int32 *ph, l_int32 *pspp );
+LEPT_DLL extern l_int32 pixWriteWebP ( const char *filename, PIX *pixs, l_int32 quality, l_int32 lossless );
+LEPT_DLL extern l_int32 pixWriteStreamWebP ( FILE *fp, PIX *pixs, l_int32 quality, l_int32 lossless );
+LEPT_DLL extern l_int32 pixWriteMemWebP ( l_uint8 **pencdata, size_t *pencsize, PIX *pixs, l_int32 quality, l_int32 lossless );
+LEPT_DLL extern l_int32 pixaWriteFiles ( const char *rootname, PIXA *pixa, l_int32 format );
+LEPT_DLL extern l_int32 pixWrite ( const char *filename, PIX *pix, l_int32 format );
+LEPT_DLL extern l_int32 pixWriteAutoFormat ( const char *filename, PIX *pix );
+LEPT_DLL extern l_int32 pixWriteStream ( FILE *fp, PIX *pix, l_int32 format );
+LEPT_DLL extern l_int32 pixWriteImpliedFormat ( const char *filename, PIX *pix, l_int32 quality, l_int32 progressive );
+LEPT_DLL extern l_int32 pixWriteTempfile ( const char *dir, const char *tail, PIX *pix, l_int32 format, char **pfilename );
+LEPT_DLL extern l_int32 pixChooseOutputFormat ( PIX *pix );
+LEPT_DLL extern l_int32 getImpliedFileFormat ( const char *filename );
+LEPT_DLL extern l_int32 pixGetAutoFormat ( PIX *pix, l_int32 *pformat );
+LEPT_DLL extern const char * getFormatExtension ( l_int32 format );
+LEPT_DLL extern l_int32 pixWriteMem ( l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 format );
+LEPT_DLL extern l_int32 pixDisplay ( PIX *pixs, l_int32 x, l_int32 y );
+LEPT_DLL extern l_int32 pixDisplayWithTitle ( PIX *pixs, l_int32 x, l_int32 y, const char *title, l_int32 dispflag );
+LEPT_DLL extern l_int32 pixDisplayMultiple ( const char *filepattern );
+LEPT_DLL extern l_int32 pixDisplayWrite ( PIX *pixs, l_int32 reduction );
+LEPT_DLL extern l_int32 pixDisplayWriteFormat ( PIX *pixs, l_int32 reduction, l_int32 format );
+LEPT_DLL extern l_int32 pixSaveTiled ( PIX *pixs, PIXA *pixa, l_float32 scalefactor, l_int32 newrow, l_int32 space, l_int32 dp );
+LEPT_DLL extern l_int32 pixSaveTiledOutline ( PIX *pixs, PIXA *pixa, l_float32 scalefactor, l_int32 newrow, l_int32 space, l_int32 linewidth, l_int32 dp );
+LEPT_DLL extern l_int32 pixSaveTiledWithText ( PIX *pixs, PIXA *pixa, l_int32 outwidth, l_int32 newrow, l_int32 space, l_int32 linewidth, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 location );
+LEPT_DLL extern void l_chooseDisplayProg ( l_int32 selection );
+LEPT_DLL extern l_uint8 * zlibCompress ( l_uint8 *datain, size_t nin, size_t *pnout );
+LEPT_DLL extern l_uint8 * zlibUncompress ( l_uint8 *datain, size_t nin, size_t *pnout );
+
+#ifdef __cplusplus
+}
+#endif  /* __cplusplus */
+#endif /* NO_PROTOS */
+
+
+#endif /* LEPTONICA_ALLHEADERS_H */
+
diff --git a/src/allheaders_bot.txt b/src/allheaders_bot.txt
new file mode 100644 (file)
index 0000000..a04c681
--- /dev/null
@@ -0,0 +1,5 @@
+#endif /* NO_PROTOS */
+
+
+#endif /* LEPTONICA_ALLHEADERS_H */
+
diff --git a/src/allheaders_top.txt b/src/allheaders_top.txt
new file mode 100644 (file)
index 0000000..6cbb9b8
--- /dev/null
@@ -0,0 +1,36 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_ALLHEADERS_H
+#define  LEPTONICA_ALLHEADERS_H
+
+
+#define LIBLEPT_MAJOR_VERSION   1
+#define LIBLEPT_MINOR_VERSION   73
+
+#include "alltypes.h"
+
+#ifndef NO_PROTOS
diff --git a/src/alltypes.h b/src/alltypes.h
new file mode 100644 (file)
index 0000000..b9512a0
--- /dev/null
@@ -0,0 +1,65 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_ALLTYPES_H
+#define  LEPTONICA_ALLTYPES_H
+
+    /* Standard */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+    /* General and configuration defs */
+#include "environ.h"
+
+    /* Generic and non-image-specific containers */
+#include "array.h"
+#include "bbuffer.h"
+#include "heap.h"
+#include "list.h"
+#include "ptra.h"
+#include "queue.h"
+#include "rbtree.h"
+#include "stack.h"
+
+    /* Imaging */
+#include "arrayaccess.h"
+#include "bmf.h"
+#include "ccbord.h"
+#include "dewarp.h"
+#include "gplot.h"
+#include "imageio.h"
+#include "jbclass.h"
+#include "morph.h"
+#include "pix.h"
+#include "recog.h"
+#include "regutils.h"
+#include "stringcode.h"
+#include "sudoku.h"
+#include "watershed.h"
+
+
+#endif /* LEPTONICA_ALLTYPES_H */
diff --git a/src/array.h b/src/array.h
new file mode 100644 (file)
index 0000000..d4e6dda
--- /dev/null
@@ -0,0 +1,152 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_ARRAY_H
+#define  LEPTONICA_ARRAY_H
+
+/*
+ *  Contains the following structs:
+ *      struct Numa
+ *      struct Numaa
+ *      struct L_Dna
+ *      struct L_Dnaa
+ *      struct L_DnaHash
+ *      struct Sarray
+ *      struct L_Bytea
+ *
+ *  Contains definitions for:
+ *      Numa interpolation flags
+ *      Numa and FPix border flags
+ *      Numa data type conversion to string
+ */
+
+
+/*------------------------------------------------------------------------*
+ *                             Array Structs                              *
+ *------------------------------------------------------------------------*/
+
+#define  NUMA_VERSION_NUMBER     1
+
+    /* Number array: an array of floats */
+struct Numa
+{
+    l_int32          nalloc;    /* size of allocated number array      */
+    l_int32          n;         /* number of numbers saved             */
+    l_int32          refcount;  /* reference count (1 if no clones)    */
+    l_float32        startx;    /* x value assigned to array[0]        */
+    l_float32        delx;      /* change in x value as i --> i + 1    */
+    l_float32       *array;     /* number array                        */
+};
+typedef struct Numa  NUMA;
+
+    /* Array of number arrays */
+struct Numaa
+{
+    l_int32          nalloc;    /* size of allocated ptr array          */
+    l_int32          n;         /* number of Numa saved                 */
+    struct Numa    **numa;      /* array of Numa                        */
+};
+typedef struct Numaa  NUMAA;
+
+#define  DNA_VERSION_NUMBER     1
+
+    /* Double number array: an array of doubles */
+struct L_Dna
+{
+    l_int32          nalloc;    /* size of allocated number array      */
+    l_int32          n;         /* number of numbers saved             */
+    l_int32          refcount;  /* reference count (1 if no clones)    */
+    l_float64        startx;    /* x value assigned to array[0]        */
+    l_float64        delx;      /* change in x value as i --> i + 1    */
+    l_float64       *array;     /* number array                        */
+};
+typedef struct L_Dna  L_DNA;
+
+    /* Array of double number arrays */
+struct L_Dnaa
+{
+    l_int32          nalloc;    /* size of allocated ptr array          */
+    l_int32          n;         /* number of L_Dna saved                */
+    struct L_Dna   **dna;       /* array of L_Dna                       */
+};
+typedef struct L_Dnaa  L_DNAA;
+
+    /* A hash table of Dnas */
+struct L_DnaHash
+{
+    l_int32          nbuckets;
+    l_int32          initsize;   /* initial size of each dna that is made  */
+    struct L_Dna   **dna;
+};
+typedef struct L_DnaHash L_DNAHASH;
+
+#define  SARRAY_VERSION_NUMBER     1
+
+    /* String array: an array of C strings */
+struct Sarray
+{
+    l_int32          nalloc;    /* size of allocated ptr array         */
+    l_int32          n;         /* number of strings allocated         */
+    l_int32          refcount;  /* reference count (1 if no clones)    */
+    char           **array;     /* string array                        */
+};
+typedef struct Sarray SARRAY;
+
+    /* Byte array (analogous to C++ "string") */
+struct L_Bytea
+{
+    size_t           nalloc;    /* number of bytes allocated in data array  */
+    size_t           size;      /* number of bytes presently used           */
+    l_int32          refcount;  /* reference count (1 if no clones)         */
+    l_uint8         *data;      /* data array                               */
+};
+typedef struct L_Bytea L_BYTEA;
+
+
+/*------------------------------------------------------------------------*
+ *                              Array flags                               *
+ *------------------------------------------------------------------------*/
+    /* Flags for interpolation in Numa */
+enum {
+    L_LINEAR_INTERP = 1,        /* linear     */
+    L_QUADRATIC_INTERP = 2      /* quadratic  */
+};
+
+    /* Flags for added borders in Numa and Fpix */
+enum {
+    L_CONTINUED_BORDER = 1,     /* extended with same value                  */
+    L_SLOPE_BORDER = 2,         /* extended with constant normal derivative  */
+    L_MIRRORED_BORDER = 3       /* mirrored                                  */
+};
+
+    /* Flags for data type converted from Numa */
+enum {
+    L_INTEGER_VALUE = 1,        /* convert to integer  */
+    L_FLOAT_VALUE = 2           /* convert to float    */
+};
+
+
+#endif  /* LEPTONICA_ARRAY_H */
diff --git a/src/arrayaccess.c b/src/arrayaccess.c
new file mode 100644 (file)
index 0000000..00ddfc4
--- /dev/null
@@ -0,0 +1,360 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  arrayaccess.c
+ *
+ *     Access within an array of 32-bit words
+ *
+ *           l_int32     l_getDataBit()
+ *           void        l_setDataBit()
+ *           void        l_clearDataBit()
+ *           void        l_setDataBitVal()
+ *           l_int32     l_getDataDibit()
+ *           void        l_setDataDibit()
+ *           void        l_clearDataDibit()
+ *           l_int32     l_getDataQbit()
+ *           void        l_setDataQbit()
+ *           void        l_clearDataQbit()
+ *           l_int32     l_getDataByte()
+ *           void        l_setDataByte()
+ *           l_int32     l_getDataTwoBytes()
+ *           void        l_setDataTwoBytes()
+ *           l_int32     l_getDataFourBytes()
+ *           void        l_setDataFourBytes()
+ *
+ *     Note that these all require 32-bit alignment, and hence an input
+ *     ptr to l_uint32.  However, this is not enforced by the compiler.
+ *     Instead, we allow the use of a void* ptr, because the line ptrs
+ *     are an efficient way to get random access (see pixGetLinePtrs()).
+ *     It is then necessary to cast internally within each function
+ *     because ptr arithmetic requires knowing the size of the units
+ *     being referenced.
+ */
+
+#include "allheaders.h"
+
+
+/*----------------------------------------------------------------------*
+ *                 Access within an array of 32-bit words               *
+ *----------------------------------------------------------------------*/
+/*!
+ *  l_getDataBit()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *      Return: val of the nth (1-bit) pixel.
+ */
+l_int32
+l_getDataBit(void    *line,
+             l_int32  n)
+{
+    return (*((l_uint32 *)line + (n >> 5)) >> (31 - (n & 31))) & 1;
+}
+
+
+/*!
+ *  l_setDataBit()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *      Return: void
+ *
+ *  Action: sets the pixel to 1
+ */
+void
+l_setDataBit(void    *line,
+             l_int32  n)
+{
+    *((l_uint32 *)line + (n >> 5)) |= (0x80000000 >> (n & 31));
+}
+
+
+/*!
+ *  l_clearDataBit()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *      Return: void
+ *
+ *  Action: sets the (1-bit) pixel to 0
+ */
+void
+l_clearDataBit(void    *line,
+               l_int32  n)
+{
+    *((l_uint32 *)line + (n >> 5)) &= ~(0x80000000 >> (n & 31));
+}
+
+
+/*!
+ *  l_setDataBitVal()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *              val   (val to be inserted: 0 or 1)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This is an accessor for a 1 bpp pix.
+ *      (2) It is actually a little slower than using:
+ *            if (val == 0)
+ *                l_ClearDataBit(line, n);
+ *            else
+ *                l_SetDataBit(line, n);
+ */
+void
+l_setDataBitVal(void    *line,
+                l_int32  n,
+                l_int32  val)
+{
+l_uint32    *pword;
+
+    pword = (l_uint32 *)line + (n >> 5);
+    *pword &= ~(0x80000000 >> (n & 31));  /* clear */
+    *pword |= val << (31 - (n & 31));   /* set */
+    return;
+}
+
+
+/*!
+ *  l_getDataDibit()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *      Return: val of the nth (2-bit) pixel.
+ */
+l_int32
+l_getDataDibit(void    *line,
+               l_int32  n)
+{
+    return (*((l_uint32 *)line + (n >> 4)) >> (2 * (15 - (n & 15)))) & 3;
+}
+
+
+/*!
+ *  l_setDataDibit()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *              val   (val to be inserted: 0 - 3)
+ *      Return: void
+ */
+void
+l_setDataDibit(void    *line,
+               l_int32  n,
+               l_int32  val)
+{
+l_uint32    *pword;
+
+    pword = (l_uint32 *)line + (n >> 4);
+    *pword &= ~(0xc0000000 >> (2 * (n & 15)));  /* clear */
+    *pword |= (val & 3) << (30 - 2 * (n & 15));   /* set */
+    return;
+}
+
+
+/*!
+ *  l_clearDataDibit()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *      Return: void
+ *
+ *  Action: sets the (2-bit) pixel to 0
+ */
+void
+l_clearDataDibit(void    *line,
+                 l_int32  n)
+{
+    *((l_uint32 *)line + (n >> 4)) &= ~(0xc0000000 >> (2 * (n & 15)));
+}
+
+
+/*!
+ *  l_getDataQbit()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *      Return: val of the nth (4-bit) pixel.
+ */
+l_int32
+l_getDataQbit(void    *line,
+              l_int32  n)
+{
+    return (*((l_uint32 *)line + (n >> 3)) >> (4 * (7 - (n & 7)))) & 0xf;
+}
+
+
+/*!
+ *  l_setDataQbit()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *              val   (val to be inserted: 0 - 0xf)
+ *      Return: void
+ */
+void
+l_setDataQbit(void    *line,
+              l_int32  n,
+              l_int32  val)
+{
+l_uint32    *pword;
+
+    pword = (l_uint32 *)line + (n >> 3);
+    *pword &= ~(0xf0000000 >> (4 * (n & 7)));  /* clear */
+    *pword |= (val & 15) << (28 - 4 * (n & 7));   /* set */
+    return;
+}
+
+
+/*!
+ *  l_clearDataQbit()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *      Return: void
+ *
+ *  Action: sets the (4-bit) pixel to 0
+ */
+void
+l_clearDataQbit(void    *line,
+                l_int32  n)
+{
+    *((l_uint32 *)line + (n >> 3)) &= ~(0xf0000000 >> (4 * (n & 7)));
+}
+
+
+/*!
+ *  l_getDataByte()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *      Return: value of the n-th (byte) pixel
+ */
+l_int32
+l_getDataByte(void    *line,
+              l_int32  n)
+{
+#ifdef  L_BIG_ENDIAN
+    return *((l_uint8 *)line + n);
+#else  /* L_LITTLE_ENDIAN */
+    return *(l_uint8 *)((l_uintptr_t)((l_uint8 *)line + n) ^ 3);
+#endif  /* L_BIG_ENDIAN */
+}
+
+
+/*!
+ *  l_setDataByte()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *              val   (val to be inserted: 0 - 0xff)
+ *      Return: void
+ */
+void
+l_setDataByte(void    *line,
+              l_int32  n,
+              l_int32  val)
+{
+#ifdef  L_BIG_ENDIAN
+    *((l_uint8 *)line + n) = val;
+#else  /* L_LITTLE_ENDIAN */
+    *(l_uint8 *)((l_uintptr_t)((l_uint8 *)line + n) ^ 3) = val;
+#endif  /* L_BIG_ENDIAN */
+}
+
+
+/*!
+ *  l_getDataTwoBytes()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *      Return: value of the n-th (2-byte) pixel
+ */
+l_int32
+l_getDataTwoBytes(void    *line,
+                  l_int32  n)
+{
+#ifdef  L_BIG_ENDIAN
+    return *((l_uint16 *)line + n);
+#else  /* L_LITTLE_ENDIAN */
+    return *(l_uint16 *)((l_uintptr_t)((l_uint16 *)line + n) ^ 2);
+#endif  /* L_BIG_ENDIAN */
+}
+
+
+/*!
+ *  l_setDataTwoBytes()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *              val   (val to be inserted: 0 - 0xffff)
+ *      Return: void
+ */
+void
+l_setDataTwoBytes(void    *line,
+                  l_int32  n,
+                  l_int32  val)
+{
+#ifdef  L_BIG_ENDIAN
+    *((l_uint16 *)line + n) = val;
+#else  /* L_LITTLE_ENDIAN */
+    *(l_uint16 *)((l_uintptr_t)((l_uint16 *)line + n) ^ 2) = val;
+#endif  /* L_BIG_ENDIAN */
+}
+
+
+/*!
+ *  l_getDataFourBytes()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *      Return: value of the n-th (4-byte) pixel
+ */
+l_int32
+l_getDataFourBytes(void    *line,
+                   l_int32  n)
+{
+    return *((l_uint32 *)line + n);
+}
+
+
+/*!
+ *  l_setDataFourBytes()
+ *
+ *      Input:  line  (ptr to beginning of data line)
+ *              n     (pixel index)
+ *              val   (val to be inserted: 0 - 0xffffffff)
+ *      Return: void
+ */
+void
+l_setDataFourBytes(void    *line,
+                   l_int32  n,
+                   l_int32  val)
+{
+    *((l_uint32 *)line + n) = val;
+}
diff --git a/src/arrayaccess.h b/src/arrayaccess.h
new file mode 100644 (file)
index 0000000..2ac8c4a
--- /dev/null
@@ -0,0 +1,205 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_ARRAY_ACCESS_H
+#define  LEPTONICA_ARRAY_ACCESS_H
+
+/*
+ *  arrayaccess.h
+ *
+ *  1, 2, 4, 8, 16 and 32 bit data access within an array of 32-bit words
+ *
+ *  This is used primarily to access 1, 2, 4, 8, 16 and 32 bit pixels
+ *  in a line of image data, represented as an array of 32-bit words.
+ *
+ *     pdata:  pointer to first 32-bit word in the array
+ *     n:      index of the pixel in the array
+ *
+ *  Function calls for these accessors are defined in arrayaccess.c.
+ *
+ *  However, for efficiency we use the inline macros for all accesses.
+ *  Even though the 2 and 4 bit set* accessors are more complicated,
+ *  they are about 10% faster than the function calls.
+ *
+ *  The 32 bit access is just a cast and ptr arithmetic.  We include
+ *  it so that the input ptr can be void*.
+ *
+ *  At the end of this file is code for invoking the function calls
+ *  instead of inlining.
+ *
+ *  The macro SET_DATA_BIT_VAL(pdata, n, val) is a bit slower than
+ *      if (val == 0)
+ *          CLEAR_DATA_BIT(pdata, n);
+ *      else
+ *          SET_DATA_BIT(pdata, n);
+ */
+
+
+    /* Use the inline accessors (except with _MSC_VER), because they
+     * are faster.  */
+#define  USE_INLINE_ACCESSORS    1
+
+#if USE_INLINE_ACCESSORS
+#ifndef _MSC_VER
+
+    /*--------------------------------------------------*
+     *                     1 bit access                 *
+     *--------------------------------------------------*/
+#define  GET_DATA_BIT(pdata, n) \
+    ((*((l_uint32 *)(pdata) + ((n) >> 5)) >> (31 - ((n) & 31))) & 1)
+
+#define  SET_DATA_BIT(pdata, n) \
+    (*((l_uint32 *)(pdata) + ((n) >> 5)) |= (0x80000000 >> ((n) & 31)))
+
+#define  CLEAR_DATA_BIT(pdata, n) \
+    (*((l_uint32 *)(pdata) + ((n) >> 5)) &= ~(0x80000000 >> ((n) & 31)))
+
+#define  SET_DATA_BIT_VAL(pdata, n, val) \
+    ({l_uint32 *_TEMP_WORD_PTR_; \
+     _TEMP_WORD_PTR_ = (l_uint32 *)(pdata) + ((n) >> 5); \
+     *_TEMP_WORD_PTR_ &= ~(0x80000000 >> ((n) & 31)); \
+     *_TEMP_WORD_PTR_ |= ((val) << (31 - ((n) & 31))); \
+    })
+
+
+    /*--------------------------------------------------*
+     *                     2 bit access                 *
+     *--------------------------------------------------*/
+#define  GET_DATA_DIBIT(pdata, n) \
+    ((*((l_uint32 *)(pdata) + ((n) >> 4)) >> (2 * (15 - ((n) & 15)))) & 3)
+
+#define  SET_DATA_DIBIT(pdata, n, val) \
+    ({l_uint32 *_TEMP_WORD_PTR_; \
+     _TEMP_WORD_PTR_ = (l_uint32 *)(pdata) + ((n) >> 4); \
+     *_TEMP_WORD_PTR_ &= ~(0xc0000000 >> (2 * ((n) & 15))); \
+     *_TEMP_WORD_PTR_ |= (((val) & 3) << (30 - 2 * ((n) & 15))); \
+    })
+
+#define  CLEAR_DATA_DIBIT(pdata, n) \
+    (*((l_uint32 *)(pdata) + ((n) >> 4)) &= ~(0xc0000000 >> (2 * ((n) & 15))))
+
+
+    /*--------------------------------------------------*
+     *                     4 bit access                 *
+     *--------------------------------------------------*/
+#define  GET_DATA_QBIT(pdata, n) \
+     ((*((l_uint32 *)(pdata) + ((n) >> 3)) >> (4 * (7 - ((n) & 7)))) & 0xf)
+
+#define  SET_DATA_QBIT(pdata, n, val) \
+    ({l_uint32 *_TEMP_WORD_PTR_; \
+     _TEMP_WORD_PTR_ = (l_uint32 *)(pdata) + ((n) >> 3); \
+     *_TEMP_WORD_PTR_ &= ~(0xf0000000 >> (4 * ((n) & 7))); \
+     *_TEMP_WORD_PTR_ |= (((val) & 15) << (28 - 4 * ((n) & 7))); \
+    })
+
+#define  CLEAR_DATA_QBIT(pdata, n) \
+    (*((l_uint32 *)(pdata) + ((n) >> 3)) &= ~(0xf0000000 >> (4 * ((n) & 7))))
+
+
+    /*--------------------------------------------------*
+     *                     8 bit access                 *
+     *--------------------------------------------------*/
+#ifdef  L_BIG_ENDIAN
+#define  GET_DATA_BYTE(pdata, n) \
+             (*((l_uint8 *)(pdata) + (n)))
+#else  /* L_LITTLE_ENDIAN */
+#define  GET_DATA_BYTE(pdata, n) \
+             (*(l_uint8 *)((l_uintptr_t)((l_uint8 *)(pdata) + (n)) ^ 3))
+#endif  /* L_BIG_ENDIAN */
+
+#ifdef  L_BIG_ENDIAN
+#define  SET_DATA_BYTE(pdata, n, val) \
+             (*((l_uint8 *)(pdata) + (n)) = (val))
+#else  /* L_LITTLE_ENDIAN */
+#define  SET_DATA_BYTE(pdata, n, val) \
+             (*(l_uint8 *)((l_uintptr_t)((l_uint8 *)(pdata) + (n)) ^ 3) = (val))
+#endif  /* L_BIG_ENDIAN */
+
+
+    /*--------------------------------------------------*
+     *                    16 bit access                 *
+     *--------------------------------------------------*/
+#ifdef  L_BIG_ENDIAN
+#define  GET_DATA_TWO_BYTES(pdata, n) \
+             (*((l_uint16 *)(pdata) + (n)))
+#else  /* L_LITTLE_ENDIAN */
+#define  GET_DATA_TWO_BYTES(pdata, n) \
+             (*(l_uint16 *)((l_uintptr_t)((l_uint16 *)(pdata) + (n)) ^ 2))
+#endif  /* L_BIG_ENDIAN */
+
+#ifdef  L_BIG_ENDIAN
+#define  SET_DATA_TWO_BYTES(pdata, n, val) \
+             (*((l_uint16 *)(pdata) + (n)) = (val))
+#else  /* L_LITTLE_ENDIAN */
+#define  SET_DATA_TWO_BYTES(pdata, n, val) \
+             (*(l_uint16 *)((l_uintptr_t)((l_uint16 *)(pdata) + (n)) ^ 2) = (val))
+#endif  /* L_BIG_ENDIAN */
+
+
+    /*--------------------------------------------------*
+     *                    32 bit access                 *
+     *--------------------------------------------------*/
+#define  GET_DATA_FOUR_BYTES(pdata, n) \
+             (*((l_uint32 *)(pdata) + (n)))
+
+#define  SET_DATA_FOUR_BYTES(pdata, n, val) \
+             (*((l_uint32 *)(pdata) + (n)) = (val))
+
+
+#endif  /* ! _MSC_VER */
+#endif  /* USE_INLINE_ACCESSORS */
+
+
+
+    /*--------------------------------------------------*
+     *  Slower, using function calls for all accessors  *
+     *--------------------------------------------------*/
+#if !USE_INLINE_ACCESSORS || defined(_MSC_VER)
+#define  GET_DATA_BIT(pdata, n)               l_getDataBit(pdata, n)
+#define  SET_DATA_BIT(pdata, n)               l_setDataBit(pdata, n)
+#define  CLEAR_DATA_BIT(pdata, n)             l_clearDataBit(pdata, n)
+#define  SET_DATA_BIT_VAL(pdata, n, val)      l_setDataBitVal(pdata, n, val)
+
+#define  GET_DATA_DIBIT(pdata, n)             l_getDataDibit(pdata, n)
+#define  SET_DATA_DIBIT(pdata, n, val)        l_setDataDibit(pdata, n, val)
+#define  CLEAR_DATA_DIBIT(pdata, n)           l_clearDataDibit(pdata, n)
+
+#define  GET_DATA_QBIT(pdata, n)              l_getDataQbit(pdata, n)
+#define  SET_DATA_QBIT(pdata, n, val)         l_setDataQbit(pdata, n, val)
+#define  CLEAR_DATA_QBIT(pdata, n)            l_clearDataQbit(pdata, n)
+
+#define  GET_DATA_BYTE(pdata, n)              l_getDataByte(pdata, n)
+#define  SET_DATA_BYTE(pdata, n, val)         l_setDataByte(pdata, n, val)
+
+#define  GET_DATA_TWO_BYTES(pdata, n)         l_getDataTwoBytes(pdata, n)
+#define  SET_DATA_TWO_BYTES(pdata, n, val)    l_setDataTwoBytes(pdata, n, val)
+
+#define  GET_DATA_FOUR_BYTES(pdata, n)         l_getDataFourBytes(pdata, n)
+#define  SET_DATA_FOUR_BYTES(pdata, n, val)    l_setDataFourBytes(pdata, n, val)
+#endif  /* !USE_INLINE_ACCESSORS || _MSC_VER */
+
+
+#endif /* LEPTONICA_ARRAY_ACCESS_H */
diff --git a/src/bardecode.c b/src/bardecode.c
new file mode 100644 (file)
index 0000000..002fd5e
--- /dev/null
@@ -0,0 +1,1005 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  bardecode.c
+ *
+ *      Dispatcher
+ *          char            *barcodeDispatchDecoder()
+ *
+ *      Format Determination
+ *          static l_int32   barcodeFindFormat()
+ *          l_int32          barcodeFormatIsSupported()
+ *          static l_int32   barcodeVerifyFormat()
+ *
+ *      Decode 2 of 5
+ *          static char     *barcodeDecode2of5()
+ *
+ *      Decode Interleaved 2 of 5
+ *          static char     *barcodeDecodeI2of5()
+ *
+ *      Decode Code 93
+ *          static char     *barcodeDecode93()
+ *
+ *      Decode Code 39
+ *          static char     *barcodeDecode39()
+ *
+ *      Decode Codabar
+ *          static char     *barcodeDecodeCodabar()
+ *
+ *      Decode UPC-A
+ *          static char     *barcodeDecodeUpca()
+ *
+ *      Decode EAN 13
+ *          static char     *barcodeDecodeEan13()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+#include "readbarcode.h"
+
+
+static l_int32 barcodeFindFormat(char *barstr);
+static l_int32 barcodeVerifyFormat(char *barstr, l_int32 format,
+                                   l_int32 *pvalid, l_int32 *preverse);
+static char *barcodeDecode2of5(char *barstr, l_int32 debugflag);
+static char *barcodeDecodeI2of5(char *barstr, l_int32 debugflag);
+static char *barcodeDecode93(char *barstr, l_int32 debugflag);
+static char *barcodeDecode39(char *barstr, l_int32 debugflag);
+static char *barcodeDecodeCodabar(char *barstr, l_int32 debugflag);
+static char *barcodeDecodeUpca(char *barstr, l_int32 debugflag);
+static char *barcodeDecodeEan13(char *barstr, l_int32 first, l_int32 debugflag);
+
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_CODES       0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*------------------------------------------------------------------------*
+ *                           Decoding dispatcher                          *
+ *------------------------------------------------------------------------*/
+/*!
+ *  barcodeDispatchDecoder()
+ *
+ *      Input:  barstr (string of integers in set {1,2,3,4} of bar widths)
+ *              format (L_BF_ANY, L_BF_CODEI2OF5, L_BF_CODE93, ...)
+ *              debugflag (use 1 to generate debug output)
+ *      Return: data (string of decoded barcode data), or null on error
+ */
+char *
+barcodeDispatchDecoder(char    *barstr,
+                       l_int32  format,
+                       l_int32  debugflag)
+{
+char  *data = NULL;
+
+    PROCNAME("barcodeDispatchDecoder");
+
+    if (!barstr)
+        return (char *)ERROR_PTR("barstr not defined", procName, NULL);
+
+    debugflag = FALSE;  /* not used yet */
+
+    if (format == L_BF_ANY)
+        format = barcodeFindFormat(barstr);
+
+    if (format == L_BF_CODE2OF5)
+        data = barcodeDecode2of5(barstr, debugflag);
+    else if (format == L_BF_CODEI2OF5)
+        data = barcodeDecodeI2of5(barstr, debugflag);
+    else if (format == L_BF_CODE93)
+        data = barcodeDecode93(barstr, debugflag);
+    else if (format == L_BF_CODE39)
+       data = barcodeDecode39(barstr, debugflag);
+    else if (format == L_BF_CODABAR)
+       data = barcodeDecodeCodabar(barstr, debugflag);
+    else if (format == L_BF_UPCA)
+       data = barcodeDecodeUpca(barstr, debugflag);
+    else if (format == L_BF_EAN13)
+       data = barcodeDecodeEan13(barstr, 0, debugflag);
+    else
+        return (char *)ERROR_PTR("format not implemented", procName, NULL);
+
+    return data;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                      Barcode format determination                      *
+ *------------------------------------------------------------------------*/
+/*!
+ *  barcodeFindFormat()
+ *
+ *      Input:  barstr (of barcode widths, in set {1,2,3,4})
+ *      Return: format (for barcode), or L_BF_UNKNOWN if not recognized
+ */
+static l_int32
+barcodeFindFormat(char    *barstr)
+{
+l_int32  i, format, valid;
+
+   PROCNAME("barcodeFindFormat");
+
+   if (!barstr)
+       return ERROR_INT("barstr not defined", procName, L_BF_UNKNOWN);
+
+   for (i = 0; i < NumSupportedBarcodeFormats; i++) {
+       format = SupportedBarcodeFormat[i];
+       barcodeVerifyFormat(barstr, format, &valid, NULL);
+       if (valid) {
+           L_INFO("Barcode format: %s\n", procName,
+                   SupportedBarcodeFormatName[i]);
+           return format;
+       }
+   }
+   return L_BF_UNKNOWN;
+}
+
+
+/*!
+ *  barcodeFormatIsSupported()
+ *
+ *      Input:  format
+ *      Return: 1 if format is one of those supported; 0 otherwise
+ *
+ */
+l_int32
+barcodeFormatIsSupported(l_int32  format)
+{
+l_int32  i;
+
+   for (i = 0; i < NumSupportedBarcodeFormats; i++) {
+       if (format == SupportedBarcodeFormat[i])
+           return 1;
+   }
+   return 0;
+}
+
+
+/*!
+ *  barcodeVerifyFormat()
+ *
+ *      Input:  barstr (of barcode widths, in set {1,2,3,4})
+ *              format (L_BF_CODEI2OF5, L_BF_CODE93, ...)
+ *              &valid (<return> 0 if not valid, 1 and 2 if valid)
+ *              &reverse (<optional return> 1 if reversed; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If valid == 1, the barcode is of the given format in the
+ *          forward order; if valid == 2, it is backwards.
+ *      (2) If the barcode needs to be reversed to read it, and &reverse
+ *          is provided, a 1 is put into @reverse.
+ *      (3) Add to this as more formats are supported.
+ */
+static l_int32
+barcodeVerifyFormat(char     *barstr,
+                    l_int32   format,
+                    l_int32  *pvalid,
+                    l_int32  *preverse)
+{
+char    *revbarstr;
+l_int32  i, start, len, stop, mid;
+
+    PROCNAME("barcodeVerifyFormat");
+
+    if (!pvalid)
+        return ERROR_INT("barstr not defined", procName, 1);
+    *pvalid = 0;
+    if (preverse) *preverse = 0;
+    if (!barstr)
+        return ERROR_INT("barstr not defined", procName, 1);
+
+    switch (format)
+    {
+    case L_BF_CODE2OF5:
+        start = !strncmp(barstr, Code2of5[C25_START], 3);
+        len = strlen(barstr);
+        stop = !strncmp(&barstr[len - 5], Code2of5[C25_STOP], 5);
+        if (start && stop) {
+            *pvalid = 1;
+        } else {
+            revbarstr = stringReverse(barstr);
+            start = !strncmp(revbarstr, Code2of5[C25_START], 3);
+            stop = !strncmp(&revbarstr[len - 5], Code2of5[C25_STOP], 5);
+            LEPT_FREE(revbarstr);
+            if (start && stop) {
+                *pvalid = 1;
+                if (preverse) *preverse = 1;
+            }
+        }
+        break;
+    case L_BF_CODEI2OF5:
+        start = !strncmp(barstr, CodeI2of5[CI25_START], 4);
+        len = strlen(barstr);
+        stop = !strncmp(&barstr[len - 3], CodeI2of5[CI25_STOP], 3);
+        if (start && stop) {
+            *pvalid = 1;
+        } else {
+            revbarstr = stringReverse(barstr);
+            start = !strncmp(revbarstr, CodeI2of5[CI25_START], 4);
+            stop = !strncmp(&revbarstr[len - 3], CodeI2of5[CI25_STOP], 3);
+            LEPT_FREE(revbarstr);
+            if (start && stop) {
+                *pvalid = 1;
+                if (preverse) *preverse = 1;
+            }
+        }
+        break;
+    case L_BF_CODE93:
+        start = !strncmp(barstr, Code93[C93_START], 6);
+        len = strlen(barstr);
+        stop = !strncmp(&barstr[len - 7], Code93[C93_STOP], 6);
+        if (start && stop) {
+            *pvalid = 1;
+        } else {
+            revbarstr = stringReverse(barstr);
+            start = !strncmp(revbarstr, Code93[C93_START], 6);
+            stop = !strncmp(&revbarstr[len - 7], Code93[C93_STOP], 6);
+            LEPT_FREE(revbarstr);
+            if (start && stop) {
+                *pvalid = 1;
+                if (preverse) *preverse = 1;
+            }
+        }
+        break;
+    case L_BF_CODE39:
+        start = !strncmp(barstr, Code39[C39_START], 9);
+        len = strlen(barstr);
+        stop = !strncmp(&barstr[len - 9], Code39[C39_STOP], 9);
+        if (start && stop) {
+            *pvalid = 1;
+        } else {
+            revbarstr = stringReverse(barstr);
+            start = !strncmp(revbarstr, Code39[C39_START], 9);
+            stop = !strncmp(&revbarstr[len - 9], Code39[C39_STOP], 9);
+            LEPT_FREE(revbarstr);
+            if (start && stop) {
+                *pvalid = 1;
+                if (preverse) *preverse = 1;
+            }
+        }
+        break;
+    case L_BF_CODABAR:
+        start = stop = 0;
+        len = strlen(barstr);
+        for (i = 16; i <= 19; i++)  /* any of these will do */
+            start += !strncmp(barstr, Codabar[i], 7);
+        for (i = 16; i <= 19; i++)  /* ditto */
+            stop += !strncmp(&barstr[len - 7], Codabar[i], 7);
+        if (start && stop) {
+            *pvalid = 1;
+        } else {
+            start = stop = 0;
+            revbarstr = stringReverse(barstr);
+            for (i = 16; i <= 19; i++)
+                start += !strncmp(revbarstr, Codabar[i], 7);
+            for (i = 16; i <= 19; i++)
+                stop += !strncmp(&revbarstr[len - 7], Codabar[i], 7);
+            LEPT_FREE(revbarstr);
+            if (start && stop) {
+                *pvalid = 1;
+                if (preverse) *preverse = 1;
+            }
+        }
+        break;
+    case L_BF_UPCA:
+    case L_BF_EAN13:
+        len = strlen(barstr);
+        if (len == 59) {
+            start = !strncmp(barstr, Upca[UPCA_START], 3);
+            mid = !strncmp(&barstr[27], Upca[UPCA_MID], 5);
+            stop = !strncmp(&barstr[len - 3], Upca[UPCA_STOP], 3);
+            if (start && mid && stop)
+                *pvalid = 1;
+        }
+        break;
+    default:
+        return ERROR_INT("format not supported", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                             Code 2 of 5                                *
+ *------------------------------------------------------------------------*/
+/*!
+ *  barcodeDecode2of5()
+ *
+ *      Input:  barstr (of widths, in set {1, 2})
+ *              debugflag
+ *      Return: data (string of digits), or null if none found or on error
+ *
+ *  Notes:
+ *      (1) Ref: http://en.wikipedia.org/wiki/Two-out-of-five_code (Note:
+ *                 the codes given here are wrong!)
+ *               http://morovia.com/education/symbology/code25.asp
+ *      (2) This is a very low density encoding for the 10 digits.
+ *          Each digit is encoded with 5 black bars, of which 2 are wide
+ *          and 3 are narrow.  No information is carried in the spaces
+ *          between the bars, which are all equal in width, represented by
+ *          a "1" in our encoding.
+ *      (3) The mapping from the sequence of five bar widths to the
+ *          digit is identical to the mapping used by the interleaved
+ *          2 of 5 code.  The start code is 21211, representing two
+ *          wide bars and a narrow bar, and the interleaved "1" spaces
+ *          are explicit.  The stop code is 21112.  For all codes
+ *          (including start and stop), the trailing space "1" is
+ *          implicit -- there is no reason to represent it in the
+ *          Code2of5[] array.
+ */
+static char *
+barcodeDecode2of5(char    *barstr,
+                  l_int32  debugflag)
+{
+char    *data, *vbarstr;
+char     code[10];
+l_int32  valid, reverse, i, j, len, error, ndigits, start, found;
+
+    PROCNAME("barcodeDecodeI2of5");
+
+    if (!barstr)
+        return (char *)ERROR_PTR("barstr not defined", procName, NULL);
+
+        /* Verify format; reverse if necessary */
+    barcodeVerifyFormat(barstr, L_BF_CODE2OF5, &valid, &reverse);
+    if (!valid)
+        return (char *)ERROR_PTR("barstr not in 2of5 format", procName, NULL);
+    if (reverse)
+        vbarstr = stringReverse(barstr);
+    else
+        vbarstr = stringNew(barstr);
+
+        /* Verify size */
+    len = strlen(vbarstr);
+    if ((len - 11) % 10 != 0)
+        return (char *)ERROR_PTR("size not divisible by 10: invalid 2of5 code",
+                                 procName, NULL);
+
+    error = FALSE;
+    ndigits = (len - 11) / 10;
+    data = (char *)LEPT_CALLOC(ndigits + 1, sizeof(char));
+    memset(code, 0, 10);
+    for (i = 0; i < ndigits; i++) {
+        start = 6 + 10 * i;
+        for (j = 0; j < 9; j++)
+            code[j] = vbarstr[start + j];
+
+        if (debugflag)
+            fprintf(stderr, "code: %s\n", code);
+
+        found = FALSE;
+        for (j = 0; j < 10; j++) {
+            if (!strcmp(code, Code2of5[j])) {
+                data[i] = 0x30 + j;
+                found = TRUE;
+                break;
+            }
+        }
+        if (!found) error = TRUE;
+    }
+    LEPT_FREE(vbarstr);
+
+    if (error) {
+        LEPT_FREE(data);
+        return (char *)ERROR_PTR("error in decoding", procName, NULL);
+    }
+
+    return data;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                       Interleaved Code 2 of 5                          *
+ *------------------------------------------------------------------------*/
+/*!
+ *  barcodeDecodeI2of5()
+ *
+ *      Input:  barstr (of widths, in set {1, 2})
+ *              debugflag
+ *      Return: data (string of digits), or null if none found or on error
+ *
+ *  Notes:
+ *      (1) Ref: http://en.wikipedia.org/wiki/Interleaved_2_of_5
+ *      (2) This always encodes an even number of digits.
+ *          The start code is 1111; the stop code is 211.
+ */
+static char *
+barcodeDecodeI2of5(char    *barstr,
+                   l_int32  debugflag)
+{
+char    *data, *vbarstr;
+char     code1[6], code2[6];
+l_int32  valid, reverse, i, j, len, error, npairs, start, found;
+
+    PROCNAME("barcodeDecodeI2of5");
+
+    if (!barstr)
+        return (char *)ERROR_PTR("barstr not defined", procName, NULL);
+
+        /* Verify format; reverse if necessary */
+    barcodeVerifyFormat(barstr, L_BF_CODEI2OF5, &valid, &reverse);
+    if (!valid)
+        return (char *)ERROR_PTR("barstr not in i2of5 format", procName, NULL);
+    if (reverse)
+        vbarstr = stringReverse(barstr);
+    else
+        vbarstr = stringNew(barstr);
+
+        /* Verify size */
+    len = strlen(vbarstr);
+    if ((len - 7) % 10 != 0)
+        return (char *)ERROR_PTR("size not divisible by 10: invalid I2of5 code",
+                                 procName, NULL);
+
+    error = FALSE;
+    npairs = (len - 7) / 10;
+    data = (char *)LEPT_CALLOC(2 * npairs + 1, sizeof(char));
+    memset(code1, 0, 6);
+    memset(code2, 0, 6);
+    for (i = 0; i < npairs; i++) {
+        start = 4 + 10 * i;
+        for (j = 0; j < 5; j++) {
+            code1[j] = vbarstr[start + 2 * j];
+            code2[j] = vbarstr[start + 2 * j + 1];
+        }
+
+        if (debugflag)
+            fprintf(stderr, "code1: %s, code2: %s\n", code1, code2);
+
+        found = FALSE;
+        for (j = 0; j < 10; j++) {
+            if (!strcmp(code1, CodeI2of5[j])) {
+                data[2 * i] = 0x30 + j;
+                found = TRUE;
+                break;
+            }
+        }
+        if (!found) error = TRUE;
+        found = FALSE;
+        for (j = 0; j < 10; j++) {
+            if (!strcmp(code2, CodeI2of5[j])) {
+                data[2 * i + 1] = 0x30 + j;
+                found = TRUE;
+                break;
+            }
+        }
+        if (!found) error = TRUE;
+    }
+    LEPT_FREE(vbarstr);
+
+    if (error) {
+        LEPT_FREE(data);
+        return (char *)ERROR_PTR("error in decoding", procName, NULL);
+    }
+
+    return data;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                                 Code 93                                *
+ *------------------------------------------------------------------------*/
+/*!
+ *  barcodeDecode93()
+ *
+ *      Input:  barstr (of widths, in set {1, 2, 3, 4})
+ *              debugflag
+ *      Return: data (string of digits), or null if none found or on error
+ *
+ *  Notes:
+ *      (1) Ref:  http://en.wikipedia.org/wiki/Code93
+ *                http://morovia.com/education/symbology/code93.asp
+ *      (2) Each symbol has 3 black and 3 white bars.
+ *          The start and stop codes are 111141; the stop code then is
+ *          terminated with a final (1) bar.
+ *      (3) The last two codes are check codes.  We are checking them
+ *          for correctness, and issuing a warning on failure.  Should
+ *          probably not return any data on failure.
+ */
+static char *
+barcodeDecode93(char    *barstr,
+                l_int32  debugflag)
+{
+const char  *checkc, *checkk;
+char        *data, *vbarstr;
+char         code[7];
+l_int32      valid, reverse, i, j, len, error, nsymb, start, found, sum;
+l_int32     *index;
+
+    PROCNAME("barcodeDecode93");
+
+    if (!barstr)
+        return (char *)ERROR_PTR("barstr not defined", procName, NULL);
+
+        /* Verify format; reverse if necessary */
+    barcodeVerifyFormat(barstr, L_BF_CODE93, &valid, &reverse);
+    if (!valid)
+        return (char *)ERROR_PTR("barstr not in code93 format", procName, NULL);
+    if (reverse)
+        vbarstr = stringReverse(barstr);
+    else
+        vbarstr = stringNew(barstr);
+
+        /* Verify size; skip the first 6 and last 7 bars. */
+    len = strlen(vbarstr);
+    if ((len - 13) % 6 != 0)
+        return (char *)ERROR_PTR("size not divisible by 6: invalid code 93",
+                                 procName, NULL);
+
+        /* Decode the symbols */
+    nsymb = (len - 13) / 6;
+    data = (char *)LEPT_CALLOC(nsymb + 1, sizeof(char));
+    index = (l_int32 *)LEPT_CALLOC(nsymb, sizeof(l_int32));
+    memset(code, 0, 7);
+    error = FALSE;
+    for (i = 0; i < nsymb; i++) {
+        start = 6 + 6 * i;
+        for (j = 0; j < 6; j++)
+            code[j] = vbarstr[start + j];
+
+        if (debugflag)
+            fprintf(stderr, "code: %s\n", code);
+
+        found = FALSE;
+        for (j = 0; j < C93_START; j++) {
+            if (!strcmp(code, Code93[j])) {
+                data[i] = Code93Val[j];
+                index[i] = j;
+                found = TRUE;
+                break;
+            }
+        }
+        if (!found) error = TRUE;
+    }
+    LEPT_FREE(vbarstr);
+
+    if (error) {
+        LEPT_FREE(index);
+        LEPT_FREE(data);
+        return (char *)ERROR_PTR("error in decoding", procName, NULL);
+    }
+
+        /* Do check sums.  For character "C", use only the
+         * actual data in computing the sum.  For character "K",
+         * use the actual data plus the check character "C". */
+    sum = 0;
+    for (i = 0; i < nsymb - 2; i++)  /* skip the "C" and "K" */
+        sum += ((i % 20) + 1) * index[nsymb - 3 - i];
+    if (data[nsymb - 2] != Code93Val[sum % 47])
+        L_WARNING("Error for check C\n", procName);
+
+    if (debugflag) {
+        checkc = Code93[sum % 47];
+        fprintf(stderr, "checkc = %s\n", checkc);
+    }
+
+    sum = 0;
+    for (i = 0; i < nsymb - 1; i++)  /* skip the "K" */
+        sum += ((i % 15) + 1) * index[nsymb - 2 - i];
+    if (data[nsymb - 1] != Code93Val[sum % 47])
+        L_WARNING("Error for check K\n", procName);
+
+    if (debugflag) {
+        checkk = Code93[sum % 47];
+        fprintf(stderr, "checkk = %s\n", checkk);
+    }
+
+        /* Remove the two check codes from the output */
+    data[nsymb - 2] = '\0';
+
+    LEPT_FREE(index);
+    return data;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                                 Code 39                                *
+ *------------------------------------------------------------------------*/
+/*!
+ *  barcodeDecode39()
+ *
+ *      Input:  barstr (of widths, in set {1, 2})
+ *              debugflag
+ *      Return: data (string of digits), or null if none found or on error
+ *
+ *  Notes:
+ *      (1) Ref:  http://en.wikipedia.org/wiki/Code39
+ *                http://morovia.com/education/symbology/code39.asp
+ *      (2) Each symbol has 5 black and 4 white bars.
+ *          The start and stop codes are 121121211 (the asterisk)
+ *      (3) This decoder was contributed by Roger Hyde.
+ */
+static char *
+barcodeDecode39(char    *barstr,
+                l_int32  debugflag)
+{
+char     *data, *vbarstr;
+char      code[10];
+l_int32   valid, reverse, i, j, len, error, nsymb, start, found;
+
+    PROCNAME("barcodeDecode39");
+
+    if (!barstr)
+        return (char *)ERROR_PTR("barstr not defined", procName, NULL);
+
+        /* Verify format; reverse if necessary */
+    barcodeVerifyFormat(barstr, L_BF_CODE39, &valid, &reverse);
+    if (!valid)
+        return (char *)ERROR_PTR("barstr not in code39 format", procName, NULL);
+    if (reverse)
+        vbarstr = stringReverse(barstr);
+    else
+        vbarstr = stringNew(barstr);
+
+        /* Verify size */
+    len = strlen(vbarstr);
+    if ((len + 1) % 10 != 0)
+        return (char *)ERROR_PTR("size+1 not divisible by 10: invalid code 39",
+                                 procName, NULL);
+
+        /* Decode the symbols */
+    nsymb = (len - 19) / 10;
+    data = (char *)LEPT_CALLOC(nsymb + 1, sizeof(char));
+    memset(code, 0, 10);
+    error = FALSE;
+    for (i = 0; i < nsymb; i++) {
+        start = 10 + 10 * i;
+        for (j = 0; j < 9; j++)
+            code[j] = vbarstr[start + j];
+
+        if (debugflag)
+            fprintf(stderr, "code: %s\n", code);
+
+        found = FALSE;
+        for (j = 0; j < C39_START; j++) {
+            if (!strcmp(code, Code39[j])) {
+                data[i] = Code39Val[j];
+                found = TRUE;
+                break;
+            }
+        }
+        if (!found) error = TRUE;
+    }
+    LEPT_FREE(vbarstr);
+
+    if (error) {
+        LEPT_FREE(data);
+        return (char *)ERROR_PTR("error in decoding", procName, NULL);
+    }
+
+    return data;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                                 Codabar                                *
+ *------------------------------------------------------------------------*/
+/*!
+ *  barcodeDecodeCodabar()
+ *
+ *      Input:  barstr (of widths, in set {1, 2})
+ *              debugflag
+ *      Return: data (string of digits), or null if none found or on error
+ *
+ *  Notes:
+ *      (1) Ref:  http://en.wikipedia.org/wiki/Codabar
+ *                http://morovia.com/education/symbology/codabar.asp
+ *      (2) Each symbol has 4 black and 3 white bars.  They represent the
+ *          10 digits, and optionally 6 other characters.  The start and
+ *          stop codes can be any of four (typically denoted A,B,C,D).
+ */
+static char *
+barcodeDecodeCodabar(char    *barstr,
+                     l_int32  debugflag)
+{
+char     *data, *vbarstr;
+char      code[8];
+l_int32   valid, reverse, i, j, len, error, nsymb, start, found;
+
+    PROCNAME("barcodeDecodeCodabar");
+
+    if (!barstr)
+        return (char *)ERROR_PTR("barstr not defined", procName, NULL);
+
+        /* Verify format; reverse if necessary */
+    barcodeVerifyFormat(barstr, L_BF_CODABAR, &valid, &reverse);
+    if (!valid)
+        return (char *)ERROR_PTR("barstr not in codabar format",
+                                 procName, NULL);
+    if (reverse)
+        vbarstr = stringReverse(barstr);
+    else
+        vbarstr = stringNew(barstr);
+
+        /* Verify size */
+    len = strlen(vbarstr);
+    if ((len + 1) % 8 != 0)
+        return (char *)ERROR_PTR("size+1 not divisible by 8: invalid codabar",
+                                 procName, NULL);
+
+        /* Decode the symbols */
+    nsymb = (len - 15) / 8;
+    data = (char *)LEPT_CALLOC(nsymb + 1, sizeof(char));
+    memset(code, 0, 8);
+    error = FALSE;
+    for (i = 0; i < nsymb; i++) {
+        start = 8 + 8 * i;
+        for (j = 0; j < 7; j++)
+            code[j] = vbarstr[start + j];
+
+        if (debugflag)
+            fprintf(stderr, "code: %s\n", code);
+
+        found = FALSE;
+        for (j = 0; j < 16; j++) {
+            if (!strcmp(code, Codabar[j])) {
+                data[i] = CodabarVal[j];
+                found = TRUE;
+                break;
+            }
+        }
+        if (!found) error = TRUE;
+    }
+    LEPT_FREE(vbarstr);
+
+    if (error) {
+        LEPT_FREE(data);
+        return (char *)ERROR_PTR("error in decoding", procName, NULL);
+    }
+
+    return data;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                               Code UPC-A                               *
+ *------------------------------------------------------------------------*/
+/*!
+ *  barcodeDecodeUpca()
+ *
+ *      Input:  barstr (of widths, in set {1, 2, 3, 4})
+ *              debugflag
+ *      Return: data (string of digits), or null if none found or on error
+ *
+ *  Notes:
+ *      (1) Ref:  http://en.wikipedia.org/wiki/UniversalProductCode
+ *                http://morovia.com/education/symbology/upc-a.asp
+ *      (2) Each symbol has 2 black and 2 white bars, and encodes a digit.
+ *          The start and stop codes are 111 and 111.  There are a total of
+ *          30 black bars, encoding 12 digits in two sets of 6, with
+ *          2 black bars separating the sets.
+ *      (3) The last digit is a check digit.  We check for correctness, and
+ *          issue a warning on failure.  Should probably not return any
+ *          data on failure.
+ */
+static char *
+barcodeDecodeUpca(char    *barstr,
+                  l_int32  debugflag)
+{
+char     *data, *vbarstr;
+char      code[5];
+l_int32   valid, i, j, len, error, start, found, sum, checkdigit;
+
+    PROCNAME("barcodeDecodeUpca");
+
+    if (!barstr)
+        return (char *)ERROR_PTR("barstr not defined", procName, NULL);
+
+        /* Verify format; reverse has no meaning here -- we must test both */
+    barcodeVerifyFormat(barstr, L_BF_UPCA, &valid, NULL);
+    if (!valid)
+        return (char *)ERROR_PTR("barstr not in UPC-A format", procName, NULL);
+
+        /* Verify size */
+    len = strlen(barstr);
+    if (len != 59)
+        return (char *)ERROR_PTR("size not 59; invalid UPC-A barcode",
+                                 procName, NULL);
+
+        /* Check the first digit.  If invalid, reverse the string. */
+    memset(code, 0, 5);
+    for (i = 0; i < 4; i++)
+        code[i] = barstr[i + 3];
+    found = FALSE;
+    for (i = 0; i < 10; i++) {
+        if (!strcmp(code, Upca[i])) {
+            found = TRUE;
+            break;
+        }
+    }
+    if (found == FALSE)
+        vbarstr = stringReverse(barstr);
+    else
+        vbarstr = stringNew(barstr);
+
+        /* Decode the 12 symbols */
+    data = (char *)LEPT_CALLOC(13, sizeof(char));
+    memset(code, 0, 5);
+    error = FALSE;
+    for (i = 0; i < 12; i++) {
+        if (i < 6)
+            start = 3 + 4 * i;
+        else
+            start = 32 + 4 * (i - 6);
+        for (j = 0; j < 4; j++)
+            code[j] = vbarstr[start + j];
+
+        if (debugflag)
+            fprintf(stderr, "code: %s\n", code);
+
+        found = FALSE;
+        for (j = 0; j < 10; j++) {
+            if (!strcmp(code, Upca[j])) {
+                data[i] = 0x30 + j;
+                found = TRUE;
+                break;
+            }
+        }
+        if (!found) error = TRUE;
+    }
+    LEPT_FREE(vbarstr);
+
+    if (error) {
+        LEPT_FREE(data);
+        return (char *)ERROR_PTR("error in decoding", procName, NULL);
+    }
+
+        /* Calculate the check digit (data[11]). */
+    sum = 0;
+    for (i = 0; i < 12; i += 2)  /* "even" digits */
+        sum += 3 * (data[i] - 0x30);
+    for (i = 1; i < 11; i += 2)  /* "odd" digits */
+        sum += (data[i] - 0x30);
+    checkdigit = sum % 10;
+    if (checkdigit)  /* not 0 */
+        checkdigit = 10 - checkdigit;
+    if (checkdigit + 0x30 != data[11])
+        L_WARNING("Error for UPC-A check character\n", procName);
+
+    return data;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                               Code EAN-13                              *
+ *------------------------------------------------------------------------*/
+/*!
+ *  barcodeDecodeEan13()
+ *
+ *      Input:  barstr (of widths, in set {1, 2, 3, 4})
+ *              first (first digit: 0 - 9)
+ *              debugflag
+ *      Return: data (string of digits), or null if none found or on error
+ *
+ *  Notes:
+ *      (1) Ref:  http://en.wikipedia.org/wiki/UniversalProductCode
+ *                http://morovia.com/education/symbology/ean-13.asp
+ *      (2) The encoding is essentially the same as UPC-A, except
+ *          there are 13 digits in total, of which 12 are encoded
+ *          by bars (as with UPC-A) and the 13th is a leading digit
+ *          that determines the encoding of the next 6 digits,
+ *          selecting each digit from one of two tables.
+ *          encoded in the bars (as with UPC-A).  If the first digit
+ *          is 0, the encoding is identical to UPC-A.
+ *      (3) As with UPC-A, the last digit is a check digit.
+ *      (4) For now, we assume the first digit is input to this function.
+ *          Eventually, we will read it by pattern matching.
+ *
+ *    TODO: fix this for multiple tables, depending on the value of @first
+ */
+static char *
+barcodeDecodeEan13(char    *barstr,
+                   l_int32  first,
+                   l_int32  debugflag)
+{
+char     *data, *vbarstr;
+char      code[5];
+l_int32   valid, i, j, len, error, start, found, sum, checkdigit;
+
+    PROCNAME("barcodeDecodeEan13");
+
+    if (!barstr)
+        return (char *)ERROR_PTR("barstr not defined", procName, NULL);
+
+        /* Verify format.  You can't tell the orientation by the start
+         * and stop codes, but you can by the location of the digits.
+         * Use the UPCA verifier for EAN 13 -- it is identical. */
+    barcodeVerifyFormat(barstr, L_BF_UPCA, &valid, NULL);
+    if (!valid)
+        return (char *)ERROR_PTR("barstr not in EAN 13 format", procName, NULL);
+
+        /* Verify size */
+    len = strlen(barstr);
+    if (len != 59)
+        return (char *)ERROR_PTR("size not 59; invalid EAN 13 barcode",
+                                 procName, NULL);
+
+        /* Check the first digit.  If invalid, reverse the string. */
+    memset(code, 0, 5);
+    for (i = 0; i < 4; i++)
+        code[i] = barstr[i + 3];
+    found = FALSE;
+    for (i = 0; i < 10; i++) {
+        if (!strcmp(code, Upca[i])) {
+            found = TRUE;
+            break;
+        }
+    }
+    if (found == FALSE)
+        vbarstr = stringReverse(barstr);
+    else
+        vbarstr = stringNew(barstr);
+
+        /* Decode the 12 symbols */
+    data = (char *)LEPT_CALLOC(13, sizeof(char));
+    memset(code, 0, 5);
+    error = FALSE;
+    for (i = 0; i < 12; i++) {
+        if (i < 6)
+            start = 3 + 4 * i;
+        else
+            start = 32 + 4 * (i - 6);
+        for (j = 0; j < 4; j++)
+            code[j] = vbarstr[start + j];
+
+        if (debugflag)
+            fprintf(stderr, "code: %s\n", code);
+
+        found = FALSE;
+        for (j = 0; j < 10; j++) {
+            if (!strcmp(code, Upca[j])) {
+                data[i] = 0x30 + j;
+                found = TRUE;
+                break;
+            }
+        }
+        if (!found) error = TRUE;
+    }
+    LEPT_FREE(vbarstr);
+
+    if (error) {
+        LEPT_FREE(data);
+        return (char *)ERROR_PTR("error in decoding", procName, NULL);
+    }
+
+        /* Calculate the check digit (data[11]). */
+    sum = 0;
+    for (i = 0; i < 12; i += 2)  /* "even" digits */
+        sum += 3 * (data[i] - 0x30);
+    for (i = 1; i < 12; i += 2)  /* "odd" digits */
+        sum += (data[i] - 0x30);
+    checkdigit = sum % 10;
+    if (checkdigit)  /* not 0 */
+        checkdigit = 10 - checkdigit;
+    if (checkdigit + 0x30 != data[11])
+        L_WARNING("Error for EAN-13 check character\n", procName);
+
+    return data;
+}
diff --git a/src/baseline.c b/src/baseline.c
new file mode 100644 (file)
index 0000000..8d05951
--- /dev/null
@@ -0,0 +1,569 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  baseline.c
+ *
+ *      Locate text baselines in an image
+ *           NUMA     *pixFindBaselines()
+ *
+ *      Projective transform to remove local skew
+ *           PIX      *pixDeskewLocal()
+ *
+ *      Determine local skew
+ *           l_int32   pixGetLocalSkewTransform()
+ *           NUMA     *pixGetLocalSkewAngles()
+ *
+ *  We have two apparently different functions here:
+ *    - finding baselines
+ *    - finding a projective transform to remove keystone warping
+ *  The function pixGetLocalSkewAngles() returns an array of angles,
+ *  one for each raster line, and the baselines of the text lines
+ *  should intersect the left edge of the image with that angle.
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_PLOT          0
+#endif  /* NO_CONSOLE_IO */
+
+    /* Min to travel after finding max before abandoning peak */
+static const l_int32  MIN_DIST_IN_PEAK = 35;
+
+    /* Thresholds for peaks and zeros, relative to the max peak */
+static const l_int32  PEAK_THRESHOLD_RATIO = 20;
+static const l_int32  ZERO_THRESHOLD_RATIO = 100;
+
+    /* Default values for determining local skew */
+static const l_int32  DEFAULT_SLICES = 10;
+static const l_int32  DEFAULT_SWEEP_REDUCTION = 2;
+static const l_int32  DEFAULT_BS_REDUCTION = 1;
+static const l_float32  DEFAULT_SWEEP_RANGE = 5.;   /* degrees */
+static const l_float32  DEFAULT_SWEEP_DELTA = 1.;   /* degrees */
+static const l_float32  DEFAULT_MINBS_DELTA = 0.01;   /* degrees */
+
+    /* Overlap slice fraction added to top and bottom of each slice */
+static const l_float32  OVERLAP_FRACTION = 0.5;
+
+    /* Minimum allowed confidence (ratio) for accepting a value */
+static const l_float32  MIN_ALLOWED_CONFIDENCE = 3.0;
+
+
+/*---------------------------------------------------------------------*
+ *                    Locate text baselines in an image                *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixFindBaselines()
+ *
+ *      Input:  pixs (1 bpp)
+ *              &pta (<optional return> pairs of pts corresponding to
+ *                    approx. ends of each text line)
+ *              debug (usually 0; set to 1 for debugging output)
+ *      Return: na (of baseline y values), or null on error
+ *
+ *  Notes:
+ *      (1) Input binary image must have text lines already aligned
+ *          horizontally.  This can be done by either rotating the
+ *          image with pixDeskew(), or, if a projective transform
+ *          is required, by doing pixDeskewLocal() first.
+ *      (2) Input null for &pta if you don't want this returned.
+ *          The pta will come in pairs of points (left and right end
+ *          of each baseline).
+ *      (3) Caution: this will not work properly on text with multiple
+ *          columns, where the lines are not aligned between columns.
+ *          If there are multiple columns, they should be extracted
+ *          separately before finding the baselines.
+ *      (4) This function constructs different types of output
+ *          for baselines; namely, a set of raster line values and
+ *          a set of end points of each baseline.
+ *      (5) This function was designed to handle short and long text lines
+ *          without using dangerous thresholds on the peak heights.  It does
+ *          this by combining the differential signal with a morphological
+ *          analysis of the locations of the text lines.  One can also
+ *          combine this data to normalize the peak heights, by weighting
+ *          the differential signal in the region of each baseline
+ *          by the inverse of the width of the text line found there.
+ *      (6) There are various debug sections that can be turned on
+ *          with the debug flag.
+ */
+NUMA *
+pixFindBaselines(PIX     *pixs,
+                 PTA    **ppta,
+                 l_int32  debug)
+{
+l_int32    h, i, j, nbox, val1, val2, ndiff, bx, by, bw, bh;
+l_int32    imaxloc, peakthresh, zerothresh, inpeak;
+l_int32    mintosearch, max, maxloc, nloc, locval;
+l_int32   *array;
+l_float32  maxval;
+BOXA      *boxa1, *boxa2, *boxa3;
+GPLOT     *gplot;
+NUMA      *nasum, *nadiff, *naloc, *naval;
+PIX       *pixt1, *pixt2;
+PTA       *pta;
+
+    PROCNAME("pixFindBaselines");
+
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    pta = NULL;
+    if (ppta) {
+        pta = ptaCreate(0);
+        *ppta = pta;
+    }
+
+        /* Close up the text characters, removing noise */
+    pixt1 = pixMorphSequence(pixs, "c25.1 + e3.1", 0);
+
+        /* Save the difference of adjacent row sums.
+         * The high positive-going peaks are the baselines */
+    if ((nasum = pixCountPixelsByRow(pixt1, NULL)) == NULL)
+        return (NUMA *)ERROR_PTR("nasum not made", procName, NULL);
+    h = pixGetHeight(pixs);
+    nadiff = numaCreate(h);
+    numaGetIValue(nasum, 0, &val2);
+    for (i = 0; i < h - 1; i++) {
+        val1 = val2;
+        numaGetIValue(nasum, i + 1, &val2);
+        numaAddNumber(nadiff, val1 - val2);
+    }
+
+    if (debug)  /* show the difference signal */
+        gplotSimple1(nadiff, GPLOT_X11, "junkdiff", "difference");
+
+        /* Use the zeroes of the profile to locate each baseline. */
+    array = numaGetIArray(nadiff);
+    ndiff = numaGetCount(nadiff);
+    numaGetMax(nadiff, &maxval, &imaxloc);
+        /* Use this to begin locating a new peak: */
+    peakthresh = (l_int32)maxval / PEAK_THRESHOLD_RATIO;
+        /* Use this to begin a region between peaks: */
+    zerothresh = (l_int32)maxval / ZERO_THRESHOLD_RATIO;
+    naloc = numaCreate(0);
+    naval = numaCreate(0);
+    inpeak = FALSE;
+    for (i = 0; i < ndiff; i++) {
+        if (inpeak == FALSE) {
+            if (array[i] > peakthresh) {  /* transition to in-peak */
+                inpeak = TRUE;
+                mintosearch = i + MIN_DIST_IN_PEAK; /* accept no zeros
+                                               * between i and mintosearch */
+                max = array[i];
+                maxloc = i;
+            }
+        } else {  /* inpeak == TRUE; look for max */
+            if (array[i] > max) {
+                max = array[i];
+                maxloc = i;
+                mintosearch = i + MIN_DIST_IN_PEAK;
+            } else if (i > mintosearch && array[i] <= zerothresh) {  /* leave */
+                inpeak = FALSE;
+                numaAddNumber(naval, max);
+                numaAddNumber(naloc, maxloc);
+            }
+        }
+    }
+
+        /* If array[ndiff-1] is max, eg. no descenders, baseline at bottom */
+    if (inpeak) {
+        numaAddNumber(naval, max);
+        numaAddNumber(naloc, maxloc);
+    }
+    LEPT_FREE(array);
+
+    if (debug) {  /* show the raster locations for the peaks */
+        gplot = gplotCreate("junkloc", GPLOT_X11, "Peak locations",
+                            "rasterline", "height");
+        gplotAddPlot(gplot, naloc, naval, GPLOT_POINTS, "locs");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+    }
+
+        /* Generate an approximate profile of text line width.
+         * First, filter the boxes of text, where there may be
+         * more than one box for a given textline. */
+    pixt2 = pixMorphSequence(pixt1, "r11 + c25.1 + o7.1 +c1.3", 0);
+    boxa1 = pixConnComp(pixt2, NULL, 4);
+    boxa2 = boxaTransform(boxa1, 0, 0, 4., 4.);
+    boxa3 = boxaSort(boxa2, L_SORT_BY_Y, L_SORT_INCREASING, NULL);
+
+        /* Then find the baseline segments */
+    if (pta) {
+      nloc = numaGetCount(naloc);
+      nbox = boxaGetCount(boxa3);
+      for (i = 0; i < nbox; i++) {
+          boxaGetBoxGeometry(boxa3, i, &bx, &by, &bw, &bh);
+          for (j = 0; j < nloc; j++) {
+              numaGetIValue(naloc, j, &locval);
+              if (L_ABS(locval - (by + bh)) > 25)
+                  continue;
+              ptaAddPt(pta, bx, locval);
+              ptaAddPt(pta, bx + bw, locval);
+              break;
+          }
+      }
+    }
+
+    if (debug) {  /* display baselines */
+        PIX     *pixd;
+        l_int32  npts, x1, y1, x2, y2;
+        if (pta) {
+            pixd = pixConvertTo32(pixs);
+            npts = ptaGetCount(pta);
+            for (i = 0; i < npts; i += 2) {
+                ptaGetIPt(pta, i, &x1, &y1);
+                ptaGetIPt(pta, i + 1, &x2, &y2);
+                pixRenderLineArb(pixd, x1, y1, x2, y2, 1, 255, 0, 0);
+            }
+            pixDisplay(pixd, 200, 200);
+            pixWrite("junkbaselines", pixd, IFF_PNG);
+            pixDestroy(&pixd);
+        }
+    }
+
+    boxaDestroy(&boxa1);
+    boxaDestroy(&boxa2);
+    boxaDestroy(&boxa3);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    numaDestroy(&nasum);
+    numaDestroy(&nadiff);
+    numaDestroy(&naval);
+    return naloc;
+}
+
+
+/*---------------------------------------------------------------------*
+ *               Projective transform to remove local skew             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixDeskewLocal()
+ *
+ *      Input:  pixs
+ *              nslices  (the number of horizontal overlapping slices; must
+ *                  be larger than 1 and not exceed 20; use 0 for default)
+ *              redsweep (sweep reduction factor: 1, 2, 4 or 8;
+ *                        use 0 for default value)
+ *              redsearch (search reduction factor: 1, 2, 4 or 8, and
+ *                         not larger than redsweep; use 0 for default value)
+ *              sweeprange (half the full range, assumed about 0; in degrees;
+ *                          use 0.0 for default value)
+ *              sweepdelta (angle increment of sweep; in degrees;
+ *                          use 0.0 for default value)
+ *              minbsdelta (min binary search increment angle; in degrees;
+ *                          use 0.0 for default value)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This function allows deskew of a page whose skew changes
+ *          approximately linearly with vertical position.  It uses
+ *          a projective transform that in effect does a differential
+ *          shear about the LHS of the page, and makes all text lines
+ *          horizontal.
+ *      (2) The origin of the keystoning can be either a cheap document
+ *          feeder that rotates the page as it is passed through, or a
+ *          camera image taken from either the left or right side
+ *          of the vertical.
+ *      (3) The image transformation is a projective warping,
+ *          not a rotation.  Apart from this function, the text lines
+ *          must be properly aligned vertically with respect to each
+ *          other.  This can be done by pre-processing the page; e.g.,
+ *          by rotating or horizontally shearing it.
+ *          Typically, this can be achieved by vertically aligning
+ *          the page edge.
+ */
+PIX *
+pixDeskewLocal(PIX       *pixs,
+               l_int32    nslices,
+               l_int32    redsweep,
+               l_int32    redsearch,
+               l_float32  sweeprange,
+               l_float32  sweepdelta,
+               l_float32  minbsdelta)
+{
+l_int32    ret;
+PIX       *pixd;
+PTA       *ptas, *ptad;
+
+    PROCNAME("pixDeskewLocal");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+        /* Skew array gives skew angle (deg) as fctn of raster line
+         * where it intersects the LHS of the image */
+    ret = pixGetLocalSkewTransform(pixs, nslices, redsweep, redsearch,
+                                   sweeprange, sweepdelta, minbsdelta,
+                                   &ptas, &ptad);
+    if (ret != 0)
+        return (PIX *)ERROR_PTR("transform pts not found", procName, NULL);
+
+        /* Use a projective transform */
+    pixd = pixProjectiveSampledPta(pixs, ptad, ptas, L_BRING_IN_WHITE);
+
+    ptaDestroy(&ptas);
+    ptaDestroy(&ptad);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                       Determine the local skew                      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixGetLocalSkewTransform()
+ *
+ *      Input:  pixs
+ *              nslices  (the number of horizontal overlapping slices; must
+ *                  be larger than 1 and not exceed 20; use 0 for default)
+ *              redsweep (sweep reduction factor: 1, 2, 4 or 8;
+ *                        use 0 for default value)
+ *              redsearch (search reduction factor: 1, 2, 4 or 8, and
+ *                         not larger than redsweep; use 0 for default value)
+ *              sweeprange (half the full range, assumed about 0; in degrees;
+ *                          use 0.0 for default value)
+ *              sweepdelta (angle increment of sweep; in degrees;
+ *                          use 0.0 for default value)
+ *              minbsdelta (min binary search increment angle; in degrees;
+ *                          use 0.0 for default value)
+ *              &ptas  (<return> 4 points in the source)
+ *              &ptad  (<return> the corresponding 4 pts in the dest)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This generates two pairs of points in the src, each pair
+ *          corresponding to a pair of points that would lie along
+ *          the same raster line in a transformed (dewarped) image.
+ *      (2) The sets of 4 src and 4 dest points returned by this function
+ *          can then be used, in a projective or bilinear transform,
+ *          to remove keystoning in the src.
+ */
+l_int32
+pixGetLocalSkewTransform(PIX       *pixs,
+                         l_int32    nslices,
+                         l_int32    redsweep,
+                         l_int32    redsearch,
+                         l_float32  sweeprange,
+                         l_float32  sweepdelta,
+                         l_float32  minbsdelta,
+                         PTA      **pptas,
+                         PTA      **pptad)
+{
+l_int32    w, h, i;
+l_float32  deg2rad, angr, angd, dely;
+NUMA      *naskew;
+PTA       *ptas, *ptad;
+
+    PROCNAME("pixGetLocalSkewTransform");
+
+    if (!pptas || !pptad)
+        return ERROR_INT("&ptas and &ptad not defined", procName, 1);
+    *pptas = *pptad = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (nslices < 2 || nslices > 20)
+        nslices = DEFAULT_SLICES;
+    if (redsweep < 1 || redsweep > 8)
+        redsweep = DEFAULT_SWEEP_REDUCTION;
+    if (redsearch < 1 || redsearch > redsweep)
+        redsearch = DEFAULT_BS_REDUCTION;
+    if (sweeprange == 0.0)
+        sweeprange = DEFAULT_SWEEP_RANGE;
+    if (sweepdelta == 0.0)
+        sweepdelta = DEFAULT_SWEEP_DELTA;
+    if (minbsdelta == 0.0)
+        minbsdelta = DEFAULT_MINBS_DELTA;
+
+    naskew = pixGetLocalSkewAngles(pixs, nslices, redsweep, redsearch,
+                                   sweeprange, sweepdelta, minbsdelta,
+                                   NULL, NULL);
+    if (!naskew)
+        return ERROR_INT("naskew not made", procName, 1);
+
+    deg2rad = 3.14159265 / 180.;
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    ptas = ptaCreate(4);
+    ptad = ptaCreate(4);
+    *pptas = ptas;
+    *pptad = ptad;
+
+        /* Find i for skew line that intersects LHS at i and RHS at h / 20 */
+    for (i = 0; i < h; i++) {
+        numaGetFValue(naskew, i, &angd);
+        angr = angd * deg2rad;
+        dely = w * tan(angr);
+        if (i - dely > 0.05 * h)
+            break;
+    }
+    ptaAddPt(ptas, 0, i);
+    ptaAddPt(ptas, w - 1, i - dely);
+    ptaAddPt(ptad, 0, i);
+    ptaAddPt(ptad, w - 1, i);
+
+        /* Find i for skew line that intersects LHS at i and RHS at 19h / 20 */
+    for (i = h - 1; i > 0; i--) {
+        numaGetFValue(naskew, i, &angd);
+        angr = angd * deg2rad;
+        dely = w * tan(angr);
+        if (i - dely < 0.95 * h)
+            break;
+    }
+    ptaAddPt(ptas, 0, i);
+    ptaAddPt(ptas, w - 1, i - dely);
+    ptaAddPt(ptad, 0, i);
+    ptaAddPt(ptad, w - 1, i);
+
+    numaDestroy(&naskew);
+    return 0;
+}
+
+
+/*!
+ *  pixGetLocalSkewAngles()
+ *
+ *      Input:  pixs
+ *              nslices  (the number of horizontal overlapping slices; must
+ *                  be larger than 1 and not exceed 20; use 0 for default)
+ *              redsweep (sweep reduction factor: 1, 2, 4 or 8;
+ *                        use 0 for default value)
+ *              redsearch (search reduction factor: 1, 2, 4 or 8, and
+ *                         not larger than redsweep; use 0 for default value)
+ *              sweeprange (half the full range, assumed about 0; in degrees;
+ *                          use 0.0 for default value)
+ *              sweepdelta (angle increment of sweep; in degrees;
+ *                          use 0.0 for default value)
+ *              minbsdelta (min binary search increment angle; in degrees;
+ *                          use 0.0 for default value)
+ *              &a (<optional return> slope of skew as fctn of y)
+ *              &b (<optional return> intercept at y=0 of skew as fctn of y)
+ *      Return: naskew, or null on error
+ *
+ *  Notes:
+ *      (1) The local skew is measured in a set of overlapping strips.
+ *          We then do a least square linear fit parameters to get
+ *          the slope and intercept parameters a and b in
+ *              skew-angle = a * y + b  (degrees)
+ *          for the local skew as a function of raster line y.
+ *          This is then used to make naskew, which can be interpreted
+ *          as the computed skew angle (in degrees) at the left edge
+ *          of each raster line.
+ *      (2) naskew can then be used to find the baselines of text, because
+ *          each text line has a baseline that should intersect
+ *          the left edge of the image with the angle given by this
+ *          array, evaluated at the raster line of intersection.
+ */
+NUMA *
+pixGetLocalSkewAngles(PIX        *pixs,
+                      l_int32     nslices,
+                      l_int32     redsweep,
+                      l_int32     redsearch,
+                      l_float32   sweeprange,
+                      l_float32   sweepdelta,
+                      l_float32   minbsdelta,
+                      l_float32  *pa,
+                      l_float32  *pb)
+{
+l_int32    w, h, hs, i, ystart, yend, ovlap, npts;
+l_float32  angle, conf, ycenter, a, b;
+BOX       *box;
+NUMA      *naskew;
+PIX       *pix;
+PTA       *pta;
+
+    PROCNAME("pixGetLocalSkewAngles");
+
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (nslices < 2 || nslices > 20)
+        nslices = DEFAULT_SLICES;
+    if (redsweep < 1 || redsweep > 8)
+        redsweep = DEFAULT_SWEEP_REDUCTION;
+    if (redsearch < 1 || redsearch > redsweep)
+        redsearch = DEFAULT_BS_REDUCTION;
+    if (sweeprange == 0.0)
+        sweeprange = DEFAULT_SWEEP_RANGE;
+    if (sweepdelta == 0.0)
+        sweepdelta = DEFAULT_SWEEP_DELTA;
+    if (minbsdelta == 0.0)
+        minbsdelta = DEFAULT_MINBS_DELTA;
+
+    h = pixGetHeight(pixs);
+    w = pixGetWidth(pixs);
+    hs = h / nslices;
+    ovlap = (l_int32)(OVERLAP_FRACTION * hs);
+    pta = ptaCreate(nslices);
+    for (i = 0; i < nslices; i++) {
+        ystart = L_MAX(0, hs * i - ovlap);
+        yend = L_MIN(h - 1, hs * (i + 1) + ovlap);
+        ycenter = (ystart + yend) / 2;
+        box = boxCreate(0, ystart, w, yend - ystart + 1);
+        pix = pixClipRectangle(pixs, box, NULL);
+        pixFindSkewSweepAndSearch(pix, &angle, &conf, redsweep, redsearch,
+                                  sweeprange, sweepdelta, minbsdelta);
+        if (conf > MIN_ALLOWED_CONFIDENCE)
+            ptaAddPt(pta, ycenter, angle);
+        pixDestroy(&pix);
+        boxDestroy(&box);
+    }
+/*    ptaWriteStream(stderr, pta, 0); */
+
+        /* Do linear least squares fit */
+    if ((npts = ptaGetCount(pta)) < 2) {
+        ptaDestroy(&pta);
+        return (NUMA *)ERROR_PTR("can't fit skew", procName, NULL);
+    }
+    ptaGetLinearLSF(pta, &a, &b, NULL);
+    if (pa) *pa = a;
+    if (pb) *pb = b;
+
+        /* Make skew angle array as function of raster line */
+    naskew = numaCreate(h);
+    for (i = 0; i < h; i++) {
+        angle = a * i + b;
+        numaAddNumber(naskew, angle);
+    }
+
+#if  DEBUG_PLOT
+{ NUMA   *nax, *nay;
+  GPLOT  *gplot;
+    ptaGetArrays(pta, &nax, &nay);
+    gplot = gplotCreate("junkskew", GPLOT_X11, "skew as fctn of y",
+                        "y (in raster lines from top)", "angle (in degrees)");
+    gplotAddPlot(gplot, NULL, naskew, GPLOT_POINTS, "linear lsf");
+    gplotAddPlot(gplot, nax, nay, GPLOT_POINTS, "actual data pts");
+    gplotMakeOutput(gplot);
+    gplotDestroy(&gplot);
+    numaDestroy(&nax);
+    numaDestroy(&nay);
+}
+#endif  /* DEBUG_PLOT */
+
+    ptaDestroy(&pta);
+    return naskew;
+}
diff --git a/src/bbuffer.c b/src/bbuffer.c
new file mode 100644 (file)
index 0000000..cab216d
--- /dev/null
@@ -0,0 +1,470 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   bbuffer.c
+ *
+ *      Create/Destroy BBuffer
+ *          L_BBUFFER      *bbufferCreate()
+ *          void           *bbufferDestroy()
+ *          l_uint8        *bbufferDestroyAndSaveData()
+ *
+ *      Operations to read data TO a BBuffer
+ *          l_int32         bbufferRead()
+ *          l_int32         bbufferReadStream()
+ *          l_int32         bbufferExtendArray()
+ *
+ *      Operations to write data FROM a BBuffer
+ *          l_int32         bbufferWrite()
+ *          l_int32         bbufferWriteStream()
+ *
+ *    The bbuffer is an implementation of a byte queue.
+ *    The bbuffer holds a byte array from which bytes are
+ *    processed in a first-in/first-out fashion.  As with
+ *    any queue, bbuffer maintains two "pointers," one to the
+ *    tail of the queue (where you read new bytes onto it)
+ *    and one to the head of the queue (where you start from
+ *    when writing bytes out of it.
+ *
+ *    The queue can be visualized:
+ *
+ *
+ *  byte 0                                           byte (nalloc - 1)
+ *       |                                                |
+ *       --------------------------------------------------
+ *                 H                             T
+ *       [   aw   ][  bytes currently on queue  ][  anr   ]
+ *
+ *       ---:  all allocated data in bbuffer
+ *       H:    queue head (ptr to next byte to be written out)
+ *       T:    queue tail (ptr to first byte to be written to)
+ *       aw:   already written from queue
+ *       anr:  allocated but not yet read to
+ *
+ *    The purpose of bbuffer is to allow you to safely read
+ *    bytes in, and to sequentially write them out as well.
+ *    In the process of writing bytes out, you don't actually
+ *    remove the bytes in the array; you just move the pointer
+ *    (nwritten) which points to the head of the queue.  In
+ *    the process of reading bytes in, you sometimes need to
+ *    expand the array size.  If a read is performed after a
+ *    write, so that the head of the queue is not at the
+ *    beginning of the array, the bytes already written are
+ *    first removed by copying the others over them; then the
+ *    new bytes are read onto the tail of the queue.
+ *
+ *    Note that the meaning of "read into" and "write from"
+ *    the bbuffer is OPPOSITE to that for a stream, where
+ *    you read "from" a stream and write "into" a stream.
+ *    As a mnemonic for remembering the direction:
+ *        - to read bytes from a stream into the bbuffer,
+ *          you call fread on the stream
+ *        - to write bytes from the bbuffer into a stream,
+ *          you call fwrite on the stream
+ *
+ *    See zlibmem.c for an example use of bbuffer, where we
+ *    compress and decompress an array of bytes in memory.
+ *
+ *    We can also use the bbuffer trivially to read from stdin
+ *    into memory; e.g., to capture bytes piped from the stdout
+ *    of another program.  This is equivalent to repeatedly
+ *    calling bbufferReadStream() until the input queue is empty.
+ *    This is implemented in l_binaryReadStream().
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  INITIAL_BUFFER_ARRAYSIZE = 1024;   /* n'importe quoi */
+
+/*--------------------------------------------------------------------------*
+ *                         BBuffer create/destroy                           *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  bbufferCreate()
+ *
+ *      Input:  buffer address in memory (<optional>)
+ *              size of byte array to be alloc'd (0 for default)
+ *      Return: bbuffer, or null on error
+ *
+ *  Notes:
+ *      (1) If a buffer address is given, you should read all the data in.
+ *      (2) Allocates a bbuffer with associated byte array of
+ *          the given size.  If a buffer address is given,
+ *          it then reads the number of bytes into the byte array.
+ */
+L_BBUFFER *
+bbufferCreate(l_uint8  *indata,
+              l_int32   nalloc)
+{
+L_BBUFFER  *bb;
+
+    PROCNAME("bbufferCreate");
+
+    if (nalloc <= 0)
+        nalloc = INITIAL_BUFFER_ARRAYSIZE;
+
+    if ((bb = (L_BBUFFER *)LEPT_CALLOC(1, sizeof(L_BBUFFER))) == NULL)
+        return (L_BBUFFER *)ERROR_PTR("bb not made", procName, NULL);
+    if ((bb->array = (l_uint8 *)LEPT_CALLOC(nalloc, sizeof(l_uint8))) == NULL)
+        return (L_BBUFFER *)ERROR_PTR("byte array not made", procName, NULL);
+    bb->nalloc = nalloc;
+    bb->nwritten = 0;
+
+    if (indata) {
+        memcpy((l_uint8 *)bb->array, indata, nalloc);
+        bb->n = nalloc;
+    } else {
+        bb->n = 0;
+    }
+
+    return bb;
+}
+
+
+/*!
+ *  bbufferDestroy()
+ *
+ *      Input:  &bbuffer  (<to be nulled>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Destroys the byte array in the bbuffer and then the bbuffer;
+ *          then nulls the contents of the input ptr.
+ */
+void
+bbufferDestroy(L_BBUFFER  **pbb)
+{
+L_BBUFFER  *bb;
+
+    PROCNAME("bbufferDestroy");
+
+    if (pbb == NULL) {
+        L_WARNING("ptr address is NULL\n", procName);
+        return;
+    }
+
+    if ((bb = *pbb) == NULL)
+        return;
+
+    if (bb->array)
+        LEPT_FREE(bb->array);
+    LEPT_FREE(bb);
+    *pbb = NULL;
+
+    return;
+}
+
+
+/*!
+ *  bbufferDestroyAndSaveData()
+ *
+ *      Input:  &bbuffer (<to be nulled>)
+ *              &nbytes  (<return> number of bytes saved in array)
+ *      Return: barray (newly allocated array of data)
+ *
+ *  Notes:
+ *      (1) Copies data to newly allocated array; then destroys the bbuffer.
+ */
+l_uint8 *
+bbufferDestroyAndSaveData(L_BBUFFER  **pbb,
+                          size_t      *pnbytes)
+{
+l_uint8    *array;
+size_t      nbytes;
+L_BBUFFER  *bb;
+
+    PROCNAME("bbufferDestroyAndSaveData");
+
+    if (pbb == NULL) {
+        L_WARNING("ptr address is NULL\n", procName);
+        return NULL;
+    }
+    if (pnbytes == NULL) {
+        L_WARNING("&nbytes is NULL\n", procName);
+        bbufferDestroy(pbb);
+        return NULL;
+    }
+
+    if ((bb = *pbb) == NULL)
+        return NULL;
+
+        /* write all unwritten bytes out to a new array */
+    nbytes = bb->n - bb->nwritten;
+    *pnbytes = nbytes;
+    if ((array = (l_uint8 *)LEPT_CALLOC(nbytes, sizeof(l_uint8))) == NULL) {
+        L_WARNING("calloc failure for array\n", procName);
+        return NULL;
+    }
+    memcpy((void *)array, (void *)(bb->array + bb->nwritten), nbytes);
+
+    bbufferDestroy(pbb);
+    return array;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                   Operations to read data INTO a BBuffer                 *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  bbufferRead()
+ *
+ *      Input:  bbuffer
+ *              src      (source memory buffer from which bytes are read)
+ *              nbytes   (bytes to be read)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For a read after write, first remove the written
+ *          bytes by shifting the unwritten bytes in the array,
+ *          then check if there is enough room to add the new bytes.
+ *          If not, realloc with bbufferExpandArray(), resulting
+ *          in a second writing of the unwritten bytes.  While less
+ *          efficient, this is simpler than making a special case
+ *          of reallocNew().
+ */
+l_int32
+bbufferRead(L_BBUFFER  *bb,
+            l_uint8    *src,
+            l_int32     nbytes)
+{
+l_int32  navail, nadd, nwritten;
+
+    PROCNAME("bbufferRead");
+
+    if (!bb)
+        return ERROR_INT("bb not defined", procName, 1);
+    if (!src)
+        return ERROR_INT("src not defined", procName, 1);
+    if (nbytes == 0)
+        return ERROR_INT("no bytes to read", procName, 1);
+
+    if ((nwritten = bb->nwritten)) {  /* move the unwritten bytes over */
+        memmove((l_uint8 *)bb->array, (l_uint8 *)(bb->array + nwritten),
+                 bb->n - nwritten);
+        bb->nwritten = 0;
+        bb->n -= nwritten;
+    }
+
+        /* If necessary, expand the allocated array.  Do so by
+         * by at least a factor of two. */
+    navail = bb->nalloc - bb->n;
+    if (nbytes > navail) {
+        nadd = L_MAX(bb->nalloc, nbytes);
+        bbufferExtendArray(bb, nadd);
+    }
+
+        /* Read in the new bytes */
+    memcpy((l_uint8 *)(bb->array + bb->n), src, nbytes);
+    bb->n += nbytes;
+
+    return 0;
+}
+
+
+/*!
+ *  bbufferReadStream()
+ *
+ *      Input:  bbuffer
+ *              fp      (source stream from which bytes are read)
+ *              nbytes   (bytes to be read)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+bbufferReadStream(L_BBUFFER  *bb,
+                  FILE       *fp,
+                  l_int32     nbytes)
+{
+l_int32  navail, nadd, nread, nwritten;
+
+    PROCNAME("bbufferReadStream");
+
+    if (!bb)
+        return ERROR_INT("bb not defined", procName, 1);
+    if (!fp)
+        return ERROR_INT("fp not defined", procName, 1);
+    if (nbytes == 0)
+        return ERROR_INT("no bytes to read", procName, 1);
+
+    if ((nwritten = bb->nwritten)) {  /* move any unwritten bytes over */
+        memmove((l_uint8 *)bb->array, (l_uint8 *)(bb->array + nwritten),
+                 bb->n - nwritten);
+        bb->nwritten = 0;
+        bb->n -= nwritten;
+    }
+
+        /* If necessary, expand the allocated array.  Do so by
+         * by at least a factor of two. */
+    navail = bb->nalloc - bb->n;
+    if (nbytes > navail) {
+        nadd = L_MAX(bb->nalloc, nbytes);
+        bbufferExtendArray(bb, nadd);
+    }
+
+        /* Read in the new bytes */
+    nread = fread((void *)(bb->array + bb->n), 1, nbytes, fp);
+    bb->n += nread;
+
+    return 0;
+}
+
+
+/*!
+ *  bbufferExtendArray()
+ *
+ *      Input:  bbuffer
+ *              nbytes  (number of bytes to extend array size)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) reallocNew() copies all bb->nalloc bytes, even though
+ *          only bb->n are data.
+ */
+l_int32
+bbufferExtendArray(L_BBUFFER  *bb,
+                   l_int32     nbytes)
+{
+    PROCNAME("bbufferExtendArray");
+
+    if (!bb)
+        return ERROR_INT("bb not defined", procName, 1);
+
+    if ((bb->array = (l_uint8 *)reallocNew((void **)&bb->array,
+                                bb->nalloc,
+                                bb->nalloc + nbytes)) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+
+    bb->nalloc += nbytes;
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                  Operations to write data FROM a BBuffer                 *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  bbufferWrite()
+ *
+ *      Input:  bbuffer
+ *              dest     (dest memory buffer to which bytes are written)
+ *              nbytes   (bytes requested to be written)
+ *              &nout    (<return> bytes actually written)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+bbufferWrite(L_BBUFFER  *bb,
+             l_uint8    *dest,
+             size_t      nbytes,
+             size_t     *pnout)
+{
+l_int32  nleft, nout;
+
+    PROCNAME("bbufferWrite");
+
+    if (!bb)
+        return ERROR_INT("bb not defined", procName, 1);
+    if (!dest)
+        return ERROR_INT("dest not defined", procName, 1);
+    if (nbytes <= 0)
+        return ERROR_INT("no bytes requested to write", procName, 1);
+    if (!pnout)
+        return ERROR_INT("&nout not defined", procName, 1);
+
+    nleft = bb->n - bb->nwritten;
+    nout = L_MIN(nleft, nbytes);
+    *pnout = nout;
+
+    if (nleft == 0) {   /* nothing to write; reinitialize the buffer */
+        bb->n = 0;
+        bb->nwritten = 0;
+        return 0;
+    }
+
+        /* nout > 0; transfer the data out */
+    memcpy(dest, (l_uint8 *)(bb->array + bb->nwritten), nout);
+    bb->nwritten += nout;
+
+        /* If all written; "empty" the buffer */
+    if (nout == nleft) {
+        bb->n = 0;
+        bb->nwritten = 0;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  bbufferWriteStream()
+ *
+ *      Input:  bbuffer
+ *              fp       (dest stream to which bytes are written)
+ *              nbytes   (bytes requested to be written)
+ *              &nout    (<return> bytes actually written)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+bbufferWriteStream(L_BBUFFER  *bb,
+                   FILE       *fp,
+                   size_t      nbytes,
+                   size_t     *pnout)
+{
+l_int32  nleft, nout;
+
+    PROCNAME("bbufferWriteStream");
+
+    if (!bb)
+        return ERROR_INT("bb not defined", procName, 1);
+    if (!fp)
+        return ERROR_INT("output stream not defined", procName, 1);
+    if (nbytes <= 0)
+        return ERROR_INT("no bytes requested to write", procName, 1);
+    if (!pnout)
+        return ERROR_INT("&nout not defined", procName, 1);
+
+    nleft = bb->n - bb->nwritten;
+    nout = L_MIN(nleft, nbytes);
+    *pnout = nout;
+
+    if (nleft == 0) {   /* nothing to write; reinitialize the buffer */
+        bb->n = 0;
+        bb->nwritten = 0;
+        return 0;
+    }
+
+        /* nout > 0; transfer the data out */
+    fwrite((void *)(bb->array + bb->nwritten), 1, nout, fp);
+    bb->nwritten += nout;
+
+        /* If all written; "empty" the buffer */
+    if (nout == nleft) {
+        bb->n = 0;
+        bb->nwritten = 0;
+    }
+
+    return 0;
+}
+
diff --git a/src/bbuffer.h b/src/bbuffer.h
new file mode 100644 (file)
index 0000000..f9a5d13
--- /dev/null
@@ -0,0 +1,57 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_BBUFFER_H
+#define  LEPTONICA_BBUFFER_H
+
+/*
+ *  bbuffer.h
+ *
+ *      Expandable byte buffer for reading data in from memory and
+ *      writing data out to other memory.
+ *
+ *      This implements a queue of bytes, so data read in is put
+ *      on the "back" of the queue (i.e., the end of the byte array)
+ *      and data written out is taken from the "front" of the queue
+ *      (i.e., from an index marker "nwritten" that is initially set at
+ *      the beginning of the array.)  As usual with expandable
+ *      arrays, we keep the size of the allocated array and the
+ *      number of bytes that have been read into the array.
+ *
+ *      For implementation details, see bbuffer.c.
+ */
+
+struct L_ByteBuffer
+{
+    l_int32      nalloc;       /* size of allocated byte array            */
+    l_int32      n;            /* number of bytes read into to the array  */
+    l_int32      nwritten;     /* number of bytes written from the array  */
+    l_uint8     *array;        /* byte array                              */
+};
+typedef struct L_ByteBuffer L_BBUFFER;
+
+
+#endif  /* LEPTONICA_BBUFFER_H */
diff --git a/src/bilateral.c b/src/bilateral.c
new file mode 100644 (file)
index 0000000..ce87ad7
--- /dev/null
@@ -0,0 +1,790 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  bilateral.c
+ *
+ *     Top level approximate separable grayscale or color bilateral filtering
+ *          PIX                 *pixBilateral()
+ *          PIX                 *pixBilateralGray()
+ *
+ *     Implementation of approximate separable bilateral filter
+ *          static L_BILATERAL  *bilateralCreate()
+ *          static void         *bilateralDestroy()
+ *          static PIX          *bilateralApply()
+ *
+ *     Slow, exact implementation of grayscale or color bilateral filtering
+ *          PIX                 *pixBilateralExact()
+ *          PIX                 *pixBilateralGrayExact()
+ *          PIX                 *pixBlockBilateralExact()
+ *
+ *     Kernel helper function
+ *          L_KERNEL            *makeRangeKernel()
+ *
+ *  This includes both a slow, exact implementation of the bilateral
+ *  filter algorithm (given by Sylvain Paris and Frédo Durand),
+ *  and a fast, approximate and separable implementation (following
+ *  Yang, Tan and Ahuja).  See bilateral.h for algorithmic details.
+ *
+ *  The bilateral filter has the nice property of applying a gaussian
+ *  filter to smooth parts of the image that don't vary too quickly,
+ *  while at the same time preserving edges.  The filter is nonlinear
+ *  and cannot be decomposed into two separable filters; however,
+ *  there exists an approximate method that is separable.  To further
+ *  speed up the separable implementation, you can generate the
+ *  intermediate data at reduced resolution.
+ *
+ *  The full kernel is composed of two parts: a spatial gaussian filter
+ *  and a nonlinear "range" filter that depends on the intensity difference
+ *  between the reference pixel at the spatial kernel origin and any other
+ *  pixel within the kernel support.
+ *
+ *  In our implementations, the range filter is a parameterized,
+ *  one-sided, 256-element, monotonically decreasing gaussian function
+ *  of the absolute value of the difference between pixel values; namely,
+ *  abs(I2 - I1).  In general, any decreasing function can be used,
+ *  and more generally,  any two-dimensional kernel can be used if
+ *  you wish to relax the 'abs' condition.  (In that case, the range
+ *  filter can be 256 x 256).
+ */
+
+#include <math.h>
+#include "allheaders.h"
+#include "bilateral.h"
+
+static L_BILATERAL *bilateralCreate(PIX *pixs, l_float32 spatial_stdev,
+                                    l_float32 range_stdev, l_int32 ncomps,
+                                    l_int32 reduction);
+static PIX *bilateralApply(L_BILATERAL *bil);
+static void bilateralDestroy(L_BILATERAL **pbil);
+
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_BILATERAL    0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*--------------------------------------------------------------------------*
+ *  Top level approximate separable grayscale or color bilateral filtering  *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  pixBilateral()
+ *
+ *      Input:  pixs (8 bpp gray or 32 bpp rgb, no colormap)
+ *              spatial_stdev  (of gaussian kernel; in pixels, > 0.5)
+ *              range_stdev  (of gaussian range kernel; > 5.0; typ. 50.0)
+ *              ncomps (number of intermediate sums J(k,x); in [4 ... 30])
+ *              reduction  (1, 2 or 4)
+ *      Return: pixd (bilateral filtered image), or null on error
+ *
+ *  Notes:
+ *      (1) This performs a relatively fast, separable bilateral
+ *          filtering operation.  The time is proportional to ncomps
+ *          and varies inversely approximately as the cube of the
+ *          reduction factor.  See bilateral.h for algorithm details.
+ *      (2) We impose minimum values for range_stdev and ncomps to
+ *          avoid nasty artifacts when either are too small.  We also
+ *          impose a constraint on their product:
+ *               ncomps * range_stdev >= 100.
+ *          So for values of range_stdev >= 25, ncomps can be as small as 4.
+ *          Here is a qualitative, intuitive explanation for this constraint.
+ *          Call the difference in k values between the J(k) == 'delta', where
+ *              'delta' ~ 200 / ncomps
+ *          Then this constraint is roughly equivalent to the condition:
+ *              'delta' < 2 * range_stdev
+ *          Note that at an intensity difference of (2 * range_stdev), the
+ *          range part of the kernel reduces the effect by the factor 0.14.
+ *          This constraint requires that we have a sufficient number of
+ *          PCBs (i.e, a small enough 'delta'), so that for any value of
+ *          image intensity I, there exists a k (and a PCB, J(k), such that
+ *              |I - k| < range_stdev
+ *          Any fewer PCBs and we don't have enough to support this condition.
+ *      (3) The upper limit of 30 on ncomps is imposed because the
+ *          gain in accuracy is not worth the extra computation.
+ *      (4) The size of the gaussian kernel is twice the spatial_stdev
+ *          on each side of the origin.  The minimum value of
+ *          spatial_stdev, 0.5, is required to have a finite sized
+ *          spatial kernel.  In practice, a much larger value is used.
+ *      (5) Computation of the intermediate images goes inversely
+ *          as the cube of the reduction factor.  If you can use a
+ *          reduction of 2 or 4, it is well-advised.
+ *      (6) The range kernel is defined over the absolute value of pixel
+ *          grayscale differences, and hence must have size 256 x 1.
+ *          Values in the array represent the multiplying weight
+ *          depending on the absolute gray value difference between
+ *          the source pixel and the neighboring pixel, and should
+ *          be monotonically decreasing.
+ *      (7) Interesting observation.  Run this on prog/fish24.jpg, with
+ *          range_stdev = 60, ncomps = 6, and spatial_dev = {10, 30, 50}.
+ *          As spatial_dev gets larger, we get the counter-intuitive
+ *          result that the body of the red fish becomes less blurry.
+ */
+PIX *
+pixBilateral(PIX       *pixs,
+             l_float32  spatial_stdev,
+             l_float32  range_stdev,
+             l_int32    ncomps,
+             l_int32    reduction)
+{
+l_int32       d;
+l_float32     sstdev;  /* scaled spatial stdev */
+PIX          *pixt, *pixr, *pixg, *pixb, *pixd;
+
+    PROCNAME("pixBilateral");
+
+    if (!pixs || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs not defined or cmapped", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+    if (reduction != 1 && reduction != 2 && reduction != 4)
+        return (PIX *)ERROR_PTR("reduction invalid", procName, NULL);
+    sstdev = spatial_stdev / (l_float32)reduction;  /* reduced spat. stdev */
+    if (sstdev < 0.5)
+        return (PIX *)ERROR_PTR("sstdev < 0.5", procName, NULL);
+    if (range_stdev <= 5.0)
+        return (PIX *)ERROR_PTR("range_stdev <= 5.0", procName, NULL);
+    if (ncomps < 4 || ncomps > 30)
+        return (PIX *)ERROR_PTR("ncomps not in [4 ... 30]", procName, NULL);
+    if (ncomps * range_stdev < 100.0)
+        return (PIX *)ERROR_PTR("ncomps * range_stdev < 100.0", procName, NULL);
+
+    if (d == 8)
+        return pixBilateralGray(pixs, spatial_stdev, range_stdev,
+                                ncomps, reduction);
+
+    pixt = pixGetRGBComponent(pixs, COLOR_RED);
+    pixr = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps,
+                            reduction);
+    pixDestroy(&pixt);
+    pixt = pixGetRGBComponent(pixs, COLOR_GREEN);
+    pixg = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps,
+                            reduction);
+    pixDestroy(&pixt);
+    pixt = pixGetRGBComponent(pixs, COLOR_BLUE);
+    pixb = pixBilateralGray(pixt, spatial_stdev, range_stdev, ncomps,
+                            reduction);
+    pixDestroy(&pixt);
+    pixd = pixCreateRGBImage(pixr, pixg, pixb);
+    pixDestroy(&pixr);
+    pixDestroy(&pixg);
+    pixDestroy(&pixb);
+    return pixd;
+}
+
+
+/*!
+ *  pixBilateralGray()
+ *
+ *      Input:  pixs (8 bpp gray)
+ *              spatial_stdev  (of gaussian kernel; in pixels, > 0.5)
+ *              range_stdev  (of gaussian range kernel; > 5.0; typ. 50.0)
+ *              ncomps (number of intermediate sums J(k,x); in [4 ... 30])
+ *              reduction  (1, 2 or 4)
+ *      Return: pixd (8 bpp bilateral filtered image), or null on error
+ *
+ *  Notes:
+ *      (1) See pixBilateral() for constraints on the input parameters.
+ *      (2) See pixBilateral() for algorithm details.
+ */
+PIX *
+pixBilateralGray(PIX       *pixs,
+                 l_float32  spatial_stdev,
+                 l_float32  range_stdev,
+                 l_int32    ncomps,
+                 l_int32    reduction)
+{
+l_float32     sstdev;  /* scaled spatial stdev */
+PIX          *pixd;
+L_BILATERAL  *bil;
+
+    PROCNAME("pixBilateralGray");
+
+    if (!pixs || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs not defined or cmapped", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp gray", procName, NULL);
+    if (reduction != 1 && reduction != 2 && reduction != 4)
+        return (PIX *)ERROR_PTR("reduction invalid", procName, NULL);
+    sstdev = spatial_stdev / (l_float32)reduction;  /* reduced spat. stdev */
+    if (sstdev < 0.5)
+        return (PIX *)ERROR_PTR("sstdev < 0.5", procName, NULL);
+    if (range_stdev <= 5.0)
+        return (PIX *)ERROR_PTR("range_stdev <= 5.0", procName, NULL);
+    if (ncomps < 4 || ncomps > 30)
+        return (PIX *)ERROR_PTR("ncomps not in [4 ... 30]", procName, NULL);
+    if (ncomps * range_stdev < 100.0)
+        return (PIX *)ERROR_PTR("ncomps * range_stdev < 100.0", procName, NULL);
+
+    bil = bilateralCreate(pixs, spatial_stdev, range_stdev, ncomps, reduction);
+    if (!bil) return (PIX *)ERROR_PTR("bil not made", procName, NULL);
+    pixd = bilateralApply(bil);
+    bilateralDestroy(&bil);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *       Implementation of approximate separable bilateral filter       *
+ *----------------------------------------------------------------------*/
+/*!
+ *  bilateralCreate()
+ *
+ *      Input:  pixs (8 bpp gray, no colormap)
+ *              spatial_stdev  (of gaussian kernel; in pixels, > 0.5)
+ *              range_stdev  (of gaussian range kernel; > 5.0; typ. 50.0)
+ *              ncomps (number of intermediate sums J(k,x); in [4 ... 30])
+ *              reduction  (1, 2 or 4)
+ *      Return: bil, or null on error
+ *
+ *  Notes:
+ *      (1) This initializes a bilateral filtering operation, generating all
+ *          the data required.  It takes most of the time in the bilateral
+ *          filtering operation.
+ *      (2) See bilateral.h for details of the algorithm.
+ *      (3) See pixBilateral() for constraints on input parameters, which
+ *          are not checked here.
+ */
+static L_BILATERAL *
+bilateralCreate(PIX       *pixs,
+                l_float32  spatial_stdev,
+                l_float32  range_stdev,
+                l_int32    ncomps,
+                l_int32    reduction)
+{
+l_int32       w, ws, wd, h, hs, hd, i, j, k, index;
+l_int32       border, minval, maxval, spatial_size;
+l_int32       halfwidth, wpls, wplt, wpld, kval, nval, dval;
+l_float32     sstdev, fval1, fval2, denom, sum, norm, kern;
+l_int32      *nc, *kindex;
+l_float32    *kfract, *range, *spatial;
+l_uint32     *datas, *datat, *datad, *lines, *linet, *lined;
+L_BILATERAL  *bil;
+PIX          *pixt, *pixt2, *pixsc, *pixd;
+PIXA         *pixac;
+
+    PROCNAME("bilateralCreate");
+
+    sstdev = spatial_stdev / (l_float32)reduction;  /* reduced spat. stdev */
+    if ((bil = (L_BILATERAL *)LEPT_CALLOC(1, sizeof(L_BILATERAL))) == NULL)
+        return (L_BILATERAL *)ERROR_PTR("bil not made", procName, NULL);
+    bil->spatial_stdev = sstdev;
+    bil->range_stdev = range_stdev;
+    bil->reduction = reduction;
+    bil->ncomps = ncomps;
+
+    if (reduction == 1) {
+        pixt = pixClone(pixs);
+    } else if (reduction == 2) {
+        pixt = pixScaleAreaMap2(pixs);
+    } else {  /* reduction == 4) */
+        pixt2 = pixScaleAreaMap2(pixs);
+        pixt = pixScaleAreaMap2(pixt2);
+        pixDestroy(&pixt2);
+    }
+
+    pixGetExtremeValue(pixt, 1, L_SELECT_MIN, NULL, NULL, NULL, &minval);
+    pixGetExtremeValue(pixt, 1, L_SELECT_MAX, NULL, NULL, NULL, &maxval);
+    bil->minval = minval;
+    bil->maxval = maxval;
+
+    border = (l_int32)(2 * sstdev + 1);
+    pixsc = pixAddMirroredBorder(pixt, border, border, border, border);
+    bil->pixsc = pixsc;
+    pixDestroy(&pixt);
+    bil->pixs = pixClone(pixs);
+
+
+    /* -------------------------------------------------------------------- *
+     * Generate arrays for interpolation of J(k,x):
+     *  (1.0 - kfract[.]) * J(kindex[.], x) + kfract[.] * J(kindex[.] + 1, x),
+     * where I(x) is the index into kfract[] and kindex[],
+     * and x is an index into the 2D image array.
+     * -------------------------------------------------------------------- */
+        /* nc is the set of k values to be used in J(k,x) */
+    nc = (l_int32 *)LEPT_CALLOC(ncomps, sizeof(l_int32));
+    for (i = 0; i < ncomps; i++)
+        nc[i] = minval + i * (maxval - minval) / (ncomps - 1);
+    bil->nc = nc;
+
+        /* kindex maps from intensity I(x) to the lower k index for J(k,x) */
+    kindex = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    for (i = minval, k = 0; i <= maxval && k < ncomps - 1; k++) {
+        fval2 = nc[k + 1];
+        while (i < fval2) {
+            kindex[i] = k;
+            i++;
+        }
+    }
+    kindex[maxval] = ncomps - 2;
+    bil->kindex = kindex;
+
+        /* kfract maps from intensity I(x) to the fraction of J(k+1,x) used */
+    kfract = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32));  /* from lower */
+    for (i = minval, k = 0; i <= maxval && k < ncomps - 1; k++) {
+        fval1 = nc[k];
+        fval2 = nc[k + 1];
+        while (i < fval2) {
+            kfract[i] = (l_float32)(i - fval1) / (l_float32)(fval2 - fval1);
+            i++;
+        }
+    }
+    kfract[maxval] = 1.0;
+    bil->kfract = kfract;
+
+#if  DEBUG_BILATERAL
+    for (i = minval; i <= maxval; i++)
+      fprintf(stderr, "kindex[%d] = %d; kfract[%d] = %5.3f\n",
+              i, kindex[i], i, kfract[i]);
+    for (i = 0; i < ncomps; i++)
+      fprintf(stderr, "nc[%d] = %d\n", i, nc[i]);
+#endif  /* DEBUG_BILATERAL */
+
+
+    /* -------------------------------------------------------------------- *
+     *             Generate 1-D kernel arrays (spatial and range)           *
+     * -------------------------------------------------------------------- */
+    spatial_size = 2 * sstdev + 1;
+    spatial = (l_float32 *)LEPT_CALLOC(spatial_size, sizeof(l_float32));
+    denom = 2. * sstdev * sstdev;
+    for (i = 0; i < spatial_size; i++)
+        spatial[i] = expf(-(l_float32)(i * i) / denom);
+    bil->spatial = spatial;
+
+    range = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32));
+    denom = 2. * range_stdev * range_stdev;
+    for (i = 0; i < 256; i++)
+        range[i] = expf(-(l_float32)(i * i) / denom);
+    bil->range = range;
+
+
+    /* -------------------------------------------------------------------- *
+     *            Generate principal bilateral component images             *
+     * -------------------------------------------------------------------- */
+    pixac = pixaCreate(ncomps);
+    pixGetDimensions(pixsc, &ws, &hs, NULL);
+    datas = pixGetData(pixsc);
+    wpls = pixGetWpl(pixsc);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    wd = (w + reduction - 1) / reduction;
+    hd = (h + reduction - 1) / reduction;
+    halfwidth = (l_int32)(2.0 * sstdev);
+    for (index = 0; index < ncomps; index++) {
+        pixt = pixCopy(NULL, pixsc);
+        datat = pixGetData(pixt);
+        wplt = pixGetWpl(pixt);
+        kval = nc[index];
+            /* Separable convolutions: horizontal first */
+        for (i = 0; i < hd; i++) {
+            lines = datas + (border + i) * wpls;
+            linet = datat + (border + i) * wplt;
+            for (j = 0; j < wd; j++) {
+                sum = 0.0;
+                norm = 0.0;
+                for (k = -halfwidth; k <= halfwidth; k++) {
+                    nval = GET_DATA_BYTE(lines, border + j + k);
+                    kern = spatial[L_ABS(k)] * range[L_ABS(kval - nval)];
+                    sum += kern * nval;
+                    norm += kern;
+                }
+                dval = (l_int32)((sum / norm) + 0.5);
+                SET_DATA_BYTE(linet, border + j, dval);
+            }
+        }
+            /* Vertical convolution */
+        pixd = pixCreate(wd, hd, 8);
+        datad = pixGetData(pixd);
+        wpld = pixGetWpl(pixd);
+        for (i = 0; i < hd; i++) {
+            linet = datat + (border + i) * wplt;
+            lined = datad + i * wpld;
+            for (j = 0; j < wd; j++) {
+                sum = 0.0;
+                norm = 0.0;
+                for (k = -halfwidth; k <= halfwidth; k++) {
+                    nval = GET_DATA_BYTE(linet + k * wplt, border + j);
+                    kern = spatial[L_ABS(k)] * range[L_ABS(kval - nval)];
+                    sum += kern * nval;
+                    norm += kern;
+                }
+                dval = (l_int32)((sum / norm) + 0.5);
+                SET_DATA_BYTE(lined, j, dval);
+            }
+        }
+        pixDestroy(&pixt);
+        pixaAddPix(pixac, pixd, L_INSERT);
+    }
+    bil->pixac = pixac;
+    bil->lineset = (l_uint32 ***)pixaGetLinePtrs(pixac, NULL);
+
+    return bil;
+}
+
+
+/*!
+ *  bilateralApply()
+ *
+ *      Input:  bil
+ *      Return: pixd
+ */
+static PIX *
+bilateralApply(L_BILATERAL  *bil)
+{
+l_int32      i, j, k, ired, jred, w, h, wpls, wpld, ncomps, reduction;
+l_int32      vals, vald, lowval, hival;
+l_int32     *kindex;
+l_float32    fract;
+l_float32   *kfract;
+l_uint32    *lines, *lined, *datas, *datad;
+l_uint32  ***lineset = NULL;  /* for set of PBC */
+PIX         *pixs, *pixd;
+PIXA        *pixac;
+
+    PROCNAME("bilateralApply");
+
+    if (!bil)
+        return (PIX *)ERROR_PTR("bil not defined", procName, NULL);
+    pixs = bil->pixs;
+    ncomps = bil->ncomps;
+    kindex = bil->kindex;
+    kfract = bil->kfract;
+    reduction = bil->reduction;
+    pixac = bil->pixac;
+    lineset = bil->lineset;
+    if (pixaGetCount(pixac) != ncomps)
+        return (PIX *)ERROR_PTR("PBC images do not exist", procName, NULL);
+
+    if ((pixd = pixCreateTemplate(pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        ired = i / reduction;
+        for (j = 0; j < w; j++) {
+            jred = j / reduction;
+            vals = GET_DATA_BYTE(lines, j);
+            k = kindex[vals];
+            lowval = GET_DATA_BYTE(lineset[k][ired], jred);
+            hival = GET_DATA_BYTE(lineset[k + 1][ired], jred);
+            fract = kfract[vals];
+            vald = (l_int32)((1.0 - fract) * lowval + fract * hival + 0.5);
+            SET_DATA_BYTE(lined, j, vald);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  bilateralDestroy()
+ *
+ *      Input:  &bil
+ *      Return: void
+ */
+static void
+bilateralDestroy(L_BILATERAL  **pbil)
+{
+l_int32       i;
+L_BILATERAL  *bil;
+
+    PROCNAME("bilateralDestroy");
+
+    if (pbil == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((bil = *pbil) == NULL)
+        return;
+
+    pixDestroy(&bil->pixs);
+    pixDestroy(&bil->pixsc);
+    pixaDestroy(&bil->pixac);
+    LEPT_FREE(bil->spatial);
+    LEPT_FREE(bil->range);
+    LEPT_FREE(bil->nc);
+    LEPT_FREE(bil->kindex);
+    LEPT_FREE(bil->kfract);
+    for (i = 0; i < bil->ncomps; i++)
+        LEPT_FREE(bil->lineset[i]);
+    LEPT_FREE(bil->lineset);
+    LEPT_FREE(bil);
+    *pbil = NULL;
+    return;
+}
+
+
+/*----------------------------------------------------------------------*
+ *    Exact implementation of grayscale or color bilateral filtering    *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixBilateralExact()
+ *
+ *      Input:  pixs (8 bpp gray or 32 bpp rgb)
+ *              spatial_kel  (gaussian kernel)
+ *              range_kel (<optional> 256 x 1, monotonically decreasing)
+ *      Return: pixd (8 bpp bilateral filtered image)
+ *
+ *  Notes:
+ *      (1) The spatial_kel is a conventional smoothing kernel, typically a
+ *          2-d Gaussian kernel or other block kernel.  It can be either
+ *          normalized or not, but must be everywhere positive.
+ *      (2) The range_kel is defined over the absolute value of pixel
+ *          grayscale differences, and hence must have size 256 x 1.
+ *          Values in the array represent the multiplying weight for each
+ *          gray value difference between the target pixel and center of the
+ *          kernel, and should be monotonically decreasing.
+ *      (3) If range_kel == NULL, a constant weight is applied regardless
+ *          of the range value difference.  This degenerates to a regular
+ *          pixConvolve() with a normalized kernel.
+ */
+PIX *
+pixBilateralExact(PIX       *pixs,
+                  L_KERNEL  *spatial_kel,
+                  L_KERNEL  *range_kel)
+{
+l_int32  d;
+PIX     *pixt, *pixr, *pixg, *pixb, *pixd;
+
+    PROCNAME("pixBilateralExact");
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs) != NULL)
+        return (PIX *)ERROR_PTR("pixs is cmapped", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+    if (!spatial_kel)
+        return (PIX *)ERROR_PTR("spatial_ke not defined", procName, NULL);
+
+    if (d == 8) {
+        return pixBilateralGrayExact(pixs, spatial_kel, range_kel);
+    } else {  /* d == 32 */
+        pixt = pixGetRGBComponent(pixs, COLOR_RED);
+        pixr = pixBilateralGrayExact(pixt, spatial_kel, range_kel);
+        pixDestroy(&pixt);
+        pixt = pixGetRGBComponent(pixs, COLOR_GREEN);
+        pixg = pixBilateralGrayExact(pixt, spatial_kel, range_kel);
+        pixDestroy(&pixt);
+        pixt = pixGetRGBComponent(pixs, COLOR_BLUE);
+        pixb = pixBilateralGrayExact(pixt, spatial_kel, range_kel);
+        pixDestroy(&pixt);
+        pixd = pixCreateRGBImage(pixr, pixg, pixb);
+
+        pixDestroy(&pixr);
+        pixDestroy(&pixg);
+        pixDestroy(&pixb);
+        return pixd;
+    }
+}
+
+
+/*!
+ *  pixBilateralGrayExact()
+ *
+ *      Input:  pixs (8 bpp gray)
+ *              spatial_kel  (gaussian kernel)
+ *              range_kel (<optional> 256 x 1, monotonically decreasing)
+ *      Return: pixd (8 bpp bilateral filtered image)
+ *
+ *  Notes:
+ *      (1) See pixBilateralExact().
+ */
+PIX *
+pixBilateralGrayExact(PIX       *pixs,
+                      L_KERNEL  *spatial_kel,
+                      L_KERNEL  *range_kel)
+{
+l_int32    i, j, id, jd, k, m, w, h, d, sx, sy, cx, cy, wplt, wpld;
+l_int32    val, center_val;
+l_uint32  *datat, *datad, *linet, *lined;
+l_float32  sum, weight_sum, weight;
+L_KERNEL  *keli;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixBilateralGrayExact");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs must be gray", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (!spatial_kel)
+        return (PIX *)ERROR_PTR("spatial kel not defined", procName, NULL);
+
+    if (!range_kel)
+      return pixConvolve(pixs, spatial_kel, 8, 1);
+    if (range_kel->sx != 256 || range_kel->sy != 1)
+        return (PIX *)ERROR_PTR("range kel not {256 x 1", procName, NULL);
+
+    keli = kernelInvert(spatial_kel);
+    kernelGetParameters(keli, &sy, &sx, &cy, &cx);
+    if ((pixt = pixAddMirroredBorder(pixs, cx, sx - cx, cy, sy - cy)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+    pixd = pixCreate(w, h, 8);
+    datat = pixGetData(pixt);
+    datad = pixGetData(pixd);
+    wplt = pixGetWpl(pixt);
+    wpld = pixGetWpl(pixd);
+    for (i = 0, id = 0; id < h; i++, id++) {
+        lined = datad + id * wpld;
+        for (j = 0, jd = 0; jd < w; j++, jd++) {
+            center_val = GET_DATA_BYTE(datat + (i + cy) * wplt, j + cx);
+            weight_sum = 0.0;
+            sum = 0.0;
+            for (k = 0; k < sy; k++) {
+                linet = datat + (i + k) * wplt;
+                for (m = 0; m < sx; m++) {
+                    val = GET_DATA_BYTE(linet, j + m);
+                    weight = keli->data[k][m] *
+                        range_kel->data[0][L_ABS(center_val - val)];
+                    weight_sum += weight;
+                    sum += val * weight;
+                }
+            }
+            SET_DATA_BYTE(lined, jd, (l_int32)(sum / weight_sum + 0.5));
+        }
+    }
+
+    kernelDestroy(&keli);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixBlockBilateralExact()
+ *
+ *      Input:  pixs (8 bpp gray or 32 bpp rgb)
+ *              spatial_stdev (> 0.0)
+ *              range_stdev (> 0.0)
+ *      Return: pixd (8 bpp or 32 bpp bilateral filtered image)
+ *
+ *  Notes:
+ *      (1) See pixBilateralExact().  This provides an interface using
+ *          the standard deviations of the spatial and range filters.
+ *      (2) The convolution window halfwidth is 2 * spatial_stdev,
+ *          and the square filter size is 4 * spatial_stdev + 1.
+ *          The kernel captures 95% of total energy.  This is compensated
+ *          by normalization.
+ *      (3) The range_stdev is analogous to spatial_halfwidth in the
+ *          grayscale domain [0...255], and determines how much damping of the
+ *          smoothing operation is applied across edges.  The larger this
+ *          value is, the smaller the damping.  The smaller the value, the
+ *          more edge details are preserved.  These approximations are useful
+ *          for deciding the appropriate cutoff.
+ *              kernel[1 * stdev] ~= 0.6  * kernel[0]
+ *              kernel[2 * stdev] ~= 0.14 * kernel[0]
+ *              kernel[3 * stdev] ~= 0.01 * kernel[0]
+ *          If range_stdev is infinite there is no damping, and this
+ *          becomes a conventional gaussian smoothing.
+ *          This value does not affect the run time.
+ *      (4) If range_stdev is negative or zero, the range kernel is
+ *          ignored and this degenerates to a straight gaussian convolution.
+ *      (5) This is very slow for large spatial filters.  The time
+ *          on a 3GHz pentium is roughly
+ *             T = 1.2 * 10^-8 * (A * sh^2)  sec
+ *          where A = # of pixels, sh = spatial halfwidth of filter.
+ */
+PIX*
+pixBlockBilateralExact(PIX       *pixs,
+                       l_float32  spatial_stdev,
+                       l_float32  range_stdev)
+{
+l_int32    d, halfwidth;
+L_KERNEL  *spatial_kel, *range_kel;
+PIX       *pixd;
+
+    PROCNAME("pixBlockBilateralExact");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+    if (pixGetColormap(pixs) != NULL)
+        return (PIX *)ERROR_PTR("pixs is cmapped", procName, NULL);
+    if (spatial_stdev <= 0.0)
+        return (PIX *)ERROR_PTR("invalid spatial stdev", procName, NULL);
+    if (range_stdev <= 0.0)
+        return (PIX *)ERROR_PTR("invalid range stdev", procName, NULL);
+
+    halfwidth = 2 * spatial_stdev;
+    spatial_kel = makeGaussianKernel(halfwidth, halfwidth, spatial_stdev, 1.0);
+    range_kel = makeRangeKernel(range_stdev);
+    pixd = pixBilateralExact(pixs, spatial_kel, range_kel);
+    kernelDestroy(&spatial_kel);
+    kernelDestroy(&range_kel);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                         Kernel helper function                       *
+ *----------------------------------------------------------------------*/
+/*!
+ *  makeRangeKernel()
+ *
+ *      Input:  range_stdev (> 0)
+ *      Return: kel, or null on error
+ *
+ *  Notes:
+ *      (1) Creates a one-sided Gaussian kernel with the given
+ *          standard deviation.  At grayscale difference of one stdev,
+ *          the kernel falls to 0.6, and to 0.01 at three stdev.
+ *      (2) A typical input number might be 20.  Then pixels whose
+ *          value differs by 60 from the center pixel have their
+ *          weight in the convolution reduced by a factor of about 0.01.
+ */
+L_KERNEL *
+makeRangeKernel(l_float32  range_stdev)
+{
+l_int32    x;
+l_float32  val, denom;
+L_KERNEL  *kel;
+
+    PROCNAME("makeRangeKernel");
+
+    if (range_stdev <= 0.0)
+        return (L_KERNEL *)ERROR_PTR("invalid stdev <= 0", procName, NULL);
+
+    denom = 2. * range_stdev * range_stdev;
+    if ((kel = kernelCreate(1, 256)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
+    kernelSetOrigin(kel, 0, 0);
+    for (x = 0; x < 256; x++) {
+        val = expf(-(l_float32)(x * x) / denom);
+        kernelSetElement(kel, 0, x, val);
+    }
+    return kel;
+}
diff --git a/src/bilateral.h b/src/bilateral.h
new file mode 100644 (file)
index 0000000..520799d
--- /dev/null
@@ -0,0 +1,130 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_BILATERAL_H
+#define  LEPTONICA_BILATERAL_H
+
+/*
+ *  Contains the following struct
+ *      struct L_Bilateral
+ *
+ *
+ *  For a tutorial introduction to bilateral filters, which apply a
+ *  gaussian blur to smooth parts of the image while preserving edges, see
+ *    http://people.csail.mit.edu/sparis/bf_course/slides/03_definition_bf.pdf
+ *
+ *  We give an implementation of a bilateral filtering algorithm given in:
+ *    "Real-Time O(1) Bilateral Filtering," by Yang, Tan and Ahuja, CVPR 2009
+ *  which is at:
+ *    http://vision.ai.uiuc.edu/~qyang6/publications/cvpr-09-qingxiong-yang.pdf
+ *  This is based on an earlier algorithm by Sylvain Paris and Frédo Durand:
+ *    http://people.csail.mit.edu/sparis/publi/2006/eccv/
+ *               Paris_06_Fast_Approximation.pdf
+ *
+ *  The kernel of the filter is a product of a spatial gaussian and a
+ *  monotonically decreasing function of the difference in intensity
+ *  between the source pixel and the neighboring pixel.  The intensity
+ *  part of the filter gives higher influence for pixels with intensities
+ *  that are near to the source pixel, and the spatial part of the
+ *  filter gives higher weight to pixels that are near the source pixel.
+ *  This combination smooths in relatively uniform regions, while
+ *  maintaining edges.
+ *
+ *  The advantage of the appoach of Yang et al is that it is separable,
+ *  so the computation time is linear in the gaussian filter size.
+ *  Furthermore, it is possible to do much of the computation as a reduced
+ *  scale, which gives a good approximation to the full resolution version
+ *  but greatly speeds it up.
+ *
+ *  The bilateral filtered value at x is:
+ *
+ *            sum[y in N(x)]: spatial(|y - x|) * range(|I(x) - I(y)|) * I(y)
+ *    I'(x) = --------------------------------------------------------------
+ *            sum[y in N(x)]: spatial(|y - x|) * range(|I(x) - I(y)|)
+ *
+ *  where I() is the input image, I'() is the filtered image, N(x) is the
+ *  set of pixels around x in the filter support, and spatial() and range()
+ *  are gaussian functions:
+ *          spatial(x) = exp(-x^2 / (2 * s_s^2))
+ *          range(x) = exp(-x^2 / (2 * s_r^2))
+ *  and s_s and s_r and the standard deviations of the two gaussians.
+ *
+ *  Yang et al use a separable approximation to this, by defining a set
+ *  of related but separable functions J(k,x), that we call Principal
+ *  Bilateral Components (PBC):
+ *
+ *             sum[y in N(x)]: spatial(|y - x|) * range(|k - I(y)|) * I(y)
+ *    J(k,x) = -----------------------------------------------------------
+ *             sum[y in N(x)]: spatial(|y - x|) * range(|k - I(y)|)
+ *
+ *  which are computed quickly for a set of n values k[p], p = 0 ... n-1.
+ *  Then each output pixel is found using a linear interpolation:
+ *
+ *    I'(x) = (1 - q) * J(k[p],x) + q * J(k[p+1],x)
+ *
+ *  where J(k[p],x) and J(k[p+1],x) are PBC for which
+ *    k[p] <= I(x) and k[p+1] >= I(x), and
+ *    q = (I(x) - k[p]) / (k[p+1] - k[p]).
+ *
+ *  We can also subsample I(x), create subsampled versions of J(k,x),
+ *  which are then interpolated between for I'(x).
+ *
+ *  We generate 'pixsc', by optionally downscaling the input image
+ *  (using area mapping by the factor 'reduction'), and then adding
+ *  a mirrored border to avoid boundary cases.  This is then used
+ *  to compute 'ncomps' PBCs.
+ *
+ *  The 'spatial_stdev' is also downscaled by 'reduction'.  The size
+ *  of the 'spatial' array is 4 * (reduced 'spatial_stdev') + 1.
+ *  The size of the 'range' array is 256.
+ */
+
+
+/*------------------------------------------------------------------------*
+ *                          Bilateral filter                              *
+ *------------------------------------------------------------------------*/
+struct L_Bilateral
+{
+    struct Pix      *pixs;           /* clone of source pix                  */
+    struct Pix      *pixsc;          /* downscaled pix with mirrored border  */
+    l_int32          reduction;      /* 1, 2 or 4x for intermediates         */
+    l_float32        spatial_stdev;  /* stdev of spatial gaussian            */
+    l_float32        range_stdev;    /* stdev of range gaussian              */
+    l_float32       *spatial;        /* 1D gaussian spatial kernel           */
+    l_float32       *range;          /* one-sided gaussian range kernel      */
+    l_int32          minval;         /* min value in 8 bpp pix               */
+    l_int32          maxval;         /* max value in 8 bpp pix               */
+    l_int32          ncomps;         /* number of intermediate results       */
+    l_int32         *nc;             /* set of k values (size ncomps)        */
+    l_int32         *kindex;         /* mapping from intensity to lower k    */
+    l_float32       *kfract;         /* mapping from intensity to fract k    */
+    struct Pixa     *pixac;          /* intermediate result images (PBC)     */
+    l_uint32      ***lineset;        /* lineptrs for pixac                   */
+};
+typedef struct L_Bilateral  L_BILATERAL;
+
+
+#endif  /* LEPTONICA_BILATERAL_H */
diff --git a/src/bilinear.c b/src/bilinear.c
new file mode 100644 (file)
index 0000000..b963745
--- /dev/null
@@ -0,0 +1,897 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  bilinear.c
+ *
+ *      Bilinear (4 pt) image transformation using a sampled
+ *      (to nearest integer) transform on each dest point
+ *           PIX      *pixBilinearSampledPta()
+ *           PIX      *pixBilinearSampled()
+ *
+ *      Bilinear (4 pt) image transformation using interpolation
+ *      (or area mapping) for anti-aliasing images that are
+ *      2, 4, or 8 bpp gray, or colormapped, or 32 bpp RGB
+ *           PIX      *pixBilinearPta()
+ *           PIX      *pixBilinear()
+ *           PIX      *pixBilinearPtaColor()
+ *           PIX      *pixBilinearColor()
+ *           PIX      *pixBilinearPtaGray()
+ *           PIX      *pixBilinearGray()
+ *
+ *      Bilinear transform including alpha (blend) component
+ *           PIX      *pixBilinearPtaWithAlpha()
+ *
+ *      Bilinear coordinate transformation
+ *           l_int32   getBilinearXformCoeffs()
+ *           l_int32   bilinearXformSampledPt()
+ *           l_int32   bilinearXformPt()
+ *
+ *      A bilinear transform can be specified as a specific functional
+ *      mapping between 4 points in the source and 4 points in the dest.
+ *      It can be used as an approximation to a (nonlinear) projective
+ *      transform, because for small warps it is very similar and
+ *      it is more stable.  (Projective transforms have a division
+ *      by a quantity that can get arbitrarily small.)
+ *
+ *      We give both a bilinear coordinate transformation and
+ *      a bilinear image transformation.
+ *
+ *      For the former, we ask for the coordinate value (x',y')
+ *      in the transformed space for any point (x,y) in the original
+ *      space.  The coefficients of the transformation are found by
+ *      solving 8 simultaneous equations for the 8 coordinates of
+ *      the 4 points in src and dest.  The transformation can then
+ *      be used to compute the associated image transform, by
+ *      computing, for each dest pixel, the relevant pixel(s) in
+ *      the source.  This can be done either by taking the closest
+ *      src pixel to each transformed dest pixel ("sampling") or
+ *      by doing an interpolation and averaging over 4 source
+ *      pixels with appropriate weightings ("interpolated").
+ *
+ *      A typical application would be to remove some of the
+ *      keystoning due to a projective transform in the imaging system.
+ *
+ *      The bilinear transform is given by specifying two equations:
+ *
+ *          x' = ax + by + cxy + d
+ *          y' = ex + fy + gxy + h
+ *
+ *      where the eight coefficients have been computed from four
+ *      sets of these equations, each for two corresponding data pts.
+ *      In practice, once the coefficients are known, we use the
+ *      equations "backwards": for each point (x,y) in the dest image,
+ *      these two equations are used to compute the corresponding point
+ *      (x',y') in the src.  That computed point in the src is then used
+ *      to determine the corresponding dest pixel value in one of two ways:
+ *
+ *       - sampling: simply take the value of the src pixel in which this
+ *                   point falls
+ *       - interpolation: take appropriate linear combinations of the
+ *                        four src pixels that this dest pixel would
+ *                        overlap, with the coefficients proportional
+ *                        to the amount of overlap
+ *
+ *      For small warp, like rotation, area mapping in the
+ *      interpolation is equivalent to linear interpolation.
+ *
+ *      Typical relative timing of transforms (sampled = 1.0):
+ *      8 bpp:   sampled        1.0
+ *               interpolated   1.6
+ *      32 bpp:  sampled        1.0
+ *               interpolated   1.8
+ *      Additionally, the computation time/pixel is nearly the same
+ *      for 8 bpp and 32 bpp, for both sampled and interpolated.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+extern l_float32  AlphaMaskBorderVals[2];
+
+
+/*-------------------------------------------------------------*
+ *             Sampled bilinear image transformation           *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixBilinearSampledPta()
+ *
+ *      Input:  pixs (all depths)
+ *              ptad  (4 pts of final coordinate space)
+ *              ptas  (4 pts of initial coordinate space)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Brings in either black or white pixels from the boundary.
+ *      (2) Retains colormap, which you can do for a sampled transform..
+ *      (3) No 3 of the 4 points may be collinear.
+ *      (4) For 8 and 32 bpp pix, better quality is obtained by the
+ *          somewhat slower pixBilinearPta().  See that
+ *          function for relative timings between sampled and interpolated.
+ */
+PIX *
+pixBilinearSampledPta(PIX     *pixs,
+                      PTA     *ptad,
+                      PTA     *ptas,
+                      l_int32  incolor)
+{
+l_float32  *vc;
+PIX        *pixd;
+
+    PROCNAME("pixBilinearSampledPta");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+    if (ptaGetCount(ptas) != 4)
+        return (PIX *)ERROR_PTR("ptas count not 4", procName, NULL);
+    if (ptaGetCount(ptad) != 4)
+        return (PIX *)ERROR_PTR("ptad count not 4", procName, NULL);
+
+        /* Get backwards transform from dest to src, and apply it */
+    getBilinearXformCoeffs(ptad, ptas, &vc);
+    pixd = pixBilinearSampled(pixs, vc, incolor);
+    LEPT_FREE(vc);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixBilinearSampled()
+ *
+ *      Input:  pixs (all depths)
+ *              vc  (vector of 8 coefficients for bilinear transformation)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Brings in either black or white pixels from the boundary.
+ *      (2) Retains colormap, which you can do for a sampled transform..
+ *      (3) For 8 or 32 bpp, much better quality is obtained by the
+ *          somewhat slower pixBilinear().  See that function
+ *          for relative timings between sampled and interpolated.
+ */
+PIX *
+pixBilinearSampled(PIX        *pixs,
+                   l_float32  *vc,
+                   l_int32     incolor)
+{
+l_int32     i, j, w, h, d, x, y, wpls, wpld, color, cmapindex;
+l_uint32    val;
+l_uint32   *datas, *datad, *lines, *lined;
+PIX        *pixd;
+PIXCMAP    *cmap;
+
+    PROCNAME("pixBilinearSampled");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!vc)
+        return (PIX *)ERROR_PTR("vc not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("depth not 1, 2, 4, 8 or 16", procName, NULL);
+
+        /* Init all dest pixels to color to be brought in from outside */
+    pixd = pixCreateTemplate(pixs);
+    if ((cmap = pixGetColormap(pixs)) != NULL) {
+        if (incolor == L_BRING_IN_WHITE)
+            color = 1;
+        else
+            color = 0;
+        pixcmapAddBlackOrWhite(cmap, color, &cmapindex);
+        pixSetAllArbitrary(pixd, cmapindex);
+    } else {
+        if ((d == 1 && incolor == L_BRING_IN_WHITE) ||
+            (d > 1 && incolor == L_BRING_IN_BLACK)) {
+            pixClearAll(pixd);
+        } else {
+            pixSetAll(pixd);
+        }
+    }
+
+        /* Scan over the dest pixels */
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            bilinearXformSampledPt(vc, j, i, &x, &y);
+            if (x < 0 || y < 0 || x >=w || y >= h)
+                continue;
+            lines = datas + y * wpls;
+            if (d == 1) {
+                val = GET_DATA_BIT(lines, x);
+                SET_DATA_BIT_VAL(lined, j, val);
+            } else if (d == 8) {
+                val = GET_DATA_BYTE(lines, x);
+                SET_DATA_BYTE(lined, j, val);
+            } else if (d == 32) {
+                lined[j] = lines[x];
+            } else if (d == 2) {
+                val = GET_DATA_DIBIT(lines, x);
+                SET_DATA_DIBIT(lined, j, val);
+            } else if (d == 4) {
+                val = GET_DATA_QBIT(lines, x);
+                SET_DATA_QBIT(lined, j, val);
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *            Interpolated bilinear image transformation             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixBilinearPta()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              ptad  (4 pts of final coordinate space)
+ *              ptas  (4 pts of initial coordinate space)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Brings in either black or white pixels from the boundary
+ *      (2) Removes any existing colormap, if necessary, before transforming
+ */
+PIX *
+pixBilinearPta(PIX     *pixs,
+               PTA     *ptad,
+               PTA     *ptas,
+               l_int32  incolor)
+{
+l_int32   d;
+l_uint32  colorval;
+PIX      *pixt1, *pixt2, *pixd;
+
+    PROCNAME("pixBilinearPta");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+    if (ptaGetCount(ptas) != 4)
+        return (PIX *)ERROR_PTR("ptas count not 4", procName, NULL);
+    if (ptaGetCount(ptad) != 4)
+        return (PIX *)ERROR_PTR("ptad count not 4", procName, NULL);
+
+    if (pixGetDepth(pixs) == 1)
+        return pixBilinearSampledPta(pixs, ptad, ptas, incolor);
+
+        /* Remove cmap if it exists, and unpack to 8 bpp if necessary */
+    pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    d = pixGetDepth(pixt1);
+    if (d < 8)
+        pixt2 = pixConvertTo8(pixt1, FALSE);
+    else
+        pixt2 = pixClone(pixt1);
+    d = pixGetDepth(pixt2);
+
+        /* Compute actual color to bring in from edges */
+    colorval = 0;
+    if (incolor == L_BRING_IN_WHITE) {
+        if (d == 8)
+            colorval = 255;
+        else  /* d == 32 */
+            colorval = 0xffffff00;
+    }
+
+    if (d == 8)
+        pixd = pixBilinearPtaGray(pixt2, ptad, ptas, colorval);
+    else  /* d == 32 */
+        pixd = pixBilinearPtaColor(pixt2, ptad, ptas, colorval);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    return pixd;
+}
+
+
+/*!
+ *  pixBilinear()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              vc  (vector of 8 coefficients for bilinear transformation)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Brings in either black or white pixels from the boundary
+ *      (2) Removes any existing colormap, if necessary, before transforming
+ */
+PIX *
+pixBilinear(PIX        *pixs,
+            l_float32  *vc,
+            l_int32     incolor)
+{
+l_int32   d;
+l_uint32  colorval;
+PIX      *pixt1, *pixt2, *pixd;
+
+    PROCNAME("pixBilinear");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!vc)
+        return (PIX *)ERROR_PTR("vc not defined", procName, NULL);
+
+    if (pixGetDepth(pixs) == 1)
+        return pixBilinearSampled(pixs, vc, incolor);
+
+        /* Remove cmap if it exists, and unpack to 8 bpp if necessary */
+    pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    d = pixGetDepth(pixt1);
+    if (d < 8)
+        pixt2 = pixConvertTo8(pixt1, FALSE);
+    else
+        pixt2 = pixClone(pixt1);
+    d = pixGetDepth(pixt2);
+
+        /* Compute actual color to bring in from edges */
+    colorval = 0;
+    if (incolor == L_BRING_IN_WHITE) {
+        if (d == 8)
+            colorval = 255;
+        else  /* d == 32 */
+            colorval = 0xffffff00;
+    }
+
+    if (d == 8)
+        pixd = pixBilinearGray(pixt2, vc, colorval);
+    else  /* d == 32 */
+        pixd = pixBilinearColor(pixt2, vc, colorval);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    return pixd;
+}
+
+
+/*!
+ *  pixBilinearPtaColor()
+ *
+ *      Input:  pixs (32 bpp)
+ *              ptad  (4 pts of final coordinate space)
+ *              ptas  (4 pts of initial coordinate space)
+ *              colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixBilinearPtaColor(PIX      *pixs,
+                    PTA      *ptad,
+                    PTA      *ptas,
+                    l_uint32  colorval)
+{
+l_float32  *vc;
+PIX        *pixd;
+
+    PROCNAME("pixBilinearPtaColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL);
+    if (ptaGetCount(ptas) != 4)
+        return (PIX *)ERROR_PTR("ptas count not 4", procName, NULL);
+    if (ptaGetCount(ptad) != 4)
+        return (PIX *)ERROR_PTR("ptad count not 4", procName, NULL);
+
+        /* Get backwards transform from dest to src, and apply it */
+    getBilinearXformCoeffs(ptad, ptas, &vc);
+    pixd = pixBilinearColor(pixs, vc, colorval);
+    LEPT_FREE(vc);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixBilinearColor()
+ *
+ *      Input:  pixs (32 bpp)
+ *              vc  (vector of 8 coefficients for bilinear transformation)
+ *              colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixBilinearColor(PIX        *pixs,
+                 l_float32  *vc,
+                 l_uint32    colorval)
+{
+l_int32    i, j, w, h, d, wpls, wpld;
+l_uint32   val;
+l_uint32  *datas, *datad, *lined;
+l_float32  x, y;
+PIX       *pix1, *pix2, *pixd;
+
+    PROCNAME("pixBilinearColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32)
+        return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL);
+    if (!vc)
+        return (PIX *)ERROR_PTR("vc not defined", procName, NULL);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreateTemplate(pixs);
+    pixSetAllArbitrary(pixd, colorval);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* Iterate over destination pixels */
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+                /* Compute float src pixel location corresponding to (i,j) */
+            bilinearXformPt(vc, j, i, &x, &y);
+            linearInterpolatePixelColor(datas, wpls, w, h, x, y, colorval,
+                                        &val);
+            *(lined + j) = val;
+        }
+    }
+
+        /* If rgba, transform the pixs alpha channel and insert in pixd */
+    if (pixGetSpp(pixs) == 4) {
+        pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+        pix2 = pixBilinearGray(pix1, vc, 255);  /* bring in opaque */
+        pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixBilinearPtaGray()
+ *
+ *      Input:  pixs (8 bpp)
+ *              ptad  (4 pts of final coordinate space)
+ *              ptas  (4 pts of initial coordinate space)
+ *              grayval (0 to bring in BLACK, 255 for WHITE)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixBilinearPtaGray(PIX     *pixs,
+                   PTA     *ptad,
+                   PTA     *ptas,
+                   l_uint8  grayval)
+{
+l_float32  *vc;
+PIX        *pixd;
+
+    PROCNAME("pixBilinearPtaGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs must be 8 bpp", procName, NULL);
+    if (ptaGetCount(ptas) != 4)
+        return (PIX *)ERROR_PTR("ptas count not 4", procName, NULL);
+    if (ptaGetCount(ptad) != 4)
+        return (PIX *)ERROR_PTR("ptad count not 4", procName, NULL);
+
+        /* Get backwards transform from dest to src, and apply it */
+    getBilinearXformCoeffs(ptad, ptas, &vc);
+    pixd = pixBilinearGray(pixs, vc, grayval);
+    LEPT_FREE(vc);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixBilinearGray()
+ *
+ *      Input:  pixs (8 bpp)
+ *              vc  (vector of 8 coefficients for bilinear transformation)
+ *              grayval (0 to bring in BLACK, 255 for WHITE)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixBilinearGray(PIX        *pixs,
+                l_float32  *vc,
+                l_uint8     grayval)
+{
+l_int32    i, j, w, h, wpls, wpld, val;
+l_uint32  *datas, *datad, *lined;
+l_float32  x, y;
+PIX       *pixd;
+
+    PROCNAME("pixBilinearGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs must be 8 bpp", procName, NULL);
+    if (!vc)
+        return (PIX *)ERROR_PTR("vc not defined", procName, NULL);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreateTemplate(pixs);
+    pixSetAllArbitrary(pixd, grayval);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* Iterate over destination pixels */
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+                /* Compute float src pixel location corresponding to (i,j) */
+            bilinearXformPt(vc, j, i, &x, &y);
+            linearInterpolatePixelGray(datas, wpls, w, h, x, y, grayval, &val);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------------------*
+ *           Bilinear transform including alpha (blend) component          *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  pixBilinearPtaWithAlpha()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              ptad  (4 pts of final coordinate space)
+ *              ptas  (4 pts of initial coordinate space)
+ *              pixg (<optional> 8 bpp, can be null)
+ *              fract (between 0.0 and 1.0, with 0.0 fully transparent
+ *                     and 1.0 fully opaque)
+ *              border (of pixels added to capture transformed source pixels)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The alpha channel is transformed separately from pixs,
+ *          and aligns with it, being fully transparent outside the
+ *          boundary of the transformed pixs.  For pixels that are fully
+ *          transparent, a blending function like pixBlendWithGrayMask()
+ *          will give zero weight to corresponding pixels in pixs.
+ *      (2) If pixg is NULL, it is generated as an alpha layer that is
+ *          partially opaque, using @fract.  Otherwise, it is cropped
+ *          to pixs if required and @fract is ignored.  The alpha channel
+ *          in pixs is never used.
+ *      (3) Colormaps are removed.
+ *      (4) When pixs is transformed, it doesn't matter what color is brought
+ *          in because the alpha channel will be transparent (0) there.
+ *      (5) To avoid losing source pixels in the destination, it may be
+ *          necessary to add a border to the source pix before doing
+ *          the bilinear transformation.  This can be any non-negative number.
+ *      (6) The input @ptad and @ptas are in a coordinate space before
+ *          the border is added.  Internally, we compensate for this
+ *          before doing the bilinear transform on the image after
+ *          the border is added.
+ *      (7) The default setting for the border values in the alpha channel
+ *          is 0 (transparent) for the outermost ring of pixels and
+ *          (0.5 * fract * 255) for the second ring.  When blended over
+ *          a second image, this
+ *          (a) shrinks the visible image to make a clean overlap edge
+ *              with an image below, and
+ *          (b) softens the edges by weakening the aliasing there.
+ *          Use l_setAlphaMaskBorder() to change these values.
+ */
+PIX *
+pixBilinearPtaWithAlpha(PIX       *pixs,
+                        PTA       *ptad,
+                        PTA       *ptas,
+                        PIX       *pixg,
+                        l_float32  fract,
+                        l_int32    border)
+{
+l_int32  ws, hs, d;
+PIX     *pixd, *pixb1, *pixb2, *pixg2, *pixga;
+PTA     *ptad2, *ptas2;
+
+    PROCNAME("pixBilinearPtaWithAlpha");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &ws, &hs, &d);
+    if (d != 32 && pixGetColormap(pixs) == NULL)
+        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
+    if (pixg && pixGetDepth(pixg) != 8) {
+        L_WARNING("pixg not 8 bpp; using @fract transparent alpha\n", procName);
+        pixg = NULL;
+    }
+    if (!pixg && (fract < 0.0 || fract > 1.0)) {
+        L_WARNING("invalid fract; using 1.0 (fully transparent)\n", procName);
+        fract = 1.0;
+    }
+    if (!pixg && fract == 0.0)
+        L_WARNING("fully opaque alpha; image cannot be blended\n", procName);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+
+        /* Add border; the color doesn't matter */
+    pixb1 = pixAddBorder(pixs, border, 0);
+
+        /* Transform the ptr arrays to work on the bordered image */
+    ptad2 = ptaTransform(ptad, border, border, 1.0, 1.0);
+    ptas2 = ptaTransform(ptas, border, border, 1.0, 1.0);
+
+        /* Do separate bilinear transform of rgb channels of pixs and of pixg */
+    pixd = pixBilinearPtaColor(pixb1, ptad2, ptas2, 0);
+    if (!pixg) {
+        pixg2 = pixCreate(ws, hs, 8);
+        if (fract == 1.0)
+            pixSetAll(pixg2);
+        else
+            pixSetAllArbitrary(pixg2, (l_int32)(255.0 * fract));
+    } else {
+        pixg2 = pixResizeToMatch(pixg, NULL, ws, hs);
+    }
+    if (ws > 10 && hs > 10) {  /* see note 7 */
+        pixSetBorderRingVal(pixg2, 1,
+                            (l_int32)(255.0 * fract * AlphaMaskBorderVals[0]));
+        pixSetBorderRingVal(pixg2, 2,
+                            (l_int32)(255.0 * fract * AlphaMaskBorderVals[1]));
+
+    }
+    pixb2 = pixAddBorder(pixg2, border, 0);  /* must be black border */
+    pixga = pixBilinearPtaGray(pixb2, ptad2, ptas2, 0);
+    pixSetRGBComponent(pixd, pixga, L_ALPHA_CHANNEL);
+    pixSetSpp(pixd, 4);
+
+    pixDestroy(&pixg2);
+    pixDestroy(&pixb1);
+    pixDestroy(&pixb2);
+    pixDestroy(&pixga);
+    ptaDestroy(&ptad2);
+    ptaDestroy(&ptas2);
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ *                Bilinear coordinate transformation           *
+ *-------------------------------------------------------------*/
+/*!
+ *  getBilinearXformCoeffs()
+ *
+ *      Input:  ptas  (source 4 points; unprimed)
+ *              ptad  (transformed 4 points; primed)
+ *              &vc   (<return> vector of coefficients of transform)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  We have a set of 8 equations, describing the bilinear
+ *  transformation that takes 4 points (ptas) into 4 other
+ *  points (ptad).  These equations are:
+ *
+ *          x1' = c[0]*x1 + c[1]*y1 + c[2]*x1*y1 + c[3]
+ *          y1' = c[4]*x1 + c[5]*y1 + c[6]*x1*y1 + c[7]
+ *          x2' = c[0]*x2 + c[1]*y2 + c[2]*x2*y2 + c[3]
+ *          y2' = c[4]*x2 + c[5]*y2 + c[6]*x2*y2 + c[7]
+ *          x3' = c[0]*x3 + c[1]*y3 + c[2]*x3*y3 + c[3]
+ *          y3' = c[4]*x3 + c[5]*y3 + c[6]*x3*y3 + c[7]
+ *          x4' = c[0]*x4 + c[1]*y4 + c[2]*x4*y4 + c[3]
+ *          y4' = c[4]*x4 + c[5]*y4 + c[6]*x4*y4 + c[7]
+ *
+ *  This can be represented as
+ *
+ *           AC = B
+ *
+ *  where B and C are column vectors
+ *
+ *         B = [ x1' y1' x2' y2' x3' y3' x4' y4' ]
+ *         C = [ c[0] c[1] c[2] c[3] c[4] c[5] c[6] c[7] ]
+ *
+ *  and A is the 8x8 matrix
+ *
+ *             x1   y1   x1*y1   1   0    0      0     0
+ *              0    0     0     0   x1   y1   x1*y1   1
+ *             x2   y2   x2*y2   1   0    0      0     0
+ *              0    0     0     0   x2   y2   x2*y2   1
+ *             x3   y3   x3*y3   1   0    0      0     0
+ *              0    0     0     0   x3   y3   x3*y3   1
+ *             x4   y4   x4*y4   1   0    0      0     0
+ *              0    0     0     0   x4   y4   x4*y4   1
+ *
+ *  These eight equations are solved here for the coefficients C.
+ *
+ *  These eight coefficients can then be used to find the mapping
+ *  (x,y) --> (x',y'):
+ *
+ *           x' = c[0]x + c[1]y + c[2]xy + c[3]
+ *           y' = c[4]x + c[5]y + c[6]xy + c[7]
+ *
+ *  that are implemented in bilinearXformSampledPt() and
+ *  bilinearXFormPt().
+ */
+l_int32
+getBilinearXformCoeffs(PTA         *ptas,
+                       PTA         *ptad,
+                       l_float32  **pvc)
+{
+l_int32     i;
+l_float32   x1, y1, x2, y2, x3, y3, x4, y4;
+l_float32  *b;   /* rhs vector of primed coords X'; coeffs returned in *pvc */
+l_float32  *a[8];  /* 8x8 matrix A  */
+
+    PROCNAME("getBilinearXformCoeffs");
+
+    if (!ptas)
+        return ERROR_INT("ptas not defined", procName, 1);
+    if (!ptad)
+        return ERROR_INT("ptad not defined", procName, 1);
+    if (!pvc)
+        return ERROR_INT("&vc not defined", procName, 1);
+
+    if ((b = (l_float32 *)LEPT_CALLOC(8, sizeof(l_float32))) == NULL)
+        return ERROR_INT("b not made", procName, 1);
+    *pvc = b;
+
+    ptaGetPt(ptas, 0, &x1, &y1);
+    ptaGetPt(ptas, 1, &x2, &y2);
+    ptaGetPt(ptas, 2, &x3, &y3);
+    ptaGetPt(ptas, 3, &x4, &y4);
+    ptaGetPt(ptad, 0, &b[0], &b[1]);
+    ptaGetPt(ptad, 1, &b[2], &b[3]);
+    ptaGetPt(ptad, 2, &b[4], &b[5]);
+    ptaGetPt(ptad, 3, &b[6], &b[7]);
+
+    for (i = 0; i < 8; i++) {
+        if ((a[i] = (l_float32 *)LEPT_CALLOC(8, sizeof(l_float32))) == NULL)
+            return ERROR_INT("a[i] not made", procName, 1);
+    }
+
+    a[0][0] = x1;
+    a[0][1] = y1;
+    a[0][2] = x1 * y1;
+    a[0][3] = 1.;
+    a[1][4] = x1;
+    a[1][5] = y1;
+    a[1][6] = x1 * y1;
+    a[1][7] = 1.;
+    a[2][0] = x2;
+    a[2][1] = y2;
+    a[2][2] = x2 * y2;
+    a[2][3] = 1.;
+    a[3][4] = x2;
+    a[3][5] = y2;
+    a[3][6] = x2 * y2;
+    a[3][7] = 1.;
+    a[4][0] = x3;
+    a[4][1] = y3;
+    a[4][2] = x3 * y3;
+    a[4][3] = 1.;
+    a[5][4] = x3;
+    a[5][5] = y3;
+    a[5][6] = x3 * y3;
+    a[5][7] = 1.;
+    a[6][0] = x4;
+    a[6][1] = y4;
+    a[6][2] = x4 * y4;
+    a[6][3] = 1.;
+    a[7][4] = x4;
+    a[7][5] = y4;
+    a[7][6] = x4 * y4;
+    a[7][7] = 1.;
+
+    gaussjordan(a, b, 8);
+
+    for (i = 0; i < 8; i++)
+        LEPT_FREE(a[i]);
+
+    return 0;
+}
+
+
+/*!
+ *  bilinearXformSampledPt()
+ *
+ *      Input:  vc (vector of 8 coefficients)
+ *              (x, y)  (initial point)
+ *              (&xp, &yp)   (<return> transformed point)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This finds the nearest pixel coordinates of the transformed point.
+ *      (2) It does not check ptrs for returned data!
+ */
+l_int32
+bilinearXformSampledPt(l_float32  *vc,
+                       l_int32     x,
+                       l_int32     y,
+                       l_int32    *pxp,
+                       l_int32    *pyp)
+{
+
+    PROCNAME("bilinearXformSampledPt");
+
+    if (!vc)
+        return ERROR_INT("vc not defined", procName, 1);
+
+    *pxp = (l_int32)(vc[0] * x + vc[1] * y + vc[2] * x * y + vc[3] + 0.5);
+    *pyp = (l_int32)(vc[4] * x + vc[5] * y + vc[6] * x * y + vc[7] + 0.5);
+    return 0;
+}
+
+
+/*!
+ *  bilinearXformPt()
+ *
+ *      Input:  vc (vector of 8 coefficients)
+ *              (x, y)  (initial point)
+ *              (&xp, &yp)   (<return> transformed point)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This computes the floating point location of the transformed point.
+ *      (2) It does not check ptrs for returned data!
+ */
+l_int32
+bilinearXformPt(l_float32  *vc,
+                l_int32     x,
+                l_int32     y,
+                l_float32  *pxp,
+                l_float32  *pyp)
+{
+    PROCNAME("bilinearXformPt");
+
+    if (!vc)
+        return ERROR_INT("vc not defined", procName, 1);
+
+    *pxp = vc[0] * x + vc[1] * y + vc[2] * x * y + vc[3];
+    *pyp = vc[4] * x + vc[5] * y + vc[6] * x * y + vc[7];
+    return 0;
+}
diff --git a/src/binarize.c b/src/binarize.c
new file mode 100644 (file)
index 0000000..594b5fd
--- /dev/null
@@ -0,0 +1,999 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  binarize.c
+ *
+ *  ===================================================================
+ *  Image binarization algorithms are found in:
+ *    grayquant.c:   standard, simple, general grayscale quantization
+ *    adaptmap.c:    local adaptive; mostly gray-to-gray in preparation
+ *                   for binarization
+ *    binarize.c:    special binarization methods, locally adaptive and
+ *                   global.
+ *  ===================================================================
+ *
+ *      Adaptive Otsu-based thresholding
+ *          l_int32    pixOtsuAdaptiveThreshold()       8 bpp
+ *
+ *      Otsu thresholding on adaptive background normalization
+ *          PIX       *pixOtsuThreshOnBackgroundNorm()  8 bpp
+ *
+ *      Masking and Otsu estimate on adaptive background normalization
+ *          PIX       *pixMaskedThreshOnBackgroundNorm()  8 bpp
+ *
+ *      Sauvola local thresholding
+ *          l_int32    pixSauvolaBinarizeTiled()
+ *          l_int32    pixSauvolaBinarize()
+ *          PIX       *pixSauvolaGetThreshold()
+ *          PIX       *pixApplyLocalThreshold();
+ *
+ *      Thresholding using connected components
+ *          PIX       *pixThresholdByConnComp()
+ *
+ *  Notes:
+ *      (1) pixOtsuAdaptiveThreshold() computes a global threshold over each
+ *          tile and performs the threshold operation, resulting in a
+ *          binary image for each tile.  These are stitched into the
+ *          final result.
+ *      (2) pixOtsuThreshOnBackgroundNorm() and
+ *          pixMaskedThreshOnBackgroundNorm() are binarization functions
+ *          that use background normalization with other techniques.
+ *      (3) Sauvola binarization computes a local threshold based on
+ *          the local average and square average.  It takes two constants:
+ *          the window size for the measurment at each pixel and a
+ *          parameter that determines the amount of normalized local
+ *          standard deviation to subtract from the local average value.
+ *      (4) pixThresholdByCC() uses the numbers of 4 and 8 connected
+ *          components at different thresholding to determine if a
+ *          global threshold can be used (for text or line-art) and the
+ *          value it should have.
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+/*------------------------------------------------------------------*
+ *                 Adaptive Otsu-based thresholding                 *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixOtsuAdaptiveThreshold()
+ *
+ *      Input:  pixs (8 bpp)
+ *              sx, sy (desired tile dimensions; actual size may vary)
+ *              smoothx, smoothy (half-width of convolution kernel applied to
+ *                                threshold array: use 0 for no smoothing)
+ *              scorefract (fraction of the max Otsu score; typ. 0.1;
+ *                          use 0.0 for standard Otsu)
+ *              &pixth (<optional return> array of threshold values
+ *                      found for each tile)
+ *              &pixd (<optional return> thresholded input pixs, based on
+ *                     the threshold array)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The Otsu method finds a single global threshold for an image.
+ *          This function allows a locally adapted threshold to be
+ *          found for each tile into which the image is broken up.
+ *      (2) The array of threshold values, one for each tile, constitutes
+ *          a highly downscaled image.  This array is optionally
+ *          smoothed using a convolution.  The full width and height of the
+ *          convolution kernel are (2 * @smoothx + 1) and (2 * @smoothy + 1).
+ *      (3) The minimum tile dimension allowed is 16.  If such small
+ *          tiles are used, it is recommended to use smoothing, because
+ *          without smoothing, each small tile determines the splitting
+ *          threshold independently.  A tile that is entirely in the
+ *          image bg will then hallucinate fg, resulting in a very noisy
+ *          binarization.  The smoothing should be large enough that no
+ *          tile is only influenced by one type (fg or bg) of pixels,
+ *          because it will force a split of its pixels.
+ *      (4) To get a single global threshold for the entire image, use
+ *          input values of @sx and @sy that are larger than the image.
+ *          For this situation, the smoothing parameters are ignored.
+ *      (5) The threshold values partition the image pixels into two classes:
+ *          one whose values are less than the threshold and another
+ *          whose values are greater than or equal to the threshold.
+ *          This is the same use of 'threshold' as in pixThresholdToBinary().
+ *      (6) The scorefract is the fraction of the maximum Otsu score, which
+ *          is used to determine the range over which the histogram minimum
+ *          is searched.  See numaSplitDistribution() for details on the
+ *          underlying method of choosing a threshold.
+ *      (7) This uses enables a modified version of the Otsu criterion for
+ *          splitting the distribution of pixels in each tile into a
+ *          fg and bg part.  The modification consists of searching for
+ *          a minimum in the histogram over a range of pixel values where
+ *          the Otsu score is within a defined fraction, @scorefract,
+ *          of the max score.  To get the original Otsu algorithm, set
+ *          @scorefract == 0.
+ *      (8) N.B. This method is NOT recommended for images with weak text
+ *          and significant background noise, such as bleedthrough, because
+ *          of the problem noted in (3) above for tiling.  Use Sauvola.
+ */
+l_int32
+pixOtsuAdaptiveThreshold(PIX       *pixs,
+                         l_int32    sx,
+                         l_int32    sy,
+                         l_int32    smoothx,
+                         l_int32    smoothy,
+                         l_float32  scorefract,
+                         PIX      **ppixth,
+                         PIX      **ppixd)
+{
+l_int32     w, h, nx, ny, i, j, thresh;
+l_uint32    val;
+PIX        *pixt, *pixb, *pixthresh, *pixth, *pixd;
+PIXTILING  *pt;
+
+    PROCNAME("pixOtsuAdaptiveThreshold");
+
+    if (!ppixth && !ppixd)
+        return ERROR_INT("neither &pixth nor &pixd defined", procName, 1);
+    if (ppixth) *ppixth = NULL;
+    if (ppixd) *ppixd = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (sx < 16 || sy < 16)
+        return ERROR_INT("sx and sy must be >= 16", procName, 1);
+
+        /* Compute the threshold array for the tiles */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    nx = L_MAX(1, w / sx);
+    ny = L_MAX(1, h / sy);
+    smoothx = L_MIN(smoothx, (nx - 1) / 2);
+    smoothy = L_MIN(smoothy, (ny - 1) / 2);
+    pt = pixTilingCreate(pixs, nx, ny, 0, 0, 0, 0);
+    pixthresh = pixCreate(nx, ny, 8);
+    for (i = 0; i < ny; i++) {
+        for (j = 0; j < nx; j++) {
+            pixt = pixTilingGetTile(pt, i, j);
+            pixSplitDistributionFgBg(pixt, scorefract, 1, &thresh,
+                                     NULL, NULL, 0);
+            pixSetPixel(pixthresh, j, i, thresh);  /* see note (4) */
+            pixDestroy(&pixt);
+        }
+    }
+
+        /* Optionally smooth the threshold array */
+    if (smoothx > 0 || smoothy > 0)
+        pixth = pixBlockconv(pixthresh, smoothx, smoothy);
+    else
+        pixth = pixClone(pixthresh);
+    pixDestroy(&pixthresh);
+
+        /* Optionally apply the threshold array to binarize pixs */
+    if (ppixd) {
+        pixd = pixCreate(w, h, 1);
+        pixCopyResolution(pixd, pixs);
+        for (i = 0; i < ny; i++) {
+            for (j = 0; j < nx; j++) {
+                pixt = pixTilingGetTile(pt, i, j);
+                pixGetPixel(pixth, j, i, &val);
+                pixb = pixThresholdToBinary(pixt, val);
+                pixTilingPaintTile(pixd, i, j, pixb, pt);
+                pixDestroy(&pixt);
+                pixDestroy(&pixb);
+            }
+        }
+        *ppixd = pixd;
+    }
+
+    if (ppixth)
+        *ppixth = pixth;
+    else
+        pixDestroy(&pixth);
+
+    pixTilingDestroy(&pt);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *      Otsu thresholding on adaptive background normalization      *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixOtsuThreshOnBackgroundNorm()
+ *
+ *      Input:  pixs (8 bpp grayscale; not colormapped)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null)
+ *              sx, sy (tile size in pixels)
+ *              thresh (threshold for determining foreground)
+ *              mincount (min threshold on counts in a tile)
+ *              bgval (target bg val; typ. > 128)
+ *              smoothx (half-width of block convolution kernel width)
+ *              smoothy (half-width of block convolution kernel height)
+ *              scorefract (fraction of the max Otsu score; typ. 0.1)
+ *              &thresh (<optional return> threshold value that was
+ *                       used on the normalized image)
+ *      Return: pixd (1 bpp thresholded image), or null on error
+ *
+ *  Notes:
+ *      (1) This does background normalization followed by Otsu
+ *          thresholding.  Otsu binarization attempts to split the
+ *          image into two roughly equal sets of pixels, and it does
+ *          a very poor job when there are large amounts of dark
+ *          background.  By doing a background normalization first,
+ *          to get the background near 255, we remove this problem.
+ *          Then we use a modified Otsu to estimate the best global
+ *          threshold on the normalized image.
+ *      (2) See pixBackgroundNorm() for meaning and typical values
+ *          of input parameters.  For a start, you can try:
+ *            sx, sy = 10, 15
+ *            thresh = 100
+ *            mincount = 50
+ *            bgval = 255
+ *            smoothx, smoothy = 2
+ */
+PIX *
+pixOtsuThreshOnBackgroundNorm(PIX       *pixs,
+                              PIX       *pixim,
+                              l_int32    sx,
+                              l_int32    sy,
+                              l_int32    thresh,
+                              l_int32    mincount,
+                              l_int32    bgval,
+                              l_int32    smoothx,
+                              l_int32    smoothy,
+                              l_float32  scorefract,
+                              l_int32   *pthresh)
+{
+l_int32   w, h;
+l_uint32  val;
+PIX      *pixn, *pixt, *pixd;
+
+    PROCNAME("pixOtsuThreshOnBackgroundNorm");
+
+    if (pthresh) *pthresh = 0;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs is colormapped", procName, NULL);
+    if (sx < 4 || sy < 4)
+        return (PIX *)ERROR_PTR("sx and sy must be >= 4", procName, NULL);
+    if (mincount > sx * sy) {
+        L_WARNING("mincount too large for tile size\n", procName);
+        mincount = (sx * sy) / 3;
+    }
+
+    pixn = pixBackgroundNorm(pixs, pixim, NULL, sx, sy, thresh,
+                             mincount, bgval, smoothx, smoothy);
+    if (!pixn)
+        return (PIX *)ERROR_PTR("pixn not made", procName, NULL);
+
+        /* Just use 1 tile for a global threshold, which is stored
+         * as a single pixel in pixt. */
+    pixGetDimensions(pixn, &w, &h, NULL);
+    pixOtsuAdaptiveThreshold(pixn, w, h, 0, 0, scorefract, &pixt, &pixd);
+    pixDestroy(&pixn);
+
+    if (pixt && pthresh) {
+        pixGetPixel(pixt, 0, 0, &val);
+        *pthresh = val;
+    }
+    pixDestroy(&pixt);
+
+    if (!pixd)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    else
+        return pixd;
+}
+
+
+
+/*----------------------------------------------------------------------*
+ *    Masking and Otsu estimate on adaptive background normalization    *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixMaskedThreshOnBackgroundNorm()
+ *
+ *      Input:  pixs (8 bpp grayscale; not colormapped)
+ *              pixim (<optional> 1 bpp 'image' mask; can be null)
+ *              sx, sy (tile size in pixels)
+ *              thresh (threshold for determining foreground)
+ *              mincount (min threshold on counts in a tile)
+ *              smoothx (half-width of block convolution kernel width)
+ *              smoothy (half-width of block convolution kernel height)
+ *              scorefract (fraction of the max Otsu score; typ. ~ 0.1)
+ *              &thresh (<optional return> threshold value that was
+ *                       used on the normalized image)
+ *      Return: pixd (1 bpp thresholded image), or null on error
+ *
+ *  Notes:
+ *      (1) This begins with a standard background normalization.
+ *          Additionally, there is a flexible background norm, that
+ *          will adapt to a rapidly varying background, and this
+ *          puts white pixels in the background near regions with
+ *          significant foreground.  The white pixels are turned into
+ *          a 1 bpp selection mask by binarization followed by dilation.
+ *          Otsu thresholding is performed on the input image to get an
+ *          estimate of the threshold in the non-mask regions.
+ *          The background normalized image is thresholded with two
+ *          different values, and the result is combined using
+ *          the selection mask.
+ *      (2) Note that the numbers 255 (for bgval target) and 190 (for
+ *          thresholding on pixn) are tied together, and explicitly
+ *          defined in this function.
+ *      (3) See pixBackgroundNorm() for meaning and typical values
+ *          of input parameters.  For a start, you can try:
+ *            sx, sy = 10, 15
+ *            thresh = 100
+ *            mincount = 50
+ *            smoothx, smoothy = 2
+ */
+PIX *
+pixMaskedThreshOnBackgroundNorm(PIX       *pixs,
+                                PIX       *pixim,
+                                l_int32    sx,
+                                l_int32    sy,
+                                l_int32    thresh,
+                                l_int32    mincount,
+                                l_int32    smoothx,
+                                l_int32    smoothy,
+                                l_float32  scorefract,
+                                l_int32   *pthresh)
+{
+l_int32   w, h;
+l_uint32  val;
+PIX      *pixn, *pixm, *pixd, *pixt1, *pixt2, *pixt3, *pixt4;
+
+    PROCNAME("pixMaskedThreshOnBackgroundNorm");
+
+    if (pthresh) *pthresh = 0;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs is colormapped", procName, NULL);
+    if (sx < 4 || sy < 4)
+        return (PIX *)ERROR_PTR("sx and sy must be >= 4", procName, NULL);
+    if (mincount > sx * sy) {
+        L_WARNING("mincount too large for tile size\n", procName);
+        mincount = (sx * sy) / 3;
+    }
+
+        /* Standard background normalization */
+    pixn = pixBackgroundNorm(pixs, pixim, NULL, sx, sy, thresh,
+                             mincount, 255, smoothx, smoothy);
+    if (!pixn)
+        return (PIX *)ERROR_PTR("pixn not made", procName, NULL);
+
+        /* Special background normalization for adaptation to quickly
+         * varying background.  Threshold on the very light parts,
+         * which tend to be near significant edges, and dilate to
+         * form a mask over regions that are typically text.  The
+         * dilation size is chosen to cover the text completely,
+         * except for very thick fonts. */
+    pixt1 = pixBackgroundNormFlex(pixs, 7, 7, 1, 1, 20);
+    pixt2 = pixThresholdToBinary(pixt1, 240);
+    pixInvert(pixt2, pixt2);
+    pixm = pixMorphSequence(pixt2, "d21.21", 0);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+        /* Use Otsu to get a global threshold estimate for the image,
+         * which is stored as a single pixel in pixt3. */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixOtsuAdaptiveThreshold(pixs, w, h, 0, 0, scorefract, &pixt3, NULL);
+    if (pixt3 && pthresh) {
+        pixGetPixel(pixt3, 0, 0, &val);
+        *pthresh = val;
+    }
+    pixDestroy(&pixt3);
+
+        /* Threshold the background normalized images differentially,
+         * using a high value correlated with the background normalization
+         * for the part of the image under the mask (i.e., near the
+         * darker, thicker foreground), and a value that depends on the Otsu
+         * threshold for the rest of the image.  This gives a solid
+         * (high) thresholding for the foreground parts of the image,
+         * while allowing the background and light foreground to be
+         * reasonably well cleaned using a threshold adapted to the
+         * input image. */
+    pixd = pixThresholdToBinary(pixn, val + 30);  /* for bg and light fg */
+    pixt4 = pixThresholdToBinary(pixn, 190);  /* for heavier fg */
+    pixCombineMasked(pixd, pixt4, pixm);
+    pixDestroy(&pixt4);
+    pixDestroy(&pixm);
+    pixDestroy(&pixn);
+
+    if (!pixd)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    else
+        return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                           Sauvola binarization                       *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixSauvolaBinarizeTiled()
+ *
+ *      Input:  pixs (8 bpp grayscale, not colormapped)
+ *              whsize (window half-width for measuring local statistics)
+ *              factor (factor for reducing threshold due to variance; >= 0)
+ *              nx, ny (subdivision into tiles; >= 1)
+ *              &pixth (<optional return> Sauvola threshold values)
+ *              &pixd (<optional return> thresholded image)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The window width and height are 2 * @whsize + 1.  The minimum
+ *          value for @whsize is 2; typically it is >= 7..
+ *      (2) For nx == ny == 1, this defaults to pixSauvolaBinarize().
+ *      (3) Why a tiled version?
+ *          (a) Because the mean value accumulator is a uint32, overflow
+ *              can occur for an image with more than 16M pixels.
+ *          (b) The mean value accumulator array for 16M pixels is 64 MB.
+ *              The mean square accumulator array for 16M pixels is 128 MB.
+ *              Using tiles reduces the size of these arrays.
+ *          (c) Each tile can be processed independently, in parallel,
+ *              on a multicore processor.
+ *      (4) The Sauvola threshold is determined from the formula:
+ *              t = m * (1 - k * (1 - s / 128))
+ *          See pixSauvolaBinarize() for details.
+ */
+l_int32
+pixSauvolaBinarizeTiled(PIX       *pixs,
+                        l_int32    whsize,
+                        l_float32  factor,
+                        l_int32    nx,
+                        l_int32    ny,
+                        PIX      **ppixth,
+                        PIX      **ppixd)
+{
+l_int32     i, j, w, h, xrat, yrat;
+PIX        *pixth, *pixd, *tileth, *tiled, *pixt;
+PIX       **ptileth, **ptiled;
+PIXTILING  *pt;
+
+    PROCNAME("pixSauvolaBinarizeTiled");
+
+    if (!ppixth && !ppixd)
+        return ERROR_INT("no outputs", procName, 1);
+    if (ppixth) *ppixth = NULL;
+    if (ppixd) *ppixd = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs undefined or not 8 bpp", procName, 1);
+    if (pixGetColormap(pixs))
+        return ERROR_INT("pixs is cmapped", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (whsize < 2)
+        return ERROR_INT("whsize must be >= 2", procName, 1);
+    if (w < 2 * whsize + 3 || h < 2 * whsize + 3)
+        return ERROR_INT("whsize too large for image", procName, 1);
+    if (factor < 0.0)
+        return ERROR_INT("factor must be >= 0", procName, 1);
+
+    if (nx <= 1 && ny <= 1)
+        return pixSauvolaBinarize(pixs, whsize, factor, 1, NULL, NULL,
+                                  ppixth, ppixd);
+
+        /* Test to see if the tiles are too small.  The required
+         * condition is that the tile dimensions must be at least
+         * (whsize + 2) x (whsize + 2).  */
+    xrat = w / nx;
+    yrat = h / ny;
+    if (xrat < whsize + 2) {
+        nx = w / (whsize + 2);
+        L_WARNING("tile width too small; nx reduced to %d\n", procName, nx);
+    }
+    if (yrat < whsize + 2) {
+        ny = h / (whsize + 2);
+        L_WARNING("tile height too small; ny reduced to %d\n", procName, ny);
+    }
+    if (nx <= 1 && ny <= 1)
+        return pixSauvolaBinarize(pixs, whsize, factor, 1, NULL, NULL,
+                                  ppixth, ppixd);
+
+        /* We can use pixtiling for painting both outputs, if requested */
+    if (ppixth) {
+        pixth = pixCreateNoInit(w, h, 8);
+        *ppixth = pixth;
+    }
+    if (ppixd) {
+        pixd = pixCreateNoInit(w, h, 1);
+        *ppixd = pixd;
+    }
+    pt = pixTilingCreate(pixs, nx, ny, 0, 0, whsize + 1, whsize + 1);
+    pixTilingNoStripOnPaint(pt);  /* pixSauvolaBinarize() does the stripping */
+
+    for (i = 0; i < ny; i++) {
+        for (j = 0; j < nx; j++) {
+            pixt = pixTilingGetTile(pt, i, j);
+            ptileth = (ppixth) ? &tileth : NULL;
+            ptiled = (ppixd) ? &tiled : NULL;
+            pixSauvolaBinarize(pixt, whsize, factor, 0, NULL, NULL,
+                               ptileth, ptiled);
+            if (ppixth) {  /* do not strip */
+                pixTilingPaintTile(pixth, i, j, tileth, pt);
+                pixDestroy(&tileth);
+            }
+            if (ppixd) {
+                pixTilingPaintTile(pixd, i, j, tiled, pt);
+                pixDestroy(&tiled);
+            }
+            pixDestroy(&pixt);
+        }
+    }
+
+    pixTilingDestroy(&pt);
+    return 0;
+}
+
+
+/*!
+ *  pixSauvolaBinarize()
+ *
+ *      Input:  pixs (8 bpp grayscale; not colormapped)
+ *              whsize (window half-width for measuring local statistics)
+ *              factor (factor for reducing threshold due to variance; >= 0)
+ *              addborder (1 to add border of width (@whsize + 1) on all sides)
+ *              &pixm (<optional return> local mean values)
+ *              &pixsd (<optional return> local standard deviation values)
+ *              &pixth (<optional return> threshold values)
+ *              &pixd (<optional return> thresholded image)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The window width and height are 2 * @whsize + 1.  The minimum
+ *          value for @whsize is 2; typically it is >= 7..
+ *      (2) The local statistics, measured over the window, are the
+ *          average and standard deviation.
+ *      (3) The measurements of the mean and standard deviation are
+ *          performed inside a border of (@whsize + 1) pixels.  If pixs does
+ *          not have these added border pixels, use @addborder = 1 to add
+ *          it here; otherwise use @addborder = 0.
+ *      (4) The Sauvola threshold is determined from the formula:
+ *            t = m * (1 - k * (1 - s / 128))
+ *          where:
+ *            t = local threshold
+ *            m = local mean
+ *            k = @factor (>= 0)   [ typ. 0.35 ]
+ *            s = local standard deviation, which is maximized at
+ *                127.5 when half the samples are 0 and half are 255.
+ *      (5) The basic idea of Niblack and Sauvola binarization is that
+ *          the local threshold should be less than the median value,
+ *          and the larger the variance, the closer to the median
+ *          it should be chosen.  Typical values for k are between
+ *          0.2 and 0.5.
+ */
+l_int32
+pixSauvolaBinarize(PIX       *pixs,
+                   l_int32    whsize,
+                   l_float32  factor,
+                   l_int32    addborder,
+                   PIX      **ppixm,
+                   PIX      **ppixsd,
+                   PIX      **ppixth,
+                   PIX      **ppixd)
+{
+l_int32  w, h;
+PIX     *pixg, *pixsc, *pixm, *pixms, *pixth, *pixd;
+
+    PROCNAME("pixSauvolaBinarize");
+
+    if (ppixm) *ppixm = NULL;
+    if (ppixsd) *ppixsd = NULL;
+    if (ppixth) *ppixth = NULL;
+    if (ppixd) *ppixd = NULL;
+    if (!ppixm && !ppixsd && !ppixth && !ppixd)
+        return ERROR_INT("no outputs", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs undefined or not 8 bpp", procName, 1);
+    if (pixGetColormap(pixs))
+        return ERROR_INT("pixs is cmapped", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (whsize < 2)
+        return ERROR_INT("whsize must be >= 2", procName, 1);
+    if (w < 2 * whsize + 3 || h < 2 * whsize + 3)
+        return ERROR_INT("whsize too large for image", procName, 1);
+    if (factor < 0.0)
+        return ERROR_INT("factor must be >= 0", procName, 1);
+
+    if (addborder) {
+        pixg = pixAddMirroredBorder(pixs, whsize + 1, whsize + 1,
+                                    whsize + 1, whsize + 1);
+        pixsc = pixClone(pixs);
+    } else {
+        pixg = pixClone(pixs);
+        pixsc = pixRemoveBorder(pixs, whsize + 1);
+    }
+    if (!pixg || !pixsc)
+        return ERROR_INT("pixg and pixsc not made", procName, 1);
+
+        /* All these functions strip off the border pixels. */
+    if (ppixm || ppixth || ppixd)
+        pixm = pixWindowedMean(pixg, whsize, whsize, 1, 1);
+    if (ppixsd || ppixth || ppixd)
+        pixms = pixWindowedMeanSquare(pixg, whsize, whsize, 1);
+    if (ppixth || ppixd)
+        pixth = pixSauvolaGetThreshold(pixm, pixms, factor, ppixsd);
+    if (ppixd) {
+        pixd = pixApplyLocalThreshold(pixsc, pixth, 1);
+        pixCopyResolution(pixd, pixs);
+    }
+
+    if (ppixm)
+        *ppixm = pixm;
+    else
+        pixDestroy(&pixm);
+    pixDestroy(&pixms);
+    if (ppixth)
+        *ppixth = pixth;
+    else
+        pixDestroy(&pixth);
+    if (ppixd)
+        *ppixd = pixd;
+    else
+        pixDestroy(&pixd);
+    pixDestroy(&pixg);
+    pixDestroy(&pixsc);
+    return 0;
+}
+
+
+/*!
+ *  pixSauvolaGetThreshold()
+ *
+ *      Input:  pixm (8 bpp grayscale; not colormapped)
+ *              pixms (32 bpp)
+ *              factor (factor for reducing threshold due to variance; >= 0)
+ *              &pixsd (<optional return> local standard deviation)
+ *      Return: pixd (8 bpp, sauvola threshold values), or null on error
+ *
+ *  Notes:
+ *      (1) The Sauvola threshold is determined from the formula:
+ *            t = m * (1 - k * (1 - s / 128))
+ *          where:
+ *            t = local threshold
+ *            m = local mean
+ *            k = @factor (>= 0)   [ typ. 0.35 ]
+ *            s = local standard deviation, which is maximized at
+ *                127.5 when half the samples are 0 and half are 255.
+ *      (2) See pixSauvolaBinarize() for other details.
+ *      (3) Important definitions and relations for computing averages:
+ *            v == pixel value
+ *            E(p) == expected value of p == average of p over some pixel set
+ *            S(v) == square of v == v * v
+ *            mv == E(v) == expected pixel value == mean value
+ *            ms == E(S(v)) == expected square of pixel values
+ *               == mean square value
+ *            var == variance == expected square of deviation from mean
+ *                == E(S(v - mv)) = E(S(v) - 2 * S(v * mv) + S(mv))
+ *                                = E(S(v)) - S(mv)
+ *                                = ms - mv * mv
+ *            s == standard deviation = sqrt(var)
+ *          So for evaluating the standard deviation in the Sauvola
+ *          threshold, we take
+ *            s = sqrt(ms - mv * mv)
+ */
+PIX *
+pixSauvolaGetThreshold(PIX       *pixm,
+                       PIX       *pixms,
+                       l_float32  factor,
+                       PIX      **ppixsd)
+{
+l_int32     i, j, w, h, tabsize, wplm, wplms, wplsd, wpld, usetab;
+l_int32     mv, ms, var, thresh;
+l_uint32   *datam, *datams, *datasd, *datad;
+l_uint32   *linem, *linems, *linesd, *lined;
+l_float32   sd;
+l_float32  *tab;  /* of 2^16 square roots */
+PIX        *pixsd, *pixd;
+
+    PROCNAME("pixSauvolaGetThreshold");
+
+    if (ppixsd) *ppixsd = NULL;
+    if (!pixm || pixGetDepth(pixm) != 8)
+        return (PIX *)ERROR_PTR("pixm undefined or not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixm))
+        return (PIX *)ERROR_PTR("pixm is colormapped", procName, NULL);
+    if (!pixms || pixGetDepth(pixms) != 32)
+        return (PIX *)ERROR_PTR("pixms undefined or not 32 bpp",
+                                procName, NULL);
+    if (factor < 0.0)
+        return (PIX *)ERROR_PTR("factor must be >= 0", procName, NULL);
+
+        /* Only make a table of 2^16 square roots if there
+         * are enough pixels to justify it. */
+    pixGetDimensions(pixm, &w, &h, NULL);
+    usetab = (w * h > 100000) ? 1 : 0;
+    if (usetab) {
+        tabsize = 1 << 16;
+        tab = (l_float32 *)LEPT_CALLOC(tabsize, sizeof(l_float32));
+        for (i = 0; i < tabsize; i++)
+            tab[i] = sqrtf((l_float32)i);
+    }
+
+    pixd = pixCreate(w, h, 8);
+    if (ppixsd) {
+        pixsd = pixCreate(w, h, 8);
+        *ppixsd = pixsd;
+    }
+    datam = pixGetData(pixm);
+    datams = pixGetData(pixms);
+    if (ppixsd) datasd = pixGetData(pixsd);
+    datad = pixGetData(pixd);
+    wplm = pixGetWpl(pixm);
+    wplms = pixGetWpl(pixms);
+    if (ppixsd) wplsd = pixGetWpl(pixsd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        linem = datam + i * wplm;
+        linems = datams + i * wplms;
+        if (ppixsd) linesd = datasd + i * wplsd;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            mv = GET_DATA_BYTE(linem, j);
+            ms = linems[j];
+            var = ms - mv * mv;
+            if (usetab)
+                sd = tab[var];
+            else
+                sd = sqrtf((l_float32)var);
+            if (ppixsd) SET_DATA_BYTE(linesd, j, (l_int32)sd);
+            thresh = (l_int32)(mv * (1.0 - factor * (1.0 - sd / 128.)));
+            SET_DATA_BYTE(lined, j, thresh);
+        }
+    }
+
+    if (usetab) LEPT_FREE(tab);
+    return pixd;
+}
+
+
+/*!
+ *  pixApplyLocalThreshold()
+ *
+ *      Input:  pixs (8 bpp grayscale; not colormapped)
+ *              pixth (8 bpp array of local thresholds)
+ *              redfactor ( ... )
+ *      Return: pixd (1 bpp, thresholded image), or null on error
+ */
+PIX *
+pixApplyLocalThreshold(PIX     *pixs,
+                       PIX     *pixth,
+                       l_int32  redfactor)
+{
+l_int32    i, j, w, h, wpls, wplt, wpld, vals, valt;
+l_uint32  *datas, *datat, *datad, *lines, *linet, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixApplyLocalThreshold");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs is colormapped", procName, NULL);
+    if (!pixth || pixGetDepth(pixth) != 8)
+        return (PIX *)ERROR_PTR("pixth undefined or not 8 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixCreate(w, h, 1);
+    datas = pixGetData(pixs);
+    datat = pixGetData(pixth);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wplt = pixGetWpl(pixth);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            vals = GET_DATA_BYTE(lines, j);
+            valt = GET_DATA_BYTE(linet, j);
+            if (vals < valt)
+                SET_DATA_BIT(lined, j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                  Thresholding using connected components             *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixThresholdByConnComp()
+ *
+ *      Input:  pixs (depth > 1, colormap OK)
+ *              pixm (<optional> 1 bpp mask giving region to ignore by setting
+ *                    pixels to white; use NULL if no mask)
+ *              start, end, incr (binarization threshold levels to test)
+ *              thresh48 (threshold on normalized difference between the
+ *                        numbers of 4 and 8 connected components)
+ *              threshdiff (threshold on normalized difference between the
+ *                          number of 4 cc at successive iterations)
+ *              &globthresh (<optional return> best global threshold; 0
+ *                           if no threshold is found)
+ *              &pixd (<optional return> image thresholded to binary, or
+ *                     null if no threshold is found)
+ *              debugflag (1 for plotted results)
+ *      Return: 0 if OK, 1 on error or if no threshold is found
+ *
+ *  Notes:
+ *      (1) This finds a global threshold based on connected components.
+ *          Although slow, it is reasonable to use it in a situation where
+ *          (a) the background in the image is relatively uniform, and
+ *          (b) the result will be fed to an OCR program that accepts 1 bpp
+ *              images and works best with easily segmented characters.
+ *          The reason for (b) is that this selects a threshold with a
+ *          minimum number of both broken characters and merged characters.
+ *      (2) If the pix has color, it is converted to gray using the
+ *          max component.
+ *      (3) Input 0 to use default values for any of these inputs:
+ *          @start, @end, @incr, @thresh48, @threshdiff.
+ *      (4) This approach can be understood as follows.  When the
+ *          binarization threshold is varied, the numbers of c.c. identify
+ *          four regimes:
+ *          (a) For low thresholds, text is broken into small pieces, and
+ *              the number of c.c. is large, with the 4 c.c. significantly
+ *              exceeding the 8 c.c.
+ *          (b) As the threshold rises toward the optimum value, the text
+ *              characters coalesce and there is very little difference
+ *              between the numbers of 4 and 8 c.c, which both go
+ *              through a minimum.
+ *          (c) Above this, the image background gets noisy because some
+ *              pixels are(thresholded to foreground, and the numbers
+ *              of c.c. quickly increase, with the 4 c.c. significantly
+ *              larger than the 8 c.c.
+ *          (d) At even higher thresholds, the image background noise
+ *              coalesces as it becomes mostly foreground, and the
+ *              number of c.c. drops quickly.
+ *      (5) If there is no global threshold that distinguishes foreground
+ *          text from background (e.g., weak text over a background that
+ *          has significant variation and/or bleedthrough), this returns 1,
+ *          which the caller should check.
+ */
+l_int32
+pixThresholdByConnComp(PIX       *pixs,
+                       PIX       *pixm,
+                       l_int32    start,
+                       l_int32    end,
+                       l_int32    incr,
+                       l_float32  thresh48,
+                       l_float32  threshdiff,
+                       l_int32   *pglobthresh,
+                       PIX      **ppixd,
+                       l_int32    debugflag)
+{
+l_int32    i, thresh, n, n4, n8, mincounts, found, globthresh;
+l_float32  count4, count8, firstcount4, prevcount4, diff48, diff4;
+GPLOT     *gplot;
+NUMA      *na4, *na8;
+PIX       *pix1, *pix2, *pix3;
+
+    PROCNAME("pixThresholdByConnComp");
+
+    if (pglobthresh) *pglobthresh = 0;
+    if (ppixd) *ppixd = NULL;
+    if (!pixs || pixGetDepth(pixs) == 1)
+        return ERROR_INT("pixs undefined or 1 bpp", procName, 1);
+    if (pixm && pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm must be 1 bpp", procName, 1);
+
+        /* Assign default values if requested */
+    if (start <= 0) start = 80;
+    if (end <= 0) end = 200;
+    if (incr <= 0) incr = 10;
+    if (thresh48 <= 0.0) thresh48 = 0.01;
+    if (threshdiff <= 0.0) threshdiff = 0.01;
+    if (start > end)
+        return ERROR_INT("invalid start,end", procName, 1);
+
+        /* Make 8 bpp, using the max component if color. */
+    if (pixGetColormap(pixs))
+        pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    else
+        pix1 = pixClone(pixs);
+    if (pixGetDepth(pix1) == 32)
+        pix2 = pixConvertRGBToGrayMinMax(pix1, L_CHOOSE_MAX);
+    else
+        pix2 = pixConvertTo8(pix1, 0);
+    pixDestroy(&pix1);
+
+        /* Mask out any non-text regions.  Do this in-place, because pix2
+         * can never be the same pix as pixs. */
+    if (pixm)
+        pixSetMasked(pix2, pixm, 255);
+
+        /* Make sure there are enough components to get a valid signal */
+    pix3 = pixConvertTo1(pix2, start);
+    pixCountConnComp(pix3, 4, &n4);
+    pixDestroy(&pix3);
+    mincounts = 500;
+    if (n4 < mincounts) {
+        L_INFO("Insufficient component count: %d\n", procName, n4);
+        pixDestroy(&pix2);
+        return 1;
+    }
+
+        /* Compute the c.c. data */
+    na4 = numaCreate(0);
+    na8 = numaCreate(0);
+    numaSetParameters(na4, start, incr);
+    numaSetParameters(na8, start, incr);
+    for (thresh = start, i = 0; thresh <= end; thresh += incr, i++) {
+        pix3 = pixConvertTo1(pix2, thresh);
+        pixCountConnComp(pix3, 4, &n4);
+        pixCountConnComp(pix3, 8, &n8);
+        numaAddNumber(na4, n4);
+        numaAddNumber(na8, n8);
+        pixDestroy(&pix3);
+    }
+    if (debugflag) {
+        gplot = gplotCreate("/tmp/threshroot", GPLOT_PNG,
+                            "number of cc vs. threshold",
+                            "threshold", "number of cc");
+        gplotAddPlot(gplot, NULL, na4, GPLOT_LINES, "plot 4cc");
+        gplotAddPlot(gplot, NULL, na8, GPLOT_LINES, "plot 8cc");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+    }
+
+    n = numaGetCount(na4);
+    found = FALSE;
+    for (i = 0; i < n; i++) {
+        if (i == 0) {
+            numaGetFValue(na4, i, &firstcount4);
+            prevcount4 = firstcount4;
+        } else {
+            numaGetFValue(na4, i, &count4);
+            numaGetFValue(na8, i, &count8);
+            diff48 = (count4 - count8) / firstcount4;
+            diff4 = L_ABS(prevcount4 - count4) / firstcount4;
+            if (debugflag) {
+                fprintf(stderr, "diff48 = %7.3f, diff4 = %7.3f\n",
+                        diff48, diff4);
+            }
+            if (diff48 < thresh48 && diff4 < threshdiff) {
+                found = TRUE;
+                break;
+            }
+            prevcount4 = count4;
+        }
+    }
+    numaDestroy(&na4);
+    numaDestroy(&na8);
+
+    if (found) {
+        globthresh = start + i * incr;
+        if (pglobthresh) *pglobthresh = globthresh;
+        if (ppixd) {
+            *ppixd = pixConvertTo1(pix2, globthresh);
+            pixCopyResolution(*ppixd, pixs);
+        }
+        if (debugflag) fprintf(stderr, "global threshold = %d\n", globthresh);
+        pixDestroy(&pix2);
+        return 0;
+    }
+
+    if (debugflag) fprintf(stderr, "no global threshold found\n");
+    pixDestroy(&pix2);
+    return 1;
+}
+
diff --git a/src/binexpand.c b/src/binexpand.c
new file mode 100644 (file)
index 0000000..be23477
--- /dev/null
@@ -0,0 +1,315 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  binexpand.c
+ *
+ *      Replicated expansion (integer scaling)
+ *         PIX     *pixExpandBinaryReplicate()
+ *
+ *      Special case: power of 2 replicated expansion
+ *         PIX     *pixExpandBinaryPower2()
+ *
+ *      Expansion tables for power of 2 expansion
+ *         static l_uint16    *makeExpandTab2x()
+ *         static l_uint32    *makeExpandTab4x()
+ *         static l_uint32    *makeExpandTab8x()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+    /* Static table functions and tables */
+static l_uint16 * makeExpandTab2x(void);
+static l_uint32 * makeExpandTab4x(void);
+static l_uint32 * makeExpandTab8x(void);
+static  l_uint32 expandtab16[] = {
+              0x00000000, 0x0000ffff, 0xffff0000, 0xffffffff};
+
+
+/*------------------------------------------------------------------*
+ *              Replicated expansion (integer scaling)              *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixExpandBinaryReplicate()
+ *
+ *      Input:  pixs (1 bpp)
+ *              factor (integer scale factor for replicative expansion)
+ *      Return: pixd (scaled up), or null on error
+ */
+PIX *
+pixExpandBinaryReplicate(PIX     *pixs,
+                         l_int32  factor)
+{
+l_int32    w, h, d, wd, hd, wpls, wpld, i, j, k, start;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixExpandBinaryReplicate");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1)
+        return (PIX *)ERROR_PTR("pixs not binary", procName, NULL);
+    if (factor <= 0)
+        return (PIX *)ERROR_PTR("factor <= 0; invalid", procName, NULL);
+
+    if (factor == 1)
+        return pixCopy(NULL, pixs);
+    if (factor == 2 || factor == 4 || factor == 8 || factor == 16)
+        return pixExpandBinaryPower2(pixs, factor);
+
+    wpls = pixGetWpl(pixs);
+    datas = pixGetData(pixs);
+    wd = factor * w;
+    hd = factor * h;
+    if ((pixd = pixCreate(wd, hd, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, (l_float32)factor, (l_float32)factor);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + factor * i * wpld;
+        for (j = 0; j < w; j++) {
+            if (GET_DATA_BIT(lines, j)) {
+                start = factor * j;
+                for (k = 0; k < factor; k++)
+                    SET_DATA_BIT(lined, start + k);
+            }
+        }
+        for (k = 1; k < factor; k++)
+            memcpy(lined + k * wpld, lined, 4 * wpld);
+    }
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                      Power of 2 expansion                        *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixExpandBinaryPower2()
+ *
+ *      Input:  pixs (1 bpp)
+ *              factor (expansion factor: 1, 2, 4, 8, 16)
+ *      Return: pixd (expanded 1 bpp by replication), or null on error
+ */
+PIX *
+pixExpandBinaryPower2(PIX     *pixs,
+                      l_int32  factor)
+{
+l_uint8    sval;
+l_uint16  *tab2;
+l_int32    i, j, k, w, h, d, wd, hd, wpls, wpld, sdibits, sqbits, sbytes;
+l_uint32  *datas, *datad, *lines, *lined, *tab4, *tab8;
+PIX       *pixd;
+
+    PROCNAME("pixExpandBinaryPower2");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1)
+        return (PIX *)ERROR_PTR("pixs not binary", procName, NULL);
+    if (factor == 1)
+        return pixCopy(NULL, pixs);
+    if (factor != 2 && factor != 4 && factor != 8 && factor != 16)
+        return (PIX *)ERROR_PTR("factor must be in {2,4,8,16}", procName, NULL);
+
+    wpls = pixGetWpl(pixs);
+    datas = pixGetData(pixs);
+    wd = factor * w;
+    hd = factor * h;
+    if ((pixd = pixCreate(wd, hd, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, (l_float32)factor, (l_float32)factor);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    if (factor == 2) {
+        if ((tab2 = makeExpandTab2x()) == NULL)
+            return (PIX *)ERROR_PTR("tab2 not made", procName, NULL);
+        sbytes = (w + 7) / 8;
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + 2 * i * wpld;
+            for (j = 0; j < sbytes; j++) {
+                sval = GET_DATA_BYTE(lines, j);
+                SET_DATA_TWO_BYTES(lined, j, tab2[sval]);
+            }
+            memcpy((char *)(lined + wpld), (char *)lined, 4 * wpld);
+        }
+        LEPT_FREE(tab2);
+    } else if (factor == 4) {
+        if ((tab4 = makeExpandTab4x()) == NULL)
+            return (PIX *)ERROR_PTR("tab4 not made", procName, NULL);
+        sbytes = (w + 7) / 8;
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + 4 * i * wpld;
+            for (j = 0; j < sbytes; j++) {
+                sval = GET_DATA_BYTE(lines, j);
+                lined[j] = tab4[sval];
+            }
+            for (k = 1; k < 4; k++)
+                memcpy((char *)(lined + k * wpld), (char *)lined, 4 * wpld);
+        }
+        LEPT_FREE(tab4);
+    } else if (factor == 8) {
+        if ((tab8 = makeExpandTab8x()) == NULL)
+            return (PIX *)ERROR_PTR("tab8 not made", procName, NULL);
+        sqbits = (w + 3) / 4;
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + 8 * i * wpld;
+            for (j = 0; j < sqbits; j++) {
+                sval = GET_DATA_QBIT(lines, j);
+                if (sval > 15)
+                    L_WARNING("sval = %d; should be < 16\n", procName, sval);
+                lined[j] = tab8[sval];
+            }
+            for (k = 1; k < 8; k++)
+                memcpy((char *)(lined + k * wpld), (char *)lined, 4 * wpld);
+        }
+        LEPT_FREE(tab8);
+    } else {  /* factor == 16 */
+        sdibits = (w + 1) / 2;
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + 16 * i * wpld;
+            for (j = 0; j < sdibits; j++) {
+                sval = GET_DATA_DIBIT(lines, j);
+                lined[j] = expandtab16[sval];
+            }
+            for (k = 1; k < 16; k++)
+                memcpy((char *)(lined + k * wpld), (char *)lined, 4 * wpld);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------------*
+ *             Expansion tables for 2x, 4x and 8x expansion          *
+ *-------------------------------------------------------------------*/
+static l_uint16 *
+makeExpandTab2x(void)
+{
+l_uint16  *tab;
+l_int32    i;
+
+    PROCNAME("makeExpandTab2x");
+
+    if ((tab = (l_uint16 *) LEPT_CALLOC(256, sizeof(l_uint16))) == NULL)
+        return (l_uint16 *)ERROR_PTR("tab not made", procName, NULL);
+
+    for (i = 0; i < 256; i++) {
+        if (i & 0x01)
+            tab[i] = 0x3;
+        if (i & 0x02)
+            tab[i] |= 0xc;
+        if (i & 0x04)
+            tab[i] |= 0x30;
+        if (i & 0x08)
+            tab[i] |= 0xc0;
+        if (i & 0x10)
+            tab[i] |= 0x300;
+        if (i & 0x20)
+            tab[i] |= 0xc00;
+        if (i & 0x40)
+            tab[i] |= 0x3000;
+        if (i & 0x80)
+            tab[i] |= 0xc000;
+    }
+
+    return tab;
+}
+
+
+static l_uint32 *
+makeExpandTab4x(void)
+{
+l_uint32  *tab;
+l_int32    i;
+
+    PROCNAME("makeExpandTab4x");
+
+    if ((tab = (l_uint32 *) LEPT_CALLOC(256, sizeof(l_uint32))) == NULL)
+        return (l_uint32 *)ERROR_PTR("tab not made", procName, NULL);
+
+    for (i = 0; i < 256; i++) {
+        if (i & 0x01)
+            tab[i] = 0xf;
+        if (i & 0x02)
+            tab[i] |= 0xf0;
+        if (i & 0x04)
+            tab[i] |= 0xf00;
+        if (i & 0x08)
+            tab[i] |= 0xf000;
+        if (i & 0x10)
+            tab[i] |= 0xf0000;
+        if (i & 0x20)
+            tab[i] |= 0xf00000;
+        if (i & 0x40)
+            tab[i] |= 0xf000000;
+        if (i & 0x80)
+            tab[i] |= 0xf0000000;
+    }
+
+    return tab;
+}
+
+
+static l_uint32 *
+makeExpandTab8x(void)
+{
+l_uint32  *tab;
+l_int32    i;
+
+    PROCNAME("makeExpandTab8x");
+
+    if ((tab = (l_uint32 *) LEPT_CALLOC(16, sizeof(l_uint32))) == NULL)
+        return (l_uint32 *)ERROR_PTR("tab not made", procName, NULL);
+
+    for (i = 0; i < 16; i++) {
+        if (i & 0x01)
+            tab[i] = 0xff;
+        if (i & 0x02)
+            tab[i] |= 0xff00;
+        if (i & 0x04)
+            tab[i] |= 0xff0000;
+        if (i & 0x08)
+            tab[i] |= 0xff000000;
+    }
+
+    return tab;
+}
diff --git a/src/binreduce.c b/src/binreduce.c
new file mode 100644 (file)
index 0000000..86c7d59
--- /dev/null
@@ -0,0 +1,399 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  binreduce.c
+ *
+ *      Subsampled 2x reduction
+ *           PIX      *pixReduceBinary2()
+ *
+ *      Rank filtered 2x reductions
+ *           PIX      *pixReduceRankBinaryCascade()
+ *           PIX      *pixReduceRankBinary2()
+ *
+ *      Permutation table for 2x rank binary reduction
+ *           l_uint8  *makeSubsampleTab2x(void)
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+
+/*------------------------------------------------------------------*
+ *                       Subsampled reduction                       *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixReduceBinary2()
+ *
+ *      Input:  pixs
+ *              tab (<optional>; if null, a table is made here
+ *                   and destroyed before exit)
+ *      Return: pixd (2x subsampled), or null on error
+ *
+ *  Notes:
+ *      (1) After folding, the data is in bytes 0 and 2 of the word,
+ *          and the bits in each byte are in the following order
+ *          (with 0 being the leftmost originating pair and 7 being
+ *          the rightmost originating pair):
+ *               0 4 1 5 2 6 3 7
+ *          These need to be permuted to
+ *               0 1 2 3 4 5 6 7
+ *          which is done with an 8-bit table generated by makeSubsampleTab2x().
+ */
+PIX *
+pixReduceBinary2(PIX      *pixs,
+                 l_uint8  *intab)
+{
+l_uint8    byte0, byte1;
+l_uint8   *tab;
+l_uint16   shortd;
+l_int32    i, id, j, ws, hs, wpls, wpld, wplsi;
+l_uint32   word;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixReduceBinary2");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not binary", procName, NULL);
+
+    if (intab) {  /* use input table */
+        tab = intab;
+    } else {
+        if ((tab = makeSubsampleTab2x()) == NULL)
+            return (PIX *)ERROR_PTR("tab not made", procName, NULL);
+    }
+
+    ws = pixGetWidth(pixs);
+    hs = pixGetHeight(pixs);
+    if (hs <= 1)
+        return (PIX *)ERROR_PTR("hs must be at least 2", procName, NULL);
+    wpls = pixGetWpl(pixs);
+    datas = pixGetData(pixs);
+
+    if ((pixd = pixCreate(ws / 2, hs / 2, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 0.5, 0.5);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+
+        /* e.g., if ws = 65: wd = 32, wpls = 3, wpld = 1 --> trouble */
+    wplsi = L_MIN(wpls, 2 * wpld);  /* iterate over this number of words */
+
+    for (i = 0, id = 0; i < hs - 1; i += 2, id++) {
+        lines = datas + i * wpls;
+        lined = datad + id * wpld;
+        for (j = 0; j < wplsi; j++) {
+            word = *(lines + j);
+            word = word & 0xaaaaaaaa;  /* mask */
+            word = word | (word << 7);  /* fold; data in bytes 0 & 2 */
+            byte0 = word >> 24;
+            byte1 = (word >> 8) & 0xff;
+            shortd = (tab[byte0] << 8) | tab[byte1];
+            SET_DATA_TWO_BYTES(lined, j, shortd);
+        }
+    }
+
+    if (intab == NULL)
+        LEPT_FREE(tab);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                   Rank filtered binary reductions                *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixReduceRankBinaryCascade()
+ *
+ *      Input:  pixs (1 bpp)
+ *              level1, ... level 4 (thresholds, in the set {0, 1, 2, 3, 4})
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This performs up to four cascaded 2x rank reductions.
+ *      (2) Use level = 0 to truncate the cascade.
+ */
+PIX *
+pixReduceRankBinaryCascade(PIX     *pixs,
+                           l_int32  level1,
+                           l_int32  level2,
+                           l_int32  level3,
+                           l_int32  level4)
+{
+PIX      *pix1, *pix2, *pix3, *pix4;
+l_uint8  *tab;
+
+    PROCNAME("pixReduceRankBinaryCascade");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be binary", procName, NULL);
+    if (level1 > 4 || level2 > 4 || level3 > 4 || level4 > 4)
+        return (PIX *)ERROR_PTR("levels must not exceed 4", procName, NULL);
+
+    if (level1 <= 0) {
+        L_WARNING("no reduction because level1 not > 0\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+    if ((tab = makeSubsampleTab2x()) == NULL)
+        return (PIX *)ERROR_PTR("tab not made", procName, NULL);
+
+    pix1 = pixReduceRankBinary2(pixs, level1, tab);
+    if (level2 <= 0) {
+        LEPT_FREE(tab);
+        return pix1;
+    }
+
+    pix2 = pixReduceRankBinary2(pix1, level2, tab);
+    pixDestroy(&pix1);
+    if (level3 <= 0) {
+        LEPT_FREE(tab);
+        return pix2;
+    }
+
+    pix3 = pixReduceRankBinary2(pix2, level3, tab);
+    pixDestroy(&pix2);
+    if (level4 <= 0) {
+        LEPT_FREE(tab);
+        return pix3;
+    }
+
+    pix4 = pixReduceRankBinary2(pix3, level4, tab);
+    pixDestroy(&pix3);
+    LEPT_FREE(tab);
+    return pix4;
+}
+
+
+/*!
+ *  pixReduceRankBinary2()
+ *
+ *      Input:  pixs (1 bpp)
+ *              level (rank threshold: 1, 2, 3, 4)
+ *              intab (<optional>; if null, a table is made here
+ *                     and destroyed before exit)
+ *      Return: pixd (1 bpp, 2x rank threshold reduced), or null on error
+ *
+ *  Notes:
+ *      (1) pixd is downscaled by 2x from pixs.
+ *      (2) The rank threshold specifies the minimum number of ON
+ *          pixels in each 2x2 region of pixs that are required to
+ *          set the corresponding pixel ON in pixd.
+ *      (3) Rank filtering is done to the UL corner of each 2x2 pixel block,
+ *          using only logical operations.  Then these pixels are chosen
+ *          in the 2x subsampling process, subsampled, as described
+ *          above in pixReduceBinary2().
+ */
+PIX *
+pixReduceRankBinary2(PIX      *pixs,
+                     l_int32   level,
+                     l_uint8  *intab)
+{
+l_uint8    byte0, byte1;
+l_uint8   *tab;
+l_uint16   shortd;
+l_int32    i, id, j, ws, hs, wpls, wpld, wplsi;
+l_uint32   word1, word2, word3, word4;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixReduceRankBinary2");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not binary", procName, NULL);
+    if (level < 1 || level > 4)
+        return (PIX *)ERROR_PTR("level must be in set {1,2,3,4}",
+            procName, NULL);
+
+    if (intab) {  /* use input table */
+        tab = intab;
+    } else {
+        if ((tab = makeSubsampleTab2x()) == NULL)
+            return (PIX *)ERROR_PTR("tab not made", procName, NULL);
+    }
+
+    ws = pixGetWidth(pixs);
+    hs = pixGetHeight(pixs);
+    if (hs <= 1)
+        return (PIX *)ERROR_PTR("hs must be at least 2", procName, NULL);
+    wpls = pixGetWpl(pixs);
+    datas = pixGetData(pixs);
+
+    if ((pixd = pixCreate(ws / 2, hs / 2, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 0.5, 0.5);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+
+        /* e.g., if ws = 65: wd = 32, wpls = 3, wpld = 1 --> trouble */
+    wplsi = L_MIN(wpls, 2 * wpld);  /* iterate over this number of words */
+
+    switch (level)
+    {
+
+    case 1:
+        for (i = 0, id = 0; i < hs - 1; i += 2, id++) {
+            lines = datas + i * wpls;
+            lined = datad + id * wpld;
+            for (j = 0; j < wplsi; j++) {
+                word1 = *(lines + j);
+                word2 = *(lines + wpls + j);
+
+                    /* OR/OR */
+                word2 = word1 | word2;
+                word2 = word2 | (word2 << 1);
+
+                word2 = word2 & 0xaaaaaaaa;  /* mask */
+                word1 = word2 | (word2 << 7);  /* fold; data in bytes 0 & 2 */
+                byte0 = word1 >> 24;
+                byte1 = (word1 >> 8) & 0xff;
+                shortd = (tab[byte0] << 8) | tab[byte1];
+                SET_DATA_TWO_BYTES(lined, j, shortd);
+            }
+        }
+        break;
+
+    case 2:
+        for (i = 0, id = 0; i < hs - 1; i += 2, id++) {
+            lines = datas + i * wpls;
+            lined = datad + id * wpld;
+            for (j = 0; j < wplsi; j++) {
+                word1 = *(lines + j);
+                word2 = *(lines + wpls + j);
+
+                    /* (AND/OR) OR (OR/AND) */
+                word3 = word1 & word2;
+                word3 = word3 | (word3 << 1);
+                word4 = word1 | word2;
+                word4 = word4 & (word4 << 1);
+                word2 = word3 | word4;
+
+                word2 = word2 & 0xaaaaaaaa;  /* mask */
+                word1 = word2 | (word2 << 7);  /* fold; data in bytes 0 & 2 */
+                byte0 = word1 >> 24;
+                byte1 = (word1 >> 8) & 0xff;
+                shortd = (tab[byte0] << 8) | tab[byte1];
+                SET_DATA_TWO_BYTES(lined, j, shortd);
+            }
+        }
+        break;
+
+    case 3:
+        for (i = 0, id = 0; i < hs - 1; i += 2, id++) {
+            lines = datas + i * wpls;
+            lined = datad + id * wpld;
+            for (j = 0; j < wplsi; j++) {
+                word1 = *(lines + j);
+                word2 = *(lines + wpls + j);
+
+                    /* (AND/OR) AND (OR/AND) */
+                word3 = word1 & word2;
+                word3 = word3 | (word3 << 1);
+                word4 = word1 | word2;
+                word4 = word4 & (word4 << 1);
+                word2 = word3 & word4;
+
+                word2 = word2 & 0xaaaaaaaa;  /* mask */
+                word1 = word2 | (word2 << 7);  /* fold; data in bytes 0 & 2 */
+                byte0 = word1 >> 24;
+                byte1 = (word1 >> 8) & 0xff;
+                shortd = (tab[byte0] << 8) | tab[byte1];
+                SET_DATA_TWO_BYTES(lined, j, shortd);
+            }
+        }
+        break;
+
+    case 4:
+        for (i = 0, id = 0; i < hs - 1; i += 2, id++) {
+            lines = datas + i * wpls;
+            lined = datad + id * wpld;
+            for (j = 0; j < wplsi; j++) {
+                word1 = *(lines + j);
+                word2 = *(lines + wpls + j);
+
+                    /* AND/AND */
+                word2 = word1 & word2;
+                word2 = word2 & (word2 << 1);
+
+                word2 = word2 & 0xaaaaaaaa;  /* mask */
+                word1 = word2 | (word2 << 7);  /* fold; data in bytes 0 & 2 */
+                byte0 = word1 >> 24;
+                byte1 = (word1 >> 8) & 0xff;
+                shortd = (tab[byte0] << 8) | tab[byte1];
+                SET_DATA_TWO_BYTES(lined, j, shortd);
+            }
+        }
+        break;
+    }
+
+    if (!intab)
+        LEPT_FREE(tab);
+    return pixd;
+}
+
+
+/*!
+ *  makeSubsampleTab2x()
+ *
+ *  This table permutes the bits in a byte, from
+ *      0 4 1 5 2 6 3 7
+ *  to
+ *      0 1 2 3 4 5 6 7
+ */
+l_uint8 *
+makeSubsampleTab2x(void)
+{
+l_uint8  *tab;
+l_int32   i;
+
+    PROCNAME("makeSubsampleTab2x");
+
+    if ((tab = (l_uint8 *) LEPT_CALLOC(256, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("tab not made", procName, NULL);
+
+    for (i = 0; i < 256; i++)
+        tab[i] = ((i & 0x01)     ) |    /* 7 */
+                 ((i & 0x04) >> 1) |    /* 6 */
+                 ((i & 0x10) >> 2) |    /* 5 */
+                 ((i & 0x40) >> 3) |    /* 4 */
+                 ((i & 0x02) << 3) |    /* 3 */
+                 ((i & 0x08) << 2) |    /* 2 */
+                 ((i & 0x20) << 1) |    /* 1 */
+                 ((i & 0x80)     );     /* 0 */
+
+    return tab;
+}
diff --git a/src/blend.c b/src/blend.c
new file mode 100644 (file)
index 0000000..c0dea56
--- /dev/null
@@ -0,0 +1,2128 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  blend.c
+ *
+ *      Blending two images that are not colormapped
+ *           PIX             *pixBlend()
+ *           PIX             *pixBlendMask()
+ *           PIX             *pixBlendGray()
+ *           PIX             *pixBlendGrayInverse()
+ *           PIX             *pixBlendColor()
+ *           PIX             *pixBlendColorByChannel()
+ *           PIX             *pixBlendGrayAdapt()
+ *           static l_int32   blendComponents()
+ *           PIX             *pixFadeWithGray()
+ *           PIX             *pixBlendHardLight()
+ *           static l_int32   blendHardLightComponents()
+ *
+ *      Blending two colormapped images
+ *           l_int32          pixBlendCmap()
+ *
+ *      Blending two images using a third (alpha mask)
+ *           PIX             *pixBlendWithGrayMask()
+ *
+ *      Blending background to a specific color
+ *           PIX             *pixBlendBackgroundToColor()
+ *
+ *      Multiplying by a specific color
+ *           PIX             *pixMultiplyByColor()
+ *
+ *      Rendering with alpha blending over a uniform background
+ *           PIX             *pixAlphaBlendUniform()
+ *
+ *      Adding an alpha layer for blending
+ *           PIX             *pixAddAlphaToBlend()
+ *
+ *      Setting a transparent alpha component over a white background
+ *           PIX             *pixSetAlphaOverWhite()
+ *
+ *  In blending operations a new pix is produced where typically
+ *  a subset of pixels in src1 are changed by the set of pixels
+ *  in src2, when src2 is located in a given position relative
+ *  to src1.  This is similar to rasterop, except that the
+ *  blending operations we allow are more complex, and typically
+ *  result in dest pixels that are a linear combination of two
+ *  pixels, such as src1 and its inverse.  I find it convenient
+ *  to think of src2 as the "blender" (the one that takes the action)
+ *  and src1 as the "blendee" (the one that changes).
+ *
+ *  Blending works best when src1 is 8 or 32 bpp.  We also allow
+ *  src1 to be colormapped, but the colormap is removed before blending,
+ *  so if src1 is colormapped, we can't allow in-place blending.
+ *
+ *  Because src2 is typically smaller than src1, we can implement by
+ *  clipping src2 to src1 and then transforming some of the dest
+ *  pixels that are under the support of src2.  In practice, we
+ *  do the clipping in the inner pixel loop.  For grayscale and
+ *  color src2, we also allow a simple form of transparency, where
+ *  pixels of a particular value in src2 are transparent; for those pixels,
+ *  no blending is done.
+ *
+ *  The blending functions are categorized by the depth of src2,
+ *  the blender, and not that of src1, the blendee.
+ *
+ *   - If src2 is 1 bpp, we can do one of three things:
+ *     (1) L_BLEND_WITH_INVERSE: Blend a given fraction of src1 with its
+ *         inverse color for those pixels in src2 that are fg (ON),
+ *         and leave the dest pixels unchanged for pixels in src2 that
+ *         are bg (OFF).
+ *     (2) L_BLEND_TO_WHITE: Fade the src1 pixels toward white by a
+ *         given fraction for those pixels in src2 that are fg (ON),
+ *         and leave the dest pixels unchanged for pixels in src2 that
+ *         are bg (OFF).
+ *     (3) L_BLEND_TO_BLACK: Fade the src1 pixels toward black by a
+ *         given fraction for those pixels in src2 that are fg (ON),
+ *         and leave the dest pixels unchanged for pixels in src2 that
+ *         are bg (OFF).
+ *     The blending function is pixBlendMask().
+ *
+ *   - If src2 is 8 bpp grayscale, we can do one of two things
+ *     (but see pixFadeWithGray() below):
+ *     (1) L_BLEND_GRAY: If src1 is 8 bpp, mix the two values, using
+ *         a fraction of src2 and (1 - fraction) of src1.
+ *         If src1 is 32 bpp (rgb), mix the fraction of src2 with
+ *         each of the color components in src1.
+ *     (2) L_BLEND_GRAY_WITH_INVERSE: Use the grayscale value in src2
+ *         to determine how much of the inverse of a src1 pixel is
+ *         to be combined with the pixel value.  The input fraction
+ *         further acts to scale the change in the src1 pixel.
+ *     The blending function is pixBlendGray().
+ *
+ *   - If src2 is color, we blend a given fraction of src2 with
+ *     src1.  If src1 is 8 bpp, the resulting image is 32 bpp.
+ *     The blending function is pixBlendColor().
+ *
+ *   - For all three blending functions -- pixBlendMask(), pixBlendGray()
+ *     and pixBlendColor() -- you can apply the blender to the blendee
+ *     either in-place or generating a new pix.  For the in-place
+ *     operation, this requires that the depth of the resulting pix
+ *     must equal that of the input pixs1.
+ *
+ *   - We remove colormaps from src1 and src2 before blending.
+ *     Any quantization would have to be done after blending.
+ *
+ *  We include another function, pixFadeWithGray(), that blends
+ *  a gray or color src1 with a gray src2.  It does one of these things:
+ *     (1) L_BLEND_TO_WHITE: Fade the src1 pixels toward white by
+ *         a number times the value in src2.
+ *     (2) L_BLEND_TO_BLACK: Fade the src1 pixels toward black by
+ *         a number times the value in src2.
+ *
+ *  Also included is a generalization of the so-called "hard light"
+ *  blending: pixBlendHardLight().  We generalize by allowing a fraction < 1.0
+ *  of the blender to be admixed with the blendee.  The standard function
+ *  does full mixing.
+ */
+
+
+#include "allheaders.h"
+
+static l_int32 blendComponents(l_int32 a, l_int32 b, l_float32 fract);
+static l_int32 blendHardLightComponents(l_int32 a, l_int32 b, l_float32 fract);
+
+
+/*-------------------------------------------------------------*
+ *         Blending two images that are not colormapped        *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixBlend()
+ *
+ *      Input:  pixs1 (blendee)
+ *              pixs2 (blender; typ. smaller)
+ *              x,y  (origin (UL corner) of pixs2 relative to
+ *                    the origin of pixs1; can be < 0)
+ *              fract (blending fraction)
+ *      Return: pixd (blended image), or null on error
+ *
+ *  Notes:
+ *      (1) This is a simple top-level interface.  For more flexibility,
+ *          call directly into pixBlendMask(), etc.
+ */
+PIX *
+pixBlend(PIX       *pixs1,
+         PIX       *pixs2,
+         l_int32    x,
+         l_int32    y,
+         l_float32  fract)
+{
+l_int32    w1, h1, d1, d2;
+BOX       *box;
+PIX       *pixc, *pixt, *pixd;
+
+    PROCNAME("pixBlend");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, NULL);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, NULL);
+
+        /* check relative depths */
+    d1 = pixGetDepth(pixs1);
+    d2 = pixGetDepth(pixs2);
+    if (d1 == 1 && d2 > 1)
+        return (PIX *)ERROR_PTR("mixing gray or color with 1 bpp",
+                                procName, NULL);
+
+        /* remove colormap from pixs2 if necessary */
+    pixt = pixRemoveColormap(pixs2, REMOVE_CMAP_BASED_ON_SRC);
+    d2 = pixGetDepth(pixt);
+
+        /* Check if pixs2 is clipped by its position with respect
+         * to pixs1; if so, clip it and redefine x and y if necessary.
+         * This actually isn't necessary, as the specific blending
+         * functions do the clipping directly in the pixel loop
+         * over pixs2, but it's included here to show how it can
+         * easily be done on pixs2 first. */
+    pixGetDimensions(pixs1, &w1, &h1, NULL);
+    box = boxCreate(-x, -y, w1, h1);  /* box of pixs1 relative to pixs2 */
+    pixc = pixClipRectangle(pixt, box, NULL);
+    boxDestroy(&box);
+    if (!pixc) {
+        L_WARNING("box doesn't overlap pix\n", procName);
+        return NULL;
+    }
+    x = L_MAX(0, x);
+    y = L_MAX(0, y);
+
+    if (d2 == 1) {
+        pixd = pixBlendMask(NULL, pixs1, pixc, x, y, fract,
+                            L_BLEND_WITH_INVERSE);
+    } else if (d2 == 8) {
+        pixd = pixBlendGray(NULL, pixs1, pixc, x, y, fract,
+                            L_BLEND_GRAY, 0, 0);
+    } else {  /* d2 == 32 */
+        pixd = pixBlendColor(NULL, pixs1, pixc, x, y, fract, 0, 0);
+    }
+
+    pixDestroy(&pixc);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixBlendMask()
+ *
+ *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
+ *              pixs1 (blendee, depth > 1)
+ *              pixs2 (blender, 1 bpp; typ. smaller in size than pixs1)
+ *              x,y  (origin (UL corner) of pixs2 relative to
+ *                    the origin of pixs1; can be < 0)
+ *              fract (blending fraction)
+ *              type (L_BLEND_WITH_INVERSE, L_BLEND_TO_WHITE, L_BLEND_TO_BLACK)
+ *      Return: pixd if OK; null on error
+ *
+ *  Notes:
+ *      (1) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
+ *      (2) If pixs1 has a colormap, it is removed.
+ *      (3) For inplace operation (pixs1 not cmapped), call it this way:
+ *            pixBlendMask(pixs1, pixs1, pixs2, ...)
+ *      (4) For generating a new pixd:
+ *            pixd = pixBlendMask(NULL, pixs1, pixs2, ...)
+ *      (5) Only call in-place if pixs1 does not have a colormap.
+ *      (6) Invalid @fract defaults to 0.5 with a warning.
+ *          Invalid @type defaults to L_BLEND_WITH_INVERSE with a warning.
+ */
+PIX *
+pixBlendMask(PIX       *pixd,
+             PIX       *pixs1,
+             PIX       *pixs2,
+             l_int32    x,
+             l_int32    y,
+             l_float32  fract,
+             l_int32    type)
+{
+l_int32    i, j, d, wc, hc, w, h, wplc;
+l_int32    val, rval, gval, bval;
+l_uint32   pixval;
+l_uint32  *linec, *datac;
+PIX       *pixc, *pix1, *pix2;
+
+    PROCNAME("pixBlendMask");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, NULL);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, NULL);
+    if (pixGetDepth(pixs1) == 1)
+        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, NULL);
+    if (pixGetDepth(pixs2) != 1)
+        return (PIX *)ERROR_PTR("pixs2 not 1 bpp", procName, NULL);
+    if (pixd == pixs1 && pixGetColormap(pixs1))
+        return (PIX *)ERROR_PTR("inplace; pixs1 has colormap", procName, NULL);
+    if (pixd && (pixd != pixs1))
+        return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, NULL);
+    if (fract < 0.0 || fract > 1.0) {
+        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", procName);
+        fract = 0.5;
+    }
+    if (type != L_BLEND_WITH_INVERSE && type != L_BLEND_TO_WHITE &&
+        type != L_BLEND_TO_BLACK) {
+        L_WARNING("invalid blend type; setting to L_BLEND_WITH_INVERSE\n",
+                  procName);
+        type = L_BLEND_WITH_INVERSE;
+    }
+
+        /* If pixd != NULL, we know that it is equal to pixs1 and
+         * that pixs1 does not have a colormap, so that an in-place operation
+         * can be done.  Otherwise, remove colormap from pixs1 if
+         * it exists and unpack to at least 8 bpp if necessary,
+         * to do the blending on a new pix. */
+    if (!pixd) {
+        pix1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC);
+        if (pixGetDepth(pix1) < 8)
+            pix2 = pixConvertTo8(pix1, FALSE);
+        else
+            pix2 = pixClone(pix1);
+        pixd = pixCopy(NULL, pix2);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    pixGetDimensions(pixd, &w, &h, &d);  /* d must be either 8 or 32 bpp */
+    pixc = pixClone(pixs2);
+    wc = pixGetWidth(pixc);
+    hc = pixGetHeight(pixc);
+    datac = pixGetData(pixc);
+    wplc = pixGetWpl(pixc);
+
+        /* Check limits for src1, in case clipping was not done. */
+    switch (type)
+    {
+    case L_BLEND_WITH_INVERSE:
+            /*
+             * The basic logic for this blending is:
+             *      p -->  (1 - f) * p + f * (1 - p)
+             * where p is a normalized value: p = pixval / 255.
+             * Thus,
+             *      p -->  p + f * (1 - 2 * p)
+             */
+        for (i = 0; i < hc; i++) {
+            if (i + y < 0  || i + y >= h) continue;
+            linec = datac + i * wplc;
+            for (j = 0; j < wc; j++) {
+                if (j + x < 0  || j + x >= w) continue;
+                bval = GET_DATA_BIT(linec, j);
+                if (bval) {
+                    switch (d)
+                    {
+                    case 8:
+                        pixGetPixel(pixd, x + j, y + i, &pixval);
+                        val = (l_int32)(pixval + fract * (255 - 2 * pixval));
+                        pixSetPixel(pixd, x + j, y + i, val);
+                        break;
+                    case 32:
+                        pixGetPixel(pixd, x + j, y + i, &pixval);
+                        extractRGBValues(pixval, &rval, &gval, &bval);
+                        rval = (l_int32)(rval + fract * (255 - 2 * rval));
+                        gval = (l_int32)(gval + fract * (255 - 2 * gval));
+                        bval = (l_int32)(bval + fract * (255 - 2 * bval));
+                        composeRGBPixel(rval, gval, bval, &pixval);
+                        pixSetPixel(pixd, x + j, y + i, pixval);
+                        break;
+                    default:
+                        L_WARNING("d neither 8 nor 32 bpp; no blend\n",
+                                  procName);
+                    }
+                }
+            }
+        }
+        break;
+    case L_BLEND_TO_WHITE:
+            /*
+             * The basic logic for this blending is:
+             *      p -->  p + f * (1 - p)    (p normalized to [0...1])
+             */
+        for (i = 0; i < hc; i++) {
+            if (i + y < 0  || i + y >= h) continue;
+            linec = datac + i * wplc;
+            for (j = 0; j < wc; j++) {
+                if (j + x < 0  || j + x >= w) continue;
+                bval = GET_DATA_BIT(linec, j);
+                if (bval) {
+                    switch (d)
+                    {
+                    case 8:
+                        pixGetPixel(pixd, x + j, y + i, &pixval);
+                        val = (l_int32)(pixval + fract * (255 - pixval));
+                        pixSetPixel(pixd, x + j, y + i, val);
+                        break;
+                    case 32:
+                        pixGetPixel(pixd, x + j, y + i, &pixval);
+                        extractRGBValues(pixval, &rval, &gval, &bval);
+                        rval = (l_int32)(rval + fract * (255 - rval));
+                        gval = (l_int32)(gval + fract * (255 - gval));
+                        bval = (l_int32)(bval + fract * (255 - bval));
+                        composeRGBPixel(rval, gval, bval, &pixval);
+                        pixSetPixel(pixd, x + j, y + i, pixval);
+                        break;
+                    default:
+                        L_WARNING("d neither 8 nor 32 bpp; no blend\n",
+                                  procName);
+                    }
+                }
+            }
+        }
+        break;
+    case L_BLEND_TO_BLACK:
+            /*
+             * The basic logic for this blending is:
+             *      p -->  (1 - f) * p     (p normalized to [0...1])
+             */
+        for (i = 0; i < hc; i++) {
+            if (i + y < 0  || i + y >= h) continue;
+            linec = datac + i * wplc;
+            for (j = 0; j < wc; j++) {
+                if (j + x < 0  || j + x >= w) continue;
+                bval = GET_DATA_BIT(linec, j);
+                if (bval) {
+                    switch (d)
+                    {
+                    case 8:
+                        pixGetPixel(pixd, x + j, y + i, &pixval);
+                        val = (l_int32)((1. - fract) * pixval);
+                        pixSetPixel(pixd, x + j, y + i, val);
+                        break;
+                    case 32:
+                        pixGetPixel(pixd, x + j, y + i, &pixval);
+                        extractRGBValues(pixval, &rval, &gval, &bval);
+                        rval = (l_int32)((1. - fract) * rval);
+                        gval = (l_int32)((1. - fract) * gval);
+                        bval = (l_int32)((1. - fract) * bval);
+                        composeRGBPixel(rval, gval, bval, &pixval);
+                        pixSetPixel(pixd, x + j, y + i, pixval);
+                        break;
+                    default:
+                        L_WARNING("d neither 8 nor 32 bpp; no blend\n",
+                                  procName);
+                    }
+                }
+            }
+        }
+        break;
+    default:
+        L_WARNING("invalid binary mask blend type\n", procName);
+        break;
+    }
+
+    pixDestroy(&pixc);
+    return pixd;
+}
+
+
+/*!
+ *  pixBlendGray()
+ *
+ *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
+ *              pixs1 (blendee, depth > 1)
+ *              pixs2 (blender, any depth; typ. smaller in size than pixs1)
+ *              x,y  (origin (UL corner) of pixs2 relative to
+ *                    the origin of pixs1; can be < 0)
+ *              fract (blending fraction)
+ *              type (L_BLEND_GRAY, L_BLEND_GRAY_WITH_INVERSE)
+ *              transparent (1 to use transparency; 0 otherwise)
+ *              transpix (pixel grayval in pixs2 that is to be transparent)
+ *      Return: pixd if OK; pixs1 on error
+ *
+ *  Notes:
+ *      (1) For inplace operation (pixs1 not cmapped), call it this way:
+ *            pixBlendGray(pixs1, pixs1, pixs2, ...)
+ *      (2) For generating a new pixd:
+ *            pixd = pixBlendGray(NULL, pixs1, pixs2, ...)
+ *      (3) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
+ *      (4) If pixs1 has a colormap, it is removed; otherwise, if pixs1
+ *          has depth < 8, it is unpacked to generate a 8 bpp pix.
+ *      (5) If transparent = 0, the blending fraction (fract) is
+ *          applied equally to all pixels.
+ *      (6) If transparent = 1, all pixels of value transpix (typically
+ *          either 0 or 0xff) in pixs2 are transparent in the blend.
+ *      (7) After processing pixs1, it is either 8 bpp or 32 bpp:
+ *          - if 8 bpp, the fraction of pixs2 is mixed with pixs1.
+ *          - if 32 bpp, each component of pixs1 is mixed with
+ *            the same fraction of pixs2.
+ *      (8) For L_BLEND_GRAY_WITH_INVERSE, the white values of the blendee
+ *          (cval == 255 in the code below) result in a delta of 0.
+ *          Thus, these pixels are intrinsically transparent!
+ *          The "pivot" value of the src, at which no blending occurs, is
+ *          128.  Compare with the adaptive pivot in pixBlendGrayAdapt().
+ *      (9) Invalid @fract defaults to 0.5 with a warning.
+ *          Invalid @type defaults to L_BLEND_GRAY with a warning.
+ */
+PIX *
+pixBlendGray(PIX       *pixd,
+             PIX       *pixs1,
+             PIX       *pixs2,
+             l_int32    x,
+             l_int32    y,
+             l_float32  fract,
+             l_int32    type,
+             l_int32    transparent,
+             l_uint32   transpix)
+{
+l_int32    i, j, d, wc, hc, w, h, wplc, wpld, delta;
+l_int32    ival, irval, igval, ibval, cval, dval;
+l_uint32   val32;
+l_uint32  *linec, *lined, *datac, *datad;
+PIX       *pixc, *pix1, *pix2;
+
+    PROCNAME("pixBlendGray");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
+    if (pixGetDepth(pixs1) == 1)
+        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, pixd);
+    if (pixd == pixs1 && pixGetColormap(pixs1))
+        return (PIX *)ERROR_PTR("can't do in-place with cmap", procName, pixd);
+    if (pixd && (pixd != pixs1))
+        return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, pixd);
+    if (fract < 0.0 || fract > 1.0) {
+        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", procName);
+        fract = 0.5;
+    }
+    if (type != L_BLEND_GRAY && type != L_BLEND_GRAY_WITH_INVERSE) {
+        L_WARNING("invalid blend type; setting to L_BLEND_GRAY\n", procName);
+        type = L_BLEND_GRAY;
+    }
+
+        /* If pixd != NULL, we know that it is equal to pixs1 and
+         * that pixs1 does not have a colormap, so that an in-place operation
+         * can be done.  Otherwise, remove colormap from pixs1 if
+         * it exists and unpack to at least 8 bpp if necessary,
+         * to do the blending on a new pix. */
+    if (!pixd) {
+        pix1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC);
+        if (pixGetDepth(pix1) < 8)
+            pix2 = pixConvertTo8(pix1, FALSE);
+        else
+            pix2 = pixClone(pix1);
+        pixd = pixCopy(NULL, pix2);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    pixGetDimensions(pixd, &w, &h, &d);  /* 8 or 32 bpp */
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    pixc = pixConvertTo8(pixs2, 0);
+    pixGetDimensions(pixc, &wc, &hc, NULL);
+    datac = pixGetData(pixc);
+    wplc = pixGetWpl(pixc);
+
+        /* Check limits for src1, in case clipping was not done */
+    if (type == L_BLEND_GRAY) {
+        /*
+         * The basic logic for this blending is:
+         *      p -->  (1 - f) * p + f * c
+         * where c is the 8 bpp blender.  All values are normalized to [0...1].
+         */
+        for (i = 0; i < hc; i++) {
+            if (i + y < 0  || i + y >= h) continue;
+            linec = datac + i * wplc;
+            lined = datad + (i + y) * wpld;
+            switch (d)
+            {
+            case 8:
+                for (j = 0; j < wc; j++) {
+                    if (j + x < 0  || j + x >= w) continue;
+                    cval = GET_DATA_BYTE(linec, j);
+                    if (transparent == 0 ||
+                        (transparent != 0 && cval != transpix)) {
+                        dval = GET_DATA_BYTE(lined, j + x);
+                        ival = (l_int32)((1. - fract) * dval + fract * cval);
+                        SET_DATA_BYTE(lined, j + x, ival);
+                    }
+                }
+                break;
+            case 32:
+                for (j = 0; j < wc; j++) {
+                    if (j + x < 0  || j + x >= w) continue;
+                    cval = GET_DATA_BYTE(linec, j);
+                    if (transparent == 0 ||
+                        (transparent != 0 && cval != transpix)) {
+                        val32 = *(lined + j + x);
+                        extractRGBValues(val32, &irval, &igval, &ibval);
+                        irval = (l_int32)((1. - fract) * irval + fract * cval);
+                        igval = (l_int32)((1. - fract) * igval + fract * cval);
+                        ibval = (l_int32)((1. - fract) * ibval + fract * cval);
+                        composeRGBPixel(irval, igval, ibval, &val32);
+                        *(lined + j + x) = val32;
+                    }
+                }
+                break;
+            default:
+                break;   /* shouldn't happen */
+            }
+        }
+    } else {  /* L_BLEND_GRAY_WITH_INVERSE */
+        for (i = 0; i < hc; i++) {
+            if (i + y < 0  || i + y >= h) continue;
+            linec = datac + i * wplc;
+            lined = datad + (i + y) * wpld;
+            switch (d)
+            {
+            case 8:
+                /*
+                 * For 8 bpp, the dest pix is shifted by a signed amount
+                 * proportional to the distance from 128 (the pivot value),
+                 * and to the darkness of src2.  If the dest is darker
+                 * than 128, it becomes lighter, and v.v.
+                 * The basic logic is:
+                 *     d  -->  d + f * (0.5 - d) * (1 - c)
+                 * where d and c are normalized pixel values for src1 and
+                 * src2, respectively, with 8 bit normalization to [0...1].
+                 */
+                for (j = 0; j < wc; j++) {
+                    if (j + x < 0  || j + x >= w) continue;
+                    cval = GET_DATA_BYTE(linec, j);
+                    if (transparent == 0 ||
+                        (transparent != 0 && cval != transpix)) {
+                        ival = GET_DATA_BYTE(lined, j + x);
+                        delta = (128 - ival) * (255 - cval) / 256;
+                        ival += (l_int32)(fract * delta + 0.5);
+                        SET_DATA_BYTE(lined, j + x, ival);
+                    }
+                }
+                break;
+            case 32:
+                /* Each component is shifted by the same formula for 8 bpp */
+                for (j = 0; j < wc; j++) {
+                    if (j + x < 0  || j + x >= w) continue;
+                    cval = GET_DATA_BYTE(linec, j);
+                    if (transparent == 0 ||
+                        (transparent != 0 && cval != transpix)) {
+                        val32 = *(lined + j + x);
+                        extractRGBValues(val32, &irval, &igval, &ibval);
+                        delta = (128 - irval) * (255 - cval) / 256;
+                        irval += (l_int32)(fract * delta + 0.5);
+                        delta = (128 - igval) * (255 - cval) / 256;
+                        igval += (l_int32)(fract * delta + 0.5);
+                        delta = (128 - ibval) * (255 - cval) / 256;
+                        ibval += (l_int32)(fract * delta + 0.5);
+                        composeRGBPixel(irval, igval, ibval, &val32);
+                        *(lined + j + x) = val32;
+                    }
+                }
+                break;
+            default:
+                break;   /* shouldn't happen */
+            }
+        }
+    }
+
+    pixDestroy(&pixc);
+    return pixd;
+}
+
+
+/*!
+ *  pixBlendGrayInverse()
+ *
+ *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
+ *              pixs1 (blendee, depth > 1)
+ *              pixs2 (blender, any depth; typ. smaller in size than pixs1)
+ *              x,y  (origin (UL corner) of pixs2 relative to
+ *                    the origin of pixs1; can be < 0)
+ *              fract (blending fraction)
+ *      Return: pixd if OK; pixs1 on error
+ *
+ *  Notes:
+ *      (1) For inplace operation (pixs1 not cmapped), call it this way:
+ *            pixBlendGrayInverse(pixs1, pixs1, pixs2, ...)
+ *      (2) For generating a new pixd:
+ *            pixd = pixBlendGrayInverse(NULL, pixs1, pixs2, ...)
+ *      (3) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
+ *      (4) If pixs1 has a colormap, it is removed; otherwise if pixs1
+ *          has depth < 8, it is unpacked to generate a 8 bpp pix.
+ *      (5) This is a no-nonsense blender.  It changes the src1 pixel except
+ *          when the src1 pixel is midlevel gray.  Use fract == 1 for the most
+ *          aggressive blending, where, if the gray pixel in pixs2 is 0,
+ *          we get a complete inversion of the color of the src pixel in pixs1.
+ *      (6) The basic logic is that each component transforms by:
+                 d  -->  c * d + (1 - c ) * (f * (1 - d) + d * (1 - f))
+ *          where c is the blender pixel from pixs2,
+ *                f is @fract,
+ *                c and d are normalized to [0...1]
+ *          This has the property that for f == 0 (no blend) or c == 1 (white):
+ *               d  -->  d
+ *          For c == 0 (black) we get maximum inversion:
+ *               d  -->  f * (1 - d) + d * (1 - f)   [inversion by fraction f]
+ */
+PIX *
+pixBlendGrayInverse(PIX       *pixd,
+                    PIX       *pixs1,
+                    PIX       *pixs2,
+                    l_int32    x,
+                    l_int32    y,
+                    l_float32  fract)
+{
+l_int32    i, j, d, wc, hc, w, h, wplc, wpld;
+l_int32    irval, igval, ibval, cval, dval;
+l_float32  a;
+l_uint32   val32;
+l_uint32  *linec, *lined, *datac, *datad;
+PIX       *pixc, *pix1, *pix2;
+
+    PROCNAME("pixBlendGrayInverse");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
+    if (pixGetDepth(pixs1) == 1)
+        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, pixd);
+    if (pixd == pixs1 && pixGetColormap(pixs1))
+        return (PIX *)ERROR_PTR("can't do in-place with cmap", procName, pixd);
+    if (pixd && (pixd != pixs1))
+        return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, pixd);
+    if (fract < 0.0 || fract > 1.0) {
+        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", procName);
+        fract = 0.5;
+    }
+
+        /* If pixd != NULL, we know that it is equal to pixs1 and
+         * that pixs1 does not have a colormap, so that an in-place operation
+         * can be done.  Otherwise, remove colormap from pixs1 if
+         * it exists and unpack to at least 8 bpp if necessary,
+         * to do the blending on a new pix. */
+    if (!pixd) {
+        pix1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC);
+        if (pixGetDepth(pix1) < 8)
+            pix2 = pixConvertTo8(pix1, FALSE);
+        else
+            pix2 = pixClone(pix1);
+        pixd = pixCopy(NULL, pix2);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    pixGetDimensions(pixd, &w, &h, &d);  /* 8 or 32 bpp */
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    pixc = pixConvertTo8(pixs2, 0);
+    pixGetDimensions(pixc, &wc, &hc, NULL);
+    datac = pixGetData(pixc);
+    wplc = pixGetWpl(pixc);
+
+        /* Check limits for src1, in case clipping was not done */
+    for (i = 0; i < hc; i++) {
+        if (i + y < 0  || i + y >= h) continue;
+        linec = datac + i * wplc;
+        lined = datad + (i + y) * wpld;
+        switch (d)
+        {
+        case 8:
+            for (j = 0; j < wc; j++) {
+                if (j + x < 0  || j + x >= w) continue;
+                cval = GET_DATA_BYTE(linec, j);
+                dval = GET_DATA_BYTE(lined, j + x);
+                a = (1.0 - fract) * dval + fract * (255.0 - dval);
+                dval = (l_int32)(cval * dval / 255.0 +
+                                  a * (255.0 - cval) / 255.0);
+                SET_DATA_BYTE(lined, j + x, dval);
+            }
+            break;
+        case 32:
+            for (j = 0; j < wc; j++) {
+                if (j + x < 0  || j + x >= w) continue;
+                cval = GET_DATA_BYTE(linec, j);
+                val32 = *(lined + j + x);
+                extractRGBValues(val32, &irval, &igval, &ibval);
+                a = (1.0 - fract) * irval + fract * (255.0 - irval);
+                irval = (l_int32)(cval * irval / 255.0 +
+                                  a * (255.0 - cval) / 255.0);
+                a = (1.0 - fract) * igval + fract * (255.0 - igval);
+                igval = (l_int32)(cval * igval / 255.0 +
+                                  a * (255.0 - cval) / 255.0);
+                a = (1.0 - fract) * ibval + fract * (255.0 - ibval);
+                ibval = (l_int32)(cval * ibval / 255.0 +
+                                  a * (255.0 - cval) / 255.0);
+                composeRGBPixel(irval, igval, ibval, &val32);
+                *(lined + j + x) = val32;
+            }
+            break;
+        default:
+            break;   /* shouldn't happen */
+        }
+    }
+
+    pixDestroy(&pixc);
+    return pixd;
+}
+
+
+/*!
+ *  pixBlendColor()
+ *
+ *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
+ *              pixs1 (blendee; depth > 1)
+ *              pixs2 (blender, any depth;; typ. smaller in size than pixs1)
+ *              x,y  (origin (UL corner) of pixs2 relative to
+ *                    the origin of pixs1)
+ *              fract (blending fraction)
+ *              transparent (1 to use transparency; 0 otherwise)
+ *              transpix (pixel color in pixs2 that is to be transparent)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) For inplace operation (pixs1 must be 32 bpp), call it this way:
+ *            pixBlendColor(pixs1, pixs1, pixs2, ...)
+ *      (2) For generating a new pixd:
+ *            pixd = pixBlendColor(NULL, pixs1, pixs2, ...)
+ *      (3) If pixs2 is not 32 bpp rgb, it is converted.
+ *      (4) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
+ *      (5) If pixs1 has a colormap, it is removed to generate a 32 bpp pix.
+ *      (6) If pixs1 has depth < 32, it is unpacked to generate a 32 bpp pix.
+ *      (7) If transparent = 0, the blending fraction (fract) is
+ *          applied equally to all pixels.
+ *      (8) If transparent = 1, all pixels of value transpix (typically
+ *          either 0 or 0xffffff00) in pixs2 are transparent in the blend.
+ */
+PIX *
+pixBlendColor(PIX       *pixd,
+              PIX       *pixs1,
+              PIX       *pixs2,
+              l_int32    x,
+              l_int32    y,
+              l_float32  fract,
+              l_int32    transparent,
+              l_uint32   transpix)
+{
+l_int32    i, j, wc, hc, w, h, wplc, wpld;
+l_int32    rval, gval, bval, rcval, gcval, bcval;
+l_uint32   cval32, val32;
+l_uint32  *linec, *lined, *datac, *datad;
+PIX       *pixc;
+
+    PROCNAME("pixBlendColor");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, NULL);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, NULL);
+    if (pixGetDepth(pixs1) == 1)
+        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, NULL);
+    if (pixd == pixs1 && pixGetDepth(pixs1) != 32)
+        return (PIX *)ERROR_PTR("inplace; pixs1 not 32 bpp", procName, NULL);
+    if (pixd && (pixd != pixs1))
+        return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, NULL);
+    if (fract < 0.0 || fract > 1.0) {
+        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", procName);
+        fract = 0.5;
+    }
+
+        /* If pixd != null, we know that it is equal to pixs1 and
+         * that pixs1 is 32 bpp rgb, so that an in-place operation
+         * can be done.  Otherwise, pixConvertTo32() will remove a
+         * colormap from pixs1 if it exists and unpack to 32 bpp
+         * (if necessary) to do the blending on a new 32 bpp Pix. */
+    if (!pixd)
+        pixd = pixConvertTo32(pixs1);
+    pixGetDimensions(pixd, &w, &h, NULL);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    pixc = pixConvertTo32(pixs2);  /* blend with 32 bpp rgb */
+    pixGetDimensions(pixc, &wc, &hc, NULL);
+    datac = pixGetData(pixc);
+    wplc = pixGetWpl(pixc);
+
+        /* Check limits for src1, in case clipping was not done */
+    for (i = 0; i < hc; i++) {
+        /*
+         * The basic logic for this blending is:
+         *      p -->  (1 - f) * p + f * c
+         * for each color channel.  c is a color component of the blender.
+         * All values are normalized to [0...1].
+         */
+        if (i + y < 0  || i + y >= h) continue;
+        linec = datac + i * wplc;
+        lined = datad + (i + y) * wpld;
+        for (j = 0; j < wc; j++) {
+            if (j + x < 0  || j + x >= w) continue;
+            cval32 = *(linec + j);
+            if (transparent == 0 ||
+                (transparent != 0 &&
+                     ((cval32 & 0xffffff00) != (transpix & 0xffffff00)))) {
+                val32 = *(lined + j + x);
+                extractRGBValues(cval32, &rcval, &gcval, &bcval);
+                extractRGBValues(val32, &rval, &gval, &bval);
+                rval = (l_int32)((1. - fract) * rval + fract * rcval);
+                gval = (l_int32)((1. - fract) * gval + fract * gcval);
+                bval = (l_int32)((1. - fract) * bval + fract * bcval);
+                composeRGBPixel(rval, gval, bval, &val32);
+                *(lined + j + x) = val32;
+            }
+        }
+    }
+
+    pixDestroy(&pixc);
+    return pixd;
+}
+
+
+/*
+ *  pixBlendColorByChannel()
+ *
+ *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
+ *              pixs1 (blendee; depth > 1)
+ *              pixs2 (blender, any depth; typ. smaller in size than pixs1)
+ *              x,y  (origin (UL corner) of pixs2 relative to
+ *                    the origin of pixs1)
+ *              rfract, gfract, bfract (blending fractions by channel)
+ *              transparent (1 to use transparency; 0 otherwise)
+ *              transpix (pixel color in pixs2 that is to be transparent)
+ *      Return: pixd if OK; pixs1 on error
+ *
+ * Notes:
+ *     (1) This generalizes pixBlendColor() in two ways:
+ *         (a) The mixing fraction is specified per channel.
+ *         (b) The mixing fraction may be < 0 or > 1, in which case,
+ *             the min or max of two images are taken, respectively.
+ *     (2) Specifically,
+ *         for p = pixs1[i], c = pixs2[i], f = fract[i], i = 1, 2, 3:
+ *             f < 0.0:          p --> min(p, c)
+ *             0.0 <= f <= 1.0:  p --> (1 - f) * p + f * c
+ *             f > 1.0:          p --> max(a, c)
+ *         Special cases:
+ *             f = 0:   p --> p
+ *             f = 1:   p --> c
+ *     (3) See usage notes in pixBlendColor()
+ *     (4) pixBlendColor() would be equivalent to
+ *           pixBlendColorChannel(..., fract, fract, fract, ...);
+ *         at a small cost of efficiency.
+ */
+PIX *
+pixBlendColorByChannel(PIX       *pixd,
+                       PIX       *pixs1,
+                       PIX       *pixs2,
+                       l_int32    x,
+                       l_int32    y,
+                       l_float32  rfract,
+                       l_float32  gfract,
+                       l_float32  bfract,
+                       l_int32    transparent,
+                       l_uint32   transpix)
+{
+l_int32    i, j, wc, hc, w, h, wplc, wpld;
+l_int32    rval, gval, bval, rcval, gcval, bcval;
+l_uint32   cval32, val32;
+l_uint32  *linec, *lined, *datac, *datad;
+PIX       *pixc;
+
+    PROCNAME("pixBlendColorByChannel");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
+    if (pixGetDepth(pixs1) == 1)
+        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, pixd);
+    if (pixd == pixs1 && pixGetDepth(pixs1) != 32)
+        return (PIX *)ERROR_PTR("inplace; pixs1 not 32 bpp", procName, pixd);
+    if (pixd && (pixd != pixs1))
+        return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, pixd);
+
+        /* If pixd != NULL, we know that it is equal to pixs1 and
+         * that pixs1 is 32 bpp rgb, so that an in-place operation
+         * can be done.  Otherwise, pixConvertTo32() will remove a
+         * colormap from pixs1 if it exists and unpack to 32 bpp
+         * (if necessary) to do the blending on a new 32 bpp Pix. */
+    if (!pixd)
+        pixd = pixConvertTo32(pixs1);
+    pixGetDimensions(pixd, &w, &h, NULL);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    pixc = pixConvertTo32(pixs2);
+    pixGetDimensions(pixc, &wc, &hc, NULL);
+    datac = pixGetData(pixc);
+    wplc = pixGetWpl(pixc);
+
+        /* Check limits for src1, in case clipping was not done */
+    for (i = 0; i < hc; i++) {
+        if (i + y < 0  || i + y >= h) continue;
+        linec = datac + i * wplc;
+        lined = datad + (i + y) * wpld;
+        for (j = 0; j < wc; j++) {
+            if (j + x < 0  || j + x >= w) continue;
+            cval32 = *(linec + j);
+            if (transparent == 0 ||
+                (transparent != 0 &&
+                     ((cval32 & 0xffffff00) != (transpix & 0xffffff00)))) {
+                val32 = *(lined + j + x);
+                extractRGBValues(cval32, &rcval, &gcval, &bcval);
+                extractRGBValues(val32, &rval, &gval, &bval);
+                rval = blendComponents(rval, rcval, rfract);
+                gval = blendComponents(gval, gcval, gfract);
+                bval = blendComponents(bval, bcval, bfract);
+                composeRGBPixel(rval, gval, bval, &val32);
+                *(lined + j + x) = val32;
+            }
+        }
+    }
+
+    pixDestroy(&pixc);
+    return pixd;
+}
+
+
+static l_int32
+blendComponents(l_int32    a,
+                l_int32    b,
+                l_float32  fract)
+{
+    if (fract < 0.)
+        return ((a < b) ? a : b);
+    if (fract > 1.)
+        return ((a > b) ? a : b);
+    return (l_int32)((1. - fract) * a + fract * b);
+}
+
+
+/*!
+ *  pixBlendGrayAdapt()
+ *
+ *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
+ *              pixs1 (blendee, depth > 1)
+ *              pixs2 (blender, any depth; typ. smaller in size than pixs1)
+ *              x,y  (origin (UL corner) of pixs2 relative to
+ *                    the origin of pixs1; can be < 0)
+ *              fract (blending fraction)
+ *              shift (>= 0 but <= 128: shift of zero blend value from
+ *                     median source; use -1 for default value; )
+ *      Return: pixd if OK; pixs1 on error
+ *
+ *  Notes:
+ *      (1) For inplace operation (pixs1 not cmapped), call it this way:
+ *            pixBlendGrayAdapt(pixs1, pixs1, pixs2, ...)
+ *          For generating a new pixd:
+ *            pixd = pixBlendGrayAdapt(NULL, pixs1, pixs2, ...)
+ *      (2) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
+ *      (3) If pixs1 has a colormap, it is removed.
+ *      (4) If pixs1 has depth < 8, it is unpacked to generate a 8 bpp pix.
+ *      (5) This does a blend with inverse.  Whereas in pixGlendGray(), the
+ *          zero blend point is where the blendee pixel is 128, here
+ *          the zero blend point is found adaptively, with respect to the
+ *          median of the blendee region.  If the median is < 128,
+ *          the zero blend point is found from
+ *              median + shift.
+ *          Otherwise, if the median >= 128, the zero blend point is
+ *              median - shift.
+ *          The purpose of shifting the zero blend point away from the
+ *          median is to prevent a situation in pixBlendGray() where
+ *          the median is 128 and the blender is not visible.
+ *          The default value of shift is 64.
+ *      (6) After processing pixs1, it is either 8 bpp or 32 bpp:
+ *          - if 8 bpp, the fraction of pixs2 is mixed with pixs1.
+ *          - if 32 bpp, each component of pixs1 is mixed with
+ *            the same fraction of pixs2.
+ *      (7) The darker the blender, the more it mixes with the blendee.
+ *          A blender value of 0 has maximum mixing; a value of 255
+ *          has no mixing and hence is transparent.
+ */
+PIX *
+pixBlendGrayAdapt(PIX       *pixd,
+                  PIX       *pixs1,
+                  PIX       *pixs2,
+                  l_int32    x,
+                  l_int32    y,
+                  l_float32  fract,
+                  l_int32    shift)
+{
+l_int32    i, j, d, wc, hc, w, h, wplc, wpld, delta, overlap;
+l_int32    rval, gval, bval, cval, dval, mval, median, pivot;
+l_uint32   val32;
+l_uint32  *linec, *lined, *datac, *datad;
+l_float32  fmedian, factor;
+BOX       *box, *boxt;
+PIX       *pixc, *pix1, *pix2;
+
+    PROCNAME("pixBlendGrayAdapt");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
+    if (pixGetDepth(pixs1) == 1)
+        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, pixd);
+    if (pixd == pixs1 && pixGetColormap(pixs1))
+        return (PIX *)ERROR_PTR("can't do in-place with cmap", procName, pixd);
+    if (pixd && (pixd != pixs1))
+        return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", procName, pixd);
+    if (fract < 0.0 || fract > 1.0) {
+        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", procName);
+        fract = 0.5;
+    }
+    if (shift == -1) shift = 64;   /* default value */
+    if (shift < 0 || shift > 127) {
+        L_WARNING("invalid shift; setting to 64\n", procName);
+        shift = 64;
+    }
+
+        /* Test for overlap */
+    pixGetDimensions(pixs1, &w, &h, NULL);
+    pixGetDimensions(pixs2, &wc, &hc, NULL);
+    box = boxCreate(x, y, wc, hc);
+    boxt = boxCreate(0, 0, w, h);
+    boxIntersects(box, boxt, &overlap);
+    boxDestroy(&boxt);
+    if (!overlap) {
+        boxDestroy(&box);
+        return (PIX *)ERROR_PTR("no image overlap", procName, pixd);
+    }
+
+        /* If pixd != NULL, we know that it is equal to pixs1 and
+         * that pixs1 does not have a colormap, so that an in-place operation
+         * can be done.  Otherwise, remove colormap from pixs1 if
+         * it exists and unpack to at least 8 bpp if necessary,
+         * to do the blending on a new pix. */
+    if (!pixd) {
+        pix1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC);
+        if (pixGetDepth(pix1) < 8)
+            pix2 = pixConvertTo8(pix1, FALSE);
+        else
+            pix2 = pixClone(pix1);
+        pixd = pixCopy(NULL, pix2);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+        /* Get the median value in the region of blending */
+    pix1 = pixClipRectangle(pixd, box, NULL);
+    pix2 = pixConvertTo8(pix1, 0);
+    pixGetRankValueMasked(pix2, NULL, 0, 0, 1, 0.5, &fmedian, NULL);
+    median = (l_int32)(fmedian + 0.5);
+    if (median < 128)
+        pivot = median + shift;
+    else
+        pivot = median - shift;
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    boxDestroy(&box);
+
+        /* Process over src2; clip to src1. */
+    d = pixGetDepth(pixd);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    pixc = pixConvertTo8(pixs2, 0);
+    datac = pixGetData(pixc);
+    wplc = pixGetWpl(pixc);
+    for (i = 0; i < hc; i++) {
+        if (i + y < 0  || i + y >= h) continue;
+        linec = datac + i * wplc;
+        lined = datad + (i + y) * wpld;
+        switch (d)
+        {
+        case 8:
+                /*
+                 * For 8 bpp, the dest pix is shifted by an amount
+                 * proportional to the distance from the pivot value,
+                 * and to the darkness of src2.  In no situation will it
+                 * pass the pivot value in intensity.
+                 * The basic logic is:
+                 *     d  -->  d + f * (np - d) * (1 - c)
+                 * where np, d and c are normalized pixel values for
+                 * the pivot, src1 and src2, respectively, with normalization
+                 * to 255.
+                 */
+            for (j = 0; j < wc; j++) {
+                if (j + x < 0  || j + x >= w) continue;
+                dval = GET_DATA_BYTE(lined, j + x);
+                cval = GET_DATA_BYTE(linec, j);
+                delta = (pivot - dval) * (255 - cval) / 256;
+                dval += (l_int32)(fract * delta + 0.5);
+                SET_DATA_BYTE(lined, j + x, dval);
+            }
+            break;
+        case 32:
+                /*
+                 * For 32 bpp, the dest pix is shifted by an amount
+                 * proportional to the max component distance from the
+                 * pivot value, and to the darkness of src2.  Each component
+                 * is shifted by the same fraction, either up or down,
+                 * depending on the shift direction (which is toward the
+                 * pivot).   The basic logic for the red component is:
+                 *     r  -->  r + f * (np - m) * (1 - c) * (r / m)
+                 * where np, r, m and c are normalized pixel values for
+                 * the pivot, the r component of src1, the max component
+                 * of src1, and src2, respectively, again with normalization
+                 * to 255.  Likewise for the green and blue components.
+                 */
+            for (j = 0; j < wc; j++) {
+                if (j + x < 0  || j + x >= w) continue;
+                cval = GET_DATA_BYTE(linec, j);
+                val32 = *(lined + j + x);
+                extractRGBValues(val32, &rval, &gval, &bval);
+                mval = L_MAX(rval, gval);
+                mval = L_MAX(mval, bval);
+                mval = L_MAX(mval, 1);
+                delta = (pivot - mval) * (255 - cval) / 256;
+                factor = fract * delta / mval;
+                rval += (l_int32)(factor * rval + 0.5);
+                gval += (l_int32)(factor * gval + 0.5);
+                bval += (l_int32)(factor * bval + 0.5);
+                composeRGBPixel(rval, gval, bval, &val32);
+                *(lined + j + x) = val32;
+            }
+            break;
+        default:
+            break;   /* shouldn't happen */
+        }
+    }
+
+    pixDestroy(&pixc);
+    return pixd;
+}
+
+
+/*!
+ *  pixFadeWithGray()
+ *
+ *      Input:  pixs (colormapped or 8 bpp or 32 bpp)
+ *              pixb (8 bpp blender)
+ *              factor (multiplicative factor to apply to blender value)
+ *              type (L_BLEND_TO_WHITE, L_BLEND_TO_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This function combines two pix aligned to the UL corner; they
+ *          need not be the same size.
+ *      (2) Each pixel in pixb is multiplied by 'factor' divided by 255, and
+ *          clipped to the range [0 ... 1].  This gives the fade fraction
+ *          to be appied to pixs.  Fade either to white (L_BLEND_TO_WHITE)
+ *          or to black (L_BLEND_TO_BLACK).
+ */
+PIX *
+pixFadeWithGray(PIX       *pixs,
+                PIX       *pixb,
+                l_float32  factor,
+                l_int32    type)
+{
+l_int32    i, j, w, h, d, wb, hb, db, wd, hd, wplb, wpld;
+l_int32    valb, vald, nvald, rval, gval, bval, nrval, ngval, nbval;
+l_float32  nfactor, fract;
+l_uint32   val32, nval32;
+l_uint32  *lined, *datad, *lineb, *datab;
+PIX       *pixd;
+
+    PROCNAME("pixFadeWithGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!pixb)
+        return (PIX *)ERROR_PTR("pixb not defined", procName, NULL);
+    if (pixGetDepth(pixs) == 1)
+        return (PIX *)ERROR_PTR("pixs is 1 bpp", procName, NULL);
+    pixGetDimensions(pixb, &wb, &hb, &db);
+    if (db != 8)
+        return (PIX *)ERROR_PTR("pixb not 8 bpp", procName, NULL);
+    if (factor < 0.0 || factor > 255.0)
+        return (PIX *)ERROR_PTR("factor not in [0.0...255.0]", procName, NULL);
+    if (type != L_BLEND_TO_WHITE && type != L_BLEND_TO_BLACK)
+        return (PIX *)ERROR_PTR("invalid fade type", procName, NULL);
+
+        /* Remove colormap if it exists; otherwise copy */
+    pixd = pixRemoveColormapGeneral(pixs, REMOVE_CMAP_BASED_ON_SRC, L_COPY);
+    pixGetDimensions(pixd, &wd, &hd, &d);
+    w = L_MIN(wb, wd);
+    h = L_MIN(hb, hd);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datab = pixGetData(pixb);
+    wplb = pixGetWpl(pixb);
+
+        /* The basic logic for this blending is, for each component p of pixs:
+         *   fade-to-white:   p -->  p + (f * c) * (1 - p)
+         *   fade-to-black:   p -->  p - (f * c) * p
+         * with c being the 8 bpp blender pixel of pixb, and with both
+         * p and c normalized to [0...1]. */
+    nfactor = factor / 255.;
+    for (i = 0; i < h; i++) {
+        lineb = datab + i * wplb;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            valb = GET_DATA_BYTE(lineb, j);
+            fract = nfactor * (l_float32)valb;
+            fract = L_MIN(fract, 1.0);
+            if (d == 8) {
+                vald = GET_DATA_BYTE(lined, j);
+                if (type == L_BLEND_TO_WHITE)
+                    nvald = vald + (l_int32)(fract * (255. - (l_float32)vald));
+                else  /* L_BLEND_TO_BLACK */
+                    nvald = vald - (l_int32)(fract * (l_float32)vald);
+                SET_DATA_BYTE(lined, j, nvald);
+            } else {  /* d == 32 */
+                val32 = lined[j];
+                extractRGBValues(val32, &rval, &gval, &bval);
+                if (type == L_BLEND_TO_WHITE) {
+                    nrval = rval + (l_int32)(fract * (255. - (l_float32)rval));
+                    ngval = gval + (l_int32)(fract * (255. - (l_float32)gval));
+                    nbval = bval + (l_int32)(fract * (255. - (l_float32)bval));
+                } else {
+                    nrval = rval - (l_int32)(fract * (l_float32)rval);
+                    ngval = gval - (l_int32)(fract * (l_float32)gval);
+                    nbval = bval - (l_int32)(fract * (l_float32)bval);
+                }
+                composeRGBPixel(nrval, ngval, nbval, &nval32);
+                lined[j] = nval32;
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*
+ *  pixBlendHardLight()
+ *
+ *      Input:  pixd (<optional>; either NULL or equal to pixs1 for in-place)
+ *              pixs1 (blendee; depth > 1, may be cmapped)
+ *              pixs2 (blender, 8 or 32 bpp; may be colormapped;
+ *                     typ. smaller in size than pixs1)
+ *              x,y  (origin (UL corner) of pixs2 relative to
+ *                    the origin of pixs1)
+ *              fract (blending fraction, or 'opacity factor')
+ *      Return: pixd if OK; pixs1 on error
+ *
+ *  Notes:
+ *      (1) pixs2 must be 8 or 32 bpp; either may have a colormap.
+ *      (2) Clipping of pixs2 to pixs1 is done in the inner pixel loop.
+ *      (3) Only call in-place if pixs1 is not colormapped.
+ *      (4) If pixs1 has a colormap, it is removed to generate either an
+ *          8 or 32 bpp pix, depending on the colormap.
+ *      (5) For inplace operation, call it this way:
+ *            pixBlendHardLight(pixs1, pixs1, pixs2, ...)
+ *      (6) For generating a new pixd:
+ *            pixd = pixBlendHardLight(NULL, pixs1, pixs2, ...)
+ *      (7) This is a generalization of the usual hard light blending,
+ *          where fract == 1.0.
+ *      (8) "Overlay" blending is the same as hard light blending, with
+ *          fract == 1.0, except that the components are switched
+ *          in the test.  (Note that the result is symmetric in the
+ *          two components.)
+ *      (9) See, e.g.:
+ *           http://www.pegtop.net/delphi/articles/blendmodes/hardlight.htm
+ *           http://www.digitalartform.com/imageArithmetic.htm
+ *      (10) This function was built by Paco Galanes.
+ */
+PIX *
+pixBlendHardLight(PIX       *pixd,
+                  PIX       *pixs1,
+                  PIX       *pixs2,
+                  l_int32    x,
+                  l_int32    y,
+                  l_float32  fract)
+{
+l_int32    i, j, w, h, d, wc, hc, dc, wplc, wpld;
+l_int32    cval, dval, rcval, gcval, bcval, rdval, gdval, bdval;
+l_uint32   cval32, dval32;
+l_uint32  *linec, *lined, *datac, *datad;
+PIX       *pixc, *pixt;
+
+    PROCNAME("pixBlendHardLight");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
+    pixGetDimensions(pixs1, &w, &h, &d);
+    pixGetDimensions(pixs2, &wc, &hc, &dc);
+    if (d == 1)
+        return (PIX *)ERROR_PTR("pixs1 is 1 bpp", procName, pixd);
+    if (dc != 8 && dc != 32)
+        return (PIX *)ERROR_PTR("pixs2 not 8 or 32 bpp", procName, pixd);
+    if (pixd && (pixd != pixs1))
+        return (PIX *)ERROR_PTR("inplace and pixd != pixs1", procName, pixd);
+    if (pixd == pixs1 && pixGetColormap(pixs1))
+        return (PIX *)ERROR_PTR("inplace and pixs1 cmapped", procName, pixd);
+    if (pixd && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("inplace and not 8 or 32 bpp", procName, pixd);
+
+    if (fract < 0.0 || fract > 1.0) {
+        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", procName);
+        fract = 0.5;
+    }
+
+        /* If pixs2 has a colormap, remove it */
+    pixc = pixRemoveColormap(pixs2, REMOVE_CMAP_BASED_ON_SRC);  /* clone ok */
+    dc = pixGetDepth(pixc);
+
+        /* There are 4 cases:
+         *    * pixs1 has or doesn't have a colormap
+         *    * pixc is either 8 or 32 bpp
+         * In all situations, if pixs has a colormap it must be removed,
+         * and pixd must have a depth that is equal to or greater than pixc. */
+    if (dc == 32) {
+        if (pixGetColormap(pixs1)) {  /* pixd == NULL */
+            pixd = pixRemoveColormap(pixs1, REMOVE_CMAP_TO_FULL_COLOR);
+        } else {
+            if (!pixd) {
+                pixd = pixConvertTo32(pixs1);
+            } else {
+                pixt = pixConvertTo32(pixs1);
+                pixCopy(pixd, pixt);
+                pixDestroy(&pixt);
+            }
+        }
+        d = 32;
+    } else {  /* dc == 8 */
+        if (pixGetColormap(pixs1))   /* pixd == NULL */
+            pixd = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC);
+        else
+            pixd = pixCopy(pixd, pixs1);
+        d = pixGetDepth(pixd);
+    }
+
+    if (!(d == 8 && dc == 8) &&   /* 3 cases only */
+        !(d == 32 && dc == 8) &&
+        !(d == 32 && dc == 32)) {
+        pixDestroy(&pixc);
+        return (PIX *)ERROR_PTR("bad! -- invalid depth combo!", procName, pixd);
+    }
+
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    datac = pixGetData(pixc);
+    wplc = pixGetWpl(pixc);
+    for (i = 0; i < hc; i++) {
+        if (i + y < 0  || i + y >= h) continue;
+        linec = datac + i * wplc;
+        lined = datad + (i + y) * wpld;
+        for (j = 0; j < wc; j++) {
+            if (j + x < 0  || j + x >= w) continue;
+            if (d == 8 && dc == 8) {
+                dval = GET_DATA_BYTE(lined, x + j);
+                cval = GET_DATA_BYTE(linec, j);
+                dval = blendHardLightComponents(dval, cval, fract);
+                SET_DATA_BYTE(lined, x + j, dval);
+            } else if (d == 32 && dc == 8) {
+                dval32 = *(lined + x + j);
+                extractRGBValues(dval32, &rdval, &gdval, &bdval);
+                cval = GET_DATA_BYTE(linec, j);
+                rdval = blendHardLightComponents(rdval, cval, fract);
+                gdval = blendHardLightComponents(gdval, cval, fract);
+                bdval = blendHardLightComponents(bdval, cval, fract);
+                composeRGBPixel(rdval, gdval, bdval, &dval32);
+                *(lined + x + j) = dval32;
+            } else if (d == 32 && dc == 32) {
+                dval32 = *(lined + x + j);
+                extractRGBValues(dval32, &rdval, &gdval, &bdval);
+                cval32 = *(linec + j);
+                extractRGBValues(cval32, &rcval, &gcval, &bcval);
+                rdval = blendHardLightComponents(rdval, rcval, fract);
+                gdval = blendHardLightComponents(gdval, gcval, fract);
+                bdval = blendHardLightComponents(bdval, bcval, fract);
+                composeRGBPixel(rdval, gdval, bdval, &dval32);
+                *(lined + x + j) = dval32;
+            }
+        }
+    }
+
+    pixDestroy(&pixc);
+    return pixd;
+}
+
+
+/*
+ *  blendHardLightComponents()
+ *      Input:  a (8 bpp blendee component)
+ *              b (8 bpp blender component)
+ *              fract (fraction of blending; use 1.0 for usual definition)
+ *      Return: blended 8 bpp component
+ *
+ *  Notes:
+ *
+ *    The basic logic for this blending is:
+ *      b < 0.5:
+ *          a --> 2 * a * (0.5 - f * (0.5 - b))
+ *      b >= 0.5:
+ *          a --> 1 - 2 * (1 - a) * (1 - (0.5 - f * (0.5 - b)))
+ *
+ *    In the limit that f == 1 (standard hardlight blending):
+ *      b < 0.5:   a --> 2 * a * b
+ *                     or
+ *                 a --> a - a * (1 - 2 * b)
+ *      b >= 0.5:  a --> 1 - 2 * (1 - a) * (1 - b)
+ *                     or
+ *                 a --> a + (1 - a) * (2 * b - 1)
+ *
+ *    You can see that for standard hardlight blending:
+ *      b < 0.5:   a is pushed linearly with b down to 0
+ *      b >= 0.5:  a is pushed linearly with b up to 1
+ *    a is unchanged if b = 0.5
+ *
+ *    Our opacity factor f reduces the deviation of b from 0.5:
+ *      f == 0:  b -->  0.5, so no blending occurs
+ *      f == 1:  b -->  b, so we get full conventional blending
+ *
+ *    There is a variant of hardlight blending called "softlight" blending:
+ *    (e.g., http://jswidget.com/blog/tag/hard-light/)
+ *      b < 0.5:
+ *          a --> a - a * (0.5 - b) * (1 - Abs(2 * a - 1))
+ *      b >= 0.5:
+ *          a --> a + (1 - a) * (b - 0.5) * (1 - Abs(2 * a - 1))
+ *    which limits the amount that 'a' can be moved to a maximum of
+ *    halfway toward 0 or 1, and further reduces it as 'a' moves
+ *    away from 0.5.
+ *    As you can see, there are a nearly infinite number of different
+ *    blending formulas that can be conjured up.
+ */
+static l_int32 blendHardLightComponents(l_int32    a,
+                                        l_int32    b,
+                                        l_float32  fract)
+{
+    if (b < 0x80) {
+        b = 0x80 - (l_int32)(fract * (0x80 - b));
+        return (a * b) >> 7;
+    } else {
+        b = 0x80 + (l_int32)(fract * (b - 0x80));
+        return  0xff - (((0xff - b) * (0xff - a)) >> 7);
+    }
+}
+
+
+/*-------------------------------------------------------------*
+ *               Blending two colormapped images               *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixBlendCmap()
+ *
+ *      Input:  pixs (2, 4 or 8 bpp, with colormap)
+ *              pixb (colormapped blender)
+ *              x, y (UL corner of blender relative to pixs)
+ *              sindex (colormap index of pixels in pixs to be changed)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Note:
+ *      (1) This function combines two colormaps, and replaces the pixels
+ *          in pixs that have a specified color value with those in pixb.
+ *      (2) sindex must be in the existing colormap; otherwise an
+ *          error is returned.  In use, sindex will typically be the index
+ *          for white (255, 255, 255).
+ *      (3) Blender colors that already exist in the colormap are used;
+ *          others are added.  If any blender colors cannot be
+ *          stored in the colormap, an error is returned.
+ *      (4) In the implementation, a mapping is generated from each
+ *          original blender colormap index to the corresponding index
+ *          in the expanded colormap for pixs.  Then for each pixel in
+ *          pixs with value sindex, and which is covered by a blender pixel,
+ *          the new index corresponding to the blender pixel is substituted
+ *          for sindex.
+ */
+l_int32
+pixBlendCmap(PIX     *pixs,
+             PIX     *pixb,
+             l_int32  x,
+             l_int32  y,
+             l_int32  sindex)
+{
+l_int32    rval, gval, bval;
+l_int32    i, j, w, h, d, ncb, wb, hb, wpls;
+l_int32    index, val, nadded;
+l_int32    lut[256];
+l_uint32   pval;
+l_uint32  *lines, *datas;
+PIXCMAP   *cmaps, *cmapb, *cmapsc;
+
+    PROCNAME("pixBlendCmap");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixb)
+        return ERROR_INT("pixb not defined", procName, 1);
+    if ((cmaps = pixGetColormap(pixs)) == NULL)
+        return ERROR_INT("no colormap in pixs", procName, 1);
+    if ((cmapb = pixGetColormap(pixb)) == NULL)
+        return ERROR_INT("no colormap in pixb", procName, 1);
+    ncb = pixcmapGetCount(cmapb);
+
+        /* Make a copy of cmaps; we'll add to this if necessary
+         * and substitute at the end if we found there was enough room
+         * to hold all the new colors. */
+    cmapsc = pixcmapCopy(cmaps);
+
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 2 && d != 4 && d != 8)
+        return ERROR_INT("depth not in {2,4,8}", procName, 1);
+
+        /* Add new colors if necessary; get mapping array between
+         * cmaps and cmapb. */
+    for (i = 0, nadded = 0; i < ncb; i++) {
+        pixcmapGetColor(cmapb, i, &rval, &gval, &bval);
+        if (pixcmapGetIndex(cmapsc, rval, gval, bval, &index)) { /* not found */
+            if (pixcmapAddColor(cmapsc, rval, gval, bval)) {
+                pixcmapDestroy(&cmapsc);
+                return ERROR_INT("not enough room in cmaps", procName, 1);
+            }
+            lut[i] = pixcmapGetCount(cmapsc) - 1;
+            nadded++;
+        } else {
+            lut[i] = index;
+        }
+    }
+
+        /* Replace cmaps if colors have been added. */
+    if (nadded == 0)
+        pixcmapDestroy(&cmapsc);
+    else
+        pixSetColormap(pixs, cmapsc);
+
+        /* Replace each pixel value sindex by mapped colormap index when
+         * a blender pixel in pixbc overlays it. */
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixGetDimensions(pixb, &wb, &hb, NULL);
+    for (i = 0; i < hb; i++) {
+        if (i + y < 0  || i + y >= h) continue;
+        lines = datas + (y + i) * wpls;
+        for (j = 0; j < wb; j++) {
+            if (j + x < 0  || j + x >= w) continue;
+            switch (d) {
+            case 2:
+                val = GET_DATA_DIBIT(lines, x + j);
+                if (val == sindex) {
+                    pixGetPixel(pixb, j, i, &pval);
+                    SET_DATA_DIBIT(lines, x + j, lut[pval]);
+                }
+                break;
+            case 4:
+                val = GET_DATA_QBIT(lines, x + j);
+                if (val == sindex) {
+                    pixGetPixel(pixb, j, i, &pval);
+                    SET_DATA_QBIT(lines, x + j, lut[pval]);
+                }
+                break;
+            case 8:
+                val = GET_DATA_BYTE(lines, x + j);
+                if (val == sindex) {
+                    pixGetPixel(pixb, j, i, &pval);
+                    SET_DATA_BYTE(lines, x + j, lut[pval]);
+                }
+                break;
+            default:
+                return ERROR_INT("depth not in {2,4,8}", procName, 1);
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                  Blending two images using a third                  *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixBlendWithGrayMask()
+ *
+ *      Input:  pixs1 (8 bpp gray, rgb, rgba or colormapped)
+ *              pixs2 (8 bpp gray, rgb, rgba or colormapped)
+ *              pixg (<optional> 8 bpp gray, for transparency of pixs2;
+ *                    can be null)
+ *              x, y (UL corner of pixs2 and pixg with respect to pixs1)
+ *      Return: pixd (blended image), or null on error
+ *
+ *  Notes:
+ *      (1) The result is 8 bpp grayscale if both pixs1 and pixs2 are
+ *          8 bpp gray.  Otherwise, the result is 32 bpp rgb.
+ *      (2) pixg is an 8 bpp transparency image, where 0 is transparent
+ *          and 255 is opaque.  It determines the transparency of pixs2
+ *          when applied over pixs1.  It can be null if pixs2 is rgba,
+ *          in which case we use the alpha component of pixs2.
+ *      (3) If pixg exists, it need not be the same size as pixs2.
+ *          However, we assume their UL corners are aligned with each other,
+ *          and placed at the location (x, y) in pixs1.
+ *      (4) The pixels in pixd are a combination of those in pixs1
+ *          and pixs2, where the amount from pixs2 is proportional to
+ *          the value of the pixel (p) in pixg, and the amount from pixs1
+ *          is proportional to (255 - p).  Thus pixg is a transparency
+ *          image (usually called an alpha blender) where each pixel
+ *          can be associated with a pixel in pixs2, and determines
+ *          the amount of the pixs2 pixel in the final result.
+ *          For example, if pixg is all 0, pixs2 is transparent and
+ *          the result in pixd is simply pixs1.
+ *      (5) A typical use is for the pixs2/pixg combination to be
+ *          a small watermark that is applied to pixs1.
+ */
+PIX *
+pixBlendWithGrayMask(PIX     *pixs1,
+                     PIX     *pixs2,
+                     PIX     *pixg,
+                     l_int32  x,
+                     l_int32  y)
+{
+l_int32    w1, h1, d1, w2, h2, d2, spp, wg, hg, wmin, hmin, wpld, wpls, wplg;
+l_int32    i, j, val, dval, sval;
+l_int32    drval, dgval, dbval, srval, sgval, sbval;
+l_uint32   dval32, sval32;
+l_uint32  *datad, *datas, *datag, *lined, *lines, *lineg;
+l_float32  fract;
+PIX       *pixr1, *pixr2, *pix1, *pix2, *pixg2, *pixd;
+
+    PROCNAME("pixBlendWithGrayMask");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, NULL);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, NULL);
+    pixGetDimensions(pixs1, &w1, &h1, &d1);
+    pixGetDimensions(pixs2, &w2, &h2, &d2);
+    if (d1 == 1 || d2 == 1)
+        return (PIX *)ERROR_PTR("pixs1 or pixs2 is 1 bpp", procName, NULL);
+    if (pixg) {
+        if (pixGetDepth(pixg) != 8)
+            return (PIX *)ERROR_PTR("pixg not 8 bpp", procName, NULL);
+        pixGetDimensions(pixg, &wg, &hg, NULL);
+        wmin = L_MIN(w2, wg);
+        hmin = L_MIN(h2, hg);
+        pixg2 = pixClone(pixg);
+    } else {  /* use the alpha component of pixs2 */
+        spp = pixGetSpp(pixs2);
+        if (d2 != 32 || spp != 4)
+            return (PIX *)ERROR_PTR("no alpha; pixs2 not rgba", procName, NULL);
+        wmin = w2;
+        hmin = h2;
+        pixg2 = pixGetRGBComponent(pixs2, L_ALPHA_CHANNEL);
+    }
+
+        /* Remove colormaps if they exist; clones are OK */
+    pixr1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC);
+    pixr2 = pixRemoveColormap(pixs2, REMOVE_CMAP_BASED_ON_SRC);
+
+        /* Regularize to the same depth if necessary */
+    d1 = pixGetDepth(pixr1);
+    d2 = pixGetDepth(pixr2);
+    if (d1 == 32) {  /* convert d2 to rgb if necessary */
+        pix1 = pixClone(pixr1);
+        if (d2 != 32)
+            pix2 = pixConvertTo32(pixr2);
+        else
+            pix2 = pixClone(pixr2);
+    } else if (d2 == 32) {   /* and d1 != 32; convert to 32 */
+        pix2 = pixClone(pixr2);
+        pix1 = pixConvertTo32(pixr1);
+    } else {  /* both are 8 bpp or less */
+        pix1 = pixConvertTo8(pixr1, FALSE);
+        pix2 = pixConvertTo8(pixr2, FALSE);
+    }
+    pixDestroy(&pixr1);
+    pixDestroy(&pixr2);
+
+        /* Sanity check */
+    d1 = pixGetDepth(pix1);
+    d2 = pixGetDepth(pix2);
+    if (d1 != d2) {
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+        return (PIX *)ERROR_PTR("depths not regularized! bad!", procName, NULL);
+    }
+
+        /* Start with a copy of pix1 */
+    pixd = pixCopy(NULL, pix1);
+    pixDestroy(&pix1);
+
+        /* Blend pix2 onto pixd, using pixg2.
+         * Let the normalized pixel value of pixg2 be f = pixval / 255,
+         * and the pixel values of pixd and pix2 be p1 and p2, rsp.
+         * Then the blended value is:
+         *      p = (1.0 - f) * p1 + f * p2
+         * Blending is done component-wise if rgb.
+         * Scan over pix2 and pixg2, clipping to pixd where necessary.  */
+    datad = pixGetData(pixd);
+    datas = pixGetData(pix2);
+    datag = pixGetData(pixg2);
+    wpld = pixGetWpl(pixd);
+    wpls = pixGetWpl(pix2);
+    wplg = pixGetWpl(pixg2);
+    for (i = 0; i < hmin; i++) {
+        if (i + y < 0  || i + y >= h1) continue;
+        lined = datad + (i + y) * wpld;
+        lines = datas + i * wpls;
+        lineg = datag + i * wplg;
+        for (j = 0; j < wmin; j++) {
+            if (j + x < 0  || j + x >= w1) continue;
+            val = GET_DATA_BYTE(lineg, j);
+            if (val == 0) continue;  /* pix2 is transparent */
+            fract = (l_float32)val / 255.;
+            switch (d1) {
+            case 8:
+                dval = GET_DATA_BYTE(lined, j + x);
+                sval = GET_DATA_BYTE(lines, j);
+                dval = (l_int32)((1.0 - fract) * dval + fract * sval);
+                SET_DATA_BYTE(lined, j + x, dval);
+                break;
+            case 32:
+                dval32 = *(lined + j + x);
+                sval32 = *(lines + j);
+                extractRGBValues(dval32, &drval, &dgval, &dbval);
+                extractRGBValues(sval32, &srval, &sgval, &sbval);
+                drval = (l_int32)((1.0 - fract) * drval + fract * srval);
+                dgval = (l_int32)((1.0 - fract) * dgval + fract * sgval);
+                dbval = (l_int32)((1.0 - fract) * dbval + fract * sbval);
+                composeRGBPixel(drval, dgval, dbval, &dval32);
+                *(lined + j + x) = dval32;
+                break;
+            default:
+                return (PIX *)ERROR_PTR("impossible error", procName, NULL);
+            }
+        }
+    }
+
+    pixDestroy(&pixg2);
+    pixDestroy(&pix2);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                Blending background to a specific color              *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixBlendBackgroundToColor()
+ *
+ *      Input:  pixd (can be NULL or pixs)
+ *              pixs (32 bpp rgb)
+ *              box (region for blending; can be NULL))
+ *              color (32 bit color in 0xrrggbb00 format)
+ *              gamma, minval, maxval (args for grayscale TRC mapping)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) This in effect replaces light background pixels in pixs
+ *          by the input color.  It does it by alpha blending so that
+ *          there are no visible artifacts from hard cutoffs.
+ *      (2) If pixd == pixs, this is done in-place.
+ *      (3) If box == NULL, this is performed on all of pixs.
+ *      (4) The alpha component for blending is derived from pixs,
+ *          by converting to grayscale and enhancing with a TRC.
+ *      (5) The last three arguments specify the TRC operation.
+ *          Suggested values are: @gamma = 0.3, @minval = 50, @maxval = 200.
+ *          To skip the TRC, use @gamma == 1, @minval = 0, @maxval = 255.
+ *          See pixGammaTRC() for details.
+ */
+PIX *
+pixBlendBackgroundToColor(PIX       *pixd,
+                          PIX       *pixs,
+                          BOX       *box,
+                          l_uint32   color,
+                          l_float32  gamma,
+                          l_int32    minval,
+                          l_int32    maxval)
+{
+l_int32  x, y, w, h;
+BOX     *boxt;
+PIX     *pixt, *pixc, *pixr, *pixg;
+
+    PROCNAME("pixBlendBackgroundToColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, pixd);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd neither null nor pixs", procName, pixd);
+
+        /* Extract the (optionally cropped) region, pixr, and generate
+         * an identically sized pixc with the uniform color. */
+    if (!pixd)
+        pixd = pixCopy(NULL, pixs);
+    if (box) {
+        pixr = pixClipRectangle(pixd, box, &boxt);
+        boxGetGeometry(boxt, &x, &y, &w, &h);
+        pixc = pixCreate(w, h, 32);
+        boxDestroy(&boxt);
+    } else {
+        pixc = pixCreateTemplate(pixs);
+        pixr = pixClone(pixd);
+    }
+    pixSetAllArbitrary(pixc, color);
+
+        /* Set up the alpha channel */
+    pixg = pixConvertTo8(pixr, 0);
+    pixGammaTRC(pixg, pixg, gamma, minval, maxval);
+    pixSetRGBComponent(pixc, pixg, L_ALPHA_CHANNEL);
+
+        /* Blend and replace in pixd */
+    pixt = pixBlendWithGrayMask(pixr, pixc, NULL, 0, 0);
+    if (box) {
+        pixRasterop(pixd, x, y, w, h, PIX_SRC, pixt, 0, 0);
+        pixDestroy(&pixt);
+    } else {
+        pixTransferAllData(pixd, &pixt, 0, 0);
+    }
+
+    pixDestroy(&pixc);
+    pixDestroy(&pixr);
+    pixDestroy(&pixg);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                     Multiplying by a specific color                 *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixMultiplyByColor()
+ *
+ *      Input:  pixd (can be NULL or pixs)
+ *              pixs (32 bpp rgb)
+ *              box (region for filtering; can be NULL))
+ *              color (32 bit color in 0xrrggbb00 format)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) This filters all pixels in the specified region by
+ *          multiplying each component by the input color.
+ *          This leaves black invariant and transforms white to the
+ *          input color.
+ *      (2) If pixd == pixs, this is done in-place.
+ *      (3) If box == NULL, this is performed on all of pixs.
+ */
+PIX *
+pixMultiplyByColor(PIX       *pixd,
+                   PIX       *pixs,
+                   BOX       *box,
+                   l_uint32   color)
+{
+l_int32    i, j, bx, by, w, h, wpl;
+l_int32    red, green, blue, rval, gval, bval, nrval, ngval, nbval;
+l_float32  frval, fgval, fbval;
+l_uint32  *data, *line;
+PIX       *pixt;
+
+    PROCNAME("pixMultiplyByColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, pixd);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd neither null nor pixs", procName, pixd);
+
+    if (!pixd)
+        pixd = pixCopy(NULL, pixs);
+    if (box) {
+        boxGetGeometry(box, &bx, &by, NULL, NULL);
+        pixt = pixClipRectangle(pixd, box, NULL);
+    } else {
+        pixt = pixClone(pixd);
+    }
+
+        /* Multiply each pixel in pixt by the color */
+    extractRGBValues(color, &red, &green, &blue);
+    frval = (1. / 255.) * red;
+    fgval = (1. / 255.) * green;
+    fbval = (1. / 255.) * blue;
+    data = pixGetData(pixt);
+    wpl = pixGetWpl(pixt);
+    pixGetDimensions(pixt, &w, &h, NULL);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(line[j], &rval, &gval, &bval);
+            nrval = (l_int32)(frval * rval + 0.5);
+            ngval = (l_int32)(fgval * gval + 0.5);
+            nbval = (l_int32)(fbval * bval + 0.5);
+            composeRGBPixel(nrval, ngval, nbval, line + j);
+        }
+    }
+
+        /* Replace */
+    if (box)
+        pixRasterop(pixd, bx, by, w, h, PIX_SRC, pixt, 0, 0);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *       Rendering with alpha blending over a uniform background       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixAlphaBlendUniform()
+ *
+ *      Input:  pixs (32 bpp rgba, with alpha)
+ *              color (32 bit color in 0xrrggbb00 format)
+ *      Return: pixd (32 bpp rgb: pixs blended over uniform color @color),
+ *                    a clone of pixs if no alpha, and null on error
+ *
+ *  Notes:
+ *      (1) This is a convenience function that renders 32 bpp RGBA images
+ *          (with an alpha channel) over a uniform background of
+ *          value @color.  To render over a white background,
+ *          use @color = 0xffffff00.  The result is an RGB image.
+ *      (2) If pixs does not have an alpha channel, it returns a clone
+ *          of pixs.
+ */
+PIX *
+pixAlphaBlendUniform(PIX      *pixs,
+                     l_uint32  color)
+{
+PIX  *pixt, *pixd;
+
+    PROCNAME("pixAlphaBlendUniform");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (pixGetSpp(pixs) != 4) {
+        L_WARNING("no alpha channel; returning clone\n", procName);
+        return pixClone(pixs);
+    }
+
+    pixt = pixCreateTemplate(pixs);
+    pixSetAllArbitrary(pixt, color);
+    pixSetSpp(pixt, 3);  /* not required */
+    pixd = pixBlendWithGrayMask(pixt, pixs, NULL, 0, 0);
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                   Adding an alpha layer for blending                *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixAddAlphaToBlend()
+ *
+ *      Input:  pixs (any depth)
+ *              fract (fade fraction in the alpha component)
+ *              invert (1 to photometrically invert pixs)
+ *      Return: pixd (32 bpp with alpha), or null on error
+ *
+ *  Notes:
+ *      (1) This is a simple alpha layer generator, where typically white has
+ *          maximum transparency and black has minimum.
+ *      (2) If @invert == 1, generate the same alpha layer but invert
+ *          the input image photometrically.  This is useful for blending
+ *          over dark images, where you want dark regions in pixs, such
+ *          as text, to be lighter in the blended image.
+ *      (3) The fade @fract gives the minimum transparency (i.e.,
+ *          maximum opacity).  A small fraction is useful for adding
+ *          a watermark to an image.
+ *      (4) If pixs has a colormap, it is removed to rgb.
+ *      (5) If pixs already has an alpha layer, it is overwritten.
+ */
+PIX *
+pixAddAlphaToBlend(PIX       *pixs,
+                   l_float32  fract,
+                   l_int32    invert)
+{
+PIX  *pixd, *pix1, *pix2;
+
+    PROCNAME("pixAddAlphaToBlend");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (fract < 0.0 || fract > 1.0)
+        return (PIX *)ERROR_PTR("invalid fract", procName, NULL);
+
+        /* Convert to 32 bpp */
+    if (pixGetColormap(pixs))
+        pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+    else
+        pix1 = pixClone(pixs);
+    pixd = pixConvertTo32(pix1);  /* new */
+
+        /* Use an inverted image if this will be blended with a dark image */
+    if (invert) pixInvert(pixd, pixd);
+
+        /* Generate alpha layer */
+    pix2 = pixConvertTo8(pix1, 0);  /* new */
+    pixInvert(pix2, pix2);
+    pixMultConstantGray(pix2, fract);
+    pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL);
+
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    return pixd;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *    Setting a transparent alpha component over a white background    *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixSetAlphaOverWhite()
+ *
+ *      Input:  pixs (colormapped or 32 bpp rgb; no alpha)
+ *      Return: pixd (new pix with meaningful alpha component),
+ *                   or null on error
+ *
+ *  Notes:
+ *      (1) The generated alpha component is transparent over white
+ *          (background) pixels in pixs, and quickly grades to opaque
+ *          away from the transparent parts.  This is a cheap and
+ *          dirty alpha generator.  The 2 pixel gradation is useful
+ *          to blur the boundary between the transparent region
+ *          (that will render entirely from a backing image) and
+ *          the remainder which renders from pixs.
+ *      (2) All alpha component bits in pixs are overwritten.
+ */
+PIX *
+pixSetAlphaOverWhite(PIX  *pixs)
+{
+PIX  *pixd, *pix1, *pix2, *pix3, *pix4;
+
+    PROCNAME("pixSetAlphaOverWhite");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!(pixGetDepth(pixs) == 32 || pixGetColormap(pixs)))
+        return (PIX *)ERROR_PTR("pixs not 32 bpp or cmapped", procName, NULL);
+
+        /* Remove colormap if it exists; otherwise copy */
+    pixd = pixRemoveColormapGeneral(pixs, REMOVE_CMAP_TO_FULL_COLOR, L_COPY);
+
+        /* Generate a 1 bpp image where a white pixel in pixd is 0.
+         * In the comments below, a "white" pixel refers to pixd.
+         * pix1 is rgb, pix2 is 8 bpp gray, pix3 is 1 bpp. */
+    pix1 = pixInvert(NULL, pixd);  /* send white (255) to 0 for each sample */
+    pix2 = pixConvertRGBToGrayMinMax(pix1, L_CHOOSE_MAX);  /* 0 if white */
+    pix3 = pixThresholdToBinary(pix2, 1);  /* sets white pixels to 1 */
+    pixInvert(pix3, pix3);  /* sets white pixels to 0 */
+
+        /* Generate the alpha component using the distance transform,
+         * which measures the distance to the nearest bg (0) pixel in pix3.
+         * After multiplying by 128, its value is 0 (transparent)
+         * over white pixels, and goes to opaque (255) two pixels away
+         * from the nearest white pixel. */
+    pix4 = pixDistanceFunction(pix3, 8, 8, L_BOUNDARY_FG);
+    pixMultConstantGray(pix4, 128.0);
+    pixSetRGBComponent(pixd, pix4, L_ALPHA_CHANNEL);
+
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    pixDestroy(&pix3);
+    pixDestroy(&pix4);
+    return pixd;
+}
diff --git a/src/bmf.c b/src/bmf.c
new file mode 100644 (file)
index 0000000..93e7555
--- /dev/null
+++ b/src/bmf.c
@@ -0,0 +1,856 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  bmf.c
+ *
+ *   Acquisition and generation of bitmap fonts.
+ *
+ *       L_BMF           *bmfCreate()
+ *       L_BMF           *bmfDestroy()
+ *
+ *       PIX             *bmfGetPix()
+ *       l_int32          bmfGetWidth()
+ *       l_int32          bmfGetBaseline()
+ *
+ *       PIXA            *pixaGetFont()
+ *       l_int32          pixaSaveFont()
+ *       PIXA            *pixaGenerateFontFromFile()
+ *       PIXA            *pixaGenerateFontFromString()
+ *       PIXA            *pixaGenerateFont()
+ *       static l_int32   pixGetTextBaseline()
+ *       static l_int32   bmfMakeAsciiTables()
+ *
+ *   This is not a very general utility, because it only uses bitmap
+ *   representations of a single font, Palatino-Roman, with the
+ *   normal style.  It uses bitmaps generated for nine sizes, from
+ *   4 to 20 pts, rendered at 300 ppi.  Generalization to different
+ *   fonts, styles and sizes is straightforward.
+ *
+ *   I chose Palatino-Roman is because I like it.
+ *   The input font images were generated from a set of small
+ *   PostScript files, such as chars-12.ps, which were rendered
+ *   into the inputfont[] bitmap files using GhostScript.  See, for
+ *   example, the bash script prog/ps2tiff, which will "rip" a
+ *   PostScript file into a set of ccitt-g4 compressed tiff files.
+ *
+ *   The set of ascii characters from 32 through 126 are the 95
+ *   printable ascii chars.  Palatino-Roman is missing char 92, '\'.
+ *   I have substituted an LR flip of '/', char 47, for 92, so that
+ *   there are no missing printable chars in this set.  The space is
+ *   char 32, and I have given it a width equal to twice the width of '!'.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+#include "bmfdata.h"
+
+static const l_float32  VERT_FRACT_SEP = 0.3;
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_BASELINE     0
+#define  DEBUG_CHARS        0
+#define  DEBUG_FONT_GEN     0
+#endif  /* ~NO_CONSOLE_IO */
+
+static l_int32 pixGetTextBaseline(PIX *pixs, l_int32 *tab8, l_int32 *py);
+static l_int32 bmfMakeAsciiTables(L_BMF *bmf);
+
+
+/*---------------------------------------------------------------------*/
+/*                           Bmf create/destroy                        */
+/*---------------------------------------------------------------------*/
+/*!
+ *  bmfCreate()
+ *
+ *      Input:  dir (<optional> directory holding pixa of character set)
+ *              fontsize (4, 6, 8, ... , 20)
+ *      Return: bmf (holding the bitmap font and associated information)
+ *
+ *  Notes:
+ *      (1) If @dir == null, this generates the font bitmaps from a
+ *          compiled string.
+ *      (2) Otherwise, this tries to read a pre-computed pixa file with the
+ *          95 ascii chars in it.  If the file is not found, it then
+ *          attempts to generate the pixa and associated baseline
+ *          data from a tiff image containing all the characters.  If
+ *          that fails, it uses the compiled string.
+ */
+L_BMF *
+bmfCreate(const char  *dir,
+          l_int32      fontsize)
+{
+L_BMF   *bmf;
+PIXA  *pixa;
+
+    PROCNAME("bmfCreate");
+
+    if (fontsize < 4 || fontsize > 20 || (fontsize % 2))
+        return (L_BMF *)ERROR_PTR("fontsize must be in {4, 6, ..., 20}",
+                                  procName, NULL);
+    if ((bmf = (L_BMF *)LEPT_CALLOC(1, sizeof(L_BMF))) == NULL)
+        return (L_BMF *)ERROR_PTR("bmf not made", procName, NULL);
+
+    if (!dir) {  /* Generate from a string */
+        L_INFO("Generating pixa of bitmap fonts from string\n", procName);
+        pixa = pixaGenerateFontFromString(fontsize, &bmf->baseline1,
+                                          &bmf->baseline2, &bmf->baseline3);
+    } else {  /* Look for the pixa in a directory */
+        L_INFO("Locating pixa of bitmap fonts in a file\n", procName);
+        pixa = pixaGetFont(dir, fontsize, &bmf->baseline1, &bmf->baseline2,
+                           &bmf->baseline3);
+        if (!pixa) {  /* Not found; make it from a file */
+            L_INFO("Generating pixa of bitmap fonts from file\n", procName);
+            pixa = pixaGenerateFontFromFile(dir, fontsize, &bmf->baseline1,
+                                            &bmf->baseline2, &bmf->baseline3);
+            if (!pixa) {  /* Not made; make it from a string after all */
+                L_ERROR("Failed to make font; use string\n", procName);
+                pixa = pixaGenerateFontFromString(fontsize, &bmf->baseline1,
+                                          &bmf->baseline2, &bmf->baseline3);
+            }
+        }
+    }
+
+    if (!pixa) {
+        bmfDestroy(&bmf);
+        return (L_BMF *)ERROR_PTR("font pixa not made", procName, NULL);
+    }
+
+    bmf->pixa = pixa;
+    bmf->size = fontsize;
+    if (dir) bmf->directory = stringNew(dir);
+    bmfMakeAsciiTables(bmf);
+    return bmf;
+}
+
+
+/*!
+ *  bmfDestroy()
+ *
+ *      Input:  &bmf (<set to null>)
+ *      Return: void
+ */
+void
+bmfDestroy(L_BMF  **pbmf)
+{
+L_BMF  *bmf;
+
+    PROCNAME("bmfDestroy");
+
+    if (pbmf == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((bmf = *pbmf) == NULL)
+        return;
+
+    pixaDestroy(&bmf->pixa);
+    LEPT_FREE(bmf->directory);
+    LEPT_FREE(bmf->fonttab);
+    LEPT_FREE(bmf->baselinetab);
+    LEPT_FREE(bmf->widthtab);
+    LEPT_FREE(bmf);
+    *pbmf = NULL;
+    return;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*                             Bmf accessors                           */
+/*---------------------------------------------------------------------*/
+/*!
+ *  bmfGetPix()
+ *
+ *      Input:  bmf
+ *              chr (should be one of the 95 supported printable bitmaps)
+ *      Return: pix (clone of pix in bmf), or null on error
+ */
+PIX *
+bmfGetPix(L_BMF  *bmf,
+          char    chr)
+{
+l_int32  i, index;
+PIXA    *pixa;
+
+    PROCNAME("bmfGetPix");
+
+    if ((index = (l_int32)chr) == 10)  /* NL */
+        return NULL;
+    if (!bmf)
+        return (PIX *)ERROR_PTR("bmf not defined", procName, NULL);
+
+    i = bmf->fonttab[index];
+    if (i == UNDEF) {
+        L_ERROR("no bitmap representation for %d\n", procName, index);
+        return NULL;
+    }
+
+    if ((pixa = bmf->pixa) == NULL)
+        return (PIX *)ERROR_PTR("pixa not found", procName, NULL);
+
+    return pixaGetPix(pixa, i, L_CLONE);
+}
+
+
+/*!
+ *  bmfGetWidth()
+ *
+ *      Input:  bmf
+ *              chr (should be one of the 95 supported bitmaps)
+ *              &w (<return> character width; -1 if not printable)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+bmfGetWidth(L_BMF    *bmf,
+            char      chr,
+            l_int32  *pw)
+{
+l_int32  i, index;
+PIXA    *pixa;
+
+    PROCNAME("bmfGetWidth");
+
+    if (!pw)
+        return ERROR_INT("&w not defined", procName, 1);
+    *pw = -1;
+    if (!bmf)
+        return ERROR_INT("bmf not defined", procName, 1);
+    if ((index = (l_int32)chr) == 10)  /* NL */
+        return 0;
+
+    i = bmf->fonttab[index];
+    if (i == UNDEF) {
+        L_ERROR("no bitmap representation for %d\n", procName, index);
+        return 1;
+    }
+
+    if ((pixa = bmf->pixa) == NULL)
+        return ERROR_INT("pixa not found", procName, 1);
+
+    return pixaGetPixDimensions(pixa, i, pw, NULL, NULL);
+}
+
+
+/*!
+ *  bmfGetBaseline()
+ *
+ *      Input:  bmf
+ *              chr (should be one of the 95 supported bitmaps)
+ *              &baseline (<return>; distance below UL corner of bitmap char)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+bmfGetBaseline(L_BMF    *bmf,
+               char      chr,
+               l_int32  *pbaseline)
+{
+l_int32  bl, index;
+
+    PROCNAME("bmfGetBaseline");
+
+    if (!pbaseline)
+        return ERROR_INT("&baseline not defined", procName, 1);
+    *pbaseline = 0;
+    if (!bmf)
+        return ERROR_INT("bmf not defined", procName, 1);
+    if ((index = (l_int32)chr) == 10)  /* NL */
+        return 0;
+
+    bl = bmf->baselinetab[index];
+    if (bl == UNDEF) {
+        L_ERROR("no bitmap representation for %d\n", procName, index);
+        return 1;
+    }
+
+    *pbaseline = bl;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*               Font bitmap acquisition and generation                */
+/*---------------------------------------------------------------------*/
+/*!
+ *  pixaGetFont()
+ *
+ *      Input:  dir (directory holding pixa of character set)
+ *              fontsize (4, 6, 8, ... , 20)
+ *              &bl1 (<return> baseline of row 1)
+ *              &bl2 (<return> baseline of row 2)
+ *              &bl3 (<return> baseline of row 3)
+ *      Return: pixa of font bitmaps for 95 characters, or null on error
+ *
+ *  Notes:
+ *      (1) This reads a pre-computed pixa file with the 95 ascii chars.
+ */
+PIXA *
+pixaGetFont(const char  *dir,
+            l_int32      fontsize,
+            l_int32     *pbl0,
+            l_int32     *pbl1,
+            l_int32     *pbl2)
+{
+char     *pathname;
+l_int32   fileno;
+PIXA     *pixa;
+
+    PROCNAME("pixaGetFont");
+
+    fileno = (fontsize / 2) - 2;
+    if (fileno < 0 || fileno > NUM_FONTS)
+        return (PIXA *)ERROR_PTR("font size invalid", procName, NULL);
+    if (!pbl0 || !pbl1 || !pbl2)
+        return (PIXA *)ERROR_PTR("&bl not all defined", procName, NULL);
+    *pbl0 = baselines[fileno][0];
+    *pbl1 = baselines[fileno][1];
+    *pbl2 = baselines[fileno][2];
+
+    pathname = genPathname(dir, outputfonts[fileno]);
+    pixa = pixaRead(pathname);
+    LEPT_FREE(pathname);
+
+    if (!pixa)
+        L_WARNING("pixa of char bitmaps not found\n", procName);
+    return pixa;
+}
+
+
+/*!
+ *  pixaSaveFont()
+ *
+ *      Input:  indir (<optional> directory holding image of character set)
+ *              outdir (directory into which the output pixa file
+ *                      will be written)
+ *              fontsize (in pts, at 300 ppi)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This saves a font of a particular size.
+ *      (2) If @dir == null, this generates the font bitmaps from a
+ *          compiled string.
+ *      (3) prog/genfonts calls this function for each of the
+ *          nine font sizes, to generate all the font pixa files.
+ */
+l_int32
+pixaSaveFont(const char  *indir,
+             const char  *outdir,
+             l_int32      fontsize)
+{
+char    *pathname;
+l_int32  bl1, bl2, bl3;
+PIXA    *pixa;
+
+    PROCNAME("pixaSaveFont");
+
+    if (fontsize < 4 || fontsize > 20 || (fontsize % 2))
+        return ERROR_INT("fontsize must be in {4, 6, ..., 20}", procName, 1);
+
+    if (!indir) {  /* Generate from a string */
+        L_INFO("Generating pixa of bitmap fonts from string\n", procName);
+        pixa = pixaGenerateFontFromString(fontsize, &bl1, &bl2, &bl3);
+    } else {  /* Generate from an image file */
+        L_INFO("Generating pixa of bitmap fonts from a file\n", procName);
+        pixa = pixaGenerateFontFromFile(indir, fontsize, &bl1, &bl2, &bl3);
+    }
+    if (!pixa)
+        return ERROR_INT("pixa not made", procName, 1);
+
+    pathname = genPathname(outdir, outputfonts[(fontsize - 4) / 2]);
+    pixaWrite(pathname, pixa);
+
+#if  DEBUG_FONT_GEN
+    L_INFO("Found %d chars in font size %d\n", procName, pixaGetCount(pixa),
+           fontsize);
+    L_INFO("Baselines are at: %d, %d, %d\n", procName, bl1, bl2, bl3);
+#endif  /* DEBUG_FONT_GEN */
+
+    LEPT_FREE(pathname);
+    pixaDestroy(&pixa);
+    return 0;
+}
+
+
+/*!
+ *  pixaGenerateFontFromFile()
+ *
+ *      Input:  dir (directory holding image of character set)
+ *              fontsize (4, 6, 8, ... , 20, in pts at 300 ppi)
+ *              &bl1 (<return> baseline of row 1)
+ *              &bl2 (<return> baseline of row 2)
+ *              &bl3 (<return> baseline of row 3)
+ *      Return: pixa of font bitmaps for 95 characters, or null on error
+ *
+ *  These font generation functions use 9 sets, each with bitmaps
+ *  of 94 ascii characters, all in Palatino-Roman font.
+ *  Each input bitmap has 3 rows of characters.  The range of
+ *  ascii values in each row is as follows:
+ *    row 0:  32-57   (32 is a space)
+ *    row 1:  58-91   (92, '\', is not represented in this font)
+ *    row 2:  93-126
+ *  We LR flip the '/' char to generate a bitmap for the missing
+ *  '\' character, so that we have representations of all 95
+ *  printable chars.
+ *
+ *  Typically, use pixaGetFont() to generate the character bitmaps
+ *  in memory for a bmf.  This will simply access the bitmap files
+ *  in a serialized pixa that were produced in prog/genfonts.c using
+ *  this function.
+ */
+PIXA *
+pixaGenerateFontFromFile(const char  *dir,
+                         l_int32      fontsize,
+                         l_int32     *pbl0,
+                         l_int32     *pbl1,
+                         l_int32     *pbl2)
+{
+char    *pathname;
+l_int32  fileno;
+PIX     *pix;
+PIXA    *pixa;
+
+    PROCNAME("pixaGenerateFontFromFile");
+
+    if (!pbl0 || !pbl1 || !pbl2)
+        return (PIXA *)ERROR_PTR("&bl not all defined", procName, NULL);
+    *pbl0 = *pbl1 = *pbl2 = 0;
+    if (!dir)
+        return (PIXA *)ERROR_PTR("dir not defined", procName, NULL);
+    fileno = (fontsize / 2) - 2;
+    if (fileno < 0 || fileno > NUM_FONTS)
+        return (PIXA *)ERROR_PTR("font size invalid", procName, NULL);
+
+    pathname = genPathname(dir, inputfonts[fileno]);
+    pix = pixRead(pathname);
+    LEPT_FREE(pathname);
+    if (!pix)
+        return (PIXA *)ERROR_PTR("pix not all defined", procName, NULL);
+
+    pixa = pixaGenerateFont(pix, fontsize, pbl0, pbl1, pbl2);
+    pixDestroy(&pix);
+    return pixa;
+}
+
+
+/*!
+ *  pixaGenerateFontFromString()
+ *
+ *      Input:  fontsize (4, 6, 8, ... , 20, in pts at 300 ppi)
+ *              &bl1 (<return> baseline of row 1)
+ *              &bl2 (<return> baseline of row 2)
+ *              &bl3 (<return> baseline of row 3)
+ *      Return: pixa of font bitmaps for 95 characters, or null on error
+ *
+ *  Notes:
+ *      (1) See pixaGenerateFontFromFile() for details.
+ */
+PIXA *
+pixaGenerateFontFromString(l_int32   fontsize,
+                           l_int32  *pbl0,
+                           l_int32  *pbl1,
+                           l_int32  *pbl2)
+{
+l_uint8  *data;
+l_int32   redsize, nbytes;
+PIX      *pix;
+PIXA     *pixa;
+
+    PROCNAME("pixaGenerateFontFromString");
+
+    if (!pbl0 || !pbl1 || !pbl2)
+        return (PIXA *)ERROR_PTR("&bl not all defined", procName, NULL);
+    *pbl0 = *pbl1 = *pbl2 = 0;
+    redsize = (fontsize / 2) - 2;
+    if (redsize < 0 || redsize > NUM_FONTS)
+        return (PIXA *)ERROR_PTR("invalid font size", procName, NULL);
+
+    if (fontsize == 4) {
+        data = decodeBase64(fontdata_4, strlen(fontdata_4), &nbytes);
+    } else if (fontsize == 6) {
+        data = decodeBase64(fontdata_6, strlen(fontdata_6), &nbytes);
+    } else if (fontsize == 8) {
+        data = decodeBase64(fontdata_8, strlen(fontdata_8), &nbytes);
+    } else if (fontsize == 10) {
+        data = decodeBase64(fontdata_10, strlen(fontdata_10), &nbytes);
+    } else if (fontsize == 12) {
+        data = decodeBase64(fontdata_12, strlen(fontdata_12), &nbytes);
+    } else if (fontsize == 14) {
+        data = decodeBase64(fontdata_14, strlen(fontdata_14), &nbytes);
+    } else if (fontsize == 16) {
+        data = decodeBase64(fontdata_16, strlen(fontdata_16), &nbytes);
+    } else if (fontsize == 18) {
+        data = decodeBase64(fontdata_18, strlen(fontdata_18), &nbytes);
+    } else {  /* fontsize == 20 */
+        data = decodeBase64(fontdata_20, strlen(fontdata_20), &nbytes);
+    }
+    if (!data)
+        return (PIXA *)ERROR_PTR("data not made", procName, NULL);
+
+    pix = pixReadMem(data, nbytes);
+    LEPT_FREE(data);
+    if (!pix)
+        return (PIXA *)ERROR_PTR("pix not made", procName, NULL);
+
+    pixa = pixaGenerateFont(pix, fontsize, pbl0, pbl1, pbl2);
+    pixDestroy(&pix);
+    return pixa;
+}
+
+
+/*!
+ *  pixaGenerateFont()
+ *
+ *      Input:  pix (of 95 characters in 3 rows)
+ *              fontsize (4, 6, 8, ... , 20, in pts at 300 ppi)
+ *              &bl1 (<return> baseline of row 1)
+ *              &bl2 (<return> baseline of row 2)
+ *              &bl3 (<return> baseline of row 3)
+ *      Return: pixa of font bitmaps for 95 characters, or null on error
+ *
+ *  Notes:
+ *      (1) This does all the work.  See pixaGenerateFontFromFile()
+ *          for an overview.
+ *      (2) The pix is for one of the 9 fonts.  @fontsize is only
+ *          used here for debugging.
+ */
+PIXA *
+pixaGenerateFont(PIX      *pixs,
+                 l_int32   fontsize,
+                 l_int32  *pbl0,
+                 l_int32  *pbl1,
+                 l_int32  *pbl2)
+{
+l_int32   i, j, nrows, nrowchars, nchars, h, yval;
+l_int32   width, height;
+l_int32   baseline[3];
+l_int32  *tab = NULL;
+BOX      *box, *box1, *box2;
+BOXA     *boxar, *boxac, *boxacs;
+PIX      *pix1, *pix2, *pixr, *pixrc, *pixc;
+PIXA     *pixa;
+l_int32   n, w, inrow, top;
+l_int32  *ia;
+NUMA     *na;
+
+    PROCNAME("pixaGenerateFont");
+
+    if (!pbl0 || !pbl1 || !pbl2)
+        return (PIXA *)ERROR_PTR("&bl not all defined", procName, NULL);
+    *pbl0 = *pbl1 = *pbl2 = 0;
+    if (!pixs)
+        return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
+
+        /* Locate the 3 rows of characters */
+    w = pixGetWidth(pixs);
+    na = pixCountPixelsByRow(pixs, NULL);
+    boxar = boxaCreate(0);
+    n = numaGetCount(na);
+    ia = numaGetIArray(na);
+    inrow = 0;
+    for (i = 0; i < n; i++) {
+        if (!inrow && ia[i] > 0) {
+            inrow = 1;
+            top = i;
+        } else if (inrow && ia[i] == 0) {
+            inrow = 0;
+            box = boxCreate(0, top, w, i - top);
+            boxaAddBox(boxar, box, L_INSERT);
+        }
+    }
+    LEPT_FREE(ia);
+    numaDestroy(&na);
+    nrows = boxaGetCount(boxar);
+#if  DEBUG_FONT_GEN
+    L_INFO("For fontsize %s, have %d rows\n", procName, fontsize, nrows);
+#endif  /* DEBUG_FONT_GEN */
+    if (nrows != 3) {
+        L_INFO("nrows = %d; skipping fontsize %d\n", procName, nrows, fontsize);
+        return (PIXA *)ERROR_PTR("3 rows not generated", procName, NULL);
+    }
+
+        /* Grab the character images and baseline data */
+#if DEBUG_BASELINE
+    lept_rmdir("baseline");
+    lept_mkdir("baseline");
+#endif  /* DEBUG_BASELINE */
+    tab = makePixelSumTab8();
+    pixa = pixaCreate(95);
+    for (i = 0; i < nrows; i++) {
+        box = boxaGetBox(boxar, i, L_CLONE);
+        pixr = pixClipRectangle(pixs, box, NULL);  /* row of chars */
+        pixGetTextBaseline(pixr, tab, &yval);
+        baseline[i] = yval;
+
+#if DEBUG_BASELINE
+        L_INFO("Baseline info: row %d, yval = %d, h = %d\n", procName,
+               i, yval, pixGetHeight(pixr));
+        pix1 = pixCopy(NULL, pixr);
+        pixRenderLine(pix1, 0, yval, pixGetWidth(pix1), yval, 1,
+                      L_FLIP_PIXELS);
+        if (i == 0 )
+            pixWrite("/tmp/baseline/row0.png", pix1, IFF_PNG);
+        else if (i == 1)
+            pixWrite("/tmp/baseline/row1.png", pix1, IFF_PNG);
+        else
+            pixWrite("/tmp/baseline/row2.png", pix1, IFF_PNG);
+        pixDestroy(&pix1);
+#endif  /* DEBUG_BASELINE */
+
+        boxDestroy(&box);
+        pixrc = pixCloseSafeBrick(NULL, pixr, 1, 35);
+        boxac = pixConnComp(pixrc, NULL, 8);
+        boxacs = boxaSort(boxac, L_SORT_BY_X, L_SORT_INCREASING, NULL);
+        if (i == 0) {  /* consolidate the two components of '"' */
+            box1 = boxaGetBox(boxacs, 1, L_CLONE);
+            box2 = boxaGetBox(boxacs, 2, L_CLONE);
+            box1->w = box2->x + box2->w - box1->x;  /* increase width */
+            boxDestroy(&box1);
+            boxDestroy(&box2);
+            boxaRemoveBox(boxacs, 2);
+        }
+        h = pixGetHeight(pixr);
+        nrowchars = boxaGetCount(boxacs);
+        for (j = 0; j < nrowchars; j++) {
+            box = boxaGetBox(boxacs, j, L_COPY);
+            if (box->w <= 2 && box->h == 1) {  /* skip 1x1, 2x1 components */
+                boxDestroy(&box);
+                continue;
+            }
+            box->y = 0;
+            box->h = h - 1;
+            pixc = pixClipRectangle(pixr, box, NULL);
+            boxDestroy(&box);
+            if (i == 0 && j == 0)  /* add a pix for the space; change later */
+                pixaAddPix(pixa, pixc, L_COPY);
+            if (i == 2 && j == 0)  /* add a pix for the '\'; change later */
+                pixaAddPix(pixa, pixc, L_COPY);
+            pixaAddPix(pixa, pixc, L_INSERT);
+        }
+        pixDestroy(&pixr);
+        pixDestroy(&pixrc);
+        boxaDestroy(&boxac);
+        boxaDestroy(&boxacs);
+    }
+    LEPT_FREE(tab);
+
+    nchars = pixaGetCount(pixa);
+    if (nchars != 95)
+        return (PIXA *)ERROR_PTR("95 chars not generated", procName, NULL);
+
+    *pbl0 = baseline[0];
+    *pbl1 = baseline[1];
+    *pbl2 = baseline[2];
+
+        /* Fix the space character up; it should have no ON pixels,
+         * and be about twice as wide as the '!' character.    */
+    pix1 = pixaGetPix(pixa, 0, L_CLONE);
+    width = 2 * pixGetWidth(pix1);
+    height = pixGetHeight(pix1);
+    pixDestroy(&pix1);
+    pix1 = pixCreate(width, height, 1);
+    pixaReplacePix(pixa, 0, pix1, NULL);
+
+        /* Fix up the '\' character; use a LR flip of the '/' char */
+    pix1 = pixaGetPix(pixa, 15, L_CLONE);
+    pix2 = pixFlipLR(NULL, pix1);
+    pixDestroy(&pix1);
+    pixaReplacePix(pixa, 60, pix2, NULL);
+
+#if DEBUG_CHARS
+    pix1 = pixaDisplayTiled(pixa, 1500, 0, 10);
+    pixDisplay(pix1, 100 * i, 200);
+    pixDestroy(&pix1);
+#endif  /* DEBUG_CHARS */
+
+    boxaDestroy(&boxar);
+    return pixa;
+}
+
+
+/*!
+ *  pixGetTextBaseline()
+ *
+ *      Input:  pixs (1 bpp, one textline character set)
+ *              tab8 (<optional> pixel sum table)
+ *              &y   (<return> baseline value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Method: find the largest difference in pixel sums from one
+ *          raster line to the next one below it.  The baseline is the
+ *          upper raster line for the pair of raster lines that
+ *          maximizes this function.
+ */
+static l_int32
+pixGetTextBaseline(PIX      *pixs,
+                   l_int32  *tab8,
+                   l_int32  *py)
+{
+l_int32   i, h, val1, val2, diff, diffmax, ymax;
+l_int32  *tab;
+NUMA     *na;
+
+    PROCNAME("pixGetTextBaseline");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!py)
+        return ERROR_INT("&y not defined", procName, 1);
+    *py = 0;
+    if (!tab8)
+        tab = makePixelSumTab8();
+    else
+        tab = tab8;
+
+    na = pixCountPixelsByRow(pixs, tab);
+    h = numaGetCount(na);
+    diffmax = 0;
+    ymax = 0;
+    for (i = 1; i < h; i++) {
+        numaGetIValue(na, i - 1, &val1);
+        numaGetIValue(na, i, &val2);
+        diff = L_MAX(0, val1 - val2);
+        if (diff > diffmax) {
+            diffmax = diff;
+            ymax = i - 1;  /* upper raster line */
+        }
+    }
+    *py = ymax;
+
+    if (!tab8)
+        LEPT_FREE(tab);
+    numaDestroy(&na);
+    return 0;
+}
+
+
+/*!
+ *  bmfMakeAsciiTables
+ *
+ *      Input:  bmf
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This makes three tables, each of size 128, as follows:
+ *          - fonttab is a table containing the index of the Pix
+ *            that corresponds to each input ascii character;
+ *            it maps (ascii-index) --> Pixa index
+ *          - baselinetab is a table containing the baseline offset
+ *            for the Pix that corresponds to each input ascii character;
+ *            it maps (ascii-index) --> baseline offset
+ *          - widthtab is a table containing the character width in
+ *            pixels for the Pix that corresponds to that character;
+ *            it maps (ascii-index) --> bitmap width
+ *     (2) This also computes
+ *          - lineheight (sum of maximum character extensions above and
+ *                        below the baseline)
+ *          - kernwidth (spacing between characters within a word)
+ *          - spacewidth (space between words)
+ *          - vertlinesep (extra vertical spacing between textlines)
+ *     (3) The baselines apply as follows:
+ *          baseline1   (ascii 32 - 57), ascii 92
+ *          baseline2   (ascii 58 - 91)
+ *          baseline3   (ascii 93 - 126)
+ *     (4) The only array in bmf that is not ascii-based is the
+ *         array of bitmaps in the pixa, which starts at ascii 32.
+ */
+static l_int32
+bmfMakeAsciiTables(L_BMF  *bmf)
+{
+l_int32   i, maxh, height, charwidth, xwidth, kernwidth;
+l_int32  *fonttab, *baselinetab, *widthtab;
+PIX      *pix;
+
+    PROCNAME("bmfMakeAsciiTables");
+
+    if (!bmf)
+        return ERROR_INT("bmf not defined", procName, 1);
+
+        /* First get the fonttab; we use this later for the char widths */
+    if ((fonttab = (l_int32 *)LEPT_CALLOC(128, sizeof(l_int32))) == NULL)
+        return ERROR_INT("fonttab not made", procName, 1);
+    bmf->fonttab = fonttab;
+    for (i = 0; i < 128; i++)
+        fonttab[i] = UNDEF;
+    for (i = 32; i < 127; i++)
+        fonttab[i] = i - 32;
+
+    if ((baselinetab = (l_int32 *)LEPT_CALLOC(128, sizeof(l_int32))) == NULL)
+        return ERROR_INT("baselinetab not made", procName, 1);
+    bmf->baselinetab = baselinetab;
+    for (i = 0; i < 128; i++)
+        baselinetab[i] = UNDEF;
+    for (i = 32; i <= 57; i++)
+        baselinetab[i] = bmf->baseline1;
+    for (i = 58; i <= 91; i++)
+        baselinetab[i] = bmf->baseline2;
+    baselinetab[92] = bmf->baseline1;  /* the '\' char */
+    for (i = 93; i < 127; i++)
+        baselinetab[i] = bmf->baseline3;
+
+        /* Generate array of character widths; req's fonttab to exist */
+    if ((widthtab = (l_int32 *)LEPT_CALLOC(128, sizeof(l_int32))) == NULL)
+        return ERROR_INT("widthtab not made", procName, 1);
+    bmf->widthtab = widthtab;
+    for (i = 0; i < 128; i++)
+        widthtab[i] = UNDEF;
+    for (i = 32; i < 127; i++) {
+        bmfGetWidth(bmf, i, &charwidth);
+        widthtab[i] = charwidth;
+    }
+
+        /* Get the line height of text characters, from the highest
+         * ascender to the lowest descender; req's fonttab to exist. */
+    pix =  bmfGetPix(bmf, 32);
+    maxh =  pixGetHeight(pix);
+    pixDestroy(&pix);
+    pix =  bmfGetPix(bmf, 58);
+    height =  pixGetHeight(pix);
+    pixDestroy(&pix);
+    maxh = L_MAX(maxh, height);
+    pix =  bmfGetPix(bmf, 93);
+    height =  pixGetHeight(pix);
+    pixDestroy(&pix);
+    maxh = L_MAX(maxh, height);
+    bmf->lineheight = maxh;
+
+        /* Get the kern width (distance between characters).
+         * We let it be the same for all characters in a given
+         * font size, and scale it linearly with the size;
+         * req's fonttab to be built first. */
+    bmfGetWidth(bmf, 120, &xwidth);
+    kernwidth = (l_int32)(0.08 * (l_float32)xwidth + 0.5);
+    bmf->kernwidth = L_MAX(1, kernwidth);
+
+        /* Save the space width (between words) */
+    bmfGetWidth(bmf, 32, &charwidth);
+    bmf->spacewidth = charwidth;
+
+        /* Save the extra vertical space between lines */
+    bmf->vertlinesep = (l_int32)(VERT_FRACT_SEP * bmf->lineheight + 0.5);
+
+    return 0;
+}
diff --git a/src/bmf.h b/src/bmf.h
new file mode 100644 (file)
index 0000000..25d40f2
--- /dev/null
+++ b/src/bmf.h
@@ -0,0 +1,62 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_BMF_H
+#define  LEPTONICA_BMF_H
+
+/*
+ *  bmf.h
+ *
+ *     Simple data structure to hold bitmap fonts and related data
+ */
+
+    /* Constants for deciding when text block is divided into paragraphs */
+enum {
+    SPLIT_ON_LEADING_WHITE = 1,    /* tab or space at beginning of line   */
+    SPLIT_ON_BLANK_LINE    = 2,    /* newline with optional white space   */
+    SPLIT_ON_BOTH          = 3     /* leading white space or newline      */
+};
+
+
+struct L_Bmf
+{
+    struct Pixa  *pixa;        /* pixa of bitmaps for 93 characters        */
+    l_int32       size;        /* font size (in points at 300 ppi)         */
+    char         *directory;   /* directory containing font bitmaps        */
+    l_int32       baseline1;   /* baseline offset for ascii 33 - 57        */
+    l_int32       baseline2;   /* baseline offset for ascii 58 - 91        */
+    l_int32       baseline3;   /* baseline offset for ascii 93 - 126       */
+    l_int32       lineheight;  /* max height of line of chars              */
+    l_int32       kernwidth;   /* pixel dist between char bitmaps          */
+    l_int32       spacewidth;  /* pixel dist between word bitmaps          */
+    l_int32       vertlinesep; /* extra vertical space between text lines  */
+    l_int32      *fonttab;     /* table mapping ascii --> font index       */
+    l_int32      *baselinetab; /* table mapping ascii --> baseline offset  */
+    l_int32      *widthtab;    /* table mapping ascii --> char width       */
+};
+typedef struct L_Bmf L_BMF;
+
+#endif  /* LEPTONICA_BMF_H */
diff --git a/src/bmfdata.h b/src/bmfdata.h
new file mode 100644 (file)
index 0000000..6c46e6d
--- /dev/null
@@ -0,0 +1,634 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  bmfdata.h
+ *
+ *  This file contains data for constructing the bitmap fonts.
+ *
+ *  The fontdata string holds all 9 sets of bitmap fonts in a base64
+ *  encoding of a pixacomp representation of the tiff compressed images.
+ *  It was generated by prog/genfonts and pasted in.  This allows
+ *  the use of the bitmap fonts for iamge labelling without accessing
+ *  stored versions of either the tiff images for each set, or the pixa
+ *  of the 95 printable character images that was derived from the tiff image.
+ *
+ *  In use, to get the bmf for a specific font size, from the encoded
+ *  string in this file, call
+ *      bmfCreate(NULL, fontsize);
+ */
+
+#ifndef  LEPTONICA_BMFDATA_H
+#define  LEPTONICA_BMFDATA_H
+
+#define  NUM_FONTS  9
+static const char  *inputfonts[] = {"chars-4.tif", "chars-6.tif",
+                                    "chars-8.tif", "chars-10.tif",
+                                    "chars-12.tif", "chars-14.tif",
+                                    "chars-16.tif", "chars-18.tif",
+                                    "chars-20.tif"};
+static const char  *outputfonts[] = {"chars-4.pa", "chars-6.pa",
+                                     "chars-8.pa", "chars-10.pa",
+                                     "chars-12.pa", "chars-14.pa",
+                                     "chars-16.pa", "chars-18.pa",
+                                     "chars-20.pa"};
+static const l_int32  baselines[NUM_FONTS][3] = {{11, 12, 12}, {18, 18, 18},
+                                                 {24, 24, 24}, {30, 30, 30},
+                                                 {36, 36, 36}, {42, 42, 42},
+                                                 {48, 48, 48}, {54, 54, 54},
+                                                 {60, 60, 60}};
+
+static const char  fontdata_4[] =
+    "SUkqACYFAAAmoHICP///////////////////////kFcchgc45Bgc45AgcgxBY5DY5DY5Agcg"
+    "jkM45A8GocgxBA8M45BfCGgchhzOQxZBiNe/CDQRT6RQ+k4QV6BHcgvBBjCC+KoSjQI7wjj/"
+    "16I+EUPTpV0rI4LilVtAjjyPuR58jg3CRd6dJkcDMCj+v//qlVsMgQPVY6vugih9Lr/8RCF+"
+    "OqUUK6C/fHFV9RStf8MulG10fKcN6X+lXOBg+GexX71wxSPCf4/+kE0uR5zE0rtfCFg3oIp0"
+    "R+GF5DSmQaMS/oG1xen0X2wyh8WXwoI46VPt/kNYcf9J4h/pUHB///2H+t+lkCByDj/r9ZBX"
+    "H1BAtUr7u/IEOQanrS0eByO16tpVaSWtaEVsNiG66WrBgg05wM4bCYNWDCWIiDCER6HGhERE"
+    "RER3ZHBfXjaSQ7iOP/////////////////////////////////////////////////////+Q"
+    "JgK95DIDRZAjCDccgRMhn4g5yC9CD0IL+QxhuIfCCYQTC4IJhBiyLBB7J4QX4gvQgxxBehBi"
+    "yGDkPhdkEw1kPZY5cEHck5BIJOQc9aI+wjE7DL7RdsMu2GXoZehGDYaDCDQaDSCDQdIOGEEX"
+    "bDLzCLthl5ojzkeL0NMJhNNbVoJ6kclXuggyOGfugnw3vugv/0u+9IN7pBvdJ//brT3VtdLy"
+    "B4NxyGsOPRnv9R7xx3/9L+EU/3/f4jj/t+3TdDvkFZyC7hYdKkCCKHQI76SW/pD/6XCKdAin"
+    "29L9L6/9eEUOrD0kv8IIMNKkq/j/zD5h+P4r//99LfBKcDR9utK62NLxEIIhnmGGlpek3Lz/"
+    "jj5cv/ul7f+EvimH///0l6CENpfrHt/y9l7kr/4RT/f7f+PwRTkG7/tpav26XtrxoVI5/vSx"
+    "xsP/7ful7fdd1tv/7FRoj//DLgQZgQCFhlYlfv1kx9//28mPx/7ruu3/t9K3pEh/IKzkF3DL"
+    "g2BENDtBr9Jh4S12H/+3+17GwwltpbZBx0u0unr0v9IMjhrBYYpO0KZmDikMJsYTCDCeE2Gh"
+    "p6DTdiEE2KCdo8GcNj3pJsJofjiIiIiIiIiI4iIiIiIhhCIiIiIiIr1SMwyQbOkEiGQCvd4i"
+    "I//////////////////////////////////////////////////////+QVo7IEDkGwchpOQV"
+    "nIa0ENKCGhyC7kHchocgZschnHIMPtKk7oIP7ulv6f9Yj5DIDaH/3gjjr///+rI4aiIEXngg"
+    "RZBfCBEWQXsofKggu5DD5Y+Qw5UHghiCoIEYQw5VkCMIO5TkF7shhzOQxZ4IJZxy3IO5nIJZ"
+    "4IP//1iiPOGd0R+iPQgR3TQIIXZ3/S7BBnezui87MOiPbKHRHqftNNXvTTUjy/9JkcFjTpOk"
+    "9NsKmFTu+Etppw06VtMjhhO0OLCd3S+rSdIUvyDD+Iha8fQ//+K//3/+D/vbQRT7d9LsjhgI"
+    "7nH8Ivf/lw0bS/4RT////7f//pfq+lhr6/v/Yf/t//3/+D/sO2NNhpfiP66Xat8L/2//3S0r"
+    "XIMD/rvUEd9Isf/4Mp5wCDgYBlOzgO0fB3aem2mmnYTtipwCAZQ6DnAXDgynapwk20h/+IiI"
+    "iIy9ERxEREREZHDLiIiIiIjjj6kNWdP//qP/pMjhq8bSXwojsGkEwmliIiP/////////////"
+    "/////////////////////////wAQAQ4AAAEDAAEAAACSAwAAAQEDAAEAAAA2AgAAAgEDAAEA"
+    "AAABAAAAAwEDAAEAAAAEAAAABgEDAAEAAAABAAAAEQEEAAEAAAAIAAAAEgEDAAEAAAABAAAA"
+    "FQEDAAEAAAABAAAAFgEDAAEAAAA2AgAAFwEEAAEAAAAeBQAAGgEFAAEAAADUBQAAGwEFAAEA"
+    "AADcBQAAHAEDAAEAAAABAAAAKAEDAAEAAAACAAAAAAAAAAAAwBIAAAQAAADAEgAABAA=";
+
+static const char  fontdata_6[] =
+    "SUkqAMoGAAAmoHVf///////////////////////////////IZAUfsgeBdyGdyDjkMgI+QPKC"
+    "GIO5AhzOgyGiCMcgYtUrIKHohowhschs4hnwgXcgRQhsgguQQXwhov6/QYQI7qgRUUk2QIfV"
+    "F5hQmmugqCMTCBHj/9F8j9JuknWm7rSbCBFPLtou2sjhlBSOKkE3Qf3+kv9fpcMQaXY9PTwR"
+    "T6WvpX/0v19aVbeQ0D6X7+v/X//QIQfj6xSS4QLS3xx69IVtL/EQy8CvbSqhq4I7//pJeVnT"
+    "Dr/+Niloufj9fpJLxalYrDtdr2DGk/etf6CDrkduzQkw21/w2prRfYZcNbj1+kQMQuL03hF5"
+    "sQRT+CEMMj7pAjuk/5DVDINfr+k9b06Stj+GXgW6pN9/kNsdL/XQg/+nSx/0v20vxSv0v/S3"
+    "/yDA/19sV/6WkQ0D5DY/6+lkDyf/SX9h65BRBDTdJ/StLILuk2lWkl399U2kw0Thpa0r7S0U"
+    "A7S20rSVtJL/iGrFMSPJv+qYoEaA+KBA4pikmKCWIiDVCINaQ0KiIiIiIoFhoRfSodbS1xbp"
+    "Id0hx8f///////////////////////////////////////////////////IHMFnMgTA0hyGQ"
+    "G45DLcg0jkQfyGQDNxBv5DLcg3QQ2EEHDIEaEHDIaDkMTJzIeZBJkEmTwh5kNmEPhB7ITCGi"
+    "ZDOghsmQ0IIbJhHUEMzPAh8jYOeIuRsEZFHCZEHBDhdoww1DLm0bOGXGwZccGXHCMDgwQMED"
+    "BAwQMEi4ZwQdAg2GEEbYYZc2EbYYZcwwjB5dmDgwQMIMJoNbQNqHuRxF6I7YQIN+6BBrDf+E"
+    "E//pf3oEG9tAg3vC9//126bQWlXh0gyODd+l7fXwv/0u1gio0m90m916x9uu60nXXyB4G7kN"
+    "tx6JwU9oEU/4944qP/pcEU8EU+37f7f4j/q6q2tpDXhYaShBBDer1XfJD5IdL/0vtf9L9L//"
+    "ergin9JukvIHk5BiAggw+kn1fSr///9L3r2/fS30of9r1exWqXp4QQYaWl9XH/a2vH+l9/t/"
+    "6X58mgN//r07dJe04QRDYGGGgvpVeXb/jj5gT8X7r7f+CX6CDD/bp6bXY/xEIIQw16Xq8N/y"
+    "5ZcvT/Lp/de3/j+2QMd/r/p0l6CDdf0h73//ZF7/w37r99/fuD/vVq9SP3S9hpd+lLj/6444"
+    "a/9v7r39L0tt/7Xq9b0vDDIbAwQQu2ElKHq/fr3f/2/dfb39/b/V6jjSb1Io/hhiEFbEECFK"
+    "r/euRR+//28ivxXt913XZBcf/jaevr8geTkCHDDCCIF3bEk9XpN6X7f/7f7+xtpbaW+l2l9K"
+    "3pfpqGGEErBhJfCTBk4wl+wf/7f9fsMJba7cMJbDSa9JvSX2sPCwxCQYQaFBikIQQwQMMYIG"
+    "CBggeCBsNCgg3CBhBuGKBA2KBA24hAgbFdOlYIGh+NCIiIiIiIiI4iIiIhxEGCERERERER9L"
+    "GHfVBF0Tgtg0dSBoDTYk+h40PiP/////////////////////////////////////////////"
+    "//////5A887IHkOQbLIE8EFaCGvBBmsgosgaDcg3HIbHwaIbIvVVIZTkGHVUtv9IOHRHBU+D"
+    "g5DJBx//QRTr69fr/+3X+I+v/pa//v/9N0Q2XnshsshsjIaMyGjMhlOQIHycZAhyDUOQy+IZ"
+    "xzWQUWUOQYc7kGMyGdyTkH41kH4scnZB4JwQxhrIYp/64hF56DCLzBF4aLzQNF8+DyuCguuF"
+    "Kw/ApXIvMFTCI7FhU0XmgYUL/ap0tow3/6TdN2XCTpB0rVJqJHmHD6BYbNhoDEjzSbDDLhJo"
+    "NnHSdQ4cMJoMJQ0DpBphVC//x9v/ScMEkwqf9Lpp6dJum18cQwX3V9XXWv/pN9OkKX/9f6X1"
+    "1/TpdX+6umrDdRSS2yBGFv4iQZu/9D//4r//f/58CP3XI/p7pL9F9peEYv/zAF8NL/hFP///"
+    "/t/utrrutN6SQYr0F//7Ff+3////g3/11dJ+l+I/+ld7ey4KP+3//fpX5DOOD/3sb8j+6X/9"
+    "en1+v/b//dLr//Vuo0rY0ib//aphKGYdtAinbLfROC//Yf/8NKGEmwvaUOwvtK3SX/7DPcUG"
+    "NjhsUEHhBwwg8JuEGEGEHDCDhhiopiCKcIOKeJHTd8JNuh/+IiIiIsubERxEREREZcNKIiIi"
+    "IiNDj+En/X/IbQdf/+Cj/9Npd6SXq3WLDSrwSEdigkEGCDrEREf/////////////////////"
+    "///////4AIAIAA4AAAEDAAEAAABBBAAAAQEDAAEAAAA6AgAAAgEDAAEAAAABAAAAAwEDAAEA"
+    "AAAEAAAABgEDAAEAAAABAAAAEQEEAAEAAAAIAAAAEgEDAAEAAAABAAAAFQEDAAEAAAABAAAA"
+    "FgEDAAEAAAA6AgAAFwEEAAEAAADBBgAAGgEFAAEAAAB4BwAAGwEFAAEAAACABwAAHAEDAAEA"
+    "AAABAAAAKAEDAAEAAAACAAAAAAAAAAAAwBIAAAQAAADAEgAABAA=";
+
+static const char  fontdata_8[] =
+    "SUkqALIIAAAmoHcGf/////////////////////////////////kMgMsfUgeDaOQLjkHHIZAN"
+    "T5A8K5AiDQQ0OW7kMqCEHIZthNJkcMwuGQG8g34gYcgo8go4hmwQIDIGIIL1EGOIKO1/wRmG"
+    "cvBqEX3S3dBGJhUwmlQSpGINF2/9cIxkfa9U+k2Q2OlpNgqaNzWwgWk2k33Veluk2q6STadJ"
+    "U2jHlzcJtZcGlS4RJOt9f9f9L62GMw+vC0np5HXS/0n/6Vf9dapwxpdj7rr6Wl/f//v9dJLa"
+    "kG76X/XXpf//v/j62kl4I2i4ZVd8caX8UrS/xEgvV7aVMUP19f615+S7/6BmGXBh70tK21ev"
+    "60lxefkmGla/8WxVZM9Y31/RDYOEl5uappMV/1sGKhNfYX/1EOuEHiR57DbXfUMOieIxwZgN"
+    "vjpfrI7a9XQdJF9sSOv+QL+qLzSt//9IW6x6tUg21+Q2qpHnS3Tf5BtTkNSi/06710rYpeDM"
+    "MuBi6pNq3+QZX6/S0J8DHdUn8f+v3S/Fb9L/63r8hnH9f26/rS0sgXj9fXpV+vuP9X9Igofy"
+    "DD1el6WQPCR/pL+w7XIZUEGx660nS3V0vSrv/qm0m2UBr61T7S0dAd13XSTdBL+r0l6YYX+t"
+    "JtK1hhK7CTDCSthJLpeIpIMUGJHaf9rYohsQsQiBhDEIMQtiECCxESCjKESKPdDQqIiIiIig"
+    "sGhF1Wh16pfbSSrFtKh3odkcHWI/////////////////////////////////////////////"
+    "////5A7AyfkDqG265DJBRxDKmQanIZWpDKDIOnIaBhB05BQGQwgkcgiCCIIIglxBEEG/kGPI"
+    "J5DzIN6EG+pDKoQ2akDFCGBBBDkdCCUI5kE8iuRfIPxCwCZBHIYGMFhMI2w8M42COFBnCDIN"
+    "7JWQz2SsEcKQzwDBENEENkENkQRDRANwQNgwQRthhnDYRthgzZhhGG5cjZQYIGXDOCBhNYYW"
+    "k2rMBNcu2ECBhptBtAgdoGHQPQdFwTv+l6T4QIGG0Gwi4UOg2gg0777dNXg2gg9Qq+m0g37p"
+    "eG/8Jf/pd96Cb7Sb9f//1pvbS0vV0rT9L3/0v/0vWCKjV91fdJ//dK/0n1Xx6eXX0vvHGv/0"
+    "uXTkde9Jv0m//6+/T20rSevIZCggrxpErPFpX+O36j/6C/X2//7/Ecf95dUnSdIUvCsNLCCC"
+    "I6vvpL+RR8ij//pe3++lfpev+2l1ffdJeQPCOQ0OEEw9Un6+q3/0v/S/S9v/S/q//tfYp1S9"
+    "NMIIMNKkq1uwS////0vb/b9+t9KZg0fdL3Wm0v/CCDBpdfvF/wwsMLx/pfpff+Evz+ygMr9+"
+    "ldPdJe00EEQbpww0tV0rmDf8cfNhfxD9/2/8/foEw//f/Y0vEQQQgw6+l3wb/mB5gfoP8wn9"
+    "pe/+P4bBv90vfvS9Ag2l10lff++//7fv+3/3+Qau/vtK0kXTaX6bq9ePe9L/shZ/+39pfff/"
+    "th/3S9/+vhhL/SkcJ//HHBr/2/f9v0vS23/vdL0m9LwwwgmRwb20R1SW/f/d//b+0vff2/b/"
+    "3r70m9LwwyDdOEENsHpHH3+9LIUfv/9vIUff9vuvryGcf9dY2KX1IUfwYMQgnFik0r1b0v2/"
+    "/2++K+9tLbXbuu+Oum9L8geEchogMMEEQzXbFBb9N6Wvf/7f7+xvX1t6+k0+k/X6ahhhAk2G"
+    "kt6TZDj4S/b//b0v92GEttLb0tgwvTS3pL/QbQWGDBL7CQYMFTCVhbDBrffbaYW2r3YYSthh"
+    "K7gwguKr0m9Jfaw8JoMQgQYIMIQgxCQhAhkHQGIRBhBI5BEZBhAYaGCB4IGQSmGIRBugMQiG"
+    "hDDiiCg4YT+EoZDOhD8aERERERERERxERERDiIMIRERERERH1xb+qQfpJBF2UAZhn9EDUFTK"
+    "B7xoQYSB7Qjj/////////////////////////////////////////////////kDxf7IHgQOQ"
+    "VbIH1kCSyCrZA8cEMyCBqHcgYcgYfIHh7IF4TChVCkM1yGhwoVe+loHBwi8gdNMOHS2/tL6H"
+    "/yGSCkP/6BFOvrtNeE//Sv9cR+v/p1////W6////p1zZkNnZAv2bCDcchsHyLGQ2DmwnZAuO"
+    "bCBfiBcc3EGochoHNBAjsg3HIQcguOSHLHLHIJMm5LiC7kMocmOWOWOQXciv/62JDZPQZBv5"
+    "DYhF5z4Zy8yr0yDGEGM1yDGJoMgxyYRiDIEYmQboIYxNF2HPg8lkaH6hMjhDjQ//p0Xb0XmE"
+    "YmEYcJNhNJj0Xn+gtUXqL3ReaQbVF5ou1qk4TVQwgYQYWDCDoIMIMKXH/9bSbig6CDoIOlyO"
+    "jAbFVthw+gsG4qwbbSsGKDYQQcMSPJRSBwd6dPbSfpL/6f6tdXqx1YVf6XTCevem168GYDR9"
+    "fSutLS/9WxeuqrV/9/wl/7pXXXQ/91p7pXjSW5DRhFH+sLuor///6C//33X4P91bl1pjdJKt"
+    "hovBr4iQPKn/x/X/F////7NAz/v0tavW9aYaXhG3/+YDM2l/zCf///+3+9e3TvSTeglDFegv"
+    "//bS/9v//+vw3/q3Wt6pf0PpfV3+xX/t//3635DNv9utb0R9t1X4/+vreyOGZ/2//+uvyGx3"
+    "/16elvVIjH//Xp3/X/2//3X3//WKjjSeNb/+10rtWyMfX/2//7q0rX6u1d2kraSr/3RdYaTD"
+    "LdsIv2GvJAZ/+w//2GErCCbCLr2EoNiR161b0l/9g0HI6FBimKg2KCB2CBwwQPBA2wQMEDBA"
+    "4MEDhhiFFBisETgwITTCg2vCTDaQ//ERERERZg2IjiIiIiIzAa8REREREccfwgg/9f6X+v+Q"
+    "ZK///0x/+m0sF0q9W0sW6XyGSGkOkI7YSr4rYhAkEGCDrFhCI4//////////////////////"
+    "///////////8AEAEDgAAAQMAAQAAAP8EAAABAQMAAQAAAFUCAAACAQMAAQAAAAEAAAADAQMA"
+    "AQAAAAQAAAAGAQMAAQAAAAEAAAARAQQAAQAAAAgAAAASAQMAAQAAAAEAAAAVAQMAAQAAAAEA"
+    "AAAWAQMAAQAAAFUCAAAXAQQAAQAAAKoIAAAaAQUAAQAAAGAJAAAbAQUAAQAAAGgJAAAcAQMA"
+    "AQAAAAEAAAAoAQMAAQAAAAIAAAAAAAAAAADAEgAABAAAAMASAAAEAA==";
+
+static const char  fontdata_10[] =
+    "SUkqAGwKAAAmoFQGz///////////////////////////5DIBocgZg0PkDwy3JvkFdyB4Qchl"
+    "DkGB7yB5OnZBQ5J8hmckQ0rBNUyDSOkQWnIZXkMqZBrghs0INDkM/kdkDfsLqqhGYKDEHp0k"
+    "G0HkFEwoQaaqCcWQzzCMMPXfwg0m0gi89KyCgekkYmCpppYQKgjc0m//0Yy8/16VtP0EGwqN"
+    "to22ugtBBtJv2vpLdJtJJ1SbTpJKwjnoOgg2swGmFLgiStb3+lXf/69v1bYLpuuR1pLVX//X"
+    "r/S60mwYorKXH/dfS69J/2vX/9UvYyGU699PXXpa/3//4+l1S2EcXqvXHX1qr/8RIMCP17SS"
+    "pwggnqvj1XpClpf1+3SWlS2l/v6S+btbr/IKbknv62KH2Fel/VJeEGlTDS/1W9tJKiGL8f/1"
+    "Sri83qxVr/sQ2K1JBpXel/RAuOFXm29On//YMUk/dhf+qEOuEHQtWG2v+w9GEwZuXj1/Uuw1"
+    "6bnzaSDtF1/wbSI+Sdx/X9IQ6WPCb0YbYr38MvvCMTVv8gqlyGsR/pX/ukkHaS8gqiMOkk2l"
+    "f/pfpOlvXSTYa/9/b2/yBO9f9cTQMzuu4/RBSgnHpJe2l+KX6Wv6ST1j//7f/2lpdf/pfkM8"
+    "el+xVr0/pEMofIZV16+v//9tda/pdZAh1vS+sge4/0kv3fyGbBBVeutK126dLtJLuq+ttJuH"
+    "+FTV/SOR19dJPSWqr6SX2gyx+ur7S0LbS20n/oJf8PS20mwjeNtf0noINYMJBBwwk2kk2kEF"
+    "texFJBiExCYXXTWwwkCBrEIEDimGEErDCQILERBgsQwgafFRSDEIRDCEMIMUIYhQWQyAaHER"
+    "bSrERER/0q90tfukqxbWh3odtLbSxH//////////////////////////////////////////"
+    "////yBTDMpkFsFhyB4YOQyAboILYFByB4hyB4vkMgCIK4iOQsFWQ07IZxyBEeQyQ1PINNLIZ"
+    "icEDIMeWcgoBkFy4IGQIIIoZByCDhkHIInkMEEDFCGyhBJkFzggyDcYCDINxgQMgwoIIGRDk"
+    "EIIp0O0MhjrIPyZDCj0GCD4aOEHEN3CPDDaDTQaapp6bwjxByc2EeIOTmGEcbw1TTT7ppJ1U"
+    "4B46aPGGmQabJeECIJZDPZEmDNhIM2JQIHBggwQMEDBAwSBAwQNo4DdkCHQIGyCiw2gQNkFF"
+    "htBB5cZwWGCIMOGCBhBglBggdBA6U2Ca5c2EbDvwbSayCZh8Ogg+/6C329JvbSb3SD777/q3"
+    "TdQq9INoIN/oL2/9J//S7W9IN9pBvv//tJ720m0tL/SbT3X2/9L/9L+XXSvdK90v//1p0nrS"
+    "+npuXX0vb66X/9Ll0176b/b///eu++1/yGQxyBwOOk63+++ONV/6X8uu3r+l/iOP2t6uk9Cl"
+    "4WHqR8e7r6SH/Uf/S+19v3/f/96dGF7q0kvCw0qCBAn6vpff//pe9e39/3pX/a9XTaTql5A9"
+    "wQ2QEmHWgmKer6X8iPkR1/9L7X30vSS///991bpL1TCCDBpKv76Vb/9f+l719+/W+lD/erXW"
+    "K0v7wggw0qS9K4YIL////QX3+3/pfpMoBq/a9XTTapfWCCIFy4MNL694g/44+P9fdL2/8Jfn"
+    "mzoGZ96dX+6S92ggsMNLS9bmyD///i/v9v/P/6BMP+/r22KS8RCBCGGl+teDf84POD82DH79"
+    "1//5HDL+Gw3+6/a/XhBBhpddK+/9PT//N7/r2/8b9yGpT/q1ek2l9BBuvS6vu9f+yDuRj/+3"
+    "9r7ff/2D/2r16MLpfT9+kh7/X/xf/t+9e39fW2/71q2qV6XsML+qV//jjkCM/9h/a+36+u2/"
+    "/9dU3peGDCCbdtalw/2/93/9v3r/f2/b/20r71frwwyGWXBBVbaL8JK/+l9//t/a+33X1//7"
+    "G+levhh4QIXYqKNFX7fWQR9v/9vIO+9e3uu2ltkND/rHUaTekQw/hhiEE2IpK+l6///7elx+"
+    "33X+313TXX6X5A9uQUQGGEEQa4tKr9vS/b//b/a9jbS20tvX16dJvS/TChgwgk2Gkr6TDILj"
+    "4S/Yf/7f/+2ltpfdbaX6Tfr90GwgtsJd4JNhcEtLb//b/r3YaWw0tu0uDBJp9fSX/B4WGeNB"
+    "NNCEGZkghCCGEGGZlCDCDCDwg2GhhN0GE3YYJBBsMEEEGw4YJBBsV00kw0Gh+1QeE0xCCDBB"
+    "hBMQkCChBsQggwQYQeEG2FBA8IGCBuGIQQYYoINuIQINr8JWCBr4qIiDCERBhCIgygDw1IiI"
+    "tCLhghBghEGEIMJrxER+hEaERDiIiPpaB/0g/SIGwCcdJFzOgGgr6jEGvGgamgH2EL4j////"
+    "//////////////////////////////////////////+QP6EDob+QPBoHIElkDw9kCyyBJBA8"
+    "F7INVkDYDEZDLjyGVCZBXmCqQZPIaUENEAoKlt5A8sTSfV00/S2/6BwdF3D+Dg//pr6Q/+QW"
+    "wbj//MKvrtNeC/9JN1/iP//+vr//+k3////9r///+k9ZeECzPy+IZY5BuP5AuOXhHhDKHL4g"
+    "tOXxBowscg3HLjIGByHHIG9CMci+Qzv/+3BEMyeEGQMUCGQLzyBimgwUgRmRewVNBgqDIZXg"
+    "qYQsFTIEUyGzAUgucuippgmRLIOcuhDFX/pYhPTChGHCNzROBBuKAXpgoLoLBU0wVMIwwwVN"
+    "Fzgqow2icEgoYIGCDBYMK0EGEDClxP/7YRtvl20YOgg6CDYVBNaMXfQXovNGK6MUIJt0XbCT"
+    "WqCDhX336B6apJL/0ug3bpB0nSsGbDZZsNghBsHB9BYNhiE2GIQbSbBsNoJwYkergzYN4P1p"
+    "9pXXX/q3vTaWrr6V1/pf9at02vTX/t7fTaT+l/9Y/rr0370/6XTT0/fr44/6WnuukKpdkFFk"
+    "K/pN+9DWv//6C//S/rq/7+XVJum9Kt0DXxEF9V///9f/991+ZgY+6Tf8VrQSww0YwaXkDwOE"
+    "f/H3X/H////sH/+k2k1dJN6SQYrwjj//Ng1dL/m0////9h/t1/tvpN6SQa9Av//ev/b////w"
+    "3/rpN6ekrelQ+v//sMJf+3///X4N/3t+lt6X4+l6V33hiF/7f/9+t+D/ulr6L70q////+XBp"
+    "/7f//XX5BQO/9/TdJNvpER//16d1fS/9v/919//1emONK71r//0rtb1/9h//3Wla/XrHWrxS"
+    "S//YRdbpsijtourZFfT/9v/9+0E2vrZ3hourW0k26X/7aWgwgmGFYaVsMJJzWBDtPTYaaYTt"
+    "O20oaTYRhUGnUUxV76V0kF/9ioOXQpigxUNiggbYQOGEDwg3CBggwg4MIHDYaCimIWEHDCCa"
+    "ah9OrDeP/2ENBoNMIQwhbERxkcMgYqbQTCxDEJpoX8RocfxEREUYE4jiOIiIj/2En/r/IG5d"
+    "J/1/////H69JtLIH9NJf3S6uq9ISh0CxdL8gt46iO2kl6FbYSCQIMIHWGISCTCbWIiI/////"
+    "/////////////////////////wAQAQ4AAAEDAAEAAACoBQAAAQEDAAEAAABCAgAAAgEDAAEA"
+    "AAABAAAAAwEDAAEAAAAEAAAABgEDAAEAAAABAAAAEQEEAAEAAAAIAAAAEgEDAAEAAAABAAAA"
+    "FQEDAAEAAAABAAAAFgEDAAEAAABCAgAAFwEEAAEAAABkCgAAGgEFAAEAAAAaCwAAGwEFAAEA"
+    "AAAiCwAAHAEDAAEAAAABAAAAKAEDAAEAAAACAAAAAAAAAAAAwBIAAAQAAADAEgAABAA=";
+
+static const char  fontdata_12[] =
+    "SUkqAFAMAAAmoFsNP/////////////////////////////////kMgNpyBoLGQPBocjfIEkED"
+    "wU3ILjrkDxwmnkGmKIa+ENfFshpj0Qy5kNIcg0UIHhxyCjCLhDSHIa9kG8yGZPCqpAvBK4YR"
+    "oCU0km4PTChBkMqgJxhMhnCBBhB6u/QIoBubbpPSb0gjbYKmEH4S0bNo43/rhBpNqjHpKyBh"
+    "/SDYVNNLCBUkG0EG//0Yi7fdJOqt3S02CzjaPNroLSdJv6qtLDS2qT1TaaVLo5UEDwQb5gGx"
+    "TAYXdf/ql9PS+t3rVwurp0XXS6SdW+v9f9fpJwxRcUrj7/9JUv/7v1X/Wkl2DGv9aTpel16X"
+    "v66/6/pbkMyK79/S+tf2///H6tJLbBHv6/4/66Vpf4iQYUfqulXhAioHSrx6S9If//9uq0kk"
+    "tL/f0v9K0v/v62KHbq9f60vNNdhpX+QJ4JXe6pV7X1+qSXhB0kw0tf6Ye2l0RNFxb1/oEF8W"
+    "pf0xC/14gwxCSTXv6/yBiiXON4Qattr/sGOmtcL/0oNeEDappMO1+thpIxyIRuOl+kjDdcJ4"
+    "lzemwwjC/4byL6TbNgp//6ENpY3CDpBG5sV/qQaCEgjc0rfyDKTIbWiX6T+9WqCDbVbkGRRL"
+    "t6Tav/1/pWl9PShsNL14dJK6b/1X9LXLHf1Scf//bVv8gtRVfpPEX71vXRAnslG6SX2l+K39"
+    "a/qlrjX/+3/1paX/pb1+Qbj+l+2la/+lkM26/9L1T/+26/Sf1IZg9f6X//0l+xT1/6VrkNDp"
+    "N0vSWQPOOvX+2/yGlBBkdetLr/WrVLTX+km0m2H+Cp1a6RB3b+0n1eku/9L+0DLHtLpNXrQu"
+    "0t6tKrUJfXD0knpgwQt/+rSTW0EnYSbpW0kF/weEtsJMTcF/Tqw0iBepYYSIZurDCTDCSsMJ"
+    "BLa1DEQkgxCYQa0taoMV8QriExVMQiCjsREGFiGEGm8aHaEQYQsIMIQwoWQyA2nER6pIRERH"
+    "3Vf26pf0kq9v1xbSSHdKFtpDt11WI///////////////////////////////////////////"
+    "/kC0GD5AzAxBA8DCCGQCoQQMw0yCB4EEEDwYoQyA1YNxDuQ8Hwg2YQ24vIZILHkNQ+QaS4IG"
+    "QzqyGWkILkwQMhs1ITUg+pB9SD6kJQhjUhmHIGDkMUIZyAgyBgGEGQMBAgZDPQhaEEqIQggm"
+    "hCoQ1QyBFqQX5MgwGQl1hBgg7hhHyBw/CPkD///vCPEHDCPEHDRxhx/r+CeE6i5wDwxTCPkG"
+    "pDSmT9GwSQ0TIzkMuZF8homR+EcB2Q2eQI8g38g38g3+cBQfDUaPgoZDZYQIGGQMTJTCBAwy"
+    "BiZKaBA+QI4hnsGfAgEDBWQe00CbWvRttGwR7CDYQQdhEE9hA0wgaQQdpppppBNPTtIINsIN"
+    "oINsINpPLhDgmmnaaVyGzkgepgCPwg2EEGHe2k+GHvuk//pdrek3uk3uk//6/t02lSX7aTa+"
+    "l4f/Sf/0v70m9tJvbX/967SbV60vS0nvdL2/9Kv/S9b0n9J//3+9td0m0tL90m5dfX2/9L/9"
+    "Ll0+XT9vfb3Sr/3S/ur9J8erX9L7xxX/9L+XXb1/X/f6/+6dJ0q/IZAdyBY+pCQ9X+O/0P/o"
+    "L7X36v6v8Rx+/RhVbW0hS8LD6BBny1fpL/X/0vevb1f1f/90r/un0vCw0lRyddXr9//+l9r9"
+    "/f96V/3ule6TaSXkDzggogJMHVIJjdX6/yFfIV//0vf9vS9JL//dL3Suuv00wggw1Vf7wku/"
+    "+l/6X2l7f//pQ//691bVL1sEEGGlpVpeEFX///6Xv+/vpb6TB/36t7FaSX+EEDDqkv3iv//h"
+    "hf0vtL2/9L8IKdQ0/uk39U3SXvhBEMomGGgv+rg/44+P9ff+/8JfnOynBp/f1q+qXtMIIFhh"
+    "paXq84Qf//8X9pe3/nP/BBv961b7Yr8RCCww0vSXvITv58efH5wNH79/2/9hfuG/9ev3S8II"
+    "QwaX9Je3/CDwg//zif2l7/4/tkNQP9vbXpPS8IINpdfvvf///7fv+339/kNqf+l7a20l8IN1"
+    "fpJX36/9kGCP/Df6Xt//7Yf+/r0Y//v+lx7/X/3/7f3/fpeltv+9at0lel8MEt/ST9/33chs"
+    "//2/evb39/b/9f1pvS8MMIJvbRHWpgMfv8cbD/+39r79/f7/t02l6vpeGGQaSYQT3YXX/9L/"
+    "/9v3r2/r62//X29K9Lww8IIXYrCR4Sv2/9v/9h5Bgftfb3XbXbINx/1/rpX8gw/hg8IKwwmI"
+    "S76V6WQXf//29divvuvrbuu9uo46vS/DDEIJsWkkr9vS12//2//29tLbrtV+o3dJvS/IHnBA"
+    "vYMMEEQ04bFLfpvS62//2/39jettLfrdWqpX0v0woYYQSbaS3pNkM4+l+3/+3/Xu2l2lt69p"
+    "fpXr+tBhhArbCVPhJhhcJft//t67+7DS20tu62GvT030v+G0FsMJLagkygWmRaYLsNdf21BV"
+    "q12GEsMMJd2EtgwSafX0gv9B4WGfMIEUAgNCgxSEIhlkyC+oZoOQY0IXQhjXIZ9GDQyGEOCI"
+    "YYKAIsGCRAvoydogX0YcGEiGXoxX0CTBkC+iH7Sh4TQYhJqgQYSBLhiCu/t1vTtwxCsMQrbY"
+    "hWwunSbv8aERDCERBghEQZIA8GWIiNCLhghBghEGCEGF+IiP0IjQiJA8C+CIiK64QP6pB+kk"
+    "gf+i4zUBoDN0iBKb0INfCigak4HhI0QMw1IvYQjj////////////////////////////////"
+    "////////////kD9BA6hrjkM2CGYP5DIDUggeBiyB9hBYsgeGVBDVggbQ2ZiVHkGiCB4rkDfy"
+    "B4bJqQN5kNdyCiCBEyDVNBbeQPHyqqqqaf/e6aRBYsgeBfEXcgUYnZDRZDUtLb/90hf//9NL"
+    "1/8gtgsP/8xtfS2mvBf/X/8R//6ptfX+v/Xr///+m1////V////9K0iGb/kMz8g0fkD4fyB4"
+    "ZxyG3MhmjkDwUp5DMHIYHIHgTj//uwQTycyDTMhl0wnhPLmQy4BcheyBeC5kfgpcwQYKXMg1"
+    "0M5DZBPAg8FBSBBBM5DCCK5EoQx5C4QcgmcguI/9KxT0wQYQ0bmiQGgwyGBFMhsmQInpZDPN"
+    "NBkNk00cYZAiaDCGQXmFRttEgHkWbuune7//7hGDeEGEbOEbOEEGwqQfT10C9NNU0EG1QYRs"
+    "uqQcL4YIGCBgkyFsG0CDBAwUwFX/pXQfRt0EGggg6V6TWjDZBRZDZmlkFFow2jDkFGIw2k5D"
+    "RiMG0EGiGy1p1Bwd6fp0n6S/+n24hBtXSDpNgzYF84CgQg3voLiEGIQbYhBtJtiEDaTxLuuQ"
+    "0W76991paX/rdPCdLp/0un/S6rp+6dLhP//WtNq36//TY+366X71/pdNPWr02vjtft72rpdV"
+    "SXZAxhBx/X66f9v/f8Jf+9X/1Y/62i602lqKXug0/pv9RS1///QX/6/pfD/br3WKbpJBbaDS"
+    "8RIHgYPv/DC//+v//7/ygDH/dbprVIJYbRuBhLwRmv/x9pf8X//v/7B/6V17vShh4QVBj8I8"
+    "f/4L6/5tP////Yf7fq2vfTeqQa9Av/5wNS2l/7f///+G/9J66vVK9KgYXpf/+w0v/b///r8G"
+    "/2+9+26Sf8fX6u/2K/9v/+/W/Iav/6WlaSL71S/H69f7wwv/b//66/D///pb0v//16vouGp/"
+    "2//3X/yGU7+rdOrGrd9EKP/+vttr6/+3//daTf/36xVJNukkv/66Xe3pf+3///Wv16sfpXGl"
+    "//aLraTbYRhYZCPp/+3/+2laTYX1u0XWmnV9L/+wl3CbIjsMJbDCXIwG//Yf/7aVoKGEbXus"
+    "zthLfqm2kl/9iFMwXBhJhhJiFMwzjIMEWQYRBkMEZBghhkEIIYIMRMwwDg2GlDCTELIMaQwS"
+    "ioqZgY7glB6H/7XL4pimlYVtp3fbV3dp2xCimF6EJ2uq92v/2hoMIMINCGEIbERxDBCIiIhh"
+    "TeEGsQwmgwhd6EccfsREREIwE4jiOIiIjX+Egf//1f9f8gVq6/6////S1H0vSb8gfo0v90vu"
+    "v0m4WLrXkFsGsdRHtJL7S2GCCr4rDFEDwUYQyQ0yCCqGlhgqXaxERH//////////////////"
+    "/////////////////////4AIAIAOAAABAwABAAAAYwYAAAEBAwABAAAAeAIAAAIBAwABAAAA"
+    "AQAAAAMBAwABAAAABAAAAAYBAwABAAAAAQAAABEBBAABAAAACAAAABIBAwABAAAAAQAAABUB"
+    "AwABAAAAAQAAABYBAwABAAAAeAIAABcBBAABAAAASAwAABoBBQABAAAA/gwAABsBBQABAAAA"
+    "Bg0AABwBAwABAAAAAQAAACgBAwABAAAAAgAAAAAAAAAAAMASAAAEAAAAwBIAAAQA";
+
+static const char  fontdata_14[] =
+    "SUkqAKINAAAmoCAz/////////////////////////yGQBw/kMgGYcgw5DJBpvIHg1wR3kCuC"
+    "B4NFhbrIHiwnZAxZFjIafUQ2+BJJshrRkGnyGtBBqmQ05kNqyBcQQ1YINyZBRMhpfhf1CMwz"
+    "S5hqg9W4aggwoIGCDCWC4QYIPXrwR1BQm6Wkm6pGzYKmn2EFQRsgwjhB/9UjeXg0m1RifVkM"
+    "t1VBNhUGE1pAtBBtBN//hBYdboJOkk2nVJNgj3R4s8b8JUk6TftfpYfdafV09VbQXCDcEHWX"
+    "BWCmAIraTf/9eldL0ld1VcLp6bRddKkqff91Vf9fXbDeqtwum0v9L11v/+v+uqSwxR+rx/3S"
+    "9LS+vfqtf9da7DHr+/pel/79f1/9dKr5Boha9Lr/9L1/a/8fXSqsI/ev/HS9Kkrrv/IZ0n9V"
+    "aSXYIEU467ePX6j2v+I/tqulSulfX+qX0ldf/e9U6Q9wr1X6pfJ+u2l/kFqyO/tJYr2vr/qv"
+    "BA9JhpX/XeG0qqtq9f1SS9NIl3DS1/pg8MQlyJWuP/9JfF4QaTFN//EMaVd36/SIZrhNLnCe"
+    "EGob1/2U4bUJ/cLX/iDXQQb06Ydr0uw6RvZCaePX6V106EwdK2GF38NqQnJOzgE/1/SkcbS2"
+    "nhBtQjc2JfX6kGrSgjDDW3/r+hDfi3CekEG2v62XmoQTdN/kDgCIKtS/pOl+2qQba/IHCTD0"
+    "rat//X6Ta/XSuGEl/htaur/0v9et91SbH/+l1evIH0a/pOhJAaf0t/ogtWRY3Wm9v/GutLX/"
+    "S0sdfpfbS/X9L/0t/r9L9v/pv63r19L8gXH//tL9ddKiDVn9fX19JfbFPXXWkQan+npekv//"
+    "99df0tLIbHW+vXIHjj11S6bf8hrWQJHp/Sb/rVfS01/rddu/BUH2lpaW2k9JNpJa63pJX3D6"
+    "6TX9IoZddrf+gvrvS3psIMk7/9N1odpbpOkraQS/70km0mGEcxWvWrpJqwwknDCCbSStJL+o"
+    "PCW2EmKDXWtUwwkQy06xCINQyKYaWGGEECC2vDEQkgxBMINN/TSsV9bCYhJMUCBYiJBppiGC"
+    "DC0hxoMIRBghYIMIQwULIZAHDiIvpKIiIj91X7qtfdUvuklXtrS4t0o+lC20h263SxH/////"
+    "////////////////////////////////////yBlyPyBmCy5A8NUMhkrQgaA6CB4NKCB4ZhyG"
+    "QBxZCDkHcg8EUcg3cgr35BbB5kGw6kNRQQ1QZAgwQaBogwBkGgGQ0VkPWQxWQxWQxWQShBes"
+    "g0oINBBDCCDcMhmJyGWrIaichmKwQMhoEyD1kEDIPUQQiPjIMTIaOIL0IKMIEDc8B4WCBggd"
+    "sMIMMgYZkOCDDQYQaDCDShoNwg7QQMMGEDYYQeGE0GEGg0mGk1uutMIPBnthGYRAzwIGQaMO"
+    "nIKMPWEZhiQL8DBEMrgYIhldOBlngbcEDZDKgIzEYM8EYRmIyGbhCURwJwZ4C5gFAIGEGCwY"
+    "QNoEHSr7CMxA03ISYQIgxjkGJ5BiMgvCBB6apqkqtK9AgYbg2gQMPBsIINTAU8FT70/T0G1m"
+    "A2L5gbRwF34dBB8N/4QT/+gv70E3toJveuv/XT20m6pfSDhBBhp7aT4b/pBV/6Xa3oIN7oIN"
+    "7aT/+3X7aTpaX02k/ul7f+k//pf+k/aT+v1/+qT1daX/TaML6Xt/6X/6XMJowswnre63vX/7"
+    "ave2rpaXi6Tffpff///hL/9vSb9Jv1//6/0m168hkA3H0np/r3xxS//S9tL2/f9/xHH/tGF2"
+    "6ehXwpA/foh7bW/Ue/Uf/S//b0r9K//20vtK0rSS8LDpIEzZ19Vv9f+l9pf+/7//+9e6vpeF"
+    "hrhHmR/at6r/r/6Xv+3r9L9X+2lq3t1aSXkDyggYgJMHSSCjf+vvIO+Qd//0v0vb6/q9f/79"
+    "LSbSr00wggw10mtJ9Kt/+v/QXt/t/ev6V//pPtpevqmEEGGlr/eEl//X/0v0vb1fpX6Yf7aT"
+    "98baSX3ggQYaSSXpPhAv///9L2/2/9L8JSQCr/+vadJL/CCDDS6r7j//+P9L9L//S/CTNYa/"
+    "3S1dJq+vpoIIg0AQYaWv1yXDZ+OP/0/b/b/wl+ZDIgNP999+6S+00EFhh116vOCB///xf6Xt"
+    "/5Z/4Jh//pe3el4iEFhhpaql3g3//OAX/ft/t/8L9wb/bSferYpLwghBg0F9aT7f84D5wH//"
+    "Ob/S9v/H9shr1f/1arpeEEGGvX97f1///t+3///7kFU/7pWr6MJtV4QINpeqST7////7f6Xt"
+    "9/f2Df9//7r8IPX1xfd6/9kNGn/t+3+39fW2//ulaSTel9+36Xu//7//t/17e/v7b/tpe+k3"
+    "pfDBf1pf+scchld/7ftr7fr2u7//1ev14MMIJvdUpgGH96/b/+3//919d/71a9U3peGGEE7d"
+    "yOqSX79e//7ftpe3v/7f/avuqV6+GDINYEEEO2EnCW39/9//t//t91t1t/09aV6vpeGHhArY"
+    "qKLtL6fSyGd9//28hoftL2/X12yDd69bX/Sb0iGx/DDwQTYaYSW3rel/f/7f7/t7dbdf/f8b"
+    "1V9fhhiEE2IpL9N6/t//hv+K9vbXtdv/V6qNX0vyB5QQy7DDCCINsWtPq3pft//sPXf/tLet"
+    "vS26jd0r1/TBIGGEEm2l3pN6X7f/7f9extpbaW3a9r1Svpfrhgwgk20l9JhkNj4S12H/+3+/"
+    "u2lsNL+uwk19N6S/dBhhBbDCVN4JMMJYIL9h//t6XXuw0ttLbhhLYYS/Svpf8PBYYMIJO0KY"
+    "MFQhIUmwYVNNPTbQ03TTdhhBJsMJJtwwkmxVNOraaH9JB4TTFEFAZDGqCDEIIIg0AZBisMUQ"
+    "z1kPWQxXkNlbBhSC+mQlRDGmGKIZVYZQwiGVWwcQiDTW0/QJQZDKrX2sPCaBgvRTg2BIhA0u"
+    "GS4KP+/te4YLDEL2Fhr+n/xoREGCERIKgYiJBVDERxERxEODBCDBCIMEIMF04iI+oiNCIkDw"
+    "1bEREfrCB/WEH60gf0qMMH6VIIGU4GoKfSIEsGKCDV9UQNA9IeNA1JAHnhD4j///////////"
+    "//////////////////////////////+QPkEDMFW+yGQBPBA8NSAmQZ4IHhqQQ2oIEoDFkGuC"
+    "GlHkDwN4ILMyB4NM1ILMyB4NMyGrNLYeQPF4g14kFC4UgqQQLwFCpbe9pEGbiB4NfIu5As5N"
+    "Mg34hr9X+qu6Qd1t3Xb+0vUf//9G1/S+vIGYZj//tr67TXhf/S6/xH1//bX///9L/X///bX/"
+    "//9Lr///9Jtf/////8l/kNTiHwg2f/+k3LhpGgZclMhqeQaJ5Bp/INU9BkGiCBeMgnZDLgIM"
+    "IMhmwgyDXMg1QSmQ1KE3IF4JYQUHyGbBBdyBGhJBDXchrcQfCC4ZGggwE//xCDwgwQMIYIPJ"
+    "OCD0wUF1yCj00wVMEDBUGEMFCgg8gY8h+8hjRSEQE1//9JsJ6YUKEcMMIYRsjqBFMhsOC6BY"
+    "KmmQ0HTRsgwUINSDB1RgcI6BiCgz4OCBnwSDBBtAgz4OCmARf/thGxvTCOFCODoINhJJrRg3"
+    "+gvRt0YN6MGwgg3phGxVqkGgvvvbh6dqkv/S6D6MDaCDoIHS9J9BByBjCDfNLIGJhtJyBfEE"
+    "HSbIKMRgVoIHIKMVJ1IaMIJnTrTaTpaX/7e8Qm0mknSbIN8VnAMCn/S6YpuKem4hB0uJdpcg"
+    "oz3+9tb//9Lq6DpaTr9XV/hBdV1avTaXQff+61S66pL/9t3r/6b1en/S6aenW/Xof/dW/bSd"
+    "dL8gpD+lj7aTrr//+l//T02vVj/1ownTaV0KSW2QzMv6b/xr///0F//39ff9r1r060luEDXx"
+    "ELuq///+l/+vv/B/vTa3TFeqWw0DS8hkBoI/+Gv1/xf/+/r7JAZn7+n2m6Sr0bMMJeQyAXmb"
+    "P/j7X/v////Z1Av90v19UmHhBJBj8I8P/8iAMXr/nE////9h/3tpN03dJN/QYXoL/+cBs2l/"
+    "7f///+G/3S/W3XfSSBr0vr/2GEv/b///r8H//W6+kr9ofS//9iF/7f///+Q16f39Poum3pfj"
+    "6X93+GC/9v/+61vwf90m10lb1S//9L+9mA1v+3///X7/39N6T3SX//07r6X/t//v+/kMt3/d"
+    "LX0rdVId//11u9vS/9v/+0tK//19jikm+q//16bbX1/9v/9/rX69YqnVtvS//tdL0XWyDj6/"
+    "+3//aVpNr39our/XFJf/6L+GgmGQo7aW2vf/t//t1DSsIwvpWW8NL6pJt0l/9sJcMJMMKwwl"
+    "sMLyXAv/2H/+2lDCCYaX2lFMVbTurdKl/7EKDiExTFScNAogRrDIMazQMHUGJAjVsg+pDGpt"
+    "JOCHUQ0DQGEopiFkCKoYSdqThlfBKD0P/60Y07WGFt/+wuv9iFCDXxCaa3pqnf/8MIWgYQME"
+    "DCEMEIcRHFghEREQwU5BBhYhhNBhDT4jQ4/iIiIhGw7xHEcRERH/0g/9f4Sf//yB+Bf+l/6X"
+    "/9f/+ra+PVfXWCf/q2uC6r9NoLpuq9RHHS/IGeOltpV9rtpJehWwwSIHg08EDCDrDEKECDIM"
+    "tVYYIfaxER/////////////////////////////+ACACAA4AAAEDAAEAAAATBwAAAQEDAAEA"
+    "AABKAgAAAgEDAAEAAAABAAAAAwEDAAEAAAAEAAAABgEDAAEAAAABAAAAEQEEAAEAAAAIAAAA"
+    "EgEDAAEAAAABAAAAFQEDAAEAAAABAAAAFgEDAAEAAABKAgAAFwEEAAEAAACZDQAAGgEFAAEA"
+    "AABQDgAAGwEFAAEAAABYDgAAHAEDAAEAAAABAAAAKAEDAAEAAAACAAAAAAAAAAAAwBIAAAQA"
+    "AADAEgAABAA=";
+
+static const char  fontdata_16[] =
+    "SUkqAHAPAAAmoCQP/////////////////////////////////IZJx0QyQzjkM45DJA3vIHhr"
+    "2RbyB9BA8Gy00/IHg8XZDMsiXkGzqIK/Akk2Q2nSINUyG25DVoQ1aEGSCGUoINjkFEyGPIZU"
+    "yGrPBVXqwQahNUm4PCBhQQYQMFwQcYIGED131IZoaNsOk6SbVII4bBQgwmlhAtHDDCOEH79Y"
+    "QNINqnrZBoHrQQbCpp+EFSCDYQQb/1wjkXbSekbfSbT9JsFTR82uEFpOk3/+gsOtqk6STadJ"
+    "LYR9Z4bhBv0FSTdX9fpYf6SeltP6cILhBtBOswCkpsNFdX666S+m1/p7pJbgtJ6bRddBVVNp"
+    "X++v69LpK2G164XT1/pa/v79a/69dWGKJ2krY+3ul6XS6V/69f9a0uGP/rX/Wkv//9f9fSps"
+    "Ol/vWl6Wv7/X//1pa6kGu9f/0vS69f+v8fW6S8Izf6/xr/1uu99yGga/qtaSbBH1HS28fS9I"
+    "atf8R/dVdJLwlf/6S+q9f/fdVpD9PpL9VXkvqmGl//uqxCW2r//18EDVbSv8gerIl3tpVW7C"
+    "vS/VKvQekw0tevb7SVrx//pBJcXRH9MNBf/yhQxCrIUZXf/0kvahA1Ypv/qIMMJQmv+l+pBp"
+    "cIOueG8J0w9f1ZLgyJNVuC/9JCDXhB9NWG2v1sNQjnIWvx0v6uug3EwTSu19cMNIh/SsGcF/"
+    "/6UuNpdaBB8I5hsMI2lv4N4QaTeP6X6iG1xbptJBBtiF/5DU1SCON07//9But61SDtfkFgal"
+    "29INrf5BZEyDInS/S1/bpINtJf4dJK1b/0v9JuvrVXBhf+303Tf//6Wu+9U2P/ukv3X6pdaT"
+    "oSGDZ9JXrogerIl79Orf5A8S6/0v/Wtev9Jb3S/FJ/S1/pXrH//2//v0t69fX/0v20v0tdKi"
+    "Gl36/X0Qyn/+20nr+tIhpj/v16XS/SX8f6X9L5BQ9dL0lr//7Vr7+k2l6V9euQPDx/pJdNv+"
+    "Q2o7rS62/VdUsJ//trbD/BSBPiWulf6T0k3SXfrpJdWw3rVPetIhiel3V/0gv+9LdWwgyKP/"
+    "qlfobaW2k6STapa9XpJXTDCH/XulrDSuwk3S6QS3pYelthJibabS10m0kGsMIJOwk2ltpBBd"
+    "LyjggkgxCaDX9PtpEMwGsUQ0xDEJsJJMNBBBbXgxFYYTCYT/tbFfC4TELDFEMueIiQa0JCGC"
+    "Bq6FIUgwhEGCEMIMIQwUFkMk3ERdaxEREf60vbVL/qkvbSX9+ku7SS8W0qHekttIdtLbS3ax"
+    "H//////////////////////////////////////8gMBZD1yBoDQ5A8GXQhkg31IGgFAZA8G0"
+    "MgeGsQQyQ2oIG45AkvyC2GvMgqoTIa6QhtBCGgbINQqQYFCDWoIbBBBBBDAghgQQwIIOgguI"
+    "INYZDTIIYIIGKgREA0EwDYRANBMBqgyGgoIYGEMVEHrIY0IYqyC+hAiZBvMhg5DL4gQLMzA8"
+    "PBAyGsn4MIHIqGZoED//9bwQcGCDgwgf/64J9pcLCYQOyG0kBGgeQboIQgg1AZBQYCMweQLz"
+    "IGJkMuZDLmQy5o+GWZgqOZgYZDNxHwoZBpORaI+FDINJyKdHhNENlCBjAZoBgEDNAzyGzNHA"
+    "zuv7CNBA1Z8I0CB2CIMHZ4GEzwLwgQO00001CadJtoIIGHBA2EEDDYIG0EDzYc+HtNNU1dEC"
+    "9EgdJmwUL5smEfBh24NhIO4N4fDoIP/6Xe+gg27aCDe2k01u+364eg3wkvQQbQQYfugnww9/"
+    "Sa/9L1vSb20m90n//p/tJ0v+nQTa7aTW3/pP/6Xfek3uk3tpfX7/dNq3Wl+2kG79L2/9L/9L"
+    "1vSb7Sb///tK1V6tJUvS0nRhd0vf/S//CXcwswnpPuk+6X///tpOlpfugm+/r2/9L/9LmFX3"
+    "6b+m/3/9unutJv68dbS/X28cV//S+69vW/W/X//XRhdv0tfIZAaQ5A8Ufp9/r/6//QXuv30/"
+    "q/4jj7/2raTdCl4WHpEH5tb6Ue/Uf/S+69vX+v/+6tf0nVLwsOlBM3dP9b/X/pe6+39/3//q"
+    "9PTdWkl4WDWkeb/vSX/X/0vuvb1fpX0r/br79XqvIHhYIZdhWHWEE6TaT//kF3yC7//pe6+/"
+    "XrX//rpatpWkvhNMIIMNUko/vS9v/r/0vuvv7670r/er3punVL7UEEGHXvpegq////S9/2//"
+    "/ph/3ut+k3SX9hAgw0tKvfCS//r/6X2l7er6SvpQ/9enVjtKvXCCDDSSS9bhggX///+l7/t/"
+    "6X4SZ1BW+3X/T6++EEQaBMMNL/p4h/668f6C+0vf/S/CTIgGz+ut01aSX00EFhg0tV+4P+OP"
+    "/0/f+/8JfmIYP96un23SS9poILDDS6rSeeCB///xf6Xt/5ZH8Ew/73XXvS8RCCwYaX6XeDf/"
+    "88GX+H7f7f+wX7hh/69XVsVXgghDDrX0vb/ngfPA//57f6+3/j+2Q2hH717+6+EEGGl0l77f"
+    "++//7ft17f/+2QV9f7W19PS8IEGHX6S3v9b7//t/r//9bkFNH709NqjabSXwg2v/T93///+3"
+    "7de33Xf2G/7/6S9L4Qer1SQvf1/7IN6v/b/X2//9sP+66V9N9fa79V+/X+9/+37de3v7+2//"
+    "Xvrevwwv6pX/+OOQzJ/+3+vt+l6W//e2ukk+l4YMIJ7fVGwz/vX7D/+37df9/f2/7pdXpN6X"
+    "hhhArfRdUqf36///b/X29//b/7/f768MMhqiYIJrbS0Et/f+//7ft17fpb1t/7paWqT6+GHh"
+    "BC22lpU/vpff/7f6+339r/6X33SV6RBRHhh4QVsUxCJ2t9XrkG77f/7eQUPt17e9b1tkC8V/"
+    "exv76VeDB4QThhMJa9W+v//9vX/77S7S2/73Sr0m9L8MMQgmxGlf70tdv/9v+K99v39vS3X9"
+    "ikr6/IHhYINEBhhBEFS7S70m9L9v/9v9/b3S20v/umKrV9fwmChhggSbaVP03hLrv/9v+vY7"
+    "S7S29L136b0v7UMMILYYSW9WGQLvpft//sPS3/bS20tu67S90r0l/oMMIJNtL8JMMJYS1ww/"
+    "/2//3YaW2lt2lthWqpX0v1w2gsMMElbwSYMElIOfW2Gt3fbarbXuGwgrYaCu7CVsGEv0r6C+"
+    "6QPC2DCSpoQgxoQkNWDCqq6txrppuwYSUMMElbgwknFe6tpof1h4TQYhEDGpBisIIMIIIg1C"
+    "hBgQGIRDQIIIIIYEZBuIDBhSC9TRDjCD1OxCIZohiEQzRDBxCINYwNNNUCTBkMsQvtUHhNBh"
+    "eiXBVClWGrwZCAX/7r/4YWGFuGFhhf1/44iIMEIiDOoZIaDUGQEQiIuIhwYISCmGIgwQhgvx"
+    "ER9IRHERIHgrwIiI11hGgGwCzroO+qCB+loP9JGCNQGwGXpECYGYPSCBkuBsBt9Q0qBr0ooS"
+    "GciHjQMJHQDx6IGobv8IRx///////////////////////////////////////yB49PIZIsED"
+    "wZIIHgxxA8rIHgqWQVrIEsM2yGnZDUvyGQoIM8yB4KnhSB/MgeDZMhtTCWw8geCTIamBIFIH"
+    "g2IUgzEEFeCGXAKC1t7rXrpp+v9WpA+4geCryMHIHvk0yBfiCp1b7ql6Q/+vf2vr///o4tel"
+    "015AzBmj/6tf9prwv/q/64j4X/0rS//r/vf9f//0rX///+m////9df///6b////1dL///+rg"
+    "iGpTIvkG2ZDS/IaX5DUpkpkNOCGXGQf8hmOR+QTyGnBKZDXoQ04I5kNqhJyGVBLiBc+QanIZ"
+    "4IZ4ISCOCOCDa5BUwgvxBeCJBFciuQz8Qxf/q4gg8EDBAwgeCB4IPCBgoLrkC/BBhBgoQMED"
+    "BQgYIGFBQoI1gokMzgWOMg9VkKGQwdY44//qwnphQhhHDDR1BQbJnnpkFCCGdGlkM6EGgyDc"
+    "hMI4QZBuhNDIEIVGx0ageQqAZoGAQMEoMJuCDBBhL/6unphHChGxwgg2FCCY9P9AvCp6aCDe"
+    "gwjg1qEg0F9pphbCB0mg1MBhf/Vo2K9GyYQQcIIHScKrWjZMgY8go/QWQUejhsI4bIGPRsmk"
+    "2QL+jZNAg5BR/ThSBHkMe9PbtpPX/+r0H0EG0g6TpXLx4MtPEJ3fS2IJiE7EJ0m3QINhIO6p"
+    "Pu/6039aX/pXvEJtLSDpNj8+GWn/S6aenVuKDpcS5pXIF+9tJu1dJ1pL/6em6etf9J6b/QXT"
+    "TdNpPTa9P//61aXWl/63/q6Wl/1/pdV19/XQ//dft039Vv9Nj03/7evv+l7vuk9Nr9j+6ujC"
+    "aTpaQqvZBp4gQ/q/6Qpa///hL///S6v/q/7SvSrcINP6t9////9Bf/97/3+2vTdN06SSWw0D"
+    "S8RIHgrU9f///0v/1//ZQDX/3XVj9IILDDQa+QyAatP15OJ+v+L//39fmoMz91dfTdbegpsg"
+    "0vIZAZlDd/8ff/3////sH+66tpPqkw8JJBivCPj//KgDF0v+eT////2/+nr3fSb0kga8IF//"
+    "PBt7S/9v//+vwb/bW1bq7SSfqg16X1/7df+3////hv/r9b9K/wwvS//9hhL/2////8g2Eft+"
+    "l0rdfq0P/93+GIX/t//39X5BUn/pN7ejG9Uvx9L0v/Bgv/b//61/D/39apNvSX//7v3y4bf/"
+    "b//f6/IZkP+6Wr0t2qX//1d74S/9v//X2//19+1Sf0iGH//XX1vS/9v/+60v//bWK6Stuv//"
+    "+m219f/b//f1tfrtetjpvVJf/sLpdq3r/7f/9pXTa/+sbWk2xSX/9owtoJttGFhkHfT/9v/9"
+    "urQTa9pWSHbRftbS+lX/thBcNJhkOOwwlsMJcqwyv+w//20rCUMI2v9pwwl9aTbS//xXDCCb"
+    "CsQrY1hra6sGtrrbaUNBMQtbSYpit/VvSBf/ak4ZzCFMbUkBsRDPU2QYrlAOawzyBFbkHrIP"
+    "WauUA5rCFAbGlEINZAhPDCCpqUBmp2gSg9D/9hdNNBrDC2//YXX+xCnkmF8U01vXTtf/hoaB"
+    "hAwQaEMEIOIjiyOGQCwCwwgYWIMEGgwh/EccfoREREI2CPEcRxEREa/wgg///hJ/6/0n/X/I"
+    "HiiX///pdfT+n/+tpePX9fhfX1bSyB49NKvptL7/1IHg1wEYA1CxdKvSEdtJLyBmDU/2l/YS"
+    "2wkl8eGGEEQPDXcJBBhBpYYhMQgQMgUVwsGaAeCsF7WIiI//////////////////////////"
+    "////////wAQAQA4AAAEDAAEAAADOBwAAAQEDAAEAAAB3AgAAAgEDAAEAAAABAAAAAwEDAAEA"
+    "AAAEAAAABgEDAAEAAAABAAAAEQEEAAEAAAAIAAAAEgEDAAEAAAABAAAAFQEDAAEAAAABAAAA"
+    "FgEDAAEAAAB3AgAAFwEEAAEAAABoDwAAGgEFAAEAAAAeEAAAGwEFAAEAAAAmEAAAHAEDAAEA"
+    "AAABAAAAKAEDAAEAAAACAAAAAAAAAAAAwBIAAAQAAADAEgAABAA=";
+
+static const char  fontdata_18[] =
+    "SUkqAEARAAAmoCq/////////////////////////////////+QyXe5DJDVchncgthMyB4NFk"
+    "TMgeJBA8FKE06yB9ad5DbxIgScCpNkFYdSGnQgrOQbKENqhA3ghmWQz2QVRyBxZDMoQbJ4XU"
+    "g0YQl4IHhBhUm4OggwoIGCBhYwQZBuJggYIHhf1CJwazjaSdJNpqEGFQaYWgSwmg9d6yGanQ"
+    "Qb10m+gjxMKEGEGlhAtHhhhHyf/4QaVpIw3rZBpelQQbCpp+EFSCDaCDe/XSOMwbSfSDpJN3"
+    "1TYKqMyraC0nQTfT/pYaW0gv06dKk4Iz8+K4Qb9BUk2k/+ugt9+npbTXVtBcINwnWYAnTNg3"
+    "77f1+v1aS+k3dVXC6em0YXSqkrSv99UvX/S8N6q3C6dJ/0tLff/r9/S6pJsN0RB6rH2/S9JL"
+    "XX/r0v//WwxRfqt6XvXpaX1fv9f+lqlThjrf+tfS//////6pbdf910vrS9X+tdf9LSWsKQ1L"
+    "pfr/9fuv/f/H+1rcIzH+v8aS9LS17/yGwU96S0klsEf2OvePpfj3X+I/2v0l4Svr/S/SStf3"
+    "/9JwkwqevXf9L+m6/rvdLihW6vpfpL8jmktpf5A8WyEu+6She16/rSXggekwwgr/XvbXr2E3"
+    "1/SSXgmpHPYaWv+3tpJNEGt/H1/0viHhBpMU3/SyGoYhaZCg/v/0gSS7oINWtf9ifDVCSC6T"
+    "/X+Qa9Pnx1Tph3/0yEAkpr3Ba/pQaXBBvTUMNtf9hpI4mD+PX9JpdBvRwnV2F/2HhBOQxhFj"
+    "9f1mBh/TxBB0gjiDYYRxL1wbSIP6Tdj/X9QgbSxdJtQgg2xX/kNVPhA6t/1/SF/unpINwvel"
+    "DMbSCOG1b/IM4vX6TaW9OqQOGvogzCmD6TaT/5BSEL+k6X7aSTbSX+G0km+/9f+r/r0nDBf+"
+    "/TpN/9V1paWRjv6VNj/+kt7deQPBe9f0nQj99XS8geWEJe/6t/61fpf+lv8Kv0qb7S/FX9LX"
+    "9a6x1/X2/+v0v/pXX/0v20v110t69fr6IZcf/vv9daVENXt+vX/9L7bS6/9SGo//S9Ja/1/s"
+    "Va6t+ldZAu6V6X1/+l9tf11069//pZA9RX6/T/5BWhddK63p0krSSwnr9JOtsP8hteQLMpv0"
+    "tLtL1fS7/177Yb1qmvWk3X7aXaSWv/SXTcHr0nTa6IGBDXbSvSeku+r0ttJhhFIBO/XXS1tL"
+    "tJ0u1CXroPSSdWwj3f/ptJNbQSbaTaSTaQQXXw8JbDCCiE1117DSIZsBBlusMJENSAg4YQTD"
+    "CSsMIIILetkNQgkmKDQYVr00rFRGrEKJrDOKYqmKCC2FqDEVgwmEGnodqmvhbCYShhAgsREh"
+    "phpCDBBr0hUMIRBhCGCDCEMKCyGScCItpdCIiI/6S+9VX2uvvSX9qlXvWuraSVYtpUO9Jdqw"
+    "ttIdtLbS2mFiP///////////////////////////////////+QEwate8gaApwQPArqIZINtZ"
+    "A1DU1IHgpaEDwZCCC3wQPA4jyC2DJMgpIyG0BkFdQQUCZDUKSBAuQ1CZAuDIYBkFwZDAMguD"
+    "IYDIMAyGpqQ19CGNCGYnINYMg1DRBUBkGsbBEYDchgQQwwQYLlOGCIBc1A5GAxIEVENnkMqZ"
+    "BihBp4hoK5OB4KJBbBU1IbYxODBEaDORcGpYIH//63hA4MIHBhBxX/+QXDUJrwsIMIPDYRoC"
+    "MMGHDhGgOGGCDBBggYQYLDBBuEHDCCMw4YYYRmHYYaPBA3DBBhMEGEwSYYV9112EGpBXoQiA"
+    "gQNEDFCD6EGs5B6EDByCUCBA5AxQhmUIZtCGbQhmUR8Gg0BSonDMZBrwIEDZBqOQiAgQMMg1"
+    "nIQgIINZBvQg0YDNAzQgYLIKMdHAb0p8C69MIoCB3QdBB6IaEMIhsthEM9oIGmmmmmqaurpB"
+    "NsIG0E2wgbSDzAOZg9qnp9yGUdIN1BV84NozBh7hsJB3BvfdBP/6W6tukGHhtIMPDaT/7v6a"
+    "Qbw2lST8INhAgbT3QT7f+En/9LvvSb2wk3vXX/dfuk/pekHSb+0vDf+k//pevpP9P6T9P/uv"
+    "aVpf/aCDae6Xt/6X/4QX96Te2k3t//7pd09OlpfTpN/r7f/X/6XazCek36Tfpf/3+rat1per"
+    "03ML9Vv/r/+lzCza7et7re//+62vaTrpeOk636X3/pf/pfaXt9P9P///dPe9XX/pXT+vt44/"
+    "/6Xv/et+t+uOP/zCpNpNpCl5DIBocgeDj9Ot/Q/6Q/+l917+/7/j+6Wt+rpJeFh6RDH3T9Lt"
+    "+v/pe6+3pfpf/9/vt1el4WDrCDNzrev/1/6X3Xt/f99f7q2l1pOlXhYapBH2//qv//9L3X2/"
+    "fpX1v+6+1bStJfCw60E6t0/r+QIfIEOv/oL7r719f6//6fb30vIM9kM2wQQYaSQSf1vS9v//"
+    "/S919vr+vW/3S1/SbSS9BoMIIMNLqK6fSr/+v/S+/3++u+r/vvum0ukvVMIEGGuvreEF////"
+    "pe6Xt++kr6TD/enp16bX+8IIMOtL6fBBL+uq/+l9/t6+v6UH/a6+x2kl/hBBg0kl9eIL////"
+    "S/S+/9L8JMpwZf/03tPSX1hBEGsXDDS6r7lQGX/XXj/S9v9v/S/BBSoDb+6WvVWqXu0EFgw0"
+    "Fr6Twf8cf/p/pe/+EvzaMH/e36tulX1ggsMNLr+58IH///F+3+3/k9P4IMP966b9ul9oMIIL"
+    "DDS+kqfIPT/+fBo/f6Xt/8L9oN/669NxpeIhBCGGlqut7f8+GM+GP/z6fv/f+wX7YN/39Wqb"
+    "SXhAgw6/6fb/w+H//b+69v/H9sgyDP3SbV/0vCCDBpdaS+39f//7fuvt//7kDjT+//Ta/CCD"
+    "aXpfe////+w/uvfuu67B/3rq6Rvel8IP/0k/d6773/4b919v//bDf+urql6Xwgem/WL3f/9y"
+    "BhP/7f3X3v7+2/7f1+3pfDC/qkv/X/3/7fuvt+v+7/9Nq3SX18ML71V/f445Bqp/7f3Xt/Xp"
+    "dv/a/6Tevhhggm71WbBv/1+w//t+6+/f37f+9daq3peGGEFvouqSX79e//7f3+3v7+2/+urf"
+    "XpeGGEE3bfpd/f///t/pff19bf+3TddJN6XgwZDXFwghd0nCST++l9//t+3+32va/+l+uq3/"
+    "hh4QVthKIRPO+r/3//byBiPS9vet62yGU9f2vt6T6RDKjww8IJtimkkn1b0sgXjv/9v9v9vt"
+    "Lutv+9666pvS/DDwQLDCYKv/0v2//2/4r32/ddv911Y2Nb0vwwxCCbEV76b0utv/9v9/vdLt"
+    "L7S7pivSfr8gz2Qa4DDCCIMsNpJPq3r///t6XXt91t1t/69aV6/pkMzwwYQSDYaXfTelrt//"
+    "t/v7G2ltpbeltpe1vpV9qDDCCVsJK+kwyGaPpft//t/17tpbaW312rVaTekvrQYYIJNhpVeE"
+    "mwuEF+w//2/3920ttL7S20t9K9L+8MNBbDCS+kwYSUJa7f/7D16/bS20tu0uDBfaV9Jf0g8F"
+    "sGEltQSYYLZB0KFTDDVNNPTbUFTdNN2GEk2GEk24YSTYpqtK01C/WHhYYhINNCEGKCEJDTDB"
+    "gqaaem2hp6abhhhBJsGEEmw0GEk2v1YacfvQeE0GEQzKyGcQEEGEECIahQQIBhiCINwZDAMg"
+    "uDyCgGwYUgwnaIOIIYTuxCINQMMUQagcOIRDUBhhbuCTBkMwGvsLDwg0GF8hAZQUgQNnDJMM"
+    "r9b1/uGFhgtwwthNVVf+KQiIMEIiDBCIgynDIBRERGhFwYISBPqIkNGogwX4iI/QiOIiQPBZ"
+    "cRER9cIzA2gb+qCB/WEH60g/rQNdJJGxlOMjgbQaOpAmg2D1CBj+oaQg1egoogahpaXjQMKa"
+    "gPBjRA1Dbv4Q1sIcf////////////////////////////////////IFmpA0AkvUhkhlQQPAr"
+    "gIMgeFsgeBxBAkggTA0rIa9kNe/IZAJBA8vIHgT+FIHjmQPBS8gyeEth5A9HIa2BIgfkEDwU"
+    "iFIHxBA8FUghtkJbeHtL/IElkMu/S291UgeL5A8Cf0YOQPHNNMhleQZP/+6d0g4dNN3rfpV9"
+    "If/Xv00vX/yBoGoP/84n+l7+v/q6/7VeC//V167CDXhf/V1/xH//6tf/3/9J69f//9tf///0"
+    "v////2////9LX////br///9LQIg2UyE8gqTIa08hqzyDZTIsyGu5BofyDU5F8hiZDVgizIbZ"
+    "yGs5EmQVqEVyGa5F4hlnyGlBDZBA8G1yDJhA8NQC/9W2CBB4IGCBhA8EDwQPCBgpDK7ILzwU"
+    "IGEGChAwQMFCBhA1BQoI1hlQUFIaE5F0IOnUgmpBc5BjyDFZE0//rEIPCYQYQwnNQGHpkMs5"
+    "BufXIFzhNMhlnTCDIGDhMIZDYdQuSsgwcg5/77///VtPQYUI2OEeGwoR4iIDRTChdAsKmmFT"
+    "CPjYVBhHBYVUcFhDBQUgXoQI4hsopBjg2jYFzQCDwNzYLr/6sI4L0wjwsIIHQQbBUEGsJ/oL"
+    "008JoIMPTQQa1QQcF9qmug6TtV/+k9BvRwdBBoJB0m9J9HBshl+QMfoLIZfo4VHCZDL9HBtB"
+    "NkC/o4NhBByBj1UOkQ2eQY+6bvbV/S//bp9Ag2gg6TpWQL8k1EJ3fS2IQYhOxCdJ3QIG0ndG"
+    "3rmI8DTd/Wm60qS/9Lp4p10nSfVngzQg/6XCYTwg6txTpcQnVx/6e90m66//b7ptL3W+rq/0"
+    "F003V03Ta9Nf+2utWlrpf+l709Ol/77/hL/3Xp66f/+r1bS61//bHq1/SfXr/S6p69utfHH9"
+    "906em8UktshpYQ2P6X7+v/+/6Xv7pe2vV/2lzH7S1S/CBw/2/0hr7//9Bf/7/X3/9+k2r1SW"
+    "2g0vS//1///CX//e/8H+2lpXvVqkltoNLxEgeBORvr/9f+v/6//shhp/991iulBAsGDRww18"
+    "hkArV+vx9//F//7+vynBo+3WldNN6QMPBAkgwYS8ETv/9el/3////sP/W999JJh6SQYrwjMX"
+    "/8pAy7f/zyf///+w//dLSt+m9Kg16Bf/z4K+0v/b////w3+3X3vapfpA16X1/7aX/t///1+D"
+    "f+tq9JvSvpYYXpf/+wwgv/b////wb/bp6tbvSv2h9L+7/Yr/2//7/vyCuR/r/oxvSS/H/1/8"
+    "ML/2//+tL8H/erSel36/9L6/vBgv/b//f/8P/a/apN2kl//+22r6MArf9v//+n8g1O//TpPS"
+    "Tb6//+l/4S/9v/+0tb//Vtb7VK70iC8f/r1drel/7f//9f/69R/vpJf//q2+3pf+3/+3Wtr9"
+    "er7T0k231//YXS7X1/9v//StJtf+1j40rikq//RhbQTDbRhWyBA+n/7f/7faCte0rtG1qnSb"
+    "df/20uGk3YS4YXv/2//20oaTYRtf7Juwwgt3pXpJf/sILgwgmGQsMMILYYXlIGZ/2H/+2lDB"
+    "BMMJfDSjiuvSt0l/9irMPFMUxVsUnDTtPTYaaYTtO2GEopiFoM0WEmqTenVukP/6jmEnakgC"
+    "6IaE7DIMDJIDBThokNCdyC6cgunLGSGCEYHJANxChMLIaBEQqakMGn0CTB6/+wtIMINBrYLf"
+    "/2F/+wp5INegmmFtNPW//4NDQMIMEDQgwhBsRHEMjhkhAsGEDCxBhBoGEP4jQ4/iIiIhHATi"
+    "OI4iIiP/hBA3/X+Eg///pf9f6T/r/ZA8OLf//+l16T1///a6/S+k9aj//a5Arq0q9JtL//wY"
+    "YQLdtKvSEcdV8gaIddtKvTS20kvj2GEkvrYYIIgeCsOEggyB6sIOtiFFEFsGRPE6AZgsLDCY"
+    "XsFkDYDScREcf////////////////////////////////////4AIAIAADgAAAQMAAQAAAIEI"
+    "AAABAQMAAQAAAIsCAAACAQMAAQAAAAEAAAADAQMAAQAAAAQAAAAGAQMAAQAAAAEAAAARAQQA"
+    "AQAAAAgAAAASAQMAAQAAAAEAAAAVAQMAAQAAAAEAAAAWAQMAAQAAAIsCAAAXAQQAAQAAADcR"
+    "AAAaAQUAAQAAAO4RAAAbAQUAAQAAAPYRAAAcAQMAAQAAAAEAAAAoAQMAAQAAAAIAAAAAAAAA"
+    "AADAEgAABAAAAMASAAAEAA==";
+
+static const char  fontdata_20[] =
+    "SUkqABATAAAmoDgf////////////////////////////+QyQy7IGwGXPIZILLkNA/kDwVrIW"
+    "3IHgvBA8FqE00sgeC9pp5BWhIFSvIHhpOQPDToQK3ILYb01TTINOELmCJwypBY8FVsgy2kQ1"
+    "6BSCocEDBSDQBEFfCBcWINJwQeF/qDCDSCD0m4eCBhSDZWEGFwTwQMIPC1VKQa6keMPTpJu8"
+    "IMKEGmuECwg0fIP3dcIGgg2kE9JukkeGwqDQaWECwj42EEG//wiRhpN6ON0lZDSetBBsFTXw"
+    "gqQQNoJv/9HnJetpIJ1201SSbCpo0JroLQTdP/+EFh6b1ekm060mwRp5mNwQb8JUrat//1uu"
+    "kk+laeklhBaBA6QdZsCsKcAwqdK/qukv3/pXuvbgum4TaMLpUq3T7u9KltVaS61bfpcLp6/p"
+    "Kqp1vr1/1/qlcNpJK2wvfdL0tf3//X/S+qsGMjvrHpuuvS6XS//6//SVWw0c6X/q6+lr/f//"
+    "/pfXhj1b/9L0uv3+mvX/9JJLyGtiX9PX+uvr+/VePpf7YR9f/XGkvqquv/1X+u0klwUi3pL/"
+    "/66Wvf+Qbi/uqWklVhGaY/Xj6XpD3X+I/37pVuCT/36SX6Vpf1/0lpQl6vS/qv0lbr/720u0"
+    "h7hX+/6XkWfVpfv+6pRVbXpL+kvggaqw0r/IHgmELd7aWwldq//SSrwg6qGEtfpW9hoJKu2v"
+    "S/0kvCakWisNL/Xg8MV5DNp43/9IL4h4QaTEJv/4wwSSkEUf6X6gklrhB0+v+yXBmprW//0Q"
+    "06l5mK0k1DDv/5JgUIJrvBf+lB1wgbwnTDbX/Yejyh1sdL/tLhPo8TW9fqgw0kmyC/Eu//0k"
+    "cGH9NxBA2kEGwwjaX6hvIYmleP6X6UEG0tp0n0cbYYS+lyGqESCDSd//+hbXF4TaQSBuK/8h"
+    "tJ8I8w0m/9L9J673VINsL3+YbSQQbSv8gflZA4Cf6TaW9dJBsNJeQPyjD6Te3//+k/+2lTtf"
+    "+G0ltW/+kv0rS+vScGC/9+nr/1+utcijv6STY1/6S3t1/SX6vEfuu6/7/q3+QPDZi7fpa/0r"
+    "/RA8PhC56VN7a/il+lr+tdY/+vt1//pf+krr/6X9/6WulvX/1//+2k9f1pUQ1/30vS6ohmv6"
+    "X22tr/6RBsH//9dV+v9uv0m/SC110vVf/pfsV/9aTrkDELel6X//9tfpfpXX06/pZA8Hj/SS"
+    "6d+sgy3uul+39JWklhB//utsN/BSB4b4lddNpeler6X1rpJX3DtcKn/pdbaTpVpJaa1f/TcP"
+    "+k0m0tItXaX16t0l+vpJOkw0GQg71r7+h2l2k2lVqEv+Hpba2EeRrn/S9patpJthJulbSQX/"
+    "D0km0mdWmKtfdJtJNYYSuGEmwkraQS/4eEttBMQg09de2kQaUiygwiGuTk5ptKmGEEEFtexE"
+    "JJimgwv+kmDBLWK2ITBgkrBgkCC2qwYWDBBhBp0hSDWGIXwuExCoMQgQLERIauiQhggwr8Ug"
+    "whEGCEMEGEIYUFkMkMyMgeC/EMu+qwZwMgMo4B4axwDg8B4axwG0LxEW1SxEREfetL9Uv26p"
+    "L9JfVvpL20kv+qSxbSUd1S26YW2kO2ltpb1tNYj/////////////////////////////////"
+    "5AWBqr/IGoNMEMgk5DJBaGEDYGKyB4FlZA8CjQgtgYghkJ/IGYMUIHCLIKgkCKsC5QCHQNcl"
+    "oaCWBqlIGYVAMFWGCoBcqwXKgCDoDBUBrmoFQ1Bg6g1EQGmVYaREwZCrDSIoCqDIGKEDGhDC"
+    "ghnGEMAyBCchgvZDRWQUYQzaENEyGlMhsBfBAyCuMWRYFYjYaBKcNPQIiYF//9reEHIuG0HB"
+    "hA4r//IMEZBcOuaAeGnCDBA+wg4Pwgf//94QcMIOGEH/+uE1tetMIOyDIKBFAPIKCwhpi5DK"
+    "FQigGEQzCCGaQQzCCGaQQzCEaBmkgFNokBlshpzCJwcMhpaEQgInBwyGloRA0fD8g3IINPkD"
+    "CCDTMgoRkDE7R8NDqvTCJAQNQzMEYQIhsoGZgQ2aBns0BiEaAXtNNNNIJp6baQIG2aAu0CBt"
+    "mgLsIIPtNO01YaIGEUQMISnwy9do0Ah3g6CD5BQG5BRbkG9oIO00000gmm0naCCDDcNhBBhu"
+    "G6DzYYNAxap91oNpN1BV84Kwggw120EHwb38JB//S/vSb20m90nrf+vVtJtKkvQQbQQbvcJN"
+    "Yb/0E//pdrekG9tIN7aT7/v970/pfToIN+2gvb/pL/9L+9Jv0m/S//tLWm1bS/90m19L2/9J"
+    "//S770n3Sb2+v/fb/SetL0m0E97r7f+l/+l6za9X3X6//3Xat02lpf6TaNr9e3/1/+lzac2u"
+    "3pN7pN71//rrTaT1pfT0336Xv/r/+l+v3q/q///71362vXjq6T+vt44pf/pe3Xt9X9X6v/9q"
+    "2u3Tpa/pX39ff//6C/X29X9X/Ecff5tV0m6FLyGQCm5A8ND9Pv0kO/pD/6XvXv9/3//1902l"
+    "apeFg9SGKE/tL////0vtfb0n9J//7XSfb3pLwsOqCe+3qvfr/6XvXt/fXfX+9P/pWlXhYapB"
+    "GZv0vqt/r/0vtfv//1v+1vbSbSdV8Fh1oJtb76X///S969vV9Vfr/f0v6bpLyB4eyDVYSDDS"
+    "0unSfS/yGeMhnhf/S+19+vqvW/+v9tdJL0GEwggw9JJ//S+///9L3r2/v++r/tdJtXSbWvWw"
+    "gQYaWsfTelW//X/pfa+3/pfpMP96b+rrpL7TCCDBpaXr4QS////oL+vvV/V9KH/f/sU2kv+E"
+    "EGGlpV7eCC//1/9L2/2/9L9JmoFn7paTf6pf4QQYaSX6XEwGn//4/0v0vf/S/CCkICn//0mm"
+    "6S/wQRDTKwYaWl/cgQZn8Lhf/X2/2/8JfkKdlICt+1dJvabSr6aCCww6+tJ4P+OP/0/0vb/y"
+    "xwQX5tWD/er/v196CCww0tf+Zh3///F+3/f+1/BMP+//bbSS9hBoILDDS6VJJvBh//zMGv9/"
+    "pe3/wX7Qb/dLSbS40vEQgWGDS//w3///37f7/8F+2Df/v/bVeEEIYaWte3hv6mYEZmBH/5nv"
+    "9fb/x/bIKYT9tbX09Lwggw0v0kvf/ff/9v3r2//9yCwn/66bSV18EEDDrpf+////9v7X77ru"
+    "tsgtB///zadJfCDaX9JN73r/3/7fvXt//9h/20tL0ndL4Qer0lj7fr/yGZZBI/9v7X3//22/"
+    "/fb1vr4Qff6T3//3i//b969vf39sP+2trpJN6XwwX/X/////2/tfb9L0t//rp+vX4YX3SSv7"
+    "6xxshpI/9h+9e339/b//96t6XhhhBPeqSNgY++v4f/2/v+//9v/bSdLpJvrwwYQVu3LrX/fX"
+    "u//t+6XvvW9bb//e2qXpeGGEE3elqrf+v3/+39/t9r37/7df+3peGGQ2ysIJp20sIKv2+v//"
+    "2/0vb+vS7/9dL0vXww8ECG2wk4SW+vX2//w37f7e37+3Xpe+2kk3peGHhBOGKiieU/t9ZDLj"
+    "//28hmR6X36XaW2Qy4+9tLS9W+iDRHhg8IKwwmEq3revW3/+3+3+3t+3W///G/Xpfgw8IJsW"
+    "Cqn6b0tdv/9vXivfdL/vrdW640m9fwwxCCbFL9X0v7//b639vuttLb17qOqW9L8geHshp2GG"
+    "CCIG92q31D0v2//2/69vbS20tvr13dJ9L9MFDDCCCbaVPq3pft//t/v8baXpbeu3tV031+mE"
+    "gwwgVtpLek3hL9v/9v/920tuvtLtL6W9JfvDBhBK2Et9JhkM2NLXb//b12ve0thpbd1tha3S"
+    "fX/wwwgrYYSSvCTDCWCX7D//b//dhpbYS27CXaTtaV6S/pA6Cwwwl+CQYMElIEB6Ww17/bVd"
+    "rtw2Ethpd2lsMJf7ekF/w8LYMIJO0ITIwXEJDTDBqmqemw409Ndgwgkwwwgk24MJJs1DTW0k"
+    "2mh/0HhUGQyQIGCDoMUEECkNCgM1iyGxQQYVkMKMgXUMMJAgbQIiAzg3ZqJEGpQwYJEGpQw5"
+    "0JENRWxXWCwyDWo/aw8JoMQgQMhsBggQYSBAuGJIAxrrYYUgQIwUhgQ4YhYYhbDQhbX4SYev"
+    "sJIPBNBgvkmBwCkaBU4ZAgzf+/v7hgsMLcGFhhU71/40IiDCERILIgREgsCBEcREaEQ4MEJA"
+    "sTiIMEIME9UIiP5BbBq8hkhnmQWy/EgeDIOQUuIZAuEDwZuIHgz0ER9IRHEWZgSgVf0ED9LQ"
+    "f1QQP0tB/pI5g/SpBEQuSe+iByA3HqEDH6UMKINfUUQNgJXfCoGFNYHhlxhAwvogbAzB/hCO"
+    "P/////////////////////////////////+QPFrIGoF8IHYG3PIZIbdkMg4CZA8CIIZAb2QJ"
+    "7IE0NbMg255DIAw5A8PMhkLwpA8H8geBZMgpzCCww/CkDy0IHgVhqQPFDIHgcaEFXRLbyB4L"
+    "0INvL/kFiyDRnpbe6aXrhNP17+0iB4PMhkL0bKQPB/tMhlTIKc//XfSD67vW/2l6Q/+vfqvr"
+    "///zyf6W7XkDUo/9LS/6/C//Ta+uGgwvBf+lev8R//+rX/+v/V/1///q1////V////9K0v//"
+    "/9v////S/////bX///9LnA2ycNKQkyDLMg2/kG2eQ26ZEoQ2oINT+Q0nIkyDEyDa5EoQVzkN"
+    "qCEmQZTkRyDTBF4hmoyGq5Aw5A8FNyBxf/03BAg8EDIbWoIHggeQ19cIGFBcgwfBQgYQMFCB"
+    "ggYKCBhA1ChMEU4ZkFBSGwQRKiC4yyD1EGEENHkFxhEqIaBv/1cQg8JhBhDCDwg9Mg1UIGKa"
+    "4KmmQaKJhAwVBhDIGKKE8g0UIEU9V7X//6sJ6DChQjw2ERAFzxEGGygyBjQho00CyCgMJpkD"
+    "AaDCPjZAvog1IaNFR4WiMB5BNCBhBDP5BQjIEUtHAUEgGAX/6ujYr0wjwsI8LCCDYSQQfX9B"
+    "emE9UEG9MI8L1QQcL7TTSYaIGDpMINTYEL/6sJA+jg2gg4QQOk2FSawjxP9BejxtHieEeJpB"
+    "h6ODaBA16QcF96fp96SS/9XpvhB0EHQQdJvSfQINkM2ZDKnoLIZvoEHQTZDLmgQbSchlzQIN"
+    "hIOQy5qr5DRMhnn/e2ldVX/1e+kG0nSdLIZXqzMGgU7vpcUxCdinSbdIOk7o2eshl+7uk602"
+    "k/pf+r08QnWldW9XhP+lwmnhOtxCbS4hPv/ff9aWv/pX9Wk6T19XTf6XTTdN03TpdOv/06aT"
+    "aT+l/+33ul1rf//oL/9fbX6/9tf20rS6/9LF61169Xr/S6rr2666HH/rTp0nxSS2yGpxAu/7"
+    "ft/6v/v+l7vul7a/f77c2q33SX4Qafpf9Cl///4QX/+//V/2vXulqqW6DX6b/X///9L///S+"
+    "H/1q2k2k9JJbaBpeIgu///X/pf/97/sH+2rdfFWtILDDQa+QyQ31/XX//9P/9f/5qDU/1rdN"
+    "PpJbaPEQzXkMgCsR6/H3/8f/+/r7IgGn7/Tq1dJBh4QVBivBFB///S/7////2H/aVr16qw8J"
+    "JBrwjQT/8gYZt6/59P////Yf7703tvST6SQNegX/8zBT2l/7f//9fhv+6/Sbtav6DXhBfX/t"
+    "pf+3////hv/XSvTekn6UGF6X//tpf+3////g3+9N7/6V6tD//d/sMJf+3///35Bk0/tddJNt"
+    "V/H0v//Yhf+3//daX5AkR+/03o3vpL//0v28ML/2////8H/tK10km7Wv/S/d/wzCBf+3/+/1"
+    "+/771elvSX//1d/pD/7f//WrfkGuP+0tX1b9IgRH//r63hL/2//7Xr/f1+/SpttJf/9em730"
+    "v/b//f1v/7V6er0r9L//XSttb1/9v//rptfr+1jY0ntUq/+197X1/9v/+0rS1/9XqqVtiqX/"
+    "9bQVtowrZDYPr/7D//b7SYa9pW2jCxu6V6//thG/aTdhLbC9/+3/+6VoK1/yKPYS3rSbaS//"
+    "hhLhhJhkMOwwlw15AgaP+3//aVpMMEc/aVhOGEuulfS//iFoMIJhhWKthhJYd2urBra922lB"
+    "ggmK1tJimK/eraSBf/asqIpgwVqGwYJBA2GCDhhA8IG2EDBAwQODBBw2GlFMLCKcDDCVVBu1"
+    "hJh6H/7Cjm0ExQanQMoUQ2F7IEF50DBLhokNghyGC5DBcEDnQHIgEOgFwYIKE1kNgOUGE0wo"
+    "OtoKw9f/a6DQaDC2Cw//sF/+xCn0gwvimg1vXW0//hhDQMIMEDQgwhDYiOLLhkhlBYMEDCxD"
+    "BBoMEO+I44/iIkCThILGBHgTiQyQaOxHIHh+EREa/xFBEM58fH/sIJ/6/wk///pP+l/kDwIK"
+    "////X/3r+uvStf///8ev0nrC+v9pZA8WvSr0g2l//7aXtpfqJTgi4GwGeP16QjtpJeQNAanX"
+    "tL+0tsJJehWwwgklX2GEgkmg6wxCBIgZiCCB+DrDChfCyBtDOdkcDMMcMLEREf//////////"
+    "////////////////////4AIAIAAOAAABAwABAAAATAkAAAEBAwABAAAAcwIAAAIBAwABAAAA"
+    "AQAAAAMBAwABAAAABAAAAAYBAwABAAAAAQAAABEBBAABAAAACAAAABIBAwABAAAAAQAAABUB"
+    "AwABAAAAAQAAABYBAwABAAAAcwIAABcBBAABAAAABxMAABoBBQABAAAAvhMAABsBBQABAAAA"
+    "xhMAABwBAwABAAAAAQAAACgBAwABAAAAAgAAAAAAAAAAAMASAAAEAAAAwBIAAAQA";
+
+#endif  /* LEPTONICA_BMFDATA_H */
+
+
diff --git a/src/bmp.h b/src/bmp.h
new file mode 100644 (file)
index 0000000..212dce5
--- /dev/null
+++ b/src/bmp.h
@@ -0,0 +1,85 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_BMP_H
+#define  LEPTONICA_BMP_H
+
+/*
+ * This file is here to describe the fields in the header of
+ * the BMP file.  These fields are not used directly in Leptonica.
+ * The only thing we use are the sizes of these two headers.
+ * Furthermore, because of potential namespace conflicts with
+ * the typedefs and defined sizes, we have changed the names
+ * to protect anyone who may also need to use the original definitions.
+ * Thanks to J. D. Bryan for pointing out the potential problems when
+ * developing on Win32 compatible systems.
+ */
+
+/*-------------------------------------------------------------*
+ *                       BMP file header                       *
+ *-------------------------------------------------------------*/
+struct BMP_FileHeader
+{
+    l_int16        bfType;                /* file type; must be "BM" */
+    l_int16        bfSize;                /* length of the file;
+                                   sizeof(BMP_FileHeader) +
+                                   sizeof(BMP_InfoHeader) +
+                                   size of color table +
+                                   size of DIB bits */
+    l_int16        bfFill1;        /* remainder of the bfSize field */
+    l_int16        bfReserved1;        /* don't care (set to 0)*/
+    l_int16        bfReserved2;        /* don't care (set to 0)*/
+    l_int16        bfOffBits;        /* offset from beginning of file */
+    l_int16        bfFill2;        /* remainder of the bfOffBits field */
+};
+typedef struct BMP_FileHeader  BMP_FH;
+
+#define BMP_FHBYTES  sizeof(BMP_FH)
+
+
+/*-------------------------------------------------------------*
+ *                       BMP info header                       *
+ *-------------------------------------------------------------*/
+struct BMP_InfoHeader
+{
+    l_int32        biSize;                  /* size of the BMP_InfoHeader struct */
+    l_int32        biWidth;         /* bitmap width in pixels */
+    l_int32        biHeight;         /* bitmap height in pixels */
+    l_int16        biPlanes;         /* number of bitmap planes */
+    l_int16        biBitCount;         /* number of bits per pixel */
+    l_int32        biCompression;         /* compression format (0 == uncompressed) */
+    l_int32        biSizeImage;         /* size of image in bytes */
+    l_int32        biXPelsPerMeter; /* pixels per meter in x direction */
+    l_int32        biYPelsPerMeter; /* pixels per meter in y direction */
+    l_int32        biClrUsed;          /* number of colors used */
+    l_int32        biClrImportant;         /* number of important colors used */
+};
+typedef struct BMP_InfoHeader  BMP_IH;
+
+#define BMP_IHBYTES  sizeof(BMP_IH)
+
+#endif  /* LEPTONICA_BMP_H */
diff --git a/src/bmpio.c b/src/bmpio.c
new file mode 100644 (file)
index 0000000..e332756
--- /dev/null
@@ -0,0 +1,663 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  bmpio.c
+ *
+ *      Read bmp from file
+ *           PIX          *pixReadStreamBmp()
+ *
+ *      Write bmp to file
+ *           l_int32       pixWriteStreamBmp()
+ *
+ *      Read/write to memory
+ *           PIX          *pixReadMemBmp()
+ *           l_int32       pixWriteMemBmp()
+ *
+ *    On systems like windows without fmemopen() and open_memstream(),
+ *    we write data to a temp file and read it back for operations
+ *    between pix and compressed-data, such as pixReadMemPng() and
+ *    pixWriteMemPng().
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+#include "bmp.h"
+
+/* --------------------------------------------*/
+#if  USE_BMPIO   /* defined in environ.h */
+/* --------------------------------------------*/
+
+    /* Here we're setting the pixel value 0 to white (255) and the
+     * value 1 to black (0).  This is the convention for grayscale, but
+     * the opposite of the convention for 1 bpp, where 0 is white
+     * and 1 is black.  Both colormap entries are opaque (alpha = 255) */
+RGBA_QUAD   bwmap[2] = { {255,255,255,255}, {0,0,0,255} };
+
+    /* Colormap size limit */
+static const l_int32  L_MAX_ALLOWED_NUM_COLORS = 256;
+
+    /* Image dimension limits */
+static const l_int32  L_MAX_ALLOWED_WIDTH = 1000000;
+static const l_int32  L_MAX_ALLOWED_HEIGHT = 1000000;
+static const l_int64  L_MAX_ALLOWED_AREA = 400000000LL;
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG     0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*!
+ *  pixReadStreamBmp()
+ *
+ *      Input:  stream opened for read
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) Here are references on the bmp file format:
+ *          http://en.wikipedia.org/wiki/BMP_file_format
+ *          http://www.fortunecity.com/skyscraper/windows/364/bmpffrmt.html
+ */
+PIX *
+pixReadStreamBmp(FILE  *fp)
+{
+l_uint16   sval;
+l_uint32   ival;
+l_int16    bfType, bfSize, bfFill1, bfReserved1, bfReserved2;
+l_int16    offset, bfFill2, biPlanes, depth, d;
+l_int32    biSize, width, height, xres, yres, compression;
+l_int32    imagebytes, biClrUsed, biClrImportant;
+size_t     filesize;
+l_uint8   *colormapBuf;
+l_int32    colormapEntries;
+l_int32    fileBpl, extrabytes;
+l_int32    pixWpl, pixBpl;
+l_int32    i, j, k;
+l_int64    area;
+l_uint8    pel[4];
+l_uint8   *data;
+l_uint32  *line, *pword;
+PIX        *pix, *pixt;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixReadStreamBmp");
+
+    if (!fp)
+        return (PIX *)ERROR_PTR("fp not defined", procName, NULL);
+
+        /* Read bitmap file header */
+    if (fread((char *)&sval, 2, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 1 not read", procName, NULL);
+    bfType = convertOnBigEnd16(sval);
+    if (bfType != BMP_ID)
+        return (PIX *)ERROR_PTR("not bmf format", procName, NULL);
+
+    if (fread((char *)&sval, 2, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 2 not read", procName, NULL);
+    bfSize = convertOnBigEnd16(sval);
+    if (fread((char *)&sval, 2, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 3 not read", procName, NULL);
+    bfFill1 = convertOnBigEnd16(sval);
+    if (fread((char *)&sval, 2, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 4 not read", procName, NULL);
+    bfReserved1 = convertOnBigEnd16(sval);
+    if (fread((char *)&sval, 2, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 5 not read", procName, NULL);
+    bfReserved2 = convertOnBigEnd16(sval);
+    if (fread((char *)&sval, 2, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 6 not read", procName, NULL);
+    offset = convertOnBigEnd16(sval);
+    if (fread((char *)&sval, 2, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 7 not read", procName, NULL);
+    bfFill2 = convertOnBigEnd16(sval);
+
+        /* Read bitmap info header */
+    if (fread((char *)&ival, 4, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 8 not read", procName, NULL);
+    biSize = convertOnBigEnd32(ival);
+    if (fread((char *)&ival, 4, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 9 not read", procName, NULL);
+    width = convertOnBigEnd32(ival);
+    if (fread((char *)&ival, 4, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 10 not read", procName, NULL);
+    height = convertOnBigEnd32(ival);
+    if (fread((char *)&sval, 2, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 11 not read", procName, NULL);
+    biPlanes = convertOnBigEnd16(sval);
+    if (fread((char *)&sval, 2, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 12 not read", procName, NULL);
+    depth = convertOnBigEnd16(sval);
+    if (fread((char *)&ival, 4, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 13 not read", procName, NULL);
+    compression = convertOnBigEnd32(ival);
+    if (fread((char *)&ival, 4, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 14 not read", procName, NULL);
+    imagebytes = convertOnBigEnd32(ival);
+    if (fread((char *)&ival, 4, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 15 not read", procName, NULL);
+    xres = convertOnBigEnd32(ival);
+    if (fread((char *)&ival, 4, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 16 not read", procName, NULL);
+    yres = convertOnBigEnd32(ival);
+    if (fread((char *)&ival, 4, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 17 not read", procName, NULL);
+    biClrUsed = convertOnBigEnd32(ival);
+    if (fread((char *)&ival, 4, 1, fp) != 1)
+        return (PIX *)ERROR_PTR("item 18 not read", procName, NULL);
+    biClrImportant = convertOnBigEnd32(ival);
+
+    if (compression != 0)
+        return (PIX *)ERROR_PTR("cannot read compressed BMP files",
+                                procName, NULL);
+
+        /* Some sanity checking.  We impose limits on the image
+         * dimensions and number of pixels.  We make sure the file
+         * is large enough to hold the amount of uncompressed data
+         * that is specified in the header.  The number of colormap
+         * entries is checked: it can be either 0 (no cmap) or some
+         * number between 2 and 256.
+         * Note that the imagebytes for uncompressed images is either
+         * 0 or the size of the file data.  (The fact that it can
+         * be 0 is perhaps some legacy glitch). */
+    if (width < 1)
+        return (PIX *)ERROR_PTR("width < 1", procName, NULL);
+    if (width > L_MAX_ALLOWED_WIDTH)
+        return (PIX *)ERROR_PTR("width too large", procName, NULL);
+    if (height < 1)
+        return (PIX *)ERROR_PTR("height < 1", procName, NULL);
+    if (height > L_MAX_ALLOWED_HEIGHT)
+        return (PIX *)ERROR_PTR("height too large", procName, NULL);
+    area = 1LL * width * height;
+    if (area > L_MAX_ALLOWED_AREA)
+        return (PIX *)ERROR_PTR("area too large", procName, NULL);
+    if (depth != 1 && depth != 2 && depth != 4 && depth != 8 &&
+        depth != 16 && depth != 24 && depth != 32)
+        return (PIX *)ERROR_PTR("depth not in {1, 2, 4, 8, 16, 24, 32}",
+                                procName,NULL);
+    fileBpl = 4 * ((1LL * width * depth + 31)/32);
+    if (imagebytes != 0 && imagebytes != fileBpl * height)
+        return (PIX *)ERROR_PTR("invalid imagebytes", procName, NULL);
+    colormapEntries = (offset - BMP_FHBYTES - BMP_IHBYTES) / sizeof(RGBA_QUAD);
+    if (colormapEntries < 0 || colormapEntries == 1)
+        return (PIX *)ERROR_PTR("invalid: cmap size < 0 or 1", procName, NULL);
+    if (colormapEntries > L_MAX_ALLOWED_NUM_COLORS)
+        return (PIX *)ERROR_PTR("invalid cmap: too large", procName,NULL);
+    filesize = fnbytesInFile(fp);
+    if (filesize < 1LL * fileBpl * height)
+        return (PIX *)ERROR_PTR("file too small to hold data", procName,NULL);
+
+        /* Handle the colormap */
+    colormapBuf = NULL;
+    if (colormapEntries > 0) {
+        if ((colormapBuf = (l_uint8 *)LEPT_CALLOC(colormapEntries,
+                                             sizeof(RGBA_QUAD))) == NULL)
+            return (PIX *)ERROR_PTR("colormapBuf alloc fail", procName, NULL );
+
+            /* Read colormap */
+        if (fread(colormapBuf, sizeof(RGBA_QUAD), colormapEntries, fp)
+                 != colormapEntries) {
+            LEPT_FREE(colormapBuf);
+            return (PIX *)ERROR_PTR( "colormap read fail", procName, NULL);
+        }
+    }
+
+        /* Make a 32 bpp pix if depth is 24 bpp */
+    d = depth;
+    if (depth == 24)
+        d = 32;
+    if ((pix = pixCreate(width, height, d)) == NULL) {
+        LEPT_FREE(colormapBuf);
+        return (PIX *)ERROR_PTR( "pix not made", procName, NULL);
+    }
+    pixSetXRes(pix, (l_int32)((l_float32)xres / 39.37 + 0.5));  /* to ppi */
+    pixSetYRes(pix, (l_int32)((l_float32)yres / 39.37 + 0.5));  /* to ppi */
+    pixSetInputFormat(pix, IFF_BMP);
+    pixWpl = pixGetWpl(pix);
+    pixBpl = 4 * pixWpl;
+
+    cmap = NULL;
+    if (colormapEntries > 0) {  /* import the colormap to the pix cmap */
+        cmap = pixcmapCreate(L_MIN(d, 8));
+        LEPT_FREE(cmap->array);  /* remove generated cmap array */
+        cmap->array  = (void *)colormapBuf;  /* and replace */
+        cmap->n = L_MIN(colormapEntries, 256);
+    }
+    pixSetColormap(pix, cmap);
+
+        /* Seek to the start of the image data in the file */
+    fseek(fp, offset, 0);
+
+    if (depth != 24) {  /* typ. 1 or 8 bpp */
+        data = (l_uint8 *)pixGetData(pix) + pixBpl * (height - 1);
+        for (i = 0; i < height; i++) {
+            if (fread(data, 1, fileBpl, fp) != fileBpl) {
+                pixDestroy(&pix);
+                return (PIX *)ERROR_PTR("BMP read fail", procName, NULL);
+            }
+            data -= pixBpl;
+        }
+    } else {  /*  24 bpp file; 32 bpp pix
+             *  Note: for bmp files, pel[0] is blue, pel[1] is green,
+             *  and pel[2] is red.  This is opposite to the storage
+             *  in the pix, which puts the red pixel in the 0 byte,
+             *  the green in the 1 byte and the blue in the 2 byte.
+             *  Note also that all words are endian flipped after
+             *  assignment on L_LITTLE_ENDIAN platforms.
+             *
+             *  We can then make these assignments for little endians:
+             *      SET_DATA_BYTE(pword, 1, pel[0]);      blue
+             *      SET_DATA_BYTE(pword, 2, pel[1]);      green
+             *      SET_DATA_BYTE(pword, 3, pel[2]);      red
+             *  This looks like:
+             *          3  (R)     2  (G)        1  (B)        0
+             *      |-----------|------------|-----------|-----------|
+             *  and after byte flipping:
+             *           3          2  (B)     1  (G)        0  (R)
+             *      |-----------|------------|-----------|-----------|
+             *
+             *  For big endians we set:
+             *      SET_DATA_BYTE(pword, 2, pel[0]);      blue
+             *      SET_DATA_BYTE(pword, 1, pel[1]);      green
+             *      SET_DATA_BYTE(pword, 0, pel[2]);      red
+             *  This looks like:
+             *          0  (R)     1  (G)        2  (B)        3
+             *      |-----------|------------|-----------|-----------|
+             *  so in both cases we get the correct assignment in the PIX.
+             *
+             *  Can we do a platform-independent assignment?
+             *  Yes, set the bytes without using macros:
+             *      *((l_uint8 *)pword) = pel[2];           red
+             *      *((l_uint8 *)pword + 1) = pel[1];       green
+             *      *((l_uint8 *)pword + 2) = pel[0];       blue
+             *  For little endians, before flipping, this looks again like:
+             *          3  (R)     2  (G)        1  (B)        0
+             *      |-----------|------------|-----------|-----------|
+             */
+        extrabytes = fileBpl - 3 * width;
+        line = pixGetData(pix) + pixWpl * (height - 1);
+        for (i = 0; i < height; i++) {
+            for (j = 0; j < width; j++) {
+                pword = line + j;
+                if (fread(&pel, 1, 3, fp) != 3) {
+                    pixDestroy(&pix);
+                    return (PIX *)ERROR_PTR("bmp(1) read fail", procName, NULL);
+                }
+                *((l_uint8 *)pword + COLOR_RED) = pel[2];
+                *((l_uint8 *)pword + COLOR_GREEN) = pel[1];
+                *((l_uint8 *)pword + COLOR_BLUE) = pel[0];
+            }
+            if (extrabytes) {
+                for (k = 0; k < extrabytes; k++) {
+                    if (fread(&pel, 1, 1, fp) != 1) {
+                        pixDestroy(&pix);
+                        return (PIX *)ERROR_PTR("bmp(2) read fail",
+                                                procName, NULL);
+                    }
+                }
+            }
+            line -= pixWpl;
+        }
+    }
+
+    pixEndianByteSwap(pix);
+
+        /* ----------------------------------------------
+         * The bmp colormap determines the values of black
+         * and white pixels for binary in the following way:
+         * (a) white = 0 [255], black = 1 [0]
+         *      255, 255, 255, 255, 0, 0, 0, 255
+         * (b) black = 0 [0], white = 1 [255]
+         *      0, 0, 0, 255, 255, 255, 255, 255
+         * We have no need for a 1 bpp pix with a colormap!
+         * Note: the alpha component here is 255 (opaque)
+         * ---------------------------------------------- */
+    if (depth == 1 && cmap) {
+        pixt = pixRemoveColormap(pix, REMOVE_CMAP_TO_BINARY);
+        pixDestroy(&pix);
+        pix = pixt;  /* rename */
+    }
+
+    return pix;
+}
+
+
+
+/*!
+ *  pixWriteStreamBmp()
+ *
+ *      Input:  stream opened for write
+ *              pix (1, 4, 8, 32 bpp)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) We position fp at the beginning of the stream, so it
+ *          truncates any existing data
+ *      (2) 2 bpp Bmp files are apparently not valid!.  We can
+ *          write and read them, but nobody else can read ours.
+ */
+l_int32
+pixWriteStreamBmp(FILE  *fp,
+                  PIX   *pix)
+{
+l_uint32    offbytes, filebytes, fileimagebytes;
+l_int32     width, height, depth, d, xres, yres;
+l_uint16    bfType, bfSize, bfFill1, bfReserved1, bfReserved2;
+l_uint16    bfOffBits, bfFill2, biPlanes, biBitCount;
+l_uint16    sval;
+l_uint32    biSize, biWidth, biHeight, biCompression, biSizeImage;
+l_uint32    biXPelsPerMeter, biYPelsPerMeter, biClrUsed, biClrImportant;
+l_int32     pixWpl, pixBpl, extrabytes, writeerror;
+l_int32     fileBpl, fileWpl;
+l_int32     i, j, k;
+l_int32     heapcm;  /* extra copy of cta on the heap ? 1 : 0 */
+l_uint8    *data;
+l_uint8     pel[4];
+l_uint32   *line, *pword;
+PIXCMAP    *cmap;
+l_uint8    *cta;          /* address of the bmp color table array */
+l_int32     cmaplen;      /* number of bytes in the bmp colormap */
+l_int32     ncolors, val, stepsize;
+RGBA_QUAD  *pquad;
+
+    PROCNAME("pixWriteStreamBmp");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    width  = pixGetWidth(pix);
+    height = pixGetHeight(pix);
+    d  = pixGetDepth(pix);
+    if (d == 2)
+        L_WARNING("writing 2 bpp bmp file; nobody else can read\n", procName);
+    depth = d;
+    if (d == 32)
+        depth = 24;
+    xres = (l_int32)(39.37 * (l_float32)pixGetXRes(pix) + 0.5);  /* to ppm */
+    yres = (l_int32)(39.37 * (l_float32)pixGetYRes(pix) + 0.5);  /* to ppm */
+
+    pixWpl = pixGetWpl(pix);
+    pixBpl = 4 * pixWpl;
+    fileWpl = (width * depth + 31) / 32;
+    fileBpl = 4 * fileWpl;
+    fileimagebytes = height * fileBpl;
+
+    heapcm = 0;
+    if (d == 32) {   /* 24 bpp rgb; no colormap */
+        ncolors = 0;
+        cmaplen = 0;
+    } else if ((cmap = pixGetColormap(pix))) {   /* existing colormap */
+        ncolors = pixcmapGetCount(cmap);
+        cmaplen = ncolors * sizeof(RGBA_QUAD);
+        cta = (l_uint8 *)cmap->array;
+    } else {   /* no existing colormap; make a binary or gray one */
+        if (d == 1) {
+            cmaplen  = sizeof(bwmap);
+            ncolors = 2;
+            cta = (l_uint8 *)bwmap;
+        } else {   /* d != 32; output grayscale version */
+            ncolors = 1 << depth;
+            cmaplen = ncolors * sizeof(RGBA_QUAD);
+
+            heapcm = 1;
+            if ((cta = (l_uint8 *)LEPT_CALLOC(cmaplen, 1)) == NULL)
+                return ERROR_INT("colormap alloc fail", procName, 1);
+
+            stepsize = 255 / (ncolors - 1);
+            for (i = 0, val = 0, pquad = (RGBA_QUAD *)cta;
+                 i < ncolors;
+                 i++, val += stepsize, pquad++) {
+                pquad->blue = pquad->green = pquad->red = val;
+                pquad->alpha = 255;  /* opaque */
+            }
+        }
+    }
+
+#if DEBUG
+    {l_uint8  *pcmptr;
+        pcmptr = (l_uint8 *)pixGetColormap(pix)->array;
+        fprintf(stderr, "Pix colormap[0] = %c%c%c%d\n",
+            pcmptr[0], pcmptr[1], pcmptr[2], pcmptr[3]);
+        fprintf(stderr, "Pix colormap[1] = %c%c%c%d\n",
+            pcmptr[4], pcmptr[5], pcmptr[6], pcmptr[7]);
+    }
+#endif  /* DEBUG */
+
+    fseek(fp, 0L, 0);
+
+        /* Convert to little-endian and write the file header data */
+    bfType = convertOnBigEnd16(BMP_ID);
+    offbytes = BMP_FHBYTES + BMP_IHBYTES + cmaplen;
+    filebytes = offbytes + fileimagebytes;
+    sval = filebytes & 0x0000ffff;
+    bfSize = convertOnBigEnd16(sval);
+    sval = (filebytes >> 16) & 0x0000ffff;
+    bfFill1 = convertOnBigEnd16(sval);
+    bfReserved1 = 0;
+    bfReserved2 = 0;
+    sval = offbytes & 0x0000ffff;
+    bfOffBits = convertOnBigEnd16(sval);
+    sval = (offbytes >> 16) & 0x0000ffff;
+    bfFill2 = convertOnBigEnd16(sval);
+    fwrite(&bfType, 1, 2, fp);
+    fwrite(&bfSize, 1, 2, fp);
+    fwrite(&bfFill1, 1, 2, fp);
+    fwrite(&bfReserved1, 1, 2, fp);
+    fwrite(&bfReserved2, 1, 2, fp);
+    fwrite(&bfOffBits, 1, 2, fp);
+    fwrite(&bfFill2, 1, 2, fp);
+
+        /* Convert to little-endian and write the info header data */
+    biSize = convertOnBigEnd32(BMP_IHBYTES);
+    biWidth = convertOnBigEnd32(width);
+    biHeight = convertOnBigEnd32(height);
+    biPlanes = convertOnBigEnd16(1);
+    biBitCount = convertOnBigEnd16(depth);
+    biCompression   = 0;
+    biSizeImage = convertOnBigEnd32(fileimagebytes);
+    biXPelsPerMeter = convertOnBigEnd32(xres);
+    biYPelsPerMeter = convertOnBigEnd32(yres);
+    biClrUsed = convertOnBigEnd32(ncolors);
+    biClrImportant = convertOnBigEnd32(ncolors);
+    fwrite(&biSize, 1, 4, fp);
+    fwrite(&biWidth, 1, 4, fp);
+    fwrite(&biHeight, 1, 4, fp);
+    fwrite(&biPlanes, 1, 2, fp);
+    fwrite(&biBitCount, 1, 2, fp);
+    fwrite(&biCompression, 1, 4, fp);
+    fwrite(&biSizeImage, 1, 4, fp);
+    fwrite(&biXPelsPerMeter, 1, 4, fp);
+    fwrite(&biYPelsPerMeter, 1, 4, fp);
+    fwrite(&biClrUsed, 1, 4, fp);
+    fwrite(&biClrImportant, 1, 4, fp);
+
+        /* Write the colormap data */
+    if (ncolors > 0) {
+        if (fwrite(cta, 1, cmaplen, fp) != cmaplen) {
+            if (heapcm)
+                LEPT_FREE(cta);
+            return ERROR_INT("colormap write fail", procName, 1);
+        }
+        if (heapcm)
+            LEPT_FREE(cta);
+    }
+
+        /* When you write a binary image with a colormap
+         * that sets BLACK to 0, you must invert the data */
+    if (depth == 1 && cmap && ((l_uint8 *)(cmap->array))[0] == 0x0) {
+        pixInvert(pix, pix);
+    }
+
+    pixEndianByteSwap(pix);
+
+    writeerror = 0;
+    if (depth != 24) {   /* typ 1 or 8 bpp */
+        data = (l_uint8 *)pixGetData(pix) + pixBpl * (height - 1);
+        for (i = 0; i < height; i++) {
+            if (fwrite(data, 1, fileBpl, fp) != fileBpl)
+                writeerror = 1;
+            data -= pixBpl;
+        }
+    } else {  /* 32 bpp pix; 24 bpp file
+             * See the comments in pixReadStreamBMP() to
+             * understand the logic behind the pixel ordering below.
+             * Note that we have again done an endian swap on
+             * little endian machines before arriving here, so that
+             * the bytes are ordered on both platforms as:
+                        Red         Green        Blue         --
+                    |-----------|------------|-----------|-----------|
+             */
+        extrabytes = fileBpl - 3 * width;
+        line = pixGetData(pix) + pixWpl * (height - 1);
+        for (i = 0; i < height; i++) {
+            for (j = 0; j < width; j++) {
+                pword = line + j;
+                pel[2] = *((l_uint8 *)pword + COLOR_RED);
+                pel[1] = *((l_uint8 *)pword + COLOR_GREEN);
+                pel[0] = *((l_uint8 *)pword + COLOR_BLUE);
+                if (fwrite(&pel, 1, 3, fp) != 3)
+                    writeerror = 1;
+            }
+            if (extrabytes) {
+                for (k = 0; k < extrabytes; k++)
+                    fwrite(&pel, 1, 1, fp);
+            }
+            line -= pixWpl;
+        }
+    }
+
+        /* Restore to original state */
+    pixEndianByteSwap(pix);
+    if (depth == 1 && cmap && ((l_uint8 *)(cmap->array))[0] == 0x0)
+        pixInvert(pix, pix);
+
+    if (writeerror)
+        return ERROR_INT("image write fail", procName, 1);
+
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Read/write to memory                        *
+ *---------------------------------------------------------------------*/
+#if HAVE_FMEMOPEN
+extern FILE *open_memstream(char **data, size_t *size);
+extern FILE *fmemopen(void *data, size_t size, const char *mode);
+#endif  /* HAVE_FMEMOPEN */
+
+/*!
+ *  pixReadMemBmp()
+ *
+ *      Input:  cdata (const; bmp-encoded)
+ *              size (of data)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) The @size byte of @data must be a null character.
+ */
+PIX *
+pixReadMemBmp(const l_uint8  *cdata,
+              size_t          size)
+{
+FILE  *fp;
+PIX   *pix;
+
+    PROCNAME("pixReadMemBmp");
+
+    if (!cdata)
+        return (PIX *)ERROR_PTR("cdata not defined", procName, NULL);
+
+#if HAVE_FMEMOPEN
+    if ((fp = fmemopen((l_uint8 *)cdata, size, "rb")) == NULL)
+        return (PIX *)ERROR_PTR("stream not opened", procName, NULL);
+#else
+    L_WARNING("work-around: writing to a temp file\n", procName);
+    if ((fp = tmpfile()) == NULL)
+        return (PIX *)ERROR_PTR("tmpfile stream not opened", procName, NULL);
+    fwrite(cdata, 1, size, fp);
+    rewind(fp);
+#endif  /* HAVE_FMEMOPEN */
+    pix = pixReadStreamBmp(fp);
+    fclose(fp);
+    if (!pix) L_ERROR("pix not read\n", procName);
+    return pix;
+}
+
+
+/*!
+ *  pixWriteMemBmp()
+ *
+ *      Input:  &data (<return> data of tiff compressed image)
+ *              &size (<return> size of returned data)
+ *              pix
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See pixWriteStreamBmp() for usage.  This version writes to
+ *          memory instead of to a file stream.
+ */
+l_int32
+pixWriteMemBmp(l_uint8  **pdata,
+               size_t    *psize,
+               PIX       *pix)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("pixWriteMemBmp");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1 );
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1 );
+    if (!pix)
+        return ERROR_INT("&pix not defined", procName, 1 );
+
+#if HAVE_FMEMOPEN
+    if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    ret = pixWriteStreamBmp(fp, pix);
+#else
+    L_WARNING("work-around: writing to a temp file\n", procName);
+    if ((fp = tmpfile()) == NULL)
+        return ERROR_INT("tmpfile stream not opened", procName, 1);
+    ret = pixWriteStreamBmp(fp, pix);
+    rewind(fp);
+    *pdata = l_binaryReadStream(fp, psize);
+#endif  /* HAVE_FMEMOPEN */
+    fclose(fp);
+    return ret;
+}
+
+/* --------------------------------------------*/
+#endif  /* USE_BMPIO */
+/* --------------------------------------------*/
diff --git a/src/bmpiostub.c b/src/bmpiostub.c
new file mode 100644 (file)
index 0000000..671be55
--- /dev/null
@@ -0,0 +1,67 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   bmpiostub.c
+ *
+ *      Stubs for bmpio.c functions
+ */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !USE_BMPIO   /* defined in environ.h */
+/* --------------------------------------------*/
+
+PIX * pixReadStreamBmp(FILE *fp)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadStreamBmp", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteStreamBmp(FILE *fp, PIX *pix)
+{
+    return ERROR_INT("function not present", "pixWriteStreamBmp", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadMemBmp(const l_uint8 *cdata, size_t size)
+{
+    return (PIX *)ERROR_PTR("function not present", "pixReadMemBmp", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteMemBmp(l_uint8 **pdata, size_t *psize, PIX *pix)
+{
+    return ERROR_INT("function not present", "pixWriteMemBmp", 1);
+}
+
+/* --------------------------------------------*/
+#endif  /* !USE_BMPIO */
+/* --------------------------------------------*/
diff --git a/src/bootnumgen1.c b/src/bootnumgen1.c
new file mode 100644 (file)
index 0000000..b898944
--- /dev/null
@@ -0,0 +1,273 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   bootnumgen1.c
+ *
+ *   Function for generating prog/recog/digits/bootnum1.pa from an
+ *   encoded, gzipped and serialized string.
+ *
+ *   This was generated using the stringcode utility, slightly edited,
+ *   and then merged into a single file.
+ *
+ *   The code and encoded strings were made using the stringcode utility:
+ *
+ *       L_STRCODE  *strc;
+ *       strc = strcodeCreate(101);   // arbitrary integer
+ *       strcodeGenerate(strc, "recog/digits/bootnum1.pa", "PIXA");
+ *       strcodeFinalize(&strc, ".");
+ *
+ *   The two output files, autogen.101.c and autogen.101.h, were
+ *   then slightly edited and merged into this file.
+ *
+ *   Call this way:
+ *       Pixa  *pixa = (PIXA *)l_bootnum_gen1();
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*/
+/*                         Serialized string                           */
+/*---------------------------------------------------------------------*/
+static const char *l_bootnum1 =
+    "eJy9nAdUU1m3x+8lkFACCQgYakJTRMTQQhAkAQIiNsCGYwsIiAoIioqKJqEEBKQoKihK1cGx"
+    "YVdsCaGpIOBYQFEJomNBDaIYNJAXymXmzZ3vWze+NW/WZMzKynL+v5xz9v6fvc+5qn5r4oJI"
+    "i0I3bFyzPopkpzp3U2Rw6AbS+jBS9Jo40nSSLdlBVdVj/X/4TvD6uNCNsm+RVYe+vpS8fBop"
+    "bsPIJ9akraPvVHf5zZ2hrqqvCgCA+kwfRoDsT7zsRQBl/wGePqUXy/5QifZZshGA/jkcPMFu"
+    "6MNYr8BYz/WRkaFRsQD5TO+N32QfUmcy3Bco568SSwuFOJoAt5mLS9EzdguJE7OFZPwOZji6"
+    "BMP8AhpxtF1DqMoL4rOFKHufT8StgP4DMwIFcDMf+h/M9JrLOO3BTBiWbYtYttqYbMV9024i"
+    "km3b+mW3q+xDC0j2N7YwUUGAUuKizb8AGW6kYkygJkrZiMmMkwDA0lPjP69cttUAptEOsUZ1"
+    "2Wv8sEbxM7E1Io127gPtXNmH00c1Upn8aHpCMSeRsxtMatUj5PFTjTI+2pMlL5liM75IkCCc"
+    "oSRIIWJx88iGUcxwJhvUICkAl7XUdxyb6vkcpt0esXaNsd+383rPd0Ta7Snfu4dmkzv0+84X"
+    "iOjcYk5K8e7k4sOc4htg2uB5suSPILGJQOTOFWJweXd55Fsczg0ORy+BQC/iJAsBnABjAGBW"
+    "juPOjlvBgel3kEv/yG8/7p3n1J/Tv1ogmsEt3j2s3yjj3uVh7RiGHXNPspCGB5UMCJlO5AtO"
+    "5AqUpponTc2TiBPg9LhECvBpi05kTonZJJh+R7nmju6wfnvO5F2I9DuUH9l6XvbhVEj/C76o"
+    "kCMkEgaCmGLQm0ggGLNbbNHcjVhsL4EQQsB3YAItsCCQ5KS50MjWuhqmliJXEBn5ta12f45F"
+    "pNbxRf5MC9mHMyC1vAYRkNqqZyyyd61YHScpFPazuClSPeN6e9eoMPELnkgKaBvReM3x7OIe"
+    "AGWfHZNdfCtH6GovoO3kSr8C2quMtUmztsTBKJwQU2iOzXkHvcmFiCgo3tmEoW/OhCjqZEIT"
+    "b3CEqI0ogSBRHZsSJLbFS9czwzGd6KKDtQJ1bCOOIwb9sFwaXdSDwccTLKWE8o+eLs2zi45I"
+    "pQbA0kU2Cm/Czh2BcVD/zbnvZHvAUkX2IQWKOzxeM48NqGKxxEDCYC9ZciBIPFEgwqRGEHa2"
+    "kuPZRfpcjCMW0/EGpzxdggIWx4330Pmo0AsT7SzXFBqZ8MIWkxZEoqnFlo55sg+dR0Tr3Dmr"
+    "DpCUAb6T7v6T2LPRU1oVL1do77m7uOlQFynhcKONvjhiiZGlYahzwLlT01Dndx6J7yfodLUe"
+    "+GoF020rXwKVc+47P5qqNDRIbtCsITVFswuFZHvPjfaoH+7oPhRWEv0FqI6UzZvVTHHD0Nwn"
+    "GIWE0+p57ViPxm6sugSL6wafu+iUh58u9ICrR55HtWUvk2H1PV6XkOVRKP3PhdQ3DXKEmJcY"
+    "NHm2mCcSeYICPhGNTeFc0ctQIrS6i3FY+45av4sf/LCPpGC4Lp4Y78cTscEkTh/w4Qd44RsO"
+    "Ly2UgsCUCfZJlZY64+A0yDOuluxFHKapmlxkjogGcgU2Y1GTxRYSf4BcIr2dnUQEeoibUw2P"
+    "inH2za7eRVexeKBEak8GgMtUMndr4fe7cLXIc6z2mNomx+ZViNRC/mDpiNopd/kqbDqW0TJV"
+    "l+nbphFxW2OBa+ui7pAPpLrpqTG65q6EMOVI5e/Oah+TzTLObyLfIdUfu/fq0RH/+sps4y6b"
+    "VaK5fHuDSEfT3VaPTxAzPtSd+SPiFcoMtLGaUkx9A0dDnn6HRtF4GO31RcItRGhQ+l04ghZ6"
+    "96w6m6SdJCwMDKxUKLVN49b4WC97WQD4qmrFM09a3+zacLU0soQa86lzNsdyvqRpocLyxDcz"
+    "F/xw+Pa67emEpinVqnfrKoXZEzWbxae2AXZWjpYNmwjNcCjkOVlnbK0YpzxjI4KCcrLvmFUW"
+    "YgRSjF88kGbAzgMTpGAPkcT1lDk4InutsULPy0QaIRMVQ8P3omztwpkd7LyNeBUn9mwmUwzU"
+    "kPqkUntg91f6A+7xImc4CfJ8PW5s5k15e1cfEQmUrxeNksTxsnnRQCaRbMhUDO7HYOukntEu"
+    "2k4ilK3fabD8WD9dwfOHzFrvJB2RcjhCmjZxkM9rxpbl59j6qfeCSmh0Ub+6Xy+RTJMZWSkA"
+    "zHox9YezhscUOBXy/P0Tqx/K36NUkY2ySUfGK/K9FG0SF93U9nU3jfaZlRLe9ZZnAlZdD/VI"
+    "mDn3F6NNeKcSpjiiQGM7tb1+jubVzWvGZZjol+hNvxR86/Pqr3PTRX3jPuyrtn+rdLyHrIhZ"
+    "ngtP5rbIs/lQlCANU03TCzVARAVl8zEn+8KPF83mcIpBJTV2zA8MCj2IU8J+JrqRG1hANY5G"
+    "ltCUaxr92jWqu/ESnLqUnyjtQX2tZwHAY7RzVF/9bRs4APLM/hOTDcrsgSMA04djAZ7eMi/0"
+    "rarTk90dygS1R1V+V2p302e6Pxinlf54oOk8wZGqPe7VSd81VQ9/7dK47GSxXuNH2zesx+3s"
+    "395XVL7avENVPVi3ZMvWL95qfQrvlaasndEQBHeLdsgTv9ZYjFPZspGDiAtK/PNGuBbKphuP"
+    "NBS+/f3Ckk5WhTO0T25qqzYrKzNLCSjSnrc5xs1eQ3Nm/ROLSkbm0YimhG+NMVYkVUGY5Kt4"
+    "2/1316Yfwp0rH/x1/lRhglXicWHuOjgOciegOxbdvlaEJSLCgZzAnDH3K0kBFVnkB+R4cZBA"
+    "1JGFFbjjB+tQtY3Mhioetkqql1o19LZZuYq+A8PPSxGa4BMbpH6y/aCAhw5fpy5g1dKAtpNk"
+    "ibqJLgYOI9/WewTGatCwABEMZATMoQDnx29m8nlM0JQYSI4vQisc3MhOLOaASRrRAFDzzHRJ"
+    "YiB1EK4RefofSidmwxrnz4tPR6QRSv+ho+m/wVY1kU5gCCfqPmmLOrCuW5ikrDvpxp496Sed"
+    "WmPnW/fba6XjVthiz6psKjHN3LNkXafFOrPfI8Obl66+63puaf444fIHksWMgr5L7vETt6f2"
+    "fWGlZh2+tHfKWcLlE2b3cUWWLupPdD8uh0PKZwRGoldrzc5gRJCQERj1OKEjMdnTv1Q792R2"
+    "S5SidyrD/bbySYU7psbCNwGkBLMij18XP3e6bvbkVSP594o4hvRge7aYtKtyFslm0vcYQnwG"
+    "VdWP9scLK0//8/725ZgPx3gGji8pSX/Uz74FR0NuB3TH4tpdHrACERpkBxaM2YFSrlCKMSCh"
+    "7+PYYiD7GVCNpWCl9EA+VzgvQV2QlaTPxdEdiRyxCZ6I7gwjfWQxBTxcdX2HFNQrWkLx21gs"
+    "BrUTu/QD6QSg9LPjc/Ln8Ww4E3JjMDQnTYeZ8sadsUHEBBmDX0aYljcWy2KatlfLLHysYm15"
+    "yAVfyhvfDI9EXsA3L9SAw8LU0o+zjLfgupbHSJIicsedNnlQ87hlxxfzHu02yybFi4LANYfm"
+    "vor9dLDuEnsmxp+1R+eIydRib7riuSmndsHJkJuDnwhvkDkIhMxBsaZsIjJamvD5WgG+iqEm"
+    "poT7uhMOJxWD5p2vqzSxCj3thcuf7PEz9ego2B23WT33kp4Jy6jKV2FdvmugS/n72toz7VcP"
+    "bPKavvDImfrQpQnthfvtaAv3zM2BcyG3Bz8xYpA98B8dsfpiddkmQqm5kMQ7fOq9vpP+niQw"
+    "bxcp7yWFvmjZ90nFCaQjW6lrAkngmm27mKVFFR43EtwSp7cJeJMvXkgxOxWkXMpnbetvpl40"
+    "GJ/sGVDWXf4MDoTcLvyEy4bswpK/uLihrcMLX29m7mIq4Wh11UWyTZVyrrbidIfNuit79mzZ"
+    "cuF4QHlXcMHCg6s/nTnl0sNYklLjQ8gvaljdfsTs7WWfxKt3v9ZNP+xvMKl17+NB9b5dU7Ev"
+    "7ulcgIHZI/cLumMjtdFcewoiMMgvLIHWFuSDisxjQK3HFUzLWWoGncGVKlH0mTHOSXOnrBjw"
+    "m7wg6Vx3l/5V8+wHG2MvXSW6ZbqbqBRMDikiNN9ueXLo1zUdH89hc65/XX/dO3uJcdgBR8fL"
+    "R2b8Axhy5zB+bMT0N6jfQAQGqyHUibOEOE8jAU7NhYtjdRGMJS9RLxOZDbXRIpqB50vWBGoY"
+    "g48Nj1f2Ll+NWpWtR+o1HtyLetpT7J0pnMExan7OwtJ/gIrOdikDBCIBToPcOmDHaGpct5Qg"
+    "ooGsgx5EI5J5bSBNQ5vsWqSloK+EAjILLDSP8lzK4cLkKxeMzJ8tLdV4RMIgv8Ac9dF3zqon"
+    "kglKwo1eu5zqVZ+z99nRzVMDKnQjgilxFsk1hastSsRlkzUs/DW3T4nPXdLU0DotKtFi6Qra"
+    "ocI3C7pnLtC8diMS/9jyiWfB3uYIE42nlvuXZNx6rtmvdjaK4h1VmZ4H50NuFX6CD7IKQSN8"
+    "m2TrI2koQrNMSa5b6Go2kbM6GHSfWcfGHSAX51j3ozckTIw816u3N5zor7lanRLat/Ru08B5"
+    "RzuLBCvbtMOvC+c211tqrd11QhAX86X6cdinuU6XFzml//jlCyY12tUkJ7xFAw4on2GQMwBA"
+    "hmHpmGG4JTMMRNJgEIpPZItBPJHdiSaFVQSJMVVoCpalupawM748SCzb1XHW4lA9MaQoTPDm"
+    "Ijc0WhetzATf/cHJU1qQnuAejAthhyWS/Z4l0gBRlRtjfrmyEhzt/8U3LP5rbNMGWsy077fY"
+    "qZb6T9P35etPsKH3q5l+HreqpOa9jfO9yTFlKhYhvSIPPY+Btov5kRPOXaRrznir+s7Brf3u"
+    "N56pVfDSqpCgh1k637MNdjfSKqtObs2FY8lnGuQcMcg0LB/FajirIjMNycKVs/OrQEPZvx/S"
+    "UtdRM6KCKT75nSJXHUZXz6Ys4lKl0hkpPvt8/Lcwzk2JOHbm7Z7lWnuk88e7KwQW2YazXme9"
+    "1b3e23pjZdmvdiVu674qTrlM2Vriq/krHE6+woL5MFxx6kR1RHCQc5gP7ZGkUpQghuSmXK2O"
+    "xRLyBrIAlJQGovtZaKZEisNLLdmdOKyURu/xlH2nZqgknJpfnDeAEtAsBwVZPBo90DhB2op6"
+    "9Z4GALt6vH9P4/OocCTk3uEnqtl/KzWM7DZkW/J5Xof8XQFP2wRNpXUWwTkZHfeVVKn+09Im"
+    "Lo+pFIwXtB6foD2vYF549JzpRPelHk4mMy6f4KN3FViqbtl9U/Dk2Lf13TUzNusZZO6YxpoS"
+    "Hx4W9BDG5fD/Yh3GIseLwbG9uZlsb07ECfhSV2yjdElRHoejh3Jiv/UmDNbxojtwKAEGrTxY"
+    "d48nktKUz5PIze0v6JIOYnOmPul0RVAccOEbyqx58yANCAihNMWp3F0ER0NuHoYix8guivTt"
+    "2nJEaJB58B5DkyYKcS9xXCJWBzsUFROVBByiFhYj9SE7DL5khqOFUkNst1TPp6rR7wbJnh7G"
+    "aYjnkcBLqJDxzXOBmzirPueLmUVwCuSmYei90TDFtYM8e0QUkGnQhygEPIBBIxDcyEVsFDPG"
+    "LgcFCOoJOd/cjrfDlclXZRipUr0/ES5GpAxyDcugKsPoksgKb28Jy4096YWxVLKYzJ+gx7mv"
+    "lOYMXloRcynWYdmh42WPYwxW2/smalUMbFZ55ikVMRY42/EZ7x+0uDzueOAf9gd29T7T9G18"
+    "cW3uB7C0xKbLf5UKD84mn2MYYVNcg6pFxAY5hvl/svHIyoq8rGVlF5xEqsZTzBh3gvWSNS0n"
+    "WSvj0/biXXHOBMXadDUnXeMl1G8+N2av1tJtsH+3pIZm0O/2Mc1h1tSW3+NCtpCk+2qJH8ZZ"
+    "frjBfXsZjiRfj0HO4YI8wmgPes4wEhblfuyusrFX6dlYykZKj7LVQuWzKoxklWUzXBdpTa4O"
+    "vWO5nB5vYaA5wXyZdNL6rVXRV4XL6k4dOx7Jkua2zzhG2bmd2vUPIUs+RyDnuoYcAZRdxDwm"
+    "X8R2TxAChkvJhmLZIifhid5V6g30BqBKl24N1jbSqIRMqZAplmaBCVIzskt2k0buKnQnl4j/"
+    "iuFtXkiWMCV8UdYABnj5lgw87ox+D0dC7gZwspfhMNL+T6335UJyGkUKZwazUX0srIRZreGZ"
+    "ISUQugkEMUGjwlYSFCzGMdSxUhyzCI12KXKOIO3DbgbKLQh36/VUd8Jly5fn5VwlkIkJ+LNO"
+    "LYsASi3nLQM6/V1/WUS31qUEKR6YVMKeYxVy4+P7BvzET8fQpVdj1F8V+M7MmlrDWZCo5FV5"
+    "bvfFpxNpFqETKTMcll66RZw7ngUS2Ra7wIWKdnAi+ToKI9GWabJWExER5FygM2tMOkqWKWQe"
+    "ZZFs8phwhNJEDD4en3aIk2fEkfZ4iohbLS+xeCJUGu1BqVDKxAKxL5zGuXVcew3T7Yg8jf9E"
+    "wxeyJ2MldsEgR4gyw9trkw2ZZCAY5NbSFLCNJAkurVhSS1Twc0RjcUUOgQM4NPo1C4vtrhfT"
+    "EholKZxisPwy+Bsm5DHxNQ5493BC/kZKjjocBnni/okWL+RJRlu8c4bbH3jPlqyACvSqslU6"
+    "3u53gmOXvPQuuq7gbuhvXjPZ+vCOhog4B2uCeumdh2otv6++3hrvMq3Sy+gB4FWbHzhYejg0"
+    "+dpap4g5t7Zdwjzqnfycle5LgkMhz+NDpQy9YajPS1N3IIKC3MhoNW1OxnC5uvokI8EmWME7"
+    "yOZOknVfKVnBQun7H3htbZu5n/Pf3EveWFFhvwrbejD8yJscAsqf1Vp3bWOEw9aFN+d8D3gw"
+    "qeDd9Gf1O1FT0s3NxVULBuBAyNP/UDVj5EzT65kmdYiAIGNiDC0VNgByuQpclBIWl+qNwdIr"
+    "SR1NGMC6cfyOM31urXB18iVw/WF1hWL3ZkTqIHMy2rG1aDirySbJtvyf8HhfzAWm0iMGiP4l"
+    "/0AL2mJZeEdq+r3Gux+n3pi2Jfr0bb3Hlnnd1oqTGbc3HPw9xfPL04eu/h9OUibGJ18qphwf"
+    "KFa/OYUFvuJPWFO2MQTeGHBEnsOHItJIY+eXsvYeRFSQLRndDO9oKJZNIkCpxUzHx5u54OaJ"
+    "buWTAQn0cx5tRVruljTF3XGfM11S1fxPXWfexqf7UJa6RFxs3KMe1X748I7IcQ51pwY1r7Fa"
+    "bnUQb2Sv1Pc68gXYtMglK82HcgiOhTyjD60Ni2Eszst5JxBhQdZk1lj54iVRIMXRJUUaqZwv"
+    "ICmKHcxlRYsxqSx2hYA21OrAccS2Q62OzYQo5iomkxlMByZwe9Xj8fYqQXmHMVIQ2K3vd57X"
+    "QxfDUeQ76TdhGEV1zRwPRChQJh9t3W5quKZJZ2ozhL1Z9HLFffpL9XXChe/oluTTX1Ox/APd"
+    "DcYbxl+tqsqay0jrf9VecvaJ1eTayN6pmPS6zyax+d7+khr19hKdd5uAGw7TKcITmbFwHOQZ"
+    "XndsZKJMzPchwoEyfNhfKmf0oVCch9/1RcFcv9jSitq4q3b/3EtJ6ionjbgdHgnjA/dHmBbg"
+    "GbMepp7Dp7op7+486LX9ZMlgqE3beEqBknS2+LGIvBNcFXXT+RxtWsEzS7t3bi1xEf1Wwc3q"
+    "v1JdMw7yJMZwSvkaAyOUybqF+xFR/q1e4SWw1WCTsErNt97W3ql1bL+y/9AEG2O1Kq/WZS6b"
+    "yYvCZ8bdFphlmDppseef0RdFd2nG/V7lkrSN+4CSdfRGbc3OD+/DNPaXnBoI2tvqKt32cRbf"
+    "YePCUhgSBbkhGMqhI/WKcfmnkEVnyBCM1pdCdw9bM8WpvjmOrr94M5NIqr6LVA/FycxZgIij"
+    "tWTirEeFhTmHeW8XfCRnBhWgTvf7H/OPvWvqldy4ZubWradvTOhCe6+v/XiHe/1sO+299F3P"
+    "45gHUmDipilvFSf5dMPh5GsLyBkvIIMQ+tfjOEPFmOyAKnTQ3gYvjkJqTdUEPZPQtPraWG3t"
+    "M87v772xiNXa7WPh92hX+/TLcXM3oW2mE1jX+7fTFD+jlSIEbfs9ReHS8L0q4Y4WJc7Fix7w"
+    "B7M8DiVX5i03NrlKc3diqC2BQyI3DHoAlF8n3lu3DhEkZBjmQZDXhiC9WiZf9bmtHFEV0XQn"
+    "2NjCrKyMnhfoUVLs/PBHdhm1raZsF2fXlfFHbLb5XsjA6b4knvCZen3tbxxqt9HhiM7fSivj"
+    "gY1B5KSy5vMRcBzkdmHoWsWI/2m6/RbZYSPILliNHR1mA+pYLEaLoOHKjAM8MWT8AlcwSZ/t"
+    "w+G4g4ydfgDwTmyoecD2xmG4UuTW4SfurUDWgQ5lI36TiJ0vBMw8USjtCieyiMjMpvFExFR9"
+    "wqCTnWR1iLiuQZRSKEyZLkjZxkVJNdP0CO6pKUbAPVDLQtyaA78bQkFuEn6i+gqZhNG6jFfD"
+    "aJvTXjuXnFZqqzC30yrjc4basplBccbc6s73gy5x5rXesdp+3edLb4e6RMVMVhPOyfS6xjne"
+    "4f/J8QRYMEt6eM827xDv5om/hS+eOBBIyhMr6Da57jnk2noOzibf8UI5wxrEFvi/dpyMFk/t"
+    "JYLSrdrmaKbFtEcVuUoeMzQ1bxaXxO1Jv3a+Vr9S592GiwcmHTweXHZXs53qH0PaPn+zUZdp"
+    "r2n64OXi2B8VTk6Pq5oO78yNA95smz5Av3Qd3oGjILcNQxFtpAxb8Jo1DhEX3AEVZwqlGBeu"
+    "dOjAh7YT3dbTSEDD65avEq/yqxOVFmUKBRjlncrVKcVXjFLbOHn5oB7ZvSHeL0XpqUkXvU8q"
+    "xgCbamiKHF4NvCxIQW4ZtMam38n9i30RofztBOimBhON4eMQa6ysnBUWv31IBRcfLLV2Zz6+"
+    "WHLbruwYuqe9aUZGmkhVkFbBWOZ+ocbscuuErZTTlPKUHURSZQZr9RnLPYepGcyDU6cyXvQr"
+    "PGd4Tegxo/LgVPJZBDnbUJARWgkZoVGqefgHD/ssqaeTkxaQrgY4q0Qn3tE5ZDMrwcvVudfW"
+    "6OzBik5lB60nKvpK+Ulrqz5kNm1TVTuTb71rzjhPteraM88iVZegUtanXbbx/k16fcr7V6DB"
+    "Lep96j0beE3NCbld+ImNBeSA5vx1XeEV+Z6KSTb8o1ccVr6+ZN2iHMu0U6juFR2ouF+dHqbI"
+    "aTozt8s9N7H83ubLvnu1P85etmE/eW3risE3Hk63rdNn1WJOBTjFBk46twIOI995wxEP/ubi"
+    "IhdEMJD3iRjd+2UOtXuxXkRTCyVUiPKdyXYErzRPa/ShEMU7k0+K3eb2mC/tY1E+cudx/ftz"
+    "tgYm9M1ctvBLtTDx6QxW/uvtxyK9rq0/7X1zPqOW4tzHsL/icqdqUhhxE6np26ZTG2qeaR05"
+    "vfLL70QtXWf8u6M5RDgrcpegOzZwV+LqUhGxQlYoaGzgVJLosmAv9WtlVIBCfc3TRyfzGBHd"
+    "+0yXobgG2B9R0WaJU3/kF+v4t10JVNn/VHPc0YPer70PzDu6KfHDx851Gc/zs0OXV969ty+j"
+    "kXEzCR3z3Sls+9MZkgAWPd+BzM1edh8OiNw3DAGOHPNz6744AREgZIOgA+VilgQnoNXSsCRx"
+    "YjFXOCNZXZBCo2Bxg/VkV4lAEC0iKp9nx5hpO0lXsVeFkcIUO70tG5UXqvFdsTgOIUS3vgGn"
+    "44h3IZA2D/4hRQHAWsofPs/2NcGpkHuMobQ8UuJ6cf94FyIqyA1NhqgW80V1CUK2kieKsAMo"
+    "JTKKcD7KVpZ8kXuCEMSg8A7RACA+RJqM8SgwgktFbid+ohr3D22eJDJeqcV9PP4++MQXf/SK"
+    "oiqJ/cbjl3MMs1uyXSA+LFLzeoRDe6Ko0G/iZ+u58yTPDpp1ODwNWLI3VNvCVzcUdyPOY8ez"
+    "0pg53DNcqxKcwdvSd0pR/CntSbZrLsHZ/tXbCn87QzrSwho+QzrJXiFj8zW/spCjJNVHZIMk"
+    "GyVT6sS0xwUS3KfZD3xyd1HVcmaevLezchbRxjl1xg/dB3ox635/n/AcfcWaP8WG4vkbN1Ly"
+    "5GEGpQe1vdf1ZZx5DLwH74TcUfzEsEGOwufPdQMKpGHmDVi+lJ43AAqIYcqDYBW9H/cBR3BD"
+    "9awi7cRV/1Ec54a/a29F5tFR+MRGD7tvRE8FAaaIikcBGjmOucx1UR/hHP9qjwHiGOviSkUo"
+    "gRTlcwVM0yOJiXipexXN0I0cTxVzhPZ+H2QYGtV1HJkpMtAjFYGpwteGSQfVHk3liVDm336X"
+    "2Zia6W81nde2wSmQ2wctQO4LJJAp8hujqOMIU5KHWuh4N4CfQW9gsYW9LMVBEq66Ad+fhdMg"
+    "uw52ALVSFpnZIaC3Y6sbiPRWMKONs02MM2aG0+LZQrxLqyvgRaUeS35mnAbjocrnF0Z4Jhka"
+    "TETE87fOz5gdwunsu/H5yooTZQnr6FfoMTqKeXgt7c5dtz/e2+Adgy0x6zO9pPAw7O69rTo9"
+    "3zNm4lccSa7Rryb2C40/PZxpfXPdYNveqOSHVhHT7h+H3yujIjcNWoDcdy4gB+Ty59VWHlCl"
+    "zuwGajCE7zSPCldlmhuvQioCuVJSu0ZN9/CQyeYfP4FVFBRylfUZA0ixdg8bdjej4dKRe4Cf"
+    "GAzI70DNaui6SLqlpR5n4TzfnD37Us+lngdjbQK6sieteLXe6LqW2bj9qb0WB28Hss+FphvM"
+    "f3O3mPiWZAwsXryuZfGLm427TCN/u7Fq1iEa67lV1qzfE2fAkZBn/Z9YL/AbMJpsEuDZEpsh"
+    "tKzU11lXUxqtTtVlZ869lfyr7pn6VzYty04an9x/atmppGbi05QVXr715V3urMGK5DgDpj1l"
+    "Hc26d6G44MTUwetU56AJB+/Bcf6S7u3IfwLJ3v+nUcKP3FfwPf75TyTjqqEXGNW8ye3vSJCR"
+    "ge5OiwUveCLOVY4QXIUS8FHq2BQimUxmMZnMGYIP9HYJWP4a9Is0w6O+SjrWA8BD0vhJBc51"
+    "7nDljnIpH3qvOay8IRDwQaT87+dW41jsYg6YYUgwNuSlsNfyAMBqpTY5PwiVChdHkUscdkwc"
+    "3zsah0gcZE9soZ/VjxdNB5L0CYTXtmRJIgnEf1/FE/3KFs4ABSkYLA5HNowAjgcxAaCyWHNN"
+    "7xw6GS7aSS7R/+kZGP9FNOQ73P4MNs2A5xxr7B9xhMEesiQnaOhiFJhhLDO1HUFiXpUoiyOk"
+    "4R3p/jSUskMF2bAjKI6JAnrZOkWFd26XwQmochHgxgjUdx6sRkQApeVpozHnzjUVgIQFWgSm"
+    "209k1yh+iwbPXshmSAzPuF5/qbN4WcU6lfkEj/lpe8blFCafv9GvTGhCtxP0gEdf9OCnsKnO"
+    "cv/+//Qcg/+iHkrHzn8eqhKhMtyGDr2hPHCDZNek4kQWQaOVbJgdFMcTiAqThSw1reAgcV1V"
+    "M0/QTEIB4Q3GQZjWDPh2z5ksl/j/9FiA/yIeyr2j9UmvquFwD/CzlvnZeXgHOUSczWurNvOr"
+    "pG+4U5BZmRsQbZIc75yYJ+a5PPHjZ1xc0nN5Ny0pWr+NdoisV9b5AQWvBDvbyj19Rhgklpaa"
+    "iBj+fs5iZAH04mwlmBIKuXOVGMPYQPpSlynE4B/axgOlGCn9TQeNK5ASDkptL8j2bexWo6rF"
+    "97vs4drt5J48OsPaV5h2miPSDqXbadDkkS1VdoowWk3Qg8J+YCqGGLpWidhK5hoMTCDhiR69"
+    "cwBHEBPZ9X0YbAqOXU0UyUadTnCzreDA+ybO9v/21Icyq+fY441ShInqApQxF2WNbcRiP2Do"
+    "qUYZpO7XaC4Ki8UNHTQkS1YHiZsEoqwUYRZWgCs6rc+V7b+7WcDiDsLj9DZLeG/LWb6bfyOn"
+    "pU7suHoBkTmAMumfBXoxG1QYR9BgizBK3FqiKraR3iBJ4XCM9KSChJwu93eH3Anl30jNBSgV"
+    "VeZG92Ms4ElvygAISE0s0F8Km/fD9cv3BJ6f3Pybjj0ZgwcwpATCIJnsQCbRUYFdxOOpqLRB"
+    "2d9i+03v4yuCzxa4wn/10D2UVINHFDo22KoKhuz9+vvlG9/fexx9YN3V854anSfeWL67/1tB"
+    "Vbh9QH1VYmgQLWYXReXs/DZL65D70c+xZ/p6+wbP+h1IXRtqcq7/8tWYQ+0ncP5hOYu3p/VW"
+    "PE8S0gZ0i6eCubbRVvf3wmuEzvLd6B856JX1EpTvKU7+Y3NoJ1uI6gW5mAwMgV7Rw0F52uMv"
+    "s4BOogZ2gP6dpkyj8kRZoI50NXDbli7CodE7LcMwQUUE++YCdJF+9lVSr3ufZC9NNhkO27B+"
+    "wffUwoHku633f3uExPCdcbIyo8Ug0d91ke8B493+BH6AvmrIBq0rPj1PpDlCH2L9s4zcRQLe"
+    "GvfnfZaU9HPFWUyQ5ntGhRhQOPnN7JDvGL2zFBP69a8P4CTyncGTc3lDyRl6GMbIIyT0ztOY"
+    "VLEU9LTXdiKjbFHKE8lk9lkF2Q5aCf2aiMUOsGQLqDRoJylMCuZifguKLwIeYey+gcp2sjG5"
+    "dHSi1uvpZzfBSGzJ8l3kNxhGWVLZaYcIBUrVs0e78KP1pRbi0XoC+c6H/QkXGyZkRIem+fDL"
+    "jsbtDt3xyCRMZ1ecci4/cofi4/uPMsIG+RMtBx8Itw5u+jR9wPXZ0+Xf0lcELrYAPPqaNP8B"
+    "Rr4H+vzk4yKgy3hUkpTD4bizQbQ+WzYwsn0xjS1OBPHjyRIWUI1xI0vWK3aGkcIGZQvHKEEq"
+    "8rRtLiD24SVg0nsTMagsrctkSaUAIDjsfob74qvXP+D8q5VzKI2P1v4W3hvaYmKThYWJe0Br"
+    "wxq+tTXTvr1a03rSBj57S5zuyr3Xt6ucUnmXnBZ9PuZsQOX0APMN71Ye51I3FG7Zn0m/T6fc"
+    "tHXeu+/srFkVEXlGA8em8QfjgLaBqVfrDejw4GZLlm/7PLKEGLfupyFig5L87DF/y2ILQVl0"
+    "Q8mimz6B3srh9IMo1A/M7AZsdQbdGR8OdKLo2E6FZL1BKbtjJ+lXo7I+DmqBUvPsojYMe3M8"
+    "U7aI7hwmZkQ4a/+vm1L/A0W3YQM=";
+
+
+/*---------------------------------------------------------------------*/
+/*                      Auto-generated deserializer                    */
+/*---------------------------------------------------------------------*/
+/*!
+ *  l_bootnum_gen1()
+ *
+ *      Return: the bootnum1 pixa
+ *
+ *  Call this way:
+ *      PIXA  *pixa = (PIXA *)l_bootnum_gen1();   (C)
+ *      Pixa  *pixa = (Pixa *)l_bootnum_gen1();   (C++)
+ */
+void *
+l_bootnum_gen1(void)
+{
+l_uint8  *data1, *data2;
+l_int32   size1;
+size_t    size2;
+void     *result;
+
+    lept_mkdir("lept/auto");
+
+        /* Unencode selected string, write to file, and read it */
+    data1 = decodeBase64(l_bootnum1, strlen(l_bootnum1), &size1);
+    data2 = zlibUncompress(data1, size1, &size2);
+    l_binaryWrite("/tmp/lept/auto/data.bin","w", data2, size2);
+    result = (void *)pixaRead("/tmp/lept/auto/data.bin");
+    lept_free(data1);
+    lept_free(data2);
+    return result;
+}
+
+
+
+
diff --git a/src/bootnumgen2.c b/src/bootnumgen2.c
new file mode 100644 (file)
index 0000000..d42fcb5
--- /dev/null
@@ -0,0 +1,287 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   bootnumgen2.c
+ *
+ *   Function for generating prog/recog/digits/bootnum2.pa from an
+ *   encoded, gzipped and serialized string.
+ *
+ *   This was generated using the stringcode utility, slightly edited,
+ *   and then merged into a single file.
+ *
+ *   The code and encoded strings were made using the stringcode utility:
+ *
+ *       L_STRCODE  *strc;
+ *       strc = strcodeCreate(102);   // arbitrary integer
+ *       strcodeGenerate(strc, "recog/digits/bootnum2.pa", "PIXA");
+ *       strcodeFinalize(&strc, ".");
+ *
+ *   The two output files, autogen.102.c and autogen.102.h, were
+ *   then slightly edited and merged into this file.
+ *
+ *   Call this way:
+ *       Pixa  *pixa = (PIXA *)l_bootnum_gen2();
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*---------------------------------------------------------------------*/
+/*                         Serialized string                           */
+/*---------------------------------------------------------------------*/
+static const char *l_bootnum2 =
+    "eJy1nAlUUun//y9eBNOrF3dUBFxKsw00t1xA0dRWc6xsR22x3XanTEANtExtmdJqUlu+U00z"
+    "ZautglczS1NbZqysRMvMagbTihS5f1DxO+d3+58Dzvl20jgcq/eL+zyf9+f5PJ/nMYxcnhRL"
+    "n7Vk/Ybla9fQ3Q2nbVodt2Q9fe1SeuLyJHoAnenuaWgYvPb/8zNxa5OWbFD9FMNQ/ePzGAsm"
+    "0JPW978zmv7jwCvDzMhpYcaGtoYAABhHhIdEqf9UfZFxqm/AsF2Tvqn/SAyfswHQ/Po5boS7"
+    "+s2NoTEbOWtXr16yZiPg+So/YrjqzYCIkKBog/x4uYNEJsClU8MZ/kpJIiXLlscYH82gnAeG"
+    "KaVcORwCQaiduA3YX1yaFrQwVSDRO/GpAwbcnlI6fgVdDNT/S0TotJDfg7mpfdqZWms3V33R"
+    "+7Q3VKTE6aR9lkY7Ok4gRXLwQiJEh9KzIqtRvvQbSoT+rpeZLz3vJqyEzchHeUAzzRBCUUZh"
+    "Uw+70aT8Q3gemqMHsiI/JhcueMvy5yYlyXAI/IUILPcLXHf3+YdMDJS71lDq1/Z9UDcOiT10"
+    "ggruh1pyp8iYz7BgS64Gze4+7EAKn9gerzfL1dBIEm06d9U+80o7iwlpobMiSr/M8fI9cfcJ"
+    "ftPiLfedfvmwd8kn0DxhxJalK11uYgA8tAawUH1R+wDwy8E7OgHEDDyVBFQhKPrWQRQSWmGI"
+    "yM2jZXxkMShozk74PMhsIQoRGkTORk25cjQHp09DXcR10B0Iqo7c7Be5AKWBQiVNH6pkJXOT"
+    "cmUCKbGTCIy6OKGMpfdyKQZrvNZYkBqtD2vPLulNnbAmDGAlccV1dHGd6jsqkKJEYS93/DeQ"
+    "w8GFk1w/5wikLJInv05BIHwjEFJwws2gChO4/NLK7HUYZQxGuKdOM9yyT/hlyzkuOgn30ggX"
+    "S+qAIDOol8VOCMxbfIrLzeVym7htquldK5aV4tJbvRky0JQI2sX0CgyBiGjTzPL5tHKMaC+t"
+    "RZsODqJm9t1UnURP7xe94P4NUz4dCJFsMDAYI4la9XFCseUH/JQpVXSk0mcE4XBj2KbiTy4x"
+    "J+esvrnG4oSxaZf1jT25M1yWdxXk19uvcixbnlnw7ZcnZ27Q8KWe091K17VgaLx1mhKOfTRb"
+    "6stJOtHM7qdZXV2smtMQIMlxfbh0/yrxJIifngd58U3JkpO77t4pEqQ2nzlI/Wr+1f6VuO1e"
+    "Zfru1DHn/njDL6zf9PspS4XknNNrxqXg9ksXJi3Jqfyped8bwt2x/o9la2RrMVQ+WlOZqL7s"
+    "+qgWu6ZFDyVSxfsoRdImmpBgQz4Tm4AidagoNpD8mXMiifxsDrn1KkNBPAYaPObxBagByDMC"
+    "txlx/GAEZyckegKua2mFytDUexgAX50GWT/Ahd1zmDoBzOwHsK9mGqpCbZp00QSfrenUJ+Hj"
+    "ZiW2tVnOpcw3ohbeMaFJ9T+Rz7X9ckoya1ezffOVt7UvXoyY++lFyOnm9rgpUyqidjAycz+S"
+    "3ju+Ln3aQ/2Me6E/dvm8F3fGY5iY2hu6Gsq2D+rH+p37dYKa1g/leV8NRdKvHzZydMDe2T1x"
+    "0stWUa/JY1/rpe7eUWiXWpG2XPje9K3xg+lJhnMucfbPbqA8jJwSndaOUK/k+r0vv+QW7Jrz"
+    "p/V2wyrnPMmoIAGWRnuLNxuMA8O2bBDoRBM5GLyS+VIA6qRxfZQogGyjB+akw8XgGzLMoPgn"
+    "8qUykCh8S6OQlSiXXcErJE9NRmEiIaVw6xcikXwIpnITgGedAlT1Lzb4fjy38PwVLI/27q6e"
+    "M/3pVvPNjm6teHyKXD3zVG/6auYMHUnMExXd2qGaFHrFVxnjOxnAycIMUdHuHUUFO4JuC4KU"
+    "AGQf28Yqq3ugFyWTwsCl9RaLZk77BcQK183V+3OtCTZL7HQSvnRgWNX0pSXp0gJ67nncqP01"
+    "E/RcnbNCUtkXgs3xX6p2/icRTyoouXk/YP3q8nsG5JVdWVXMJWG9N176L63N9d/ufmBa70NG"
+    "pv9Nnwd15+ch+wtrCt7X3x99ffnaRy+LFhWft6uq9rK4s4z/HUrtTX4IIU1DOVGTUa5FZDdF"
+    "UoWpUOmiGnT8SBY+IDaBV5YohxHQTggzuiKE0gAY+WYl/BbGdnsjSVCak9pZSKLCBHnLAp7J"
+    "nHtPQ3Y+WArtHV9N0e/4Cx2bnXWi8NBQcJFEvqgoc4d6nN3CpR+aR7aNYafCi9kGd4H1T23Y"
+    "UQoufsYGvr6rEdBRCzOplNpGrGTt/R4elFzclGmrk+RAjWRxpUxJRr6F0R+bTKzJIdseZVK4"
+    "8T6qd/m7pThzBDdC2GkM9cJkOY0fTtdz81NmSHE0YGan7XzcnI/1WPXa+7vZ4OTwJlrO10l9"
+    "/EC2kvWranIAITn0XPui1KC2NQbOTht/cjJwHnXJLvpAZGBY1I6k1k9/SakZqzIem61z+CNk"
+    "VX5e4M5fE0oz3tCTzIoOvprZtc02Dk6fbhLo1wA1fOL0nG0lP8mldY0RjnuS5zchzMMpAouo"
+    "vdkPIRBrEGdqHpBEFYhBBCcksihkEyUuliBEwuV0sh//DSsaeKL3JK1LEYt/yN5Oo/DX0/jd"
+    "KQzGePCJO8j0I11FBbLDklYfdg1wZja+fWYgAGTPH//n8mkPsCk9U/sEQJ2K9XvlAXPpGZ2g"
+    "BrzS/p46qJHY9QXR0ZudhbFmj6t+x/vWe5bQz8/egsfveP3m19ofOtLHUIS2H89t/iN26fSk"
+    "EZ++rhJuLB/JiQSHPdvxsiHTOm4rLvOss97MX479gaFx1835dUxnNDTRmkcU/0osE1wTSDsQ"
+    "gsoU7VSrLKCZBUEfWfQSm7sswIIFRHfybY59AZ1ya+rEiXIgsgYkEDYXTkghKdPEH9jbP7KT"
+    "ZU4hr2AlAcie65YVEmb2Acukm//3M8253uyuE9PUfqap9/sy55B62hJmThl5f2ZM9ZTUsMR3"
+    "jmZ56anN7048sv96MutMUoTrlWHtZTZTup9d5Jz/9Lz4+ssmxqmjcUvQS9fmFJzqBqjDXe6I"
+    "toQ2YGG0N3/LwTn0/tcEuU4wMzQPCJGlSTkCfSSNZgYR0XAGRVmJu1MTaZs6XCyX0oyEMC2F"
+    "IW4SwVaJAfTPH1EyuYHJiPwLJnKcIl8IYDMINnMjpzCUAJDX7S6zqWxagAXSPilQR22r/ri3"
+    "seeSVkDjTx398SLw3zWxnJct5dEQXqCQlwzxxG1EGlLpJaxdDeU8J2dfZR5ZG99GdPQiGCRw"
+    "4+S1hD/0gt4Cc1/bjG06scYDq1x7o1enM7Q+5bWedfE6KZ8yWDrqBBGUGJlM2mknyDMVoL4p"
+    "XDlYxr5WALMYyYTmt5HJ1Iy/wY5k+gFWrhgAURj4HJh+qIeUub2QL4UjlTSECBjkhoFzLHFv"
+    "sSza2/0QMn4Ni88gi2rOo/H0QKi8JrI6S8zjN7MEcu/Imi30XkpGLdjRkWYMGUMkIE7wuaWs"
+    "1g6Yv9j9bPnG+yuxurX3fPPB6dB6mVyqk27OoG4FDkEr2cnknYcFeTapaDFHRqMrcaqnALM9"
+    "aQI5k0RbK2xKcW0lxvlw5wNMI2M0NpIrlxSq0nDlBFn5k+DtWATtjV9tIP1py7LQH3g6IfgO"
+    "ToACKY+F8FKEPG6XqEBKZCHEFGGlAqqUnBL8LBDswRnYkoPu0dtofghtqZD1ATj7h3lKNX0r"
+    "duXrrr2d/4uZ66kRzsqRllIQFqPbqVLGyZaCeQrQHH/sx2SIMgWii8tlOfBO5XmmQjVxURD4"
+    "W2qZvjGRPAorWnu7HsIaRCN68uCA6QBBMgvfnBbZaFCeRVLAGSg/sZkopPUNl84WGMqBeVwf"
+    "uLyXvZkVCYNpOBBvQK/+EaqA66tFVdtVkbP042R75JdtczEoHtp7teVg/KF/vbFAJ5SIQZQW"
+    "IoLCkQpgJwysA0kebBlrBw3iGaYwFHJAQmMpuUlQea1A7pP7KkWcKhAI0nB4W34XKgpfKi2j"
+    "qUbr5vOT18zDz7iEBdHeoK0HJ/HfC0sqhvJM+ktbJM6MXY6d7kH769tinKIvhR3U81U4j3EJ"
+    "uHZ+U/uVrfcnFzazBReytnx9fMayy/rGYf+nlsNX2XpfCJvLG4FuU+B6FwVFXDhz1Q+Lor09"
+    "qwul/ZX3OROfT9IJJWQwf5LIkFQpCAUAxwxVeS67rQkWIixyNlhvIgwcJgw0Ef7IWEycuMkM"
+    "2kShL5YAei5ksjKRodgHOALHt1L8RJHlllgG7R15CDkghoF3W6CKSBadHBnLVQlWsPhyFxJP"
+    "5WdsBVVlBetYpFpinM0XAoGUCzajHww2sD/w2ArQVdnJA4HHb0aUm0fU/Y5l0N6b1Tlff43k"
+    "1djcIzoxDHjzmGoHE9WQCqmfHnz99/mnr3LW27++PilbMEZCtz0QcSrpW1WGwxnzX/M6J9Go"
+    "1y69ah850yRtDLJX3Kpn4RhB/YC+2/co9wUrcLIzdX88FIhl0d6bLQbHVGrvuoShPQ/0myrP"
+    "EKniVUUWjawM6qCRAuUC6TgSSmiuZW+qFXT9SP+LGJdCFwoIBFCIEvndcJyE3iHUIxEAY0v2"
+    "Ausp5Swsg/Y+PYSaldfEXHKB6s3QwTEllL6iCWmeEE3J8Fcg6mJIBwv6yC2PRMRiMPjDGEhB"
+    "JL8WtOEMOtJmoObCL06QaBuZepeRfAoHrCulnD5Ea07FQmjv1OpZ5dAHQRW94OsEMX9gIVFT"
+    "bMqnqwZVqJ6rJGr0/dxGfplriL0dvZGP+8pgJm2wVlxDZCGgJKBoPTIGnm0xdZ/dnkqXUwse"
+    "N09KXJHssc7QPMVxPUp5tmtu/NeVU7+YvjBteW3Ee+G9xsBmrCmWTXszV/9lSh/buYzlmTqx"
+    "/Xcf9FZ/AmhSrjCG0Nd2ZGWDN0Nxai1XLisVSB0saOnNrSn0pYS4OwRCq8gY+kijl6BEkNOT"
+    "Q8ATdnryVnF9FKocEhf5iphxDSDbe7691uTyAkulvdsPYemnoYrWhIEBZzlukZ821d3ML3Lx"
+    "mOMW1YKYplMljnrJhhUrR79vqQopMFhksKE5t8bwkOnKkozue1MXb2eaV4k+vCE+rhMtmLfP"
+    "Y+uKmC5z1psmZldy9YVoDNN47W1/CGUuDVPQPxZMLBpC8xXSNqtmE9NfXpkoo3HS7DmgHWhu"
+    "Dm4zB1EcYysQ/IG9kV/0jYRIvITEZIj2Afi602qO9QG9eCyA9navTnit+wDcdn/aOBSAMWVF"
+    "pmI6KVRy0FdwIv3SvZq1UGuI8xg2MRzv6Ph0zrSlLgeYP5MMu5p+3isWX7P1UnZPOzajgjGr"
+    "tXHcyLHW7fMMdoZhAbQ3+SEsOjQAmq24moFRFbYnvMqgMeFpuvtwDwfDPxK9wn8MDWFk7G3e"
+    "tMiLkDGKFGB2m7R6uE+G2w1KgeM+W7Kpv9Ok9i+H/FLIV0R2O59dXBK9YtGio0kgecX4XRt/"
+    "IoZjsbT3/X+BtagfK0A9WegW+LqLxxbjybunOhef34ifXR9Zcuca7s24jKTy8mbPLQc5a01/"
+    "Zte6m+Y4HXIZTdpk9dP++MiDl+eEn/0p0VMyL5jc1XlRvuTCyovLt70I8f18Eu5MxiU99nEf"
+    "vTwKu386XvuUgDQYCxZMd4d1whtIl+3LJCo8iF2/dmsbOftMOuftCp+SicOMcpOk3BDkfrxb"
+    "DiN97OOQDZNarhdHzJqGi93g/Ee2Epy34aTPQgOn/cWdVNpv06hLLlpFAVgQ3dbqQwzVA8Mv"
+    "tHqgngVOLlmMF8bvOX5vbvjIPMTO+ViEQC8MrFh5ljdxS7P7MeOQF5m5CkPHn9ej/8knPzk8"
+    "93nIz5ZlRk3G4bTmuoDJ0cfOdp9bvUgOvGWO0edwqrGNOON1W8r3lyC2O889rRPWwHZ9wH3J"
+    "MDED4tTTJrBny1ztTzptTGeP6BxhK6mkBEc9CjaB8XtjZk7JOqqXQdwvuSFFEiY//tRyfeSe"
+    "C35eCYe3+0NvvEgBvJbHRlWnRzR+3udZg8XRbb9ex+oQxlBfRYplORK+NGwHDhGxCBCstFZl"
+    "PqhAyrC46hT7pRWGIIg+XyyWI3zBtxyAfBEFYwlQo0mFSLDCRK+DU3gUNtSHWPylirWqNWeA"
+    "nsfBRsuYI1gq3fbrbfqoRjY5LtKKyj2ot1EI/He/Xv5NJBUYIzgbIYEqtLIS9o6G0HmRM2CE"
+    "RxWi7hDKvyuUTjdGclgQDWX4K1V5nhFHtd4+rxr3oneUupQ91WZYgv9pAUBDsEQT5ZiGaWyL"
+    "dCkNOrExp9n45l7vjLhYm5xLXKlns/HC62OsKiMMYuWHYsmPiTsSCdltDZOa53y7XlCnmOJ1"
+    "4/nKYNrLqm0V/IUCr3lnjafx/gj7j2vnFc6o/PIExaasqyXAxINeNg+fZ9IxkJ665Qj9j+mO"
+    "Y0SsTpADsc5TE+toY1/GSKYmejbjsrOd7TbuEA5nx881N19E9SgyHUn5+eLFkb9eUKzk2N5u"
+    "0c+JaWaOjW+8e8LNV+wtsj4otT9iYH54ExZE+1xBPd7M+0AytuF8dQJhaMbbV81446lG1549"
+    "xgiL3o3IaMLbaKyPXFQkUP3aIf1GBZ662pyP2ie9hdWrfWowhA4vjd7xGr0ygZSP46QBCAUn"
+    "RCGGMxCKQmSlBUPxTSzrBJG3MFTLYlBYYkBfD7gYYPm1ddz7aqxm7X1f/Rn352Pm7ZxxOmme"
+    "+M/PGEJwVsJAK+E3CFLl/x+MoWpSubjuQZksRyQtgBCWnZDlCbGUjGSlWCYHESkRMh4NQaRy"
+    "EHiw3GJG8q16bKrvqZu991cjpfUO9UOLTJIHMkG+FHDicJw4nRyCUgR94i7m5Up5Hghvs5Cn"
+    "gHgfbFIeeyTL78tkMCKFIeMsCGKMR1mAxV6rv0vf/OaLJdDe14dQs9AQDGztjLnPNGGzLQhS"
+    "0x0PjUsyt1dGuTo7TwpL/OntiG34CTs+dGc2H14TLvF1czkQ5TP2/cotXsuHTymtqLo8fmJ7"
+    "EzXrV29H4f1TjheVySnUmTdHlIxmvMEu+j21d3T1ZDDtA5JMTNQu49IAuWsyriB1Ax5evDtx"
+    "xm4xmT2qPt0w8EeG64WL3UXlVRW3vBe22jpt9Y8uvdAKchOgN39dm9OLVaxbLV7HzVyN4qmD"
+    "OeIwVWrFrl+0VdZmYD/8+MiuJvLvxzYCszNK2LdGpk981zJD397H8H1EVEjET6fSA0ctqfS4"
+    "3eQ0ph5/OGn38xLqFXFjZw6tus5+xrayUicsjG5L+n4TQEPlE3SCGeh7mqrJE2knVcuU0ZPW"
+    "ueqvPBlv3CBestOgZOPdrbtrl2013RPKeTwi2+gAP+7pw9cLVidO4G3p7G15GRP6qpGWCKMG"
+    "TFHpCbj2scN69NSLyVge7Z17CK3aGp65GudWJYh0izQpm5iYfj1i9rr9L7duPJm6Cz+fLYsL"
+    "6rZd+U6alovA8zKvkPRDlrJHU4t/LClqsa7oujUp7+7IKofskifvDx+aOD9q+OS95tSeZasu"
+    "ygCX9QGCisLdzzFkXtrb9b8gWzDQr11dPIzPJoXUw1aJwxNcrlw4aWDFHXblGDlGaOSVyF0v"
+    "KHkwfAQhJ0I4vXra8iNZF68VL4t3s39HNFkdfc09GI05ErNk/2v/3zZ8XSE8tL5q5GEob2NB"
+    "yUd9j93+fz4zf/obFk57Cx9CLuL7xzh9tSuFaQIzvVLGz5Em2nF6HEjtPLEY5rA394DCVhAi"
+    "kslUBiOZy5W/wk9cDaFPycpOpqIlXh5bmViYLW1iCVtQgPMf6twKxxCsS3pp7+zqvLG/+NcR"
+    "euW2ThhzNYt+dZ8HOUQaRrr8gq9Psv9tVO7OB6HXw2c07LNM/2S9g53c+mZvy8KQw6E/ly8M"
+    "jXK+yrcbi0rONdLzHX3cEnasueWNTpjKjJB47YkizvByWPZqF9Ur3SuYN4yO3YD00m0Hvj/o"
+    "sa/41epEFj7o/znSNHsE9BW2bIc+roY+iMhyWFBFhBTEwlP2WSbeDH/VE5KIZQqA0+lAiiPs"
+    "rSQ/+0Y2ucv0Px8v74SB2W8pt8Zdy8Lux3v9Txf4Go6Bmv/Usr72wtD6GxKagDHsuAPpbnbd"
+    "zEmkn86yO61dL0RIY0lHKGta5q5zsQ7uffb3+gvDH4aPuluf7W54N4nQHVJwuqzC4djCLj8X"
+    "B/MJz3FYFu1zAXWpub/mv5plO0snltmaUvPA/sW6GyY3sld2JXw2MJlV/JYdO5nhdOiAfnBq"
+    "UruLR1twVvAHovSnmHxOah1jBphi8XD09cBJDWeOHSr6JV0yXSjakfzi+bJkn/qlC08t9OIY"
+    "lZ0/isXSrUWvP9VUuLqa6oQ1mKR1qoYaBekIFLashj6KyA0ObYDFZXCGKqEkCEXGUCnMULDK"
+    "ExUkjgfJPj5hbbxPY6VMnC2tZAE3xlEeziwlpGEJtM8Q1KG6f1k/5t197aqv/ycMzKwpVlcp"
+    "8GJ4ZNRW/d171t3cOergnJG5jitvekeeDi7MIOtJr/31TWRdZtVsnDl51PVh142yowPl77xe"
+    "LHctnNl0g37BbpbLcEfv+BMT5ZOOzLs3nUCr8vBZzE+fehVLpn26MISFsYZs4UBZqeaacV/T"
+    "dxA+vQScm7h8Pz+EPY+u94xhr6gTvFni65z6yzDRw12Nhp6OS9Yd2HOrvDJOOhtcUPEeav30"
+    "bD13qiDE2e+Ry+5pDe1L/8xsP9fzS9CUESaP9EuZAVx/6uccLJ32ycO/cKHBIDejbyABNE4n"
+    "U9jK6Rt8gjZA73JafQdBSJwC0fLISgZTNfrUPaQd8ZColkn5K95HZV7sbOlXFmD6yZ4/avfU"
+    "AxgOb91W9v1Bbld72wqdOEL/GawpSKdqBlWSu2kCEgpufMzjF/UCSAcIsUwYCh4Q+pbGvESL"
+    "S2DdqUvJLtpO5dywR3B+whwUuCB3XnNyfdtULIRuHXn9D8NiPUe7Vb0G4gfNtsxvai9Nl3qf"
+    "PrUY/4e4Y/fEVO5vK8l8y3uEnza9oWeEvYDjrcbsOhRyreLl2V2NLZVvkzyPIMtPBg9vm8xo"
+    "vHHz/gHFnp1i3nPnS+04u1xXH75+JwGLpFtfno7NGBqkeZredgczPt0iXfrKuWbSJwrlfrgL"
+    "OcC8JVRGKLcmH51eL6t5sKohFC6aGvzCMdTxmn7UV1rl79lzwgL3xvXm2ln8kMZJbFvRXQtf"
+    "+Xrh40XDb2b3VvF2NbK6fI69O+OGPUblrX1+oF7G9a9LWyMcKrVC8/Dq/qC248HDX5HiRDFf"
+    "KgIQnsEhYCJbiENYoJDFdhbL4LtLgOMEi0uL+AIJXyAFOT1EAH+LYpUbScf2FXrr1p2nY76m"
+    "UT2wWbugpthYTFcF6hzXqDLXrFZLx51n9Wf+wU0fNzLyLifqpE/VhV6vNdMTndyg4WHO03Bh"
+    "PV1854PtzOWBCeT8tuyEfRd+/bP5xqMiqCllc83NI9dnnHn39h7rwxwfy7Nu+l1YNu2zA/UT"
+    "6d8B2N4Qr12tQ8MWMHg+xxigQ6ESEXgieu7w87FGI8PlThzQebjzVeoP0DLyfYP4Hpfg0WEH"
+    "3HB2Dx7dPuQ+yaJ+E85a5JxJyi5pxorXPgcwGxQfUvpwp07ipw2uB3h8Ka4HJ+wUwYwuFr4Z"
+    "JpKfSRpwk2hcOVzGvobmwKolgegUKoYswft+/HWs8FqYQPhCM4EUNHoSi8x5QvUHgLpzvjeX"
+    "m2+mYmm0zweGUPDT0Aw2TvZNjqIgfuxR1fwgd88Wy47zpQKcQS9uvQ1ZqUpt0raDackgWAqA"
+    "dpH3icAKvvmCwt5XCqxu3Qr5Ohb9/s8Qik+iI3Vi1e8yAJ/FfiyuaxTXPRDLCnZIYSMEpgph"
+    "dnmZjOaqZ7GWyxU6jCgkWOW+JHoB9HK4umrqqO8chdRtW78/2JaNKtTucIpG/GDnba1qCNFU"
+    "Q4hVTSSnMKhcuaIJQDpVA0kZj8ACOSNyjB8eXZa+U/A6xacwluCzjUXqBJ+cB2uWAu3LgWeb"
+    "+CgA1BYEZVZtMh2LYfH5n679NSwDXrj6frEpnwFxZry4Fl5lSK05vhhcGme+bjT3ugVlYsTU"
+    "DrftReyb7qTmu9KSN/tXZp7c63qv6Pbpq9/21P3nd0JkbemCbYcfxkIxLteVjXLc6Qq/6lXz"
+    "YwKwSLrZe3/oVcy7o919ABqkfxRq+AyAUz+OSkFmTtjp5JPaBPy+f+ZOo8fzJHG7owJucsIN"
+    "NllbR46ed0gaTup0If1QOGLG8d+8Mhptjk2ZvuylctKjT60pSZkLj3VWNsO7m89ex4l8/R2X"
+    "+OBHY8m0d/kh1Ac1ZIPtxpJaGd8m3WS8O8XfIBQVtDEQdLMQVUA0vmGDDdXDQ7EWv4sFFH0h"
+    "QjXccsiZ5j7ey4PCXZYkhgHmUofj4FTb41gE3br7dNw91iAMHK0Pvc80FNNJePGOUaO37130"
+    "nBubP84man7+ODl+slGQNymcSbm6ZVd+hmnGmnRa+QbTk2bXetcsmXz1ZOlI5Pat2/wnwEHH"
+    "P0I9Tlm/if7To/7Y1ky7ovyxT+56npiJ5dLe74dQ7cQ8GlapKgHZBiJgK0GIEAVyHN47kH0e"
+    "BStQD6fH2wOB12BFLYvL5U4s+0CH9r8nNo+YWDi3sBv+ZAyUWLi22m5+GYVF0G0DYIhh7QcN"
+    "QmUKX+rRghO2wCaMI2iTuC4n/TmLwm0j7kR5BuUiQYlNRr5qhYw7g9YC5TR2Ay6Vx+BKSL4/"
+    "qo/fEA4h0PjjQUoA+RslAgGnAw0uDHd8iEXSbVNfx+WyBmmgjrHg3kANmmMx0gY3sdPNNHTS"
+    "rNSdntPicAdHOu7weOd6dPG3wGdTfDJEwufGodnXix6vs3bZWrLY+vN2szzlsS+P9FLuXMFf"
+    "zgx5UJ6fcf2T/pg7/u3Mtw/LsFjau/4Q6hiMc5231BHDffB0ZKVMQUXQDdDXAmY18VgY9Cmm"
+    "HmToXRSsaKXFtbHuJCop6oIavAkAaiiP5h+mY5tIfbT3+yH0LmoUTx9sR0DTpLROWEjLYpED"
+    "GQpukqwoVdqRYyhEeKPJ2egeboJIyqNAHxKr2YDT4ytpxXgyjRBvJBTx3MiBCuDZHINLN+RE"
+    "4CHOc2fhBIoxFke3DKB/X/jyO652fQganMFCkosqAwA7cUJiFpFswvMR16FvAUTBM4F6BA1K"
+    "Fmj4EqU5muxloZUCKTOyl1h4m/ZBlR100gDAXuK4Ge4ZiTVJX918v7/Cx3VYodsQGtx1RZQq"
+    "AieShzPD3zWcmyTnCKR0Ek15XFynEOGynu9h+JlAszvBU8RTpfaLCSSWEhjfCZZns9iKAhQH"
+    "jL881uawvl4ulkK3Xj4dD69oKJgaimW1MjQA6XwLiWgeFN7dOpMQWmGGvaUAz0p9TOFVy+S+"
+    "yNsWiCXHXT1ifwlf8W4YVrBuDq5j86FG8DiNYKdaWVqBVBIrRNhduUU2GVQ+yYOQmuGd9gSY"
+    "GehfLRPYn/iaEwA8f0zZNv0cD7u88NWtJ69/V9uw9dBDneQObp5UonwpsRMUwhBMpiaHc30U"
+    "HEFRL2zgwTiPxnObUGJR7CoG4d5nkBk+1S+8Fy4MLFxYOEyVjyztrVU90tebHHcd3+JahcXQ"
+    "3pwtB4eJYFWe39CmK6JIk4IILCRClhCR7Ey2DQxnjFe2cBNQuMweUnAb+ak9ArLzKyh6iwwm"
+    "Rp7Dk+bxa74hchBIljhwM91P3scS6NZ017+XbW61dbZOBIOHnZapD8aqUgzcBhCRVhpDH1Xr"
+    "DBB/kcdlS7gdRPYmtidsI+jmb1U2eAPAiRY7w/CNLdhOW9//6VJbI3qw6FmpipJE1TopR7XU"
+    "9k9O5ks7KkFIpEopElBcmUl4nkBgP054pkeVaShwcXfSRESVNYDMOgWLvU35XBUrZYaMHGXD"
+    "b9grFXx166XrT7sL5EF1Qxs+lQqBlNhCJBiMVvsWX9oEg4w1CiegAoJqwvNshnmDbyRyEYDE"
+    "nrqdxvagAjV6+QbuPbQeIvDAw619b3ZyPpZAe/u1AjS9tcCRRz06EUwdJJCJpDm7jZGcCTZC"
+    "GF1Cpirf4pqthGnsZDkP5HhQaPz4pc7V2c4vs04oGTEsozoK9Xw+TVnFUJwqxj2bhchKP8OA"
+    "kadfiYXlcmz51ld78yUMxlAgAHyuFQyzoWu3v3ryaG7niuXiyqxSzwtwBsDrB8Pq5582DMVI"
+    "YjK0t1N1PaM/0USvSYJ10kT/x60btUKpCALTjA08gaf7nWyEBAJdD9A3dpi07UwW+h2B2jul"
+    "Ot3pX5+U/Zmv3f0OGoGOg1tfaVIEJhAY3cBElkERbEnHL/M/zwVwJsCiky4/r0kzV35Hovbe"
+    "qH6uZn0SI2V7tTto9H+fKxdIJQidfGKNOEbA9Taj7c/Sqg2/o0l7A/wXmqwGNOUh4rK6NP2j"
+    "uH1j2HcB4LhsmL7J08uHvyNLe0MzGhxuNrmGhKE9zQ6+VAISCOQJMlUWt6QtSI+TpscBQRIO"
+    "GOHMqNoDuXl+R6L2jqV+3e+5s6uCF+okkaqZpYHiuiRVqMQJRSFOhc0gkcxvEuOAb3NoM/Vz"
+    "w+jfkae9Nw3hSjmNPOfBwZZGIAhBM4g2mpwSzQ2jcdtwpDmM8eHsjTggf4GlpOfNrLzviNTe"
+    "eIZwCw7mM0SROrG4TjUE6YgMEcZ0sYU4PAW4TbP08f12re078rR3FfVrUp88Iv7RxyHJU32G"
+    "qsdK5RaKGPjF3j4Mhg/DPxEAqveavxZOArFblUyG9j6h/vT6TW/8G7O3OskbPThJ+pt37YQE"
+    "g72xSUCYV0M9cVUbAIN4CjdW7oLIXhGAn2yoJqcntT7FitXhzrMh7HJhZvQPYjHAgQ3Jgdxj"
+    "MDf93vZIIBTSg+iqiWLZZtd7ZlTsd2KhDheZqV/reK2nRuIITc1elcDJcISJ5BX+4sRciy5S"
+    "Fv2D6vnb8u8KigQ44HKFS8rnEfn7vyPzf3o/mTfzoKt6WacpjiSJyxL5KkWConxBkNIA4dHb"
+    "BQ42rt3L0ti8Few2IqlXcCyHR05RrWLfKelTJkSle39HsvYuM4S6rkayn8Zl0IKiAkFfN/ke"
+    "gSBf8CI/SOmbpthG6OmEvnTaKME9/ga9F5fJwYms9qA20KurhQVci7HfEvqaNeU72nVrexqi"
+    "du9/ascRyDbkPJvXOWm5AaTPHoqCWtmio1Ia6amHAnecxu3CZah04yBBCYqbu9cu+v3ZJdiL"
+    "GJg63DA2hGPmGt0DBZAxd/pKg/r1pTgnpu1L55YTXQYWIxOCTkfqSR03PNw37W2ce2jnnc+/"
+    "uvrkSsNjEL/V/wmcAq8ZH22x2GfzCRnuwF9eH+9dLxz3HQzd1lg6XmqlwfjvDSRH1LMPx/7Q"
+    "iVLID9At3Dy4jP1FwSKS/wI70AK2wjSDxelQn6yt+BhZncKX0sZWwxL0DQwA86/7/HQ46gv7"
+    "Owy6lTN1PFKqYdBsBvqIy+vEQDBbzMuIRR2E6GoI7elrxdwHFrE207uOqzuwVMsW1aol3kee"
+    "I2WQ7VEicNXUjna+qxF7Kpupw7Vi6kyiv0biIRil3Y6GRr3mjEjbV24Cl48zIfSYCLtpECpI"
+    "ICC8YULUCkLFbTC59xS7EOZ2HdcDDrmRp+EDr2PPiDB1uDJMXdTpr8Wa5/+mndVoBMcMniM3"
+    "7uuLH/eRgvOKtfRmzjqQ3eRGtv6RE7uruIjebD5y9O+BjTsM1i82ghUxU36u2Es4G2cmdZwx"
+    "dtgF7w2PeEfvkebs7Slf4Rzz7lPNiGVSo6iFIXXRMwrasWA63B6mDkX9WfGdl4k2OoFN1pzs"
+    "LR7W14H53tlm+PHQRCAs0cLJqnQjISJ2VENMxnReVyJjT8fHGx1SutWdVK/FCUEBtpGyLYpF"
+    "mZ2jvmy13JcXOvsw1XrUqGMi+6Ujv8PyP21b1rBoNvYTuPFcbhxfbwRBOR36VED+wouXwyG1"
+    "3DayuuyP9ogz4HRllaCOxuiOJQbz2oEr6+xNfB59xB7kZ+pwQ9i/CEia6yJ8FGnqgJRqQ6YU"
+    "ygGwUwRCClqRvANH+JH+kicHEHQpfamoiUevzhHzcmXw4tGwhMduoJ66olqAft7BmHWSCGLv"
+    "L2fqcCvYEHbzNBBsjanFImIxXQIQjCN9CopVacQ6MK13g/p2grd2ZOVXsNgfj15cq75u67Mx"
+    "xCqUg1aQGdpTigPCERez2i3vg74DoJsrD3EqhGqmgvoEnvqQewx/eOTOk1W7y/wTTxDiwaR5"
+    "Zi3Ws0ZwTNc5uO/YCXnyjpsvULJthpc+WEsuSLH77dVpXmw3MN3SbXYy1wX5J8X/A4d2+ho=";
+
+
+/*---------------------------------------------------------------------*/
+/*                      Auto-generated deserializer                    */
+/*---------------------------------------------------------------------*/
+/*!
+ *  l_bootnum_gen2()
+ *
+ *      Return: the bootnum2 pixa
+ *
+ *  Call this way:
+ *      PIXA  *pixa = (PIXA *)l_bootnum_gen2();   (C)
+ *      Pixa  *pixa = (Pixa *)l_bootnum_gen2();   (C++)
+ */
+void *
+l_bootnum_gen2(void)
+{
+l_uint8  *data1, *data2;
+l_int32   size1;
+size_t    size2;
+void     *result;
+
+    lept_mkdir("lept/auto");
+
+        /* Unencode selected string, write to file, and read it */
+    data1 = decodeBase64(l_bootnum2, strlen(l_bootnum2), &size1);
+    data2 = zlibUncompress(data1, size1, &size2);
+    l_binaryWrite("/tmp/lept/auto/data.bin", "w", data2, size2);
+    result = (void *)pixaRead("/tmp/lept/auto/data.bin");
+    lept_free(data1);
+    lept_free(data2);
+    return result;
+}
+
+
diff --git a/src/boxbasic.c b/src/boxbasic.c
new file mode 100644 (file)
index 0000000..46e1c43
--- /dev/null
@@ -0,0 +1,2123 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   boxbasic.c
+ *
+ *   Basic 'class' functions for box, boxa and boxaa,
+ *   including accessors and serialization.
+ *
+ *      Box creation, copy, clone, destruction
+ *           BOX      *boxCreate()
+ *           BOX      *boxCreateValid()
+ *           BOX      *boxCopy()
+ *           BOX      *boxClone()
+ *           void      boxDestroy()
+ *
+ *      Box accessors
+ *           l_int32   boxGetGeometry()
+ *           l_int32   boxSetGeometry()
+ *           l_int32   boxGetSideLocation()
+ *           l_int32   boxGetRefcount()
+ *           l_int32   boxChangeRefcount()
+ *           l_int32   boxIsValid()
+ *
+ *      Boxa creation, copy, destruction
+ *           BOXA     *boxaCreate()
+ *           BOXA     *boxaCopy()
+ *           void      boxaDestroy()
+ *
+ *      Boxa array extension
+ *           l_int32   boxaAddBox()
+ *           l_int32   boxaExtendArray()
+ *           l_int32   boxaExtendArrayToSize()
+ *
+ *      Boxa accessors
+ *           l_int32   boxaGetCount()
+ *           l_int32   boxaGetValidCount()
+ *           BOX      *boxaGetBox()
+ *           BOX      *boxaGetValidBox()
+ *           l_int32   boxaGetBoxGeometry()
+ *           l_int32   boxaIsFull()
+ *
+ *      Boxa array modifiers
+ *           l_int32   boxaReplaceBox()
+ *           l_int32   boxaInsertBox()
+ *           l_int32   boxaRemoveBox()
+ *           l_int32   boxaRemoveBoxAndSave()
+ *           BOXA     *boxaSaveValid()
+ *           l_int32   boxaInitFull()
+ *           l_int32   boxaClear()
+ *
+ *      Boxaa creation, copy, destruction
+ *           BOXAA    *boxaaCreate()
+ *           BOXAA    *boxaaCopy()
+ *           void      boxaaDestroy()
+ *
+ *      Boxaa array extension
+ *           l_int32   boxaaAddBoxa()
+ *           l_int32   boxaaExtendArray()
+ *           l_int32   boxaaExtendArrayToSize()
+ *
+ *      Boxaa accessors
+ *           l_int32   boxaaGetCount()
+ *           l_int32   boxaaGetBoxCount()
+ *           BOXA     *boxaaGetBoxa()
+ *           BOX      *boxaaGetBox()
+ *
+ *      Boxaa array modifiers
+ *           l_int32   boxaaInitFull()
+ *           l_int32   boxaaExtendWithInit()
+ *           l_int32   boxaaReplaceBoxa()
+ *           l_int32   boxaaInsertBoxa()
+ *           l_int32   boxaaRemoveBoxa()
+ *           l_int32   boxaaAddBox()
+ *
+ *      Boxaa serialized I/O
+ *           BOXAA    *boxaaReadFromFiles()
+ *           BOXAA    *boxaaRead()
+ *           BOXAA    *boxaaReadStream()
+ *           l_int32   boxaaWrite()
+ *           l_int32   boxaaWriteStream()
+ *
+ *      Boxa serialized I/O
+ *           BOXA     *boxaRead()
+ *           BOXA     *boxaReadStream()
+ *           BOXA     *boxaReadMem()
+ *           l_int32   boxaWrite()
+ *           l_int32   boxaWriteStream()
+ *           l_int32   boxaWriteMem()
+ *
+ *      Box print (for debug)
+ *           l_int32   boxPrintStreamInfo()
+ *
+ *   Most functions use only valid boxes, which are boxes that have both
+ *   width and height > 0.  However, a few functions, such as
+ *   boxaGetMedian() do not assume that all boxes are valid.  For any
+ *   function that can use a boxa with invalid boxes, it is convenient
+ *   to use these accessors:
+ *       boxaGetValidCount()   :  count of valid boxes
+ *       boxaGetValidBox()     :  returns NULL for invalid boxes
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;   /* n'import quoi */
+
+
+/*---------------------------------------------------------------------*
+ *                  Box creation, destruction and copy                 *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxCreate()
+ *
+ *      Input:  x, y, w, h
+ *      Return: box, or null on error
+ *
+ *  Notes:
+ *      (1) This clips the box to the +quad.  If no part of the
+ *          box is in the +quad, this returns NULL.
+ *      (2) We allow you to make a box with w = 0 and/or h = 0.
+ *          This does not represent a valid region, but it is useful
+ *          as a placeholder in a boxa for which the index of the
+ *          box in the boxa is important.  This is an atypical
+ *          situation; usually you want to put only valid boxes with
+ *          nonzero width and height in a boxa.  If you have a boxa
+ *          with invalid boxes, the accessor boxaGetValidBox()
+ *          will return NULL on each invalid box.
+ *      (3) If you want to create only valid boxes, use boxCreateValid(),
+ *          which returns NULL if either w or h is 0.
+ */
+BOX *
+boxCreate(l_int32  x,
+          l_int32  y,
+          l_int32  w,
+          l_int32  h)
+{
+BOX  *box;
+
+    PROCNAME("boxCreate");
+
+    if (w < 0 || h < 0)
+        return (BOX *)ERROR_PTR("w and h not both >= 0", procName, NULL);
+    if (x < 0) {  /* take part in +quad */
+        w = w + x;
+        x = 0;
+        if (w <= 0)
+            return (BOX *)ERROR_PTR("x < 0 and box off +quad", procName, NULL);
+    }
+    if (y < 0) {  /* take part in +quad */
+        h = h + y;
+        y = 0;
+        if (h <= 0)
+            return (BOX *)ERROR_PTR("y < 0 and box off +quad", procName, NULL);
+    }
+
+    if ((box = (BOX *)LEPT_CALLOC(1, sizeof(BOX))) == NULL)
+        return (BOX *)ERROR_PTR("box not made", procName, NULL);
+    boxSetGeometry(box, x, y, w, h);
+    box->refcount = 1;
+
+    return box;
+}
+
+
+/*!
+ *  boxCreateValid()
+ *
+ *      Input:  x, y, w, h
+ *      Return: box, or null on error
+ *
+ *  Notes:
+ *      (1) This returns NULL if either w = 0 or h = 0.
+ */
+BOX *
+boxCreateValid(l_int32  x,
+               l_int32  y,
+               l_int32  w,
+               l_int32  h)
+{
+    PROCNAME("boxCreateValid");
+
+    if (w <= 0 || h <= 0)
+        return (BOX *)ERROR_PTR("w and h not both > 0", procName, NULL);
+    return boxCreate(x, y, w, h);
+}
+
+
+/*!
+ *  boxCopy()
+ *
+ *      Input:  box
+ *      Return: copy of box, or null on error
+ */
+BOX *
+boxCopy(BOX  *box)
+{
+BOX  *boxc;
+
+    PROCNAME("boxCopy");
+
+    if (!box)
+        return (BOX *)ERROR_PTR("box not defined", procName, NULL);
+
+    boxc = boxCreate(box->x, box->y, box->w, box->h);
+
+    return boxc;
+}
+
+
+/*!
+ *  boxClone()
+ *
+ *      Input:  box
+ *      Return: ptr to same box, or null on error
+ */
+BOX *
+boxClone(BOX  *box)
+{
+
+    PROCNAME("boxClone");
+
+    if (!box)
+        return (BOX *)ERROR_PTR("box not defined", procName, NULL);
+
+    boxChangeRefcount(box, 1);
+    return box;
+}
+
+
+/*!
+ *  boxDestroy()
+ *
+ *      Input:  &box (<will be set to null before returning>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Decrements the ref count and, if 0, destroys the box.
+ *      (2) Always nulls the input ptr.
+ */
+void
+boxDestroy(BOX  **pbox)
+{
+BOX  *box;
+
+    PROCNAME("boxDestroy");
+
+    if (pbox == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+    if ((box = *pbox) == NULL)
+        return;
+
+    boxChangeRefcount(box, -1);
+    if (boxGetRefcount(box) <= 0)
+        LEPT_FREE(box);
+    *pbox = NULL;
+    return;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                              Box accessors                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxGetGeometry()
+ *
+ *      Input:  box
+ *              &x, &y, &w, &h (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxGetGeometry(BOX      *box,
+               l_int32  *px,
+               l_int32  *py,
+               l_int32  *pw,
+               l_int32  *ph)
+{
+    PROCNAME("boxGetGeometry");
+
+    if (px) *px = 0;
+    if (py) *py = 0;
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (px) *px = box->x;
+    if (py) *py = box->y;
+    if (pw) *pw = box->w;
+    if (ph) *ph = box->h;
+    return 0;
+}
+
+
+/*!
+ *  boxSetGeometry()
+ *
+ *      Input:  box
+ *              x, y, w, h (use -1 to leave unchanged)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxSetGeometry(BOX     *box,
+               l_int32  x,
+               l_int32  y,
+               l_int32  w,
+               l_int32  h)
+{
+    PROCNAME("boxSetGeometry");
+
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (x != -1) box->x = x;
+    if (y != -1) box->y = y;
+    if (w != -1) box->w = w;
+    if (h != -1) box->h = h;
+    return 0;
+}
+
+
+/*!
+ *  boxGetSideLocation()
+ *
+ *      Input:  box
+ *              side (L_GET_LEFT, L_GET_RIGHT, L_GET_TOP, L_GET_BOT)
+ *              &loc (<return> location)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) All returned values are within the box.  In particular:
+ *            right = left + width - 1
+ *            bottom = top + height - 1
+ */
+l_int32
+boxGetSideLocation(BOX      *box,
+                   l_int32   side,
+                   l_int32  *ploc)
+{
+l_int32  x, y, w, h;
+
+    PROCNAME("boxGetSideLocation");
+
+    if (!ploc)
+        return ERROR_INT("&loc not defined", procName, 1);
+    *ploc = 0;
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+
+    boxGetGeometry(box, &x, &y, &w, &h);
+    if (side == L_GET_LEFT)
+        *ploc = x;
+    else if (side == L_GET_RIGHT)
+        *ploc = x + w - 1;
+    else if (side == L_GET_TOP)
+        *ploc = y;
+    else if (side == L_GET_BOT)
+        *ploc = y + h - 1;
+    else
+        return ERROR_INT("invalid side", procName, 1);
+    return 0;
+}
+
+
+l_int32
+boxGetRefcount(BOX  *box)
+{
+    PROCNAME("boxGetRefcount");
+
+    if (!box)
+        return ERROR_INT("box not defined", procName, UNDEF);
+
+    return box->refcount;
+}
+
+
+l_int32
+boxChangeRefcount(BOX     *box,
+                  l_int32  delta)
+{
+    PROCNAME("boxChangeRefcount");
+
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+
+    box->refcount += delta;
+    return 0;
+}
+
+
+/*!
+ *  boxIsValid()
+ *
+ *      Input:  box
+ *              &valid (<return> 1 if valid; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxIsValid(BOX      *box,
+           l_int32  *pvalid)
+{
+    PROCNAME("boxIsValid");
+
+    if (!pvalid)
+        return ERROR_INT("&valid not defined", procName, 1);
+    *pvalid = 0;
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+
+    if (box->w > 0 && box->h > 0)
+        *pvalid = 1;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *             Boxa creation, destruction, copy, extension             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaCreate()
+ *
+ *      Input:  n  (initial number of ptrs)
+ *      Return: boxa, or null on error
+ */
+BOXA *
+boxaCreate(l_int32  n)
+{
+BOXA  *boxa;
+
+    PROCNAME("boxaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((boxa = (BOXA *)LEPT_CALLOC(1, sizeof(BOXA))) == NULL)
+        return (BOXA *)ERROR_PTR("boxa not made", procName, NULL);
+    boxa->n = 0;
+    boxa->nalloc = n;
+    boxa->refcount = 1;
+
+    if ((boxa->box = (BOX **)LEPT_CALLOC(n, sizeof(BOX *))) == NULL)
+        return (BOXA *)ERROR_PTR("boxa ptrs not made", procName, NULL);
+
+    return boxa;
+}
+
+
+/*!
+ *  boxaCopy()
+ *
+ *      Input:  boxa
+ *              copyflag (L_COPY, L_CLONE, L_COPY_CLONE)
+ *      Return: new boxa, or null on error
+ *
+ *  Notes:
+ *      (1) See pix.h for description of the copyflag.
+ *      (2) The copy-clone makes a new boxa that holds clones of each box.
+ */
+BOXA *
+boxaCopy(BOXA    *boxa,
+         l_int32  copyflag)
+{
+l_int32  i;
+BOX     *boxc;
+BOXA    *boxac;
+
+    PROCNAME("boxaCopy");
+
+    if (!boxa)
+        return (BOXA *)ERROR_PTR("boxa not defined", procName, NULL);
+
+    if (copyflag == L_CLONE) {
+        boxa->refcount++;
+        return boxa;
+    }
+
+    if (copyflag != L_COPY && copyflag != L_COPY_CLONE)
+        return (BOXA *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    if ((boxac = boxaCreate(boxa->nalloc)) == NULL)
+        return (BOXA *)ERROR_PTR("boxac not made", procName, NULL);
+    for (i = 0; i < boxa->n; i++) {
+        if (copyflag == L_COPY)
+            boxc = boxaGetBox(boxa, i, L_COPY);
+        else   /* copy-clone */
+            boxc = boxaGetBox(boxa, i, L_CLONE);
+        boxaAddBox(boxac, boxc, L_INSERT);
+    }
+    return boxac;
+}
+
+
+/*!
+ *  boxaDestroy()
+ *
+ *      Input:  &boxa (<will be set to null before returning>)
+ *      Return: void
+ *
+ *  Note:
+ *      - Decrements the ref count and, if 0, destroys the boxa.
+ *      - Always nulls the input ptr.
+ */
+void
+boxaDestroy(BOXA  **pboxa)
+{
+l_int32  i;
+BOXA    *boxa;
+
+    PROCNAME("boxaDestroy");
+
+    if (pboxa == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((boxa = *pboxa) == NULL)
+        return;
+
+        /* Decrement the ref count.  If it is 0, destroy the boxa. */
+    boxa->refcount--;
+    if (boxa->refcount <= 0) {
+        for (i = 0; i < boxa->n; i++)
+            boxDestroy(&boxa->box[i]);
+        LEPT_FREE(boxa->box);
+        LEPT_FREE(boxa);
+    }
+
+    *pboxa = NULL;
+    return;
+}
+
+
+/*!
+ *  boxaAddBox()
+ *
+ *      Input:  boxa
+ *              box  (to be added)
+ *              copyflag (L_INSERT, L_COPY, L_CLONE)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaAddBox(BOXA    *boxa,
+           BOX     *box,
+           l_int32  copyflag)
+{
+l_int32  n;
+BOX     *boxc;
+
+    PROCNAME("boxaAddBox");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+
+    if (copyflag == L_INSERT)
+        boxc = box;
+    else if (copyflag == L_COPY)
+        boxc = boxCopy(box);
+    else if (copyflag == L_CLONE)
+        boxc = boxClone(box);
+    else
+        return ERROR_INT("invalid copyflag", procName, 1);
+    if (!boxc)
+        return ERROR_INT("boxc not made", procName, 1);
+
+    n = boxaGetCount(boxa);
+    if (n >= boxa->nalloc)
+        boxaExtendArray(boxa);
+    boxa->box[n] = boxc;
+    boxa->n++;
+
+    return 0;
+}
+
+
+/*!
+ *  boxaExtendArray()
+ *
+ *      Input:  boxa
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Reallocs with doubled size of ptr array.
+ */
+l_int32
+boxaExtendArray(BOXA  *boxa)
+{
+    PROCNAME("boxaExtendArray");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    return boxaExtendArrayToSize(boxa, 2 * boxa->nalloc);
+}
+
+
+/*!
+ *  boxaExtendArrayToSize()
+ *
+ *      Input:  boxa
+ *              size (new size of boxa array)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) If necessary, reallocs new boxa ptr array to @size.
+ */
+l_int32
+boxaExtendArrayToSize(BOXA    *boxa,
+                      l_int32  size)
+{
+    PROCNAME("boxaExtendArrayToSize");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    if (size > boxa->nalloc) {
+        if ((boxa->box = (BOX **)reallocNew((void **)&boxa->box,
+                                            sizeof(BOX *) * boxa->nalloc,
+                                            size * sizeof(BOX *))) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+        boxa->nalloc = size;
+    }
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                             Boxa accessors                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaGetCount()
+ *
+ *      Input:  boxa
+ *      Return: count (of all boxes); 0 if no boxes or on error
+ */
+l_int32
+boxaGetCount(BOXA  *boxa)
+{
+    PROCNAME("boxaGetCount");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 0);
+    return boxa->n;
+}
+
+
+/*!
+ *  boxaGetValidCount()
+ *
+ *      Input:  boxa
+ *      Return: count (of valid boxes); 0 if no valid boxes or on error
+ */
+l_int32
+boxaGetValidCount(BOXA  *boxa)
+{
+l_int32  n, i, w, h, count;
+
+    PROCNAME("boxaGetValidCount");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 0);
+
+    n = boxaGetCount(boxa);
+    for (i = 0, count = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, NULL, NULL, &w, &h);
+        if (w > 0 && h > 0)
+            count++;
+    }
+    return count;
+}
+
+
+/*!
+ *  boxaGetBox()
+ *
+ *      Input:  boxa
+ *              index  (to the index-th box)
+ *              accessflag  (L_COPY or L_CLONE)
+ *      Return: box, or null on error
+ */
+BOX *
+boxaGetBox(BOXA    *boxa,
+           l_int32  index,
+           l_int32  accessflag)
+{
+    PROCNAME("boxaGetBox");
+
+    if (!boxa)
+        return (BOX *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (index < 0 || index >= boxa->n)
+        return (BOX *)ERROR_PTR("index not valid", procName, NULL);
+
+    if (accessflag == L_COPY)
+        return boxCopy(boxa->box[index]);
+    else if (accessflag == L_CLONE)
+        return boxClone(boxa->box[index]);
+    else
+        return (BOX *)ERROR_PTR("invalid accessflag", procName, NULL);
+}
+
+
+/*!
+ *  boxaGetValidBox()
+ *
+ *      Input:  boxa
+ *              index  (to the index-th box)
+ *              accessflag  (L_COPY or L_CLONE)
+ *      Return: box, or null if box is not valid or on error
+ *
+ *  Notes:
+ *      (1) This returns NULL for an invalid box in a boxa.
+ *          For a box to be valid, both the width and height must be > 0.
+ *      (2) We allow invalid boxes, with w = 0 or h = 0, as placeholders
+ *          in boxa for which the index of the box in the boxa is important.
+ *          This is an atypical situation; usually you want to put only
+ *          valid boxes in a boxa.
+ */
+BOX *
+boxaGetValidBox(BOXA    *boxa,
+                l_int32  index,
+                l_int32  accessflag)
+{
+l_int32  w, h;
+BOX     *box;
+
+    PROCNAME("boxaGetValidBox");
+
+    if (!boxa)
+        return (BOX *)ERROR_PTR("boxa not defined", procName, NULL);
+
+    if ((box = boxaGetBox(boxa, index, accessflag)) == NULL)
+        return (BOX *)ERROR_PTR("box not returned", procName, NULL);
+    boxGetGeometry(box, NULL, NULL, &w, &h);
+    if (w <= 0 || h <= 0)  /* not valid, but not necessarily an error */
+        boxDestroy(&box);
+    return box;
+}
+
+
+/*!
+ *  boxaGetBoxGeometry()
+ *
+ *      Input:  boxa
+ *              index  (to the index-th box)
+ *              &x, &y, &w, &h (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaGetBoxGeometry(BOXA     *boxa,
+                   l_int32   index,
+                   l_int32  *px,
+                   l_int32  *py,
+                   l_int32  *pw,
+                   l_int32  *ph)
+{
+BOX  *box;
+
+    PROCNAME("boxaGetBoxGeometry");
+
+    if (px) *px = 0;
+    if (py) *py = 0;
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (index < 0 || index >= boxa->n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    if ((box = boxaGetBox(boxa, index, L_CLONE)) == NULL)
+        return ERROR_INT("box not found!", procName, 1);
+    boxGetGeometry(box, px, py, pw, ph);
+    boxDestroy(&box);
+    return 0;
+}
+
+
+/*!
+ *  boxaIsFull()
+ *
+ *      Input:  boxa
+ *              &full (return> 1 if boxa is full)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaIsFull(BOXA     *boxa,
+           l_int32  *pfull)
+{
+l_int32  i, n, full;
+BOX     *box;
+
+    PROCNAME("boxaIsFull");
+
+    if (!pfull)
+        return ERROR_INT("&full not defined", procName, 1);
+    *pfull = 0;
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    n = boxaGetCount(boxa);
+    full = 1;
+    for (i = 0; i < n; i++) {
+        if ((box = boxaGetBox(boxa, i, L_CLONE)) == NULL) {
+            full = 0;
+            break;
+        }
+        boxDestroy(&box);
+    }
+    *pfull = full;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Boxa array modifiers                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaReplaceBox()
+ *
+ *      Input:  boxa
+ *              index  (to the index-th box)
+ *              box (insert to replace existing one)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) In-place replacement of one box.
+ *      (2) The previous box at that location, if any, is destroyed.
+ */
+l_int32
+boxaReplaceBox(BOXA    *boxa,
+               l_int32  index,
+               BOX     *box)
+{
+    PROCNAME("boxaReplaceBox");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (index < 0 || index >= boxa->n)
+        return ERROR_INT("index not valid", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+
+    boxDestroy(&(boxa->box[index]));
+    boxa->box[index] = box;
+    return 0;
+}
+
+
+/*!
+ *  boxaInsertBox()
+ *
+ *      Input:  boxa
+ *              index (location in boxa to insert new value)
+ *              box (new box to be inserted)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This shifts box[i] --> box[i + 1] for all i >= index,
+ *          and then inserts box as box[index].
+ *      (2) To insert at the beginning of the array, set index = 0.
+ *      (3) To append to the array, it's easier to use boxaAddBox().
+ *      (4) This should not be used repeatedly to insert into large arrays,
+ *          because the function is O(n).
+ */
+l_int32
+boxaInsertBox(BOXA    *boxa,
+              l_int32  index,
+              BOX     *box)
+{
+l_int32  i, n;
+BOX    **array;
+
+    PROCNAME("boxaInsertBox");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    n = boxaGetCount(boxa);
+    if (index < 0 || index > n)
+        return ERROR_INT("index not in {0...n}", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+
+    if (n >= boxa->nalloc)
+        boxaExtendArray(boxa);
+    array = boxa->box;
+    boxa->n++;
+    for (i = n; i > index; i--)
+        array[i] = array[i - 1];
+    array[index] = box;
+
+    return 0;
+}
+
+
+/*!
+ *  boxaRemoveBox()
+ *
+ *      Input:  boxa
+ *              index (of box to be removed)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This removes box[index] and then shifts
+ *          box[i] --> box[i - 1] for all i > index.
+ *      (2) It should not be used repeatedly to remove boxes from
+ *          large arrays, because the function is O(n).
+ */
+l_int32
+boxaRemoveBox(BOXA    *boxa,
+              l_int32  index)
+{
+l_int32  i, n;
+BOX    **array;
+
+    PROCNAME("boxaRemoveBox");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    n = boxaGetCount(boxa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not in {0...n - 1}", procName, 1);
+
+    array = boxa->box;
+    boxDestroy(&array[index]);
+    for (i = index + 1; i < n; i++)
+        array[i - 1] = array[i];
+    array[n - 1] = NULL;
+    boxa->n--;
+
+    return 0;
+}
+
+
+/*!
+ *  boxaRemoveBoxAndSave()
+ *
+ *      Input:  boxa
+ *              index (of box to be removed)
+ *              &box (<optional return> removed box)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This removes box[index] and then shifts
+ *          box[i] --> box[i - 1] for all i > index.
+ *      (2) It should not be used repeatedly to remove boxes from
+ *          large arrays, because the function is O(n).
+ */
+l_int32
+boxaRemoveBoxAndSave(BOXA    *boxa,
+                     l_int32  index,
+                     BOX    **pbox)
+{
+l_int32  i, n;
+BOX    **array;
+
+    PROCNAME("boxaRemoveBoxAndSave");
+
+    if (pbox) *pbox = NULL;
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    n = boxaGetCount(boxa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not in {0...n - 1}", procName, 1);
+
+    if (pbox)
+        *pbox = boxaGetBox(boxa, index, L_CLONE);
+    array = boxa->box;
+    boxDestroy(&array[index]);
+    for (i = index + 1; i < n; i++)
+        array[i - 1] = array[i];
+    array[n - 1] = NULL;
+    boxa->n--;
+
+    return 0;
+}
+
+
+/*!
+ *  boxaSaveValid()
+ *
+ *      Input:  boxa
+ *              copyflag (L_COPY or L_CLONE)
+ *      Return: boxad if OK, null on error
+ *
+ *  Notes:
+ *      (1) This makes a copy/clone of each valid box.
+ */
+BOXA *
+boxaSaveValid(BOXA    *boxas,
+              l_int32  copyflag)
+{
+l_int32  i, n;
+BOX     *box;
+BOXA    *boxad;
+
+    PROCNAME("boxaSaveValid");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (copyflag != L_COPY && copyflag != L_CLONE)
+        return (BOXA *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    n = boxaGetCount(boxas);
+    boxad = boxaCreate(n);
+    for (i = 0; i < n; i++) {
+        if ((box = boxaGetValidBox(boxas, i, copyflag)) != NULL)
+            boxaAddBox(boxad, box, L_INSERT);
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxaInitFull()
+ *
+ *      Input:  boxa (typically empty)
+ *              box (<optional> to be replicated into the entire ptr array)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This initializes a boxa by filling up the entire box ptr array
+ *          with copies of @box.  If @box == NULL, use a placeholder box
+ *          of zero size.  Any existing boxes are destroyed.
+ *          After this opepration, the number of boxes is equal to
+ *          the number of allocated ptrs.
+ *      (2) Note that we use boxaReplaceBox() instead of boxaInsertBox().
+ *          They both have the same effect when inserting into a NULL ptr
+ *          in the boxa ptr array:
+ *      (3) Example usage.  This function is useful to prepare for a
+ *          random insertion (or replacement) of boxes into a boxa.
+ *          To randomly insert boxes into a boxa, up to some index "max":
+ *             Boxa *boxa = boxaCreate(max);
+ *             boxaInitFull(boxa, NULL);
+ *          If you want placeholder boxes of non-zero size:
+ *             Boxa *boxa = boxaCreate(max);
+ *             Box *box = boxCreate(...);
+ *             boxaInitFull(boxa, box);
+ *             boxDestroy(&box);
+ *          If we have an existing boxa with a smaller ptr array, it can
+ *          be reused for up to max boxes:
+ *             boxaExtendArrayToSize(boxa, max);
+ *             boxaInitFull(boxa, NULL);
+ *          The initialization allows the boxa to always be properly
+ *          filled, even if all the boxes are not later replaced.
+ *          If you want to know which boxes have been replaced,
+ *          and you initialized with invalid zero-sized boxes,
+ *          use boxaGetValidBox() to return NULL for the invalid boxes.
+ */
+l_int32
+boxaInitFull(BOXA  *boxa,
+             BOX   *box)
+{
+l_int32  i, n;
+BOX     *boxt;
+
+    PROCNAME("boxaInitFull");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    n = boxa->nalloc;
+    boxa->n = n;
+    for (i = 0; i < n; i++) {
+        if (box)
+            boxt = boxCopy(box);
+        else
+            boxt = boxCreate(0, 0, 0, 0);
+        boxaReplaceBox(boxa, i, boxt);
+    }
+    return 0;
+}
+
+
+/*!
+ *  boxaClear()
+ *
+ *      Input:  boxa
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This destroys all boxes in the boxa, setting the ptrs
+ *          to null.  The number of allocated boxes, n, is set to 0.
+ */
+l_int32
+boxaClear(BOXA  *boxa)
+{
+l_int32  i, n;
+
+    PROCNAME("boxaClear");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    n = boxaGetCount(boxa);
+    for (i = 0; i < n; i++)
+        boxDestroy(&boxa->box[i]);
+    boxa->n = 0;
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                     Boxaa creation, destruction                          *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  boxaaCreate()
+ *
+ *      Input:  size of boxa ptr array to be alloc'd (0 for default)
+ *      Return: baa, or null on error
+ */
+BOXAA *
+boxaaCreate(l_int32  n)
+{
+BOXAA  *baa;
+
+    PROCNAME("boxaaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((baa = (BOXAA *)LEPT_CALLOC(1, sizeof(BOXAA))) == NULL)
+        return (BOXAA *)ERROR_PTR("baa not made", procName, NULL);
+    if ((baa->boxa = (BOXA **)LEPT_CALLOC(n, sizeof(BOXA *))) == NULL)
+        return (BOXAA *)ERROR_PTR("boxa ptr array not made", procName, NULL);
+
+    baa->nalloc = n;
+    baa->n = 0;
+
+    return baa;
+}
+
+
+/*!
+ *  boxaaCopy()
+ *
+ *      Input:  baas (input boxaa to be copied)
+ *              copyflag (L_COPY, L_CLONE)
+ *      Return: baad (new boxaa, composed of copies or clones of the boxa
+ *                    in baas), or null on error
+ *
+ *  Notes:
+ *      (1) L_COPY makes a copy of each boxa in baas.
+ *          L_CLONE makes a clone of each boxa in baas.
+ */
+BOXAA *
+boxaaCopy(BOXAA   *baas,
+          l_int32  copyflag)
+{
+l_int32  i, n;
+BOXA    *boxa;
+BOXAA   *baad;
+
+    PROCNAME("boxaaCopy");
+
+    if (!baas)
+        return (BOXAA *)ERROR_PTR("baas not defined", procName, NULL);
+    if (copyflag != L_COPY && copyflag != L_CLONE)
+        return (BOXAA *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    n = boxaaGetCount(baas);
+    baad = boxaaCreate(n);
+    for (i = 0; i < n; i++) {
+        boxa = boxaaGetBoxa(baas, i, copyflag);
+        boxaaAddBoxa(baad, boxa, L_INSERT);
+    }
+
+    return baad;
+}
+
+
+/*!
+ *  boxaaDestroy()
+ *
+ *      Input:  &boxaa (<will be set to null before returning>)
+ *      Return: void
+ */
+void
+boxaaDestroy(BOXAA  **pbaa)
+{
+l_int32  i;
+BOXAA   *baa;
+
+    PROCNAME("boxaaDestroy");
+
+    if (pbaa == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+
+    if ((baa = *pbaa) == NULL)
+        return;
+
+    for (i = 0; i < baa->n; i++)
+        boxaDestroy(&baa->boxa[i]);
+    LEPT_FREE(baa->boxa);
+    LEPT_FREE(baa);
+    *pbaa = NULL;
+
+    return;
+}
+
+
+
+/*--------------------------------------------------------------------------*
+ *                              Add Boxa to Boxaa                           *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  boxaaAddBoxa()
+ *
+ *      Input:  boxaa
+ *              boxa     (to be added)
+ *              copyflag  (L_INSERT, L_COPY, L_CLONE)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaaAddBoxa(BOXAA   *baa,
+             BOXA    *ba,
+             l_int32  copyflag)
+{
+l_int32  n;
+BOXA    *bac;
+
+    PROCNAME("boxaaAddBoxa");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+    if (!ba)
+        return ERROR_INT("ba not defined", procName, 1);
+    if (copyflag != L_INSERT && copyflag != L_COPY && copyflag != L_CLONE)
+        return ERROR_INT("invalid copyflag", procName, 1);
+
+    if (copyflag == L_INSERT)
+        bac = ba;
+    else
+        bac = boxaCopy(ba, copyflag);
+
+    n = boxaaGetCount(baa);
+    if (n >= baa->nalloc)
+        boxaaExtendArray(baa);
+    baa->boxa[n] = bac;
+    baa->n++;
+    return 0;
+}
+
+
+/*!
+ *  boxaaExtendArray()
+ *
+ *      Input:  boxaa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaaExtendArray(BOXAA  *baa)
+{
+
+    PROCNAME("boxaaExtendArray");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+
+    if ((baa->boxa = (BOXA **)reallocNew((void **)&baa->boxa,
+                              sizeof(BOXA *) * baa->nalloc,
+                              2 * sizeof(BOXA *) * baa->nalloc)) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+
+    baa->nalloc *= 2;
+    return 0;
+}
+
+
+/*!
+ *  boxaaExtendArrayToSize()
+ *
+ *      Input:  boxaa
+ *              size (new size of boxa array)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) If necessary, reallocs the boxa ptr array to @size.
+ */
+l_int32
+boxaaExtendArrayToSize(BOXAA   *baa,
+                       l_int32  size)
+{
+    PROCNAME("boxaaExtendArrayToSize");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+
+    if (size > baa->nalloc) {
+        if ((baa->boxa = (BOXA **)reallocNew((void **)&baa->boxa,
+                                             sizeof(BOXA *) * baa->nalloc,
+                                             size * sizeof(BOXA *))) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+        baa->nalloc = size;
+    }
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                           Boxaa accessors                            *
+ *----------------------------------------------------------------------*/
+/*!
+ *  boxaaGetCount()
+ *
+ *      Input:  boxaa
+ *      Return: count (number of boxa), or 0 if no boxa or on error
+ */
+l_int32
+boxaaGetCount(BOXAA  *baa)
+{
+    PROCNAME("boxaaGetCount");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 0);
+    return baa->n;
+}
+
+
+/*!
+ *  boxaaGetBoxCount()
+ *
+ *      Input:  boxaa
+ *      Return: count (number of boxes), or 0 if no boxes or on error
+ */
+l_int32
+boxaaGetBoxCount(BOXAA  *baa)
+{
+BOXA    *boxa;
+l_int32  n, sum, i;
+
+    PROCNAME("boxaaGetBoxCount");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 0);
+
+    n = boxaaGetCount(baa);
+    for (sum = 0, i = 0; i < n; i++) {
+        boxa = boxaaGetBoxa(baa, i, L_CLONE);
+        sum += boxaGetCount(boxa);
+        boxaDestroy(&boxa);
+    }
+
+    return sum;
+}
+
+
+/*!
+ *  boxaaGetBoxa()
+ *
+ *      Input:  boxaa
+ *              index  (to the index-th boxa)
+ *              accessflag   (L_COPY or L_CLONE)
+ *      Return: boxa, or null on error
+ */
+BOXA *
+boxaaGetBoxa(BOXAA   *baa,
+             l_int32  index,
+             l_int32  accessflag)
+{
+l_int32  n;
+
+    PROCNAME("boxaaGetBoxa");
+
+    if (!baa)
+        return (BOXA *)ERROR_PTR("baa not defined", procName, NULL);
+    n = boxaaGetCount(baa);
+    if (index < 0 || index >= n)
+        return (BOXA *)ERROR_PTR("index not valid", procName, NULL);
+    if (accessflag != L_COPY && accessflag != L_CLONE)
+        return (BOXA *)ERROR_PTR("invalid accessflag", procName, NULL);
+
+    return boxaCopy(baa->boxa[index], accessflag);
+}
+
+
+/*!
+ *  boxaaGetBox()
+ *
+ *      Input:  baa
+ *              iboxa  (index into the boxa array in the boxaa)
+ *              ibox  (index into the box array in the boxa)
+ *              accessflag   (L_COPY or L_CLONE)
+ *      Return: box, or null on error
+ */
+BOX *
+boxaaGetBox(BOXAA   *baa,
+            l_int32  iboxa,
+            l_int32  ibox,
+            l_int32  accessflag)
+{
+BOX   *box;
+BOXA  *boxa;
+
+    PROCNAME("boxaaGetBox");
+
+    if ((boxa = boxaaGetBoxa(baa, iboxa, L_CLONE)) == NULL)
+        return (BOX *)ERROR_PTR("boxa not retrieved", procName, NULL);
+    if ((box = boxaGetBox(boxa, ibox, accessflag)) == NULL)
+        L_ERROR("box not retrieved\n", procName);
+    boxaDestroy(&boxa);
+    return box;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                           Boxaa array modifiers                      *
+ *----------------------------------------------------------------------*/
+/*!
+ *  boxaaInitFull()
+ *
+ *      Input:  boxaa (typically empty)
+ *              boxa (to be replicated into the entire ptr array)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This initializes a boxaa by filling up the entire boxa ptr array
+ *          with copies of @boxa.  Any existing boxa are destroyed.
+ *          After this operation, the number of boxa is equal to
+ *          the number of allocated ptrs.
+ *      (2) Note that we use boxaaReplaceBox() instead of boxaInsertBox().
+ *          They both have the same effect when inserting into a NULL ptr
+ *          in the boxa ptr array
+ *      (3) Example usage.  This function is useful to prepare for a
+ *          random insertion (or replacement) of boxa into a boxaa.
+ *          To randomly insert boxa into a boxaa, up to some index "max":
+ *             Boxaa *baa = boxaaCreate(max);
+ *               // initialize the boxa
+ *             Boxa *boxa = boxaCreate(...);
+ *             ...  [optionally fix with boxes]
+ *             boxaaInitFull(baa, boxa);
+ *          A typical use is to initialize the array with empty boxa,
+ *          and to replace only a subset that must be aligned with
+ *          something else, such as a pixa.
+ */
+l_int32
+boxaaInitFull(BOXAA  *baa,
+              BOXA   *boxa)
+{
+l_int32  i, n;
+BOXA    *boxat;
+
+    PROCNAME("boxaaInitFull");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    n = baa->nalloc;
+    baa->n = n;
+    for (i = 0; i < n; i++) {
+        boxat = boxaCopy(boxa, L_COPY);
+        boxaaReplaceBoxa(baa, i, boxat);
+    }
+    return 0;
+}
+
+
+/*!
+ *  boxaaExtendWithInit()
+ *
+ *      Input:  boxaa
+ *              maxindex
+ *              boxa (to be replicated into the extended ptr array)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This should be used on an existing boxaa that has been
+ *          fully loaded with boxa.  It then extends the boxaa,
+ *          loading all the additional ptrs with copies of boxa.
+ *          Typically, boxa will be empty.
+ */
+l_int32
+boxaaExtendWithInit(BOXAA   *baa,
+                    l_int32  maxindex,
+                    BOXA    *boxa)
+{
+l_int32  i, n;
+
+    PROCNAME("boxaaExtendWithInit");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+        /* Extend the ptr array if necessary */
+    n = boxaaGetCount(baa);
+    if (maxindex < n) return 0;
+    boxaaExtendArrayToSize(baa, maxindex + 1);
+
+        /* Fill the new entries with copies of boxa */
+    for (i = n; i <= maxindex; i++)
+        boxaaAddBoxa(baa, boxa, L_COPY);
+    return 0;
+}
+
+
+/*!
+ *  boxaaReplaceBoxa()
+ *
+ *      Input:  boxaa
+ *              index  (to the index-th boxa)
+ *              boxa (insert and replace any existing one)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Any existing boxa is destroyed, and the input one
+ *          is inserted in its place.
+ *      (2) If the index is invalid, return 1 (error)
+ */
+l_int32
+boxaaReplaceBoxa(BOXAA   *baa,
+                 l_int32  index,
+                 BOXA    *boxa)
+{
+l_int32  n;
+
+    PROCNAME("boxaaReplaceBoxa");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    n = boxaaGetCount(baa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    boxaDestroy(&baa->boxa[index]);
+    baa->boxa[index] = boxa;
+    return 0;
+}
+
+
+/*!
+ *  boxaaInsertBoxa()
+ *
+ *      Input:  boxaa
+ *              index (location in boxaa to insert new boxa)
+ *              boxa (new boxa to be inserted)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This shifts boxa[i] --> boxa[i + 1] for all i >= index,
+ *          and then inserts boxa as boxa[index].
+ *      (2) To insert at the beginning of the array, set index = 0.
+ *      (3) To append to the array, it's easier to use boxaaAddBoxa().
+ *      (4) This should not be used repeatedly to insert into large arrays,
+ *          because the function is O(n).
+ */
+l_int32
+boxaaInsertBoxa(BOXAA   *baa,
+                l_int32  index,
+                BOXA    *boxa)
+{
+l_int32  i, n;
+BOXA   **array;
+
+    PROCNAME("boxaaInsertBoxa");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+    n = boxaaGetCount(baa);
+    if (index < 0 || index > n)
+        return ERROR_INT("index not in {0...n}", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    if (n >= baa->nalloc)
+        boxaaExtendArray(baa);
+    array = baa->boxa;
+    baa->n++;
+    for (i = n; i > index; i--)
+        array[i] = array[i - 1];
+    array[index] = boxa;
+
+    return 0;
+}
+
+
+/*!
+ *  boxaaRemoveBoxa()
+ *
+ *      Input:  boxaa
+ *              index  (of the boxa to be removed)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This removes boxa[index] and then shifts
+ *          boxa[i] --> boxa[i - 1] for all i > index.
+ *      (2) The removed boxaa is destroyed.
+ *      (2) This should not be used repeatedly on large arrays,
+ *          because the function is O(n).
+ */
+l_int32
+boxaaRemoveBoxa(BOXAA   *baa,
+                l_int32  index)
+{
+l_int32  i, n;
+BOXA   **array;
+
+    PROCNAME("boxaaRemoveBox");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+    n = boxaaGetCount(baa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    array = baa->boxa;
+    boxaDestroy(&array[index]);
+    for (i = index + 1; i < n; i++)
+        array[i - 1] = array[i];
+    array[n - 1] = NULL;
+    baa->n--;
+
+    return 0;
+}
+
+
+/*!
+ *  boxaaAddBox()
+ *
+ *      Input:  boxaa
+ *              index (of boxa with boxaa)
+ *              box (to be added)
+ *              accessflag (L_INSERT, L_COPY or L_CLONE)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Adds to an existing boxa only.
+ */
+l_int32
+boxaaAddBox(BOXAA   *baa,
+            l_int32  index,
+            BOX     *box,
+            l_int32  accessflag)
+{
+l_int32  n;
+BOXA    *boxa;
+    PROCNAME("boxaaAddBox");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+    n = boxaaGetCount(baa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not valid", procName, 1);
+    if (accessflag != L_INSERT && accessflag != L_COPY && accessflag != L_CLONE)
+        return ERROR_INT("invalid accessflag", procName, 1);
+
+    boxa = boxaaGetBoxa(baa, index, L_CLONE);
+    boxaAddBox(boxa, box, accessflag);
+    boxaDestroy(&boxa);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Boxaa serialized I/O                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaaReadFromFiles()
+ *
+ *      Input:  dirname (directory)
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              first (0-based)
+ *              nfiles (use 0 for everything from @first to the end)
+ *      Return: baa, or null on error or if no boxa files are found.
+ *
+ *  Notes:
+ *      (1) The files must be serialized boxa files (e.g., *.ba).
+ *          If some files cannot be read, warnings are issued.
+ *      (2) Use @substr to filter filenames in the directory.  If
+ *          @substr == NULL, this takes all files.
+ *      (3) After filtering, use @first and @nfiles to select
+ *          a contiguous set of files, that have been lexically
+ *          sorted in increasing order.
+ */
+BOXAA *
+boxaaReadFromFiles(const char  *dirname,
+                   const char  *substr,
+                   l_int32      first,
+                   l_int32      nfiles)
+{
+char    *fname;
+l_int32  i, n;
+BOXA    *boxa;
+BOXAA   *baa;
+SARRAY  *sa;
+
+  PROCNAME("boxaaReadFromFiles");
+
+  if (!dirname)
+      return (BOXAA *)ERROR_PTR("dirname not defined", procName, NULL);
+
+  sa = getSortedPathnamesInDirectory(dirname, substr, first, nfiles);
+  if (!sa || ((n = sarrayGetCount(sa)) == 0)) {
+      sarrayDestroy(&sa);
+      return (BOXAA *)ERROR_PTR("no pixa files found", procName, NULL);
+  }
+
+  baa = boxaaCreate(n);
+  for (i = 0; i < n; i++) {
+      fname = sarrayGetString(sa, i, L_NOCOPY);
+      if ((boxa = boxaRead(fname)) == NULL) {
+          L_ERROR("boxa not read for %d-th file", procName, i);
+          continue;
+      }
+      boxaaAddBoxa(baa, boxa, L_INSERT);
+  }
+
+  sarrayDestroy(&sa);
+  return baa;
+}
+
+
+/*!
+ *  boxaaRead()
+ *
+ *      Input:  filename
+ *      Return: boxaa, or null on error
+ */
+BOXAA *
+boxaaRead(const char  *filename)
+{
+FILE   *fp;
+BOXAA  *baa;
+
+    PROCNAME("boxaaRead");
+
+    if (!filename)
+        return (BOXAA *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (BOXAA *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((baa = boxaaReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (BOXAA *)ERROR_PTR("boxaa not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return baa;
+}
+
+
+/*!
+ *  boxaaReadStream()
+ *
+ *      Input:  stream
+ *      Return: boxaa, or null on error
+ */
+BOXAA *
+boxaaReadStream(FILE  *fp)
+{
+l_int32  n, i, x, y, w, h, version;
+l_int32  ignore;
+BOXA    *boxa;
+BOXAA   *baa;
+
+    PROCNAME("boxaaReadStream");
+
+    if (!fp)
+        return (BOXAA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nBoxaa Version %d\n", &version) != 1)
+        return (BOXAA *)ERROR_PTR("not a boxaa file", procName, NULL);
+    if (version != BOXAA_VERSION_NUMBER)
+        return (BOXAA *)ERROR_PTR("invalid boxa version", procName, NULL);
+    if (fscanf(fp, "Number of boxa = %d\n", &n) != 1)
+        return (BOXAA *)ERROR_PTR("not a boxaa file", procName, NULL);
+
+    if ((baa = boxaaCreate(n)) == NULL)
+        return (BOXAA *)ERROR_PTR("boxaa not made", procName, NULL);
+
+    for (i = 0; i < n; i++) {
+        if (fscanf(fp, "\nBoxa[%d] extent: x = %d, y = %d, w = %d, h = %d",
+                   &ignore, &x, &y, &w, &h) != 5)
+            return (BOXAA *)ERROR_PTR("boxa descr not valid", procName, NULL);
+        if ((boxa = boxaReadStream(fp)) == NULL)
+            return (BOXAA *)ERROR_PTR("boxa not made", procName, NULL);
+        boxaaAddBoxa(baa, boxa, L_INSERT);
+    }
+
+    return baa;
+}
+
+/*!
+ *  boxaaWrite()
+ *
+ *      Input:  filename
+ *              boxaa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaaWrite(const char  *filename,
+           BOXAA       *baa)
+{
+FILE  *fp;
+
+    PROCNAME("boxaaWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "w")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (boxaaWriteStream(fp, baa))
+        return ERROR_INT("baa not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  boxaaWriteStream()
+ *
+ *      Input: stream
+ *             boxaa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaaWriteStream(FILE   *fp,
+                 BOXAA  *baa)
+{
+l_int32  n, i, x, y, w, h;
+BOX     *box;
+BOXA    *boxa;
+
+    PROCNAME("boxaaWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+
+    n = boxaaGetCount(baa);
+    fprintf(fp, "\nBoxaa Version %d\n", BOXAA_VERSION_NUMBER);
+    fprintf(fp, "Number of boxa = %d\n", n);
+
+    for (i = 0; i < n; i++) {
+        if ((boxa = boxaaGetBoxa(baa, i, L_CLONE)) == NULL)
+            return ERROR_INT("boxa not found", procName, 1);
+        boxaGetExtent(boxa, NULL, NULL, &box);
+        boxGetGeometry(box, &x, &y, &w, &h);
+        fprintf(fp, "\nBoxa[%d] extent: x = %d, y = %d, w = %d, h = %d",
+                i, x, y, w, h);
+        boxaWriteStream(fp, boxa);
+        boxDestroy(&box);
+        boxaDestroy(&boxa);
+    }
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Boxa serialized I/O                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaRead()
+ *
+ *      Input:  filename
+ *      Return: boxa, or null on error
+ */
+BOXA *
+boxaRead(const char  *filename)
+{
+FILE  *fp;
+BOXA  *boxa;
+
+    PROCNAME("boxaRead");
+
+    if (!filename)
+        return (BOXA *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (BOXA *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((boxa = boxaReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (BOXA *)ERROR_PTR("boxa not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return boxa;
+}
+
+
+/*!
+ *  boxaReadStream()
+ *
+ *      Input:  stream
+ *      Return: boxa, or null on error
+ */
+BOXA *
+boxaReadStream(FILE  *fp)
+{
+l_int32  n, i, x, y, w, h, version;
+l_int32  ignore;
+BOX     *box;
+BOXA    *boxa;
+
+    PROCNAME("boxaReadStream");
+
+    if (!fp)
+        return (BOXA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nBoxa Version %d\n", &version) != 1)
+        return (BOXA *)ERROR_PTR("not a boxa file", procName, NULL);
+    if (version != BOXA_VERSION_NUMBER)
+        return (BOXA *)ERROR_PTR("invalid boxa version", procName, NULL);
+    if (fscanf(fp, "Number of boxes = %d\n", &n) != 1)
+        return (BOXA *)ERROR_PTR("not a boxa file", procName, NULL);
+
+    if ((boxa = boxaCreate(n)) == NULL)
+        return (BOXA *)ERROR_PTR("boxa not made", procName, NULL);
+
+    for (i = 0; i < n; i++) {
+        if (fscanf(fp, "  Box[%d]: x = %d, y = %d, w = %d, h = %d\n",
+                &ignore, &x, &y, &w, &h) != 5)
+            return (BOXA *)ERROR_PTR("box descr not valid", procName, NULL);
+        if ((box = boxCreate(x, y, w, h)) == NULL)
+            return (BOXA *)ERROR_PTR("box not made", procName, NULL);
+        boxaAddBox(boxa, box, L_INSERT);
+    }
+
+    return boxa;
+}
+
+
+/*!
+ *  boxaReadMem()
+ *
+ *      Input:  data (ascii)
+ *              size (of data; can use strlen to get it)
+ *      Return: boxa, or null on error
+ */
+BOXA *
+boxaReadMem(const l_uint8  *data,
+            size_t          size)
+{
+FILE  *fp;
+BOXA  *boxa;
+
+    PROCNAME("boxaReadMem");
+
+    if (!data)
+        return (BOXA *)ERROR_PTR("data not defined", procName, NULL);
+
+        /* De-serialize: write serialized data to file and read back as boxa.
+         * We are writing to file first, instead of reading from the memory
+         * buffer, because the gnu extension fmemopen() is not available
+         * with other runtimes. */
+    if ((fp = tmpfile()) == NULL)
+        return (BOXA *)ERROR_PTR("tmpfile stream not opened", procName, NULL);
+    fwrite(data, 1, size, fp);
+    rewind(fp);
+    boxa = boxaReadStream(fp);
+    fclose(fp);
+    if (!boxa) L_ERROR("boxa not read\n", procName);
+    return boxa;
+}
+
+
+/*!
+ *  boxaWrite()
+ *
+ *      Input:  filename
+ *              boxa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaWrite(const char  *filename,
+          BOXA        *boxa)
+{
+FILE  *fp;
+
+    PROCNAME("boxaWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "w")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (boxaWriteStream(fp, boxa))
+        return ERROR_INT("boxa not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  boxaWriteStream()
+ *
+ *      Input: stream
+ *             boxa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaWriteStream(FILE  *fp,
+                BOXA  *boxa)
+{
+l_int32  n, i;
+BOX     *box;
+
+    PROCNAME("boxaWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    n = boxaGetCount(boxa);
+    fprintf(fp, "\nBoxa Version %d\n", BOXA_VERSION_NUMBER);
+    fprintf(fp, "Number of boxes = %d\n", n);
+    for (i = 0; i < n; i++) {
+        if ((box = boxaGetBox(boxa, i, L_CLONE)) == NULL)
+            return ERROR_INT("box not found", procName, 1);
+        fprintf(fp, "  Box[%d]: x = %d, y = %d, w = %d, h = %d\n",
+                i, box->x, box->y, box->w, box->h);
+        boxDestroy(&box);
+    }
+    return 0;
+}
+
+
+/*!
+ *  boxaWriteMem()
+ *
+ *      Input:  &data (<return> data of serialized boxa; ascii)
+ *              &size (<return> size of returned data)
+ *              boxa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaWriteMem(l_uint8  **pdata,
+             size_t    *psize,
+             BOXA      *boxa)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("boxaWriteMem");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1);
+    *psize = 0;
+    if (!boxa)
+        return ERROR_INT("&boxa not defined", procName, 1);
+
+        /* Serialize: write to file and read serialized data back into memory */
+    if ((fp = tmpfile()) == NULL)
+        return ERROR_INT("tmpfile stream not opened", procName, 1);
+    ret = boxaWriteStream(fp, boxa);
+    rewind(fp);
+    *pdata = l_binaryReadStream(fp, psize);
+    fclose(fp);
+    return ret;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                            Debug printing                           *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxPrintStreamInfo()
+ *
+ *      Input:  stream
+ *              box
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This outputs debug info.  Use serialization functions to
+ *          write to file if you want to read the data back.
+ */
+l_int32
+boxPrintStreamInfo(FILE  *fp,
+                   BOX   *box)
+{
+    PROCNAME("boxPrintStreamInfo");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+
+    fprintf(fp, " Box: x = %d, y = %d, w = %d, h = %d\n",
+            box->x, box->y, box->w, box->h);
+    return 0;
+}
diff --git a/src/boxfunc1.c b/src/boxfunc1.c
new file mode 100644 (file)
index 0000000..bb90f66
--- /dev/null
@@ -0,0 +1,1758 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   boxfunc1.c
+ *
+ *      Box geometry
+ *           l_int32   boxContains()
+ *           l_int32   boxIntersects()
+ *           BOXA     *boxaContainedInBox()
+ *           BOXA     *boxaIntersectsBox()
+ *           BOXA     *boxaClipToBox()
+ *           BOXA     *boxaCombineOverlaps()
+ *           BOX      *boxOverlapRegion()
+ *           BOX      *boxBoundingRegion()
+ *           l_int32   boxOverlapFraction()
+ *           l_int32   boxOverlapArea()
+ *           BOXA     *boxaHandleOverlaps()
+ *           l_int32   boxSeparationDistance()
+ *           l_int32   boxContainsPt()
+ *           BOX      *boxaGetNearestToPt()
+ *           l_int32   boxIntersectByLine()
+ *           l_int32   boxGetCenter()
+ *           BOX      *boxClipToRectangle()
+ *           l_int32   boxClipToRectangleParams()
+ *           BOX      *boxRelocateOneSide()
+ *           BOX      *boxAdjustSides()
+ *           BOXA     *boxaSetSide()
+ *           BOXA     *boxaAdjustWidthToTarget()
+ *           BOXA     *boxaAdjustHeightToTarget()
+ *           l_int32   boxEqual()
+ *           l_int32   boxaEqual()
+ *           l_int32   boxSimilar()
+ *           l_int32   boxaSimilar()
+ *
+ *      Boxa combine and split
+ *           l_int32   boxaJoin()
+ *           l_int32   boxaaJoin()
+ *           l_int32   boxaSplitEvenOdd()
+ *           BOXA     *boxaMergeEvenOdd()
+ */
+
+#include "allheaders.h"
+
+
+/*---------------------------------------------------------------------*
+ *                             Box geometry                            *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxContains()
+ *
+ *      Input:  box1, box2
+ *              &result (<return> 1 if box2 is entirely contained within
+ *                       box1, and 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxContains(BOX     *box1,
+            BOX     *box2,
+            l_int32 *presult)
+{
+    PROCNAME("boxContains");
+
+    if (!box1 || !box2)
+        return ERROR_INT("box1 and box2 not both defined", procName, 1);
+
+l_int32  x1, y1, w1, h1, x2, y2, w2, h2;
+
+    boxGetGeometry(box1, &x1, &y1, &w1, &h1);
+    boxGetGeometry(box2, &x2, &y2, &w2, &h2);
+    if (x1 <= x2 && y1 <= y2 && (x1 + w1 >= x2 + w2) && (y1 + h1 >= y2 + h2))
+        *presult = 1;
+    else
+        *presult = 0;
+    return 0;
+}
+
+
+/*!
+ *  boxIntersects()
+ *
+ *      Input:  box1, box2
+ *              &result (<return> 1 if any part of box2 is contained
+ *                      in box1, and 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxIntersects(BOX      *box1,
+              BOX      *box2,
+              l_int32  *presult)
+{
+l_int32  l1, l2, r1, r2, t1, t2, b1, b2, w1, h1, w2, h2;
+
+    PROCNAME("boxIntersects");
+
+    if (!box1 || !box2)
+        return ERROR_INT("box1 and box2 not both defined", procName, 1);
+
+    boxGetGeometry(box1, &l1, &t1, &w1, &h1);
+    boxGetGeometry(box2, &l2, &t2, &w2, &h2);
+    r1 = l1 + w1 - 1;
+    r2 = l2 + w2 - 1;
+    b1 = t1 + h1 - 1;
+    b2 = t2 + h2 - 1;
+    if (b2 < t1 || b1 < t2 || r1 < l2 || r2 < l1)
+        *presult = 0;
+    else
+        *presult = 1;
+    return 0;
+}
+
+
+/*!
+ *  boxaContainedInBox()
+ *
+ *      Input:  boxas
+ *              box (for containment)
+ *      Return: boxad (boxa with all boxes in boxas that are
+ *                     entirely contained in box), or null on error
+ *
+ *  Notes:
+ *      (1) All boxes in boxa that are entirely outside box are removed.
+ */
+BOXA *
+boxaContainedInBox(BOXA  *boxas,
+                   BOX   *box)
+{
+l_int32  i, n, val;
+BOX     *boxt;
+BOXA    *boxad;
+
+    PROCNAME("boxaContainedInBox");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (!box)
+        return (BOXA *)ERROR_PTR("box not defined", procName, NULL);
+    if ((n = boxaGetCount(boxas)) == 0)
+        return boxaCreate(1);  /* empty */
+
+    boxad = boxaCreate(0);
+    for (i = 0; i < n; i++) {
+        boxt = boxaGetBox(boxas, i, L_CLONE);
+        boxContains(box, boxt, &val);
+        if (val == 1)
+            boxaAddBox(boxad, boxt, L_COPY);
+        boxDestroy(&boxt);  /* destroy the clone */
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxaIntersectsBox()
+ *
+ *      Input:  boxas
+ *              box (for intersecting)
+ *      Return  boxad (boxa with all boxes in boxas that intersect box),
+ *                     or null on error
+ *
+ *  Notes:
+ *      (1) All boxes in boxa that intersect with box (i.e., are completely
+ *          or partially contained in box) are retained.
+ */
+BOXA *
+boxaIntersectsBox(BOXA  *boxas,
+                  BOX   *box)
+{
+l_int32  i, n, val;
+BOX     *boxt;
+BOXA    *boxad;
+
+    PROCNAME("boxaIntersectsBox");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (!box)
+        return (BOXA *)ERROR_PTR("box not defined", procName, NULL);
+    if ((n = boxaGetCount(boxas)) == 0)
+        return boxaCreate(1);  /* empty */
+
+    boxad = boxaCreate(0);
+    for (i = 0; i < n; i++) {
+        boxt = boxaGetBox(boxas, i, L_CLONE);
+        boxIntersects(box, boxt, &val);
+        if (val == 1)
+            boxaAddBox(boxad, boxt, L_COPY);
+        boxDestroy(&boxt);  /* destroy the clone */
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxaClipToBox()
+ *
+ *      Input:  boxas
+ *              box (for clipping)
+ *      Return  boxad (boxa with boxes in boxas clipped to box),
+ *                     or null on error
+ *
+ *  Notes:
+ *      (1) All boxes in boxa not intersecting with box are removed, and
+ *          the remaining boxes are clipped to box.
+ */
+BOXA *
+boxaClipToBox(BOXA  *boxas,
+              BOX   *box)
+{
+l_int32  i, n;
+BOX     *boxt, *boxo;
+BOXA    *boxad;
+
+    PROCNAME("boxaClipToBox");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (!box)
+        return (BOXA *)ERROR_PTR("box not defined", procName, NULL);
+    if ((n = boxaGetCount(boxas)) == 0)
+        return boxaCreate(1);  /* empty */
+
+    boxad = boxaCreate(0);
+    for (i = 0; i < n; i++) {
+        boxt = boxaGetBox(boxas, i, L_CLONE);
+        if ((boxo = boxOverlapRegion(box, boxt)) != NULL)
+            boxaAddBox(boxad, boxo, L_INSERT);
+        boxDestroy(&boxt);
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxaCombineOverlaps()
+ *
+ *      Input:  boxas
+ *      Return: boxad (where each set of boxes in boxas that overlap are
+ *                     combined into a single bounding box in boxad), or
+ *                     null on error.
+ *
+ *  Notes:
+ *      (1) If there are no overlapping boxes, it simply returns a copy
+ *          of @boxas.
+ *      (2) The alternative method of painting each rectanle and finding
+ *          the 4-connected components gives the wrong result, because
+ *          two non-overlapping rectangles, when rendered, can still
+ *          be 4-connected, and hence they will be joined.
+ *      (3) A bad case is to have n boxes, none of which overlap.
+ *          Then you have one iteration with O(n^2) compares.  This
+ *          is still faster than painting each rectangle and finding
+ *          the connected components, even for thousands of rectangles.
+ */
+BOXA *
+boxaCombineOverlaps(BOXA  *boxas)
+{
+l_int32  i, j, n1, n2, inter, interfound, niters;
+BOX     *box1, *box2, *box3;
+BOXA    *boxat1, *boxat2;
+
+    PROCNAME("boxaCombineOverlaps");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+
+    boxat1 = boxaCopy(boxas, L_COPY);
+    n1 = boxaGetCount(boxat1);
+    niters = 0;
+/*    fprintf(stderr, "%d iters: %d boxes\n", niters, n1); */
+    while (1) {  /* loop until no change from previous iteration */
+        niters++;
+        boxat2 = boxaCreate(n1);
+        for (i = 0; i < n1; i++) {
+            box1 = boxaGetBox(boxat1, i, L_COPY);
+            if (i == 0) {
+                boxaAddBox(boxat2, box1, L_INSERT);
+                continue;
+            }
+            n2 = boxaGetCount(boxat2);
+                /* Now test box1 against all boxes already put in boxat2.
+                 * If it is found to intersect with an existing box,
+                 * replace that box by the union of the two boxes,
+                 * and break to the outer loop.  If no overlap is
+                 * found, add box1 to boxat2. */
+            interfound = FALSE;
+            for (j = 0; j < n2; j++) {
+                box2 = boxaGetBox(boxat2, j, L_CLONE);
+                boxIntersects(box1, box2, &inter);
+                if (inter == 1) {
+                    box3 = boxBoundingRegion(box1, box2);
+                    boxaReplaceBox(boxat2, j, box3);
+                    boxDestroy(&box1);
+                    boxDestroy(&box2);
+                    interfound = TRUE;
+                    break;
+                }
+                boxDestroy(&box2);
+            }
+            if (interfound == FALSE)
+                boxaAddBox(boxat2, box1, L_INSERT);
+        }
+
+        n2 = boxaGetCount(boxat2);
+/*        fprintf(stderr, "%d iters: %d boxes\n", niters, n2); */
+        if (n2 == n1)  /* we're done */
+            break;
+
+        n1 = n2;
+        boxaDestroy(&boxat1);
+        boxat1 = boxat2;
+    }
+    boxaDestroy(&boxat1);
+    return boxat2;
+}
+
+
+/*!
+ *  boxOverlapRegion()
+ *
+ *      Input:  box1, box2 (two boxes)
+ *      Return: box (of overlap region between input boxes),
+ *              or null if no overlap or on error
+ *
+ *  Notes:
+ *      (1) This is the geometric intersection of the two rectangles.
+ */
+BOX *
+boxOverlapRegion(BOX  *box1,
+                 BOX  *box2)
+{
+l_int32  l1, l2, r1, r2, t1, t2, b1, b2, w1, h1, w2, h2, ld, td, rd, bd;
+
+    PROCNAME("boxOverlapRegion");
+
+    if (!box1)
+        return (BOX *)ERROR_PTR("box1 not defined", procName, NULL);
+    if (!box2)
+        return (BOX *)ERROR_PTR("box2 not defined", procName, NULL);
+
+    boxGetGeometry(box1, &l1, &t1, &w1, &h1);
+    boxGetGeometry(box2, &l2, &t2, &w2, &h2);
+    r1 = l1 + w1 - 1;
+    r2 = l2 + w2 - 1;
+    b1 = t1 + h1 - 1;
+    b2 = t2 + h2 - 1;
+    if (b2 < t1 || b1 < t2 || r1 < l2 || r2 < l1)
+        return NULL;
+
+    ld = L_MAX(l1, l2);
+    td = L_MAX(t1, t2);
+    rd = L_MIN(r1, r2);
+    bd = L_MIN(b1, b2);
+    return boxCreate(ld, td, rd - ld + 1, bd - td + 1);
+}
+
+
+/*!
+ *  boxBoundingRegion()
+ *
+ *      Input:  box1, box2 (two boxes)
+ *      Return: box (of bounding region containing the input boxes),
+ *              or null on error
+ *
+ *  Notes:
+ *      (1) This is the geometric union of the two rectangles.
+ */
+BOX *
+boxBoundingRegion(BOX  *box1,
+                  BOX  *box2)
+{
+l_int32  l1, l2, r1, r2, t1, t2, b1, b2, w1, h1, w2, h2, ld, td, rd, bd;
+
+    PROCNAME("boxBoundingRegion");
+
+    if (!box1)
+        return (BOX *)ERROR_PTR("box1 not defined", procName, NULL);
+    if (!box2)
+        return (BOX *)ERROR_PTR("box2 not defined", procName, NULL);
+
+    boxGetGeometry(box1, &l1, &t1, &w1, &h1);
+    boxGetGeometry(box2, &l2, &t2, &w2, &h2);
+    r1 = l1 + w1 - 1;
+    r2 = l2 + w2 - 1;
+    b1 = t1 + h1 - 1;
+    b2 = t2 + h2 - 1;
+    ld = L_MIN(l1, l2);
+    td = L_MIN(t1, t2);
+    rd = L_MAX(r1, r2);
+    bd = L_MAX(b1, b2);
+    return boxCreate(ld, td, rd - ld + 1, bd - td + 1);
+}
+
+
+/*!
+ *  boxOverlapFraction()
+ *
+ *      Input:  box1, box2 (two boxes)
+ *              &fract (<return> the fraction of box2 overlapped by box1)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) The result depends on the order of the input boxes,
+ *          because the overlap is taken as a fraction of box2.
+ */
+l_int32
+boxOverlapFraction(BOX        *box1,
+                   BOX        *box2,
+                   l_float32  *pfract)
+{
+l_int32  w2, h2, w, h;
+BOX     *boxo;
+
+    PROCNAME("boxOverlapFraction");
+
+    if (!pfract)
+        return ERROR_INT("&fract not defined", procName, 1);
+    *pfract = 0.0;
+    if (!box1)
+        return ERROR_INT("box1 not defined", procName, 1);
+    if (!box2)
+        return ERROR_INT("box2 not defined", procName, 1);
+
+    if ((boxo = boxOverlapRegion(box1, box2)) == NULL)  /* no overlap */
+        return 0;
+
+    boxGetGeometry(box2, NULL, NULL, &w2, &h2);
+    boxGetGeometry(boxo, NULL, NULL, &w, &h);
+    *pfract = (l_float32)(w * h) / (l_float32)(w2 * h2);
+    boxDestroy(&boxo);
+    return 0;
+}
+
+
+/*!
+ *  boxOverlapArea()
+ *
+ *      Input:  box1, box2 (two boxes)
+ *              &area (<return> the number of pixels in the overlap)
+ *      Return: 0 if OK, 1 on error.
+ */
+l_int32
+boxOverlapArea(BOX      *box1,
+               BOX      *box2,
+               l_int32  *parea)
+{
+l_int32  w, h;
+BOX     *box;
+
+    PROCNAME("boxOverlapArea");
+
+    if (!parea)
+        return ERROR_INT("&area not defined", procName, 1);
+    *parea = 0;
+    if (!box1)
+        return ERROR_INT("box1 not defined", procName, 1);
+    if (!box2)
+        return ERROR_INT("box2 not defined", procName, 1);
+
+    if ((box = boxOverlapRegion(box1, box2)) == NULL)  /* no overlap */
+        return 0;
+
+    boxGetGeometry(box, NULL, NULL, &w, &h);
+    *parea = w * h;
+    boxDestroy(&box);
+    return 0;
+}
+
+
+/*!
+ *  boxaHandleOverlaps()
+ *
+ *      Input:  boxas
+ *              op (L_COMBINE, L_REMOVE_SMALL)
+ *              range (> 0, forward distance over which overlaps are checked)
+ *              min_overlap (minimum fraction of smaller box required for
+ *                           overlap to count; 0.0 to ignore)
+ *              max_ratio (maximum fraction of small/large areas for
+ *                         overlap to count; 1.0 to ignore)
+ *              &namap (<optional return> combining map)
+ *      Return: boxad, or null on error.
+ *
+ *  Notes:
+ *      (1) For all n(n-1)/2 box pairings, if two boxes overlap, either:
+ *          (a) op == L_COMBINE: get the bounding region for the two,
+ *              replace the larger with the bounding region, and remove
+ *              the smaller of the two, or
+ *          (b) op == L_REMOVE_SMALL: just remove the smaller.
+ *      (2) If boxas is 2D sorted, range can be small, but if it is
+ *          not spatially sorted, range should be large to allow all
+ *          pairwise comparisons to be made.
+ *      (3) The @min_overlap parameter allows ignoring small overlaps.
+ *          If @min_overlap == 1.0, only boxes fully contained in larger
+ *          boxes can be considered for removal; if @min_overlap == 0.0,
+ *          this constraint is ignored.
+ *      (4) The @max_ratio parameter allows ignoring overlaps between
+ *          boxes that are not too different in size.  If @max_ratio == 0.0,
+ *          no boxes can be removed; if @max_ratio == 1.0, this constraint
+ *          is ignored.
+ */
+BOXA *
+boxaHandleOverlaps(BOXA    *boxas,
+                   l_int32  op,
+                   l_int32  range,
+                   l_float32  min_overlap,
+                   l_float32  max_ratio,
+                   NUMA   **pnamap)
+{
+l_int32    i, j, n, w, h, area1, area2, val;
+l_int32    overlap_area;
+l_float32  overlap_ratio, area_ratio;
+BOX       *box1, *box2, *box3;
+BOXA      *boxat, *boxad;
+NUMA      *namap;
+
+    PROCNAME("boxaHandleOverlaps");
+
+    if (pnamap) *pnamap = NULL;
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (op != L_COMBINE && op != L_REMOVE_SMALL)
+        return (BOXA *)ERROR_PTR("invalid op", procName, NULL);
+
+    n = boxaGetCount(boxas);
+    if (n == 0)
+        return boxaCreate(1);  /* empty */
+    if (range == 0) {
+        L_WARNING("range is 0\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+
+        /* Identify smaller boxes in overlap pairs, and mark to eliminate. */
+    namap = numaMakeConstant(-1, n);
+    for (i = 0; i < n; i++) {
+        box1 = boxaGetBox(boxas, i, L_CLONE);
+        boxGetGeometry(box1, NULL, NULL, &w, &h);
+        area1 = w * h;
+        if (area1 == 0) {
+            boxDestroy(&box1);
+            continue;
+        }
+        for (j = i + 1; j < i + 1 + range && j < n; j++) {
+            box2 = boxaGetBox(boxas, j, L_CLONE);
+            boxOverlapArea(box1, box2, &overlap_area);
+            if (overlap_area > 0) {
+                boxGetGeometry(box2, NULL, NULL, &w, &h);
+                area2 = w * h;
+                if (area2 == 0) {
+                    /* do nothing */
+                } else if (area1 >= area2) {
+                    overlap_ratio = (l_float32)overlap_area / area2;
+                    area_ratio = (l_float32)area2 / (l_float32)area1;
+                    if (overlap_ratio >= min_overlap &&
+                        area_ratio <= max_ratio) {
+                        numaSetValue(namap, j, i);
+                    }
+                } else {
+                    overlap_ratio = overlap_area / area1;
+                    area_ratio = (l_float32)area1 / (l_float32)area2;
+                    if (overlap_ratio >= min_overlap &&
+                        area_ratio <= max_ratio) {
+                        numaSetValue(namap, i, j);
+                    }
+                }
+            }
+            boxDestroy(&box2);
+        }
+        boxDestroy(&box1);
+    }
+
+    boxat = boxaCopy(boxas, L_COPY);
+    if (op == L_COMBINE) {
+            /* Resize the larger of the pair to the bounding region */
+        for (i = 0; i < n; i++) {
+            numaGetIValue(namap, i, &val);
+            if (val >= 0) {
+                box1 = boxaGetBox(boxas, i, L_CLONE);  /* smaller */
+                box2 = boxaGetBox(boxas, val, L_CLONE);  /* larger */
+                box3 = boxBoundingRegion(box1, box2);
+                boxaReplaceBox(boxat, val, box3);
+                boxDestroy(&box1);
+                boxDestroy(&box2);
+            }
+        }
+    }
+
+        /* Remove the smaller of the pairs */
+    boxad = boxaCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(namap, i, &val);
+        if (val == -1) {
+            box1 = boxaGetBox(boxat, i, L_COPY);
+            boxaAddBox(boxad, box1, L_INSERT);
+        }
+    }
+    boxaDestroy(&boxat);
+    if (pnamap)
+        *pnamap = namap;
+    else
+        numaDestroy(&namap);
+    return boxad;
+}
+
+
+/*!
+ *  boxSeparationDistance()
+ *
+ *      Input:  box1, box2 (two boxes, in any order)
+ *              &h_sep (<optional return> horizontal separation)
+ *              &v_sep (<optional return> vertical separation)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This measures horizontal and vertical separation of the
+ *          two boxes.  If the boxes are touching but have no pixels
+ *          in common, the separation is 0.  If the boxes overlap by
+ *          a distance d, the returned separation is -d.
+ */
+l_int32
+boxSeparationDistance(BOX      *box1,
+                      BOX      *box2,
+                      l_int32  *ph_sep,
+                      l_int32  *pv_sep)
+{
+l_int32  l1, t1, w1, h1, r1, b1, l2, t2, w2, h2, r2, b2;
+
+    PROCNAME("boxSeparationDistance");
+
+    if (!ph_sep && !pv_sep)
+        return ERROR_INT("nothing to do", procName, 1);
+    if (ph_sep) *ph_sep = 0;
+    if (pv_sep) *pv_sep = 0;
+    if (!box1 || !box2)
+        return ERROR_INT("box1 and box2 not both defined", procName, 1);
+
+    if (ph_sep) {
+        boxGetGeometry(box1, &l1, NULL, &w1, NULL);
+        boxGetGeometry(box2, &l2, NULL, &w2, NULL);
+        r1 = l1 + w1;  /* 1 pixel to the right of box 1 */
+        r2 = l2 + w2;
+        if (l2 >= l1)
+            *ph_sep = l2 - r1;
+        else
+            *ph_sep = l1 - r2;
+    }
+    if (pv_sep) {
+        boxGetGeometry(box1, NULL, &t1, NULL, &h1);
+        boxGetGeometry(box2, NULL, &t2, NULL, &h2);
+        b1 = t1 + h1;  /* 1 pixel below box 1 */
+        b2 = t2 + h2;
+        if (t2 >= t1)
+            *pv_sep = t2 - b1;
+        else
+            *pv_sep = t1 - b2;
+    }
+    return 0;
+}
+
+
+/*!
+ *  boxContainsPt()
+ *
+ *      Input:  box
+ *              x, y (a point)
+ *              &contains (<return> 1 if box contains point; 0 otherwise)
+ *      Return: 0 if OK, 1 on error.
+ */
+l_int32
+boxContainsPt(BOX       *box,
+              l_float32  x,
+              l_float32  y,
+              l_int32   *pcontains)
+{
+l_int32  bx, by, bw, bh;
+
+    PROCNAME("boxContainsPt");
+
+    if (!pcontains)
+        return ERROR_INT("&contains not defined", procName, 1);
+    *pcontains = 0;
+    if (!box)
+        return ERROR_INT("&box not defined", procName, 1);
+    boxGetGeometry(box, &bx, &by, &bw, &bh);
+    if (x >= bx && x < bx + bw && y >= by && y < by + bh)
+        *pcontains = 1;
+    return 0;
+}
+
+
+/*!
+ *  boxaGetNearestToPt()
+ *
+ *      Input:  boxa
+ *              x, y  (point)
+ *      Return  box (box with centroid closest to the given point [x,y]),
+ *              or NULL if no boxes in boxa)
+ *
+ *  Notes:
+ *      (1) Uses euclidean distance between centroid and point.
+ */
+BOX *
+boxaGetNearestToPt(BOXA    *boxa,
+                   l_int32  x,
+                   l_int32  y)
+{
+l_int32    i, n, minindex;
+l_float32  delx, dely, dist, mindist, cx, cy;
+BOX       *box;
+
+    PROCNAME("boxaGetNearestToPt");
+
+    if (!boxa)
+        return (BOX *)ERROR_PTR("boxa not defined", procName, NULL);
+    if ((n = boxaGetCount(boxa)) == 0)
+        return (BOX *)ERROR_PTR("n = 0", procName, NULL);
+
+    mindist = 1000000000.;
+    minindex = 0;
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        boxGetCenter(box, &cx, &cy);
+        delx = (l_float32)(cx - x);
+        dely = (l_float32)(cy - y);
+        dist = delx * delx + dely * dely;
+        if (dist < mindist) {
+            minindex = i;
+            mindist = dist;
+        }
+        boxDestroy(&box);
+    }
+
+    return boxaGetBox(boxa, minindex, L_COPY);
+}
+
+
+/*!
+ *  boxGetCenter()
+ *
+ *      Input:  box
+ *              &cx, &cy (<return> location of center of box)
+ *      Return  0 if OK, 1 on error
+ */
+l_int32
+boxGetCenter(BOX        *box,
+             l_float32  *pcx,
+             l_float32  *pcy)
+{
+l_int32  x, y, w, h;
+
+    PROCNAME("boxGetCenter");
+
+    if (!pcx || !pcy)
+        return ERROR_INT("&cx, &cy not both defined", procName, 1);
+    *pcx = *pcy = 0;
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    boxGetGeometry(box, &x, &y, &w, &h);
+    *pcx = (l_float32)(x + 0.5 * w);
+    *pcy = (l_float32)(y + 0.5 * h);
+
+    return 0;
+}
+
+
+/*!
+ *  boxIntersectByLine()
+ *
+ *      Input:  box
+ *              x, y (point that line goes through)
+ *              slope (of line)
+ *              (&x1, &y1) (<return> 1st point of intersection with box)
+ *              (&x2, &y2) (<return> 2nd point of intersection with box)
+ *              &n (<return> number of points of intersection)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If the intersection is at only one point (a corner), the
+ *          coordinates are returned in (x1, y1).
+ *      (2) Represent a vertical line by one with a large but finite slope.
+ */
+l_int32
+boxIntersectByLine(BOX       *box,
+                   l_int32    x,
+                   l_int32    y,
+                   l_float32  slope,
+                   l_int32   *px1,
+                   l_int32   *py1,
+                   l_int32   *px2,
+                   l_int32   *py2,
+                   l_int32   *pn)
+{
+l_int32    bx, by, bw, bh, xp, yp, xt, yt, i, n;
+l_float32  invslope;
+PTA       *pta;
+
+    PROCNAME("boxIntersectByLine");
+
+    if (!px1 || !py1 || !px2 || !py2)
+        return ERROR_INT("&x1, &y1, &x2, &y2 not all defined", procName, 1);
+    *px1 = *py1 = *px2 = *py2 = 0;
+    if (!pn)
+        return ERROR_INT("&n not defined", procName, 1);
+    *pn = 0;
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    boxGetGeometry(box, &bx, &by, &bw, &bh);
+
+    if (slope == 0.0) {
+        if (y >= by && y < by + bh) {
+            *py1 = *py2 = y;
+            *px1 = bx;
+            *px2 = bx + bw - 1;
+        }
+        return 0;
+    }
+
+    if (slope > 1000000.0) {
+        if (x >= bx && x < bx + bw) {
+            *px1 = *px2 = x;
+            *py1 = by;
+            *py2 = by + bh - 1;
+        }
+        return 0;
+    }
+
+        /* Intersection with top and bottom lines of box */
+    pta = ptaCreate(2);
+    invslope = 1.0 / slope;
+    xp = (l_int32)(x + invslope * (y - by));
+    if (xp >= bx && xp < bx + bw)
+        ptaAddPt(pta, xp, by);
+    xp = (l_int32)(x + invslope * (y - by - bh + 1));
+    if (xp >= bx && xp < bx + bw)
+        ptaAddPt(pta, xp, by + bh - 1);
+
+        /* Intersection with left and right lines of box */
+    yp = (l_int32)(y + slope * (x - bx));
+    if (yp >= by && yp < by + bh)
+        ptaAddPt(pta, bx, yp);
+    yp = (l_int32)(y + slope * (x - bx - bw + 1));
+    if (yp >= by && yp < by + bh)
+        ptaAddPt(pta, bx + bw - 1, yp);
+
+        /* There is a maximum of 2 unique points; remove duplicates.  */
+    n = ptaGetCount(pta);
+    if (n > 0) {
+        ptaGetIPt(pta, 0, px1, py1);  /* accept the first one */
+        *pn = 1;
+    }
+    for (i = 1; i < n; i++) {
+        ptaGetIPt(pta, i, &xt, &yt);
+        if ((*px1 != xt) || (*py1 != yt)) {
+            *px2 = xt;
+            *py2 = yt;
+            *pn = 2;
+            break;
+        }
+    }
+
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  boxClipToRectangle()
+ *
+ *      Input:  box
+ *              wi, hi (rectangle representing image)
+ *      Return: part of box within given rectangle, or NULL on error
+ *              or if box is entirely outside the rectangle
+ *
+ *  Notes:
+ *      (1) This can be used to clip a rectangle to an image.
+ *          The clipping rectangle is assumed to have a UL corner at (0, 0),
+ *          and a LR corner at (wi - 1, hi - 1).
+ */
+BOX *
+boxClipToRectangle(BOX     *box,
+                   l_int32  wi,
+                   l_int32  hi)
+{
+BOX  *boxd;
+
+    PROCNAME("boxClipToRectangle");
+
+    if (!box)
+        return (BOX *)ERROR_PTR("box not defined", procName, NULL);
+    if (box->x >= wi || box->y >= hi ||
+        box->x + box->w <= 0 || box->y + box->h <= 0)
+        return (BOX *)ERROR_PTR("box outside rectangle", procName, NULL);
+
+    boxd = boxCopy(box);
+    if (boxd->x < 0) {
+        boxd->w += boxd->x;
+        boxd->x = 0;
+    }
+    if (boxd->y < 0) {
+        boxd->h += boxd->y;
+        boxd->y = 0;
+    }
+    if (boxd->x + boxd->w > wi)
+        boxd->w = wi - boxd->x;
+    if (boxd->y + boxd->h > hi)
+        boxd->h = hi - boxd->y;
+    return boxd;
+}
+
+
+/*!
+ *  boxClipToRectangleParams()
+ *
+ *      Input:  box (<optional> requested box; can be null)
+ *              w, h (clipping box size; typ. the size of an image)
+ *              &xstart (<return>)
+ *              &ystart (<return>)
+ *              &xend (<return> one pixel beyond clipping box)
+ *              &yend (<return> one pixel beyond clipping box)
+ *              &bw (<optional return> clipped width)
+ *              &bh (<optional return> clipped height)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The return value should be checked.  If it is 1, the
+ *          returned parameter values are bogus.
+ *      (2) This simplifies the selection of pixel locations within
+ *          a given rectangle:
+ *             for (i = ystart; i < yend; i++ {
+ *                 ...
+ *                 for (j = xstart; j < xend; j++ {
+ *                     ....
+ */
+l_int32
+boxClipToRectangleParams(BOX      *box,
+                         l_int32   w,
+                         l_int32   h,
+                         l_int32  *pxstart,
+                         l_int32  *pystart,
+                         l_int32  *pxend,
+                         l_int32  *pyend,
+                         l_int32  *pbw,
+                         l_int32  *pbh)
+{
+l_int32  bw, bh;
+BOX     *boxc;
+
+    PROCNAME("boxClipToRectangleParams");
+
+    if (!pxstart || !pystart || !pxend || !pyend)
+        return ERROR_INT("invalid ptr input", procName, 1);
+    *pxstart = *pystart = 0;
+    *pxend = w;
+    *pyend = h;
+    if (pbw) *pbw = w;
+    if (pbh) *pbh = h;
+    if (!box) return 0;
+
+    if ((boxc = boxClipToRectangle(box, w, h)) == NULL)
+        return ERROR_INT("box outside image", procName, 1);
+    boxGetGeometry(boxc, pxstart, pystart, &bw, &bh);
+    boxDestroy(&boxc);
+
+    if (pbw) *pbw = bw;
+    if (pbh) *pbh = bh;
+    if (bw == 0 || bh == 0)
+        return ERROR_INT("invalid clipping box", procName, 1);
+    *pxend = *pxstart + bw;  /* 1 past the end */
+    *pyend = *pystart + bh;  /* 1 past the end */
+    return 0;
+}
+
+
+/*!
+ *  boxRelocateOneSide()
+ *
+ *      Input:  boxd (<optional>; this can be null, equal to boxs,
+ *                    or different from boxs);
+ *              boxs (starting box; to have one side relocated)
+ *              loc (new location of the side that is changing)
+ *              sideflag (L_FROM_LEFT, etc., indicating the side that moves)
+ *      Return: boxd, or null on error or if the computed boxd has
+ *              width or height <= 0.
+ *
+ *  Notes:
+ *      (1) Set boxd == NULL to get new box; boxd == boxs for in-place;
+ *          or otherwise to resize existing boxd.
+ *      (2) For usage, suggest one of these:
+ *               boxd = boxRelocateOneSide(NULL, boxs, ...);   // new
+ *               boxRelocateOneSide(boxs, boxs, ...);          // in-place
+ *               boxRelocateOneSide(boxd, boxs, ...);          // other
+ */
+BOX *
+boxRelocateOneSide(BOX     *boxd,
+                   BOX     *boxs,
+                   l_int32  loc,
+                   l_int32  sideflag)
+{
+l_int32  x, y, w, h;
+
+    PROCNAME("boxRelocateOneSide");
+
+    if (!boxs)
+        return (BOX *)ERROR_PTR("boxs not defined", procName, NULL);
+    if (!boxd)
+        boxd = boxCopy(boxs);
+
+    boxGetGeometry(boxs, &x, &y, &w, &h);
+    if (sideflag == L_FROM_LEFT)
+        boxSetGeometry(boxd, loc, -1, w + x - loc, -1);
+    else if (sideflag == L_FROM_RIGHT)
+        boxSetGeometry(boxd, -1, -1, loc - x + 1, -1);
+    else if (sideflag == L_FROM_TOP)
+        boxSetGeometry(boxd, -1, loc, -1, h + y - loc);
+    else if (sideflag == L_FROM_BOT)
+        boxSetGeometry(boxd, -1, -1, -1, loc - y + 1);
+    return boxd;
+}
+
+
+/*!
+ *  boxAdjustSides()
+ *
+ *      Input:  boxd  (<optional>; this can be null, equal to boxs,
+ *                     or different from boxs)
+ *              boxs  (starting box; to have sides adjusted)
+ *              delleft, delright, deltop, delbot (changes in location of
+ *                                                 each side)
+ *      Return: boxd, or null on error or if the computed boxd has
+ *              width or height <= 0.
+ *
+ *  Notes:
+ *      (1) Set boxd == NULL to get new box; boxd == boxs for in-place;
+ *          or otherwise to resize existing boxd.
+ *      (2) For usage, suggest one of these:
+ *               boxd = boxAdjustSides(NULL, boxs, ...);   // new
+ *               boxAdjustSides(boxs, boxs, ...);          // in-place
+ *               boxAdjustSides(boxd, boxs, ...);          // other
+ *      (1) New box dimensions are cropped at left and top to x >= 0 and y >= 0.
+ *      (2) For example, to expand in-place by 20 pixels on each side, use
+ *             boxAdjustSides(box, box, -20, 20, -20, 20);
+ */
+BOX *
+boxAdjustSides(BOX     *boxd,
+               BOX     *boxs,
+               l_int32  delleft,
+               l_int32  delright,
+               l_int32  deltop,
+               l_int32  delbot)
+{
+l_int32  x, y, w, h, xl, xr, yt, yb, wnew, hnew;
+
+    PROCNAME("boxAdjustSides");
+
+    if (!boxs)
+        return (BOX *)ERROR_PTR("boxs not defined", procName, NULL);
+
+    boxGetGeometry(boxs, &x, &y, &w, &h);
+    xl = L_MAX(0, x + delleft);
+    yt = L_MAX(0, y + deltop);
+    xr = x + w + delright;  /* one pixel beyond right edge */
+    yb = y + h + delbot;    /* one pixel below bottom edge */
+    wnew = xr - xl;
+    hnew = yb - yt;
+
+    if (wnew < 1 || hnew < 1)
+        return (BOX *)ERROR_PTR("boxd has 0 area", procName, NULL);
+    if (!boxd)
+        return boxCreate(xl, yt, wnew, hnew);
+
+    boxSetGeometry(boxd, xl, yt, wnew, hnew);
+    return boxd;
+}
+
+
+/*!
+ *  boxaSetSide()
+ *
+ *      Input:  boxad (use null to get a new one; same as boxas for in-place)
+ *              boxas
+ *              side (L_SET_LEFT, L_SET_RIGHT, L_SET_TOP, L_SET_BOT)
+ *              val (location to set for given side, for each box)
+ *              thresh (min abs difference to cause resetting to @val)
+ *      Return: boxad, or null on error
+ *
+ *  Notes:
+ *      (1) Sets the given side of each box.  Use boxad == NULL for a new
+ *          boxa, and boxad == boxas for in-place.
+ *      (2) Use one of these:
+ *               boxad = boxaSetSide(NULL, boxas, ...);   // new
+ *               boxaSetSide(boxas, boxas, ...);  // in-place
+ */
+BOXA *
+boxaSetSide(BOXA    *boxad,
+            BOXA    *boxas,
+            l_int32  side,
+            l_int32  val,
+            l_int32  thresh)
+{
+l_int32  x, y, w, h, n, i, diff;
+BOX     *box;
+
+    PROCNAME("boxaSetSide");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (boxad && (boxas != boxad))
+        return (BOXA *)ERROR_PTR("not in-place", procName, NULL);
+    if (side != L_SET_LEFT && side != L_SET_RIGHT &&
+        side != L_SET_TOP && side != L_SET_BOT)
+        return (BOXA *)ERROR_PTR("invalid side", procName, NULL);
+    if (val < 0)
+        return (BOXA *)ERROR_PTR("val < 0", procName, NULL);
+
+    if (!boxad)
+        boxad = boxaCopy(boxas, L_COPY);
+    n = boxaGetCount(boxad);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxad, i, L_CLONE);
+        boxGetGeometry(box, &x, &y, &w, &h);
+        if (side == L_SET_LEFT) {
+            diff = x - val;
+            if (L_ABS(diff) >= thresh)
+                boxSetGeometry(box, val, y, w + diff, h);
+        } else if (side == L_SET_RIGHT) {
+            diff = x + w -1 - val;
+            if (L_ABS(diff) >= thresh)
+                boxSetGeometry(box, x, y, val - x + 1, h);
+        } else if (side == L_SET_TOP) {
+            diff = y - val;
+            if (L_ABS(diff) >= thresh)
+                boxSetGeometry(box, x, val, w, h + diff);
+        } else { /* side == L_SET_BOT */
+            diff = y + h - 1 - val;
+            if (L_ABS(diff) >= thresh)
+                boxSetGeometry(box, x, y, w, val - y + 1);
+        }
+        boxDestroy(&box);
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxaAdjustWidthToTarget()
+ *
+ *      Input:  boxad (use null to get a new one; same as boxas for in-place)
+ *              boxas
+ *              sides (L_ADJUST_LEFT, L_ADJUST_RIGHT, L_ADJUST_LEFTL_AND_RIGHT)
+ *              target (target width if differs by more than thresh)
+ *              thresh (min abs difference in width to cause adjustment)
+ *      Return: boxad, or null on error
+ *
+ *  Notes:
+ *      (1) Conditionally adjusts the width of each box, by moving
+ *          the indicated edges (left and/or right) if the width differs
+ *          by @thresh or more from @target.
+ *      (2) Use boxad == NULL for a new boxa, and boxad == boxas for in-place.
+ *          Use one of these:
+ *               boxad = boxaAdjustWidthToTarget(NULL, boxas, ...);   // new
+ *               boxaAdjustWidthToTarget(boxas, boxas, ...);  // in-place
+ */
+BOXA *
+boxaAdjustWidthToTarget(BOXA    *boxad,
+                        BOXA    *boxas,
+                        l_int32  sides,
+                        l_int32  target,
+                        l_int32  thresh)
+{
+l_int32  x, y, w, h, n, i, diff;
+BOX     *box;
+
+    PROCNAME("boxaAdjustWidthToTarget");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (boxad && (boxas != boxad))
+        return (BOXA *)ERROR_PTR("not in-place", procName, NULL);
+    if (sides != L_ADJUST_LEFT && sides != L_ADJUST_RIGHT &&
+        sides != L_ADJUST_LEFT_AND_RIGHT)
+        return (BOXA *)ERROR_PTR("invalid sides", procName, NULL);
+    if (target < 1)
+        return (BOXA *)ERROR_PTR("target < 1", procName, NULL);
+
+    if (!boxad)
+        boxad = boxaCopy(boxas, L_COPY);
+    n = boxaGetCount(boxad);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxad, i, L_CLONE);
+        boxGetGeometry(box, &x, &y, &w, &h);
+        diff = w - target;
+        if (sides == L_ADJUST_LEFT) {
+            if (L_ABS(diff) >= thresh)
+                boxSetGeometry(box, L_MAX(0, x + diff), y, target, h);
+        } else if (sides == L_ADJUST_RIGHT) {
+            if (L_ABS(diff) >= thresh)
+                boxSetGeometry(box, x, y, target, h);
+        } else { /* sides == L_ADJUST_LEFT_AND_RIGHT */
+            if (L_ABS(diff) >= thresh)
+                boxSetGeometry(box, L_MAX(0, x + diff/2), y, target, h);
+        }
+        boxDestroy(&box);
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxaAdjustHeightToTarget()
+ *
+ *      Input:  boxad (use null to get a new one)
+ *              boxas
+ *              sides (L_ADJUST_TOP, L_ADJUST_BOT, L_ADJUST_TOP_AND_BOT)
+ *              target (target height if differs by more than thresh)
+ *              thresh (min abs difference in height to cause adjustment)
+ *      Return: boxad, or null on error
+ *
+ *  Notes:
+ *      (1) Conditionally adjusts the height of each box, by moving
+ *          the indicated edges (top and/or bot) if the height differs
+ *          by @thresh or more from @target.
+ *      (2) Use boxad == NULL for a new boxa, and boxad == boxas for in-place.
+ *          Use one of these:
+ *               boxad = boxaAdjustHeightToTarget(NULL, boxas, ...);   // new
+ *               boxaAdjustHeightToTarget(boxas, boxas, ...);  // in-place
+ */
+BOXA *
+boxaAdjustHeightToTarget(BOXA    *boxad,
+                         BOXA    *boxas,
+                        l_int32  sides,
+                        l_int32  target,
+                        l_int32  thresh)
+{
+l_int32  x, y, w, h, n, i, diff;
+BOX     *box;
+
+    PROCNAME("boxaAdjustHeightToTarget");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (boxad && (boxas != boxad))
+        return (BOXA *)ERROR_PTR("not in-place", procName, NULL);
+    if (sides != L_ADJUST_TOP && sides != L_ADJUST_BOT &&
+        sides != L_ADJUST_TOP_AND_BOT)
+        return (BOXA *)ERROR_PTR("invalid sides", procName, NULL);
+    if (target < 1)
+        return (BOXA *)ERROR_PTR("target < 1", procName, NULL);
+
+    if (!boxad)
+        boxad = boxaCopy(boxas, L_COPY);
+    n = boxaGetCount(boxad);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxad, i, L_CLONE);
+        boxGetGeometry(box, &x, &y, &w, &h);
+        if (w == 0 || h == 0) {  /* invalid; do not alter */
+            boxDestroy(&box);
+            continue;
+        }
+        diff = h - target;
+        if (sides == L_ADJUST_TOP) {
+            if (L_ABS(diff) >= thresh)
+                boxSetGeometry(box, x, L_MAX(0, y + diff), w, target);
+        } else if (sides == L_ADJUST_BOT) {
+            if (L_ABS(diff) >= thresh)
+                boxSetGeometry(box, x, y, w, target);
+        } else { /* sides == L_ADJUST_TOP_AND_BOT */
+            if (L_ABS(diff) >= thresh)
+                boxSetGeometry(box, x, L_MAX(0, y + diff/2), w, target);
+        }
+        boxDestroy(&box);
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxEqual()
+ *
+ *      Input:  box1
+ *              box2
+ *              &same (<return> 1 if equal; 0 otherwise)
+ *      Return  0 if OK, 1 on error
+ */
+l_int32
+boxEqual(BOX      *box1,
+         BOX      *box2,
+         l_int32  *psame)
+{
+    PROCNAME("boxEqual");
+
+    if (!psame)
+        return ERROR_INT("&same not defined", procName, 1);
+    *psame = 0;
+    if (!box1 || !box2)
+        return ERROR_INT("box1 and box2 not both defined", procName, 1);
+    if (box1->x == box2->x && box1->y == box2->y &&
+        box1->w == box2->w && box1->h == box2->h)
+        *psame = 1;
+    return 0;
+}
+
+
+/*!
+ *  boxaEqual()
+ *
+ *      Input:  boxa1
+ *              boxa2
+ *              maxdist
+ *              &naindex (<optional return> index array of correspondences
+ *              &same (<return> 1 if equal; 0 otherwise)
+ *      Return  0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The two boxa are the "same" if they contain the same
+ *          boxes and each box is within @maxdist of its counterpart
+ *          in their positions within the boxa.  This allows for
+ *          small rearrangements.  Use 0 for maxdist if the boxa
+ *          must be identical.
+ *      (2) This applies only to geometry and ordering; refcounts
+ *          are not considered.
+ *      (3) @maxdist allows some latitude in the ordering of the boxes.
+ *          For the boxa to be the "same", corresponding boxes must
+ *          be within @maxdist of each other.  Note that for large
+ *          @maxdist, we should use a hash function for efficiency.
+ *      (4) naindex[i] gives the position of the box in boxa2 that
+ *          corresponds to box i in boxa1.  It is only returned if the
+ *          boxa are equal.
+ */
+l_int32
+boxaEqual(BOXA     *boxa1,
+          BOXA     *boxa2,
+          l_int32   maxdist,
+          NUMA    **pnaindex,
+          l_int32  *psame)
+{
+l_int32   i, j, n, jstart, jend, found, samebox;
+l_int32  *countarray;
+BOX      *box1, *box2;
+NUMA     *na;
+
+    PROCNAME("boxaEqual");
+
+    if (pnaindex) *pnaindex = NULL;
+    if (!psame)
+        return ERROR_INT("&same not defined", procName, 1);
+    *psame = 0;
+    if (!boxa1 || !boxa2)
+        return ERROR_INT("boxa1 and boxa2 not both defined", procName, 1);
+    n = boxaGetCount(boxa1);
+    if (n != boxaGetCount(boxa2))
+        return 0;
+
+    countarray = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32));
+    na = numaMakeConstant(0.0, n);
+
+    for (i = 0; i < n; i++) {
+        box1 = boxaGetBox(boxa1, i, L_CLONE);
+        jstart = L_MAX(0, i - maxdist);
+        jend = L_MIN(n-1, i + maxdist);
+        found = FALSE;
+        for (j = jstart; j <= jend; j++) {
+            box2 = boxaGetBox(boxa2, j, L_CLONE);
+            boxEqual(box1, box2, &samebox);
+            if (samebox && countarray[j] == 0) {
+                countarray[j] = 1;
+                numaReplaceNumber(na, i, j);
+                found = TRUE;
+                boxDestroy(&box2);
+                break;
+            }
+            boxDestroy(&box2);
+        }
+        boxDestroy(&box1);
+        if (!found) {
+            numaDestroy(&na);
+            LEPT_FREE(countarray);
+            return 0;
+        }
+    }
+
+    *psame = 1;
+    if (pnaindex)
+        *pnaindex = na;
+    else
+        numaDestroy(&na);
+    LEPT_FREE(countarray);
+    return 0;
+}
+
+
+/*!
+ *  boxSimilar()
+ *
+ *      Input:  box1
+ *              box2
+ *              leftdiff, rightdiff, topdiff, botdiff
+ *              &similar (<return> 1 if similar; 0 otherwise)
+ *      Return  0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The values of leftdiff (etc) are the maximum allowed deviations
+ *          between the locations of the left (etc) sides.  If any side
+ *          pairs differ by more than this amount, the boxes are not similar.
+ */
+l_int32
+boxSimilar(BOX      *box1,
+           BOX      *box2,
+           l_int32   leftdiff,
+           l_int32   rightdiff,
+           l_int32   topdiff,
+           l_int32   botdiff,
+           l_int32  *psimilar)
+{
+l_int32  loc1, loc2;
+
+    PROCNAME("boxSimilar");
+
+    if (!psimilar)
+        return ERROR_INT("&similar not defined", procName, 1);
+    *psimilar = 0;
+    if (!box1 || !box2)
+        return ERROR_INT("box1 and box2 not both defined", procName, 1);
+
+    boxGetSideLocation(box1, L_GET_LEFT, &loc1);
+    boxGetSideLocation(box2, L_GET_LEFT, &loc2);
+    if (L_ABS(loc1 - loc2) > leftdiff)
+        return 0;
+    boxGetSideLocation(box1, L_GET_RIGHT, &loc1);
+    boxGetSideLocation(box2, L_GET_RIGHT, &loc2);
+    if (L_ABS(loc1 - loc2) > rightdiff)
+        return 0;
+    boxGetSideLocation(box1, L_GET_TOP, &loc1);
+    boxGetSideLocation(box2, L_GET_TOP, &loc2);
+    if (L_ABS(loc1 - loc2) > topdiff)
+        return 0;
+    boxGetSideLocation(box1, L_GET_BOT, &loc1);
+    boxGetSideLocation(box2, L_GET_BOT, &loc2);
+    if (L_ABS(loc1 - loc2) > botdiff)
+        return 0;
+
+    *psimilar = 1;
+    return 0;
+}
+
+
+/*!
+ *  boxaSimilar()
+ *
+ *      Input:  boxa1
+ *              boxa2
+ *              leftdiff, rightdiff, topdiff, botdiff
+ *              debug (output details of non-similar boxes)
+ *              &similar (<return> 1 if similar; 0 otherwise)
+ *              &nasim (<optional return> na containing 1 if similar; else 0)
+ *      Return  0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See boxSimilar() for parameter usage.
+ *      (2) Corresponding boxes are taken in order in the two boxa.
+ *      (3) @nasim is an indicator array with a (0/1) for each box pair.
+ *      (4) With @nasim or debug == 1, boxes continue to be tested
+ *          after failure.
+ */
+l_int32
+boxaSimilar(BOXA     *boxa1,
+            BOXA     *boxa2,
+            l_int32   leftdiff,
+            l_int32   rightdiff,
+            l_int32   topdiff,
+            l_int32   botdiff,
+            l_int32   debug,
+            l_int32  *psimilar,
+            NUMA    **pnasim)
+{
+l_int32  i, n1, n2, match, mismatch;
+BOX     *box1, *box2;
+
+    PROCNAME("boxaSimilar");
+
+    if (psimilar) *psimilar = 0;
+    if (pnasim) *pnasim = NULL;
+    if (!boxa1 || !boxa2)
+        return ERROR_INT("boxa1 and boxa2 not both defined", procName, 1);
+    if (!psimilar)
+        return ERROR_INT("&similar not defined", procName, 1);
+    n1 = boxaGetCount(boxa1);
+    n2 = boxaGetCount(boxa2);
+    if (n1 != n2) {
+        L_ERROR("boxa counts differ: %d vs %d\n", procName, n1, n2);
+        return 1;
+    }
+    if (pnasim) *pnasim = numaCreate(n1);
+
+    mismatch = FALSE;
+    for (i = 0; i < n1; i++) {
+        box1 = boxaGetBox(boxa1, i, L_CLONE);
+        box2 = boxaGetBox(boxa2, i, L_CLONE);
+        boxSimilar(box1, box2, leftdiff, rightdiff, topdiff, botdiff,
+                   &match);
+        boxDestroy(&box1);
+        boxDestroy(&box2);
+        if (pnasim)
+            numaAddNumber(*pnasim, match);
+        if (!match) {
+            mismatch = TRUE;
+            if (!debug && pnasim == NULL)
+                return 0;
+            else if (debug)
+                L_INFO("box %d not similar\n", procName, i);
+        }
+    }
+
+    if (!mismatch) *psimilar = 1;
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                      Boxa combine and split                          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  boxaJoin()
+ *
+ *      Input:  boxad  (dest boxa; add to this one)
+ *              boxas  (source boxa; add from this one)
+ *              istart  (starting index in boxas)
+ *              iend  (ending index in boxas; use -1 to cat all)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This appends a clone of each indicated box in boxas to boxad
+ *      (2) istart < 0 is taken to mean 'read from the start' (istart = 0)
+ *      (3) iend < 0 means 'read to the end'
+ *      (4) if boxas == NULL or has no boxes, this is a no-op.
+ */
+l_int32
+boxaJoin(BOXA    *boxad,
+         BOXA    *boxas,
+         l_int32  istart,
+         l_int32  iend)
+{
+l_int32  n, i;
+BOX     *box;
+
+    PROCNAME("boxaJoin");
+
+    if (!boxad)
+        return ERROR_INT("boxad not defined", procName, 1);
+    if (!boxas || ((n = boxaGetCount(boxas)) == 0))
+        return 0;
+
+    if (istart < 0)
+        istart = 0;
+    if (iend < 0 || iend >= n)
+        iend = n - 1;
+    if (istart > iend)
+        return ERROR_INT("istart > iend; nothing to add", procName, 1);
+
+    for (i = istart; i <= iend; i++) {
+        box = boxaGetBox(boxas, i, L_CLONE);
+        boxaAddBox(boxad, box, L_INSERT);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  boxaaJoin()
+ *
+ *      Input:  baad  (dest boxaa; add to this one)
+ *              baas  (source boxaa; add from this one)
+ *              istart  (starting index in baas)
+ *              iend  (ending index in baas; use -1 to cat all)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This appends a clone of each indicated boxa in baas to baad
+ *      (2) istart < 0 is taken to mean 'read from the start' (istart = 0)
+ *      (3) iend < 0 means 'read to the end'
+ *      (4) if baas == NULL, this is a no-op.
+ */
+l_int32
+boxaaJoin(BOXAA   *baad,
+          BOXAA   *baas,
+          l_int32  istart,
+          l_int32  iend)
+{
+l_int32  n, i;
+BOXA    *boxa;
+
+    PROCNAME("boxaaJoin");
+
+    if (!baad)
+        return ERROR_INT("baad not defined", procName, 1);
+    if (!baas)
+        return 0;
+
+    if (istart < 0)
+        istart = 0;
+    n = boxaaGetCount(baas);
+    if (iend < 0 || iend >= n)
+        iend = n - 1;
+    if (istart > iend)
+        return ERROR_INT("istart > iend; nothing to add", procName, 1);
+
+    for (i = istart; i <= iend; i++) {
+        boxa = boxaaGetBoxa(baas, i, L_CLONE);
+        boxaaAddBoxa(baad, boxa, L_INSERT);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  boxaSplitEvenOdd()
+ *
+ *      Input:  boxa
+ *              fillflag (1 to put invalid boxes in place; 0 to omit)
+ *              &boxae, &boxao (<return> save even and odd boxes in their
+ *                 separate boxa, setting the other type to invalid boxes.)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If @fillflag == 1, boxae has copies of the even boxes
+ *          in their original location, and nvalid boxes are placed
+ *          in the odd array locations.  And v.v.
+ *      (2) If @fillflag == 0, boxae has only copies of the even boxes.
+ */
+l_int32
+boxaSplitEvenOdd(BOXA    *boxa,
+                 l_int32  fillflag,
+                 BOXA   **pboxae,
+                 BOXA   **pboxao)
+{
+l_int32  i, n;
+BOX     *box, *boxt;
+
+    PROCNAME("boxaSplitEvenOdd");
+
+    if (!pboxae || !pboxao)
+        return ERROR_INT("&boxae and &boxao not defined", procName, 1);
+    *pboxae = *pboxao = NULL;
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    n = boxaGetCount(boxa);
+    *pboxae = boxaCreate(n);
+    *pboxao = boxaCreate(n);
+    if (fillflag == 0) {
+            /* don't fill with invalid boxes; end up with half-size boxa */
+        for (i = 0; i < n; i++) {
+            box = boxaGetBox(boxa, i, L_COPY);
+            if ((i & 1) == 0)
+                boxaAddBox(*pboxae, box, L_INSERT);
+            else
+                boxaAddBox(*pboxao, box, L_INSERT);
+        }
+    } else {
+        for (i = 0; i < n; i++) {
+            box = boxaGetBox(boxa, i, L_COPY);
+            boxt = boxCreate(0, 0, 0, 0);  /* empty placeholder */
+            if ((i & 1) == 0) {
+                boxaAddBox(*pboxae, box, L_INSERT);
+                boxaAddBox(*pboxao, boxt, L_INSERT);
+            } else {
+                boxaAddBox(*pboxae, boxt, L_INSERT);
+                boxaAddBox(*pboxao, box, L_INSERT);
+            }
+        }
+    }
+    return 0;
+}
+
+
+/*!
+ *  boxaMergeEvenOdd()
+ *
+ *      Input:  boxae (boxes to go in even positions in merged boxa)
+ *              boxao (boxes to go in odd positions in merged boxa)
+ *              fillflag (1 if there are invalid boxes in placeholders)
+ *      Return: boxad (merged), or null on error
+ *
+ *  Notes:
+ *      (1) This is essentially the inverse of boxaSplitEvenOdd().
+ *          Typically, boxae and boxao were generated by boxaSplitEvenOdd(),
+ *          and the value of @fillflag needs to be the same in both calls.
+ *      (2) If @fillflag == 1, both boxae and boxao are of the same size;
+ *          otherwise boxae may have one more box than boxao.
+ */
+BOXA *
+boxaMergeEvenOdd(BOXA    *boxae,
+                 BOXA    *boxao,
+                 l_int32  fillflag)
+{
+l_int32  i, n, ne, no;
+BOX     *box;
+BOXA    *boxad;
+
+    PROCNAME("boxaMergeEvenOdd");
+
+    if (!boxae || !boxao)
+        return (BOXA *)ERROR_PTR("boxae and boxao not defined", procName, NULL);
+    ne = boxaGetCount(boxae);
+    no = boxaGetCount(boxao);
+    if (ne < no || ne > no + 1)
+        return (BOXA *)ERROR_PTR("boxa sizes invalid", procName, NULL);
+
+    boxad = boxaCreate(ne);
+    if (fillflag == 0) {  /* both are approx. half-sized; all valid boxes */
+        n = ne + no;
+        for (i = 0; i < n; i++) {
+            if ((i & 1) == 0)
+                box = boxaGetBox(boxae, i / 2, L_COPY);
+            else
+                box = boxaGetBox(boxao, i / 2, L_COPY);
+            boxaAddBox(boxad, box, L_INSERT);
+        }
+    } else {  /* both are full size and have invalid placeholders */
+        for (i = 0; i < ne; i++) {
+            if ((i & 1) == 0)
+                box = boxaGetBox(boxae, i, L_COPY);
+            else
+                box = boxaGetBox(boxao, i, L_COPY);
+            boxaAddBox(boxad, box, L_INSERT);
+        }
+    }
+    return boxad;
+}
+
diff --git a/src/boxfunc2.c b/src/boxfunc2.c
new file mode 100644 (file)
index 0000000..4ca0fa6
--- /dev/null
@@ -0,0 +1,1609 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   boxfunc2.c
+ *
+ *      Boxa/Box transform (shift, scale) and orthogonal rotation
+ *           BOXA            *boxaTransform()
+ *           BOX             *boxTransform()
+ *           BOXA            *boxaTransformOrdered()
+ *           BOX             *boxTransformOrdered()
+ *           BOXA            *boxaRotateOrth()
+ *           BOX             *boxRotateOrth()
+ *
+ *      Boxa sort
+ *           BOXA            *boxaSort()
+ *           BOXA            *boxaBinSort()
+ *           BOXA            *boxaSortByIndex()
+ *           BOXAA           *boxaSort2d()
+ *           BOXAA           *boxaSort2dByIndex()
+ *
+ *      Boxa statistics
+ *           BOX             *boxaGetRankSize()
+ *           BOX             *boxaGetMedian()
+ *           l_int32          boxaGetAverageSize()
+ *
+ *      Boxa array extraction
+ *           l_int32          boxaExtractAsNuma()
+ *           l_int32          boxaExtractAsPta()
+ *
+ *      Other Boxaa functions
+ *           l_int32          boxaaGetExtent()
+ *           BOXA            *boxaaFlattenToBoxa()
+ *           BOXA            *boxaaFlattenAligned()
+ *           BOXAA           *boxaEncapsulateAligned()
+ *           l_int32          boxaaAlignBox()
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+    /* For more than this number of c.c. in a binarized image of
+     * semi-perimeter (w + h) about 5000 or less, the O(n) binsort
+     * is faster than the O(nlogn) shellsort.  */
+static const l_int32   MIN_COMPS_FOR_BIN_SORT = 200;
+
+
+/*---------------------------------------------------------------------*
+ *      Boxa/Box transform (shift, scale) and orthogonal rotation      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaTransform()
+ *
+ *      Input:  boxa
+ *              shiftx, shifty
+ *              scalex, scaley
+ *      Return: boxad, or null on error
+ *
+ *  Notes:
+ *      (1) This is a very simple function that first shifts, then scales.
+ */
+BOXA *
+boxaTransform(BOXA      *boxas,
+              l_int32    shiftx,
+              l_int32    shifty,
+              l_float32  scalex,
+              l_float32  scaley)
+{
+l_int32  i, n;
+BOX     *boxs, *boxd;
+BOXA    *boxad;
+
+    PROCNAME("boxaTransform");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    n = boxaGetCount(boxas);
+    if ((boxad = boxaCreate(n)) == NULL)
+        return (BOXA *)ERROR_PTR("boxad not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        if ((boxs = boxaGetBox(boxas, i, L_CLONE)) == NULL)
+            return (BOXA *)ERROR_PTR("boxs not found", procName, NULL);
+        boxd = boxTransform(boxs, shiftx, shifty, scalex, scaley);
+        boxDestroy(&boxs);
+        boxaAddBox(boxad, boxd, L_INSERT);
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxTransform()
+ *
+ *      Input:  box
+ *              shiftx, shifty
+ *              scalex, scaley
+ *      Return: boxd, or null on error
+ *
+ *  Notes:
+ *      (1) This is a very simple function that first shifts, then scales.
+ *      (2) If the box is invalid, a new invalid box is returned.
+ */
+BOX *
+boxTransform(BOX       *box,
+             l_int32    shiftx,
+             l_int32    shifty,
+             l_float32  scalex,
+             l_float32  scaley)
+{
+    PROCNAME("boxTransform");
+
+    if (!box)
+        return (BOX *)ERROR_PTR("box not defined", procName, NULL);
+    if (box->w <= 0 || box->h <= 0)
+        return boxCreate(0, 0, 0, 0);
+    else
+        return boxCreate((l_int32)(scalex * (box->x + shiftx) + 0.5),
+                         (l_int32)(scaley * (box->y + shifty) + 0.5),
+                         (l_int32)(L_MAX(1.0, scalex * box->w + 0.5)),
+                         (l_int32)(L_MAX(1.0, scaley * box->h + 0.5)));
+}
+
+
+/*!
+ *  boxaTransformOrdered()
+ *
+ *      Input:  boxa
+ *              shiftx, shifty
+ *              scalex, scaley
+ *              xcen, ycen (center of rotation)
+ *              angle (in radians; clockwise is positive)
+ *              order (one of 6 combinations: L_TR_SC_RO, ...)
+ *      Return: boxd, or null on error
+ *
+ *  Notes:
+ *      (1) This allows a sequence of linear transforms on each box.
+ *          the transforms are from the affine set, composed of
+ *          shift, scaling and rotation, and the order of the
+ *          transforms is specified.
+ *      (2) Although these operations appear to be on an infinite
+ *          2D plane, in practice the region of interest is clipped
+ *          to a finite image.  The center of rotation is usually taken
+ *          with respect to the image (either the UL corner or the
+ *          center).  A translation can have two very different effects:
+ *            (a) Moves the boxes across the fixed image region.
+ *            (b) Moves the image origin, causing a change in the image
+ *                region and an opposite effective translation of the boxes.
+ *          This function should only be used for (a), where the image
+ *          region is fixed on translation.  If the image region is
+ *          changed by the translation, use instead the functions
+ *          in affinecompose.c, where the image region and rotation
+ *          center can be computed from the actual clipping due to
+ *          translation of the image origin.
+ *      (3) See boxTransformOrdered() for usage and implementation details.
+ */
+BOXA *
+boxaTransformOrdered(BOXA      *boxas,
+                     l_int32    shiftx,
+                     l_int32    shifty,
+                     l_float32  scalex,
+                     l_float32  scaley,
+                     l_int32    xcen,
+                     l_int32    ycen,
+                     l_float32  angle,
+                     l_int32    order)
+{
+l_int32  i, n;
+BOX     *boxs, *boxd;
+BOXA    *boxad;
+
+    PROCNAME("boxaTransformOrdered");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    n = boxaGetCount(boxas);
+    if ((boxad = boxaCreate(n)) == NULL)
+        return (BOXA *)ERROR_PTR("boxad not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        if ((boxs = boxaGetBox(boxas, i, L_CLONE)) == NULL)
+            return (BOXA *)ERROR_PTR("boxs not found", procName, NULL);
+        boxd = boxTransformOrdered(boxs, shiftx, shifty, scalex, scaley,
+                                   xcen, ycen, angle, order);
+        boxDestroy(&boxs);
+        boxaAddBox(boxad, boxd, L_INSERT);
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxTransformOrdered()
+ *
+ *      Input:  boxs
+ *              shiftx, shifty
+ *              scalex, scaley
+ *              xcen, ycen (center of rotation)
+ *              angle (in radians; clockwise is positive)
+ *              order (one of 6 combinations: L_TR_SC_RO, ...)
+ *      Return: boxd, or null on error
+ *
+ *  Notes:
+ *      (1) This allows a sequence of linear transforms, composed of
+ *          shift, scaling and rotation, where the order of the
+ *          transforms is specified.
+ *      (2) The rotation is taken about a point specified by (xcen, ycen).
+ *          Let the components of the vector from the center of rotation
+ *          to the box center be (xdif, ydif):
+ *            xdif = (bx + 0.5 * bw) - xcen
+ *            ydif = (by + 0.5 * bh) - ycen
+ *          Then the box center after rotation has new components:
+ *            bxcen = xcen + xdif * cosa + ydif * sina
+ *            bycen = ycen + ydif * cosa - xdif * sina
+ *          where cosa and sina are the cos and sin of the angle,
+ *          and the enclosing box for the rotated box has size:
+ *            rw = |bw * cosa| + |bh * sina|
+ *            rh = |bh * cosa| + |bw * sina|
+ *          where bw and bh are the unrotated width and height.
+ *          Then the box UL corner (rx, ry) is
+ *            rx = bxcen - 0.5 * rw
+ *            ry = bycen - 0.5 * rh
+ *      (3) The center of rotation specified by args @xcen and @ycen
+ *          is the point BEFORE any translation or scaling.  If the
+ *          rotation is not the first operation, this function finds
+ *          the actual center at the time of rotation.  It does this
+ *          by making the following assumptions:
+ *             (1) Any scaling is with respect to the UL corner, so
+ *                 that the center location scales accordingly.
+ *             (2) A translation does not affect the center of
+ *                 the image; it just moves the boxes.
+ *          We always use assumption (1).  However, assumption (2)
+ *          will be incorrect if the apparent translation is due
+ *          to a clipping operation that, in effect, moves the
+ *          origin of the image.  In that case, you should NOT use
+ *          these simple functions.  Instead, use the functions
+ *          in affinecompose.c, where the rotation center can be
+ *          computed from the actual clipping due to translation
+ *          of the image origin.
+ */
+BOX *
+boxTransformOrdered(BOX       *boxs,
+                    l_int32    shiftx,
+                    l_int32    shifty,
+                    l_float32  scalex,
+                    l_float32  scaley,
+                    l_int32    xcen,
+                    l_int32    ycen,
+                    l_float32  angle,
+                    l_int32    order)
+{
+l_int32    bx, by, bw, bh, tx, ty, tw, th;
+l_int32    xcent, ycent;  /* transformed center of rotation due to scaling */
+l_float32  sina, cosa, xdif, ydif, rx, ry, rw, rh;
+BOX       *boxd;
+
+    PROCNAME("boxTransformOrdered");
+
+    if (!boxs)
+        return (BOX *)ERROR_PTR("boxs not defined", procName, NULL);
+    if (order != L_TR_SC_RO && order != L_SC_RO_TR && order != L_RO_TR_SC &&
+        order != L_TR_RO_SC && order != L_RO_SC_TR && order != L_SC_TR_RO)
+        return (BOX *)ERROR_PTR("order invalid", procName, NULL);
+
+    boxGetGeometry(boxs, &bx, &by, &bw, &bh);
+    if (bw <= 0 || bh <= 0)  /* invalid */
+        return boxCreate(0, 0, 0, 0);
+    if (angle != 0.0) {
+        sina = sin(angle);
+        cosa = cos(angle);
+    }
+
+    if (order == L_TR_SC_RO) {
+        tx = (l_int32)(scalex * (bx + shiftx) + 0.5);
+        ty = (l_int32)(scaley * (by + shifty) + 0.5);
+        tw = (l_int32)(L_MAX(1.0, scalex * bw + 0.5));
+        th = (l_int32)(L_MAX(1.0, scaley * bh + 0.5));
+        xcent = (l_int32)(scalex * xcen + 0.5);
+        ycent = (l_int32)(scaley * ycen + 0.5);
+        if (angle == 0.0) {
+            boxd = boxCreate(tx, ty, tw, th);
+        } else {
+            xdif = tx + 0.5 * tw - xcent;
+            ydif = ty + 0.5 * th - ycent;
+            rw = L_ABS(tw * cosa) + L_ABS(th * sina);
+            rh = L_ABS(th * cosa) + L_ABS(tw * sina);
+            rx = xcent + xdif * cosa - ydif * sina - 0.5 * rw;
+            ry = ycent + ydif * cosa + xdif * sina - 0.5 * rh;
+            boxd = boxCreate((l_int32)rx, (l_int32)ry, (l_int32)rw,
+                             (l_int32)rh);
+        }
+    } else if (order == L_SC_TR_RO) {
+        tx = (l_int32)(scalex * bx + shiftx + 0.5);
+        ty = (l_int32)(scaley * by + shifty + 0.5);
+        tw = (l_int32)(L_MAX(1.0, scalex * bw + 0.5));
+        th = (l_int32)(L_MAX(1.0, scaley * bh + 0.5));
+        xcent = (l_int32)(scalex * xcen + 0.5);
+        ycent = (l_int32)(scaley * ycen + 0.5);
+        if (angle == 0.0) {
+            boxd = boxCreate(tx, ty, tw, th);
+        } else {
+            xdif = tx + 0.5 * tw - xcent;
+            ydif = ty + 0.5 * th - ycent;
+            rw = L_ABS(tw * cosa) + L_ABS(th * sina);
+            rh = L_ABS(th * cosa) + L_ABS(tw * sina);
+            rx = xcent + xdif * cosa - ydif * sina - 0.5 * rw;
+            ry = ycent + ydif * cosa + xdif * sina - 0.5 * rh;
+            boxd = boxCreate((l_int32)rx, (l_int32)ry, (l_int32)rw,
+                             (l_int32)rh);
+        }
+    } else if (order == L_RO_TR_SC) {
+        if (angle == 0.0) {
+            rx = bx;
+            ry = by;
+            rw = bw;
+            rh = bh;
+        } else {
+            xdif = bx + 0.5 * bw - xcen;
+            ydif = by + 0.5 * bh - ycen;
+            rw = L_ABS(bw * cosa) + L_ABS(bh * sina);
+            rh = L_ABS(bh * cosa) + L_ABS(bw * sina);
+            rx = xcen + xdif * cosa - ydif * sina - 0.5 * rw;
+            ry = ycen + ydif * cosa + xdif * sina - 0.5 * rh;
+        }
+        tx = (l_int32)(scalex * (rx + shiftx) + 0.5);
+        ty = (l_int32)(scaley * (ry + shifty) + 0.5);
+        tw = (l_int32)(L_MAX(1.0, scalex * rw + 0.5));
+        th = (l_int32)(L_MAX(1.0, scaley * rh + 0.5));
+        boxd = boxCreate(tx, ty, tw, th);
+    } else if (order == L_RO_SC_TR) {
+        if (angle == 0.0) {
+            rx = bx;
+            ry = by;
+            rw = bw;
+            rh = bh;
+        } else {
+            xdif = bx + 0.5 * bw - xcen;
+            ydif = by + 0.5 * bh - ycen;
+            rw = L_ABS(bw * cosa) + L_ABS(bh * sina);
+            rh = L_ABS(bh * cosa) + L_ABS(bw * sina);
+            rx = xcen + xdif * cosa - ydif * sina - 0.5 * rw;
+            ry = ycen + ydif * cosa + xdif * sina - 0.5 * rh;
+        }
+        tx = (l_int32)(scalex * rx + shiftx + 0.5);
+        ty = (l_int32)(scaley * ry + shifty + 0.5);
+        tw = (l_int32)(L_MAX(1.0, scalex * rw + 0.5));
+        th = (l_int32)(L_MAX(1.0, scaley * rh + 0.5));
+        boxd = boxCreate(tx, ty, tw, th);
+    } else if (order == L_TR_RO_SC) {
+        tx = bx + shiftx;
+        ty = by + shifty;
+        if (angle == 0.0) {
+            rx = tx;
+            ry = ty;
+            rw = bw;
+            rh = bh;
+        } else {
+            xdif = tx + 0.5 * bw - xcen;
+            ydif = ty + 0.5 * bh - ycen;
+            rw = L_ABS(bw * cosa) + L_ABS(bh * sina);
+            rh = L_ABS(bh * cosa) + L_ABS(bw * sina);
+            rx = xcen + xdif * cosa - ydif * sina - 0.5 * rw;
+            ry = ycen + ydif * cosa + xdif * sina - 0.5 * rh;
+        }
+        tx = (l_int32)(scalex * rx + 0.5);
+        ty = (l_int32)(scaley * ry + 0.5);
+        tw = (l_int32)(L_MAX(1.0, scalex * rw + 0.5));
+        th = (l_int32)(L_MAX(1.0, scaley * rh + 0.5));
+        boxd = boxCreate(tx, ty, tw, th);
+    } else {  /* order == L_SC_RO_TR) */
+        tx = (l_int32)(scalex * bx + 0.5);
+        ty = (l_int32)(scaley * by + 0.5);
+        tw = (l_int32)(L_MAX(1.0, scalex * bw + 0.5));
+        th = (l_int32)(L_MAX(1.0, scaley * bh + 0.5));
+        xcent = (l_int32)(scalex * xcen + 0.5);
+        ycent = (l_int32)(scaley * ycen + 0.5);
+        if (angle == 0.0) {
+            rx = tx;
+            ry = ty;
+            rw = tw;
+            rh = th;
+        } else {
+            xdif = tx + 0.5 * tw - xcent;
+            ydif = ty + 0.5 * th - ycent;
+            rw = L_ABS(tw * cosa) + L_ABS(th * sina);
+            rh = L_ABS(th * cosa) + L_ABS(tw * sina);
+            rx = xcent + xdif * cosa - ydif * sina - 0.5 * rw;
+            ry = ycent + ydif * cosa + xdif * sina - 0.5 * rh;
+        }
+        tx = (l_int32)(rx + shiftx + 0.5);
+        ty = (l_int32)(ry + shifty + 0.5);
+        tw = (l_int32)(rw + 0.5);
+        th = (l_int32)(rh + 0.5);
+        boxd = boxCreate(tx, ty, tw, th);
+    }
+
+    return boxd;
+}
+
+
+/*!
+ *  boxaRotateOrth()
+ *
+ *      Input:  boxa
+ *              w, h (of image in which the boxa is embedded)
+ *              rotation (0 = noop, 1 = 90 deg, 2 = 180 deg, 3 = 270 deg;
+ *                        all rotations are clockwise)
+ *      Return: boxad, or null on error
+ *
+ *  Notes:
+ *      (1) See boxRotateOrth() for details.
+ */
+BOXA *
+boxaRotateOrth(BOXA    *boxas,
+               l_int32  w,
+               l_int32  h,
+               l_int32  rotation)
+{
+l_int32  i, n;
+BOX     *boxs, *boxd;
+BOXA    *boxad;
+
+    PROCNAME("boxaRotateOrth");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (rotation < 0 || rotation > 3)
+        return (BOXA *)ERROR_PTR("rotation not in {0,1,2,3}", procName, NULL);
+    if (rotation == 0)
+        return boxaCopy(boxas, L_COPY);
+
+    n = boxaGetCount(boxas);
+    if ((boxad = boxaCreate(n)) == NULL)
+        return (BOXA *)ERROR_PTR("boxad not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        if ((boxs = boxaGetBox(boxas, i, L_CLONE)) == NULL)
+            return (BOXA *)ERROR_PTR("boxs not found", procName, NULL);
+        boxd = boxRotateOrth(boxs, w, h, rotation);
+        boxDestroy(&boxs);
+        boxaAddBox(boxad, boxd, L_INSERT);
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxRotateOrth()
+ *
+ *      Input:  box
+ *              w, h (of image in which the box is embedded)
+ *              rotation (0 = noop, 1 = 90 deg, 2 = 180 deg, 3 = 270 deg;
+ *                        all rotations are clockwise)
+ *      Return: boxd, or null on error
+ *
+ *  Notes:
+ *      (1) Rotate the image with the embedded box by the specified amount.
+ *      (2) After rotation, the rotated box is always measured with
+ *          respect to the UL corner of the image.
+ */
+BOX *
+boxRotateOrth(BOX     *box,
+              l_int32  w,
+              l_int32  h,
+              l_int32  rotation)
+{
+l_int32  bx, by, bw, bh, xdist, ydist;
+
+    PROCNAME("boxRotateOrth");
+
+    if (!box)
+        return (BOX *)ERROR_PTR("box not defined", procName, NULL);
+    if (rotation < 0 || rotation > 3)
+        return (BOX *)ERROR_PTR("rotation not in {0,1,2,3}", procName, NULL);
+    if (rotation == 0)
+        return boxCopy(box);
+
+    boxGetGeometry(box, &bx, &by, &bw, &bh);
+    if (bw <= 0 || bh <= 0)  /* invalid */
+        return boxCreate(0, 0, 0, 0);
+    ydist = h - by - bh;  /* below box */
+    xdist = w - bx - bw;  /* to right of box */
+    if (rotation == 1)   /* 90 deg cw */
+        return boxCreate(ydist, bx, bh, bw);
+    else if (rotation == 2)  /* 180 deg cw */
+        return boxCreate(xdist, ydist, bw, bh);
+    else  /*  rotation == 3, 270 deg cw */
+        return boxCreate(by, xdist, bh, bw);
+}
+
+
+/*---------------------------------------------------------------------*
+ *                              Boxa sort                              *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaSort()
+ *
+ *      Input:  boxa
+ *              sorttype (L_SORT_BY_X, L_SORT_BY_Y,
+ *                        L_SORT_BY_RIGHT, L_SORT_BY_BOT,
+ *                        L_SORT_BY_WIDTH, L_SORT_BY_HEIGHT,
+ *                        L_SORT_BY_MIN_DIMENSION, L_SORT_BY_MAX_DIMENSION,
+ *                        L_SORT_BY_PERIMETER, L_SORT_BY_AREA,
+ *                        L_SORT_BY_ASPECT_RATIO)
+ *              sortorder  (L_SORT_INCREASING, L_SORT_DECREASING)
+ *              &naindex (<optional return> index of sorted order into
+ *                        original array)
+ *      Return: boxad (sorted version of boxas), or null on error
+ */
+BOXA *
+boxaSort(BOXA    *boxas,
+         l_int32  sorttype,
+         l_int32  sortorder,
+         NUMA   **pnaindex)
+{
+l_int32    i, n, x, y, w, h, size;
+BOXA      *boxad;
+NUMA      *na, *naindex;
+
+    PROCNAME("boxaSort");
+
+    if (pnaindex) *pnaindex = NULL;
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y &&
+        sorttype != L_SORT_BY_RIGHT && sorttype != L_SORT_BY_BOT &&
+        sorttype != L_SORT_BY_WIDTH && sorttype != L_SORT_BY_HEIGHT &&
+        sorttype != L_SORT_BY_MIN_DIMENSION &&
+        sorttype != L_SORT_BY_MAX_DIMENSION &&
+        sorttype != L_SORT_BY_PERIMETER &&
+        sorttype != L_SORT_BY_AREA &&
+        sorttype != L_SORT_BY_ASPECT_RATIO)
+        return (BOXA *)ERROR_PTR("invalid sort type", procName, NULL);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return (BOXA *)ERROR_PTR("invalid sort order", procName, NULL);
+
+        /* Use O(n) binsort if possible */
+    n = boxaGetCount(boxas);
+    if (n > MIN_COMPS_FOR_BIN_SORT &&
+        ((sorttype == L_SORT_BY_X) || (sorttype == L_SORT_BY_Y) ||
+         (sorttype == L_SORT_BY_WIDTH) || (sorttype == L_SORT_BY_HEIGHT) ||
+         (sorttype == L_SORT_BY_PERIMETER)))
+        return boxaBinSort(boxas, sorttype, sortorder, pnaindex);
+
+        /* Build up numa of specific data */
+    if ((na = numaCreate(n)) == NULL)
+        return (BOXA *)ERROR_PTR("na not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxas, i, &x, &y, &w, &h);
+        switch (sorttype)
+        {
+        case L_SORT_BY_X:
+            numaAddNumber(na, x);
+            break;
+        case L_SORT_BY_Y:
+            numaAddNumber(na, y);
+            break;
+        case L_SORT_BY_RIGHT:
+            numaAddNumber(na, x + w - 1);
+            break;
+        case L_SORT_BY_BOT:
+            numaAddNumber(na, y + h - 1);
+            break;
+        case L_SORT_BY_WIDTH:
+            numaAddNumber(na, w);
+            break;
+        case L_SORT_BY_HEIGHT:
+            numaAddNumber(na, h);
+            break;
+        case L_SORT_BY_MIN_DIMENSION:
+            size = L_MIN(w, h);
+            numaAddNumber(na, size);
+            break;
+        case L_SORT_BY_MAX_DIMENSION:
+            size = L_MAX(w, h);
+            numaAddNumber(na, size);
+            break;
+        case L_SORT_BY_PERIMETER:
+            size = w + h;
+            numaAddNumber(na, size);
+            break;
+        case L_SORT_BY_AREA:
+            size = w * h;
+            numaAddNumber(na, size);
+            break;
+        case L_SORT_BY_ASPECT_RATIO:
+            numaAddNumber(na, (l_float32)w / (l_float32)h);
+            break;
+        default:
+            L_WARNING("invalid sort type\n", procName);
+        }
+    }
+
+        /* Get the sort index for data array */
+    if ((naindex = numaGetSortIndex(na, sortorder)) == NULL)
+        return (BOXA *)ERROR_PTR("naindex not made", procName, NULL);
+
+        /* Build up sorted boxa using sort index */
+    boxad = boxaSortByIndex(boxas, naindex);
+
+    if (pnaindex)
+        *pnaindex = naindex;
+    else
+        numaDestroy(&naindex);
+    numaDestroy(&na);
+    return boxad;
+}
+
+
+/*!
+ *  boxaBinSort()
+ *
+ *      Input:  boxa
+ *              sorttype (L_SORT_BY_X, L_SORT_BY_Y, L_SORT_BY_WIDTH,
+ *                        L_SORT_BY_HEIGHT, L_SORT_BY_PERIMETER)
+ *              sortorder  (L_SORT_INCREASING, L_SORT_DECREASING)
+ *              &naindex (<optional return> index of sorted order into
+ *                        original array)
+ *      Return: boxad (sorted version of boxas), or null on error
+ *
+ *  Notes:
+ *      (1) For a large number of boxes (say, greater than 1000), this
+ *          O(n) binsort is much faster than the O(nlogn) shellsort.
+ *          For 5000 components, this is over 20x faster than boxaSort().
+ *      (2) Consequently, boxaSort() calls this function if it will
+ *          likely go much faster.
+ */
+BOXA *
+boxaBinSort(BOXA    *boxas,
+            l_int32  sorttype,
+            l_int32  sortorder,
+            NUMA   **pnaindex)
+{
+l_int32  i, n, x, y, w, h;
+BOXA    *boxad;
+NUMA    *na, *naindex;
+
+    PROCNAME("boxaBinSort");
+
+    if (pnaindex) *pnaindex = NULL;
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y &&
+        sorttype != L_SORT_BY_WIDTH && sorttype != L_SORT_BY_HEIGHT &&
+        sorttype != L_SORT_BY_PERIMETER)
+        return (BOXA *)ERROR_PTR("invalid sort type", procName, NULL);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return (BOXA *)ERROR_PTR("invalid sort order", procName, NULL);
+
+        /* Generate Numa of appropriate box dimensions */
+    n = boxaGetCount(boxas);
+    if ((na = numaCreate(n)) == NULL)
+        return (BOXA *)ERROR_PTR("na not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxas, i, &x, &y, &w, &h);
+        switch (sorttype)
+        {
+        case L_SORT_BY_X:
+            numaAddNumber(na, x);
+            break;
+        case L_SORT_BY_Y:
+            numaAddNumber(na, y);
+            break;
+        case L_SORT_BY_WIDTH:
+            numaAddNumber(na, w);
+            break;
+        case L_SORT_BY_HEIGHT:
+            numaAddNumber(na, h);
+            break;
+        case L_SORT_BY_PERIMETER:
+            numaAddNumber(na, w + h);
+            break;
+        default:
+            L_WARNING("invalid sort type\n", procName);
+        }
+    }
+
+        /* Get the sort index for data array */
+    if ((naindex = numaGetBinSortIndex(na, sortorder)) == NULL)
+        return (BOXA *)ERROR_PTR("naindex not made", procName, NULL);
+
+        /* Build up sorted boxa using the sort index */
+    boxad = boxaSortByIndex(boxas, naindex);
+
+    if (pnaindex)
+        *pnaindex = naindex;
+    else
+        numaDestroy(&naindex);
+    numaDestroy(&na);
+    return boxad;
+}
+
+
+/*!
+ *  boxaSortByIndex()
+ *
+ *      Input:  boxas
+ *              naindex (na that maps from the new boxa to the input boxa)
+ *      Return: boxad (sorted), or null on error
+ */
+BOXA *
+boxaSortByIndex(BOXA  *boxas,
+                NUMA  *naindex)
+{
+l_int32  i, n, index;
+BOX     *box;
+BOXA    *boxad;
+
+    PROCNAME("boxaSortByIndex");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (!naindex)
+        return (BOXA *)ERROR_PTR("naindex not defined", procName, NULL);
+
+    n = boxaGetCount(boxas);
+    boxad = boxaCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(naindex, i, &index);
+        box = boxaGetBox(boxas, index, L_COPY);
+        boxaAddBox(boxad, box, L_INSERT);
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxaSort2d()
+ *
+ *      Input:  boxas
+ *              &naa (<optional return> numaa with sorted indices
+ *                    whose values are the indices of the input array)
+ *              delta1 (min overlap that permits aggregation of a box
+ *                      onto a boxa of horizontally-aligned boxes; pass 1)
+ *              delta2 (min overlap that permits aggregation of a box
+ *                      onto a boxa of horizontally-aligned boxes; pass 2)
+ *              minh1 (components less than this height either join an
+ *                     existing boxa or are set aside for pass 2)
+ *      Return: baa (2d sorted version of boxa), or null on error
+ *
+ *  Notes:
+ *      (1) The final result is a sort where the 'fast scan' direction is
+ *          left to right, and the 'slow scan' direction is from top
+ *          to bottom.  Each boxa in the baa represents a sorted set
+ *          of boxes from left to right.
+ *      (2) Three passes are used to aggregate the boxas, which can correspond
+ *          to characters or words in a line of text.  In pass 1, only
+ *          taller components, which correspond to xheight or larger,
+ *          are permitted to start a new boxa.  In pass 2, the remaining
+ *          vertically-challenged components are allowed to join an
+ *          existing boxa or start a new one.  In pass 3, boxa whose extent
+ *          is overlapping are joined.  After that, the boxes in each
+ *          boxa are sorted horizontally, and finally the boxa are
+ *          sorted vertically.
+ *      (3) If delta1 < 0, the first pass allows aggregation when
+ *          boxes in the same boxa do not overlap vertically.
+ *          The distance by which they can miss and still be aggregated
+ *          is the absolute value |delta1|.   Similar for delta2 on
+ *          the second pass.
+ *      (4) On the first pass, any component of height less than minh1
+ *          cannot start a new boxa; it's put aside for later insertion.
+ *      (5) On the second pass, any small component that doesn't align
+ *          with an existing boxa can start a new one.
+ *      (6) This can be used to identify lines of text from
+ *          character or word bounding boxes.
+ */
+BOXAA *
+boxaSort2d(BOXA    *boxas,
+           NUMAA  **pnaad,
+           l_int32  delta1,
+           l_int32  delta2,
+           l_int32  minh1)
+{
+l_int32  i, index, h, nt, ne, n, m, ival;
+BOX     *box;
+BOXA    *boxa, *boxae, *boxan, *boxa1, *boxa2, *boxa3, *boxav, *boxavs;
+BOXAA   *baa, *baa1, *baad;
+NUMA    *naindex, *nae, *nan, *nah, *nav, *na1, *na2, *nad, *namap;
+NUMAA   *naa, *naa1, *naad;
+
+    PROCNAME("boxaSort2d");
+
+    if (pnaad) *pnaad = NULL;
+    if (!boxas)
+        return (BOXAA *)ERROR_PTR("boxas not defined", procName, NULL);
+
+        /* Sort from left to right */
+    if ((boxa = boxaSort(boxas, L_SORT_BY_X, L_SORT_INCREASING, &naindex))
+                    == NULL)
+        return (BOXAA *)ERROR_PTR("boxa not made", procName, NULL);
+
+        /* First pass: assign taller boxes to boxa by row */
+    nt = boxaGetCount(boxa);
+    baa = boxaaCreate(0);
+    naa = numaaCreate(0);
+    boxae = boxaCreate(0);  /* save small height boxes here */
+    nae = numaCreate(0);  /* keep track of small height boxes */
+    for (i = 0; i < nt; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        boxGetGeometry(box, NULL, NULL, NULL, &h);
+        if (h < minh1) {  /* save for 2nd pass */
+            boxaAddBox(boxae, box, L_INSERT);
+            numaAddNumber(nae, i);
+        } else {
+            n = boxaaGetCount(baa);
+            boxaaAlignBox(baa, box, delta1, &index);
+            if (index < n) {  /* append to an existing boxa */
+                boxaaAddBox(baa, index, box, L_INSERT);
+            } else {  /* doesn't align, need new boxa */
+                boxan = boxaCreate(0);
+                boxaAddBox(boxan, box, L_INSERT);
+                boxaaAddBoxa(baa, boxan, L_INSERT);
+                nan = numaCreate(0);
+                numaaAddNuma(naa, nan, L_INSERT);
+            }
+            numaGetIValue(naindex, i, &ival);
+            numaaAddNumber(naa, index, ival);
+        }
+    }
+    boxaDestroy(&boxa);
+    numaDestroy(&naindex);
+
+        /* Second pass: feed in small height boxes */
+    ne = boxaGetCount(boxae);
+    for (i = 0; i < ne; i++) {
+        box = boxaGetBox(boxae, i, L_CLONE);
+        n = boxaaGetCount(baa);
+        boxaaAlignBox(baa, box, delta2, &index);
+        if (index < n) {  /* append to an existing boxa */
+            boxaaAddBox(baa, index, box, L_INSERT);
+        } else {  /* doesn't align, need new boxa */
+            boxan = boxaCreate(0);
+            boxaAddBox(boxan, box, L_INSERT);
+            boxaaAddBoxa(baa, boxan, L_INSERT);
+            nan = numaCreate(0);
+            numaaAddNuma(naa, nan, L_INSERT);
+        }
+        numaGetIValue(nae, i, &ival);  /* location in original boxas */
+        numaaAddNumber(naa, index, ival);
+    }
+
+        /* Third pass: merge some boxa whose extent is overlapping.
+         * Think of these boxa as text lines, where the bounding boxes
+         * of the text lines can overlap, but likely won't have
+         * a huge overlap.
+         * First do a greedy find of pairs of overlapping boxa, where
+         * the two boxa overlap by at least 50% of the smaller, and
+         * the smaller is not more than half the area of the larger.
+         * For such pairs, call the larger one the primary boxa.  The
+         * boxes in the smaller one are appended to those in the primary
+         * in pass 3a, and the primaries are extracted in pass 3b.
+         * In this way, all boxes in the original baa are saved. */
+    n = boxaaGetCount(baa);
+    boxaaGetExtent(baa, NULL, NULL, NULL, &boxa3);
+    boxa1 = boxaHandleOverlaps(boxa3, L_REMOVE_SMALL, 1000, 0.5, 0.5, &namap);
+    boxaDestroy(&boxa1);
+    boxaDestroy(&boxa3);
+    for (i = 0; i < n; i++) {  /* Pass 3a: join selected copies of boxa */
+        numaGetIValue(namap, i, &ival);
+        if (ival >= 0) {  /* join current to primary boxa[ival] */
+            boxa1 = boxaaGetBoxa(baa, i, L_COPY);
+            boxa2 = boxaaGetBoxa(baa, ival, L_CLONE);
+            boxaJoin(boxa2, boxa1, 0, -1);
+            boxaDestroy(&boxa2);
+            boxaDestroy(&boxa1);
+            na1 = numaaGetNuma(naa, i, L_COPY);
+            na2 = numaaGetNuma(naa, ival, L_CLONE);
+            numaJoin(na2, na1, 0, -1);
+            numaDestroy(&na1);
+            numaDestroy(&na2);
+        }
+    }
+    baa1 = boxaaCreate(n);
+    naa1 = numaaCreate(n);
+    for (i = 0; i < n; i++) {  /* Pass 3b: save primary boxa */
+        numaGetIValue(namap, i, &ival);
+        if (ival == -1) {
+            boxa1 = boxaaGetBoxa(baa, i, L_CLONE);
+            boxaaAddBoxa(baa1, boxa1, L_INSERT);
+            na1 = numaaGetNuma(naa, i, L_CLONE);
+            numaaAddNuma(naa1, na1, L_INSERT);
+        }
+    }
+    numaDestroy(&namap);
+    boxaaDestroy(&baa);
+    baa = baa1;
+    numaaDestroy(&naa);
+    naa = naa1;
+
+        /* Sort the boxes in each boxa horizontally */
+    m = boxaaGetCount(baa);
+    for (i = 0; i < m; i++) {
+        boxa1 = boxaaGetBoxa(baa, i, L_CLONE);
+        boxa2 = boxaSort(boxa1, L_SORT_BY_X, L_SORT_INCREASING, &nah);
+        boxaaReplaceBoxa(baa, i, boxa2);
+        na1 = numaaGetNuma(naa, i, L_CLONE);
+        na2 = numaSortByIndex(na1, nah);
+        numaaReplaceNuma(naa, i, na2);
+        boxaDestroy(&boxa1);
+        numaDestroy(&na1);
+        numaDestroy(&nah);
+    }
+
+        /* Sort the boxa vertically within boxaa, using the first box
+         * in each boxa. */
+    m = boxaaGetCount(baa);
+    boxav = boxaCreate(m);  /* holds first box in each boxa in baa */
+    naad = numaaCreate(m);
+    if (pnaad)
+        *pnaad = naad;
+    baad = boxaaCreate(m);
+    for (i = 0; i < m; i++) {
+        boxa1 = boxaaGetBoxa(baa, i, L_CLONE);
+        box = boxaGetBox(boxa1, 0, L_CLONE);
+        boxaAddBox(boxav, box, L_INSERT);
+        boxaDestroy(&boxa1);
+    }
+    boxavs = boxaSort(boxav, L_SORT_BY_Y, L_SORT_INCREASING, &nav);
+    for (i = 0; i < m; i++) {
+        numaGetIValue(nav, i, &index);
+        boxa = boxaaGetBoxa(baa, index, L_CLONE);
+        boxaaAddBoxa(baad, boxa, L_INSERT);
+        nad = numaaGetNuma(naa, index, L_CLONE);
+        numaaAddNuma(naad, nad, L_INSERT);
+    }
+
+
+/*    fprintf(stderr, "box count = %d, numaa count = %d\n", nt,
+            numaaGetNumberCount(naad)); */
+
+    boxaaDestroy(&baa);
+    boxaDestroy(&boxav);
+    boxaDestroy(&boxavs);
+    boxaDestroy(&boxae);
+    numaDestroy(&nav);
+    numaDestroy(&nae);
+    numaaDestroy(&naa);
+    if (!pnaad)
+        numaaDestroy(&naad);
+
+    return baad;
+}
+
+
+/*!
+ *  boxaSort2dByIndex()
+ *
+ *      Input:  boxas
+ *              naa (numaa that maps from the new baa to the input boxa)
+ *      Return: baa (sorted boxaa), or null on error
+ */
+BOXAA *
+boxaSort2dByIndex(BOXA   *boxas,
+                  NUMAA  *naa)
+{
+l_int32  ntot, boxtot, i, j, n, nn, index;
+BOX     *box;
+BOXA    *boxa;
+BOXAA   *baa;
+NUMA    *na;
+
+    PROCNAME("boxaSort2dByIndex");
+
+    if (!boxas)
+        return (BOXAA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (!naa)
+        return (BOXAA *)ERROR_PTR("naindex not defined", procName, NULL);
+
+        /* Check counts */
+    ntot = numaaGetNumberCount(naa);
+    boxtot = boxaGetCount(boxas);
+    if (ntot != boxtot)
+        return (BOXAA *)ERROR_PTR("element count mismatch", procName, NULL);
+
+    n = numaaGetCount(naa);
+    baa = boxaaCreate(n);
+    for (i = 0; i < n; i++) {
+        na = numaaGetNuma(naa, i, L_CLONE);
+        nn = numaGetCount(na);
+        boxa = boxaCreate(nn);
+        for (j = 0; j < nn; j++) {
+            numaGetIValue(na, i, &index);
+            box = boxaGetBox(boxas, index, L_COPY);
+            boxaAddBox(boxa, box, L_INSERT);
+        }
+        boxaaAddBoxa(baa, boxa, L_INSERT);
+        numaDestroy(&na);
+    }
+
+    return baa;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Boxa array extraction                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaExtractAsNuma()
+ *
+ *      Input:  boxa
+ *              &nal (<optional return> array of left locations)
+ *              &nat (<optional return> array of top locations)
+ *              &nar (<optional return> array of right locations)
+ *              &nab (<optional return> array of bottom locations)
+ *              &naw (<optional return> array of widths)
+ *              &nah (<optional return> array of heights)
+ *              keepinvalid (1 to keep invalid boxes; 0 to remove them)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If you are counting or sorting values, such as determining
+ *          rank order, you must remove invalid boxes.
+ *      (2) If you are parametrizing the values, or doing an evaluation
+ *          where the position in the boxa sequence is important, you
+ *          must replace the invalid boxes with valid ones before
+ *          doing the extraction. This is easily done with boxaFillSequence().
+ */
+l_int32
+boxaExtractAsNuma(BOXA    *boxa,
+                  NUMA   **pnal,
+                  NUMA   **pnat,
+                  NUMA   **pnar,
+                  NUMA   **pnab,
+                  NUMA   **pnaw,
+                  NUMA   **pnah,
+                  l_int32  keepinvalid)
+{
+l_int32  i, n, left, top, right, bot, w, h;
+
+    PROCNAME("boxaExtractAsNuma");
+
+    if (!pnal && !pnat && !pnar && !pnab && !pnaw && !pnah)
+        return ERROR_INT("no output requested", procName, 1);
+    if (pnal) *pnal = NULL;
+    if (pnat) *pnat = NULL;
+    if (pnar) *pnar = NULL;
+    if (pnab) *pnab = NULL;
+    if (pnaw) *pnaw = NULL;
+    if (pnah) *pnah = NULL;
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (!keepinvalid && boxaGetValidCount(boxa) == 0)
+        return ERROR_INT("no valid boxes", procName, 1);
+
+    n = boxaGetCount(boxa);
+    if (pnal) *pnal = numaCreate(n);
+    if (pnat) *pnat = numaCreate(n);
+    if (pnar) *pnar = numaCreate(n);
+    if (pnab) *pnab = numaCreate(n);
+    if (pnaw) *pnaw = numaCreate(n);
+    if (pnah) *pnah = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, &left, &top, &w, &h);
+        if (!keepinvalid && (w <= 0 || h <= 0))
+            continue;
+        right = left + w - 1;
+        bot = top + h - 1;
+        if (pnal) numaAddNumber(*pnal, left);
+        if (pnat) numaAddNumber(*pnat, top);
+        if (pnar) numaAddNumber(*pnar, right);
+        if (pnab) numaAddNumber(*pnab, bot);
+        if (pnaw) numaAddNumber(*pnaw, w);
+        if (pnah) numaAddNumber(*pnah, h);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  boxaExtractAsPta()
+ *
+ *      Input:  boxa
+ *              &ptal (<optional return> array of left locations vs. index)
+ *              &ptat (<optional return> array of top locations vs. index)
+ *              &ptar (<optional return> array of right locations vs. index)
+ *              &ptab (<optional return> array of bottom locations vs. index)
+ *              &ptaw (<optional return> array of widths vs. index)
+ *              &ptah (<optional return> array of heights vs. index)
+ *              keepinvalid (1 to keep invalid boxes; 0 to remove them)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For most applications, such as counting, sorting, fitting
+ *          to some parametrized form, plotting or filtering in general,
+ *          you should remove the invalid boxes.  Each pta saves the
+ *          box index in the x array, so replacing invalid boxes by
+ *          filling with boxaFillSequence(), which is required for
+ *          boxaExtractAsNuma(), is not necessary.
+ *      (2) If invalid boxes are retained, each one will result in
+ *          entries (typically 0) in all selected output pta.
+ */
+l_int32
+boxaExtractAsPta(BOXA    *boxa,
+                 PTA    **pptal,
+                 PTA    **pptat,
+                 PTA    **pptar,
+                 PTA    **pptab,
+                 PTA    **pptaw,
+                 PTA    **pptah,
+                 l_int32  keepinvalid)
+{
+l_int32  i, n, left, top, right, bot, w, h;
+
+    PROCNAME("boxaExtractAsPta");
+
+    if (!pptal && !pptar && !pptat && !pptab && !pptaw && !pptah)
+        return ERROR_INT("no output requested", procName, 1);
+    if (pptal) *pptal = NULL;
+    if (pptat) *pptat = NULL;
+    if (pptar) *pptar = NULL;
+    if (pptab) *pptab = NULL;
+    if (pptaw) *pptaw = NULL;
+    if (pptah) *pptah = NULL;
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (!keepinvalid && boxaGetValidCount(boxa) == 0)
+        return ERROR_INT("no valid boxes", procName, 1);
+
+    n = boxaGetCount(boxa);
+    if (pptal) *pptal = ptaCreate(n);
+    if (pptat) *pptat = ptaCreate(n);
+    if (pptar) *pptar = ptaCreate(n);
+    if (pptab) *pptab = ptaCreate(n);
+    if (pptaw) *pptaw = ptaCreate(n);
+    if (pptah) *pptah = ptaCreate(n);
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, &left, &top, &w, &h);
+        if (!keepinvalid && (w <= 0 || h <= 0))
+            continue;
+        right = left + w - 1;
+        bot = top + h - 1;
+        if (pptal) ptaAddPt(*pptal, i, left);
+        if (pptat) ptaAddPt(*pptat, i, top);
+        if (pptar) ptaAddPt(*pptar, i, right);
+        if (pptab) ptaAddPt(*pptab, i, bot);
+        if (pptaw) ptaAddPt(*pptaw, i, w);
+        if (pptah) ptaAddPt(*pptah, i, h);
+    }
+
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                            Boxa statistics                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaGetRankSize()
+ *
+ *      Input:  boxa
+ *              fract (use 0.0 for smallest, 1.0 for largest)
+ *      Return: box (with rank values for x, y, w, h), or null on error
+ *              or if the boxa is empty (has no valid boxes)
+ *
+ *  Notes:
+ *      (1) This function does not assume that all boxes in the boxa are valid
+ *      (2) The four box parameters are sorted independently.
+ *          For rank order, the width and height are sorted in increasing
+ *          order.  But what does it mean to sort x and y in "rank order"?
+ *          If the boxes are of comparable size and somewhat
+ *          aligned (e.g., from multiple images), it makes some sense
+ *          to give a "rank order" for x and y by sorting them in
+ *          decreasing order.  But in general, the interpretation of a rank
+ *          order on x and y is highly application dependent.  In summary:
+ *             - x and y are sorted in decreasing order
+ *             - w and h are sorted in increasing order
+ */
+BOX *
+boxaGetRankSize(BOXA      *boxa,
+                l_float32  fract)
+{
+l_float32  xval, yval, wval, hval;
+NUMA      *nax, *nay, *naw, *nah;
+BOX       *box;
+
+    PROCNAME("boxaGetRankSize");
+
+    if (!boxa)
+        return (BOX *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (fract < 0.0 || fract > 1.0)
+        return (BOX *)ERROR_PTR("fract not in [0.0 ... 1.0]", procName, NULL);
+    if (boxaGetValidCount(boxa) == 0)
+        return (BOX *)ERROR_PTR("no valid boxes in boxa", procName, NULL);
+
+        /* Use only the valid boxes */
+    boxaExtractAsNuma(boxa, &nax, &nay, NULL, NULL, &naw, &nah, 0);
+
+    numaGetRankValue(nax, 1.0 - fract, NULL, 1, &xval);
+    numaGetRankValue(nay, 1.0 - fract, NULL, 1, &yval);
+    numaGetRankValue(naw, fract, NULL, 1, &wval);
+    numaGetRankValue(nah, fract, NULL, 1, &hval);
+    box = boxCreate((l_int32)xval, (l_int32)yval, (l_int32)wval, (l_int32)hval);
+
+    numaDestroy(&nax);
+    numaDestroy(&nay);
+    numaDestroy(&naw);
+    numaDestroy(&nah);
+    return box;
+}
+
+
+/*!
+ *  boxaGetMedian()
+ *
+ *      Input:  boxa
+ *      Return: box (with median values for x, y, w, h), or null on error
+ *              or if the boxa is empty.
+ *
+ *  Notes:
+ *      (1) See boxaGetRankSize()
+ */
+BOX *
+boxaGetMedian(BOXA  *boxa)
+{
+    PROCNAME("boxaGetMedian");
+
+    if (!boxa)
+        return (BOX *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (boxaGetCount(boxa) == 0)
+        return (BOX *)ERROR_PTR("boxa is empty", procName, NULL);
+
+    return boxaGetRankSize(boxa, 0.5);
+}
+
+
+/*!
+ *  boxaGetAverageSize()
+ *
+ *      Input:  boxa
+ *              &w  (<optional return> average width)
+ *              &h  (<optional return> average height)
+ *      Return: 0 if OK, 1 on error or if the boxa is empty
+ */
+l_int32
+boxaGetAverageSize(BOXA       *boxa,
+                   l_float32  *pw,
+                   l_float32  *ph)
+{
+l_int32    i, n, bw, bh;
+l_float32  sumw, sumh;
+
+    PROCNAME("boxaGetAverageSize");
+
+    if (pw) *pw = 0.0;
+    if (ph) *ph = 0.0;
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if ((n = boxaGetCount(boxa)) == 0)
+        return ERROR_INT("boxa is empty", procName, 1);
+
+    sumw = sumh = 0.0;
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, NULL, NULL, &bw, &bh);
+        sumw += bw;
+        sumh += bh;
+    }
+
+    if (pw) *pw = sumw / n;
+    if (ph) *ph = sumh / n;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Other Boxaa functions                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaaGetExtent()
+ *
+ *      Input:  baa
+ *              &w  (<optional return> width)
+ *              &h  (<optional return> height)
+ *              &box (<optional return>, minimum box containing all boxa
+ *                    in boxaa)
+ *              &boxa (<optional return>, boxa containing all boxes in each
+ *                     boxa in the boxaa)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The returned w and h are the minimum size image
+ *          that would contain all boxes untranslated.
+ *      (2) Each box in the returned boxa is the minimum box required to
+ *          hold all the boxes in the respective boxa of baa.
+ *      (3) If there are no valid boxes in a boxa, the box corresponding
+ *          to its extent has all fields set to 0 (an invalid box).
+ */
+l_int32
+boxaaGetExtent(BOXAA    *baa,
+               l_int32  *pw,
+               l_int32  *ph,
+               BOX     **pbox,
+               BOXA    **pboxa)
+{
+l_int32  i, n, x, y, w, h, xmax, ymax, xmin, ymin, found;
+BOX     *box1;
+BOXA    *boxa, *boxa1;
+
+    PROCNAME("boxaaGetExtent");
+
+    if (!pw && !ph && !pbox && !pboxa)
+        return ERROR_INT("no ptrs defined", procName, 1);
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pbox) *pbox = NULL;
+    if (pboxa) *pboxa = NULL;
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+
+    n = boxaaGetCount(baa);
+    if (n == 0)
+        return ERROR_INT("no boxa in baa", procName, 1);
+
+    boxa = boxaCreate(n);
+    xmax = ymax = 0;
+    xmin = ymin = 100000000;
+    found = FALSE;
+    for (i = 0; i < n; i++) {
+        boxa1 = boxaaGetBoxa(baa, i, L_CLONE);
+        boxaGetExtent(boxa1, NULL, NULL, &box1);
+        boxaDestroy(&boxa1);
+        boxGetGeometry(box1, &x, &y, &w, &h);
+        if (w > 0 && h > 0) {  /* a valid extent box */
+            found = TRUE;  /* found at least one valid extent box */
+            xmin = L_MIN(xmin, x);
+            ymin = L_MIN(ymin, y);
+            xmax = L_MAX(xmax, x + w);
+            ymax = L_MAX(ymax, y + h);
+        }
+        boxaAddBox(boxa, box1, L_INSERT);
+    }
+    if (found == FALSE)  /* no valid extent boxes */
+        xmin = ymin = 0;
+
+    if (pw) *pw = xmax;
+    if (ph) *ph = ymax;
+    if (pbox)
+        *pbox = boxCreate(xmin, ymin, xmax - xmin, ymax - ymin);
+    if (pboxa)
+        *pboxa = boxa;
+    else
+        boxaDestroy(&boxa);
+    return 0;
+}
+
+
+/*!
+ *  boxaaFlattenToBoxa()
+ *
+ *      Input:  baa
+ *              &naindex  (<optional return> the boxa index in the baa)
+ *              copyflag  (L_COPY or L_CLONE)
+ *      Return: boxa, or null on error
+ *
+ *  Notes:
+ *      (1) This 'flattens' the baa to a boxa, taking the boxes in
+ *          order in the first boxa, then the second, etc.
+ *      (2) If a boxa is empty, we generate an invalid, placeholder box
+ *          of zero size.  This is useful when converting from a baa
+ *          where each boxa has either 0 or 1 boxes, and it is necessary
+ *          to maintain a 1:1 correspondence between the initial
+ *          boxa array and the resulting box array.
+ *      (3) If &naindex is defined, we generate a Numa that gives, for
+ *          each box in the baa, the index of the boxa to which it belongs.
+ */
+BOXA *
+boxaaFlattenToBoxa(BOXAA   *baa,
+                   NUMA   **pnaindex,
+                   l_int32  copyflag)
+{
+l_int32  i, j, m, n;
+BOXA    *boxa, *boxat;
+BOX     *box;
+NUMA    *naindex;
+
+    PROCNAME("boxaaFlattenToBoxa");
+
+    if (pnaindex) *pnaindex = NULL;
+    if (!baa)
+        return (BOXA *)ERROR_PTR("baa not defined", procName, NULL);
+    if (copyflag != L_COPY && copyflag != L_CLONE)
+        return (BOXA *)ERROR_PTR("invalid copyflag", procName, NULL);
+    if (pnaindex) {
+        naindex = numaCreate(0);
+        *pnaindex = naindex;
+    }
+
+    n = boxaaGetCount(baa);
+    boxa = boxaCreate(n);
+    for (i = 0; i < n; i++) {
+        boxat = boxaaGetBoxa(baa, i, L_CLONE);
+        m = boxaGetCount(boxat);
+        if (m == 0) {  /* placeholder box */
+            box = boxCreate(0, 0, 0, 0);
+            boxaAddBox(boxa, box, L_INSERT);
+            if (pnaindex)
+                numaAddNumber(naindex, i);  /* save 'row' number */
+        } else {
+            for (j = 0; j < m; j++) {
+                box = boxaGetBox(boxat, j, copyflag);
+                boxaAddBox(boxa, box, L_INSERT);
+                if (pnaindex)
+                    numaAddNumber(naindex, i);  /* save 'row' number */
+            }
+        }
+        boxaDestroy(&boxat);
+    }
+
+    return boxa;
+}
+
+
+/*!
+ *  boxaaFlattenAligned()
+ *
+ *      Input:  baa
+ *              num (number extracted from each)
+ *              fillerbox (<optional> that fills if necessary)
+ *              copyflag  (L_COPY or L_CLONE)
+ *      Return: boxa, or null on error
+ *
+ *  Notes:
+ *      (1) This 'flattens' the baa to a boxa, taking the first @num
+ *          boxes from each boxa.
+ *      (2) In each boxa, if there are less than @num boxes, we preserve
+ *          the alignment between the input baa and the output boxa
+ *          by inserting one or more fillerbox(es) or, if @fillerbox == NULL,
+ *          one or more invalid placeholder boxes.
+ */
+BOXA *
+boxaaFlattenAligned(BOXAA   *baa,
+                    l_int32  num,
+                    BOX     *fillerbox,
+                    l_int32  copyflag)
+{
+l_int32  i, j, m, n, mval, nshort;
+BOXA    *boxat, *boxad;
+BOX     *box;
+
+    PROCNAME("boxaaFlattenAligned");
+
+    if (!baa)
+        return (BOXA *)ERROR_PTR("baa not defined", procName, NULL);
+    if (copyflag != L_COPY && copyflag != L_CLONE)
+        return (BOXA *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    n = boxaaGetCount(baa);
+    boxad = boxaCreate(n);
+    for (i = 0; i < n; i++) {
+        boxat = boxaaGetBoxa(baa, i, L_CLONE);
+        m = boxaGetCount(boxat);
+        mval = L_MIN(m, num);
+        nshort = num - mval;
+        for (j = 0; j < mval; j++) {  /* take the first @num if possible */
+            box = boxaGetBox(boxat, j, copyflag);
+            boxaAddBox(boxad, box, L_INSERT);
+        }
+        for (j = 0; j < nshort; j++) {  /* add fillers if necessary */
+            if (fillerbox) {
+                boxaAddBox(boxad, fillerbox, L_COPY);
+            } else {
+                box = boxCreate(0, 0, 0, 0);  /* invalid placeholder box */
+                boxaAddBox(boxad, box, L_INSERT);
+            }
+        }
+        boxaDestroy(&boxat);
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxaEncapsulateAligned()
+ *
+ *      Input:  boxa
+ *              num (number put into each boxa in the baa)
+ *              copyflag  (L_COPY or L_CLONE)
+ *      Return: baa, or null on error
+ *
+ *  Notes:
+ *      (1) This puts @num boxes from the input @boxa into each of a
+ *          set of boxa within an output baa.
+ *      (2) This assumes that the boxes in @boxa are in sets of @num each.
+ */
+BOXAA *
+boxaEncapsulateAligned(BOXA    *boxa,
+                       l_int32  num,
+                       l_int32  copyflag)
+{
+l_int32  i, j, n, nbaa, index;
+BOX     *box;
+BOXA    *boxat;
+BOXAA   *baa;
+
+    PROCNAME("boxaEncapsulateAligned");
+
+    if (!boxa)
+        return (BOXAA *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (copyflag != L_COPY && copyflag != L_CLONE)
+        return (BOXAA *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    n = boxaGetCount(boxa);
+    nbaa = n / num;
+    if (num * nbaa != n)
+        L_ERROR("inconsistent alignment: num doesn't divide n\n", procName);
+    baa = boxaaCreate(nbaa);
+    for (i = 0, index = 0; i < nbaa; i++) {
+        boxat = boxaCreate(num);
+        for (j = 0; j < num; j++, index++) {
+            box = boxaGetBox(boxa, index, copyflag);
+            boxaAddBox(boxat, box, L_INSERT);
+        }
+        boxaaAddBoxa(baa, boxat, L_INSERT);
+    }
+
+    return baa;
+}
+
+
+/*!
+ *  boxaaAlignBox()
+ *
+ *      Input:  baa
+ *              box (to be aligned with the bext boxa in the baa, if possible)
+ *              delta (amount by which consecutive components can miss
+ *                     in overlap and still be included in the array)
+ *              &index (of boxa with best overlap, or if none match,
+ *                      this is the index of the next boxa to be generated)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is not greedy.  It finds the boxa whose vertical
+ *          extent has the closest overlap with the input box.
+ */
+l_int32
+boxaaAlignBox(BOXAA    *baa,
+              BOX      *box,
+              l_int32   delta,
+              l_int32  *pindex)
+{
+l_int32  i, n, m, y, yt, h, ht, ovlp, maxovlp, maxindex;
+BOX     *boxt;
+BOXA    *boxa;
+
+    PROCNAME("boxaaAlignBox");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+
+    n = boxaaGetCount(baa);
+    boxGetGeometry(box, NULL, &y, NULL, &h);
+    maxovlp = -10000000;
+    for (i = 0; i < n; i++) {
+        boxa = boxaaGetBoxa(baa, i, L_CLONE);
+        if ((m = boxaGetCount(boxa)) == 0) {
+            L_WARNING("no boxes in boxa\n", procName);
+            continue;
+        }
+        boxaGetExtent(boxa, NULL, NULL, &boxt);
+        boxGetGeometry(boxt, NULL, &yt, NULL, &ht);
+        boxDestroy(&boxt);
+        boxaDestroy(&boxa);
+
+            /* Overlap < 0 means the components do not overlap vertically */
+        if (yt >= y)
+            ovlp = y + h - 1 - yt;
+        else
+            ovlp = yt + ht - 1 - y;
+        if (ovlp > maxovlp) {
+            maxovlp = ovlp;
+            maxindex = i;
+        }
+    }
+
+    if (maxovlp + delta >= 0)
+        *pindex = maxindex;
+    else
+        *pindex = n;
+    return 0;
+}
diff --git a/src/boxfunc3.c b/src/boxfunc3.c
new file mode 100644 (file)
index 0000000..9dc1ff3
--- /dev/null
@@ -0,0 +1,1466 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   boxfunc3.c
+ *
+ *      Boxa/Boxaa painting into pix
+ *           PIX             *pixMaskConnComp()
+ *           PIX             *pixMaskBoxa()
+ *           PIX             *pixPaintBoxa()
+ *           PIX             *pixSetBlackOrWhiteBoxa()
+ *           PIX             *pixPaintBoxaRandom()
+ *           PIX             *pixBlendBoxaRandom()
+ *           PIX             *pixDrawBoxa()
+ *           PIX             *pixDrawBoxaRandom()
+ *           PIX             *boxaaDisplay()
+ *
+ *      Split mask components into Boxa
+ *           BOXA            *pixSplitIntoBoxa()
+ *           BOXA            *pixSplitComponentIntoBoxa()
+ *           static l_int32   pixSearchForRectangle()
+ *
+ *      Represent horizontal or vertical mosaic strips
+ *           BOXA            *makeMosaicStrips()
+ *
+ *      Comparison between boxa
+ *           l_int32          boxaCompareRegions()
+ *
+ *      Reliable selection of a single large box
+ *           BOX             *pixSelectLargeULComp()
+ *           BOX             *boxaSelectLargeULBox()
+ *
+ *  See summary in pixPaintBoxa() of various ways to paint and draw
+ *  boxes on images.
+ */
+
+#include "allheaders.h"
+
+static l_int32 pixSearchForRectangle(PIX *pixs, BOX *boxs, l_int32 minsum,
+                                     l_int32 skipdist, l_int32 delta,
+                                     l_int32 maxbg, l_int32 sideflag,
+                                     BOXA *boxat, NUMA *nascore);
+
+#ifndef NO_CONSOLE_IO
+#define  DEBUG_SPLIT     0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*---------------------------------------------------------------------*
+ *                     Boxa/Boxaa painting into Pix                    *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixMaskConnComp()
+ *
+ *      Input:  pixs (1 bpp)
+ *              connectivity (4 or 8)
+ *              &boxa (<optional return> bounding boxes of c.c.)
+ *      Return: pixd (1 bpp mask over the c.c.), or null on error
+ *
+ *  Notes:
+ *      (1) This generates a mask image with ON pixels over the
+ *          b.b. of the c.c. in pixs.  If there are no ON pixels in pixs,
+ *          pixd will also have no ON pixels.
+ */
+PIX *
+pixMaskConnComp(PIX     *pixs,
+                l_int32  connectivity,
+                BOXA   **pboxa)
+{
+BOXA  *boxa;
+PIX   *pixd;
+
+    PROCNAME("pixMaskConnComp");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+    boxa = pixConnComp(pixs, NULL, connectivity);
+    pixd = pixCreateTemplate(pixs);
+    if (boxaGetCount(boxa) != 0)
+        pixMaskBoxa(pixd, pixd, boxa, L_SET_PIXELS);
+    if (pboxa)
+        *pboxa = boxa;
+    else
+        boxaDestroy(&boxa);
+    return pixd;
+}
+
+
+/*!
+ *  pixMaskBoxa()
+ *
+ *      Input:  pixd (<optional> may be null)
+ *              pixs (any depth; not cmapped)
+ *              boxa (of boxes, to paint)
+ *              op (L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS)
+ *      Return: pixd (with masking op over the boxes), or null on error
+ *
+ *  Notes:
+ *      (1) This can be used with:
+ *              pixd = NULL  (makes a new pixd)
+ *              pixd = pixs  (in-place)
+ *      (2) If pixd == NULL, this first makes a copy of pixs, and then
+ *          bit-twiddles over the boxes.  Otherwise, it operates directly
+ *          on pixs.
+ *      (3) This simple function is typically used with 1 bpp images.
+ *          It uses the 1-image rasterop function, rasteropUniLow(),
+ *          to set, clear or flip the pixels in pixd.
+ *      (4) If you want to generate a 1 bpp mask of ON pixels from the boxes
+ *          in a Boxa, in a pix of size (w,h):
+ *              pix = pixCreate(w, h, 1);
+ *              pixMaskBoxa(pix, pix, boxa, L_SET_PIXELS);
+ */
+PIX *
+pixMaskBoxa(PIX     *pixd,
+            PIX     *pixs,
+            BOXA    *boxa,
+            l_int32  op)
+{
+l_int32  i, n, x, y, w, h;
+BOX     *box;
+
+    PROCNAME("pixMaskBoxa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs is cmapped", procName, NULL);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("if pixd, must be in-place", procName, NULL);
+    if (!boxa)
+        return (PIX *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return (PIX *)ERROR_PTR("invalid op", procName, NULL);
+
+    pixd = pixCopy(pixd, pixs);
+    if ((n = boxaGetCount(boxa)) == 0) {
+        L_WARNING("no boxes to mask\n", procName);
+        return pixd;
+    }
+
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        boxGetGeometry(box, &x, &y, &w, &h);
+        if (op == L_SET_PIXELS)
+            pixRasterop(pixd, x, y, w, h, PIX_SET, NULL, 0, 0);
+        else if (op == L_CLEAR_PIXELS)
+            pixRasterop(pixd, x, y, w, h, PIX_CLR, NULL, 0, 0);
+        else  /* op == L_FLIP_PIXELS */
+            pixRasterop(pixd, x, y, w, h, PIX_NOT(PIX_DST), NULL, 0, 0);
+        boxDestroy(&box);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixPaintBoxa()
+ *
+ *      Input:  pixs (any depth, can be cmapped)
+ *              boxa (of boxes, to paint)
+ *              val (rgba color to paint)
+ *      Return: pixd (with painted boxes), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs is 1 bpp or is colormapped, it is converted to 8 bpp
+ *          and the boxa is painted using a colormap; otherwise,
+ *          it is converted to 32 bpp rgb.
+ *      (2) There are several ways to display a box on an image:
+ *            * Paint it as a solid color
+ *            * Draw the outline
+ *            * Blend the outline or region with the existing image
+ *          We provide painting and drawing here; blending is in blend.c.
+ *          When painting or drawing, the result can be either a
+ *          cmapped image or an rgb image.  The dest will be cmapped
+ *          if the src is either 1 bpp or has a cmap that is not full.
+ *          To force RGB output, use pixConvertTo8(pixs, FALSE)
+ *          before calling any of these paint and draw functions.
+ */
+PIX *
+pixPaintBoxa(PIX      *pixs,
+             BOXA     *boxa,
+             l_uint32  val)
+{
+l_int32   i, n, d, rval, gval, bval, newindex;
+l_int32   mapvacancy;   /* true only if cmap and not full */
+BOX      *box;
+PIX      *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixPaintBoxa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!boxa)
+        return (PIX *)ERROR_PTR("boxa not defined", procName, NULL);
+
+    if ((n = boxaGetCount(boxa)) == 0) {
+        L_WARNING("no boxes to paint; returning a copy\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+    mapvacancy = FALSE;
+    if ((cmap = pixGetColormap(pixs)) != NULL) {
+        if (pixcmapGetCount(cmap) < 256)
+            mapvacancy = TRUE;
+    }
+    if (pixGetDepth(pixs) == 1 || mapvacancy)
+        pixd = pixConvertTo8(pixs, TRUE);
+    else
+        pixd = pixConvertTo32(pixs);
+    if (!pixd)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    d = pixGetDepth(pixd);
+    if (d == 8) {  /* colormapped */
+        cmap = pixGetColormap(pixd);
+        extractRGBValues(val, &rval, &gval, &bval);
+        if (pixcmapAddNewColor(cmap, rval, gval, bval, &newindex))
+            return (PIX *)ERROR_PTR("cmap full; can't add", procName, NULL);
+    }
+
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        if (d == 8)
+            pixSetInRectArbitrary(pixd, box, newindex);
+        else
+            pixSetInRectArbitrary(pixd, box, val);
+        boxDestroy(&box);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixSetBlackOrWhiteBoxa()
+ *
+ *      Input:  pixs (any depth, can be cmapped)
+ *              boxa (<optional> of boxes, to clear or set)
+ *              op (L_SET_BLACK, L_SET_WHITE)
+ *      Return: pixd (with boxes filled with white or black), or null on error
+ */
+PIX *
+pixSetBlackOrWhiteBoxa(PIX     *pixs,
+                       BOXA    *boxa,
+                       l_int32  op)
+{
+l_int32   i, n, d, index;
+l_uint32  color;
+BOX      *box;
+PIX      *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixSetBlackOrWhiteBoxa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!boxa)
+        return pixCopy(NULL, pixs);
+    if ((n = boxaGetCount(boxa)) == 0)
+        return pixCopy(NULL, pixs);
+
+    pixd = pixCopy(NULL, pixs);
+    d = pixGetDepth(pixd);
+    if (d == 1) {
+        for (i = 0; i < n; i++) {
+            box = boxaGetBox(boxa, i, L_CLONE);
+            if (op == L_SET_WHITE)
+                pixClearInRect(pixd, box);
+            else
+                pixSetInRect(pixd, box);
+            boxDestroy(&box);
+        }
+        return pixd;
+    }
+
+    cmap = pixGetColormap(pixs);
+    if (cmap) {
+        color = (op == L_SET_WHITE) ? 1 : 0;
+        pixcmapAddBlackOrWhite(cmap, color, &index);
+    } else if (d == 8) {
+        color = (op == L_SET_WHITE) ? 0xff : 0x0;
+    } else if (d == 32) {
+        color = (op == L_SET_WHITE) ? 0xffffff00 : 0x0;
+    } else if (d == 2) {
+        color = (op == L_SET_WHITE) ? 0x3 : 0x0;
+    } else if (d == 4) {
+        color = (op == L_SET_WHITE) ? 0xf : 0x0;
+    } else if (d == 16) {
+        color = (op == L_SET_WHITE) ? 0xffff : 0x0;
+    } else {
+        pixDestroy(&pixd);
+        return (PIX *)ERROR_PTR("invalid depth", procName, NULL);
+    }
+
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        if (cmap)
+            pixSetInRectArbitrary(pixd, box, index);
+        else
+            pixSetInRectArbitrary(pixd, box, color);
+        boxDestroy(&box);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixPaintBoxaRandom()
+ *
+ *      Input:  pixs (any depth, can be cmapped)
+ *              boxa (of boxes, to paint)
+ *      Return: pixd (with painted boxes), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs is 1 bpp, we paint the boxa using a colormap;
+ *          otherwise, we convert to 32 bpp.
+ *      (2) We use up to 254 different colors for painting the regions.
+ *      (3) If boxes overlap, the later ones paint over earlier ones.
+ */
+PIX *
+pixPaintBoxaRandom(PIX   *pixs,
+                   BOXA  *boxa)
+{
+l_int32   i, n, d, rval, gval, bval, index;
+l_uint32  val;
+BOX      *box;
+PIX      *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixPaintBoxaRandom");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!boxa)
+        return (PIX *)ERROR_PTR("boxa not defined", procName, NULL);
+
+    if ((n = boxaGetCount(boxa)) == 0) {
+        L_WARNING("no boxes to paint; returning a copy\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+    if (pixGetDepth(pixs) == 1)
+        pixd = pixConvert1To8(NULL, pixs, 255, 0);
+    else
+        pixd = pixConvertTo32(pixs);
+    if (!pixd)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    cmap = pixcmapCreateRandom(8, 1, 1);
+    d = pixGetDepth(pixd);
+    if (d == 8)  /* colormapped */
+        pixSetColormap(pixd, cmap);
+
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        index = 1 + (i % 254);
+        if (d == 8) {
+            pixSetInRectArbitrary(pixd, box, index);
+        } else {  /* d == 32 */
+            pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+            composeRGBPixel(rval, gval, bval, &val);
+            pixSetInRectArbitrary(pixd, box, val);
+        }
+        boxDestroy(&box);
+    }
+
+    if (d == 32)
+        pixcmapDestroy(&cmap);
+    return pixd;
+}
+
+
+/*!
+ *  pixBlendBoxaRandom()
+ *
+ *      Input:  pixs (any depth; can be cmapped)
+ *              boxa (of boxes, to blend/paint)
+ *              fract (of box color to use)
+ *      Return: pixd (32 bpp, with blend/painted boxes), or null on error
+ *
+ *  Notes:
+ *      (1) pixs is converted to 32 bpp.
+ *      (2) This differs from pixPaintBoxaRandom(), in that the
+ *          colors here are blended with the color of pixs.
+ *      (3) We use up to 254 different colors for painting the regions.
+ *      (4) If boxes overlap, the final color depends only on the last
+ *          rect that is used.
+ */
+PIX *
+pixBlendBoxaRandom(PIX       *pixs,
+                   BOXA      *boxa,
+                   l_float32  fract)
+{
+l_int32   i, n, rval, gval, bval, index;
+l_uint32  val;
+BOX      *box;
+PIX      *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixBlendBoxaRandom");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!boxa)
+        return (PIX *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (fract < 0.0 || fract > 1.0) {
+        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", procName);
+        fract = 0.5;
+    }
+
+    if ((n = boxaGetCount(boxa)) == 0) {
+        L_WARNING("no boxes to paint; returning a copy\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+    if ((pixd = pixConvertTo32(pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not defined", procName, NULL);
+
+    cmap = pixcmapCreateRandom(8, 1, 1);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        index = 1 + (i % 254);
+        pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+        composeRGBPixel(rval, gval, bval, &val);
+        pixBlendInRect(pixd, box, val, fract);
+        boxDestroy(&box);
+    }
+
+    pixcmapDestroy(&cmap);
+    return pixd;
+}
+
+
+/*!
+ *  pixDrawBoxa()
+ *
+ *      Input:  pixs (any depth; can be cmapped)
+ *              boxa (of boxes, to draw)
+ *              width (of lines)
+ *              val (rgba color to draw)
+ *      Return: pixd (with outlines of boxes added), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs is 1 bpp or is colormapped, it is converted to 8 bpp
+ *          and the boxa is drawn using a colormap; otherwise,
+ *          it is converted to 32 bpp rgb.
+ */
+PIX *
+pixDrawBoxa(PIX      *pixs,
+            BOXA     *boxa,
+            l_int32   width,
+            l_uint32  val)
+{
+l_int32   rval, gval, bval, newindex;
+l_int32   mapvacancy;   /* true only if cmap and not full */
+PIX      *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixDrawBoxa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!boxa)
+        return (PIX *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (width < 1)
+        return (PIX *)ERROR_PTR("width must be >= 1", procName, NULL);
+
+    if (boxaGetCount(boxa) == 0) {
+        L_WARNING("no boxes to draw; returning a copy\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+    mapvacancy = FALSE;
+    if ((cmap = pixGetColormap(pixs)) != NULL) {
+        if (pixcmapGetCount(cmap) < 256)
+            mapvacancy = TRUE;
+    }
+    if (pixGetDepth(pixs) == 1 || mapvacancy)
+        pixd = pixConvertTo8(pixs, TRUE);
+    else
+        pixd = pixConvertTo32(pixs);
+    if (!pixd)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    extractRGBValues(val, &rval, &gval, &bval);
+    if (pixGetDepth(pixd) == 8) {  /* colormapped */
+        cmap = pixGetColormap(pixd);
+        pixcmapAddNewColor(cmap, rval, gval, bval, &newindex);
+    }
+
+    pixRenderBoxaArb(pixd, boxa, width, rval, gval, bval);
+    return pixd;
+}
+
+
+/*!
+ *  pixDrawBoxaRandom()
+ *
+ *      Input:  pixs (any depth, can be cmapped)
+ *              boxa (of boxes, to draw)
+ *              width (thickness of line)
+ *      Return: pixd (with box outlines drawn), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs is 1 bpp, we draw the boxa using a colormap;
+ *          otherwise, we convert to 32 bpp.
+ *      (2) We use up to 254 different colors for drawing the boxes.
+ *      (3) If boxes overlap, the later ones draw over earlier ones.
+ */
+PIX *
+pixDrawBoxaRandom(PIX     *pixs,
+                  BOXA    *boxa,
+                  l_int32  width)
+{
+l_int32   i, n, rval, gval, bval, index;
+BOX      *box;
+PIX      *pixd;
+PIXCMAP  *cmap;
+PTAA     *ptaa;
+
+    PROCNAME("pixDrawBoxaRandom");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!boxa)
+        return (PIX *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (width < 1)
+        return (PIX *)ERROR_PTR("width must be >= 1", procName, NULL);
+
+    if ((n = boxaGetCount(boxa)) == 0) {
+        L_WARNING("no boxes to draw; returning a copy\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+        /* Input depth = 1 bpp; generate cmapped output */
+    if (pixGetDepth(pixs) == 1) {
+        ptaa = generatePtaaBoxa(boxa);
+        pixd = pixRenderRandomCmapPtaa(pixs, ptaa, 1, width, 1);
+        ptaaDestroy(&ptaa);
+        return pixd;
+    }
+
+        /* Generate rgb output */
+    pixd = pixConvertTo32(pixs);
+    cmap = pixcmapCreateRandom(8, 1, 1);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        index = 1 + (i % 254);
+        pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+        pixRenderBoxArb(pixd, box, width, rval, gval, bval);
+        boxDestroy(&box);
+    }
+    pixcmapDestroy(&cmap);
+    return pixd;
+}
+
+
+/*!
+ *  boxaaDisplay()
+ *
+ *      Input:  baa
+ *              linewba (line width to display boxa)
+ *              linewb (line width to display box)
+ *              colorba (color to display boxa)
+ *              colorb (color to display box)
+ *              w (of pix; use 0 if determined by baa)
+ *              h (of pix; use 0 if determined by baa)
+ *      Return: 0 if OK, 1 on error
+ */
+PIX *
+boxaaDisplay(BOXAA    *baa,
+             l_int32   linewba,
+             l_int32   linewb,
+             l_uint32  colorba,
+             l_uint32  colorb,
+             l_int32   w,
+             l_int32   h)
+{
+l_int32   i, j, n, m, rbox, gbox, bbox, rboxa, gboxa, bboxa;
+BOX      *box;
+BOXA     *boxa;
+PIX      *pix;
+PIXCMAP  *cmap;
+
+    PROCNAME("boxaaDisplay");
+
+    if (!baa)
+        return (PIX *)ERROR_PTR("baa not defined", procName, NULL);
+    if (w == 0 || h == 0)
+        boxaaGetExtent(baa, &w, &h, NULL, NULL);
+
+    pix = pixCreate(w, h, 8);
+    cmap = pixcmapCreate(8);
+    pixSetColormap(pix, cmap);
+    extractRGBValues(colorb, &rbox, &gbox, &bbox);
+    extractRGBValues(colorba, &rboxa, &gboxa, &bboxa);
+    pixcmapAddColor(cmap, 255, 255, 255);
+    pixcmapAddColor(cmap, rbox, gbox, bbox);
+    pixcmapAddColor(cmap, rboxa, gboxa, bboxa);
+
+    n = boxaaGetCount(baa);
+    for (i = 0; i < n; i++) {
+        boxa = boxaaGetBoxa(baa, i, L_CLONE);
+        boxaGetExtent(boxa, NULL, NULL, &box);
+        pixRenderBoxArb(pix, box, linewba, rboxa, gboxa, bboxa);
+        boxDestroy(&box);
+        m = boxaGetCount(boxa);
+        for (j = 0; j < m; j++) {
+            box = boxaGetBox(boxa, j, L_CLONE);
+            pixRenderBoxArb(pix, box, linewb, rbox, gbox, bbox);
+            boxDestroy(&box);
+        }
+        boxaDestroy(&boxa);
+    }
+
+    return pix;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                   Split mask components into Boxa                   *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixSplitIntoBoxa()
+ *
+ *      Input:  pixs (1 bpp)
+ *              minsum  (minimum pixels to trigger propagation)
+ *              skipdist (distance before computing sum for propagation)
+ *              delta (difference required to stop propagation)
+ *              maxbg (maximum number of allowed bg pixels in ref scan)
+ *              maxcomps (use 0 for unlimited number of subdivided components)
+ *              remainder (set to 1 to get b.b. of remaining stuff)
+ *      Return: boxa (of rectangles covering the fg of pixs), or null on error
+ *
+ *  Notes:
+ *      (1) This generates a boxa of rectangles that covers
+ *          the fg of a mask.  For each 8-connected component in pixs,
+ *          it does a greedy partitioning, choosing the largest
+ *          rectangle found from each of the four directions at each iter.
+ *          See pixSplitComponentIntoBoxa() for details.
+ *      (2) The input parameters give some flexibility for boundary
+ *          noise.  The resulting set of rectangles may cover some
+ *          bg pixels.
+ *      (3) This should be used when there are a small number of
+ *          mask components, each of which has sides that are close
+ *          to horizontal and vertical.  The input parameters @delta
+ *          and @maxbg determine whether or not holes in the mask are covered.
+ *      (4) The parameter @maxcomps gives the maximum number of allowed
+ *          rectangles extracted from any single connected component.
+ *          Use 0 if no limit is to be applied.
+ *      (5) The flag @remainder specifies whether we take a final bounding
+ *          box for anything left after the maximum number of allowed
+ *          rectangle is extracted.
+ */
+BOXA *
+pixSplitIntoBoxa(PIX     *pixs,
+                 l_int32  minsum,
+                 l_int32  skipdist,
+                 l_int32  delta,
+                 l_int32  maxbg,
+                 l_int32  maxcomps,
+                 l_int32  remainder)
+{
+l_int32  i, n;
+BOX     *box;
+BOXA    *boxa, *boxas, *boxad;
+PIX     *pix;
+PIXA    *pixas;
+
+    PROCNAME("pixSplitIntoBoxa");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+
+    boxas = pixConnComp(pixs, &pixas, 8);
+    n = boxaGetCount(boxas);
+    boxad = boxaCreate(0);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixas, i, L_CLONE);
+        box = boxaGetBox(boxas, i, L_CLONE);
+        boxa = pixSplitComponentIntoBoxa(pix, box, minsum, skipdist,
+                                         delta, maxbg, maxcomps, remainder);
+        boxaJoin(boxad, boxa, 0, -1);
+        pixDestroy(&pix);
+        boxDestroy(&box);
+        boxaDestroy(&boxa);
+    }
+
+    pixaDestroy(&pixas);
+    boxaDestroy(&boxas);
+    return boxad;
+}
+
+
+/*!
+ *  pixSplitComponentIntoBoxa()
+ *
+ *      Input:  pixs (1 bpp)
+ *              box (<optional> location of pixs w/rt an origin)
+ *              minsum  (minimum pixels to trigger propagation)
+ *              skipdist (distance before computing sum for propagation)
+ *              delta (difference required to stop propagation)
+ *              maxbg (maximum number of allowed bg pixels in ref scan)
+ *              maxcomps (use 0 for unlimited number of subdivided components)
+ *              remainder (set to 1 to get b.b. of remaining stuff)
+ *      Return: boxa (of rectangles covering the fg of pixs), or null on error
+ *
+ *  Notes:
+ *      (1) This generates a boxa of rectangles that covers
+ *          the fg of a mask.  It does so by a greedy partitioning of
+ *          the mask, choosing the largest rectangle found from
+ *          each of the four directions at each step.
+ *      (2) The input parameters give some flexibility for boundary
+ *          noise.  The resulting set of rectangles must cover all
+ *          the fg pixels and, in addition, may cover some bg pixels.
+ *          Using small input parameters on a noiseless mask (i.e., one
+ *          that has only large vertical and horizontal edges) will
+ *          result in a proper covering of only the fg pixels of the mask.
+ *      (3) The input is assumed to be a single connected component, that
+ *          may have holes.  From each side, sweep inward, counting
+ *          the pixels.  If the count becomes greater than @minsum,
+ *          and we have moved forward a further amount @skipdist,
+ *          record that count ('countref'), but don't accept if the scan
+ *          contains more than @maxbg bg pixels.  Continue the scan
+ *          until we reach a count that differs from countref by at
+ *          least @delta, at which point the propagation stops.  The box
+ *          swept out gets a score, which is the sum of fg pixels
+ *          minus a penalty.  The penalty is the number of bg pixels
+ *          in the box.  This is done from all four sides, and the
+ *          side with the largest score is saved as a rectangle.
+ *          The process repeats until there is either no rectangle
+ *          left, or there is one that can't be captured from any
+ *          direction.  For the latter case, we simply accept the
+ *          last rectangle.
+ *      (4) The input box is only used to specify the location of
+ *          the UL corner of pixs, with respect to an origin that
+ *          typically represents the UL corner of an underlying image,
+ *          of which pixs is one component.  If @box is null,
+ *          the UL corner is taken to be (0, 0).
+ *      (5) The parameter @maxcomps gives the maximum number of allowed
+ *          rectangles extracted from any single connected component.
+ *          Use 0 if no limit is to be applied.
+ *      (6) The flag @remainder specifies whether we take a final bounding
+ *          box for anything left after the maximum number of allowed
+ *          rectangle is extracted.
+ *      (7) So if @maxcomps > 0, it specifies that we want no more than
+ *          the first @maxcomps rectangles that satisfy the input
+ *          criteria.  After this, we can get a final rectangle that
+ *          bounds everything left over by setting @remainder == 1.
+ *          If @remainder == 0, we only get rectangles that satisfy
+ *          the input criteria.
+ *      (8) It should be noted that the removal of rectangles can
+ *          break the original c.c. into several c.c.
+ *      (9) Summing up:
+ *            * If @maxcomp == 0, the splitting proceeds as far as possible.
+ *            * If @maxcomp > 0, the splitting stops when @maxcomps are
+ *                found, or earlier if no more components can be selected.
+ *            * If @remainder == 1 and components remain that cannot be
+ *                selected, they are returned as a single final rectangle;
+ *                otherwise, they are ignored.
+ */
+BOXA *
+pixSplitComponentIntoBoxa(PIX     *pix,
+                          BOX     *box,
+                          l_int32  minsum,
+                          l_int32  skipdist,
+                          l_int32  delta,
+                          l_int32  maxbg,
+                          l_int32  maxcomps,
+                          l_int32  remainder)
+{
+l_int32  i, w, h, boxx, boxy, bx, by, bw, bh, maxdir, maxscore;
+l_int32  iter;
+BOX     *boxs;  /* shrinks as rectangular regions are removed */
+BOX     *boxt1, *boxt2, *boxt3;
+BOXA    *boxat;  /* stores rectangle data for each side in an iteration */
+BOXA    *boxad;
+NUMA    *nascore, *nas;
+PIX     *pixs;
+
+    PROCNAME("pixSplitComponentIntoBoxa");
+
+    if (!pix || pixGetDepth(pix) != 1)
+        return (BOXA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL);
+
+    pixs = pixCopy(NULL, pix);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (box)
+        boxGetGeometry(box, &boxx, &boxy, NULL, NULL);
+    else
+        boxx = boxy = 0;
+    boxs = boxCreate(0, 0, w, h);
+    boxad = boxaCreate(0);
+
+    iter = 0;
+    while (boxs != NULL) {
+        boxGetGeometry(boxs, &bx, &by, &bw, &bh);
+        boxat = boxaCreate(4);  /* potential rectangular regions */
+        nascore = numaCreate(4);
+        for (i = 0; i < 4; i++) {
+            pixSearchForRectangle(pixs, boxs, minsum, skipdist, delta, maxbg,
+                                  i, boxat, nascore);
+        }
+        nas = numaGetSortIndex(nascore, L_SORT_DECREASING);
+        numaGetIValue(nas, 0, &maxdir);
+        numaGetIValue(nascore, maxdir, &maxscore);
+#if  DEBUG_SPLIT
+        fprintf(stderr, "Iteration: %d\n", iter);
+        boxPrintStreamInfo(stderr, boxs);
+        boxaWriteStream(stderr, boxat);
+        fprintf(stderr, "\nmaxdir = %d, maxscore = %d\n\n", maxdir, maxscore);
+#endif  /* DEBUG_SPLIT */
+        if (maxscore > 0) {  /* accept this */
+            boxt1 = boxaGetBox(boxat, maxdir, L_CLONE);
+            boxt2 = boxTransform(boxt1, boxx, boxy, 1.0, 1.0);
+            boxaAddBox(boxad, boxt2, L_INSERT);
+            pixClearInRect(pixs, boxt1);
+            boxDestroy(&boxt1);
+            pixClipBoxToForeground(pixs, boxs, NULL, &boxt3);
+            boxDestroy(&boxs);
+            boxs = boxt3;
+            if (boxs) {
+                boxGetGeometry(boxs, NULL, NULL, &bw, &bh);
+                if (bw < 2 || bh < 2)
+                    boxDestroy(&boxs);  /* we're done */
+            }
+        } else {  /* no more valid rectangles can be found */
+            if (remainder == 1) {  /* save the last box */
+                boxt1 = boxTransform(boxs, boxx, boxy, 1.0, 1.0);
+                boxaAddBox(boxad, boxt1, L_INSERT);
+            }
+            boxDestroy(&boxs);  /* we're done */
+        }
+        boxaDestroy(&boxat);
+        numaDestroy(&nascore);
+        numaDestroy(&nas);
+
+        iter++;
+        if ((iter == maxcomps) && boxs) {
+            if (remainder == 1) {  /* save the last box */
+                boxt1 = boxTransform(boxs, boxx, boxy, 1.0, 1.0);
+                boxaAddBox(boxad, boxt1, L_INSERT);
+            }
+            boxDestroy(&boxs);  /* we're done */
+        }
+    }
+
+    pixDestroy(&pixs);
+    return boxad;
+}
+
+
+/*!
+ *  pixSearchForRectangle()
+ *
+ *      Input:  pixs (1 bpp)
+ *              boxs (current region to investigate)
+ *              minsum  (minimum pixels to trigger propagation)
+ *              skipdist (distance before computing sum for propagation)
+ *              delta (difference required to stop propagation)
+ *              maxbg (maximum number of allowed bg pixels in ref scan)
+ *              sideflag (side to search from)
+ *              boxat (add result of rectangular region found here)
+ *              nascore (add score for this rectangle here)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See pixSplitComponentIntoBoxa() for an explanation of the algorithm.
+ *          This does the sweep from a single side.  For each iteration
+ *          in pixSplitComponentIntoBoxa(), this will be called 4 times,
+ *          for @sideflag = {0, 1, 2, 3}.
+ *      (2) If a valid rectangle is not found, add a score of 0 and
+ *          input a minimum box.
+ */
+static l_int32
+pixSearchForRectangle(PIX     *pixs,
+                      BOX     *boxs,
+                      l_int32  minsum,
+                      l_int32  skipdist,
+                      l_int32  delta,
+                      l_int32  maxbg,
+                      l_int32  sideflag,
+                      BOXA    *boxat,
+                      NUMA    *nascore)
+{
+l_int32  bx, by, bw, bh, width, height, setref, atref;
+l_int32  minincol, maxincol, mininrow, maxinrow, minval, maxval, bgref;
+l_int32  x, y, x0, y0, xref, yref, colsum, rowsum, score, countref, diff;
+void   **lines1;
+BOX     *boxr;
+
+    PROCNAME("pixSearchForRectangle");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs undefined or not 1 bpp", procName, 1);
+    if (!boxs)
+        return ERROR_INT("boxs not defined", procName, 1);
+    if (!boxat)
+        return ERROR_INT("boxat not defined", procName, 1);
+    if (!nascore)
+        return ERROR_INT("nascore not defined", procName, 1);
+
+    lines1 = pixGetLinePtrs(pixs, NULL);
+    boxGetGeometry(boxs, &bx, &by, &bw, &bh);
+    boxr = NULL;
+    setref = 0;
+    atref = 0;
+    maxval = 0;
+    minval = 100000;
+    score = 0;  /* sum of all (fg - bg) pixels seen in the scan */
+    xref = yref = 100000;  /* init to impossibly big number */
+    if (sideflag == L_FROM_LEFT) {
+        for (x = bx; x < bx + bw; x++) {
+            colsum = 0;
+            maxincol = 0;
+            minincol = 100000;
+            for (y = by; y < by + bh; y++) {
+                if (GET_DATA_BIT(lines1[y], x)) {
+                    colsum++;
+                    if (y > maxincol) maxincol = y;
+                    if (y < minincol) minincol = y;
+                }
+            }
+            score += colsum;
+
+                /* Enough fg to sweep out a rectangle? */
+            if (!setref && colsum >= minsum) {
+                setref = 1;
+                xref = x + 10;
+                if (xref >= bx + bw)
+                    goto failure;
+            }
+
+                /* Reached the reference line; save the count;
+                 * if there is too much bg, the rectangle is invalid. */
+            if (setref && x == xref) {
+                atref = 1;
+                countref = colsum;
+                bgref = maxincol - minincol + 1 - countref;
+                if (bgref > maxbg)
+                    goto failure;
+            }
+
+                /* Have we left the rectangle?  If so, save it along
+                 * with the score. */
+            if (atref) {
+                diff = L_ABS(colsum - countref);
+                if (diff >= delta || x == bx + bw - 1) {
+                    height = maxval - minval + 1;
+                    width = x - bx;
+                    if (x == bx + bw - 1) width = x - bx + 1;
+                    boxr = boxCreate(bx, minval, width, height);
+                    score = 2 * score - width * height;
+                    goto success;
+                }
+            }
+            maxval = L_MAX(maxval, maxincol);
+            minval = L_MIN(minval, minincol);
+        }
+        goto failure;
+    } else if (sideflag == L_FROM_RIGHT) {
+        for (x = bx + bw - 1; x >= bx; x--) {
+            colsum = 0;
+            maxincol = 0;
+            minincol = 100000;
+            for (y = by; y < by + bh; y++) {
+                if (GET_DATA_BIT(lines1[y], x)) {
+                    colsum++;
+                    if (y > maxincol) maxincol = y;
+                    if (y < minincol) minincol = y;
+                }
+            }
+            score += colsum;
+            if (!setref && colsum >= minsum) {
+                setref = 1;
+                xref = x - 10;
+                if (xref < bx)
+                    goto failure;
+            }
+            if (setref && x == xref) {
+                atref = 1;
+                countref = colsum;
+                bgref = maxincol - minincol + 1 - countref;
+                if (bgref > maxbg)
+                    goto failure;
+            }
+            if (atref) {
+                diff = L_ABS(colsum - countref);
+                if (diff >= delta || x == bx) {
+                    height = maxval - minval + 1;
+                    x0 = x + 1;
+                    if (x == bx) x0 = x;
+                    width = bx + bw - x0;
+                    boxr = boxCreate(x0, minval, width, height);
+                    score = 2 * score - width * height;
+                    goto success;
+                }
+            }
+            maxval = L_MAX(maxval, maxincol);
+            minval = L_MIN(minval, minincol);
+        }
+        goto failure;
+    } else if (sideflag == L_FROM_TOP) {
+        for (y = by; y < by + bh; y++) {
+            rowsum = 0;
+            maxinrow = 0;
+            mininrow = 100000;
+            for (x = bx; x < bx + bw; x++) {
+                if (GET_DATA_BIT(lines1[y], x)) {
+                    rowsum++;
+                    if (x > maxinrow) maxinrow = x;
+                    if (x < mininrow) mininrow = x;
+                }
+            }
+            score += rowsum;
+            if (!setref && rowsum >= minsum) {
+                setref = 1;
+                yref = y + 10;
+                if (yref >= by + bh)
+                    goto failure;
+            }
+            if (setref && y == yref) {
+                atref = 1;
+                countref = rowsum;
+                bgref = maxinrow - mininrow + 1 - countref;
+                if (bgref > maxbg)
+                    goto failure;
+            }
+            if (atref) {
+                diff = L_ABS(rowsum - countref);
+                if (diff >= delta || y == by + bh - 1) {
+                    width = maxval - minval + 1;
+                    height = y - by;
+                    if (y == by + bh - 1) height = y - by + 1;
+                    boxr = boxCreate(minval, by, width, height);
+                    score = 2 * score - width * height;
+                    goto success;
+                }
+            }
+            maxval = L_MAX(maxval, maxinrow);
+            minval = L_MIN(minval, mininrow);
+        }
+        goto failure;
+    } else if (sideflag == L_FROM_BOT) {
+        for (y = by + bh - 1; y >= by; y--) {
+            rowsum = 0;
+            maxinrow = 0;
+            mininrow = 100000;
+            for (x = bx; x < bx + bw; x++) {
+                if (GET_DATA_BIT(lines1[y], x)) {
+                    rowsum++;
+                    if (x > maxinrow) maxinrow = x;
+                    if (x < mininrow) mininrow = x;
+                }
+            }
+            score += rowsum;
+            if (!setref && rowsum >= minsum) {
+                setref = 1;
+                yref = y - 10;
+                if (yref < by)
+                    goto failure;
+            }
+            if (setref && y == yref) {
+                atref = 1;
+                countref = rowsum;
+                bgref = maxinrow - mininrow + 1 - countref;
+                if (bgref > maxbg)
+                    goto failure;
+            }
+            if (atref) {
+                diff = L_ABS(rowsum - countref);
+                if (diff >= delta || y == by) {
+                    width = maxval - minval + 1;
+                    y0 = y + 1;
+                    if (y == by) y0 = y;
+                    height = by + bh - y0;
+                    boxr = boxCreate(minval, y0, width, height);
+                    score = 2 * score - width * height;
+                    goto success;
+                }
+            }
+            maxval = L_MAX(maxval, maxinrow);
+            minval = L_MIN(minval, mininrow);
+        }
+        goto failure;
+    }
+
+failure:
+    numaAddNumber(nascore, 0);
+    boxaAddBox(boxat, boxCreate(0, 0, 1, 1), L_INSERT);  /* min box */
+    LEPT_FREE(lines1);
+    return 0;
+
+success:
+    numaAddNumber(nascore, score);
+    boxaAddBox(boxat, boxr, L_INSERT);
+    LEPT_FREE(lines1);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *             Represent horizontal or vertical mosaic strips          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  makeMosaicStrips()
+ *
+ *      Input:  w, h
+ *              direction (L_SCAN_HORIZONTAL or L_SCAN_VERTICAL)
+ *              size (of strips in the scan direction)
+ *      Return: boxa, or null on error
+ *
+ *  Notes:
+ *      (1) For example, this can be used to generate a pixa of
+ *          vertical strips of width 10 from an image, using:
+ *             pixGetDimensions(pix, &w, &h, NULL);
+ *             boxa = makeMosaicStrips(w, h, L_SCAN_HORIZONTAL, 10);
+ *             pixa = pixClipRectangles(pix, boxa);
+ *          All strips except the last will be the same width.  The
+ *          last strip will have width w % 10.
+ */
+BOXA *
+makeMosaicStrips(l_int32  w,
+                 l_int32  h,
+                 l_int32  direction,
+                 l_int32  size)
+{
+l_int32  i, nstrips, extra;
+BOX     *box;
+BOXA    *boxa;
+
+    PROCNAME("makeMosaicStrips");
+
+    if (w < 1 || h < 1)
+        return (BOXA *)ERROR_PTR("invalid w or h", procName, NULL);
+    if (direction != L_SCAN_HORIZONTAL && direction != L_SCAN_VERTICAL)
+        return (BOXA *)ERROR_PTR("invalid direction", procName, NULL);
+    if (size < 1)
+        return (BOXA *)ERROR_PTR("size < 1", procName, NULL);
+
+    boxa = boxaCreate(0);
+    if (direction == L_SCAN_HORIZONTAL) {
+        nstrips = w / size;
+        for (i = 0; i < nstrips; i++) {
+            box = boxCreate(i * size, 0, size, h);
+            boxaAddBox(boxa, box, L_INSERT);
+        }
+        if ((extra = w % size) > 0) {
+            box = boxCreate(nstrips * size, 0, extra, h);
+            boxaAddBox(boxa, box, L_INSERT);
+        }
+    } else {
+        nstrips = h / size;
+        for (i = 0; i < nstrips; i++) {
+            box = boxCreate(0, i * size, w, size);
+            boxaAddBox(boxa, box, L_INSERT);
+        }
+        if ((extra = h % size) > 0) {
+            box = boxCreate(0, nstrips * size, w, extra);
+            boxaAddBox(boxa, box, L_INSERT);
+        }
+    }
+    return boxa;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Comparison between boxa                      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaCompareRegions()
+ *
+ *      Input:  boxa1, boxa2
+ *              areathresh (minimum area of boxes to be considered)
+ *              &pnsame  (<return> true if same number of boxes)
+ *              &pdiffarea (<return> fractional difference in total area)
+ *              &pdiffxor (<optional return> fractional difference
+ *                         in xor of regions)
+ *              &pixdb (<optional return> debug pix showing two boxa)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This takes 2 boxa, removes all boxes smaller than a given area,
+ *          and compares the remaining boxes between the boxa.
+ *      (2) The area threshold is introduced to help remove noise from
+ *          small components.  Any box with a smaller value of w * h
+ *          will be removed from consideration.
+ *      (3) The xor difference is the most stringent test, requiring alignment
+ *          of the corresponding boxes.  It is also more computationally
+ *          intensive and is optionally returned.  Alignment is to the
+ *          UL corner of each region containing all boxes, as given by
+ *          boxaGetExtent().
+ *      (4) Both fractional differences are with respect to the total
+ *          area in the two boxa.  They range from 0.0 to 1.0.
+ *          A perfect match has value 0.0.  If both boxa are empty,
+ *          we return 0.0; if one is empty we return 1.0.
+ *      (5) An example input might be the rectangular regions of a
+ *          segmentation mask for text or images from two pages.
+ */
+l_int32
+boxaCompareRegions(BOXA       *boxa1,
+                   BOXA       *boxa2,
+                   l_int32     areathresh,
+                   l_int32    *pnsame,
+                   l_float32  *pdiffarea,
+                   l_float32  *pdiffxor,
+                   PIX       **ppixdb)
+{
+l_int32   w, h, x3, y3, w3, h3, x4, y4, w4, h4, n3, n4, area1, area2;
+l_int32   count3, count4, countxor;
+l_int32  *tab;
+BOX      *box3, *box4;
+BOXA     *boxa3, *boxa4, *boxa3t, *boxa4t;
+PIX      *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXA     *pixa;
+
+    PROCNAME("boxaCompareRegions");
+
+    if (!pnsame)
+        return ERROR_INT("&nsame not defined", procName, 1);
+    *pnsame = FALSE;
+    if (!pdiffarea)
+        return ERROR_INT("&diffarea not defined", procName, 1);
+    *pdiffarea = 1.0;
+    if (!boxa1 || !boxa2)
+        return ERROR_INT("boxa1 and boxa2 not both defined", procName, 1);
+    if (pdiffxor) *pdiffxor = 1.0;
+    if (ppixdb) *ppixdb = NULL;
+
+    boxa3 = boxaSelectByArea(boxa1, areathresh, L_SELECT_IF_GTE, NULL);
+    boxa4 = boxaSelectByArea(boxa2, areathresh, L_SELECT_IF_GTE, NULL);
+    n3 = boxaGetCount(boxa3);
+    n4 = boxaGetCount(boxa4);
+    if (n3 == n4)
+        *pnsame = TRUE;
+
+        /* There are no boxes in one or both */
+    if (n3 == 0 || n4 == 0) {
+        boxaDestroy(&boxa3);
+        boxaDestroy(&boxa4);
+        if (n3 == 0 && n4 == 0) { /* they are both empty: we say they are the
+                                   * same; otherwise, they differ maximally
+                                   * and retain the default value. */
+            *pdiffarea = 0.0;
+            if (pdiffxor) *pdiffxor = 0.0;
+        }
+        return 0;
+    }
+
+        /* There are boxes in both */
+    boxaGetArea(boxa3, &area1);
+    boxaGetArea(boxa4, &area2);
+    *pdiffarea = (l_float32)L_ABS(area1 - area2) / (l_float32)(area1 + area2);
+    if (!pdiffxor) {
+        boxaDestroy(&boxa3);
+        boxaDestroy(&boxa4);
+        return 0;
+    }
+
+        /* The easiest way to get the xor of aligned boxes is to work
+         * with images of each boxa.  This is done by translating each
+         * boxa so that the UL corner of the region that includes all
+         * boxes in the boxa is placed at the origin of each pix. */
+    boxaGetExtent(boxa3, &w, &h, &box3);
+    boxaGetExtent(boxa4, &w, &h, &box4);
+    boxGetGeometry(box3, &x3, &y3, &w3, &h3);
+    boxGetGeometry(box4, &x4, &y4, &w4, &h4);
+    boxa3t = boxaTransform(boxa3, -x3, -y3, 1.0, 1.0);
+    boxa4t = boxaTransform(boxa4, -x4, -y4, 1.0, 1.0);
+    w = L_MAX(x3 + w3, x4 + w4);
+    h = L_MAX(y3 + h3, y4 + h4);
+    pix3 = pixCreate(w, h, 1);  /* use the max to keep everything in the xor */
+    pix4 = pixCreate(w, h, 1);
+    pixMaskBoxa(pix3, pix3, boxa3t, L_SET_PIXELS);
+    pixMaskBoxa(pix4, pix4, boxa4t, L_SET_PIXELS);
+    tab = makePixelSumTab8();
+    pixCountPixels(pix3, &count3, tab);
+    pixCountPixels(pix4, &count4, tab);
+    pix5 = pixXor(NULL, pix3, pix4);
+    pixCountPixels(pix5, &countxor, tab);
+    LEPT_FREE(tab);
+    *pdiffxor = (l_float32)countxor / (l_float32)(count3 + count4);
+
+    if (ppixdb) {
+        pixa = pixaCreate(2);
+        pix1 = pixCreate(w, h, 32);
+        pixSetAll(pix1);
+        pixRenderHashBoxaBlend(pix1, boxa3, 5, 1, L_POS_SLOPE_LINE, 2,
+                               255, 0, 0, 0.5);
+        pixRenderHashBoxaBlend(pix1, boxa4, 5, 1, L_NEG_SLOPE_LINE, 2,
+                               0, 255, 0, 0.5);
+        pixaAddPix(pixa, pix1, L_INSERT);
+        pix2 = pixCreate(w, h, 32);
+        pixPaintThroughMask(pix2, pix3, x3, y3, 0xff000000);
+        pixPaintThroughMask(pix2, pix4, x4, y4, 0x00ff0000);
+        pixAnd(pix3, pix3, pix4);
+        pixPaintThroughMask(pix2, pix3, x3, y3, 0x0000ff00);
+        pixaAddPix(pixa, pix2, L_INSERT);
+        *ppixdb = pixaDisplayTiledInRows(pixa, 32, 1000, 1.0, 0, 30, 2);
+        pixaDestroy(&pixa);
+    }
+
+    boxDestroy(&box3);
+    boxDestroy(&box4);
+    boxaDestroy(&boxa3);
+    boxaDestroy(&boxa3t);
+    boxaDestroy(&boxa4);
+    boxaDestroy(&boxa4t);
+    pixDestroy(&pix3);
+    pixDestroy(&pix4);
+    pixDestroy(&pix5);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                Reliable selection of a single large box             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixSelectLargeULComp()
+ *
+ *      Input:  pixs (1 bpp)
+ *              areaslop (fraction near but less than 1.0)
+ *              yslop (number of pixels in y direction)
+ *              connectivity (4 or 8)
+ *      Return: box, or null on error
+ *
+ *  Notes:
+ *      (1) This selects a box near the top (first) and left (second)
+ *          of the image, from the set of all boxes that have
+ *                area >= @areaslop * (area of biggest box),
+ *          where @areaslop is some fraction; say ~ 0.9.
+ *      (2) For all boxes satisfying the above condition, select
+ *          the left-most box that is within @yslop (say, 20) pixels
+ *          of the box nearest the top.
+ *      (3) This can be used to reliably select a specific one of
+ *          the largest regions in an image, for applications where
+ *          there are expected to be small variations in region size
+ *          and location.
+ *      (4) See boxSelectLargeULBox() for implementation details.
+ */
+BOX *
+pixSelectLargeULComp(PIX       *pixs,
+                     l_float32  areaslop,
+                     l_int32    yslop,
+                     l_int32    connectivity)
+{
+BOX   *box;
+BOXA  *boxa1;
+
+    PROCNAME("pixSelectLargeULComp");
+
+    if (!pixs)
+        return (BOX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (areaslop < 0.0 || areaslop > 1.0)
+        return (BOX *)ERROR_PTR("invalid value for areaslop", procName, NULL);
+    yslop = L_MAX(0, yslop);
+
+    boxa1 = pixConnCompBB(pixs, connectivity);
+    if (boxaGetCount(boxa1) == 0)
+        return NULL;
+    box = boxaSelectLargeULBox(boxa1, areaslop, yslop);
+    boxaDestroy(&boxa1);
+    return box;
+}
+
+
+/*!
+ *  boxaSelectLargeULBox()
+ *
+ *      Input:  boxa (1 bpp)
+ *              areaslop (fraction near but less than 1.0)
+ *              yslop (number of pixels in y direction)
+ *              connectivity (4 or 8)
+ *      Return: box, or null on error
+ *
+ *  Notes:
+ *      (1) See usage notes in pixSelectLargeULComp().
+ */
+BOX *
+boxaSelectLargeULBox(BOXA      *boxas,
+                     l_float32  areaslop,
+                     l_int32    yslop)
+{
+l_int32    w, h, i, n, x1, y1, x2, y2, select;
+l_float32  area, max_area;
+BOX       *box;
+BOXA      *boxa1, *boxa2, *boxa3;
+
+    PROCNAME("boxaSelectLargeULBox");
+
+    if (!boxas)
+        return (BOX *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (boxaGetCount(boxas) == 0)
+        return (BOX *)ERROR_PTR("no boxes in boxas", procName, NULL);
+    if (areaslop < 0.0 || areaslop > 1.0)
+        return (BOX *)ERROR_PTR("invalid value for areaslop", procName, NULL);
+    yslop = L_MAX(0, yslop);
+
+    boxa1 = boxaSort(boxas, L_SORT_BY_AREA, L_SORT_DECREASING, NULL);
+    boxa2 = boxaSort(boxa1, L_SORT_BY_Y, L_SORT_INCREASING, NULL);
+    n = boxaGetCount(boxa2);
+    boxaGetBoxGeometry(boxa1, 0, NULL, NULL, &w, &h);  /* biggest box by area */
+    max_area = (l_float32)(w * h);
+
+        /* boxa3 collects all boxes eligible by area, sorted top-down */
+    boxa3 = boxaCreate(4);
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa2, i, NULL, NULL, &w, &h);
+        area = (l_float32)(w * h);
+        if (area / max_area >= areaslop) {
+            box = boxaGetBox(boxa2, i, L_COPY);
+            boxaAddBox(boxa3, box, L_INSERT);
+        }
+    }
+
+        /* Take the first (top-most box) unless the second (etc) has
+         * nearly the same y value but a smaller x value. */
+    n = boxaGetCount(boxa3);
+    boxaGetBoxGeometry(boxa3, 0, &x1, &y1, NULL, NULL);
+    select = 0;
+    for (i = 1; i < n; i++) {
+        boxaGetBoxGeometry(boxa3, i, &x2, &y2, NULL, NULL);
+        if (y2 - y1 < yslop && x2 < x1) {
+            select = i;
+            x1 = x2;  /* but always compare against y1 */
+        }
+    }
+
+    box = boxaGetBox(boxa3, select, L_COPY);
+    boxaDestroy(&boxa1);
+    boxaDestroy(&boxa2);
+    boxaDestroy(&boxa3);
+    return box;
+}
+
+
diff --git a/src/boxfunc4.c b/src/boxfunc4.c
new file mode 100644 (file)
index 0000000..291166e
--- /dev/null
@@ -0,0 +1,2330 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   boxfunc4.c
+ *
+ *      Boxa and Boxaa range selection
+ *           BOXA     *boxaSelectRange()
+ *           BOXAA    *boxaaSelectRange()
+ *
+ *      Boxa size selection
+ *           BOXA     *boxaSelectBySize()
+ *           NUMA     *boxaMakeSizeIndicator()
+ *           BOXA     *boxaSelectByArea()
+ *           NUMA     *boxaMakeAreaIndicator()
+ *           BOXA     *boxaSelectWithIndicator()
+ *
+ *      Boxa permutation
+ *           BOXA     *boxaPermutePseudorandom()
+ *           BOXA     *boxaPermuteRandom()
+ *           l_int32   boxaSwapBoxes()
+ *
+ *      Boxa and box conversions
+ *           PTA      *boxaConvertToPta()
+ *           BOXA     *ptaConvertToBoxa()
+ *           PTA      *boxConvertToPta()
+ *           BOX      *ptaConvertToBox()
+ *
+ *      Boxa sequence fitting
+ *           BOXA     *boxaSmoothSequenceLS()
+ *           BOXA     *boxaSmoothSequenceMedian()
+ *           BOXA     *boxaLinearFit()
+ *           BOXA     *boxaWindowedMedian()
+ *           BOXA     *boxaModifyWithBoxa()
+ *           BOXA     *boxaConstrainSize()
+ *           BOXA     *boxaReconcileEvenOddHeight()
+ *           BOXA     *boxaReconcilePairWidth()
+ *           l_int32   boxaPlotSides()    [for debugging]
+ *           BOXA     *boxaFillSequence()
+ *    static l_int32   boxaFillAll()
+ *
+ *      Miscellaneous boxa functions
+ *           l_int32   boxaGetExtent()
+ *           l_int32   boxaGetCoverage()
+ *           l_int32   boxaaSizeRange()
+ *           l_int32   boxaSizeRange()
+ *           l_int32   boxaLocationRange()
+ *           l_int32   boxaGetArea()
+ *           PIX      *boxaDisplayTiled()
+ */
+
+#include "allheaders.h"
+
+static l_int32 boxaFillAll(BOXA *boxa);
+
+
+/*---------------------------------------------------------------------*
+ *                     Boxa and boxaa range selection                  *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaSelectRange()
+ *
+ *      Input:  boxas
+ *              first (use 0 to select from the beginning)
+ *              last (use 0 to select to the end)
+ *              copyflag (L_COPY, L_CLONE)
+ *      Return: boxad, or null on error
+ *
+ *  Notes:
+ *      (1) The copyflag specifies what we do with each box from boxas.
+ *          Specifically, L_CLONE inserts a clone into boxad of each
+ *          selected box from boxas.
+ */
+BOXA *
+boxaSelectRange(BOXA    *boxas,
+                l_int32  first,
+                l_int32  last,
+                l_int32  copyflag)
+{
+l_int32  n, nbox, i;
+BOX     *box;
+BOXA    *boxad;
+
+    PROCNAME("boxaSelectRange");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (copyflag != L_COPY && copyflag != L_CLONE)
+        return (BOXA *)ERROR_PTR("invalid copyflag", procName, NULL);
+    n = boxaGetCount(boxas);
+    first = L_MAX(0, first);
+    if (last <= 0) last = n - 1;
+    if (first >= n)
+        return (BOXA *)ERROR_PTR("invalid first", procName, NULL);
+    if (first > last)
+        return (BOXA *)ERROR_PTR("first > last", procName, NULL);
+
+    nbox = last - first + 1;
+    boxad = boxaCreate(nbox);
+    for (i = first; i <= last; i++) {
+        box = boxaGetBox(boxas, i, copyflag);
+        boxaAddBox(boxad, box, L_INSERT);
+    }
+    return boxad;
+}
+
+
+/*!
+ *  boxaaSelectRange()
+ *
+ *      Input:  baas
+ *              first (use 0 to select from the beginning)
+ *              last (use 0 to select to the end)
+ *              copyflag (L_COPY, L_CLONE)
+ *      Return: baad, or null on error
+ *
+ *  Notes:
+ *      (1) The copyflag specifies what we do with each boxa from baas.
+ *          Specifically, L_CLONE inserts a clone into baad of each
+ *          selected boxa from baas.
+ */
+BOXAA *
+boxaaSelectRange(BOXAA   *baas,
+                 l_int32  first,
+                 l_int32  last,
+                 l_int32  copyflag)
+{
+l_int32  n, nboxa, i;
+BOXA    *boxa;
+BOXAA   *baad;
+
+    PROCNAME("boxaaSelectRange");
+
+    if (!baas)
+        return (BOXAA *)ERROR_PTR("baas not defined", procName, NULL);
+    if (copyflag != L_COPY && copyflag != L_CLONE)
+        return (BOXAA *)ERROR_PTR("invalid copyflag", procName, NULL);
+    n = boxaaGetCount(baas);
+    first = L_MAX(0, first);
+    if (last <= 0) last = n - 1;
+    if (first >= n)
+        return (BOXAA *)ERROR_PTR("invalid first", procName, NULL);
+    if (first > last)
+        return (BOXAA *)ERROR_PTR("first > last", procName, NULL);
+
+    nboxa = last - first + 1;
+    baad = boxaaCreate(nboxa);
+    for (i = first; i <= last; i++) {
+        boxa = boxaaGetBoxa(baas, i, copyflag);
+        boxaaAddBoxa(baad, boxa, L_INSERT);
+    }
+    return baad;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                          Boxa size selection                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaSelectBySize()
+ *
+ *      Input:  boxas
+ *              width, height (threshold dimensions)
+ *              type (L_SELECT_WIDTH, L_SELECT_HEIGHT,
+ *                    L_SELECT_IF_EITHER, L_SELECT_IF_BOTH)
+ *              relation (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                        L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *              &changed (<optional return> 1 if changed; 0 if clone returned)
+ *      Return: boxad (filtered set), or null on error
+ *
+ *  Notes:
+ *      (1) The args specify constraints on the size of the
+ *          components that are kept.
+ *      (2) Uses box clones in the new boxa.
+ *      (3) If the selection type is L_SELECT_WIDTH, the input
+ *          height is ignored, and v.v.
+ *      (4) To keep small components, use relation = L_SELECT_IF_LT or
+ *          L_SELECT_IF_LTE.
+ *          To keep large components, use relation = L_SELECT_IF_GT or
+ *          L_SELECT_IF_GTE.
+ */
+BOXA *
+boxaSelectBySize(BOXA     *boxas,
+                 l_int32   width,
+                 l_int32   height,
+                 l_int32   type,
+                 l_int32   relation,
+                 l_int32  *pchanged)
+{
+BOXA  *boxad;
+NUMA  *na;
+
+    PROCNAME("boxaSelectBySize");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (type != L_SELECT_WIDTH && type != L_SELECT_HEIGHT &&
+        type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH)
+        return (BOXA *)ERROR_PTR("invalid type", procName, NULL);
+    if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT &&
+        relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE)
+        return (BOXA *)ERROR_PTR("invalid relation", procName, NULL);
+    if (pchanged) *pchanged = FALSE;
+
+        /* Compute the indicator array for saving components */
+    na = boxaMakeSizeIndicator(boxas, width, height, type, relation);
+
+        /* Filter to get output */
+    boxad = boxaSelectWithIndicator(boxas, na, pchanged);
+
+    numaDestroy(&na);
+    return boxad;
+}
+
+
+/*!
+ *  boxaMakeSizeIndicator()
+ *
+ *      Input:  boxa
+ *              width, height (threshold dimensions)
+ *              type (L_SELECT_WIDTH, L_SELECT_HEIGHT,
+ *                    L_SELECT_IF_EITHER, L_SELECT_IF_BOTH)
+ *              relation (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                        L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *      Return: na (indicator array), or null on error
+ *
+ *  Notes:
+ *      (1) The args specify constraints on the size of the
+ *          components that are kept.
+ *      (2) If the selection type is L_SELECT_WIDTH, the input
+ *          height is ignored, and v.v.
+ *      (3) To keep small components, use relation = L_SELECT_IF_LT or
+ *          L_SELECT_IF_LTE.
+ *          To keep large components, use relation = L_SELECT_IF_GT or
+ *          L_SELECT_IF_GTE.
+ */
+NUMA *
+boxaMakeSizeIndicator(BOXA     *boxa,
+                      l_int32   width,
+                      l_int32   height,
+                      l_int32   type,
+                      l_int32   relation)
+{
+l_int32  i, n, w, h, ival;
+NUMA    *na;
+
+    PROCNAME("boxaMakeSizeIndicator");
+
+    if (!boxa)
+        return (NUMA *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (type != L_SELECT_WIDTH && type != L_SELECT_HEIGHT &&
+        type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH)
+        return (NUMA *)ERROR_PTR("invalid type", procName, NULL);
+    if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT &&
+        relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE)
+        return (NUMA *)ERROR_PTR("invalid relation", procName, NULL);
+
+    n = boxaGetCount(boxa);
+    na = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        ival = 0;
+        boxaGetBoxGeometry(boxa, i, NULL, NULL, &w, &h);
+        switch (type)
+        {
+        case L_SELECT_WIDTH:
+            if ((relation == L_SELECT_IF_LT && w < width) ||
+                (relation == L_SELECT_IF_GT && w > width) ||
+                (relation == L_SELECT_IF_LTE && w <= width) ||
+                (relation == L_SELECT_IF_GTE && w >= width))
+                ival = 1;
+            break;
+        case L_SELECT_HEIGHT:
+            if ((relation == L_SELECT_IF_LT && h < height) ||
+                (relation == L_SELECT_IF_GT && h > height) ||
+                (relation == L_SELECT_IF_LTE && h <= height) ||
+                (relation == L_SELECT_IF_GTE && h >= height))
+                ival = 1;
+            break;
+        case L_SELECT_IF_EITHER:
+            if (((relation == L_SELECT_IF_LT) && (w < width || h < height)) ||
+                ((relation == L_SELECT_IF_GT) && (w > width || h > height)) ||
+               ((relation == L_SELECT_IF_LTE) && (w <= width || h <= height)) ||
+                ((relation == L_SELECT_IF_GTE) && (w >= width || h >= height)))
+                    ival = 1;
+            break;
+        case L_SELECT_IF_BOTH:
+            if (((relation == L_SELECT_IF_LT) && (w < width && h < height)) ||
+                ((relation == L_SELECT_IF_GT) && (w > width && h > height)) ||
+               ((relation == L_SELECT_IF_LTE) && (w <= width && h <= height)) ||
+                ((relation == L_SELECT_IF_GTE) && (w >= width && h >= height)))
+                    ival = 1;
+            break;
+        default:
+            L_WARNING("can't get here!\n", procName);
+            break;
+        }
+        numaAddNumber(na, ival);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  boxaSelectByArea()
+ *
+ *      Input:  boxas
+ *              area (threshold value of width * height)
+ *              relation (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                        L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *              &changed (<optional return> 1 if changed; 0 if clone returned)
+ *      Return: boxad (filtered set), or null on error
+ *
+ *  Notes:
+ *      (1) Uses box clones in the new boxa.
+ *      (2) To keep small components, use relation = L_SELECT_IF_LT or
+ *          L_SELECT_IF_LTE.
+ *          To keep large components, use relation = L_SELECT_IF_GT or
+ *          L_SELECT_IF_GTE.
+ */
+BOXA *
+boxaSelectByArea(BOXA     *boxas,
+                 l_int32   area,
+                 l_int32   relation,
+                 l_int32  *pchanged)
+{
+BOXA  *boxad;
+NUMA  *na;
+
+    PROCNAME("boxaSelectByArea");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT &&
+        relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE)
+        return (BOXA *)ERROR_PTR("invalid relation", procName, NULL);
+    if (pchanged) *pchanged = FALSE;
+
+        /* Compute the indicator array for saving components */
+    na = boxaMakeAreaIndicator(boxas, area, relation);
+
+        /* Filter to get output */
+    boxad = boxaSelectWithIndicator(boxas, na, pchanged);
+
+    numaDestroy(&na);
+    return boxad;
+}
+
+
+/*!
+ *  boxaMakeAreaIndicator()
+ *
+ *      Input:  boxa
+ *              area (threshold value of width * height)
+ *              relation (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                        L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *      Return: na (indicator array), or null on error
+ *
+ *  Notes:
+ *      (1) To keep small components, use relation = L_SELECT_IF_LT or
+ *          L_SELECT_IF_LTE.
+ *          To keep large components, use relation = L_SELECT_IF_GT or
+ *          L_SELECT_IF_GTE.
+ */
+NUMA *
+boxaMakeAreaIndicator(BOXA     *boxa,
+                      l_int32   area,
+                      l_int32   relation)
+{
+l_int32  i, n, w, h, ival;
+NUMA    *na;
+
+    PROCNAME("boxaMakeAreaIndicator");
+
+    if (!boxa)
+        return (NUMA *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT &&
+        relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE)
+        return (NUMA *)ERROR_PTR("invalid relation", procName, NULL);
+
+    n = boxaGetCount(boxa);
+    na = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        ival = 0;
+        boxaGetBoxGeometry(boxa, i, NULL, NULL, &w, &h);
+
+        if ((relation == L_SELECT_IF_LT && w * h < area) ||
+            (relation == L_SELECT_IF_GT && w * h > area) ||
+            (relation == L_SELECT_IF_LTE && w * h <= area) ||
+            (relation == L_SELECT_IF_GTE && w * h >= area))
+            ival = 1;
+        numaAddNumber(na, ival);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  boxaSelectWithIndicator()
+ *
+ *      Input:  boxas
+ *              na (indicator numa)
+ *              &changed (<optional return> 1 if changed; 0 if clone returned)
+ *      Return: boxad, or null on error
+ *
+ *  Notes:
+ *      (1) Returns a boxa clone if no components are removed.
+ *      (2) Uses box clones in the new boxa.
+ *      (3) The indicator numa has values 0 (ignore) and 1 (accept).
+ */
+BOXA *
+boxaSelectWithIndicator(BOXA     *boxas,
+                        NUMA     *na,
+                        l_int32  *pchanged)
+{
+l_int32  i, n, ival, nsave;
+BOX     *box;
+BOXA    *boxad;
+
+    PROCNAME("boxaSelectWithIndicator");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (!na)
+        return (BOXA *)ERROR_PTR("na not defined", procName, NULL);
+
+    nsave = 0;
+    n = numaGetCount(na);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(na, i, &ival);
+        if (ival == 1) nsave++;
+    }
+
+    if (nsave == n) {
+        if (pchanged) *pchanged = FALSE;
+        return boxaCopy(boxas, L_CLONE);
+    }
+    if (pchanged) *pchanged = TRUE;
+    boxad = boxaCreate(nsave);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(na, i, &ival);
+        if (ival == 0) continue;
+        box = boxaGetBox(boxas, i, L_CLONE);
+        boxaAddBox(boxad, box, L_INSERT);
+    }
+
+    return boxad;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                           Boxa Permutation                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaPermutePseudorandom()
+ *
+ *      Input:  boxas (input boxa)
+ *      Return: boxad (with boxes permuted), or null on error
+ *
+ *  Notes:
+ *      (1) This does a pseudorandom in-place permutation of the boxes.
+ *      (2) The result is guaranteed not to have any boxes in their
+ *          original position, but it is not very random.  If you
+ *          need randomness, use boxaPermuteRandom().
+ */
+BOXA *
+boxaPermutePseudorandom(BOXA  *boxas)
+{
+l_int32  n;
+NUMA    *na;
+BOXA    *boxad;
+
+    PROCNAME("boxaPermutePseudorandom");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxa not defined", procName, NULL);
+
+    n = boxaGetCount(boxas);
+    na = numaPseudorandomSequence(n, 0);
+    boxad = boxaSortByIndex(boxas, na);
+    numaDestroy(&na);
+    return boxad;
+}
+
+
+/*!
+ *  boxaPermuteRandom()
+ *
+ *      Input:  boxad (<optional> can be null or equal to boxas)
+ *              boxas (input boxa)
+ *      Return: boxad (with boxes permuted), or null on error
+ *
+ *  Notes:
+ *      (1) If boxad is null, make a copy of boxas and permute the copy.
+ *          Otherwise, boxad must be equal to boxas, and the operation
+ *          is done in-place.
+ *      (2) This does a random in-place permutation of the boxes,
+ *          by swapping each box in turn with a random box.  The
+ *          result is almost guaranteed not to have any boxes in their
+ *          original position.
+ *      (3) MSVC rand() has MAX_RAND = 2^15 - 1, so it will not do
+ *          a proper permutation is the number of boxes exceeds this.
+ */
+BOXA *
+boxaPermuteRandom(BOXA  *boxad,
+                  BOXA  *boxas)
+{
+l_int32  i, n, index;
+
+    PROCNAME("boxaPermuteRandom");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (boxad && (boxad != boxas))
+        return (BOXA *)ERROR_PTR("boxad defined but in-place", procName, NULL);
+
+    if (!boxad)
+        boxad = boxaCopy(boxas, L_COPY);
+    n = boxaGetCount(boxad);
+    index = (l_uint32)rand() % n;
+    index = L_MAX(1, index);
+    boxaSwapBoxes(boxad, 0, index);
+    for (i = 1; i < n; i++) {
+        index = (l_uint32)rand() % n;
+        if (index == i) index--;
+        boxaSwapBoxes(boxad, i, index);
+    }
+
+    return boxad;
+}
+
+
+/*!
+ *  boxaSwapBoxes()
+ *
+ *      Input:  boxa
+ *              i, j (two indices of boxes, that are to be swapped)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaSwapBoxes(BOXA    *boxa,
+              l_int32  i,
+              l_int32  j)
+{
+l_int32  n;
+BOX     *box;
+
+    PROCNAME("boxaSwapBoxes");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    n = boxaGetCount(boxa);
+    if (i < 0 || i >= n)
+        return ERROR_INT("i invalid", procName, 1);
+    if (j < 0 || j >= n)
+        return ERROR_INT("j invalid", procName, 1);
+    if (i == j)
+        return ERROR_INT("i == j", procName, 1);
+
+    box = boxa->box[i];
+    boxa->box[i] = boxa->box[j];
+    boxa->box[j] = box;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                     Boxa and Box Conversions                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaConvertToPta()
+ *
+ *      Input:  boxa
+ *              ncorners (2 or 4 for the representation of each box)
+ *      Return: pta (with @ncorners points for each box in the boxa),
+ *                   or null on error
+ *
+ *  Notes:
+ *      (1) If ncorners == 2, we select the UL and LR corners.
+ *          Otherwise we save all 4 corners in this order: UL, UR, LL, LR.
+ */
+PTA *
+boxaConvertToPta(BOXA    *boxa,
+                 l_int32  ncorners)
+{
+l_int32  i, n;
+BOX     *box;
+PTA     *pta, *pta1;
+
+    PROCNAME("boxaConvertToPta");
+
+    if (!boxa)
+        return (PTA *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (ncorners != 2 && ncorners != 4)
+        return (PTA *)ERROR_PTR("ncorners not 2 or 4", procName, NULL);
+
+    n = boxaGetCount(boxa);
+    if ((pta = ptaCreate(n)) == NULL)
+        return (PTA *)ERROR_PTR("pta not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_COPY);
+        pta1 = boxConvertToPta(box, ncorners);
+        ptaJoin(pta, pta1, 0, -1);
+        boxDestroy(&box);
+        ptaDestroy(&pta1);
+    }
+
+    return pta;
+}
+
+
+/*!
+ *  ptaConvertToBoxa()
+ *
+ *      Input:  pta
+ *              ncorners (2 or 4 for the representation of each box)
+ *      Return: boxa (with one box for each 2 or 4 points in the pta),
+ *                    or null on error
+ *
+ *  Notes:
+ *      (1) For 2 corners, the order of the 2 points is UL, LR.
+ *          For 4 corners, the order of points is UL, UR, LL, LR.
+ *      (2) Each derived box is the minimum size containing all corners.
+ */
+BOXA *
+ptaConvertToBoxa(PTA     *pta,
+                 l_int32  ncorners)
+{
+l_int32  i, n, nbox, x1, y1, x2, y2, x3, y3, x4, y4, x, y, xmax, ymax;
+BOX     *box;
+BOXA    *boxa;
+
+    PROCNAME("ptaConvertToBoxa");
+
+    if (!pta)
+        return (BOXA *)ERROR_PTR("pta not defined", procName, NULL);
+    if (ncorners != 2 && ncorners != 4)
+        return (BOXA *)ERROR_PTR("ncorners not 2 or 4", procName, NULL);
+    n = ptaGetCount(pta);
+    if (n % ncorners != 0)
+        return (BOXA *)ERROR_PTR("size % ncorners != 0", procName, NULL);
+    nbox = n / ncorners;
+    if ((boxa = boxaCreate(nbox)) == NULL)
+        return (BOXA *)ERROR_PTR("boxa not made", procName, NULL);
+    for (i = 0; i < n; i += ncorners) {
+        ptaGetIPt(pta, i, &x1, &y1);
+        ptaGetIPt(pta, i + 1, &x2, &y2);
+        if (ncorners == 2) {
+            box = boxCreate(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
+            boxaAddBox(boxa, box, L_INSERT);
+            continue;
+        }
+        ptaGetIPt(pta, i + 2, &x3, &y3);
+        ptaGetIPt(pta, i + 3, &x4, &y4);
+        x = L_MIN(x1, x3);
+        y = L_MIN(y1, y2);
+        xmax = L_MAX(x2, x4);
+        ymax = L_MAX(y3, y4);
+        box = boxCreate(x, y, xmax - x + 1, ymax - y + 1);
+        boxaAddBox(boxa, box, L_INSERT);
+    }
+
+    return boxa;
+}
+
+
+/*!
+ *  boxConvertToPta()
+ *
+ *      Input:  box
+ *              ncorners (2 or 4 for the representation of the box)
+ *      Return: pta (with @ncorners points), or null on error
+ *
+ *  Notes:
+ *      (1) If ncorners == 2, we select the UL and LR corners.
+ *          Otherwise we save all 4 corners in this order: UL, UR, LL, LR.
+ */
+PTA *
+boxConvertToPta(BOX     *box,
+                l_int32  ncorners)
+{
+l_int32  x, y, w, h;
+PTA     *pta;
+
+    PROCNAME("boxConvertToPta");
+
+    if (!box)
+        return (PTA *)ERROR_PTR("box not defined", procName, NULL);
+    if (ncorners != 2 && ncorners != 4)
+        return (PTA *)ERROR_PTR("ncorners not 2 or 4", procName, NULL);
+
+    if ((pta = ptaCreate(ncorners)) == NULL)
+        return (PTA *)ERROR_PTR("pta not made", procName, NULL);
+    boxGetGeometry(box, &x, &y, &w, &h);
+    ptaAddPt(pta, x, y);
+    if (ncorners == 2) {
+        ptaAddPt(pta, x + w - 1, y + h - 1);
+    } else {
+        ptaAddPt(pta, x + w - 1, y);
+        ptaAddPt(pta, x, y + h - 1);
+        ptaAddPt(pta, x + w - 1, y + h - 1);
+    }
+
+    return pta;
+}
+
+
+/*!
+ *  ptaConvertToBox()
+ *
+ *      Input:  pta
+ *      Return: box (minimum containing all points in the pta), or null on error
+ *
+ *  Notes:
+ *      (1) For 2 corners, the order of the 2 points is UL, LR.
+ *          For 4 corners, the order of points is UL, UR, LL, LR.
+ */
+BOX *
+ptaConvertToBox(PTA  *pta)
+{
+l_int32  n, x1, y1, x2, y2, x3, y3, x4, y4, x, y, xmax, ymax;
+
+    PROCNAME("ptaConvertToBox");
+
+    if (!pta)
+        return (BOX *)ERROR_PTR("pta not defined", procName, NULL);
+    n = ptaGetCount(pta);
+    ptaGetIPt(pta, 0, &x1, &y1);
+    ptaGetIPt(pta, 1, &x2, &y2);
+    if (n == 2)
+        return boxCreate(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
+
+        /* 4 corners */
+    ptaGetIPt(pta, 2, &x3, &y3);
+    ptaGetIPt(pta, 3, &x4, &y4);
+    x = L_MIN(x1, x3);
+    y = L_MIN(y1, y2);
+    xmax = L_MAX(x2, x4);
+    ymax = L_MAX(y3, y4);
+    return boxCreate(x, y, xmax - x + 1, ymax - y + 1);
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Boxa sequence fitting                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaSmoothSequenceLS()
+ *
+ *      Input:  boxas (source boxa)
+ *              factor (reject outliers with widths and heights deviating
+ *                      from the median by more than @factor times
+ *                      the median variation from the median; typically ~3)
+ *              subflag (L_USE_MINSIZE, L_USE_MAXSIZE, L_SUB_ON_BIG_DIFF,
+ *                       L_USE_CAPPED_MIN or L_USE_CAPPED_MAX)
+ *              maxdiff (parameter used with L_SUB_ON_BIG_DIFF and
+ *                       L_USE_CAPPED_MAX)
+ *              debug (1 for debug output)
+ *      Return: boxad (fitted boxa), or null on error
+ *
+ *  Notes:
+ *      (1) This returns a modified version of @boxas by constructing
+ *          for each input box a box that has been linear least square fit
+ *          (LSF) to the entire set.  The linear fitting is done to each of
+ *          the box sides independently, after outliers are rejected,
+ *          and it is computed separately for sequences of even and
+ *          odd boxes.  Once the linear LSF box is found, the output box
+ *          (in @boxad) is constructed from the input box and the LSF
+ *          box, depending on @subflag.  See boxaModifyWithBoxa() for
+ *          details on the use of @subflag and @maxdiff.
+ *      (2) This is useful if, in both the even and odd sets, the box
+ *          edges vary roughly linearly with its index in the set.
+ */
+BOXA *
+boxaSmoothSequenceLS(BOXA      *boxas,
+                     l_float32  factor,
+                     l_int32    subflag,
+                     l_int32    maxdiff,
+                     l_int32    debug)
+{
+l_int32  n;
+BOXA    *boxae, *boxao, *boxalfe, *boxalfo, *boxame, *boxamo, *boxad;
+
+    PROCNAME("boxaSmoothSequenceLS");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (factor <= 0.0) {
+        L_WARNING("factor must be > 0.0; returning copy\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+    if (maxdiff < 0) {
+        L_WARNING("maxdiff must be >= 0; returning copy\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+    if (subflag != L_USE_MINSIZE && subflag != L_USE_MAXSIZE &&
+        subflag != L_SUB_ON_BIG_DIFF && subflag != L_USE_CAPPED_MIN &&
+        subflag != L_USE_CAPPED_MAX) {
+        L_WARNING("invalid subflag; returning copy\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+    if ((n = boxaGetCount(boxas)) < 4) {
+        L_WARNING("need at least 4 boxes; returning copy\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+
+    boxaSplitEvenOdd(boxas, 1, &boxae, &boxao);
+    if (debug) {
+        lept_mkdir("smooth");
+        boxaWrite("/tmp/smooth/boxae.ba", boxae);
+        boxaWrite("/tmp/smooth/boxao.ba", boxao);
+    }
+
+    boxalfe = boxaLinearFit(boxae, factor, debug);
+    boxalfo = boxaLinearFit(boxao, factor, debug);
+    if (debug) {
+        boxaWrite("/tmp/smooth/boxalfe.ba", boxalfe);
+        boxaWrite("/tmp/smooth/boxalfo.ba", boxalfo);
+    }
+
+    boxame = boxaModifyWithBoxa(boxae, boxalfe, subflag, maxdiff);
+    boxamo = boxaModifyWithBoxa(boxao, boxalfo, subflag, maxdiff);
+    if (debug) {
+        boxaWrite("/tmp/smooth/boxame.ba", boxame);
+        boxaWrite("/tmp/smooth/boxamo.ba", boxamo);
+    }
+
+    boxad = boxaMergeEvenOdd(boxame, boxamo, 1);
+    boxaDestroy(&boxae);
+    boxaDestroy(&boxao);
+    boxaDestroy(&boxalfe);
+    boxaDestroy(&boxalfo);
+    boxaDestroy(&boxame);
+    boxaDestroy(&boxamo);
+    return boxad;
+}
+
+
+/*!
+ *  boxaSmoothSequenceMedian()
+ *
+ *      Input:  boxas (source boxa)
+ *              halfwin (half-width of sliding window; used to find median)
+ *              subflag (L_USE_MINSIZE, L_USE_MAXSIZE, L_SUB_ON_BIG_DIFF,
+ *                       L_USE_CAPPED_MIN or L_USE_CAPPED_MAX)
+ *              maxdiff (parameter used with L_SUB_ON_BIG_DIFF,
+ *                       L_USE_CAPPED_MIN and L_USE_CAPPED_MAX)
+ *              debug (1 for debug output)
+ *      Return: boxad (fitted boxa), or null on error
+ *
+ *  Notes:
+ *      (1) The target width of the sliding window is 2 * @halfwin + 1.
+ *          If necessary, this will be reduced by boxaWindowedMedian().
+ *      (2) This returns a modified version of @boxas by constructing
+ *          for each input box a box that has been smoothed with windowed
+ *          median filtering.  The filtering is done to each of the
+ *          box sides independently, and it is computed separately for
+ *          sequences of even and odd boxes.  The output @boxad is
+ *          constructed from the input box and the filtered boxa,
+ *          box, depending on @subflag.  See boxaModifyWithBoxa() for
+ *          details on the use of @subflag and @maxdiff.
+ *      (3) This is useful for removing noise separately in the even
+ *          and odd sets, where the box edge locations can have
+ *          discontinuities but otherwise vary roughly linearly within
+ *          intervals of size @halfwin or larger.
+ *      (4) If you don't need to handle even and odd sets separately,
+ *          just do this:
+ *              boxam = boxaWindowedMedian(boxas, halfwin, debug);
+ *              boxad = boxaModifyWithBoxa(boxas, boxam, subflag, maxdiff);
+ *              boxaDestroy(&boxam);
+ */
+BOXA *
+boxaSmoothSequenceMedian(BOXA    *boxas,
+                         l_int32  halfwin,
+                         l_int32  subflag,
+                         l_int32  maxdiff,
+                         l_int32  debug)
+{
+l_int32  n;
+BOXA    *boxae, *boxao, *boxamede, *boxamedo, *boxame, *boxamo, *boxad;
+
+    PROCNAME("boxaSmoothSequenceMedian");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (halfwin <= 0) {
+        L_WARNING("halfwin must be > 0; returning copy\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+    if (maxdiff < 0) {
+        L_WARNING("maxdiff must be >= 0; returning copy\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+    if (subflag != L_USE_MINSIZE && subflag != L_USE_MAXSIZE &&
+        subflag != L_SUB_ON_BIG_DIFF && subflag != L_USE_CAPPED_MIN &&
+        subflag != L_USE_CAPPED_MAX) {
+        L_WARNING("invalid subflag; returning copy\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+    if ((n = boxaGetCount(boxas)) < 6) {
+        L_WARNING("need at least 6 boxes; returning copy\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+
+    boxaSplitEvenOdd(boxas, 0, &boxae, &boxao);
+    if (debug) {
+        lept_mkdir("smooth");
+        boxaWrite("/tmp/smooth/boxae.ba", boxae);
+        boxaWrite("/tmp/smooth/boxao.ba", boxao);
+    }
+
+    boxamede = boxaWindowedMedian(boxae, halfwin, debug);
+    boxamedo = boxaWindowedMedian(boxao, halfwin, debug);
+    if (debug) {
+        boxaWrite("/tmp/smooth/boxamede.ba", boxamede);
+        boxaWrite("/tmp/smooth/boxamedo.ba", boxamedo);
+    }
+
+    boxame = boxaModifyWithBoxa(boxae, boxamede, subflag, maxdiff);
+    boxamo = boxaModifyWithBoxa(boxao, boxamedo, subflag, maxdiff);
+    if (debug) {
+        boxaWrite("/tmp/smooth/boxame.ba", boxame);
+        boxaWrite("/tmp/smooth/boxamo.ba", boxamo);
+    }
+
+    boxad = boxaMergeEvenOdd(boxame, boxamo, 0);
+    if (debug) {
+        boxaPlotSides(boxas, NULL, NULL, NULL, NULL, NULL, GPLOT_X11);
+        boxaPlotSides(boxad, NULL, NULL, NULL, NULL, NULL, GPLOT_X11);
+    }
+
+    boxaDestroy(&boxae);
+    boxaDestroy(&boxao);
+    boxaDestroy(&boxamede);
+    boxaDestroy(&boxamedo);
+    boxaDestroy(&boxame);
+    boxaDestroy(&boxamo);
+    return boxad;
+}
+
+
+/*!
+ *  boxaLinearFit()
+ *
+ *      Input:  boxas (source boxa)
+ *              factor (reject outliers with widths and heights deviating
+ *                      from the median by more than @factor times
+ *                      the median deviation from the median; typically ~3)
+ *              debug (1 for debug output)
+ *      Return: boxad (fitted boxa), or null on error
+ *
+ *  Notes:
+ *      (1) This finds a set of boxes (boxad) where each edge of each box is
+ *          a linear least square fit (LSF) to the edges of the
+ *          input set of boxes (boxas).  Before fitting, outliers in
+ *          the boxes in boxas are removed (see below).
+ *      (2) This is useful when each of the box edges in boxas are expected
+ *          to vary linearly with box index in the set.  These could
+ *          be, for example, noisy measurements of similar regions
+ *          on successive scanned pages.
+ *      (3) Method: there are 2 steps:
+ *          (a) Find and remove outliers, separately based on the deviation
+ *              from the median of the width and height of the box.
+ *              Use @factor to specify tolerance to outliers; use a very
+ *              large value of @factor to avoid rejecting any box sides
+ *              in the linear LSF.
+ *          (b) On the remaining boxes, do a linear LSF independently
+ *              for each of the four sides.
+ *      (4) Invalid input boxes are not used in computation of the LSF.
+ *      (5) The returned boxad can then be used in boxaModifyWithBoxa()
+ *          to selectively change the boxes in boxas.
+ */
+BOXA *
+boxaLinearFit(BOXA      *boxas,
+              l_float32  factor,
+              l_int32    debug)
+{
+l_int32    n, i, w, h, lval, tval, rval, bval, rejectlr, rejecttb;
+l_float32  al, bl, at, bt, ar, br, ab, bb;  /* LSF coefficients */
+l_float32  medw, medh, medvarw, medvarh;
+BOX       *box, *boxempty;
+BOXA      *boxalr, *boxatb, *boxad;
+NUMA      *naw, *nah;
+PTA       *ptal, *ptat, *ptar, *ptab;
+
+    PROCNAME("boxaLinearFit");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if ((n = boxaGetCount(boxas)) < 2)
+        return (BOXA *)ERROR_PTR("need at least 2 boxes", procName, NULL);
+
+        /* Remove outliers based on width and height.
+         * First find the median width and the median deviation from
+         * the median width.  Ditto for the height. */
+    boxaExtractAsNuma(boxas, NULL, NULL, NULL, NULL, &naw, &nah, 0);
+    numaGetMedianVariation(naw, &medw, &medvarw);
+    numaGetMedianVariation(nah, &medh, &medvarh);
+    numaDestroy(&naw);
+    numaDestroy(&nah);
+
+    if (debug) {
+        fprintf(stderr, "medw = %7.3f, medvarw = %7.3f\n", medw, medvarw);
+        fprintf(stderr, "medh = %7.3f, medvarh = %7.3f\n", medh, medvarh);
+    }
+
+        /* To fit the left and right sides, only use boxes whose
+         * width is within (factor * medvarw) of the median width.
+         * Ditto for the top and bottom sides.  Add empty boxes
+         * in as placeholders so that the index remains the same
+         * as in boxas. */
+    boxalr = boxaCreate(n);
+    boxatb = boxaCreate(n);
+    boxempty = boxCreate(0, 0, 0, 0);  /* placeholders */
+    rejectlr = rejecttb = 0;
+    for (i = 0; i < n; i++) {
+        if ((box = boxaGetValidBox(boxas, i, L_CLONE)) == NULL) {
+            boxaAddBox(boxalr, boxempty, L_COPY);
+            boxaAddBox(boxatb, boxempty, L_COPY);
+            continue;
+        }
+        boxGetGeometry(box, NULL, NULL, &w, &h);
+        if (L_ABS(w - medw) <= factor * medvarw) {
+            boxaAddBox(boxalr, box, L_COPY);
+        } else {
+            rejectlr++;
+            boxaAddBox(boxalr, boxempty, L_COPY);
+        }
+        if (L_ABS(h - medh) <= factor * medvarh) {
+            boxaAddBox(boxatb, box, L_COPY);
+        } else {
+            rejecttb++;
+            boxaAddBox(boxatb, boxempty, L_COPY);
+        }
+        boxDestroy(&box);
+    }
+    boxDestroy(&boxempty);
+    if (boxaGetCount(boxalr) < 2 || boxaGetCount(boxatb) < 2) {
+        boxaDestroy(&boxalr);
+        boxaDestroy(&boxatb);
+        return (BOXA *)ERROR_PTR("need at least 2 valid boxes", procName, NULL);
+    }
+
+    if (debug) {
+        L_INFO("# lr reject = %d, # tb reject = %d\n", procName,
+               rejectlr, rejecttb);
+        lept_mkdir("linfit");
+        boxaWrite("/tmp/linfit/boxalr.ba", boxalr);
+        boxaWrite("/tmp/linfit/boxatb.ba", boxatb);
+    }
+
+        /* Extract the valid left and right box sides, along with the box
+         * index, from boxalr.  This only extracts pts corresponding to
+         * valid boxes.  Ditto: top and bottom sides from boxatb. */
+    boxaExtractAsPta(boxalr, &ptal, NULL, &ptar, NULL, NULL, NULL, 0);
+    boxaExtractAsPta(boxatb, NULL, &ptat, NULL, &ptab, NULL, NULL, 0);
+    boxaDestroy(&boxalr);
+    boxaDestroy(&boxatb);
+
+    if (debug) {
+        ptaWrite("/tmp/linfit/ptal.pta", ptal, 1);
+        ptaWrite("/tmp/linfit/ptar.pta", ptar, 1);
+        ptaWrite("/tmp/linfit/ptat.pta", ptat, 1);
+        ptaWrite("/tmp/linfit/ptab.pta", ptab, 1);
+    }
+
+        /* Do a linear LSF fit to the points that are width and height
+         * validated.  Because we've eliminated the outliers, there is no
+         * need to use ptaNoisyLinearLSF(ptal, factor, NULL, &al, &bl, ...) */
+    ptaGetLinearLSF(ptal, &al, &bl, NULL);
+    ptaGetLinearLSF(ptat, &at, &bt, NULL);
+    ptaGetLinearLSF(ptar, &ar, &br, NULL);
+    ptaGetLinearLSF(ptab, &ab, &bb, NULL);
+
+        /* Return the LSF smoothed values, interleaved with invalid
+         * boxes when the corresponding box in boxas is invalid. */
+    boxad = boxaCreate(n);
+    boxempty = boxCreate(0, 0, 0, 0);  /* use for placeholders */
+    for (i = 0; i < n; i++) {
+        lval = (l_int32)(al * i + bl + 0.5);
+        tval = (l_int32)(at * i + bt + 0.5);
+        rval = (l_int32)(ar * i + br + 0.5);
+        bval = (l_int32)(ab * i + bb + 0.5);
+        if ((box = boxaGetValidBox(boxas, i, L_CLONE)) == NULL) {
+            boxaAddBox(boxad, boxempty, L_COPY);
+        } else {
+            boxDestroy(&box);
+            box = boxCreate(lval, tval, rval - lval + 1, bval - tval + 1);
+            boxaAddBox(boxad, box, L_INSERT);
+        }
+    }
+    boxDestroy(&boxempty);
+
+    if (debug)
+        boxaPlotSides(boxad, NULL, NULL, NULL, NULL, NULL, GPLOT_X11);
+
+    ptaDestroy(&ptal);
+    ptaDestroy(&ptat);
+    ptaDestroy(&ptar);
+    ptaDestroy(&ptab);
+    return boxad;
+}
+
+
+/*!
+ *  boxaWindowedMedian()
+ *
+ *      Input:  boxas (source boxa)
+ *              halfwin (half width of window over which the median is found)
+ *              debug (1 for debug output)
+ *      Return: boxad (smoothed boxa), or null on error
+ *
+ *  Notes:
+ *      (1) This finds a set of boxes (boxad) where each edge of each box is
+ *          a windowed median smoothed value to the edges of the
+ *          input set of boxes (boxas).
+ *      (2) Invalid input boxes are filled from nearby ones.
+ *      (3) The returned boxad can then be used in boxaModifyWithBoxa()
+ *          to selectively change the boxes in the source boxa.
+ */
+BOXA *
+boxaWindowedMedian(BOXA    *boxas,
+                   l_int32  halfwin,
+                   l_int32  debug)
+{
+l_int32  n, i, left, top, right, bot;
+BOX     *box;
+BOXA    *boxaf, *boxad;
+NUMA    *nal, *nat, *nar, *nab, *naml, *namt, *namr, *namb;
+
+    PROCNAME("boxaWindowedMedian");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if ((n = boxaGetCount(boxas)) < 3) {
+        L_WARNING("less than 3 boxes; returning a copy\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+    if (halfwin <= 0) {
+        L_WARNING("halfwin must be > 0; returning copy\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+
+        /* Fill invalid boxes in the input sequence */
+    if ((boxaf = boxaFillSequence(boxas, L_USE_ALL_BOXES, debug)) == NULL)
+        return (BOXA *)ERROR_PTR("filled boxa not made", procName, NULL);
+
+        /* Get the windowed median output from each of the sides */
+    boxaExtractAsNuma(boxaf, &nal, &nat, &nar, &nab, NULL, NULL, 0);
+    naml = numaWindowedMedian(nal, halfwin);
+    namt = numaWindowedMedian(nat, halfwin);
+    namr = numaWindowedMedian(nar, halfwin);
+    namb = numaWindowedMedian(nab, halfwin);
+
+    n = boxaGetCount(boxaf);
+    boxad = boxaCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(naml, i, &left);
+        numaGetIValue(namt, i, &top);
+        numaGetIValue(namr, i, &right);
+        numaGetIValue(namb, i, &bot);
+        box = boxCreate(left, top, right - left + 1, bot - top + 1);
+        boxaAddBox(boxad, box, L_INSERT);
+    }
+
+    if (debug) {
+        boxaPlotSides(boxaf, NULL, NULL, NULL, NULL, NULL, GPLOT_X11);
+        boxaPlotSides(boxad, NULL, NULL, NULL, NULL, NULL, GPLOT_X11);
+    }
+
+    boxaDestroy(&boxaf);
+    numaDestroy(&nal);
+    numaDestroy(&nat);
+    numaDestroy(&nar);
+    numaDestroy(&nab);
+    numaDestroy(&naml);
+    numaDestroy(&namt);
+    numaDestroy(&namr);
+    numaDestroy(&namb);
+    return boxad;
+}
+
+
+/*!
+ *  boxaModifyWithBoxa()
+ *
+ *      Input:  boxas
+ *              boxam (boxa with boxes used to modify those in boxas)
+ *              subflag (L_USE_MINSIZE, L_USE_MAXSIZE, L_SUB_ON_BIG_DIFF,
+ *                       L_USE_CAPPED_MIN or L_USE_CAPPED_MAX)
+ *              maxdiff (parameter used with L_SUB_ON_BIG_DIFF,
+ *                       L_USE_CAPPED_MIN and L_USE_CAPPED_MAX)
+ *      Return: boxad (result after adjusting boxes in boxas), or null
+ *                     on error.
+ *
+ *  Notes:
+ *      (1) This takes two input boxa (boxas, boxam) and constructs boxad,
+ *          where each box in boxad is generated from the corresponding
+ *          boxes in boxas and boxam.  The rule for constructing each
+ *          output box depends on @subflag and @maxdiff.  Let boxs be
+ *          a box from @boxas and boxm be a box from @boxam.
+ *          If @subflag == L_USE_MINSIZE, the output box is the intersection
+ *          of the two input boxes.
+ *          If @subflag == L_USE_MAXSIZE, the output box is the union of the
+ *          two input boxes; i.e., the minimum bounding rectangle for the
+ *          two input boxes.
+ *          For the last two flags, each side of the output box is found
+ *          separately from the corresponding side of boxs and boxm,
+ *          according to these rules, where "smaller"("bigger") mean in a
+ *          direction that decreases(increases) the size of the output box:
+ *            If @subflag == L_SUB_ON_BIG_DIFF, use boxs if within
+ *            @maxdiff pixels of boxm; otherwise, use boxm.
+ *            If @subflag == L_USE_CAPPED_MIN, use the Min of boxm
+ *            with the Max of (boxs, boxm +- @maxdiff), where the sign
+ *            is adjusted to make the box smaller (e.g., use "+" on left side).
+ *            If @subflag == L_USE_CAPPED_MAX, use the Max of boxm
+ *            with the Min of (boxs, boxm +- @maxdiff), where the sign
+ *            is adjusted to make the box bigger (e.g., use "-" on left side).
+ *          Use of the last 2 flags is further explained in (3) and (4).
+ *      (2) boxas and boxam must be the same size.  If boxam == NULL,
+ *          this returns a copy of boxas with a warning.
+ *      (3) If @subflag == L_SUB_ON_BIG_DIFF, use boxm for each side
+ *          where the corresponding sides differ by more than @maxdiff.
+ *          Two extreme cases:
+ *          (a) set @maxdiff == 0 to use only values from boxam in boxad.
+ *          (b) set @maxdiff == 10000 to ignore all values from boxam;
+ *              then boxad will be the same as boxas.
+ *      (4) If @subflag == L_USE_CAPPED_MAX: use boxm if boxs is smaller;
+ *          use boxs if boxs is bigger than boxm by an amount up to @maxdiff;
+ *          and use boxm +- @maxdiff (the 'capped' value) if boxs is
+ *          bigger than boxm by an amount larger than @maxdiff.
+ *          Similarly, with interchange of Min/Max and sign of @maxdiff,
+ *          for @subflag == L_USE_CAPPED_MIN.
+ *      (5) If either of corresponding boxes in boxas and boxam is invalid,
+ *          an invalid box is copied to the result.
+ *      (6) Typical input for boxam may be the output of boxaLinearFit().
+ *          where outliers have been removed and each side is LS fit to a line.
+ *      (7) Unlike boxaAdjustWidthToTarget() and boxaAdjustHeightToTarget(),
+ *          this is not dependent on a difference threshold to change the size.
+ *          Additional constraints on the size of each box can be enforced
+ *          by following this operation with boxaConstrainSize(), taking
+ *          boxad as input.
+ */
+BOXA *
+boxaModifyWithBoxa(BOXA    *boxas,
+                   BOXA    *boxam,
+                   l_int32  subflag,
+                   l_int32  maxdiff)
+{
+l_int32  n, i, ls, ts, rs, bs, ws, hs, lm, tm, rm, bm, wm, hm, ld, td, rd, bd;
+BOX     *boxs, *boxm, *boxd, *boxempty;
+BOXA    *boxad;
+
+    PROCNAME("boxaModifyWithBoxa");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (!boxam) {
+        L_WARNING("boxam not defined; returning copy", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+    if (subflag != L_USE_MINSIZE && subflag != L_USE_MAXSIZE &&
+        subflag != L_SUB_ON_BIG_DIFF && subflag != L_USE_CAPPED_MIN &&
+        subflag != L_USE_CAPPED_MAX) {
+        L_WARNING("invalid subflag; returning copy", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+    n = boxaGetCount(boxas);
+    if (n != boxaGetCount(boxam)) {
+        L_WARNING("boxas and boxam sizes differ; returning copy", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+
+    boxad = boxaCreate(n);
+    boxempty = boxCreate(0, 0, 0, 0);  /* placeholders */
+    for (i = 0; i < n; i++) {
+        boxs = boxaGetValidBox(boxas, i, L_CLONE);
+        boxm = boxaGetValidBox(boxam, i, L_CLONE);
+        if (!boxs || !boxm) {
+            boxaAddBox(boxad, boxempty, L_COPY);
+        } else {
+            boxGetGeometry(boxs, &ls, &ts, &ws, &hs);
+            boxGetGeometry(boxm, &lm, &tm, &wm, &hm);
+            rs = ls + ws - 1;
+            bs = ts + hs - 1;
+            rm = lm + wm - 1;
+            bm = tm + hm - 1;
+            if (subflag == L_USE_MINSIZE) {
+                ld = L_MAX(ls, lm);
+                rd = L_MIN(rs, rm);
+                td = L_MAX(ts, tm);
+                bd = L_MIN(bs, bm);
+            } else if (subflag == L_USE_MAXSIZE) {
+                ld = L_MIN(ls, lm);
+                rd = L_MAX(rs, rm);
+                td = L_MIN(ts, tm);
+                bd = L_MAX(bs, bm);
+            } else if (subflag == L_SUB_ON_BIG_DIFF) {
+                ld = (L_ABS(lm - ls) <= maxdiff) ? ls : lm;
+                td = (L_ABS(tm - ts) <= maxdiff) ? ts : tm;
+                rd = (L_ABS(rm - rs) <= maxdiff) ? rs : rm;
+                bd = (L_ABS(bm - bs) <= maxdiff) ? bs : bm;
+            } else if (subflag == L_USE_CAPPED_MIN) {
+                ld = L_MAX(lm, L_MIN(ls, lm + maxdiff));
+                td = L_MAX(tm, L_MIN(ts, tm + maxdiff));
+                rd = L_MIN(rm, L_MAX(rs, rm - maxdiff));
+                bd = L_MIN(bm, L_MAX(bs, bm - maxdiff));
+            } else {  /* subflag == L_USE_CAPPED_MAX */
+                ld = L_MIN(lm, L_MAX(ls, lm - maxdiff));
+                td = L_MIN(tm, L_MAX(ts, tm - maxdiff));
+                rd = L_MAX(rm, L_MIN(rs, rm + maxdiff));
+                bd = L_MAX(bm, L_MIN(bs, bm + maxdiff));
+            }
+            boxd = boxCreate(ld, td, rd - ld + 1, bd - td + 1);
+            boxaAddBox(boxad, boxd, L_INSERT);
+        }
+        boxDestroy(&boxs);
+        boxDestroy(&boxm);
+    }
+    boxDestroy(&boxempty);
+
+    return boxad;
+}
+
+
+/*!
+ *  boxaConstrainSize()
+ *
+ *      Input:  boxas
+ *              width (force width of all boxes to this size;
+ *                     input 0 to use the median width)
+ *              widthflag (L_ADJUST_SKIP, L_ADJUST_LEFT, L_ADJUST_RIGHT,
+ *                         or L_ADJUST_LEFT_AND_RIGHT)
+ *              height (force height of all boxes to this size;
+ *                      input 0 to use the median height)
+ *              heightflag (L_ADJUST_SKIP, L_ADJUST_TOP, L_ADJUST_BOT,
+ *                          or L_ADJUST_TOP_AND_BOT)
+ *      Return: boxad (adjusted so all boxes are the same size)
+ *
+ *  Notes:
+ *      (1) Forces either width or height (or both) of every box in
+ *          the boxa to a specified size, by moving the indicated sides.
+ *      (2) All input boxes should be valid.  Median values will be
+ *          used with invalid boxes.
+ *      (3) Typical input might be the output of boxaLinearFit(),
+ *          where each side has been fit.
+ *      (4) Unlike boxaAdjustWidthToTarget() and boxaAdjustHeightToTarget(),
+ *          this is not dependent on a difference threshold to change the size.
+ */
+BOXA *
+boxaConstrainSize(BOXA    *boxas,
+                  l_int32  width,
+                  l_int32  widthflag,
+                  l_int32  height,
+                  l_int32  heightflag)
+{
+l_int32  n, i, w, h, delw, delh, del_left, del_right, del_top, del_bot;
+BOX     *medbox, *boxs, *boxd;
+BOXA    *boxad;
+
+    PROCNAME("boxaConstrainSize");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+
+        /* Use median value if requested */
+    medbox = boxaGetMedian(boxas);
+    if (width == 0 || height == 0) {
+        boxGetGeometry(medbox, NULL, NULL, &w, &h);
+        if (width == 0)
+            width = w;
+        if (height == 0)
+            height = h;
+    }
+
+    n = boxaGetCount(boxas);
+    boxad = boxaCreate(n);
+    for (i = 0; i < n; i++) {
+        boxs = boxaGetValidBox(boxas, i, L_CLONE);
+        if (!boxs) {
+            L_ERROR("invalid box %d; using median\n", procName, i);
+            boxs = boxCopy(medbox);
+        }
+        boxGetGeometry(boxs, NULL, NULL, &w, &h);
+        delw = width - w;
+        delh = height - h;
+        del_left = del_right = del_top = del_bot = 0;
+        if (widthflag == L_ADJUST_LEFT) {
+            del_left = -delw;
+        } else if (widthflag == L_ADJUST_RIGHT) {
+            del_right = delw;
+        } else {
+            del_left = -delw / 2;
+            del_right = delw / 2 + L_SIGN(delw) * (delw & 1);
+        }
+        if (heightflag == L_ADJUST_TOP) {
+            del_top = -delh;
+        } else if (heightflag == L_ADJUST_BOT) {
+            del_bot = delh;
+        } else {
+            del_top = -delh / 2;
+            del_bot = delh / 2 + L_SIGN(delh) * (delh & 1);
+        }
+        boxd = boxAdjustSides(NULL, boxs, del_left, del_right,
+                              del_top, del_bot);
+        boxaAddBox(boxad, boxd, L_INSERT);
+        boxDestroy(&boxs);
+    }
+
+    boxDestroy(&medbox);
+    return boxad;
+}
+
+
+/*!
+ *  boxaReconcileEvenOddHeight()
+ *
+ *      Input:  boxas (containing at least 3 valid boxes in even and odd)
+ *              sides (L_ADJUST_TOP, L_ADJUST_BOT, L_ADJUST_TOP_AND_BOT)
+ *              delh (threshold on median height difference)
+ *              op (L_ADJUST_CHOOSE_MIN, L_ADJUST_CHOOSE_MAX)
+ *              factor (> 0.0, typically near 1.0)
+ *      Return: boxad (adjusted), or a copy of boxas on error
+ *
+ *  Notes:
+ *      (1) The basic idea is to reconcile differences in box height
+ *          in the even and odd boxes, by moving the top and/or bottom
+ *          edges in the even and odd boxes.  Choose the edge or edges
+ *          to be moved, whether to adjust the boxes with the min
+ *          or the max of the medians, and the threshold on the median
+ *          difference between even and odd box heights for the operations
+ *          to take place.  The same threshold is also used to
+ *          determine if each individual box edge is to be adjusted.
+ *      (2) Boxes are conditionally reset with either the same top (y)
+ *          value or the same bottom value, or both.  The value is
+ *          determined by the greater or lesser of the medians of the
+ *          even and odd boxes, with the choice depending on the value
+ *          of @op, which selects for either min or max median height.
+ *          If the median difference between even and odd boxes is
+ *          greater than @dely, then any individual box edge that differs
+ *          from the selected median by more than @dely is set to
+ *          the selected median times a factor typically near 1.0.
+ *      (3) Note that if selecting for minimum height, you will choose
+ *          the largest y-value for the top and the smallest y-value for
+ *          the bottom of the box.
+ *      (4) Typical input might be the output of boxaSmoothSequence(),
+ *          where even and odd boxa have been independently regulated.
+ *      (5) Require at least 3 valid even boxes and 3 valid odd boxes.
+ *          Median values will be used for invalid boxes.
+ */
+BOXA *
+boxaReconcileEvenOddHeight(BOXA      *boxas,
+                           l_int32    sides,
+                           l_int32    delh,
+                           l_int32    op,
+                           l_float32  factor)
+{
+l_int32  n, ne, no, he, ho, hmed, doeven;
+BOX     *boxe, *boxo;
+BOXA    *boxae, *boxao, *boxa1e, *boxa1o, *boxad;
+
+    PROCNAME("boxaReconcileEvenOddHeight");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (sides != L_ADJUST_TOP && sides != L_ADJUST_BOT &&
+        sides != L_ADJUST_TOP_AND_BOT) {
+        L_WARNING("no action requested; returning copy\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+    if ((n = boxaGetValidCount(boxas)) < 6) {
+        L_WARNING("need at least 6 valid boxes; returning copy\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+    if (factor <= 0.0) {
+        L_WARNING("invalid factor; setting to 1.0\n", procName);
+        factor = 1.0;
+    }
+
+        /* Require at least 3 valid boxes of both types */
+    boxaSplitEvenOdd(boxas, 0, &boxae, &boxao);
+    if (boxaGetValidCount(boxae) < 3 || boxaGetValidCount(boxao) < 3) {
+        boxaDestroy(&boxae);
+        boxaDestroy(&boxao);
+        return boxaCopy(boxas, L_COPY);
+    }
+    ne = boxaGetCount(boxae);
+    no = boxaGetCount(boxao);
+
+        /* Get the median heights for each set */
+    boxa1e = boxaSort(boxae, L_SORT_BY_HEIGHT, L_SORT_INCREASING, NULL);
+    boxa1o = boxaSort(boxao, L_SORT_BY_HEIGHT, L_SORT_INCREASING, NULL);
+    boxe = boxaGetBox(boxa1e, ne / 2, L_COPY);  /* median ht even boxes */
+    boxo = boxaGetBox(boxa1o, no / 2, L_COPY);  /* median ht odd boxes */
+    boxGetGeometry(boxe, NULL, NULL, NULL, &he);
+    boxGetGeometry(boxo, NULL, NULL, NULL, &ho);
+    boxaDestroy(&boxa1e);
+    boxaDestroy(&boxa1o);
+    boxDestroy(&boxe);
+    boxDestroy(&boxo);
+    L_INFO("median he = %d, median ho = %d\n", procName, he, ho);
+
+        /* If the difference in median height reaches the threshold @delh,
+         * only adjust the side(s) of one of the sets.  If we choose
+         * the minimum median height as the target, allow the target
+         * to be scaled by a factor, typically near 1.0, of the
+         * minimum median height.  And similarly if the target is
+         * the maximum median height. */
+    if (L_ABS(he - ho) > delh) {
+        if (op == L_ADJUST_CHOOSE_MIN) {
+            doeven = (ho < he) ? TRUE : FALSE;
+            hmed = (l_int32)(factor * L_MIN(he, ho));
+            hmed = L_MIN(hmed, L_MAX(he, ho));  /* don't make it bigger! */
+        } else {  /* max height */
+            doeven = (ho > he) ? TRUE : FALSE;
+            hmed = (l_int32)(factor * L_MAX(he, ho));
+            hmed = L_MAX(hmed, L_MIN(he, ho));  /* don't make it smaller! */
+        }
+        if (doeven) boxaAdjustHeightToTarget(boxae, boxae, sides, hmed, delh);
+        if (!doeven) boxaAdjustHeightToTarget(boxao, boxao, sides, hmed, delh);
+    }
+
+    boxad = boxaMergeEvenOdd(boxae, boxao, 0);
+    boxaDestroy(&boxae);
+    boxaDestroy(&boxao);
+    return boxad;
+}
+
+
+/*!
+ *  boxaReconcilePairWidth()
+ *
+ *      Input:  boxas
+ *              delw (threshold on adjacent width difference)
+ *              op (L_ADJUST_CHOOSE_MIN, L_ADJUST_CHOOSE_MAX)
+ *              factor (> 0.0, typically near 1.0)
+ *              na (<optional> indicator array allowing change)
+ *      Return: boxad (adjusted), or a copy of boxas on error
+ *
+ *  Notes:
+ *      (1) This reconciles differences in the width of adjacent boxes,
+ *          by moving one side of one of the boxes in each pair.
+ *          If the widths in the pair differ by more than some
+ *          threshold, move either the left side for even boxes or
+ *          the right side for odd boxes, depending on if we're choosing
+ *          the min or max.  If choosing min, the width of the max is
+ *          set to factor * (width of min).  If choosing max, the width
+ *          of the min is set to factor * (width of max).
+ *      (2) If @na exists, it is an indicator array corresponding to the
+ *          boxes in @boxas.  If @na != NULL, only boxes with an
+ *          indicator value of 1 are allowed to adjust; otherwise,
+ *          all boxes can adjust.
+ *      (3) Typical input might be the output of boxaSmoothSequence(),
+ *          where even and odd boxa have been independently regulated.
+ */
+BOXA *
+boxaReconcilePairWidth(BOXA      *boxas,
+                       l_int32    delw,
+                       l_int32    op,
+                       l_float32  factor,
+                       NUMA      *na)
+{
+l_int32  i, ne, no, nmin, xe, we, xo, wo, inde, indo, x, w;
+BOX     *boxe, *boxo;
+BOXA    *boxae, *boxao, *boxad;
+
+    PROCNAME("boxaReconcilePairWidth");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (factor <= 0.0) {
+        L_WARNING("invalid factor; setting to 1.0\n", procName);
+        factor = 1.0;
+    }
+
+        /* Taking the boxes in pairs, if the difference in width reaches
+         * the threshold @delw, adjust the left or right side of one
+         * of the pair. */
+    boxaSplitEvenOdd(boxas, 0, &boxae, &boxao);
+    ne = boxaGetCount(boxae);
+    no = boxaGetCount(boxao);
+    nmin = L_MIN(ne, no);
+    for (i = 0; i < nmin; i++) {
+            /* Set indicator values */
+        if (na) {
+            numaGetIValue(na, 2 * i, &inde);
+            numaGetIValue(na, 2 * i + 1, &indo);
+        } else {
+            inde = indo = 1;
+        }
+        if (inde == 0 && indo == 0) continue;
+
+        boxe = boxaGetBox(boxae, i, L_CLONE);
+        boxo = boxaGetBox(boxao, i, L_CLONE);
+        boxGetGeometry(boxe, &xe, NULL, &we, NULL);
+        boxGetGeometry(boxo, &xo, NULL, &wo, NULL);
+        if (we == 0 || wo == 0) {  /* if either is invalid; skip */
+            boxDestroy(&boxe);
+            boxDestroy(&boxo);
+            continue;
+        } else if (L_ABS(we - wo) > delw) {
+            if (op == L_ADJUST_CHOOSE_MIN) {
+                if (we > wo && inde == 1) {
+                        /* move left side of even to the right */
+                    w = factor * wo;
+                    x = xe + (we - w);
+                    boxSetGeometry(boxe, x, -1, w, -1);
+                } else if (we < wo && indo == 1) {
+                        /* move right side of odd to the left */
+                    w = factor * we;
+                    boxSetGeometry(boxo, -1, -1, w, -1);
+                }
+            } else {  /* maximize width */
+                if (we < wo && inde == 1) {
+                        /* move left side of even to the left */
+                    w = factor * wo;
+                    x = L_MAX(0, xe + (we - w));
+                    w = we + (xe - x);  /* covers both cases for the max */
+                    boxSetGeometry(boxe, x, -1, w, -1);
+                } else if (we > wo && indo == 1) {
+                        /* move right side of odd to the right */
+                    w = factor * we;
+                    boxSetGeometry(boxo, -1, -1, w, -1);
+                }
+            }
+        }
+        boxDestroy(&boxe);
+        boxDestroy(&boxo);
+    }
+
+    boxad = boxaMergeEvenOdd(boxae, boxao, 0);
+    boxaDestroy(&boxae);
+    boxaDestroy(&boxao);
+    return boxad;
+}
+
+
+/*!
+ *  boxaPlotSides()
+ *
+ *      Input:  boxas (source boxa)
+ *              plotname (<optional>, can be NULL)
+ *              &nal (<optional return> na of left sides)
+ *              &nat (<optional return> na of top sides)
+ *              &nar (<optional return> na of right sides)
+ *              &nab (<optional return> na of bottom sides)
+ *              outformat (GPLOT_NONE for no output; GPLOT_PNG for png, etc)
+ *               ut
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a debugging function to show the progression of
+ *          the four sides in the boxes.  There must be at least 2 boxes.
+ *      (2) If there are invalid boxes (e.g., if only even or odd
+ *          indices have valid boxes), this will fill them with the
+ *          nearest valid box before plotting.
+ *      (3) The plotfiles are put in /tmp/plotsides, and are named either
+ *          with @plotname or, if NULL, a default name.
+ */
+l_int32
+boxaPlotSides(BOXA        *boxa,
+              const char  *plotname,
+              NUMA       **pnal,
+              NUMA       **pnat,
+              NUMA       **pnar,
+              NUMA       **pnab,
+              l_int32      outformat)
+{
+char            buf[128];
+static l_int32  plotid = 0;
+l_int32         n, i, w, h, left, top, right, bot;
+BOX            *box;
+BOXA           *boxat;
+GPLOT          *gplot;
+NUMA           *nal, *nat, *nar, *nab;
+
+    PROCNAME("boxaPlotSides");
+
+    if (pnal) *pnal = NULL;
+    if (pnat) *pnat = NULL;
+    if (pnar) *pnar = NULL;
+    if (pnab) *pnab = NULL;
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if ((n = boxaGetCount(boxa)) < 2)
+        return ERROR_INT("less than 2 boxes", procName, 1);
+
+    boxat = boxaFillSequence(boxa, L_USE_ALL_BOXES, 0);
+
+        /* Build the numas for each side */
+    nal = numaCreate(n);
+    nat = numaCreate(n);
+    nar = numaCreate(n);
+    nab = numaCreate(n);
+
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxat, i, L_CLONE);
+        boxGetGeometry(box, &left, &top, &w, &h);
+        right = left + w - 1;
+        bot = top + h - 1;
+        numaAddNumber(nal, left);
+        numaAddNumber(nat, top);
+        numaAddNumber(nar, right);
+        numaAddNumber(nab, bot);
+        boxDestroy(&box);
+    }
+    boxaDestroy(&boxat);
+
+        /* Plot them */
+    if (outformat < 0 || outformat > GPLOT_LATEX) {
+        L_ERROR("invalid gplot format\n", procName);
+        outformat = 0;
+    }
+
+    if (outformat > 0) {
+        lept_mkdir("plotsides");
+        if (plotname)
+            snprintf(buf, sizeof(buf), "/tmp/plotsides/%s", plotname);
+        else
+            snprintf(buf, sizeof(buf), "/tmp/plotsides/sides.%d", plotid++);
+        gplot = gplotCreate(buf, outformat, "Box sides vs. box index",
+                            "box index", "box location");
+        gplotAddPlot(gplot, NULL, nal, GPLOT_LINES, "left side");
+        gplotAddPlot(gplot, NULL, nat, GPLOT_LINES, "top side");
+        gplotAddPlot(gplot, NULL, nar, GPLOT_LINES, "right side");
+        gplotAddPlot(gplot, NULL, nab, GPLOT_LINES, "bottom side");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+    }
+
+    if (pnal)
+        *pnal = nal;
+    else
+        numaDestroy(&nal);
+    if (pnat)
+        *pnat = nat;
+    else
+        numaDestroy(&nat);
+    if (pnar)
+        *pnar = nar;
+    else
+        numaDestroy(&nar);
+    if (pnab)
+        *pnab = nab;
+    else
+        numaDestroy(&nab);
+    return 0;
+}
+
+
+/*!
+ *  boxaFillSequence()
+ *
+ *      Input:  boxas (with at least 3 boxes)
+ *              useflag (L_USE_ALL_BOXES, L_USE_SAME_PARITY_BOXES)
+ *              debug (1 for debug output)
+ *      Return: boxad (filled boxa), or null on error
+ *
+ *  Notes:
+ *      (1) This simple function replaces invalid boxes with a copy of
+ *          the nearest valid box, selected from either the entire
+ *          sequence (L_USE_ALL_BOXES) or from the boxes with the
+ *          same parity (L_USE_SAME_PARITY_BOXES).  It returns a new boxa.
+ *      (2) This is useful if you expect boxes in the sequence to
+ *          vary slowly with index.
+ */
+BOXA *
+boxaFillSequence(BOXA    *boxas,
+                 l_int32  useflag,
+                 l_int32  debug)
+{
+l_int32  n, nv;
+BOXA    *boxae, *boxao, *boxad;
+
+    PROCNAME("boxaFillSequence");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (useflag != L_USE_ALL_BOXES && useflag != L_USE_SAME_PARITY_BOXES)
+        return (BOXA *)ERROR_PTR("invalid useflag", procName, NULL);
+
+    n = boxaGetCount(boxas);
+    nv = boxaGetValidCount(boxas);
+    if (n == nv)
+        return boxaCopy(boxas, L_COPY);  /* all valid */
+    if (debug)
+        L_INFO("%d valid boxes, %d invalid boxes\n", procName, nv, n - nv);
+    if (useflag == L_USE_SAME_PARITY_BOXES && n < 3) {
+        L_WARNING("n < 3; some invalid\n", procName);
+        return boxaCopy(boxas, L_COPY);
+    }
+
+    if (useflag == L_USE_ALL_BOXES) {
+        boxad = boxaCopy(boxas, L_COPY);
+        boxaFillAll(boxad);
+    } else {
+        boxaSplitEvenOdd(boxas, 0, &boxae, &boxao);
+        boxaFillAll(boxae);
+        boxaFillAll(boxao);
+        boxad = boxaMergeEvenOdd(boxae, boxao, 0);
+        boxaDestroy(&boxae);
+        boxaDestroy(&boxao);
+    }
+
+    nv = boxaGetValidCount(boxad);
+    if (n != nv)
+        L_WARNING("there are still %d invalid boxes\n", procName, n - nv);
+
+    return boxad;
+}
+
+
+/*!
+ *  boxaFillAll()
+ *
+ *      Input:  boxa
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This static function replaces every invalid box with the
+ *          nearest valid box.  If there are no valid boxes, it
+ *          issues a warning.
+ */
+static l_int32
+boxaFillAll(BOXA  *boxa)
+{
+l_int32   n, nv, i, j, spandown, spanup;
+l_int32  *indic;
+BOX      *box, *boxt;
+
+    PROCNAME("boxaFillAll");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    n = boxaGetCount(boxa);
+    nv = boxaGetValidCount(boxa);
+    if (n == nv) return 0;
+    if (nv == 0) {
+        L_WARNING("no valid boxes out of %d boxes\n", procName, n);
+        return 0;
+    }
+
+        /* Make indicator array for valid boxes */
+    if ((indic = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32))) == NULL)
+        return ERROR_INT("indic not made", procName, 1);
+    for (i = 0; i < n; i++) {
+        box = boxaGetValidBox(boxa, i, L_CLONE);
+        if (box)
+            indic[i] = 1;
+        boxDestroy(&box);
+    }
+
+        /* Replace invalid boxes with the nearest valid one */
+    for (i = 0; i < n; i++) {
+        box = boxaGetValidBox(boxa, i, L_CLONE);
+        if (!box) {
+            spandown = spanup = 10000000;
+            for (j = i - 1; j >= 0; j--) {
+                if (indic[j] == 1) {
+                    spandown = i - j;
+                    break;
+                }
+            }
+            for (j = i + 1; j < n; j++) {
+                if (indic[j] == 1) {
+                    spanup = j - i;
+                    break;
+                }
+            }
+            if (spandown < spanup)
+                boxt = boxaGetBox(boxa, i - spandown, L_COPY);
+            else
+                boxt = boxaGetBox(boxa, i + spanup, L_COPY);
+            boxaReplaceBox(boxa, i, boxt);
+        }
+        boxDestroy(&box);
+    }
+
+    LEPT_FREE(indic);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                    Miscellaneous Boxa functions                     *
+ *---------------------------------------------------------------------*/
+/*!
+ *  boxaGetExtent()
+ *
+ *      Input:  boxa
+ *              &w  (<optional return> width)
+ *              &h  (<optional return> height)
+ *              &box (<optional return>, minimum box containing all boxes
+ *                    in boxa)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The returned w and h are the minimum size image
+ *          that would contain all boxes untranslated.
+ *      (2) If there are no valid boxes, returned w and h are 0 and
+ *          all parameters in the returned box are 0.  This
+ *          is not an error, because an empty boxa is valid and
+ *          boxaGetExtent() is required for serialization.
+ */
+l_int32
+boxaGetExtent(BOXA     *boxa,
+              l_int32  *pw,
+              l_int32  *ph,
+              BOX     **pbox)
+{
+l_int32  i, n, x, y, w, h, xmax, ymax, xmin, ymin, found;
+
+    PROCNAME("boxaGetExtent");
+
+    if (!pw && !ph && !pbox)
+        return ERROR_INT("no ptrs defined", procName, 1);
+    if (pbox) *pbox = NULL;
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    n = boxaGetCount(boxa);
+    xmax = ymax = 0;
+    xmin = ymin = 100000000;
+    found = FALSE;
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h);
+        if (w <= 0 || h <= 0)
+            continue;
+        found = TRUE;
+        xmin = L_MIN(xmin, x);
+        ymin = L_MIN(ymin, y);
+        xmax = L_MAX(xmax, x + w);
+        ymax = L_MAX(ymax, y + h);
+    }
+    if (found == FALSE)  /* no valid boxes in boxa */
+        xmin = ymin = 0;
+    if (pw) *pw = xmax;
+    if (ph) *ph = ymax;
+    if (pbox)
+      *pbox = boxCreate(xmin, ymin, xmax - xmin, ymax - ymin);
+
+    return 0;
+}
+
+
+/*!
+ *  boxaGetCoverage()
+ *
+ *      Input:  boxa
+ *              wc, hc (dimensions of overall clipping rectangle with UL
+ *                      corner at (0, 0) that is covered by the boxes.
+ *              exactflag (1 for guaranteeing an exact result; 0 for getting
+ *                         an exact result only if the boxes do not overlap)
+ *              &fract (<return> sum of box area as fraction of w * h)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The boxes in boxa are clipped to the input rectangle.
+ *      (2) * When @exactflag == 1, we generate a 1 bpp pix of size
+ *            wc x hc, paint all the boxes black, and count the fg pixels.
+ *            This can take 1 msec on a large page with many boxes.
+ *          * When @exactflag == 0, we clip each box to the wc x hc region
+ *            and sum the resulting areas.  This is faster.
+ *          * The results are the same when none of the boxes overlap
+ *            within the wc x hc region.
+ */
+l_int32
+boxaGetCoverage(BOXA       *boxa,
+                l_int32     wc,
+                l_int32     hc,
+                l_int32     exactflag,
+                l_float32  *pfract)
+{
+l_int32  i, n, x, y, w, h, sum;
+BOX     *box, *boxc;
+PIX     *pixt;
+
+    PROCNAME("boxaGetCoverage");
+
+    if (!pfract)
+        return ERROR_INT("&fract not defined", procName, 1);
+    *pfract = 0.0;
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    n = boxaGetCount(boxa);
+    if (n == 0)
+        return ERROR_INT("no boxes in boxa", procName, 1);
+
+    if (exactflag == 0) {  /* quick and dirty */
+        sum = 0;
+        for (i = 0; i < n; i++) {
+            box = boxaGetBox(boxa, i, L_CLONE);
+            if ((boxc = boxClipToRectangle(box, wc, hc)) != NULL) {
+                boxGetGeometry(boxc, NULL, NULL, &w, &h);
+                sum += w * h;
+                boxDestroy(&boxc);
+            }
+            boxDestroy(&box);
+        }
+    } else {  /* slower and exact */
+        pixt = pixCreate(wc, hc, 1);
+        for (i = 0; i < n; i++) {
+            box = boxaGetBox(boxa, i, L_CLONE);
+            boxGetGeometry(box, &x, &y, &w, &h);
+            pixRasterop(pixt, x, y, w, h, PIX_SET, NULL, 0, 0);
+            boxDestroy(&box);
+        }
+        pixCountPixels(pixt, &sum, NULL);
+        pixDestroy(&pixt);
+    }
+
+    *pfract = (l_float32)sum / (l_float32)(wc * hc);
+    return 0;
+}
+
+
+/*!
+ *  boxaaSizeRange()
+ *
+ *      Input:  baa
+ *              &minw, &minh, &maxw, &maxh (<optional return> range of
+ *                                          dimensions of all boxes)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaaSizeRange(BOXAA    *baa,
+               l_int32  *pminw,
+               l_int32  *pminh,
+               l_int32  *pmaxw,
+               l_int32  *pmaxh)
+{
+l_int32  minw, minh, maxw, maxh, minbw, minbh, maxbw, maxbh, i, n;
+BOXA    *boxa;
+
+    PROCNAME("boxaaSizeRange");
+
+    if (!baa)
+        return ERROR_INT("baa not defined", procName, 1);
+    if (!pminw && !pmaxw && !pminh && !pmaxh)
+        return ERROR_INT("no data can be returned", procName, 1);
+
+    minw = minh = 100000000;
+    maxw = maxh = 0;
+    n = boxaaGetCount(baa);
+    for (i = 0; i < n; i++) {
+        boxa = boxaaGetBoxa(baa, i, L_CLONE);
+        boxaSizeRange(boxa, &minbw, &minbh, &maxbw, &maxbh);
+        if (minbw < minw)
+            minw = minbw;
+        if (minbh < minh)
+            minh = minbh;
+        if (maxbw > maxw)
+            maxw = maxbw;
+        if (maxbh > maxh)
+            maxh = maxbh;
+    }
+
+    if (pminw) *pminw = minw;
+    if (pminh) *pminh = minh;
+    if (pmaxw) *pmaxw = maxw;
+    if (pmaxh) *pmaxh = maxh;
+    return 0;
+}
+
+
+/*!
+ *  boxaSizeRange()
+ *
+ *      Input:  boxa
+ *              &minw, &minh, &maxw, &maxh (<optional return> range of
+ *                                          dimensions of box in the array)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaSizeRange(BOXA     *boxa,
+              l_int32  *pminw,
+              l_int32  *pminh,
+              l_int32  *pmaxw,
+              l_int32  *pmaxh)
+{
+l_int32  minw, minh, maxw, maxh, i, n, w, h;
+
+    PROCNAME("boxaSizeRange");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (!pminw && !pmaxw && !pminh && !pmaxh)
+        return ERROR_INT("no data can be returned", procName, 1);
+
+    minw = minh = 100000000;
+    maxw = maxh = 0;
+    n = boxaGetCount(boxa);
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, NULL, NULL, &w, &h);
+        if (w < minw)
+            minw = w;
+        if (h < minh)
+            minh = h;
+        if (w > maxw)
+            maxw = w;
+        if (h > maxh)
+            maxh = h;
+    }
+
+    if (pminw) *pminw = minw;
+    if (pminh) *pminh = minh;
+    if (pmaxw) *pmaxw = maxw;
+    if (pmaxh) *pmaxh = maxh;
+    return 0;
+}
+
+
+/*!
+ *  boxaLocationRange()
+ *
+ *      Input:  boxa
+ *              &minx, &miny, &maxx, &maxy (<optional return> range of
+ *                                          UL corner positions)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+boxaLocationRange(BOXA     *boxa,
+                  l_int32  *pminx,
+                  l_int32  *pminy,
+                  l_int32  *pmaxx,
+                  l_int32  *pmaxy)
+{
+l_int32  minx, miny, maxx, maxy, i, n, x, y;
+
+    PROCNAME("boxaLocationRange");
+
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (!pminx && !pminy && !pmaxx && !pmaxy)
+        return ERROR_INT("no data can be returned", procName, 1);
+
+    minx = miny = 100000000;
+    maxx = maxy = 0;
+    n = boxaGetCount(boxa);
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, &x, &y, NULL, NULL);
+        if (x < minx)
+            minx = x;
+        if (y < miny)
+            miny = y;
+        if (x > maxx)
+            maxx = x;
+        if (y > maxy)
+            maxy = y;
+    }
+
+    if (pminx) *pminx = minx;
+    if (pminy) *pminy = miny;
+    if (pmaxx) *pmaxx = maxx;
+    if (pmaxy) *pmaxy = maxy;
+
+    return 0;
+}
+
+
+/*!
+ *  boxaGetArea()
+ *
+ *      Input:  boxa
+ *              &area (<return> total area of all boxes)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Measures the total area of the boxes, without regard to overlaps.
+ */
+l_int32
+boxaGetArea(BOXA     *boxa,
+            l_int32  *parea)
+{
+l_int32  i, n, w, h;
+
+    PROCNAME("boxaGetArea");
+
+    if (!parea)
+        return ERROR_INT("&area not defined", procName, 1);
+    *parea = 0;
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    n = boxaGetCount(boxa);
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, NULL, NULL, &w, &h);
+        *parea += w * h;
+    }
+    return 0;
+}
+
+
+/*!
+ *  boxaDisplayTiled()
+ *
+ *      Input:  boxa
+ *              pixa (<optional> background for each box)
+ *              maxwidth (of output image)
+ *              linewidth (width of box outlines, before scaling)
+ *              scalefactor (applied to every box; use 1.0 for no scaling)
+ *              background (0 for white, 1 for black; this is the color
+ *                          of the spacing between the images)
+ *              spacing  (between images, and on outside)
+ *              border (width of black border added to each image;
+ *                      use 0 for no border)
+ *              fontdir (<optional> can be NULL; use to number the boxes)
+ *      Return: pixd (of tiled images of boxes), or null on error
+ *
+ *  Notes:
+ *      (1) Displays each box separately in a tiled 32 bpp image.
+ *      (2) If pixa is defined, it must have the same count as the boxa,
+ *          and it will be a background over with each box is rendered.
+ *          If pixa is not defined, the boxes will be rendered over
+ *          blank images of identical size.
+ *      (3) See pixaDisplayTiledInRows() for other parameters.
+ */
+PIX *
+boxaDisplayTiled(BOXA        *boxas,
+                 PIXA        *pixa,
+                 l_int32      maxwidth,
+                 l_int32      linewidth,
+                 l_float32    scalefactor,
+                 l_int32      background,
+                 l_int32      spacing,
+                 l_int32      border,
+                 const char  *fontdir)
+{
+char     buf[32];
+l_int32  i, n, npix, w, h, fontsize;
+L_BMF   *bmf;
+BOX     *box;
+BOXA    *boxa;
+PIX     *pix1, *pix2, *pixd;
+PIXA    *pixat;
+
+    PROCNAME("boxaDisplayTiled");
+
+    if (!boxas)
+        return (PIX *)ERROR_PTR("boxas not defined", procName, NULL);
+
+    boxa = boxaSaveValid(boxas, L_COPY);
+    n = boxaGetCount(boxa);
+    if (pixa) {
+        npix = pixaGetCount(pixa);
+        if (n != npix) {
+            boxaDestroy(&boxa);
+            return (PIX *)ERROR_PTR("boxa and pixa counts differ",
+                                    procName, NULL);
+        }
+    }
+
+        /* Because the bitmap font will be reduced when tiled, choose the
+         * font size inversely with the scale factor. */
+    if (scalefactor > 0.8)
+        fontsize = 6;
+    else if (scalefactor > 0.6)
+        fontsize = 10;
+    else if (scalefactor > 0.4)
+        fontsize = 14;
+    else if (scalefactor > 0.3)
+        fontsize = 18;
+    else fontsize = 20;
+    bmf = NULL;
+    if (fontdir) {
+        if ((bmf = bmfCreate(fontdir, fontsize)) == NULL) {
+            L_ERROR("can't find fonts; skipping them\n", procName);
+            fontdir = NULL;
+        }
+    }
+
+    pixat = pixaCreate(n);
+    boxaGetExtent(boxa, &w, &h, NULL);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        if (!pixa) {
+            pix1 = pixCreate(w, h, 32);
+            pixSetAll(pix1);
+        } else {
+            pix1 = pixaGetPix(pixa, i, L_COPY);
+        }
+        if (fontdir) {
+            pixSetBorderVal(pix1, 0, 0, 0, 2, 0x0000ff00);
+            snprintf(buf, sizeof(buf), "%d", i);
+            pix2 = pixAddSingleTextblock(pix1, bmf, buf, 0x00ff0000,
+                                         L_ADD_BELOW, NULL);
+        } else {
+            pix2 = pixClone(pix1);
+        }
+        pixDestroy(&pix1);
+        pixRenderBoxArb(pix2, box, linewidth, 255, 0, 0);
+        pixaAddPix(pixat, pix2, L_INSERT);
+        boxDestroy(&box);
+    }
+    bmfDestroy(&bmf);
+    boxaDestroy(&boxa);
+
+    pixd = pixaDisplayTiledInRows(pixat, 32, maxwidth, scalefactor, background,
+                                  spacing, border);
+    pixaDestroy(&pixat);
+    return pixd;
+}
+
diff --git a/src/bytearray.c b/src/bytearray.c
new file mode 100644 (file)
index 0000000..59cb42c
--- /dev/null
@@ -0,0 +1,620 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   bytearray.c
+ *
+ *   Functions for handling byte arrays, in analogy with C++ 'strings'
+ *
+ *      Creation, copy, clone, destruction
+ *           L_BYTEA      *l_byteaCreate()
+ *           L_BYTEA      *l_byteaInitFromMem()
+ *           L_BYTEA      *l_byteaInitFromFile()
+ *           L_BYTEA      *l_byteaInitFromStream()
+ *           L_BYTEA      *l_byteaCopy()
+ *           L_BYTEA      *l_byteaClone()
+ *           void          l_byteaDestroy()
+ *
+ *      Accessors
+ *           size_t        l_byteaGetSize()
+ *           l_uint8      *l_byteaGetData()
+ *           l_uint8      *l_byteaCopyData()
+ *
+ *      Appending
+ *           l_int32       l_byteaAppendData()
+ *           l_int32       l_byteaAppendString()
+ *           static l_int32  l_byteaExtendArrayToSize()
+ *
+ *      Join/Split
+ *           l_int32       l_byteaJoin()
+ *           l_int32       l_byteaSplit()
+ *
+ *      Search
+ *           l_int32       l_byteaFindEachSequence()
+ *
+ *      Output to file
+ *           l_int32       l_byteaWrite()
+ *           l_int32       l_byteaWriteStream()
+ *
+ *   The internal data array is always null-terminated, for ease of use
+ *   in the event that it is an ascii string without null bytes.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  INITIAL_ARRAYSIZE = 200;   /* n'import quoi */
+
+    /* Static function */
+static l_int32 l_byteaExtendArrayToSize(L_BYTEA *ba, size_t size);
+
+
+/*---------------------------------------------------------------------*
+ *                  Creation, copy, clone, destruction                 *
+ *---------------------------------------------------------------------*/
+/*!
+ *  l_byteaCreate()
+ *
+ *      Input:  n (determines initial size of data array)
+ *      Return: l_bytea, or null on error
+ *
+ *  Notes:
+ *      (1) The allocated array is n + 1 bytes.  This allows room
+ *          for null termination.
+ */
+L_BYTEA *
+l_byteaCreate(size_t  nbytes)
+{
+L_BYTEA  *ba;
+
+    PROCNAME("l_byteaCreate");
+
+    if (nbytes <= 0)
+        nbytes = INITIAL_ARRAYSIZE;
+
+    if ((ba = (L_BYTEA *)LEPT_CALLOC(1, sizeof(L_BYTEA))) == NULL)
+        return (L_BYTEA *)ERROR_PTR("ba not made", procName, NULL);
+
+    if ((ba->data = (l_uint8 *)LEPT_CALLOC(nbytes + 1, sizeof(l_uint8))) == NULL)
+        return (L_BYTEA *)ERROR_PTR("ba array not made", procName, NULL);
+    ba->nalloc = nbytes + 1;
+    ba->refcount = 1;
+
+    return ba;
+}
+
+
+/*!
+ *  l_byteaInitFromMem()
+ *
+ *      Input:  data (to be copied to the array)
+ *              size (amount of data)
+ *      Return: l_bytea, or null on error
+ */
+L_BYTEA *
+l_byteaInitFromMem(l_uint8  *data,
+                   size_t    size)
+{
+L_BYTEA  *ba;
+
+    PROCNAME("l_byteaInitFromMem");
+
+    if (!data)
+        return (L_BYTEA *)ERROR_PTR("data not defined", procName, NULL);
+    if (size <= 0)
+        return (L_BYTEA *)ERROR_PTR("no bytes to initialize", procName, NULL);
+
+    if ((ba = l_byteaCreate(size)) == NULL)
+        return (L_BYTEA *)ERROR_PTR("ba not made", procName, NULL);
+    memcpy(ba->data, data, size);
+    ba->size = size;
+    return ba;
+}
+
+
+/*!
+ *  l_byteaInitFromFile()
+ *
+ *      Input:  fname
+ *      Return: l_bytea, or null on error
+ */
+L_BYTEA *
+l_byteaInitFromFile(const char  *fname)
+{
+FILE     *fp;
+L_BYTEA  *ba;
+
+    PROCNAME("l_byteaInitFromFile");
+
+    if (!fname)
+        return (L_BYTEA *)ERROR_PTR("fname not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(fname)) == NULL)
+        return (L_BYTEA *)ERROR_PTR("file stream not opened", procName, NULL);
+    if ((ba = l_byteaInitFromStream(fp)) == NULL)
+        return (L_BYTEA *)ERROR_PTR("ba not made", procName, NULL);
+    fclose(fp);
+    return ba;
+}
+
+
+/*!
+ *  l_byteaInitFromStream()
+ *
+ *      Input:  stream
+ *      Return: l_bytea, or null on error
+ */
+L_BYTEA *
+l_byteaInitFromStream(FILE  *fp)
+{
+l_uint8  *data;
+size_t    nbytes;
+L_BYTEA  *ba;
+
+    PROCNAME("l_byteaInitFromStream");
+
+    if (!fp)
+        return (L_BYTEA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if ((data = l_binaryReadStream(fp, &nbytes)) == NULL)
+        return (L_BYTEA *)ERROR_PTR("data not read", procName, NULL);
+    if ((ba = l_byteaCreate(nbytes)) == NULL)
+        return (L_BYTEA *)ERROR_PTR("ba not made", procName, NULL);
+    memcpy(ba->data, data, nbytes);
+    ba->size = nbytes;
+    LEPT_FREE(data);
+    return ba;
+}
+
+
+/*!
+ *  l_byteaCopy()
+ *
+ *      Input:  bas  (source lba)
+ *              copyflag (L_COPY, L_CLONE)
+ *      Return: clone or copy of bas, or null on error
+ *
+ *  Notes:
+ *      (1) If cloning, up the refcount and return a ptr to @bas.
+ */
+L_BYTEA *
+l_byteaCopy(L_BYTEA  *bas,
+            l_int32   copyflag)
+{
+    PROCNAME("l_byteaCopy");
+
+    if (!bas)
+        return (L_BYTEA *)ERROR_PTR("bas not defined", procName, NULL);
+
+    if (copyflag == L_CLONE) {
+        bas->refcount++;
+        return bas;
+    }
+
+    return l_byteaInitFromMem(bas->data, bas->size);
+}
+
+
+/*!
+ *  l_byteaDestroy()
+ *
+ *      Input:  &ba (<will be set to null before returning>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Decrements the ref count and, if 0, destroys the lba.
+ *      (2) Always nulls the input ptr.
+ *      (3) If the data has been previously removed, the lba will
+ *          have been nulled, so this will do nothing.
+ */
+void
+l_byteaDestroy(L_BYTEA  **pba)
+{
+L_BYTEA  *ba;
+
+    PROCNAME("l_byteaDestroy");
+
+    if (pba == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((ba = *pba) == NULL)
+        return;
+
+        /* Decrement the ref count.  If it is 0, destroy the lba. */
+    ba->refcount--;
+    if (ba->refcount <= 0) {
+        if (ba->data) LEPT_FREE(ba->data);
+        LEPT_FREE(ba);
+    }
+
+    *pba = NULL;
+    return;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                               Accessors                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  l_byteaGetSize()
+ *
+ *      Input:  ba
+ *      Return: size of stored byte array, or 0 on error
+ */
+size_t
+l_byteaGetSize(L_BYTEA  *ba)
+{
+    PROCNAME("l_byteaGetSize");
+
+    if (!ba)
+        return ERROR_INT("ba not defined", procName, 0);
+    return ba->size;
+}
+
+
+/*!
+ *  l_byteaGetData()
+ *
+ *      Input:  ba
+ *              &size (<returned> size of data in lba)
+ *      Return: ptr to existing data array, or NULL on error
+ *
+ *  Notes:
+ *      (1) The returned ptr is owned by @ba.  Do not free it!
+ */
+l_uint8 *
+l_byteaGetData(L_BYTEA  *ba,
+               size_t   *psize)
+{
+    PROCNAME("l_byteaGetData");
+
+    if (!ba)
+        return (l_uint8 *)ERROR_PTR("ba not defined", procName, NULL);
+    if (!psize)
+        return (l_uint8 *)ERROR_PTR("&size not defined", procName, NULL);
+
+    *psize = ba->size;
+    return ba->data;
+}
+
+
+/*!
+ *  l_byteaCopyData()
+ *
+ *      Input:  ba
+ *              &size (<returned> size of data in lba)
+ *      Return: copy of data in use in the data array, or null on error.
+ *
+ *  Notes:
+ *      (1) The returned data is owned by the caller.  The input @ba
+ *          still owns the original data array.
+ */
+l_uint8 *
+l_byteaCopyData(L_BYTEA  *ba,
+                size_t   *psize)
+{
+l_uint8  *data;
+
+    PROCNAME("l_byteaCopyData");
+
+    if (!psize)
+        return (l_uint8 *)ERROR_PTR("&size not defined", procName, NULL);
+    *psize = 0;
+    if (!ba)
+        return (l_uint8 *)ERROR_PTR("ba not defined", procName, NULL);
+
+    data = l_byteaGetData(ba, psize);
+    return l_binaryCopy(data, *psize);
+}
+
+
+/*---------------------------------------------------------------------*
+ *                               Appending                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  l_byteaAppendData()
+ *
+ *      Input:  ba
+ *              newdata (byte array to be appended)
+ *              size (size of data array)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_byteaAppendData(L_BYTEA  *ba,
+                  l_uint8  *newdata,
+                  size_t    newbytes)
+{
+size_t  size, nalloc, reqsize;
+
+    PROCNAME("l_byteaAppendData");
+
+    if (!ba)
+        return ERROR_INT("ba not defined", procName, 1);
+    if (!newdata)
+        return ERROR_INT("newdata not defined", procName, 1);
+
+    size = l_byteaGetSize(ba);
+    reqsize = size + newbytes + 1;
+    nalloc = ba->nalloc;
+    if (nalloc < reqsize)
+        l_byteaExtendArrayToSize(ba, 2 * reqsize);
+
+    memcpy((char *)(ba->data + size), (char *)newdata, newbytes);
+    ba->size += newbytes;
+    return 0;
+}
+
+
+/*!
+ *  l_byteaAppendString()
+ *
+ *      Input:  ba
+ *              str (null-terminated string to be appended)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_byteaAppendString(L_BYTEA  *ba,
+                    char     *str)
+{
+size_t  size, len, nalloc, reqsize;
+
+    PROCNAME("l_byteaAppendString");
+
+    if (!ba)
+        return ERROR_INT("ba not defined", procName, 1);
+    if (!str)
+        return ERROR_INT("str not defined", procName, 1);
+
+    size = l_byteaGetSize(ba);
+    len = strlen(str);
+    reqsize = size + len + 1;
+    nalloc = ba->nalloc;
+    if (nalloc < reqsize)
+        l_byteaExtendArrayToSize(ba, 2 * reqsize);
+
+    memcpy(ba->data + size, str, len);
+    ba->size += len;
+    return 0;
+}
+
+
+/*!
+ *  l_byteaExtendArrayToSize()
+ *
+ *      Input:  ba
+ *              size (new size of lba data array)
+ *      Return: 0 if OK; 1 on error
+ */
+static l_int32
+l_byteaExtendArrayToSize(L_BYTEA  *ba,
+                         size_t    size)
+{
+    PROCNAME("l_byteaExtendArrayToSize");
+
+    if (!ba)
+        return ERROR_INT("ba not defined", procName, 1);
+
+    if (size > ba->nalloc) {
+        if ((ba->data =
+            (l_uint8 *)reallocNew((void **)&ba->data, ba->nalloc, size))
+                 == NULL)
+            return ERROR_INT("new array not returned", procName, 1);
+        ba->nalloc = size;
+    }
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        String join/split                            *
+ *---------------------------------------------------------------------*/
+/*!
+ *  l_byteaJoin()
+ *
+ *      Input:  ba1
+ *              &ba2 (data array is added to the one in ba1, and
+ *                     then ba2 is destroyed)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) It is a no-op, not an error, for @ba2 to be null.
+ */
+l_int32
+l_byteaJoin(L_BYTEA   *ba1,
+            L_BYTEA  **pba2)
+{
+l_uint8  *data2;
+size_t    nbytes2;
+L_BYTEA  *ba2;
+
+    PROCNAME("l_byteaJoin");
+
+    if (!ba1)
+        return ERROR_INT("ba1 not defined", procName, 1);
+    if (!pba2)
+        return ERROR_INT("&ba2 not defined", procName, 1);
+    if ((ba2 = *pba2) == NULL) return 0;
+
+    data2 = l_byteaGetData(ba2, &nbytes2);
+    l_byteaAppendData(ba1, data2, nbytes2);
+
+    l_byteaDestroy(pba2);
+    return 0;
+}
+
+
+/*!
+ *  l_byteaSplit()
+ *
+ *      Input:  ba1 (lba to split; array bytes nulled beyond the split loc)
+ *              splitloc (location in ba1 to split; ba2 begins there)
+ *              &ba2 (<return> with data starting at splitloc)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_byteaSplit(L_BYTEA   *ba1,
+             size_t     splitloc,
+             L_BYTEA  **pba2)
+{
+l_uint8  *data1;
+size_t    nbytes1, nbytes2;
+
+    PROCNAME("l_byteaSplit");
+
+    if (!pba2)
+        return ERROR_INT("&ba2 not defined", procName, 1);
+    *pba2 = NULL;
+    if (!ba1)
+        return ERROR_INT("ba1 not defined", procName, 1);
+
+    data1 = l_byteaGetData(ba1, &nbytes1);
+    if (splitloc >= nbytes1)
+        return ERROR_INT("splitloc invalid", procName, 1);
+    nbytes2 = nbytes1 - splitloc;
+
+        /* Make the new lba */
+    *pba2 = l_byteaInitFromMem(data1 + splitloc, nbytes2);
+
+        /* Null the removed bytes in the input lba */
+    memset(data1 + splitloc, 0, nbytes2);
+    ba1->size = splitloc;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                                Search                               *
+ *---------------------------------------------------------------------*/
+/*!
+ *  l_byteaFindEachSequence()
+ *
+ *      Input:  ba
+ *              sequence (subarray of bytes to find in data)
+ *              seqlen (length of sequence, in bytes)
+ *              &da (<return> byte positions of each occurrence of @sequence)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_byteaFindEachSequence(L_BYTEA   *ba,
+                        l_uint8   *sequence,
+                        l_int32    seqlen,
+                        L_DNA    **pda)
+{
+l_uint8  *data;
+size_t    size;
+
+    PROCNAME("l_byteaFindEachSequence");
+
+    if (!pda)
+        return ERROR_INT("&da not defined", procName, 1);
+    *pda = NULL;
+    if (!ba)
+        return ERROR_INT("ba not defined", procName, 1);
+    if (!sequence)
+        return ERROR_INT("sequence not defined", procName, 1);
+
+    data = l_byteaGetData(ba, &size);
+    *pda = arrayFindEachSequence(data, size, sequence, seqlen);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                              Output to file                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  l_byteaWrite()
+ *
+ *      Input:  fname (output file)
+ *              ba
+ *              startloc (first byte to output)
+ *              endloc (last byte to output; use 0 to write to the
+ *                      end of the data array)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_byteaWrite(const char  *fname,
+             L_BYTEA     *ba,
+             size_t       startloc,
+             size_t       endloc)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("l_byteaWrite");
+
+    if (!fname)
+        return ERROR_INT("fname not defined", procName, 1);
+    if (!ba)
+        return ERROR_INT("ba not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(fname, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    ret = l_byteaWriteStream(fp, ba, startloc, endloc);
+    fclose(fp);
+    return ret;
+}
+
+
+/*!
+ *  l_byteaWriteStream()
+ *
+ *      Input:  stream (opened for binary write)
+ *              ba
+ *              startloc (first byte to output)
+ *              endloc (last byte to output; use 0 to write to the
+ *                      end of the data array)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_byteaWriteStream(FILE     *fp,
+                   L_BYTEA  *ba,
+                   size_t    startloc,
+                   size_t    endloc)
+{
+l_uint8  *data;
+size_t    size, nbytes;
+
+    PROCNAME("l_byteaWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!ba)
+        return ERROR_INT("ba not defined", procName, 1);
+
+    data = l_byteaGetData(ba, &size);
+    if (startloc >= size)
+        return ERROR_INT("invalid startloc", procName, 1);
+    if (endloc == 0) endloc = size - 1;
+    nbytes = endloc - startloc + 1;
+    if (nbytes < 1)
+        return ERROR_INT("endloc must be >= startloc", procName, 1);
+
+    fwrite(data + startloc, 1, nbytes, fp);
+    return 0;
+}
diff --git a/src/ccbord.c b/src/ccbord.c
new file mode 100644 (file)
index 0000000..a296a40
--- /dev/null
@@ -0,0 +1,2532 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  ccbord.c
+ *
+ *     CCBORDA and CCBORD creation and destruction
+ *         CCBORDA     *ccbaCreate()
+ *         void        *ccbaDestroy()
+ *         CCBORD      *ccbCreate()
+ *         void        *ccbDestroy()
+ *
+ *     CCBORDA addition
+ *         l_int32      ccbaAddCcb()
+ *         static l_int32  ccbaExtendArray()
+ *
+ *     CCBORDA accessors
+ *         l_int32      ccbaGetCount()
+ *         l_int32      ccbaGetCcb()
+ *
+ *     Top-level border-finding routines
+ *         CCBORDA     *pixGetAllCCBorders()
+ *         CCBORD      *pixGetCCBorders()
+ *         PTAA        *pixGetOuterBordersPtaa()
+ *         PTA         *pixGetOuterBorderPta()
+ *
+ *     Lower-level border location routines
+ *         l_int32      pixGetOuterBorder()
+ *         l_int32      pixGetHoleBorder()
+ *         l_int32      findNextBorderPixel()
+ *         void         locateOutsideSeedPixel()
+ *
+ *     Border conversions
+ *         l_int32      ccbaGenerateGlobalLocs()
+ *         l_int32      ccbaGenerateStepChains()
+ *         l_int32      ccbaStepChainsToPixCoords()
+ *         l_int32      ccbaGenerateSPGlobalLocs()
+ *
+ *     Conversion to single path
+ *         l_int32      ccbaGenerateSinglePath()
+ *         PTA         *getCutPathForHole()
+ *
+ *     Border and full image rendering
+ *         PIX         *ccbaDisplayBorder()
+ *         PIX         *ccbaDisplaySPBorder()
+ *         PIX         *ccbaDisplayImage1()
+ *         PIX         *ccbaDisplayImage2()
+ *
+ *     Serialize for I/O
+ *         l_int32      ccbaWrite()
+ *         l_int32      ccbaWriteStream()
+ *         l_int32      ccbaRead()
+ *         l_int32      ccbaReadStream()
+ *
+ *     SVG output
+ *         l_int32      ccbaWriteSVG()
+ *         char        *ccbaWriteSVGString()
+ *
+ *
+ *     Border finding is tricky because components can have
+ *     holes, which also need to be traced out.  The outer
+ *     border can be connected with all the hole borders,
+ *     so that there is a single border for each component.
+ *     [Alternatively, the connecting paths can be eliminated if
+ *     you're willing to have a set of borders for each
+ *     component (an exterior border and some number of
+ *     interior ones), with "line to" operations tracing
+ *     out each border and "move to" operations going from
+ *     one border to the next.]
+ *
+ *     Here's the plan.  We get the pix for each connected
+ *     component, and trace its exterior border.  We then
+ *     find the holes (if any) in the pix, and separately
+ *     trace out their borders, all using the same
+ *     border-following rule that has ON pixels on the right
+ *     side of the path.
+ *
+ *     [For svg, we may want to turn each set of borders for a c.c.
+ *     into a closed path.  This can be done by tunnelling
+ *     through the component from the outer border to each of the
+ *     holes, going in and coming out along the same path so
+ *     the connection will be invisible in any rendering
+ *     (display or print) from the outline.  The result is a
+ *     closed path, where the outside border is traversed
+ *     cw and each hole is traversed ccw.  The svg renderer
+ *     is assumed to handle these closed borders properly.]
+ *
+ *     Each border is a closed path that is traversed in such
+ *     a way that the stuff inside the c.c. is on the right
+ *     side of the traveller.  The border of a singly-connected
+ *     component is thus traversed cw, and the border of the
+ *     holes inside a c.c. are traversed ccw.  Suppose we have
+ *     a list of all the borders of each c.c., both the cw and ccw
+ *     traversals.  How do we reconstruct the image?
+ *
+ *   Reconstruction:
+ *
+ *     Method 1.  Topological method using connected components.
+ *     We have closed borders composed of cw border pixels for the
+ *     exterior of c.c. and ccw border pixels for the interior (holes)
+ *     in the c.c.
+ *         (a) Initialize the destination to be OFF.  Then,
+ *             in any order:
+ *         (b) Fill the components within and including the cw borders,
+ *             and sequentially XOR them onto the destination.
+ *         (c) Fill the components within but not including the ccw
+ *             borders and sequentially XOR them onto the destination.
+ *     The components that are XOR'd together can be generated as follows:
+ *         (a) For each closed cw path, use pixFillClosedBorders():
+ *               (1) Turn on the path pixels in a subimage that
+ *                   minimally supports the border.
+ *               (2) Do a 4-connected fill from a seed of 1 pixel width
+ *                   on the border, using the inverted image in (1) as
+ *                   a filling mask.
+ *               (3) Invert the fill result: this gives the component
+ *                   including the exterior cw path, with all holes
+ *                   filled.
+ *         (b) For each closed ccw path (hole):
+ *               (1) Turn on the path pixels in a subimage that minimally
+ *                   supports the path.
+ *               (2) Find a seed pixel on the inside of this path.
+ *               (3) Do a 4-connected fill from this seed pixel, using
+ *                   the inverted image of the path in (1) as a filling
+ *                   mask.
+ *
+ *     ------------------------------------------------------
+ *
+ *     Method 2.  A variant of Method 1.  Topological.
+ *     In Method 1, we treat the exterior border differently from
+ *     the interior (hole) borders.  Here, all borders in a c.c.
+ *     are treated equally:
+ *         (1) Start with a pix with a 1 pixel OFF boundary
+ *             enclosing all the border pixels of the c.c.
+ *             This is the filling mask.
+ *         (2) Make a seed image of the same size as follows:  for
+ *             each border, put one seed pixel OUTSIDE the border
+ *             (where OUTSIDE is determined by the inside/outside
+ *             convention for borders).
+ *         (3) Seedfill into the seed image, filling in the regions
+ *             determined by the filling mask.  The fills are clipped
+ *             by the border pixels.
+ *         (4) Inverting this, we get the c.c. properly filled,
+ *             with the holes empty!
+ *         (5) Rasterop using XOR the filled c.c. (but not the 1
+ *             pixel boundary) into the full dest image.
+ *
+ *     Method 2 is about 1.2x faster than Method 1 on text images,
+ *     and about 2x faster on complex images (e.g., with halftones).
+ *
+ *     ------------------------------------------------------
+ *
+ *     Method 3.  The traditional way to fill components delineated
+ *     by boundaries is through scan line conversion.  It's a bit
+ *     tricky, and I have not yet tried to implement it.
+ *
+ *     ------------------------------------------------------
+ *
+ *     Method 4.  [Nota Bene: this method probably doesn't work, and
+ *     won't be implemented.  If I get a more traditional scan line
+ *     conversion algorithm working, I'll erase these notes.]
+ *     Render all border pixels on a destination image,
+ *     which will be the final result after scan conversion.  Assign
+ *     a value 1 to pixels on cw paths, 2 to pixels on ccw paths,
+ *     and 3 to pixels that are on both paths.  Each of the paths
+ *     is an 8-connected component.  Now scan across each raster
+ *     line.  The attempt is to make rules for each scan line
+ *     that are independent of neighboring scanlines.  Here are
+ *     a set of rules for writing ON pixels on a destination raster image:
+ *
+ *         (a) The rasterizer will be in one of two states: ON and OFF.
+ *         (b) Start each line in the OFF state.  In the OFF state,
+ *             skip pixels until you hit a path of any type.  Turn
+ *             the path pixel ON.
+ *         (c) If the state is ON, each pixel you encounter will
+ *             be turned on, until and including hitting a path pixel.
+ *         (d) When you hit a path pixel, if the path does NOT cut
+ *             through the line, so that there is not an 8-cc path
+ *             pixel (of any type) both above and below, the state
+ *             is unchanged (it stays either ON or OFF).
+ *         (e) If the path does cut through, but with a possible change
+ *             of pixel type, then we decide whether or
+ *             not to toggle the state based on the values of the
+ *             path pixel and the path pixels above and below:
+ *               (1) if a 1 path cuts through, toggle;
+ *               (1) if a 2 path cuts through, toggle;
+ *               (3) if a 3 path cuts through, do not toggle;
+ *               (4) if on one side a 3 touches both a 1 and a 2, use the 2
+ *               (5) if a 3 has any 1 neighbors, toggle; else if it has
+ *                   no 1 neighbors, do not toggle;
+ *               (6) if a 2 has any neighbors that are 1 or 3,
+ *                   do not toggle
+ *               (7) if a 1 has neighbors 1 and x (x = 2 or 3),
+ *                   toggle
+ *
+ *
+ *     To visualize how these rules work, consider the following
+ *     component with border pixels labeled according to the scheme
+ *     above.  We also show the values of the interior pixels
+ *     (w=OFF, b=ON), but these of course must be inferred properly
+ *     from the rules above:
+ *
+ *                     3
+ *                  3  w  3             1  1  1
+ *                  1  2  1          1  b  2  b  1
+ *                  1  b  1             3  w  2  1
+ *                  3  b  1          1  b  2  b  1
+ *               3  w  3                1  1  1
+ *               3  w  3
+ *            1  b  2  b  1
+ *            1  2  w  2  1
+ *         1  b  2  w  2  b  1
+ *            1  2  w  2  1
+ *               1  2  b  1
+ *               1  b  1
+ *                  1
+ *
+ *
+ *     Even if this works, which is unlikely, it will certainly be
+ *     slow because decisions have to be made on a pixel-by-pixel
+ *     basis when encountering borders.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;    /* n'import quoi */
+
+    /* In ccbaGenerateSinglePath(): don't save holes
+     * in c.c. with ridiculously many small holes   */
+static const l_int32  NMAX_HOLES = 150;
+
+    /*  Tables used to trace the border.
+     *   - The 8 pixel positions of neighbors Q are labelled:
+     *                  1   2   3
+     *                  0   P   4
+     *                  7   6   5
+     *     where the labels are the index offset [0, ... 7] of Q relative to P.
+     *   - xpostab[] and ypostab[] give the actual x and y pixel offsets
+     *     of Q relative to P, indexed by the index offset.
+     *   - qpostab[pos] gives the new index offset of Q relative to P, at
+     *     the time that a new P has been chosen to be in index offset
+     *     position 'pos' relative to the previous P.   The relation
+     *     between P and Q is always 4-connected.  */
+static const l_int32   xpostab[] = {-1, -1, 0, 1, 1, 1, 0, -1};
+static const l_int32   ypostab[] = {0, -1, -1, -1, 0, 1, 1, 1};
+static const l_int32   qpostab[] = {6, 6, 0, 0, 2, 2, 4, 4};
+
+    /* Static function */
+static l_int32 ccbaExtendArray(CCBORDA  *ccba);
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_PRINT   0
+#endif   /* NO CONSOLE_IO */
+
+
+/*---------------------------------------------------------------------*
+ *                   ccba and ccb creation and destruction             *
+ *---------------------------------------------------------------------*/
+/*!
+ *   ccbaCreate()
+ *
+ *       Input:  pixs  (binary image; can be null)
+ *               n  (initial number of ptrs)
+ *       Return: ccba, or null on error
+ */
+CCBORDA *
+ccbaCreate(PIX     *pixs,
+           l_int32  n)
+{
+CCBORDA  *ccba;
+
+    PROCNAME("ccbaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((ccba = (CCBORDA *)LEPT_CALLOC(1, sizeof(CCBORDA))) == NULL)
+        return (CCBORDA *)ERROR_PTR("ccba not made", procName, NULL);
+    if (pixs) {
+        ccba->pix = pixClone(pixs);
+        ccba->w = pixGetWidth(pixs);
+        ccba->h = pixGetHeight(pixs);
+    }
+    ccba->n = 0;
+    ccba->nalloc = n;
+
+    if ((ccba->ccb = (CCBORD **)LEPT_CALLOC(n, sizeof(CCBORD *))) == NULL)
+        return (CCBORDA *)ERROR_PTR("ccba ptrs not made", procName, NULL);
+
+    return ccba;
+}
+
+
+/*!
+ *  ccbaDestroy()
+ *
+ *     Input:  &ccba  (<to be nulled>)
+ *     Return: void
+ */
+void
+ccbaDestroy(CCBORDA  **pccba)
+{
+l_int32   i;
+CCBORDA  *ccba;
+
+    PROCNAME("ccbaDestroy");
+
+    if (pccba == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+
+    if ((ccba = *pccba) == NULL)
+        return;
+
+    pixDestroy(&ccba->pix);
+    for (i = 0; i < ccba->n; i++)
+        ccbDestroy(&ccba->ccb[i]);
+    LEPT_FREE(ccba->ccb);
+    LEPT_FREE(ccba);
+    *pccba = NULL;
+    return;
+}
+
+
+/*!
+ *  ccbCreate()
+ *
+ *     Input:  pixs  (<optional>)
+ *     Return: ccb or null on error
+ */
+CCBORD *
+ccbCreate(PIX  *pixs)
+{
+BOXA    *boxa;
+CCBORD  *ccb;
+PTA     *start;
+PTAA    *local;
+
+    PROCNAME("ccbCreate");
+
+    if (pixs) {
+        if (pixGetDepth(pixs) != 1)
+            return (CCBORD *)ERROR_PTR("pixs not binary", procName, NULL);
+    }
+
+    if ((ccb = (CCBORD *)LEPT_CALLOC(1, sizeof(CCBORD))) == NULL)
+        return (CCBORD *)ERROR_PTR("ccb not made", procName, NULL);
+    ccb->refcount++;
+    if (pixs)
+        ccb->pix = pixClone(pixs);
+    if ((boxa = boxaCreate(1)) == NULL)
+        return (CCBORD *)ERROR_PTR("boxa not made", procName, NULL);
+    ccb->boxa = boxa;
+    if ((start = ptaCreate(1)) == NULL)
+        return (CCBORD *)ERROR_PTR("start pta not made", procName, NULL);
+    ccb->start = start;
+    if ((local = ptaaCreate(1)) == NULL)
+        return (CCBORD *)ERROR_PTR("local ptaa not made", procName, NULL);
+    ccb->local = local;
+
+    return ccb;
+}
+
+
+/*!
+ *  ccbDestroy()
+ *
+ *     Input:  &ccb (<to be nulled>)
+ *     Return: void
+ */
+void
+ccbDestroy(CCBORD  **pccb)
+{
+CCBORD  *ccb;
+
+    PROCNAME("ccbDestroy");
+
+    if (pccb == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+
+    if ((ccb = *pccb) == NULL)
+        return;
+
+    ccb->refcount--;
+    if (ccb->refcount == 0) {
+        if (ccb->pix)
+            pixDestroy(&ccb->pix);
+        if (ccb->boxa)
+            boxaDestroy(&ccb->boxa);
+        if (ccb->start)
+            ptaDestroy(&ccb->start);
+        if (ccb->local)
+            ptaaDestroy(&ccb->local);
+        if (ccb->global)
+            ptaaDestroy(&ccb->global);
+        if (ccb->step)
+            numaaDestroy(&ccb->step);
+        if (ccb->splocal)
+            ptaDestroy(&ccb->splocal);
+        if (ccb->spglobal)
+            ptaDestroy(&ccb->spglobal);
+        LEPT_FREE(ccb);
+        *pccb = NULL;
+    }
+    return;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                            ccba addition                            *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ccbaAddCcb()
+ *
+ *      Input:  ccba
+ *              ccb (to be added by insertion)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+ccbaAddCcb(CCBORDA  *ccba,
+           CCBORD   *ccb)
+{
+l_int32  n;
+
+    PROCNAME("ccbaAddCcb");
+
+    if (!ccba)
+        return ERROR_INT("ccba not defined", procName, 1);
+    if (!ccb)
+        return ERROR_INT("ccb not defined", procName, 1);
+
+    n = ccbaGetCount(ccba);
+    if (n >= ccba->nalloc)
+        ccbaExtendArray(ccba);
+    ccba->ccb[n] = ccb;
+    ccba->n++;
+    return 0;
+}
+
+
+/*!
+ *  ccbaExtendArray()
+ *
+ *      Input:  ccba
+ *      Return: 0 if OK; 1 on error
+ */
+static l_int32
+ccbaExtendArray(CCBORDA  *ccba)
+{
+    PROCNAME("ccbaExtendArray");
+
+    if (!ccba)
+        return ERROR_INT("ccba not defined", procName, 1);
+
+    if ((ccba->ccb = (CCBORD **)reallocNew((void **)&ccba->ccb,
+                                sizeof(CCBORD *) * ccba->nalloc,
+                                2 * sizeof(CCBORD *) * ccba->nalloc)) == NULL)
+        return ERROR_INT("new ptr array not returned", procName, 1);
+
+    ccba->nalloc = 2 * ccba->nalloc;
+    return 0;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                            ccba accessors                           *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ccbaGetCount()
+ *
+ *     Input:  ccba
+ *     Return: count, with 0 on error
+ */
+l_int32
+ccbaGetCount(CCBORDA  *ccba)
+{
+
+    PROCNAME("ccbaGetCount");
+
+    if (!ccba)
+        return ERROR_INT("ccba not defined", procName, 0);
+
+    return ccba->n;
+}
+
+
+/*!
+ *  ccbaGetCcb()
+ *
+ *     Input:  ccba
+ *     Return: ccb, or null on error
+ */
+CCBORD *
+ccbaGetCcb(CCBORDA  *ccba,
+           l_int32   index)
+{
+CCBORD  *ccb;
+
+    PROCNAME("ccbaGetCcb");
+
+    if (!ccba)
+        return (CCBORD *)ERROR_PTR("ccba not defined", procName, NULL);
+    if (index < 0 || index >= ccba->n)
+        return (CCBORD *)ERROR_PTR("index out of bounds", procName, NULL);
+
+    ccb = ccba->ccb[index];
+    ccb->refcount++;
+    return ccb;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                   Top-level border-finding routines                 *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixGetAllCCBorders()
+ *
+ *      Input:  pixs (1 bpp)
+ *      Return: ccborda, or null on error
+ */
+CCBORDA *
+pixGetAllCCBorders(PIX  *pixs)
+{
+l_int32   n, i;
+BOX      *box;
+BOXA     *boxa;
+CCBORDA  *ccba;
+CCBORD   *ccb;
+PIX      *pix;
+PIXA     *pixa;
+
+    PROCNAME("pixGetAllCCBorders");
+
+    if (!pixs)
+        return (CCBORDA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (CCBORDA *)ERROR_PTR("pixs not binary", procName, NULL);
+
+    if ((boxa = pixConnComp(pixs, &pixa, 8)) == NULL)
+        return (CCBORDA *)ERROR_PTR("boxa not made", procName, NULL);
+    n = boxaGetCount(boxa);
+
+    if ((ccba = ccbaCreate(pixs, n)) == NULL)
+        return (CCBORDA *)ERROR_PTR("ccba not made", procName, NULL);
+
+    for (i = 0; i < n; i++) {
+        if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL)
+            return (CCBORDA *)ERROR_PTR("pix not found", procName, NULL);
+        if ((box = pixaGetBox(pixa, i, L_CLONE)) == NULL)
+            return (CCBORDA *)ERROR_PTR("box not found", procName, NULL);
+        if ((ccb = pixGetCCBorders(pix, box)) == NULL)
+            return (CCBORDA *)ERROR_PTR("ccb not made", procName, NULL);
+/*        ptaWriteStream(stderr, ccb->local, 1); */
+        ccbaAddCcb(ccba, ccb);
+        pixDestroy(&pix);
+        boxDestroy(&box);
+    }
+
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixa);
+    return ccba;
+}
+
+
+/*!
+ *  pixGetCCBorders()
+ *
+ *      Input:  pixs (1 bpp, one 8-connected component)
+ *              box  (xul, yul, width, height) in global coords
+ *      Return: ccbord, or null on error
+ *
+ *  Notes:
+ *      (1) We are finding the exterior and interior borders
+ *          of an 8-connected component.   This should be used
+ *          on a pix that has exactly one 8-connected component.
+ *      (2) Typically, pixs is a c.c. in some larger pix.  The
+ *          input box gives its location in global coordinates.
+ *          This box is saved, as well as the boxes for the
+ *          borders of any holes within the c.c., but the latter
+ *          are given in relative coords within the c.c.
+ *      (3) The calculations for the exterior border are done
+ *          on a pix with a 1-pixel
+ *          added border, but the saved pixel coordinates
+ *          are the correct (relative) ones for the input pix
+ *          (without a 1-pixel border)
+ *      (4) For the definition of the three tables -- xpostab[], ypostab[]
+ *          and qpostab[] -- see above where they are defined.
+ */
+CCBORD *
+pixGetCCBorders(PIX      *pixs,
+                BOX      *box)
+{
+l_int32   allzero, i, x, xh, w, nh;
+l_int32   xs, ys;   /* starting hole border pixel, relative in pixs */
+l_uint32  val;
+BOX      *boxt, *boxe;
+BOXA     *boxa;
+CCBORD   *ccb;
+PIX      *pixh;  /* for hole components */
+PIX      *pixt;
+PIXA     *pixa;
+
+    PROCNAME("pixGetCCBorders");
+
+    if (!pixs)
+        return (CCBORD *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!box)
+        return (CCBORD *)ERROR_PTR("box not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (CCBORD *)ERROR_PTR("pixs not binary", procName, NULL);
+
+    pixZero(pixs, &allzero);
+    if (allzero)
+        return (CCBORD *)ERROR_PTR("pixs all 0", procName, NULL);
+
+    if ((ccb = ccbCreate(pixs)) == NULL)
+        return (CCBORD *)ERROR_PTR("ccb not made", procName, NULL);
+
+        /* Get the exterior border */
+    pixGetOuterBorder(ccb, pixs, box);
+
+        /* Find the holes, if any */
+    if ((pixh = pixHolesByFilling(pixs, 4)) == NULL)
+        return (CCBORD *)ERROR_PTR("pixh not made", procName, NULL);
+    pixZero(pixh, &allzero);
+    if (allzero) {  /* no holes */
+        pixDestroy(&pixh);
+        return ccb;
+    }
+
+        /* Get c.c. and locations of the holes */
+    if ((boxa = pixConnComp(pixh, &pixa, 4)) == NULL)
+        return (CCBORD *)ERROR_PTR("boxa not made", procName, NULL);
+    nh = boxaGetCount(boxa);
+/*    fprintf(stderr, "%d holes\n", nh); */
+
+        /* For each hole, find an interior pixel within the hole,
+         * then march to the right and stop at the first border
+         * pixel.  Save the bounding box of the border, which
+         * is 1 pixel bigger on each side than the bounding box
+         * of the hole itself.  Note that we use a pix of the
+         * c.c. of the hole itself to be sure that we start
+         * with a pixel in the hole of the proper component.
+         * If we did everything from the parent component, it is
+         * possible to start in a different hole that is within
+         * the b.b. of a larger hole.  */
+    w = pixGetWidth(pixs);
+    for (i = 0; i < nh; i++) {
+        boxt = boxaGetBox(boxa, i, L_CLONE);
+        pixt = pixaGetPix(pixa, i, L_CLONE);
+        ys = boxt->y;   /* there must be a hole pixel on this raster line */
+        for (x = 0; x < boxt->w; x++) {  /* look for (fg) hole pixel */
+            pixGetPixel(pixt, x, 0, &val);
+            if (val == 1) {
+                xh = x;
+                break;
+            }
+        }
+        if (x == boxt->w) {
+            L_WARNING("no hole pixel found!\n", procName);
+            continue;
+        }
+        for (x = xh + boxt->x; x < w; x++) {  /* look for (fg) border pixel */
+            pixGetPixel(pixs, x, ys, &val);
+            if (val == 1) {
+                xs = x;
+                break;
+            }
+        }
+        boxe = boxCreate(boxt->x - 1, boxt->y - 1, boxt->w + 2, boxt->h + 2);
+#if  DEBUG_PRINT
+        boxPrintStreamInfo(stderr, box);
+        boxPrintStreamInfo(stderr, boxe);
+        fprintf(stderr, "xs = %d, ys = %d\n", xs, ys);
+#endif   /* DEBUG_PRINT */
+        pixGetHoleBorder(ccb, pixs, boxe, xs, ys);
+        boxDestroy(&boxt);
+        boxDestroy(&boxe);
+        pixDestroy(&pixt);
+    }
+
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixa);
+    pixDestroy(&pixh);
+
+    return ccb;
+}
+
+
+/*!
+ *  pixGetOuterBordersPtaa()
+ *
+ *      Input:  pixs (1 bpp)
+ *      Return: ptaa (of outer borders, in global coords), or null on error
+ */
+PTAA *
+pixGetOuterBordersPtaa(PIX  *pixs)
+{
+l_int32  i, n;
+BOX     *box;
+BOXA    *boxa;
+PIX     *pix;
+PIXA    *pixa;
+PTA     *pta;
+PTAA    *ptaa;
+
+    PROCNAME("pixGetOuterBordersPtaa");
+
+    if (!pixs)
+        return (PTAA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PTAA *)ERROR_PTR("pixs not binary", procName, NULL);
+
+    boxa = pixConnComp(pixs, &pixa, 8);
+    n = boxaGetCount(boxa);
+    if (n == 0) {
+        boxaDestroy(&boxa);
+        pixaDestroy(&pixa);
+        return (PTAA *)ERROR_PTR("pixs empty", procName, NULL);
+    }
+
+    ptaa = ptaaCreate(n);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        pta = pixGetOuterBorderPta(pix, box);
+        if (pta)
+            ptaaAddPta(ptaa, pta, L_INSERT);
+        boxDestroy(&box);
+        pixDestroy(&pix);
+    }
+
+    pixaDestroy(&pixa);
+    boxaDestroy(&boxa);
+    return ptaa;
+}
+
+
+/*!
+ *  pixGetOuterBorderPta()
+ *
+ *      Input:  pixs (1 bpp, one 8-connected component)
+ *              box  (<optional> of pixs, in global coordinates)
+ *      Return: pta (of outer border, in global coords), or null on error
+ *
+ *  Notes:
+ *      (1) We are finding the exterior border of a single 8-connected
+ *          component.
+ *      (2) If box is NULL, the outline returned is in the local coords
+ *          of the input pix.  Otherwise, box is assumed to give the
+ *          location of the pix in global coordinates, and the returned
+ *          pta will be in those global coordinates.
+ */
+PTA *
+pixGetOuterBorderPta(PIX  *pixs,
+                     BOX  *box)
+{
+l_int32  allzero, x, y;
+BOX     *boxt;
+CCBORD  *ccb;
+PTA     *ptaloc, *ptad;
+
+    PROCNAME("pixGetOuterBorderPta");
+
+    if (!pixs)
+        return (PTA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PTA *)ERROR_PTR("pixs not binary", procName, NULL);
+
+    pixZero(pixs, &allzero);
+    if (allzero)
+        return (PTA *)ERROR_PTR("pixs all 0", procName, NULL);
+
+    if ((ccb = ccbCreate(pixs)) == NULL)
+        return (PTA *)ERROR_PTR("ccb not made", procName, NULL);
+    if (!box)
+        boxt = boxCreate(0, 0, pixGetWidth(pixs), pixGetHeight(pixs));
+    else
+        boxt = boxClone(box);
+
+        /* Get the exterior border in local coords */
+    pixGetOuterBorder(ccb, pixs, boxt);
+    if ((ptaloc = ptaaGetPta(ccb->local, 0, L_CLONE)) == NULL) {
+        ccbDestroy(&ccb);
+        boxDestroy(&boxt);
+        return (PTA *)ERROR_PTR("ptaloc not made", procName, NULL);
+    }
+
+        /* Transform to global coordinates, if they are given */
+    if (box) {
+        boxGetGeometry(box, &x, &y, NULL, NULL);
+        ptad = ptaTransform(ptaloc, x, y, 1.0, 1.0);
+    } else {
+        ptad = ptaClone(ptaloc);
+    }
+
+    ptaDestroy(&ptaloc);
+    boxDestroy(&boxt);
+    ccbDestroy(&ccb);
+    return ptad;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                   Lower-level border-finding routines               *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixGetOuterBorder()
+ *
+ *      Input:  ccb  (unfilled)
+ *              pixs (for the component at hand)
+ *              box  (for the component, in global coords)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) the border is saved in relative coordinates within
+ *          the c.c. (pixs).  Because the calculation is done
+ *          in pixb with added 1 pixel border, we must subtract
+ *          1 from each pixel value before storing it.
+ *      (2) the stopping condition is that after the first pixel is
+ *          returned to, the next pixel is the second pixel.  Having
+ *          these 2 pixels recur in sequence proves the path is closed,
+ *          and we do not store the second pixel again.
+ */
+l_int32
+pixGetOuterBorder(CCBORD   *ccb,
+                  PIX      *pixs,
+                  BOX      *box)
+{
+l_int32    fpx, fpy, spx, spy, qpos;
+l_int32    px, py, npx, npy;
+l_int32    w, h, wpl;
+l_uint32  *data;
+PTA       *pta;
+PIX       *pixb;  /* with 1 pixel border */
+
+    PROCNAME("pixGetOuterBorder");
+
+    if (!ccb)
+        return ERROR_INT("ccb not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+
+        /* Add 1-pixel border all around, and find start pixel */
+    if ((pixb = pixAddBorder(pixs, 1, 0)) == NULL)
+        return ERROR_INT("pixs not made", procName, 1);
+    if (!nextOnPixelInRaster(pixb, 1, 1, &px, &py))
+        return ERROR_INT("no start pixel found", procName, 1);
+    qpos = 0;   /* relative to p */
+    fpx = px;  /* save location of first pixel on border */
+    fpy = py;
+
+        /* Save box and start pixel in relative coords */
+    boxaAddBox(ccb->boxa, box, L_COPY);
+    ptaAddPt(ccb->start, px - 1, py - 1);
+
+    if ((pta = ptaCreate(0)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    ptaaAddPta(ccb->local, pta, L_INSERT);
+    ptaAddPt(pta, px - 1, py - 1);   /* initial point */
+
+    w = pixGetWidth(pixb);
+    h = pixGetHeight(pixb);
+    data = pixGetData(pixb);
+    wpl = pixGetWpl(pixb);
+
+        /* Get the second point; if there is none, return */
+    if (findNextBorderPixel(w, h, data, wpl, px, py, &qpos, &npx, &npy)) {
+        pixDestroy(&pixb);
+        return 0;
+    }
+
+    spx = npx;  /* save location of second pixel on border */
+    spy = npy;
+    ptaAddPt(pta, npx - 1, npy - 1);   /* second point */
+    px = npx;
+    py = npy;
+
+    while (1) {
+        findNextBorderPixel(w, h, data, wpl, px, py, &qpos, &npx, &npy);
+        if (px == fpx && py == fpy && npx == spx && npy == spy)
+            break;
+        ptaAddPt(pta, npx - 1, npy - 1);
+        px = npx;
+        py = npy;
+    }
+
+    pixDestroy(&pixb);
+    return 0;
+}
+
+
+/*!
+ *  pixGetHoleBorder()
+ *
+ *      Input:  ccb  (the exterior border is already made)
+ *              pixs (for the connected component at hand)
+ *              box  (for the specific hole border, in relative
+ *                    coordinates to the c.c.)
+ *              xs, ys   (first pixel on hole border, relative to c.c.)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) we trace out hole border on pixs without addition
+ *          of single pixel added border to pixs
+ *      (2) therefore all coordinates are relative within the c.c. (pixs)
+ *      (3) same position tables and stopping condition as for
+ *          exterior borders
+ */
+l_int32
+pixGetHoleBorder(CCBORD   *ccb,
+                 PIX      *pixs,
+                 BOX      *box,
+                 l_int32   xs,
+                 l_int32   ys)
+{
+l_int32    fpx, fpy, spx, spy, qpos;
+l_int32    px, py, npx, npy;
+l_int32    w, h, wpl;
+l_uint32  *data;
+PTA       *pta;
+
+    PROCNAME("pixGetHoleBorder");
+
+    if (!ccb)
+        return ERROR_INT("ccb not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+
+        /* Add border and find start pixel */
+    qpos = 0;   /* orientation of Q relative to P */
+    fpx = xs;  /* save location of first pixel on border */
+    fpy = ys;
+
+        /* Save box and start pixel */
+    boxaAddBox(ccb->boxa, box, L_COPY);
+    ptaAddPt(ccb->start, xs, ys);
+
+    if ((pta = ptaCreate(0)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    ptaaAddPta(ccb->local, pta, L_INSERT);
+    ptaAddPt(pta, xs, ys);   /* initial pixel */
+
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+
+        /* Get the second point; there should always be at least 4 pts
+         * in a minimal hole border!  */
+    if (findNextBorderPixel(w, h, data, wpl, xs, ys, &qpos, &npx, &npy))
+        return ERROR_INT("isolated hole border point!", procName, 1);
+
+    spx = npx;  /* save location of second pixel on border */
+    spy = npy;
+    ptaAddPt(pta, npx, npy);   /* second pixel */
+    px = npx;
+    py = npy;
+
+    while (1) {
+        findNextBorderPixel(w, h, data, wpl, px, py, &qpos, &npx, &npy);
+        if (px == fpx && py == fpy && npx == spx && npy == spy)
+            break;
+        ptaAddPt(pta, npx, npy);
+        px = npx;
+        py = npy;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  findNextBorderPixel()
+ *
+ *      Input:  w, h, data, wpl
+ *              (px, py),     (current P)
+ *              &qpos (input current Q; <return> new Q)
+ *              (&npx, &npy)    (<return> new P)
+ *      Return: 0 if next pixel found; 1 otherwise
+ *
+ *  Notes:
+ *      (1) qpos increases clockwise from 0 to 7, with 0 at
+ *          location with Q to left of P:   Q P
+ *      (2) this is a low-level function that does not check input
+ *          parameters.  All calling functions should check them.
+ */
+l_int32
+findNextBorderPixel(l_int32    w,
+                    l_int32    h,
+                    l_uint32  *data,
+                    l_int32    wpl,
+                    l_int32    px,
+                    l_int32    py,
+                    l_int32   *pqpos,
+                    l_int32   *pnpx,
+                    l_int32   *pnpy)
+{
+l_int32    qpos, i, pos, npx, npy, val;
+l_uint32  *line;
+
+    qpos = *pqpos;
+    for (i = 1; i < 8; i++) {
+        pos = (qpos + i) % 8;
+        npx = px + xpostab[pos];
+        npy = py + ypostab[pos];
+        line = data + npy * wpl;
+        val = GET_DATA_BIT(line, npx);
+        if (val) {
+            *pnpx = npx;
+            *pnpy = npy;
+            *pqpos = qpostab[pos];
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+
+/*!
+ *  locateOutsideSeedPixel()
+ *
+ *      Input: fpx, fpy    (location of first pixel)
+ *             spx, spy    (location of second pixel)
+ *             &xs, &xy    (seed pixel to be returned)
+ *
+ *  Notes:
+ *      (1) the first and second pixels must be 8-adjacent,
+ *          so |dx| <= 1 and |dy| <= 1 and both dx and dy
+ *          cannot be 0.  There are 8 possible cases.
+ *      (2) the seed pixel is OUTSIDE the foreground of the c.c.
+ *      (3) these rules are for the situation where the INSIDE
+ *          of the c.c. is on the right as you follow the border:
+ *          cw for an exterior border and ccw for a hole border.
+ */
+void
+locateOutsideSeedPixel(l_int32   fpx,
+                       l_int32   fpy,
+                       l_int32   spx,
+                       l_int32   spy,
+                       l_int32  *pxs,
+                       l_int32  *pys)
+{
+l_int32  dx, dy;
+
+    dx = spx - fpx;
+    dy = spy - fpy;
+
+    if (dx * dy == 1) {
+        *pxs = fpx + dx;
+        *pys = fpy;
+    } else if (dx * dy == -1) {
+        *pxs = fpx;
+        *pys = fpy + dy;
+    } else if (dx == 0) {
+        *pxs = fpx + dy;
+        *pys = fpy + dy;
+    } else  /* dy == 0 */ {
+        *pxs = fpx + dx;
+        *pys = fpy - dx;
+    }
+
+    return;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                            Border conversions                       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ccbaGenerateGlobalLocs()
+ *
+ *      Input:  ccba (with local chain ptaa of borders computed)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Action: this uses the pixel locs in the local ptaa, which are all
+ *          relative to each c.c., to find the global pixel locations,
+ *          and stores them in the global ptaa.
+ */
+l_int32
+ccbaGenerateGlobalLocs(CCBORDA  *ccba)
+{
+l_int32  ncc, nb, n, i, j, k, xul, yul, x, y;
+CCBORD  *ccb;
+PTAA    *ptaal, *ptaag;
+PTA     *ptal, *ptag;
+
+    PROCNAME("ccbaGenerateGlobalLocs");
+
+    if (!ccba)
+        return ERROR_INT("ccba not defined", procName, 1);
+
+    ncc = ccbaGetCount(ccba);  /* number of c.c. */
+    for (i = 0; i < ncc; i++) {
+        ccb = ccbaGetCcb(ccba, i);
+
+            /* Get the UL corner in global coords, (xul, yul), of the c.c. */
+        boxaGetBoxGeometry(ccb->boxa, 0, &xul, &yul, NULL, NULL);
+
+            /* Make a new global ptaa, removing any old one */
+        ptaal = ccb->local;
+        nb = ptaaGetCount(ptaal);   /* number of borders */
+        if (ccb->global)   /* remove old one */
+            ptaaDestroy(&ccb->global);
+        if ((ptaag = ptaaCreate(nb)) == NULL)
+            return ERROR_INT("ptaag not made", procName, 1);
+        ccb->global = ptaag;  /* save new one */
+
+            /* Iterate through the borders for this c.c. */
+        for (j = 0; j < nb; j++) {
+            ptal = ptaaGetPta(ptaal, j, L_CLONE);
+            n = ptaGetCount(ptal);   /* number of pixels in border */
+            if ((ptag = ptaCreate(n)) == NULL)
+                return ERROR_INT("ptag not made", procName, 1);
+            ptaaAddPta(ptaag, ptag, L_INSERT);
+            for (k = 0; k < n; k++) {
+                ptaGetIPt(ptal, k, &x, &y);
+                ptaAddPt(ptag, x  + xul, y + yul);
+            }
+            ptaDestroy(&ptal);
+        }
+        ccbDestroy(&ccb);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  ccbaGenerateStepChains()
+ *
+ *      Input:  ccba (with local chain ptaa of borders computed)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This uses the pixel locs in the local ptaa,
+ *          which are all relative to each c.c., to find
+ *          the step directions for successive pixels in
+ *          the chain, and stores them in the step numaa.
+ *      (2) To get the step direction, use
+ *              1   2   3
+ *              0   P   4
+ *              7   6   5
+ *          where P is the previous pixel at (px, py).  The step direction
+ *          is the number (from 0 through 7) for each relative location
+ *          of the current pixel at (cx, cy).  It is easily found by
+ *          indexing into a 2-d 3x3 array (dirtab).
+ */
+l_int32
+ccbaGenerateStepChains(CCBORDA  *ccba)
+{
+l_int32  ncc, nb, n, i, j, k;
+l_int32  px, py, cx, cy, stepdir;
+l_int32  dirtab[][3] = {{1, 2, 3}, {0, -1, 4}, {7, 6, 5}};
+CCBORD  *ccb;
+NUMA    *na;
+NUMAA   *naa;   /* step chain code; to be made */
+PTA     *ptal;
+PTAA    *ptaal;  /* local chain code */
+
+    PROCNAME("ccbaGenerateStepChains");
+
+    if (!ccba)
+        return ERROR_INT("ccba not defined", procName, 1);
+
+    ncc = ccbaGetCount(ccba);  /* number of c.c. */
+    for (i = 0; i < ncc; i++) {
+        ccb = ccbaGetCcb(ccba, i);
+
+            /* Make a new step numaa, removing any old one */
+        ptaal = ccb->local;
+        nb = ptaaGetCount(ptaal);   /* number of borders */
+        if (ccb->step)   /* remove old one */
+            numaaDestroy(&ccb->step);
+        if ((naa = numaaCreate(nb)) == NULL)
+            return ERROR_INT("naa not made", procName, 1);
+        ccb->step = naa;  /* save new one */
+
+            /* Iterate through the borders for this c.c. */
+        for (j = 0; j < nb; j++) {
+            ptal = ptaaGetPta(ptaal, j, L_CLONE);
+            n = ptaGetCount(ptal);   /* number of pixels in border */
+            if (n == 1) {  /* isolated pixel */
+                na = numaCreate(1);   /* but leave it empty */
+            } else {   /* trace out the boundary */
+                if ((na = numaCreate(n)) == NULL)
+                    return ERROR_INT("na not made", procName, 1);
+                ptaGetIPt(ptal, 0, &px, &py);
+                for (k = 1; k < n; k++) {
+                    ptaGetIPt(ptal, k, &cx, &cy);
+                    stepdir = dirtab[1 + cy - py][1 + cx - px];
+                    numaAddNumber(na, stepdir);
+                    px = cx;
+                    py = cy;
+                }
+            }
+            numaaAddNuma(naa, na, L_INSERT);
+            ptaDestroy(&ptal);
+        }
+        ccbDestroy(&ccb);  /* just decrement refcount */
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  ccbaStepChainsToPixCoords()
+ *
+ *      Input:  ccba (with step chains numaa of borders)
+ *              coordtype  (CCB_GLOBAL_COORDS or CCB_LOCAL_COORDS)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This uses the step chain data in each ccb to determine
+ *          the pixel locations, either global or local,
+ *          and stores them in the appropriate ptaa,
+ *          either global or local.  For the latter, the
+ *          pixel locations are relative to the c.c.
+ */
+l_int32
+ccbaStepChainsToPixCoords(CCBORDA  *ccba,
+                          l_int32   coordtype)
+{
+l_int32  ncc, nb, n, i, j, k;
+l_int32  xul, yul, xstart, ystart, x, y, stepdir;
+BOXA    *boxa;
+CCBORD  *ccb;
+NUMA    *na;
+NUMAA   *naa;
+PTAA    *ptaan;  /* new pix coord ptaa */
+PTA     *ptas, *ptan;
+
+    PROCNAME("ccbaStepChainsToPixCoords");
+
+    if (!ccba)
+        return ERROR_INT("ccba not defined", procName, 1);
+    if (coordtype != CCB_GLOBAL_COORDS && coordtype != CCB_LOCAL_COORDS)
+        return ERROR_INT("coordtype not valid", procName, 1);
+
+    ncc = ccbaGetCount(ccba);  /* number of c.c. */
+    for (i = 0; i < ncc; i++) {
+        ccb = ccbaGetCcb(ccba, i);
+        if ((naa = ccb->step) == NULL)
+            return ERROR_INT("step numaa not found", procName, 1);
+        if ((boxa = ccb->boxa) == NULL)
+            return ERROR_INT("boxa not found", procName, 1);
+        if ((ptas = ccb->start) == NULL)
+            return ERROR_INT("start pta not found", procName, 1);
+
+            /* For global coords, get the (xul, yul) of the c.c.;
+             * otherwise, use relative coords. */
+        if (coordtype == CCB_LOCAL_COORDS) {
+            xul = 0;
+            yul = 0;
+        } else {  /* coordtype == CCB_GLOBAL_COORDS */
+                /* Get UL corner in global coords */
+            if (boxaGetBoxGeometry(boxa, 0, &xul, &yul, NULL, NULL))
+                return ERROR_INT("bounding rectangle not found", procName, 1);
+        }
+
+            /* Make a new ptaa, removing any old one */
+        nb = numaaGetCount(naa);   /* number of borders */
+        if ((ptaan = ptaaCreate(nb)) == NULL)
+            return ERROR_INT("ptaan not made", procName, 1);
+        if (coordtype == CCB_LOCAL_COORDS) {
+            if (ccb->local)   /* remove old one */
+                ptaaDestroy(&ccb->local);
+            ccb->local = ptaan;  /* save new local chain */
+        } else {   /* coordtype == CCB_GLOBAL_COORDS */
+            if (ccb->global)   /* remove old one */
+                ptaaDestroy(&ccb->global);
+            ccb->global = ptaan;  /* save new global chain */
+        }
+
+            /* Iterate through the borders for this c.c. */
+        for (j = 0; j < nb; j++) {
+            na = numaaGetNuma(naa, j, L_CLONE);
+            n = numaGetCount(na);   /* number of steps in border */
+            if ((ptan = ptaCreate(n + 1)) == NULL)
+                return ERROR_INT("ptan not made", procName, 1);
+            ptaaAddPta(ptaan, ptan, L_INSERT);
+            ptaGetIPt(ptas, j, &xstart, &ystart);
+            x = xul + xstart;
+            y = yul + ystart;
+            ptaAddPt(ptan, x, y);
+            for (k = 0; k < n; k++) {
+                numaGetIValue(na, k, &stepdir);
+                x += xpostab[stepdir];
+                y += ypostab[stepdir];
+                ptaAddPt(ptan, x, y);
+            }
+            numaDestroy(&na);
+        }
+        ccbDestroy(&ccb);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  ccbaGenerateSPGlobalLocs()
+ *
+ *      Input:  ccba
+ *              ptsflag  (CCB_SAVE_ALL_PTS or CCB_SAVE_TURNING_PTS)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This calculates the splocal rep if not yet made.
+ *      (2) It uses the local pixel values in splocal, the single
+ *          path pta, which are all relative to each c.c., to find
+ *          the corresponding global pixel locations, and stores
+ *          them in the spglobal pta.
+ *      (3) This lists only the turning points: it both makes a
+ *          valid svg file and is typically about half the size
+ *          when all border points are listed.
+ */
+l_int32
+ccbaGenerateSPGlobalLocs(CCBORDA  *ccba,
+                         l_int32   ptsflag)
+{
+l_int32  ncc, npt, i, j, xul, yul, x, y, delx, dely;
+l_int32  xp, yp, delxp, delyp;   /* prev point and increments */
+CCBORD  *ccb;
+PTA     *ptal, *ptag;
+
+    PROCNAME("ccbaGenerateSPGlobalLocs");
+
+    if (!ccba)
+        return ERROR_INT("ccba not defined", procName, 1);
+
+        /* Make sure we have a local single path representation */
+    if ((ccb = ccbaGetCcb(ccba, 0)) == NULL)
+        return ERROR_INT("no ccb", procName, 1);
+    if (!ccb->splocal)
+        ccbaGenerateSinglePath(ccba);
+    ccbDestroy(&ccb);  /* clone ref */
+
+    ncc = ccbaGetCount(ccba);  /* number of c.c. */
+    for (i = 0; i < ncc; i++) {
+        ccb = ccbaGetCcb(ccba, i);
+
+            /* Get the UL corner in global coords, (xul, yul), of the c.c. */
+        if (boxaGetBoxGeometry(ccb->boxa, 0, &xul, &yul, NULL, NULL))
+            return ERROR_INT("bounding rectangle not found", procName, 1);
+
+            /* Make a new spglobal pta, removing any old one */
+        ptal = ccb->splocal;
+        npt = ptaGetCount(ptal);   /* number of points */
+        if (ccb->spglobal)   /* remove old one */
+            ptaDestroy(&ccb->spglobal);
+        if ((ptag = ptaCreate(npt)) == NULL)
+            return ERROR_INT("ptag not made", procName, 1);
+        ccb->spglobal = ptag;  /* save new one */
+
+            /* Convert local to global */
+        if (ptsflag == CCB_SAVE_ALL_PTS) {
+            for (j = 0; j < npt; j++) {
+                ptaGetIPt(ptal, j, &x, &y);
+                ptaAddPt(ptag, x  + xul, y + yul);
+            }
+        } else {   /* ptsflag = CCB_SAVE_TURNING_PTS */
+            ptaGetIPt(ptal, 0, &xp, &yp);   /* get the 1st pt */
+            ptaAddPt(ptag, xp  + xul, yp + yul);   /* save the 1st pt */
+            if (npt == 2) {  /* get and save the 2nd pt  */
+                ptaGetIPt(ptal, 1, &x, &y);
+                ptaAddPt(ptag, x  + xul, y + yul);
+            } else if (npt > 2)  {
+                ptaGetIPt(ptal, 1, &x, &y);
+                delxp = x - xp;
+                delyp = y - yp;
+                xp = x;
+                yp = y;
+                for (j = 2; j < npt; j++) {
+                    ptaGetIPt(ptal, j, &x, &y);
+                    delx = x - xp;
+                    dely = y - yp;
+                    if (delx != delxp || dely != delyp)
+                        ptaAddPt(ptag, xp  + xul, yp + yul);
+                    xp = x;
+                    yp = y;
+                    delxp = delx;
+                    delyp = dely;
+                }
+                ptaAddPt(ptag, xp  + xul, yp + yul);
+            }
+        }
+
+        ccbDestroy(&ccb);  /* clone ref */
+    }
+
+    return 0;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                       Conversion to single path                     *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ccbaGenerateSinglePath()
+ *
+ *      Input:  ccba
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Generates a single border in local pixel coordinates.
+ *          For each c.c., if there is just an outer border, copy it.
+ *          If there are also hole borders, for each hole border,
+ *          determine the smallest horizontal or vertical
+ *          distance from the border to the outside of the c.c.,
+ *          and find a path through the c.c. for this cut.
+ *          We do this in a way that guarantees a pixel from the
+ *          hole border is the starting point of the path, and
+ *          we must verify that the path intersects the outer
+ *          border (if it intersects it, then it ends on it).
+ *          One can imagine pathological cases, but they may not
+ *          occur in images of text characters and un-textured
+ *          line graphics.
+ *      (2) Once it is verified that the path through the c.c.
+ *          intersects both the hole and outer borders, we
+ *          generate the full single path for all borders in the
+ *          c.c.  Starting at the start point on the outer
+ *          border, when we hit a line on a cut, we take
+ *          the cut, do the hold border, and return on the cut
+ *          to the outer border.  We compose a pta of the
+ *          outer border pts that are on cut paths, and for
+ *          every point on the outer border (as we go around),
+ *          we check against this pta.  When we find a matching
+ *          point in the pta, we do its cut path and hole border.
+ *          The single path is saved in the ccb.
+ */
+l_int32
+ccbaGenerateSinglePath(CCBORDA  *ccba)
+{
+l_int32   i, j, k, ncc, nb, ncut, npt, dir, len, state, lostholes;
+l_int32   x, y, xl, yl, xf, yf;
+BOX      *boxinner;
+BOXA     *boxa;
+CCBORD   *ccb;
+PTA      *pta, *ptac, *ptah;
+PTA      *ptahc;  /* cyclic permutation of hole border, with end pts at cut */
+PTA      *ptas;  /* output result: new single path for c.c. */
+PTA      *ptaf;  /* points on the hole borders that intersect with cuts */
+PTA      *ptal;  /* points on outer border that intersect with cuts */
+PTA      *ptap, *ptarp;   /* path and reverse path between borders */
+PTAA     *ptaa;
+PTAA     *ptaap;  /* ptaa for all paths between borders */
+
+    PROCNAME("ccbaGenerateSinglePath");
+
+    if (!ccba)
+        return ERROR_INT("ccba not defined", procName, 1);
+
+    ncc = ccbaGetCount(ccba);   /* number of c.c. */
+    lostholes = 0;
+    for (i = 0; i < ncc; i++) {
+        ccb = ccbaGetCcb(ccba, i);
+        if ((ptaa = ccb->local) == NULL) {
+            L_WARNING("local pixel loc array not found\n", procName);
+            continue;
+        }
+        nb = ptaaGetCount(ptaa);   /* number of borders in the c.c.  */
+
+            /* Prepare the output pta */
+        if (ccb->splocal)
+            ptaDestroy(&ccb->splocal);
+        if ((ptas = ptaCreate(0)) == NULL)
+            return ERROR_INT("ptas not made", procName, 1);
+        ccb->splocal = ptas;
+
+            /* If no holes, just concat the outer border */
+        pta = ptaaGetPta(ptaa, 0, L_CLONE);
+        if (nb == 1 || nb > NMAX_HOLES + 1) {
+            ptaJoin(ptas, pta, 0, -1);
+            ptaDestroy(&pta);  /* remove clone */
+            ccbDestroy(&ccb);  /* remove clone */
+            continue;
+        }
+
+            /* Find the (nb - 1) cut paths that connect holes
+             * with outer border */
+        boxa = ccb->boxa;
+        if ((ptaap = ptaaCreate(nb - 1)) == NULL)
+            return ERROR_INT("ptaap not made", procName, 1);
+        if ((ptaf = ptaCreate(nb - 1)) == NULL)
+            return ERROR_INT("ptaf not made", procName, 1);
+        if ((ptal = ptaCreate(nb - 1)) == NULL)
+            return ERROR_INT("ptal not made", procName, 1);
+        for (j = 1; j < nb; j++) {
+            boxinner = boxaGetBox(boxa, j, L_CLONE);
+
+                /* Find a short path and store it */
+            ptac = getCutPathForHole(ccb->pix, pta, boxinner, &dir, &len);
+            if (len == 0) {  /* bad: we lose the hole! */
+                lostholes++;
+/*                boxPrintStreamInfo(stderr, boxa->box[0]); */
+            }
+            ptaaAddPta(ptaap, ptac, L_INSERT);
+/*            fprintf(stderr, "dir = %d, length = %d\n", dir, len); */
+/*            ptaWriteStream(stderr, ptac, 1); */
+
+                /* Store the first and last points in the cut path,
+                 * which must be on a hole border and the outer
+                 * border, respectively */
+            ncut = ptaGetCount(ptac);
+            if (ncut == 0) {   /* missed hole; neg coords won't match */
+                ptaAddPt(ptaf, -1, -1);
+                ptaAddPt(ptal, -1, -1);
+            } else {
+                ptaGetIPt(ptac, 0, &x, &y);
+                ptaAddPt(ptaf, x, y);
+                ptaGetIPt(ptac, ncut - 1, &x, &y);
+                ptaAddPt(ptal, x, y);
+            }
+            boxDestroy(&boxinner);
+        }
+
+            /* Make a single path for the c.c. using these connections */
+        npt = ptaGetCount(pta);  /* outer border pts */
+        for (k = 0; k < npt; k++) {
+            ptaGetIPt(pta, k, &x, &y);
+            if (k == 0) {   /* if there is a cut at the first point,
+                             * we can wait until the end to take it */
+                ptaAddPt(ptas, x, y);
+                continue;
+            }
+            state = L_NOT_FOUND;
+            for (j = 0; j < nb - 1; j++) {  /* iterate over cut end pts */
+                ptaGetIPt(ptal, j, &xl, &yl);  /* cut point on outer border */
+                if (x == xl && y == yl) {  /* take this cut to the hole */
+                    state = L_FOUND;
+                    ptap = ptaaGetPta(ptaap, j, L_CLONE);
+                    if ((ptarp = ptaReverse(ptap, 1)) == NULL)
+                        return ERROR_INT("ptarp not made", procName, 1);
+                        /* Cut point on hole border: */
+                    ptaGetIPt(ptaf, j, &xf, &yf);
+                        /* Hole border: */
+                    ptah = ptaaGetPta(ptaa, j + 1, L_CLONE);
+                    ptahc = ptaCyclicPerm(ptah, xf, yf);
+/*                    ptaWriteStream(stderr, ptahc, 1); */
+                    ptaJoin(ptas, ptarp, 0, -1);
+                    ptaJoin(ptas, ptahc, 0, -1);
+                    ptaJoin(ptas, ptap, 0, -1);
+                    ptaDestroy(&ptap);
+                    ptaDestroy(&ptarp);
+                    ptaDestroy(&ptah);
+                    ptaDestroy(&ptahc);
+                    break;
+                }
+            }
+            if (state == L_NOT_FOUND)
+                ptaAddPt(ptas, x, y);
+        }
+
+/*        ptaWriteStream(stderr, ptas, 1); */
+        ptaaDestroy(&ptaap);
+        ptaDestroy(&ptaf);
+        ptaDestroy(&ptal);
+        ptaDestroy(&pta);  /* remove clone */
+        ccbDestroy(&ccb);  /* remove clone */
+    }
+
+    if (lostholes > 0)
+        L_WARNING("***** %d lost holes *****\n", procName, lostholes);
+
+    return 0;
+}
+
+
+/*!
+ *  getCutPathForHole()
+ *
+ *      Input:  pix  (of c.c.)
+ *              pta  (of outer border)
+ *              boxinner (b.b. of hole path)
+ *              &dir  (direction (0-3), returned; only needed for debug)
+ *              &len  (length of path, returned)
+ *      Return: pta of pts on cut path from the hole border
+ *              to the outer border, including end points on
+ *              both borders; or null on error
+ *
+ *  Notes:
+ *      (1) If we don't find a path, we return a pta with no pts
+ *          in it and len = 0.
+ *      (2) The goal is to get a reasonably short path between the
+ *          inner and outer borders, that goes entirely within the fg of
+ *          the pix.  This function is cheap-and-dirty, may fail for some
+ *          holes in complex topologies such as those you might find in a
+ *          moderately dark scanned halftone.  If it fails to find a
+ *          path to any particular hole, it gives a warning, and because
+ *          that hole path is not included, the hole will not be rendered.
+ */
+PTA *
+getCutPathForHole(PIX      *pix,
+                  PTA      *pta,
+                  BOX      *boxinner,
+                  l_int32  *pdir,
+                  l_int32  *plen)
+{
+l_int32   w, h, nc, x, y, xl, yl, xmid, ymid;
+l_uint32  val;
+PTA      *ptac;
+
+    PROCNAME("getCutPathForHole");
+
+    if (!pix)
+        return (PTA *)ERROR_PTR("pix not defined", procName, NULL);
+    if (!pta)
+        return (PTA *)ERROR_PTR("pta not defined", procName, NULL);
+    if (!boxinner)
+        return (PTA *)ERROR_PTR("boxinner not defined", procName, NULL);
+
+    w = pixGetWidth(pix);
+    h = pixGetHeight(pix);
+
+    if ((ptac = ptaCreate(4)) == NULL)
+        return (PTA *)ERROR_PTR("ptac not made", procName, NULL);
+    xmid = boxinner->x + boxinner->w / 2;
+    ymid = boxinner->y + boxinner->h / 2;
+
+        /* try top first */
+    for (y = ymid; y >= 0; y--) {
+        pixGetPixel(pix, xmid, y, &val);
+        if (val == 1) {
+            ptaAddPt(ptac, xmid, y);
+            break;
+        }
+    }
+    for (y = y - 1; y >= 0; y--) {
+        pixGetPixel(pix, xmid, y, &val);
+        if (val == 1)
+            ptaAddPt(ptac, xmid, y);
+        else
+            break;
+    }
+    nc = ptaGetCount(ptac);
+    ptaGetIPt(ptac, nc - 1, &xl, &yl);
+    if (ptaContainsPt(pta, xl, yl)) {
+        *pdir = 1;
+        *plen = nc;
+        return ptac;
+    }
+
+        /* Next try bottom */
+    ptaEmpty(ptac);
+    for (y = ymid; y < h; y++) {
+        pixGetPixel(pix, xmid, y, &val);
+        if (val == 1) {
+            ptaAddPt(ptac, xmid, y);
+            break;
+        }
+    }
+    for (y = y + 1; y < h; y++) {
+        pixGetPixel(pix, xmid, y, &val);
+        if (val == 1)
+            ptaAddPt(ptac, xmid, y);
+        else
+            break;
+    }
+    nc = ptaGetCount(ptac);
+    ptaGetIPt(ptac, nc - 1, &xl, &yl);
+    if (ptaContainsPt(pta, xl, yl)) {
+        *pdir = 3;
+        *plen = nc;
+        return ptac;
+    }
+
+        /* Next try left */
+    ptaEmpty(ptac);
+    for (x = xmid; x >= 0; x--) {
+        pixGetPixel(pix, x, ymid, &val);
+        if (val == 1) {
+            ptaAddPt(ptac, x, ymid);
+            break;
+        }
+    }
+    for (x = x - 1; x >= 0; x--) {
+        pixGetPixel(pix, x, ymid, &val);
+        if (val == 1)
+            ptaAddPt(ptac, x, ymid);
+        else
+            break;
+    }
+    nc = ptaGetCount(ptac);
+    ptaGetIPt(ptac, nc - 1, &xl, &yl);
+    if (ptaContainsPt(pta, xl, yl)) {
+        *pdir = 0;
+        *plen = nc;
+        return ptac;
+    }
+
+        /* Finally try right */
+    ptaEmpty(ptac);
+    for (x = xmid; x < w; x++) {
+        pixGetPixel(pix, x, ymid, &val);
+        if (val == 1) {
+            ptaAddPt(ptac, x, ymid);
+            break;
+        }
+    }
+    for (x = x + 1; x < w; x++) {
+        pixGetPixel(pix, x, ymid, &val);
+        if (val == 1)
+            ptaAddPt(ptac, x, ymid);
+        else
+            break;
+    }
+    nc = ptaGetCount(ptac);
+    ptaGetIPt(ptac, nc - 1, &xl, &yl);
+    if (ptaContainsPt(pta, xl, yl)) {
+        *pdir = 2;
+        *plen = nc;
+        return ptac;
+    }
+
+        /* If we get here, we've failed! */
+    ptaEmpty(ptac);
+    L_WARNING("no path found\n", procName);
+    *plen = 0;
+    return ptac;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                            Border rendering                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ccbaDisplayBorder()
+ *
+ *      Input:  ccba
+ *      Return: pix of border pixels, or null on error
+ *
+ *  Notes:
+ *      (1) Uses global ptaa, which gives each border pixel in
+ *          global coordinates, and must be computed in advance
+ *          by calling ccbaGenerateGlobalLocs().
+ */
+PIX *
+ccbaDisplayBorder(CCBORDA  *ccba)
+{
+l_int32  ncc, nb, n, i, j, k, x, y;
+CCBORD  *ccb;
+PIX     *pixd;
+PTAA    *ptaa;
+PTA     *pta;
+
+    PROCNAME("ccbaDisplayBorder");
+
+    if (!ccba)
+        return (PIX *)ERROR_PTR("ccba not defined", procName, NULL);
+
+    if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    ncc = ccbaGetCount(ccba);   /* number of c.c. */
+    for (i = 0; i < ncc; i++) {
+        ccb = ccbaGetCcb(ccba, i);
+        if ((ptaa = ccb->global) == NULL) {
+            L_WARNING("global pixel loc array not found", procName);
+            continue;
+        }
+        nb = ptaaGetCount(ptaa);   /* number of borders in the c.c.  */
+        for (j = 0; j < nb; j++) {
+            pta = ptaaGetPta(ptaa, j, L_CLONE);
+            n = ptaGetCount(pta);   /* number of pixels in the border */
+            for (k = 0; k < n; k++) {
+                ptaGetIPt(pta, k, &x, &y);
+                pixSetPixel(pixd, x, y, 1);
+            }
+            ptaDestroy(&pta);
+        }
+        ccbDestroy(&ccb);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  ccbaDisplaySPBorder()
+ *
+ *      Input:  ccba
+ *      Return: pix of border pixels, or null on error
+ *
+ *  Notes:
+ *      (1) Uses spglobal pta, which gives each border pixel in
+ *          global coordinates, one path per c.c., and must
+ *          be computed in advance by calling ccbaGenerateSPGlobalLocs().
+ */
+PIX *
+ccbaDisplaySPBorder(CCBORDA  *ccba)
+{
+l_int32  ncc, npt, i, j, x, y;
+CCBORD  *ccb;
+PIX     *pixd;
+PTA     *ptag;
+
+    PROCNAME("ccbaDisplaySPBorder");
+
+    if (!ccba)
+        return (PIX *)ERROR_PTR("ccba not defined", procName, NULL);
+
+    if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    ncc = ccbaGetCount(ccba);   /* number of c.c. */
+    for (i = 0; i < ncc; i++) {
+        ccb = ccbaGetCcb(ccba, i);
+        if ((ptag = ccb->spglobal) == NULL) {
+            L_WARNING("spglobal pixel loc array not found\n", procName);
+            continue;
+        }
+        npt = ptaGetCount(ptag);   /* number of pixels on path */
+        for (j = 0; j < npt; j++) {
+            ptaGetIPt(ptag, j, &x, &y);
+            pixSetPixel(pixd, x, y, 1);
+        }
+        ccbDestroy(&ccb);  /* clone ref */
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  ccbaDisplayImage1()
+ *
+ *      Input:  ccborda
+ *      Return: pix of image, or null on error
+ *
+ *  Notes:
+ *      (1) Uses local ptaa, which gives each border pixel in
+ *          local coordinates, so the actual pixel positions must
+ *          be computed using all offsets.
+ *      (2) For the holes, use coordinates relative to the c.c.
+ *      (3) This is slower than Method 2.
+ *      (4) This uses topological properties (Method 1) to do scan
+ *          conversion to raster
+ *
+ *  This algorithm deserves some commentary.
+ *
+ *  I first tried the following:
+ *    - outer borders: 4-fill from outside, stopping at the
+ *         border, using pixFillClosedBorders()
+ *    - inner borders: 4-fill from outside, stopping again
+ *         at the border, XOR with the border, and invert
+ *         to get the hole.  This did not work, because if
+ *         you have a hole border that looks like:
+ *
+ *                x x x x x x
+ *                x          x
+ *                x   x x x   x
+ *                  x x o x   x
+ *                      x     x
+ *                      x     x
+ *                        x x x
+ *
+ *         if you 4-fill from the outside, the pixel 'o' will
+ *         not be filled!  XORing with the border leaves it OFF.
+ *         Inverting then gives a single bad ON pixel that is not
+ *         actually part of the hole.
+ *
+ *  So what you must do instead is 4-fill the holes from inside.
+ *  You can do this from a seedfill, using a pix with the hole
+ *  border as the filling mask.  But you need to start with a
+ *  pixel inside the hole.  How is this determined?  The best
+ *  way is from the contour.  We have a right-hand shoulder
+ *  rule for inside (i.e., the filled region).   Take the
+ *  first 2 pixels of the hole border, and compute dx and dy
+ *  (second coord minus first coord:  dx = sx - fx, dy = sy - fy).
+ *  There are 8 possibilities, depending on the values of dx and
+ *  dy (which can each be -1, 0, and +1, but not both 0).
+ *  These 8 cases can be broken into 4; see the simple algorithm below.
+ *  Once you have an interior seed pixel, you fill from the seed,
+ *  clipping with the hole border pix by filling into its invert.
+ *
+ *  You then successively XOR these interior filled components, in any order.
+ */
+PIX *
+ccbaDisplayImage1(CCBORDA  *ccba)
+{
+l_int32  ncc, i, nb, n, j, k, x, y, xul, yul, xoff, yoff, w, h;
+l_int32  fpx, fpy, spx, spy, xs, ys;
+BOX     *box;
+BOXA    *boxa;
+CCBORD  *ccb;
+PIX     *pixd, *pixt, *pixh;
+PTAA    *ptaa;
+PTA     *pta;
+
+    PROCNAME("ccbaDisplayImage1");
+
+    if (!ccba)
+        return (PIX *)ERROR_PTR("ccba not defined", procName, NULL);
+
+    if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    ncc = ccbaGetCount(ccba);
+    for (i = 0; i < ncc; i++) {
+        ccb = ccbaGetCcb(ccba, i);
+        if ((boxa = ccb->boxa) == NULL)
+            return (PIX *)ERROR_PTR("boxa not found", procName, NULL);
+
+            /* Render border in pixt */
+        if ((ptaa = ccb->local) == NULL) {
+            L_WARNING("local chain array not found\n", procName);
+            continue;
+        }
+
+        nb = ptaaGetCount(ptaa);   /* number of borders in the c.c.  */
+        for (j = 0; j < nb; j++) {
+            if ((box = boxaGetBox(boxa, j, L_CLONE)) == NULL)
+                return (PIX *)ERROR_PTR("b. box not found", procName, NULL);
+            if (j == 0) {
+                boxGetGeometry(box, &xul, &yul, &w, &h);
+                xoff = yoff = 0;
+            } else {
+                boxGetGeometry(box, &xoff, &yoff, &w, &h);
+            }
+            boxDestroy(&box);
+
+                /* Render the border in a minimum-sized pix;
+                 * subtract xoff and yoff because the pixel
+                 * location is stored relative to the c.c., but
+                 * we need it relative to just the hole border. */
+            if ((pixt = pixCreate(w, h, 1)) == NULL)
+                return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+            pta = ptaaGetPta(ptaa, j, L_CLONE);
+            n = ptaGetCount(pta);   /* number of pixels in the border */
+            for (k = 0; k < n; k++) {
+                ptaGetIPt(pta, k, &x, &y);
+                pixSetPixel(pixt, x - xoff, y - yoff, 1);
+                if (j > 0) {   /* need this for finding hole border pixel */
+                    if (k == 0) {
+                        fpx = x - xoff;
+                        fpy = y - yoff;
+                    }
+                    if (k == 1) {
+                        spx = x - xoff;
+                        spy = y - yoff;
+                    }
+                }
+            }
+            ptaDestroy(&pta);
+
+                /* Get the filled component */
+            if (j == 0) {  /* if outer border, fill from outer boundary */
+                if ((pixh = pixFillClosedBorders(pixt, 4)) == NULL)
+                    return (PIX *)ERROR_PTR("pixh not made", procName, NULL);
+            } else {   /* fill the hole from inside */
+                    /* get the location of a seed pixel in the hole */
+                locateOutsideSeedPixel(fpx, fpy, spx, spy, &xs, &ys);
+
+                    /* Put seed in hole and fill interior of hole,
+                     * using pixt as clipping mask */
+                if ((pixh = pixCreateTemplate(pixt)) == NULL)
+                    return (PIX *)ERROR_PTR("pixh not made", procName, NULL);
+                pixSetPixel(pixh, xs, ys, 1);  /* put seed pixel in hole */
+                pixInvert(pixt, pixt);  /* to make filling mask */
+                pixSeedfillBinary(pixh, pixh, pixt, 4);  /* 4-fill hole */
+            }
+
+                /* XOR into the dest */
+            pixRasterop(pixd, xul + xoff, yul + yoff, w, h, PIX_XOR,
+                        pixh, 0, 0);
+            pixDestroy(&pixt);
+            pixDestroy(&pixh);
+        }
+
+        ccbDestroy(&ccb);
+    }
+
+    return pixd;
+}
+
+
+
+/*!
+ *  ccbaDisplayImage2()
+ *
+ *      Input: ccborda
+ *      Return: pix of image, or null on error
+ *
+ *  Notes:
+ *      (1) Uses local chain ptaa, which gives each border pixel in
+ *          local coordinates, so the actual pixel positions must
+ *          be computed using all offsets.
+ *      (2) Treats exterior and hole borders on equivalent
+ *          footing, and does all calculations on a pix
+ *          that spans the c.c. with a 1 pixel added boundary.
+ *      (3) This uses topological properties (Method 2) to do scan
+ *          conversion to raster
+ *      (4) The algorithm is described at the top of this file (Method 2).
+ *          It is preferred to Method 1 because it is between 1.2x and 2x
+ *          faster than Method 1.
+ */
+PIX *
+ccbaDisplayImage2(CCBORDA  *ccba)
+{
+l_int32  ncc, nb, n, i, j, k, x, y, xul, yul, w, h;
+l_int32  fpx, fpy, spx, spy, xs, ys;
+BOXA    *boxa;
+CCBORD  *ccb;
+PIX     *pixd, *pixc, *pixs;
+PTAA    *ptaa;
+PTA     *pta;
+
+    PROCNAME("ccbaDisplayImage2");
+
+    if (!ccba)
+        return (PIX *)ERROR_PTR("ccba not defined", procName, NULL);
+
+    if ((pixd = pixCreate(ccba->w, ccba->h, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    ncc = ccbaGetCount(ccba);
+    for (i = 0; i < ncc; i++) {
+
+            /* Generate clipping mask from border pixels and seed image
+             * from one seed for each closed border. */
+        ccb = ccbaGetCcb(ccba, i);
+        if ((boxa = ccb->boxa) == NULL)
+            return (PIX *)ERROR_PTR("boxa not found", procName, NULL);
+        if (boxaGetBoxGeometry(boxa, 0, &xul, &yul, &w, &h))
+            return (PIX *)ERROR_PTR("b. box not found", procName, NULL);
+        if ((pixc = pixCreate(w + 2, h + 2, 1)) == NULL)
+            return (PIX *)ERROR_PTR("pixc not made", procName, NULL);
+        if ((pixs = pixCreateTemplate(pixc)) == NULL)
+            return (PIX *)ERROR_PTR("pixs not made", procName, NULL);
+
+        if ((ptaa = ccb->local) == NULL) {
+            L_WARNING("local chain array not found\n", procName);
+            continue;
+        }
+        nb = ptaaGetCount(ptaa);   /* number of borders in the c.c.  */
+        for (j = 0; j < nb; j++) {
+            pta = ptaaGetPta(ptaa, j, L_CLONE);
+            n = ptaGetCount(pta);   /* number of pixels in the border */
+
+                /* Render border pixels in pixc */
+            for (k = 0; k < n; k++) {
+                ptaGetIPt(pta, k, &x, &y);
+                pixSetPixel(pixc, x + 1, y + 1, 1);
+                if (k == 0) {
+                    fpx = x + 1;
+                    fpy = y + 1;
+                } else if (k == 1) {
+                    spx = x + 1;
+                    spy = y + 1;
+                }
+            }
+
+                /* Get and set seed pixel for this border in pixs */
+            if (n > 1)
+                locateOutsideSeedPixel(fpx, fpy, spx, spy, &xs, &ys);
+            else  /* isolated c.c. */
+                xs = ys = 0;
+            pixSetPixel(pixs, xs, ys, 1);
+            ptaDestroy(&pta);
+        }
+
+            /* Fill from seeds in pixs, using pixc as the clipping mask,
+             * to reconstruct the c.c. */
+        pixInvert(pixc, pixc);  /* to convert clipping -> filling mask */
+        pixSeedfillBinary(pixs, pixs, pixc, 4);  /* 4-fill */
+        pixInvert(pixs, pixs);  /* to make the c.c. */
+
+            /* XOR into the dest */
+        pixRasterop(pixd, xul, yul, w, h, PIX_XOR, pixs, 1, 1);
+
+        pixDestroy(&pixc);
+        pixDestroy(&pixs);
+        ccbDestroy(&ccb);  /* ref-counted */
+    }
+
+    return pixd;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                            Serialize for I/O                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ccbaWrite()
+ *
+ *      Input:  filename
+ *              ccba
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+ccbaWrite(const char  *filename,
+          CCBORDA     *ccba)
+{
+FILE  *fp;
+
+    PROCNAME("ccbaWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!ccba)
+        return ERROR_INT("ccba not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb+")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (ccbaWriteStream(fp, ccba)) {
+        fclose(fp);
+        return ERROR_INT("ccba not written to stream", procName, 1);
+    }
+
+    fclose(fp);
+    return 0;
+}
+
+
+
+/*!
+ *  ccbaWriteStream()
+ *
+ *      Input:  stream
+ *              ccba
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Format:  ccba: %7d cc\n (num. c.c.) (ascii)   (18B)
+ *           pix width (4B)
+ *           pix height (4B)
+ *           [for i = 1, ncc]
+ *               ulx  (4B)
+ *               uly  (4B)
+ *               w    (4B)       -- not req'd for reconstruction
+ *               h    (4B)       -- not req'd for reconstruction
+ *               number of borders (4B)
+ *               [for j = 1, nb]
+ *                   startx  (4B)
+ *                   starty  (4B)
+ *                   [for k = 1, nb]
+ *                        2 steps (1B)
+ *                   end in z8 or 88  (1B)
+ */
+l_int32
+ccbaWriteStream(FILE     *fp,
+                CCBORDA  *ccba)
+{
+char        strbuf[256];
+l_uint8     bval;
+l_uint8    *datain, *dataout;
+l_int32     i, j, k, bx, by, bw, bh, val, startx, starty;
+l_int32     ncc, nb, n;
+l_uint32    w, h;
+size_t      inbytes, outbytes;
+L_BBUFFER  *bbuf;
+CCBORD     *ccb;
+NUMA       *na;
+NUMAA      *naa;
+PTA        *pta;
+
+    PROCNAME("ccbaWriteStream");
+
+#if  !HAVE_LIBZ  /* defined in environ.h */
+    return ERROR_INT("no libz: can't write data", procName, 1);
+#else
+
+    if (!fp)
+        return ERROR_INT("stream not open", procName, 1);
+    if (!ccba)
+        return ERROR_INT("ccba not defined", procName, 1);
+
+    if ((bbuf = bbufferCreate(NULL, 1000)) == NULL)
+        return ERROR_INT("bbuf not made", procName, 1);
+
+    ncc = ccbaGetCount(ccba);
+    sprintf(strbuf, "ccba: %7d cc\n", ncc);
+    bbufferRead(bbuf, (l_uint8 *)strbuf, 18);
+    w = pixGetWidth(ccba->pix);
+    h = pixGetHeight(ccba->pix);
+    bbufferRead(bbuf, (l_uint8 *)&w, 4);  /* width */
+    bbufferRead(bbuf, (l_uint8 *)&h, 4);  /* height */
+    for (i = 0; i < ncc; i++) {
+        ccb = ccbaGetCcb(ccba, i);
+        if (boxaGetBoxGeometry(ccb->boxa, 0, &bx, &by, &bw, &bh))
+            return ERROR_INT("bounding box not found", procName, 1);
+        bbufferRead(bbuf, (l_uint8 *)&bx, 4);  /* ulx of c.c. */
+        bbufferRead(bbuf, (l_uint8 *)&by, 4);  /* uly of c.c. */
+        bbufferRead(bbuf, (l_uint8 *)&bw, 4);  /* w of c.c. */
+        bbufferRead(bbuf, (l_uint8 *)&bh, 4);  /* h of c.c. */
+        if ((naa = ccb->step) == NULL) {
+            ccbaGenerateStepChains(ccba);
+            naa = ccb->step;
+        }
+        nb = numaaGetCount(naa);
+        bbufferRead(bbuf, (l_uint8 *)&nb, 4);  /* number of borders in c.c. */
+        pta = ccb->start;
+        for (j = 0; j < nb; j++) {
+            ptaGetIPt(pta, j, &startx, &starty);
+            bbufferRead(bbuf, (l_uint8 *)&startx, 4);  /* starting x in border */
+            bbufferRead(bbuf, (l_uint8 *)&starty, 4);  /* starting y in border */
+            na = numaaGetNuma(naa, j, L_CLONE);
+            n = numaGetCount(na);
+            for (k = 0; k < n; k++) {
+                numaGetIValue(na, k, &val);
+                if (k % 2 == 0)
+                    bval = (l_uint8)val << 4;
+                else
+                    bval |= (l_uint8)val;
+                if (k % 2 == 1)
+                    bbufferRead(bbuf, (l_uint8 *)&bval, 1);  /* 2 border steps */
+            }
+            if (n % 2 == 1) {
+                bval |= 0x8;
+                bbufferRead(bbuf, (l_uint8 *)&bval, 1); /* end with 0xz8,   */
+                                             /* where z = {0..7} */
+            } else {  /* n % 2 == 0 */
+                bval = 0x88;
+                bbufferRead(bbuf, (l_uint8 *)&bval, 1);   /* end with 0x88 */
+            }
+            numaDestroy(&na);
+        }
+        ccbDestroy(&ccb);
+    }
+
+    datain = bbufferDestroyAndSaveData(&bbuf, &inbytes);
+    dataout = zlibCompress(datain, inbytes, &outbytes);
+    fwrite(dataout, 1, outbytes, fp);
+
+    LEPT_FREE(datain);
+    LEPT_FREE(dataout);
+    return 0;
+
+#endif  /* !HAVE_LIBZ */
+}
+
+
+/*!
+ *  ccbaRead()
+ *
+ *      Input:  filename
+ *      Return: ccba, or null on error
+ */
+CCBORDA *
+ccbaRead(const char  *filename)
+{
+FILE     *fp;
+CCBORDA  *ccba;
+
+    PROCNAME("ccbaRead");
+
+    if (!filename)
+        return (CCBORDA *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (CCBORDA *)ERROR_PTR("stream not opened", procName, NULL);
+    ccba = ccbaReadStream(fp);
+    fclose(fp);
+
+    if (!ccba)
+        return (CCBORDA *)ERROR_PTR("ccba not returned", procName, NULL);
+    return ccba;
+}
+
+
+/*!
+ *  ccbaReadStream()
+ *
+ *      Input:   stream
+ *      Return:  ccba, or null on error
+ *
+ *  Format:  ccba: %7d cc\n (num. c.c.) (ascii)   (17B)
+ *           pix width (4B)
+ *           pix height (4B)
+ *           [for i = 1, ncc]
+ *               ulx  (4B)
+ *               uly  (4B)
+ *               w    (4B)       -- not req'd for reconstruction
+ *               h    (4B)       -- not req'd for reconstruction
+ *               number of borders (4B)
+ *               [for j = 1, nb]
+ *                   startx  (4B)
+ *                   starty  (4B)
+ *                   [for k = 1, nb]
+ *                        2 steps (1B)
+ *                   end in z8 or 88  (1B)
+ */
+CCBORDA *
+ccbaReadStream(FILE  *fp)
+{
+char      strbuf[256];
+l_uint8   bval;
+l_uint8  *datain, *dataout;
+l_int32   i, j, startx, starty;
+l_int32   offset, nib1, nib2;
+l_int32   ncc, nb;
+l_uint32  width, height, w, h, xoff, yoff;
+size_t    inbytes, outbytes;
+BOX      *box;
+CCBORD   *ccb;
+CCBORDA  *ccba;
+NUMA     *na;
+NUMAA    *step;
+
+    PROCNAME("ccbaReadStream");
+
+#if  !HAVE_LIBZ  /* defined in environ.h */
+    return (CCBORDA *)ERROR_PTR("no libz: can't read data", procName, NULL);
+#else
+
+    if (!fp)
+        return (CCBORDA *)ERROR_PTR("stream not open", procName, NULL);
+
+    if ((datain = l_binaryReadStream(fp, &inbytes)) == NULL)
+        return (CCBORDA *)ERROR_PTR("data not read from file", procName, NULL);
+
+    if ((dataout = zlibUncompress(datain, inbytes, &outbytes)) == NULL)
+        return (CCBORDA *)ERROR_PTR("dataout not made", procName, NULL);
+
+    offset = 18;
+    memcpy((void *)strbuf, (void *)dataout, offset);
+    strbuf[17] = '\0';
+    if (strncmp(strbuf, "ccba:", 5))
+        return (CCBORDA *)ERROR_PTR("file not type ccba", procName, NULL);
+    sscanf(strbuf, "ccba: %7d cc\n", &ncc);
+/*    fprintf(stderr, "ncc = %d\n", ncc); */
+    if ((ccba = ccbaCreate(NULL, ncc)) == NULL)
+        return (CCBORDA *)ERROR_PTR("ccba not made", procName, NULL);
+
+    memcpy((void *)&width, (void *)(dataout + offset), 4);
+    offset += 4;
+    memcpy((void *)&height, (void *)(dataout + offset), 4);
+    offset += 4;
+    ccba->w = width;
+    ccba->h = height;
+/*    fprintf(stderr, "width = %d, height = %d\n", width, height); */
+
+    for (i = 0; i < ncc; i++) {  /* should be ncc */
+        if ((ccb = ccbCreate(NULL)) == NULL)
+            return (CCBORDA *)ERROR_PTR("ccb not made", procName, NULL);
+        ccbaAddCcb(ccba, ccb);
+
+        memcpy((void *)&xoff, (void *)(dataout + offset), 4);
+        offset += 4;
+        memcpy((void *)&yoff, (void *)(dataout + offset), 4);
+        offset += 4;
+        memcpy((void *)&w, (void *)(dataout + offset), 4);
+        offset += 4;
+        memcpy((void *)&h, (void *)(dataout + offset), 4);
+        offset += 4;
+        if ((box = boxCreate(xoff, yoff, w, h)) == NULL)
+            return (CCBORDA *)ERROR_PTR("box not made", procName, NULL);
+        boxaAddBox(ccb->boxa, box, L_INSERT);
+/*        fprintf(stderr, "xoff = %d, yoff = %d, w = %d, h = %d\n",
+                xoff, yoff, w, h); */
+
+        memcpy((void *)&nb, (void *)(dataout + offset), 4);
+        offset += 4;
+/*        fprintf(stderr, "num borders = %d\n", nb); */
+        if ((step = numaaCreate(nb)) == NULL)
+            return (CCBORDA *)ERROR_PTR("step numaa not made", procName, NULL);
+        ccb->step = step;
+
+        for (j = 0; j < nb; j++) {  /* should be nb */
+            memcpy((void *)&startx, (void *)(dataout + offset), 4);
+            offset += 4;
+            memcpy((void *)&starty, (void *)(dataout + offset), 4);
+            offset += 4;
+            ptaAddPt(ccb->start, startx, starty);
+/*            fprintf(stderr, "startx = %d, starty = %d\n", startx, starty); */
+            if ((na = numaCreate(0)) == NULL)
+                return (CCBORDA *)ERROR_PTR("na not made", procName, NULL);
+            numaaAddNuma(step, na, L_INSERT);
+
+            while(1) {
+                bval = *(dataout + offset);
+                offset++;
+                nib1 = (bval >> 4);
+                nib2 = bval & 0xf;
+                if (nib1 != 8)
+                    numaAddNumber(na, nib1);
+                else
+                    break;
+                if (nib2 != 8)
+                    numaAddNumber(na, nib2);
+                else
+                    break;
+            }
+        }
+    }
+    LEPT_FREE(datain);
+    LEPT_FREE(dataout);
+
+    return ccba;
+
+#endif  /* !HAVE_LIBZ */
+}
+
+
+/*---------------------------------------------------------------------*
+ *                                SVG Output                           *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ccbaWriteSVG()
+ *
+ *      Input:  filename
+ *              ccba
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+ccbaWriteSVG(const char  *filename,
+             CCBORDA     *ccba)
+{
+char  *svgstr;
+
+    PROCNAME("ccbaWriteSVG");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!ccba)
+        return ERROR_INT("ccba not defined", procName, 1);
+
+    if ((svgstr = ccbaWriteSVGString(filename, ccba)) == NULL)
+        return ERROR_INT("svgstr not made", procName, 1);
+
+    l_binaryWrite(filename, "w", svgstr, strlen(svgstr));
+    LEPT_FREE(svgstr);
+
+    return 0;
+}
+
+
+/*!
+ *  ccbaWriteSVGString()
+ *
+ *      Input:  filename
+ *              ccba
+ *      Return: string in svg-formatted, that can be written to file,
+ *              or null on error.
+ */
+char  *
+ccbaWriteSVGString(const char  *filename,
+                   CCBORDA     *ccba)
+{
+char    *svgstr;
+char     smallbuf[256];
+char     line0[] = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>";
+char     line1[] = "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20000303 Stylable//EN\" \"http://www.w3.org/TR/2000/03/WD-SVG-20000303/DTD/svg-20000303-stylable.dtd\">";
+char     line2[] = "<svg>";
+char     line3[] = "<polygon style=\"stroke-width:1;stroke:black;\" points=\"";
+char     line4[] = "\" />";
+char     line5[] = "</svg>";
+char     space[] = " ";
+l_int32  i, j, ncc, npt, x, y;
+CCBORD  *ccb;
+PTA     *pta;
+SARRAY  *sa;
+
+    PROCNAME("ccbaWriteSVGString");
+
+    if (!filename)
+        return (char *)ERROR_PTR("filename not defined", procName, NULL);
+    if (!ccba)
+        return (char *)ERROR_PTR("ccba not defined", procName, NULL);
+
+    if ((sa = sarrayCreate(0)) == NULL)
+        return (char *)ERROR_PTR("sa not made", procName, NULL);
+    sarrayAddString(sa, line0, L_COPY);
+    sarrayAddString(sa, line1, L_COPY);
+    sarrayAddString(sa, line2, L_COPY);
+
+    ncc = ccbaGetCount(ccba);
+    for (i = 0; i < ncc; i++) {
+        if ((ccb = ccbaGetCcb(ccba, i)) == NULL)
+            return (char *)ERROR_PTR("ccb not found", procName, NULL);
+        if ((pta = ccb->spglobal) == NULL)
+            return (char *)ERROR_PTR("spglobal not made", procName, NULL);
+        sarrayAddString(sa, line3, L_COPY);
+        npt = ptaGetCount(pta);
+        for (j = 0; j < npt; j++) {
+            ptaGetIPt(pta, j, &x, &y);
+            sprintf(smallbuf, "%0d,%0d", x, y);
+            sarrayAddString(sa, smallbuf, L_COPY);
+        }
+        sarrayAddString(sa, line4, L_COPY);
+        ccbDestroy(&ccb);
+    }
+    sarrayAddString(sa, line5, L_COPY);
+    sarrayAddString(sa, space, L_COPY);
+
+    svgstr = sarrayToString(sa, 1);
+/*    fprintf(stderr, "%s", svgstr); */
+
+    sarrayDestroy(&sa);
+    return svgstr;
+}
diff --git a/src/ccbord.h b/src/ccbord.h
new file mode 100644 (file)
index 0000000..47ec57a
--- /dev/null
@@ -0,0 +1,114 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_CCBORD_H
+#define  LEPTONICA_CCBORD_H
+
+/*
+ *  ccbord.h
+ *
+ *           CCBord:   represents a single connected component
+ *           CCBorda:  an array of CCBord
+ */
+
+    /* Use in ccbaStepChainsToPixCoords() */
+enum {
+      CCB_LOCAL_COORDS = 1,
+      CCB_GLOBAL_COORDS = 2
+};
+
+    /* Use in ccbaGenerateSPGlobalLocs() */
+enum {
+      CCB_SAVE_ALL_PTS = 1,
+      CCB_SAVE_TURNING_PTS = 2
+};
+
+
+    /* CCBord contains:
+     *
+     *    (1) a minimally-clipped bitmap of the component (pix),
+     *    (2) a boxa consisting of:
+     *          for the primary component:
+     *                (xul, yul) pixel location in global coords
+     *                (w, h) of the bitmap
+     *          for the hole components:
+     *                (x, y) in relative coordinates in primary component
+     *                (w, h) of the hole border (which is 2 pixels
+     *                       larger in each direction than the hole itself)
+     *    (3) a pta ('start') of the initial border pixel location for each
+     *        closed curve, all in relative coordinates of the primary
+     *        component.  This is given for the primary component,
+     *        followed by the hole components, if any.
+     *    (4) a refcount of the ccbord; used internally when a ccbord
+     *        is accessed from a ccborda (array of ccbord)
+     *    (5) a ptaa for the chain code for the border in relative
+     *        coordinates, where the first pta is the exterior border
+     *        and all other pta are for interior borders (holes)
+     *    (6) a ptaa for the global pixel loc rendition of the border,
+     *        where the first pta is the exterior border and all other
+     *        pta are for interior borders (holes).
+     *        This is derived from the local or step chain code.
+     *    (7) a numaa for the chain code for the border as orientation
+     *        directions between successive border pixels, where
+     *        the first numa is the exterior border and all other
+     *        numa are for interior borders (holes).  This is derived
+     *        from the local chain code.  The 8 directions are 0 - 7.
+     *    (8) a pta for a single chain for each c.c., comprised of outer
+     *        and hole borders, plus cut paths between them, all in
+     *        local coords.
+     *    (9) a pta for a single chain for each c.c., comprised of outer
+     *        and hole borders, plus cut paths between them, all in
+     *        global coords.
+     */
+struct CCBord
+{
+    struct Pix          *pix;            /* component bitmap (min size)      */
+    struct Boxa         *boxa;           /* regions of each closed curve     */
+    struct Pta          *start;          /* initial border pixel locations   */
+    l_int32              refcount;       /* number of handles; start at 1    */
+    struct Ptaa         *local;          /* ptaa of chain pixels (local)     */
+    struct Ptaa         *global;         /* ptaa of chain pixels (global)    */
+    struct Numaa        *step;           /* numaa of chain code (step dir)   */
+    struct Pta          *splocal;        /* pta of single chain (local)      */
+    struct Pta          *spglobal;       /* pta of single chain (global)     */
+};
+typedef struct CCBord CCBORD;
+
+
+struct CCBorda
+{
+    struct Pix          *pix;            /* input pix (may be null)          */
+    l_int32              w;              /* width of pix                     */
+    l_int32              h;              /* height of pix                    */
+    l_int32              n;              /* number of ccbord in ptr array    */
+    l_int32              nalloc;         /* number of ccbord ptrs allocated  */
+    struct CCBord      **ccb;            /* ccb ptr array                    */
+};
+typedef struct CCBorda CCBORDA;
+
+
+#endif  /* LEPTONICA_CCBORD_H */
+
diff --git a/src/ccthin.c b/src/ccthin.c
new file mode 100644 (file)
index 0000000..8ac7ae6
--- /dev/null
@@ -0,0 +1,472 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  ccthin.c
+ *
+ *     PIX    *pixThin()
+ *     PIX    *pixThinGeneral()
+ *     PIX    *pixThinExamples()
+ */
+
+#include "allheaders.h"
+
+
+    /* ------------------------------------------------------------
+     * These sels (and their rotated counterparts) are the useful
+     * 3x3 Sels for thinning.   The notation is based on
+     * "Connectivity-preserving morphological image transformations,"
+     * a version of which can be found at
+     *           http://www.leptonica.com/papers/conn.pdf
+     * ------------------------------------------------------------ */
+
+    /* Sels for 4-connected thinning */
+static const char *sel_4_1 = "  x"
+                             "oCx"
+                             "  x";
+
+static const char *sel_4_2 = "  x"
+                             "oCx"
+                             " o ";
+
+static const char *sel_4_3 = " o "
+                             "oCx"
+                             "  x";
+
+static const char *sel_4_4 = " o "
+                             "oCx"
+                             " o ";
+
+static const char *sel_4_5 = " ox"
+                             "oCx"
+                             " o ";
+
+static const char *sel_4_6 = " o "
+                             "oCx"
+                             " ox";
+
+static const char *sel_4_7 = " xx"
+                             "oCx"
+                             " o ";
+
+static const char *sel_4_8 = "  x"
+                             "oCx"
+                             "o x";
+
+static const char *sel_4_9 = "o x"
+                             "oCx"
+                             "  x";
+
+    /* Sels for 8-connected thinning */
+static const char *sel_8_1 = " x "
+                             "oCx"
+                             " x ";
+
+static const char *sel_8_2 = " x "
+                             "oCx"
+                             "o  ";
+
+static const char *sel_8_3 = "o  "
+                             "oCx"
+                             " x ";
+
+static const char *sel_8_4 = "o  "
+                             "oCx"
+                             "o  ";
+
+static const char *sel_8_5 = "o x"
+                             "oCx"
+                             "o  ";
+
+static const char *sel_8_6 = "o  "
+                             "oCx"
+                             "o x";
+
+static const char *sel_8_7 = " x "
+                             "oCx"
+                             "oo ";
+
+static const char *sel_8_8 = " x "
+                             "oCx"
+                             "ox ";
+
+static const char *sel_8_9 = "ox "
+                             "oCx"
+                             " x ";
+
+    /* Sels for both 4 and 8-connected thinning */
+static const char *sel_48_1 = " xx"
+                              "oCx"
+                              "oo ";
+
+static const char *sel_48_2 = "o x"
+                              "oCx"
+                              "o x";
+
+#ifndef NO_CONSOLE_IO
+#define  DEBUG_SELS     0
+#endif   /* ~NO_CONSOLE_IO */
+
+
+/*----------------------------------------------------------------*
+ *                      CC-preserving thinning                    *
+ *----------------------------------------------------------------*/
+/*!
+ *  pixThin()
+ *
+ *      Input:  pixs (1 bpp)
+ *              type (L_THIN_FG, L_THIN_BG)
+ *              connectivity (4 or 8)
+ *              maxiters (max number of iters allowed; use 0 to iterate
+ *                        until completion)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) See "Connectivity-preserving morphological image transformations,"
+ *          Dan S. Bloomberg, in SPIE Visual Communications and Image
+ *          Processing, Conference 1606, pp. 320-334, November 1991,
+ *          Boston, MA.   A web version is available at
+ *              http://www.leptonica.com/papers/conn.pdf
+ *      (2) We implement here two of the best iterative
+ *          morphological thinning algorithms, for 4 c.c and 8 c.c.
+ *          Each iteration uses a mixture of parallel operations
+ *          (using several different 3x3 Sels) and serial operations.
+ *          Specifically, each thinning iteration consists of
+ *          four sequential thinnings from each of four directions.
+ *          Each of these thinnings is a parallel composite
+ *          operation, where the union of a set of HMTs are set
+ *          subtracted from the input.  For 4-cc thinning, we
+ *          use 3 HMTs in parallel, and for 8-cc thinning we use 4 HMTs.
+ *      (3) A "good" thinning algorithm is one that generates a skeleton
+ *          that is near the medial axis and has neither pruned
+ *          real branches nor left extra dendritic branches.
+ *      (4) To thin the foreground, which is the usual situation,
+ *          use type == L_THIN_FG.  Thickening the foreground is equivalent
+ *          to thinning the background (type == L_THIN_BG), where the
+ *          opposite connectivity gets preserved.  For example, to thicken
+ *          the fg using 4-connectivity, we thin the bg using Sels that
+ *          preserve 8-connectivity.
+ */
+PIX *
+pixThin(PIX     *pixs,
+        l_int32  type,
+        l_int32  connectivity,
+        l_int32  maxiters)
+{
+PIX   *pixd;
+SEL   *sel;
+SELA  *sela;
+
+    PROCNAME("pixThin");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (type != L_THIN_FG && type != L_THIN_BG)
+        return (PIX *)ERROR_PTR("invalid fg/bg type", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+    if (maxiters == 0) maxiters = 10000;
+
+    sela = selaCreate(4);
+    if (connectivity == 4) {
+        sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_4_2, 3, 3, "sel_4_2");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_4_3, 3, 3, "sel_4_3");
+        selaAddSel(sela, sel, NULL, 0);
+    } else {  /* connectivity == 8 */
+        sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6");
+        selaAddSel(sela, sel, NULL, 0);
+    }
+
+    pixd = pixThinGeneral(pixs, type, sela, maxiters);
+
+    selaDestroy(&sela);
+    return pixd;
+}
+
+
+/*!
+ *  pixThinGeneral()
+ *
+ *      Input:  pixs (1 bpp)
+ *              type (L_THIN_FG, L_THIN_BG)
+ *              sela (of Sels for parallel composite HMTs)
+ *              maxiters (max number of iters allowed; use 0 to iterate
+ *                        until completion)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) See notes in pixThin().  That function chooses among
+ *          the best of the Sels for thinning.
+ *      (2) This is a general function that takes a Sela of HMTs
+ *          that are used in parallel for thinning from each
+ *          of four directions.  One iteration consists of four
+ *          such parallel thins.
+ */
+PIX *
+pixThinGeneral(PIX     *pixs,
+               l_int32  type,
+               SELA    *sela,
+               l_int32  maxiters)
+{
+l_int32  i, j, r, nsels, same;
+PIXA    *pixahmt;
+PIX    **pixhmt;  /* array owned by pixahmt; do not destroy! */
+PIX     *pixd, *pixt;
+SEL     *sel, *selr;
+
+    PROCNAME("pixThinGeneral");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (type != L_THIN_FG && type != L_THIN_BG)
+        return (PIX *)ERROR_PTR("invalid fg/bg type", procName, NULL);
+    if (!sela)
+        return (PIX *)ERROR_PTR("sela not defined", procName, NULL);
+    if (maxiters == 0) maxiters = 10000;
+
+        /* Set up array of temp pix to hold hmts */
+    nsels = selaGetCount(sela);
+    pixahmt = pixaCreate(nsels);
+    for (i = 0; i < nsels; i++) {
+        pixt = pixCreateTemplate(pixs);
+        pixaAddPix(pixahmt, pixt, L_INSERT);
+    }
+    pixhmt = pixaGetPixArray(pixahmt);
+    if (!pixhmt)
+        return (PIX *)ERROR_PTR("pixhmt array not made", procName, NULL);
+
+#if  DEBUG_SELS
+    pixt = selaDisplayInPix(sela, 35, 3, 15, 4);
+    pixDisplayWithTitle(pixt, 100, 100, "allsels", 1);
+    pixDestroy(&pixt);
+#endif  /* DEBUG_SELS */
+
+        /* Set up initial image for fg thinning */
+    if (type == L_THIN_FG)
+        pixd = pixCopy(NULL, pixs);
+    else  /* bg thinning */
+        pixd = pixInvert(NULL, pixs);
+
+        /* Thin the fg, with up to maxiters iterations */
+    for (i = 0; i < maxiters; i++) {
+        pixt = pixCopy(NULL, pixd);  /* test for completion */
+        for (r = 0; r < 4; r++) {  /* over 90 degree rotations of Sels */
+            for (j = 0; j < nsels; j++) {  /* over individual sels in sela */
+                sel = selaGetSel(sela, j);  /* not a copy */
+                selr = selRotateOrth(sel, r);
+                pixHMT(pixhmt[j], pixd, selr);
+                selDestroy(&selr);
+                if (j > 0)
+                    pixOr(pixhmt[0], pixhmt[0], pixhmt[j]);  /* accum result */
+            }
+            pixSubtract(pixd, pixd, pixhmt[0]);  /* remove result */
+        }
+        pixEqual(pixd, pixt, &same);
+        pixDestroy(&pixt);
+        if (same) {
+            L_INFO("%d iterations to completion\n", procName, i);
+            break;
+        }
+    }
+
+    if (type == L_THIN_BG)
+        pixInvert(pixd, pixd);
+
+    pixaDestroy(&pixahmt);
+    return pixd;
+}
+
+
+/*!
+ *  pixThinExamples()
+ *
+ *      Input:  pixs (1 bpp)
+ *              type (L_THIN_FG, L_THIN_BG)
+ *              index (into specific examples; valid 1-9; see notes)
+ *              maxiters (max number of iters allowed; use 0 to iterate
+ *                        until completion)
+ *              selfile (<optional> filename for output sel display)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) See notes in pixThin().  The examples are taken from
+ *          the paper referenced there.
+ *      (2) Here we allow specific sets of HMTs to be used in
+ *          parallel for thinning from each of four directions.
+ *          One iteration consists of four such parallel thins.
+ *      (3) The examples are indexed as follows:
+ *          Thinning  (e.g., run to completion):
+ *              index = 1     sel_4_1, sel_4_5, sel_4_6
+ *              index = 2     sel_4_1, sel_4_7, sel_4_7_rot
+ *              index = 3     sel_48_1, sel_48_1_rot, sel_48_2
+ *              index = 4     sel_8_2, sel_8_3, sel_48_2
+ *              index = 5     sel_8_1, sel_8_5, sel_8_6
+ *              index = 6     sel_8_2, sel_8_3, sel_8_8, sel_8_9
+ *              index = 7     sel_8_5, sel_8_6, sel_8_7, sel_8_7_rot
+ *          Thickening:
+ *              index = 8     sel_4_2, sel_4_3 (e.g,, do just a few iterations)
+ *              index = 9     sel_8_4 (e.g., do just a few iterations)
+ */
+PIX *
+pixThinExamples(PIX         *pixs,
+                l_int32      type,
+                l_int32      index,
+                l_int32      maxiters,
+                const char  *selfile)
+{
+PIX   *pixd, *pixt;
+SEL   *sel;
+SELA  *sela;
+
+    PROCNAME("pixThinExamples");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (type != L_THIN_FG && type != L_THIN_BG)
+        return (PIX *)ERROR_PTR("invalid fg/bg type", procName, NULL);
+    if (index < 1 || index > 9)
+        return (PIX *)ERROR_PTR("invalid index", procName, NULL);
+    if (maxiters == 0) maxiters = 10000;
+
+    switch(index)
+    {
+    case 1:
+        sela = selaCreate(3);
+        sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_4_5, 3, 3, "sel_4_5");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_4_6, 3, 3, "sel_4_6");
+        selaAddSel(sela, sel, NULL, 0);
+        break;
+    case 2:
+        sela = selaCreate(3);
+        sel = selCreateFromString(sel_4_1, 3, 3, "sel_4_1");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_4_7, 3, 3, "sel_4_7");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selRotateOrth(sel, 1);
+        selaAddSel(sela, sel, "sel_4_7_rot", 0);
+        break;
+    case 3:
+        sela = selaCreate(3);
+        sel = selCreateFromString(sel_48_1, 3, 3, "sel_48_1");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selRotateOrth(sel, 1);
+        selaAddSel(sela, sel, "sel_48_1_rot", 0);
+        sel = selCreateFromString(sel_48_2, 3, 3, "sel_48_2");
+        selaAddSel(sela, sel, NULL, 0);
+        break;
+    case 4:
+        sela = selaCreate(3);
+        sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_48_2, 3, 3, "sel_48_2");
+        selaAddSel(sela, sel, NULL, 0);
+        break;
+    case 5:
+        sela = selaCreate(3);
+        sel = selCreateFromString(sel_8_1, 3, 3, "sel_8_1");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6");
+        selaAddSel(sela, sel, NULL, 0);
+        break;
+    case 6:
+        sela = selaCreate(4);
+        sel = selCreateFromString(sel_8_2, 3, 3, "sel_8_2");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_8_3, 3, 3, "sel_8_3");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_8_8, 3, 3, "sel_8_8");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_8_9, 3, 3, "sel_8_9");
+        selaAddSel(sela, sel, NULL, 0);
+        break;
+    case 7:
+        sela = selaCreate(4);
+        sel = selCreateFromString(sel_8_5, 3, 3, "sel_8_5");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_8_6, 3, 3, "sel_8_6");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_8_7, 3, 3, "sel_8_7");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selRotateOrth(sel, 1);
+        selaAddSel(sela, sel, "sel_8_7_rot", 0);
+        break;
+    case 8:  /* thicken for this one; just a few iterations */
+        sela = selaCreate(2);
+        sel = selCreateFromString(sel_4_2, 3, 3, "sel_4_2");
+        selaAddSel(sela, sel, NULL, 0);
+        sel = selCreateFromString(sel_4_3, 3, 3, "sel_4_3");
+        selaAddSel(sela, sel, NULL, 0);
+        pixt = pixThinGeneral(pixs, type, sela, maxiters);
+        pixd = pixRemoveBorderConnComps(pixt, 4);
+        pixDestroy(&pixt);
+        break;
+    case 9:  /* thicken for this one; just a few iterations */
+        sela = selaCreate(1);
+        sel = selCreateFromString(sel_8_4, 3, 3, "sel_8_4");
+        selaAddSel(sela, sel, NULL, 0);
+        pixt = pixThinGeneral(pixs, type, sela, maxiters);
+        pixd = pixRemoveBorderConnComps(pixt, 4);
+        pixDestroy(&pixt);
+        break;
+    default:
+        return (PIX *)ERROR_PTR("invalid index", procName, NULL);
+    }
+
+    if (index <= 7)
+        pixd = pixThinGeneral(pixs, type, sela, maxiters);
+
+        /* Optionally display the sels */
+    if (selfile) {
+        pixt = selaDisplayInPix(sela, 35, 3, 15, 4);
+        pixWrite(selfile, pixt, IFF_PNG);
+        pixDestroy(&pixt);
+    }
+
+    selaDestroy(&sela);
+    return pixd;
+}
diff --git a/src/classapp.c b/src/classapp.c
new file mode 100644 (file)
index 0000000..9bce47a
--- /dev/null
@@ -0,0 +1,910 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  classapp.c
+ *
+ *      Top-level jb2 correlation and rank-hausdorff
+ *
+ *         l_int32         jbCorrelation()
+ *         l_int32         jbRankHaus()
+ *
+ *      Extract and classify words in textline order
+ *
+ *         JBCLASSER      *jbWordsInTextlines()
+ *         l_int32         pixGetWordsInTextlines()
+ *         l_int32         pixGetWordBoxesInTextlines()
+ *
+ *      Use word bounding boxes to compare page images
+ *
+ *         NUMAA          *boxaExtractSortedPattern()
+ *         l_int32         numaaCompareImagesByBoxes()
+ *         static l_int32  testLineAlignmentX()
+ *         static l_int32  countAlignedMatches()
+ *         static void     printRowIndices()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  L_BUF_SIZE = 512;
+static const l_int32  JB_WORDS_MIN_WIDTH = 5;  /* pixels */
+static const l_int32  JB_WORDS_MIN_HEIGHT = 3;  /* pixels */
+
+    /* Static comparison functions */
+static l_int32 testLineAlignmentX(NUMA *na1, NUMA *na2, l_int32 shiftx,
+                                  l_int32 delx, l_int32 nperline);
+static l_int32 countAlignedMatches(NUMA *nai1, NUMA *nai2, NUMA *nasx,
+                                   NUMA *nasy, l_int32 n1, l_int32 n2,
+                                   l_int32 delx, l_int32 dely,
+                                   l_int32 nreq, l_int32 *psame,
+                                   l_int32 debugflag);
+static void printRowIndices(l_int32 *index1, l_int32 n1,
+                            l_int32 *index2, l_int32 n2);
+
+
+/*------------------------------------------------------------------*
+ *          Top-level jb2 correlation and rank-hausdorff            *
+ *------------------------------------------------------------------*/
+/*!
+ *  jbCorrelation()
+ *
+ *       Input:  dirin (directory of input images)
+ *               thresh (typically ~0.8)
+ *               weight (typically ~0.6)
+ *               components (JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS)
+ *               rootname (for output files)
+ *               firstpage (0-based)
+ *               npages (use 0 for all pages in dirin)
+ *               renderflag (1 to render from templates; 0 to skip)
+ *       Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The images must be 1 bpp.  If they are not, you can convert
+ *          them using convertFilesTo1bpp().
+ *      (2) See prog/jbcorrelation for generating more output (e.g.,
+ *          for debugging)
+ */
+l_int32
+jbCorrelation(const char  *dirin,
+              l_float32    thresh,
+              l_float32    weight,
+              l_int32      components,
+              const char  *rootname,
+              l_int32      firstpage,
+              l_int32      npages,
+              l_int32      renderflag)
+{
+char        filename[L_BUF_SIZE];
+l_int32     nfiles, i, numpages;
+JBDATA     *data;
+JBCLASSER  *classer;
+PIX        *pix;
+PIXA       *pixa;
+SARRAY     *safiles;
+
+    PROCNAME("jbCorrelation");
+
+    if (!dirin)
+        return ERROR_INT("dirin not defined", procName, 1);
+    if (!rootname)
+        return ERROR_INT("rootname not defined", procName, 1);
+    if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
+        components != JB_WORDS)
+        return ERROR_INT("components invalid", procName, 1);
+
+    safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages);
+    nfiles = sarrayGetCount(safiles);
+
+        /* Classify components */
+    classer = jbCorrelationInit(components, 0, 0, thresh, weight);
+    jbAddPages(classer, safiles);
+
+        /* Save data */
+    data = jbDataSave(classer);
+    jbDataWrite(rootname, data);
+
+        /* Optionally, render pages using class templates */
+    if (renderflag) {
+        pixa = jbDataRender(data, FALSE);
+        numpages = pixaGetCount(pixa);
+        if (numpages != nfiles)
+            fprintf(stderr, "numpages = %d, nfiles = %d, not equal!\n",
+                    numpages, nfiles);
+        for (i = 0; i < numpages; i++) {
+            pix = pixaGetPix(pixa, i, L_CLONE);
+            snprintf(filename, L_BUF_SIZE, "%s.%04d", rootname, i);
+            fprintf(stderr, "filename: %s\n", filename);
+            pixWrite(filename, pix, IFF_PNG);
+            pixDestroy(&pix);
+        }
+        pixaDestroy(&pixa);
+    }
+
+    sarrayDestroy(&safiles);
+    jbClasserDestroy(&classer);
+    jbDataDestroy(&data);
+    return 0;
+}
+
+
+/*!
+ *  jbRankHaus()
+ *
+ *       Input:  dirin (directory of input images)
+ *               size (of Sel used for dilation; typ. 2)
+ *               rank (rank value of match; typ. 0.97)
+ *               components (JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS)
+ *               rootname (for output files)
+ *               firstpage (0-based)
+ *               npages (use 0 for all pages in dirin)
+ *               renderflag (1 to render from templates; 0 to skip)
+ *       Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See prog/jbrankhaus for generating more output (e.g.,
+ *          for debugging)
+ */
+l_int32
+jbRankHaus(const char  *dirin,
+           l_int32      size,
+           l_float32    rank,
+           l_int32      components,
+           const char  *rootname,
+           l_int32      firstpage,
+           l_int32      npages,
+           l_int32      renderflag)
+{
+char        filename[L_BUF_SIZE];
+l_int32     nfiles, i, numpages;
+JBDATA     *data;
+JBCLASSER  *classer;
+PIX        *pix;
+PIXA       *pixa;
+SARRAY     *safiles;
+
+    PROCNAME("jbRankHaus");
+
+    if (!dirin)
+        return ERROR_INT("dirin not defined", procName, 1);
+    if (!rootname)
+        return ERROR_INT("rootname not defined", procName, 1);
+    if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
+        components != JB_WORDS)
+        return ERROR_INT("components invalid", procName, 1);
+
+    safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages);
+    nfiles = sarrayGetCount(safiles);
+
+        /* Classify components */
+    classer = jbRankHausInit(components, 0, 0, size, rank);
+    jbAddPages(classer, safiles);
+
+        /* Save data */
+    data = jbDataSave(classer);
+    jbDataWrite(rootname, data);
+
+        /* Optionally, render pages using class templates */
+    if (renderflag) {
+        pixa = jbDataRender(data, FALSE);
+        numpages = pixaGetCount(pixa);
+        if (numpages != nfiles)
+            fprintf(stderr, "numpages = %d, nfiles = %d, not equal!\n",
+                    numpages, nfiles);
+        for (i = 0; i < numpages; i++) {
+            pix = pixaGetPix(pixa, i, L_CLONE);
+            snprintf(filename, L_BUF_SIZE, "%s.%04d", rootname, i);
+            fprintf(stderr, "filename: %s\n", filename);
+            pixWrite(filename, pix, IFF_PNG);
+            pixDestroy(&pix);
+        }
+        pixaDestroy(&pixa);
+    }
+
+    sarrayDestroy(&safiles);
+    jbClasserDestroy(&classer);
+    jbDataDestroy(&data);
+    return 0;
+}
+
+
+
+/*------------------------------------------------------------------*
+ *           Extract and classify words in textline order           *
+ *------------------------------------------------------------------*/
+/*!
+ *  jbWordsInTextlines()
+ *
+ *      Input:  dirin (directory of input pages)
+ *              reduction (1 for full res; 2 for half-res)
+ *              maxwidth (of word mask components, to be kept)
+ *              maxheight (of word mask components, to be kept)
+ *              thresh (on correlation; 0.80 is reasonable)
+ *              weight (for handling thick text; 0.6 is reasonable)
+ *              natl (<return> numa with textline index for each component)
+ *              firstpage (0-based)
+ *              npages (use 0 for all pages in dirin)
+ *      Return: classer (for the set of pages)
+ *
+ *  Notes:
+ *      (1) This is a high-level function.  See prog/jbwords for example
+ *          of usage.
+ *      (2) Typically, words can be found reasonably well at a resolution
+ *          of about 150 ppi.  For highest accuracy, you should use 300 ppi.
+ *          Assuming that the input images are 300 ppi, use reduction = 1
+ *          for finding words at full res, and reduction = 2 for finding
+ *          them at 150 ppi.
+ */
+JBCLASSER *
+jbWordsInTextlines(const char  *dirin,
+                   l_int32      reduction,
+                   l_int32      maxwidth,
+                   l_int32      maxheight,
+                   l_float32    thresh,
+                   l_float32    weight,
+                   NUMA       **pnatl,
+                   l_int32      firstpage,
+                   l_int32      npages)
+{
+char       *fname;
+l_int32     nfiles, i, w, h;
+BOXA       *boxa;
+JBCLASSER  *classer;
+NUMA       *nai, *natl;
+PIX        *pix;
+PIXA       *pixa;
+SARRAY     *safiles;
+
+    PROCNAME("jbWordsInTextlines");
+
+    if (!pnatl)
+        return (JBCLASSER *)ERROR_PTR("&natl not defined", procName, NULL);
+    *pnatl = NULL;
+    if (!dirin)
+        return (JBCLASSER *)ERROR_PTR("dirin not defined", procName, NULL);
+    if (reduction != 1 && reduction != 2)
+        return (JBCLASSER *)ERROR_PTR("reduction not in {1,2}", procName, NULL);
+
+    safiles = getSortedPathnamesInDirectory(dirin, NULL, firstpage, npages);
+    nfiles = sarrayGetCount(safiles);
+
+        /* Classify components */
+    classer = jbCorrelationInit(JB_WORDS, maxwidth, maxheight, thresh, weight);
+    classer->safiles = sarrayCopy(safiles);
+    natl = numaCreate(0);
+    *pnatl = natl;
+    for (i = 0; i < nfiles; i++) {
+        fname = sarrayGetString(safiles, i, L_NOCOPY);
+        if ((pix = pixRead(fname)) == NULL) {
+            L_WARNING("image file %d not read\n", procName, i);
+            continue;
+        }
+        pixGetDimensions(pix, &w, &h, NULL);
+        if (reduction == 1) {
+            classer->w = w;
+            classer->h = h;
+        } else {  /* reduction == 2 */
+            classer->w = w / 2;
+            classer->h = h / 2;
+        }
+        pixGetWordsInTextlines(pix, reduction, JB_WORDS_MIN_WIDTH,
+                               JB_WORDS_MIN_HEIGHT, maxwidth, maxheight,
+                               &boxa, &pixa, &nai);
+        jbAddPageComponents(classer, pix, boxa, pixa);
+        numaJoin(natl, nai, 0, -1);
+        pixDestroy(&pix);
+        numaDestroy(&nai);
+        boxaDestroy(&boxa);
+        pixaDestroy(&pixa);
+    }
+
+    sarrayDestroy(&safiles);
+    return classer;
+}
+
+
+/*!
+ *  pixGetWordsInTextlines()
+ *
+ *      Input:  pixs (1 bpp, typ. 300 ppi)
+ *              reduction (1 for input res; 2 for 2x reduction of input res)
+ *              minwidth, minheight (of saved components; smaller are discarded)
+ *              maxwidth, maxheight (of saved components; larger are discarded)
+ *              &boxad (<return> word boxes sorted in textline line order)
+ *              &pixad (<return> word images sorted in textline line order)
+ *              &naindex (<return> index of textline for each word)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The input should be at a resolution of about 300 ppi.
+ *          The word masks and word images can be computed at either
+ *          150 ppi or 300 ppi.  For the former, set reduction = 2.
+ *      (2) The four size constraints on saved components are all
+ *          scaled by @reduction.
+ *      (3) The result are word images (and their b.b.), extracted in
+ *          textline order, at either full res or 2x reduction,
+ *          and with a numa giving the textline index for each word.
+ *      (4) The pixa and boxa interfaces should make this type of
+ *          application simple to put together.  The steps are:
+ *           - optionally reduce by 2x
+ *           - generate first estimate of word masks
+ *           - get b.b. of these, and remove the small and big ones
+ *           - extract pixa of the word images, using the b.b.
+ *           - sort actual word images in textline order (2d)
+ *           - flatten them to a pixa (1d), saving the textline index
+ *             for each pix
+ *      (5) In an actual application, it may be desirable to pre-filter
+ *          the input image to remove large components, to extract
+ *          single columns of text, and to deskew them.  For example,
+ *          to remove both large components and small noisy components
+ *          that can interfere with the statistics used to estimate
+ *          parameters for segmenting by words, but still retain text lines,
+ *          the following image preprocessing can be done:
+ *                Pix *pixt = pixMorphSequence(pixs, "c40.1", 0);
+ *                Pix *pixf = pixSelectBySize(pixt, 0, 60, 8,
+ *                                     L_SELECT_HEIGHT, L_SELECT_IF_LT, NULL);
+ *                pixAnd(pixf, pixf, pixs);  // the filtered image
+ *          The closing turns text lines into long blobs, but does not
+ *          significantly increase their height.  But if there are many
+ *          small connected components in a dense texture, this is likely
+ *          to generate tall components that will be eliminated in pixf.
+ */
+l_int32
+pixGetWordsInTextlines(PIX     *pixs,
+                       l_int32  reduction,
+                       l_int32  minwidth,
+                       l_int32  minheight,
+                       l_int32  maxwidth,
+                       l_int32  maxheight,
+                       BOXA   **pboxad,
+                       PIXA   **ppixad,
+                       NUMA   **pnai)
+{
+l_int32  maxdil;
+BOXA    *boxa1, *boxad;
+BOXAA   *baa;
+NUMA    *nai;
+NUMAA   *naa;
+PIXA    *pixa1, *pixad;
+PIX     *pix1;
+PIXAA   *paa;
+
+    PROCNAME("pixGetWordsInTextlines");
+
+    if (!pboxad || !ppixad || !pnai)
+        return ERROR_INT("&boxad, &pixad, &nai not all defined", procName, 1);
+    *pboxad = NULL;
+    *ppixad = NULL;
+    *pnai = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (reduction != 1 && reduction != 2)
+        return ERROR_INT("reduction not in {1,2}", procName, 1);
+
+    if (reduction == 1) {
+        pix1 = pixClone(pixs);
+        maxdil = 18;
+    } else {  /* reduction == 2 */
+        pix1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
+        maxdil = 9;
+    }
+
+        /* Get the bounding boxes of the words from the word mask. */
+    pixWordBoxesByDilation(pix1, maxdil, minwidth, minheight,
+                           maxwidth, maxheight, &boxa1, NULL);
+
+        /* Generate a pixa of the word images */
+    pixa1 = pixaCreateFromBoxa(pix1, boxa1, NULL);  /* mask over each word */
+
+        /* Sort the bounding boxes of these words by line.  We use the
+         * index mapping to allow identical sorting of the pixa. */
+    baa = boxaSort2d(boxa1, &naa, -1, -1, 4);
+    paa = pixaSort2dByIndex(pixa1, naa, L_CLONE);
+
+        /* Flatten the word paa */
+    pixad = pixaaFlattenToPixa(paa, &nai, L_CLONE);
+    boxad = pixaGetBoxa(pixad, L_COPY);
+
+    *pnai = nai;
+    *pboxad = boxad;
+    *ppixad = pixad;
+
+    pixDestroy(&pix1);
+    pixaDestroy(&pixa1);
+    boxaDestroy(&boxa1);
+    boxaaDestroy(&baa);
+    pixaaDestroy(&paa);
+    numaaDestroy(&naa);
+    return 0;
+}
+
+
+/*!
+ *  pixGetWordBoxesInTextlines()
+ *
+ *      Input:  pixs (1 bpp, typ. 300 ppi)
+ *              reduction (1 for input res; 2 for 2x reduction of input res)
+ *              minwidth, minheight (of saved components; smaller are discarded)
+ *              maxwidth, maxheight (of saved components; larger are discarded)
+ *              &boxad (<return> word boxes sorted in textline line order)
+ *              &naindex (<optional return> index of textline for each word)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The input should be at a resolution of about 300 ppi.
+ *          The word masks can be computed at either 150 ppi or 300 ppi.
+ *          For the former, set reduction = 2.
+ *      (2) This is a special version of pixGetWordsInTextlines(), that
+ *          just finds the word boxes in line order, with a numa
+ *          giving the textline index for each word.
+ *          See pixGetWordsInTextlines() for more details.
+ */
+l_int32
+pixGetWordBoxesInTextlines(PIX     *pixs,
+                           l_int32  reduction,
+                           l_int32  minwidth,
+                           l_int32  minheight,
+                           l_int32  maxwidth,
+                           l_int32  maxheight,
+                           BOXA   **pboxad,
+                           NUMA   **pnai)
+{
+l_int32  maxdil;
+BOXA    *boxa1;
+BOXAA   *baa;
+NUMA    *nai;
+PIX     *pix1;
+
+    PROCNAME("pixGetWordBoxesInTextlines");
+
+    if (pnai) *pnai = NULL;
+    if (!pboxad)
+        return ERROR_INT("&boxad and &nai not both defined", procName, 1);
+    *pboxad = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (reduction != 1 && reduction != 2)
+        return ERROR_INT("reduction not in {1,2}", procName, 1);
+
+    if (reduction == 1) {
+        pix1 = pixClone(pixs);
+        maxdil = 18;
+    } else {  /* reduction == 2 */
+        pix1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
+        maxdil = 9;
+    }
+
+        /* Get the bounding boxes of the words from the word mask. */
+    pixWordBoxesByDilation(pix1, maxdil, minwidth, minheight,
+                           maxwidth, maxheight, &boxa1, NULL);
+
+        /* 2D sort the bounding boxes of these words. */
+    baa = boxaSort2d(boxa1, NULL, 3, -5, 5);
+
+        /* Flatten the boxaa, saving the boxa index for each box */
+    *pboxad = boxaaFlattenToBoxa(baa, &nai, L_CLONE);
+
+    if (pnai)
+        *pnai = nai;
+    else
+        numaDestroy(&nai);
+    pixDestroy(&pix1);
+    boxaDestroy(&boxa1);
+    boxaaDestroy(&baa);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *           Use word bounding boxes to compare page images         *
+ *------------------------------------------------------------------*/
+/*!
+ *  boxaExtractSortedPattern()
+ *
+ *      Input:  boxa (typ. of word bounding boxes, in textline order)
+ *              numa (index of textline for each box in boxa)
+ *      Return: naa (numaa, where each numa represents one textline),
+ *                   or null on error
+ *
+ *  Notes:
+ *      (1) The input is expected to come from pixGetWordBoxesInTextlines().
+ *      (2) Each numa in the output consists of an average y coordinate
+ *          of the first box in the textline, followed by pairs of
+ *          x coordinates representing the left and right edges of each
+ *          of the boxes in the textline.
+ */
+NUMAA *
+boxaExtractSortedPattern(BOXA  *boxa,
+                         NUMA  *na)
+{
+l_int32  index, nbox, row, prevrow, x, y, w, h;
+BOX     *box;
+NUMA    *nad;
+NUMAA   *naa;
+
+    PROCNAME("boxaExtractSortedPattern");
+
+    if (!boxa)
+        return (NUMAA *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (!na)
+        return (NUMAA *)ERROR_PTR("na not defined", procName, NULL);
+
+    naa = numaaCreate(0);
+    nbox = boxaGetCount(boxa);
+    if (nbox == 0)
+        return naa;
+
+    prevrow = -1;
+    for (index = 0; index < nbox; index++) {
+        box = boxaGetBox(boxa, index, L_CLONE);
+        numaGetIValue(na, index, &row);
+        if (row > prevrow) {
+            if (index > 0)
+                numaaAddNuma(naa, nad, L_INSERT);
+            nad = numaCreate(0);
+            prevrow = row;
+            boxGetGeometry(box, NULL, &y, NULL, &h);
+            numaAddNumber(nad, y + h / 2);
+        }
+        boxGetGeometry(box, &x, NULL, &w, NULL);
+        numaAddNumber(nad, x);
+        numaAddNumber(nad, x + w - 1);
+        boxDestroy(&box);
+    }
+    numaaAddNuma(naa, nad, L_INSERT);
+
+    return naa;
+}
+
+
+/*!
+ *  numaaCompareImagesByBoxes()
+ *
+ *      Input:  naa1 (for image 1, formatted by boxaExtractSortedPattern())
+ *              naa2 (ditto; for image 2)
+ *              nperline (number of box regions to be used in each textline)
+ *              nreq (number of complete row matches required)
+ *              maxshiftx (max allowed x shift between two patterns, in pixels)
+ *              maxshifty (max allowed y shift between two patterns, in pixels)
+ *              delx (max allowed difference in x data, after alignment)
+ *              dely (max allowed difference in y data, after alignment)
+ *              &same (<return> 1 if @nreq row matches are found; 0 otherwise)
+ *              debugflag (1 for debug output)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Each input numaa describes a set of sorted bounding boxes
+ *          (sorted by textline and, within each textline, from
+ *          left to right) in the images from which they are derived.
+ *          See boxaExtractSortedPattern() for a description of the data
+ *          format in each of the input numaa.
+ *      (2) This function does an alignment between the input
+ *          descriptions of bounding boxes for two images. The
+ *          input parameter @nperline specifies the number of boxes
+ *          to consider in each line when testing for a match, and
+ *          @nreq is the required number of lines that must be well-aligned
+ *          to get a match.
+ *      (3) Testing by alignment has 3 steps:
+ *          (a) Generating the location of word bounding boxes from the
+ *              images (prior to calling this function).
+ *          (b) Listing all possible pairs of aligned rows, based on
+ *              tolerances in horizontal and vertical positions of
+ *              the boxes.  Specifically, all pairs of rows are enumerated
+ *              whose first @nperline boxes can be brought into close
+ *              alignment, based on the delx parameter for boxes in the
+ *              line and within the overall the @maxshiftx and @maxshifty
+ *              constraints.
+ *          (c) Each pair, starting with the first, is used to search
+ *              for a set of @nreq - 1 other pairs that can all be aligned
+ *              with a difference in global translation of not more
+ *              than (@delx, @dely).
+ */
+l_int32
+numaaCompareImagesByBoxes(NUMAA    *naa1,
+                          NUMAA    *naa2,
+                          l_int32   nperline,
+                          l_int32   nreq,
+                          l_int32   maxshiftx,
+                          l_int32   maxshifty,
+                          l_int32   delx,
+                          l_int32   dely,
+                          l_int32  *psame,
+                          l_int32   debugflag)
+{
+l_int32   n1, n2, i, j, nbox, y1, y2, xl1, xl2;
+l_int32   shiftx, shifty, match;
+l_int32  *line1, *line2;  /* indicator for sufficient boxes in a line */
+l_int32  *yloc1, *yloc2;  /* arrays of y value for first box in a line */
+l_int32  *xleft1, *xleft2;  /* arrays of x value for left side of first box */
+NUMA     *na1, *na2, *nai1, *nai2, *nasx, *nasy;
+
+    PROCNAME("numaaCompareImagesByBoxes");
+
+    if (!psame)
+        return ERROR_INT("&same not defined", procName, 1);
+    *psame = 0;
+    if (!naa1)
+        return ERROR_INT("naa1 not defined", procName, 1);
+    if (!naa2)
+        return ERROR_INT("naa2 not defined", procName, 1);
+    if (nperline < 1)
+        return ERROR_INT("nperline < 1", procName, 1);
+    if (nreq < 1)
+        return ERROR_INT("nreq < 1", procName, 1);
+
+    n1 = numaaGetCount(naa1);
+    n2 = numaaGetCount(naa2);
+    if (n1 < nreq || n2 < nreq)
+        return 0;
+
+        /* Find the lines in naa1 and naa2 with sufficient boxes.
+         * Also, find the y-values for each of the lines, and the
+         * LH x-values of the first box in each line. */
+    line1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32));
+    line2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32));
+    yloc1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32));
+    yloc2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32));
+    xleft1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32));
+    xleft2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32));
+    for (i = 0; i < n1; i++) {
+        na1 = numaaGetNuma(naa1, i, L_CLONE);
+        numaGetIValue(na1, 0, yloc1 + i);
+        numaGetIValue(na1, 1, xleft1 + i);
+        nbox = (numaGetCount(na1) - 1) / 2;
+        if (nbox >= nperline)
+            line1[i] = 1;
+        numaDestroy(&na1);
+    }
+    for (i = 0; i < n2; i++) {
+        na2 = numaaGetNuma(naa2, i, L_CLONE);
+        numaGetIValue(na2, 0, yloc2 + i);
+        numaGetIValue(na2, 1, xleft2 + i);
+        nbox = (numaGetCount(na2) - 1) / 2;
+        if (nbox >= nperline)
+            line2[i] = 1;
+        numaDestroy(&na2);
+    }
+
+        /* Enumerate all possible line matches.  A 'possible' line
+         * match is one where the x and y shifts for the first box
+         * in each line are within the maxshiftx and maxshifty
+         * constraints, and the left and right sides of the remaining
+         * (nperline - 1) successive boxes are within delx of each other.
+         * The result is a set of four numas giving parameters of
+         * each set of matching lines. */
+    nai1 = numaCreate(0);  /* line index 1 of match */
+    nai2 = numaCreate(0);  /* line index 2 of match */
+    nasx = numaCreate(0);  /* shiftx for match */
+    nasy = numaCreate(0);  /* shifty for match */
+    for (i = 0; i < n1; i++) {
+        if (line1[i] == 0) continue;
+        y1 = yloc1[i];
+        xl1 = xleft1[i];
+        na1 = numaaGetNuma(naa1, i, L_CLONE);
+        for (j = 0; j < n2; j++) {
+            if (line2[j] == 0) continue;
+            y2 = yloc2[j];
+            if (L_ABS(y1 - y2) > maxshifty) continue;
+            xl2 = xleft2[j];
+            if (L_ABS(xl1 - xl2) > maxshiftx) continue;
+            shiftx = xl1 - xl2;  /* shift to add to x2 values */
+            shifty = y1 - y2;  /* shift to add to y2 values */
+            na2 = numaaGetNuma(naa2, j, L_CLONE);
+
+                /* Now check if 'nperline' boxes in the two lines match */
+            match = testLineAlignmentX(na1, na2, shiftx, delx, nperline);
+            if (match) {
+                numaAddNumber(nai1, i);
+                numaAddNumber(nai2, j);
+                numaAddNumber(nasx, shiftx);
+                numaAddNumber(nasy, shifty);
+            }
+            numaDestroy(&na2);
+        }
+        numaDestroy(&na1);
+    }
+
+        /* Determine if there are a sufficient number of mutually
+         * aligned matches.  Mutually aligned matches place an additional
+         * constraint on the 'possible' matches, where the relative
+         * shifts must not exceed the (delx, dely) distances. */
+    countAlignedMatches(nai1, nai2, nasx, nasy, n1, n2, delx, dely,
+                        nreq, psame, debugflag);
+
+    LEPT_FREE(line1);
+    LEPT_FREE(line2);
+    LEPT_FREE(yloc1);
+    LEPT_FREE(yloc2);
+    LEPT_FREE(xleft1);
+    LEPT_FREE(xleft2);
+    numaDestroy(&nai1);
+    numaDestroy(&nai2);
+    numaDestroy(&nasx);
+    numaDestroy(&nasy);
+    return 0;
+}
+
+
+static l_int32
+testLineAlignmentX(NUMA    *na1,
+                   NUMA    *na2,
+                   l_int32  shiftx,
+                   l_int32  delx,
+                   l_int32  nperline)
+{
+l_int32  i, xl1, xr1, xl2, xr2, diffl, diffr;
+
+    PROCNAME("testLineAlignmentX");
+
+    if (!na1)
+        return ERROR_INT("na1 not defined", procName, 1);
+    if (!na2)
+        return ERROR_INT("na2 not defined", procName, 1);
+
+    for (i = 0; i < nperline; i++) {
+        numaGetIValue(na1, i + 1, &xl1);
+        numaGetIValue(na1, i + 2, &xr1);
+        numaGetIValue(na2, i + 1, &xl2);
+        numaGetIValue(na2, i + 2, &xr2);
+        diffl = L_ABS(xl1 - xl2 - shiftx);
+        diffr = L_ABS(xr1 - xr2 - shiftx);
+        if (diffl > delx || diffr > delx)
+            return 0;
+    }
+
+    return 1;
+}
+
+
+/*
+ *  countAlignedMatches()
+ *      Input:  nai1, nai2 (numas of row pairs for matches)
+ *              nasx, nasy (numas of x and y shifts for the matches)
+ *              n1, n2 (number of rows in images 1 and 2)
+ *              delx, dely (allowed difference in shifts of the match,
+ *                          compared to the reference match)
+ *              nreq (number of required aligned matches)
+ *              &same (<return> 1 if @nreq row matches are found; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This takes 4 input arrays giving parameters of all the
+ *          line matches.  It looks for the maximum set of aligned
+ *          matches (matches with approximately the same overall shifts)
+ *          that do not use rows from either image more than once.
+ */
+static l_int32
+countAlignedMatches(NUMA     *nai1,
+                    NUMA     *nai2,
+                    NUMA     *nasx,
+                    NUMA     *nasy,
+                    l_int32   n1,
+                    l_int32   n2,
+                    l_int32   delx,
+                    l_int32   dely,
+                    l_int32   nreq,
+                    l_int32  *psame,
+                    l_int32   debugflag)
+{
+l_int32   i, j, nm, shiftx, shifty, nmatch, diffx, diffy;
+l_int32  *ia1, *ia2, *iasx, *iasy, *index1, *index2;
+
+    PROCNAME("countAlignedMatches");
+
+    if (!nai1 || !nai2 || !nasx || !nasy)
+        return ERROR_INT("4 input numas not defined", procName, 1);
+    if (!psame)
+        return ERROR_INT("&same not defined", procName, 1);
+    *psame = 0;
+
+        /* Check for sufficient aligned matches, doing a double iteration
+         * over the set of raw matches.  The row index arrays
+         * are used to verify that the same rows in either image
+         * are not used in more than one match.  Whenever there
+         * is a match that is properly aligned, those rows are
+         * marked in the index arrays.  */
+    nm = numaGetCount(nai1);  /* number of matches */
+    if (nm < nreq)
+        return 0;
+
+    ia1 = numaGetIArray(nai1);
+    ia2 = numaGetIArray(nai2);
+    iasx = numaGetIArray(nasx);
+    iasy = numaGetIArray(nasy);
+    index1 = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32));  /* keep track of rows */
+    index2 = (l_int32 *)LEPT_CALLOC(n2, sizeof(l_int32));
+    for (i = 0; i < nm; i++) {
+        if (*psame == 1)
+            break;
+
+            /* Reset row index arrays */
+        memset(index1, 0, 4 * n1);
+        memset(index2, 0, 4 * n2);
+        nmatch = 1;
+        index1[ia1[i]] = nmatch;  /* mark these rows as taken */
+        index2[ia2[i]] = nmatch;
+        shiftx = iasx[i];  /* reference shift between two rows */
+        shifty = iasy[i];  /* ditto */
+        if (nreq == 1) {
+            *psame = 1;
+            break;
+        }
+        for (j = 0; j < nm; j++) {
+            if (j == i) continue;
+                /* Rows must both be different from any previously seen */
+            if (index1[ia1[j]] > 0 || index2[ia2[j]] > 0) continue;
+                /* Check the shift for this match */
+            diffx = L_ABS(shiftx - iasx[j]);
+            diffy = L_ABS(shifty - iasy[j]);
+            if (diffx > delx || diffy > dely) continue;
+                /* We have a match */
+            nmatch++;
+            index1[ia1[j]] = nmatch;  /* mark the rows */
+            index2[ia2[j]] = nmatch;
+            if (nmatch >= nreq) {
+                *psame = 1;
+                if (debugflag)
+                    printRowIndices(index1, n1, index2, n2);
+                break;
+            }
+        }
+    }
+
+    LEPT_FREE(ia1);
+    LEPT_FREE(ia2);
+    LEPT_FREE(iasx);
+    LEPT_FREE(iasy);
+    LEPT_FREE(index1);
+    LEPT_FREE(index2);
+    return 0;
+}
+
+
+static void
+printRowIndices(l_int32  *index1,
+                l_int32   n1,
+                l_int32  *index2,
+                l_int32   n2)
+{
+l_int32  i;
+
+    fprintf(stderr, "Index1: ");
+    for (i = 0; i < n1; i++) {
+        if (i && (i % 20 == 0))
+            fprintf(stderr, "\n        ");
+        fprintf(stderr, "%3d", index1[i]);
+    }
+    fprintf(stderr, "\n");
+
+    fprintf(stderr, "Index2: ");
+    for (i = 0; i < n2; i++) {
+        if (i && (i % 20 == 0))
+            fprintf(stderr, "\n        ");
+        fprintf(stderr, "%3d", index2[i]);
+    }
+    fprintf(stderr, "\n");
+    return;
+}
diff --git a/src/colorcontent.c b/src/colorcontent.c
new file mode 100644 (file)
index 0000000..fca7c8d
--- /dev/null
@@ -0,0 +1,1533 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  colorcontent.c
+ *
+ *      Builds an image of the color content, on a per-pixel basis,
+ *      as a measure of the amount of divergence of each color
+ *      component (R,G,B) from gray.
+ *         l_int32    pixColorContent()
+ *
+ *      Finds the 'amount' of color in an image, on a per-pixel basis,
+ *      as a measure of the difference of the pixel color from gray.
+ *         PIX       *pixColorMagnitude()
+ *
+ *      Generates a mask over pixels that have sufficient color and
+ *      are not too close to gray pixels.
+ *         PIX       *pixMaskOverColorPixels()
+ *
+ *      Generates mask over pixels within a prescribed cube in RGB space
+ *         PIX       *pixMaskOverColorRange()
+ *
+ *      Finds the fraction of pixels with "color" that are not close to black
+ *         l_int32    pixColorFraction()
+ *
+ *      Finds the number of perceptually significant gray intensities
+ *      in a grayscale image.
+ *         l_int32    pixNumSignificantGrayColors()
+ *
+ *      Identifies images where color quantization will cause posterization
+ *      due to the existence of many colors in low-gradient regions.
+ *         l_int32    pixColorsForQuantization()
+ *
+ *      Finds the number of unique colors in an image
+ *         l_int32    pixNumColors()
+ *
+ *      Find the most "populated" colors in the image (and quantize)
+ *         l_int32    pixGetMostPopulatedColors()
+ *         PIX       *pixSimpleColorQuantize()
+ *
+ *      Constructs a color histogram based on rgb indices
+ *         NUMA      *pixGetRGBHistogram()
+ *         l_int32    makeRGBIndexTables()
+ *         l_int32    getRGBFromIndex()
+ *
+ *      Identify images that have highlight (red) color
+ *         l_int32    pixHasHighlightRed()
+ *
+ *  Color is tricky.  If we consider gray (r = g = b) to have no color
+ *  content, how should we define the color content in each component
+ *  of an arbitrary pixel, as well as the overall color magnitude?
+ *
+ *  I can think of three ways to define the color content in each component:
+ *
+ *  (1) Linear.  For each component, take the difference from the average
+ *      of all three.
+ *  (2) Linear.  For each component, take the difference from the average
+ *      of the other two.
+ *  (3) Nonlinear.  For each component, take the minimum of the differences
+ *      from the other two.
+ *
+ *  How might one choose from among these?  Consider two different situations:
+ *  (a) r = g = 0, b = 255            {255}   /255/
+ *  (b) r = 0, g = 127, b = 255       {191}   /128/
+ *  How much g is in each of these?  The three methods above give:
+ *  (a)  1: 85   2: 127   3: 0        [85]
+ *  (b)  1: 0    2: 0     3: 127      [0]
+ *  How much b is in each of these?
+ *  (a)  1: 170  2: 255   3: 255      [255]
+ *  (b)  1: 127  2: 191   3: 127      [191]
+ *  The number I'd "like" to give is in [].  (Please don't ask why, it's
+ *  just a feeling.
+ *
+ *  So my preferences seem to be somewhere between (1) and (2).
+ *  (3) is just too "decisive!"  Let's pick (2).
+ *
+ *  We also allow compensation for white imbalance.  For each
+ *  component, we do a linear TRC (gamma = 1.0), where the black
+ *  point remains at 0 and the white point is given by the input
+ *  parameter.  This is equivalent to doing a global remapping,
+ *  as with pixGlobalNormRGB(), followed by color content (or magnitude)
+ *  computation, but without the overhead of first creating the
+ *  white point normalized image.
+ *
+ *  Another useful property is the overall color magnitude in the pixel.
+ *  For this there are again several choices, such as:
+ *      (a) rms deviation from the mean
+ *      (b) the average L1 deviation from the mean
+ *      (c) the maximum (over components) of one of the color
+ *          content measures given above.
+ *
+ *  For now, we will choose two of the methods in (c):
+ *     L_MAX_DIFF_FROM_AVERAGE_2
+ *        Define the color magnitude as the maximum over components
+ *        of the difference between the component value and the
+ *        average of the other two.  It is easy to show that
+ *        this is equivalent to selecting the two component values
+ *        that are closest to each other, averaging them, and
+ *        using the distance from that average to the third component.
+ *        For (a) and (b) above, this value is in {..}.
+ *    L_MAX_MIN_DIFF_FROM_2
+ *        Define the color magnitude as the maximum over components
+ *        of the minimum difference between the component value and the
+ *        other two values.  It is easy to show that this is equivalent
+ *        to selecting the intermediate value of the three differences
+ *        between the three components.  For (a) and (b) above,
+ *        this value is in /../.
+ */
+
+#include "allheaders.h"
+
+/* ----------------------------------------------------------------------- *
+ *      Builds an image of the color content, on a per-pixel basis,        *
+ *      as a measure of the amount of divergence of each color             *
+ *      component (R,G,B) from gray.                                       *
+ * ----------------------------------------------------------------------- */
+/*!
+ *  pixColorContent()
+ *
+ *      Input:  pixs  (32 bpp rgb or 8 bpp colormapped)
+ *              rwhite, gwhite, bwhite (color value associated with white point)
+ *              mingray (min gray value for which color is measured)
+ *              &pixr (<optional return> 8 bpp red 'content')
+ *              &pixg (<optional return> 8 bpp green 'content')
+ *              &pixb (<optional return> 8 bpp blue 'content')
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This returns the color content in each component, which is
+ *          a measure of the deviation from gray, and is defined
+ *          as the difference between the component and the average of
+ *          the other two components.  See the discussion at the
+ *          top of this file.
+ *      (2) The three numbers (rwhite, gwhite and bwhite) can be thought
+ *          of as the values in the image corresponding to white.
+ *          They are used to compensate for an unbalanced color white point.
+ *          They must either be all 0 or all non-zero.  To turn this
+ *          off, set them all to 0.
+ *      (3) If the maximum component after white point correction,
+ *          max(r,g,b), is less than mingray, all color components
+ *          for that pixel are set to zero.
+ *          Use mingray = 0 to turn off this filtering of dark pixels.
+ *      (4) Therefore, use 0 for all four input parameters if the color
+ *          magnitude is to be calculated without either white balance
+ *          correction or dark filtering.
+ */
+l_int32
+pixColorContent(PIX     *pixs,
+                l_int32  rwhite,
+                l_int32  gwhite,
+                l_int32  bwhite,
+                l_int32  mingray,
+                PIX    **ppixr,
+                PIX    **ppixg,
+                PIX    **ppixb)
+{
+l_int32    w, h, d, i, j, wplc, wplr, wplg, wplb;
+l_int32    rval, gval, bval, rgdiff, rbdiff, gbdiff, maxval, colorval;
+l_int32   *rtab, *gtab, *btab;
+l_uint32   pixel;
+l_uint32  *datac, *datar, *datag, *datab, *linec, *liner, *lineg, *lineb;
+NUMA      *nar, *nag, *nab;
+PIX       *pixc;   /* rgb */
+PIX       *pixr, *pixg, *pixb;   /* 8 bpp grayscale */
+PIXCMAP   *cmap;
+
+    PROCNAME("pixColorContent");
+
+    if (!ppixr && !ppixg && !ppixb)
+        return ERROR_INT("no return val requested", procName, 1);
+    if (ppixr) *ppixr = NULL;
+    if (ppixg) *ppixg = NULL;
+    if (ppixb) *ppixb = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (mingray < 0) mingray = 0;
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (mingray > 255)
+        return ERROR_INT("mingray > 255", procName, 1);
+    if (rwhite < 0 || gwhite < 0 || bwhite < 0)
+        return ERROR_INT("some white vals are negative", procName, 1);
+    if ((rwhite || gwhite || bwhite) && (rwhite * gwhite * bwhite == 0))
+        return ERROR_INT("white vals not all zero or all nonzero", procName, 1);
+
+    cmap = pixGetColormap(pixs);
+    if (!cmap && d != 32)
+        return ERROR_INT("pixs neither cmapped nor 32 bpp", procName, 1);
+    if (cmap)
+        pixc = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+    else
+        pixc = pixClone(pixs);
+
+    pixr = pixg = pixb = NULL;
+    pixGetDimensions(pixc, &w, &h, NULL);
+    if (ppixr) {
+        pixr = pixCreate(w, h, 8);
+        datar = pixGetData(pixr);
+        wplr = pixGetWpl(pixr);
+        *ppixr = pixr;
+    }
+    if (ppixg) {
+        pixg = pixCreate(w, h, 8);
+        datag = pixGetData(pixg);
+        wplg = pixGetWpl(pixg);
+        *ppixg = pixg;
+    }
+    if (ppixb) {
+        pixb = pixCreate(w, h, 8);
+        datab = pixGetData(pixb);
+        wplb = pixGetWpl(pixb);
+        *ppixb = pixb;
+    }
+
+    datac = pixGetData(pixc);
+    wplc = pixGetWpl(pixc);
+    if (rwhite) {  /* all white pt vals are nonzero */
+        nar = numaGammaTRC(1.0, 0, rwhite);
+        rtab = numaGetIArray(nar);
+        nag = numaGammaTRC(1.0, 0, gwhite);
+        gtab = numaGetIArray(nag);
+        nab = numaGammaTRC(1.0, 0, bwhite);
+        btab = numaGetIArray(nab);
+    }
+    for (i = 0; i < h; i++) {
+        linec = datac + i * wplc;
+        if (pixr)
+            liner = datar + i * wplr;
+        if (pixg)
+            lineg = datag + i * wplg;
+        if (pixb)
+            lineb = datab + i * wplb;
+        for (j = 0; j < w; j++) {
+            pixel = linec[j];
+            extractRGBValues(pixel, &rval, &gval, &bval);
+            if (rwhite) {  /* color correct for white point */
+                rval = rtab[rval];
+                gval = gtab[gval];
+                bval = btab[bval];
+            }
+            if (mingray > 0) {  /* dark pixels have no color value */
+                maxval = L_MAX(rval, gval);
+                maxval = L_MAX(maxval, bval);
+                if (maxval < mingray)
+                    continue;  /* colorval = 0 for each component */
+            }
+            rgdiff = L_ABS(rval - gval);
+            rbdiff = L_ABS(rval - bval);
+            gbdiff = L_ABS(gval - bval);
+            if (pixr) {
+                colorval = (rgdiff + rbdiff) / 2;
+                SET_DATA_BYTE(liner, j, colorval);
+            }
+            if (pixg) {
+                colorval = (rgdiff + gbdiff) / 2;
+                SET_DATA_BYTE(lineg, j, colorval);
+            }
+            if (pixb) {
+                colorval = (rbdiff + gbdiff) / 2;
+                SET_DATA_BYTE(lineb, j, colorval);
+            }
+        }
+    }
+
+    if (rwhite) {
+        numaDestroy(&nar);
+        numaDestroy(&nag);
+        numaDestroy(&nab);
+        LEPT_FREE(rtab);
+        LEPT_FREE(gtab);
+        LEPT_FREE(btab);
+    }
+    pixDestroy(&pixc);
+    return 0;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ *      Finds the 'amount' of color in an image, on a per-pixel basis,     *
+ *      as a measure of the difference of the pixel color from gray.       *
+ * ----------------------------------------------------------------------- */
+/*!
+ *  pixColorMagnitude()
+ *
+ *      Input:  pixs  (32 bpp rgb or 8 bpp colormapped)
+ *              rwhite, gwhite, bwhite (color value associated with white point)
+ *              type (chooses the method for calculating the color magnitude:
+ *                    L_MAX_DIFF_FROM_AVERAGE_2, L_MAX_MIN_DIFF_FROM_2,
+ *                    L_MAX_DIFF)
+ *      Return: pixd (8 bpp, amount of color in each source pixel),
+ *                    or NULL on error
+ *
+ *  Notes:
+ *      (1) For an RGB image, a gray pixel is one where all three components
+ *          are equal.  We define the amount of color in an RGB pixel as
+ *          a function depending on the absolute value of the differences
+ *          between the three color components.  Consider the two largest
+ *          of these differences.  The pixel component in common to these
+ *          two differences is the color farthest from the other two.
+ *          The color magnitude in an RGB pixel can be taken as one
+ *          of these three definitions:
+ *            (a) The average of these two differences.  This is the
+ *                average distance from the two components that are
+ *                nearest to each other to the third component.
+ *            (b) The minimum value of these two differences.  This is
+ *                the intermediate value of the three distances between
+ *                component values.  Stated otherwise, it is the
+ *                maximum over all components of the minimum distance
+ *                from that component to the other two components.
+ *            (c) The maximum difference between component values.
+ *      (2) As an example, suppose that R and G are the closest in
+ *          magnitude.  Then the color is determined as either:
+ *            (a) The average distance of B from these two:
+ *                   (|B - R| + |B - G|) / 2
+ *            (b) The minimum distance of B from these two:
+ *                   min(|B - R|, |B - G|).
+ *            (c) The maximum distance of B from these two:
+ *                   max(|B - R|, |B - G|)
+ *      (3) The three methods for choosing the color magnitude from
+ *          the components are selected with these flags:
+ *            (a) L_MAX_DIFF_FROM_AVERAGE_2
+ *            (b) L_MAX_MIN_DIFF_FROM_2
+ *            (c) L_MAX_DIFF
+ *      (4) The three numbers (rwhite, gwhite and bwhite) can be thought
+ *          of as the values in the image corresponding to white.
+ *          They are used to compensate for an unbalanced color white point.
+ *          They must either be all 0 or all non-zero.  To turn this
+ *          off, set them all to 0.
+ */
+PIX *
+pixColorMagnitude(PIX     *pixs,
+                  l_int32  rwhite,
+                  l_int32  gwhite,
+                  l_int32  bwhite,
+                  l_int32  type)
+{
+l_int32    w, h, d, i, j, wplc, wpld;
+l_int32    rval, gval, bval, rdist, gdist, bdist, colorval;
+l_int32    rgdist, rbdist, gbdist, mindist, maxdist, minval, maxval;
+l_int32   *rtab, *gtab, *btab;
+l_uint32   pixel;
+l_uint32  *datac, *datad, *linec, *lined;
+NUMA      *nar, *nag, *nab;
+PIX       *pixc, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixColorMagnitude");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (type != L_MAX_DIFF_FROM_AVERAGE_2 && type != L_MAX_MIN_DIFF_FROM_2 &&
+        type != L_MAX_DIFF)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    if (rwhite < 0 || gwhite < 0 || bwhite < 0)
+        return (PIX *)ERROR_PTR("some white vals are negative", procName, NULL);
+    if ((rwhite || gwhite || bwhite) && (rwhite * gwhite * bwhite == 0))
+        return (PIX *)ERROR_PTR("white vals not all zero or all nonzero",
+                                procName, NULL);
+
+    cmap = pixGetColormap(pixs);
+    if (!cmap && d != 32)
+        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
+    if (cmap)
+        pixc = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+    else
+        pixc = pixClone(pixs);
+
+    pixd = pixCreate(w, h, 8);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datac = pixGetData(pixc);
+    wplc = pixGetWpl(pixc);
+    if (rwhite) {  /* all white pt vals are nonzero */
+        nar = numaGammaTRC(1.0, 0, rwhite);
+        rtab = numaGetIArray(nar);
+        nag = numaGammaTRC(1.0, 0, gwhite);
+        gtab = numaGetIArray(nag);
+        nab = numaGammaTRC(1.0, 0, bwhite);
+        btab = numaGetIArray(nab);
+    }
+    for (i = 0; i < h; i++) {
+        linec = datac + i * wplc;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            pixel = linec[j];
+            extractRGBValues(pixel, &rval, &gval, &bval);
+            if (rwhite) {  /* color correct for white point */
+                rval = rtab[rval];
+                gval = gtab[gval];
+                bval = btab[bval];
+            }
+            if (type == L_MAX_DIFF_FROM_AVERAGE_2) {
+                rdist = ((gval + bval ) / 2 - rval);
+                rdist = L_ABS(rdist);
+                gdist = ((rval + bval ) / 2 - gval);
+                gdist = L_ABS(gdist);
+                bdist = ((rval + gval ) / 2 - bval);
+                bdist = L_ABS(bdist);
+                colorval = L_MAX(rdist, gdist);
+                colorval = L_MAX(colorval, bdist);
+            } else if (type == L_MAX_MIN_DIFF_FROM_2) {  /* intermediate dist */
+                rgdist = L_ABS(rval - gval);
+                rbdist = L_ABS(rval - bval);
+                gbdist = L_ABS(gval - bval);
+                maxdist = L_MAX(rgdist, rbdist);
+                if (gbdist >= maxdist) {
+                    colorval = maxdist;
+                } else {  /* gbdist is smallest or intermediate */
+                    mindist = L_MIN(rgdist, rbdist);
+                    colorval = L_MAX(mindist, gbdist);
+                }
+            } else {  /* type == L_MAX_DIFF */
+                minval = L_MIN(rval, gval);
+                minval = L_MIN(minval, bval);
+                maxval = L_MAX(rval, gval);
+                maxval = L_MAX(maxval, bval);
+                colorval = maxval - minval;
+            }
+            SET_DATA_BYTE(lined, j, colorval);
+        }
+    }
+
+    if (rwhite) {
+        numaDestroy(&nar);
+        numaDestroy(&nag);
+        numaDestroy(&nab);
+        LEPT_FREE(rtab);
+        LEPT_FREE(gtab);
+        LEPT_FREE(btab);
+    }
+    pixDestroy(&pixc);
+    return pixd;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ *      Generates a mask over pixels that have sufficient color and        *
+ *      are not too close to gray pixels.                                  *
+ * ----------------------------------------------------------------------- */
+/*!
+ *  pixMaskOverColorPixels()
+ *
+ *      Input:  pixs  (32 bpp rgb or 8 bpp colormapped)
+ *              threshdiff (threshold for minimum of the max difference
+ *                          between components)
+ *              mindist (minimum allowed distance from nearest non-color pixel)
+ *      Return: pixd (1 bpp, mask over color pixels), or null on error
+ *
+ *  Notes:
+ *      (1) The generated mask identifies each pixel as either color or
+ *          non-color.  For a pixel to be color, it must satisfy two
+ *          constraints:
+ *            (a) The max difference between the r,g and b components must
+ *                equal or exceed a threshold @threshdiff.
+ *            (b) It must be at least @mindist (in an 8-connected way)
+ *                from the nearest non-color pixel.
+ *      (2) The distance constraint (b) is only applied if @mindist > 1.
+ *          For example, if @mindist == 2, the color pixels identified
+ *          by (a) are eroded by a 3x3 Sel.  In general, the Sel size
+ *          for erosion is 2 * (@mindist - 1) + 1.
+ *          Why have this constraint?  In scanned images that are
+ *          essentially gray, color artifacts are typically introduced
+ *          in transition regions near sharp edges that go from dark
+ *          to light, so this allows these transition regions to be removed.
+ */
+PIX *
+pixMaskOverColorPixels(PIX     *pixs,
+                       l_int32  threshdiff,
+                       l_int32  mindist)
+{
+l_int32    w, h, d, i, j, wpls, wpld, size;
+l_int32    rval, gval, bval, minval, maxval;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixc, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixMaskOverColorPixels");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+
+    cmap = pixGetColormap(pixs);
+    if (!cmap && d != 32)
+        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
+    if (cmap)
+        pixc = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+    else
+        pixc = pixClone(pixs);
+
+    pixd = pixCreate(w, h, 1);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datas = pixGetData(pixc);
+    wpls = pixGetWpl(pixc);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            minval = L_MIN(rval, gval);
+            minval = L_MIN(minval, bval);
+            maxval = L_MAX(rval, gval);
+            maxval = L_MAX(maxval, bval);
+            if (maxval - minval >= threshdiff)
+                SET_DATA_BIT(lined, j);
+        }
+    }
+
+    if (mindist > 1) {
+        size = 2 * (mindist - 1) + 1;
+        pixErodeBrick(pixd, pixd, size, size);
+    }
+
+    pixDestroy(&pixc);
+    return pixd;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ *      Generates a mask over pixels that have RGB color components        *
+ *      within the prescribed range (a cube in RGB color space)            *
+ * ----------------------------------------------------------------------- */
+/*!
+ *  pixMaskOverColorRange()
+ *
+ *      Input:  pixs  (32 bpp rgb or 8 bpp colormapped)
+ *              rmin, rmax (min and max allowed values for red component)
+ *              gmin, gmax
+ *              bmin, bmax
+ *      Return: pixd (1 bpp, mask over color pixels), or null on error
+ */
+PIX *
+pixMaskOverColorRange(PIX     *pixs,
+                      l_int32  rmin,
+                      l_int32  rmax,
+                      l_int32  gmin,
+                      l_int32  gmax,
+                      l_int32  bmin,
+                      l_int32  bmax)
+{
+l_int32    w, h, d, i, j, wpls, wpld;
+l_int32    rval, gval, bval;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixc, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixMaskOverColorRange");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+
+    cmap = pixGetColormap(pixs);
+    if (!cmap && d != 32)
+        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
+    if (cmap)
+        pixc = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+    else
+        pixc = pixClone(pixs);
+
+    pixd = pixCreate(w, h, 1);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datas = pixGetData(pixc);
+    wpls = pixGetWpl(pixc);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            if (rval < rmin || rval > rmax) continue;
+            if (gval < gmin || gval > gmax) continue;
+            if (bval < bmin || bval > bmax) continue;
+            SET_DATA_BIT(lined, j);
+        }
+    }
+
+    pixDestroy(&pixc);
+    return pixd;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ *   Finds the fraction of pixels with "color" that are not close to black *
+ * ----------------------------------------------------------------------- */
+/*!
+ *  pixColorFraction()
+ *
+ *      Input:  pixs  (32 bpp rgb)
+ *              darkthresh (threshold near black; if the lightest component
+ *                          is below this, the pixel is not considered in
+ *                          the statistics; typ. 20)
+ *              lightthresh (threshold near white; if the darkest component
+ *                           is above this, the pixel is not considered in
+ *                           the statistics; typ. 244)
+ *              diffthresh (thresh for the maximum difference between
+ *                          component value; below this the pixel is not
+ *                          considered to have sufficient color)
+ *              factor (subsampling factor)
+ *              &pixfract (<return> fraction of pixels in intermediate
+ *                         brightness range that were considered
+ *                         for color content)
+ *              &colorfract (<return> fraction of pixels that meet the
+ *                           criterion for sufficient color; 0.0 on error)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function is asking the question: to what extent does the
+ *          image appear to have color?   The amount of color a pixel
+ *          appears to have depends on both the deviation of the
+ *          individual components from their average and on the average
+ *          intensity itself.  For example, the color will be much more
+ *          obvious with a small deviation from white than the same
+ *          deviation from black.
+ *      (2) Any pixel that meets these three tests is considered a
+ *          colorful pixel:
+ *            (a) the lightest component must equal or exceed @darkthresh
+ *            (b) the darkest component must not exceed @lightthresh
+ *            (c) the max difference between components must equal or
+ *                exceed @diffthresh.
+ *      (3) The dark pixels are removed from consideration because
+ *          they don't appear to have color.
+ *      (4) The very lightest pixels are removed because if an image
+ *          has a lot of "white", the color fraction will be artificially
+ *          low, even if all the other pixels are colorful.
+ *      (5) If pixfract is very small, there are few pixels that are neither
+ *          black nor white.  If colorfract is very small, the pixels
+ *          that are neither black nor white have very little color
+ *          content.  The product 'pixfract * colorfract' gives the
+ *          fraction of pixels with significant color content.
+ *      (6) One use of this function is as a preprocessing step for median
+ *          cut quantization (colorquant2.c), which does a very poor job
+ *          splitting the color space into rectangular volume elements when
+ *          all the pixels are near the diagonal of the color cube.  For
+ *          octree quantization of an image with only gray values, the
+ *          2^(level) octcubes on the diagonal are the only ones
+ *          that can be occupied.
+ */
+l_int32
+pixColorFraction(PIX        *pixs,
+                 l_int32     darkthresh,
+                 l_int32     lightthresh,
+                 l_int32     diffthresh,
+                 l_int32     factor,
+                 l_float32  *ppixfract,
+                 l_float32  *pcolorfract)
+{
+l_int32    i, j, w, h, wpl, rval, gval, bval, minval, maxval;
+l_int32    total, npix, ncolor;
+l_uint32   pixel;
+l_uint32  *data, *line;
+
+    PROCNAME("pixColorFraction");
+
+    if (ppixfract) *ppixfract = 0.0;
+    if (pcolorfract) *pcolorfract = 0.0;
+    if (!ppixfract || !pcolorfract)
+        return ERROR_INT("&pixfract and &colorfract not defined",
+                         procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not defined or not 32 bpp", procName, 1);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    npix = ncolor = total = 0;
+    for (i = 0; i < h; i += factor) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j += factor) {
+            total++;
+            pixel = line[j];
+            extractRGBValues(pixel, &rval, &gval, &bval);
+            minval = L_MIN(rval, gval);
+            minval = L_MIN(minval, bval);
+            if (minval > lightthresh)  /* near white */
+                continue;
+            maxval = L_MAX(rval, gval);
+            maxval = L_MAX(maxval, bval);
+            if (maxval < darkthresh)  /* near black */
+                continue;
+
+            npix++;
+            if (maxval - minval >= diffthresh)
+                ncolor++;
+        }
+    }
+
+    if (npix == 0) {
+        L_WARNING("No pixels found for consideration\n", procName);
+        return 0;
+    }
+    *ppixfract = (l_float32)npix / (l_float32)total;
+    *pcolorfract = (l_float32)ncolor / (l_float32)npix;
+    return 0;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ *      Finds the number of perceptually significant gray intensities      *
+ *      in a grayscale image.                                              *
+ * ----------------------------------------------------------------------- */
+/*!
+ *  pixNumSignificantGrayColors()
+ *
+ *      Input:  pixs  (8 bpp gray)
+ *              darkthresh (dark threshold for minimum intensity to be
+ *                          considered; typ. 20)
+ *              lightthresh (threshold near white, for maximum intensity
+ *                           to be considered; typ. 236)
+ *              minfract (minimum fraction of all pixels to include a level
+ *                        as significant; typ. 0.0001; should be < 0.001)
+ *              factor (subsample factor; integer >= 1)
+ *              &ncolors (<return> number of significant colors; 0 on error)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function is asking the question: how many perceptually
+ *          significant gray color levels is in this pix?
+ *          A color level must meet 3 criteria to be significant:
+ *            - it can't be too close to black
+ *            - it can't be too close to white
+ *            - it must have at least some minimum fractional population
+ *      (2) Use -1 for default values for darkthresh, lightthresh and minfract.
+ *      (3) Choose default of darkthresh = 20, because variations in very
+ *          dark pixels are not visually significant.
+ *      (4) Choose default of lightthresh = 236, because document images
+ *          that have been jpeg'd typically have near-white pixels in the
+ *          8x8 jpeg blocks, and these should not be counted.  It is desirable
+ *          to obtain a clean image by quantizing this noise away.
+ */
+l_int32
+pixNumSignificantGrayColors(PIX       *pixs,
+                            l_int32    darkthresh,
+                            l_int32    lightthresh,
+                            l_float32  minfract,
+                            l_int32    factor,
+                            l_int32   *pncolors)
+{
+l_int32  i, w, h, count, mincount, ncolors;
+NUMA    *na;
+
+    PROCNAME("pixNumSignificantGrayColors");
+
+    if (!pncolors)
+        return ERROR_INT("&ncolors not defined", procName, 1);
+    *pncolors = 0;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (darkthresh < 0) darkthresh = 20;  /* defaults */
+    if (lightthresh < 0) lightthresh = 236;
+    if (minfract < 0.0) minfract = 0.0001;
+    if (minfract > 1.0)
+        return ERROR_INT("minfract > 1.0", procName, 1);
+    if (minfract >= 0.001)
+        L_WARNING("minfract too big; likely to underestimate ncolors\n",
+                  procName);
+    if (lightthresh > 255 || darkthresh >= lightthresh)
+        return ERROR_INT("invalid thresholds", procName, 1);
+    if (factor < 1) factor = 1;
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    mincount = (l_int32)(minfract * w * h * factor * factor);
+    if ((na = pixGetGrayHistogram(pixs, factor)) == NULL)
+        return ERROR_INT("na not made", procName, 1);
+    ncolors = 2;  /* add in black and white */
+    for (i = darkthresh; i <= lightthresh; i++) {
+        numaGetIValue(na, i, &count);
+        if (count >= mincount)
+            ncolors++;
+    }
+
+    *pncolors = ncolors;
+    numaDestroy(&na);
+    return 0;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ *   Identifies images where color quantization will cause posterization   *
+ *   due to the existence of many colors in low-gradient regions.          *
+ * ----------------------------------------------------------------------- */
+/*!
+ *  pixColorsForQuantization()
+ *      Input:  pixs (8 bpp gray or 32 bpp rgb; with or without colormap)
+ *              thresh (binary threshold on edge gradient; 0 for default)
+ *              &ncolors (<return> the number of colors found)
+ *              &iscolor (<optional return> 1 if significant color is found;
+ *                        0 otherwise.  If pixs is 8 bpp, and does not have
+ *                        a colormap with color entries, this is 0)
+ *              debug (1 to output masked image that is tested for colors;
+ *                     0 otherwise)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) This function finds a measure of the number of colors that are
+ *          found in low-gradient regions of an image.  By its
+ *          magnitude relative to some threshold (not specified in
+ *          this function), it gives a good indication of whether
+ *          quantization will generate posterization.   This number
+ *          is larger for images with regions of slowly varying
+ *          intensity (if 8 bpp) or color (if rgb). Such images, if
+ *          quantized, may require dithering to avoid posterization,
+ *          and lossless compression is then expected to be poor.
+ *      (2) If pixs has a colormap, the number of colors returned is
+ *          the number in the colormap.
+ *      (3) It is recommended that document images be reduced to a width
+ *          of 800 pixels before applying this function.  Then it can
+ *          be expected that color detection will be fairly accurate
+ *          and the number of colors will reflect both the content and
+ *          the type of compression to be used.  For less than 15 colors,
+ *          there is unlikely to be a halftone image, and lossless
+ *          quantization should give both a good visual result and
+ *          better compression.
+ *      (4) When using the default threshold on the gradient (15),
+ *          images (both gray and rgb) where ncolors is greater than
+ *          about 15 will compress poorly with either lossless
+ *          compression or dithered quantization, and they may be
+ *          posterized with non-dithered quantization.
+ *      (5) For grayscale images, or images without significant color,
+ *          this returns the number of significant gray levels in
+ *          the low-gradient regions.  The actual number of gray levels
+ *          can be large due to jpeg compression noise in the background.
+ *      (6) Similarly, for color images, the actual number of different
+ *          (r,g,b) colors in the low-gradient regions (rather than the
+ *          number of occupied level 4 octcubes) can be quite large, e.g.,
+ *          due to jpeg compression noise, even for regions that appear
+ *          to be of a single color.  By quantizing to level 4 octcubes,
+ *          most of these superfluous colors are removed from the counting.
+ *      (7) The image is tested for color.  If there is very little color,
+ *          it is thresholded to gray and the number of gray levels in
+ *          the low gradient regions is found.  If the image has color,
+ *          the number of occupied level 4 octcubes is found.
+ *      (8) The number of colors in the low-gradient regions increases
+ *          monotonically with the threshold @thresh on the edge gradient.
+ *      (9) Background: grayscale and color quantization is often useful
+ *          to achieve highly compressed images with little visible
+ *          distortion.  However, gray or color washes (regions of
+ *          low gradient) can defeat this approach to high compression.
+ *          How can one determine if an image is expected to compress
+ *          well using gray or color quantization?  We use the fact that
+ *            * gray washes, when quantized with less than 50 intensities,
+ *              have posterization (visible boundaries between regions
+ *              of uniform 'color') and poor lossless compression
+ *            * color washes, when quantized with level 4 octcubes,
+ *              typically result in both posterization and the occupancy
+ *              of many level 4 octcubes.
+ *          Images can have colors either intrinsically or as jpeg
+ *          compression artifacts.  This function reduces but does not
+ *          completely eliminate measurement of jpeg quantization noise
+ *          in the white background of grayscale or color images.
+ */
+l_int32
+pixColorsForQuantization(PIX      *pixs,
+                         l_int32   thresh,
+                         l_int32  *pncolors,
+                         l_int32  *piscolor,
+                         l_int32   debug)
+{
+l_int32    w, h, d, minside, factor;
+l_float32  pixfract, colorfract;
+PIX       *pixt, *pixsc, *pixg, *pixe, *pixb, *pixm;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixColorsForQuantization");
+
+    if (piscolor) *piscolor = 0;
+    if (!pncolors)
+        return ERROR_INT("&ncolors not defined", procName, 1);
+    *pncolors = 0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if ((cmap = pixGetColormap(pixs)) != NULL) {
+        *pncolors = pixcmapGetCount(cmap);
+        if (piscolor)
+            pixcmapHasColor(cmap, piscolor);
+        return 0;
+    }
+
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 32)
+        return ERROR_INT("pixs not 8 or 32 bpp", procName, 1);
+    if (thresh <= 0)
+        thresh = 15;
+
+        /* First test if 32 bpp has any significant color; if not,
+         * convert it to gray.  Colors whose average values are within
+         * 20 of black or 8 of white are ignored because they're not
+         * very 'colorful'.  If less than 2.5/10000 of the pixels have
+         * significant color, consider the image to be gray. */
+    minside = L_MIN(w, h);
+    if (d == 8) {
+        pixt = pixClone(pixs);
+    } else {  /* d == 32 */
+        factor = L_MAX(1, minside / 400);
+        pixColorFraction(pixs, 20, 248, 30, factor, &pixfract, &colorfract);
+        if (pixfract * colorfract < 0.00025) {
+            pixt = pixGetRGBComponent(pixs, COLOR_RED);
+            d = 8;
+        } else {  /* d == 32 */
+            pixt = pixClone(pixs);
+            if (piscolor)
+                *piscolor = 1;
+        }
+    }
+
+        /* If the smallest side is less than 1000, do not downscale.
+         * If it is in [1000 ... 2000), downscale by 2x.  If it is >= 2000,
+         * downscale by 4x.  Factors of 2 are chosen for speed.  The
+         * actual resolution at which subsequent calculations take place
+         * is not strongly dependent on downscaling.  */
+    factor = L_MAX(1, minside / 500);
+    if (factor == 1)
+        pixsc = pixCopy(NULL, pixt);  /* to be sure pixs is unchanged */
+    else if (factor == 2 || factor == 3)
+        pixsc = pixScaleAreaMap2(pixt);
+    else
+        pixsc = pixScaleAreaMap(pixt, 0.25, 0.25);
+
+        /* Basic edge mask generation procedure:
+         *   - work on a grayscale image
+         *   - get a 1 bpp edge mask by using an edge filter and
+         *     thresholding to get fg pixels at the edges
+         *   - for gray, dilate with a 3x3 brick Sel to get mask over
+         *     all pixels within a distance of 1 pixel from the nearest
+         *     edge pixel
+         *   - for color, dilate with a 7x7 brick Sel to get mask over
+         *     all pixels within a distance of 3 pixels from the nearest
+         *     edge pixel  */
+    if (d == 8)
+        pixg = pixClone(pixsc);
+    else  /* d == 32 */
+        pixg = pixConvertRGBToLuminance(pixsc);
+    pixe = pixSobelEdgeFilter(pixg, L_ALL_EDGES);
+    pixb = pixThresholdToBinary(pixe, thresh);
+    pixInvert(pixb, pixb);
+    if (d == 8)
+        pixm = pixMorphSequence(pixb, "d3.3", 0);
+    else
+        pixm = pixMorphSequence(pixb, "d7.7", 0);
+
+        /* Mask the near-edge pixels to white, and count the colors.
+         * If grayscale, don't count colors within 20 levels of
+         * black or white, and only count colors with a fraction
+         * of at least 1/10000 of the image pixels.
+         * If color, count the number of level 4 octcubes that
+         * contain at least 20 pixels.  These magic numbers are guesses
+         * as to what might work, based on a small data set.  Results
+         * should not be overly sensitive to their actual values. */
+    if (d == 8) {
+        pixSetMasked(pixg, pixm, 0xff);
+        if (debug) pixWrite("junkpix8.png", pixg, IFF_PNG);
+        pixNumSignificantGrayColors(pixg, 20, 236, 0.0001, 1, pncolors);
+    } else {  /* d == 32 */
+        pixSetMasked(pixsc, pixm, 0xffffffff);
+        if (debug) pixWrite("junkpix32.png", pixsc, IFF_PNG);
+        pixNumberOccupiedOctcubes(pixsc, 4, 20, -1, pncolors);
+    }
+
+    pixDestroy(&pixt);
+    pixDestroy(&pixsc);
+    pixDestroy(&pixg);
+    pixDestroy(&pixe);
+    pixDestroy(&pixb);
+    pixDestroy(&pixm);
+    return 0;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ *               Finds the number of unique colors in an image             *
+ * ----------------------------------------------------------------------- */
+/*!
+ *  pixNumColors()
+ *      Input:  pixs (2, 4, 8, 32 bpp)
+ *              factor (subsampling factor; integer)
+ *              &ncolors (<return> the number of colors found, or 0 if
+ *                        there are more than 256)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) This returns the actual number of colors found in the image,
+ *          even if there is a colormap.  If @factor == 1 and the
+ *          number of colors differs from the number of entries
+ *          in the colormap, a warning is issued.
+ *      (2) Use @factor == 1 to find the actual number of colors.
+ *          Use @factor > 1 to quickly find the approximate number of colors.
+ *      (3) For d = 2, 4 or 8 bpp grayscale, this returns the number
+ *          of colors found in the image in 'ncolors'.
+ *      (4) For d = 32 bpp (rgb), if the number of colors is
+ *          greater than 256, this returns 0 in 'ncolors'.
+ */
+l_int32
+pixNumColors(PIX      *pixs,
+             l_int32   factor,
+             l_int32  *pncolors)
+{
+l_int32    w, h, d, i, j, wpl, hashsize, sum, count;
+l_int32    rval, gval, bval, val;
+l_int32   *inta;
+l_uint32   pixel;
+l_uint32  *data, *line;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixNumColors");
+
+    if (!pncolors)
+        return ERROR_INT("&ncolors not defined", procName, 1);
+    *pncolors = 0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 2 && d != 4 && d != 8 && d != 32)
+        return ERROR_INT("d not in {2, 4, 8, 32}", procName, 1);
+    if (factor < 1) factor = 1;
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    sum = 0;
+    if (d != 32) {  /* grayscale */
+        inta = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+        for (i = 0; i < h; i += factor) {
+            line = data + i * wpl;
+            for (j = 0; j < w; j += factor) {
+                if (d == 8)
+                    val = GET_DATA_BYTE(line, j);
+                else if (d == 4)
+                    val = GET_DATA_QBIT(line, j);
+                else  /* d == 2 */
+                    val = GET_DATA_DIBIT(line, j);
+                inta[val] = 1;
+            }
+        }
+        for (i = 0; i < 256; i++)
+            if (inta[i]) sum++;
+        *pncolors = sum;
+        LEPT_FREE(inta);
+
+        cmap = pixGetColormap(pixs);
+        if (cmap && factor == 1) {
+            count = pixcmapGetCount(cmap);
+            if (sum != count)
+                L_WARNING("colormap size %d differs from actual colors\n",
+                          procName, count);
+        }
+        return 0;
+    }
+
+        /* 32 bpp rgb; quit if we get above 256 colors */
+    hashsize = 5507;  /* big and prime; collisions are not likely */
+    inta = (l_int32 *)LEPT_CALLOC(hashsize, sizeof(l_int32));
+    for (i = 0; i < h; i += factor) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j += factor) {
+            pixel = line[j];
+            extractRGBValues(pixel, &rval, &gval, &bval);
+            val = (137 * rval + 269 * gval + 353 * bval) % hashsize;
+            if (inta[val] == 0) {
+                inta[val] = 1;
+                sum++;
+                if (sum > 256) {
+                    LEPT_FREE(inta);
+                    return 0;
+                }
+            }
+        }
+    }
+
+    *pncolors = sum;
+    LEPT_FREE(inta);
+    return 0;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ *       Find the most "populated" colors in the image (and quantize)      *
+ * ----------------------------------------------------------------------- */
+/*!
+ *  pixGetMostPopulatedColors()
+ *      Input:  pixs (32 bpp rgb)
+ *              sigbits (2-6, significant bits retained in the quantizer
+ *                       for each component of the input image)
+ *              factor (subsampling factor; use 1 for no subsampling)
+ *              ncolors (the number of most populated colors to select)
+ *              &array (<optional return> array of colors, each as 0xrrggbb00)
+ *              &cmap (<optional return> colormap of the colors)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This finds the @ncolors most populated cubes in rgb colorspace,
+ *          where the cube size depends on @sigbits as
+ *               cube side = (256 >> sigbits)
+ *      (2) The rgb color components are found at the center of the cube.
+ *      (3) The output array of colors can be displayed using
+ *               pixDisplayColorArray(array, ncolors, ...);
+ */
+l_int32
+pixGetMostPopulatedColors(PIX        *pixs,
+                          l_int32     sigbits,
+                          l_int32     factor,
+                          l_int32     ncolors,
+                          l_uint32  **parray,
+                          PIXCMAP   **pcmap)
+{
+l_int32  n, i, rgbindex, rval, gval, bval;
+NUMA    *nahisto, *naindex;
+
+    PROCNAME("pixGetMostPopulatedColors");
+
+    if (!parray && !pcmap)
+        return ERROR_INT("no return val requested", procName, 1);
+    if (parray) *parray = NULL;
+    if (pcmap) *pcmap = NULL;
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (sigbits < 2 || sigbits > 6)
+        return ERROR_INT("sigbits not in [2 ... 6]", procName, 1);
+    if (factor < 1 || ncolors < 1)
+        return ERROR_INT("factor < 1 or ncolors < 1", procName, 1);
+
+    if ((nahisto = pixGetRGBHistogram(pixs, sigbits, factor)) == NULL)
+        return ERROR_INT("nahisto not made", procName, 1);
+
+        /* naindex contains the index into nahisto, which is the rgbindex */
+    naindex = numaSortIndexAutoSelect(nahisto, L_SORT_DECREASING);
+    numaDestroy(&nahisto);
+    if (!naindex)
+        return ERROR_INT("naindex not made", procName, 1);
+
+    n = numaGetCount(naindex);
+    ncolors = L_MIN(n, ncolors);
+    if (parray) *parray = (l_uint32 *)LEPT_CALLOC(ncolors, sizeof(l_uint32));
+    if (pcmap) *pcmap = pixcmapCreate(8);
+    for (i = 0; i < ncolors; i++) {
+        numaGetIValue(naindex, i, &rgbindex);  /* rgb index */
+        getRGBFromIndex(rgbindex, sigbits, &rval, &gval, &bval);
+        if (parray) composeRGBPixel(rval, gval, bval, *parray + i);
+        if (pcmap) pixcmapAddColor(*pcmap, rval, gval, bval);
+    }
+
+    numaDestroy(&naindex);
+    return 0;
+}
+
+
+/*!
+ *  pixSimpleColorQuantize()
+ *      Input:  pixs (32 bpp rgb)
+ *              sigbits (2-4, significant bits retained in the quantizer
+ *                       for each component of the input image)
+ *              factor (subsampling factor; use 1 for no subsampling)
+ *              ncolors (the number of most populated colors to select)
+ *      Return: pixd (8 bpp cmapped) or NULL on error
+ *
+ *  Notes:
+ *      (1) If you want to do color quantization for real, use octcube
+ *          or modified median cut.  This function shows that it is
+ *          easy to make a simple quantizer based solely on the population
+ *          in cells of a given size in rgb color space.
+ *      (2) The @ncolors most populated cells at the @sigbits level form
+ *          the colormap for quantizing, and this uses octcube indexing
+ *          under the covers to assign each pixel to the nearest color.
+ *      (3) @sigbits is restricted to 2, 3 and 4.  At the low end, the
+ *          color discrimination is very crude; at the upper end, a set of
+ *          similar colors can dominate the result.  Interesting results
+ *          are generally found for @sigbits = 3 and ncolors ~ 20.
+ *      (4) See also pixColorSegment() for a method of quantizing the
+ *          colors to generate regions of similar color.
+ */
+PIX *
+pixSimpleColorQuantize(PIX        *pixs,
+                       l_int32     sigbits,
+                       l_int32     factor,
+                       l_int32     ncolors)
+{
+l_int32   w, h;
+PIX      *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixSimpleColorQuantize");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (sigbits < 2 || sigbits > 4)
+        return (PIX *)ERROR_PTR("sigbits not in {2,3,4}", procName, NULL);
+
+    pixGetMostPopulatedColors(pixs, sigbits, factor, ncolors, NULL, &cmap);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixCreate(w, h, 8);
+    pixSetColormap(pixd, cmap);
+    pixAssignToNearestColor(pixd, pixs, NULL, 4, NULL);
+    return pixd;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ *            Constructs a color histogram based on rgb indices            *
+ * ----------------------------------------------------------------------- */
+/*!
+ *  pixGetRGBHistogram()
+ *      Input:  pixs (32 bpp rgb)
+ *              sigbits (2-6, significant bits retained in the quantizer
+ *                       for each component of the input image)
+ *              factor (subsampling factor; use 1 for no subsampling)
+ *      Return: numa (histogram of colors, indexed by RGB
+ *                    components), or null on error
+ *
+ *  Notes:
+ *      (1) This uses a simple, fast method of indexing into an rgb image.
+ *      (2) The output is a 1D histogram of count vs. rgb-index, which
+ *          uses red sigbits as the most significant and blue as the least.
+ *      (3) This function produces the same result as pixMedianCutHisto().
+ */
+NUMA *
+pixGetRGBHistogram(PIX     *pixs,
+                   l_int32  sigbits,
+                   l_int32  factor)
+{
+l_int32     w, h, i, j, size, wpl, rval, gval, bval, npts;
+l_uint32    val32, rgbindex;
+l_float32  *array;
+l_uint32   *data, *line, *rtab, *gtab, *btab;
+NUMA       *na;
+
+    PROCNAME("pixGetRGBHistogram");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (sigbits < 2 || sigbits > 6)
+        return (NUMA *)ERROR_PTR("sigbits not in [2 ... 6]", procName, NULL);
+    if (factor < 1)
+        return (NUMA *)ERROR_PTR("factor < 1", procName, NULL);
+
+        /* Get histogram size: 2^(3 * sigbits) */
+    size = 1 << (3 * sigbits);  /* 64, 512, 4096, 32768, 262144 */
+    na = numaMakeConstant(0, size);  /* init to all 0 */
+    array = numaGetFArray(na, L_NOCOPY);
+
+    makeRGBIndexTables(&rtab, &gtab, &btab, sigbits);
+
+        /* Check the number of sampled pixels */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    npts = ((w + factor - 1) / factor) * ((h + factor - 1) / factor);
+    if (npts < 1000)
+        L_WARNING("only sampling %d pixels\n", procName, npts);
+    wpl = pixGetWpl(pixs);
+    data = pixGetData(pixs);
+    for (i = 0; i < h; i += factor) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j += factor) {
+            val32 = *(line + j);
+            extractRGBValues(val32, &rval, &gval, &bval);
+            rgbindex = rtab[rval] | gtab[gval] | btab[bval];
+            array[rgbindex]++;
+        }
+    }
+
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    return na;
+}
+
+
+/*!
+ *  makeRGBIndexTables()
+ *
+ *      Input:  &rtab, &gtab, &btab (<return> 256-entry index tables)
+ *              sigbits (2-6, significant bits retained in the quantizer
+ *                       for each component of the input image)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) These tables are used to map from rgb sample values to
+ *          an rgb index, using
+ *             rgbindex = rtab[rval] | gtab[gval] | btab[bval]
+ *          where, e.g., if sigbits = 3, the index is a 9 bit integer:
+ *             r7 r6 r5 g7 g6 g5 b7 b6 b5
+ */
+l_int32
+makeRGBIndexTables(l_uint32  **prtab,
+                   l_uint32  **pgtab,
+                   l_uint32  **pbtab,
+                   l_int32     sigbits)
+{
+l_int32    i;
+l_uint32  *rtab, *gtab, *btab;
+
+    PROCNAME("makeRGBIndexTables");
+
+    if (prtab) *prtab = NULL;
+    if (pgtab) *pgtab = NULL;
+    if (pbtab) *pbtab = NULL;
+    if (!prtab || !pgtab || !pbtab)
+        return ERROR_INT("not all table ptrs defined", procName, 1);
+    if (sigbits < 2 || sigbits > 6)
+        return ERROR_INT("sigbits not in [2 ... 6]", procName, 1);
+
+    rtab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32));
+    gtab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32));
+    btab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32));
+    *prtab = rtab;
+    *pgtab = gtab;
+    *pbtab = btab;
+    switch (sigbits) {
+    case 2:
+        for (i = 0; i < 256; i++) {
+            rtab[i] = (i & 0xc0) >> 2;
+            gtab[i] = (i & 0xc0) >> 4;
+            btab[i] = (i & 0xc0) >> 6;
+        }
+        break;
+    case 3:
+        for (i = 0; i < 256; i++) {
+            rtab[i] = (i & 0xe0) << 1;
+            gtab[i] = (i & 0xe0) >> 2;
+            btab[i] = (i & 0xe0) >> 5;
+        }
+        break;
+    case 4:
+        for (i = 0; i < 256; i++) {
+            rtab[i] = (i & 0xf0) << 4;
+            gtab[i] = (i & 0xf0);
+            btab[i] = (i & 0xf0) >> 4;
+        }
+        break;
+    case 5:
+        for (i = 0; i < 256; i++) {
+          rtab[i] = (i & 0xf8) << 7;
+          gtab[i] = (i & 0xf8) << 2;
+          btab[i] = (i & 0xf8) >> 3;
+        }
+        break;
+    case 6:
+        for (i = 0; i < 256; i++) {
+          rtab[i] = (i & 0xfc) << 10;
+          gtab[i] = (i & 0xfc) << 4;
+          btab[i] = (i & 0xfc) >> 2;
+        }
+        break;
+    default:
+        L_ERROR("Illegal sigbits = %d\n", procName, sigbits);
+        return ERROR_INT("sigbits not in [2 ... 6]", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  getRGBFromIndex()
+ *
+ *      Input:  index (rgbindex)
+ *              sigbits (2-6, significant bits retained in the quantizer
+ *                       for each component of the input image)
+ *              &rval, &gval, &bval (<return> rgb values)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The @index is expressed in bits, based on the the
+ *          @sigbits of the r, g and b components, as
+ *             r7 r6 ... g7 g6 ... b7 b6 ...
+ *      (2) The computed rgb values are in the center of the quantized cube.
+ *          The extra bit that is OR'd accomplishes this.
+ */
+l_int32
+getRGBFromIndex(l_uint32  index,
+                l_int32   sigbits,
+                l_int32  *prval,
+                l_int32  *pgval,
+                l_int32  *pbval)
+{
+    PROCNAME("getRGBFromIndex");
+
+    if (prval) *prval = 0;
+    if (pgval) *pgval = 0;
+    if (pbval) *pbval = 0;
+    if (!prval || !pgval || !pbval)
+        return ERROR_INT("not all component ptrs defined", procName, 1);
+    if (sigbits < 2 || sigbits > 6)
+        return ERROR_INT("sigbits not in [2 ... 6]", procName, 1);
+
+    switch (sigbits) {
+    case 2:
+        *prval = ((index << 2) & 0xc0) | 0x20;
+        *pgval = ((index << 4) & 0xc0) | 0x20;
+        *pbval = ((index << 6) & 0xc0) | 0x20;
+        break;
+    case 3:
+        *prval = ((index >> 1) & 0xe0) | 0x10;
+        *pgval = ((index << 2) & 0xe0) | 0x10;
+        *pbval = ((index << 5) & 0xe0) | 0x10;
+        break;
+    case 4:
+        *prval = ((index >> 4) & 0xf0) | 0x08;
+        *pgval = (index & 0xf0) | 0x08;
+        *pbval = ((index << 4) & 0xf0) | 0x08;
+        break;
+    case 5:
+        *prval = ((index >> 7) & 0xf8) | 0x04;
+        *pgval = ((index >> 2) & 0xf8) | 0x04;
+        *pbval = ((index << 3) & 0xf8) | 0x04;
+        break;
+    case 6:
+        *prval = ((index >> 10) & 0xfc) | 0x02;
+        *pgval = ((index >> 4) & 0xfc) | 0x02;
+        *pbval = ((index << 2) & 0xfc) | 0x02;
+        break;
+    default:
+        L_ERROR("Illegal sigbits = %d\n", procName, sigbits);
+        return ERROR_INT("sigbits not in [2 ... 6]", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/* ----------------------------------------------------------------------- *
+ *             Identify images that have highlight (red) color             *
+ * ----------------------------------------------------------------------- */
+/*!
+ *  pixHasHighlightRed()
+ *
+ *      Input:  pixs  (32 bpp rgb)
+ *              factor (subsampling; an integer >= 1; use 1 for all pixels)
+ *              fract (threshold fraction of all image pixels)
+ *              fthresh (threshold on a function of the components; typ. ~2.5)
+ *              &hasred (<return> 1 if red pixels are above threshold)
+ *              &ratio (<optional return> normalized fraction of threshold
+ *                      red pixels that is actually observed)
+ *              &pixdb (<optional return> seed pixel mask)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Pixels are identified as red if they satisfy two conditions:
+ *          (a) The components satisfy (R-B)/B > @fthresh   (red or dark fg)
+ *          (b) The red component satisfied R > 128  (red or light bg)
+ *          Masks are generated for (a) and (b), and the intersection
+ *          gives the pixels that are red but not either light bg or
+ *          dark fg.
+ *      (2) A typical value for fract = 0.0001, which gives sensitivity
+ *          to an image where a small fraction of the pixels are printed
+ *          in red.
+ *      (3) A typical value for fthresh = 2.5.  Higher values give less
+ *          sensitivity to red, and fewer false positives.
+ */
+l_int32
+pixHasHighlightRed(PIX        *pixs,
+                   l_int32     factor,
+                   l_float32   fract,
+                   l_float32   fthresh,
+                   l_int32    *phasred,
+                   l_float32  *pratio,
+                   PIX       **ppixdb)
+{
+l_int32    w, h, count;
+l_float32  ratio;
+PIX       *pix1, *pix2, *pix3, *pix4;
+FPIX      *fpix;
+
+    PROCNAME("pixHasHighlightRed");
+
+    if (pratio) *pratio = 0.0;
+    if (ppixdb) *ppixdb = NULL;
+    if (phasred) *phasred = 0;
+    if (!pratio && !ppixdb)
+        return ERROR_INT("no return val requested", procName, 1);
+    if (!phasred)
+        return ERROR_INT("&hasred not defined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not defined or not 32 bpp", procName, 1);
+    if (fthresh < 1.5 || fthresh > 3.5)
+        L_WARNING("fthresh = %f is out of normal bounds\n", procName, fthresh);
+
+    if (factor > 1)
+        pix1 = pixScaleByIntSampling(pixs, factor);
+    else
+        pix1 = pixClone(pixs);
+
+        /* Identify pixels that are either red or dark foreground */
+    fpix = pixComponentFunction(pix1, 1.0, 0.0, -1.0, 0.0, 0.0, 1.0);
+    pix2 = fpixThresholdToPix(fpix, fthresh);
+    pixInvert(pix2, pix2);
+
+        /* Identify pixels that are either red or light background */
+    pix3 = pixGetRGBComponent(pix1, COLOR_RED);
+    pix4 = pixThresholdToBinary(pix3, 130);
+    pixInvert(pix4, pix4);
+
+    pixAnd(pix4, pix4, pix2);
+    pixCountPixels(pix4, &count, NULL);
+    pixGetDimensions(pix4, &w, &h, NULL);
+    L_INFO("count = %d, thresh = %d\n", procName, count,
+           (l_int32)(fract * w * h));
+    ratio = (l_float32)count / (fract * w * h);
+    if (pratio) *pratio = ratio;
+    if (ratio >= 1.0)
+        *phasred = 1;
+    if (ppixdb)
+        *ppixdb = pix4;
+    else
+        pixDestroy(&pix4);
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    pixDestroy(&pix3);
+    fpixDestroy(&fpix);
+    return 0;
+}
+
+
diff --git a/src/coloring.c b/src/coloring.c
new file mode 100644 (file)
index 0000000..83eeb3c
--- /dev/null
@@ -0,0 +1,1017 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  coloring.c
+ *
+ *      Coloring "gray" pixels
+ *           PIX             *pixColorGrayRegions()
+ *           l_int32          pixColorGray()
+ *           PIX             *pixColorGrayMasked()
+ *
+ *      Adjusting one or more colors to a target color
+ *           PIX             *pixSnapColor()
+ *           PIX             *pixSnapColorCmap()
+ *
+ *      Piecewise linear color mapping based on a source/target pair
+ *           PIX             *pixLinearMapToTargetColor()
+ *           l_int32          pixelLinearMapToTargetColor()
+ *
+ *      Fractional shift of RGB towards black or white
+ *           PIX             *pixShiftByComponent()
+ *           l_int32          pixelShiftByComponent()
+ *           l_int32          pixelFractionalShift()
+ *
+ *  There are several "coloring" functions in leptonica.
+ *  You can find them in these files:
+ *       coloring.c
+ *       paintcmap.c
+ *       pix2.c
+ *       blend.c
+ *       enhance.c
+ *
+ *  They fall into the following categories:
+ *
+ *  (1) Moving either the light or dark pixels toward a
+ *      specified color. (pixColorGray, pixColorGrayMasked)
+ *  (2) Forcing all pixels whose color is within some delta of a
+ *      specified color to move to that color. (pixSnapColor)
+ *  (3) Doing a piecewise linear color shift specified by a source
+ *      and a target color.  Each component shifts independently.
+ *      (pixLinearMapToTargetColor)
+ *  (4) Shifting all colors by a given fraction of their distance
+ *      from 0 (if shifting down) or from 255 (if shifting up).
+ *      This is useful for colorizing either the background or
+ *      the foreground of a grayscale image. (pixShiftByComponent)
+ *  (5) Shifting all colors by a component-dependent fraction of
+ *      their distance from 0 (if shifting down) or from 255 (if
+ *      shifting up).  This is useful for modifying the color to
+ *      compensate for color shifts in acquisition, for example
+ *      (enhance.c: pixColorShiftRGB).
+ *  (6) Repainting selected pixels. (paintcmap.c: pixSetSelectMaskedCmap)
+ *  (7) Blending a fraction of a specific color with the existing RGB
+ *      color.  (pix2.c: pixBlendInRect())
+ *  (8) Changing selected colors in a colormap.
+ *      (paintcmap.c: pixSetSelectCmap, pixSetSelectMaskedCmap)
+ *  (9) Shifting all the pixels towards black or white depending on
+ *      the gray value of a second image.  (blend.c: pixFadeWithGray)
+ *  (10) Changing the hue, saturation or brightness, by changing the
+ *      appropriate parameter in HSV color space by a fraction of
+ *      the distance toward its end-point.  For example, you can change
+ *      the brightness by moving each pixel's v-parameter a specified
+ *      fraction of the distance toward 0 (darkening) or toward 255
+ *      (brightening).  (enhance.c: pixModifySaturation,
+ *      pixModifyHue, pixModifyBrightness)
+ */
+
+#include "allheaders.h"
+
+
+/*---------------------------------------------------------------------*
+ *                        Coloring "gray" pixels                       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixColorGrayRegions()
+ *
+ *      Input:  pixs (2, 4 or 8 bpp gray, rgb, or colormapped)
+ *              boxa (of regions in which to apply color)
+ *              type (L_PAINT_LIGHT, L_PAINT_DARK)
+ *              thresh (average value below/above which pixel is unchanged)
+ *              rval, gval, bval (new color to paint)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This generates a new image, where some of the pixels in each
+ *          box in the boxa are colorized.  See pixColorGray() for usage
+ *          with @type and @thresh.  Note that @thresh is only used for
+ *          rgb; it is ignored for colormapped images.
+ *      (2) If the input image is colormapped, the new image will be 8 bpp
+ *          colormapped if possible; otherwise, it will be converted
+ *          to 32 bpp rgb.  Only pixels that are strictly gray will be
+ *          colorized.
+ *      (3) If the input image is not colormapped, it is converted to rgb.
+ *          A "gray" value for a pixel is determined by averaging the
+ *          components, and the output rgb value is determined from this.
+ *      (4) This can be used in conjunction with pixFindColorRegions() to
+ *          add highlight color to a grayscale image.
+ */
+PIX *
+pixColorGrayRegions(PIX     *pixs,
+                    BOXA    *boxa,
+                    l_int32  type,
+                    l_int32  thresh,
+                    l_int32  rval,
+                    l_int32  gval,
+                    l_int32  bval)
+{
+l_int32   i, n, ncolors, ngray;
+BOX      *box;
+PIX      *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixColorGrayRegions");
+
+    if (!pixs || pixGetDepth(pixs) == 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (!boxa)
+        return (PIX *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (type != L_PAINT_LIGHT && type != L_PAINT_DARK)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+        /* If cmapped and there is room in an 8 bpp colormap for
+         * expansion, convert pixs to 8 bpp, and colorize. */
+    cmap = pixGetColormap(pixs);
+    if (cmap) {
+        ncolors = pixcmapGetCount(cmap);
+        pixcmapCountGrayColors(cmap, &ngray);
+        if (ncolors + ngray < 255) {
+            pixd = pixConvertTo8(pixs, 1);  /* always new image */
+            pixColorGrayRegionsCmap(pixd, boxa, type, rval, gval, bval);
+            return pixd;
+        }
+    }
+
+        /* The output will be rgb.  Make sure the thresholds are valid */
+    if (type == L_PAINT_LIGHT) {  /* thresh should be low */
+        if (thresh >= 255)
+            return (PIX *)ERROR_PTR("thresh must be < 255", procName, NULL);
+        if (thresh > 127)
+            L_WARNING("threshold set very high\n", procName);
+    } else {  /* type == L_PAINT_DARK; thresh should be high */
+        if (thresh <= 0)
+            return (PIX *)ERROR_PTR("thresh must be > 0", procName, NULL);
+        if (thresh < 128)
+            L_WARNING("threshold set very low\n", procName);
+    }
+
+    pixd = pixConvertTo32(pixs);  /* always new image */
+    n = boxaGetCount(boxa);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        pixColorGray(pixd, box, type, thresh, rval, gval, bval);
+        boxDestroy(&box);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixColorGray()
+ *
+ *      Input:  pixs (8 bpp gray, rgb or colormapped image)
+ *              box (<optional> region in which to apply color; can be NULL)
+ *              type (L_PAINT_LIGHT, L_PAINT_DARK)
+ *              thresh (average value below/above which pixel is unchanged)
+ *              rval, gval, bval (new color to paint)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place operation; pixs is modified.
+ *          If pixs is colormapped, the operation will add colors to the
+ *          colormap.  Otherwise, pixs will be converted to 32 bpp rgb if
+ *          it is initially 8 bpp gray.
+ *      (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels,
+ *          preserving antialiasing.
+ *          If type == L_PAINT_DARK, it colorizes non-white pixels,
+ *          preserving antialiasing.
+ *      (3) If box is NULL, applies function to the entire image; otherwise,
+ *          clips the operation to the intersection of the box and pix.
+ *      (4) If colormapped, calls pixColorGrayCmap(), which applies the
+ *          coloring algorithm only to pixels that are strictly gray.
+ *      (5) For RGB, determines a "gray" value by averaging; then uses this
+ *          value, plus the input rgb target, to generate the output
+ *          pixel values.
+ *      (6) thresh is only used for rgb; it is ignored for colormapped pix.
+ *          If type == L_PAINT_LIGHT, use thresh = 0 if all pixels are to
+ *          be colored (black pixels will be unaltered).
+ *          In situations where there are a lot of black pixels,
+ *          setting thresh > 0 will make the function considerably
+ *          more efficient without affecting the final result.
+ *          If type == L_PAINT_DARK, use thresh = 255 if all pixels
+ *          are to be colored (white pixels will be unaltered).
+ *          In situations where there are a lot of white pixels,
+ *          setting thresh < 255 will make the function considerably
+ *          more efficient without affecting the final result.
+ */
+l_int32
+pixColorGray(PIX     *pixs,
+             BOX     *box,
+             l_int32  type,
+             l_int32  thresh,
+             l_int32  rval,
+             l_int32  gval,
+             l_int32  bval)
+{
+l_int32    i, j, w, h, d, wpl, x1, x2, y1, y2, bw, bh;
+l_int32    nrval, ngval, nbval, aveval;
+l_float32  factor;
+l_uint32   val32;
+l_uint32  *line, *data;
+PIX       *pixt;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixColorGray");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (type != L_PAINT_LIGHT && type != L_PAINT_DARK)
+        return ERROR_INT("invalid type", procName, 1);
+
+    cmap = pixGetColormap(pixs);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (!cmap && d != 8 && d != 32)
+        return ERROR_INT("pixs not cmapped, 8 bpp or rgb", procName, 1);
+    if (cmap)
+        return pixColorGrayCmap(pixs, box, type, rval, gval, bval);
+
+        /* rgb or 8 bpp gray image; check the thresh */
+    if (type == L_PAINT_LIGHT) {  /* thresh should be low */
+        if (thresh >= 255)
+            return ERROR_INT("thresh must be < 255; else this is a no-op",
+                             procName, 1);
+        if (thresh > 127)
+            L_WARNING("threshold set very high\n", procName);
+    } else {  /* type == L_PAINT_DARK; thresh should be high */
+        if (thresh <= 0)
+            return ERROR_INT("thresh must be > 0; else this is a no-op",
+                             procName, 1);
+        if (thresh < 128)
+            L_WARNING("threshold set very low\n", procName);
+    }
+
+        /* In-place conversion to 32 bpp if necessary */
+    if (d == 8) {
+        pixt = pixConvertTo32(pixs);
+        pixTransferAllData(pixs, &pixt, 1, 0);
+    }
+
+    if (!box) {
+        x1 = y1 = 0;
+        x2 = w;
+        y2 = h;
+    } else {
+        boxGetGeometry(box, &x1, &y1, &bw, &bh);
+        x2 = x1 + bw - 1;
+        y2 = y1 + bh - 1;
+    }
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    factor = 1. / 255.;
+    for (i = y1; i <= y2; i++) {
+        if (i < 0 || i >= h)
+            continue;
+        line = data + i * wpl;
+        for (j = x1; j <= x2; j++) {
+            if (j < 0 || j >= w)
+                continue;
+            val32 = *(line + j);
+            aveval = ((val32 >> 24) + ((val32 >> 16) & 0xff) +
+                      ((val32 >> 8) & 0xff)) / 3;
+            if (type == L_PAINT_LIGHT) {
+                if (aveval < thresh)  /* skip sufficiently dark pixels */
+                    continue;
+                nrval = (l_int32)(rval * aveval * factor);
+                ngval = (l_int32)(gval * aveval * factor);
+                nbval = (l_int32)(bval * aveval * factor);
+            } else {  /* type == L_PAINT_DARK */
+                if (aveval > thresh)  /* skip sufficiently light pixels */
+                    continue;
+                nrval = rval + (l_int32)((255. - rval) * aveval * factor);
+                ngval = gval + (l_int32)((255. - gval) * aveval * factor);
+                nbval = bval + (l_int32)((255. - bval) * aveval * factor);
+            }
+            composeRGBPixel(nrval, ngval, nbval, &val32);
+            *(line + j) = val32;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixColorGrayMasked()
+ *
+ *      Input:  pixs (8 bpp gray, rgb or colormapped image)
+ *              pixm (1 bpp mask, through which to apply color)
+ *              type (L_PAINT_LIGHT, L_PAINT_DARK)
+ *              thresh (average value below/above which pixel is unchanged)
+ *              rval, gval, bval (new color to paint)
+ *      Return: pixd (colorized), or null on error
+ *
+ *  Notes:
+ *      (1) This generates a new image, where some of the pixels under
+ *          FG in the mask are colorized.
+ *      (2) See pixColorGray() for usage with @type and @thresh.  Note
+ *          that @thresh is only used for rgb; it is ignored for
+ *          colormapped images.  In most cases, the mask will be over
+ *          the darker parts and @type == L_PAINT_DARK.
+ *      (3) If pixs is colormapped this calls pixColorMaskedCmap(),
+ *          which adds colors to the colormap for pixd; it only adds
+ *          colors corresponding to strictly gray colors in the colormap.
+ *          Otherwise, if pixs is 8 bpp gray, pixd will be 32 bpp rgb.
+ *      (4) If pixs is 32 bpp rgb, for each pixel a "gray" value is
+ *          found by averaging.  This average is then used with the
+ *          input rgb target to generate the output pixel values.
+ *      (5) This can be used in conjunction with pixFindColorRegions() to
+ *          add highlight color to a grayscale image.
+ */
+PIX *
+pixColorGrayMasked(PIX     *pixs,
+                   PIX     *pixm,
+                   l_int32  type,
+                   l_int32  thresh,
+                   l_int32  rval,
+                   l_int32  gval,
+                   l_int32  bval)
+{
+l_int32    i, j, w, h, d, wm, hm, wmin, hmin, wpl, wplm;
+l_int32    nrval, ngval, nbval, aveval;
+l_float32  factor;
+l_uint32   val32;
+l_uint32  *line, *data, *linem, *datam;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixColorGrayMasked");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!pixm || pixGetDepth(pixm) != 1)
+        return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", procName, NULL);
+    if (type != L_PAINT_LIGHT && type != L_PAINT_DARK)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+    cmap = pixGetColormap(pixs);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (!cmap && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not cmapped, 8 bpp gray or 32 bpp",
+                                procName, NULL);
+    if (cmap) {
+        pixd = pixCopy(NULL, pixs);
+        pixColorGrayMaskedCmap(pixd, pixm, type, rval, gval, bval);
+        return pixd;
+    }
+
+        /* rgb or 8 bpp gray image; check the thresh */
+    if (type == L_PAINT_LIGHT) {  /* thresh should be low */
+        if (thresh >= 255)
+            return (PIX *)ERROR_PTR(
+                "thresh must be < 255; else this is a no-op", procName, NULL);
+        if (thresh > 127)
+            L_WARNING("threshold set very high\n", procName);
+    } else {  /* type == L_PAINT_DARK; thresh should be high */
+        if (thresh <= 0)
+            return (PIX *)ERROR_PTR(
+                "thresh must be > 0; else this is a no-op", procName, NULL);
+        if (thresh < 128)
+            L_WARNING("threshold set very low\n", procName);
+    }
+
+    pixGetDimensions(pixm, &wm, &hm, NULL);
+    if (wm != w)
+        L_WARNING("wm = %d differs from w = %d\n", procName, wm, w);
+    if (hm != h)
+        L_WARNING("hm = %d differs from h = %d\n", procName, hm, h);
+    wmin = L_MIN(w, wm);
+    hmin = L_MIN(h, hm);
+    if (d == 8)
+        pixd = pixConvertTo32(pixs);
+    else
+        pixd = pixCopy(NULL, pixs);
+
+    data = pixGetData(pixd);
+    wpl = pixGetWpl(pixd);
+    datam = pixGetData(pixm);
+    wplm = pixGetWpl(pixm);
+    factor = 1. / 255.;
+    for (i = 0; i < hmin; i++) {
+        line = data + i * wpl;
+        linem = datam + i * wplm;
+        for (j = 0; j < wmin; j++) {
+            if (GET_DATA_BIT(linem, j) == 0)
+                continue;
+            val32 = *(line + j);
+            aveval = ((val32 >> 24) + ((val32 >> 16) & 0xff) +
+                      ((val32 >> 8) & 0xff)) / 3;
+            if (type == L_PAINT_LIGHT) {
+                if (aveval < thresh)  /* skip sufficiently dark pixels */
+                    continue;
+                nrval = (l_int32)(rval * aveval * factor);
+                ngval = (l_int32)(gval * aveval * factor);
+                nbval = (l_int32)(bval * aveval * factor);
+            } else {  /* type == L_PAINT_DARK */
+                if (aveval > thresh)  /* skip sufficiently light pixels */
+                    continue;
+                nrval = rval + (l_int32)((255. - rval) * aveval * factor);
+                ngval = gval + (l_int32)((255. - gval) * aveval * factor);
+                nbval = bval + (l_int32)((255. - bval) * aveval * factor);
+            }
+            composeRGBPixel(nrval, ngval, nbval, &val32);
+            *(line + j) = val32;
+        }
+    }
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *            Adjusting one or more colors to a target color        *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixSnapColor()
+ *
+ *      Input:  pixd (<optional>; either NULL or equal to pixs for in-place)
+ *              pixs (colormapped or 8 bpp gray or 32 bpp rgb)
+ *              srcval (color center to be selected for change: 0xrrggbb00)
+ *              dstval (target color for pixels: 0xrrggbb00)
+ *              diff (max absolute difference, applied to all components)
+ *      Return: pixd (with all pixels within diff of pixval set to pixval),
+ *                    or pixd on error
+ *
+ *  Notes:
+ *      (1) For inplace operation, call it this way:
+ *           pixSnapColor(pixs, pixs, ... )
+ *      (2) For generating a new pixd:
+ *           pixd = pixSnapColor(NULL, pixs, ...)
+ *      (3) If pixs has a colormap, it is handled by pixSnapColorCmap().
+ *      (4) All pixels within 'diff' of 'srcval', componentwise,
+ *          will be changed to 'dstval'.
+ */
+PIX *
+pixSnapColor(PIX      *pixd,
+             PIX      *pixs,
+             l_uint32  srcval,
+             l_uint32  dstval,
+             l_int32   diff)
+{
+l_int32    val, sval, dval;
+l_int32    rval, gval, bval, rsval, gsval, bsval;
+l_int32    i, j, w, h, d, wpl;
+l_uint32   pixel;
+l_uint32  *line, *data;
+
+    PROCNAME("pixSnapColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
+
+    if (pixGetColormap(pixs))
+        return pixSnapColorCmap(pixd, pixs, srcval, dstval, diff);
+
+        /* pixs does not have a colormap; it must be 8 bpp gray or
+         * 32 bpp rgb. */
+    if (pixGetDepth(pixs) < 8)
+        return (PIX *)ERROR_PTR("pixs is < 8 bpp", procName, pixd);
+
+        /* Do the work on pixd */
+    if (!pixd)
+        pixd = pixCopy(NULL, pixs);
+
+    pixGetDimensions(pixd, &w, &h, &d);
+    data = pixGetData(pixd);
+    wpl = pixGetWpl(pixd);
+    if (d == 8) {
+        sval = srcval & 0xff;
+        dval = dstval & 0xff;
+        for (i = 0; i < h; i++) {
+            line = data + i * wpl;
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_BYTE(line, j);
+                if (L_ABS(val - sval) <= diff)
+                    SET_DATA_BYTE(line, j, dval);
+            }
+        }
+    } else {  /* d == 32 */
+        extractRGBValues(srcval, &rsval, &gsval, &bsval);
+        for (i = 0; i < h; i++) {
+            line = data + i * wpl;
+            for (j = 0; j < w; j++) {
+                pixel = *(line + j);
+                extractRGBValues(pixel, &rval, &gval, &bval);
+                if ((L_ABS(rval - rsval) <= diff) &&
+                    (L_ABS(gval - gsval) <= diff) &&
+                    (L_ABS(bval - bsval) <= diff))
+                    *(line + j) = dstval;  /* replace */
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixSnapColorCmap()
+ *
+ *      Input:  pixd (<optional>; either NULL or equal to pixs for in-place)
+ *              pixs (colormapped)
+ *              srcval (color center to be selected for change: 0xrrggbb00)
+ *              dstval (target color for pixels: 0xrrggbb00)
+ *              diff (max absolute difference, applied to all components)
+ *      Return: pixd (with all pixels within diff of srcval set to dstval),
+ *                    or pixd on error
+ *
+ *  Notes:
+ *      (1) For inplace operation, call it this way:
+ *           pixSnapCcmap(pixs, pixs, ... )
+ *      (2) For generating a new pixd:
+ *           pixd = pixSnapCmap(NULL, pixs, ...)
+ *      (3) pixs must have a colormap.
+ *      (4) All colors within 'diff' of 'srcval', componentwise,
+ *          will be changed to 'dstval'.
+ */
+PIX *
+pixSnapColorCmap(PIX      *pixd,
+                 PIX      *pixs,
+                 l_uint32  srcval,
+                 l_uint32  dstval,
+                 l_int32   diff)
+{
+l_int32    i, ncolors, index, found;
+l_int32    rval, gval, bval, rsval, gsval, bsval, rdval, gdval, bdval;
+l_int32   *tab;
+PIX       *pixm;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixSnapColorCmap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (!pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("cmap not found", procName, pixd);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
+
+    if (!pixd)
+        pixd = pixCopy(NULL, pixs);
+
+        /* If no free colors, look for one close to the target
+         * that can be commandeered. */
+    cmap = pixGetColormap(pixd);
+    ncolors = pixcmapGetCount(cmap);
+    extractRGBValues(srcval, &rsval, &gsval, &bsval);
+    extractRGBValues(dstval, &rdval, &gdval, &bdval);
+    found = FALSE;
+    if (pixcmapGetFreeCount(cmap) == 0) {
+        for (i = 0; i < ncolors; i++) {
+            pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+            if ((L_ABS(rval - rsval) <= diff) &&
+                (L_ABS(gval - gsval) <= diff) &&
+                (L_ABS(bval - bsval) <= diff)) {
+                index = i;
+                pixcmapResetColor(cmap, index, rdval, gdval, bdval);
+                found = TRUE;
+                break;
+            }
+        }
+    } else {  /* just add the new color */
+        pixcmapAddColor(cmap, rdval, gdval, bdval);
+        ncolors = pixcmapGetCount(cmap);
+        index = ncolors - 1;  /* index of new destination color */
+        found = TRUE;
+    }
+
+    if (!found) {
+        L_INFO("nothing to do\n", procName);
+        return pixd;
+    }
+
+        /* For each color in cmap that is close enough to srcval,
+         * set the tab value to 1.  Then generate a 1 bpp mask with
+         * fg pixels for every pixel in pixd that is close enough
+         * to srcval (i.e., has value 1 in tab). */
+    if ((tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("tab not made", procName, pixd);
+    for (i = 0; i < ncolors; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        if ((L_ABS(rval - rsval) <= diff) &&
+            (L_ABS(gval - gsval) <= diff) &&
+            (L_ABS(bval - bsval) <= diff))
+            tab[i] = 1;
+    }
+    pixm = pixMakeMaskFromLUT(pixd, tab);
+    LEPT_FREE(tab);
+
+        /* Use the binary mask to set all selected pixels to
+         * the dest color index. */
+    pixSetMasked(pixd, pixm, dstval);
+    pixDestroy(&pixm);
+
+        /* Remove all unused colors from the colormap. */
+    pixRemoveUnusedColors(pixd);
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *     Piecewise linear color mapping based on a source/target pair    *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixLinearMapToTargetColor()
+ *
+ *      Input:  pixd (<optional>; either NULL or equal to pixs for in-place)
+ *              pixs (32 bpp rgb)
+ *              srcval (source color: 0xrrggbb00)
+ *              dstval (target color: 0xrrggbb00)
+ *      Return: pixd (with all pixels mapped based on the srcval/destval
+ *                    mapping), or pixd on error
+ *
+ *  Notes:
+ *      (1) For each component (r, b, g) separately, this does a piecewise
+ *          linear mapping of the colors in pixs to colors in pixd.
+ *          If rs and rd are the red src and dest components in @srcval and
+ *          @dstval, then the range [0 ... rs] in pixs is mapped to
+ *          [0 ... rd] in pixd.  Likewise, the range [rs ... 255] in pixs
+ *          is mapped to [rd ... 255] in pixd.  And similarly for green
+ *          and blue.
+ *      (2) The mapping will in general change the hue of the pixels.
+ *          However, if the src and dst targets are related by
+ *          a transformation given by pixelFractionalShift(), the hue
+ *          is invariant.
+ *      (3) For inplace operation, call it this way:
+ *            pixLinearMapToTargetColor(pixs, pixs, ... )
+ *      (4) For generating a new pixd:
+ *            pixd = pixLinearMapToTargetColor(NULL, pixs, ...)
+ */
+PIX *
+pixLinearMapToTargetColor(PIX      *pixd,
+                          PIX      *pixs,
+                          l_uint32  srcval,
+                          l_uint32  dstval)
+{
+l_int32    i, j, w, h, wpl;
+l_int32    rval, gval, bval, rsval, gsval, bsval, rdval, gdval, bdval;
+l_int32   *rtab, *gtab, *btab;
+l_uint32   pixel;
+l_uint32  *line, *data;
+
+    PROCNAME("pixLinearMapToTargetColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs is not 32 bpp", procName, pixd);
+
+        /* Do the work on pixd */
+    if (!pixd)
+        pixd = pixCopy(NULL, pixs);
+
+    extractRGBValues(srcval, &rsval, &gsval, &bsval);
+    extractRGBValues(dstval, &rdval, &gdval, &bdval);
+    rsval = L_MIN(254, L_MAX(1, rsval));
+    gsval = L_MIN(254, L_MAX(1, gsval));
+    bsval = L_MIN(254, L_MAX(1, bsval));
+    rtab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    gtab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    btab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    for (i = 0; i < 256; i++) {
+        if (i <= rsval)
+            rtab[i] = (i * rdval) / rsval;
+        else
+            rtab[i] = rdval + ((255 - rdval) * (i - rsval)) / (255 - rsval);
+        if (i <= gsval)
+            gtab[i] = (i * gdval) / gsval;
+        else
+            gtab[i] = gdval + ((255 - gdval) * (i - gsval)) / (255 - gsval);
+        if (i <= bsval)
+            btab[i] = (i * bdval) / bsval;
+        else
+            btab[i] = bdval + ((255 - bdval) * (i - bsval)) / (255 - bsval);
+    }
+    pixGetDimensions(pixd, &w, &h, NULL);
+    data = pixGetData(pixd);
+    wpl = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            pixel = line[j];
+            extractRGBValues(pixel, &rval, &gval, &bval);
+            composeRGBPixel(rtab[rval], gtab[gval], btab[bval], &pixel);
+            line[j] = pixel;
+        }
+    }
+
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    return pixd;
+}
+
+
+/*!
+ *  pixelLinearMapToTargetColor()
+ *
+ *      Input:  scolor (rgb source color: 0xrrggbb00)
+ *              srcmap (source mapping color: 0xrrggbb00)
+ *              dstmap (target mapping color: 0xrrggbb00)
+ *              &pdcolor (<return> rgb dest color: 0xrrggbb00)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does this does a piecewise linear mapping of each
+ *          component of @scolor to @dcolor, based on the relation
+ *          between the components of @srcmap and @dstmap.  It is the
+ *          same transformation, performed on a single color, as mapped
+ *          on every pixel in a pix by pixLinearMapToTargetColor().
+ *      (2) For each component, if the sval is larger than the smap,
+ *          the dval will be pushed up from dmap towards white.
+ *          Otherwise, dval will be pushed down from dmap towards black.
+ *          This is because you can visualize the transformation as
+ *          a linear stretching where smap moves to dmap, and everything
+ *          else follows linearly with 0 and 255 fixed.
+ *      (3) The mapping will in general change the hue of @scolor.
+ *          However, if the @srcmap and @dstmap targets are related by
+ *          a transformation given by pixelFractionalShift(), the hue
+ *          will be invariant.
+ */
+l_int32
+pixelLinearMapToTargetColor(l_uint32   scolor,
+                            l_uint32   srcmap,
+                            l_uint32   dstmap,
+                            l_uint32  *pdcolor)
+{
+l_int32    srval, sgval, sbval, drval, dgval, dbval;
+l_int32    srmap, sgmap, sbmap, drmap, dgmap, dbmap;
+
+    PROCNAME("pixelLinearMapToTargetColor");
+
+    if (!pdcolor)
+        return ERROR_INT("&dcolor not defined", procName, 1);
+    *pdcolor = 0;
+
+    extractRGBValues(scolor, &srval, &sgval, &sbval);
+    extractRGBValues(srcmap, &srmap, &sgmap, &sbmap);
+    extractRGBValues(dstmap, &drmap, &dgmap, &dbmap);
+    srmap = L_MIN(254, L_MAX(1, srmap));
+    sgmap = L_MIN(254, L_MAX(1, sgmap));
+    sbmap = L_MIN(254, L_MAX(1, sbmap));
+
+    if (srval <= srmap)
+        drval = (srval * drmap) / srmap;
+    else
+        drval = drmap + ((255 - drmap) * (srval - srmap)) / (255 - srmap);
+    if (sgval <= sgmap)
+        dgval = (sgval * dgmap) / sgmap;
+    else
+        dgval = dgmap + ((255 - dgmap) * (sgval - sgmap)) / (255 - sgmap);
+    if (sbval <= sbmap)
+        dbval = (sbval * dbmap) / sbmap;
+    else
+        dbval = dbmap + ((255 - dbmap) * (sbval - sbmap)) / (255 - sbmap);
+
+    composeRGBPixel(drval, dgval, dbval, pdcolor);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *          Fractional shift of RGB towards black or white          *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixShiftByComponent()
+ *
+ *      Input:  pixd (<optional>; either NULL or equal to pixs for in-place)
+ *              pixs (32 bpp rgb)
+ *              srcval (source color: 0xrrggbb00)
+ *              dstval (target color: 0xrrggbb00)
+ *      Return: pixd (with all pixels mapped based on the srcval/destval
+ *                    mapping), or pixd on error
+ *
+ *  Notes:
+ *      (1) For each component (r, b, g) separately, this does a linear
+ *          mapping of the colors in pixs to colors in pixd.
+ *          Let rs and rd be the red src and dest components in @srcval and
+ *          @dstval, and rval is the red component of the src pixel.
+ *          Then for all pixels in pixs, the mapping for the red
+ *          component from pixs to pixd is:
+ *             if (rd <= rs)   (shift toward black)
+ *                 rval --> (rd/rs) * rval
+ *             if (rd > rs)    (shift toward white)
+ *                (255 - rval) --> ((255 - rs)/(255 - rd)) * (255 - rval)
+ *          Thus if rd <= rs, the red component of all pixels is
+ *          mapped by the same fraction toward white, and if rd > rs,
+ *          they are mapped by the same fraction toward black.
+ *          This is essentially a different linear TRC (gamma = 1)
+ *          for each component.  The source and target color inputs are
+ *          just used to generate the three fractions.
+ *      (2) Note that this mapping differs from that in
+ *          pixLinearMapToTargetColor(), which maps rs --> rd and does
+ *          a piecewise stretching in between.
+ *      (3) For inplace operation, call it this way:
+ *            pixFractionalShiftByComponent(pixs, pixs, ... )
+ *      (4) For generating a new pixd:
+ *            pixd = pixLinearMapToTargetColor(NULL, pixs, ...)
+ *      (5) A simple application is to color a grayscale image.
+ *          A light background can be colored using srcval = 0xffffff00
+ *          and picking a target background color for dstval.
+ *          A dark foreground can be colored by using srcval = 0x0
+ *          and choosing a target foreground color for dstval.
+ */
+PIX *
+pixShiftByComponent(PIX      *pixd,
+                    PIX      *pixs,
+                    l_uint32  srcval,
+                    l_uint32  dstval)
+{
+l_int32    i, j, w, h, wpl;
+l_int32    rval, gval, bval, rsval, gsval, bsval, rdval, gdval, bdval;
+l_int32   *rtab, *gtab, *btab;
+l_uint32   pixel;
+l_uint32  *line, *data;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixShiftByComponent");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd not null or == pixs", procName, pixd);
+    if (pixGetDepth(pixs) != 32 && !pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, pixd);
+
+        /* Do the work on pixd */
+    if (!pixd)
+        pixd = pixCopy(NULL, pixs);
+
+        /* If colormapped, just modify it */
+    if ((cmap = pixGetColormap(pixd)) != NULL) {
+        pixcmapShiftByComponent(cmap, srcval, dstval);
+        return pixd;
+    }
+
+    extractRGBValues(srcval, &rsval, &gsval, &bsval);
+    extractRGBValues(dstval, &rdval, &gdval, &bdval);
+    rtab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    gtab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    btab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    for (i = 0; i < 256; i++) {
+        if (rdval == rsval)
+            rtab[i] = i;
+        else if (rdval < rsval)
+            rtab[i] = (i * rdval) / rsval;
+        else
+            rtab[i] = 255 - (255 - rdval) * (255 - i) / (255 - rsval);
+        if (gdval == gsval)
+            gtab[i] = i;
+        else if (gdval < gsval)
+            gtab[i] = (i * gdval) / gsval;
+        else
+            gtab[i] = 255 - (255 - gdval) * (255 - i) / (255 - gsval);
+        if (bdval == bsval)
+            btab[i] = i;
+        else if (bdval < bsval)
+            btab[i] = (i * bdval) / bsval;
+        else
+            btab[i] = 255 - (255 - bdval) * (255 - i) / (255 - bsval);
+    }
+    pixGetDimensions(pixd, &w, &h, NULL);
+    data = pixGetData(pixd);
+    wpl = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            pixel = line[j];
+            extractRGBValues(pixel, &rval, &gval, &bval);
+            composeRGBPixel(rtab[rval], gtab[gval], btab[bval], &pixel);
+            line[j] = pixel;
+        }
+    }
+
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    return pixd;
+}
+
+
+/*!
+ *  pixelShiftByComponent()
+ *
+ *      Input:  rval, gval, bval
+ *              srcval (source color: 0xrrggbb00)
+ *              dstval (target color: 0xrrggbb00)
+ *              &ppixel (<return> rgb value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a linear transformation that gives the same result
+ *          on a single pixel as pixShiftByComponent() gives
+ *          on a pix.  Each component is handled separately.  If
+ *          the dest component is larger than the src, then the
+ *          component is pushed toward 255 by the same fraction as
+ *          the src --> dest shift.
+ */
+l_int32
+pixelShiftByComponent(l_int32    rval,
+                      l_int32    gval,
+                      l_int32    bval,
+                      l_uint32   srcval,
+                      l_uint32   dstval,
+                      l_uint32  *ppixel)
+{
+l_int32  rsval, rdval, gsval, gdval, bsval, bdval, rs, gs, bs;
+
+    PROCNAME("pixelShiftByComponent");
+
+    if (!ppixel)
+        return ERROR_INT("&pixel defined", procName, 1);
+
+    extractRGBValues(srcval, &rsval, &gsval, &bsval);
+    extractRGBValues(dstval, &rdval, &gdval, &bdval);
+    if (rdval == rsval)
+        rs = rval;
+    else if (rdval < rsval)
+        rs = (rval * rdval) / rsval;
+    else
+        rs = 255 - (255 - rdval) * (255 - rval) / (255 - rsval);
+    if (gdval == gsval)
+        gs = gval;
+    else if (gdval < gsval)
+        gs = (gval * gdval) / gsval;
+    else
+        gs = 255 - (255 - gdval) * (255 - gval) / (255 - gsval);
+    if (bdval == bsval)
+        bs = bval;
+    else if (bdval < bsval)
+        bs = (bval * bdval) / bsval;
+    else
+        bs = 255 - (255 - bdval) * (255 - bval) / (255 - bsval);
+    composeRGBPixel(rs, gs, bs, ppixel);
+    return 0;
+}
+
+
+/*!
+ *  pixelFractionalShift()
+ *
+ *      Input:  rval, gval, bval
+ *              fraction (negative toward black; positive toward white)
+ *              &ppixel (<return> rgb value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This transformation leaves the hue invariant, while changing
+ *          the saturation and intensity.  It can be used for that
+ *          purpose in pixLinearMapToTargetColor().
+ *      (2) @fraction is in the range [-1 .... +1].  If @fraction < 0,
+ *          saturation is increased and brightness is reduced.  The
+ *          opposite results if @fraction > 0.  If @fraction == -1,
+ *          the resulting pixel is black; @fraction == 1 results in white.
+ */
+l_int32
+pixelFractionalShift(l_int32    rval,
+                     l_int32    gval,
+                     l_int32    bval,
+                     l_float32  fraction,
+                     l_uint32  *ppixel)
+{
+l_int32  nrval, ngval, nbval;
+
+    PROCNAME("pixelFractionalShift");
+
+    if (!ppixel)
+        return ERROR_INT("&pixel defined", procName, 1);
+    if (fraction < -1.0 || fraction > 1.0)
+        return ERROR_INT("fraction not in [-1 ... +1]", procName, 1);
+
+    nrval = (fraction < 0) ? (l_int32)((1.0 + fraction) * rval + 0.5) :
+            rval + (l_int32)(fraction * (255 - rval) + 0.5);
+    ngval = (fraction < 0) ? (l_int32)((1.0 + fraction) * gval + 0.5) :
+            gval + (l_int32)(fraction * (255 - gval) + 0.5);
+    nbval = (fraction < 0) ? (l_int32)((1.0 + fraction) * bval + 0.5) :
+            bval + (l_int32)(fraction * (255 - bval) + 0.5);
+    composeRGBPixel(nrval, ngval, nbval, ppixel);
+    return 0;
+}
diff --git a/src/colormap.c b/src/colormap.c
new file mode 100644 (file)
index 0000000..ba6a9c6
--- /dev/null
@@ -0,0 +1,2016 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  colormap.c
+ *
+ *      Colormap creation, copy, destruction, addition
+ *           PIXCMAP    *pixcmapCreate()
+ *           PIXCMAP    *pixcmapCreateRandom()
+ *           PIXCMAP    *pixcmapCreateLinear()
+ *           PIXCMAP    *pixcmapCopy()
+ *           void        pixcmapDestroy()
+ *           l_int32     pixcmapAddColor()
+ *           l_int32     pixcmapAddRGBA()
+ *           l_int32     pixcmapAddNewColor()
+ *           l_int32     pixcmapAddNearestColor()
+ *           l_int32     pixcmapUsableColor()
+ *           l_int32     pixcmapAddBlackOrWhite()
+ *           l_int32     pixcmapSetBlackAndWhite()
+ *           l_int32     pixcmapGetCount()
+ *           l_int32     pixcmapGetDepth()
+ *           l_int32     pixcmapGetMinDepth()
+ *           l_int32     pixcmapGetFreeCount()
+ *           l_int32     pixcmapClear()
+ *
+ *      Colormap random access and test
+ *           l_int32     pixcmapGetColor()
+ *           l_int32     pixcmapGetColor32()
+ *           l_int32     pixcmapGetRGBA()
+ *           l_int32     pixcmapGetRGBA32()
+ *           l_int32     pixcmapResetColor()
+ *           l_int32     pixcmapSetAlpha()
+ *           l_int32     pixcmapGetIndex()
+ *           l_int32     pixcmapHasColor()
+ *           l_int32     pixcmapIsOpaque()
+ *           l_int32     pixcmapIsBlackAndWhite()
+ *           l_int32     pixcmapCountGrayColors()
+ *           l_int32     pixcmapGetRankIntensity()
+ *           l_int32     pixcmapGetNearestIndex()
+ *           l_int32     pixcmapGetNearestGrayIndex()
+ *           l_int32     pixcmapGetComponentRange()
+ *           l_int32     pixcmapGetExtremeValue()
+ *
+ *      Colormap conversion
+ *           PIXCMAP    *pixcmapGrayToColor()
+ *           PIXCMAP    *pixcmapColorToGray()
+ *
+ *      Colormap I/O
+ *           l_int32     pixcmapRead()
+ *           l_int32     pixcmapReadStream()
+ *           l_int32     pixcmapWrite()
+ *           l_int32     pixcmapWriteStream()
+ *
+ *      Extract colormap arrays and serialization
+ *           l_int32     pixcmapToArrays()
+ *           l_int32     pixcmapToRGBTable()
+ *           l_int32     pixcmapSerializeToMemory()
+ *           PIXCMAP    *pixcmapDeserializeFromMemory()
+ *           char       *pixcmapConvertToHex()
+ *
+ *      Colormap transforms
+ *           l_int32     pixcmapGammaTRC()
+ *           l_int32     pixcmapContrastTRC()
+ *           l_int32     pixcmapShiftIntensity()
+ *           l_int32     pixcmapShiftByComponent()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+
+/*-------------------------------------------------------------*
+ *                Colormap creation and addition               *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixcmapCreate()
+ *
+ *      Input:  depth (bpp, of pix)
+ *      Return: cmap, or null on error
+ */
+PIXCMAP *
+pixcmapCreate(l_int32  depth)
+{
+RGBA_QUAD  *cta;
+PIXCMAP    *cmap;
+
+    PROCNAME("pixcmapCreate");
+
+    if (depth != 1 && depth != 2 && depth !=4 && depth != 8)
+        return (PIXCMAP *)ERROR_PTR("depth not in {1,2,4,8}", procName, NULL);
+
+    if ((cmap = (PIXCMAP *)LEPT_CALLOC(1, sizeof(PIXCMAP))) == NULL)
+        return (PIXCMAP *)ERROR_PTR("cmap not made", procName, NULL);
+    cmap->depth = depth;
+    cmap->nalloc = 1 << depth;
+    if ((cta = (RGBA_QUAD *)LEPT_CALLOC(cmap->nalloc, sizeof(RGBA_QUAD)))
+        == NULL)
+        return (PIXCMAP *)ERROR_PTR("cta not made", procName, NULL);
+    cmap->array = cta;
+    cmap->n = 0;
+
+    return cmap;
+}
+
+
+/*!
+ *  pixcmapCreateRandom()
+ *
+ *      Input:  depth (bpp, of pix; 2, 4 or 8)
+ *              hasblack (1 if the first color is black; 0 if no black)
+ *              haswhite (1 if the last color is white; 0 if no white)
+ *      Return: cmap, or null on error
+ *
+ *  Notes:
+ *      (1) This sets up a colormap with random colors,
+ *          where the first color is optionally black, the last color
+ *          is optionally white, and the remaining colors are
+ *          chosen randomly.
+ *      (2) The number of randomly chosen colors is:
+ *               2^(depth) - haswhite - hasblack
+ *      (3) Because rand() is seeded, it might disrupt otherwise
+ *          deterministic results if also used elsewhere in a program.
+ *      (4) rand() is not threadsafe, and will generate garbage if run
+ *          on multiple threads at once -- though garbage is generally
+ *          what you want from a random number generator!
+ *      (5) Modern rand()s have equal randomness in low and high order
+ *          bits, but older ones don't.  Here, we're just using rand()
+ *          to choose colors for output.
+ */
+PIXCMAP *
+pixcmapCreateRandom(l_int32  depth,
+                    l_int32  hasblack,
+                    l_int32  haswhite)
+{
+l_int32   ncolors, i;
+l_int32   red[256], green[256], blue[256];
+PIXCMAP  *cmap;
+
+    PROCNAME("pixcmapCreateRandom");
+
+    if (depth != 2 && depth != 4 && depth != 8)
+        return (PIXCMAP *)ERROR_PTR("depth not in {2, 4, 8}", procName, NULL);
+    if (hasblack != 0) hasblack = 1;
+    if (haswhite != 0) haswhite = 1;
+
+    cmap = pixcmapCreate(depth);
+    ncolors = 1 << depth;
+    if (hasblack)  /* first color is optionally black */
+        pixcmapAddColor(cmap, 0, 0, 0);
+    for (i = hasblack; i < ncolors - haswhite; i++) {
+        red[i] = (l_uint32)rand() & 0xff;
+        green[i] = (l_uint32)rand() & 0xff;
+        blue[i] = (l_uint32)rand() & 0xff;
+        pixcmapAddColor(cmap, red[i], green[i], blue[i]);
+    }
+    if (haswhite)  /* last color is optionally white */
+        pixcmapAddColor(cmap, 255, 255, 255);
+
+    return cmap;
+}
+
+
+/*!
+ *  pixcmapCreateLinear()
+ *
+ *      Input:  d (depth of pix for this colormap; 1, 2, 4 or 8)
+ *              nlevels (valid in range [2, 2^d])
+ *      Return: cmap, or null on error
+ *
+ *  Notes:
+ *      (1) Colormap has equally spaced gray color values
+ *          from black (0, 0, 0) to white (255, 255, 255).
+ */
+PIXCMAP *
+pixcmapCreateLinear(l_int32  d,
+                    l_int32  nlevels)
+{
+l_int32   maxlevels, i, val;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixcmapCreateLinear");
+
+    if (d != 1 && d != 2 && d !=4 && d != 8)
+        return (PIXCMAP *)ERROR_PTR("d not in {1, 2, 4, 8}", procName, NULL);
+    maxlevels = 1 << d;
+    if (nlevels < 2 || nlevels > maxlevels)
+        return (PIXCMAP *)ERROR_PTR("invalid nlevels", procName, NULL);
+
+    cmap = pixcmapCreate(d);
+    for (i = 0; i < nlevels; i++) {
+        val = (255 * i) / (nlevels - 1);
+        pixcmapAddColor(cmap, val, val, val);
+    }
+    return cmap;
+}
+
+
+/*!
+ *  pixcmapCopy()
+ *
+ *      Input:  cmaps
+ *      Return: cmapd, or null on error
+ */
+PIXCMAP *
+pixcmapCopy(PIXCMAP  *cmaps)
+{
+l_int32   nbytes;
+PIXCMAP  *cmapd;
+
+    PROCNAME("pixcmapCopy");
+
+    if (!cmaps)
+        return (PIXCMAP *)ERROR_PTR("cmaps not defined", procName, NULL);
+    if (cmaps->nalloc > 256)
+        return (PIXCMAP *)ERROR_PTR("nalloc > 256", procName, NULL);
+
+    if ((cmapd = (PIXCMAP *)LEPT_CALLOC(1, sizeof(PIXCMAP))) == NULL)
+        return (PIXCMAP *)ERROR_PTR("cmapd not made", procName, NULL);
+    nbytes = cmaps->nalloc * sizeof(RGBA_QUAD);
+    if ((cmapd->array = (void *)LEPT_CALLOC(1, nbytes)) == NULL)
+        return (PIXCMAP *)ERROR_PTR("cmap array not made", procName, NULL);
+    memcpy(cmapd->array, cmaps->array, nbytes);
+    cmapd->n = cmaps->n;
+    cmapd->nalloc = cmaps->nalloc;
+    cmapd->depth = cmaps->depth;
+    return cmapd;
+}
+
+
+/*!
+ *  pixcmapDestroy()
+ *
+ *      Input:  &cmap (<set to null>)
+ *      Return: void
+ */
+void
+pixcmapDestroy(PIXCMAP  **pcmap)
+{
+PIXCMAP  *cmap;
+
+    PROCNAME("pixcmapDestroy");
+
+    if (pcmap == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((cmap = *pcmap) == NULL)
+        return;
+
+    LEPT_FREE(cmap->array);
+    LEPT_FREE(cmap);
+    *pcmap = NULL;
+    return;
+}
+
+
+/*!
+ *  pixcmapAddColor()
+ *
+ *      Input:  cmap
+ *              rval, gval, bval (colormap entry to be added; each number
+ *                                is in range [0, ... 255])
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This always adds the color if there is room.
+ *      (2) The alpha component is 255 (opaque)
+ */
+l_int32
+pixcmapAddColor(PIXCMAP  *cmap,
+                l_int32   rval,
+                l_int32   gval,
+                l_int32   bval)
+{
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixcmapAddColor");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (cmap->n >= cmap->nalloc)
+        return ERROR_INT("no free color entries", procName, 1);
+
+    cta = (RGBA_QUAD *)cmap->array;
+    cta[cmap->n].red = rval;
+    cta[cmap->n].green = gval;
+    cta[cmap->n].blue = bval;
+    cta[cmap->n].alpha = 255;
+    cmap->n++;
+    return 0;
+}
+
+
+/*!
+ *  pixcmapAddRGBA()
+ *
+ *      Input:  cmap
+ *              rval, gval, bval, aval (colormap entry to be added;
+ *                                      each number is in range [0, ... 255])
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This always adds the color if there is room.
+ */
+l_int32
+pixcmapAddRGBA(PIXCMAP  *cmap,
+               l_int32   rval,
+               l_int32   gval,
+               l_int32   bval,
+               l_int32   aval)
+{
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixcmapAddRGBA");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (cmap->n >= cmap->nalloc)
+        return ERROR_INT("no free color entries", procName, 1);
+
+    cta = (RGBA_QUAD *)cmap->array;
+    cta[cmap->n].red = rval;
+    cta[cmap->n].green = gval;
+    cta[cmap->n].blue = bval;
+    cta[cmap->n].alpha = aval;
+    cmap->n++;
+    return 0;
+}
+
+
+/*!
+ *  pixcmapAddNewColor()
+ *
+ *      Input:  cmap
+ *              rval, gval, bval (colormap entry to be added; each number
+ *                                is in range [0, ... 255])
+ *              &index (<return> index of color)
+ *      Return: 0 if OK, 1 on error; 2 if unable to add color
+ *
+ *  Notes:
+ *      (1) This only adds color if not already there.
+ *      (2) The alpha component is 255 (opaque)
+ *      (3) This returns the index of the new (or existing) color.
+ *      (4) Returns 2 with a warning if unable to add this color;
+ *          the caller should check the return value.
+ */
+l_int32
+pixcmapAddNewColor(PIXCMAP  *cmap,
+                   l_int32   rval,
+                   l_int32   gval,
+                   l_int32   bval,
+                   l_int32  *pindex)
+{
+    PROCNAME("pixcmapAddNewColor");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+        /* Check if the color is already present. */
+    if (!pixcmapGetIndex(cmap, rval, gval, bval, pindex))  /* found */
+        return 0;
+
+        /* We need to add the color.  Is there room? */
+    if (cmap->n >= cmap->nalloc) {
+        L_WARNING("no free color entries\n", procName);
+        return 2;
+    }
+
+        /* There's room.  Add it. */
+    pixcmapAddColor(cmap, rval, gval, bval);
+    *pindex = pixcmapGetCount(cmap) - 1;
+    return 0;
+}
+
+
+/*!
+ *  pixcmapAddNearestColor()
+ *
+ *      Input:  cmap
+ *              rval, gval, bval (colormap entry to be added; each number
+ *                                is in range [0, ... 255])
+ *              &index (<return> index of color)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This only adds color if not already there.
+ *      (2) The alpha component is 255 (opaque)
+ *      (3) If it's not in the colormap and there is no room to add
+ *          another color, this returns the index of the nearest color.
+ */
+l_int32
+pixcmapAddNearestColor(PIXCMAP  *cmap,
+                       l_int32   rval,
+                       l_int32   gval,
+                       l_int32   bval,
+                       l_int32  *pindex)
+{
+    PROCNAME("pixcmapAddNearestColor");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+        /* Check if the color is already present. */
+    if (!pixcmapGetIndex(cmap, rval, gval, bval, pindex))  /* found */
+        return 0;
+
+        /* We need to add the color.  Is there room? */
+    if (cmap->n < cmap->nalloc) {
+        pixcmapAddColor(cmap, rval, gval, bval);
+        *pindex = pixcmapGetCount(cmap) - 1;
+        return 0;
+    }
+
+        /* There's no room.  Return the index of the nearest color */
+    pixcmapGetNearestIndex(cmap, rval, gval, bval, pindex);
+    return 0;
+}
+
+
+/*!
+ *  pixcmapUsableColor()
+ *
+ *      Input:  cmap
+ *              rval, gval, bval (colormap entry to be added; each number
+ *                                is in range [0, ... 255])
+ *              usable (<return> 1 if usable; 0 if not)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This checks if the color already exists or if there is
+ *          room to add it.  It makes no change in the colormap.
+ */
+l_int32
+pixcmapUsableColor(PIXCMAP  *cmap,
+                   l_int32   rval,
+                   l_int32   gval,
+                   l_int32   bval,
+                   l_int32  *pusable)
+{
+l_int32  index;
+
+    PROCNAME("pixcmapUsableColor");
+
+    if (!pusable)
+        return ERROR_INT("&usable not defined", procName, 1);
+    *pusable = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+        /* Is there room to add it? */
+    if (cmap->n < cmap->nalloc) {
+        *pusable = 1;
+        return 0;
+    }
+
+        /* No room; check if the color is already present. */
+    if (!pixcmapGetIndex(cmap, rval, gval, bval, &index))   /* found */
+        *pusable = 1;
+    return 0;
+}
+
+
+/*!
+ *  pixcmapAddBlackOrWhite()
+ *
+ *      Input:  cmap
+ *              color (0 for black, 1 for white)
+ *              &index (<optional return> index of color; can be null)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This only adds color if not already there.
+ *      (2) The alpha component is 255 (opaque)
+ *      (3) This sets index to the requested color.
+ *      (4) If there is no room in the colormap, returns the index
+ *          of the closest color.
+ */
+l_int32
+pixcmapAddBlackOrWhite(PIXCMAP  *cmap,
+                       l_int32   color,
+                       l_int32  *pindex)
+{
+l_int32  index;
+
+    PROCNAME("pixcmapAddBlackOrWhite");
+
+    if (pindex) *pindex = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    if (color == 0) {  /* black */
+        if (pixcmapGetFreeCount(cmap) > 0)
+            pixcmapAddNewColor(cmap, 0, 0, 0, &index);
+        else
+            pixcmapGetRankIntensity(cmap, 0.0, &index);
+    } else {  /* white */
+        if (pixcmapGetFreeCount(cmap) > 0)
+            pixcmapAddNewColor(cmap, 255, 255, 255, &index);
+        else
+            pixcmapGetRankIntensity(cmap, 1.0, &index);
+    }
+
+    if (pindex)
+        *pindex = index;
+    return 0;
+}
+
+
+/*!
+ *  pixcmapSetBlackAndWhite()
+ *
+ *      Input:  cmap
+ *              setblack (0 for no operation; 1 to set darkest color to black)
+ *              setwhite (0 for no operation; 1 to set lightest color to white)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixcmapSetBlackAndWhite(PIXCMAP  *cmap,
+                        l_int32   setblack,
+                        l_int32   setwhite)
+{
+l_int32  index;
+
+    PROCNAME("pixcmapSetBlackAndWhite");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    if (setblack) {
+        pixcmapGetRankIntensity(cmap, 0.0, &index);
+        pixcmapResetColor(cmap, index, 0, 0, 0);
+    }
+    if (setwhite) {
+        pixcmapGetRankIntensity(cmap, 1.0, &index);
+        pixcmapResetColor(cmap, index, 255, 255, 255);
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixcmapGetCount()
+ *
+ *      Input:  cmap
+ *      Return: count, or 0 on error
+ */
+l_int32
+pixcmapGetCount(PIXCMAP  *cmap)
+{
+    PROCNAME("pixcmapGetCount");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 0);
+    return cmap->n;
+}
+
+
+/*!
+ *  pixcmapGetFreeCount()
+ *
+ *      Input:  cmap
+ *      Return: free entries, or 0 on error
+ */
+l_int32
+pixcmapGetFreeCount(PIXCMAP  *cmap)
+{
+    PROCNAME("pixcmapGetFreeCount");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 0);
+    return (cmap->nalloc - cmap->n);
+}
+
+
+/*!
+ *  pixcmapGetDepth()
+ *
+ *      Input:  cmap
+ *      Return: depth, or 0 on error
+ */
+l_int32
+pixcmapGetDepth(PIXCMAP  *cmap)
+{
+    PROCNAME("pixcmapGetDepth");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 0);
+    return cmap->depth;
+}
+
+
+/*!
+ *  pixcmapGetMinDepth()
+ *
+ *      Input:  cmap
+ *              &mindepth (<return> minimum depth to support the colormap)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) On error, &mindepth is returned as 0.
+ */
+l_int32
+pixcmapGetMinDepth(PIXCMAP  *cmap,
+                   l_int32  *pmindepth)
+{
+l_int32  ncolors;
+
+    PROCNAME("pixcmapGetMinDepth");
+
+    if (!pmindepth)
+        return ERROR_INT("&mindepth not defined", procName, 1);
+    *pmindepth = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    ncolors = pixcmapGetCount(cmap);
+    if (ncolors <= 4)
+        *pmindepth = 2;
+    else if (ncolors <= 16)
+        *pmindepth = 4;
+    else  /* ncolors > 16 */
+        *pmindepth = 8;
+    return 0;
+}
+
+
+/*!
+ *  pixcmapClear()
+ *
+ *      Input:  cmap
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Note: this removes the colors by setting the count to 0.
+ */
+l_int32
+pixcmapClear(PIXCMAP  *cmap)
+{
+    PROCNAME("pixcmapClear");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    cmap->n = 0;
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *                      Colormap random access                 *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixcmapGetColor()
+ *
+ *      Input:  cmap
+ *              index
+ *              &rval, &gval, &bval (<return> each color value)
+ *      Return: 0 if OK, 1 if not accessible (caller should check)
+ */
+l_int32
+pixcmapGetColor(PIXCMAP  *cmap,
+                l_int32   index,
+                l_int32  *prval,
+                l_int32  *pgval,
+                l_int32  *pbval)
+{
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixcmapGetColor");
+
+    if (!prval || !pgval || !pbval)
+        return ERROR_INT("&rval, &gval, &bval not all defined", procName, 1);
+    *prval = *pgval = *pbval = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (index < 0 || index >= cmap->n)
+        return ERROR_INT("index out of bounds", procName, 1);
+
+    cta = (RGBA_QUAD *)cmap->array;
+    *prval = cta[index].red;
+    *pgval = cta[index].green;
+    *pbval = cta[index].blue;
+    return 0;
+}
+
+
+/*!
+ *  pixcmapGetColor32()
+ *
+ *      Input:  cmap
+ *              index
+ *              &val32 (<return> 32-bit rgb color value)
+ *      Return: 0 if OK, 1 if not accessible (caller should check)
+ *
+ *  Notes:
+ *      (1) The returned alpha channel value is 255.
+ */
+l_int32
+pixcmapGetColor32(PIXCMAP   *cmap,
+                  l_int32    index,
+                  l_uint32  *pval32)
+{
+l_int32  rval, gval, bval;
+
+    PROCNAME("pixcmapGetColor32");
+
+    if (!pval32)
+        return ERROR_INT("&val32 not defined", procName, 1);
+    *pval32 = 0;
+
+    if (pixcmapGetColor(cmap, index, &rval, &gval, &bval) != 0)
+        return ERROR_INT("rgb values not found", procName, 1);
+    composeRGBAPixel(rval, gval, bval, 255, pval32);
+    return 0;
+}
+
+
+/*!
+ *  pixcmapGetRGBA()
+ *
+ *      Input:  cmap
+ *              index
+ *              &rval, &gval, &bval, &aval (<return> each color value)
+ *      Return: 0 if OK, 1 if not accessible (caller should check)
+ */
+l_int32
+pixcmapGetRGBA(PIXCMAP  *cmap,
+               l_int32   index,
+               l_int32  *prval,
+               l_int32  *pgval,
+               l_int32  *pbval,
+               l_int32  *paval)
+{
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixcmapGetRGBA");
+
+    if (!prval || !pgval || !pbval || !paval)
+        return ERROR_INT("&rval, &gval, &bval, &aval not all defined",
+                procName, 1);
+    *prval = *pgval = *pbval = *paval = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (index < 0 || index >= cmap->n)
+        return ERROR_INT("index out of bounds", procName, 1);
+
+    cta = (RGBA_QUAD *)cmap->array;
+    *prval = cta[index].red;
+    *pgval = cta[index].green;
+    *pbval = cta[index].blue;
+    *paval = cta[index].alpha;
+    return 0;
+}
+
+
+/*!
+ *  pixcmapGetRGBA32()
+ *
+ *      Input:  cmap
+ *              index
+ *              &val32 (<return> 32-bit rgba color value)
+ *      Return: 0 if OK, 1 if not accessible (caller should check)
+ */
+l_int32
+pixcmapGetRGBA32(PIXCMAP   *cmap,
+                 l_int32    index,
+                 l_uint32  *pval32)
+{
+l_int32  rval, gval, bval, aval;
+
+    PROCNAME("pixcmapGetRGBA32");
+
+    if (!pval32)
+        return ERROR_INT("&val32 not defined", procName, 1);
+    *pval32 = 0;
+
+    if (pixcmapGetRGBA(cmap, index, &rval, &gval, &bval, &aval) != 0)
+        return ERROR_INT("rgba values not found", procName, 1);
+    composeRGBAPixel(rval, gval, bval, aval, pval32);
+    return 0;
+}
+
+
+/*!
+ *  pixcmapResetColor()
+ *
+ *      Input:  cmap
+ *              index
+ *              rval, gval, bval (colormap entry to be reset; each number
+ *                                is in range [0, ... 255])
+ *      Return: 0 if OK, 1 if not accessible (caller should check)
+ *
+ *  Notes:
+ *      (1) This resets sets the color of an entry that has already
+ *          been set and included in the count of colors.
+ *      (2) The alpha component is 255 (opaque)
+ */
+l_int32
+pixcmapResetColor(PIXCMAP  *cmap,
+                  l_int32   index,
+                  l_int32   rval,
+                  l_int32   gval,
+                  l_int32   bval)
+{
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixcmapResetColor");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (index < 0 || index >= cmap->n)
+        return ERROR_INT("index out of bounds", procName, 1);
+
+    cta = (RGBA_QUAD *)cmap->array;
+    cta[index].red = rval;
+    cta[index].green = gval;
+    cta[index].blue = bval;
+    cta[index].alpha = 255;
+    return 0;
+}
+
+
+/*!
+ *  pixcmapSetAlpha()
+ *
+ *      Input:  cmap
+ *              index
+ *              aval (in range [0, ... 255])
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This modifies the transparency of one entry in a colormap.
+ *          The alpha component by default is 255 (opaque).
+ *          This is used when extracting the colormap from a PNG file
+ *          without decoding the image.
+ */
+l_int32
+pixcmapSetAlpha(PIXCMAP  *cmap,
+                l_int32   index,
+                l_int32   aval)
+{
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixcmapSetAlpha");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (index < 0 || index >= cmap->n)
+        return ERROR_INT("index out of bounds", procName, 1);
+
+    cta = (RGBA_QUAD *)cmap->array;
+    cta[index].alpha = aval;
+    return 0;
+}
+
+
+/*!
+ *  pixcmapGetIndex()
+ *
+ *      Input:  cmap
+ *              rval, gval, bval (colormap colors to search for; each number
+ *                                is in range [0, ... 255])
+ *              &index (<return>)
+ *      Return: 0 if found, 1 if not found (caller must check)
+ */
+l_int32
+pixcmapGetIndex(PIXCMAP  *cmap,
+                l_int32   rval,
+                l_int32   gval,
+                l_int32   bval,
+                l_int32  *pindex)
+{
+l_int32     n, i;
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixcmapGetIndex");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    n = pixcmapGetCount(cmap);
+
+    cta = (RGBA_QUAD *)cmap->array;
+    for (i = 0; i < n; i++) {
+        if (rval == cta[i].red &&
+            gval == cta[i].green &&
+            bval == cta[i].blue) {
+            *pindex = i;
+            return 0;
+        }
+    }
+    return 1;
+}
+
+
+/*!
+ *  pixcmapHasColor()
+ *
+ *      Input:  cmap
+ *              &color (<return> TRUE if cmap has color; FALSE otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixcmapHasColor(PIXCMAP  *cmap,
+                l_int32  *pcolor)
+{
+l_int32   n, i;
+l_int32  *rmap, *gmap, *bmap;
+
+    PROCNAME("pixcmapHasColor");
+
+    if (!pcolor)
+        return ERROR_INT("&color not defined", procName, 1);
+    *pcolor = FALSE;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    if (pixcmapToArrays(cmap, &rmap, &gmap, &bmap, NULL))
+        return ERROR_INT("colormap arrays not made", procName, 1);
+    n = pixcmapGetCount(cmap);
+    for (i = 0; i < n; i++) {
+        if ((rmap[i] != gmap[i]) || (rmap[i] != bmap[i])) {
+            *pcolor = TRUE;
+            break;
+        }
+    }
+
+    LEPT_FREE(rmap);
+    LEPT_FREE(gmap);
+    LEPT_FREE(bmap);
+    return 0;
+}
+
+
+/*!
+ *  pixcmapIsOpaque()
+ *
+ *      Input:  cmap
+ *              &opaque (<return> TRUE if fully opaque: all entries are 255)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixcmapIsOpaque(PIXCMAP  *cmap,
+                l_int32  *popaque)
+{
+l_int32     i, n;
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixcmapIsOpaque");
+
+    if (!popaque)
+        return ERROR_INT("&opaque not defined", procName, 1);
+    *popaque = TRUE;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    n = pixcmapGetCount(cmap);
+    cta = (RGBA_QUAD *)cmap->array;
+    for (i = 0; i < n; i++) {
+        if (cta[i].alpha != 255) {
+            *popaque = FALSE;
+            break;
+        }
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixcmapIsBlackAndWhite()
+ *
+ *      Input:  cmap
+ *              &blackwhite (<return> TRUE if the cmap has only two colors:
+ *                           black (0,0,0) and white (255,255,255))
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixcmapIsBlackAndWhite(PIXCMAP  *cmap,
+                       l_int32  *pblackwhite)
+{
+l_int32     val0, val1, hascolor;
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixcmapIsBlackAndWhite");
+
+    if (!pblackwhite)
+        return ERROR_INT("&blackwhite not defined", procName, 1);
+    *pblackwhite = FALSE;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (pixcmapGetCount(cmap) != 2)
+        return 0;
+
+    pixcmapHasColor(cmap, &hascolor);
+    if (hascolor) return 0;
+
+    cta = (RGBA_QUAD *)cmap->array;
+    val0 = cta[0].red;
+    val1 = cta[1].red;
+    if ((val0 == 0 && val1 == 255) || (val0 == 255 && val1 == 0))
+        *pblackwhite = TRUE;
+    return 0;
+}
+
+
+/*!
+ *  pixcmapCountGrayColors()
+ *
+ *      Input:  cmap
+ *              &ngray (<return> number of gray colors)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This counts the unique gray colors, including black and white.
+ */
+l_int32
+pixcmapCountGrayColors(PIXCMAP  *cmap,
+                       l_int32  *pngray)
+{
+l_int32   n, i, rval, gval, bval, count;
+l_int32  *array;
+
+    PROCNAME("pixcmapCountGrayColors");
+
+    if (!pngray)
+        return ERROR_INT("&ngray not defined", procName, 1);
+    *pngray = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    array = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    n = pixcmapGetCount(cmap);
+    count = 0;
+    for (i = 0; i < n; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        if ((rval == gval) && (rval == bval) && (array[rval] == 0)) {
+            array[rval] = 1;
+            count++;
+        }
+    }
+
+    LEPT_FREE(array);
+    *pngray = count;
+    return 0;
+}
+
+
+/*!
+ *  pixcmapGetRankIntensity()
+ *
+ *      Input:  cmap
+ *              rankval (0.0 for darkest, 1.0 for lightest color)
+ *              &index (<return> the index into the colormap that
+ *                      corresponds to the rank intensity color)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixcmapGetRankIntensity(PIXCMAP    *cmap,
+                        l_float32   rankval,
+                        l_int32    *pindex)
+{
+l_int32  n, i, rval, gval, bval, rankindex;
+NUMA    *na, *nasort;
+
+    PROCNAME("pixcmapGetRankIntensity");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (rankval < 0.0 || rankval > 1.0)
+        return ERROR_INT("rankval not in [0.0 ... 1.0]", procName, 1);
+
+    n = pixcmapGetCount(cmap);
+    na = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        numaAddNumber(na, rval + gval + bval);
+    }
+    nasort = numaGetSortIndex(na, L_SORT_INCREASING);
+    rankindex = (l_int32)(rankval * (n - 1) + 0.5);
+    numaGetIValue(nasort, rankindex, pindex);
+
+    numaDestroy(&na);
+    numaDestroy(&nasort);
+    return 0;
+}
+
+
+/*!
+ *  pixcmapGetNearestIndex()
+ *
+ *      Input:  cmap
+ *              rval, gval, bval (colormap colors to search for; each number
+ *                                is in range [0, ... 255])
+ *              &index (<return> the index of the nearest color)
+ *      Return: 0 if OK, 1 on error (caller must check)
+ *
+ *  Notes:
+ *      (1) Returns the index of the exact color if possible, otherwise the
+ *          index of the color closest to the target color.
+ *      (2) Nearest color is that which is the least sum-of-squares distance
+ *          from the target color.
+ */
+l_int32
+pixcmapGetNearestIndex(PIXCMAP  *cmap,
+                       l_int32   rval,
+                       l_int32   gval,
+                       l_int32   bval,
+                       l_int32  *pindex)
+{
+l_int32     i, n, delta, dist, mindist;
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixcmapGetNearestIndex");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = UNDEF;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    if ((cta = (RGBA_QUAD *)cmap->array) == NULL)
+        return ERROR_INT("cta not defined(!)", procName, 1);
+    n = pixcmapGetCount(cmap);
+
+    mindist = 3 * 255 * 255 + 1;
+    for (i = 0; i < n; i++) {
+        delta = cta[i].red - rval;
+        dist = delta * delta;
+        delta = cta[i].green - gval;
+        dist += delta * delta;
+        delta = cta[i].blue - bval;
+        dist += delta * delta;
+        if (dist < mindist) {
+            *pindex = i;
+            if (dist == 0)
+                break;
+            mindist = dist;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixcmapGetNearestGrayIndex()
+ *
+ *      Input:  cmap
+ *              val (gray value to search for; in range [0, ... 255])
+ *              &index (<return> the index of the nearest color)
+ *      Return: 0 if OK, 1 on error (caller must check)
+ *
+ *  Notes:
+ *      (1) This should be used on gray colormaps.  It uses only the
+ *          green value of the colormap.
+ *      (2) Returns the index of the exact color if possible, otherwise the
+ *          index of the color closest to the target color.
+ */
+l_int32
+pixcmapGetNearestGrayIndex(PIXCMAP  *cmap,
+                           l_int32   val,
+                           l_int32  *pindex)
+{
+l_int32     i, n, dist, mindist;
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixcmapGetNearestGrayIndex");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (val < 0 || val > 255)
+        return ERROR_INT("val not in [0 ... 255]", procName, 1);
+
+    if ((cta = (RGBA_QUAD *)cmap->array) == NULL)
+        return ERROR_INT("cta not defined(!)", procName, 1);
+    n = pixcmapGetCount(cmap);
+
+    mindist = 256;
+    for (i = 0; i < n; i++) {
+        dist = cta[i].green - val;
+        dist = L_ABS(dist);
+        if (dist < mindist) {
+            *pindex = i;
+            if (dist == 0)
+                break;
+            mindist = dist;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixcmapGetComponentRange()
+ *
+ *      Input:  cmap
+ *              color (L_SELECT_RED, L_SELECT_GREEN or L_SELECT_BLUE)
+ *              &minval (<optional return> minimum value of component)
+ *              &maxval (<optional return> minimum value of component)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Returns for selected components the extreme value
+ *          (either min or max) of the color component that is
+ *          found in the colormap.
+ */
+l_int32
+pixcmapGetComponentRange(PIXCMAP  *cmap,
+                         l_int32   color,
+                         l_int32  *pminval,
+                         l_int32  *pmaxval)
+{
+    PROCNAME("pixcmapGetComponentRange");
+
+    if (pminval) *pminval = 0;
+    if (pmaxval) *pmaxval = 0;
+    if (!pminval && !pmaxval)
+        return ERROR_INT("no result requested", procName, 1);
+
+    if (color == L_SELECT_RED) {
+        pixcmapGetExtremeValue(cmap, L_SELECT_MIN, pminval, NULL, NULL);
+        pixcmapGetExtremeValue(cmap, L_SELECT_MAX, pmaxval, NULL, NULL);
+    } else if (color == L_SELECT_GREEN) {
+        pixcmapGetExtremeValue(cmap, L_SELECT_MIN, NULL, pminval, NULL);
+        pixcmapGetExtremeValue(cmap, L_SELECT_MAX, NULL, pmaxval, NULL);
+    } else if (color == L_SELECT_BLUE) {
+        pixcmapGetExtremeValue(cmap, L_SELECT_MIN, NULL, NULL, pminval);
+        pixcmapGetExtremeValue(cmap, L_SELECT_MAX, NULL, NULL, pmaxval);
+    } else {
+        return ERROR_INT("invalid color", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixcmapGetExtremeValue()
+ *
+ *      Input:  cmap
+ *              type (L_SELECT_MIN or L_SELECT_MAX)
+ *              &rval (<optional return> red component)
+ *              &gval (<optional return> green component)
+ *              &bval (<optional return> blue component)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Returns for selected components the extreme value
+ *          (either min or max) of the color component that is
+ *          found in the colormap.
+ */
+l_int32
+pixcmapGetExtremeValue(PIXCMAP  *cmap,
+                       l_int32   type,
+                       l_int32  *prval,
+                       l_int32  *pgval,
+                       l_int32  *pbval)
+{
+l_int32  i, n, rval, gval, bval, extrval, extgval, extbval;
+
+    PROCNAME("pixcmapGetExtremeValue");
+
+    if (!prval && !pgval && !pbval)
+        return ERROR_INT("no result requested for return", procName, 1);
+    if (prval) *prval = 0;
+    if (pgval) *pgval = 0;
+    if (pbval) *pbval = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (type != L_SELECT_MIN && type != L_SELECT_MAX)
+        return ERROR_INT("invalid type", procName, 1);
+
+    if (type == L_SELECT_MIN) {
+        extrval = 100000;
+        extgval = 100000;
+        extbval = 100000;
+    } else {
+        extrval = 0;
+        extgval = 0;
+        extbval = 0;
+    }
+
+    n = pixcmapGetCount(cmap);
+    for (i = 0; i < n; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        if ((type == L_SELECT_MIN && rval < extrval) ||
+            (type == L_SELECT_MAX && rval > extrval))
+            extrval = rval;
+        if ((type == L_SELECT_MIN && gval < extgval) ||
+            (type == L_SELECT_MAX && gval > extgval))
+            extgval = gval;
+        if ((type == L_SELECT_MIN && bval < extbval) ||
+            (type == L_SELECT_MAX && bval > extbval))
+            extbval = bval;
+    }
+    if (prval) *prval = extrval;
+    if (pgval) *pgval = extgval;
+    if (pbval) *pbval = extbval;
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *                       Colormap conversion                   *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixcmapGrayToColor()
+ *
+ *      Input:  color
+ *      Return: cmap, or null on error
+ *
+ *  Notes:
+ *      (1) This creates a colormap that maps from gray to
+ *          a specific color.  In the mapping, each component
+ *          is faded to white, depending on the gray value.
+ *      (2) In use, this is simply attached to a grayscale pix
+ *          to give it the input color.
+ */
+PIXCMAP *
+pixcmapGrayToColor(l_uint32  color)
+{
+l_int32   i, rval, gval, bval;
+PIXCMAP  *cmap;
+
+    extractRGBValues(color, &rval, &gval, &bval);
+    cmap = pixcmapCreate(8);
+    for (i = 0; i < 256; i++) {
+        pixcmapAddColor(cmap, rval + (i * (255 - rval)) / 255,
+                        gval + (i * (255 - gval)) / 255,
+                        bval + (i * (255 - bval)) / 255);
+    }
+
+    return cmap;
+}
+
+
+/*!
+ *  pixcmapColorToGray()
+ *
+ *      Input:  cmap
+ *              rwt, gwt, bwt  (non-negative; these should add to 1.0)
+ *      Return: cmap (gray), or null on error
+ *
+ *  Notes:
+ *      (1) This creates a gray colormap from an arbitrary colormap.
+ *      (2) In use, attach the output gray colormap to the pix
+ *          (or a copy of it) that provided the input colormap.
+ */
+PIXCMAP *
+pixcmapColorToGray(PIXCMAP   *cmaps,
+                   l_float32  rwt,
+                   l_float32  gwt,
+                   l_float32  bwt)
+{
+l_int32    i, n, rval, gval, bval, val;
+l_float32  sum;
+PIXCMAP   *cmapd;
+
+    PROCNAME("pixcmapColorToGray");
+
+    if (!cmaps)
+        return (PIXCMAP *)ERROR_PTR("cmaps not defined", procName, NULL);
+    if (rwt < 0.0 || gwt < 0.0 || bwt < 0.0)
+        return (PIXCMAP *)ERROR_PTR("weights not all >= 0.0", procName, NULL);
+
+        /* Make sure the sum of weights is 1.0; otherwise, you can get
+         * overflow in the gray value. */
+    sum = rwt + gwt + bwt;
+    if (sum == 0.0) {
+        L_WARNING("all weights zero; setting equal to 1/3\n", procName);
+        rwt = gwt = bwt = 0.33333;
+        sum = 1.0;
+    }
+    if (L_ABS(sum - 1.0) > 0.0001) {  /* maintain ratios with sum == 1.0 */
+        L_WARNING("weights don't sum to 1; maintaining ratios\n", procName);
+        rwt = rwt / sum;
+        gwt = gwt / sum;
+        bwt = bwt / sum;
+    }
+
+    cmapd = pixcmapCopy(cmaps);
+    n = pixcmapGetCount(cmapd);
+    for (i = 0; i < n; i++) {
+        pixcmapGetColor(cmapd, i, &rval, &gval, &bval);
+        val = (l_int32)(rwt * rval + gwt * gval + bwt * bval + 0.5);
+        pixcmapResetColor(cmapd, i, val, val, val);
+    }
+
+    return cmapd;
+}
+
+
+/*-------------------------------------------------------------*
+ *                         Colormap I/O                        *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixcmapRead()
+ *
+ *      Input:  filename
+ *      Return: cmap, or null on error
+ */
+PIXCMAP *
+pixcmapRead(const char  *filename)
+{
+FILE     *fp;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixcmapRead");
+
+    if (!filename)
+        return (PIXCMAP *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PIXCMAP *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((cmap = pixcmapReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (PIXCMAP *)ERROR_PTR("cmap not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return cmap;
+}
+
+
+/*!
+ *  pixcmapReadStream()
+ *
+ *      Input:  stream
+ *      Return: cmap, or null on error
+ */
+PIXCMAP *
+pixcmapReadStream(FILE  *fp)
+{
+l_int32   rval, gval, bval, aval, ignore;
+l_int32   i, index, ret, depth, ncolors;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixcmapReadStream");
+
+    if (!fp)
+        return (PIXCMAP *)ERROR_PTR("stream not defined", procName, NULL);
+
+    ret = fscanf(fp, "\nPixcmap: depth = %d bpp; %d colors\n",
+                 &depth, &ncolors);
+    if (ret != 2 ||
+        (depth != 1 && depth != 2 && depth != 4 && depth != 8) ||
+        (ncolors < 2 || ncolors > 256))
+        return (PIXCMAP *)ERROR_PTR("invalid cmap size", procName, NULL);
+    ignore = fscanf(fp, "Color    R-val    G-val    B-val   Alpha\n");
+    ignore = fscanf(fp, "----------------------------------------\n");
+
+    if ((cmap = pixcmapCreate(depth)) == NULL)
+        return (PIXCMAP *)ERROR_PTR("cmap not made", procName, NULL);
+    for (i = 0; i < ncolors; i++) {
+        if (fscanf(fp, "%3d       %3d      %3d      %3d      %3d\n",
+                        &index, &rval, &gval, &bval, &aval) != 5)
+            return (PIXCMAP *)ERROR_PTR("invalid entry", procName, NULL);
+        pixcmapAddRGBA(cmap, rval, gval, bval, aval);
+    }
+
+    return cmap;
+}
+
+
+/*!
+ *  pixcmapWrite()
+ *
+ *      Input:  filename
+ *              cmap
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixcmapWrite(const char  *filename,
+             PIXCMAP     *cmap)
+{
+FILE  *fp;
+
+    PROCNAME("pixcmapWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "w")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (pixcmapWriteStream(fp, cmap))
+        return ERROR_INT("cmap not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+
+/*!
+ *  pixcmapWriteStream()
+ *
+ *      Input:  stream, cmap
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixcmapWriteStream(FILE     *fp,
+                   PIXCMAP  *cmap)
+{
+l_int32  *rmap, *gmap, *bmap, *amap;
+l_int32   i;
+
+    PROCNAME("pixcmapWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    if (pixcmapToArrays(cmap, &rmap, &gmap, &bmap, &amap))
+        return ERROR_INT("colormap arrays not made", procName, 1);
+
+    fprintf(fp, "\nPixcmap: depth = %d bpp; %d colors\n", cmap->depth, cmap->n);
+    fprintf(fp, "Color    R-val    G-val    B-val   Alpha\n");
+    fprintf(fp, "----------------------------------------\n");
+    for (i = 0; i < cmap->n; i++)
+        fprintf(fp, "%3d       %3d      %3d      %3d      %3d\n",
+                i, rmap[i], gmap[i], bmap[i], amap[i]);
+    fprintf(fp, "\n");
+
+    LEPT_FREE(rmap);
+    LEPT_FREE(gmap);
+    LEPT_FREE(bmap);
+    LEPT_FREE(amap);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *               Extract colormap arrays and serialization              *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixcmapToArrays()
+ *
+ *      Input:  colormap
+ *              &rmap, &gmap, &bmap  (<return> colormap arrays)
+ *              &amap (<optional return> alpha array)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixcmapToArrays(PIXCMAP   *cmap,
+                l_int32  **prmap,
+                l_int32  **pgmap,
+                l_int32  **pbmap,
+                l_int32  **pamap)
+{
+l_int32    *rmap, *gmap, *bmap, *amap;
+l_int32     i, ncolors;
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixcmapToArrays");
+
+    if (!prmap || !pgmap || !pbmap)
+        return ERROR_INT("&rmap, &gmap, &bmap not all defined", procName, 1);
+    *prmap = *pgmap = *pbmap = NULL;
+    if (pamap) *pamap = NULL;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    ncolors = pixcmapGetCount(cmap);
+    if (((rmap = (l_int32 *)LEPT_CALLOC(ncolors, sizeof(l_int32))) == NULL) ||
+        ((gmap = (l_int32 *)LEPT_CALLOC(ncolors, sizeof(l_int32))) == NULL) ||
+        ((bmap = (l_int32 *)LEPT_CALLOC(ncolors, sizeof(l_int32))) == NULL))
+            return ERROR_INT("calloc fail for *map", procName, 1);
+    *prmap = rmap;
+    *pgmap = gmap;
+    *pbmap = bmap;
+    if (pamap) {
+        amap = (l_int32 *)LEPT_CALLOC(ncolors, sizeof(l_int32));
+        *pamap = amap;
+    }
+
+    cta = (RGBA_QUAD *)cmap->array;
+    for (i = 0; i < ncolors; i++) {
+        rmap[i] = cta[i].red;
+        gmap[i] = cta[i].green;
+        bmap[i] = cta[i].blue;
+        if (pamap)
+            amap[i] = cta[i].alpha;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixcmapToRGBTable()
+ *
+ *      Input:  colormap
+ *              &tab (<return> table of rgba values for the colormap)
+ *              &ncolors (<optional return> size of table)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixcmapToRGBTable(PIXCMAP    *cmap,
+                  l_uint32  **ptab,
+                  l_int32    *pncolors)
+{
+l_int32    i, ncolors, rval, gval, bval, aval;
+l_uint32  *tab;
+
+    PROCNAME("pixcmapToRGBTable");
+
+    if (!ptab)
+        return ERROR_INT("&tab not defined", procName, 1);
+    *ptab = NULL;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    ncolors = pixcmapGetCount(cmap);
+    if (pncolors)
+        *pncolors = ncolors;
+    if ((tab = (l_uint32 *)LEPT_CALLOC(ncolors, sizeof(l_uint32))) == NULL)
+        return ERROR_INT("tab not made", procName, 1);
+    *ptab = tab;
+
+    for (i = 0; i < ncolors; i++) {
+        pixcmapGetRGBA(cmap, i, &rval, &gval, &bval, &aval);
+        composeRGBAPixel(rval, gval, bval, aval, &tab[i]);
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixcmapSerializeToMemory()
+ *
+ *      Input:  colormap
+ *              cpc (components/color: 3 for rgb, 4 for rgba)
+ *              &ncolors (<return> number of colors in table)
+ *              &data (<return> binary string, cpc bytes per color)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) When serializing to store in a pdf, use @cpc = 3.
+ */
+l_int32
+pixcmapSerializeToMemory(PIXCMAP   *cmap,
+                         l_int32    cpc,
+                         l_int32   *pncolors,
+                         l_uint8  **pdata)
+{
+l_int32   i, ncolors, rval, gval, bval, aval;
+l_uint8  *data;
+
+    PROCNAME("pixcmapSerializeToMemory");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pncolors)
+        return ERROR_INT("&ncolors not defined", procName, 1);
+    *pncolors = 0;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (cpc != 3 && cpc != 4)
+        return ERROR_INT("cpc not 3 or 4", procName, 1);
+
+    ncolors = pixcmapGetCount(cmap);
+    *pncolors = ncolors;
+    if ((data = (l_uint8 *)LEPT_CALLOC(cpc * ncolors, sizeof(l_uint8))) == NULL)
+        return ERROR_INT("data not made", procName, 1);
+    *pdata = data;
+
+    for (i = 0; i < ncolors; i++) {
+        pixcmapGetRGBA(cmap, i, &rval, &gval, &bval, &aval);
+        data[cpc * i] = rval;
+        data[cpc * i + 1] = gval;
+        data[cpc * i + 2] = bval;
+        if (cpc == 4)
+            data[cpc * i + 3] = aval;
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixcmapDeserializeFromMemory()
+ *
+ *      Input:  data (binary string, 3 or 4 bytes per color)
+ *              cpc (components/color: 3 for rgb, 4 for rgba)
+ *              ncolors
+ *      Return: cmap, or null on error
+ */
+PIXCMAP *
+pixcmapDeserializeFromMemory(l_uint8  *data,
+                             l_int32   cpc,
+                             l_int32   ncolors)
+{
+l_int32   i, d, rval, gval, bval, aval;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixcmapDeserializeFromMemory");
+
+    if (!data)
+        return (PIXCMAP *)ERROR_PTR("data not defined", procName, NULL);
+    if (cpc != 3 && cpc != 4)
+        return (PIXCMAP *)ERROR_PTR("cpc not 3 or 4", procName, NULL);
+    if (ncolors == 0)
+        return (PIXCMAP *)ERROR_PTR("no entries", procName, NULL);
+    if (ncolors > 256)
+        return (PIXCMAP *)ERROR_PTR("ncolors > 256", procName, NULL);
+
+    if (ncolors > 16)
+        d = 8;
+    else if (ncolors > 4)
+        d = 4;
+    else if (ncolors > 2)
+        d = 2;
+    else
+        d = 1;
+    cmap = pixcmapCreate(d);
+    for (i = 0; i < ncolors; i++) {
+        rval = data[cpc * i];
+        gval = data[cpc * i + 1];
+        bval = data[cpc * i + 2];
+        if (cpc == 4)
+            aval = data[cpc * i + 3];
+        else
+            aval = 255;  /* opaque */
+        pixcmapAddRGBA(cmap, rval, gval, bval, aval);
+    }
+
+    return cmap;
+}
+
+
+/*!
+ *  pixcmapConvertToHex()
+ *
+ *      Input:  data  (binary serialized data)
+ *              ncolors (in colormap)
+ *      Return: hexdata (bracketed, space-separated ascii hex string),
+ *                       or null on error.
+ *
+ *  Notes:
+ *      (1) The number of bytes in @data is 3 * ncolors.
+ *      (2) Output is in form:
+ *             < r0g0b0 r1g1b1 ... rngnbn >
+ *          where r0, g0, b0 ... are each 2 bytes of hex ascii
+ *      (3) This is used in pdf files to express the colormap as an
+ *          array in ascii (human-readable) format.
+ */
+char *
+pixcmapConvertToHex(l_uint8 *data,
+                    l_int32  ncolors)
+{
+l_int32  i, j, hexbytes;
+char    *hexdata = NULL;
+char     buf[4];
+
+    PROCNAME("pixcmapConvertToHex");
+
+    if (!data)
+        return (char *)ERROR_PTR("data not defined", procName, NULL);
+    if (ncolors < 1)
+        return (char *)ERROR_PTR("no colors", procName, NULL);
+
+    hexbytes = 2 + (2 * 3 + 1) * ncolors + 2;
+    hexdata = (char *)LEPT_CALLOC(hexbytes, sizeof(char));
+    hexdata[0] = '<';
+    hexdata[1] = ' ';
+
+    for (i = 0; i < ncolors; i++) {
+        j = 2 + (2 * 3 + 1) * i;
+        snprintf(buf, sizeof(buf), "%02x", data[3 * i]);
+        hexdata[j] = buf[0];
+        hexdata[j + 1] = buf[1];
+        snprintf(buf, sizeof(buf), "%02x", data[3 * i + 1]);
+        hexdata[j + 2] = buf[0];
+        hexdata[j + 3] = buf[1];
+        snprintf(buf, sizeof(buf), "%02x", data[3 * i + 2]);
+        hexdata[j + 4] = buf[0];
+        hexdata[j + 5] = buf[1];
+        hexdata[j + 6] = ' ';
+    }
+    hexdata[j + 7] = '>';
+    hexdata[j + 8] = '\0';
+    return hexdata;
+}
+
+
+/*-------------------------------------------------------------*
+ *                     Colormap transforms                     *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixcmapGammaTRC()
+ *
+ *      Input:  colormap
+ *              gamma (gamma correction; must be > 0.0)
+ *              minval  (input value that gives 0 for output; can be < 0)
+ *              maxval  (input value that gives 255 for output; can be > 255)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place transform
+ *      (2) See pixGammaTRC() and numaGammaTRC() in enhance.c
+ *          for description and use of transform
+ */
+l_int32
+pixcmapGammaTRC(PIXCMAP   *cmap,
+                l_float32  gamma,
+                l_int32    minval,
+                l_int32    maxval)
+{
+l_int32  rval, gval, bval, trval, tgval, tbval, i, ncolors;
+NUMA    *nag;
+
+    PROCNAME("pixcmapGammaTRC");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (gamma <= 0.0) {
+        L_WARNING("gamma must be > 0.0; setting to 1.0\n", procName);
+        gamma = 1.0;
+    }
+    if (minval >= maxval)
+        return ERROR_INT("minval not < maxval", procName, 1);
+
+    if (gamma == 1.0 && minval == 0 && maxval == 255)  /* no-op */
+        return 0;
+
+    if ((nag = numaGammaTRC(gamma, minval, maxval)) == NULL)
+        return ERROR_INT("nag not made", procName, 1);
+
+    ncolors = pixcmapGetCount(cmap);
+    for (i = 0; i < ncolors; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        numaGetIValue(nag, rval, &trval);
+        numaGetIValue(nag, gval, &tgval);
+        numaGetIValue(nag, bval, &tbval);
+        pixcmapResetColor(cmap, i, trval, tgval, tbval);
+    }
+
+    numaDestroy(&nag);
+    return 0;
+}
+
+
+/*!
+ *  pixcmapContrastTRC()
+ *
+ *      Input:  colormap
+ *              factor (generally between 0.0 (no enhancement)
+ *                      and 1.0, but can be larger than 1.0)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place transform
+ *      (2) See pixContrastTRC() and numaContrastTRC() in enhance.c
+ *          for description and use of transform
+ */
+l_int32
+pixcmapContrastTRC(PIXCMAP   *cmap,
+                   l_float32  factor)
+{
+l_int32  i, ncolors, rval, gval, bval, trval, tgval, tbval;
+NUMA    *nac;
+
+    PROCNAME("pixcmapContrastTRC");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (factor < 0.0) {
+        L_WARNING("factor must be >= 0.0; setting to 0.0\n", procName);
+        factor = 0.0;
+    }
+
+    if ((nac = numaContrastTRC(factor)) == NULL)
+        return ERROR_INT("nac not made", procName, 1);
+
+    ncolors = pixcmapGetCount(cmap);
+    for (i = 0; i < ncolors; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        numaGetIValue(nac, rval, &trval);
+        numaGetIValue(nac, gval, &tgval);
+        numaGetIValue(nac, bval, &tbval);
+        pixcmapResetColor(cmap, i, trval, tgval, tbval);
+    }
+
+    numaDestroy(&nac);
+    return 0;
+}
+
+
+/*!
+ *  pixcmapShiftIntensity()
+ *
+ *      Input:  colormap
+ *              fraction (between -1.0 and +1.0)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place transform
+ *      (2) It does a proportional shift of the intensity for each color.
+ *      (3) If fraction < 0.0, it moves all colors towards (0,0,0).
+ *          This darkens the image.
+ *          If fraction > 0.0, it moves all colors towards (255,255,255)
+ *          This fades the image.
+ *      (4) The equivalent transform can be accomplished with pixcmapGammaTRC(),
+ *          but it is considerably more difficult (see numaGammaTRC()).
+ */
+l_int32
+pixcmapShiftIntensity(PIXCMAP   *cmap,
+                      l_float32  fraction)
+{
+l_int32  i, ncolors, rval, gval, bval;
+
+    PROCNAME("pixcmapShiftIntensity");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (fraction < -1.0 || fraction > 1.0)
+        return ERROR_INT("fraction not in [-1.0, 1.0]", procName, 1);
+
+    ncolors = pixcmapGetCount(cmap);
+    for (i = 0; i < ncolors; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        if (fraction < 0.0)
+            pixcmapResetColor(cmap, i,
+                              (l_int32)((1.0 + fraction) * rval),
+                              (l_int32)((1.0 + fraction) * gval),
+                              (l_int32)((1.0 + fraction) * bval));
+        else
+            pixcmapResetColor(cmap, i,
+                              rval + (l_int32)(fraction * (255 - rval)),
+                              gval + (l_int32)(fraction * (255 - gval)),
+                              bval + (l_int32)(fraction * (255 - bval)));
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixcmapShiftByComponent()
+ *
+ *      Input:  colormap
+ *              srcval (source color: 0xrrggbb00)
+ *              dstval (target color: 0xrrggbb00)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place transform
+ *      (2) It implements pixelShiftByComponent() for each color.
+ *          The mapping is specified by srcval and dstval.
+ *      (3) If a component decreases, the component in the colormap
+ *          decreases by the same ratio.  Likewise for increasing, except
+ *          all ratios are taken with respect to the distance from 255.
+ */
+l_int32
+pixcmapShiftByComponent(PIXCMAP  *cmap,
+                        l_uint32  srcval,
+                        l_uint32  dstval)
+{
+l_int32   i, ncolors, rval, gval, bval;
+l_uint32  newval;
+
+    PROCNAME("pixcmapShiftByComponent");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    ncolors = pixcmapGetCount(cmap);
+    for (i = 0; i < ncolors; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        pixelShiftByComponent(rval, gval, bval, srcval, dstval, &newval);
+        extractRGBValues(newval, &rval, &gval, &bval);
+        pixcmapResetColor(cmap, i, rval, gval, bval);
+    }
+
+    return 0;
+}
diff --git a/src/colormorph.c b/src/colormorph.c
new file mode 100644 (file)
index 0000000..70b11a4
--- /dev/null
@@ -0,0 +1,121 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  colormorph.c
+ *
+ *      Top-level color morphological operations
+ *
+ *            PIX     *pixColorMorph()
+ *
+ *      Method: Algorithm by van Herk and Gil and Werman, 1992
+ *              Apply grayscale morphological operations separately
+ *              to each component.
+ */
+
+#include "allheaders.h"
+
+
+/*-----------------------------------------------------------------*
+ *              Top-level color morphological operations           *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixColorMorph()
+ *
+ *      Input:  pixs
+ *              type  (L_MORPH_DILATE, L_MORPH_ERODE, L_MORPH_OPEN,
+ *                     or L_MORPH_CLOSE)
+ *              hsize  (of Sel; must be odd; origin implicitly in center)
+ *              vsize  (ditto)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This does the morph operation on each component separately,
+ *          and recombines the result.
+ *      (2) Sel is a brick with all elements being hits.
+ *      (3) If hsize = vsize = 1, just returns a copy.
+ */
+PIX *
+pixColorMorph(PIX     *pixs,
+              l_int32  type,
+              l_int32  hsize,
+              l_int32  vsize)
+{
+PIX  *pixr, *pixg, *pixb, *pixrm, *pixgm, *pixbm, *pixd;
+
+    PROCNAME("pixColorMorph");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (type != L_MORPH_DILATE && type != L_MORPH_ERODE &&
+        type != L_MORPH_OPEN && type != L_MORPH_CLOSE)
+        return (PIX *)ERROR_PTR("invalid morph type", procName, NULL);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize or vsize < 1", procName, NULL);
+    if ((hsize & 1) == 0 ) {
+        L_WARNING("horiz sel size must be odd; increasing by 1\n", procName);
+        hsize++;
+    }
+    if ((vsize & 1) == 0 ) {
+        L_WARNING("vert sel size must be odd; increasing by 1\n", procName);
+        vsize++;
+    }
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(NULL, pixs);
+
+    pixr = pixGetRGBComponent(pixs, COLOR_RED);
+    pixg = pixGetRGBComponent(pixs, COLOR_GREEN);
+    pixb = pixGetRGBComponent(pixs, COLOR_BLUE);
+    if (type == L_MORPH_DILATE) {
+        pixrm = pixDilateGray(pixr, hsize, vsize);
+        pixgm = pixDilateGray(pixg, hsize, vsize);
+        pixbm = pixDilateGray(pixb, hsize, vsize);
+    } else if (type == L_MORPH_ERODE) {
+        pixrm = pixErodeGray(pixr, hsize, vsize);
+        pixgm = pixErodeGray(pixg, hsize, vsize);
+        pixbm = pixErodeGray(pixb, hsize, vsize);
+    } else if (type == L_MORPH_OPEN) {
+        pixrm = pixOpenGray(pixr, hsize, vsize);
+        pixgm = pixOpenGray(pixg, hsize, vsize);
+        pixbm = pixOpenGray(pixb, hsize, vsize);
+    } else {   /* type == L_MORPH_CLOSE */
+        pixrm = pixCloseGray(pixr, hsize, vsize);
+        pixgm = pixCloseGray(pixg, hsize, vsize);
+        pixbm = pixCloseGray(pixb, hsize, vsize);
+    }
+    pixd = pixCreateRGBImage(pixrm, pixgm, pixbm);
+    pixDestroy(&pixr);
+    pixDestroy(&pixrm);
+    pixDestroy(&pixg);
+    pixDestroy(&pixgm);
+    pixDestroy(&pixb);
+    pixDestroy(&pixbm);
+
+    return pixd;
+}
diff --git a/src/colorquant1.c b/src/colorquant1.c
new file mode 100644 (file)
index 0000000..8e51b07
--- /dev/null
@@ -0,0 +1,4033 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  colorquant1.c
+ *
+ *  Octcube color quantization
+ *
+ *  There are several different octcube/octree based quantizations.
+ *  These can be classified, in the order in which they appear in this
+ *  file, as follows:
+ *
+ *  -----------------------------------------------------------------
+ *  (1) General adaptive octree
+ *  (2) Adaptive octree by population at fixed level
+ *  (3) Adaptive octree using population and with specified number
+ *      of output colors
+ *  (4) Octcube with colormap representation of mixed color/gray
+ *  (5) 256 fixed octcubes covering color space
+ *  (6) Octcubes at fixed level for ncolors <= 256
+ *  (7) Octcubes at fixed level with RGB output
+ *  (8) Quantizing an rgb image using a specified colormap
+ *  -----------------------------------------------------------------
+ *
+ *  (1) Two-pass adaptive octree color quantization
+ *          PIX              *pixOctreeColorQuant()
+ *          PIX              *pixOctreeColorQuantGeneral()
+ *
+ *        which calls
+ *          static CQCELL  ***octreeGenerateAndPrune()
+ *          static PIX       *pixOctreeQuantizePixels()
+ *
+ *        which calls
+ *          static l_int32    octreeFindColorCell()
+ *
+ *      Helper cqcell functions
+ *          static CQCELL  ***cqcellTreeCreate()
+ *          static void       cqcellTreeDestroy()
+ *
+ *      Helper index functions
+ *          l_int32           makeRGBToIndexTables()
+ *          void              getOctcubeIndexFromRGB()
+ *          static void       getRGBFromOctcube()
+ *          static l_int32    getOctcubeIndices()
+ *          static l_int32    octcubeGetCount()
+ *
+ *  (2) Adaptive octree quantization based on population at a fixed level
+ *          PIX              *pixOctreeQuantByPopulation()
+ *          static l_int32    pixDitherOctindexWithCmap()
+ *
+ *  (3) Adaptive octree quantization to 4 and 8 bpp with specified
+ *      number of output colors in colormap
+ *          PIX              *pixOctreeQuantNumColors()
+ *
+ *  (4) Mixed color/gray quantization with specified number of colors
+ *          PIX              *pixOctcubeQuantMixedWithGray()
+ *
+ *  (5) Fixed partition octcube quantization with 256 cells
+ *          PIX              *pixFixedOctcubeQuant256()
+ *
+ *  (6) Fixed partition quantization for images with few colors
+ *          PIX              *pixFewColorsOctcubeQuant1()
+ *          PIX              *pixFewColorsOctcubeQuant2()
+ *          PIX              *pixFewColorsOctcubeQuantMixed()
+ *
+ *  (7) Fixed partition octcube quantization at specified level
+ *      with quantized output to RGB
+ *          PIX              *pixFixedOctcubeQuantGenRGB()
+ *
+ *  (8) Color quantize RGB image using existing colormap
+ *          PIX              *pixQuantFromCmap()  [high-level wrapper]
+ *          PIX              *pixOctcubeQuantFromCmap()
+ *          static PIX       *pixOctcubeQuantFromCmapLUT()
+ *
+ *      Generation of octcube histogram
+ *          NUMA             *pixOctcubeHistogram()
+ *
+ *      Get filled octcube table from colormap
+ *          l_int32          *pixcmapToOctcubeLUT()
+ *
+ *      Strip out unused elements in colormap
+ *          l_int32           pixRemoveUnusedColors()
+ *
+ *      Find number of occupied octcubes at the specified level
+ *          l_int32           pixNumberOccupiedOctcubes()
+ *
+ *  Note: leptonica also provides color quantization using a modified
+ *        form of median cut.  See colorquant2.c for details.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+
+/*  This data structure is used for pixOctreeColorQuant(),
+ *  a color octree that adjusts to the color distribution
+ *  in the image that is being quantized.  The best settings
+ *  are with CQ_NLEVELS = 6 and DITHERING set on.
+ *
+ *  Notes:  (1) the CTE (color table entry) index is sequentially
+ *              assigned as the tree is pruned back
+ *          (2) if 'bleaf' == 1, all pixels in that cube have been
+ *              assigned to one or more CTEs.  But note that if
+ *              all 8 subcubes have 'bleaf' == 1, it will have no
+ *              pixels left for assignment and will not be a CTE.
+ *          (3) 'nleaves', the number of leaves contained at the next
+ *              lower level is some number between 0 and 8, inclusive.
+ *              If it is zero, it means that all colors within this cube
+ *              are part of a single growing cluster that has not yet
+ *              been set aside as a leaf.  If 'nleaves' > 0, 'bleaf'
+ *              will be set to 1 and all pixels not assigned to leaves
+ *              at lower levels will be assigned to a CTE here.
+ *              (However, as described above, if all pixels are already
+ *              assigned, we set 'bleaf' = 1 but do not create a CTE
+ *              at this level.)
+ *          (4) To keep the maximum color error to a minimum, we
+ *              prune the tree back to level 2, and require that
+ *              all 64 level 2 cells are CTEs.
+ *          (5) We reserve an extra set of colors to prevent running out
+ *              of colors during the assignment of the final 64 level 2 cells.
+ *              This is more likely to happen with small images.
+ *          (6) When we run out of colors, the dithered image can be very
+ *              poor, so we additionally prevent dithering if the image
+ *              is small.
+ *          (7) The color content of the image is measured, and if there
+ *              is very little color, it is quantized in grayscale.
+ */
+struct ColorQuantCell
+{
+    l_int32     rc, gc, bc;   /* center values                              */
+    l_int32     n;            /* number of samples in this cell             */
+    l_int32     index;        /* CTE (color table entry) index              */
+    l_int32     nleaves;      /* # of leaves contained at next lower level  */
+    l_int32     bleaf;        /* boolean: 0 if not a leaf, 1 if so          */
+};
+typedef struct ColorQuantCell    CQCELL;
+
+    /* Constants for pixOctreeColorQuant() */
+static const l_int32  CQ_NLEVELS = 5;   /* only 4, 5 and 6 are allowed */
+static const l_int32  CQ_RESERVED_COLORS = 64;  /* to allow for level 2 */
+                                                /* remainder CTEs */
+static const l_int32  EXTRA_RESERVED_COLORS = 25;  /* to avoid running out */
+static const l_int32  TREE_GEN_WIDTH = 350;  /* big enough for good stats */
+static const l_int32  MIN_DITHER_SIZE = 250;  /* don't dither if smaller */
+
+
+/*  This data structure is used for pixOctreeQuantNumColors(),
+ *  a color octree that adjusts in a simple way to the to the color
+ *  distribution in the image that is being quantized.  It outputs
+ *  colormapped images, either 4 bpp or 8 bpp, depending on the
+ *  max number of colors and the compression desired.
+ *
+ *  The number of samples is saved as a float in the first location,
+ *  because this is required to use it as the key that orders the
+ *  cells in the priority queue.  */
+struct OctcubeQuantCell
+{
+    l_float32  n;                  /* number of samples in this cell       */
+    l_int32    octindex;           /* octcube index                        */
+    l_int32    rcum, gcum, bcum;   /* cumulative values                    */
+    l_int32    rval, gval, bval;   /* average values                       */
+};
+typedef struct OctcubeQuantCell    OQCELL;
+
+
+    /* This data structure is using for heap sorting octcubes
+     * by population.  Sort order is decreasing.  */
+struct L_OctcubePop
+{
+    l_float32        npix;    /* parameter on which to sort  */
+    l_int32          index;   /* octcube index at assigned level */
+    l_int32          rval;    /* mean red value of pixels in octcube */
+    l_int32          gval;    /* mean green value of pixels in octcube */
+    l_int32          bval;    /* mean blue value of pixels in octcube */
+};
+typedef struct L_OctcubePop  L_OCTCUBE_POP;
+
+    /* In pixDitherOctindexWithCmap(), we use these default values.
+     * To get the max value of 'dif' in the dithering color transfer,
+     * divide these "DIF_CAP" values by 8.  However, a value of
+     * 0 means that there is no cap (infinite cap).  A very small
+     * value is used for POP_DIF_CAP because dithering on the population
+     * generated colormap can be unstable without a tight cap.   */
+static const l_int32  FIXED_DIF_CAP = 0;
+static const l_int32  POP_DIF_CAP = 40;
+
+
+    /* Static octree helper function */
+static l_int32 octreeFindColorCell(l_int32 octindex, CQCELL ***cqcaa,
+                                   l_int32 *pindex, l_int32 *prval,
+                                   l_int32 *pgval, l_int32 *pbval);
+
+    /* Static cqcell functions */
+static CQCELL ***octreeGenerateAndPrune(PIX *pixs, l_int32 colors,
+                                        l_int32 reservedcolors,
+                                        PIXCMAP **pcmap);
+static PIX *pixOctreeQuantizePixels(PIX *pixs, CQCELL ***cqcaa,
+                                    l_int32 ditherflag);
+static CQCELL ***cqcellTreeCreate(void);
+static void cqcellTreeDestroy(CQCELL ****pcqcaa);
+
+    /* Static helper octcube index functions */
+static void getRGBFromOctcube(l_int32 cubeindex, l_int32 level,
+                              l_int32 *prval, l_int32 *pgval, l_int32 *pbval);
+static l_int32 getOctcubeIndices(l_int32 rgbindex, l_int32 level,
+                                 l_int32 *pbindex, l_int32 *psindex);
+static l_int32 octcubeGetCount(l_int32 level, l_int32 *psize);
+
+    /* Static function to perform octcube-indexed dithering */
+static l_int32 pixDitherOctindexWithCmap(PIX *pixs, PIX *pixd, l_uint32 *rtab,
+                                         l_uint32 *gtab, l_uint32 *btab,
+                                         l_int32 *carray, l_int32 difcap);
+
+    /* Static function to perform octcube-based quantizing from colormap */
+static PIX *pixOctcubeQuantFromCmapLUT(PIX *pixs, PIXCMAP *cmap,
+                                       l_int32 mindepth, l_int32 *cmaptab,
+                                       l_uint32 *rtab, l_uint32 *gtab,
+                                       l_uint32 *btab);
+
+#ifndef   NO_CONSOLE_IO
+#define   DEBUG_COLORQUANT      0
+#define   DEBUG_OCTINDEX        0
+#define   DEBUG_OCTCUBE_CMAP    0
+#define   DEBUG_POP             0
+#define   DEBUG_FEW_COLORS      0
+#define   PRINT_OCTCUBE_STATS   0
+#endif   /* ~NO_CONSOLE_IO */
+
+
+/*-------------------------------------------------------------------------*
+ *                Two-pass adaptive octree color quantization              *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  pixOctreeColorQuant()
+ *
+ *      Input:  pixs  (32 bpp; 24-bit color)
+ *              colors  (in colormap; some number in range [128 ... 256];
+ *                      the actual number of colors used will be smaller)
+ *              ditherflag  (1 to dither, 0 otherwise)
+ *      Return: pixd (8 bpp with colormap), or null on error
+ *
+ *  I found one description in the literature of octree color
+ *  quantization, using progressive truncation of the octree,
+ *  by M. Gervautz and W. Purgathofer in Graphics Gems, pp.
+ *  287-293, ed. A. Glassner, Academic Press, 1990.
+ *  Rather than setting up a fixed partitioning of the color
+ *  space ab initio, as we do here, they allow the octree to be
+ *  progressively truncated as new pixels are added.  They
+ *  need to set up some data structures that are traversed
+ *  with the addition of each 24 bit pixel, in order to decide
+ *  either (1) in which cluster (sub-branch of the octree) to put
+ *  the pixel, or (2) whether to truncate the octree further
+ *  to place the pixel in an existing cluster, or (3) which
+ *  two existing clusters should be merged so that the pixel
+ *  can be left to start a truncated leaf of the octree.  Such dynamic
+ *  truncation is considerably more complicated, and Gervautz et
+ *  al. did not explain how they did it in anywhere near the
+ *  detail required to check their implementation.
+ *
+ *  The simple method in pixFixedOctcubeQuant256() is very
+ *  fast, and with dithering the results are good, but you
+ *  can do better if the color clusters are selected adaptively
+ *  from the image.  We want a method that makes much better
+ *  use of color samples in regions of color space with high
+ *  pixel density, while also fairly representing small numbers
+ *  of color pixels in low density regions.  Such adaptation
+ *  requires two passes through the image: the first for generating
+ *  the pruned tree of color cubes and the second for computing the index
+ *  into the color table for each pixel.
+ *
+ *  A relatively simple adaptive method is pixOctreeQuantByPopulation().
+ *  That function first determines if the image has very few colors,
+ *  and, if so, quantizes to those colors.  If there are more than
+ *  256 colors, it generates a histogram of octcube leaf occupancy
+ *  at level 4, chooses the 192 most populated such leaves as
+ *  the first 192 colors, and sets the remaining 64 colors to the
+ *  residual average pixel values in each of the 64 level 2 octcubes.
+ *  This is a bit faster than pixOctreeColorQuant(), and does very
+ *  well without dithering, but for most images with dithering it
+ *  is clearly inferior.
+ *
+ *  We now describe pixOctreeColorQuant().  The first pass is done
+ *  on a subsampled image, because we do not need to use all the
+ *  pixels in the image to generate the tree.  Subsampling
+ *  down to 0.25 (1/16 of the pixels) makes the program run
+ *  about 1.3 times faster.
+ *
+ *  Instead of dividing the color space into 256 equal-sized
+ *  regions, we initially divide it into 2^12 or 2^15 or 2^18
+ *  equal-sized octcubes.  Suppose we choose to use 2^18 octcubes.
+ *  This gives us 6 octree levels.  We then prune back,
+ *  starting from level 6.  For every cube at level 6, there
+ *  are 8 cubes at level 5.  Call the operation of putting a
+ *  cube aside as a color table entry (CTE) a "saving."
+ *  We use a (in general) level-dependent threshold, and save
+ *  those level 6 cubes that are above threshold.
+ *  The rest are combined into the containing level 5 cube.
+ *  If between 1 and 7 level 6 cubes within a level 5
+ *  cube have been saved by thresholding, then the remaining
+ *  level 6 cubes in that level 5 cube are automatically
+ *  saved as well, without applying a threshold.  This greatly
+ *  simplifies both the description of the CTEs and the later
+ *  classification of each pixel as belonging to a CTE.
+ *  This procedure is iterated through every cube, starting at
+ *  level 5, and then 4, 3, and 2, successively.  The result is that
+ *  each CTE contains the entirety of a set of from 1 to 7 cubes
+ *  from a given level that all belong to a single cube at the
+ *  level above.   We classify the CTEs in terms of the
+ *  condition in which they are made as either being "threshold"
+ *  or "residual."  They are "threshold" CTEs if no subcubes
+ *  are CTEs (that is, they contain every pixel within the cube)
+ *  and the number of pixels exceeds the threshold for making
+ *  a CTE.  They are "residual" CTEs if at least one but not more
+ *  than 7 of the subcubes have already been determined to be CTEs;
+ *  this happens automatically -- no threshold is applied.
+ *  If all 8 subcubes are determined to be CTEs, the cube is
+ *  marked as having all pixels accounted for ('bleaf' = 1) but
+ *  is not saved as a CTE.
+ *
+ *  We stop the pruning at level 2, at which there are 64
+ *  sub-cubes.  Any pixels not already claimed in a CTE are
+ *  put in these cubes.
+ *
+ *  As the cubes are saved as color samples in the color table,
+ *  the number of remaining pixels P and the number of
+ *  remaining colors in the color table N are recomputed,
+ *  along with the average number of pixels P/N (ppc) to go in
+ *  each of the remaining colors.  This running average number is
+ *  used to set the threshold at the current level.
+ *
+ *  Because we are going to very small cubes at levels 6 or 5,
+ *  and will dither the colors for errors, it is not necessary
+ *  to compute the color center of each cluster; we can simply
+ *  use the center of the cube.  This gives us a minimax error
+ *  condition: the maximum error is half the width of the
+ *  level 2 cubes -- 32 color values out of 256 -- for each color
+ *  sample.  In practice, most of the pixels will be very much
+ *  closer to the center of their cells.  And with dithering,
+ *  the average pixel color in a small region will be closer still.
+ *  Thus with the octree quantizer, we are able to capture
+ *  regions of high color pdf (probability density function) in small
+ *  but accurate CTEs, and to have only a small number of pixels
+ *  that end up a significant distance (with a guaranteed maximum)
+ *  from their true color.
+ *
+ *  How should the threshold factor vary?  Threshold factors
+ *  are required for levels 2, 3, 4 and 5 in the pruning stage.
+ *  The threshold for level 5 is actually applied to cubes at
+ *  level 6, etc.  From various experiments, it appears that
+ *  the results do not vary appreciably for threshold values near 1.0.
+ *  If you want more colors in smaller cubes, the threshold
+ *  factors can be set lower than 1.0 for cubes at levels 4 and 5.
+ *  However, if the factor is set much lower than 1.0 for
+ *  levels 2 and 3, we can easily run out of colors.
+ *  We put aside 64 colors in the calculation of the threshold
+ *  values, because we must have 64 color centers at level 2,
+ *  that will have very few pixels in most of them.
+ *  If we reduce the factor for level 5 to 0.4, this will
+ *  generate many level 6 CTEs, and consequently
+ *  many residual cells will be formed up from those leaves,
+ *  resulting in the possibility of running out of colors.
+ *  Remember, the residual CTEs are mandatory, and are formed
+ *  without using the threshold, regardless of the number of
+ *  pixels that are absorbed.
+ *
+ *  The implementation logically has four parts:
+ *
+ *       (1) accumulation into small, fixed cells
+ *       (2) pruning back into selected CTE cubes
+ *       (3) organizing the CTEs for fast search to find
+ *           the CTE to which any image pixel belongs
+ *       (4) doing a second scan to code the image pixels by CTE
+ *
+ *  Step (1) is straightforward; we use 2^15 cells.
+ *
+ *  We've already discussed how the pruning step (2) will be performed.
+ *
+ *  Steps (3) and (4) are related, in that the organization
+ *  used by step (3) determines how the search actually
+ *  takes place for each pixel in step (4).
+ *
+ *  There are many ways to do step (3).  Let's explore a few.
+ *
+ *  (a) The simplest is to order the cubes from highest occupancy
+ *      to lowest, and traverse the list looking for the deepest
+ *      match.  To make this more efficient, so that we know when
+ *      to stop looking, any cube that has separate CTE subcubes
+ *      would be marked as such, so that we know when we hit a
+ *      true leaf.
+ *
+ *  (b) Alternatively, we can order the cubes by highest
+ *      occupancy separately each level, and work upward,
+ *      starting at level 5, so that when we find a match we
+ *      know that it will be correct.
+ *
+ *  (c) Another approach would be to order the cubes by
+ *      "address" and use a hash table to find the cube
+ *      corresponding to a pixel color.  I don't know how to
+ *      do this with a variable length address, as each CTE
+ *      will have 3*n bits, where n is the level.
+ *
+ *  (d) Another approach entirely is to put the CTE cubes into
+ *      a tree, in such a way that starting from the root, and
+ *      using 3 bits of address at a time, the correct branch of
+ *      each octree can be taken until a leaf is found.  Because
+ *      a given cube can be both a leaf and also have branches
+ *      going to sub-cubes, the search stops only when no
+ *      marked subcubes have addresses that match the given pixel.
+ *
+ *      In the tree method, we can start with a dense infrastructure,
+ *      and place the leaves corresponding to the N colors
+ *      in the tree, or we can grow from the root only those
+ *      branches that end directly on leaves.
+ *
+ *  What we do here is to take approach (d), and implement the tree
+ *  "virtually", as a set of arrays, one array for each level
+ *  of the tree.   Initially we start at level 5, an array with
+ *  2^15 cubes, each with 8 subcubes.  We then build nodes at
+ *  levels closer to the root; at level 4 there are 2^12 nodes
+ *  each with 8 subcubes; etc.  Using these arrays has
+ *  several advantages:
+ *
+ *     -  We don't need to keep track of links between cubes
+ *        and subcubes, because we can use the canonical
+ *        addressing on the cell arrays directly to determine
+ *        which nodes are parent cubes and which are sub-cubes.
+ *
+ *     -  We can prune directly on this tree
+ *
+ *     -  We can navigate the pruned tree quickly to classify
+ *        each pixel in the image.
+ *
+ *  Canonical addressing guarantees that the i-th node at level k
+ *  has 8 subnodes given by the 8*i ... 8*i+7 nodes at level k+1.
+ *
+ *  The pruning step works as follows.  We go from the lowest
+ *  level up.  At each level, the threshold is found from the
+ *  product of a factor near 1.0 and the ratio of unmarked pixels
+ *  to remaining colors (minus the 64).  We march through
+ *  the space, sequentially considering a cube and its 8 subcubes.
+ *  We first check those subcubes that are not already
+ *  marked as CTE to see if any are above threshold, and if so,
+ *  generate a CTE and mark them as such.
+ *  We then determine if any of the subcubes have been marked.
+ *  If so, and there are subcubes that are not marked,
+ *  we generate a CTE for the cube from the remaining unmarked
+ *  subcubes; this is mandatory and does not depend on how many
+ *  pixels are in the set of subcubes.  If none of the subcubes
+ *  are marked, we aggregate their pixels into the cube
+ *  containing them, but do not mark it as a CTE; that
+ *  will be determined when iterating through the next level up.
+ *
+ *  When all the pixels in a cube are accounted for in one or more
+ *  colors, we set the boolean 'bleaf' to true.  This is the
+ *  flag used to mark the cubes in the pruning step.  If a cube
+ *  is marked, and all 8 subcubes are marked, then it is not
+ *  itself given a CTE because all pixels have already been
+ *  accounted for.
+ *
+ *  Note that the pruning of the tree and labelling of the CTEs
+ *  (step 2) accomplishes step 3 implicitly, because the marked
+ *  and pruned tree is ready for use in labelling each pixel
+ *  in step 4.  We now, for every pixel in the image, traverse
+ *  the tree from the root, looking for the lowest cube that is a leaf.
+ *  At each level we have a cube and subcube.  If we reach a subcube
+ *  leaf that is marked 0, we know that the color is stored in the
+ *  cube above, and we've found the CTE.  Otherwise, the subcube
+ *  leaf is marked 1.  If we're at the last level, we've reached
+ *  the final leaf and must use it.  Otherwise, continue the
+ *  process at the next level down.
+ *
+ *  For robustness, efficiency and high quality output, we do the following:
+ *
+ *  (1) Measure the color content of the image.  If there is very little
+ *      color, quantize in grayscale.
+ *  (2) For efficiency, build the octree with a subsampled image if the
+ *      image is larger than some threshold size.
+ *  (3) Reserve an extra set of colors to prevent running out of colors
+ *      when pruning the octree; specifically, during the assignment
+ *      of those level 2 cells (out of the 64) that have unassigned
+ *      pixels.  The problem of running out is more likely to happen
+ *      with small images, because the estimation we use for the
+ *      number of pixels available is not accurate.
+ *  (4) In the unlikely event that we run out of colors, the dithered
+ *      image can be very poor.  As this would only happen with very
+ *      small images, and dithering is not particularly noticeable with
+ *      such images, turn it off.
+ */
+PIX *
+pixOctreeColorQuant(PIX     *pixs,
+                    l_int32  colors,
+                    l_int32  ditherflag)
+{
+    PROCNAME("pixOctreeColorQuant");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (colors < 128 || colors > 240)  /* further restricted */
+        return (PIX *)ERROR_PTR("colors must be in [128, 240]", procName, NULL);
+
+    return pixOctreeColorQuantGeneral(pixs, colors, ditherflag, 0.01, 0.01);
+}
+
+
+/*!
+ *  pixOctreeColorQuantGeneral()
+ *
+ *      Input:  pixs  (32 bpp; 24-bit color)
+ *              colors  (in colormap; some number in range [128 ... 240];
+ *                      the actual number of colors used will be smaller)
+ *              ditherflag  (1 to dither, 0 otherwise)
+ *              validthresh (minimum fraction of pixels neither near white
+ *                           nor black, required for color quantization;
+ *                           typically ~0.01, but smaller for images that have
+ *                           color but are nearly all white)
+ *              colorthresh (minimum fraction of pixels with color that are
+ *                           not near white or black, that are required
+ *                           for color quantization; typ. ~0.01, but smaller
+ *                           for images that have color along with a
+ *                           significant fraction of gray)
+ *      Return: pixd (8 bit with colormap), or null on error
+ *
+ *  Notes:
+ *      (1) The parameters @validthresh and @colorthresh are used to
+ *          determine if color quantization should be used on an image,
+ *          or whether, instead, it should be quantized in grayscale.
+ *          If the image has very few non-white and non-black pixels, or
+ *          if those pixels that are non-white and non-black are all
+ *          very close to either white or black, it is usually better
+ *          to treat the color as accidental and to quantize the image
+ *          to gray only.  These parameters are useful if you know
+ *          something a priori about the image.  Perhaps you know that
+ *          there is only a very small fraction of color pixels, but they're
+ *          important to preserve; then you want to use a smaller value for
+ *          these parameters.  To disable conversion to gray and force
+ *          color quantization, use @validthresh = 0.0 and @colorthresh = 0.0.
+ *      (2) See pixOctreeColorQuant() for algorithmic and implementation
+ *          details.  This function has a more general interface.
+ *      (3) See pixColorFraction() for computing the fraction of pixels
+ *          that are neither white nor black, and the fraction of those
+ *          pixels that have little color.  From the documentation there:
+ *             If pixfract is very small, there are few pixels that are
+ *             neither black nor white.  If colorfract is very small,
+ *             the pixels that are neither black nor white have very
+ *             little color content.  The product 'pixfract * colorfract'
+ *             gives the fraction of pixels with significant color content.
+ *          We test against the product @validthresh * @colorthresh
+ *          to find color in images that have either very few
+ *          intermediate gray pixels or that have many such gray pixels.
+ */
+PIX *
+pixOctreeColorQuantGeneral(PIX       *pixs,
+                           l_int32    colors,
+                           l_int32    ditherflag,
+                           l_float32  validthresh,
+                           l_float32  colorthresh)
+{
+l_int32    w, h, minside, factor, index, rval, gval, bval;
+l_float32  scalefactor;
+l_float32  pixfract;  /* fraction neither near white nor black */
+l_float32  colorfract;  /* fraction with color of the pixfract population */
+CQCELL  ***cqcaa;
+PIX       *pixd, *pixsub;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixOctreeColorQuantGeneral");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (colors < 128 || colors > 240)
+        return (PIX *)ERROR_PTR("colors must be in [128, 240]", procName, NULL);
+
+        /* Determine if the image has sufficient color content for
+         *   octree quantization, based on the input thresholds.
+         * If pixfract << 1, most pixels are close to black or white.
+         * If colorfract << 1, the pixels that are not near
+         *   black or white have very little color.
+         * If with insufficient color, quantize with a grayscale colormap. */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (validthresh > 0.0 && colorthresh > 0.0) {
+        minside = L_MIN(w, h);
+        factor = L_MAX(1, minside / 400);
+        pixColorFraction(pixs, 20, 244, 20, factor, &pixfract, &colorfract);
+        if (pixfract * colorfract < validthresh * colorthresh) {
+            L_INFO("\n  Pixel fraction neither white nor black = %6.3f"
+                   "\n  Color fraction of those pixels = %6.3f"
+                   "\n  Quantizing to 8 bpp gray\n",
+                   procName, pixfract, colorfract);
+            return pixConvertTo8(pixs, 1);
+        }
+    } else {
+        L_INFO("\n  Process in color by default\n", procName);
+    }
+
+        /* Conditionally subsample to speed up the first pass */
+    if (w > TREE_GEN_WIDTH) {
+        scalefactor = (l_float32)TREE_GEN_WIDTH / (l_float32)w;
+        pixsub = pixScaleBySampling(pixs, scalefactor, scalefactor);
+    } else {
+        pixsub = pixClone(pixs);
+    }
+
+        /* Drop the number of requested colors if image is very small */
+    if (w < MIN_DITHER_SIZE && h < MIN_DITHER_SIZE)
+        colors = L_MIN(colors, 220);
+
+        /* Make the pruned octree */
+    cqcaa = octreeGenerateAndPrune(pixsub, colors, CQ_RESERVED_COLORS, &cmap);
+    if (!cqcaa)
+        return (PIX *)ERROR_PTR("tree not made", procName, NULL);
+#if DEBUG_COLORQUANT
+    L_INFO(" Colors requested = %d\n", procName, colors);
+    L_INFO(" Actual colors = %d\n", procName, cmap->n);
+#endif  /* DEBUG_COLORQUANT */
+
+        /* Do not dither if image is very small */
+    if (w < MIN_DITHER_SIZE && h < MIN_DITHER_SIZE && ditherflag == 1) {
+        L_INFO("Small image: dithering turned off\n", procName);
+        ditherflag = 0;
+    }
+
+        /* Traverse tree from root, looking for lowest cube
+         * that is a leaf, and set dest pix value to its
+         * colortable index */
+    if ((pixd = pixOctreeQuantizePixels(pixs, cqcaa, ditherflag)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+        /* Attach colormap and copy res */
+    pixSetColormap(pixd, cmap);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+        /* Force darkest color to black if each component <= 4 */
+    pixcmapGetRankIntensity(cmap, 0.0, &index);
+    pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+    if (rval < 5 && gval < 5 && bval < 5)
+        pixcmapResetColor(cmap, index, 0, 0, 0);
+
+        /* Force lightest color to white if each component >= 252 */
+    pixcmapGetRankIntensity(cmap, 1.0, &index);
+    pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+    if (rval > 251 && gval > 251 && bval > 251)
+        pixcmapResetColor(cmap, index, 255, 255, 255);
+
+    cqcellTreeDestroy(&cqcaa);
+    pixDestroy(&pixsub);
+    return pixd;
+}
+
+
+/*!
+ *  octreeGenerateAndPrune()
+ *
+ *      Input:  pixs
+ *              number of colors to use (between 128 and 256)
+ *              number of reserved colors
+ *              &cmap  (made and returned)
+ *      Return: octree, colormap and number of colors used, or null
+ *              on error
+ *
+ *  Notes:
+ *      (1) The number of colors in the cmap may differ from the number
+ *          of colors requested, but it will not be larger than 256
+ */
+static CQCELL ***
+octreeGenerateAndPrune(PIX       *pixs,
+                       l_int32    colors,
+                       l_int32    reservedcolors,
+                       PIXCMAP  **pcmap)
+{
+l_int32    rval, gval, bval, cindex;
+l_int32    level, ncells, octindex;
+l_int32    w, h, wpls;
+l_int32    i, j, isub;
+l_int32    npix;  /* number of remaining pixels to be assigned */
+l_int32    ncolor; /* number of remaining color cells to be used */
+l_int32    ppc;  /* ave number of pixels left for each color cell */
+l_int32    rv, gv, bv;
+l_float32  thresholdFactor[] = {0.01, 0.01, 1.0, 1.0, 1.0, 1.0};
+l_float32  thresh;  /* factor of ppc for this level */
+l_uint32  *datas, *lines;
+l_uint32  *rtab, *gtab, *btab;
+CQCELL  ***cqcaa;   /* one array for each octree level */
+CQCELL   **cqca, **cqcasub;
+CQCELL    *cqc, *cqcsub;
+PIXCMAP   *cmap;
+NUMA      *nat;  /* accumulates levels for threshold cells */
+NUMA      *nar;  /* accumulates levels for residual cells */
+
+    PROCNAME("octreeGenerateAndPrune");
+
+    if (!pixs)
+        return (CQCELL ***)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (CQCELL ***)ERROR_PTR("pixs must be 32 bpp", procName, NULL);
+    if (colors < 128 || colors > 256)
+        return (CQCELL ***)ERROR_PTR("colors not in [128,256]", procName, NULL);
+    if (!pcmap)
+        return (CQCELL ***)ERROR_PTR("&cmap not defined", procName, NULL);
+
+        /* Make the canonical index tables */
+    if (makeRGBToIndexTables(&rtab, &gtab, &btab, CQ_NLEVELS))
+        return (CQCELL ***)ERROR_PTR("tables not made", procName, NULL);
+
+    if ((cqcaa = cqcellTreeCreate()) == NULL)
+        return (CQCELL ***)ERROR_PTR("cqcaa not made", procName, NULL);
+
+        /* Generate an 8 bpp cmap (max size 256) */
+    cmap = pixcmapCreate(8);
+    *pcmap = cmap;
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    npix = w * h;  /* initialize to all pixels */
+    ncolor = colors - reservedcolors - EXTRA_RESERVED_COLORS;
+    ppc = npix / ncolor;
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+
+        /* Accumulate the centers of each cluster at level CQ_NLEVELS */
+    ncells = 1 << (3 * CQ_NLEVELS);
+    cqca = cqcaa[CQ_NLEVELS];
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            octindex = rtab[rval] | gtab[gval] | btab[bval];
+            cqc = cqca[octindex];
+            cqc->n++;
+        }
+    }
+
+        /* Arrays for storing statistics */
+    if ((nat = numaCreate(0)) == NULL)
+        return (CQCELL ***)ERROR_PTR("nat not made", procName, NULL);
+    if ((nar = numaCreate(0)) == NULL)
+        return (CQCELL ***)ERROR_PTR("nar not made", procName, NULL);
+
+        /* Prune back from the lowest level and generate the colormap */
+    for (level = CQ_NLEVELS - 1; level >= 2; level--) {
+        thresh = thresholdFactor[level];
+        cqca = cqcaa[level];
+        cqcasub = cqcaa[level + 1];
+        ncells = 1 << (3 * level);
+        for (i = 0; i < ncells; i++) {  /* i is octindex at level */
+            cqc = cqca[i];
+            for (j = 0; j < 8; j++) {  /* check all subnodes */
+                isub = 8 * i + j;   /* isub is octindex at level+1 */
+                cqcsub = cqcasub[isub];
+                if (cqcsub->bleaf == 1) {  /* already a leaf? */
+                    cqc->nleaves++;   /* count the subcube leaves */
+                    continue;
+                }
+                if (cqcsub->n >= thresh * ppc) {  /* make it a true leaf? */
+                    cqcsub->bleaf = 1;
+                    if (cmap->n < 256) {
+                        cqcsub->index = cmap->n;  /* assign the color index */
+                        getRGBFromOctcube(isub, level + 1, &rv, &gv, &bv);
+                        pixcmapAddColor(cmap, rv, gv, bv);
+#if 1   /* save values */
+                        cqcsub->rc = rv;
+                        cqcsub->gc = gv;
+                        cqcsub->bc = bv;
+#endif
+                    } else {
+                            /* This doesn't seem to happen. */
+                        L_WARNING("possibly assigned pixels to wrong color\n",
+                                  procName);
+                        pixcmapGetNearestIndex(cmap, rv, gv, bv, &cindex);
+                        cqcsub->index = cindex;  /* assign to the nearest */
+                        pixcmapGetColor(cmap, cindex, &rval, &gval, &bval);
+                        cqcsub->rc = rval;
+                        cqcsub->gc = gval;
+                        cqcsub->bc = bval;
+                    }
+                    cqc->nleaves++;
+                    npix -= cqcsub->n;
+                    ncolor--;
+                    if (ncolor > 0)
+                        ppc = npix / ncolor;
+                    else if (ncolor + reservedcolors > 0)
+                        ppc = npix / (ncolor + reservedcolors);
+                    else
+                        ppc = 1000000;  /* make it big */
+                    numaAddNumber(nat, level + 1);
+
+#if  DEBUG_OCTCUBE_CMAP
+    fprintf(stderr, "Exceeds threshold: colors used = %d, colors remaining = %d\n",
+                     cmap->n, ncolor + reservedcolors);
+    fprintf(stderr, "  cell with %d pixels, npix = %d, ppc = %d\n",
+                     cqcsub->n, npix, ppc);
+    fprintf(stderr, "  index = %d, level = %d, subindex = %d\n",
+                     i, level, j);
+    fprintf(stderr, "  rv = %d, gv = %d, bv = %d\n", rv, gv, bv);
+#endif  /* DEBUG_OCTCUBE_CMAP */
+
+                }
+            }
+            if (cqc->nleaves > 0 || level == 2) { /* make the cube a leaf now */
+                cqc->bleaf = 1;
+                if (cqc->nleaves < 8) {  /* residual CTE cube: acquire the
+                                          * remaining pixels */
+                    for (j = 0; j < 8; j++) {  /* check all subnodes */
+                        isub = 8 * i + j;
+                        cqcsub = cqcasub[isub];
+                        if (cqcsub->bleaf == 0)  /* absorb */
+                            cqc->n += cqcsub->n;
+                    }
+                    if (cmap->n < 256) {
+                        cqc->index = cmap->n;  /* assign the color index */
+                        getRGBFromOctcube(i, level, &rv, &gv, &bv);
+                        pixcmapAddColor(cmap, rv, gv, bv);
+#if 1   /* save values */
+                        cqc->rc = rv;
+                        cqc->gc = gv;
+                        cqc->bc = bv;
+#endif
+                    } else {
+                        L_WARNING("possibly assigned pixels to wrong color\n",
+                                  procName);
+                            /* This is very bad.  It will only cause trouble
+                             * with dithering, and we try to avoid it with
+                             * EXTRA_RESERVED_PIXELS. */
+                        pixcmapGetNearestIndex(cmap, rv, gv, bv, &cindex);
+                        cqc->index = cindex;  /* assign to the nearest */
+                        pixcmapGetColor(cmap, cindex, &rval, &gval, &bval);
+                        cqc->rc = rval;
+                        cqc->gc = gval;
+                        cqc->bc = bval;
+                    }
+                    npix -= cqc->n;
+                    ncolor--;
+                    if (ncolor > 0)
+                        ppc = npix / ncolor;
+                    else if (ncolor + reservedcolors > 0)
+                        ppc = npix / (ncolor + reservedcolors);
+                    else
+                        ppc = 1000000;  /* make it big */
+                    numaAddNumber(nar, level);
+
+#if  DEBUG_OCTCUBE_CMAP
+    fprintf(stderr, "By remainder: colors used = %d, colors remaining = %d\n",
+                     cmap->n, ncolor + reservedcolors);
+    fprintf(stderr, "  cell with %d pixels, npix = %d, ppc = %d\n",
+                     cqc->n, npix, ppc);
+    fprintf(stderr, "  index = %d, level = %d\n", i, level);
+    fprintf(stderr, "  rv = %d, gv = %d, bv = %d\n", rv, gv, bv);
+#endif  /* DEBUG_OCTCUBE_CMAP */
+
+                }
+            } else {  /* absorb all the subpixels but don't make it a leaf */
+                for (j = 0; j < 8; j++) {  /* absorb from all subnodes */
+                    isub = 8 * i + j;
+                    cqcsub = cqcasub[isub];
+                    cqc->n += cqcsub->n;
+                }
+            }
+        }
+    }
+
+#if  PRINT_OCTCUBE_STATS
+{
+l_int32    tc[] = {0, 0, 0, 0, 0, 0, 0};
+l_int32    rc[] = {0, 0, 0, 0, 0, 0, 0};
+l_int32    nt, nr, ival;
+
+    nt = numaGetCount(nat);
+    nr = numaGetCount(nar);
+    for (i = 0; i < nt; i++) {
+        numaGetIValue(nat, i, &ival);
+        tc[ival]++;
+    }
+    for (i = 0; i < nr; i++) {
+        numaGetIValue(nar, i, &ival);
+        rc[ival]++;
+    }
+    fprintf(stderr, " Threshold cells formed: %d\n", nt);
+    for (i = 1; i < CQ_NLEVELS + 1; i++)
+        fprintf(stderr, "   level %d:  %d\n", i, tc[i]);
+    fprintf(stderr, "\n Residual cells formed: %d\n", nr);
+    for (i = 0; i < CQ_NLEVELS ; i++)
+        fprintf(stderr, "   level %d:  %d\n", i, rc[i]);
+}
+#endif  /* PRINT_OCTCUBE_STATS */
+
+    numaDestroy(&nat);
+    numaDestroy(&nar);
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+
+    return cqcaa;
+}
+
+
+/*!
+ *  pixOctreeQuantizePixels()
+ *
+ *      Input:  pixs (32 bpp)
+ *              octree in array format
+ *              ditherflag (1 for dithering, 0 for no dithering)
+ *      Return: pixd or null on error
+ *
+ *  Notes:
+ *      (1) This routine doesn't need to use the CTEs (colormap
+ *          table entries) because the color indices are embedded
+ *          in the octree.  Thus, the calling program must make
+ *          and attach the colormap to pixd after it is returned.
+ *      (2) Dithering is performed in integers, effectively rounding
+ *          to 1/8 sample increment.  The data in the integer buffers is
+ *          64 times the sample values.  The 'dif' is 8 times the
+ *          sample values, and this spread, multiplied by 8, to the
+ *          integer buffers.  Because the dif is truncated to an
+ *          integer, the dither is accurate to 1/8 of a sample increment,
+ *          or 1/2048 of the color range.
+ */
+static PIX *
+pixOctreeQuantizePixels(PIX       *pixs,
+                        CQCELL  ***cqcaa,
+                        l_int32    ditherflag)
+{
+l_uint8   *bufu8r, *bufu8g, *bufu8b;
+l_int32    rval, gval, bval;
+l_int32    octindex, index;
+l_int32    val1, val2, val3, dif;
+l_int32    w, h, wpls, wpld, i, j;
+l_int32    rc, gc, bc;
+l_int32   *buf1r, *buf1g, *buf1b, *buf2r, *buf2g, *buf2b;
+l_uint32  *rtab, *gtab, *btab;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixOctreeQuantizePixels");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL);
+    if (!cqcaa)
+        return (PIX *)ERROR_PTR("cqcaa not defined", procName, NULL);
+
+        /* Make the canonical index tables */
+    if (makeRGBToIndexTables(&rtab, &gtab, &btab, CQ_NLEVELS))
+        return (PIX *)ERROR_PTR("tables not made", procName, NULL);
+
+        /* Make output 8 bpp palette image */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* Traverse tree from root, looking for lowest cube
+         * that is a leaf, and set dest pix to its
+         * colortable index value.  The results are far
+         * better when dithering to get a more accurate
+         * average color.  */
+    if (ditherflag == 0) {    /* no dithering */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                extractRGBValues(lines[j], &rval, &gval, &bval);
+                octindex = rtab[rval] | gtab[gval] | btab[bval];
+                octreeFindColorCell(octindex, cqcaa, &index, &rc, &gc, &bc);
+                SET_DATA_BYTE(lined, j, index);
+            }
+        }
+    } else {  /* Dither */
+        bufu8r = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
+        bufu8g = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
+        bufu8b = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
+        buf1r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+        buf1g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+        buf1b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+        buf2r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+        buf2g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+        buf2b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+        if (!bufu8r || !bufu8g || !bufu8b)
+            return (PIX *)ERROR_PTR("uint8 mono line buf not made",
+                procName, NULL);
+        if (!buf1r || !buf1g || !buf1b || !buf2r || !buf2g || !buf2b)
+            return (PIX *)ERROR_PTR("mono line buf not made", procName, NULL);
+
+            /* Start by priming buf2; line 1 is above line 2 */
+        pixGetRGBLine(pixs, 0, bufu8r, bufu8g, bufu8b);
+        for (j = 0; j < w; j++) {
+            buf2r[j] = 64 * bufu8r[j];
+            buf2g[j] = 64 * bufu8g[j];
+            buf2b[j] = 64 * bufu8b[j];
+        }
+
+        for (i = 0; i < h - 1; i++) {
+                /* Swap data 2 --> 1, and read in new line 2 */
+            memcpy(buf1r, buf2r, 4 * w);
+            memcpy(buf1g, buf2g, 4 * w);
+            memcpy(buf1b, buf2b, 4 * w);
+            pixGetRGBLine(pixs, i + 1, bufu8r, bufu8g, bufu8b);
+            for (j = 0; j < w; j++) {
+                buf2r[j] = 64 * bufu8r[j];
+                buf2g[j] = 64 * bufu8g[j];
+                buf2b[j] = 64 * bufu8b[j];
+            }
+
+                /* Dither */
+            lined = datad + i * wpld;
+            for (j = 0; j < w - 1; j++) {
+                rval = buf1r[j] / 64;
+                gval = buf1g[j] / 64;
+                bval = buf1b[j] / 64;
+                octindex = rtab[rval] | gtab[gval] | btab[bval];
+                octreeFindColorCell(octindex, cqcaa, &index, &rc, &gc, &bc);
+                SET_DATA_BYTE(lined, j, index);
+
+                dif = buf1r[j] / 8 - 8 * rc;
+                if (dif != 0) {
+                    val1 = buf1r[j + 1] + 3 * dif;
+                    val2 = buf2r[j] + 3 * dif;
+                    val3 = buf2r[j + 1] + 2 * dif;
+                    if (dif > 0) {
+                        buf1r[j + 1] = L_MIN(16383, val1);
+                        buf2r[j] = L_MIN(16383, val2);
+                        buf2r[j + 1] = L_MIN(16383, val3);
+                    } else if (dif < 0) {
+                        buf1r[j + 1] = L_MAX(0, val1);
+                        buf2r[j] = L_MAX(0, val2);
+                        buf2r[j + 1] = L_MAX(0, val3);
+                    }
+                }
+
+                dif = buf1g[j] / 8 - 8 * gc;
+                if (dif != 0) {
+                    val1 = buf1g[j + 1] + 3 * dif;
+                    val2 = buf2g[j] + 3 * dif;
+                    val3 = buf2g[j + 1] + 2 * dif;
+                    if (dif > 0) {
+                        buf1g[j + 1] = L_MIN(16383, val1);
+                        buf2g[j] = L_MIN(16383, val2);
+                        buf2g[j + 1] = L_MIN(16383, val3);
+                    } else if (dif < 0) {
+                        buf1g[j + 1] = L_MAX(0, val1);
+                        buf2g[j] = L_MAX(0, val2);
+                        buf2g[j + 1] = L_MAX(0, val3);
+                    }
+                }
+
+                dif = buf1b[j] / 8 - 8 * bc;
+                if (dif != 0) {
+                    val1 = buf1b[j + 1] + 3 * dif;
+                    val2 = buf2b[j] + 3 * dif;
+                    val3 = buf2b[j + 1] + 2 * dif;
+                    if (dif > 0) {
+                        buf1b[j + 1] = L_MIN(16383, val1);
+                        buf2b[j] = L_MIN(16383, val2);
+                        buf2b[j + 1] = L_MIN(16383, val3);
+                    } else if (dif < 0) {
+                        buf1b[j + 1] = L_MAX(0, val1);
+                        buf2b[j] = L_MAX(0, val2);
+                        buf2b[j + 1] = L_MAX(0, val3);
+                    }
+                }
+            }
+
+                /* Get last pixel in row; no downward propagation */
+            rval = buf1r[w - 1] / 64;
+            gval = buf1g[w - 1] / 64;
+            bval = buf1b[w - 1] / 64;
+            octindex = rtab[rval] | gtab[gval] | btab[bval];
+            octreeFindColorCell(octindex, cqcaa, &index, &rc, &gc, &bc);
+            SET_DATA_BYTE(lined, w - 1, index);
+        }
+
+            /* Get last row of pixels; no leftward propagation */
+        lined = datad + (h - 1) * wpld;
+        for (j = 0; j < w; j++) {
+            rval = buf2r[j] / 64;
+            gval = buf2g[j] / 64;
+            bval = buf2b[j] / 64;
+            octindex = rtab[rval] | gtab[gval] | btab[bval];
+            octreeFindColorCell(octindex, cqcaa, &index, &rc, &gc, &bc);
+            SET_DATA_BYTE(lined, j, index);
+        }
+
+        LEPT_FREE(bufu8r);
+        LEPT_FREE(bufu8g);
+        LEPT_FREE(bufu8b);
+        LEPT_FREE(buf1r);
+        LEPT_FREE(buf1g);
+        LEPT_FREE(buf1b);
+        LEPT_FREE(buf2r);
+        LEPT_FREE(buf2g);
+        LEPT_FREE(buf2b);
+    }
+
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    return pixd;
+}
+
+
+/*!
+ *  octreeFindColorCell()
+ *
+ *      Input:  octindex
+ *              cqcaa
+ *              &index   (<return> index of CTE; returned to set pixel value)
+ *              &rval    (<return> of CTE)
+ *              &gval    (<return> of CTE)
+ *              &bval    (<return> of CTE)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) As this is in inner loop, we don't check input pointers!
+ *      (2) This traverses from the root (well, actually from level 2,
+ *          because the level 2 cubes are the largest CTE cubes),
+ *          and finds the index number of the cell and the color values,
+ *          which can be used either directly or in a (Floyd-Steinberg)
+ *          error-diffusion dithering algorithm.
+ */
+static l_int32
+octreeFindColorCell(l_int32    octindex,
+                    CQCELL  ***cqcaa,
+                    l_int32   *pindex,
+                    l_int32   *prval,
+                    l_int32   *pgval,
+                    l_int32   *pbval)
+{
+l_int32  level;
+l_int32  baseindex, subindex;
+CQCELL  *cqc, *cqcsub;
+
+        /* Use rgb values stored in the cubes; a little faster */
+    for (level = 2; level < CQ_NLEVELS; level++) {
+        getOctcubeIndices(octindex, level, &baseindex, &subindex);
+        cqc = cqcaa[level][baseindex];
+        cqcsub = cqcaa[level + 1][subindex];
+        if (cqcsub->bleaf == 0) {  /* use cell at level above */
+            *pindex = cqc->index;
+            *prval = cqc->rc;
+            *pgval = cqc->gc;
+            *pbval = cqc->bc;
+            break;
+        } else if (level == CQ_NLEVELS - 1) {  /* reached the bottom */
+            *pindex = cqcsub->index;
+            *prval = cqcsub->rc;
+            *pgval = cqcsub->gc;
+            *pbval = cqcsub->bc;
+             break;
+        }
+    }
+
+#if 0
+        /* Generate rgb values for each cube on the fly; slower */
+    for (level = 2; level < CQ_NLEVELS; level++) {
+        l_int32  rv, gv, bv;
+        getOctcubeIndices(octindex, level, &baseindex, &subindex);
+        cqc = cqcaa[level][baseindex];
+        cqcsub = cqcaa[level + 1][subindex];
+        if (cqcsub->bleaf == 0) {  /* use cell at level above */
+            getRGBFromOctcube(baseindex, level, &rv, &gv, &bv);
+            *pindex = cqc->index;
+            *prval = rv;
+            *pgval = gv;
+            *pbval = bv;
+            break;
+        } else if (level == CQ_NLEVELS - 1) {  /* reached the bottom */
+            getRGBFromOctcube(subindex, level + 1, &rv, &gv, &bv);
+           *pindex = cqcsub->index;
+            *prval = rv;
+            *pgval = gv;
+            *pbval = bv;
+            break;
+        }
+    }
+#endif
+
+    return 0;
+}
+
+
+
+/*------------------------------------------------------------------*
+ *                      Helper cqcell functions                     *
+ *------------------------------------------------------------------*/
+/*!
+ *  cqcellTreeCreate()
+ *
+ *      Input:  none
+ *      Return: cqcell array tree
+ */
+static CQCELL ***
+cqcellTreeCreate(void)
+{
+l_int32    level, ncells, i;
+CQCELL  ***cqcaa;
+CQCELL   **cqca;   /* one array for each octree level */
+
+    PROCNAME("cqcellTreeCreate");
+
+        /* Make array of accumulation cell arrays from levels 1 to 5 */
+    if ((cqcaa = (CQCELL ***)LEPT_CALLOC(CQ_NLEVELS + 1, sizeof(CQCELL **)))
+        == NULL)
+        return (CQCELL ***)ERROR_PTR("cqcaa not made", procName, NULL);
+    for (level = 0; level <= CQ_NLEVELS; level++) {
+        ncells = 1 << (3 * level);
+        if ((cqca = (CQCELL **)LEPT_CALLOC(ncells, sizeof(CQCELL *))) == NULL)
+            return (CQCELL ***)ERROR_PTR("cqca not made", procName, NULL);
+        cqcaa[level] = cqca;
+        for (i = 0; i < ncells; i++) {
+            if ((cqca[i] = (CQCELL *)LEPT_CALLOC(1, sizeof(CQCELL))) == NULL)
+                return (CQCELL ***)ERROR_PTR("cqc not made", procName, NULL);
+        }
+    }
+
+    return cqcaa;
+}
+
+
+/*!
+ *  cqcellTreeDestroy()
+ *
+ *      Input:  &cqcaa (<to be nulled>
+ *      Return: void
+ */
+static void
+cqcellTreeDestroy(CQCELL  ****pcqcaa)
+{
+l_int32    level, ncells, i;
+CQCELL  ***cqcaa;
+CQCELL   **cqca;
+
+    PROCNAME("cqcellTreeDestroy");
+
+    if (pcqcaa == NULL) {
+        L_WARNING("ptr address is NULL\n", procName);
+        return;
+    }
+
+    if ((cqcaa = *pcqcaa) == NULL)
+        return;
+
+    for (level = 0; level <= CQ_NLEVELS; level++) {
+        cqca = cqcaa[level];
+        ncells = 1 << (3 * level);
+        for (i = 0; i < ncells; i++)
+            LEPT_FREE(cqca[i]);
+        LEPT_FREE(cqca);
+    }
+    LEPT_FREE(cqcaa);
+    *pcqcaa = NULL;
+
+    return;
+}
+
+
+
+/*------------------------------------------------------------------*
+ *                       Helper index functions                     *
+ *------------------------------------------------------------------*/
+/*!
+ *  makeRGBToIndexTables()
+ *
+ *      Input:  &rtab, &gtab, &btab  (<return> tables)
+ *              cqlevels (can be 1, 2, 3, 4, 5 or 6)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Set up tables.  e.g., for cqlevels = 5, we need an integer 0 < i < 2^15:
+ *      rtab = (0  i7  0   0  i6  0   0  i5  0   0   i4  0   0   i3  0   0)
+ *      gtab = (0  0   i7  0   0  i6  0   0  i5  0   0   i4  0   0   i3  0)
+ *      btab = (0  0   0   i7  0  0   i6  0  0   i5  0   0   i4  0   0   i3)
+ *
+ *  The tables are then used to map from rbg --> index as follows:
+ *      index = (0  r7  g7  b7  r6  g6  b6  r5  g5  b5  r4  g4  b4  r3  g3  b3)
+ *
+ *    e.g., for cqlevels = 4, we map to
+ *      index = (0  0   0   0   r7  g7  b7  r6  g6  b6  r5  g5  b5  r4  g4  b4)
+ *
+ *  This may look a bit strange.  The notation 'r7' means the MSBit of
+ *  the r value (which has 8 bits, going down from r7 to r0).
+ *  Keep in mind that r7 is actually the r component bit for level 1 of
+ *  the octtree.  Level 1 is composed of 8 octcubes, represented by
+ *  the bits (r7 g7 b7), which divide the entire color space into
+ *  8 cubes.  At level 2, each of these 8 octcubes is further divided into
+ *  8 cubes, each labeled by the second most significant bits (r6 g6 b6)
+ *  of the rgb color.
+ */
+l_int32
+makeRGBToIndexTables(l_uint32  **prtab,
+                     l_uint32  **pgtab,
+                     l_uint32  **pbtab,
+                     l_int32     cqlevels)
+{
+l_int32    i;
+l_uint32  *rtab, *gtab, *btab;
+
+    PROCNAME("makeRGBToIndexTables");
+
+    if (cqlevels < 1 || cqlevels > 6)
+        return ERROR_INT("cqlevels must be in {1,...6}", procName, 1);
+
+    if (!prtab || !pgtab || !pbtab)
+        return ERROR_INT("&*tab not defined", procName, 1);
+    if ((rtab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32))) == NULL)
+        return ERROR_INT("rtab not made", procName, 1);
+    if ((gtab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32))) == NULL)
+        return ERROR_INT("gtab not made", procName, 1);
+    if ((btab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32))) == NULL)
+        return ERROR_INT("btab not made", procName, 1);
+    *prtab = rtab;
+    *pgtab = gtab;
+    *pbtab = btab;
+
+    switch (cqlevels)
+    {
+    case 1:
+        for (i = 0; i < 256; i++) {
+            rtab[i] = (i >> 5) & 0x0004;
+            gtab[i] = (i >> 6) & 0x0002;
+            btab[i] = (i >> 7);
+        }
+        break;
+    case 2:
+        for (i = 0; i < 256; i++) {
+            rtab[i] = ((i >> 2) & 0x0020) | ((i >> 4) & 0x0004);
+            gtab[i] = ((i >> 3) & 0x0010) | ((i >> 5) & 0x0002);
+            btab[i] = ((i >> 4) & 0x0008) | ((i >> 6) & 0x0001);
+        }
+        break;
+    case 3:
+        for (i = 0; i < 256; i++) {
+            rtab[i] = ((i << 1) & 0x0100) | ((i >> 1) & 0x0020) |
+                      ((i >> 3) & 0x0004);
+            gtab[i] = (i & 0x0080) | ((i >> 2) & 0x0010) |
+                      ((i >> 4) & 0x0002);
+            btab[i] = ((i >> 1) & 0x0040) | ((i >> 3) & 0x0008) |
+                      ((i >> 5) & 0x0001);
+        }
+        break;
+    case 4:
+        for (i = 0; i < 256; i++) {
+            rtab[i] = ((i << 4) & 0x0800) | ((i << 2) & 0x0100) |
+                      (i & 0x0020) | ((i >> 2) & 0x0004);
+            gtab[i] = ((i << 3) & 0x0400) | ((i << 1) & 0x0080) |
+                      ((i >> 1) & 0x0010) | ((i >> 3) & 0x0002);
+            btab[i] = ((i << 2) & 0x0200) | (i & 0x0040) |
+                      ((i >> 2) & 0x0008) | ((i >> 4) & 0x0001);
+        }
+        break;
+    case 5:
+        for (i = 0; i < 256; i++) {
+            rtab[i] = ((i << 7) & 0x4000) | ((i << 5) & 0x0800) |
+                      ((i << 3) & 0x0100) | ((i << 1) & 0x0020) |
+                      ((i >> 1) & 0x0004);
+            gtab[i] = ((i << 6) & 0x2000) | ((i << 4) & 0x0400) |
+                      ((i << 2) & 0x0080) | (i & 0x0010) |
+                      ((i >> 2) & 0x0002);
+            btab[i] = ((i << 5) & 0x1000) | ((i << 3) & 0x0200) |
+                      ((i << 1) & 0x0040) | ((i >> 1) & 0x0008) |
+                      ((i >> 3) & 0x0001);
+        }
+        break;
+    case 6:
+        for (i = 0; i < 256; i++) {
+            rtab[i] = ((i << 10) & 0x20000) | ((i << 8) & 0x4000) |
+                      ((i << 6) & 0x0800) | ((i << 4) & 0x0100) |
+                      ((i << 2) & 0x0020) | (i & 0x0004);
+            gtab[i] = ((i << 9) & 0x10000) | ((i << 7) & 0x2000) |
+                      ((i << 5) & 0x0400) | ((i << 3) & 0x0080) |
+                      ((i << 1) & 0x0010) | ((i >> 1) & 0x0002);
+            btab[i] = ((i << 8) & 0x8000) | ((i << 6) & 0x1000) |
+                      ((i << 4) & 0x0200) | ((i << 2) & 0x0040) |
+                      (i & 0x0008) | ((i >> 2) & 0x0001);
+        }
+        break;
+    default:
+        ERROR_INT("cqlevels not in [1...6]", procName, 1);
+        break;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  getOctcubeIndexFromRGB()
+ *
+ *      Input:  rval, gval, bval
+ *              rtab, gtab, btab  (generated with makeRGBToIndexTables())
+ *              &index (<return>)
+ *      Return: void
+ *
+ *  Note: no error checking!
+ */
+void
+getOctcubeIndexFromRGB(l_int32    rval,
+                       l_int32    gval,
+                       l_int32    bval,
+                       l_uint32  *rtab,
+                       l_uint32  *gtab,
+                       l_uint32  *btab,
+                       l_uint32  *pindex)
+{
+    *pindex = rtab[rval] | gtab[gval] | btab[bval];
+    return;
+}
+
+
+/*!
+ *  getRGBFromOctcube()
+ *
+ *      Input:  octcube index
+ *              level (at which index is expressed)
+ *              &rval  (<return> r val of this cube)
+ *              &gval  (<return> g val of this cube)
+ *              &bval  (<return> b val of this cube)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) We can consider all octcube indices to represent a
+ *          specific point in color space: namely, the location
+ *          of the 'upper-left' corner of the cube, where indices
+ *          increase down and to the right.  The upper left corner
+ *          of the color space is then 00000....
+ *      (2) The 'rgbindex' is a 24-bit representation of the location,
+ *          in octcube notation, at the center of the octcube.
+ *          To get to the center of an octcube, you choose the 111
+ *          octcube at the next lower level.
+ *      (3) For example, if the octcube index = 110101 (binary),
+ *          which is a level 2 expression, then the rgbindex
+ *          is the 24-bit representation of 110101111 (at level 3);
+ *          namely, 000110101111000000000000.  The number is padded
+ *          with 3 leading 0s (because the representation uses
+ *          only 21 bits) and 12 trailing 0s (the default for
+ *          levels 4-7, which are contained within each of the level3
+ *          octcubes.  Then the rgb values for the center of the
+ *          octcube are: rval = 11100000, gval = 10100000, bval = 01100000
+ */
+static void
+getRGBFromOctcube(l_int32   cubeindex,
+                  l_int32   level,
+                  l_int32  *prval,
+                  l_int32  *pgval,
+                  l_int32  *pbval)
+{
+l_int32  rgbindex;
+
+        /* Bring to format in 21 bits: (r7 g7 b7 r6 g6 b6 ...) */
+        /* This is valid for levels from 0 to 6 */
+    rgbindex = cubeindex << (3 * (7 - level));  /* upper corner of cube */
+    rgbindex |= (0x7 << (3 * (6 - level)));   /* index to center of cube */
+
+        /* Extract separate pieces */
+    *prval = ((rgbindex >> 13) & 0x80) |
+             ((rgbindex >> 11) & 0x40) |
+             ((rgbindex >> 9) & 0x20) |
+             ((rgbindex >> 7) & 0x10) |
+             ((rgbindex >> 5) & 0x08) |
+             ((rgbindex >> 3) & 0x04) |
+             ((rgbindex >> 1) & 0x02);
+    *pgval = ((rgbindex >> 12) & 0x80) |
+             ((rgbindex >> 10) & 0x40) |
+             ((rgbindex >> 8) & 0x20) |
+             ((rgbindex >> 6) & 0x10) |
+             ((rgbindex >> 4) & 0x08) |
+             ((rgbindex >> 2) & 0x04) |
+             (rgbindex & 0x02);
+    *pbval = ((rgbindex >> 11) & 0x80) |
+             ((rgbindex >> 9) & 0x40) |
+             ((rgbindex >> 7) & 0x20) |
+             ((rgbindex >> 5) & 0x10) |
+             ((rgbindex >> 3) & 0x08) |
+             ((rgbindex >> 1) & 0x04) |
+             ((rgbindex << 1) & 0x02);
+
+    return;
+}
+
+
+/*!
+ *  getOctcubeIndices()
+ *
+ *     Input:  rgbindex
+ *             octree level (0, 1, 2, 3, 4, 5)
+ *             &octcube base index (<return> index at the octree level)
+ *             &octcube sub index (<return> index at the next lower level)
+ *     Return: 0 if OK, 1 on error
+ *
+ *  for CQ_NLEVELS = 6, the full RGB index is in the form:
+ *     index = (0[13] 0 r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4 r3 g3 b3 r2 g2 b2)
+ *  for CQ_NLEVELS = 5, the full RGB index is in the form:
+ *     index = (0[16] 0 r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4 r3 g3 b3)
+ *  for CQ_NLEVELS = 4, the full RGB index is in the form:
+ *     index = (0[19] 0 r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4)
+ *
+ *  The base index is the index of the octcube at the level given,
+ *  whereas the sub index is the index at the next level down.
+ *
+ *  For level 0: base index = 0
+ *               sub index is the 3 bit number (r7 g7 b7)
+ *  For level 1: base index = (r7 g7 b7)
+ *               sub index = (r7 g7 b7 r6 g6 b6)
+ *  For level 2: base index = (r7 g7 b7 r6 g6 b6)
+ *               sub index = (r7 g7 b7 r6 g6 b6 r5 g5 b5)
+ *  For level 3: base index = (r7 g7 b7 r6 g6 b6 r5 g5 b5)
+ *               sub index = (r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4)
+ *  For level 4: base index = (r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4)
+ *               sub index = (r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4 r3 g3 b3)
+ *  For level 5: base index = (r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4 r3 g3 b3)
+ *               sub index = (r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4 r3 g3 b3
+ *                            r2 g2 b2)
+ */
+static l_int32
+getOctcubeIndices(l_int32   rgbindex,
+                  l_int32   level,
+                  l_int32  *pbindex,
+                  l_int32  *psindex)
+{
+    PROCNAME("getOctcubeIndex");
+
+    if (level < 0 || level > CQ_NLEVELS - 1)
+        return ERROR_INT("level must be in e.g., [0 ... 5]", procName, 1);
+    if (!pbindex)
+        return ERROR_INT("&bindex not defined", procName, 1);
+    if (!psindex)
+        return ERROR_INT("&sindex not defined", procName, 1);
+
+    *pbindex = rgbindex >> (3 * (CQ_NLEVELS - level));
+    *psindex = rgbindex >> (3 * (CQ_NLEVELS - 1 - level));
+    return 0;
+}
+
+
+/*!
+ *  octcubeGetCount()
+ *
+ *      Input:  level (valid values are in [1,...6]; there are 2^level
+ *                     cubes along each side of the rgb cube)
+ *              &size (<return> 2^(3 * level) cubes in the entire rgb cube)
+ *      Return:  0 if OK, 1 on error.  Caller must check!
+ *
+ *         level:   1        2        3        4        5        6
+ *         size:    8       64       512     4098     32784   262272
+ */
+static l_int32
+octcubeGetCount(l_int32   level,
+                l_int32  *psize)
+{
+    PROCNAME("octcubeGetCount");
+
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1);
+    if (level < 1 || level > 6)
+        return ERROR_INT("invalid level", procName, 1);
+
+    *psize = 1 << (3 * level);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *      Adaptive octree quantization based on population at a fixed level    *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixOctreeQuantByPopulation()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              level (significant bits for each of RGB; valid for {3,4},
+ *                     Use 0 for default (level 4; recommended)
+ *              ditherflag  (1 to dither, 0 otherwise)
+ *      Return: pixd (quantized to octcubes) or null on error
+ *
+ *  Notes:
+ *      (1) This color quantization method works very well without
+ *          dithering, using octcubes at two different levels:
+ *            (a) the input @level, which is either 3 or 4
+ *            (b) level 2 (64 octcubes to cover the entire color space)
+ *      (2) For best results, using @level = 4 is recommended.
+ *          Why do we provide an option for using level 3?  Because
+ *          there are 512 octcubes at level 3, and for many images
+ *          not more than 256 are filled.  As a result, on some images
+ *          a very accurate quantized representation is possible using
+ *          @level = 3.
+ *      (3) This first breaks up the color space into octcubes at the
+ *          input @level, and computes, for each octcube, the average
+ *          value of the pixels that are in it.
+ *      (4) Then there are two possible situations:
+ *            (a) If there are not more than 256 populated octcubes,
+ *                it returns a cmapped pix with those values assigned.
+ *            (b) Otherwise, it selects 192 octcubes containing the largest
+ *                number of pixels and quantizes pixels within those octcubes
+ *                to their average.  Then, to handle the residual pixels
+ *                that are not in those 192 octcubes, it generates a
+ *                level 2 octree consisting of 64 octcubes, and within
+ *                each octcube it quantizes the residual pixels to their
+ *                average within each of those level 2 octcubes.
+ *      (5) Unpopulated level 2 octcubes are represented in the colormap
+ *          by their centers.  This, of course, has no effect unless
+ *          dithering is used for the output image.
+ *      (6) The depth of pixd is the miniumum required to suppport the
+ *          number of colors found at @level; namely, 2, 4 or 8.
+ *      (7) This function works particularly well on images such as maps,
+ *          where there are a relatively small number of well-populated
+ *          colors, but due to antialiasing and compression artifacts
+ *          there may be a large number of different colors.  This will
+ *          pull out and represent accurately the highly populated colors,
+ *          while still making a reasonable approximation for the others.
+ *      (8) The highest level of octcubes allowed is 4.  Use of higher
+ *          levels typically results in having a small fraction of
+ *          pixels in the most populated 192 octcubes.  As a result,
+ *          most of the pixels are represented at level 2, which is
+ *          not sufficiently accurate.
+ *      (9) Dithering shows artifacts on some images.  If you plan to
+ *          dither, pixOctreeColorQuant() and pixFixedOctcubeQuant256()
+ *          usually give better results.
+ */
+PIX *
+pixOctreeQuantByPopulation(PIX     *pixs,
+                           l_int32  level,
+                           l_int32  ditherflag)
+{
+l_int32         w, h, wpls, wpld, i, j, depth, size, ncolors, index;
+l_int32         rval, gval, bval;
+l_int32        *rarray, *garray, *barray, *narray, *iarray;
+l_uint32        octindex, octindex2;
+l_uint32       *rtab, *gtab, *btab, *rtab2, *gtab2, *btab2;
+l_uint32       *lines, *lined, *datas, *datad;
+L_OCTCUBE_POP  *opop;
+L_HEAP         *lh;
+PIX            *pixd;
+PIXCMAP        *cmap;
+
+    PROCNAME("pixOctreeQuantByPopulation");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (level == 0) level = 4;
+    if (level < 3 || level > 4)
+        return (PIX *)ERROR_PTR("level not in {3,4}", procName, NULL);
+
+        /* Do not dither if image is very small */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (w < MIN_DITHER_SIZE && h < MIN_DITHER_SIZE && ditherflag == 1) {
+        L_INFO("Small image: dithering turned off\n", procName);
+        ditherflag = 0;
+    }
+
+    if (octcubeGetCount(level, &size))  /* array size = 2 ** (3 * level) */
+        return (PIX *)ERROR_PTR("size not returned", procName, NULL);
+    if (makeRGBToIndexTables(&rtab, &gtab, &btab, level))
+        return (PIX *)ERROR_PTR("tables not made", procName, NULL);
+
+    if ((narray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("narray not made", procName, NULL);
+    if ((rarray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("rarray not made", procName, NULL);
+    if ((garray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("garray not made", procName, NULL);
+    if ((barray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("barray not made", procName, NULL);
+
+        /* Place the pixels in octcube leaves. */
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = NULL;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            octindex = rtab[rval] | gtab[gval] | btab[bval];
+            narray[octindex]++;
+            rarray[octindex] += rval;
+            garray[octindex] += gval;
+            barray[octindex] += bval;
+        }
+    }
+
+        /* Find the number of different colors */
+    for (i = 0, ncolors = 0; i < size; i++) {
+        if (narray[i] > 0)
+            ncolors++;
+    }
+    if (ncolors <= 4)
+        depth = 2;
+    else if (ncolors <= 16)
+        depth = 4;
+    else
+        depth = 8;
+    pixd = pixCreate(w, h, depth);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    cmap = pixcmapCreate(depth);
+    pixSetColormap(pixd, cmap);
+
+        /* Average the colors in each octcube leaf. */
+    for (i = 0; i < size; i++) {
+        if (narray[i] > 0) {
+            rarray[i] /= narray[i];
+            garray[i] /= narray[i];
+            barray[i] /= narray[i];
+        }
+    }
+
+        /* If ncolors <= 256, finish immediately.  Do not dither.
+         * Re-use narray to hold the colormap index + 1  */
+    if (ncolors <= 256) {
+        for (i = 0, index = 0; i < size; i++) {
+            if (narray[i] > 0) {
+                pixcmapAddColor(cmap, rarray[i], garray[i], barray[i]);
+                narray[i] = index + 1;  /* to avoid storing 0 */
+                index++;
+            }
+        }
+
+            /* Set the cmap indices for each pixel */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                extractRGBValues(lines[j], &rval, &gval, &bval);
+                octindex = rtab[rval] | gtab[gval] | btab[bval];
+                switch (depth)
+                {
+                case 8:
+                    SET_DATA_BYTE(lined, j, narray[octindex] - 1);
+                    break;
+                case 4:
+                    SET_DATA_QBIT(lined, j, narray[octindex] - 1);
+                    break;
+                case 2:
+                    SET_DATA_DIBIT(lined, j, narray[octindex] - 1);
+                    break;
+                default:
+                    L_WARNING("shouldn't get here\n", procName);
+                }
+            }
+        }
+        goto array_cleanup;
+    }
+
+        /* More complicated.  Sort by decreasing population */
+    lh = lheapCreate(500, L_SORT_DECREASING);
+    for (i = 0; i < size; i++) {
+        if (narray[i] > 0) {
+            opop = (L_OCTCUBE_POP *)LEPT_CALLOC(1, sizeof(L_OCTCUBE_POP));
+            opop->npix = (l_float32)narray[i];
+            opop->index = i;
+            opop->rval = rarray[i];
+            opop->gval = garray[i];
+            opop->bval = barray[i];
+            lheapAdd(lh, opop);
+        }
+    }
+
+        /* Take the top 192.  These will form the first 192 colors
+         * in the cmap.  iarray[i] holds the index into the cmap. */
+    if ((iarray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("iarray not made", procName, NULL);
+    for (i = 0; i < 192; i++) {
+        opop = (L_OCTCUBE_POP*)lheapRemove(lh);
+        if (!opop) break;
+        pixcmapAddColor(cmap, opop->rval, opop->gval, opop->bval);
+        iarray[opop->index] = i + 1;  /* +1 to avoid storing 0 */
+
+#if DEBUG_POP
+        fprintf(stderr, "i = %d, n = %6.0f, (r,g,b) = (%d %d %d)\n",
+                i, opop->npix, opop->rval, opop->gval, opop->bval);
+#endif  /* DEBUG_POP */
+
+        LEPT_FREE(opop);
+    }
+
+        /* Make the octindex tables for level 2, and reuse rarray, etc. */
+    if (makeRGBToIndexTables(&rtab2, &gtab2, &btab2, 2))
+        return (PIX *)ERROR_PTR("level 2 tables not made", procName, NULL);
+    for (i = 0; i < 64; i++) {
+        narray[i] = 0;
+        rarray[i] = 0;
+        garray[i] = 0;
+        barray[i] = 0;
+    }
+
+        /* Take the rest of the occupied octcubes, assigning the pixels
+         * to these new colormap indices.  iarray[] is addressed
+         * by @level octcube indices, and it now holds the
+         * colormap indices for all pixels in pixs.  */
+    for (i = 192; i < size; i++) {
+        opop = (L_OCTCUBE_POP*)lheapRemove(lh);
+        if (!opop) break;
+        rval = opop->rval;
+        gval = opop->gval;
+        bval = opop->bval;
+        octindex2 = rtab2[rval] | gtab2[gval] | btab2[bval];
+        narray[octindex2] += (l_int32)opop->npix;
+        rarray[octindex2] += (l_int32)opop->npix * rval;
+        garray[octindex2] += (l_int32)opop->npix * gval;
+        barray[octindex2] += (l_int32)opop->npix * bval;
+        iarray[opop->index] = 192 + octindex2 + 1;  /* +1 to avoid storing 0 */
+        LEPT_FREE(opop);
+    }
+    lheapDestroy(&lh, TRUE);
+
+        /* To span the full color space, which is necessary for dithering,
+         * set each iarray element whose value is still 0 at the input
+         * level octcube leaves (because there were no pixels in those
+         * octcubes) to the colormap index corresponding to its level 2
+         * octcube. */
+    if (ditherflag) {
+        for (i = 0; i < size; i++) {
+            if (iarray[i] == 0) {
+                getRGBFromOctcube(i, level, &rval, &gval, &bval);
+                octindex2 = rtab2[rval] | gtab2[gval] | btab2[bval];
+                iarray[i] = 192 + octindex2 + 1;
+            }
+        }
+    }
+    LEPT_FREE(rtab2);
+    LEPT_FREE(gtab2);
+    LEPT_FREE(btab2);
+
+        /* Average the colors from the residuals in each level 2 octcube,
+         * and add these 64 values to the colormap. */
+    for (i = 0; i < 64; i++) {
+        if (narray[i] > 0) {
+            rarray[i] /= narray[i];
+            garray[i] /= narray[i];
+            barray[i] /= narray[i];
+        } else {  /* no pixels in this octcube; use center value */
+            getRGBFromOctcube(i, 2, &rarray[i], &garray[i], &barray[i]);
+        }
+        pixcmapAddColor(cmap, rarray[i], garray[i], barray[i]);
+    }
+
+        /* Set the cmap indices for each pixel.  Subtract 1 from
+         * the value in iarray[] because we added 1 earlier.  */
+    if (ditherflag == 0) {
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                extractRGBValues(lines[j], &rval, &gval, &bval);
+                octindex = rtab[rval] | gtab[gval] | btab[bval];
+                SET_DATA_BYTE(lined, j, iarray[octindex] - 1);
+            }
+        }
+    } else {  /* dither */
+        pixDitherOctindexWithCmap(pixs, pixd, rtab, gtab, btab,
+                                  iarray, POP_DIF_CAP);
+    }
+
+#if DEBUG_POP
+    for (i = 0; i < size / 16; i++) {
+        l_int32 j;
+        for (j = 0; j < 16; j++)
+            fprintf(stderr, "%d ", iarray[16 * i + j]);
+        fprintf(stderr, "\n");
+    }
+#endif  /* DEBUG_POP */
+
+    LEPT_FREE(iarray);
+
+array_cleanup:
+    LEPT_FREE(narray);
+    LEPT_FREE(rarray);
+    LEPT_FREE(garray);
+    LEPT_FREE(barray);
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixDitherOctindexWithCmap()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              pixd (8 bpp cmapped)
+ *              rtab, gtab, btab (tables from rval to octindex)
+ *              indexmap (array mapping octindex to cmap index)
+ *              difcap (max allowed dither transfer; use 0 for infinite cap)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This performs dithering to generate the colormap indices
+ *          in pixd.  The colormap has been calculated, along with
+ *          four input LUTs that together give the inverse colormapping
+ *          from RGB to colormap index.
+ *      (2) For pixOctreeQuantByPopulation(), @indexmap maps from the
+ *          standard octindex to colormap index (after subtracting 1).
+ *          The basic pixel-level function, without dithering, is:
+ *             extractRGBValues(lines[j], &rval, &gval, &bval);
+ *             octindex = rtab[rval] | gtab[gval] | btab[bval];
+ *             SET_DATA_BYTE(lined, j, indexmap[octindex] - 1);
+ *      (3) This can be used in any situation where the general
+ *          prescription for finding the colormap index from the rgb
+ *          value is precisely this:
+ *             cmapindex = indexmap[rtab[rval] | gtab[gval] | btab[bval]] - 1
+ *          For example, in pixFixedOctcubeQuant256(), we don't use
+ *          standard octcube indexing, the rtab (etc) LUTs map directly
+ *          to the colormap index, and @indexmap just compensates for
+ *          the 1-off indexing assumed to be in that table.
+ */
+static l_int32
+pixDitherOctindexWithCmap(PIX       *pixs,
+                          PIX       *pixd,
+                          l_uint32  *rtab,
+                          l_uint32  *gtab,
+                          l_uint32  *btab,
+                          l_int32   *indexmap,
+                          l_int32    difcap)
+{
+l_uint8   *bufu8r, *bufu8g, *bufu8b;
+l_int32    i, j, w, h, wpld, octindex, cmapindex;
+l_int32    rval, gval, bval, rc, gc, bc;
+l_int32    dif, val1, val2, val3;
+l_int32   *buf1r, *buf1g, *buf1b, *buf2r, *buf2g, *buf2b;
+l_uint32  *datad, *lined;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixDitherOctindexWithCmap");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs undefined or not 32 bpp", procName, 1);
+    if (!pixd || pixGetDepth(pixd) != 8)
+        return ERROR_INT("pixd undefined or not 8 bpp", procName, 1);
+    if ((cmap = pixGetColormap(pixd)) == NULL)
+        return ERROR_INT("pixd not cmapped", procName, 1);
+    if (!rtab || !gtab || !btab || !indexmap)
+        return ERROR_INT("not all 4 tables defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (pixGetWidth(pixd) != w || pixGetHeight(pixd) != h)
+        return ERROR_INT("pixs and pixd not same size", procName, 1);
+
+    bufu8r = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
+    bufu8g = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
+    bufu8b = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
+    buf1r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+    buf1g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+    buf1b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+    buf2r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+    buf2g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+    buf2b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+    if (!bufu8r || !bufu8g || !bufu8b)
+        return ERROR_INT("uint8 line buf not made", procName, 1);
+    if (!buf1r || !buf1g || !buf1b || !buf2r || !buf2g || !buf2b)
+        return ERROR_INT("mono line buf not made", procName, 1);
+
+        /* Start by priming buf2; line 1 is above line 2 */
+    pixGetRGBLine(pixs, 0, bufu8r, bufu8g, bufu8b);
+    for (j = 0; j < w; j++) {
+        buf2r[j] = 64 * bufu8r[j];
+        buf2g[j] = 64 * bufu8g[j];
+        buf2b[j] = 64 * bufu8b[j];
+    }
+
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h - 1; i++) {
+            /* Swap data 2 --> 1, and read in new line 2 */
+        memcpy(buf1r, buf2r, 4 * w);
+        memcpy(buf1g, buf2g, 4 * w);
+        memcpy(buf1b, buf2b, 4 * w);
+        pixGetRGBLine(pixs, i + 1, bufu8r, bufu8g, bufu8b);
+        for (j = 0; j < w; j++) {
+            buf2r[j] = 64 * bufu8r[j];
+            buf2g[j] = 64 * bufu8g[j];
+            buf2b[j] = 64 * bufu8b[j];
+        }
+
+            /* Dither */
+        lined = datad + i * wpld;
+        for (j = 0; j < w - 1; j++) {
+            rval = buf1r[j] / 64;
+            gval = buf1g[j] / 64;
+            bval = buf1b[j] / 64;
+            octindex = rtab[rval] | gtab[gval] | btab[bval];
+            cmapindex = indexmap[octindex] - 1;
+            SET_DATA_BYTE(lined, j, cmapindex);
+            pixcmapGetColor(cmap, cmapindex, &rc, &gc, &bc);
+
+            dif = buf1r[j] / 8 - 8 * rc;
+            if (difcap > 0) {
+                if (dif > difcap) dif = difcap;
+                if (dif < -difcap) dif = -difcap;
+            }
+            if (dif != 0) {
+                val1 = buf1r[j + 1] + 3 * dif;
+                val2 = buf2r[j] + 3 * dif;
+                val3 = buf2r[j + 1] + 2 * dif;
+                if (dif > 0) {
+                    buf1r[j + 1] = L_MIN(16383, val1);
+                    buf2r[j] = L_MIN(16383, val2);
+                    buf2r[j + 1] = L_MIN(16383, val3);
+                } else if (dif < 0) {
+                    buf1r[j + 1] = L_MAX(0, val1);
+                    buf2r[j] = L_MAX(0, val2);
+                    buf2r[j + 1] = L_MAX(0, val3);
+                }
+            }
+
+            dif = buf1g[j] / 8 - 8 * gc;
+            if (difcap > 0) {
+                if (dif > difcap) dif = difcap;
+                if (dif < -difcap) dif = -difcap;
+            }
+            if (dif != 0) {
+                val1 = buf1g[j + 1] + 3 * dif;
+                val2 = buf2g[j] + 3 * dif;
+                val3 = buf2g[j + 1] + 2 * dif;
+                if (dif > 0) {
+                    buf1g[j + 1] = L_MIN(16383, val1);
+                    buf2g[j] = L_MIN(16383, val2);
+                    buf2g[j + 1] = L_MIN(16383, val3);
+                } else if (dif < 0) {
+                    buf1g[j + 1] = L_MAX(0, val1);
+                    buf2g[j] = L_MAX(0, val2);
+                    buf2g[j + 1] = L_MAX(0, val3);
+                }
+            }
+
+            dif = buf1b[j] / 8 - 8 * bc;
+            if (difcap > 0) {
+                if (dif > difcap) dif = difcap;
+                if (dif < -difcap) dif = -difcap;
+            }
+            if (dif != 0) {
+                val1 = buf1b[j + 1] + 3 * dif;
+                val2 = buf2b[j] + 3 * dif;
+                val3 = buf2b[j + 1] + 2 * dif;
+                if (dif > 0) {
+                    buf1b[j + 1] = L_MIN(16383, val1);
+                    buf2b[j] = L_MIN(16383, val2);
+                    buf2b[j + 1] = L_MIN(16383, val3);
+                } else if (dif < 0) {
+                    buf1b[j + 1] = L_MAX(0, val1);
+                    buf2b[j] = L_MAX(0, val2);
+                    buf2b[j + 1] = L_MAX(0, val3);
+                }
+            }
+        }
+
+            /* Get last pixel in row; no downward propagation */
+        rval = buf1r[w - 1] / 64;
+        gval = buf1g[w - 1] / 64;
+        bval = buf1b[w - 1] / 64;
+        octindex = rtab[rval] | gtab[gval] | btab[bval];
+        cmapindex = indexmap[octindex] - 1;
+        SET_DATA_BYTE(lined, w - 1, cmapindex);
+    }
+
+        /* Get last row of pixels; no leftward propagation */
+    lined = datad + (h - 1) * wpld;
+    for (j = 0; j < w; j++) {
+        rval = buf2r[j] / 64;
+        gval = buf2g[j] / 64;
+        bval = buf2b[j] / 64;
+        octindex = rtab[rval] | gtab[gval] | btab[bval];
+        cmapindex = indexmap[octindex] - 1;
+        SET_DATA_BYTE(lined, j, cmapindex);
+    }
+
+    LEPT_FREE(bufu8r);
+    LEPT_FREE(bufu8g);
+    LEPT_FREE(bufu8b);
+    LEPT_FREE(buf1r);
+    LEPT_FREE(buf1g);
+    LEPT_FREE(buf1b);
+    LEPT_FREE(buf2r);
+    LEPT_FREE(buf2g);
+    LEPT_FREE(buf2b);
+
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *         Adaptive octree quantization to 4 and 8 bpp with max colors       *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixOctreeQuantNumColors()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              maxcolors (8 to 256; the actual number of colors used
+ *                         may be less than this)
+ *              subsample (factor for computing color distribution;
+ *                         use 0 for default)
+ *      Return: pixd (4 or 8 bpp, colormapped), or null on error
+ *
+ *  pixOctreeColorQuant() is very flexible in terms of the relative
+ *  depth of different cubes of the octree.   By contrast, this function,
+ *  pixOctreeQuantNumColors() is also adaptive, but it supports octcube
+ *  leaves at only two depths: a smaller depth that guarantees
+ *  full coverage of the color space and octcubes at one level
+ *  deeper for more accurate colors.  Its main virutes are simplicity
+ *  and speed, which are both derived from the natural indexing of
+ *  the octcubes from the RGB values.
+ *
+ *  Before describing pixOctreeQuantNumColors(), consider an even simpler
+ *  approach for 4 bpp with either 8 or 16 colors.  With 8 colors,
+ *  you simply go to level 1 octcubes and use the average color
+ *  found in each cube.  For 16 colors, you find which of the three
+ *  colors has the largest variance at the second level, and use two
+ *  indices for that color.  The result is quite poor, because (1) some
+ *  of the cubes are nearly empty and (2) you don't get much color
+ *  differentiation for the extra 8 colors.  Trust me, this method may
+ *  be simple, but it isn't worth anything.
+ *
+ *  In pixOctreeQuantNumColors(), we generate colormapped images at
+ *  either 4 bpp or 8 bpp.  For 4 bpp, we have a minimum of 8 colors
+ *  for the level 1 octcubes, plus up to 8 additional colors that
+ *  are determined from the level 2 popularity.  If the number of colors
+ *  is between 8 and 16, the output is a 4 bpp image.  If the number of
+ *  colors is greater than 16, the output is a 8 bpp image.
+ *
+ *  We use a priority queue, implemented with a heap, to select the
+ *  requisite number of most populated octcubes at the deepest level
+ *  (level 2 for 64 or fewer colors; level 3 for more than 64 colors).
+ *  These are combined with one color for each octcube one level above,
+ *  which is used to span the color space of octcubes that were not
+ *  included at the deeper level.
+ *
+ *  If the deepest level is 2, we combine the popular level 2 octcubes
+ *  (out of a total of 64) with the 8 level 1 octcubes.  If the deepest
+ *  level is 3, we combine the popular level 3 octcubes (out of a
+ *  total 512) with the 64 level 2 octcubes that span the color space.
+ *  In the latter case, we require a minimum of 64 colors for the level 2
+ *  octcubes, plus up to 192 additional colors determined from level 3
+ *  popularity.
+ *
+ *  The parameter 'maxlevel' is the deepest octcube level that is used.
+ *  The implementation also uses two LUTs, which are employed in
+ *  two successive traversals of the dest image.  The first maps
+ *  from the src octindex at 'maxlevel' to the color table index,
+ *  which is the value that is stored in the 4 or 8 bpp dest pixel.
+ *  The second LUT maps from that colormap value in the dest to a
+ *  new colormap value for a minimum sized colormap, stored back in
+ *  the dest.  It is used to remove any color map entries that
+ *  correspond to color space regions that have no pixels in the
+ *  source image.  These regions can be either from the higher level
+ *  (e.g., level 1 for 4 bpp), or from octcubes at 'maxlevel' that
+ *  are unoccupied.  This remapping results in the minimum number
+ *  of colors used according to the constraints induced by the
+ *  input 'maxcolors'.  We also compute the average R, G and B color
+ *  values in each region of the color space represented by a
+ *  colormap entry, and store them in the colormap.
+ *
+ *  The maximum number of colors is input, which determines the
+ *  following properties of the dest image and octcube regions used:
+ *
+ *     Number of colors      dest image depth      maxlevel
+ *     ----------------      ----------------      --------
+ *       8 to 16                  4 bpp               2
+ *       17 to 64                 8 bpp               2
+ *       65 to 256                8 bpp               3
+ *
+ *  It may turn out that the number of extra colors, beyond the
+ *  minimum (8 and 64 for maxlevel 2 and 3, respectively), is larger
+ *  than the actual number of occupied cubes at these levels
+ *  In that case, all the pixels are contained in this
+ *  subset of cubes at maxlevel, and no colormap colors are needed
+ *  to represent the remainder pixels one level above.  Thus, for
+ *  example, in use one often finds that the pixels in an image
+ *  occupy less than 192 octcubes at level 3, so they can be represented
+ *  by a colormap for octcubes at level 3 only.
+ */
+PIX *
+pixOctreeQuantNumColors(PIX     *pixs,
+                        l_int32  maxcolors,
+                        l_int32  subsample)
+{
+l_int32    w, h, minside, bpp, wpls, wpld, i, j, actualcolors;
+l_int32    rval, gval, bval, nbase, nextra, maxlevel, ncubes, val;
+l_int32   *lut1, *lut2;
+l_uint32   index;
+l_uint32  *lines, *lined, *datas, *datad, *pspixel;
+l_uint32  *rtab, *gtab, *btab;
+OQCELL    *oqc;
+OQCELL   **oqca;
+L_HEAP    *lh;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixOctreeQuantNumColors");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    minside = L_MIN(w, h);
+    if (subsample <= 0) {
+       subsample = L_MAX(1, minside / 200);
+    }
+
+    if (maxcolors >= 8 && maxcolors <= 16) {
+        bpp = 4;
+        pixd = pixCreate(w, h, bpp);
+        maxlevel = 2;
+        ncubes = 64;   /* 2^6 */
+        nbase = 8;
+        nextra = maxcolors - nbase;
+    } else if (maxcolors < 64) {
+        bpp = 8;
+        pixd = pixCreate(w, h, bpp);
+        maxlevel = 2;
+        ncubes = 64;  /* 2^6 */
+        nbase = 8;
+        nextra = maxcolors - nbase;
+    } else if (maxcolors >= 64 && maxcolors <= 256) {
+        bpp = 8;
+        pixd = pixCreate(w, h, bpp);
+        maxlevel = 3;
+        ncubes = 512;  /* 2^9 */
+        nbase = 64;
+        nextra = maxcolors - nbase;
+    } else {
+        return (PIX *)ERROR_PTR("maxcolors not in {8...256}", procName, NULL);
+    }
+
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+        /*----------------------------------------------------------*
+         * If we're using the minimum number of colors, it is       *
+         * much simpler.  We just use 'nbase' octcubes.             *
+         * For this case, we don't eliminate any extra colors.      *
+         *----------------------------------------------------------*/
+    if (nextra == 0) {
+            /* prepare the OctcubeQuantCell array */
+        if ((oqca = (OQCELL **)LEPT_CALLOC(nbase, sizeof(OQCELL *))) == NULL)
+            return (PIX *)ERROR_PTR("oqca not made", procName, NULL);
+        for (i = 0; i < nbase; i++) {
+            oqca[i] = (OQCELL *)LEPT_CALLOC(1, sizeof(OQCELL));
+            oqca[i]->n = 0.0;
+        }
+
+        makeRGBToIndexTables(&rtab, &gtab, &btab, maxlevel - 1);
+
+            /* Go through the entire image, gathering statistics and
+             * assigning pixels to their quantized value */
+        datad = pixGetData(pixd);
+        wpld = pixGetWpl(pixd);
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                pspixel = lines + j;
+                extractRGBValues(*pspixel, &rval, &gval, &bval);
+                getOctcubeIndexFromRGB(rval, gval, bval,
+                                       rtab, gtab, btab, &index);
+/*                fprintf(stderr, "rval = %d, gval = %d, bval = %d, index = %d\n",
+                        rval, gval, bval, index); */
+                switch (bpp) {
+                case 4:
+                    SET_DATA_QBIT(lined, j, index);
+                    break;
+                case 8:
+                    SET_DATA_BYTE(lined, j, index);
+                    break;
+                default:
+                    return (PIX *)ERROR_PTR("bpp not 4 or 8!", procName, NULL);
+                    break;
+                }
+                oqca[index]->n += 1.0;
+                oqca[index]->rcum += rval;
+                oqca[index]->gcum += gval;
+                oqca[index]->bcum += bval;
+            }
+        }
+
+            /* Compute average color values in each octcube, and
+             * generate colormap */
+        cmap = pixcmapCreate(bpp);
+        pixSetColormap(pixd, cmap);
+        for (i = 0; i < nbase; i++) {
+            oqc = oqca[i];
+            if (oqc->n != 0) {
+                oqc->rval = (l_int32)(oqc->rcum / oqc->n);
+                oqc->gval = (l_int32)(oqc->gcum / oqc->n);
+                oqc->bval = (l_int32)(oqc->bcum / oqc->n);
+            } else {
+                getRGBFromOctcube(i, maxlevel - 1, &oqc->rval,
+                                  &oqc->gval, &oqc->bval);
+            }
+            pixcmapAddColor(cmap, oqc->rval, oqc->gval, oqc->bval);
+        }
+
+        for (i = 0; i < nbase; i++)
+            LEPT_FREE(oqca[i]);
+        LEPT_FREE(oqca);
+        LEPT_FREE(rtab);
+        LEPT_FREE(gtab);
+        LEPT_FREE(btab);
+        return pixd;
+    }
+
+        /*------------------------------------------------------------*
+         * General case: we will use colors in octcubes at maxlevel.  *
+         * We also remove any colors that are not populated from      *
+         * the colormap.                                              *
+         *------------------------------------------------------------*/
+        /* Prepare the OctcubeQuantCell array */
+    if ((oqca = (OQCELL **)LEPT_CALLOC(ncubes, sizeof(OQCELL *))) == NULL)
+        return (PIX *)ERROR_PTR("oqca not made", procName, NULL);
+    for (i = 0; i < ncubes; i++) {
+        oqca[i] = (OQCELL *)LEPT_CALLOC(1, sizeof(OQCELL));
+        oqca[i]->n = 0.0;
+    }
+
+        /* Make the tables to map color to the octindex,
+         * of which there are 'ncubes' at 'maxlevel' */
+    makeRGBToIndexTables(&rtab, &gtab, &btab, maxlevel);
+
+        /* Estimate the color distribution; we want to find the
+         * most popular nextra colors at 'maxlevel' */
+    for (i = 0; i < h; i += subsample) {
+        lines = datas + i * wpls;
+        for (j = 0; j < w; j += subsample) {
+            pspixel = lines + j;
+            extractRGBValues(*pspixel, &rval, &gval, &bval);
+            getOctcubeIndexFromRGB(rval, gval, bval, rtab, gtab, btab, &index);
+            oqca[index]->n += 1.0;
+            oqca[index]->octindex = index;
+            oqca[index]->rcum += rval;
+            oqca[index]->gcum += gval;
+            oqca[index]->bcum += bval;
+        }
+    }
+
+        /* Transfer the OQCELL from the array, and order in a heap */
+    lh = lheapCreate(512, L_SORT_DECREASING);
+    for (i = 0; i < ncubes; i++)
+        lheapAdd(lh, oqca[i]);
+    LEPT_FREE(oqca);  /* don't need this array */
+
+        /* Prepare a new OctcubeQuantCell array, with maxcolors cells  */
+    if ((oqca = (OQCELL **)LEPT_CALLOC(maxcolors, sizeof(OQCELL *))) == NULL)
+        return (PIX *)ERROR_PTR("oqca not made", procName, NULL);
+    for (i = 0; i < nbase; i++) {  /* make nbase cells */
+        oqca[i] = (OQCELL *)LEPT_CALLOC(1, sizeof(OQCELL));
+        oqca[i]->n = 0.0;
+    }
+
+        /* Remove the nextra most populated ones, and put them in the array */
+    for (i = 0; i < nextra; i++) {
+        oqc = (OQCELL *)lheapRemove(lh);
+        oqc->n = 0.0;  /* reinit */
+        oqc->rcum = 0;
+        oqc->gcum = 0;
+        oqc->bcum = 0;
+        oqca[nbase + i] = oqc;  /* store it in the array */
+    }
+
+        /* Destroy the heap and its remaining contents */
+    lheapDestroy(&lh, TRUE);
+
+        /* Generate a lookup table from octindex at maxlevel
+         * to color table index */
+    if ((lut1 = (l_int32 *)LEPT_CALLOC(ncubes, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("lut1 not made", procName, NULL);
+    for (i = 0; i < nextra; i++)
+        lut1[oqca[nbase + i]->octindex] = nbase + i;
+    for (index = 0; index < ncubes; index++) {
+        if (lut1[index] == 0)  /* not one of the extras; need to assign */
+            lut1[index] = index >> 3;  /* remove the least significant bits */
+/*        fprintf(stderr, "lut1[%d] = %d\n", index, lut1[index]); */
+    }
+
+        /* Go through the entire image, gathering statistics and
+         * assigning pixels to their quantized value */
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            pspixel = lines + j;
+            extractRGBValues(*pspixel, &rval, &gval, &bval);
+            getOctcubeIndexFromRGB(rval, gval, bval, rtab, gtab, btab, &index);
+/*            fprintf(stderr, "rval = %d, gval = %d, bval = %d, index = %d\n",
+                    rval, gval, bval, index); */
+            val = lut1[index];
+            switch (bpp) {
+            case 4:
+                SET_DATA_QBIT(lined, j, val);
+                break;
+            case 8:
+                SET_DATA_BYTE(lined, j, val);
+                break;
+            default:
+                return (PIX *)ERROR_PTR("bpp not 4 or 8!", procName, NULL);
+                break;
+            }
+            oqca[val]->n += 1.0;
+            oqca[val]->rcum += rval;
+            oqca[val]->gcum += gval;
+            oqca[val]->bcum += bval;
+        }
+    }
+
+        /* Compute averages, set up a colormap, and make a second
+         * lut that converts from the color values currently in
+         * the image to a minimal set */
+    if ((lut2 = (l_int32 *)LEPT_CALLOC(ncubes, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("lut2 not made", procName, NULL);
+    cmap = pixcmapCreate(bpp);
+    pixSetColormap(pixd, cmap);
+    for (i = 0, index = 0; i < maxcolors; i++) {
+        oqc = oqca[i];
+        lut2[i] = index;
+        if (oqc->n == 0)  /* no occupancy; don't bump up index */
+            continue;
+        oqc->rval = (l_int32)(oqc->rcum / oqc->n);
+        oqc->gval = (l_int32)(oqc->gcum / oqc->n);
+        oqc->bval = (l_int32)(oqc->bcum / oqc->n);
+        pixcmapAddColor(cmap, oqc->rval, oqc->gval, oqc->bval);
+        index++;
+    }
+/*    pixcmapWriteStream(stderr, cmap); */
+    actualcolors = pixcmapGetCount(cmap);
+/*    fprintf(stderr, "Number of different colors = %d\n", actualcolors); */
+
+        /* Last time through the image; use the lookup table to
+         * remap the pixel value to the minimal colormap */
+    if (actualcolors < maxcolors) {
+        for (i = 0; i < h; i++) {
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                switch (bpp) {
+                case 4:
+                    val = GET_DATA_QBIT(lined, j);
+                    SET_DATA_QBIT(lined, j, lut2[val]);
+                    break;
+                case 8:
+                    val = GET_DATA_BYTE(lined, j);
+                    SET_DATA_BYTE(lined, j, lut2[val]);
+                    break;
+                }
+            }
+        }
+    }
+
+    for (i = 0; i < maxcolors; i++)
+        LEPT_FREE(oqca[i]);
+    LEPT_FREE(oqca);
+    LEPT_FREE(lut1);
+    LEPT_FREE(lut2);
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------------------*
+ *      Mixed color/gray quantization with specified number of colors      *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  pixOctcubeQuantMixedWithGray()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              depth (of output pix)
+ *              graylevels (grayscale)
+ *              delta (threshold for deciding if a pix is color or grayscale)
+ *      Return: pixd (quantized to octcube and gray levels) or null on error
+ *
+ *  Notes:
+ *      (1) Generates a colormapped image, where the colormap table values
+ *          have two components: octcube values representing pixels with
+ *          color content, and grayscale values for the rest.
+ *      (2) The threshold (delta) is the maximum allowable difference of
+ *          the max abs value of | r - g |, | r - b | and | g - b |.
+ *      (3) The octcube values are the averages of all pixels that are
+ *          found in the octcube, and that are far enough from gray to
+ *          be considered color.  This can roughly be visualized as all
+ *          the points in the rgb color cube that are not within a "cylinder"
+ *          of diameter approximately 'delta' along the main diagonal.
+ *      (4) We want to guarantee full coverage of the rgb color space; thus,
+ *          if the output depth is 4, the octlevel is 1 (2 x 2 x 2 = 8 cubes)
+ *          and if the output depth is 8, the octlevel is 2 (4 x 4 x 4
+ *          = 64 cubes).
+ *      (5) Consequently, we have the following constraint on the number
+ *          of allowed gray levels: for 4 bpp, 8; for 8 bpp, 192.
+ */
+PIX *
+pixOctcubeQuantMixedWithGray(PIX     *pixs,
+                             l_int32  depth,
+                             l_int32  graylevels,
+                             l_int32  delta)
+{
+l_int32    w, h, wpls, wpld, i, j, size, octlevels;
+l_int32    rval, gval, bval, del, val, midval;
+l_int32   *carray, *rarray, *garray, *barray;
+l_int32   *tabval;
+l_uint32   octindex;
+l_uint32  *rtab, *gtab, *btab;
+l_uint32  *lines, *lined, *datas, *datad;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixOctcubeQuantMixedWithGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (depth == 4) {
+        octlevels = 1;
+        size = 8;   /* 2 ** 3 */
+        if (graylevels > 8)
+            return (PIX *)ERROR_PTR("max 8 gray levels", procName, NULL);
+    } else if (depth == 8) {
+        octlevels = 2;
+        size = 64;   /* 2 ** 6 */
+        if (graylevels > 192)
+            return (PIX *)ERROR_PTR("max 192 gray levels", procName, NULL);
+    } else {
+        return (PIX *)ERROR_PTR("output depth not 4 or 8 bpp", procName, NULL);
+    }
+
+        /* Make octcube index tables */
+    if (makeRGBToIndexTables(&rtab, &gtab, &btab, octlevels))
+        return (PIX *)ERROR_PTR("tables not made", procName, NULL);
+
+        /* Make octcube arrays for storing points in each cube */
+    if ((carray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("carray not made", procName, NULL);
+    if ((rarray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("rarray not made", procName, NULL);
+    if ((garray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("garray not made", procName, NULL);
+    if ((barray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("barray not made", procName, NULL);
+
+        /* Make lookup table, using computed thresholds  */
+    if ((tabval = makeGrayQuantIndexTable(graylevels)) == NULL)
+        return (PIX *)ERROR_PTR("tabval not made", procName, NULL);
+
+        /* Make colormapped output pixd */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w, h, depth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    cmap = pixcmapCreate(depth);
+    for (j = 0; j < size; j++)  /* reserve octcube colors */
+        pixcmapAddColor(cmap, 1, 1, 1);  /* a color that won't be used */
+    for (j = 0; j < graylevels; j++) {  /* set grayscale colors */
+        val = (255 * j) / (graylevels - 1);
+        pixcmapAddColor(cmap, val, val, val);
+    }
+    pixSetColormap(pixd, cmap);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+
+        /* Go through src image: assign dest pixels to colormap values
+         * and compute average colors in each occupied octcube */
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            if (rval > gval) {
+                if (gval > bval) {   /* r > g > b */
+                    del = rval - bval;
+                    midval = gval;
+                } else if (rval > bval) {  /* r > b > g */
+                    del = rval - gval;
+                    midval = bval;
+                } else {  /* b > r > g */
+                    del = bval - gval;
+                    midval = rval;
+                }
+            } else {  /* gval >= rval */
+                if (rval > bval) {  /* g > r > b */
+                    del = gval - bval;
+                    midval = rval;
+                } else if (gval > bval) {  /* g > b > r */
+                    del = gval - rval;
+                    midval = bval;
+                } else {  /* b > g > r */
+                    del = bval - rval;
+                    midval = gval;
+                }
+            }
+            if (del > delta) {  /* assign to color */
+                octindex = rtab[rval] | gtab[gval] | btab[bval];
+                carray[octindex]++;
+                rarray[octindex] += rval;
+                garray[octindex] += gval;
+                barray[octindex] += bval;
+                if (depth == 4)
+                    SET_DATA_QBIT(lined, j, octindex);
+                else  /* depth == 8 */
+                    SET_DATA_BYTE(lined, j, octindex);
+            } else {  /* assign to grayscale */
+                val = size + tabval[midval];
+                if (depth == 4)
+                    SET_DATA_QBIT(lined, j, val);
+                else  /* depth == 8 */
+                    SET_DATA_BYTE(lined, j, val);
+            }
+        }
+    }
+
+        /* Average the colors in each bin and reset the colormap */
+    for (i = 0; i < size; i++) {
+        if (carray[i] > 0) {
+            rarray[i] /= carray[i];
+            garray[i] /= carray[i];
+            barray[i] /= carray[i];
+            pixcmapResetColor(cmap, i, rarray[i], garray[i], barray[i]);
+        }
+    }
+
+    LEPT_FREE(carray);
+    LEPT_FREE(rarray);
+    LEPT_FREE(garray);
+    LEPT_FREE(barray);
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    LEPT_FREE(tabval);
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------------------*
+ *             Fixed partition octcube quantization with 256 cells         *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  pixFixedOctcubeQuant256()
+ *
+ *      Input:  pixs  (32 bpp; 24-bit color)
+ *              ditherflag  (1 for dithering; 0 for no dithering)
+ *      Return: pixd (8 bit with colormap), or null on error
+ *
+ *  This simple 1-pass color quantization works by breaking the
+ *  color space into 256 pieces, with 3 bits quantized for each of
+ *  red and green, and 2 bits quantized for blue.  We shortchange
+ *  blue because the eye is least sensitive to blue.  This
+ *  division of the color space is into two levels of octrees,
+ *  followed by a further division by 4 (not 8), where both
+ *  blue octrees have been combined in the third level.
+ *
+ *  The color map is generated from the 256 color centers by
+ *  taking the representative color to be the center of the
+ *  cell volume.  This gives a maximum error in the red and
+ *  green values of 16 levels, and a maximum error in the
+ *  blue sample of 32 levels.
+ *
+ *  Each pixel in the 24-bit color image is placed in its containing
+ *  cell, given by the relevant MSbits of the red, green and blue
+ *  samples.  An error-diffusion dithering is performed on each
+ *  color sample to give the appearance of good average local color.
+ *  Dithering is required; without it, the contouring and visible
+ *  color errors are very bad.
+ *
+ *  I originally implemented this algorithm in two passes,
+ *  where the first pass was used to compute the weighted average
+ *  of each sample in each pre-allocated region of color space.
+ *  The idea was to use these centroids in the dithering algorithm
+ *  of the second pass, to reduce the average error that was
+ *  being dithered.  However, with dithering, there is
+ *  virtually no difference, so there is no reason to make the
+ *  first pass.  Consequently, this 1-pass version just assigns
+ *  the pixels to the centers of the pre-allocated cells.
+ *  We use dithering to spread the difference between the sample
+ *  value and the location of the center of the cell.  For speed
+ *  and simplicity, we use integer dithering and propagate only
+ *  to the right, down, and diagonally down-right, with ratios
+ *  3/8, 3/8 and 1/4, respectively.  The results should be nearly
+ *  as good, and a bit faster, with propagation only to the right
+ *  and down.
+ *
+ *  The algorithm is very fast, because there is no search,
+ *  only fast generation of the cell index for each pixel.
+ *  We use a simple mapping from the three 8 bit rgb samples
+ *  to the 8 bit cell index; namely, (r7 r6 r5 g7 g6 g5 b7 b6).
+ *  This is not in an octcube format, but it doesn't matter.
+ *  There are no storage requirements.  We could keep a
+ *  running average of the center of each sample in each
+ *  cluster, rather than using the center of the cell, but
+ *  this is just extra work, esp. with dithering.
+ *
+ *  This method gives surprisingly good results with dithering.
+ *  However, without dithering, the loss of color accuracy is
+ *  evident in regions that are very light or that have subtle
+ *  blending of colors.
+ */
+PIX *
+pixFixedOctcubeQuant256(PIX     *pixs,
+                        l_int32  ditherflag)
+{
+l_uint8    index;
+l_int32    rval, gval, bval;
+l_int32    w, h, wpls, wpld, i, j, cindex;
+l_uint32  *rtab, *gtab, *btab;
+l_int32   *itab;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixFixedOctcubeQuant256");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+
+        /* Do not dither if image is very small */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (w < MIN_DITHER_SIZE && h < MIN_DITHER_SIZE && ditherflag == 1) {
+        L_INFO("Small image: dithering turned off\n", procName);
+        ditherflag = 0;
+    }
+
+        /* Find the centers of the 256 cells, each of which represents
+         * the 3 MSBits of the red and green components, and the
+         * 2 MSBits of the blue component.  This gives a mapping
+         * from a "cube index" to the rgb values.  Save all 256
+         * rgb values of these centers in a colormap.
+         * For example, to get the red color of the cell center,
+         * you take the 3 MSBits of to the index and add the
+         * offset to the center of the cell, which is 0x10. */
+    cmap = pixcmapCreate(8);
+    for (cindex = 0; cindex < 256; cindex++) {
+        rval = (cindex & 0xe0) | 0x10;
+        gval = ((cindex << 3) & 0xe0) | 0x10;
+        bval = ((cindex << 6) & 0xc0) | 0x20;
+        pixcmapAddColor(cmap, rval, gval, bval);
+    }
+
+        /* Make output 8 bpp palette image */
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixSetColormap(pixd, cmap);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* Set dest pix values to colortable indices */
+    if (ditherflag == 0) {   /* no dithering */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                extractRGBValues(lines[j], &rval, &gval, &bval);
+                index = (rval & 0xe0) | ((gval >> 3) & 0x1c) | (bval >> 6);
+                SET_DATA_BYTE(lined, j, index);
+            }
+        }
+    } else {  /* ditherflag == 1 */
+            /* Set up conversion tables from rgb directly to the colormap
+             * index.  However, the dithering function expects these tables
+             * to generate an octcube index (+1), and the table itab[] to
+             * convert to the colormap index.  So we make a trivial
+             * itab[], that simply compensates for the -1 in
+             * pixDitherOctindexWithCmap().   No cap is required on
+             * the propagated difference.  */
+        rtab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32));
+        gtab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32));
+        btab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32));
+        itab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+        for (i = 0; i < 256; i++) {
+            rtab[i] = i & 0xe0;
+            gtab[i] = (i >> 3) & 0x1c;
+            btab[i] = i >> 6;
+            itab[i] = i + 1;
+        }
+        pixDitherOctindexWithCmap(pixs, pixd, rtab, gtab, btab, itab,
+                                  FIXED_DIF_CAP);
+        LEPT_FREE(rtab);
+        LEPT_FREE(gtab);
+        LEPT_FREE(btab);
+        LEPT_FREE(itab);
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *           Nearly exact quantization for images with few colors            *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixFewColorsOctcubeQuant1()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              level (significant bits for each of RGB; valid in [1...6])
+ *      Return: pixd (quantized to octcube) or null on error
+ *
+ *  Notes:
+ *      (1) Generates a colormapped image, where the colormap table values
+ *          are the averages of all pixels that are found in the octcube.
+ *      (2) This fails if there are more than 256 colors (i.e., more
+ *          than 256 occupied octcubes).
+ *      (3) Often level 3 (512 octcubes) will succeed because not more
+ *          than half of them are occupied with 1 or more pixels.
+ *      (4) The depth of the result, which is either 2, 4 or 8 bpp,
+ *          is the minimum required to hold the number of colors that
+ *          are found.
+ *      (5) This can be useful for quantizing orthographically generated
+ *          images such as color maps, where there may be more than 256 colors
+ *          because of aliasing or jpeg artifacts on text or lines, but
+ *          there are a relatively small number of solid colors.  Then,
+ *          use with level = 3 can often generate a compact and accurate
+ *          representation of the original RGB image.  For this purpose,
+ *          it is better than pixFewColorsOctcubeQuant2(), because it
+ *          uses the average value of pixels in the octcube rather
+ *          than the first found pixel.  It is also simpler to use,
+ *          because it generates the histogram internally.
+ */
+PIX *
+pixFewColorsOctcubeQuant1(PIX     *pixs,
+                          l_int32  level)
+{
+l_int32    w, h, wpls, wpld, i, j, depth, size, ncolors, index;
+l_int32    rval, gval, bval;
+l_int32   *carray, *rarray, *garray, *barray;
+l_uint32   octindex;
+l_uint32  *rtab, *gtab, *btab;
+l_uint32  *lines, *lined, *datas, *datad, *pspixel;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixFewColorsOctcubeQuant1");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (level < 1 || level > 6)
+        return (PIX *)ERROR_PTR("invalid level", procName, NULL);
+
+    if (octcubeGetCount(level, &size))  /* array size = 2 ** (3 * level) */
+        return (PIX *)ERROR_PTR("size not returned", procName, NULL);
+    if (makeRGBToIndexTables(&rtab, &gtab, &btab, level))
+        return (PIX *)ERROR_PTR("tables not made", procName, NULL);
+
+    if ((carray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("carray not made", procName, NULL);
+    if ((rarray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("rarray not made", procName, NULL);
+    if ((garray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("garray not made", procName, NULL);
+    if ((barray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("barray not made", procName, NULL);
+
+        /* Place the pixels in octcube leaves. */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = NULL;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        for (j = 0; j < w; j++) {
+            pspixel = lines + j;
+            extractRGBValues(*pspixel, &rval, &gval, &bval);
+            octindex = rtab[rval] | gtab[gval] | btab[bval];
+            carray[octindex]++;
+            rarray[octindex] += rval;
+            garray[octindex] += gval;
+            barray[octindex] += bval;
+        }
+    }
+
+        /* Find the number of different colors */
+    for (i = 0, ncolors = 0; i < size; i++) {
+        if (carray[i] > 0)
+            ncolors++;
+    }
+    if (ncolors > 256) {
+        L_WARNING("%d colors found; more than 256\n", procName, ncolors);
+        goto array_cleanup;
+    }
+    if (ncolors <= 4)
+        depth = 2;
+    else if (ncolors <= 16)
+        depth = 4;
+    else
+        depth = 8;
+
+        /* Average the colors in each octcube leaf and add to colormap table;
+         * then use carray to hold the colormap index + 1  */
+    cmap = pixcmapCreate(depth);
+    for (i = 0, index = 0; i < size; i++) {
+        if (carray[i] > 0) {
+            rarray[i] /= carray[i];
+            garray[i] /= carray[i];
+            barray[i] /= carray[i];
+            pixcmapAddColor(cmap, rarray[i], garray[i], barray[i]);
+            carray[i] = index + 1;  /* to avoid storing 0 */
+            index++;
+        }
+    }
+
+    pixd = pixCreate(w, h, depth);
+    pixSetColormap(pixd, cmap);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            pspixel = lines + j;
+            extractRGBValues(*pspixel, &rval, &gval, &bval);
+            octindex = rtab[rval] | gtab[gval] | btab[bval];
+            switch (depth)
+            {
+            case 2:
+                SET_DATA_DIBIT(lined, j, carray[octindex] - 1);
+                break;
+            case 4:
+                SET_DATA_QBIT(lined, j, carray[octindex] - 1);
+                break;
+            case 8:
+                SET_DATA_BYTE(lined, j, carray[octindex] - 1);
+                break;
+            default:
+                L_WARNING("shouldn't get here\n", procName);
+            }
+        }
+    }
+
+array_cleanup:
+    LEPT_FREE(carray);
+    LEPT_FREE(rarray);
+    LEPT_FREE(garray);
+    LEPT_FREE(barray);
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    return pixd;
+}
+
+
+/*!
+ *  pixFewColorsOctcubeQuant2()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              level (of octcube indexing, for histogram: 3, 4, 5, 6)
+ *              na (histogram of pixel occupation in octree leaves at
+ *                  given level)
+ *              ncolors (number of occupied octree leaves at given level)
+ *              &nerrors (<optional return> num of pixels not exactly
+ *                        represented in the colormap)
+ *      Return: pixd (2, 4 or 8 bpp with colormap), or null on error
+ *
+ *  Notes:
+ *      (1) Generates a colormapped image, where the colormap table values
+ *          are the averages of all pixels that are found in the octcube.
+ *      (2) This fails if there are more than 256 colors (i.e., more
+ *          than 256 occupied octcubes).
+ *      (3) Often level 3 (512 octcubes) will succeed because not more
+ *          than half of them are occupied with 1 or more pixels.
+ *      (4) For an image with not more than 256 colors, it is unlikely
+ *          that two pixels of different color will fall in the same
+ *          octcube at level = 4.   However it is possible, and this
+ *          function optionally returns @nerrors, the number of pixels
+ *          where, because more than one color is in the same octcube,
+ *          the pixel color is not exactly reproduced in the colormap.
+ *          The colormap for an occupied leaf of the octree contains
+ *          the color of the first pixel encountered in that octcube.
+ *      (5) This differs from pixFewColorsOctcubeQuant1(), which also
+ *          requires not more than 256 occupied leaves, but represents
+ *          the color of each leaf by an average over the pixels in
+ *          that leaf.  This also requires precomputing the histogram
+ *          of occupied octree leaves, which is generated using
+ *          pixOctcubeHistogram().
+ *      (6) This is used in pixConvertRGBToColormap() for images that
+ *          are determined, by their histogram, to have relatively few
+ *          colors.  This typically happens with orthographically
+ *          produced images (as oppopsed to natural images), where
+ *          it is expected that most of the pixels within a leaf
+ *          octcube have exactly the same color, and quantization to
+ *          that color is lossless.
+ */
+PIX *
+pixFewColorsOctcubeQuant2(PIX      *pixs,
+                          l_int32   level,
+                          NUMA     *na,
+                          l_int32   ncolors,
+                          l_int32  *pnerrors)
+{
+l_int32    w, h, wpls, wpld, i, j, nerrors;
+l_int32    ncubes, depth, cindex, oval;
+l_int32    rval, gval, bval;
+l_int32   *octarray;
+l_uint32   octindex;
+l_uint32  *rtab, *gtab, *btab;
+l_uint32  *lines, *lined, *datas, *datad, *ppixel;
+l_uint32  *colorarray;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixFewColorsOctcubeQuant2");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (level < 3 || level > 6)
+        return (PIX *)ERROR_PTR("level not in {4, 5, 6}", procName, NULL);
+    if (ncolors > 256)
+        return (PIX *)ERROR_PTR("ncolors > 256", procName, NULL);
+    if (pnerrors)
+        *pnerrors = UNDEF;
+
+        /* Represent the image with a set of leaf octcubes
+         * at 'level', one for each color. */
+    if (makeRGBToIndexTables(&rtab, &gtab, &btab, level))
+        return (PIX *)ERROR_PTR("tables not made", procName, NULL);
+
+        /* Determine the output depth from the number of colors */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if (ncolors <= 4)
+        depth = 2;
+    else if (ncolors <= 16)
+        depth = 4;
+    else  /* ncolors <= 256 */
+        depth = 8;
+
+    if ((pixd = pixCreate(w, h, depth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* The octarray will give a ptr from the octcube to the colorarray */
+    ncubes = numaGetCount(na);
+    if ((octarray = (l_int32 *)LEPT_CALLOC(ncubes, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("octarray not made", procName, NULL);
+
+        /* The colorarray will hold the colors of the first pixel
+         * that lands in the leaf octcube.  After filling, it is
+         * used to generate the colormap.  */
+    if ((colorarray = (l_uint32 *)LEPT_CALLOC(ncolors + 1, sizeof(l_uint32)))
+            == NULL)
+        return (PIX *)ERROR_PTR("colorarray not made", procName, NULL);
+
+        /* For each pixel, get the octree index for its leaf octcube.
+         * Check if a pixel has already been found in this octcube.
+         *   - If not yet found, save that color in the colorarray
+         *     and save the cindex in the octarray.
+         *   - If already found, compare the pixel color with the
+         *     color in the colorarray, and note if it differs.
+         * Then set the dest pixel value to the cindex - 1, which
+         * will be the cmap index for this color.  */
+    cindex = 1;  /* start with 1 */
+    nerrors = 0;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            ppixel = lines + j;
+            extractRGBValues(*ppixel, &rval, &gval, &bval);
+            octindex = rtab[rval] | gtab[gval] | btab[bval];
+            oval = octarray[octindex];
+            if (oval == 0) {
+                octarray[octindex] = cindex;
+                colorarray[cindex] = *ppixel;
+                setPixelLow(lined, j, depth, cindex - 1);
+                cindex++;
+            } else {  /* already have seen this color; is it unique? */
+                setPixelLow(lined, j, depth, oval - 1);
+                if (colorarray[oval] != *ppixel)
+                    nerrors++;
+            }
+        }
+    }
+    if (pnerrors)
+        *pnerrors = nerrors;
+
+#if  DEBUG_FEW_COLORS
+    fprintf(stderr, "ncubes = %d, ncolors = %d\n", ncubes, ncolors);
+    for (i = 0; i < ncolors; i++)
+        fprintf(stderr, "color[%d] = %x\n", i, colorarray[i + 1]);
+#endif  /* DEBUG_FEW_COLORS */
+
+        /* Make the colormap. */
+    cmap = pixcmapCreate(depth);
+    for (i = 0; i < ncolors; i++) {
+        ppixel = colorarray + i + 1;
+        extractRGBValues(*ppixel, &rval, &gval, &bval);
+        pixcmapAddColor(cmap, rval, gval, bval);
+    }
+    pixSetColormap(pixd, cmap);
+
+    LEPT_FREE(octarray);
+    LEPT_FREE(colorarray);
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    return pixd;
+}
+
+
+/*!
+ *  pixFewColorsOctcubeQuantMixed()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              level (significant octcube bits for each of RGB;
+ *                     valid in [1...6]; use 0 for default)
+ *              darkthresh (threshold near black; if the lightest component
+ *                          is below this, the pixel is not considered to
+ *                          be gray or color; uses 0 for default)
+ *              lightthresh (threshold near white; if the darkest component
+ *                           is above this, the pixel is not considered to
+ *                           be gray or color; use 0 for default)
+ *              diffthresh (thresh for the max difference between component
+ *                          values; for differences below this, the pixel
+ *                          is considered to be gray; use 0 for default)
+ *                          considered gray; use 0 for default)
+ *              minfract (min fraction of pixels for gray histo bin;
+ *                        use 0.0 for default)
+ *              maxspan (max size of gray histo bin; use 0 for default)
+ *      Return: pixd (8 bpp, quantized to octcube for pixels that are
+ *                    not gray; gray pixels are quantized separately
+ *                    over the full gray range), or null on error
+ *
+ *  Notes:
+ *      (1) First runs pixFewColorsOctcubeQuant1().  If this succeeds,
+ *          it separates the color from gray(ish) entries in the cmap,
+ *          and re-quantizes the gray pixels.  The result has some pixels
+ *          in color and others in gray.
+ *      (2) This fails if there are more than 256 colors (i.e., more
+ *          than 256 occupied octcubes in the color quantization).
+ *      (3) Level 3 (512 octcubes) will usually succeed because not more
+ *          than half of them are occupied with 1 or more pixels.
+ *      (4) This uses the criterion from pixColorFraction() for deciding
+ *          if a colormap entry is color; namely, if the color components
+ *          are not too close to either black or white, and the maximum
+ *          difference between component values equals or exceeds a threshold.
+ *      (5) For quantizing the gray pixels, it uses a histogram-based
+ *          method where input parameters determining the buckets are
+ *          the minimum population fraction and the maximum allowed size.
+ *      (6) Recommended input parameters are:
+ *              @level:  3 or 4  (3 is default)
+ *              @darkthresh:  20
+ *              @lightthresh: 244
+ *              @diffthresh: 20
+ *              @minfract: 0.05
+ *              @maxspan: 15
+ *          These numbers are intended to be conservative (somewhat over-
+ *          sensitive) in color detection,  It's usually better to pay
+ *          extra with octcube quantization of a grayscale image than
+ *          to use grayscale quantization on an image that has some
+ *          actual color.  Input 0 on any of these to get the default.
+ *      (7) This can be useful for quantizing orthographically generated
+ *          images such as color maps, where there may be more than 256 colors
+ *          because of aliasing or jpeg artifacts on text or lines, but
+ *          there are a relatively small number of solid colors.  It usually
+ *          gives results that are better than pixOctcubeQuantMixedWithGray(),
+ *          both in size and appearance.  But it is a bit slower.
+ */
+PIX *
+pixFewColorsOctcubeQuantMixed(PIX       *pixs,
+                              l_int32    level,
+                              l_int32    darkthresh,
+                              l_int32    lightthresh,
+                              l_int32    diffthresh,
+                              l_float32  minfract,
+                              l_int32    maxspan)
+{
+l_int32    i, j, w, h, wplc, wplm, wpld, ncolors, index;
+l_int32    rval, gval, bval, val, minval, maxval;
+l_int32   *lut;
+l_uint32  *datac, *datam, *datad, *linec, *linem, *lined;
+PIX       *pixc, *pixm, *pixg, *pixd;
+PIXCMAP   *cmap, *cmapd;
+
+    PROCNAME("pixFewColorsOctcubeQuantMixed");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+    if (level <= 0) level = 3;
+    if (level > 6)
+        return (PIX *)ERROR_PTR("invalid level", procName, NULL);
+    if (darkthresh <= 0) darkthresh = 20;
+    if (lightthresh <= 0) lightthresh = 244;
+    if (diffthresh <= 0) diffthresh = 20;
+    if (minfract <= 0.0) minfract = 0.05;
+    if (maxspan <= 2) maxspan = 15;
+
+        /* Start with a simple fixed octcube quantizer. */
+    if ((pixc = pixFewColorsOctcubeQuant1(pixs, level)) == NULL)
+        return (PIX *)ERROR_PTR("too many colors", procName, NULL);
+
+        /* Identify and save color entries in the colormap.  Set up a LUT
+         * that returns -1 for any gray pixel. */
+    cmap = pixGetColormap(pixc);
+    ncolors = pixcmapGetCount(cmap);
+    cmapd = pixcmapCreate(8);
+    lut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    for (i = 0; i < 256; i++)
+        lut[i] = -1;
+    for (i = 0, index = 0; i < ncolors; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        minval = L_MIN(rval, gval);
+        minval = L_MIN(minval, bval);
+        if (minval > lightthresh)  /* near white */
+            continue;
+        maxval = L_MAX(rval, gval);
+        maxval = L_MAX(maxval, bval);
+        if (maxval < darkthresh)  /* near black */
+            continue;
+
+            /* Use the max diff between components to test for color */
+        if (maxval - minval >= diffthresh) {
+            pixcmapAddColor(cmapd, rval, gval, bval);
+            lut[i] = index;
+            index++;
+        }
+    }
+
+        /* Generate dest pix with just the color pixels set to their
+         * colormap indices.  At the same time, make a 1 bpp mask
+         * of the non-color pixels */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixCreate(w, h, 8);
+    pixSetColormap(pixd, cmapd);
+    pixm = pixCreate(w, h, 1);
+    datac = pixGetData(pixc);
+    datam = pixGetData(pixm);
+    datad = pixGetData(pixd);
+    wplc = pixGetWpl(pixc);
+    wplm = pixGetWpl(pixm);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        linec = datac + i * wplc;
+        linem = datam + i * wplm;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(linec, j);
+            if (lut[val] == -1)
+                SET_DATA_BIT(linem, j);
+            else
+                SET_DATA_BYTE(lined, j, lut[val]);
+        }
+    }
+
+        /* Fill in the gray values.  Use a grayscale version of pixs
+         * as input, along with the mask over the actual gray pixels. */
+    pixg = pixConvertTo8(pixs, 0);
+    pixGrayQuantFromHisto(pixd, pixg, pixm, minfract, maxspan);
+
+    LEPT_FREE(lut);
+    pixDestroy(&pixc);
+    pixDestroy(&pixm);
+    pixDestroy(&pixg);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *           Fixed partition octcube quantization with RGB output            *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixFixedOctcubeQuantGenRGB()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              level (significant bits for each of r,g,b)
+ *      Return: pixd (rgb; quantized to octcube centers), or null on error
+ *
+ *  Notes:
+ *      (1) Unlike the other color quantization functions, this one
+ *          generates an rgb image.
+ *      (2) The pixel values are quantized to the center of each octcube
+ *          (at the specified level) containing the pixel.  They are
+ *          not quantized to the average of the pixels in that octcube.
+ */
+PIX *
+pixFixedOctcubeQuantGenRGB(PIX     *pixs,
+                           l_int32  level)
+{
+l_int32    w, h, wpls, wpld, i, j;
+l_int32    rval, gval, bval;
+l_uint32   octindex;
+l_uint32  *rtab, *gtab, *btab;
+l_uint32  *lines, *lined, *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixFixedOctcubeQuantGenRGB");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (level < 1 || level > 6)
+        return (PIX *)ERROR_PTR("level not in {1,...6}", procName, NULL);
+
+    if (makeRGBToIndexTables(&rtab, &gtab, &btab, level))
+        return (PIX *)ERROR_PTR("tables not made", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixCreate(w, h, 32);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            octindex = rtab[rval] | gtab[gval] | btab[bval];
+            getRGBFromOctcube(octindex, level, &rval, &gval, &bval);
+            composeRGBPixel(rval, gval, bval, lined + j);
+        }
+    }
+
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *          Color quantize RGB image using existing colormap        *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixQuantFromCmap()
+ *
+ *      Input:  pixs  (8 bpp grayscale without cmap, or 32 bpp rgb)
+ *              cmap  (to quantize to; insert copy into dest pix)
+ *              mindepth (minimum depth of pixd: can be 2, 4 or 8 bpp)
+ *              level (of octcube used for finding nearest color in cmap)
+ *              metric (L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE)
+ *      Return: pixd  (2, 4 or 8 bpp, colormapped), or null on error
+ *
+ *  Notes:
+ *      (1) This is a top-level wrapper for quantizing either grayscale
+ *          or rgb images to a specified colormap.
+ *      (2) The actual output depth is constrained by @mindepth and
+ *          by the number of colors in @cmap.
+ *      (3) For grayscale, @level and @metric are ignored.
+ *      (4) If the cmap has color and pixs is grayscale, the color is
+ *          removed from the cmap before quantizing pixs.
+ */
+PIX *
+pixQuantFromCmap(PIX      *pixs,
+                 PIXCMAP  *cmap,
+                 l_int32   mindepth,
+                 l_int32   level,
+                 l_int32   metric)
+{
+l_int32  d;
+
+    PROCNAME("pixQuantFromCmap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (mindepth != 2 && mindepth != 4 && mindepth != 8)
+        return (PIX *)ERROR_PTR("invalid mindepth", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d == 8)
+        return pixGrayQuantFromCmap(pixs, cmap, mindepth);
+    else if (d == 32)
+        return pixOctcubeQuantFromCmap(pixs, cmap, mindepth,
+                                       level, metric);
+    else
+        return (PIX *)ERROR_PTR("d not 8 or 32 bpp", procName, NULL);
+}
+
+
+
+/*!
+ *  pixOctcubeQuantFromCmap()
+ *
+ *      Input:  pixs  (32 bpp rgb)
+ *              cmap  (to quantize to; insert copy into dest pix)
+ *              mindepth (minimum depth of pixd: can be 2, 4 or 8 bpp)
+ *              level (of octcube used for finding nearest color in cmap)
+ *              metric (L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE)
+ *      Return: pixd  (2, 4 or 8 bpp, colormapped), or null on error
+ *
+ *  Notes:
+ *      (1) In typical use, we are doing an operation, such as
+ *          interpolative scaling, on a colormapped pix, where it is
+ *          necessary to remove the colormap before the operation.
+ *          We then want to re-quantize the RGB result using the same
+ *          colormap.
+ *      (2) The level is used to divide the color space into octcubes.
+ *          Each input pixel is, in effect, placed at the center of an
+ *          octcube at the given level, and it is mapped into the
+ *          exact color (given in the colormap) that is the closest
+ *          to that location.  We need to know that distance, for each color
+ *          in the colormap.  The higher the level of the octtree, the smaller
+ *          the octcubes in the color space, and hence the more accurately
+ *          we can determine the closest color in the colormap; however,
+ *          the size of the LUT, which is the total number of octcubes,
+ *          increases by a factor of 8 for each increase of 1 level.
+ *          The time required to acquire a level 4 mapping table, which has
+ *          about 4K entries, is less than 1 msec, so that is the
+ *          recommended minimum size to be used.  At that size, the
+ *          octcubes have their centers 16 units apart in each (r,g,b)
+ *          direction.  If two colors are in the same octcube, the one
+ *          closest to the center will always be chosen.  The maximum
+ *          error for any component occurs when the correct color is
+ *          at a cube corner and there is an incorrect color just inside
+ *          the cube next to the opposite corner, giving an error of
+ *          14 units (out of 256) for each component.   Using a level 5
+ *          mapping table reduces the maximum error to 6 units.
+ *      (3) Typically you should use the Euclidean metric, because the
+ *          resulting voronoi cells (which are generated using the actual
+ *          colormap values as seeds) are convex for Euclidean distance
+ *          but not for Manhattan distance.  In terms of the octcubes,
+ *          convexity of the voronoi cells means that if the 8 corners
+ *          of any cube (of which the octcubes are special cases)
+ *          are all within a cell, then every point in the cube will
+ *          lie within the cell.
+ *      (4) The depth of the output pixd is equal to the maximum of
+ *          (a) @mindepth and (b) the minimum (2, 4 or 8 bpp) necessary
+ *          to hold the indices in the colormap.
+ *      (5) We build a mapping table from octcube to colormap index so
+ *          that this function can run in a time (otherwise) independent
+ *          of the number of colors in the colormap.  This avoids a
+ *          brute-force search for the closest colormap color to each
+ *          pixel in the image.
+ *      (6) This is similar to the function pixAssignToNearestColor()
+ *          used for color segmentation.
+ *      (7) Except for very small images or when using level > 4,
+ *          it takes very little time to generate the tables,
+ *          compared to the generation of the colormapped dest pix,
+ *          so one would not typically use the low-level version.
+ */
+PIX *
+pixOctcubeQuantFromCmap(PIX      *pixs,
+                        PIXCMAP  *cmap,
+                        l_int32   mindepth,
+                        l_int32   level,
+                        l_int32   metric)
+{
+l_int32   *cmaptab;
+l_uint32  *rtab, *gtab, *btab;
+PIX       *pixd;
+
+    PROCNAME("pixOctcubeQuantFromCmap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (!cmap)
+        return (PIX *)ERROR_PTR("cmap not defined", procName, NULL);
+    if (mindepth != 2 && mindepth != 4 && mindepth != 8)
+        return (PIX *)ERROR_PTR("invalid mindepth", procName, NULL);
+    if (level < 1 || level > 6)
+        return (PIX *)ERROR_PTR("level not in {1...6}", procName, NULL);
+    if (metric != L_MANHATTAN_DISTANCE && metric != L_EUCLIDEAN_DISTANCE)
+        return (PIX *)ERROR_PTR("invalid metric", procName, NULL);
+
+        /* Set up the tables to map rgb to the nearest colormap index */
+    if (makeRGBToIndexTables(&rtab, &gtab, &btab, level))
+        return (PIX *)ERROR_PTR("index tables not made", procName, NULL);
+    if ((cmaptab = pixcmapToOctcubeLUT(cmap, level, metric)) == NULL)
+        return (PIX *)ERROR_PTR("cmaptab not made", procName, NULL);
+
+    pixd = pixOctcubeQuantFromCmapLUT(pixs, cmap, mindepth,
+                                      cmaptab, rtab, gtab, btab);
+
+    LEPT_FREE(cmaptab);
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    return pixd;
+}
+
+
+/*!
+ *  pixOctcubeQuantFromCmapLUT()
+ *
+ *      Input:  pixs  (32 bpp rgb)
+ *              cmap  (to quantize to; insert copy into dest pix)
+ *              mindepth (minimum depth of pixd: can be 2, 4 or 8 bpp)
+ *              cmaptab  (table mapping from octindex to colormap index)
+ *              rtab, gtab, btab (tables mapping from RGB to octindex)
+ *      Return: pixd  (2, 4 or 8 bpp, colormapped), or null on error
+ *
+ *  Notes:
+ *      (1) See the notes in the higher-level function
+ *          pixOctcubeQuantFromCmap().  The octcube level for
+ *          the generated octree is specified there, along with
+ *          the distance metric for determining the closest
+ *          color in the colormap to each octcube.
+ *      (2) If the colormap, level and metric information have already
+ *          been used to construct the set of mapping tables,
+ *          this low-level function can be used directly (i.e.,
+ *          independently of pixOctcubeQuantFromCmap()) to build
+ *          a colormapped pix that uses the specified colormap.
+ */
+static PIX *
+pixOctcubeQuantFromCmapLUT(PIX       *pixs,
+                           PIXCMAP   *cmap,
+                           l_int32    mindepth,
+                           l_int32   *cmaptab,
+                           l_uint32  *rtab,
+                           l_uint32  *gtab,
+                           l_uint32  *btab)
+{
+l_int32    i, j, w, h, depth, wpls, wpld;
+l_int32    rval, gval, bval, index;
+l_uint32   octindex;
+l_uint32  *lines, *lined, *datas, *datad;
+PIX       *pixd;
+PIXCMAP   *cmapc;
+
+    PROCNAME("pixOctcubeQuantFromCmapLUT");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (!cmap)
+        return (PIX *)ERROR_PTR("cmap not defined", procName, NULL);
+    if (mindepth != 2 && mindepth != 4 && mindepth != 8)
+        return (PIX *)ERROR_PTR("invalid mindepth", procName, NULL);
+    if (!rtab || !gtab || !btab || !cmaptab)
+        return (PIX *)ERROR_PTR("tables not all defined", procName, NULL);
+
+        /* Init dest pix (with minimum bpp depending on cmap) */
+    pixcmapGetMinDepth(cmap, &depth);
+    depth = L_MAX(depth, mindepth);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w, h, depth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    cmapc = pixcmapCopy(cmap);
+    pixSetColormap(pixd, cmapc);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+        /* Insert the colormap index of the color nearest to the input pixel */
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+                /* Map from rgb to octcube index */
+            getOctcubeIndexFromRGB(rval, gval, bval, rtab, gtab, btab,
+                                   &octindex);
+                /* Map from octcube index to nearest colormap index */
+            index = cmaptab[octindex];
+            if (depth == 2)
+                SET_DATA_DIBIT(lined, j, index);
+            else if (depth == 4)
+                SET_DATA_QBIT(lined, j, index);
+            else  /* depth == 8 */
+                SET_DATA_BYTE(lined, j, index);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                       Generation of octcube histogram                     *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixOctcubeHistogram()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              level (significant bits for each of RGB; valid in [1...6])
+ *              &ncolors (<optional return> number of occupied cubes)
+ *      Return: numa (histogram of color pixels, or null on error)
+ *
+ *  Notes:
+ *      (1) Input NULL for &ncolors to prevent computation and return value.
+ */
+NUMA *
+pixOctcubeHistogram(PIX      *pixs,
+                    l_int32   level,
+                    l_int32  *pncolors)
+{
+l_int32     size, i, j, w, h, wpl, ncolors, val;
+l_int32     rval, gval, bval;
+l_uint32    octindex;
+l_uint32   *rtab, *gtab, *btab;
+l_uint32   *data, *line;
+l_float32  *array;
+NUMA       *na;
+
+    PROCNAME("pixOctcubeHistogram");
+
+    if (pncolors) *pncolors = 0;
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (NUMA *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    wpl = pixGetWpl(pixs);
+    data = pixGetData(pixs);
+
+    if (octcubeGetCount(level, &size))  /* array size = 2 ** (3 * level) */
+        return (NUMA *)ERROR_PTR("size not returned", procName, NULL);
+    if (makeRGBToIndexTables(&rtab, &gtab, &btab, level))
+        return (NUMA *)ERROR_PTR("tables not made", procName, NULL);
+
+    if ((na = numaCreate(size)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetCount(na, size);
+    array = numaGetFArray(na, L_NOCOPY);
+
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(line[j], &rval, &gval, &bval);
+            octindex = rtab[rval] | gtab[gval] | btab[bval];
+#if DEBUG_OCTINDEX
+            if ((level == 1 && octindex > 7) ||
+                (level == 2 && octindex > 63) ||
+                (level == 3 && octindex > 511) ||
+                (level == 4 && octindex > 4097) ||
+                (level == 5 && octindex > 32783) ||
+                (level == 6 && octindex > 262271)) {
+                fprintf(stderr, "level = %d, octindex = %d, index error!\n",
+                        level, octindex);
+                continue;
+            }
+#endif  /* DEBUG_OCTINDEX */
+              array[octindex] += 1.0;
+        }
+    }
+
+    if (pncolors) {
+        for (i = 0, ncolors = 0; i < size; i++) {
+            numaGetIValue(na, i, &val);
+            if (val > 0)
+                ncolors++;
+        }
+        *pncolors = ncolors;
+    }
+
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    return na;
+}
+
+
+/*------------------------------------------------------------------*
+ *              Get filled octcube table from colormap              *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixcmapToOctcubeLUT()
+ *
+ *      Input:  cmap
+ *              level (significant bits for each of RGB; valid in [1...6])
+ *              metric (L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE)
+ *      Return: tab[2**(3 * level)]
+ *
+ *  Notes:
+ *      (1) This function is used to quickly find the colormap color
+ *          that is closest to any rgb color.  It is used to assign
+ *          rgb colors to an existing colormap.  It can be very expensive
+ *          to search through the entire colormap for the closest color
+ *          to each pixel.  Instead, we first set up this table, which is
+ *          populated by the colormap index nearest to each octcube
+ *          color.  Then we go through the image; for each pixel,
+ *          do two table lookups: first to generate the octcube index
+ *          from rgb and second to use this table to read out the
+ *          colormap index.
+ *      (2) Do a slight modification for white and black.  For level = 4,
+ *          each octcube size is 16.  The center of the whitest octcube
+ *          is at (248, 248, 248), which is closer to 242 than 255.
+ *          Consequently, any gray color between 242 and 254 will
+ *          be selected, even if white (255, 255, 255) exists.  This is
+ *          typically not optimal, because the original color was
+ *          likely white.  Therefore, if white exists in the colormap,
+ *          use it for any rgb color that falls into the most white octcube.
+ *          Do the similar thing for black.
+ *      (3) Here are the actual function calls for quantizing to a
+ *          specified colormap:
+ *            - first make the tables that map from rgb --> octcube index
+ *                     makeRGBToIndexTables()
+ *            - then for each pixel:
+ *                * use the tables to get the octcube index
+ *                     getOctcubeIndexFromRGB()
+ *                * use this table to get the nearest color in the colormap
+ *                     cmap_index = tab[index]
+ *      (4) Distance can be either manhattan or euclidean.
+ *      (5) In typical use, level = 4 gives reasonable results, and
+ *          level = 5 is slightly better.  When this function is used
+ *          for color segmentation, there are typically a small number
+ *          of colors and the number of levels can be small (e.g., level = 3).
+ */
+l_int32 *
+pixcmapToOctcubeLUT(PIXCMAP  *cmap,
+                    l_int32   level,
+                    l_int32   metric)
+{
+l_int32    i, k, size, ncolors, mindist, dist, mincolor, index;
+l_int32    rval, gval, bval;  /* color at center of the octcube */
+l_int32   *rmap, *gmap, *bmap, *tab;
+
+    PROCNAME("pixcmapToOctcubeLUT");
+
+    if (!cmap)
+        return (l_int32 *)ERROR_PTR("cmap not defined", procName, NULL);
+    if (level < 1 || level > 6)
+        return (l_int32 *)ERROR_PTR("level not in {1...6}", procName, NULL);
+    if (metric != L_MANHATTAN_DISTANCE && metric != L_EUCLIDEAN_DISTANCE)
+        return (l_int32 *)ERROR_PTR("invalid metric", procName, NULL);
+
+    if (octcubeGetCount(level, &size))  /* array size = 2 ** (3 * level) */
+        return (l_int32 *)ERROR_PTR("size not returned", procName, NULL);
+    if ((tab = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (l_int32 *)ERROR_PTR("tab not allocated", procName, NULL);
+
+    ncolors = pixcmapGetCount(cmap);
+    pixcmapToArrays(cmap, &rmap, &gmap, &bmap, NULL);
+
+        /* Assign based on the closest octcube center to the cmap color */
+    for (i = 0; i < size; i++) {
+        getRGBFromOctcube(i, level, &rval, &gval, &bval);
+        mindist = 1000000;
+        mincolor = 0;  /* irrelevant init */
+        for (k = 0; k < ncolors; k++) {
+            if (metric == L_MANHATTAN_DISTANCE) {
+                dist = L_ABS(rval - rmap[k]) + L_ABS(gval - gmap[k]) +
+                       L_ABS(bval - bmap[k]);
+            } else {  /* L_EUCLIDEAN_DISTANCE */
+                dist = (rval - rmap[k]) * (rval - rmap[k]) +
+                       (gval - gmap[k]) * (gval - gmap[k]) +
+                       (bval - bmap[k]) * (bval - bmap[k]);
+            }
+            if (dist < mindist) {
+                mindist = dist;
+                mincolor = k;
+            }
+        }
+        tab[i] = mincolor;
+    }
+
+        /* Reset black and white if available in the colormap.
+         * The darkest octcube is at octindex 0.
+         * The lightest octcube is at the max octindex. */
+    pixcmapGetNearestIndex(cmap, 0, 0, 0, &index);
+    pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+    if (rval < 7 && gval < 7 && bval < 7) {
+        tab[0] = index;
+    }
+    pixcmapGetNearestIndex(cmap, 255, 255, 255, &index);
+    pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+    if (rval > 248 && gval > 248 && bval > 248) {
+        tab[(1 << (3 * level)) - 1] = index;
+    }
+
+    LEPT_FREE(rmap);
+    LEPT_FREE(gmap);
+    LEPT_FREE(bmap);
+    return tab;
+}
+
+
+/*------------------------------------------------------------------*
+ *               Strip out unused elements in colormap              *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRemoveUnusedColors()
+ *
+ *      Input:  pixs  (colormapped)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place operation.
+ *      (2) If the image doesn't have a colormap, returns without error.
+ *      (3) Unusued colors are removed from the colormap, and the
+ *          image pixels are re-numbered.
+ */
+l_int32
+pixRemoveUnusedColors(PIX  *pixs)
+{
+l_int32     i, j, w, h, d, nc, wpls, val, newval, index, zerofound;
+l_int32     rval, gval, bval;
+l_uint32   *datas, *lines;
+l_int32    *histo, *map1, *map2;
+PIXCMAP    *cmap, *cmapd;
+
+    PROCNAME("pixRemoveUnusedColors");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if ((cmap = pixGetColormap(pixs)) == NULL)
+        return 0;
+
+    d = pixGetDepth(pixs);
+    if (d != 2 && d != 4 && d != 8)
+        return ERROR_INT("d not in {2, 4, 8}", procName, 1);
+
+        /* Find which indices are actually used */
+    nc = pixcmapGetCount(cmap);
+    if ((histo = (l_int32 *)LEPT_CALLOC(nc, sizeof(l_int32))) == NULL)
+        return ERROR_INT("histo not made", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    wpls = pixGetWpl(pixs);
+    datas = pixGetData(pixs);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        for (j = 0; j < w; j++) {
+            switch (d)
+            {
+            case 2:
+                val = GET_DATA_DIBIT(lines, j);
+                break;
+            case 4:
+                val = GET_DATA_QBIT(lines, j);
+                break;
+            case 8:
+                val = GET_DATA_BYTE(lines, j);
+                break;
+            default:
+                return ERROR_INT("switch ran off end!", procName, 1);
+            }
+            if (val >= nc) {
+                L_WARNING("cmap index out of bounds!\n", procName);
+                continue;
+            }
+            histo[val]++;
+        }
+    }
+
+        /* Check if there are any zeroes.  If none, quit. */
+    zerofound = FALSE;
+    for (i = 0; i < nc; i++) {
+        if (histo[i] == 0) {
+            zerofound = TRUE;
+            break;
+        }
+    }
+    if (!zerofound) {
+      LEPT_FREE(histo);
+      return 0;
+    }
+
+        /* Generate mapping tables between indices */
+    if ((map1 = (l_int32 *)LEPT_CALLOC(nc, sizeof(l_int32))) == NULL)
+        return ERROR_INT("map1 not made", procName, 1);
+    if ((map2 = (l_int32 *)LEPT_CALLOC(nc, sizeof(l_int32))) == NULL)
+        return ERROR_INT("map2 not made", procName, 1);
+    index = 0;
+    for (i = 0; i < nc; i++) {
+        if (histo[i] != 0) {
+            map1[index] = i;  /* get old index from new */
+            map2[i] = index;  /* get new index from old */
+            index++;
+        }
+    }
+
+        /* Generate new colormap and attach to pixs */
+    cmapd = pixcmapCreate(d);
+    for (i = 0; i < index; i++) {
+        pixcmapGetColor(cmap, map1[i], &rval, &gval, &bval);
+        pixcmapAddColor(cmapd, rval, gval, bval);
+    }
+    pixSetColormap(pixs, cmapd);
+
+        /* Map pixel (index) values to new cmap */
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        for (j = 0; j < w; j++) {
+            switch (d)
+            {
+            case 2:
+                val = GET_DATA_DIBIT(lines, j);
+                newval = map2[val];
+                SET_DATA_DIBIT(lines, j, newval);
+                break;
+            case 4:
+                val = GET_DATA_QBIT(lines, j);
+                newval = map2[val];
+                SET_DATA_QBIT(lines, j, newval);
+                break;
+            case 8:
+                val = GET_DATA_BYTE(lines, j);
+                newval = map2[val];
+                SET_DATA_BYTE(lines, j, newval);
+                break;
+            default:
+                return ERROR_INT("switch ran off end!", procName, 1);
+            }
+        }
+    }
+
+    LEPT_FREE(histo);
+    LEPT_FREE(map1);
+    LEPT_FREE(map2);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *      Find number of occupied octcubes at the specified level     *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixNumberOccupiedOctcubes()
+ *
+ *      Input:  pix (32 bpp)
+ *              level (of octcube)
+ *              mincount (minimum num pixels in an octcube to be counted;
+ *                        -1 to not use)
+ *              minfract (minimum fract of pixels in an octcube to be
+ *                        counted; -1 to not use)
+ *              &ncolors (<return> number of occupied octcubes)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Exactly one of (@mincount, @minfract) must be -1, so, e.g.,
+ *          if @mincount == -1, then we use @minfract.
+ *      (2) If all occupied octcubes are to count, set @mincount == 1.
+ *          Setting @minfract == 0.0 is taken to mean the same thing.
+ */
+l_int32
+pixNumberOccupiedOctcubes(PIX       *pix,
+                          l_int32    level,
+                          l_int32    mincount,
+                          l_float32  minfract,
+                          l_int32   *pncolors)
+{
+l_int32    i, j, w, h, d, wpl, ncolors, size, octindex;
+l_int32    rval, gval, bval;
+l_int32   *carray;
+l_uint32  *data, *line, *rtab, *gtab, *btab;
+
+    PROCNAME("pixNumberOccupiedOctcubes");
+
+    if (!pncolors)
+        return ERROR_INT("&ncolors not defined", procName, 1);
+    *pncolors = 0;
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 32)
+        return ERROR_INT("pix not 32 bpp", procName, 1);
+    if (level < 1 || level > 6)
+        return ERROR_INT("invalid level", procName, 1);
+    if ((mincount < 0 && minfract < 0) || (mincount >= 0.0 && minfract >= 0.0))
+        return ERROR_INT("invalid mincount/minfract", procName, 1);
+    if (mincount == 0 || minfract == 0.0)
+        mincount = 1;
+    else if (minfract > 0.0)
+        mincount = L_MIN(1, (l_int32)(minfract * w * h));
+
+    if (octcubeGetCount(level, &size))  /* array size = 2 ** (3 * level) */
+        return ERROR_INT("size not returned", procName, 1);
+    if (makeRGBToIndexTables(&rtab, &gtab, &btab, level))
+        return ERROR_INT("tables not made", procName, 1);
+    if ((carray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return ERROR_INT("carray not made", procName, 1);
+
+        /* Mark the occupied octcube leaves */
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(line[j], &rval, &gval, &bval);
+            octindex = rtab[rval] | gtab[gval] | btab[bval];
+            carray[octindex]++;
+        }
+    }
+
+        /* Count them */
+    for (i = 0, ncolors = 0; i < size; i++) {
+        if (carray[i] >= mincount)
+            ncolors++;
+    }
+    *pncolors = ncolors;
+
+    LEPT_FREE(carray);
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    return 0;
+}
diff --git a/src/colorquant2.c b/src/colorquant2.c
new file mode 100644 (file)
index 0000000..d9f511b
--- /dev/null
@@ -0,0 +1,1626 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  colorquant2.c
+ *
+ *  Modified median cut color quantization
+ *
+ *      High level
+ *          PIX              *pixMedianCutQuant()
+ *          PIX              *pixMedianCutQuantGeneral()
+ *          PIX              *pixMedianCutQuantMixed()
+ *          PIX              *pixFewColorsMedianCutQuantMixed()
+ *
+ *      Median cut indexed histogram
+ *          l_int32          *pixMedianCutHisto()
+ *
+ *      Static helpers
+ *          static PIXCMAP   *pixcmapGenerateFromHisto()
+ *          static PIX       *pixQuantizeWithColormap()
+ *          static void       getColorIndexMedianCut()
+ *          static L_BOX3D   *pixGetColorRegion()
+ *          static l_int32    medianCutApply()
+ *          static PIXCMAP   *pixcmapGenerateFromMedianCuts()
+ *          static l_int32    vboxGetAverageColor()
+ *          static l_int32    vboxGetCount()
+ *          static l_int32    vboxGetVolume()
+ *          static L_BOX3D   *box3dCreate();
+ *          static L_BOX3D   *box3dCopy();
+ *
+ *   Paul Heckbert published the median cut algorithm, "Color Image
+ *   Quantization for Frame Buffer Display," in Proc. SIGGRAPH '82,
+ *   Boston, July 1982, pp. 297-307.  See:
+ *   http://delivery.acm.org/10.1145/810000/801294/p297-heckbert.pdf
+ *
+ *   Median cut starts with either the full color space or the occupied
+ *   region of color space.  If you're not dithering, the occupied region
+ *   can be used, but with dithering, pixels can end up in any place
+ *   in the color space, so you must represent the entire color space in
+ *   the final colormap.
+ *
+ *   Color components are quantized to typically 5 or 6 significant
+ *   bits (for each of r, g and b).   Call a 3D region of color
+ *   space a 'vbox'.  Any color in this quantized space is represented
+ *   by an element of a linear histogram array, indexed by rgb value.
+ *   The initial region is then divided into two regions that have roughly
+ *   equal pixel occupancy (hence the name "median cut").  Subdivision
+ *   continues until the requisite number of vboxes has been generated.
+ *
+ *   But the devil is in the details of the subdivision process.
+ *   Here are some choices that you must make:
+ *     (1) Along which axis to subdivide?
+ *     (2) Which box to put the bin with the median pixel?
+ *     (3) How to order the boxes for subdivision?
+ *     (4) How to adequately handle boxes with very small numbers of pixels?
+ *     (5) How to prevent a little-represented but highly visible color
+ *         from being masked out by other colors in its vbox.
+ *
+ *   Taking these in order:
+ *     (1) Heckbert suggests using either the largest vbox side, or the vbox
+ *         side with the largest variance in pixel occupancy.  We choose
+ *         to divide based on the largest vbox side.
+ *     (2) Suppose you've chosen a side.  Then you have a histogram
+ *         of pixel occupancy in 2D slices of the vbox.  One of those
+ *         slices includes the median pixel.  Suppose there are L bins
+ *         to the left (smaller index) and R bins to the right.  Then
+ *         this slice (or bin) should be assigned to the box containing
+ *         the smaller of L and R.  This both shortens the larger
+ *         of the subdivided dimensions and helps a low-count color
+ *         far from the subdivision boundary to better express itself.
+ *     (2a) One can also ask if the boundary should be moved even
+ *         farther into the longer side.  This is feasible if we have
+ *         a method for doing extra subdivisions on the high count
+ *         vboxes.  And we do (see (3)).
+ *     (3) To make sure that the boxes are subdivided toward equal
+ *         occupancy, use an occupancy-sorted priority queue, rather
+ *         than a simple queue.
+ *     (4) With a priority queue, boxes with small number of pixels
+ *         won't be repeatedly subdivided.  This is good.
+ *     (5) Use of a priority queue allows tricks such as in (2a) to let
+ *         small occupancy clusters be better expressed.  In addition,
+ *         rather than splitting near the median, small occupancy colors
+ *         are best reproduced by cutting half-way into the longer side.
+ *
+ *   However, serious problems can arise with dithering if a priority
+ *   queue is used based on population alone.  If the picture has
+ *   large regions of nearly constant color, some vboxes can be very
+ *   large and have a sizeable population (but not big enough to get to
+ *   the head of the queue).  If one of these large, occupied vboxes
+ *   is near in color to a nearly constant color region of the
+ *   image, dithering can inject pixels from the large vbox into
+ *   the nearly uniform region.  These pixels can be very far away
+ *   in color, and the oscillations are highly visible.  To prevent
+ *   this, we can take either or both of these actions:
+ *
+ *     (1) Subdivide a fraction (< 1.0) based on population, and
+ *         do the rest of the subdivision based on the product of
+ *         the vbox volume and its population.  By using the product,
+ *         we avoid further subdivision of nearly empty vboxes, and
+ *         directly target large vboxes with significant population.
+ *
+ *     (2) Threshold the excess color transferred in dithering to
+ *         neighboring pixels.
+ *
+ *   Doing either of these will stop the most annoying oscillations
+ *   in dithering.  Furthermore, by doing (1), we also improve the
+ *   rendering of regions of nearly constant color, both with and
+ *   without dithering.  It turns out that the image quality is
+ *   not sensitive to the value of the parameter in (1); values
+ *   between 0.3 and 0.9 give very good results.
+ *
+ *   Here's the lesson: subdivide the color space into vboxes such
+ *   that (1) the most populated vboxes that can be further
+ *   subdivided (i.e., that occupy more than one quantum volume
+ *   in color space) all have approximately the same population,
+ *   and (2) all large vboxes have no significant population.
+ *   If these conditions are met, the quantization will be excellent.
+ *
+ *   Once the subdivision has been made, the colormap is generated,
+ *   with one color for each vbox and using the average color in the vbox.
+ *   At the same time, the histogram array is converted to an inverse
+ *   colormap table, storing the colormap index in every cell in the
+ *   vbox.  Finally, using both the colormap and the inverse colormap,
+ *   a colormapped pix is quickly generated from the original rgb pix.
+ *
+ *   In the present implementation, subdivided regions of colorspace
+ *   that are not occupied are retained, but not further subdivided.
+ *   This is required for our inverse colormap lookup table for
+ *   dithering, because dithered pixels may fall into these unoccupied
+ *   regions.  For such empty regions, we use the center as the rgb
+ *   colormap value.
+ *
+ *   This variation on median cut can be referred to as "Modified Median
+ *   Cut" quantization, or MMCQ.  Overall, the undithered MMCQ gives
+ *   comparable results to the two-pass Octcube Quantizer (OQ).
+ *   Comparing the two methods on the test24.jpg painting, we see:
+ *
+ *     (1) For rendering spot color (the various reds and pinks in
+ *         the image), MMCQ is not as good as OQ.
+ *
+ *     (2) For rendering majority color regions, MMCQ does a better
+ *         job of avoiding posterization.  That is, it does better
+ *         dividing the color space up in the most heavily populated regions.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+    /* Median cut 3-d volume element.  Sort on first element, which
+     * can be the number of pixels, the volume or a combination
+     * of these.   */
+struct L_Box3d
+{
+    l_float32        sortparam;  /* parameter on which to sort the vbox */
+    l_int32          npix;       /* number of pixels in the vbox        */
+    l_int32          vol;        /* quantized volume of vbox            */
+    l_int32          r1;         /* min r index in the vbox             */
+    l_int32          r2;         /* max r index in the vbox             */
+    l_int32          g1;         /* min g index in the vbox             */
+    l_int32          g2;         /* max g index in the vbox             */
+    l_int32          b1;         /* min b index in the vbox             */
+    l_int32          b2;         /* max b index in the vbox             */
+};
+typedef struct L_Box3d  L_BOX3D;
+
+    /* Static median cut helper functions */
+static PIXCMAP *pixcmapGenerateFromHisto(PIX *pixs, l_int32 depth,
+                                         l_int32 *histo, l_int32 histosize,
+                                         l_int32 sigbits);
+static PIX *pixQuantizeWithColormap(PIX *pixs, l_int32 ditherflag,
+                                    l_int32 outdepth,
+                                    PIXCMAP *cmap, l_int32 *indexmap,
+                                    l_int32 mapsize, l_int32 sigbits);
+static void getColorIndexMedianCut(l_uint32 pixel, l_int32 rshift,
+                                   l_uint32 mask, l_int32 sigbits,
+                                   l_int32 *pindex);
+static L_BOX3D *pixGetColorRegion(PIX *pixs, l_int32 sigbits,
+                                  l_int32 subsample);
+static l_int32 medianCutApply(l_int32 *histo, l_int32 sigbits,
+                              L_BOX3D *vbox, L_BOX3D **pvbox1,
+                              L_BOX3D **pvbox2);
+static PIXCMAP *pixcmapGenerateFromMedianCuts(L_HEAP *lh, l_int32 *histo,
+                                              l_int32 sigbits);
+static l_int32 vboxGetAverageColor(L_BOX3D *vbox, l_int32 *histo,
+                                   l_int32 sigbits, l_int32 index,
+                                   l_int32  *prval, l_int32 *pgval,
+                                   l_int32  *pbval);
+static l_int32 vboxGetCount(L_BOX3D *vbox, l_int32 *histo, l_int32 sigbits);
+static l_int32 vboxGetVolume(L_BOX3D *vbox);
+static L_BOX3D *box3dCreate(l_int32 r1, l_int32 r2, l_int32 g1,
+                            l_int32 g2, l_int32 b1, l_int32 b2);
+static L_BOX3D *box3dCopy(L_BOX3D *vbox);
+
+
+    /* 5 significant bits for each component is generally satisfactory */
+static const l_int32  DEFAULT_SIG_BITS = 5;
+static const l_int32  MAX_ITERS_ALLOWED = 5000;  /* prevents infinite looping */
+
+    /* Specify fraction of vboxes made that are sorted on population alone.
+     * The remaining vboxes are sorted on (population * vbox-volume).  */
+static const l_float32  FRACT_BY_POPULATION = 0.85;
+
+    /* To get the max value of 'dif' in the dithering color transfer,
+     * divide DIF_CAP by 8. */
+static const l_int32  DIF_CAP = 100;
+
+
+#ifndef   NO_CONSOLE_IO
+#define   DEBUG_MC_COLORS       0
+#define   DEBUG_SPLIT_AXES      0
+#endif   /* ~NO_CONSOLE_IO */
+
+
+/*------------------------------------------------------------------------*
+ *                                 High level                             *
+ *------------------------------------------------------------------------*/
+/*!
+ *  pixMedianCutQuant()
+ *
+ *      Input:  pixs  (32 bpp; rgb color)
+ *              ditherflag (1 for dither; 0 for no dither)
+ *      Return: pixd (8 bit with colormap), or null on error
+ *
+ *  Notes:
+ *      (1) Simple interface.  See pixMedianCutQuantGeneral() for
+ *          use of defaulted parameters.
+ */
+PIX *
+pixMedianCutQuant(PIX     *pixs,
+                  l_int32  ditherflag)
+{
+    return pixMedianCutQuantGeneral(pixs, ditherflag,
+                                    0, 256, DEFAULT_SIG_BITS, 1, 1);
+}
+
+
+/*!
+ *  pixMedianCutQuantGeneral()
+ *
+ *      Input:  pixs  (32 bpp; rgb color)
+ *              ditherflag (1 for dither; 0 for no dither)
+ *              outdepth (output depth; valid: 0, 1, 2, 4, 8)
+ *              maxcolors (between 2 and 256)
+ *              sigbits (valid: 5 or 6; use 0 for default)
+ *              maxsub (max subsampling, integer; use 0 for default;
+ *                      1 for no subsampling)
+ *              checkbw (1 to check if color content is very small,
+ *                       0 to assume there is sufficient color)
+ *      Return: pixd (8 bit with colormap), or null on error
+ *
+ *  Notes:
+ *      (1) @maxcolors must be in the range [2 ... 256].
+ *      (2) Use @outdepth = 0 to have the output depth computed as the
+ *          minimum required to hold the actual colors found, given
+ *          the @maxcolors constraint.
+ *      (3) Use @outdepth = 1, 2, 4 or 8 to specify the output depth.
+ *          In that case, @maxcolors must not exceed 2^(outdepth).
+ *      (4) If there are fewer quantized colors in the image than @maxcolors,
+ *          the colormap is simply generated from those colors.
+ *      (5) @maxsub is the maximum allowed subsampling to be used in the
+ *          computation of the color histogram and region of occupied
+ *          color space.  The subsampling is chosen internally for
+ *          efficiency, based on the image size, but this parameter
+ *          limits it.  Use @maxsub = 0 for the internal default, which is the
+ *          maximum allowed subsampling.  Use @maxsub = 1 to prevent
+ *          subsampling.  In general use @maxsub >= 1 to specify the
+ *          maximum subsampling to be allowed, where the actual subsampling
+ *          will be the minimum of this value and the internally
+ *          determined default value.
+ *      (6) If the image appears gray because either most of the pixels
+ *          are gray or most of the pixels are essentially black or white,
+ *          the image is trivially quantized with a grayscale colormap.  The
+ *          reason is that median cut divides the color space into rectangular
+ *          regions, and it does a very poor job if all the pixels are
+ *          near the diagonal of the color space cube.
+ */
+PIX *
+pixMedianCutQuantGeneral(PIX     *pixs,
+                         l_int32  ditherflag,
+                         l_int32  outdepth,
+                         l_int32  maxcolors,
+                         l_int32  sigbits,
+                         l_int32  maxsub,
+                         l_int32  checkbw)
+{
+l_int32    i, subsample, histosize, smalln, ncolors, niters, popcolors;
+l_int32    w, h, minside, factor, index, rval, gval, bval;
+l_int32   *histo;
+l_float32  pixfract, colorfract;
+L_BOX3D   *vbox, *vbox1, *vbox2;
+L_HEAP    *lh, *lhs;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixMedianCutQuantGeneral");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+    if (maxcolors < 2 || maxcolors > 256)
+        return (PIX *)ERROR_PTR("maxcolors not in [2...256]", procName, NULL);
+    if (outdepth != 0 && outdepth != 1 && outdepth != 2 && outdepth != 4 &&
+        outdepth != 8)
+        return (PIX *)ERROR_PTR("outdepth not in {0,1,2,4,8}", procName, NULL);
+    if (outdepth > 0 && (maxcolors > (1 << outdepth)))
+        return (PIX *)ERROR_PTR("maxcolors > 2^(outdepth)", procName, NULL);
+    if (sigbits == 0)
+        sigbits = DEFAULT_SIG_BITS;
+    else if (sigbits < 5 || sigbits > 6)
+        return (PIX *)ERROR_PTR("sigbits not 5 or 6", procName, NULL);
+    if (maxsub <= 0)
+        maxsub = 10;  /* default will prevail for 10^7 pixels or less */
+
+        /* Determine if the image has sufficient color content.
+         * If pixfract << 1, most pixels are close to black or white.
+         * If colorfract << 1, the pixels that are not near
+         *   black or white have very little color.
+         * If with little color, quantize with a grayscale colormap. */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (checkbw) {
+        minside = L_MIN(w, h);
+        factor = L_MAX(1, minside / 400);
+        pixColorFraction(pixs, 20, 244, 20, factor, &pixfract, &colorfract);
+        if (pixfract * colorfract < 0.00025) {
+            L_INFO("\n  Pixel fraction neither white nor black = %6.3f"
+                   "\n  Color fraction of those pixels = %6.3f"
+                   "\n  Quantizing in gray\n",
+                   procName, pixfract, colorfract);
+            return pixConvertTo8(pixs, 1);
+        }
+    }
+
+        /* Compute the color space histogram.  Default sampling
+         * is about 10^5 pixels.  */
+    if (maxsub == 1) {
+        subsample = 1;
+    } else {
+        subsample = (l_int32)(sqrt((l_float64)(w * h) / 100000.));
+        subsample = L_MAX(1, L_MIN(maxsub, subsample));
+    }
+    histo = pixMedianCutHisto(pixs, sigbits, subsample);
+    histosize = 1 << (3 * sigbits);
+
+        /* See if the number of quantized colors is less than maxcolors */
+    ncolors = 0;
+    smalln = TRUE;
+    for (i = 0; i < histosize; i++) {
+        if (histo[i])
+            ncolors++;
+        if (ncolors > maxcolors) {
+            smalln = FALSE;
+            break;
+        }
+    }
+    if (smalln) {  /* finish up now */
+        if (outdepth == 0) {
+            if (ncolors <= 2)
+                outdepth = 1;
+            else if (ncolors <= 4)
+                outdepth = 2;
+            else if (ncolors <= 16)
+                outdepth = 4;
+            else
+                outdepth = 8;
+        }
+        cmap = pixcmapGenerateFromHisto(pixs, outdepth,
+                                        histo, histosize, sigbits);
+        pixd = pixQuantizeWithColormap(pixs, ditherflag, outdepth, cmap,
+                                       histo, histosize, sigbits);
+        LEPT_FREE(histo);
+        return pixd;
+    }
+
+        /* Initial vbox: minimum region in colorspace occupied by pixels */
+    if (ditherflag || subsample > 1)  /* use full color space */
+        vbox = box3dCreate(0, (1 << sigbits) - 1,
+                           0, (1 << sigbits) - 1,
+                           0, (1 << sigbits) - 1);
+    else
+        vbox = pixGetColorRegion(pixs, sigbits, subsample);
+    vbox->npix = vboxGetCount(vbox, histo, sigbits);
+    vbox->vol = vboxGetVolume(vbox);
+
+        /* For a fraction 'popcolors' of the desired 'maxcolors',
+         * generate median cuts based on population, putting
+         * everything on a priority queue sorted by population. */
+    lh = lheapCreate(0, L_SORT_DECREASING);
+    lheapAdd(lh, vbox);
+    ncolors = 1;
+    niters = 0;
+    popcolors = (l_int32)(FRACT_BY_POPULATION * maxcolors);
+    while (1) {
+        vbox = (L_BOX3D *)lheapRemove(lh);
+        if (vboxGetCount(vbox, histo, sigbits) == 0)  { /* just put it back */
+            lheapAdd(lh, vbox);
+            continue;
+        }
+        medianCutApply(histo, sigbits, vbox, &vbox1, &vbox2);
+        if (!vbox1) {
+            L_WARNING("vbox1 not defined; shouldn't happen!\n", procName);
+            break;
+        }
+        if (vbox1->vol > 1)
+            vbox1->sortparam = vbox1->npix;
+        LEPT_FREE(vbox);
+        lheapAdd(lh, vbox1);
+        if (vbox2) {  /* vbox2 can be NULL */
+            if (vbox2->vol > 1)
+                vbox2->sortparam = vbox2->npix;
+            lheapAdd(lh, vbox2);
+            ncolors++;
+        }
+        if (ncolors >= popcolors)
+            break;
+        if (niters++ > MAX_ITERS_ALLOWED) {
+            L_WARNING("infinite loop; perhaps too few pixels!\n", procName);
+            break;
+        }
+    }
+
+        /* Re-sort by the product of pixel occupancy times the size
+         * in color space. */
+    lhs = lheapCreate(0, L_SORT_DECREASING);
+    while ((vbox = (L_BOX3D *)lheapRemove(lh))) {
+        vbox->sortparam = vbox->npix * vbox->vol;
+        lheapAdd(lhs, vbox);
+    }
+    lheapDestroy(&lh, TRUE);
+
+        /* For the remaining (maxcolors - popcolors), generate the
+         * median cuts using the (npix * vol) sorting. */
+    while (1) {
+        vbox = (L_BOX3D *)lheapRemove(lhs);
+        if (vboxGetCount(vbox, histo, sigbits) == 0)  { /* just put it back */
+            lheapAdd(lhs, vbox);
+            continue;
+        }
+        medianCutApply(histo, sigbits, vbox, &vbox1, &vbox2);
+        if (!vbox1) {
+            L_WARNING("vbox1 not defined; shouldn't happen!\n", procName);
+            break;
+        }
+        if (vbox1->vol > 1)
+            vbox1->sortparam = vbox1->npix * vbox1->vol;
+        LEPT_FREE(vbox);
+        lheapAdd(lhs, vbox1);
+        if (vbox2) {  /* vbox2 can be NULL */
+            if (vbox2->vol > 1)
+                vbox2->sortparam = vbox2->npix * vbox2->vol;
+            lheapAdd(lhs, vbox2);
+            ncolors++;
+        }
+        if (ncolors >= maxcolors)
+            break;
+        if (niters++ > MAX_ITERS_ALLOWED) {
+            L_WARNING("infinite loop; perhaps too few pixels!\n", procName);
+            break;
+        }
+    }
+
+        /* Re-sort by pixel occupancy.  This is not necessary,
+         * but it makes a more useful listing.  */
+    lh = lheapCreate(0, L_SORT_DECREASING);
+    while ((vbox = (L_BOX3D *)lheapRemove(lhs))) {
+        vbox->sortparam = vbox->npix;
+/*        vbox->sortparam = vbox->npix * vbox->vol; */
+        lheapAdd(lh, vbox);
+    }
+    lheapDestroy(&lhs, TRUE);
+
+        /* Generate colormap from median cuts and quantize pixd */
+    cmap = pixcmapGenerateFromMedianCuts(lh, histo, sigbits);
+    if (outdepth == 0) {
+        ncolors = pixcmapGetCount(cmap);
+        if (ncolors <= 2)
+            outdepth = 1;
+        else if (ncolors <= 4)
+            outdepth = 2;
+        else if (ncolors <= 16)
+            outdepth = 4;
+        else
+            outdepth = 8;
+    }
+    pixd = pixQuantizeWithColormap(pixs, ditherflag, outdepth, cmap,
+                                   histo, histosize, sigbits);
+
+        /* Force darkest color to black if each component <= 4 */
+    pixcmapGetRankIntensity(cmap, 0.0, &index);
+    pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+    if (rval < 5 && gval < 5 && bval < 5)
+        pixcmapResetColor(cmap, index, 0, 0, 0);
+
+        /* Force lightest color to white if each component >= 252 */
+    pixcmapGetRankIntensity(cmap, 1.0, &index);
+    pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+    if (rval > 251 && gval > 251 && bval > 251)
+        pixcmapResetColor(cmap, index, 255, 255, 255);
+
+    lheapDestroy(&lh, TRUE);
+    LEPT_FREE(histo);
+    return pixd;
+}
+
+
+/*!
+ *  pixMedianCutQuantMixed()
+ *
+ *      Input:  pixs  (32 bpp; rgb color)
+ *              ncolor (maximum number of colors assigned to pixels with
+ *                      significant color)
+ *              ngray (number of gray colors to be used; must be >= 2)
+ *              darkthresh (threshold near black; if the lightest component
+ *                          is below this, the pixel is not considered to
+ *                          be gray or color; uses 0 for default)
+ *              lightthresh (threshold near white; if the darkest component
+ *                           is above this, the pixel is not considered to
+ *                           be gray or color; use 0 for default)
+ *              diffthresh (thresh for the max difference between component
+ *                          values; for differences below this, the pixel
+ *                          is considered to be gray; use 0 for default)
+ *      Return: pixd (8 bpp cmapped), or null on error
+ *
+ *  Notes:
+ *      (1) ncolor + ngray must not exceed 255.
+ *      (2) The method makes use of pixMedianCutQuantGeneral() with
+ *          minimal addition.
+ *          (a) Preprocess the image, setting all pixels with little color
+ *              to black, and populating an auxiliary 8 bpp image with the
+ *              expected colormap values corresponding to the set of
+ *              quantized gray values.
+ *          (b) Color quantize the altered input image to n + 1 colors.
+ *          (c) Augment the colormap with the gray indices, and
+ *              substitute the gray quantized values from the auxiliary
+ *              image for those in the color quantized output that had
+ *              been quantized as black.
+ *      (3) Median cut color quantization is relatively poor for grayscale
+ *          images with many colors, when compared to octcube quantization.
+ *          Thus, for images with both gray and color, it is important
+ *          to quantize the gray pixels by another method.  Here, we
+ *          are conservative in detecting color, preferring to use
+ *          a few extra bits to encode colorful pixels that push them
+ *          to gray.  This is particularly reasonable with this function,
+ *          because it handles the gray and color pixels separately,
+ *          using median cut color quantization for the color pixels
+ *          and equal-bin grayscale quantization for the non-color pixels.
+ */
+PIX *
+pixMedianCutQuantMixed(PIX     *pixs,
+                       l_int32  ncolor,
+                       l_int32  ngray,
+                       l_int32  darkthresh,
+                       l_int32  lightthresh,
+                       l_int32  diffthresh)
+{
+l_int32    i, j, w, h, wplc, wplg, wpld, nc, unused, iscolor, factor, minside;
+l_int32    rval, gval, bval, minval, maxval, val, grayval;
+l_float32  pixfract, colorfract;
+l_int32   *lut;
+l_uint32  *datac, *datag, *datad, *linec, *lineg, *lined;
+PIX       *pixc, *pixg, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixMedianCutQuantMixed");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+    if (ngray < 2)
+        return (PIX *)ERROR_PTR("ngray < 2", procName, NULL);
+    if (ncolor + ngray > 255)
+        return (PIX *)ERROR_PTR("ncolor + ngray > 255", procName, NULL);
+    if (darkthresh <= 0) darkthresh = 20;
+    if (lightthresh <= 0) lightthresh = 244;
+    if (diffthresh <= 0) diffthresh = 20;
+
+        /* First check if this should be quantized in gray.
+         * Use a more sensitive parameter for detecting color than with
+         * pixMedianCutQuantGeneral(), because this function can handle
+         * gray pixels well.  */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    minside = L_MIN(w, h);
+    factor = L_MAX(1, minside / 400);
+    pixColorFraction(pixs, darkthresh, lightthresh, diffthresh, factor,
+                     &pixfract, &colorfract);
+    if (pixfract * colorfract < 0.0001) {
+        L_INFO("\n  Pixel fraction neither white nor black = %6.3f"
+                      "\n  Color fraction of those pixels = %6.3f"
+                      "\n  Quantizing in gray\n",
+                      procName, pixfract, colorfract);
+        pixg = pixConvertTo8(pixs, 0);
+        pixd = pixThresholdOn8bpp(pixg, ngray, 1);
+        pixDestroy(&pixg);
+        return pixd;
+    }
+
+        /* OK, there is color in the image.
+         * Preprocess to handle the gray pixels.  Set the color pixels in pixc
+         * to black, and store their (eventual) colormap indices in pixg.*/
+    pixc = pixCopy(NULL, pixs);
+    pixg = pixCreate(w, h, 8);  /* color pixels will remain 0 here */
+    datac = pixGetData(pixc);
+    datag = pixGetData(pixg);
+    wplc = pixGetWpl(pixc);
+    wplg = pixGetWpl(pixg);
+    lut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    for (i = 0; i < 256; i++)
+        lut[i] = ncolor + 1 + (i * (ngray - 1) + 128) / 255;
+    for (i = 0; i < h; i++) {
+        linec = datac + i * wplc;
+        lineg = datag + i * wplg;
+        for (j = 0; j < w; j++) {
+            iscolor = FALSE;
+            extractRGBValues(linec[j], &rval, &gval, &bval);
+            minval = L_MIN(rval, gval);
+            minval = L_MIN(minval, bval);
+            maxval = L_MAX(rval, gval);
+            maxval = L_MAX(maxval, bval);
+            if (maxval >= darkthresh &&
+                minval <= lightthresh &&
+                maxval - minval >= diffthresh) {
+                iscolor = TRUE;
+            }
+            if (!iscolor) {
+                linec[j] = 0x0;  /* set to black */
+                grayval = (maxval + minval) / 2;
+                SET_DATA_BYTE(lineg, j, lut[grayval]);
+            }
+        }
+    }
+
+        /* Median cut on color pixels plus black */
+    pixd = pixMedianCutQuantGeneral(pixc, FALSE, 8, ncolor + 1,
+                                    DEFAULT_SIG_BITS, 1, 0);
+
+        /* Augment the colormap with gray values.  The new cmap
+         * indices should agree with the values previously stored in pixg. */
+    cmap = pixGetColormap(pixd);
+    nc = pixcmapGetCount(cmap);
+    unused = ncolor  + 1 - nc;
+    if (unused < 0)
+        L_ERROR("Too many colors: extra = %d\n", procName, -unused);
+    if (unused > 0) {  /* fill in with black; these won't be used */
+        L_INFO("%d unused colors\n", procName, unused);
+        for (i = 0; i < unused; i++)
+            pixcmapAddColor(cmap, 0, 0, 0);
+    }
+    for (i = 0; i < ngray; i++) {
+        grayval = (255 * i) / (ngray - 1);
+        pixcmapAddColor(cmap, grayval, grayval, grayval);
+    }
+
+        /* Substitute cmap indices for the gray pixels into pixd */
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        lineg = datag + i * wplg;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(lineg, j);  /* if 0, it's a color pixel */
+            if (val)
+                SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    pixDestroy(&pixc);
+    pixDestroy(&pixg);
+    LEPT_FREE(lut);
+    return pixd;
+}
+
+
+/*!
+ *  pixFewColorsMedianCutQuantMixed()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              ncolor (number of colors to be assigned to pixels with
+ *                       significant color)
+ *              ngray (number of gray colors to be used; must be >= 2)
+ *              maxncolors (maximum number of colors to be returned
+ *                         from pixColorsForQuantization(); use 0 for default)
+ *              darkthresh (threshold near black; if the lightest component
+ *                          is below this, the pixel is not considered to
+ *                          be gray or color; use 0 for default)
+ *              lightthresh (threshold near white; if the darkest component
+ *                           is above this, the pixel is not considered to
+ *                           be gray or color; use 0 for default)
+ *              diffthresh (thresh for the max difference between component
+ *                          values; for differences below this, the pixel
+ *                          is considered to be gray; use 0 for default)
+ *                          considered gray; use 0 for default)
+ *      Return: pixd (8 bpp, median cut quantized for pixels that are
+ *                    not gray; gray pixels are quantized separately
+ *                    over the full gray range); null if too many colors
+ *                    or on error
+ *
+ *  Notes:
+ *      (1) This is the "few colors" version of pixMedianCutQuantMixed().
+ *          It fails (returns NULL) if it finds more than maxncolors, but
+ *          otherwise it gives the same result.
+ *      (2) Recommended input parameters are:
+ *              @maxncolors:  20
+ *              @darkthresh:  20
+ *              @lightthresh: 244
+ *              @diffthresh:  15  (any higher can miss colors differing
+ *                                 slightly from gray)
+ *      (3) Both ncolor and ngray should be at least equal to maxncolors.
+ *          If they're not, they are automatically increased, and a
+ *          warning is given.
+ *      (4) If very little color content is found, the input is
+ *          converted to gray and quantized in equal intervals.
+ *      (5) This can be useful for quantizing orthographically generated
+ *          images such as color maps, where there may be more than 256 colors
+ *          because of aliasing or jpeg artifacts on text or lines, but
+ *          there are a relatively small number of solid colors.
+ *      (6) Example of usage:
+ *             // Try to quantize, using default values for mixed med cut
+ *             Pix *pixq = pixFewColorsMedianCutQuantMixed(pixs, 100, 20,
+ *                             0, 0, 0, 0);
+ *             if (!pixq)  // too many colors; don't quantize
+ *                 pixq = pixClone(pixs);
+ */
+PIX *
+pixFewColorsMedianCutQuantMixed(PIX       *pixs,
+                                l_int32    ncolor,
+                                l_int32    ngray,
+                                l_int32    maxncolors,
+                                l_int32    darkthresh,
+                                l_int32    lightthresh,
+                                l_int32    diffthresh)
+{
+l_int32  ncolors, iscolor;
+PIX     *pixg, *pixd;
+
+    PROCNAME("pixFewColorsMedianCutQuantMixed");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+    if (maxncolors <= 0) maxncolors = 20;
+    if (darkthresh <= 0) darkthresh = 20;
+    if (lightthresh <= 0) lightthresh = 244;
+    if (diffthresh <= 0) diffthresh = 15;
+    if (ncolor < maxncolors) {
+        L_WARNING("ncolor too small; setting to %d\n", procName, maxncolors);
+        ncolor = maxncolors;
+    }
+    if (ngray < maxncolors) {
+        L_WARNING("ngray too small; setting to %d\n", procName, maxncolors);
+        ngray = maxncolors;
+    }
+
+        /* Estimate the color content and the number of colors required */
+    pixColorsForQuantization(pixs, 15, &ncolors, &iscolor, 0);
+
+        /* Note that maxncolors applies to all colors required to quantize,
+         * both gray and colorful */
+    if (ncolors > maxncolors)
+        return (PIX *)ERROR_PTR("too many colors", procName, NULL);
+
+        /* If no color, return quantized gray pix */
+    if (!iscolor) {
+        pixg = pixConvertTo8(pixs, 0);
+        pixd = pixThresholdOn8bpp(pixg, ngray, 1);
+        pixDestroy(&pixg);
+        return pixd;
+    }
+
+        /* Use the mixed gray/color quantizer */
+    return pixMedianCutQuantMixed(pixs, ncolor, ngray, darkthresh,
+                                  lightthresh, diffthresh);
+}
+
+
+
+/*------------------------------------------------------------------------*
+ *                        Median cut indexed histogram                    *
+ *------------------------------------------------------------------------*/
+/*!
+ *  pixMedianCutHisto()
+ *
+ *      Input:  pixs  (32 bpp; rgb color)
+ *              sigbits (valid: 5 or 6)
+ *              subsample (integer > 0)
+ *      Return: histo (1-d array, giving the number of pixels in
+ *                     each quantized region of color space), or null on error
+ *
+ *  Notes:
+ *      (1) Array is indexed by (3 * sigbits) bits.  The array size
+ *          is 2^(3 * sigbits).
+ *      (2) Indexing into the array from rgb uses red sigbits as
+ *          most significant and blue as least.
+ */
+l_int32 *
+pixMedianCutHisto(PIX     *pixs,
+                  l_int32  sigbits,
+                  l_int32  subsample)
+{
+l_int32    i, j, w, h, wpl, rshift, index, histosize;
+l_int32   *histo;
+l_uint32   mask, pixel;
+l_uint32  *data, *line;
+
+    PROCNAME("pixMedianCutHisto");
+
+    if (!pixs)
+        return (l_int32 *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (l_int32 *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (sigbits < 5 || sigbits > 6)
+        return (l_int32 *)ERROR_PTR("sigbits not 5 or 6", procName, NULL);
+    if (subsample <= 0)
+        return (l_int32 *)ERROR_PTR("subsample not > 0", procName, NULL);
+
+    histosize = 1 << (3 * sigbits);
+    if ((histo = (l_int32 *)LEPT_CALLOC(histosize, sizeof(l_int32))) == NULL)
+        return (l_int32 *)ERROR_PTR("histo not made", procName, NULL);
+
+    rshift = 8 - sigbits;
+    mask = 0xff >> rshift;
+    pixGetDimensions(pixs, &w, &h, NULL);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    for (i = 0; i < h; i += subsample) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j += subsample) {
+            pixel = line[j];
+            getColorIndexMedianCut(pixel, rshift, mask, sigbits, &index);
+            histo[index]++;
+        }
+    }
+
+    return histo;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                               Static helpers                           *
+ *------------------------------------------------------------------------*/
+/*!
+ *  pixcmapGenerateFromHisto()
+ *
+ *      Input:  pixs  (32 bpp; rgb color)
+ *              depth (of colormap)
+ *              histo
+ *              histosize
+ *              sigbits
+ *      Return: colormap, or null on error
+ *
+ *  Notes:
+ *      (1) This is used when the number of colors in the histo
+ *          is not greater than maxcolors.
+ *      (2) As a side-effect, the histo becomes an inverse colormap,
+ *          labeling the cmap indices for each existing color.
+ */
+static PIXCMAP *
+pixcmapGenerateFromHisto(PIX      *pixs,
+                         l_int32   depth,
+                         l_int32  *histo,
+                         l_int32   histosize,
+                         l_int32   sigbits)
+{
+l_int32   i, index, shift, rval, gval, bval;
+l_uint32  mask;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixcmapGenerateFromHisto");
+
+    if (!pixs)
+        return (PIXCMAP *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIXCMAP *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (!histo)
+        return (PIXCMAP *)ERROR_PTR("histo not defined", procName, NULL);
+
+        /* Capture the rgb values of each occupied cube in the histo,
+         * and re-label the histo value with the colormap index. */
+    cmap = pixcmapCreate(depth);
+    shift = 8 - sigbits;
+    mask = 0xff >> shift;
+    for (i = 0, index = 0; i < histosize; i++) {
+        if (histo[i]) {
+            rval = (i >> (2 * sigbits)) << shift;
+            gval = ((i >> sigbits) & mask) << shift;
+            bval = (i & mask) << shift;
+            pixcmapAddColor(cmap, rval, gval, bval);
+            histo[i] = index++;
+        }
+    }
+
+    return cmap;
+}
+
+
+/*!
+ *  pixQuantizeWithColormap()
+ *
+ *      Input:  pixs  (32 bpp; rgb color)
+ *              ditherflag (1 for dither; 0 for no dither)
+ *              outdepth
+ *              cmap
+ *              indexmap
+ *              histosize
+ *              sigbits
+ *      Return: pixd (quantized to colormap), or null on error
+ *
+ *  Notes:
+ *      (1) The indexmap is a LUT that takes the rgb indices of the
+ *          pixel and returns the index into the colormap.
+ *      (2) If ditherflag is 1, @outdepth is ignored and the output
+ *          depth is set to 8.
+ */
+static PIX *
+pixQuantizeWithColormap(PIX      *pixs,
+                        l_int32   ditherflag,
+                        l_int32   outdepth,
+                        PIXCMAP  *cmap,
+                        l_int32  *indexmap,
+                        l_int32   mapsize,
+                        l_int32   sigbits)
+{
+l_uint8   *bufu8r, *bufu8g, *bufu8b;
+l_int32    i, j, w, h, wpls, wpld, rshift, index, cmapindex;
+l_int32    rval, gval, bval, rc, gc, bc;
+l_int32    dif, val1, val2, val3;
+l_int32   *buf1r, *buf1g, *buf1b, *buf2r, *buf2g, *buf2b;
+l_uint32  *datas, *datad, *lines, *lined;
+l_uint32   mask, pixel;
+PIX       *pixd;
+
+    PROCNAME("pixQuantizeWithColormap");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (!cmap)
+        return (PIX *)ERROR_PTR("cmap not defined", procName, NULL);
+    if (!indexmap)
+        return (PIX *)ERROR_PTR("indexmap not defined", procName, NULL);
+    if (ditherflag)
+        outdepth = 8;
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixCreate(w, h, outdepth);
+    pixSetColormap(pixd, cmap);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+    rshift = 8 - sigbits;
+    mask = 0xff >> rshift;
+    if (ditherflag == 0) {
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            if (outdepth == 1) {
+                for (j = 0; j < w; j++) {
+                    pixel = lines[j];
+                    getColorIndexMedianCut(pixel, rshift, mask,
+                                           sigbits, &index);
+                    if (indexmap[index])
+                        SET_DATA_BIT(lined, j);
+                }
+            } else if (outdepth == 2) {
+                for (j = 0; j < w; j++) {
+                    pixel = lines[j];
+                    getColorIndexMedianCut(pixel, rshift, mask,
+                                           sigbits, &index);
+                    SET_DATA_DIBIT(lined, j, indexmap[index]);
+                }
+            } else if (outdepth == 4) {
+                for (j = 0; j < w; j++) {
+                    pixel = lines[j];
+                    getColorIndexMedianCut(pixel, rshift, mask,
+                                           sigbits, &index);
+                    SET_DATA_QBIT(lined, j, indexmap[index]);
+                }
+            } else {  /* outdepth == 8 */
+                for (j = 0; j < w; j++) {
+                    pixel = lines[j];
+                    getColorIndexMedianCut(pixel, rshift, mask,
+                                           sigbits, &index);
+                    SET_DATA_BYTE(lined, j, indexmap[index]);
+                }
+            }
+        }
+    } else {  /* ditherflag == 1 */
+        bufu8r = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
+        bufu8g = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
+        bufu8b = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8));
+        buf1r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+        buf1g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+        buf1b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+        buf2r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+        buf2g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+        buf2b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+        if (!bufu8r || !bufu8g || !bufu8b)
+            return (PIX *)ERROR_PTR("uint8 line buf not made", procName, NULL);
+        if (!buf1r || !buf1g || !buf1b || !buf2r || !buf2g || !buf2b)
+            return (PIX *)ERROR_PTR("mono line buf not made", procName, NULL);
+
+            /* Start by priming buf2; line 1 is above line 2 */
+        pixGetRGBLine(pixs, 0, bufu8r, bufu8g, bufu8b);
+        for (j = 0; j < w; j++) {
+            buf2r[j] = 64 * bufu8r[j];
+            buf2g[j] = 64 * bufu8g[j];
+            buf2b[j] = 64 * bufu8b[j];
+        }
+
+        for (i = 0; i < h - 1; i++) {
+                /* Swap data 2 --> 1, and read in new line 2 */
+            memcpy(buf1r, buf2r, 4 * w);
+            memcpy(buf1g, buf2g, 4 * w);
+            memcpy(buf1b, buf2b, 4 * w);
+            pixGetRGBLine(pixs, i + 1, bufu8r, bufu8g, bufu8b);
+            for (j = 0; j < w; j++) {
+                buf2r[j] = 64 * bufu8r[j];
+                buf2g[j] = 64 * bufu8g[j];
+                buf2b[j] = 64 * bufu8b[j];
+            }
+
+                /* Dither */
+            lined = datad + i * wpld;
+            for (j = 0; j < w - 1; j++) {
+                rval = buf1r[j] / 64;
+                gval = buf1g[j] / 64;
+                bval = buf1b[j] / 64;
+                index = ((rval >> rshift) << (2 * sigbits)) +
+                        ((gval >> rshift) << sigbits) + (bval >> rshift);
+                cmapindex = indexmap[index];
+                SET_DATA_BYTE(lined, j, cmapindex);
+                pixcmapGetColor(cmap, cmapindex, &rc, &gc, &bc);
+
+                dif = buf1r[j] / 8 - 8 * rc;
+                if (dif > DIF_CAP) dif = DIF_CAP;
+                if (dif < -DIF_CAP) dif = -DIF_CAP;
+                if (dif != 0) {
+                    val1 = buf1r[j + 1] + 3 * dif;
+                    val2 = buf2r[j] + 3 * dif;
+                    val3 = buf2r[j + 1] + 2 * dif;
+                    if (dif > 0) {
+                        buf1r[j + 1] = L_MIN(16383, val1);
+                        buf2r[j] = L_MIN(16383, val2);
+                        buf2r[j + 1] = L_MIN(16383, val3);
+                    } else if (dif < 0) {
+                        buf1r[j + 1] = L_MAX(0, val1);
+                        buf2r[j] = L_MAX(0, val2);
+                        buf2r[j + 1] = L_MAX(0, val3);
+                    }
+                }
+
+                dif = buf1g[j] / 8 - 8 * gc;
+                if (dif > DIF_CAP) dif = DIF_CAP;
+                if (dif < -DIF_CAP) dif = -DIF_CAP;
+                if (dif != 0) {
+                    val1 = buf1g[j + 1] + 3 * dif;
+                    val2 = buf2g[j] + 3 * dif;
+                    val3 = buf2g[j + 1] + 2 * dif;
+                    if (dif > 0) {
+                        buf1g[j + 1] = L_MIN(16383, val1);
+                        buf2g[j] = L_MIN(16383, val2);
+                        buf2g[j + 1] = L_MIN(16383, val3);
+                    } else if (dif < 0) {
+                        buf1g[j + 1] = L_MAX(0, val1);
+                        buf2g[j] = L_MAX(0, val2);
+                        buf2g[j + 1] = L_MAX(0, val3);
+                    }
+                }
+
+                dif = buf1b[j] / 8 - 8 * bc;
+                if (dif > DIF_CAP) dif = DIF_CAP;
+                if (dif < -DIF_CAP) dif = -DIF_CAP;
+                if (dif != 0) {
+                    val1 = buf1b[j + 1] + 3 * dif;
+                    val2 = buf2b[j] + 3 * dif;
+                    val3 = buf2b[j + 1] + 2 * dif;
+                    if (dif > 0) {
+                        buf1b[j + 1] = L_MIN(16383, val1);
+                        buf2b[j] = L_MIN(16383, val2);
+                        buf2b[j + 1] = L_MIN(16383, val3);
+                    } else if (dif < 0) {
+                        buf1b[j + 1] = L_MAX(0, val1);
+                        buf2b[j] = L_MAX(0, val2);
+                        buf2b[j + 1] = L_MAX(0, val3);
+                    }
+                }
+            }
+
+                /* Get last pixel in row; no downward propagation */
+            rval = buf1r[w - 1] / 64;
+            gval = buf1g[w - 1] / 64;
+            bval = buf1b[w - 1] / 64;
+            index = ((rval >> rshift) << (2 * sigbits)) +
+                    ((gval >> rshift) << sigbits) + (bval >> rshift);
+            SET_DATA_BYTE(lined, w - 1, indexmap[index]);
+        }
+
+            /* Get last row of pixels; no leftward propagation */
+        lined = datad + (h - 1) * wpld;
+        for (j = 0; j < w; j++) {
+            rval = buf2r[j] / 64;
+            gval = buf2g[j] / 64;
+            bval = buf2b[j] / 64;
+            index = ((rval >> rshift) << (2 * sigbits)) +
+                    ((gval >> rshift) << sigbits) + (bval >> rshift);
+            SET_DATA_BYTE(lined, j, indexmap[index]);
+        }
+
+        LEPT_FREE(bufu8r);
+        LEPT_FREE(bufu8g);
+        LEPT_FREE(bufu8b);
+        LEPT_FREE(buf1r);
+        LEPT_FREE(buf1g);
+        LEPT_FREE(buf1b);
+        LEPT_FREE(buf2r);
+        LEPT_FREE(buf2g);
+        LEPT_FREE(buf2b);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  getColorIndexMedianCut()
+ *
+ *      Input:  pixel (32 bit rgb)
+ *              rshift (of component: 8 - sigbits)
+ *              mask (over sigbits)
+ *              sigbits
+ *              &index (<return> rgb index value)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This is used on each pixel in the source image.  No checking
+ *          is done on input values.
+ */
+static void
+getColorIndexMedianCut(l_uint32  pixel,
+                       l_int32   rshift,
+                       l_uint32  mask,
+                       l_int32   sigbits,
+                       l_int32  *pindex)
+{
+l_int32  rval, gval, bval;
+
+    rval = pixel >> (24 + rshift);
+    gval = (pixel >> (16 + rshift)) & mask;
+    bval = (pixel >> (8 + rshift)) & mask;
+    *pindex = (rval << (2 * sigbits)) + (gval << sigbits) + bval;
+    return;
+}
+
+
+/*!
+ *  pixGetColorRegion()
+ *
+ *      Input:  pixs  (32 bpp; rgb color)
+ *              sigbits (valid: 5, 6)
+ *              subsample (integer > 0)
+ *      Return: vbox (minimum 3D box in color space enclosing all pixels),
+ *              or null on error
+ *
+ *  Notes:
+ *      (1) Computes the minimum 3D box in color space enclosing all
+ *          pixels in the image.
+ */
+static L_BOX3D *
+pixGetColorRegion(PIX     *pixs,
+                  l_int32  sigbits,
+                  l_int32  subsample)
+{
+l_int32    rmin, rmax, gmin, gmax, bmin, bmax, rval, gval, bval;
+l_int32    w, h, wpl, i, j, rshift;
+l_uint32   mask, pixel;
+l_uint32  *data, *line;
+
+    PROCNAME("pixGetColorRegion");
+
+    if (!pixs)
+        return (L_BOX3D *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    rmin = gmin = bmin = 1000000;
+    rmax = gmax = bmax = 0;
+    rshift = 8 - sigbits;
+    mask = 0xff >> rshift;
+    pixGetDimensions(pixs, &w, &h, NULL);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    for (i = 0; i < h; i += subsample) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j += subsample) {
+            pixel = line[j];
+            rval = pixel >> (24 + rshift);
+            gval = (pixel >> (16 + rshift)) & mask;
+            bval = (pixel >> (8 + rshift)) & mask;
+            if (rval < rmin)
+                rmin = rval;
+            else if (rval > rmax)
+                rmax = rval;
+            if (gval < gmin)
+                gmin = gval;
+            else if (gval > gmax)
+                gmax = gval;
+            if (bval < bmin)
+                bmin = bval;
+            else if (bval > bmax)
+                bmax = bval;
+        }
+    }
+
+    return box3dCreate(rmin, rmax, gmin, gmax, bmin, bmax);
+}
+
+
+/*!
+ *  medianCutApply()
+ *
+ *      Input:  histo  (array; in rgb colorspace)
+ *              sigbits
+ *              vbox (input 3D box)
+ *              &vbox1, vbox2 (<return> vbox split in two parts)
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+medianCutApply(l_int32   *histo,
+               l_int32    sigbits,
+               L_BOX3D   *vbox,
+               L_BOX3D  **pvbox1,
+               L_BOX3D  **pvbox2)
+{
+l_int32   i, j, k, sum, rw, gw, bw, maxw, index;
+l_int32   total, left, right;
+l_int32   partialsum[128];
+L_BOX3D  *vbox1, *vbox2;
+
+    PROCNAME("medianCutApply");
+
+    if (!histo)
+        return ERROR_INT("histo not defined", procName, 1);
+    if (!vbox)
+        return ERROR_INT("vbox not defined", procName, 1);
+    if (!pvbox1 || !pvbox2)
+        return ERROR_INT("&vbox1 and &vbox2 not both defined", procName, 1);
+
+    *pvbox1 = *pvbox2 = NULL;
+    if (vboxGetCount(vbox, histo, sigbits) == 0)
+        return ERROR_INT("no pixels in vbox", procName, 1);
+
+        /* If the vbox occupies just one element in color space, it can't
+         * be split.  Leave the 'sortparam' field at 0, so that it goes to
+         * the tail of the priority queue and stays there, thereby avoiding
+         * an infinite loop (take off, put back on the head) if it
+         * happens to be the most populous box! */
+    rw = vbox->r2 - vbox->r1 + 1;
+    gw = vbox->g2 - vbox->g1 + 1;
+    bw = vbox->b2 - vbox->b1 + 1;
+    if (rw == 1 && gw == 1 && bw == 1) {
+        *pvbox1 = box3dCopy(vbox);
+        return 0;
+    }
+
+        /* Select the longest axis for splitting */
+    maxw = L_MAX(rw, gw);
+    maxw = L_MAX(maxw, bw);
+#if  DEBUG_SPLIT_AXES
+    if (rw == maxw)
+        fprintf(stderr, "red split\n");
+    else if (gw == maxw)
+        fprintf(stderr, "green split\n");
+    else
+        fprintf(stderr, "blue split\n");
+#endif  /* DEBUG_SPLIT_AXES */
+
+        /* Find the partial sum arrays along the selected axis. */
+    total = 0;
+    if (maxw == rw) {
+        for (i = vbox->r1; i <= vbox->r2; i++) {
+            sum = 0;
+            for (j = vbox->g1; j <= vbox->g2; j++) {
+                for (k = vbox->b1; k <= vbox->b2; k++) {
+                    index = (i << (2 * sigbits)) + (j << sigbits) + k;
+                    sum += histo[index];
+                }
+            }
+            total += sum;
+            partialsum[i] = total;
+        }
+    } else if (maxw == gw) {
+        for (i = vbox->g1; i <= vbox->g2; i++) {
+            sum = 0;
+            for (j = vbox->r1; j <= vbox->r2; j++) {
+                for (k = vbox->b1; k <= vbox->b2; k++) {
+                    index = (i << sigbits) + (j << (2 * sigbits)) + k;
+                    sum += histo[index];
+                }
+            }
+            total += sum;
+            partialsum[i] = total;
+        }
+    } else {  /* maxw == bw */
+        for (i = vbox->b1; i <= vbox->b2; i++) {
+            sum = 0;
+            for (j = vbox->r1; j <= vbox->r2; j++) {
+                for (k = vbox->g1; k <= vbox->g2; k++) {
+                    index = i + (j << (2 * sigbits)) + (k << sigbits);
+                    sum += histo[index];
+                }
+            }
+            total += sum;
+            partialsum[i] = total;
+        }
+    }
+
+        /* Determine the cut planes, making sure that two vboxes
+         * are always produced.  Generate the two vboxes and compute
+         * the sum in each of them.  Choose the cut plane within
+         * the greater of the (left, right) sides of the bin in which
+         * the median pixel resides.  Here's the surprise: go halfway
+         * into that side.  By doing that, you technically move away
+         * from "median cut," but in the process a significant number
+         * of low-count vboxes are produced, allowing much better
+         * reproduction of low-count spot colors. */
+    if (maxw == rw) {
+        for (i = vbox->r1; i <= vbox->r2; i++) {
+            if (partialsum[i] > total / 2) {
+                vbox1 = box3dCopy(vbox);
+                vbox2 = box3dCopy(vbox);
+                left = i - vbox->r1;
+                right = vbox->r2 - i;
+                if (left <= right)
+                    vbox1->r2 = L_MIN(vbox->r2 - 1, i + right / 2);
+                else  /* left > right */
+                    vbox1->r2 = L_MAX(vbox->r1, i - 1 - left / 2);
+                vbox2->r1 = vbox1->r2 + 1;
+                break;
+            }
+        }
+    } else if (maxw == gw) {
+        for (i = vbox->g1; i <= vbox->g2; i++) {
+            if (partialsum[i] > total / 2) {
+                vbox1 = box3dCopy(vbox);
+                vbox2 = box3dCopy(vbox);
+                left = i - vbox->g1;
+                right = vbox->g2 - i;
+                if (left <= right)
+                    vbox1->g2 = L_MIN(vbox->g2 - 1, i + right / 2);
+                else  /* left > right */
+                    vbox1->g2 = L_MAX(vbox->g1, i - 1 - left / 2);
+                vbox2->g1 = vbox1->g2 + 1;
+                break;
+            }
+        }
+    } else {  /* maxw == bw */
+        for (i = vbox->b1; i <= vbox->b2; i++) {
+            if (partialsum[i] > total / 2) {
+                vbox1 = box3dCopy(vbox);
+                vbox2 = box3dCopy(vbox);
+                left = i - vbox->b1;
+                right = vbox->b2 - i;
+                if (left <= right)
+                    vbox1->b2 = L_MIN(vbox->b2 - 1, i + right / 2);
+                else  /* left > right */
+                    vbox1->b2 = L_MAX(vbox->b1, i - 1 - left / 2);
+                vbox2->b1 = vbox1->b2 + 1;
+                break;
+            }
+        }
+    }
+    vbox1->npix = vboxGetCount(vbox1, histo, sigbits);
+    vbox2->npix = vboxGetCount(vbox2, histo, sigbits);
+    vbox1->vol = vboxGetVolume(vbox1);
+    vbox2->vol = vboxGetVolume(vbox2);
+    *pvbox1 = vbox1;
+    *pvbox2 = vbox2;
+
+    return 0;
+}
+
+
+/*!
+ *  pixcmapGenerateFromMedianCuts()
+ *
+ *      Input:  lh (priority queue of pointers to vboxes)
+ *              histo
+ *              sigbits (valid: 5 or 6)
+ *      Return: cmap, or null on error
+ *
+ *  Notes:
+ *      (1) Each vbox in the heap represents a color in the colormap.
+ *      (2) As a side-effect, the histo becomes an inverse colormap,
+ *          where the part of the array correpsonding to each vbox
+ *          is labeled with the cmap index for that vbox.  Then
+ *          for each rgb pixel, the colormap index is found directly
+ *          by mapping the rgb value to the histo array index.
+ */
+static PIXCMAP *
+pixcmapGenerateFromMedianCuts(L_HEAP   *lh,
+                              l_int32  *histo,
+                              l_int32   sigbits)
+{
+l_int32   index, rval, gval, bval;
+L_BOX3D  *vbox;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixcmapGenerateFromMedianCuts");
+
+    if (!lh)
+        return (PIXCMAP *)ERROR_PTR("lh not defined", procName, NULL);
+    if (!histo)
+        return (PIXCMAP *)ERROR_PTR("histo not defined", procName, NULL);
+
+    rval = gval = bval = 0;  /* make compiler happy */
+    cmap = pixcmapCreate(8);
+    index = 0;
+    while (lheapGetCount(lh) > 0) {
+        vbox = (L_BOX3D *)lheapRemove(lh);
+        vboxGetAverageColor(vbox, histo, sigbits, index, &rval, &gval, &bval);
+        pixcmapAddColor(cmap, rval, gval, bval);
+        LEPT_FREE(vbox);
+        index++;
+    }
+
+    return cmap;
+}
+
+
+/*!
+ *  vboxGetAverageColor()
+ *
+ *      Input:  vbox (3d region of color space for one quantized color)
+ *              histo
+ *              sigbits (valid: 5 or 6)
+ *              index (if >= 0, assign to all colors in histo in this vbox)
+ *              &rval, &gval, &bval (<returned> average color)
+ *      Return: cmap, or null on error
+ *
+ *  Notes:
+ *      (1) The vbox represents one color in the colormap.
+ *      (2) If index >= 0, as a side-effect, all array elements in
+ *          the histo corresponding to the vbox are labeled with this
+ *          cmap index for that vbox.  Otherwise, the histo array
+ *          is not changed.
+ *      (3) The vbox is quantized in sigbits.  So the actual 8-bit color
+ *          components are found by multiplying the quantized value
+ *          by either 4 or 8.  We must add 0.5 to the quantized index
+ *          before multiplying to get the approximate 8-bit color in
+ *          the center of the vbox; otherwise we get values on
+ *          the lower corner.
+ */
+static l_int32
+vboxGetAverageColor(L_BOX3D  *vbox,
+                    l_int32  *histo,
+                    l_int32   sigbits,
+                    l_int32   index,
+                    l_int32  *prval,
+                    l_int32  *pgval,
+                    l_int32  *pbval)
+{
+l_int32  i, j, k, ntot, mult, histoindex, rsum, gsum, bsum;
+
+    PROCNAME("vboxGetAverageColor");
+
+    if (!vbox)
+        return ERROR_INT("vbox not defined", procName, 1);
+    if (!histo)
+        return ERROR_INT("histo not defined", procName, 1);
+    if (!prval || !pgval || !pbval)
+        return ERROR_INT("&p*val not all defined", procName, 1);
+
+    *prval = *pgval = *pbval = 0;
+    ntot = 0;
+    mult = 1 << (8 - sigbits);
+    rsum = gsum = bsum = 0;
+    for (i = vbox->r1; i <= vbox->r2; i++) {
+        for (j = vbox->g1; j <= vbox->g2; j++) {
+            for (k = vbox->b1; k <= vbox->b2; k++) {
+                 histoindex = (i << (2 * sigbits)) + (j << sigbits) + k;
+                 ntot += histo[histoindex];
+                 rsum += (l_int32)(histo[histoindex] * (i + 0.5) * mult);
+                 gsum += (l_int32)(histo[histoindex] * (j + 0.5) * mult);
+                 bsum += (l_int32)(histo[histoindex] * (k + 0.5) * mult);
+                 if (index >= 0)
+                     histo[histoindex] = index;
+            }
+        }
+    }
+
+    if (ntot == 0) {
+        *prval = mult * (vbox->r1 + vbox->r2 + 1) / 2;
+        *pgval = mult * (vbox->g1 + vbox->g2 + 1) / 2;
+        *pbval = mult * (vbox->b1 + vbox->b2 + 1) / 2;
+    } else {
+        *prval = rsum / ntot;
+        *pgval = gsum / ntot;
+        *pbval = bsum / ntot;
+    }
+
+#if  DEBUG_MC_COLORS
+    fprintf(stderr, "ntot[%d] = %d: [%d, %d, %d], (%d, %d, %d)\n",
+            index, ntot, vbox->r2 - vbox->r1 + 1,
+            vbox->g2 - vbox->g1 + 1, vbox->b2 - vbox->b1 + 1,
+            *prval, *pgval, *pbval);
+#endif  /* DEBUG_MC_COLORS */
+
+    return 0;
+}
+
+
+/*!
+ *  vboxGetCount()
+ *
+ *      Input:  vbox (3d region of color space for one quantized color)
+ *              histo
+ *              sigbits (valid: 5 or 6)
+ *      Return: number of image pixels in this region, or 0 on error
+ */
+static l_int32
+vboxGetCount(L_BOX3D  *vbox,
+             l_int32  *histo,
+             l_int32   sigbits)
+{
+l_int32  i, j, k, npix, index;
+
+    PROCNAME("vboxGetCount");
+
+    if (!vbox)
+        return ERROR_INT("vbox not defined", procName, 0);
+    if (!histo)
+        return ERROR_INT("histo not defined", procName, 0);
+
+    npix = 0;
+    for (i = vbox->r1; i <= vbox->r2; i++) {
+        for (j = vbox->g1; j <= vbox->g2; j++) {
+            for (k = vbox->b1; k <= vbox->b2; k++) {
+                 index = (i << (2 * sigbits)) + (j << sigbits) + k;
+                 npix += histo[index];
+            }
+        }
+    }
+
+    return npix;
+}
+
+
+/*!
+ *  vboxGetVolume()
+ *
+ *      Input:  vbox (3d region of color space for one quantized color)
+ *      Return: quantized volume of vbox, or 0 on error
+ */
+static l_int32
+vboxGetVolume(L_BOX3D  *vbox)
+{
+    PROCNAME("vboxGetVolume");
+
+    if (!vbox)
+        return ERROR_INT("vbox not defined", procName, 0);
+
+    return ((vbox->r2 - vbox->r1 + 1) * (vbox->g2 - vbox->g1 + 1) *
+            (vbox->b2 - vbox->b1 + 1));
+}
+
+
+static L_BOX3D *
+box3dCreate(l_int32  r1,
+            l_int32  r2,
+            l_int32  g1,
+            l_int32  g2,
+            l_int32  b1,
+            l_int32  b2)
+{
+L_BOX3D  *vbox;
+
+    vbox = (L_BOX3D *)LEPT_CALLOC(1, sizeof(L_BOX3D));
+    vbox->r1 = r1;
+    vbox->r2 = r2;
+    vbox->g1 = g1;
+    vbox->g2 = g2;
+    vbox->b1 = b1;
+    vbox->b2 = b2;
+    return vbox;
+}
+
+
+/*
+ *  Note: don't copy the sortparam.
+ */
+static L_BOX3D *
+box3dCopy(L_BOX3D  *vbox)
+{
+L_BOX3D  *vboxc;
+
+    PROCNAME("box3dCopy");
+
+    if (!vbox)
+        return (L_BOX3D *)ERROR_PTR("vbox not defined", procName, NULL);
+
+    vboxc = box3dCreate(vbox->r1, vbox->r2, vbox->g1, vbox->g2,
+                        vbox->b1, vbox->b2);
+    vboxc->npix = vbox->npix;
+    vboxc->vol = vbox->vol;
+    return vboxc;
+}
diff --git a/src/colorseg.c b/src/colorseg.c
new file mode 100644 (file)
index 0000000..ea3e2cc
--- /dev/null
@@ -0,0 +1,625 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  colorseg.c
+ *
+ *    Unsupervised color segmentation
+ *
+ *               PIX     *pixColorSegment()
+ *               PIX     *pixColorSegmentCluster()
+ *       static  l_int32  pixColorSegmentTryCluster()
+ *               l_int32  pixAssignToNearestColor()
+ *               l_int32  pixColorSegmentClean()
+ *               l_int32  pixColorSegmentRemoveColors()
+ */
+
+#include "allheaders.h"
+
+    /* Maximum allowed iterations in Phase 1. */
+static const l_int32  MAX_ALLOWED_ITERATIONS = 20;
+
+    /* Factor by which max dist is increased on each iteration */
+static const l_float32  DIST_EXPAND_FACT = 1.3;
+
+    /* Octcube division level for computing nearest colormap color using LUT.
+     * Using 4 should suffice for up to 50 - 100 colors, and it is
+     * very fast.  Using 5 takes 8 times as long to set up the LUT
+     * for little perceptual gain, even with 100 colors. */
+static const l_int32  LEVEL_IN_OCTCUBE = 4;
+
+
+static l_int32 pixColorSegmentTryCluster(PIX *pixd, PIX *pixs,
+                                         l_int32 maxdist, l_int32 maxcolors);
+
+#ifndef  NO_CONSOLE_IO
+#define   DEBUG    0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*------------------------------------------------------------------*
+ *                 Unsupervised color segmentation                  *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixColorSegment()
+ *
+ *      Input:  pixs  (32 bpp; 24-bit color)
+ *              maxdist (max euclidean dist to existing cluster)
+ *              maxcolors (max number of colors allowed in first pass)
+ *              selsize (linear size of sel for closing to remove noise)
+ *              finalcolors (max number of final colors allowed after 4th pass)
+ *      Return: pixd (8 bit with colormap), or null on error
+ *
+ *  Color segmentation proceeds in four phases:
+ *
+ *  Phase 1:  pixColorSegmentCluster()
+ *  The image is traversed in raster order.  Each pixel either
+ *  becomes the representative for a new cluster or is assigned to an
+ *  existing cluster.  Assignment is greedy.  The data is stored in
+ *  a colormapped image.  Three auxiliary arrays are used to hold
+ *  the colors of the representative pixels, for fast lookup.
+ *  The average color in each cluster is computed.
+ *
+ *  Phase 2.  pixAssignToNearestColor()
+ *  A second (non-greedy) clustering pass is performed, where each pixel
+ *  is assigned to the nearest cluster (average).  We also keep track
+ *  of how many pixels are assigned to each cluster.
+ *
+ *  Phase 3.  pixColorSegmentClean()
+ *  For each cluster, starting with the largest, do a morphological
+ *  closing to eliminate small components within larger ones.
+ *
+ *  Phase 4.  pixColorSegmentRemoveColors()
+ *  Eliminate all colors except the most populated 'finalcolors'.
+ *  Then remove unused colors from the colormap, and reassign those
+ *  pixels to the nearest remaining cluster, using the original pixel values.
+ *
+ *  Notes:
+ *      (1) The goal is to generate a small number of colors.
+ *          Typically this would be specified by 'finalcolors',
+ *          a number that would be somewhere between 3 and 6.
+ *          The parameter 'maxcolors' specifies the maximum number of
+ *          colors generated in the first phase.  This should be
+ *          larger than finalcolors, perhaps twice as large.
+ *          If more than 'maxcolors' are generated in the first phase
+ *          using the input 'maxdist', the distance is repeatedly
+ *          increased by a multiplicative factor until the condition
+ *          is satisfied.  The implicit relation between 'maxdist'
+ *          and 'maxcolors' is thus adjusted programmatically.
+ *      (2) As a very rough guideline, given a target value of 'finalcolors',
+ *          here are approximate values of 'maxdist' and 'maxcolors'
+ *          to start with:
+ *
+ *               finalcolors    maxcolors    maxdist
+ *               -----------    ---------    -------
+ *                   3             6          100
+ *                   4             8           90
+ *                   5            10           75
+ *                   6            12           60
+ *
+ *          For a given number of finalcolors, if you use too many
+ *          maxcolors, the result will be noisy.  If you use too few,
+ *          the result will be a relatively poor assignment of colors.
+ */
+PIX *
+pixColorSegment(PIX     *pixs,
+                l_int32  maxdist,
+                l_int32  maxcolors,
+                l_int32  selsize,
+                l_int32  finalcolors)
+{
+l_int32   *countarray;
+PIX       *pixd;
+
+    PROCNAME("pixColorSegment");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("must be rgb color", procName, NULL);
+
+        /* Phase 1; original segmentation */
+    if ((pixd = pixColorSegmentCluster(pixs, maxdist, maxcolors)) == NULL)
+        return (PIX *)ERROR_PTR("pixt1 not made", procName, NULL);
+#if DEBUG
+    pixWrite("/tmp/colorseg1.png", pixd, IFF_PNG);
+#endif  /* DEBUG */
+
+        /* Phase 2; refinement in pixel assignment */
+    if ((countarray = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("countarray not made", procName, NULL);
+    pixAssignToNearestColor(pixd, pixs, NULL, LEVEL_IN_OCTCUBE, countarray);
+#if DEBUG
+    pixWrite("/tmp/colorseg2.png", pixd, IFF_PNG);
+#endif  /* DEBUG */
+
+        /* Phase 3: noise removal by separately closing each color */
+    pixColorSegmentClean(pixd, selsize, countarray);
+    LEPT_FREE(countarray);
+#if DEBUG
+    pixWrite("/tmp/colorseg3.png", pixd, IFF_PNG);
+#endif  /* DEBUG */
+
+        /* Phase 4: removal of colors with small population and
+         * reassignment of pixels to remaining colors */
+    pixColorSegmentRemoveColors(pixd, pixs, finalcolors);
+    return pixd;
+}
+
+
+/*!
+ *  pixColorSegmentCluster()
+ *
+ *      Input:  pixs  (32 bpp; 24-bit color)
+ *              maxdist (max euclidean dist to existing cluster)
+ *              maxcolors (max number of colors allowed in first pass)
+ *      Return: pixd (8 bit with colormap), or null on error
+ *
+ *  Notes:
+ *      (1) This is phase 1.  See description in pixColorSegment().
+ *      (2) Greedy unsupervised classification.  If the limit 'maxcolors'
+ *          is exceeded, the computation is repeated with a larger
+ *          allowed cluster size.
+ *      (3) On each successive iteration, 'maxdist' is increased by a
+ *          constant factor.  See comments in pixColorSegment() for
+ *          a guideline on parameter selection.
+ *          Note that the diagonal of the 8-bit rgb color cube is about
+ *          440, so for 'maxdist' = 440, you are guaranteed to get 1 color!
+ */
+PIX *
+pixColorSegmentCluster(PIX       *pixs,
+                       l_int32    maxdist,
+                       l_int32    maxcolors)
+{
+l_int32    w, h, newmaxdist, ret, niters, ncolors, success;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixColorSegmentCluster");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("must be rgb color", procName, NULL);
+
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    cmap = pixcmapCreate(8);
+    pixSetColormap(pixd, cmap);
+    pixCopyResolution(pixd, pixs);
+
+    newmaxdist = maxdist;
+    niters = 0;
+    success = TRUE;
+    while (1) {
+        ret = pixColorSegmentTryCluster(pixd, pixs, newmaxdist, maxcolors);
+        niters++;
+        if (!ret) {
+            ncolors = pixcmapGetCount(cmap);
+            L_INFO("Success with %d colors after %d iters\n", procName,
+                   ncolors, niters);
+            break;
+        }
+        if (niters == MAX_ALLOWED_ITERATIONS) {
+            L_WARNING("too many iters; newmaxdist = %d\n",
+                      procName, newmaxdist);
+            success = FALSE;
+            break;
+        }
+        newmaxdist = (l_int32)(DIST_EXPAND_FACT * (l_float32)newmaxdist);
+    }
+
+    if (!success) {
+        pixDestroy(&pixd);
+        return (PIX *)ERROR_PTR("failure in phase 1", procName, NULL);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixColorSegmentTryCluster()
+ *
+ *      Input:  pixd
+ *              pixs
+ *              maxdist
+ *              maxcolors
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Note: This function should only be called from pixColorSegCluster()
+ */
+static l_int32
+pixColorSegmentTryCluster(PIX       *pixd,
+                          PIX       *pixs,
+                          l_int32    maxdist,
+                          l_int32    maxcolors)
+{
+l_int32    rmap[256], gmap[256], bmap[256];
+l_int32    w, h, wpls, wpld, i, j, k, found, ret, index, ncolors;
+l_int32    rval, gval, bval, dist2, maxdist2;
+l_int32    countarray[256];
+l_int32    rsum[256], gsum[256], bsum[256];
+l_uint32  *ppixel;
+l_uint32  *datas, *datad, *lines, *lined;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixColorSegmentTryCluster");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    maxdist2 = maxdist * maxdist;
+    cmap = pixGetColormap(pixd);
+    pixcmapClear(cmap);
+    for (k = 0; k < 256; k++) {
+        rsum[k] = gsum[k] = bsum[k] = 0;
+        rmap[k] = gmap[k] = bmap[k] = 0;
+    }
+
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            ppixel = lines + j;
+            rval = GET_DATA_BYTE(ppixel, COLOR_RED);
+            gval = GET_DATA_BYTE(ppixel, COLOR_GREEN);
+            bval = GET_DATA_BYTE(ppixel, COLOR_BLUE);
+            ncolors = pixcmapGetCount(cmap);
+            found = FALSE;
+            for (k = 0; k < ncolors; k++) {
+                dist2 = (rval - rmap[k]) * (rval - rmap[k]) +
+                        (gval - gmap[k]) * (gval - gmap[k]) +
+                        (bval - bmap[k]) * (bval - bmap[k]);
+                if (dist2 <= maxdist2) {  /* take it; greedy */
+                    found = TRUE;
+                    SET_DATA_BYTE(lined, j, k);
+                    countarray[k]++;
+                    rsum[k] += rval;
+                    gsum[k] += gval;
+                    bsum[k] += bval;
+                    break;
+                }
+            }
+            if (!found) {  /* Add a new color */
+                ret = pixcmapAddNewColor(cmap, rval, gval, bval, &index);
+/*                fprintf(stderr,
+                        "index = %d, (i,j) = (%d,%d), rgb = (%d, %d, %d)\n",
+                        index, i, j, rval, gval, bval); */
+                if (ret == 0 && index < maxcolors) {
+                    countarray[index] = 1;
+                    SET_DATA_BYTE(lined, j, index);
+                    rmap[index] = rval;
+                    gmap[index] = gval;
+                    bmap[index] = bval;
+                    rsum[index] = rval;
+                    gsum[index] = gval;
+                    bsum[index] = bval;
+                } else {
+                    L_INFO("maxcolors exceeded for maxdist = %d\n",
+                           procName, maxdist);
+                    return 1;
+                }
+            }
+        }
+    }
+
+        /* Replace the colors in the colormap by the averages */
+    for (k = 0; k < ncolors; k++) {
+        rval = rsum[k] / countarray[k];
+        gval = gsum[k] / countarray[k];
+        bval = bsum[k] / countarray[k];
+        pixcmapResetColor(cmap, k, rval, gval, bval);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixAssignToNearestColor()
+ *
+ *      Input:  pixd  (8 bpp, colormapped)
+ *              pixs  (32 bpp; 24-bit color)
+ *              pixm  (<optional> 1 bpp)
+ *              level (of octcube used for finding nearest color in cmap)
+ *              countarray (<optional> ptr to array, in which we can store
+ *                          the number of pixels found in each color in
+ *                          the colormap in pixd)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is used in phase 2 of color segmentation, where pixs
+ *          is the original input image to pixColorSegment(), and
+ *          pixd is the colormapped image returned from
+ *          pixColorSegmentCluster().  It is also used, with a mask,
+ *          in phase 4.
+ *      (2) This is an in-place operation.
+ *      (3) The colormap in pixd is unchanged.
+ *      (4) pixs and pixd must be the same size (w, h).
+ *      (5) The selection mask pixm can be null.  If it exists, it must
+ *          be the same size as pixs and pixd, and only pixels
+ *          corresponding to fg in pixm are assigned.  Set to
+ *          NULL if all pixels in pixd are to be assigned.
+ *      (6) The countarray can be null.  If it exists, it is pre-allocated
+ *          and of a size at least equal to the size of the colormap in pixd.
+ *      (7) This does a best-fit (non-greedy) assignment of pixels to
+ *          existing clusters.  Specifically, it assigns each pixel
+ *          in pixd to the color index in the pixd colormap that has a
+ *          color closest to the corresponding rgb pixel in pixs.
+ *      (8) 'level' is the octcube level used to quickly find the nearest
+ *          color in the colormap for each pixel.  For color segmentation,
+ *          this parameter is set to LEVEL_IN_OCTCUBE.
+ *      (9) We build a mapping table from octcube to colormap index so
+ *          that this function can run in a time (otherwise) independent
+ *          of the number of colors in the colormap.  This avoids a
+ *          brute-force search for the closest colormap color to each
+ *          pixel in the image.
+ */
+l_int32
+pixAssignToNearestColor(PIX      *pixd,
+                        PIX      *pixs,
+                        PIX      *pixm,
+                        l_int32   level,
+                        l_int32  *countarray)
+{
+l_int32    w, h, wpls, wpld, wplm, i, j;
+l_int32    rval, gval, bval, index;
+l_int32   *cmaptab;
+l_uint32   octindex;
+l_uint32  *rtab, *gtab, *btab;
+l_uint32  *ppixel;
+l_uint32  *datas, *datad, *datam, *lines, *lined, *linem;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixAssignToNearestColor");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if ((cmap = pixGetColormap(pixd)) == NULL)
+        return ERROR_INT("cmap not found", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not 32 bpp", procName, 1);
+
+        /* Set up the tables to map rgb to the nearest colormap index */
+    if (makeRGBToIndexTables(&rtab, &gtab, &btab, level))
+        return ERROR_INT("index tables not made", procName, 1);
+    if ((cmaptab = pixcmapToOctcubeLUT(cmap, level, L_MANHATTAN_DISTANCE))
+            == NULL)
+        return ERROR_INT("cmaptab not made", procName, 1);
+
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    if (pixm) {
+        datam = pixGetData(pixm);
+        wplm = pixGetWpl(pixm);
+    }
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        if (pixm)
+            linem = datam + i * wplm;
+        for (j = 0; j < w; j++) {
+            if (pixm) {
+                if (!GET_DATA_BIT(linem, j))
+                    continue;
+            }
+            ppixel = lines + j;
+            rval = GET_DATA_BYTE(ppixel, COLOR_RED);
+            gval = GET_DATA_BYTE(ppixel, COLOR_GREEN);
+            bval = GET_DATA_BYTE(ppixel, COLOR_BLUE);
+                /* Map from rgb to octcube index */
+            getOctcubeIndexFromRGB(rval, gval, bval, rtab, gtab, btab,
+                                   &octindex);
+                /* Map from octcube index to nearest colormap index */
+            index = cmaptab[octindex];
+            if (countarray)
+                countarray[index]++;
+            SET_DATA_BYTE(lined, j, index);
+        }
+    }
+
+    LEPT_FREE(cmaptab);
+    LEPT_FREE(rtab);
+    LEPT_FREE(gtab);
+    LEPT_FREE(btab);
+    return 0;
+}
+
+
+/*!
+ *  pixColorSegmentClean()
+ *
+ *      Input:  pixs  (8 bpp, colormapped)
+ *              selsize (for closing)
+ *              countarray (ptr to array containing the number of pixels
+ *                          found in each color in the colormap)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This operation is in-place.
+ *      (2) This is phase 3 of color segmentation.  It is the first
+ *          part of a two-step noise removal process.  Colors with a
+ *          large population are closed first; this operation absorbs
+ *          small sets of intercolated pixels of a different color.
+ */
+l_int32
+pixColorSegmentClean(PIX      *pixs,
+                     l_int32   selsize,
+                     l_int32  *countarray)
+{
+l_int32    i, ncolors, val;
+l_uint32   val32;
+NUMA      *na, *nasi;
+PIX       *pixt1, *pixt2;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixColorSegmentClean");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not 8 bpp", procName, 1);
+    if ((cmap = pixGetColormap(pixs)) == NULL)
+        return ERROR_INT("cmap not found", procName, 1);
+    if (!countarray)
+        return ERROR_INT("countarray not defined", procName, 1);
+    if (selsize <= 1)
+        return 0;  /* nothing to do */
+
+        /* Sort colormap indices in decreasing order of pixel population */
+    ncolors = pixcmapGetCount(cmap);
+    na = numaCreate(ncolors);
+    for (i = 0; i < ncolors; i++)
+        numaAddNumber(na, countarray[i]);
+    if ((nasi = numaGetSortIndex(na, L_SORT_DECREASING)) == NULL)
+        return ERROR_INT("nasi not made", procName, 1);
+
+        /* For each color, in order of decreasing population,
+         * do a closing and absorb the added pixels.  Note that
+         * if the closing removes pixels at the border, they'll
+         * still appear in the xor and will be properly (re)set. */
+    for (i = 0; i < ncolors; i++) {
+        numaGetIValue(nasi, i, &val);
+        pixt1 = pixGenerateMaskByValue(pixs, val, 1);
+        pixt2 = pixCloseSafeCompBrick(NULL, pixt1, selsize, selsize);
+        pixXor(pixt2, pixt2, pixt1);  /* pixels to be added to type 'val' */
+        pixcmapGetColor32(cmap, val, &val32);
+        pixSetMasked(pixs, pixt2, val32);  /* add them */
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt2);
+    }
+    numaDestroy(&na);
+    numaDestroy(&nasi);
+    return 0;
+}
+
+
+/*!
+ *  pixColorSegmentRemoveColors()
+ *
+ *      Input:  pixd  (8 bpp, colormapped)
+ *              pixs  (32 bpp rgb, with initial pixel values)
+ *              finalcolors (max number of colors to retain)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This operation is in-place.
+ *      (2) This is phase 4 of color segmentation, and the second part
+ *          of the 2-step noise removal.  Only 'finalcolors' different
+ *          colors are retained, with colors with smaller populations
+ *          being replaced by the nearest color of the remaining colors.
+ *          For highest accuracy, for pixels that are being replaced,
+ *          we find the nearest colormap color  to the original rgb color.
+ */
+l_int32
+pixColorSegmentRemoveColors(PIX     *pixd,
+                            PIX     *pixs,
+                            l_int32  finalcolors)
+{
+l_int32    i, ncolors, index, tempindex;
+l_int32   *tab;
+l_uint32   tempcolor;
+NUMA      *na, *nasi;
+PIX       *pixm;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixColorSegmentRemoveColors");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (pixGetDepth(pixd) != 8)
+        return ERROR_INT("pixd not 8 bpp", procName, 1);
+    if ((cmap = pixGetColormap(pixd)) == NULL)
+        return ERROR_INT("cmap not found", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    ncolors = pixcmapGetCount(cmap);
+    if (finalcolors >= ncolors)  /* few enough colors already; nothing to do */
+        return 0;
+
+        /* Generate a mask over all pixels that are not in the
+         * 'finalcolors' most populated colors.  Save the colormap
+         * index of any one of the retained colors in 'tempindex'.
+         * The LUT has values 0 for the 'finalcolors' most populated colors,
+         * which will be retained; and 1 for the rest, which are marked
+         * by fg pixels in pixm and will be removed. */
+    na = pixGetCmapHistogram(pixd, 1);
+    if ((nasi = numaGetSortIndex(na, L_SORT_DECREASING)) == NULL) {
+        numaDestroy(&na);
+        return ERROR_INT("nasi not made", procName, 1);
+    }
+    numaGetIValue(nasi, finalcolors - 1, &tempindex);  /* retain down to this */
+    pixcmapGetColor32(cmap, tempindex, &tempcolor);  /* use this color */
+    tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    for (i = finalcolors; i < ncolors; i++) {
+        numaGetIValue(nasi, i, &index);
+        tab[index] = 1;
+    }
+
+    pixm = pixMakeMaskFromLUT(pixd, tab);
+    LEPT_FREE(tab);
+
+        /* Reassign the masked pixels temporarily to the saved index
+         * (tempindex).  This guarantees that no pixels are labeled by
+         * a colormap index of any colors that will be removed.
+         * The actual value doesn't matter, as long as it's one
+         * of the retained colors, because these pixels will later
+         * be reassigned based on the full set of colors retained
+         * in the colormap. */
+    pixSetMasked(pixd, pixm, tempcolor);
+
+        /* Now remove unused colors from the colormap.  This reassigns
+         * image pixels as required. */
+    pixRemoveUnusedColors(pixd);
+
+        /* Finally, reassign the pixels under the mask (those that were
+         * given a 'tempindex' value) to the nearest color in the colormap.
+         * This is the function used in phase 2 on all image pixels; here
+         * it is only used on the masked pixels given by pixm. */
+    pixAssignToNearestColor(pixd, pixs, pixm, LEVEL_IN_OCTCUBE, NULL);
+
+    pixDestroy(&pixm);
+    numaDestroy(&na);
+    numaDestroy(&nasi);
+    return 0;
+}
diff --git a/src/colorspace.c b/src/colorspace.c
new file mode 100644 (file)
index 0000000..80e1e21
--- /dev/null
@@ -0,0 +1,2348 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  colorspace.c
+ *
+ *      Colorspace conversion between RGB and HSV
+ *           PIX        *pixConvertRGBToHSV()
+ *           PIX        *pixConvertHSVToRGB()
+ *           l_int32     convertRGBToHSV()
+ *           l_int32     convertHSVToRGB()
+ *           l_int32     pixcmapConvertRGBToHSV()
+ *           l_int32     pixcmapConvertHSVToRGB()
+ *           PIX        *pixConvertRGBToHue()
+ *           PIX        *pixConvertRGBToSaturation()
+ *           PIX        *pixConvertRGBToValue()
+ *
+ *      Selection and display of range of colors in HSV space
+ *           PIX        *pixMakeRangeMaskHS()
+ *           PIX        *pixMakeRangeMaskHV()
+ *           PIX        *pixMakeRangeMaskSV()
+ *           PIX        *pixMakeHistoHS()
+ *           PIX        *pixMakeHistoHV()
+ *           PIX        *pixMakeHistoSV()
+ *           PIX        *pixFindHistoPeaksHSV()
+ *           PIX        *displayHSVColorRange()
+ *
+ *      Colorspace conversion between RGB and YUV
+ *           PIX        *pixConvertRGBToYUV()
+ *           PIX        *pixConvertYUVToRGB()
+ *           l_int32     convertRGBToYUV()
+ *           l_int32     convertYUVToRGB()
+ *           l_int32     pixcmapConvertRGBToYUV()
+ *           l_int32     pixcmapConvertYUVToRGB()
+ *
+ *      Colorspace conversion between RGB and XYZ
+ *           FPIXA      *pixConvertRGBToXYZ()
+ *           PIX        *fpixaConvertXYZToRGB()
+ *           l_int32     convertRGBToXYZ()
+ *           l_int32     convertXYZToRGB()
+ *
+ *      Colorspace conversion between XYZ and LAB
+ *           FPIXA      *fpixaConvertXYZToLAB()
+ *           PIX        *fpixaConvertLABToXYZ()
+ *           l_int32     convertXYZToLAB()
+ *           l_int32     convertLABToXYZ()
+ *           static l_float32  lab_forward()
+ *           static l_float32  lab_reverse()
+ *
+ *      Colorspace conversion between RGB and LAB
+ *           FPIXA      *pixConvertRGBToLAB()
+ *           PIX        *fpixaConvertLABToRGB()
+ *           l_int32     convertRGBToLAB()
+ *           l_int32     convertLABToRGB()
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_HISTO       0
+#define  SLOW_CUBE_ROOT    0
+#endif  /* ~NO_CONSOLE_IO */
+
+    /* Functions used in xyz <--> lab conversions */
+static l_float32 lab_forward(l_float32 v);
+static l_float32 lab_reverse(l_float32 v);
+
+
+/*---------------------------------------------------------------------------*
+ *                  Colorspace conversion between RGB and HSB                *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertRGBToHSV()
+ *
+ *      Input:  pixd (can be NULL; if not NULL, must == pixs)
+ *              pixs
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) For pixs = pixd, this is in-place; otherwise pixd must be NULL.
+ *      (2) The definition of our HSV space is given in convertRGBToHSV().
+ *      (3) The h, s and v values are stored in the same places as
+ *          the r, g and b values, respectively.  Here, they are explicitly
+ *          placed in the 3 MS bytes in the pixel.
+ *      (4) Normalizing to 1 and considering the r,g,b components,
+ *          a simple way to understand the HSV space is:
+ *           - v = max(r,g,b)
+ *           - s = (max - min) / max
+ *           - h ~ (mid - min) / (max - min)  [apart from signs and constants]
+ *      (5) Normalizing to 1, some properties of the HSV space are:
+ *           - For gray values (r = g = b) along the continuum between
+ *             black and white:
+ *                s = 0  (becoming undefined as you approach black)
+ *                h is undefined everywhere
+ *           - Where one component is saturated and the others are zero:
+ *                v = 1
+ *                s = 1
+ *                h = 0 (r = max), 1/3 (g = max), 2/3 (b = max)
+ *           - Where two components are saturated and the other is zero:
+ *                v = 1
+ *                s = 1
+ *                h = 1/2 (if r = 0), 5/6 (if g = 0), 1/6 (if b = 0)
+ */
+PIX *
+pixConvertRGBToHSV(PIX  *pixd,
+                   PIX  *pixs)
+{
+l_int32    w, h, d, wpl, i, j, rval, gval, bval, hval, sval, vval;
+l_uint32  *line, *data;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixConvertRGBToHSV");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixd && pixd != pixs)
+        return (PIX *)ERROR_PTR("pixd defined and not inplace", procName, pixd);
+
+    d = pixGetDepth(pixs);
+    cmap = pixGetColormap(pixs);
+    if (!cmap && d != 32)
+        return (PIX *)ERROR_PTR("not cmapped or rgb", procName, pixd);
+
+    if (!pixd)
+        pixd = pixCopy(NULL, pixs);
+
+    cmap = pixGetColormap(pixd);
+    if (cmap) {   /* just convert the colormap */
+        pixcmapConvertRGBToHSV(cmap);
+        return pixd;
+    }
+
+        /* Convert RGB image */
+    pixGetDimensions(pixd, &w, &h, NULL);
+    wpl = pixGetWpl(pixd);
+    data = pixGetData(pixd);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(line[j], &rval, &gval, &bval);
+            convertRGBToHSV(rval, gval, bval, &hval, &sval, &vval);
+            line[j] = (hval << 24) | (sval << 16) | (vval << 8);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixConvertHSVToRGB()
+ *
+ *      Input:  pixd (can be NULL; if not NULL, must == pixs)
+ *              pixs
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) For pixs = pixd, this is in-place; otherwise pixd must be NULL.
+ *      (2) The user takes responsibility for making sure that pixs is
+ *          in our HSV space.  The definition of our HSV space is given
+ *          in convertRGBToHSV().
+ *      (3) The h, s and v values are stored in the same places as
+ *          the r, g and b values, respectively.  Here, they are explicitly
+ *          placed in the 3 MS bytes in the pixel.
+ */
+PIX *
+pixConvertHSVToRGB(PIX  *pixd,
+                   PIX  *pixs)
+{
+l_int32    w, h, d, wpl, i, j, rval, gval, bval, hval, sval, vval;
+l_uint32   pixel;
+l_uint32  *line, *data;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixConvertHSVToRGB");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixd && pixd != pixs)
+        return (PIX *)ERROR_PTR("pixd defined and not inplace", procName, pixd);
+
+    d = pixGetDepth(pixs);
+    cmap = pixGetColormap(pixs);
+    if (!cmap && d != 32)
+        return (PIX *)ERROR_PTR("not cmapped or hsv", procName, pixd);
+
+    if (!pixd)
+        pixd = pixCopy(NULL, pixs);
+
+    cmap = pixGetColormap(pixd);
+    if (cmap) {   /* just convert the colormap */
+        pixcmapConvertHSVToRGB(cmap);
+        return pixd;
+    }
+
+        /* Convert HSV image */
+    pixGetDimensions(pixd, &w, &h, NULL);
+    wpl = pixGetWpl(pixd);
+    data = pixGetData(pixd);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            pixel = line[j];
+            hval = pixel >> 24;
+            sval = (pixel >> 16) & 0xff;
+            vval = (pixel >> 8) & 0xff;
+            convertHSVToRGB(hval, sval, vval, &rval, &gval, &bval);
+            composeRGBPixel(rval, gval, bval, line + j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  convertRGBToHSV()
+ *
+ *      Input:  rval, gval, bval (RGB input)
+ *              &hval, &sval, &vval (<return> HSV values)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The range of returned values is:
+ *            h [0 ... 239]
+ *            s [0 ... 255]
+ *            v [0 ... 255]
+ *      (2) If r = g = b, the pixel is gray (s = 0), and we define h = 0.
+ *      (3) h wraps around, so that h = 0 and h = 240 are equivalent
+ *          in hue space.
+ *      (4) h has the following correspondence to color:
+ *            h = 0         magenta
+ *            h = 40        red
+ *            h = 80        yellow
+ *            h = 120       green
+ *            h = 160       cyan
+ *            h = 200       blue
+ */
+l_int32
+convertRGBToHSV(l_int32   rval,
+                l_int32   gval,
+                l_int32   bval,
+                l_int32  *phval,
+                l_int32  *psval,
+                l_int32  *pvval)
+{
+l_int32    minrg, maxrg, min, max, delta;
+l_float32  h;
+
+    PROCNAME("convertRGBToHSV");
+
+    if (phval) *phval = 0;
+    if (psval) *psval = 0;
+    if (pvval) *pvval = 0;
+    if (!phval || !psval || !pvval)
+        return ERROR_INT("&hval, &sval, &vval not all defined", procName, 1);
+
+    minrg = L_MIN(rval, gval);
+    min = L_MIN(minrg, bval);
+    maxrg = L_MAX(rval, gval);
+    max = L_MAX(maxrg, bval);
+    delta = max - min;
+
+    *pvval = max;
+    if (delta == 0) {  /* gray; no chroma */
+        *phval = 0;
+        *psval = 0;
+    } else {
+        *psval = (l_int32)(255. * (l_float32)delta / (l_float32)max + 0.5);
+        if (rval == max)  /* between magenta and yellow */
+            h = (l_float32)(gval - bval) / (l_float32)delta;
+        else if (gval == max)  /* between yellow and cyan */
+            h = 2. + (l_float32)(bval - rval) / (l_float32)delta;
+        else  /* between cyan and magenta */
+            h = 4. + (l_float32)(rval - gval) / (l_float32)delta;
+        h *= 40.0;
+        if (h < 0.0)
+            h += 240.0;
+        if (h >= 239.5)
+            h = 0.0;
+        *phval = (l_int32)(h + 0.5);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  convertHSVToRGB()
+ *
+ *      Input:  hval, sval, vval
+ *              &rval, &gval, &bval (<return> RGB values)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See convertRGBToHSV() for valid input range of HSV values
+ *          and their interpretation in color space.
+ */
+l_int32
+convertHSVToRGB(l_int32   hval,
+                l_int32   sval,
+                l_int32   vval,
+                l_int32  *prval,
+                l_int32  *pgval,
+                l_int32  *pbval)
+{
+l_int32   i, x, y, z;
+l_float32 h, f, s;
+
+    PROCNAME("convertHSVToRGB");
+
+    if (prval) *prval = 0;
+    if (pgval) *pgval = 0;
+    if (pbval) *pbval = 0;
+    if (!prval || !pgval || !pbval)
+        return ERROR_INT("&rval, &gval, &bval not all defined", procName, 1);
+
+    if (sval == 0) {  /* gray */
+        *prval = vval;
+        *pgval = vval;
+        *pbval = vval;
+    } else {
+        if (hval < 0 || hval > 240)
+            return ERROR_INT("invalid hval", procName, 1);
+        if (hval == 240)
+            hval = 0;
+        h = (l_float32)hval / 40.;
+        i = (l_int32)h;
+        f = h - i;
+        s = (l_float32)sval / 255.;
+        x = (l_int32)(vval * (1. - s) + 0.5);
+        y = (l_int32)(vval * (1. - s * f) + 0.5);
+        z = (l_int32)(vval * (1. - s * (1. - f)) + 0.5);
+        switch (i)
+        {
+        case 0:
+            *prval = vval;
+            *pgval = z;
+            *pbval = x;
+            break;
+        case 1:
+            *prval = y;
+            *pgval = vval;
+            *pbval = x;
+            break;
+        case 2:
+            *prval = x;
+            *pgval = vval;
+            *pbval = z;
+            break;
+        case 3:
+            *prval = x;
+            *pgval = y;
+            *pbval = vval;
+            break;
+        case 4:
+            *prval = z;
+            *pgval = x;
+            *pbval = vval;
+            break;
+        case 5:
+            *prval = vval;
+            *pgval = x;
+            *pbval = y;
+            break;
+        default:  /* none possible */
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixcmapConvertRGBToHSV()
+ *
+ *      Input:  colormap
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      - in-place transform
+ *      - See convertRGBToHSV() for def'n of HSV space.
+ *      - replaces: r --> h, g --> s, b --> v
+ */
+l_int32
+pixcmapConvertRGBToHSV(PIXCMAP  *cmap)
+{
+l_int32   i, ncolors, rval, gval, bval, hval, sval, vval;
+
+    PROCNAME("pixcmapConvertRGBToHSV");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    ncolors = pixcmapGetCount(cmap);
+    for (i = 0; i < ncolors; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        convertRGBToHSV(rval, gval, bval, &hval, &sval, &vval);
+        pixcmapResetColor(cmap, i, hval, sval, vval);
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixcmapConvertHSVToRGB()
+ *
+ *      Input:  colormap
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      - in-place transform
+ *      - See convertRGBToHSV() for def'n of HSV space.
+ *      - replaces: h --> r, s --> g, v --> b
+ */
+l_int32
+pixcmapConvertHSVToRGB(PIXCMAP  *cmap)
+{
+l_int32   i, ncolors, rval, gval, bval, hval, sval, vval;
+
+    PROCNAME("pixcmapConvertHSVToRGB");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    ncolors = pixcmapGetCount(cmap);
+    for (i = 0; i < ncolors; i++) {
+        pixcmapGetColor(cmap, i, &hval, &sval, &vval);
+        convertHSVToRGB(hval, sval, vval, &rval, &gval, &bval);
+        pixcmapResetColor(cmap, i, rval, gval, bval);
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixConvertRGBToHue()
+ *
+ *      Input:  pixs (32 bpp RGB or 8 bpp with colormap)
+ *      Return: pixd (8 bpp hue of HSV), or null on error
+ *
+ *  Notes:
+ *      (1) The conversion to HSV hue is in-lined here.
+ *      (2) If there is a colormap, it is removed.
+ *      (3) If you just want the hue component, this does it
+ *          at about 10 Mpixels/sec/GHz, which is about
+ *          2x faster than using pixConvertRGBToHSV()
+ */
+PIX *
+pixConvertRGBToHue(PIX  *pixs)
+{
+l_int32    w, h, d, wplt, wpld;
+l_int32    i, j, rval, gval, bval, hval, minrg, min, maxrg, max, delta;
+l_float32  fh;
+l_uint32   pixel;
+l_uint32  *linet, *lined, *datat, *datad;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixConvertRGBToHue");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32 && !pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("not cmapped or rgb", procName, NULL);
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+
+        /* Convert RGB image */
+    pixd = pixCreate(w, h, 8);
+    pixCopyResolution(pixd, pixs);
+    wplt = pixGetWpl(pixt);
+    datat = pixGetData(pixt);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            pixel = linet[j];
+            extractRGBValues(pixel, &rval, &gval, &bval);
+            minrg = L_MIN(rval, gval);
+            min = L_MIN(minrg, bval);
+            maxrg = L_MAX(rval, gval);
+            max = L_MAX(maxrg, bval);
+            delta = max - min;
+            if (delta == 0) {  /* gray; no chroma */
+                hval = 0;
+            } else {
+                if (rval == max)  /* between magenta and yellow */
+                    fh = (l_float32)(gval - bval) / (l_float32)delta;
+                else if (gval == max)  /* between yellow and cyan */
+                    fh = 2. + (l_float32)(bval - rval) / (l_float32)delta;
+                else  /* between cyan and magenta */
+                    fh = 4. + (l_float32)(rval - gval) / (l_float32)delta;
+                fh *= 40.0;
+                if (fh < 0.0)
+                    fh += 240.0;
+                hval = (l_int32)(fh + 0.5);
+            }
+            SET_DATA_BYTE(lined, j, hval);
+        }
+    }
+    pixDestroy(&pixt);
+
+    return pixd;
+}
+
+
+
+/*!
+ *  pixConvertRGBToSaturation()
+ *
+ *      Input:  pixs (32 bpp RGB or 8 bpp with colormap)
+ *      Return: pixd (8 bpp sat of HSV), or null on error
+ *
+ *  Notes:
+ *      (1) The conversion to HSV sat is in-lined here.
+ *      (2) If there is a colormap, it is removed.
+ *      (3) If you just want the saturation component, this does it
+ *          at about 12 Mpixels/sec/GHz.
+ */
+PIX *
+pixConvertRGBToSaturation(PIX  *pixs)
+{
+l_int32    w, h, d, wplt, wpld;
+l_int32    i, j, rval, gval, bval, sval, minrg, min, maxrg, max, delta;
+l_uint32   pixel;
+l_uint32  *linet, *lined, *datat, *datad;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixConvertRGBToSaturation");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32 && !pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("not cmapped or rgb", procName, NULL);
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+
+        /* Convert RGB image */
+    pixd = pixCreate(w, h, 8);
+    pixCopyResolution(pixd, pixs);
+    wplt = pixGetWpl(pixt);
+    datat = pixGetData(pixt);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            pixel = linet[j];
+            extractRGBValues(pixel, &rval, &gval, &bval);
+            minrg = L_MIN(rval, gval);
+            min = L_MIN(minrg, bval);
+            maxrg = L_MAX(rval, gval);
+            max = L_MAX(maxrg, bval);
+            delta = max - min;
+            if (delta == 0)  /* gray; no chroma */
+                sval = 0;
+            else
+                sval = (l_int32)(255. *
+                                 (l_float32)delta / (l_float32)max + 0.5);
+            SET_DATA_BYTE(lined, j, sval);
+        }
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvertRGBToValue()
+ *
+ *      Input:  pixs (32 bpp RGB or 8 bpp with colormap)
+ *      Return: pixd (8 bpp max component intensity of HSV), or null on error
+ *
+ *  Notes:
+ *      (1) The conversion to HSV sat is in-lined here.
+ *      (2) If there is a colormap, it is removed.
+ *      (3) If you just want the value component, this does it
+ *          at about 35 Mpixels/sec/GHz.
+ */
+PIX *
+pixConvertRGBToValue(PIX  *pixs)
+{
+l_int32    w, h, d, wplt, wpld;
+l_int32    i, j, rval, gval, bval, maxrg, max;
+l_uint32   pixel;
+l_uint32  *linet, *lined, *datat, *datad;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixConvertRGBToValue");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32 && !pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("not cmapped or rgb", procName, NULL);
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+
+        /* Convert RGB image */
+    pixd = pixCreate(w, h, 8);
+    pixCopyResolution(pixd, pixs);
+    wplt = pixGetWpl(pixt);
+    datat = pixGetData(pixt);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            pixel = linet[j];
+            extractRGBValues(pixel, &rval, &gval, &bval);
+            maxrg = L_MAX(rval, gval);
+            max = L_MAX(maxrg, bval);
+            SET_DATA_BYTE(lined, j, max);
+        }
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *            Selection and display of range of colors in HSV space          *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixMakeRangeMaskHS()
+ *
+ *      Input:  pixs  (32 bpp rgb)
+ *              huecenter (center value of hue range)
+ *              huehw (half-width of hue range)
+ *              satcenter (center value of saturation range)
+ *              sathw (half-width of saturation range)
+ *              regionflag (L_INCLUDE_REGION, L_EXCLUDE_REGION)
+ *      Return: pixd (1 bpp mask over selected pixels), or null on error
+ *
+ *  Notes:
+ *      (1) The pixels are selected based on the specified ranges of
+ *          hue and saturation.  For selection or exclusion, the pixel
+ *          HS component values must be within both ranges.  Care must
+ *          be taken in finding the hue range because of wrap-around.
+ *      (2) Use @regionflag == L_INCLUDE_REGION to take only those
+ *          pixels within the rectangular region specified in HS space.
+ *          Use @regionflag == L_EXCLUDE_REGION to take all pixels except
+ *          those within the rectangular region specified in HS space.
+ */
+PIX *
+pixMakeRangeMaskHS(PIX     *pixs,
+                   l_int32  huecenter,
+                   l_int32  huehw,
+                   l_int32  satcenter,
+                   l_int32  sathw,
+                   l_int32  regionflag)
+{
+l_int32    i, j, w, h, wplt, wpld, hstart, hend, sstart, send, hval, sval;
+l_int32   *hlut, *slut;
+l_uint32   pixel;
+l_uint32  *datat, *datad, *linet, *lined;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixMakeRangeMaskHS");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+    if (regionflag != L_INCLUDE_REGION && regionflag != L_EXCLUDE_REGION)
+        return (PIX *)ERROR_PTR("invalid regionflag", procName, NULL);
+
+        /* Set up LUTs for hue and saturation.  These have the value 1
+         * within the specified intervals of hue and saturation. */
+    hlut = (l_int32 *)LEPT_CALLOC(240, sizeof(l_int32));
+    slut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    sstart = L_MAX(0, satcenter - sathw);
+    send = L_MIN(255, satcenter + sathw);
+    for (i = sstart; i <= send; i++)
+        slut[i] = 1;
+    hstart = (huecenter - huehw + 240) % 240;
+    hend = (huecenter + huehw + 240) % 240;
+    if (hstart < hend) {
+        for (i = hstart; i <= hend; i++)
+            hlut[i] = 1;
+    } else {  /* wrap */
+        for (i = hstart; i < 240; i++)
+            hlut[i] = 1;
+        for (i = 0; i <= hend; i++)
+            hlut[i] = 1;
+    }
+
+        /* Generate the mask */
+    pixt = pixConvertRGBToHSV(NULL, pixs);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixCreateNoInit(w, h, 1);
+    if (regionflag == L_INCLUDE_REGION)
+        pixClearAll(pixd);
+    else  /* L_EXCLUDE_REGION */
+        pixSetAll(pixd);
+    datat = pixGetData(pixt);
+    datad = pixGetData(pixd);
+    wplt = pixGetWpl(pixt);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            pixel = linet[j];
+            hval = (pixel >> L_RED_SHIFT) & 0xff;
+            sval = (pixel >> L_GREEN_SHIFT) & 0xff;
+            if (hlut[hval] == 1 && slut[sval] == 1) {
+                if (regionflag == L_INCLUDE_REGION)
+                    SET_DATA_BIT(lined, j);
+                else  /* L_EXCLUDE_REGION */
+                    CLEAR_DATA_BIT(lined, j);
+            }
+        }
+    }
+
+    LEPT_FREE(hlut);
+    LEPT_FREE(slut);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixMakeRangeMaskHV()
+ *
+ *      Input:  pixs  (32 bpp rgb)
+ *              huecenter (center value of hue range)
+ *              huehw (half-width of hue range)
+ *              valcenter (center value of max intensity range)
+ *              valhw (half-width of max intensity range)
+ *              regionflag (L_INCLUDE_REGION, L_EXCLUDE_REGION)
+ *      Return: pixd (1 bpp mask over selected pixels), or null on error
+ *
+ *  Notes:
+ *      (1) The pixels are selected based on the specified ranges of
+ *          hue and max intensity values.  For selection or exclusion,
+ *          the pixel HV component values must be within both ranges.
+ *          Care must be taken in finding the hue range because of wrap-around.
+ *      (2) Use @regionflag == L_INCLUDE_REGION to take only those
+ *          pixels within the rectangular region specified in HV space.
+ *          Use @regionflag == L_EXCLUDE_REGION to take all pixels except
+ *          those within the rectangular region specified in HV space.
+ */
+PIX *
+pixMakeRangeMaskHV(PIX     *pixs,
+                   l_int32  huecenter,
+                   l_int32  huehw,
+                   l_int32  valcenter,
+                   l_int32  valhw,
+                   l_int32  regionflag)
+{
+l_int32    i, j, w, h, wplt, wpld, hstart, hend, vstart, vend, hval, vval;
+l_int32   *hlut, *vlut;
+l_uint32   pixel;
+l_uint32  *datat, *datad, *linet, *lined;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixMakeRangeMaskHV");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+    if (regionflag != L_INCLUDE_REGION && regionflag != L_EXCLUDE_REGION)
+        return (PIX *)ERROR_PTR("invalid regionflag", procName, NULL);
+
+        /* Set up LUTs for hue and maximum intensity (val).  These have
+         * the value 1 within the specified intervals of hue and value. */
+    hlut = (l_int32 *)LEPT_CALLOC(240, sizeof(l_int32));
+    vlut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    vstart = L_MAX(0, valcenter - valhw);
+    vend = L_MIN(255, valcenter + valhw);
+    for (i = vstart; i <= vend; i++)
+        vlut[i] = 1;
+    hstart = (huecenter - huehw + 240) % 240;
+    hend = (huecenter + huehw + 240) % 240;
+    if (hstart < hend) {
+        for (i = hstart; i <= hend; i++)
+            hlut[i] = 1;
+    } else {
+        for (i = hstart; i < 240; i++)
+            hlut[i] = 1;
+        for (i = 0; i <= hend; i++)
+            hlut[i] = 1;
+    }
+
+        /* Generate the mask */
+    pixt = pixConvertRGBToHSV(NULL, pixs);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixCreateNoInit(w, h, 1);
+    if (regionflag == L_INCLUDE_REGION)
+        pixClearAll(pixd);
+    else  /* L_EXCLUDE_REGION */
+        pixSetAll(pixd);
+    datat = pixGetData(pixt);
+    datad = pixGetData(pixd);
+    wplt = pixGetWpl(pixt);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            pixel = linet[j];
+            hval = (pixel >> L_RED_SHIFT) & 0xff;
+            vval = (pixel >> L_BLUE_SHIFT) & 0xff;
+            if (hlut[hval] == 1 && vlut[vval] == 1) {
+                if (regionflag == L_INCLUDE_REGION)
+                    SET_DATA_BIT(lined, j);
+                else  /* L_EXCLUDE_REGION */
+                    CLEAR_DATA_BIT(lined, j);
+            }
+        }
+    }
+
+    LEPT_FREE(hlut);
+    LEPT_FREE(vlut);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixMakeRangeMaskSV()
+ *
+ *      Input:  pixs  (32 bpp rgb)
+ *              satcenter (center value of saturation range)
+ *              sathw (half-width of saturation range)
+ *              valcenter (center value of max intensity range)
+ *              valhw (half-width of max intensity range)
+ *              regionflag (L_INCLUDE_REGION, L_EXCLUDE_REGION)
+ *      Return: pixd (1 bpp mask over selected pixels), or null on error
+ *
+ *  Notes:
+ *      (1) The pixels are selected based on the specified ranges of
+ *          saturation and max intensity (val).  For selection or
+ *          exclusion, the pixel SV component values must be within both ranges.
+ *      (2) Use @regionflag == L_INCLUDE_REGION to take only those
+ *          pixels within the rectangular region specified in SV space.
+ *          Use @regionflag == L_EXCLUDE_REGION to take all pixels except
+ *          those within the rectangular region specified in SV space.
+ */
+PIX *
+pixMakeRangeMaskSV(PIX     *pixs,
+                   l_int32  satcenter,
+                   l_int32  sathw,
+                   l_int32  valcenter,
+                   l_int32  valhw,
+                   l_int32  regionflag)
+{
+l_int32    i, j, w, h, wplt, wpld, sval, vval, sstart, send, vstart, vend;
+l_int32   *slut, *vlut;
+l_uint32   pixel;
+l_uint32  *datat, *datad, *linet, *lined;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixMakeRangeMaskSV");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+    if (regionflag != L_INCLUDE_REGION && regionflag != L_EXCLUDE_REGION)
+        return (PIX *)ERROR_PTR("invalid regionflag", procName, NULL);
+
+        /* Set up LUTs for saturation and max intensity (val).
+         * These have the value 1 within the specified intervals of
+         * saturation and max intensity. */
+    slut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    vlut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    sstart = L_MAX(0, satcenter - sathw);
+    send = L_MIN(255, satcenter + sathw);
+    vstart = L_MAX(0, valcenter - valhw);
+    vend = L_MIN(255, valcenter + valhw);
+    for (i = sstart; i <= send; i++)
+        slut[i] = 1;
+    for (i = vstart; i <= vend; i++)
+        vlut[i] = 1;
+
+        /* Generate the mask */
+    pixt = pixConvertRGBToHSV(NULL, pixs);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixCreateNoInit(w, h, 1);
+    if (regionflag == L_INCLUDE_REGION)
+        pixClearAll(pixd);
+    else  /* L_EXCLUDE_REGION */
+        pixSetAll(pixd);
+    datat = pixGetData(pixt);
+    datad = pixGetData(pixd);
+    wplt = pixGetWpl(pixt);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            pixel = linet[j];
+            sval = (pixel >> L_GREEN_SHIFT) & 0xff;
+            vval = (pixel >> L_BLUE_SHIFT) & 0xff;
+            if (slut[sval] == 1 && vlut[vval] == 1) {
+                if (regionflag == L_INCLUDE_REGION)
+                    SET_DATA_BIT(lined, j);
+                else  /* L_EXCLUDE_REGION */
+                    CLEAR_DATA_BIT(lined, j);
+            }
+        }
+    }
+
+    LEPT_FREE(slut);
+    LEPT_FREE(vlut);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixMakeHistoHS()
+ *
+ *      Input:  pixs  (HSV colorspace)
+ *              factor (subsampling factor; integer)
+ *              &nahue (<optional return> hue histogram)
+ *              &nasat (<optional return> saturation histogram)
+ *      Return: pixd (32 bpp histogram in hue and saturation), or null on error
+ *
+ *  Notes:
+ *      (1) pixs is a 32 bpp image in HSV colorspace; hue is in the "red"
+ *          byte, saturation is in the "green" byte.
+ *      (2) In pixd, hue is displayed vertically; saturation horizontally.
+ *          The dimensions of pixd are w = 256, h = 240, and the depth
+ *          is 32 bpp.  The value at each point is simply the number
+ *          of pixels found at that value of hue and saturation.
+ */
+PIX *
+pixMakeHistoHS(PIX     *pixs,
+               l_int32  factor,
+               NUMA   **pnahue,
+               NUMA   **pnasat)
+{
+l_int32    i, j, w, h, wplt, hval, sval, nd;
+l_uint32   pixel;
+l_uint32  *datat, *linet;
+void     **lined32;
+NUMA      *nahue, *nasat;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixMakeHistoHS");
+
+    if (pnahue) *pnahue = NULL;
+    if (pnasat) *pnasat = NULL;
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+
+    if (pnahue) {
+        nahue = numaCreate(240);
+        numaSetCount(nahue, 240);
+        *pnahue = nahue;
+    }
+    if (pnasat) {
+        nasat = numaCreate(256);
+        numaSetCount(nasat, 256);
+        *pnasat = nasat;
+    }
+
+    if (factor <= 1)
+        pixt = pixClone(pixs);
+    else
+        pixt = pixScaleBySampling(pixs, 1.0 / (l_float32)factor,
+                                  1.0 / (l_float32)factor);
+
+        /* Create the hue-saturation histogram */
+    pixd = pixCreate(256, 240, 32);
+    lined32 = pixGetLinePtrs(pixd, NULL);
+    pixGetDimensions(pixt, &w, &h, NULL);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        for (j = 0; j < w; j++) {
+            pixel = linet[j];
+            hval = (pixel >> L_RED_SHIFT) & 0xff;
+
+#if  DEBUG_HISTO
+            if (hval > 239) {
+                fprintf(stderr, "hval = %d for (%d,%d)\n", hval, i, j);
+                continue;
+            }
+#endif  /* DEBUG_HISTO */
+
+            sval = (pixel >> L_GREEN_SHIFT) & 0xff;
+            if (pnahue)
+                numaShiftValue(nahue, hval, 1.0);
+            if (pnasat)
+                numaShiftValue(nasat, sval, 1.0);
+            nd = GET_DATA_FOUR_BYTES(lined32[hval], sval);
+            SET_DATA_FOUR_BYTES(lined32[hval], sval, nd + 1);
+        }
+    }
+
+    LEPT_FREE(lined32);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixMakeHistoHV()
+ *
+ *      Input:  pixs  (HSV colorspace)
+ *              factor (subsampling factor; integer)
+ *              &nahue (<optional return> hue histogram)
+ *              &naval (<optional return> max intensity (value) histogram)
+ *      Return: pixd (32 bpp histogram in hue and value), or null on error
+ *
+ *  Notes:
+ *      (1) pixs is a 32 bpp image in HSV colorspace; hue is in the "red"
+ *          byte, max intensity ("value") is in the "blue" byte.
+ *      (2) In pixd, hue is displayed vertically; intensity horizontally.
+ *          The dimensions of pixd are w = 256, h = 240, and the depth
+ *          is 32 bpp.  The value at each point is simply the number
+ *          of pixels found at that value of hue and intensity.
+ */
+PIX *
+pixMakeHistoHV(PIX     *pixs,
+               l_int32  factor,
+               NUMA   **pnahue,
+               NUMA   **pnaval)
+{
+l_int32    i, j, w, h, wplt, hval, vval, nd;
+l_uint32   pixel;
+l_uint32  *datat, *linet;
+void     **lined32;
+NUMA      *nahue, *naval;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixMakeHistoHV");
+
+    if (pnahue) *pnahue = NULL;
+    if (pnaval) *pnaval = NULL;
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+
+    if (pnahue) {
+        nahue = numaCreate(240);
+        numaSetCount(nahue, 240);
+        *pnahue = nahue;
+    }
+    if (pnaval) {
+        naval = numaCreate(256);
+        numaSetCount(naval, 256);
+        *pnaval = naval;
+    }
+
+    if (factor <= 1)
+        pixt = pixClone(pixs);
+    else
+        pixt = pixScaleBySampling(pixs, 1.0 / (l_float32)factor,
+                                  1.0 / (l_float32)factor);
+
+        /* Create the hue-value histogram */
+    pixd = pixCreate(256, 240, 32);
+    lined32 = pixGetLinePtrs(pixd, NULL);
+    pixGetDimensions(pixt, &w, &h, NULL);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        for (j = 0; j < w; j++) {
+            pixel = linet[j];
+            hval = (pixel >> L_RED_SHIFT) & 0xff;
+            vval = (pixel >> L_BLUE_SHIFT) & 0xff;
+            if (pnahue)
+                numaShiftValue(nahue, hval, 1.0);
+            if (pnaval)
+                numaShiftValue(naval, vval, 1.0);
+            nd = GET_DATA_FOUR_BYTES(lined32[hval], vval);
+            SET_DATA_FOUR_BYTES(lined32[hval], vval, nd + 1);
+        }
+    }
+
+    LEPT_FREE(lined32);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixMakeHistoSV()
+ *
+ *      Input:  pixs  (HSV colorspace)
+ *              factor (subsampling factor; integer)
+ *              &nasat (<optional return> sat histogram)
+ *              &naval (<optional return> max intensity (value) histogram)
+ *      Return: pixd (32 bpp histogram in sat and value), or null on error
+ *
+ *  Notes:
+ *      (1) pixs is a 32 bpp image in HSV colorspace; sat is in the "green"
+ *          byte, max intensity ("value") is in the "blue" byte.
+ *      (2) In pixd, sat is displayed vertically; intensity horizontally.
+ *          The dimensions of pixd are w = 256, h = 256, and the depth
+ *          is 32 bpp.  The value at each point is simply the number
+ *          of pixels found at that value of saturation and intensity.
+ */
+PIX *
+pixMakeHistoSV(PIX     *pixs,
+               l_int32  factor,
+               NUMA   **pnasat,
+               NUMA   **pnaval)
+{
+l_int32    i, j, w, h, wplt, sval, vval, nd;
+l_uint32   pixel;
+l_uint32  *datat, *linet;
+void     **lined32;
+NUMA      *nasat, *naval;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixMakeHistoSV");
+
+    if (pnasat) *pnasat = NULL;
+    if (pnaval) *pnaval = NULL;
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+
+    if (pnasat) {
+        nasat = numaCreate(256);
+        numaSetCount(nasat, 256);
+        *pnasat = nasat;
+    }
+    if (pnaval) {
+        naval = numaCreate(256);
+        numaSetCount(naval, 256);
+        *pnaval = naval;
+    }
+
+    if (factor <= 1)
+        pixt = pixClone(pixs);
+    else
+        pixt = pixScaleBySampling(pixs, 1.0 / (l_float32)factor,
+                                  1.0 / (l_float32)factor);
+
+        /* Create the hue-value histogram */
+    pixd = pixCreate(256, 256, 32);
+    lined32 = pixGetLinePtrs(pixd, NULL);
+    pixGetDimensions(pixt, &w, &h, NULL);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        for (j = 0; j < w; j++) {
+            pixel = linet[j];
+            sval = (pixel >> L_GREEN_SHIFT) & 0xff;
+            vval = (pixel >> L_BLUE_SHIFT) & 0xff;
+            if (pnasat)
+                numaShiftValue(nasat, sval, 1.0);
+            if (pnaval)
+                numaShiftValue(naval, vval, 1.0);
+            nd = GET_DATA_FOUR_BYTES(lined32[sval], vval);
+            SET_DATA_FOUR_BYTES(lined32[sval], vval, nd + 1);
+        }
+    }
+
+    LEPT_FREE(lined32);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixFindHistoPeaksHSV()
+ *
+ *      Input:  pixs (32 bpp; HS, HV or SV histogram; not changed)
+ *              type (L_HS_HISTO, L_HV_HISTO or L_SV_HISTO)
+ *              width (half width of sliding window)
+ *              height (half height of sliding window)
+ *              npeaks (number of peaks to look for)
+ *              erasefactor (ratio of erase window size to sliding window size)
+ *              &pta (<return> locations of max for each integrated peak area)
+ *              &natot (<return> integrated peak areas)
+ *              &pixa (<optional return> pixa for debugging; NULL to skip)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) pixs is a 32 bpp histogram in a pair of HSV colorspace.  It
+ *          should be thought of as a single sample with 32 bps (bits/sample).
+ *      (2) After each peak is found, the peak is erased with a window
+ *          that is centered on the peak and scaled from the sliding
+ *          window by @erasefactor.  Typically, @erasefactor is chosen
+ *          to be > 1.0.
+ *      (3) Data for a maximum of @npeaks is returned in @pta and @natot.
+ *      (4) For debugging, after the pixa is returned, display with:
+ *          pixd = pixaDisplayTiledInRows(pixa, 32, 1000, 1.0, 0, 30, 2);
+ */
+l_int32
+pixFindHistoPeaksHSV(PIX       *pixs,
+                     l_int32    type,
+                     l_int32    width,
+                     l_int32    height,
+                     l_int32    npeaks,
+                     l_float32  erasefactor,
+                     PTA      **ppta,
+                     NUMA     **pnatot,
+                     PIXA     **ppixa)
+{
+l_int32   i, xmax, ymax, ewidth, eheight;
+l_uint32  maxval;
+BOX      *box;
+NUMA     *natot;
+PIX      *pixh, *pixw, *pixt1, *pixt2, *pixt3;
+PTA      *pta;
+
+    PROCNAME("pixFindHistoPeaksHSV");
+
+    if (ppixa) *ppixa = NULL;
+    if (ppta) *ppta = NULL;
+    if (pnatot) *pnatot = NULL;
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs undefined or not 32 bpp", procName, 1);
+    if (!ppta || !pnatot)
+        return ERROR_INT("&pta and &natot not both defined", procName, 1);
+    if (type != L_HS_HISTO && type != L_HV_HISTO && type != L_SV_HISTO)
+        return ERROR_INT("invalid HSV histo type", procName, 1);
+
+    if ((pta = ptaCreate(npeaks)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    *ppta = pta;
+    if ((natot = numaCreate(npeaks)) == NULL)
+        return ERROR_INT("natot not made", procName, 1);
+    *pnatot = natot;
+
+    *ppta = pta;
+    if (type == L_SV_HISTO)
+        pixh = pixAddMirroredBorder(pixs, width + 1, width + 1, height + 1,
+                                    height + 1);
+    else  /* type == L_HS_HISTO or type == L_HV_HISTO */
+        pixh = pixAddMixedBorder(pixs, width + 1, width + 1, height + 1,
+                                 height + 1);
+
+        /* Get the total count in the sliding window.  If the window
+         * fully covers the peak, this will be the integrated
+         * volume under the peak. */
+    pixw = pixWindowedMean(pixh, width, height, 1, 0);
+    pixDestroy(&pixh);
+
+        /* Sequentially identify and erase peaks in the histogram.
+         * If requested for debugging, save a pixa of the sequence of
+         * false color histograms. */
+    if (ppixa)
+        *ppixa = pixaCreate(0);
+    for (i = 0; i < npeaks; i++) {
+        pixGetMaxValueInRect(pixw, NULL, &maxval, &xmax, &ymax);
+        if (maxval == 0) break;
+        numaAddNumber(natot, maxval);
+        ptaAddPt(pta, xmax, ymax);
+        ewidth = (l_int32)(width * erasefactor);
+        eheight = (l_int32)(height * erasefactor);
+        box = boxCreate(xmax - ewidth, ymax - eheight, 2 * ewidth + 1,
+                        2 * eheight + 1);
+
+        if (ppixa) {
+            pixt1 = pixMaxDynamicRange(pixw, L_LINEAR_SCALE);
+            pixaAddPix(*ppixa, pixt1, L_INSERT);
+            pixt2 = pixConvertGrayToFalseColor(pixt1, 1.0);
+            pixaAddPix(*ppixa, pixt2, L_INSERT);
+            pixt1 = pixMaxDynamicRange(pixw, L_LOG_SCALE);
+            pixt2 = pixConvertGrayToFalseColor(pixt1, 1.0);
+            pixaAddPix(*ppixa, pixt2, L_INSERT);
+            pixt3 = pixConvertTo32(pixt1);
+            pixRenderHashBoxArb(pixt3, box, 6, 2, L_NEG_SLOPE_LINE,
+                                1, 255, 100, 100);
+            pixaAddPix(*ppixa, pixt3, L_INSERT);
+            pixDestroy(&pixt1);
+        }
+
+        pixClearInRect(pixw, box);
+        boxDestroy(&box);
+        if (type == L_HS_HISTO || type == L_HV_HISTO) {
+                /* clear wraps at bottom and top */
+            if (ymax - eheight < 0) {  /* overlap to bottom */
+                box = boxCreate(xmax - ewidth, 240 + ymax - eheight,
+                                2 * ewidth + 1, eheight - ymax);
+            } else if (ymax + eheight > 239) {  /* overlap to top */
+                box = boxCreate(xmax - ewidth, 0, 2 * ewidth + 1,
+                                ymax + eheight - 239);
+            } else {
+                box = NULL;
+            }
+            if (box) {
+                pixClearInRect(pixw, box);
+                boxDestroy(&box);
+            }
+        }
+    }
+
+    pixDestroy(&pixw);
+    return 0;
+}
+
+
+/*!
+ *  displayHSVColorRange()
+ *
+ *      Input:  hval (hue center value; in range [0 ... 240]
+ *              sval (saturation center value; in range [0 ... 255]
+ *              vval (max intensity value; in range [0 ... 255]
+ *              huehw (half-width of hue range; > 0)
+ *              sathw (half-width of saturation range; > 0)
+ *              nsamp (number of samplings in each half-width in hue and sat)
+ *              factor (linear size of each color square, in pixels; > 3)
+ *      Return: pixd (32 bpp set of color squares over input range),
+ *                     or null on error
+ *
+ *  Notes:
+ *      (1) The total number of color samplings in each of the hue
+ *          and saturation directions is 2 * nsamp + 1.
+ */
+PIX *
+displayHSVColorRange(l_int32  hval,
+                     l_int32  sval,
+                     l_int32  vval,
+                     l_int32  huehw,
+                     l_int32  sathw,
+                     l_int32  nsamp,
+                     l_int32  factor)
+{
+l_int32  i, j, w, huedelta, satdelta, hue, sat, rval, gval, bval;
+PIX     *pixt, *pixd;
+
+    PROCNAME("displayHSVColorRange");
+
+    if (hval < 0 || hval > 240)
+        return (PIX *)ERROR_PTR("invalid hval", procName, NULL);
+    if (huehw < 5 || huehw > 120)
+        return (PIX *)ERROR_PTR("invalid huehw", procName, NULL);
+    if (sval - sathw < 0 || sval + sathw > 255)
+        return (PIX *)ERROR_PTR("invalid sval/sathw", procName, NULL);
+    if (nsamp < 1 || factor < 3)
+        return (PIX *)ERROR_PTR("invalid nsamp or rep. factor", procName, NULL);
+    if (vval < 0 || vval > 255)
+        return (PIX *)ERROR_PTR("invalid vval", procName, NULL);
+
+    w = (2 * nsamp + 1);
+    huedelta = (l_int32)((l_float32)huehw / (l_float32)nsamp);
+    satdelta = (l_int32)((l_float32)sathw / (l_float32)nsamp);
+    pixt = pixCreate(w, w, 32);
+    for (i = 0; i < w; i++) {
+        hue = hval + huedelta * (i - nsamp);
+        if (hue < 0) hue += 240;
+        if (hue >= 240) hue -= 240;
+        for (j = 0; j < w; j++) {
+            sat = sval + satdelta * (j - nsamp);
+            convertHSVToRGB(hue, sat, vval, &rval, &gval, &bval);
+            pixSetRGBPixel(pixt, j, i, rval, gval, bval);
+        }
+    }
+
+    pixd = pixExpandReplicate(pixt, factor);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                Colorspace conversion between RGB and YUV                  *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertRGBToYUV()
+ *
+ *      Input:  pixd (can be NULL; if not NULL, must == pixs)
+ *              pixs
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) For pixs = pixd, this is in-place; otherwise pixd must be NULL.
+ *      (2) The Y, U and V values are stored in the same places as
+ *          the r, g and b values, respectively.  Here, they are explicitly
+ *          placed in the 3 MS bytes in the pixel.
+ *      (3) Normalizing to 1 and considering the r,g,b components,
+ *          a simple way to understand the YUV space is:
+ *           - Y = weighted sum of (r,g,b)
+ *           - U = weighted difference between Y and B
+ *           - V = weighted difference between Y and R
+ *      (4) Following video conventions, Y, U and V are in the range:
+ *             Y: [16, 235]
+ *             U: [16, 240]
+ *             V: [16, 240]
+ *      (5) For the coefficients in the transform matrices, see eq. 4 in
+ *          "Frequently Asked Questions about Color" by Charles Poynton,
+ *          //http://user.engineering.uiowa.edu/~aip/Misc/ColorFAQ.html
+ */
+PIX *
+pixConvertRGBToYUV(PIX  *pixd,
+                   PIX  *pixs)
+{
+l_int32    w, h, d, wpl, i, j, rval, gval, bval, yval, uval, vval;
+l_uint32  *line, *data;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixConvertRGBToYUV");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixd && pixd != pixs)
+        return (PIX *)ERROR_PTR("pixd defined and not inplace", procName, pixd);
+
+    d = pixGetDepth(pixs);
+    cmap = pixGetColormap(pixs);
+    if (!cmap && d != 32)
+        return (PIX *)ERROR_PTR("not cmapped or rgb", procName, pixd);
+
+    if (!pixd)
+        pixd = pixCopy(NULL, pixs);
+
+    cmap = pixGetColormap(pixd);
+    if (cmap) {   /* just convert the colormap */
+        pixcmapConvertRGBToYUV(cmap);
+        return pixd;
+    }
+
+        /* Convert RGB image */
+    pixGetDimensions(pixd, &w, &h, NULL);
+    wpl = pixGetWpl(pixd);
+    data = pixGetData(pixd);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(line[j], &rval, &gval, &bval);
+            convertRGBToYUV(rval, gval, bval, &yval, &uval, &vval);
+            line[j] = (yval << 24) | (uval << 16) | (vval << 8);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixConvertYUVToRGB()
+ *
+ *      Input:  pixd (can be NULL; if not NULL, must == pixs)
+ *              pixs
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) For pixs = pixd, this is in-place; otherwise pixd must be NULL.
+ *      (2) The user takes responsibility for making sure that pixs is
+ *          in YUV space.
+ *      (3) The Y, U and V values are stored in the same places as
+ *          the r, g and b values, respectively.  Here, they are explicitly
+ *          placed in the 3 MS bytes in the pixel.
+ */
+PIX *
+pixConvertYUVToRGB(PIX  *pixd,
+                   PIX  *pixs)
+{
+l_int32    w, h, d, wpl, i, j, rval, gval, bval, yval, uval, vval;
+l_uint32   pixel;
+l_uint32  *line, *data;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixConvertYUVToRGB");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixd && pixd != pixs)
+        return (PIX *)ERROR_PTR("pixd defined and not inplace", procName, pixd);
+
+    d = pixGetDepth(pixs);
+    cmap = pixGetColormap(pixs);
+    if (!cmap && d != 32)
+        return (PIX *)ERROR_PTR("not cmapped or hsv", procName, pixd);
+
+    if (!pixd)
+        pixd = pixCopy(NULL, pixs);
+
+    cmap = pixGetColormap(pixd);
+    if (cmap) {   /* just convert the colormap */
+        pixcmapConvertYUVToRGB(cmap);
+        return pixd;
+    }
+
+        /* Convert YUV image */
+    pixGetDimensions(pixd, &w, &h, NULL);
+    wpl = pixGetWpl(pixd);
+    data = pixGetData(pixd);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            pixel = line[j];
+            yval = pixel >> 24;
+            uval = (pixel >> 16) & 0xff;
+            vval = (pixel >> 8) & 0xff;
+            convertYUVToRGB(yval, uval, vval, &rval, &gval, &bval);
+            composeRGBPixel(rval, gval, bval, line + j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  convertRGBToYUV()
+ *
+ *      Input:  rval, gval, bval (RGB input)
+ *              &yval, &uval, &vval (<return> YUV values)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The range of returned values is:
+ *            Y [16 ... 235]
+ *            U [16 ... 240]
+ *            V [16 ... 240]
+ */
+l_int32
+convertRGBToYUV(l_int32   rval,
+                l_int32   gval,
+                l_int32   bval,
+                l_int32  *pyval,
+                l_int32  *puval,
+                l_int32  *pvval)
+{
+l_float32  norm;
+
+    PROCNAME("convertRGBToYUV");
+
+    if (pyval) *pyval = 0;
+    if (puval) *puval = 0;
+    if (pvval) *pvval = 0;
+    if (!pyval || !puval || !pvval)
+        return ERROR_INT("&yval, &uval, &vval not all defined", procName, 1);
+
+    norm = 1.0 / 256.;
+    *pyval = (l_int32)(16.0 +
+                norm * (65.738 * rval + 129.057 * gval + 25.064 * bval) + 0.5);
+    *puval = (l_int32)(128.0 +
+                norm * (-37.945 * rval -74.494 * gval + 112.439 * bval) + 0.5);
+    *pvval = (l_int32)(128.0 +
+                norm * (112.439 * rval - 94.154 * gval - 18.285 * bval) + 0.5);
+    return 0;
+}
+
+
+/*!
+ *  convertYUVToRGB()
+ *
+ *      Input:  yval, uval, vval
+ *              &rval, &gval, &bval (<return> RGB values)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The range of valid input values is:
+ *            Y [16 ... 235]
+ *            U [16 ... 240]
+ *            V [16 ... 240]
+ *      (2) Conversion of RGB --> YUV --> RGB leaves the image unchanged.
+ *      (3) The YUV gamut is larger than the RBG gamut; many YUV values
+ *          will result in an invalid RGB value.  We clip individual
+ *          r,g,b components to the range [0, 255], and do not test input.
+ */
+l_int32
+convertYUVToRGB(l_int32   yval,
+                l_int32   uval,
+                l_int32   vval,
+                l_int32  *prval,
+                l_int32  *pgval,
+                l_int32  *pbval)
+{
+l_int32    rval, gval, bval;
+l_float32  norm, ym, um, vm;
+
+    PROCNAME("convertYUVToRGB");
+
+    if (prval) *prval = 0;
+    if (pgval) *pgval = 0;
+    if (pbval) *pbval = 0;
+    if (!prval || !pgval || !pbval)
+        return ERROR_INT("&rval, &gval, &bval not all defined", procName, 1);
+
+    norm = 1.0 / 256.;
+    ym = yval - 16.0;
+    um = uval - 128.0;
+    vm = vval - 128.0;
+    rval =  (l_int32)(norm * (298.082 * ym + 408.583 * vm) + 0.5);
+    gval = (l_int32)(norm * (298.082 * ym - 100.291 * um - 208.120 * vm) +
+                       0.5);
+    bval = (l_int32)(norm * (298.082 * ym + 516.411 * um) + 0.5);
+    *prval = L_MIN(255, L_MAX(0, rval));
+    *pgval = L_MIN(255, L_MAX(0, gval));
+    *pbval = L_MIN(255, L_MAX(0, bval));
+
+    return 0;
+}
+
+
+/*!
+ *  pixcmapConvertRGBToYUV()
+ *
+ *      Input:  colormap
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      - in-place transform
+ *      - See convertRGBToYUV() for def'n of YUV space.
+ *      - replaces: r --> y, g --> u, b --> v
+ */
+l_int32
+pixcmapConvertRGBToYUV(PIXCMAP  *cmap)
+{
+l_int32   i, ncolors, rval, gval, bval, yval, uval, vval;
+
+    PROCNAME("pixcmapConvertRGBToYUV");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    ncolors = pixcmapGetCount(cmap);
+    for (i = 0; i < ncolors; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        convertRGBToYUV(rval, gval, bval, &yval, &uval, &vval);
+        pixcmapResetColor(cmap, i, yval, uval, vval);
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixcmapConvertYUVToRGB()
+ *
+ *      Input:  colormap
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      - in-place transform
+ *      - See convertRGBToYUV() for def'n of YUV space.
+ *      - replaces: y --> r, u --> g, v --> b
+ */
+l_int32
+pixcmapConvertYUVToRGB(PIXCMAP  *cmap)
+{
+l_int32   i, ncolors, rval, gval, bval, yval, uval, vval;
+
+    PROCNAME("pixcmapConvertYUVToRGB");
+
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    ncolors = pixcmapGetCount(cmap);
+    for (i = 0; i < ncolors; i++) {
+        pixcmapGetColor(cmap, i, &yval, &uval, &vval);
+        convertYUVToRGB(yval, uval, vval, &rval, &gval, &bval);
+        pixcmapResetColor(cmap, i, rval, gval, bval);
+    }
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                Colorspace conversion between RGB and XYZ                  *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertRGBToXYZ()
+ *
+ *      Input:  pixs (rgb)
+ *      Return: fpixa (xyz)
+ *
+ *  Notes:
+ *      (1) The [x,y,z] values are stored as float values in three fpix
+ *          that are returned in a fpixa.
+ *      (2) The XYZ color space was defined in 1931 as a reference model that
+ *          simulates human color perception.  When Y is taken as luminance,
+ *          the values of X and Z constitute a color plane representing
+ *          all the hues that can be perceived.  This gamut of colors
+ *          is larger than the gamuts that can be displayed or printed.
+ *          For example, although all rgb values map to XYZ, the converse
+ *          is not true.
+ *      (3) The value of the coefficients depends on the illuminant.  We use
+ *          coefficients for converting sRGB under D65 (the spectrum from
+ *          a 6500 degree K black body; an approximation to daylight color).
+ *          See, e.g.,
+ *             http://www.cs.rit.edu/~ncs/color/t_convert.html
+ *          For more general information on color transforms, see:
+ *             http://www.brucelindbloom.com/
+ *             http://user.engineering.uiowa.edu/~aip/Misc/ColorFAQ.html
+ *             http://en.wikipedia.org/wiki/CIE_1931_color_space
+ */
+FPIXA *
+pixConvertRGBToXYZ(PIX  *pixs)
+{
+l_int32     w, h, wpls, wpld, i, j, rval, gval, bval;
+l_uint32   *lines, *datas;
+l_float32   fxval, fyval, fzval;
+l_float32  *linex, *liney, *linez, *datax, *datay, *dataz;
+FPIX       *fpix;
+FPIXA      *fpixa;
+
+    PROCNAME("pixConvertRGBToXYZ");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (FPIXA *)ERROR_PTR("pixs undefined or not rgb", procName, NULL);
+
+        /* Convert RGB image */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    fpixa = fpixaCreate(3);
+    for (i = 0; i < 3; i++) {
+        fpix = fpixCreate(w, h);
+        fpixaAddFPix(fpixa, fpix, L_INSERT);
+    }
+    wpls = pixGetWpl(pixs);
+    wpld = fpixGetWpl(fpix);
+    datas = pixGetData(pixs);
+    datax = fpixaGetData(fpixa, 0);
+    datay = fpixaGetData(fpixa, 1);
+    dataz = fpixaGetData(fpixa, 2);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        linex = datax + i * wpld;
+        liney = datay + i * wpld;
+        linez = dataz + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            convertRGBToXYZ(rval, gval, bval, &fxval, &fyval, &fzval);
+            *(linex + j) = fxval;
+            *(liney + j) = fyval;
+            *(linez + j) = fzval;
+        }
+    }
+
+    return fpixa;
+}
+
+
+/*!
+ *  fpixaConvertXYZToRGB()
+ *
+ *      Input:  fpixa (three fpix: x,y,z)
+ *      Return: pixd (rgb)
+ *
+ *  Notes:
+ *      (1) The xyz image is stored in three fpix.
+ *      (2) For values of xyz that are out of gamut for rgb, the rgb
+ *          components are set to the closest valid color.
+ */
+PIX *
+fpixaConvertXYZToRGB(FPIXA  *fpixa)
+{
+l_int32     w, h, wpls, wpld, i, j, rval, gval, bval;
+l_float32   fxval, fyval, fzval;
+l_float32  *linex, *liney, *linez, *datax, *datay, *dataz;
+l_uint32   *lined, *datad;
+PIX        *pixd;
+FPIX       *fpix;
+
+    PROCNAME("fpixaConvertXYZToRGB");
+
+    if (!fpixa || fpixaGetCount(fpixa) != 3)
+        return (PIX *)ERROR_PTR("fpixa undefined or invalid", procName, NULL);
+
+        /* Convert XYZ image */
+    if (fpixaGetFPixDimensions(fpixa, 0, &w, &h))
+        return (PIX *)ERROR_PTR("fpixa dimensions not found", procName, NULL);
+    pixd = pixCreate(w, h, 32);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    datax = fpixaGetData(fpixa, 0);
+    datay = fpixaGetData(fpixa, 1);
+    dataz = fpixaGetData(fpixa, 2);
+    fpix = fpixaGetFPix(fpixa, 0, L_CLONE);
+    wpls = fpixGetWpl(fpix);
+    fpixDestroy(&fpix);
+    for (i = 0; i < h; i++) {
+        linex = datax + i * wpls;
+        liney = datay + i * wpls;
+        linez = dataz + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            fxval = linex[j];
+            fyval = liney[j];
+            fzval = linez[j];
+            convertXYZToRGB(fxval, fyval, fzval, 0, &rval, &gval, &bval);
+            composeRGBPixel(rval, gval, bval, lined + j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  convertRGBToXYZ()
+ *
+ *      Input:  rval, gval, bval (rgb input)
+ *              &fxval, &fyval, &fzval (<return> xyz values)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) These conversions are for illuminant D65 acting on linear sRGB
+ *          values.
+ */
+l_int32
+convertRGBToXYZ(l_int32     rval,
+                l_int32     gval,
+                l_int32     bval,
+                l_float32  *pfxval,
+                l_float32  *pfyval,
+                l_float32  *pfzval)
+{
+    PROCNAME("convertRGBToXYZ");
+
+    if (pfxval) *pfxval = 0.0;
+    if (pfyval) *pfyval = 0.0;
+    if (pfzval) *pfzval = 0.0;
+    if (!pfxval || !pfyval || !pfzval)
+        return ERROR_INT("&xval, &yval, &zval not all defined", procName, 1);
+
+    *pfxval = 0.4125 * rval + 0.3576 * gval + 0.1804 * bval;
+    *pfyval = 0.2127 * rval + 0.7152 * gval + 0.0722 * bval;
+    *pfzval = 0.0193 * rval + 0.1192 * gval + 0.9502 * bval;
+    return 0;
+}
+
+
+/*!
+ *  convertXYZToRGB()
+ *
+ *      Input:  fxval, fyval, fzval
+ *              blackout (0 to output nearest color if out of gamut;
+ *                        1 to output black)
+ *              &rval, &gval, &bval (<return> rgb values)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For values of xyz that are out of gamut for rgb, at least
+ *          one of the r, g or b components will be either less than 0
+ *          or greater than 255.  For that situation:
+ *            * if blackout == 0, the individual component(s) that are out
+ *              of gamut will be set to 0 or 255, respectively.
+ *            * if blackout == 1, the output color will be set to black
+ */
+l_int32
+convertXYZToRGB(l_float32  fxval,
+                l_float32  fyval,
+                l_float32  fzval,
+                l_int32    blackout,
+                l_int32   *prval,
+                l_int32   *pgval,
+                l_int32   *pbval)
+{
+l_int32  rval, gval, bval;
+
+    PROCNAME("convertXYZToRGB");
+
+    if (prval) *prval = 0;
+    if (pgval) *pgval = 0;
+    if (pbval) *pbval = 0;
+    if (!prval || !pgval ||!pbval)
+        return ERROR_INT("&rval, &gval, &bval not all defined", procName, 1);
+    *prval = *pgval = *pbval = 0;
+
+    rval = (l_int32)(3.2405 * fxval - 1.5372 * fyval - 0.4985 * fzval + 0.5);
+    gval = (l_int32)(-0.9693 * fxval + 1.8760 * fyval + 0.0416 * fzval + 0.5);
+    bval = (l_int32)(0.0556 * fxval - 0.2040 * fyval + 1.0573 * fzval + 0.5);
+    if (blackout == 0) {  /* the usual situation; use nearest rgb color */
+        *prval = L_MAX(0, L_MIN(rval, 255));
+        *pgval = L_MAX(0, L_MIN(gval, 255));
+        *pbval = L_MAX(0, L_MIN(bval, 255));
+    } else {  /* use black for out of gamut */
+        if (rval >= 0 && rval < 256 && gval >= 0 && gval < 256 &&
+            bval >= 0 && bval < 256) {  /* in gamut */
+            *prval = rval;
+            *pgval = gval;
+            *pbval = bval;
+        }
+    }
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *               Colorspace conversion between XYZ and LAB                   *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  fpixaConvertXYZToLAB()
+ *
+ *      Input:  fpixa (xyz)
+ *      Return: fpixa (lab)
+ *
+ *  Notes:
+ *      (1) The input [x,y,z] and output [l,a,b] values are stored as
+ *          float values, each set in three fpix.
+ *      (2) The CIE LAB color space was invented in 1976, as an
+ *          absolute reference for specifying colors that we can
+ *          perceive, independently of the rendering device.  It was
+ *          invented to align color display and print images.
+ *          For information, see:
+ *             http://www.brucelindbloom.com/
+ *             http://en.wikipedia.org/wiki/Lab_color_space
+ */
+FPIXA *
+fpixaConvertXYZToLAB(FPIXA  *fpixas)
+{
+l_int32     w, h, wpl, i, j;
+l_float32   fxval, fyval, fzval, flval, faval, fbval;
+l_float32  *linex, *liney, *linez, *datax, *datay, *dataz;
+l_float32  *linel, *linea, *lineb, *datal, *dataa, *datab;
+FPIX       *fpix;
+FPIXA      *fpixad;
+
+    PROCNAME("fpixaConvertXYZToLAB");
+
+    if (!fpixas || fpixaGetCount(fpixas) != 3)
+        return (FPIXA *)ERROR_PTR("fpixas undefined/invalid", procName, NULL);
+
+        /* Convert XYZ image */
+    if (fpixaGetFPixDimensions(fpixas, 0, &w, &h))
+        return (FPIXA *)ERROR_PTR("fpixas sizes not found", procName, NULL);
+    fpixad = fpixaCreate(3);
+    for (i = 0; i < 3; i++) {
+        fpix = fpixCreate(w, h);
+        fpixaAddFPix(fpixad, fpix, L_INSERT);
+    }
+    wpl = fpixGetWpl(fpix);
+    datax = fpixaGetData(fpixas, 0);
+    datay = fpixaGetData(fpixas, 1);
+    dataz = fpixaGetData(fpixas, 2);
+    datal = fpixaGetData(fpixad, 0);
+    dataa = fpixaGetData(fpixad, 1);
+    datab = fpixaGetData(fpixad, 2);
+
+        /* Convert XYZ image */
+    for (i = 0; i < h; i++) {
+        linex = datax + i * wpl;
+        liney = datay + i * wpl;
+        linez = dataz + i * wpl;
+        linel = datal + i * wpl;
+        linea = dataa + i * wpl;
+        lineb = datab + i * wpl;
+        for (j = 0; j < w; j++) {
+            fxval = *(linex + j);
+            fyval = *(liney + j);
+            fzval = *(linez + j);
+            convertXYZToLAB(fxval, fyval, fzval, &flval, &faval, &fbval);
+            *(linel + j) = flval;
+            *(linea + j) = faval;
+            *(lineb + j) = fbval;
+        }
+    }
+
+    return fpixad;
+}
+
+
+/*!
+ *  fpixaConvertLABToXYZ()
+ *
+ *      Input:  fpixa (lab)
+ *      Return: fpixa (xyz)
+ *
+ *  Notes:
+ *      (1) The input [l,a,b] and output [x,y,z] values are stored as
+ *          float values, each set in three fpix.
+ */
+FPIXA *
+fpixaConvertLABToXYZ(FPIXA  *fpixas)
+{
+l_int32     w, h, wpl, i, j;
+l_float32   fxval, fyval, fzval, flval, faval, fbval;
+l_float32  *linel, *linea, *lineb, *datal, *dataa, *datab;
+l_float32  *linex, *liney, *linez, *datax, *datay, *dataz;
+FPIX       *fpix;
+FPIXA      *fpixad;
+
+    PROCNAME("fpixaConvertLABToXYZ");
+
+    if (!fpixas || fpixaGetCount(fpixas) != 3)
+        return (FPIXA *)ERROR_PTR("fpixas undefined/invalid", procName, NULL);
+
+        /* Convert LAB image */
+    if (fpixaGetFPixDimensions(fpixas, 0, &w, &h))
+        return (FPIXA *)ERROR_PTR("fpixas sizes not found", procName, NULL);
+    fpixad = fpixaCreate(3);
+    for (i = 0; i < 3; i++) {
+        fpix = fpixCreate(w, h);
+        fpixaAddFPix(fpixad, fpix, L_INSERT);
+    }
+    wpl = fpixGetWpl(fpix);
+    datal = fpixaGetData(fpixas, 0);
+    dataa = fpixaGetData(fpixas, 1);
+    datab = fpixaGetData(fpixas, 2);
+    datax = fpixaGetData(fpixad, 0);
+    datay = fpixaGetData(fpixad, 1);
+    dataz = fpixaGetData(fpixad, 2);
+
+        /* Convert XYZ image */
+    for (i = 0; i < h; i++) {
+        linel = datal + i * wpl;
+        linea = dataa + i * wpl;
+        lineb = datab + i * wpl;
+        linex = datax + i * wpl;
+        liney = datay + i * wpl;
+        linez = dataz + i * wpl;
+        for (j = 0; j < w; j++) {
+            flval = *(linel + j);
+            faval = *(linea + j);
+            fbval = *(lineb + j);
+            convertLABToXYZ(flval, faval, fbval, &fxval, &fyval, &fzval);
+            *(linex + j) = fxval;
+            *(liney + j) = fyval;
+            *(linez + j) = fzval;
+        }
+    }
+
+    return fpixad;
+}
+
+
+/*!
+ *  convertXYZToLAB()
+ *
+ *      Input:  xval, yval, zval (xyz input)
+ *              &lval, &aval, &bval (<return> lab values)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+convertXYZToLAB(l_float32   xval,
+                l_float32   yval,
+                l_float32   zval,
+                l_float32  *plval,
+                l_float32  *paval,
+                l_float32  *pbval)
+{
+l_float32  xn, yn, zn, fx, fy, fz;
+
+    PROCNAME("convertXYZToLAB");
+
+    if (plval) *plval = 0.0;
+    if (paval) *paval = 0.0;
+    if (pbval) *pbval = 0.0;
+    if (!plval || !paval || !pbval)
+        return ERROR_INT("&lval, &aval, &bval not all defined", procName, 1);
+
+        /* First normalize to the corresponding white values */
+    xn = 0.0041259 * xval;
+    yn = 0.0039216 * yval;
+    zn = 0.0036012 * zval;
+        /* Then apply the lab_forward function */
+    fx = lab_forward(xn);
+    fy = lab_forward(yn);
+    fz = lab_forward(zn);
+    *plval = 116.0 * fy - 16.0;
+    *paval = 500.0 * (fx - fy);
+    *pbval = 200.0 * (fy - fz);
+    return 0;
+}
+
+
+/*!
+ *  convertLABToXYZ()
+ *
+ *      Input:  lval, aval, bval
+ *              &xval, &yval, &zval (<return> xyz values)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+convertLABToXYZ(l_float32   lval,
+                l_float32   aval,
+                l_float32   bval,
+                l_float32  *pxval,
+                l_float32  *pyval,
+                l_float32  *pzval)
+{
+l_float32  fx, fy, fz;
+l_float32  xw = 242.37;  /* x component corresponding to rgb white */
+l_float32  yw = 255.0;  /* y component corresponding to rgb white */
+l_float32  zw = 277.69;  /* z component corresponding to rgb white */
+
+    PROCNAME("convertLABToXYZ");
+
+    if (pxval) *pxval = 0.0;
+    if (pyval) *pyval = 0.0;
+    if (pzval) *pzval = 0.0;
+    if (!pxval || !pyval || !pzval)
+        return ERROR_INT("&xval, &yval, &zval not all defined", procName, 1);
+
+    fy = 0.0086207 * (16.0 + lval);
+    fx = fy + 0.002 * aval;
+    fz = fy - 0.005 * bval;
+    *pxval = xw * lab_reverse(fx);
+    *pyval = yw * lab_reverse(fy);
+    *pzval = zw * lab_reverse(fz);
+    return 0;
+}
+
+
+/*
+ * See http://en.wikipedia.org/wiki/Lab_color_space for formulas.
+ * This is the forward function: from xyz to lab.  It includes a rational
+ * function approximation over [0.008856 ... 1] to the cube root, from
+ * "Fast Color Space Transformations Using Minimax Approximations",
+ * M. Celebi et al, http://arxiv.org/pdf/1009.0854v1.pdf.
+ */
+static l_float32
+lab_forward(l_float32  v)
+{
+const l_float32  f_thresh = 0.008856;  /* (6/29)^3  */
+const l_float32  f_factor = 7.787;  /* (1/3) * (29/6)^2)  */
+const l_float32  f_offset = 0.13793;  /* 4/29 */
+
+    if (v > f_thresh) {
+#if  SLOW_CUBE_ROOT
+        return powf(v, 0.333333);
+#else
+        l_float32  num, den;
+        num = 4.37089e-04 + v * (9.52695e-02 + v * (1.25201 + v * 1.30273));
+        den = 3.91236e-03 + v * (2.95408e-01 + v * (1.71714 + v * 6.34341e-01));
+        return num / den;
+#endif
+    } else {
+        return f_factor * v + f_offset;
+    }
+}
+
+
+/*
+ * See http://en.wikipedia.org/wiki/Lab_color_space for formulas.
+ * This is the reverse (inverse) function: from lab to xyz.
+ */
+static l_float32
+lab_reverse(l_float32  v)
+{
+const l_float32  r_thresh = 0.20690;  /* 6/29  */
+const l_float32  r_factor = 0.12842;  /* 3 * (6/29)^2   */
+const l_float32  r_offset = 0.13793;  /* 4/29 */
+
+    if (v > r_thresh) {
+        return v * v * v;
+    } else {
+        return r_factor * (v - r_offset);
+    }
+}
+
+
+/*---------------------------------------------------------------------------*
+ *               Colorspace conversion between RGB and LAB                   *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertRGBToLAB()
+ *
+ *      Input:  pixs (rgb)
+ *      Return: fpixa (lab)
+ *
+ *  Notes:
+ *      (1) The [l,a,b] values are stored as float values in three fpix
+ *          that are returned in a fpixa.
+ */
+FPIXA *
+pixConvertRGBToLAB(PIX  *pixs)
+{
+l_int32     w, h, wpls, wpld, i, j, rval, gval, bval;
+l_uint32   *lines, *datas;
+l_float32   flval, faval, fbval;
+l_float32  *linel, *linea, *lineb, *datal, *dataa, *datab;
+FPIX       *fpix;
+FPIXA      *fpixa;
+
+    PROCNAME("pixConvertRGBToLAB");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (FPIXA *)ERROR_PTR("pixs undefined or not rgb", procName, NULL);
+
+        /* Convert RGB image */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    fpixa = fpixaCreate(3);
+    for (i = 0; i < 3; i++) {
+        fpix = fpixCreate(w, h);
+        fpixaAddFPix(fpixa, fpix, L_INSERT);
+    }
+    wpls = pixGetWpl(pixs);
+    wpld = fpixGetWpl(fpix);
+    datas = pixGetData(pixs);
+    datal = fpixaGetData(fpixa, 0);
+    dataa = fpixaGetData(fpixa, 1);
+    datab = fpixaGetData(fpixa, 2);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        linel = datal + i * wpld;
+        linea = dataa + i * wpld;
+        lineb = datab + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            convertRGBToLAB(rval, gval, bval, &flval, &faval, &fbval);
+            *(linel + j) = flval;
+            *(linea + j) = faval;
+            *(lineb + j) = fbval;
+        }
+    }
+
+    return fpixa;
+}
+
+
+/*!
+ *  fpixaConvertLABToRGB()
+ *
+ *      Input:  fpixa (three fpix: l,a,b)
+ *      Return: pixd (rgb)
+ *
+ *  Notes:
+ *      (1) The lab image is stored in three fpix.
+ */
+PIX *
+fpixaConvertLABToRGB(FPIXA  *fpixa)
+{
+l_int32     w, h, wpls, wpld, i, j, rval, gval, bval;
+l_float32   flval, faval, fbval;
+l_float32  *linel, *linea, *lineb, *datal, *dataa, *datab;
+l_uint32   *lined, *datad;
+PIX        *pixd;
+FPIX       *fpix;
+
+    PROCNAME("fpixaConvertLABToRGB");
+
+    if (!fpixa || fpixaGetCount(fpixa) != 3)
+        return (PIX *)ERROR_PTR("fpixa undefined or invalid", procName, NULL);
+
+        /* Convert LAB image */
+    if (fpixaGetFPixDimensions(fpixa, 0, &w, &h))
+        return (PIX *)ERROR_PTR("fpixa dimensions not found", procName, NULL);
+    pixd = pixCreate(w, h, 32);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    datal = fpixaGetData(fpixa, 0);
+    dataa = fpixaGetData(fpixa, 1);
+    datab = fpixaGetData(fpixa, 2);
+    fpix = fpixaGetFPix(fpixa, 0, L_CLONE);
+    wpls = fpixGetWpl(fpix);
+    fpixDestroy(&fpix);
+    for (i = 0; i < h; i++) {
+        linel = datal + i * wpls;
+        linea = dataa + i * wpls;
+        lineb = datab + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            flval = linel[j];
+            faval = linea[j];
+            fbval = lineb[j];
+            convertLABToRGB(flval, faval, fbval, &rval, &gval, &bval);
+            composeRGBPixel(rval, gval, bval, lined + j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  convertRGBToLAB()
+ *
+ *      Input:  rval, gval, bval (rgb input)
+ *              &flval, &faval, &fbval (<return> lab values)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) These conversions are for illuminant D65 acting on linear sRGB
+ *          values.
+ */
+l_int32
+convertRGBToLAB(l_int32     rval,
+                l_int32     gval,
+                l_int32     bval,
+                l_float32  *pflval,
+                l_float32  *pfaval,
+                l_float32  *pfbval)
+{
+l_float32  fxval, fyval, fzval;
+
+    PROCNAME("convertRGBToLAB");
+
+    if (pflval) *pflval = 0.0;
+    if (pfaval) *pfaval = 0.0;
+    if (pfbval) *pfbval = 0.0;
+    if (!pflval || !pfaval || !pfbval)
+        return ERROR_INT("&flval, &faval, &fbval not all defined", procName, 1);
+
+    convertRGBToXYZ(rval, gval, bval, &fxval, &fyval, &fzval);
+    convertXYZToLAB(fxval, fyval, fzval, pflval, pfaval, pfbval);
+    return 0;
+}
+
+
+/*!
+ *  convertLABToRGB()
+ *
+ *      Input:  flval, faval, fbval
+ *              &rval, &gval, &bval (<return> rgb values)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For values of lab that are out of gamut for rgb, the rgb
+ *          components are set to the closest valid color.
+ */
+l_int32
+convertLABToRGB(l_float32  flval,
+                l_float32  faval,
+                l_float32  fbval,
+                l_int32   *prval,
+                l_int32   *pgval,
+                l_int32   *pbval)
+{
+l_float32  fxval, fyval, fzval;
+
+    PROCNAME("convertLABToRGB");
+
+    if (prval) *prval = 0;
+    if (pgval) *pgval = 0;
+    if (pbval) *pbval = 0;
+    if (!prval || !pgval || !pbval)
+        return ERROR_INT("&rval, &gval, &bval not all defined", procName, 1);
+
+    convertLABToXYZ(flval, faval, fbval, &fxval, &fyval, &fzval);
+    convertXYZToRGB(fxval, fyval, fzval, 0, prval, pgval, pbval);
+    return 0;
+}
+
diff --git a/src/compare.c b/src/compare.c
new file mode 100644 (file)
index 0000000..9dd891a
--- /dev/null
@@ -0,0 +1,3310 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  compare.c
+ *
+ *      Test for pix equality
+ *           l_int32     pixEqual()
+ *           l_int32     pixEqualWithAlpha()
+ *           l_int32     pixEqualWithCmap()
+ *           l_int32     pixUsesCmapColor()
+ *
+ *      Binary correlation
+ *           l_int32     pixCorrelationBinary()
+ *
+ *      Difference of two images of same size
+ *           l_int32     pixDisplayDiffBinary()
+ *           l_int32     pixCompareBinary()
+ *           l_int32     pixCompareGrayOrRGB()
+ *           l_int32     pixCompareGray()
+ *           l_int32     pixCompareRGB()
+ *           l_int32     pixCompareTiled()
+ *
+ *      Other measures of the difference of two images of the same size
+ *           NUMA       *pixCompareRankDifference()
+ *           l_int32     pixTestForSimilarity()
+ *           l_int32     pixGetDifferenceStats()
+ *           NUMA       *pixGetDifferenceHistogram()
+ *           l_int32     pixGetPerceptualDiff()
+ *           l_int32     pixGetPSNR()
+ *
+ *      Comparison of photo regions by histogram
+ *           l_int32     pixaComparePhotoRegionsByHisto()  -- top-level
+ *           l_int32     pixComparePhotoRegionsByHisto()  -- top-level for 2
+ *           l_int32     pixGenPhotoHistos()
+ *           PIX        *pixPadToCenterCentroid()
+ *           l_int32     pixCentroid8()
+ *           l_int32     pixDecideIfPhotoImage()
+ *           l_int32     compareTilesByHisto()
+ *
+ *           l_int32     pixCompareGrayByHisto()  -- top-level for 2
+ *       static l_int32  pixCompareTilesByHisto()
+ *           l_int32     pixCropAlignedToCentroid()
+ *
+ *           l_uint8    *l_compressGrayHistograms()
+ *           NUMAA      *l_uncompressGrayHistograms()
+ *
+ *      Translated images at the same resolution
+ *           l_int32     pixCompareWithTranslation()
+ *           l_int32     pixBestCorrelation()
+ *
+ *  For comparing images using tiled histograms, essentially all the
+ *  computation goes into deciding if a region of an image is a photo,
+ *  whether that photo region is amenable to similarity measurements
+ *  using histograms, and finally the calculation of the gray histograms
+ *  for each of the tiled regions.  The actual comparison is essentially
+ *  instantaneous.  Therefore, with a large number of images to compare
+ *  with each other, it is important to first calculate the histograms
+ *  for each image.  Then the comparisons, which go as the square of the
+ *  number of images, actually takes no time.
+ *
+ *  A high level function that takes a pixa of images and does
+ *  all comparisons, pixaComparePhotosByHisto(), uses this split
+ *  approach.  It pads the images so that the centroid is in the center,
+ *  which will allow the tiles to be better aligned.
+ *
+ *  For testing purposes, two functions are given that do all the work
+ *  to compare just two photo regions:
+ *    *  pixComparePhotoRegionsByHisto() uses the split approach, qualifying
+ *       the images first with pixGenPhotoHistos(), and then comparing
+ *       with compareTilesByHisto().
+ *    *  pixCompareGrayByHisto() aligns the two images by centroid
+ *       and calls pixCompareTilesByHisto() to generate the histograms
+ *       and do the comparison.
+ *
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+    /* Small enough to consider equal to 0.0, for plot output */
+static const l_float32  TINY = 0.00001;
+
+static l_int32 pixCompareTilesByHisto(PIX *pix1, PIX *pix2, l_int32 maxgray,
+                                      l_int32 factor, l_int32 nx, l_int32 ny,
+                                      l_float32 *pscore, PIXA *pixadebug);
+
+
+/*------------------------------------------------------------------*
+ *                        Test for pix equality                     *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixEqual()
+ *
+ *      Input:  pix1
+ *              pix2
+ *              &same  (<return> 1 if same; 0 if different)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Equality is defined as having the same pixel values for
+ *          each respective image pixel.
+ *      (2) This works on two pix of any depth.  If one or both pix
+ *          have a colormap, the depths can be different and the
+ *          two pix can still be equal.
+ *      (3) This ignores the alpha component for 32 bpp images.
+ *      (4) If both pix have colormaps and the depths are equal,
+ *          use the pixEqualWithCmap() function, which does a fast
+ *          comparison if the colormaps are identical and a relatively
+ *          slow comparison otherwise.
+ *      (5) In all other cases, any existing colormaps must first be
+ *          removed before doing pixel comparison.  After the colormaps
+ *          are removed, the resulting two images must have the same depth.
+ *          The "lowest common denominator" is RGB, but this is only
+ *          chosen when necessary, or when both have colormaps but
+ *          different depths.
+ *      (6) For images without colormaps that are not 32 bpp, all bits
+ *          in the image part of the data array must be identical.
+ */
+l_int32
+pixEqual(PIX      *pix1,
+         PIX      *pix2,
+         l_int32  *psame)
+{
+    return pixEqualWithAlpha(pix1, pix2, 0, psame);
+}
+
+
+/*!
+ *  pixEqualWithAlpha()
+ *
+ *      Input:  pix1
+ *              pix2
+ *              use_alpha (1 to compare alpha in RGBA; 0 to ignore)
+ *              &same  (<return> 1 if same; 0 if different)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) See notes in pixEqual().
+ *      (2) This is more general than pixEqual(), in that for 32 bpp
+ *          RGBA images, where spp = 4, you can optionally include
+ *          the alpha component in the comparison.
+ */
+l_int32
+pixEqualWithAlpha(PIX      *pix1,
+                  PIX      *pix2,
+                  l_int32   use_alpha,
+                  l_int32  *psame)
+{
+l_int32    w1, h1, d1, w2, h2, d2, wpl1, wpl2;
+l_int32    spp1, spp2, i, j, color, mismatch, opaque;
+l_int32    fullwords, linebits, endbits;
+l_uint32   endmask, wordmask;
+l_uint32  *data1, *data2, *line1, *line2;
+PIX       *pixs1, *pixs2, *pixt1, *pixt2, *pixalpha;
+PIXCMAP   *cmap1, *cmap2;
+
+    PROCNAME("pixEqualWithAlpha");
+
+    if (!psame)
+        return ERROR_INT("psame not defined", procName, 1);
+    *psame = 0;  /* init to not equal */
+    if (!pix1)
+        return ERROR_INT("pix1 not defined", procName, 1);
+    if (!pix2)
+        return ERROR_INT("pix2 not defined", procName, 1);
+    pixGetDimensions(pix1, &w1, &h1, &d1);
+    pixGetDimensions(pix2, &w2, &h2, &d2);
+    if (w1 != w2 || h1 != h2) {
+        L_INFO("pix sizes differ\n", procName);
+        return 0;
+    }
+
+        /* Suppose the use_alpha flag is true.
+         * If only one of two 32 bpp images has spp == 4, we call that
+         * a "mismatch" of the alpha component.  In the case of a mismatch,
+         * if the 4 bpp pix does not have all alpha components opaque (255),
+         * the images are not-equal.  However if they are all opaque,
+         * this image is equivalent to spp == 3, so we allow the
+         * comparison to go forward, testing only for the RGB equality. */
+    spp1 = pixGetSpp(pix1);
+    spp2 = pixGetSpp(pix2);
+    mismatch = 0;
+    if (use_alpha && d1 == 32 && d2 == 32) {
+        mismatch = ((spp1 == 4 && spp2 != 4) || (spp1 != 4 && spp2 == 4));
+        if (mismatch) {
+            pixalpha = (spp1 == 4) ? pix1 : pix2;
+            pixAlphaIsOpaque(pixalpha, &opaque);
+            if (!opaque) {
+                L_INFO("just one pix has a non-opaque alpha layer\n", procName);
+                return 0;
+            }
+        }
+    }
+
+    cmap1 = pixGetColormap(pix1);
+    cmap2 = pixGetColormap(pix2);
+    if (!cmap1 && !cmap2 && (d1 != d2) && (d1 == 32 || d2 == 32)) {
+        L_INFO("no colormaps, pix depths unequal, and one of them is RGB\n",
+               procName);
+        return 0;
+    }
+
+    if (cmap1 && cmap2 && (d1 == d2))   /* use special function */
+        return pixEqualWithCmap(pix1, pix2, psame);
+
+        /* Must remove colormaps if they exist, and in the process
+         * end up with the resulting images having the same depth. */
+    if (cmap1 && !cmap2) {
+        pixUsesCmapColor(pix1, &color);
+        if (color && d2 <= 8)  /* can't be equal */
+            return 0;
+        if (d2 < 8)
+            pixs2 = pixConvertTo8(pix2, FALSE);
+        else
+            pixs2 = pixClone(pix2);
+        if (d2 <= 8)
+            pixs1 = pixRemoveColormap(pix1, REMOVE_CMAP_TO_GRAYSCALE);
+        else
+            pixs1 = pixRemoveColormap(pix1, REMOVE_CMAP_TO_FULL_COLOR);
+    } else if (!cmap1 && cmap2) {
+        pixUsesCmapColor(pix2, &color);
+        if (color && d1 <= 8)  /* can't be equal */
+            return 0;
+        if (d1 < 8)
+            pixs1 = pixConvertTo8(pix1, FALSE);
+        else
+            pixs1 = pixClone(pix1);
+        if (d1 <= 8)
+            pixs2 = pixRemoveColormap(pix2, REMOVE_CMAP_TO_GRAYSCALE);
+        else
+            pixs2 = pixRemoveColormap(pix2, REMOVE_CMAP_TO_FULL_COLOR);
+    } else if (cmap1 && cmap2) {  /* depths not equal; use rgb */
+        pixs1 = pixRemoveColormap(pix1, REMOVE_CMAP_TO_FULL_COLOR);
+        pixs2 = pixRemoveColormap(pix2, REMOVE_CMAP_TO_FULL_COLOR);
+    } else {  /* no colormaps */
+        pixs1 = pixClone(pix1);
+        pixs2 = pixClone(pix2);
+    }
+
+        /* OK, we have no colormaps, but the depths may still be different */
+    d1 = pixGetDepth(pixs1);
+    d2 = pixGetDepth(pixs2);
+    if (d1 != d2) {
+        if (d1 == 16 || d2 == 16) {
+            L_INFO("one pix is 16 bpp\n", procName);
+            pixDestroy(&pixs1);
+            pixDestroy(&pixs2);
+            return 0;
+        }
+        pixt1 = pixConvertLossless(pixs1, 8);
+        pixt2 = pixConvertLossless(pixs2, 8);
+        if (!pixt1 || !pixt2) {
+            L_INFO("failure to convert to 8 bpp\n", procName);
+            pixDestroy(&pixs1);
+            pixDestroy(&pixs2);
+            pixDestroy(&pixt1);
+            pixDestroy(&pixt2);
+            return 0;
+        }
+    } else {
+        pixt1 = pixClone(pixs1);
+        pixt2 = pixClone(pixs2);
+    }
+    pixDestroy(&pixs1);
+    pixDestroy(&pixs2);
+
+        /* No colormaps, equal depths; do pixel comparisons */
+    d1 = pixGetDepth(pixt1);
+    d2 = pixGetDepth(pixt2);
+    wpl1 = pixGetWpl(pixt1);
+    wpl2 = pixGetWpl(pixt2);
+    data1 = pixGetData(pixt1);
+    data2 = pixGetData(pixt2);
+
+    if (d1 == 32) {  /* test either RGB or RGBA pixels */
+        if (use_alpha && !mismatch)
+            wordmask = (spp1 == 3) ? 0xffffff00 : 0xffffffff;
+        else
+            wordmask = 0xffffff00;
+        for (i = 0; i < h1; i++) {
+            line1 = data1 + wpl1 * i;
+            line2 = data2 + wpl2 * i;
+            for (j = 0; j < wpl1; j++) {
+                if ((*line1 ^ *line2) & wordmask) {
+                    pixDestroy(&pixt1);
+                    pixDestroy(&pixt2);
+                    return 0;
+                }
+                line1++;
+                line2++;
+            }
+        }
+    } else {  /* all bits count */
+        linebits = d1 * w1;
+        fullwords = linebits / 32;
+        endbits = linebits & 31;
+        endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits));
+        for (i = 0; i < h1; i++) {
+            line1 = data1 + wpl1 * i;
+            line2 = data2 + wpl2 * i;
+            for (j = 0; j < fullwords; j++) {
+                if (*line1 ^ *line2) {
+                    pixDestroy(&pixt1);
+                    pixDestroy(&pixt2);
+                    return 0;
+                }
+                line1++;
+                line2++;
+            }
+            if (endbits) {
+                if ((*line1 ^ *line2) & endmask) {
+                    pixDestroy(&pixt1);
+                    pixDestroy(&pixt2);
+                    return 0;
+                }
+            }
+        }
+    }
+
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    *psame = 1;
+    return 0;
+}
+
+
+/*!
+ *  pixEqualWithCmap()
+ *
+ *      Input:  pix1
+ *              pix2
+ *              &same
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This returns same = TRUE if the images have identical content.
+ *      (2) Both pix must have a colormap, and be of equal size and depth.
+ *          If these conditions are not satisfied, it is not an error;
+ *          the returned result is same = FALSE.
+ *      (3) We then check whether the colormaps are the same; if so,
+ *          the comparison proceeds 32 bits at a time.
+ *      (4) If the colormaps are different, the comparison is done by
+ *          slow brute force.
+ */
+l_int32
+pixEqualWithCmap(PIX      *pix1,
+                 PIX      *pix2,
+                 l_int32  *psame)
+{
+l_int32    d, w, h, wpl1, wpl2, i, j, linebits, fullwords, endbits;
+l_int32    nc1, nc2, samecmaps;
+l_int32    rval1, rval2, gval1, gval2, bval1, bval2;
+l_uint32   endmask, val1, val2;
+l_uint32  *data1, *data2, *line1, *line2;
+PIXCMAP   *cmap1, *cmap2;
+
+    PROCNAME("pixEqualWithCmap");
+
+    if (!psame)
+        return ERROR_INT("&same not defined", procName, 1);
+    *psame = 0;
+    if (!pix1)
+        return ERROR_INT("pix1 not defined", procName, 1);
+    if (!pix2)
+        return ERROR_INT("pix2 not defined", procName, 1);
+
+    if (pixSizesEqual(pix1, pix2) == 0)
+        return 0;
+
+    cmap1 = pixGetColormap(pix1);
+    cmap2 = pixGetColormap(pix2);
+    if (!cmap1 || !cmap2) {
+        L_INFO("both images don't have colormap\n", procName);
+        return 0;
+    }
+    d = pixGetDepth(pix1);
+    if (d != 1 && d != 2 && d != 4 && d != 8) {
+        L_INFO("pix depth not in {1, 2, 4, 8}\n", procName);
+        return 0;
+    }
+
+    nc1 = pixcmapGetCount(cmap1);
+    nc2 = pixcmapGetCount(cmap2);
+    samecmaps = TRUE;
+    if (nc1 != nc2) {
+        L_INFO("colormap sizes are different\n", procName);
+        samecmaps = FALSE;
+    }
+
+        /* Check if colormaps are identical */
+    if (samecmaps == TRUE) {
+        for (i = 0; i < nc1; i++) {
+            pixcmapGetColor(cmap1, i, &rval1, &gval1, &bval1);
+            pixcmapGetColor(cmap2, i, &rval2, &gval2, &bval2);
+            if (rval1 != rval2 || gval1 != gval2 || bval1 != bval2) {
+                samecmaps = FALSE;
+                break;
+            }
+        }
+    }
+
+    h = pixGetHeight(pix1);
+    w = pixGetWidth(pix1);
+    if (samecmaps == TRUE) {  /* colormaps are identical; compare by words */
+        linebits = d * w;
+        wpl1 = pixGetWpl(pix1);
+        wpl2 = pixGetWpl(pix2);
+        data1 = pixGetData(pix1);
+        data2 = pixGetData(pix2);
+        fullwords = linebits / 32;
+        endbits = linebits & 31;
+        endmask = 0xffffffff << (32 - endbits);
+        for (i = 0; i < h; i++) {
+            line1 = data1 + wpl1 * i;
+            line2 = data2 + wpl2 * i;
+            for (j = 0; j < fullwords; j++) {
+                if (*line1 ^ *line2)
+                    return 0;
+                line1++;
+                line2++;
+            }
+            if (endbits) {
+                if ((*line1 ^ *line2) & endmask)
+                    return 0;
+            }
+        }
+        *psame = 1;
+        return 0;
+    }
+
+        /* Colormaps aren't identical; compare pixel by pixel */
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < w; j++) {
+            pixGetPixel(pix1, j, i, &val1);
+            pixGetPixel(pix2, j, i, &val2);
+            pixcmapGetColor(cmap1, val1, &rval1, &gval1, &bval1);
+            pixcmapGetColor(cmap2, val2, &rval2, &gval2, &bval2);
+            if (rval1 != rval2 || gval1 != gval2 || bval1 != bval2)
+                return 0;
+        }
+    }
+
+    *psame = 1;
+    return 0;
+}
+
+
+/*!
+ *  pixUsesCmapColor()
+ *
+ *      Input:  pixs
+ *              &color (<return>)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This returns color = TRUE if three things are obtained:
+ *          (a) the pix has a colormap
+ *          (b) the colormap has at least one color entry
+ *          (c) a color entry is actually used
+ *      (2) It is used in pixEqual() for comparing two images, in a
+ *          situation where it is required to know if the colormap
+ *          has color entries that are actually used in the image.
+ */
+l_int32
+pixUsesCmapColor(PIX      *pixs,
+                 l_int32  *pcolor)
+{
+l_int32   n, i, rval, gval, bval, numpix;
+NUMA     *na;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixUsesCmapColor");
+
+    if (!pcolor)
+        return ERROR_INT("&color not defined", procName, 1);
+    *pcolor = 0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    if ((cmap = pixGetColormap(pixs)) == NULL)
+        return 0;
+
+    pixcmapHasColor(cmap, pcolor);
+    if (*pcolor == 0)  /* no color */
+        return 0;
+
+        /* The cmap has color entries.  Are they used? */
+    na = pixGetGrayHistogram(pixs, 1);
+    n = pixcmapGetCount(cmap);
+    for (i = 0; i < n; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        numaGetIValue(na, i, &numpix);
+        if ((rval != gval || rval != bval) && numpix) {  /* color found! */
+            *pcolor = 1;
+            break;
+        }
+    }
+    numaDestroy(&na);
+
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *                          Binary correlation                      *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixCorrelationBinary()
+ *
+ *      Input:  pix1 (1 bpp)
+ *              pix2 (1 bpp)
+ *              &val (<return> correlation)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The correlation is a number between 0.0 and 1.0,
+ *          based on foreground similarity:
+ *                           (|1 AND 2|)**2
+ *            correlation =  --------------
+ *                             |1| * |2|
+ *          where |x| is the count of foreground pixels in image x.
+ *          If the images are identical, this is 1.0.
+ *          If they have no fg pixels in common, this is 0.0.
+ *          If one or both images have no fg pixels, the correlation is 0.0.
+ *      (2) Typically the two images are of equal size, but this
+ *          is not enforced.  Instead, the UL corners are aligned.
+ */
+l_int32
+pixCorrelationBinary(PIX        *pix1,
+                     PIX        *pix2,
+                     l_float32  *pval)
+{
+l_int32   count1, count2, countn;
+l_int32  *tab8;
+PIX      *pixn;
+
+    PROCNAME("pixCorrelationBinary");
+
+    if (!pval)
+        return ERROR_INT("&pval not defined", procName, 1);
+    *pval = 0.0;
+    if (!pix1)
+        return ERROR_INT("pix1 not defined", procName, 1);
+    if (!pix2)
+        return ERROR_INT("pix2 not defined", procName, 1);
+
+    tab8 = makePixelSumTab8();
+    pixCountPixels(pix1, &count1, tab8);
+    pixCountPixels(pix2, &count2, tab8);
+    pixn = pixAnd(NULL, pix1, pix2);
+    pixCountPixels(pixn, &countn, tab8);
+    *pval = (l_float32)countn * (l_float32)countn /
+              ((l_float32)count1 * (l_float32)count2);
+    LEPT_FREE(tab8);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *                   Difference of two images                       *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixDisplayDiffBinary()
+ *
+ *      Input:  pix1 (1 bpp)
+ *              pix2 (1 bpp)
+ *      Return: pixd (4 bpp cmapped), or null on error
+ *
+ *  Notes:
+ *      (1) This gives a color representation of the difference between
+ *          pix1 and pix2.  The color difference depends on the order.
+ *          The pixels in pixd have 4 colors:
+ *           * unchanged:  black (on), white (off)
+ *           * on in pix1, off in pix2: red
+ *           * on in pix2, off in pix1: green
+ *      (2) This aligns the UL corners of pix1 and pix2, and crops
+ *          to the overlapping pixels.
+ */
+PIX *
+pixDisplayDiffBinary(PIX  *pix1,
+                     PIX  *pix2)
+{
+l_int32   w1, h1, d1, w2, h2, d2, minw, minh;
+PIX      *pixt, *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixDisplayDiffBinary");
+
+    if (!pix1 || !pix2)
+        return (PIX *)ERROR_PTR("pix1, pix2 not both defined", procName, NULL);
+    pixGetDimensions(pix1, &w1, &h1, &d1);
+    pixGetDimensions(pix2, &w2, &h2, &d2);
+    if (d1 != 1 || d2 != 1)
+        return (PIX *)ERROR_PTR("pix1 and pix2 not 1 bpp", procName, NULL);
+    minw = L_MIN(w1, w2);
+    minh = L_MIN(h1, h2);
+
+    pixd = pixCreate(minw, minh, 4);
+    cmap = pixcmapCreate(4);
+    pixcmapAddColor(cmap, 255, 255, 255);  /* initialized to white */
+    pixcmapAddColor(cmap, 0, 0, 0);
+    pixcmapAddColor(cmap, 255, 0, 0);
+    pixcmapAddColor(cmap, 0, 255, 0);
+    pixSetColormap(pixd, cmap);
+
+    pixt = pixAnd(NULL, pix1, pix2);
+    pixPaintThroughMask(pixd, pixt, 0, 0, 0x0);  /* black */
+    pixSubtract(pixt, pix1, pix2);
+    pixPaintThroughMask(pixd, pixt, 0, 0, 0xff000000);  /* red */
+    pixSubtract(pixt, pix2, pix1);
+    pixPaintThroughMask(pixd, pixt, 0, 0, 0x00ff0000);  /* green */
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixCompareBinary()
+ *
+ *      Input:  pix1 (1 bpp)
+ *              pix2 (1 bpp)
+ *              comptype (L_COMPARE_XOR, L_COMPARE_SUBTRACT)
+ *              &fract (<return> fraction of pixels that are different)
+ *              &pixdiff (<optional return> pix of difference)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The two images are aligned at the UL corner, and do not
+ *          need to be the same size.
+ *      (2) If using L_COMPARE_SUBTRACT, pix2 is subtracted from pix1.
+ *      (3) The total number of pixels is determined by pix1.
+ */
+l_int32
+pixCompareBinary(PIX        *pix1,
+                 PIX        *pix2,
+                 l_int32     comptype,
+                 l_float32  *pfract,
+                 PIX       **ppixdiff)
+{
+l_int32   w, h, count;
+PIX      *pixt;
+
+    PROCNAME("pixCompareBinary");
+
+    if (ppixdiff) *ppixdiff = NULL;
+    if (!pfract)
+        return ERROR_INT("&pfract not defined", procName, 1);
+    *pfract = 0.0;
+    if (!pix1 || pixGetDepth(pix1) != 1)
+        return ERROR_INT("pix1 not defined or not 1 bpp", procName, 1);
+    if (!pix2 || pixGetDepth(pix2) != 1)
+        return ERROR_INT("pix2 not defined or not 1 bpp", procName, 1);
+    if (comptype != L_COMPARE_XOR && comptype != L_COMPARE_SUBTRACT)
+        return ERROR_INT("invalid comptype", procName, 1);
+
+    if (comptype == L_COMPARE_XOR)
+        pixt = pixXor(NULL, pix1, pix2);
+    else  /* comptype == L_COMPARE_SUBTRACT) */
+        pixt = pixSubtract(NULL, pix1, pix2);
+    pixCountPixels(pixt, &count, NULL);
+    pixGetDimensions(pix1, &w, &h, NULL);
+    *pfract = (l_float32)(count) / (l_float32)(w * h);
+
+    if (ppixdiff)
+        *ppixdiff = pixt;
+    else
+        pixDestroy(&pixt);
+    return 0;
+}
+
+
+/*!
+ *  pixCompareGrayOrRGB()
+ *
+ *      Input:  pix1 (8 or 16 bpp gray, 32 bpp rgb, or colormapped)
+ *              pix2 (8 or 16 bpp gray, 32 bpp rgb, or colormapped)
+ *              comptype (L_COMPARE_SUBTRACT, L_COMPARE_ABS_DIFF)
+ *              plottype (gplot plot output type, or 0 for no plot)
+ *              &same (<optional return> 1 if pixel values are identical)
+ *              &diff (<optional return> average difference)
+ *              &rmsdiff (<optional return> rms of difference)
+ *              &pixdiff (<optional return> pix of difference)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The two images are aligned at the UL corner, and do not
+ *          need to be the same size.  If they are not the same size,
+ *          the comparison will be made over overlapping pixels.
+ *      (2) If there is a colormap, it is removed and the result
+ *          is either gray or RGB depending on the colormap.
+ *      (3) If RGB, each component is compared separately.
+ *      (4) If type is L_COMPARE_ABS_DIFF, pix2 is subtracted from pix1
+ *          and the absolute value is taken.
+ *      (5) If type is L_COMPARE_SUBTRACT, pix2 is subtracted from pix1
+ *          and the result is clipped to 0.
+ *      (6) The plot output types are specified in gplot.h.
+ *          Use 0 if no difference plot is to be made.
+ *      (7) If the images are pixelwise identical, no difference
+ *          plot is made, even if requested.  The result (TRUE or FALSE)
+ *          is optionally returned in the parameter 'same'.
+ *      (8) The average difference (either subtracting or absolute value)
+ *          is optionally returned in the parameter 'diff'.
+ *      (9) The RMS difference is optionally returned in the
+ *          parameter 'rmsdiff'.  For RGB, we return the average of
+ *          the RMS differences for each of the components.
+ */
+l_int32
+pixCompareGrayOrRGB(PIX        *pix1,
+                    PIX        *pix2,
+                    l_int32     comptype,
+                    l_int32     plottype,
+                    l_int32    *psame,
+                    l_float32  *pdiff,
+                    l_float32  *prmsdiff,
+                    PIX       **ppixdiff)
+{
+l_int32  retval, d;
+PIX     *pixt1, *pixt2;
+
+    PROCNAME("pixCompareGrayOrRGB");
+
+    if (ppixdiff) *ppixdiff = NULL;
+    if (!pix1)
+        return ERROR_INT("pix1 not defined", procName, 1);
+    if (!pix2)
+        return ERROR_INT("pix2 not defined", procName, 1);
+    if (pixGetDepth(pix1) < 8 && !pixGetColormap(pix1))
+        return ERROR_INT("pix1 depth < 8 bpp and not cmapped", procName, 1);
+    if (pixGetDepth(pix2) < 8 && !pixGetColormap(pix2))
+        return ERROR_INT("pix2 depth < 8 bpp and not cmapped", procName, 1);
+    if (comptype != L_COMPARE_SUBTRACT && comptype != L_COMPARE_ABS_DIFF)
+        return ERROR_INT("invalid comptype", procName, 1);
+    if (plottype > NUM_GPLOT_OUTPUTS)
+        return ERROR_INT("invalid plottype", procName, 1);
+
+    pixt1 = pixRemoveColormap(pix1, REMOVE_CMAP_BASED_ON_SRC);
+    pixt2 = pixRemoveColormap(pix2, REMOVE_CMAP_BASED_ON_SRC);
+    d = pixGetDepth(pixt1);
+    if (d != pixGetDepth(pixt2)) {
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt2);
+        return ERROR_INT("intrinsic depths are not equal", procName, 1);
+    }
+
+    if (d == 8 || d == 16)
+        retval = pixCompareGray(pixt1, pixt2, comptype, plottype, psame,
+                                pdiff, prmsdiff, ppixdiff);
+    else  /* d == 32 */
+        retval = pixCompareRGB(pixt1, pixt2, comptype, plottype, psame,
+                               pdiff, prmsdiff, ppixdiff);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    return retval;
+}
+
+
+/*!
+ *  pixCompareGray()
+ *
+ *      Input:  pix1 (8 or 16 bpp, not cmapped)
+ *              pix2 (8 or 16 bpp, not cmapped)
+ *              comptype (L_COMPARE_SUBTRACT, L_COMPARE_ABS_DIFF)
+ *              plottype (gplot plot output type, or 0 for no plot)
+ *              &same (<optional return> 1 if pixel values are identical)
+ *              &diff (<optional return> average difference)
+ *              &rmsdiff (<optional return> rms of difference)
+ *              &pixdiff (<optional return> pix of difference)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) See pixCompareGrayOrRGB() for details.
+ *      (2) Use pixCompareGrayOrRGB() if the input pix are colormapped.
+ */
+l_int32
+pixCompareGray(PIX        *pix1,
+               PIX        *pix2,
+               l_int32     comptype,
+               l_int32     plottype,
+               l_int32    *psame,
+               l_float32  *pdiff,
+               l_float32  *prmsdiff,
+               PIX       **ppixdiff)
+{
+char            buf[64];
+static l_int32  index = 0;
+l_int32         d1, d2, same, first, last;
+GPLOT          *gplot;
+NUMA           *na, *nac;
+PIX            *pixt;
+
+    PROCNAME("pixCompareGray");
+
+    if (psame) *psame = 0;
+    if (pdiff) *pdiff = 0.0;
+    if (prmsdiff) *prmsdiff = 0.0;
+    if (ppixdiff) *ppixdiff = NULL;
+    if (!pix1)
+        return ERROR_INT("pix1 not defined", procName, 1);
+    if (!pix2)
+        return ERROR_INT("pix2 not defined", procName, 1);
+    d1 = pixGetDepth(pix1);
+    d2 = pixGetDepth(pix2);
+    if ((d1 != d2) || (d1 != 8 && d1 != 16))
+        return ERROR_INT("depths unequal or not 8 or 16 bpp", procName, 1);
+    if (pixGetColormap(pix1) || pixGetColormap(pix2))
+        return ERROR_INT("pix1 and/or pix2 are colormapped", procName, 1);
+    if (comptype != L_COMPARE_SUBTRACT && comptype != L_COMPARE_ABS_DIFF)
+        return ERROR_INT("invalid comptype", procName, 1);
+    if (plottype > NUM_GPLOT_OUTPUTS)
+        return ERROR_INT("invalid plottype", procName, 1);
+
+    lept_mkdir("lept/comp");
+
+    if (comptype == L_COMPARE_SUBTRACT)
+        pixt = pixSubtractGray(NULL, pix1, pix2);
+    else  /* comptype == L_COMPARE_ABS_DIFF) */
+        pixt = pixAbsDifference(pix1, pix2);
+
+    pixZero(pixt, &same);
+    if (same)
+        L_INFO("Images are pixel-wise identical\n", procName);
+    if (psame) *psame = same;
+
+    if (pdiff)
+        pixGetAverageMasked(pixt, NULL, 0, 0, 1, L_MEAN_ABSVAL, pdiff);
+
+        /* Don't bother to plot if the images are the same */
+    if (plottype && !same) {
+        na = pixGetGrayHistogram(pixt, 1);
+        numaGetNonzeroRange(na, TINY, &first, &last);
+        nac = numaClipToInterval(na, 0, last);
+        snprintf(buf, sizeof(buf), "/tmp/lept/comp/compare_gray%d", index++);
+        gplot = gplotCreate(buf, plottype,
+                            "Pixel Difference Histogram", "diff val",
+                            "number of pixels");
+        gplotAddPlot(gplot, NULL, nac, GPLOT_LINES, "gray");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+        numaDestroy(&na);
+        numaDestroy(&nac);
+    }
+
+    if (ppixdiff)
+        *ppixdiff = pixCopy(NULL, pixt);
+
+    if (prmsdiff) {
+        if (comptype == L_COMPARE_SUBTRACT) {  /* wrong type for rms diff */
+            pixDestroy(&pixt);
+            pixt = pixAbsDifference(pix1, pix2);
+        }
+        pixGetAverageMasked(pixt, NULL, 0, 0, 1, L_ROOT_MEAN_SQUARE, prmsdiff);
+    }
+
+    pixDestroy(&pixt);
+    return 0;
+}
+
+
+/*!
+ *  pixCompareRGB()
+ *
+ *      Input:  pix1 (32 bpp rgb)
+ *              pix2 (32 bpp rgb)
+ *              comptype (L_COMPARE_SUBTRACT, L_COMPARE_ABS_DIFF)
+ *              plottype (gplot plot output type, or 0 for no plot)
+ *              &same (<optional return> 1 if pixel values are identical)
+ *              &diff (<optional return> average difference)
+ *              &rmsdiff (<optional return> rms of difference)
+ *              &pixdiff (<optional return> pix of difference)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) See pixCompareGrayOrRGB() for details.
+ */
+l_int32
+pixCompareRGB(PIX        *pix1,
+              PIX        *pix2,
+              l_int32     comptype,
+              l_int32     plottype,
+              l_int32    *psame,
+              l_float32  *pdiff,
+              l_float32  *prmsdiff,
+              PIX       **ppixdiff)
+{
+char            buf[64];
+static l_int32  index = 0;
+l_int32         rsame, gsame, bsame, same, first, rlast, glast, blast, last;
+l_float32       rdiff, gdiff, bdiff;
+GPLOT          *gplot;
+NUMA           *nar, *nag, *nab, *narc, *nagc, *nabc;
+PIX            *pixr1, *pixr2, *pixg1, *pixg2, *pixb1, *pixb2;
+PIX            *pixr, *pixg, *pixb;
+
+    PROCNAME("pixCompareRGB");
+
+    if (psame) *psame = 0;
+    if (pdiff) *pdiff = 0.0;
+    if (prmsdiff) *prmsdiff = 0.0;
+    if (ppixdiff) *ppixdiff = NULL;
+    if (!pix1 || pixGetDepth(pix1) != 32)
+        return ERROR_INT("pix1 not defined or not 32 bpp", procName, 1);
+    if (!pix2 || pixGetDepth(pix2) != 32)
+        return ERROR_INT("pix2 not defined or not ew bpp", procName, 1);
+    if (comptype != L_COMPARE_SUBTRACT && comptype != L_COMPARE_ABS_DIFF)
+        return ERROR_INT("invalid comptype", procName, 1);
+    if (plottype > NUM_GPLOT_OUTPUTS)
+        return ERROR_INT("invalid plottype", procName, 1);
+
+    lept_mkdir("lept/comp");
+
+    pixr1 = pixGetRGBComponent(pix1, COLOR_RED);
+    pixr2 = pixGetRGBComponent(pix2, COLOR_RED);
+    pixg1 = pixGetRGBComponent(pix1, COLOR_GREEN);
+    pixg2 = pixGetRGBComponent(pix2, COLOR_GREEN);
+    pixb1 = pixGetRGBComponent(pix1, COLOR_BLUE);
+    pixb2 = pixGetRGBComponent(pix2, COLOR_BLUE);
+    if (comptype == L_COMPARE_SUBTRACT) {
+        pixr = pixSubtractGray(NULL, pixr1, pixr2);
+        pixg = pixSubtractGray(NULL, pixg1, pixg2);
+        pixb = pixSubtractGray(NULL, pixb1, pixb2);
+    } else { /* comptype == L_COMPARE_ABS_DIFF) */
+        pixr = pixAbsDifference(pixr1, pixr2);
+        pixg = pixAbsDifference(pixg1, pixg2);
+        pixb = pixAbsDifference(pixb1, pixb2);
+    }
+
+    pixZero(pixr, &rsame);
+    pixZero(pixg, &gsame);
+    pixZero(pixb, &bsame);
+    same = rsame && gsame && bsame;
+    if (same)
+        L_INFO("Images are pixel-wise identical\n", procName);
+    if (psame) *psame = same;
+
+    if (pdiff) {
+        pixGetAverageMasked(pixr, NULL, 0, 0, 1, L_MEAN_ABSVAL, &rdiff);
+        pixGetAverageMasked(pixg, NULL, 0, 0, 1, L_MEAN_ABSVAL, &gdiff);
+        pixGetAverageMasked(pixb, NULL, 0, 0, 1, L_MEAN_ABSVAL, &bdiff);
+        *pdiff = (rdiff + gdiff + bdiff) / 3.0;
+    }
+
+        /* Don't bother to plot if the images are the same */
+    if (plottype && !same) {
+        nar = pixGetGrayHistogram(pixr, 1);
+        nag = pixGetGrayHistogram(pixg, 1);
+        nab = pixGetGrayHistogram(pixb, 1);
+        numaGetNonzeroRange(nar, TINY, &first, &rlast);
+        numaGetNonzeroRange(nag, TINY, &first, &glast);
+        numaGetNonzeroRange(nab, TINY, &first, &blast);
+        last = L_MAX(rlast, glast);
+        last = L_MAX(last, blast);
+        narc = numaClipToInterval(nar, 0, last);
+        nagc = numaClipToInterval(nag, 0, last);
+        nabc = numaClipToInterval(nab, 0, last);
+        snprintf(buf, sizeof(buf), "/tmp/lept/comp/compare_rgb%d", index++);
+        gplot = gplotCreate(buf, plottype,
+                            "Pixel Difference Histogram", "diff val",
+                            "number of pixels");
+        gplotAddPlot(gplot, NULL, narc, GPLOT_LINES, "red");
+        gplotAddPlot(gplot, NULL, nagc, GPLOT_LINES, "green");
+        gplotAddPlot(gplot, NULL, nabc, GPLOT_LINES, "blue");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+        numaDestroy(&nar);
+        numaDestroy(&nag);
+        numaDestroy(&nab);
+        numaDestroy(&narc);
+        numaDestroy(&nagc);
+        numaDestroy(&nabc);
+    }
+
+    if (ppixdiff)
+        *ppixdiff = pixCreateRGBImage(pixr, pixg, pixb);
+
+    if (prmsdiff) {
+        if (comptype == L_COMPARE_SUBTRACT) {
+            pixDestroy(&pixr);
+            pixDestroy(&pixg);
+            pixDestroy(&pixb);
+            pixr = pixAbsDifference(pixr1, pixr2);
+            pixg = pixAbsDifference(pixg1, pixg2);
+            pixb = pixAbsDifference(pixb1, pixb2);
+        }
+        pixGetAverageMasked(pixr, NULL, 0, 0, 1, L_ROOT_MEAN_SQUARE, &rdiff);
+        pixGetAverageMasked(pixg, NULL, 0, 0, 1, L_ROOT_MEAN_SQUARE, &gdiff);
+        pixGetAverageMasked(pixb, NULL, 0, 0, 1, L_ROOT_MEAN_SQUARE, &bdiff);
+        *prmsdiff = (rdiff + gdiff + bdiff) / 3.0;
+    }
+
+    pixDestroy(&pixr1);
+    pixDestroy(&pixr2);
+    pixDestroy(&pixg1);
+    pixDestroy(&pixg2);
+    pixDestroy(&pixb1);
+    pixDestroy(&pixb2);
+    pixDestroy(&pixr);
+    pixDestroy(&pixg);
+    pixDestroy(&pixb);
+    return 0;
+}
+
+
+/*!
+ *  pixCompareTiled()
+ *
+ *      Input:  pix1 (8 bpp or 32 bpp rgb)
+ *              pix2 (8 bpp 32 bpp rgb)
+ *              sx, sy (tile size; must be > 1)
+ *              type (L_MEAN_ABSVAL or L_ROOT_MEAN_SQUARE)
+ *              &pixdiff (<return> pix of difference)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) With L_MEAN_ABSVAL, we compute for each tile the
+ *          average abs value of the pixel component difference between
+ *          the two (aligned) images.  With L_ROOT_MEAN_SQUARE, we
+ *          compute instead the rms difference over all components.
+ *      (2) The two input pix must be the same depth.  Comparison is made
+ *          using UL corner alignment.
+ *      (3) For 32 bpp, the distance between corresponding tiles
+ *          is found by averaging the measured difference over all three
+ *          components of each pixel in the tile.
+ *      (4) The result, pixdiff, contains one pixel for each source tile.
+ */
+l_int32
+pixCompareTiled(PIX     *pix1,
+                PIX     *pix2,
+                l_int32  sx,
+                l_int32  sy,
+                l_int32  type,
+                PIX    **ppixdiff)
+{
+l_int32    d1, d2, w, h;
+PIX       *pixt, *pixr, *pixg, *pixb;
+PIX       *pixrdiff, *pixgdiff, *pixbdiff;
+PIXACC    *pixacc;
+
+    PROCNAME("pixCompareTiled");
+
+    if (!ppixdiff)
+        return ERROR_INT("&pixdiff not defined", procName, 1);
+    *ppixdiff = NULL;
+    if (!pix1)
+        return ERROR_INT("pix1 not defined", procName, 1);
+    if (!pix2)
+        return ERROR_INT("pix2 not defined", procName, 1);
+    d1 = pixGetDepth(pix1);
+    d2 = pixGetDepth(pix2);
+    if (d1 != d2)
+        return ERROR_INT("depths not equal", procName, 1);
+    if (d1 != 8 && d1 != 32)
+        return ERROR_INT("pix1 not 8 or 32 bpp", procName, 1);
+    if (d2 != 8 && d2 != 32)
+        return ERROR_INT("pix2 not 8 or 32 bpp", procName, 1);
+    if (sx < 2 || sy < 2)
+        return ERROR_INT("sx and sy not both > 1", procName, 1);
+    if (type != L_MEAN_ABSVAL && type != L_ROOT_MEAN_SQUARE)
+        return ERROR_INT("invalid type", procName, 1);
+
+    pixt = pixAbsDifference(pix1, pix2);
+    if (d1 == 8) {
+        *ppixdiff = pixGetAverageTiled(pixt, sx, sy, type);
+    } else {  /* d1 == 32 */
+        pixr = pixGetRGBComponent(pixt, COLOR_RED);
+        pixg = pixGetRGBComponent(pixt, COLOR_GREEN);
+        pixb = pixGetRGBComponent(pixt, COLOR_BLUE);
+        pixrdiff = pixGetAverageTiled(pixr, sx, sy, type);
+        pixgdiff = pixGetAverageTiled(pixg, sx, sy, type);
+        pixbdiff = pixGetAverageTiled(pixb, sx, sy, type);
+        pixGetDimensions(pixrdiff, &w, &h, NULL);
+        pixacc = pixaccCreate(w, h, 0);
+        pixaccAdd(pixacc, pixrdiff);
+        pixaccAdd(pixacc, pixgdiff);
+        pixaccAdd(pixacc, pixbdiff);
+        pixaccMultConst(pixacc, 1. / 3.);
+        *ppixdiff = pixaccFinal(pixacc, 8);
+        pixDestroy(&pixr);
+        pixDestroy(&pixg);
+        pixDestroy(&pixb);
+        pixDestroy(&pixrdiff);
+        pixDestroy(&pixgdiff);
+        pixDestroy(&pixbdiff);
+        pixaccDestroy(&pixacc);
+    }
+    pixDestroy(&pixt);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *            Other measures of the difference of two images        *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixCompareRankDifference()
+ *
+ *      Input:  pix1 (8 bpp gray or 32 bpp rgb, or colormapped)
+ *              pix2 (8 bpp gray or 32 bpp rgb, or colormapped)
+ *              factor (subsampling factor; use 0 or 1 for no subsampling)
+ *      Return: narank (numa of rank difference), or null on error
+ *
+ *  Notes:
+ *      (1) This answers the question: if the pixel values in each
+ *          component are compared by absolute difference, for
+ *          any value of difference, what is the fraction of
+ *          pixel pairs that have a difference of this magnitude
+ *          or greater.  For a difference of 0, the fraction is 1.0.
+ *          In this sense, it is a mapping from pixel difference to
+ *          rank order of difference.
+ *      (2) The two images are aligned at the UL corner, and do not
+ *          need to be the same size.  If they are not the same size,
+ *          the comparison will be made over overlapping pixels.
+ *      (3) If there is a colormap, it is removed and the result
+ *          is either gray or RGB depending on the colormap.
+ *      (4) If RGB, pixel differences for each component are aggregated
+ *          into a single histogram.
+ */
+NUMA *
+pixCompareRankDifference(PIX     *pix1,
+                         PIX     *pix2,
+                         l_int32  factor)
+{
+l_int32     i;
+l_float32  *array1, *array2;
+NUMA       *nah, *nan, *nad;
+
+    PROCNAME("pixCompareRankDifference");
+
+    if (!pix1)
+        return (NUMA *)ERROR_PTR("pix1 not defined", procName, NULL);
+    if (!pix2)
+        return (NUMA *)ERROR_PTR("pix2 not defined", procName, NULL);
+
+    if ((nah = pixGetDifferenceHistogram(pix1, pix2, factor)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+
+    nan = numaNormalizeHistogram(nah, 1.0);
+    array1 = numaGetFArray(nan, L_NOCOPY);
+
+    nad = numaCreate(256);
+    numaSetCount(nad, 256);  /* all initialized to 0.0 */
+    array2 = numaGetFArray(nad, L_NOCOPY);
+
+        /* Do rank accumulation on normalized histo of diffs */
+    array2[0] = 1.0;
+    for (i = 1; i < 256; i++)
+        array2[i] = array2[i - 1] - array1[i - 1];
+
+    numaDestroy(&nah);
+    numaDestroy(&nan);
+    return nad;
+}
+
+
+/*!
+ *  pixTestForSimilarity()
+ *
+ *      Input:  pix1 (8 bpp gray or 32 bpp rgb, or colormapped)
+ *              pix2 (8 bpp gray or 32 bpp rgb, or colormapped)
+ *              factor (subsampling factor; use 0 or 1 for no subsampling)
+ *              mindiff (minimum pixel difference to be counted; > 0)
+ *              maxfract (maximum fraction of pixels allowed to have
+ *                        diff greater than or equal to mindiff)
+ *              maxave (maximum average difference of pixels allowed for
+ *                      pixels with diff greater than or equal to mindiff,
+ *                      after subtracting mindiff)
+ *              &similar (<return> 1 if similar, 0 otherwise)
+ *              printstats (use 1 to print normalized histogram to stderr)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This takes 2 pix that are the same size and determines using
+ *          3 input parameters if they are "similar".  The first parameter
+ *          @mindiff establishes a criterion of pixel-to-pixel similarity:
+ *          two pixels are not similar if their difference in value is
+ *          at least mindiff.  Then @maxfract and @maxave are thresholds
+ *          on the number and distribution of dissimilar pixels
+ *          allowed for the two pix to be similar.   If the pix are
+ *          to be similar, neither threshold can be exceeded.
+ *      (2) In setting the @maxfract and @maxave thresholds, you have
+ *          these options:
+ *            (a) Base the comparison only on @maxfract.  Then set
+ *                @maxave = 0.0 or 256.0.  (If 0, we always ignore it.)
+ *            (b) Base the comparison only on @maxave.  Then set
+ *                @maxfract = 1.0.
+ *            (c) Base the comparison on both thresholds.
+ *      (3) Example of values that can be expected at mindiff = 15 when
+ *          comparing lossless png encoding with jpeg encoding, q=75:
+ *             (smoothish bg)       fractdiff = 0.01, avediff = 2.5
+ *             (natural scene)      fractdiff = 0.13, avediff = 3.5
+ *          To identify these images as 'similar', select maxfract
+ *          and maxave to be upper bounds of what you expect.
+ *      (4) See pixGetDifferenceStats() for a discussion of why we subtract
+ *          mindiff from the computed average diff of the nonsimilar pixels
+ *          to get the 'avediff' returned by that function.
+ *      (5) If there is a colormap, it is removed and the result
+ *          is either gray or RGB depending on the colormap.
+ *      (6) If RGB, the maximum difference between pixel components is
+ *          saved in the histogram.
+ */
+l_int32
+pixTestForSimilarity(PIX       *pix1,
+                     PIX       *pix2,
+                     l_int32    factor,
+                     l_int32    mindiff,
+                     l_float32  maxfract,
+                     l_float32  maxave,
+                     l_int32   *psimilar,
+                     l_int32    printstats)
+{
+l_float32   fractdiff, avediff;
+
+    PROCNAME("pixTestForSimilarity");
+
+    if (!psimilar)
+        return ERROR_INT("&similar not defined", procName, 1);
+    *psimilar = 0;
+    if (!pix1)
+        return ERROR_INT("pix1 not defined", procName, 1);
+    if (!pix2)
+        return ERROR_INT("pix2 not defined", procName, 1);
+    if (pixSizesEqual(pix1, pix2) == 0)
+        return ERROR_INT("pix sizes not equal", procName, 1);
+    if (mindiff <= 0)
+        return ERROR_INT("mindiff must be > 0", procName, 1);
+
+    if (pixGetDifferenceStats(pix1, pix2, factor, mindiff,
+                              &fractdiff, &avediff, printstats))
+        return ERROR_INT("diff stats not found", procName, 1);
+
+    if (maxave <= 0.0) maxave = 256.0;
+    if (fractdiff <= maxfract && avediff <= maxave)
+        *psimilar = 1;
+    return 0;
+}
+
+
+/*!
+ *  pixGetDifferenceStats()
+ *
+ *      Input:  pix1 (8 bpp gray or 32 bpp rgb, or colormapped)
+ *              pix2 (8 bpp gray or 32 bpp rgb, or colormapped)
+ *              factor (subsampling factor; use 0 or 1 for no subsampling)
+ *              mindiff (minimum pixel difference to be counted; > 0)
+ *              &fractdiff (<return> fraction of pixels with diff greater
+ *                          than or equal to mindiff)
+ *              &avediff (<return> average difference of pixels with diff
+ *                        greater than or equal to mindiff, less mindiff)
+ *              printstats (use 1 to print normalized histogram to stderr)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This takes a threshold @mindiff and describes the difference
+ *          between two images in terms of two numbers:
+ *            (a) the fraction of pixels, @fractdiff, whose difference
+ *                equals or exceeds the threshold @mindiff, and
+ *            (b) the average value @avediff of the difference in pixel value
+ *                for the pixels in the set given by (a), after you subtract
+ *                @mindiff.  The reason for subtracting @mindiff is that
+ *                you then get a useful measure for the rate of falloff
+ *                of the distribution for larger differences.  For example,
+ *                if @mindiff = 10 and you find that @avediff = 2.5, it
+ *                says that of the pixels with diff > 10, the average of
+ *                their diffs is just mindiff + 2.5 = 12.5.  This is a
+ *                fast falloff in the histogram with increasing difference.
+ *      (2) The two images are aligned at the UL corner, and do not
+ *          need to be the same size.  If they are not the same size,
+ *          the comparison will be made over overlapping pixels.
+ *      (3) If there is a colormap, it is removed and the result
+ *          is either gray or RGB depending on the colormap.
+ *      (4) If RGB, the maximum difference between pixel components is
+ *          saved in the histogram.
+ */
+l_int32
+pixGetDifferenceStats(PIX        *pix1,
+                      PIX        *pix2,
+                      l_int32     factor,
+                      l_int32     mindiff,
+                      l_float32  *pfractdiff,
+                      l_float32  *pavediff,
+                      l_int32     printstats)
+{
+l_int32     i, first, last, diff;
+l_float32   fract, ave;
+l_float32  *array;
+NUMA       *nah, *nan, *nac;
+
+    PROCNAME("pixGetDifferenceStats");
+
+    if (pfractdiff) *pfractdiff = 0.0;
+    if (pavediff) *pavediff = 0.0;
+    if (!pfractdiff)
+        return ERROR_INT("&fractdiff not defined", procName, 1);
+    if (!pavediff)
+        return ERROR_INT("&avediff not defined", procName, 1);
+    if (!pix1)
+        return ERROR_INT("pix1 not defined", procName, 1);
+    if (!pix2)
+        return ERROR_INT("pix2 not defined", procName, 1);
+    if (mindiff <= 0)
+        return ERROR_INT("mindiff must be > 0", procName, 1);
+
+    if ((nah = pixGetDifferenceHistogram(pix1, pix2, factor)) == NULL)
+        return ERROR_INT("na not made", procName, 1);
+
+    if ((nan = numaNormalizeHistogram(nah, 1.0)) == NULL) {
+        numaDestroy(&nah);
+        return ERROR_INT("nan not made", procName, 1);
+    }
+    array = numaGetFArray(nan, L_NOCOPY);
+
+    if (printstats) {
+        numaGetNonzeroRange(nan, 0.0, &first, &last);
+        nac = numaClipToInterval(nan, first, last);
+        fprintf(stderr, "\nNonzero values in normalized histogram:");
+        numaWriteStream(stderr, nac);
+        numaDestroy(&nac);
+        fprintf(stderr, " Mindiff      fractdiff      avediff\n");
+        fprintf(stderr, " -----------------------------------\n");
+        for (diff = 1; diff < L_MIN(2 * mindiff, last); diff++) {
+            fract = 0.0;
+            ave = 0.0;
+            for (i = diff; i <= last; i++) {
+                fract += array[i];
+                ave += (l_float32)i * array[i];
+            }
+            ave = (fract == 0.0) ? 0.0 : ave / fract;
+            ave -= diff;
+            fprintf(stderr, "%5d         %7.4f        %7.4f\n",
+                    diff, fract, ave);
+        }
+        fprintf(stderr, " -----------------------------------\n");
+    }
+
+    fract = 0.0;
+    ave = 0.0;
+    for (i = mindiff; i < 256; i++) {
+      fract += array[i];
+      ave += (l_float32)i * array[i];
+    }
+    ave = (fract == 0.0) ? 0.0 : ave / fract;
+    ave -= mindiff;
+
+    *pfractdiff = fract;
+    *pavediff = ave;
+
+    numaDestroy(&nah);
+    numaDestroy(&nan);
+    return 0;
+}
+
+
+/*!
+ *  pixGetDifferenceHistogram()
+ *
+ *      Input:  pix1 (8 bpp gray or 32 bpp rgb, or colormapped)
+ *              pix2 (8 bpp gray or 32 bpp rgb, or colormapped)
+ *              factor (subsampling factor; use 0 or 1 for no subsampling)
+ *      Return: na (Numa of histogram of differences), or null on error
+ *
+ *  Notes:
+ *      (1) The two images are aligned at the UL corner, and do not
+ *          need to be the same size.  If they are not the same size,
+ *          the comparison will be made over overlapping pixels.
+ *      (2) If there is a colormap, it is removed and the result
+ *          is either gray or RGB depending on the colormap.
+ *      (3) If RGB, the maximum difference between pixel components is
+ *          saved in the histogram.
+ */
+NUMA *
+pixGetDifferenceHistogram(PIX     *pix1,
+                          PIX     *pix2,
+                          l_int32  factor)
+{
+l_int32     w1, h1, d1, w2, h2, d2, w, h, wpl1, wpl2;
+l_int32     i, j, val, val1, val2;
+l_int32     rval1, rval2, gval1, gval2, bval1, bval2;
+l_int32     rdiff, gdiff, bdiff, maxdiff;
+l_uint32   *data1, *data2, *line1, *line2;
+l_float32  *array;
+NUMA       *na;
+PIX        *pixt1, *pixt2;
+
+    PROCNAME("pixGetDifferenceHistogram");
+
+    if (!pix1)
+        return (NUMA *)ERROR_PTR("pix1 not defined", procName, NULL);
+    if (!pix2)
+        return (NUMA *)ERROR_PTR("pix2 not defined", procName, NULL);
+    d1 = pixGetDepth(pix1);
+    d2 = pixGetDepth(pix2);
+    if (d1 == 16 || d2 == 16)
+        return (NUMA *)ERROR_PTR("d == 16 not supported", procName, NULL);
+    if (d1 < 8 && !pixGetColormap(pix1))
+        return (NUMA *)ERROR_PTR("pix1 depth < 8 bpp and not cmapped",
+                                 procName, NULL);
+    if (d2 < 8 && !pixGetColormap(pix2))
+        return (NUMA *)ERROR_PTR("pix2 depth < 8 bpp and not cmapped",
+                                 procName, NULL);
+    pixt1 = pixRemoveColormap(pix1, REMOVE_CMAP_BASED_ON_SRC);
+    pixt2 = pixRemoveColormap(pix2, REMOVE_CMAP_BASED_ON_SRC);
+    pixGetDimensions(pixt1, &w1, &h1, &d1);
+    pixGetDimensions(pixt2, &w2, &h2, &d2);
+    if (d1 != d2) {
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt2);
+        return (NUMA *)ERROR_PTR("pix depths not equal", procName, NULL);
+    }
+    if (factor < 1) factor = 1;
+
+    na = numaCreate(256);
+    numaSetCount(na, 256);  /* all initialized to 0.0 */
+    array = numaGetFArray(na, L_NOCOPY);
+    w = L_MIN(w1, w2);
+    h = L_MIN(h1, h2);
+    data1 = pixGetData(pixt1);
+    data2 = pixGetData(pixt2);
+    wpl1 = pixGetWpl(pixt1);
+    wpl2 = pixGetWpl(pixt2);
+    if (d1 == 8) {
+        for (i = 0; i < h; i += factor) {
+            line1 = data1 + i * wpl1;
+            line2 = data2 + i * wpl2;
+            for (j = 0; j < w; j += factor) {
+                val1 = GET_DATA_BYTE(line1, j);
+                val2 = GET_DATA_BYTE(line2, j);
+                val = L_ABS(val1 - val2);
+                array[val]++;
+            }
+        }
+    } else {  /* d1 == 32 */
+        for (i = 0; i < h; i += factor) {
+            line1 = data1 + i * wpl1;
+            line2 = data2 + i * wpl2;
+            for (j = 0; j < w; j += factor) {
+                extractRGBValues(line1[j], &rval1, &gval1, &bval1);
+                extractRGBValues(line2[j], &rval2, &gval2, &bval2);
+                rdiff = L_ABS(rval1 - rval2);
+                gdiff = L_ABS(gval1 - gval2);
+                bdiff = L_ABS(bval1 - bval2);
+                maxdiff = L_MAX(rdiff, gdiff);
+                maxdiff = L_MAX(maxdiff, bdiff);
+                array[maxdiff]++;
+            }
+        }
+    }
+
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    return na;
+}
+
+
+/*!
+ *  pixGetPerceptualDiff()
+ *
+ *      Input:  pix1 (8 bpp gray or 32 bpp rgb, or colormapped)
+ *              pix2 (8 bpp gray or 32 bpp rgb, or colormapped)
+ *              sampling (subsampling factor; use 0 or 1 for no subsampling)
+ *              dilation (size of grayscale or color Sel; odd)
+ *              mindiff (minimum pixel difference to be counted; > 0)
+ *              &fract (<return> fraction of pixels with diff greater than
+ *                      mindiff)
+ *              &pixdiff1 (<optional return> showing difference (gray or color))
+ *              &pixdiff2 (<optional return> showing pixels of sufficient diff)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This takes 2 pix and determines, using 2 input parameters:
+ *           * @dilation specifies the amount of grayscale or color
+ *             dilation to apply to the images, to compensate for
+ *             a small amount of misregistration.  A typical number might
+ *             be 5, which uses a 5x5 Sel.  Grayscale dilation expands
+ *             lighter pixels into darker pixel regions.
+ *           * @mindiff determines the threshold on the difference in
+ *             pixel values to be counted -- two pixels are not similar
+ *             if their difference in value is at least @mindiff.  For
+ *             color pixels, we use the maximum component difference.
+ *      (2) The pixelwise comparison is always done with the UL corners
+ *          aligned.  The sizes of pix1 and pix2 need not be the same,
+ *          although in practice it can be useful to scale to the same size.
+ *      (3) If there is a colormap, it is removed and the result
+ *          is either gray or RGB depending on the colormap.
+ *      (4) Two optional diff images can be retrieved (typ. for debugging):
+ *           pixdiff1: the gray or color difference
+ *           pixdiff2: thresholded to 1 bpp for pixels exceeding @mindiff
+ *      (5) The returned value of fract can be compared to some threshold,
+ *          which is application dependent.
+ *      (6) This method is in analogy to the two-sided hausdorff transform,
+ *          except here it is for d > 1.  For d == 1 (see pixRankHaustest()),
+ *          we verify that when one pix1 is dilated, it covers at least a
+ *          given fraction of the pixels in pix2, and v.v.; in that
+ *          case, the two pix are sufficiently similar.  Here, we
+ *          do an analogous thing: subtract the dilated pix1 from pix2 to
+ *          get a 1-sided hausdorff-like transform.  Then do it the
+ *          other way.  Take the component-wise max of the two results,
+ *          and threshold to get the fraction of pixels with a difference
+ *          below the threshold.
+ */
+l_int32
+pixGetPerceptualDiff(PIX        *pixs1,
+                     PIX        *pixs2,
+                     l_int32     sampling,
+                     l_int32     dilation,
+                     l_int32     mindiff,
+                     l_float32  *pfract,
+                     PIX       **ppixdiff1,
+                     PIX       **ppixdiff2)
+{
+l_int32  d1, d2, w, h, count;
+PIX     *pix1, *pix2, *pix3, *pix4, *pix5, *pix6, *pix7, *pix8, *pix9;
+PIX     *pix10, *pix11;
+
+    PROCNAME("pixGetPerceptualDiff");
+
+    if (ppixdiff1) *ppixdiff1 = NULL;
+    if (ppixdiff2) *ppixdiff2 = NULL;
+    if (!pfract)
+        return ERROR_INT("&fract not defined", procName, 1);
+    *pfract = 1.0;  /* init to completely different */
+    if ((dilation & 1) == 0)
+        return ERROR_INT("dilation must be odd", procName, 1);
+    if (!pixs1)
+        return ERROR_INT("pixs1 not defined", procName, 1);
+    if (!pixs2)
+        return ERROR_INT("pixs2 not defined", procName, 1);
+    d1 = pixGetDepth(pixs1);
+    d2 = pixGetDepth(pixs2);
+    if (!pixGetColormap(pixs1) && d1 < 8)
+        return ERROR_INT("pixs1 not cmapped or >=8 bpp", procName, 1);
+    if (!pixGetColormap(pixs2) && d2 < 8)
+        return ERROR_INT("pixs2 not cmapped or >=8 bpp", procName, 1);
+
+        /* Integer downsample if requested */
+    if (sampling > 1) {
+        pix1 = pixScaleByIntSampling(pixs1, sampling);
+        pix2 = pixScaleByIntSampling(pixs2, sampling);
+    } else {
+        pix1 = pixClone(pixs1);
+        pix2 = pixClone(pixs2);
+    }
+
+        /* Remove colormaps */
+    if (pixGetColormap(pix1)) {
+        pix3 = pixRemoveColormap(pix1, REMOVE_CMAP_BASED_ON_SRC);
+        d1 = pixGetDepth(pix3);
+    } else {
+        pix3 = pixClone(pix1);
+    }
+    if (pixGetColormap(pix2)) {
+        pix4 = pixRemoveColormap(pix2, REMOVE_CMAP_BASED_ON_SRC);
+        d2 = pixGetDepth(pix4);
+    } else {
+        pix4 = pixClone(pix2);
+    }
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    if (d1 != d2) {
+        pixDestroy(&pix3);
+        pixDestroy(&pix4);
+        return ERROR_INT("pix3 and pix4 depths not equal", procName, 1);
+    }
+
+        /* In each direction, do a small dilation and subtract the dilated
+         * image from the other image to get a one-sided difference.
+         * Then take the max of the differences for each direction
+         * and clipping each component to 255 if necessary.  Note that
+         * for RGB images, the dilations and max selection are done
+         * component-wise, and the conversion to grayscale also uses the
+         * maximum component.  The resulting grayscale images are
+         * thresholded using @mindiff. */
+    if (d1 == 8) {
+        pix5 = pixDilateGray(pix3, dilation, dilation);
+        pixCompareGray(pix4, pix5, L_COMPARE_SUBTRACT, 0, NULL, NULL, NULL,
+                       &pix7);
+        pix6 = pixDilateGray(pix4, dilation, dilation);
+        pixCompareGray(pix3, pix6, L_COMPARE_SUBTRACT, 0, NULL, NULL, NULL,
+                       &pix8);
+        pix9 = pixMinOrMax(NULL, pix7, pix8, L_CHOOSE_MAX);
+        pix10 = pixThresholdToBinary(pix9, mindiff);
+        pixInvert(pix10, pix10);
+        pixCountPixels(pix10, &count, NULL);
+        pixGetDimensions(pix10, &w, &h, NULL);
+        *pfract = (l_float32)count / (l_float32)(w * h);
+        pixDestroy(&pix5);
+        pixDestroy(&pix6);
+        pixDestroy(&pix7);
+        pixDestroy(&pix8);
+        if (ppixdiff1)
+            *ppixdiff1 = pix9;
+        else
+            pixDestroy(&pix9);
+        if (ppixdiff2)
+            *ppixdiff2 = pix10;
+        else
+            pixDestroy(&pix10);
+    } else {  /* d1 == 32 */
+        pix5 = pixColorMorph(pix3, L_MORPH_DILATE, dilation, dilation);
+        pixCompareRGB(pix4, pix5, L_COMPARE_SUBTRACT, 0, NULL, NULL, NULL,
+                       &pix7);
+        pix6 = pixColorMorph(pix4, L_MORPH_DILATE, dilation, dilation);
+        pixCompareRGB(pix3, pix6, L_COMPARE_SUBTRACT, 0, NULL, NULL, NULL,
+                      &pix8);
+        pix9 = pixMinOrMax(NULL, pix7, pix8, L_CHOOSE_MAX);
+        pix10 = pixConvertRGBToGrayMinMax(pix9, L_CHOOSE_MAX);
+        pix11 = pixThresholdToBinary(pix10, mindiff);
+        pixInvert(pix11, pix11);
+        pixCountPixels(pix11, &count, NULL);
+        pixGetDimensions(pix11, &w, &h, NULL);
+        *pfract = (l_float32)count / (l_float32)(w * h);
+        pixDestroy(&pix5);
+        pixDestroy(&pix6);
+        pixDestroy(&pix7);
+        pixDestroy(&pix8);
+        pixDestroy(&pix10);
+        if (ppixdiff1)
+            *ppixdiff1 = pix9;
+        else
+            pixDestroy(&pix9);
+        if (ppixdiff2)
+            *ppixdiff2 = pix11;
+        else
+            pixDestroy(&pix11);
+
+    }
+    pixDestroy(&pix3);
+    pixDestroy(&pix4);
+    return 0;
+}
+
+
+/*!
+ *  pixGetPSNR()
+ *
+ *      Input:  pix1, pix2 (8 or 32 bpp; no colormap)
+ *              factor (sampling factor; >= 1)
+ *              &psnr (<return> power signal/noise ratio difference)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This computes the power S/N ratio, in dB, for the difference
+ *          between two images.  By convention, the power S/N
+ *          for a grayscale image is ('log' == log base 10,
+ *          and 'ln == log base e):
+ *            PSNR = 10 * log((255/MSE)^2)
+ *                 = 4.3429 * ln((255/MSE)^2)
+ *                 = -4.3429 * ln((MSE/255)^2)
+ *          where MSE is the mean squared error.
+ *          Here are some examples:
+ *             MSE             PSNR
+ *             ---             ----
+ *             10              28.1
+ *             3               38.6
+ *             1               48.1
+ *             0.1             68.1
+ *      (2) If pix1 and pix2 have the same pixel values, the MSE = 0.0
+ *          and the PSNR is infinity.  For that case, this returns
+ *          PSNR = 1000, which corresponds to the very small MSE of
+ *          about 10^(-48).
+ */
+l_int32
+pixGetPSNR(PIX        *pix1,
+           PIX        *pix2,
+           l_int32     factor,
+           l_float32  *ppsnr)
+{
+l_int32    same, i, j, w, h, d, wpl1, wpl2, v1, v2, r1, g1, b1, r2, g2, b2;
+l_uint32  *data1, *data2, *line1, *line2;
+l_float32  mse;  /* mean squared error */
+
+    PROCNAME("pixGetPSNR");
+
+    if (!ppsnr)
+        return ERROR_INT("&psnr not defined", procName, 1);
+    *ppsnr = 0.0;
+    if (!pix1 || !pix2)
+        return ERROR_INT("empty input pix", procName, 1);
+    if (!pixSizesEqual(pix1, pix2))
+        return ERROR_INT("pix sizes unequal", procName, 1);
+    if (pixGetColormap(pix1))
+        return ERROR_INT("pix1 has colormap", procName, 1);
+    if (pixGetColormap(pix2))
+        return ERROR_INT("pix2 has colormap", procName, 1);
+    pixGetDimensions(pix1, &w, &h, &d);
+    if (d != 8 && d != 32)
+        return ERROR_INT("pix not 8 or 32 bpp", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("invalid sampling factor", procName, 1);
+
+    pixEqual(pix1, pix2, &same);
+    if (same) {
+        *ppsnr = 1000.0;  /* crazy big exponent */
+        return 0;
+    }
+
+    data1 = pixGetData(pix1);
+    data2 = pixGetData(pix2);
+    wpl1 = pixGetWpl(pix1);
+    wpl2 = pixGetWpl(pix2);
+    mse = 0.0;
+    if (d == 8) {
+        for (i = 0; i < h; i += factor) {
+            line1 = data1 + i * wpl1;
+            line2 = data2 + i * wpl2;
+            for (j = 0; j < w; j += factor) {
+                v1 = GET_DATA_BYTE(line1, j);
+                v2 = GET_DATA_BYTE(line2, j);
+                mse += (v1 - v2) * (v1 - v2);
+            }
+        }
+    } else {  /* d == 32 */
+        for (i = 0; i < h; i += factor) {
+            line1 = data1 + i * wpl1;
+            line2 = data2 + i * wpl2;
+            for (j = 0; j < w; j += factor) {
+                extractRGBValues(line1[j], &r1, &g1, &b1);
+                extractRGBValues(line2[j], &r2, &g2, &b2);
+                mse += ((r1 - r2) * (r1 - r2) +
+                        (g1 - g2) * (g1 - g2) +
+                        (b1 - b2) * (b1 - b2)) / 3.0;
+            }
+        }
+    }
+    mse = mse / (w * h);
+
+    *ppsnr = -4.3429448 * log(mse / (255 * 255));
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *             Comparison of photo regions by histogram             *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixaComparePhotoRegionsByHisto()
+ *
+ *      Input:  pixa (any depth; colormap OK)
+ *              minratio (requiring sizes be compatible; < 1.0)
+ *              factor (subsampling; >= 1)
+ *              textthresh (threshold for text/photo; use 0 for default)
+ *              nx, ny (number of subregions to use for histograms; e.g. 3x3)
+ *              simthresh (threshold for similarity; use 0 for default)
+ *              &nai (<return> array giving similarity class indices)
+ *              &scores (<optional return> score matrix as 1-D array of
+ *                       size N^2)
+ *              &pixd (<optional return> pix of similarity classes)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function takes a pixa of cropped photo images and
+ *          compares each one to the others for similarity.
+ *          Each image is first tested to see if it is a photo that can
+ *          be compared by tiled histograms.  If so, it is padded to put
+ *          the centroid in the center of the image, and the histograms
+ *          are generated.  The final step of comparing each histogram
+ *          with all the others is very fast.
+ *      (2) An initial filter gives @score = 0 if the ratio of widths
+ *          and heights (smallest / largest) does not exceed a
+ *          threshold @minratio.  If set at 1.0, both images must be
+ *          exactly the same size.  A typical value for @minratio is 0.9.
+ *      (3) The comparison score between two images is a value in [0.0 .. 1.0].
+ *          If the comparison score >= @simthresh, the images are placed in
+ *          the same similarity class.  Default value for @simthresh is 0.25.
+ *      (4) An array @nai of similarity class indices for pix in the
+ *          input pixa is returned.
+ *      (5) There are two debugging options:
+ *          * An optional 2D matrix of scores is returned as a 1D array.
+ *            A visualization of this is written to a temp file.
+ *          * An optional pix showing the similarity classes can be
+ *            returned.  Text in each input pix is reproduced.
+ *      (6) See the notes in pixComparePhotoRegionsByHisto() for details
+ *          on the implementation.
+ */
+l_int32
+pixaComparePhotoRegionsByHisto(PIXA        *pixa,
+                               l_float32    minratio,
+                               l_float32    textthresh,
+                               l_int32      factor,
+                               l_int32      nx,
+                               l_int32      ny,
+                               l_float32    simthresh,
+                               NUMA       **pnai,
+                               l_float32  **pscores,
+                               PIX        **ppixd)
+{
+char       *text;
+l_int32     i, j, n, w, h, w1, h1, w2, h2, ival, index;
+l_float32   score;
+l_float32  *scores;
+NUMA       *nai, *naw, *nah;
+NUMAA      *naa;
+NUMAA     **n3a;  /* array of naa */
+PIX        *pix;
+
+    PROCNAME("pixaComparePhotoRegionsByHisto");
+
+    lept_mkdir("lept/comp");
+
+    if (pscores) *pscores = NULL;
+    if (ppixd) *ppixd = NULL;
+    if (!pnai)
+        return ERROR_INT("&na not defined", procName, 1);
+    *pnai = NULL;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (minratio < 0.0 || minratio > 1.0)
+        return ERROR_INT("minratio not in [0.0 ... 1.0]", procName, 1);
+    if (textthresh <= 0.0) textthresh = 1.3;
+    if (factor < 1)
+        return ERROR_INT("subsampling factor must be >= 1", procName, 1);
+    if (nx < 1 || ny < 1)
+        return ERROR_INT("nx and ny must both be > 0", procName, 1);
+    if (simthresh <= 0.0) simthresh = 0.25;
+    if (simthresh > 1.0)
+        return ERROR_INT("simthresh invalid; should be near 0.25", procName, 1);
+
+        /* Prepare the histograms */
+    n = pixaGetCount(pixa);
+    n3a = (NUMAA **)LEPT_CALLOC(n, sizeof(NUMAA *));
+    naw = numaCreate(0);
+    nah = numaCreate(0);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        text = pixGetText(pix);
+        pixSetResolution(pix, 150, 150);
+        pixGenPhotoHistos(pix, NULL, factor, textthresh, nx, ny,
+                          &naa, &w, &h, 1);
+        n3a[i] = naa;
+        numaAddNumber(naw, w);
+        numaAddNumber(nah, h);
+        if (naa)
+            fprintf(stderr, "Image %s is photo\n", text);
+        else
+            fprintf(stderr, "Image %s is NOT photo\n", text);
+        pixDestroy(&pix);
+    }
+
+        /* Do the comparisons */
+    nai = numaMakeConstant(-1, n);  /* index */
+    scores = (l_float32 *)LEPT_CALLOC(n * n, sizeof(l_float32));
+    for (i = 0, index = 0; i < n; i++) {
+        numaGetIValue(nai, i, &ival);
+        if (ival != -1)  /* already set */
+            continue;
+        numaSetValue(nai, i, index);
+        if (n3a[i] == NULL) {  /* not a photo */
+            index++;
+            continue;
+        }
+        numaGetIValue(naw, i, &w1);
+        numaGetIValue(nah, i, &h1);
+        scores[n * i + i] = 1.0;
+        for (j = i + 1; j < n; j++) {
+            numaGetIValue(nai, j, &ival);
+            if (ival != -1)  /* already set */
+                continue;
+            if (n3a[j] == NULL)  /* not a photo */
+                continue;
+            numaGetIValue(naw, j, &w2);
+            numaGetIValue(nah, j, &h2);
+            compareTilesByHisto(n3a[i], n3a[j], minratio, w1, h1, w2, h2,
+                                &score, NULL);
+            scores[n * i + j] = score;
+            scores[n * j + i] = score;
+            fprintf(stderr, "score = %5.3f\n", score);  /* comment this out */
+            if (score > simthresh) {
+                numaSetValue(nai, j, index);
+                fprintf(stderr, "Setting %d similar to %d\n", j, i);
+            }
+        }
+        index++;
+    }
+    *pnai = nai;
+
+        /* Debug: optionally save and display the score array */
+    if (pscores) {
+        l_int32    wpl, fact;
+        l_uint32  *line, *data;
+        PIX       *pix2, *pix3;
+        pix2 = pixCreate(n, n, 8);
+        data = pixGetData(pix2);
+        wpl = pixGetWpl(pix2);
+        for (i = 0; i < n; i++) {
+            line = data + i * wpl;
+            for (j = 0; j < n; j++) {
+                SET_DATA_BYTE(line, j,
+                              L_MIN(255, 4.0 * 255 * scores[n * i + j]));
+            }
+        }
+        fact = L_MAX(2, 1000 / n);
+        pix3 = pixExpandReplicate(pix2, fact);
+        fprintf(stderr, "Writing to /tmp/lept/comp/scorearray.png\n");
+        pixWrite("/tmp/lept/comp/scorearray.png", pix3, IFF_PNG);
+        pixDestroy(&pix2);
+        pixDestroy(&pix3);
+        *pscores = scores;
+    } else {
+        LEPT_FREE(scores);
+    }
+
+        /* Debug: optionally display and save the image comparisons.
+         * Image classes are displayed by column, and similar images
+         * are displayed in the same column. */
+    if (ppixd)
+        *ppixd = pixaDisplayTiledByIndex(pixa, nai, 200, 20, 2, 6, 0x0000ff00);
+
+    numaDestroy(&naw);
+    numaDestroy(&nah);
+    for (i = 0; i < n; i++)
+        numaaDestroy(&n3a[i]);
+    LEPT_FREE(n3a);
+    return 0;
+}
+
+
+/*!
+ *  pixComparePhotoRegionsByHisto()
+ *
+ *      Input:  pix1, pix2 (any depth; colormap OK)
+ *              box1, box2 (<optional> photo regions from each; can be null)
+ *              minratio (requiring sizes be compatible; < 1.0)
+ *              factor (subsampling; >= 1)
+ *              nx, ny (number of subregions to use for histograms; e.g. 3x3)
+ *              &score (<return> similarity score of histograms)
+ *              debugflag (1 for debug output; 0 for no debugging)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function compares two grayscale photo regions.  If a
+ *          box is given, the region is clipped; otherwise assume
+ *          the entire images are photo regions.  This is done with a
+ *          set of (nx * ny) spatially aligned histograms, which are
+ *          aligned using the centroid of the inverse image.
+ *      (2) An initial filter gives @score = 0 if the ratio of widths
+ *          and heights (smallest / largest) does not exceed a
+ *          threshold @minratio.  This must be between 0.5 and 1.0.
+ *          If set at 1.0, both images must be exactly the same size.
+ *          A typical value for @minratio is 0.9.
+ *      (3) Because this function should not be used on text or
+ *          line graphics, which can give false positive results
+ *          (i.e., high scores for different images), filter the images
+ *          using pixGenPhotoHistos(), which returns tiled histograms
+ *          only if an image is not text and comparison is expected
+ *          to work with histograms.  If either image fails the test,
+ *          the comparison returns a score of 0.0.
+ *      (4) The white value counts in the histograms are removed; they
+ *          are typically pixels that were padded to achieve alignment.
+ *      (5) For an efficient representation of the histogram, normalize
+ *          using a multiplicative factor so that the number in the
+ *          maximum bucket is 255.  It then takes 256 bytes to store.
+ *      (6) When comparing the histograms of two regions, use the
+ *          Earth Mover distance (EMD), with the histograms normalized
+ *          so that the sum over bins is the same.  Further normalize
+ *          by dividing by 255, so that the result is in [0.0 ... 1.0].
+ *      (7) Get a similarity score S = 1.0 - k * D, where
+ *            k is a constant, say in the range 5-10
+ *            D = normalized EMD
+ *          and for multiple tiles, take the Min(S) to be the final score.
+ *          Using aligned tiles gives protection against accidental
+ *          similarity of the overall grayscale histograms.
+ *          A small number of aligned tiles works well.
+ *      (8) With debug on, you get a pdf that shows, for each tile,
+ *          the images, histograms and score.
+ */
+l_int32
+pixComparePhotoRegionsByHisto(PIX        *pix1,
+                              PIX        *pix2,
+                              BOX        *box1,
+                              BOX        *box2,
+                              l_float32   minratio,
+                              l_int32     factor,
+                              l_int32     nx,
+                              l_int32     ny,
+                              l_float32  *pscore,
+                              l_int32     debugflag)
+{
+l_int32    w1, h1, w2, h2, w1c, h1c, w2c, h2c;
+l_float32  wratio, hratio;
+BOX       *box3, *box4;
+NUMAA     *naa1, *naa2;
+PIX       *pix3, *pix4, *pix5, *pix6, *pix7, *pix8;
+PIXA      *pixa;
+
+    PROCNAME("pixComparePhotoRegionsByHisto");
+
+    if (!pscore)
+        return ERROR_INT("&score not defined", procName, 1);
+    *pscore = 0.0;
+    if (!pix1 || !pix2)
+        return ERROR_INT("pix1 and pix2 not both defined", procName, 1);
+    if (minratio < 0.5 || minratio > 1.0)
+        return ERROR_INT("minratio not in [0.5 ... 1.0]", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("subsampling factor must be >= 1", procName, 1);
+    if (nx < 1 || ny < 1)
+        return ERROR_INT("nx and ny must both be > 0", procName, 1);
+
+    pixa = NULL;
+    if (debugflag) {
+        pixa = pixaCreate(0);
+        lept_mkdir("lept/comp");
+    }
+
+        /* Initial filter by size */
+    if (box1)
+        boxGetGeometry(box1, NULL, NULL, &w1, &h1);
+    else
+        pixGetDimensions(pix1, &w1, &h1, NULL);
+    if (box2)
+        boxGetGeometry(box2, NULL, NULL, &w2, &h2);
+    else
+        pixGetDimensions(pix1, &w2, &h2, NULL);
+    wratio = (w1 < w2) ? (l_float32)w1 / (l_float32)w2 :
+             (l_float32)w2 / (l_float32)w1;
+    hratio = (h1 < h2) ? (l_float32)h1 / (l_float32)h2 :
+             (l_float32)h2 / (l_float32)h1;
+    if (wratio < minratio || hratio < minratio)
+        return 0;
+
+        /* Initial crop, if necessary, and make histos */
+    if (box1)
+        pix3 = pixClipRectangle(pix1, box1, NULL);
+    else
+        pix3 = pixClone(pix1);
+    pixGenPhotoHistos(pix3, NULL, factor, 0, nx, ny,
+                      &naa1, &w1c, &h1c, debugflag);
+    pixDestroy(&pix3);
+    if (!naa1) return 0;
+    if (box2)
+        pix4 = pixClipRectangle(pix2, box2, NULL);
+    else
+        pix4 = pixClone(pix2);
+    pixGenPhotoHistos(pix4, NULL, factor, 0, nx, ny,
+                      &naa2, &w2c, &h2c, debugflag);
+    pixDestroy(&pix4);
+    if (!naa2) return 0;
+
+        /* Compare histograms */
+    pixa = (debugflag) ? pixaCreate(0) : NULL;
+    compareTilesByHisto(naa1, naa2, minratio, w1c, h1c, w2c, h2c, pscore, pixa);
+    pixaDestroy(&pixa);
+    return 0;
+}
+
+
+/*!
+ *  pixGenPhotoHistos()
+ *
+ *      Input:  pix (depth > 1 bpp; colormap OK)
+ *              box (<optional> region to be selected; can be null)
+ *              factor (subsampling; >= 1)
+ *              thresh (threshold for photo/text; use 0 for default)
+ *              nx, ny (number of subregions to use for histograms; e.g. 3x3)
+ *              &naa (<return> nx * ny 256-entry gray histograms)
+ *              &w (<return> width of image used to make histograms)
+ *              &h (<return> height of image used to make histograms)
+ *              debugflag (1 for debug output; 0 for no debugging)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This crops and converts to 8 bpp if necessary.  It adds a
+ *          minimal white boundary such that the centroid of the
+ *          photo-inverted image is in the center. This allows
+ *          automatic alignment with histograms of other image regions.
+ *      (2) The white value in the histogram is removed, because of
+ *          the padding.
+ *      (3) Use 0 for conservative default (1.3) for thresh.
+ *      (4) For an efficient representation of the histogram, normalize
+ *          using a multiplicative factor so that the number in the
+ *          maximum bucket is 255.  It then takes 256 bytes to store.
+ *      (5) With debug on, you get a pdf that shows, for each tile,
+ *          the images and histograms.
+ */
+l_int32
+pixGenPhotoHistos(PIX        *pixs,
+                  BOX        *box,
+                  l_int32     factor,
+                  l_float32   thresh,
+                  l_int32     nx,
+                  l_int32     ny,
+                  NUMAA     **pnaa,
+                  l_int32    *pw,
+                  l_int32    *ph,
+                  l_int32     debugflag)
+{
+NUMAA  *naa;
+PIX    *pix1, *pix2, *pix3, *pixm;
+PIXA   *pixa;
+
+    PROCNAME("pixGenPhotoHistos");
+
+    if (pnaa) *pnaa = NULL;
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (!pnaa)
+        return ERROR_INT("&naa not defined", procName, 1);
+    if (!pw || !ph)
+        return ERROR_INT("&w and &h not both defined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) == 1)
+        return ERROR_INT("pixs not defined or 1 bpp", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("subsampling factor must be >= 1", procName, 1);
+    if (nx < 1 || ny < 1)
+        return ERROR_INT("nx and ny must both be > 0", procName, 1);
+    if (thresh <= 0.0) thresh = 1.3;  /* default */
+
+    pixa = NULL;
+    if (debugflag) {
+        pixa = pixaCreate(0);
+        lept_mkdir("lept/comp");
+    }
+
+        /* Initial crop, if necessary */
+    if (box)
+        pix1 = pixClipRectangle(pixs, box, NULL);
+    else
+        pix1 = pixClone(pixs);
+
+        /* Convert to 8 bpp and pad to center the centroid */
+    pix2 = pixConvertTo8(pix1, FALSE);
+    pix3 = pixPadToCenterCentroid(pix2, factor);
+
+        /* Set to 255 all pixels above 230.  Do this so that light gray
+         * pixels do not enter into the comparison. */
+    pixm = pixThresholdToBinary(pix3, 230);
+    pixInvert(pixm, pixm);
+    pixSetMaskedGeneral(pix3, pixm, 255, 0, 0);
+
+    if (debugflag) {
+        PIX   *pix4, *pix5, *pix6, *pix7, *pix8;
+        PIXA  *pixa2;
+        pix4 = pixConvertTo32(pix2);
+        pix5 = pixConvertTo32(pix3);
+        pix6 = pixScaleToSize(pix4, 400, 0);
+        pix7 = pixScaleToSize(pix5, 400, 0);
+        pixa2 = pixaCreate(2);
+        pixaAddPix(pixa2, pix6, L_INSERT);
+        pixaAddPix(pixa2, pix7, L_INSERT);
+        pix8 = pixaDisplayTiledInRows(pixa2, 32, 1000, 1.0, 0, 50, 3);
+        pixaAddPix(pixa, pix8, L_INSERT);
+        pixDestroy(&pix4);
+        pixDestroy(&pix5);
+        pixaDestroy(&pixa2);
+    }
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    pixDestroy(&pixm);
+
+        /* Test if this is a photoimage */
+    pixDecideIfPhotoImage(pix3, factor, nx, ny, thresh, &naa, pixa);
+    if (naa) {
+        *pnaa = naa;
+        *pw = pixGetWidth(pix3);
+        *ph = pixGetHeight(pix3);
+    }
+
+    if (pixa) {
+        fprintf(stderr, "Writing to /tmp/lept/comp/tiledhistos.pdf\n");
+        pixaConvertToPdf(pixa, 300, 1.0, L_FLATE_ENCODE, 0, NULL,
+                         "/tmp/lept/comp/tiledhistos.pdf");
+        pixaDestroy(&pixa);
+    }
+
+    pixDestroy(&pix3);
+    return 0;
+}
+
+
+/*!
+ *  pixPadToCenterCentroid()
+ *
+ *      Input:  pixs (any depth, colormap OK)
+ *              factor (subsampling for centroid; >= 1)
+ *      Return: pixd (padded with white pixels), or NULL on error.
+ *
+ *  Notes:
+ *      (1) This add minimum white padding to an 8 bpp pix, such that
+ *          the centroid of the photometric inverse is in the center of
+ *          the resulting image.  Thus in computing the centroid,
+ *          black pixels have weight 255, and white pixels have weight 0.
+ */
+PIX *
+pixPadToCenterCentroid(PIX     *pixs,
+                       l_int32  factor)
+
+{
+l_float32  cx, cy;
+l_int32    xs, ys, delx, dely, icx, icy, ws, hs, wd, hd;
+PIX       *pix1, *pixd;
+
+    PROCNAME("pixPadToCenterCentroid");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (factor < 1)
+        return (PIX *)ERROR_PTR("invalid sampling factor", procName, NULL);
+
+    pix1 = pixConvertTo8(pixs, FALSE);
+    pixCentroid8(pix1, factor, &cx, &cy);
+    icx = (l_int32)(cx + 0.5);
+    icy = (l_int32)(cy + 0.5);
+    pixGetDimensions(pix1, &ws, &hs, NULL);
+    delx = ws - 2 * icx;
+    dely = hs - 2 * icy;
+    xs = L_MAX(0, delx);
+    ys = L_MAX(0, dely);
+    wd = 2 * L_MAX(icx, ws - icx);
+    hd = 2 * L_MAX(icy, hs - icy);
+    pixd = pixCreate(wd, hd, 8);
+    pixSetAll(pixd);  /* to white */
+    pixCopyResolution(pixd, pixs);
+    pixRasterop(pixd, xs, ys, ws, hs, PIX_SRC, pix1, 0, 0);
+    pixDestroy(&pix1);
+    return pixd;
+}
+
+
+/*!
+ *  pixCentroid8()
+ *
+ *      Input:  pixs (8 bpp)
+ *              factor (subsampling; >= 1)
+ *              &cx (<return> x value of centroid)
+ *              &cy (<return> y value of centroid)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This first does a photometric inversion (black = 255, white = 0).
+ *          It then finds the centroid of the result.  The inversion is
+ *          done because white is usually background, so the centroid
+ *          is computed based on the "foreground" gray pixels, and the
+ *          darker the pixel, the more weight it is given.
+ */
+l_int32
+pixCentroid8(PIX        *pixs,
+             l_int32     factor,
+             l_float32  *pcx,
+             l_float32  *pcy)
+{
+l_int32    i, j, w, h, wpl, val;
+l_float32  sumx, sumy, sumv;
+l_uint32  *data, *line;
+PIX       *pix1;
+
+    PROCNAME("pixCentroid8");
+
+    if (pcx) *pcx = 0.0;
+    if (pcy) *pcy = 0.0;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs undefined or not 8 bpp", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("subsampling factor must be >= 1", procName, 1);
+    if (!pcx || !pcy)
+        return ERROR_INT("&cx and &cy not both defined", procName, 1);
+
+    pix1 = pixInvert(NULL, pixs);
+    pixGetDimensions(pix1, &w, &h, NULL);
+    data = pixGetData(pix1);
+    wpl = pixGetWpl(pix1);
+    sumx = sumy = sumv = 0.0;
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(line, j);
+            sumx += val * j;
+            sumy += val * i;
+            sumv += val;
+        }
+    }
+    pixDestroy(&pix1);
+
+    if (sumv == 0) {
+        L_INFO("input image is white\n", procName);
+        *pcx = w / 2;
+        *pcy = h / 2;
+    } else {
+        *pcx = sumx / sumv;
+        *pcy = sumy / sumv;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixDecideIfPhotoImage()
+ *
+ *      Input:  pix (8 bpp, centroid in center)
+ *              factor (subsampling for histograms; >= 1)
+ *              nx, ny (number of subregions to use for histograms)
+ *              thresh (threshold for photo/text; use 0 for default)
+ *              &naa (<return> array of normalized histograms)
+ *              pixadebug (<optional> use only for debug output)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The input image must be 8 bpp (no colormap), and padded with
+ *          white pixels so the centroid of photo-inverted pixels is at
+ *          the center of the image.
+ *      (2) If the pix is not almost certainly a photoimage, the returned
+ *          histograms (@naa) are null.
+ *      (3) If histograms are generated, the white (255) count is set
+ *          to 0.  This removes all pixels values above 230, including
+ *          white padding from the centroid matching operation, from
+ *          consideration.  The resulting histograms are then normalized
+ *          so the maximum count is 255.
+ *      (4) Default for @thresh is 1.3; this seems sufficiently conservative.
+ *      (5) Use @pixadebug == NULL unless debug output is requested.
+ */
+l_int32
+pixDecideIfPhotoImage(PIX       *pix,
+                      l_int32    factor,
+                      l_int32    nx,
+                      l_int32    ny,
+                      l_float32  thresh,
+                      NUMAA    **pnaa,
+                      PIXA      *pixadebug)
+{
+char       buf[64];
+l_int32    i, n, istext, isphoto;
+l_float32  maxval, sum1, sum2, ratio;
+L_BMF     *bmf;
+NUMA      *na1, *na2, *na3, *narv;
+NUMAA     *naa;
+PIX       *pix1;
+PIXA      *pixa, *pixa2;
+
+    PROCNAME("pixDecideIfPhotoImage");
+
+    if (!pnaa)
+        return ERROR_INT("&naa not defined", procName, 1);
+    *pnaa = NULL;
+    if (!pix || pixGetDepth(pix) != 8 || pixGetColormap(pix))
+        return ERROR_INT("pix undefined or invalid", procName, 1);
+    if (thresh <= 0.0) thresh = 1.3;  /* default */
+
+        /* Look for text lines */
+    pixDecideIfText(pix, NULL, &istext, pixadebug);
+    if (istext) {
+        L_INFO("Image is text\n", procName);
+        return 0;
+    }
+
+        /* Evaluate histograms in each tile */
+    pixa = pixaSplitPix(pix, nx, ny, 0, 0);
+    n = nx * ny;
+    bmf = (pixadebug) ? bmfCreate(NULL, 6) : NULL;
+    naa = numaaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixa, i, L_CLONE);
+
+            /* Get histograms, set white count to 0, normalize max to 255 */
+        na1 = pixGetGrayHistogram(pix1, factor);
+        numaSetValue(na1, 255, 0);
+        na2 = numaWindowedMean(na1, 5);  /* do some smoothing */
+        numaGetMax(na2, &maxval, NULL);
+        na3 = numaTransform(na2, 0, 255.0 / maxval);
+        if (pixadebug) {
+            snprintf(buf, sizeof(buf), "/tmp/lept/comp/plot.%d", i);
+            gplotSimple1(na3, GPLOT_PNG, buf, "Histos");
+        }
+
+        numaaAddNuma(naa, na3, L_INSERT);
+        numaDestroy(&na1);
+        numaDestroy(&na2);
+        pixDestroy(&pix1);
+    }
+    if (pixadebug) {
+        pixa2 = pixaReadFiles("/tmp/lept/comp", ".png");
+        pixaJoin(pixa, pixa2, 0, -1);
+        pixaDestroy(&pixa2);
+    }
+
+        /* Compute the standard deviation between these histos to decide
+         * if the image is photo or something more like line art,
+         * which does not support good comparison by tiled histograms.  */
+    grayInterHistogramStats(naa, 5, NULL, NULL, NULL, &narv);
+
+        /* For photos, the root variance has a larger weight of
+         * values in the range [50 ... 150] compared to [200 ... 230],
+         * than text or line art.  For the latter, most of the variance
+         * between tiles is in the lightest parts of the image, well
+         * above 150.  */
+    numaGetSumOnInterval(narv, 50, 150, &sum1);
+    numaGetSumOnInterval(narv, 200, 230, &sum2);
+    if (sum2 == 0.0)  /* shouldn't happen */
+        isphoto = 0;  /* be conservative */
+    else {
+        ratio = sum1 / sum2;
+        isphoto = (ratio > thresh) ? 1 : 0;
+        if (pixadebug) {
+            if (isphoto)
+                L_INFO("ratio %f > %f; isphoto is true\n",
+                       procName, ratio, thresh);
+            else
+                L_INFO("ratio %f < %f; isphoto is false\n",
+                       procName, ratio, thresh);
+        }
+    }
+    if (isphoto)
+        *pnaa = naa;
+    else
+        numaaDestroy(&naa);
+    bmfDestroy(&bmf);
+    numaDestroy(&narv);
+    pixaDestroy(&pixa);
+    return 0;
+}
+
+
+/*!
+ *  compareTilesByHisto()
+ *
+ *      Input:  naa1, naa2 (each is a set of 256 entry histograms)
+ *              minratio (requiring image sizes be compatible; < 1.0)
+ *              w1, h1, w2, h2 (image sizes from which histograms were made)
+ *              &score (<return> similarity score of histograms)
+ *              pixadebug (<optional> use only for debug output)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) naa1 and naa2 must be generated using pixGenPhotoHistos(),
+ *          using the same tile sizes.
+ *      (2) The image dimensions must be similar.  The score is 0.0
+ *          if the ratio of widths and heights (smallest / largest)
+ *          exceeds a threshold @minratio, which must be between
+ *          0.5 and 1.0.  If set at 1.0, both images must be exactly
+ *          the same size.  A typical value for @minratio is 0.9.
+ *      (2) The input pixadebug is null unless debug output is requested.
+ */
+l_int32
+compareTilesByHisto(NUMAA      *naa1,
+                    NUMAA      *naa2,
+                    l_float32   minratio,
+                    l_int32     w1,
+                    l_int32     h1,
+                    l_int32     w2,
+                    l_int32     h2,
+                    l_float32  *pscore,
+                    PIXA       *pixadebug)
+{
+char       buf1[128], buf2[128];
+l_int32    i, n;
+l_float32  wratio, hratio, score, minscore, dist;
+L_BMF     *bmf;
+NUMA      *na1, *na2, *nadist, *nascore;
+
+    PROCNAME("compareTilesByHisto");
+
+    if (!pscore)
+        return ERROR_INT("&score not defined", procName, 1);
+    *pscore = 0.0;
+    if (!naa1 || !naa2)
+        return ERROR_INT("naa1 and naa2 not both defined", procName, 1);
+
+        /* Filter for different sizes */
+    n = numaaGetCount(naa1);
+    if (n != numaaGetCount(naa2))
+        return ERROR_INT("naa1 and naa2 are different size", procName, 1);
+
+    lept_rmdir("lept/comptile");
+    lept_mkdir("lept/comptile");
+
+    wratio = (w1 < w2) ? (l_float32)w1 / (l_float32)w2 :
+             (l_float32)w2 / (l_float32)w1;
+    hratio = (h1 < h2) ? (l_float32)h1 / (l_float32)h2 :
+             (l_float32)h2 / (l_float32)h1;
+    if (wratio < minratio || hratio < minratio) {
+        if (pixadebug)
+            L_INFO("Sizes differ: wratio = %f, hratio = %f\n",
+                   procName, wratio, hratio);
+        return 0;
+    }
+
+        /* Evaluate histograms in each tile.  Remove white before
+         * computing EMD, because there are may be a lot of white
+         * pixels due to padding, and we don't want to include them.
+         * This also makes the debug histo plots more informative. */
+    minscore = 1.0;
+    nadist = numaCreate(n);
+    nascore = numaCreate(n);
+    bmf = (pixadebug) ? bmfCreate(NULL, 6) : NULL;
+    for (i = 0; i < n; i++) {
+        na1 = numaaGetNuma(naa1, i, L_CLONE);
+        na2 = numaaGetNuma(naa2, i, L_CLONE);
+        numaSetValue(na1, 255, 0.0);
+        numaSetValue(na2, 255, 0.0);
+
+            /* To compare histograms, use the normalized earthmover distance.
+             * Further normalize to get the EM distance as a fraction of the
+             * maximum distance in the histogram (255).  Finally, scale this
+             * up by 10.0, and subtract from 1.0 to get a similarity score. */
+        numaEarthMoverDistance(na1, na2, &dist);
+        score = L_MAX(0.0, 1.0 - 10.0 * (dist / 255.));
+        numaAddNumber(nadist, dist);
+        numaAddNumber(nascore, score);
+        minscore = L_MIN(minscore, score);
+        if (pixadebug) {
+            snprintf(buf1, sizeof(buf1), "/tmp/lept/comptile/plot.%d", i);
+            gplotSimple2(na1, na2, GPLOT_PNG, buf1, "Histos");
+        }
+        numaDestroy(&na1);
+        numaDestroy(&na2);
+    }
+    *pscore = minscore;
+
+    if (pixadebug) {
+        for (i = 0; i < n; i++) {
+            PIX  *pix1, *pix2;
+            snprintf(buf1, sizeof(buf1), "/tmp/lept/comptile/plot.%d.png", i);
+            pix1 = pixRead(buf1);
+            numaGetFValue(nadist, i, &dist);
+            numaGetFValue(nascore, i, &score);
+            snprintf(buf2, sizeof(buf2),
+                 "Image %d\ndist = %5.3f, score = %5.3f", i, dist, score);
+            pix2 = pixAddTextlines(pix1, bmf, buf2, 0x0000ff00, L_ADD_BELOW);
+            pixaAddPix(pixadebug, pix2, L_INSERT);
+            pixDestroy(&pix1);
+        }
+        fprintf(stderr, "Writing to /tmp/lept/comptile/comparegray.pdf\n");
+        pixaConvertToPdf(pixadebug, 300, 1.0, L_FLATE_ENCODE, 0, NULL,
+                         "/tmp/lept/comptile/comparegray.pdf");
+        numaWrite("/tmp/lept/comptile/scores.na", nascore);
+        numaWrite("/tmp/lept/comptile/dists.na", nadist);
+    }
+
+    bmfDestroy(&bmf);
+    numaDestroy(&nadist);
+    numaDestroy(&nascore);
+    return 0;
+}
+
+
+/*!
+ *  pixCompareGrayByHisto()
+ *
+ *      Input:  pix1, pix2 (any depth; colormap OK)
+ *              box1, box2 (<optional> region selected from each; can be null)
+ *              minratio (requiring sizes be compatible; < 1.0)
+ *              maxgray (max value to keep in histo; >= 200, 255 to keep all)
+ *              factor (subsampling; >= 1)
+ *              nx, ny (number of subregions to use for histograms; e.g. 3x3)
+ *              &score (<return> similarity score of histograms)
+ *              debugflag (1 for debug output; 0 for no debugging)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function compares two grayscale photo regions.  It can
+ *          do it with a single histogram from each region, or with a
+ *          set of (nx * ny) spatially aligned histograms.  For both
+ *          cases, align the regions using the centroid of the inverse
+ *          image, and crop to the smallest of the two.
+ *      (2) An initial filter gives @score = 0 if the ratio of widths
+ *          and heights (smallest / largest) does not exceed a
+ *          threshold @minratio.  This must be between 0.5 and 1.0.
+ *          If set at 1.0, both images must be exactly the same size.
+ *          A typical value for @minratio is 0.9.
+ *      (3) The lightest values in the histogram can be disregarded.
+ *          Set @maxgray to the lightest value to be kept.  For example,
+ *          to eliminate white (255), set @maxgray = 254.  @maxgray must
+ *          be >= 200.
+ *      (4) For an efficient representation of the histogram, normalize
+ *          using a multiplicative factor so that the number in the
+ *          maximum bucket is 255.  It then takes 256 bytes to store.
+ *      (5) When comparing the histograms of two regions:
+ *          - Use @maxgray = 254 to ignore the white pixels, the number
+ *            of which may be sensitive to the crop region if the pixels
+ *            outside that region are white.
+ *          - Use the Earth Mover distance (EMD), with the histograms
+ *            normalized so that the sum over bins is the same.
+ *            Further normalize by dividing by 255, so that the result
+ *            is in [0.0 ... 1.0].
+ *      (6) Get a similarity score S = 1.0 - k * D, where
+ *            k is a constant, say in the range 5-10
+ *            D = normalized EMD
+ *          and for multiple tiles, take the Min(S) to be the final score.
+ *          Using aligned tiles gives protection against accidental
+ *          similarity of the overall grayscale histograms.
+ *          A small number of aligned tiles works well.
+ *      (7) With debug on, you get a pdf that shows, for each tile,
+ *          the images, histograms and score.
+ *      (8) When to use:
+ *          (a) Because this function should not be used on text or
+ *              line graphics, which can give false positive results
+ *              (i.e., high scores for different images), the input
+ *              images should be filtered.
+ *          (b) To filter, first use pixDecideIfText().  If that function
+ *              says the image is text, do not use it.  If the function
+ *              says it is not text, it still may be line graphics, and
+ *              in that case, use:
+ *                 pixGetGrayHistogramTiled()
+ *                 grayInterHistogramStats()
+ *              to determine whether it is photo or line graphics.
+ */
+l_int32
+pixCompareGrayByHisto(PIX        *pix1,
+                      PIX        *pix2,
+                      BOX        *box1,
+                      BOX        *box2,
+                      l_float32   minratio,
+                      l_int32     maxgray,
+                      l_int32     factor,
+                      l_int32     nx,
+                      l_int32     ny,
+                      l_float32  *pscore,
+                      l_int32     debugflag)
+{
+l_int32    w1, h1, w2, h2;
+l_float32  wratio, hratio;
+BOX       *box3, *box4;
+PIX       *pix3, *pix4, *pix5, *pix6, *pix7, *pix8;
+PIXA      *pixa;
+
+    PROCNAME("pixCompareGrayByHisto");
+
+    if (!pscore)
+        return ERROR_INT("&score not defined", procName, 1);
+    *pscore = 0.0;
+    if (!pix1 || !pix2)
+        return ERROR_INT("pix1 and pix2 not both defined", procName, 1);
+    if (minratio < 0.5 || minratio > 1.0)
+        return ERROR_INT("minratio not in [0.5 ... 1.0]", procName, 1);
+    if (maxgray < 200)
+        return ERROR_INT("invalid maxgray; should be >= 200", procName, 1);
+    maxgray = L_MIN(255, maxgray);
+    if (factor < 1)
+        return ERROR_INT("subsampling factor must be >= 1", procName, 1);
+    if (nx < 1 || ny < 1)
+        return ERROR_INT("nx and ny must both be > 0", procName, 1);
+
+    pixa = NULL;
+    if (debugflag) {
+        pixa = pixaCreate(0);
+        lept_mkdir("lept/comp");
+    }
+
+        /* Initial filter by size */
+    if (box1)
+        boxGetGeometry(box1, NULL, NULL, &w1, &h1);
+    else
+        pixGetDimensions(pix1, &w1, &h1, NULL);
+    if (box2)
+        boxGetGeometry(box2, NULL, NULL, &w2, &h2);
+    else
+        pixGetDimensions(pix1, &w2, &h2, NULL);
+    wratio = (w1 < w2) ? (l_float32)w1 / (l_float32)w2 :
+             (l_float32)w2 / (l_float32)w1;
+    hratio = (h1 < h2) ? (l_float32)h1 / (l_float32)h2 :
+             (l_float32)h2 / (l_float32)h1;
+    if (wratio < minratio || hratio < minratio)
+        return 0;
+
+        /* Initial crop, if necessary */
+    if (box1)
+        pix3 = pixClipRectangle(pix1, box1, NULL);
+    else
+        pix3 = pixClone(pix1);
+    if (box2)
+        pix4 = pixClipRectangle(pix2, box2, NULL);
+    else
+        pix4 = pixClone(pix2);
+
+        /* Convert to 8 bpp, align centroids and do maximal crop */
+    pix5 = pixConvertTo8(pix3, FALSE);
+    pix6 = pixConvertTo8(pix4, FALSE);
+    pixCropAlignedToCentroid(pix5, pix6, factor, &box3, &box4);
+    pix7 = pixClipRectangle(pix5, box3, NULL);
+    pix8 = pixClipRectangle(pix6, box4, NULL);
+    if (debugflag) {
+        PIX     *pix9, *pix10, *pix11, *pix12, *pix13;
+        PIXA    *pixa2;
+        pix9 = pixConvertTo32(pix5);
+        pix10 = pixConvertTo32(pix6);
+        pixRenderBoxArb(pix9, box3, 2, 255, 0, 0);
+        pixRenderBoxArb(pix10, box4, 2, 255, 0, 0);
+        pix11 = pixScaleToSize(pix9, 400, 0);
+        pix12 = pixScaleToSize(pix10, 400, 0);
+        pixa2 = pixaCreate(2);
+        pixaAddPix(pixa2, pix11, L_INSERT);
+        pixaAddPix(pixa2, pix12, L_INSERT);
+        pix13 = pixaDisplayTiledInRows(pixa2, 32, 1000, 1.0, 0, 50, 0);
+        pixaAddPix(pixa, pix13, L_INSERT);
+        pixDestroy(&pix9);
+        pixDestroy(&pix10);
+        pixaDestroy(&pixa2);
+    }
+    pixDestroy(&pix3);
+    pixDestroy(&pix4);
+    pixDestroy(&pix5);
+    pixDestroy(&pix6);
+    boxDestroy(&box3);
+    boxDestroy(&box4);
+
+        /* Tile and compare histograms */
+    pixCompareTilesByHisto(pix7, pix8, maxgray, factor, nx, ny, pscore, pixa);
+
+    pixDestroy(&pix7);
+    pixDestroy(&pix8);
+    pixaDestroy(&pixa);
+    return 0;
+}
+
+
+/*!
+ *  pixCompareTilesByHisto()
+ *
+ *      Input:  pix1, pix2 (8 bpp)
+ *              maxgray (max value to keep in histo; 255 to keep all)
+ *              factor (subsampling; >= 1)
+ *              nx, ny (number of subregions to use for histograms)
+ *              &score (<return> similarity score of histograms)
+ *              pixadebug (<optional> use only for debug output)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This static function is only called from pixCompareGrayByHisto().
+ *          The input images have been converted to 8 bpp if necessary,
+ *          aligned and cropped.
+ *      (2) The input pixadebug is null unless debug output is requested.
+ *      (3) See pixCompareGrayByHisto() for details.
+ */
+static l_int32
+pixCompareTilesByHisto(PIX        *pix1,
+                       PIX        *pix2,
+                       l_int32     maxgray,
+                       l_int32     factor,
+                       l_int32     nx,
+                       l_int32     ny,
+                       l_float32  *pscore,
+                       PIXA       *pixadebug)
+{
+char       buf[64];
+l_int32    i, j, n;
+l_float32  score, minscore, maxval1, maxval2, dist;
+L_BMF     *bmf;
+NUMA      *na1, *na2, *na3, *na4, *na5, *na6, *na7;
+PIX       *pix3, *pix4;
+PIXA      *pixa1, *pixa2;
+
+    PROCNAME("pixCompareTilesByHisto");
+
+    if (!pscore)
+        return ERROR_INT("&score not defined", procName, 1);
+    *pscore = 0.0;
+    if (!pix1 || !pix2)
+        return ERROR_INT("pix1 and pix2 not both defined", procName, 1);
+
+        /* Evaluate histograms in each tile */
+    pixa1 = pixaSplitPix(pix1, nx, ny, 0, 0);
+    pixa2 = pixaSplitPix(pix2, nx, ny, 0, 0);
+    n = nx * ny;
+    na7 = (pixadebug) ? numaCreate(n) : NULL;
+    bmf = (pixadebug) ? bmfCreate(NULL, 6) : NULL;
+    minscore = 1.0;
+    for (i = 0; i < n; i++) {
+        pix3 = pixaGetPix(pixa1, i, L_CLONE);
+        pix4 = pixaGetPix(pixa2, i, L_CLONE);
+
+            /* Get histograms, set white count to 0, normalize max to 255 */
+        na1 = pixGetGrayHistogram(pix3, factor);
+        na2 = pixGetGrayHistogram(pix4, factor);
+        if (maxgray < 255) {
+            for (j = maxgray + 1; j <= 255; j++) {
+                numaSetValue(na1, j, 0);
+                numaSetValue(na2, j, 0);
+            }
+        }
+        na3 = numaWindowedMean(na1, 5);
+        na4 = numaWindowedMean(na2, 5);
+        numaGetMax(na3, &maxval1, NULL);
+        numaGetMax(na4, &maxval2, NULL);
+        na5 = numaTransform(na3, 0, 255.0 / maxval1);
+        na6 = numaTransform(na4, 0, 255.0 / maxval2);
+        if (pixadebug) {
+            gplotSimple2(na5, na6, GPLOT_PNG, "/tmp/lept/comp/plot1", "Histos");
+        }
+
+            /* To compare histograms, use the normalized earthmover distance.
+             * Further normalize to get the EM distance as a fraction of the
+             * maximum distance in the histogram (255).  Finally, scale this
+             * up by 10.0, and subtract from 1.0 to get a similarity score. */
+        numaEarthMoverDistance(na5, na6, &dist);
+        score = L_MAX(0.0, 1.0 - 8.0 * (dist / 255.));
+        if (pixadebug) numaAddNumber(na7, score);
+        minscore = L_MIN(minscore, score);
+        if (pixadebug) {
+            PIX     *pix5, *pix6, *pix7, *pix8, *pix9, *pix10;
+            PIXA    *pixa3;
+            l_int32  w, h, wscale;
+            pixa3 = pixaCreate(3);
+            pixGetDimensions(pix3, &w, &h, NULL);
+            wscale = (w > h) ? 700 : 400;
+            pix5 = pixScaleToSize(pix3, wscale, 0);
+            pix6 = pixScaleToSize(pix4, wscale, 0);
+            pixaAddPix(pixa3, pix5, L_INSERT);
+            pixaAddPix(pixa3, pix6, L_INSERT);
+            pix7 = pixRead("/tmp/lept/comp/plot1.png");
+            pix8 = pixScaleToSize(pix7, 700, 0);
+            snprintf(buf, sizeof(buf), "%5.3f", score);
+            pix9 = pixAddTextlines(pix8, bmf, buf, 0x0000ff00, L_ADD_RIGHT);
+            pixaAddPix(pixa3, pix9, L_INSERT);
+            pix10 = pixaDisplayTiledInRows(pixa3, 32, 1000, 1.0, 0, 50, 0);
+            pixaAddPix(pixadebug, pix10, L_INSERT);
+            pixDestroy(&pix7);
+            pixDestroy(&pix8);
+            pixaDestroy(&pixa3);
+        }
+        numaDestroy(&na1);
+        numaDestroy(&na2);
+        numaDestroy(&na3);
+        numaDestroy(&na4);
+        numaDestroy(&na5);
+        numaDestroy(&na6);
+        pixDestroy(&pix3);
+        pixDestroy(&pix4);
+    }
+    *pscore = minscore;
+
+    if (pixadebug) {
+        pixaConvertToPdf(pixadebug, 300, 1.0, L_FLATE_ENCODE, 0, NULL,
+                         "/tmp/lept/comp/comparegray.pdf");
+        numaWrite("/tmp/lept/comp/tilescores.na", na7);
+    }
+
+    bmfDestroy(&bmf);
+    numaDestroy(&na7);
+    pixaDestroy(&pixa1);
+    pixaDestroy(&pixa2);
+    return 0;
+}
+
+
+/*!
+ *  pixCropAlignedToCentroid()
+ *
+ *      Input:  pix1, pix2 (any depth; colormap OK)
+ *              factor (subsampling; >= 1)
+ *              &box1 (<return> crop box for pix1)
+ *              &box2 (<return> crop box for pix2)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This finds the maximum crop boxes for two 8 bpp images when
+ *          their centroids of their photometric inverses are aligned.
+ *          Black pixels have weight 255; white pixels have weight 0.
+ */
+l_int32
+pixCropAlignedToCentroid(PIX     *pix1,
+                         PIX     *pix2,
+                         l_int32  factor,
+                         BOX    **pbox1,
+                         BOX    **pbox2)
+{
+l_float32  cx1, cy1, cx2, cy2;
+l_int32    w1, h1, w2, h2, icx1, icy1, icx2, icy2;
+l_int32    xm, xm1, xm2, xp, xp1, xp2, ym, ym1, ym2, yp, yp1, yp2;
+PIX       *pix3, *pix4;
+
+    PROCNAME("pixCropAlignedToCentroid");
+
+    if (pbox1) *pbox1 = NULL;
+    if (pbox2) *pbox2 = NULL;
+    if (!pix1 || !pix2)
+        return ERROR_INT("pix1 and pix2 not both defined", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("subsampling factor must be >= 1", procName, 1);
+    if (!pbox1 || !pbox2)
+        return ERROR_INT("&box1 and &box2 not both defined", procName, 1);
+
+    pix3 = pixConvertTo8(pix1, FALSE);
+    pix4 = pixConvertTo8(pix2, FALSE);
+    pixCentroid8(pix3, factor, &cx1, &cy1);
+    pixCentroid8(pix4, factor, &cx2, &cy2);
+    pixGetDimensions(pix3, &w1, &h1, NULL);
+    pixGetDimensions(pix4, &w2, &h2, NULL);
+    pixDestroy(&pix3);
+    pixDestroy(&pix4);
+
+    icx1 = (l_int32)(cx1 + 0.5);
+    icy1 = (l_int32)(cy1 + 0.5);
+    icx2 = (l_int32)(cx2 + 0.5);
+    icy2 = (l_int32)(cy2 + 0.5);
+    xm = L_MIN(icx1, icx2);
+    xm1 = icx1 - xm;
+    xm2 = icx2 - xm;
+    xp = L_MIN(w1 - icx1, w2 - icx2);  /* one pixel beyond to the right */
+    xp1 = icx1 + xp;
+    xp2 = icx2 + xp;
+    ym = L_MIN(icy1, icy2);
+    ym1 = icy1 - ym;
+    ym2 = icy2 - ym;
+    yp = L_MIN(h1 - icy1, h2 - icy2);  /* one pixel below the bottom */
+    yp1 = icy1 + yp;
+    yp2 = icy2 + yp;
+    *pbox1 = boxCreate(xm1, ym1, xp1 - xm1, yp1 - ym1);
+    *pbox2 = boxCreate(xm2, ym2, xp2 - xm2, yp2 - ym2);
+    return 0;
+}
+
+
+/*!
+ *  l_compressGrayHistograms()
+ *
+ *      Input:  numaa (set of 256-entry histograms)
+ *              w, h (size of image)
+ *              &size (<return> size of byte array)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This first writes w and h to the byte array as 4 byte ints.
+ *      (2) Then it normalizes each histogram to a max value of 255,
+ *          and saves each value as a byte.  If there are
+ *          N histograms, the output bytearray has 8 + 256 * N bytes.
+ *      (3) Further compression of the array with zlib yields only about
+ *          a 25% decrease in size, so we don't bother.  If size reduction
+ *          were important, a lossy transform using a 1-dimensional DCT
+ *          would be effective, because we don't care about the fine
+ *          details of these histograms.
+ */
+l_uint8 *
+l_compressGrayHistograms(NUMAA   *naa,
+                         l_int32  w,
+                         l_int32  h,
+                         size_t  *psize)
+{
+l_uint8   *bytea;
+l_int32    i, j, n, nn, ival;
+l_float32  maxval;
+NUMA      *na1, *na2;
+
+    PROCNAME("l_compressGrayHistograms");
+
+    if (!psize)
+        return (l_uint8 *)ERROR_PTR("&size not defined", procName, NULL);
+    *psize = 0;
+    if (!naa)
+        return (l_uint8 *)ERROR_PTR("naa not defined", procName, NULL);
+    n = numaaGetCount(naa);
+    for (i = 0; i < n; i++) {
+        nn = numaaGetNumaCount(naa, i);
+        if (nn != 256) {
+            L_ERROR("%d numbers in numa[%d]\n", procName, nn, i);
+            return NULL;
+        }
+    }
+
+    if ((bytea = (l_uint8 *)LEPT_CALLOC(8 + 256 * n, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("bytea not made", procName, NULL);
+    *psize = 8 + 256 * n;
+    l_setDataFourBytes(bytea, 0, w);
+    l_setDataFourBytes(bytea, 1, h);
+    for (i = 0; i < n; i++) {
+        na1 = numaaGetNuma(naa, i, L_COPY);
+        numaGetMax(na1, &maxval, NULL);
+        na2 = numaTransform(na1, 0, 255.0 / maxval);
+        for (j = 0; j < 256; j++) {
+            numaGetIValue(na2, j, &ival);
+            bytea[8 + 256 * i + j] = ival;
+        }
+        numaDestroy(&na1);
+        numaDestroy(&na2);
+    }
+
+    return bytea;
+}
+
+
+/*!
+ *  l_uncompressGrayHistograms()
+ *
+ *      Input:  bytea (byte array of size 8 + 256 * N, N an integer)
+ *              size (size of byte array)
+ *              &w (<return> width of the image that generated the histograms)
+ *              &h (<return> height of the image)
+ *      Return: numaa (representing N histograms, each with 256 bins),
+ *                     or null on error.
+ *
+ *  Notes:
+ *      (1) The first 8 bytes are read as two 32-bit ints.
+ *      (2) Then this constructs a numaa representing some number of
+ *          gray histograms that are normalized such that the max value
+ *          in each histogram is 255.  The data is stored as a byte
+ *          array, with 256 bytes holding the data for each histogram.
+ *          Each gray histogram was computed from a tile of a grayscale image.
+ */
+NUMAA *
+l_uncompressGrayHistograms(l_uint8  *bytea,
+                           size_t    size,
+                           l_int32  *pw,
+                           l_int32  *ph)
+{
+l_int32  i, j, n;
+NUMA    *na;
+NUMAA   *naa;
+
+    PROCNAME("l_uncompressGrayHistograms");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (!pw || !ph)
+        return (NUMAA *)ERROR_PTR("&w and &h not both defined", procName, NULL);
+    if (!bytea)
+        return (NUMAA *)ERROR_PTR("bytea not defined", procName, NULL);
+    n = (size - 8) / 256;
+    if ((size - 8) % 256 != 0)
+        return (NUMAA *)ERROR_PTR("bytea size is invalid", procName, NULL);
+
+    *pw = l_getDataFourBytes(bytea, 0);
+    *ph = l_getDataFourBytes(bytea, 1);
+    naa = numaaCreate(n);
+    for (i = 0; i < n; i++) {
+        na = numaCreate(256);
+        for (j = 0; j < 256; j++)
+            numaAddNumber(na, bytea[8 + 256 * i + j]);
+        numaaAddNuma(naa, na, L_INSERT);
+    }
+
+    return naa;
+}
+
+
+/*------------------------------------------------------------------*
+ *             Translated images at the same resolution             *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixCompareWithTranslation()
+ *
+ *      Input:  pix1, pix2 (any depth; colormap OK)
+ *              thresh (threshold for converting to 1 bpp)
+ *              &delx (<return> x translation on pix2 to align with pix1)
+ *              &dely (<return> y translation on pix2 to align with pix1)
+ *              &score (<return> correlation score at best alignment)
+ *              debugflag (1 for debug output; 0 for no debugging)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does a coarse-to-fine search for best translational
+ *          alignment of two images, measured by a scoring function
+ *          that is the correlation between the fg pixels.
+ *      (2) The threshold is used if the images aren't 1 bpp.
+ *      (3) With debug on, you get a pdf that shows, as a grayscale
+ *          image, the score as a function of shift from the initial
+ *          estimate, for each of the four levels.  The shift is 0 at
+ *          the center of the image.
+ *      (4) With debug on, you also get a pdf that shows the
+ *          difference at the best alignment between the two images,
+ *          at each of the four levels.  The red and green pixels
+ *          show locations where one image has a fg pixel and the
+ *          other doesn't.  The black pixels are where both images
+ *          have fg pixels, and white pixels are where neither image
+ *          has fg pixels.
+ */
+l_int32
+pixCompareWithTranslation(PIX        *pix1,
+                          PIX        *pix2,
+                          l_int32     thresh,
+                          l_int32    *pdelx,
+                          l_int32    *pdely,
+                          l_float32  *pscore,
+                          l_int32     debugflag)
+{
+l_uint8   *subtab;
+l_int32    i, level, area1, area2, delx, dely;
+l_int32    etransx, etransy, maxshift, dbint;
+l_int32   *stab, *ctab;
+l_float32  cx1, cx2, cy1, cy2, score;
+PIX       *pixb1, *pixb2, *pixt1, *pixt2, *pixt3, *pixt4;
+PIXA      *pixa1, *pixa2, *pixadb;
+
+    PROCNAME("pixCompareWithTranslation");
+
+    if (pdelx) *pdelx = 0;
+    if (pdely) *pdely = 0;
+    if (pscore) *pscore = 0.0;
+    if (!pdelx || !pdely)
+        return ERROR_INT("&delx and &dely not defined", procName, 1);
+    if (!pscore)
+        return ERROR_INT("&score not defined", procName, 1);
+    if (!pix1)
+        return ERROR_INT("pix1 not defined", procName, 1);
+    if (!pix2)
+        return ERROR_INT("pix2 not defined", procName, 1);
+
+        /* Make tables */
+    subtab = makeSubsampleTab2x();
+    stab = makePixelSumTab8();
+    ctab = makePixelCentroidTab8();
+
+        /* Binarize each image */
+    pixb1 = pixConvertTo1(pix1, thresh);
+    pixb2 = pixConvertTo1(pix2, thresh);
+
+        /* Make a cascade of 2x reduced images for each, thresholding
+         * with level 2 (neutral), down to 8x reduction */
+    pixa1 = pixaCreate(4);
+    pixa2 = pixaCreate(4);
+    if (debugflag)
+        pixadb = pixaCreate(4);
+    pixaAddPix(pixa1, pixb1, L_INSERT);
+    pixaAddPix(pixa2, pixb2, L_INSERT);
+    for (i = 0; i < 3; i++) {
+        pixt1 = pixReduceRankBinary2(pixb1, 2, subtab);
+        pixt2 = pixReduceRankBinary2(pixb2, 2, subtab);
+        pixaAddPix(pixa1, pixt1, L_INSERT);
+        pixaAddPix(pixa2, pixt2, L_INSERT);
+        pixb1 = pixt1;
+        pixb2 = pixt2;
+    }
+
+        /* At the lowest level, use the centroids with a maxshift of 6
+         * to search for the best alignment.  Then at higher levels,
+         * use the result from the level below as the initial approximation
+         * for the alignment, and search with a maxshift of 2. */
+    for (level = 3; level >= 0; level--) {
+        pixt1 = pixaGetPix(pixa1, level, L_CLONE);
+        pixt2 = pixaGetPix(pixa2, level, L_CLONE);
+        pixCountPixels(pixt1, &area1, stab);
+        pixCountPixels(pixt2, &area2, stab);
+        if (level == 3) {
+            pixCentroid(pixt1, ctab, stab, &cx1, &cy1);
+            pixCentroid(pixt2, ctab, stab, &cx2, &cy2);
+            etransx = lept_roundftoi(cx1 - cx2);
+            etransy = lept_roundftoi(cy1 - cy2);
+            maxshift = 6;
+        } else {
+            etransx = 2 * delx;
+            etransy = 2 * dely;
+            maxshift = 2;
+        }
+        dbint = (debugflag) ? level + 1 : 0;
+        pixBestCorrelation(pixt1, pixt2, area1, area2, etransx, etransy,
+                           maxshift, stab, &delx, &dely, &score, dbint);
+        if (debugflag) {
+            fprintf(stderr, "Level %d: delx = %d, dely = %d, score = %7.4f\n",
+                    level, delx, dely, score);
+            pixRasteropIP(pixt2, delx, dely, L_BRING_IN_WHITE);
+            pixt3 = pixDisplayDiffBinary(pixt1, pixt2);
+            pixt4 = pixExpandReplicate(pixt3, 8 / (1 << (3 - level)));
+            pixaAddPix(pixadb, pixt4, L_INSERT);
+            pixDestroy(&pixt3);
+        }
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt2);
+    }
+
+    if (debugflag) {
+        pixaConvertToPdf(pixadb, 300, 1.0, L_FLATE_ENCODE, 0, NULL,
+                         "/tmp/lept/comp/compare.pdf");
+        convertFilesToPdf("/tmp/lept/comp", "correl_", 30, 1.0, L_FLATE_ENCODE,
+                          0, "Correlation scores at levels 1 through 5",
+                          "/tmp/lept/comp/correl.pdf");
+        pixaDestroy(&pixadb);
+    }
+
+    *pdelx = delx;
+    *pdely = dely;
+    *pscore = score;
+    pixaDestroy(&pixa1);
+    pixaDestroy(&pixa2);
+    LEPT_FREE(subtab);
+    LEPT_FREE(stab);
+    LEPT_FREE(ctab);
+    return 0;
+}
+
+
+/*!
+ *  pixBestCorrelation()
+ *
+ *      Input:  pix1   (1 bpp)
+ *              pix2   (1 bpp)
+ *              area1  (number of on pixels in pix1)
+ *              area2  (number of on pixels in pix2)
+ *              etransx (estimated x translation of pix2 to align with pix1)
+ *              etransy (estimated y translation of pix2 to align with pix1)
+ *              maxshift  (max x and y shift of pix2, around the estimated
+ *                          alignment location, relative to pix1)
+ *              tab8 (<optional> sum tab for ON pixels in byte; can be NULL)
+ *              &delx (<optional return> best x shift of pix2 relative to pix1
+ *              &dely (<optional return> best y shift of pix2 relative to pix1
+ *              &score (<optional return> maximum score found; can be NULL)
+ *              debugflag (<= 0 to skip; positive to generate output.
+ *                         The integer is used to label the debug image.)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This maximizes the correlation score between two 1 bpp images,
+ *          by starting with an estimate of the alignment
+ *          (@etransx, @etransy) and computing the correlation around this.
+ *          It optionally returns the shift (@delx, @dely) that maximizes
+ *          the correlation score when pix2 is shifted by this amount
+ *          relative to pix1.
+ *      (2) Get the centroids of pix1 and pix2, using pixCentroid(),
+ *          to compute (@etransx, @etransy).  Get the areas using
+ *          pixCountPixels().
+ *      (3) The centroid of pix2 is shifted with respect to the centroid
+ *          of pix1 by all values between -maxshiftx and maxshiftx,
+ *          and likewise for the y shifts.  Therefore, the number of
+ *          correlations computed is:
+ *               (2 * maxshiftx + 1) * (2 * maxshifty + 1)
+ *          Consequently, if pix1 and pix2 are large, you should do this
+ *          in a coarse-to-fine sequence.  See the use of this function
+ *          in pixCompareWithTranslation().
+ */
+l_int32
+pixBestCorrelation(PIX        *pix1,
+                   PIX        *pix2,
+                   l_int32     area1,
+                   l_int32     area2,
+                   l_int32     etransx,
+                   l_int32     etransy,
+                   l_int32     maxshift,
+                   l_int32    *tab8,
+                   l_int32    *pdelx,
+                   l_int32    *pdely,
+                   l_float32  *pscore,
+                   l_int32     debugflag)
+{
+l_int32    shiftx, shifty, delx, dely;
+l_int32   *tab;
+l_float32  maxscore, score;
+FPIX      *fpix;
+PIX       *pix3, *pix4;
+
+    PROCNAME("pixBestCorrelation");
+
+    if (pdelx) *pdelx = 0;
+    if (pdely) *pdely = 0;
+    if (pscore) *pscore = 0.0;
+    if (!pix1 || pixGetDepth(pix1) != 1)
+        return ERROR_INT("pix1 not defined or not 1 bpp", procName, 1);
+    if (!pix2 || pixGetDepth(pix2) != 1)
+        return ERROR_INT("pix2 not defined or not 1 bpp", procName, 1);
+    if (!area1 || !area2)
+        return ERROR_INT("areas must be > 0", procName, 1);
+
+    if (debugflag > 0)
+        fpix = fpixCreate(2 * maxshift + 1, 2 * maxshift + 1);
+
+    if (!tab8)
+        tab = makePixelSumTab8();
+    else
+        tab = tab8;
+
+        /* Search over a set of {shiftx, shifty} for the max */
+    maxscore = 0;
+    delx = etransx;
+    dely = etransy;
+    for (shifty = -maxshift; shifty <= maxshift; shifty++) {
+        for (shiftx = -maxshift; shiftx <= maxshift; shiftx++) {
+            pixCorrelationScoreShifted(pix1, pix2, area1, area2,
+                                       etransx + shiftx,
+                                       etransy + shifty, tab, &score);
+            if (debugflag > 0) {
+                fpixSetPixel(fpix, maxshift + shiftx, maxshift + shifty,
+                             1000.0 * score);
+/*                fprintf(stderr, "(sx, sy) = (%d, %d): score = %6.4f\n",
+                        shiftx, shifty, score); */
+            }
+            if (score > maxscore) {
+                maxscore = score;
+                delx = etransx + shiftx;
+                dely = etransy + shifty;
+            }
+        }
+    }
+
+    if (debugflag > 0) {
+        lept_mkdir("lept/comp");
+        char  buf[128];
+        pix3 = fpixDisplayMaxDynamicRange(fpix);
+        pix4 = pixExpandReplicate(pix3, 20);
+        snprintf(buf, sizeof(buf), "/tmp/lept/comp/correl_%d.png",
+                 debugflag);
+        pixWrite(buf, pix4, IFF_PNG);
+        pixDestroy(&pix3);
+        pixDestroy(&pix4);
+        fpixDestroy(&fpix);
+    }
+
+    if (pdelx) *pdelx = delx;
+    if (pdely) *pdely = dely;
+    if (pscore) *pscore = maxscore;
+    if (!tab8) LEPT_FREE(tab);
+    return 0;
+}
diff --git a/src/conncomp.c b/src/conncomp.c
new file mode 100644 (file)
index 0000000..2a2c333
--- /dev/null
@@ -0,0 +1,1199 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  conncomp.c
+ *
+ *    Connected component counting and extraction, using Heckbert's
+ *    stack-based filling algorithm.
+ *
+ *      4- and 8-connected components: counts, bounding boxes and images
+ *
+ *      Top-level calls:
+ *           BOXA     *pixConnComp()
+ *           BOXA     *pixConnCompPixa()
+ *           BOXA     *pixConnCompBB()
+ *           l_int32   pixCountConnComp()
+ *
+ *      Identify the next c.c. to be erased:
+ *           l_int32   nextOnPixelInRaster()
+ *           l_int32   nextOnPixelInRasterLow()
+ *
+ *      Erase the c.c., saving the b.b.:
+ *           BOX      *pixSeedfillBB()
+ *           BOX      *pixSeedfill4BB()
+ *           BOX      *pixSeedfill8BB()
+ *
+ *      Just erase the c.c.:
+ *           l_int32   pixSeedfill()
+ *           l_int32   pixSeedfill4()
+ *           l_int32   pixSeedfill8()
+ *
+ *      Static stack helper functions for single raster line seedfill:
+ *           static void    pushFillsegBB()
+ *           static void    pushFillseg()
+ *           static void    popFillseg()
+ *
+ *  The basic method in pixConnCompBB() is very simple.  We scan the
+ *  image in raster order, looking for the next ON pixel.  When it
+ *  is found, we erase it and every pixel of the 4- or 8-connected
+ *  component to which it belongs, using Heckbert's seedfill
+ *  algorithm.  As pixels are erased, we keep track of the
+ *  minimum rectangle that encloses all erased pixels; after
+ *  the connected component has been erased, we save its
+ *  bounding box in an array of boxes.  When all pixels in the
+ *  image have been erased, we have an array that describes every
+ *  4- or 8-connected component in terms of its bounding box.
+ *
+ *  pixConnCompPixa() is a slight variation on pixConnCompBB(),
+ *  where we additionally save an array of images (in a Pixa)
+ *  of each of the 4- or 8-connected components.  This is done trivially
+ *  by maintaining two temporary images.  We erase a component from one,
+ *  and use the bounding box to extract the pixels within the b.b.
+ *  from each of the two images.  An XOR between these subimages
+ *  gives the erased component.  Then we erase the component from the
+ *  second image using the XOR again, with the extracted component
+ *  placed on the second image at the location of the bounding box.
+ *  Rasterop does all the work.  At the end, we have an array
+ *  of the 4- or 8-connected components, as well as an array of the
+ *  bounding boxes that describe where they came from in the original image.
+ *
+ *  If you just want the number of connected components, pixCountConnComp()
+ *  is a bit faster than pixConnCompBB(), because it doesn't have to
+ *  keep track of the bounding rectangles for each c.c.
+ */
+
+#include "allheaders.h"
+
+/*
+ *  The struct FillSeg is used by the Heckbert seedfill algorithm to
+ *  hold information about image segments that are waiting to be
+ *  investigated.  We use two Stacks, one to hold the FillSegs in use,
+ *  and an auxiliary Stack as a reservoir to hold FillSegs for re-use.
+ */
+struct FillSeg
+{
+    l_int32    xleft;    /* left edge of run */
+    l_int32    xright;   /* right edge of run */
+    l_int32    y;        /* run y  */
+    l_int32    dy;       /* parent segment direction: 1 above, -1 below) */
+};
+typedef struct FillSeg    FILLSEG;
+
+
+    /* Static accessors for FillSegs on a stack */
+static void pushFillsegBB(L_STACK *stack, l_int32 xleft, l_int32 xright,
+                          l_int32 y, l_int32 dy, l_int32 ymax,
+                          l_int32 *pminx, l_int32 *pmaxx,
+                          l_int32 *pminy, l_int32 *pmaxy);
+static void pushFillseg(L_STACK *stack, l_int32 xleft, l_int32 xright,
+                        l_int32 y, l_int32 dy, l_int32 ymax);
+static void popFillseg(L_STACK *stack, l_int32 *pxleft, l_int32 *pxright,
+                       l_int32 *py, l_int32 *pdy);
+
+
+#ifndef  NO_CONSOLE_IO
+#define   DEBUG    0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*-----------------------------------------------------------------------*
+ *                Bounding boxes of 4 Connected Components               *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixConnComp()
+ *
+ *      Input:  pixs (1 bpp)
+ *              &pixa   (<optional return> pixa of each c.c.)
+ *              connectivity (4 or 8)
+ *      Return: boxa, or null on error
+ *
+ *  Notes:
+ *      (1) This is the top-level call for getting bounding boxes or
+ *          a pixa of the components, and it can be used instead
+ *          of either pixConnCompBB() or pixConnCompPixa(), rsp.
+ */
+BOXA *
+pixConnComp(PIX     *pixs,
+            PIXA   **ppixa,
+            l_int32  connectivity)
+{
+
+    PROCNAME("pixConnComp");
+
+    if (ppixa) *ppixa = NULL;
+    if (!pixs)
+        return (BOXA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (BOXA *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+    if (!ppixa)
+        return pixConnCompBB(pixs, connectivity);
+    else
+        return pixConnCompPixa(pixs, ppixa, connectivity);
+}
+
+
+/*!
+ *  pixConnCompPixa()
+ *
+ *      Input:  pixs (1 bpp)
+ *              &pixa (<return> pixa of each c.c.)
+ *              connectivity (4 or 8)
+ *      Return: boxa, or null on error
+ *
+ *  Notes:
+ *      (1) This finds bounding boxes of 4- or 8-connected components
+ *          in a binary image, and saves images of each c.c
+ *          in a pixa array.
+ *      (2) It sets up 2 temporary pix, and for each c.c. that is
+ *          located in raster order, it erases the c.c. from one pix,
+ *          then uses the b.b. to extract the c.c. from the two pix using
+ *          an XOR, and finally erases the c.c. from the second pix.
+ *      (3) A clone of the returned boxa (where all boxes in the array
+ *          are clones) is inserted into the pixa.
+ *      (4) If the input is valid, this always returns a boxa and a pixa.
+ *          If pixs is empty, the boxa and pixa will be empty.
+ */
+BOXA *
+pixConnCompPixa(PIX     *pixs,
+                PIXA   **ppixa,
+                l_int32  connectivity)
+{
+l_int32   h, iszero;
+l_int32   x, y, xstart, ystart;
+PIX      *pixt1, *pixt2, *pixt3, *pixt4;
+PIXA     *pixa;
+BOX      *box;
+BOXA     *boxa;
+L_STACK  *stack, *auxstack;
+
+    PROCNAME("pixConnCompPixa");
+
+    if (!ppixa)
+        return (BOXA *)ERROR_PTR("&pixa not defined", procName, NULL);
+    *ppixa = NULL;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+    pixa = pixaCreate(0);
+    *ppixa = pixa;
+    pixZero(pixs, &iszero);
+    if (iszero)
+        return boxaCreate(1);  /* return empty boxa */
+
+    if ((pixt1 = pixCopy(NULL, pixs)) == NULL)
+        return (BOXA *)ERROR_PTR("pixt1 not made", procName, NULL);
+    if ((pixt2 = pixCopy(NULL, pixs)) == NULL)
+        return (BOXA *)ERROR_PTR("pixt2 not made", procName, NULL);
+
+    h = pixGetHeight(pixs);
+    if ((stack = lstackCreate(h)) == NULL)
+        return (BOXA *)ERROR_PTR("stack not made", procName, NULL);
+    if ((auxstack = lstackCreate(0)) == NULL)
+        return (BOXA *)ERROR_PTR("auxstack not made", procName, NULL);
+    stack->auxstack = auxstack;
+    if ((boxa = boxaCreate(0)) == NULL)
+        return (BOXA *)ERROR_PTR("boxa not made", procName, NULL);
+
+    xstart = 0;
+    ystart = 0;
+    while (1)
+    {
+        if (!nextOnPixelInRaster(pixt1, xstart, ystart, &x, &y))
+            break;
+
+        if ((box = pixSeedfillBB(pixt1, stack, x, y, connectivity)) == NULL)
+            return (BOXA *)ERROR_PTR("box not made", procName, NULL);
+        boxaAddBox(boxa, box, L_INSERT);
+
+            /* Save the c.c. and remove from pixt2 as well */
+        pixt3 = pixClipRectangle(pixt1, box, NULL);
+        pixt4 = pixClipRectangle(pixt2, box, NULL);
+        pixXor(pixt3, pixt3, pixt4);
+        pixRasterop(pixt2, box->x, box->y, box->w, box->h, PIX_SRC ^ PIX_DST,
+                    pixt3, 0, 0);
+        pixaAddPix(pixa, pixt3, L_INSERT);
+        pixDestroy(&pixt4);
+
+        xstart = x;
+        ystart = y;
+    }
+
+#if  DEBUG
+    pixCountPixels(pixt1, &iszero, NULL);
+    fprintf(stderr, "Number of remaining pixels = %d\n", iszero);
+    pixWrite("junkremain", pixt1, IFF_PNG);
+#endif  /* DEBUG */
+
+        /* Remove old boxa of pixa and replace with a clone copy */
+    boxaDestroy(&pixa->boxa);
+    pixa->boxa = boxaCopy(boxa, L_CLONE);
+
+        /* Cleanup, freeing the fillsegs on each stack */
+    lstackDestroy(&stack, TRUE);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    return boxa;
+}
+
+
+/*!
+ *  pixConnCompBB()
+ *
+ *      Input:  pixs (1 bpp)
+ *              connectivity (4 or 8)
+ *      Return: boxa, or null on error
+ *
+ * Notes:
+ *     (1) Finds bounding boxes of 4- or 8-connected components
+ *         in a binary image.
+ *     (2) This works on a copy of the input pix.  The c.c. are located
+ *         in raster order and erased one at a time.  In the process,
+ *         the b.b. is computed and saved.
+ */
+BOXA *
+pixConnCompBB(PIX     *pixs,
+              l_int32  connectivity)
+{
+l_int32   h, iszero;
+l_int32   x, y, xstart, ystart;
+PIX      *pixt;
+BOX      *box;
+BOXA     *boxa;
+L_STACK  *stack, *auxstack;
+
+    PROCNAME("pixConnCompBB");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (BOXA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+    pixZero(pixs, &iszero);
+    if (iszero)
+        return boxaCreate(1);  /* return empty boxa */
+
+    if ((pixt = pixCopy(NULL, pixs)) == NULL)
+        return (BOXA *)ERROR_PTR("pixt not made", procName, NULL);
+
+    h = pixGetHeight(pixs);
+    if ((stack = lstackCreate(h)) == NULL)
+        return (BOXA *)ERROR_PTR("stack not made", procName, NULL);
+    if ((auxstack = lstackCreate(0)) == NULL)
+        return (BOXA *)ERROR_PTR("auxstack not made", procName, NULL);
+    stack->auxstack = auxstack;
+    if ((boxa = boxaCreate(0)) == NULL)
+        return (BOXA *)ERROR_PTR("boxa not made", procName, NULL);
+
+    xstart = 0;
+    ystart = 0;
+    while (1)
+    {
+        if (!nextOnPixelInRaster(pixt, xstart, ystart, &x, &y))
+            break;
+
+        if ((box = pixSeedfillBB(pixt, stack, x, y, connectivity)) == NULL)
+            return (BOXA *)ERROR_PTR("box not made", procName, NULL);
+        boxaAddBox(boxa, box, L_INSERT);
+
+        xstart = x;
+        ystart = y;
+    }
+
+#if  DEBUG
+    pixCountPixels(pixt, &iszero, NULL);
+    fprintf(stderr, "Number of remaining pixels = %d\n", iszero);
+    pixWrite("junkremain", pixt1, IFF_PNG);
+#endif  /* DEBUG */
+
+        /* Cleanup, freeing the fillsegs on each stack */
+    lstackDestroy(&stack, TRUE);
+    pixDestroy(&pixt);
+
+    return boxa;
+}
+
+
+/*!
+ *  pixCountConnComp()
+ *
+ *      Input:  pixs (1 bpp)
+ *              connectivity (4 or 8)
+ *              &count (<return>
+ *      Return: 0 if OK, 1 on error
+ *
+ * Notes:
+ *     (1) This is the top-level call for getting the number of
+ *         4- or 8-connected components in a 1 bpp image.
+ *     (2) It works on a copy of the input pix.  The c.c. are located
+ *         in raster order and erased one at a time.
+ */
+l_int32
+pixCountConnComp(PIX      *pixs,
+                 l_int32   connectivity,
+                 l_int32  *pcount)
+{
+l_int32   h, iszero;
+l_int32   x, y, xstart, ystart;
+PIX      *pixt;
+L_STACK  *stack, *auxstack;
+
+    PROCNAME("pixCountConnComp");
+
+    if (!pcount)
+        return ERROR_INT("&count not defined", procName, 1);
+    *pcount = 0;  /* initialize the count to 0 */
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (connectivity != 4 && connectivity != 8)
+        return ERROR_INT("connectivity not 4 or 8", procName, 1);
+
+    pixZero(pixs, &iszero);
+    if (iszero)
+        return 0;
+
+    if ((pixt = pixCopy(NULL, pixs)) == NULL)
+        return ERROR_INT("pixt not made", procName, 1);
+
+    h = pixGetDepth(pixs);
+    if ((stack = lstackCreate(h)) == NULL)
+        return ERROR_INT("stack not made", procName, 1);
+    if ((auxstack = lstackCreate(0)) == NULL)
+        return ERROR_INT("auxstack not made", procName, 1);
+    stack->auxstack = auxstack;
+
+    xstart = 0;
+    ystart = 0;
+    while (1)
+    {
+        if (!nextOnPixelInRaster(pixt, xstart, ystart, &x, &y))
+            break;
+
+        pixSeedfill(pixt, stack, x, y, connectivity);
+        (*pcount)++;
+        xstart = x;
+        ystart = y;
+    }
+
+        /* Cleanup, freeing the fillsegs on each stack */
+    lstackDestroy(&stack, TRUE);
+    pixDestroy(&pixt);
+
+    return 0;
+}
+
+
+/*!
+ *  nextOnPixelInRaster()
+ *
+ *      Input:  pixs (1 bpp)
+ *              xstart, ystart  (starting point for search)
+ *              &x, &y  (<return> coord value of next ON pixel)
+ *      Return: 1 if a pixel is found; 0 otherwise or on error
+ */
+l_int32
+nextOnPixelInRaster(PIX      *pixs,
+                    l_int32   xstart,
+                    l_int32   ystart,
+                    l_int32  *px,
+                    l_int32  *py)
+{
+l_int32    w, h, d, wpl;
+l_uint32  *data;
+
+    PROCNAME("nextOnPixelInRaster");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 0);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1)
+        return ERROR_INT("pixs not 1 bpp", procName, 0);
+
+    wpl = pixGetWpl(pixs);
+    data = pixGetData(pixs);
+    return nextOnPixelInRasterLow(data, w, h, wpl, xstart, ystart, px, py);
+}
+
+
+l_int32
+nextOnPixelInRasterLow(l_uint32  *data,
+                       l_int32    w,
+                       l_int32    h,
+                       l_int32    wpl,
+                       l_int32    xstart,
+                       l_int32    ystart,
+                       l_int32   *px,
+                       l_int32   *py)
+{
+l_int32    i, x, y, xend, startword;
+l_uint32  *line, *pword;
+
+        /* Look at the first word */
+    line = data + ystart * wpl;
+    pword = line + (xstart / 32);
+    if (*pword) {
+        xend = xstart - (xstart % 32) + 31;
+        for (x = xstart; x <= xend && x < w; x++) {
+            if (GET_DATA_BIT(line, x)) {
+                *px = x;
+                *py = ystart;
+                return 1;
+            }
+        }
+    }
+
+        /* Continue with the rest of the line */
+    startword = (xstart / 32) + 1;
+    x = 32 * startword;
+    for (pword = line + startword; x < w; pword++, x += 32) {
+        if (*pword) {
+            for (i = 0; i < 32 && x < w; i++, x++) {
+                if (GET_DATA_BIT(line, x)) {
+                    *px = x;
+                    *py = ystart;
+                    return 1;
+                }
+            }
+        }
+    }
+
+        /* Continue with following lines */
+    for (y = ystart + 1; y < h; y++) {
+        line = data + y * wpl;
+        for (pword = line, x = 0; x < w; pword++, x += 32) {
+            if (*pword) {
+                for (i = 0; i < 32 && x < w; i++, x++) {
+                    if (GET_DATA_BIT(line, x)) {
+                        *px = x;
+                        *py = y;
+                        return 1;
+                    }
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixSeedfillBB()
+ *
+ *      Input:  pixs (1 bpp)
+ *              stack (for holding fillsegs)
+ *              x,y   (location of seed pixel)
+ *              connectivity  (4 or 8)
+ *      Return: box or null on error
+ *
+ *  Notes:
+ *      (1) This is the high-level interface to Paul Heckbert's
+ *          stack-based seedfill algorithm.
+ */
+BOX *
+pixSeedfillBB(PIX      *pixs,
+              L_STACK  *stack,
+              l_int32   x,
+              l_int32   y,
+              l_int32   connectivity)
+{
+BOX  *box;
+
+    PROCNAME("pixSeedfillBB");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (BOX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (!stack)
+        return (BOX *)ERROR_PTR("stack not defined", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (BOX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+    if (connectivity == 4) {
+        if ((box = pixSeedfill4BB(pixs, stack, x, y)) == NULL)
+            return (BOX *)ERROR_PTR("box not made", procName, NULL);
+    } else if (connectivity == 8) {
+        if ((box = pixSeedfill8BB(pixs, stack, x, y)) == NULL)
+            return (BOX *)ERROR_PTR("box not made", procName, NULL);
+    } else {
+        return (BOX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+    }
+
+    return box;
+}
+
+
+/*!
+ *  pixSeedfill4BB()
+ *
+ *      Input:  pixs (1 bpp)
+ *              stack (for holding fillsegs)
+ *              x,y   (location of seed pixel)
+ *      Return: box or null on error.
+ *
+ *  Notes:
+ *      (1) This is Paul Heckbert's stack-based 4-cc seedfill algorithm.
+ *      (2) This operates on the input 1 bpp pix to remove the fg seed
+ *          pixel, at (x,y), and all pixels that are 4-connected to it.
+ *          The seed pixel at (x,y) must initially be ON.
+ *      (3) Returns the bounding box of the erased 4-cc component.
+ *      (4) Reference: see Paul Heckbert's stack-based seed fill algorithm
+ *          in "Graphic Gems", ed. Andrew Glassner, Academic
+ *          Press, 1990.  The algorithm description is given
+ *          on pp. 275-277; working C code is on pp. 721-722.)
+ *          The code here follows Heckbert's exactly, except
+ *          we use function calls instead of macros for
+ *          pushing data on and popping data off the stack.
+ *          This makes sense to do because Heckbert's fixed-size
+ *          stack with macros is dangerous: images exist that
+ *          will overrun the stack and crash.   The stack utility
+ *          here grows dynamically as needed, and the fillseg
+ *          structures that are not in use are stored in another
+ *          stack for reuse.  It should be noted that the
+ *          overhead in the function calls (vs. macros) is negligible.
+ */
+BOX *
+pixSeedfill4BB(PIX      *pixs,
+               L_STACK  *stack,
+               l_int32   x,
+               l_int32   y)
+{
+l_int32    w, h, xstart, wpl, x1, x2, dy;
+l_int32    xmax, ymax;
+l_int32    minx, maxx, miny, maxy;  /* for bounding box of this c.c. */
+l_uint32  *data, *line;
+BOX       *box;
+
+    PROCNAME("pixSeedfill4BB");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (BOX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (!stack)
+        return (BOX *)ERROR_PTR("stack not defined", procName, NULL);
+    if (!stack->auxstack)
+        stack->auxstack = lstackCreate(0);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    xmax = w - 1;
+    ymax = h - 1;
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    line = data + y * wpl;
+
+        /* Check pix value of seed; must be within the image and ON */
+    if (x < 0 || x > xmax || y < 0 || y > ymax || (GET_DATA_BIT(line, x) == 0))
+        return NULL;
+
+        /* Init stack to seed:
+         * Must first init b.b. values to prevent valgrind from complaining;
+         * then init b.b. boundaries correctly to seed.  */
+    minx = miny = 100000;
+    maxx = maxy = 0;
+    pushFillsegBB(stack, x, x, y, 1, ymax, &minx, &maxx, &miny, &maxy);
+    pushFillsegBB(stack, x, x, y + 1, -1, ymax, &minx, &maxx, &miny, &maxy);
+    minx = maxx = x;
+    miny = maxy = y;
+
+    while (lstackGetCount(stack) > 0)
+    {
+            /* Pop segment off stack and fill a neighboring scan line */
+        popFillseg(stack, &x1, &x2, &y, &dy);
+        line = data + y * wpl;
+
+            /* A segment of scanline y - dy for x1 <= x <= x2 was
+             * previously filled.  We now explore adjacent pixels
+             * in scan line y.  There are three regions: to the
+             * left of x1 - 1, between x1 and x2, and to the right of x2.
+             * These regions are handled differently.  Leaks are
+             * possible expansions beyond the previous segment and
+             * going back in the -dy direction.  These can happen
+             * for x < x1 - 1 and for x > x2 + 1.  Any "leak" segments
+             * are plugged with a push in the -dy (opposite) direction.
+             * And any segments found anywhere are always extended
+             * in the +dy direction.  */
+        for (x = x1; x >= 0 && (GET_DATA_BIT(line, x) == 1); x--)
+            CLEAR_DATA_BIT(line,x);
+        if (x >= x1)  /* pix at x1 was off and was not cleared */
+            goto skip;
+        xstart = x + 1;
+        if (xstart < x1 - 1)   /* leak on left? */
+            pushFillsegBB(stack, xstart, x1 - 1, y, -dy,
+                          ymax, &minx, &maxx, &miny, &maxy);
+
+        x = x1 + 1;
+        do {
+            for (; x <= xmax && (GET_DATA_BIT(line, x) == 1); x++)
+                CLEAR_DATA_BIT(line, x);
+            pushFillsegBB(stack, xstart, x - 1, y, dy,
+                          ymax, &minx, &maxx, &miny, &maxy);
+            if (x > x2 + 1)   /* leak on right? */
+                pushFillsegBB(stack, x2 + 1, x - 1, y, -dy,
+                              ymax, &minx, &maxx, &miny, &maxy);
+    skip:   for (x++; x <= x2 &&
+                      x <= xmax &&
+                      (GET_DATA_BIT(line, x) == 0); x++)
+                ;
+            xstart = x;
+        } while (x <= x2 && x <= xmax);
+    }
+
+    if ((box = boxCreate(minx, miny, maxx - minx + 1, maxy - miny + 1))
+            == NULL)
+        return (BOX *)ERROR_PTR("box not made", procName, NULL);
+    return box;
+}
+
+
+/*!
+ *  pixSeedfill8BB()
+ *
+ *      Input:  pixs (1 bpp)
+ *              stack (for holding fillsegs)
+ *              x,y   (location of seed pixel)
+ *      Return: box or null on error.
+ *
+ *  Notes:
+ *      (1) This is Paul Heckbert's stack-based 8-cc seedfill algorithm.
+ *      (2) This operates on the input 1 bpp pix to remove the fg seed
+ *          pixel, at (x,y), and all pixels that are 8-connected to it.
+ *          The seed pixel at (x,y) must initially be ON.
+ *      (3) Returns the bounding box of the erased 8-cc component.
+ *      (4) Reference: see Paul Heckbert's stack-based seed fill algorithm
+ *          in "Graphic Gems", ed. Andrew Glassner, Academic
+ *          Press, 1990.  The algorithm description is given
+ *          on pp. 275-277; working C code is on pp. 721-722.)
+ *          The code here follows Heckbert's closely, except
+ *          the leak checks are changed for 8 connectivity.
+ *          See comments on pixSeedfill4BB() for more details.
+ */
+BOX *
+pixSeedfill8BB(PIX      *pixs,
+               L_STACK  *stack,
+               l_int32   x,
+               l_int32   y)
+{
+l_int32    w, h, xstart, wpl, x1, x2, dy;
+l_int32    xmax, ymax;
+l_int32    minx, maxx, miny, maxy;  /* for bounding box of this c.c. */
+l_uint32  *data, *line;
+BOX       *box;
+
+    PROCNAME("pixSeedfill8BB");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (BOX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (!stack)
+        return (BOX *)ERROR_PTR("stack not defined", procName, NULL);
+    if (!stack->auxstack)
+        stack->auxstack = lstackCreate(0);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    xmax = w - 1;
+    ymax = h - 1;
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    line = data + y * wpl;
+
+        /* Check pix value of seed; must be ON */
+    if (x < 0 || x > xmax || y < 0 || y > ymax || (GET_DATA_BIT(line, x) == 0))
+        return NULL;
+
+        /* Init stack to seed:
+         * Must first init b.b. values to prevent valgrind from complaining;
+         * then init b.b. boundaries correctly to seed.  */
+    minx = miny = 100000;
+    maxx = maxy = 0;
+    pushFillsegBB(stack, x, x, y, 1, ymax, &minx, &maxx, &miny, &maxy);
+    pushFillsegBB(stack, x, x, y + 1, -1, ymax, &minx, &maxx, &miny, &maxy);
+    minx = maxx = x;
+    miny = maxy = y;
+
+    while (lstackGetCount(stack) > 0)
+    {
+            /* Pop segment off stack and fill a neighboring scan line */
+        popFillseg(stack, &x1, &x2, &y, &dy);
+        line = data + y * wpl;
+
+            /* A segment of scanline y - dy for x1 <= x <= x2 was
+             * previously filled.  We now explore adjacent pixels
+             * in scan line y.  There are three regions: to the
+             * left of x1, between x1 and x2, and to the right of x2.
+             * These regions are handled differently.  Leaks are
+             * possible expansions beyond the previous segment and
+             * going back in the -dy direction.  These can happen
+             * for x < x1 and for x > x2.  Any "leak" segments
+             * are plugged with a push in the -dy (opposite) direction.
+             * And any segments found anywhere are always extended
+             * in the +dy direction.  */
+        for (x = x1 - 1; x >= 0 && (GET_DATA_BIT(line, x) == 1); x--)
+            CLEAR_DATA_BIT(line,x);
+        if (x >= x1 - 1)  /* pix at x1 - 1 was off and was not cleared */
+            goto skip;
+        xstart = x + 1;
+        if (xstart < x1)   /* leak on left? */
+            pushFillsegBB(stack, xstart, x1 - 1, y, -dy,
+                          ymax, &minx, &maxx, &miny, &maxy);
+
+        x = x1;
+        do {
+            for (; x <= xmax && (GET_DATA_BIT(line, x) == 1); x++)
+                CLEAR_DATA_BIT(line, x);
+            pushFillsegBB(stack, xstart, x - 1, y, dy,
+                          ymax, &minx, &maxx, &miny, &maxy);
+            if (x > x2)   /* leak on right? */
+                pushFillsegBB(stack, x2 + 1, x - 1, y, -dy,
+                              ymax, &minx, &maxx, &miny, &maxy);
+    skip:   for (x++; x <= x2 + 1 &&
+                      x <= xmax &&
+                      (GET_DATA_BIT(line, x) == 0); x++)
+                ;
+            xstart = x;
+        } while (x <= x2 + 1 && x <= xmax);
+    }
+
+    if ((box = boxCreate(minx, miny, maxx - minx + 1, maxy - miny + 1))
+            == NULL)
+        return (BOX *)ERROR_PTR("box not made", procName, NULL);
+    return box;
+}
+
+
+/*!
+ *  pixSeedfill()
+ *
+ *      Input:  pixs (1 bpp)
+ *              stack (for holding fillsegs)
+ *              x,y   (location of seed pixel)
+ *              connectivity  (4 or 8)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This removes the component from pixs with a fg pixel at (x,y).
+ *      (2) See pixSeedfill4() and pixSeedfill8() for details.
+ */
+l_int32
+pixSeedfill(PIX      *pixs,
+            L_STACK  *stack,
+            l_int32   x,
+            l_int32   y,
+            l_int32   connectivity)
+{
+l_int32  retval;
+
+    PROCNAME("pixSeedfill");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (!stack)
+        return ERROR_INT("stack not defined", procName, 1);
+    if (connectivity != 4 && connectivity != 8)
+        return ERROR_INT("connectivity not 4 or 8", procName, 1);
+
+    if (connectivity == 4)
+        retval = pixSeedfill4(pixs, stack, x, y);
+    else  /* connectivity == 8  */
+        retval = pixSeedfill8(pixs, stack, x, y);
+
+    return retval;
+}
+
+
+/*!
+ *  pixSeedfill4()
+ *
+ *      Input:  pixs (1 bpp)
+ *              stack (for holding fillsegs)
+ *              x,y   (location of seed pixel)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is Paul Heckbert's stack-based 4-cc seedfill algorithm.
+ *      (2) This operates on the input 1 bpp pix to remove the fg seed
+ *          pixel, at (x,y), and all pixels that are 4-connected to it.
+ *          The seed pixel at (x,y) must initially be ON.
+ *      (3) Reference: see pixSeedFill4BB()
+ */
+l_int32
+pixSeedfill4(PIX      *pixs,
+             L_STACK  *stack,
+             l_int32   x,
+             l_int32   y)
+{
+l_int32    w, h, xstart, wpl, x1, x2, dy;
+l_int32    xmax, ymax;
+l_uint32  *data, *line;
+
+    PROCNAME("pixSeedfill4");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (!stack)
+        return ERROR_INT("stack not defined", procName, 1);
+    if (!stack->auxstack)
+        stack->auxstack = lstackCreate(0);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    xmax = w - 1;
+    ymax = h - 1;
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    line = data + y * wpl;
+
+        /* Check pix value of seed; must be within the image and ON */
+    if (x < 0 || x > xmax || y < 0 || y > ymax || (GET_DATA_BIT(line, x) == 0))
+        return 0;
+
+        /* Init stack to seed */
+    pushFillseg(stack, x, x, y, 1, ymax);
+    pushFillseg(stack, x, x, y + 1, -1, ymax);
+
+    while (lstackGetCount(stack) > 0)
+    {
+            /* Pop segment off stack and fill a neighboring scan line */
+        popFillseg(stack, &x1, &x2, &y, &dy);
+        line = data + y * wpl;
+
+            /* A segment of scanline y - dy for x1 <= x <= x2 was
+             * previously filled.  We now explore adjacent pixels
+             * in scan line y.  There are three regions: to the
+             * left of x1 - 1, between x1 and x2, and to the right of x2.
+             * These regions are handled differently.  Leaks are
+             * possible expansions beyond the previous segment and
+             * going back in the -dy direction.  These can happen
+             * for x < x1 - 1 and for x > x2 + 1.  Any "leak" segments
+             * are plugged with a push in the -dy (opposite) direction.
+             * And any segments found anywhere are always extended
+             * in the +dy direction.  */
+        for (x = x1; x >= 0 && (GET_DATA_BIT(line, x) == 1); x--)
+            CLEAR_DATA_BIT(line,x);
+        if (x >= x1)  /* pix at x1 was off and was not cleared */
+            goto skip;
+        xstart = x + 1;
+        if (xstart < x1 - 1)   /* leak on left? */
+            pushFillseg(stack, xstart, x1 - 1, y, -dy, ymax);
+
+        x = x1 + 1;
+        do {
+            for (; x <= xmax && (GET_DATA_BIT(line, x) == 1); x++)
+                CLEAR_DATA_BIT(line, x);
+            pushFillseg(stack, xstart, x - 1, y, dy, ymax);
+            if (x > x2 + 1)   /* leak on right? */
+                pushFillseg(stack, x2 + 1, x - 1, y, -dy, ymax);
+    skip:   for (x++; x <= x2 &&
+                      x <= xmax &&
+                      (GET_DATA_BIT(line, x) == 0); x++)
+                ;
+            xstart = x;
+        } while (x <= x2 && x <= xmax);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixSeedfill8()
+ *
+ *      Input:  pixs (1 bpp)
+ *              stack (for holding fillsegs)
+ *              x,y   (location of seed pixel)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is Paul Heckbert's stack-based 8-cc seedfill algorithm.
+ *      (2) This operates on the input 1 bpp pix to remove the fg seed
+ *          pixel, at (x,y), and all pixels that are 8-connected to it.
+ *          The seed pixel at (x,y) must initially be ON.
+ *      (3) Reference: see pixSeedFill8BB()
+ */
+l_int32
+pixSeedfill8(PIX      *pixs,
+             L_STACK  *stack,
+             l_int32   x,
+             l_int32   y)
+{
+l_int32    w, h, xstart, wpl, x1, x2, dy;
+l_int32    xmax, ymax;
+l_uint32  *data, *line;
+
+    PROCNAME("pixSeedfill8");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (!stack)
+        return ERROR_INT("stack not defined", procName, 1);
+    if (!stack->auxstack)
+        stack->auxstack = lstackCreate(0);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    xmax = w - 1;
+    ymax = h - 1;
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    line = data + y * wpl;
+
+        /* Check pix value of seed; must be ON */
+    if (x < 0 || x > xmax || y < 0 || y > ymax || (GET_DATA_BIT(line, x) == 0))
+        return 0;
+
+        /* Init stack to seed */
+    pushFillseg(stack, x, x, y, 1, ymax);
+    pushFillseg(stack, x, x, y + 1, -1, ymax);
+
+    while (lstackGetCount(stack) > 0)
+    {
+            /* Pop segment off stack and fill a neighboring scan line */
+        popFillseg(stack, &x1, &x2, &y, &dy);
+        line = data + y * wpl;
+
+            /* A segment of scanline y - dy for x1 <= x <= x2 was
+             * previously filled.  We now explore adjacent pixels
+             * in scan line y.  There are three regions: to the
+             * left of x1, between x1 and x2, and to the right of x2.
+             * These regions are handled differently.  Leaks are
+             * possible expansions beyond the previous segment and
+             * going back in the -dy direction.  These can happen
+             * for x < x1 and for x > x2.  Any "leak" segments
+             * are plugged with a push in the -dy (opposite) direction.
+             * And any segments found anywhere are always extended
+             * in the +dy direction.  */
+        for (x = x1 - 1; x >= 0 && (GET_DATA_BIT(line, x) == 1); x--)
+            CLEAR_DATA_BIT(line,x);
+        if (x >= x1 - 1)  /* pix at x1 - 1 was off and was not cleared */
+            goto skip;
+        xstart = x + 1;
+        if (xstart < x1)   /* leak on left? */
+            pushFillseg(stack, xstart, x1 - 1, y, -dy, ymax);
+
+        x = x1;
+        do {
+            for (; x <= xmax && (GET_DATA_BIT(line, x) == 1); x++)
+                CLEAR_DATA_BIT(line, x);
+            pushFillseg(stack, xstart, x - 1, y, dy, ymax);
+            if (x > x2)   /* leak on right? */
+                pushFillseg(stack, x2 + 1, x - 1, y, -dy, ymax);
+    skip:   for (x++; x <= x2 + 1 &&
+                      x <= xmax &&
+                      (GET_DATA_BIT(line, x) == 0); x++)
+                ;
+            xstart = x;
+        } while (x <= x2 + 1 && x <= xmax);
+    }
+
+    return 0;
+}
+
+
+
+/*-----------------------------------------------------------------------*
+ *          Static stack helper functions: push and pop fillsegs         *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pushFillsegBB()
+ *
+ *      Input:  stack
+ *              xleft, xright
+ *              y
+ *              dy
+ *              ymax,
+ *              &minx (<return>)
+ *              &maxx (<return>)
+ *              &miny (<return>)
+ *              &maxy (<return>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This adds a line segment to the stack, and returns its size.
+ *      (2) The auxiliary stack is used as a storage area to recycle
+ *          fillsegs that are no longer in use.  We only calloc new
+ *          fillsegs if the auxiliary stack is empty.
+ */
+static void
+pushFillsegBB(L_STACK  *stack,
+              l_int32   xleft,
+              l_int32   xright,
+              l_int32   y,
+              l_int32   dy,
+              l_int32   ymax,
+              l_int32  *pminx,
+              l_int32  *pmaxx,
+              l_int32  *pminy,
+              l_int32  *pmaxy)
+{
+FILLSEG  *fseg;
+L_STACK  *auxstack;
+
+    PROCNAME("pushFillsegBB");
+
+    if (!stack) {
+        L_ERROR("stack not defined\n", procName);
+        return;
+    }
+
+    *pminx = L_MIN(*pminx, xleft);
+    *pmaxx = L_MAX(*pmaxx, xright);
+    *pminy = L_MIN(*pminy, y);
+    *pmaxy = L_MAX(*pmaxy, y);
+
+    if (y + dy >= 0 && y + dy <= ymax) {
+        if ((auxstack = stack->auxstack) == NULL) {
+            L_ERROR("auxstack not defined\n", procName);
+            return;
+        }
+
+            /* Get a fillseg to use */
+        if (lstackGetCount(auxstack) > 0) {
+            fseg = (FILLSEG *)lstackRemove(auxstack);
+        } else {
+            if ((fseg = (FILLSEG *)LEPT_CALLOC(1, sizeof(FILLSEG))) == NULL) {
+                L_ERROR("fillseg not made\n", procName);
+                return;
+            }
+        }
+
+        fseg->xleft = xleft;
+        fseg->xright = xright;
+        fseg->y = y;
+        fseg->dy = dy;
+        lstackAdd(stack, fseg);
+    }
+    return;
+}
+
+
+/*!
+ *  pushFillseg()
+ *
+ *      Input:  stack
+ *              xleft, xright
+ *              y
+ *              dy
+ *              ymax
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This adds a line segment to the stack.
+ *      (2) The auxiliary stack is used as a storage area to recycle
+ *          fillsegs that are no longer in use.  We only calloc new
+ *          fillsegs if the auxiliary stack is empty.
+ */
+static void
+pushFillseg(L_STACK  *stack,
+            l_int32   xleft,
+            l_int32   xright,
+            l_int32   y,
+            l_int32   dy,
+            l_int32   ymax)
+{
+FILLSEG  *fseg;
+L_STACK  *auxstack;
+
+    PROCNAME("pushFillseg");
+
+    if (!stack) {
+        L_ERROR("stack not defined\n", procName);
+        return;
+    }
+
+    if (y + dy >= 0 && y + dy <= ymax) {
+        if ((auxstack = stack->auxstack) == NULL) {
+            L_ERROR("auxstack not defined\n", procName);
+            return;
+        }
+
+            /* Get a fillseg to use */
+        if (lstackGetCount(auxstack) > 0) {
+            fseg = (FILLSEG *)lstackRemove(auxstack);
+        } else {
+            if ((fseg = (FILLSEG *)LEPT_CALLOC(1, sizeof(FILLSEG))) == NULL) {
+                L_ERROR("fillseg not made\n", procName);
+                return;
+            }
+        }
+
+        fseg->xleft = xleft;
+        fseg->xright = xright;
+        fseg->y = y;
+        fseg->dy = dy;
+        lstackAdd(stack, fseg);
+    }
+    return;
+}
+
+
+/*!
+ *  popFillseg()
+ *
+ *      Input:  stack
+ *              &xleft (<return>)
+ *              &xright (<return>)
+ *              &y (<return>)
+ *              &dy (<return>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This removes a line segment from the stack, and returns its size.
+ *      (2) The surplussed fillseg is placed on the auxiliary stack
+ *          for future use.
+ */
+static void
+popFillseg(L_STACK  *stack,
+           l_int32  *pxleft,
+           l_int32  *pxright,
+           l_int32  *py,
+           l_int32  *pdy)
+{
+FILLSEG  *fseg;
+L_STACK  *auxstack;
+
+    PROCNAME("popFillseg");
+
+    if (!stack) {
+        L_ERROR("stack not defined\n", procName);
+        return;
+    }
+    if ((auxstack = stack->auxstack) == NULL) {
+        L_ERROR("auxstack not defined\n", procName);
+        return;
+    }
+
+    if ((fseg = (FILLSEG *)lstackRemove(stack)) == NULL)
+        return;
+
+    *pxleft = fseg->xleft;
+    *pxright = fseg->xright;
+    *py = fseg->y + fseg->dy;  /* this now points to the new line */
+    *pdy = fseg->dy;
+
+        /* Save it for re-use */
+    lstackAdd(auxstack, fseg);
+    return;
+}
diff --git a/src/convertfiles.c b/src/convertfiles.c
new file mode 100644 (file)
index 0000000..204eaca
--- /dev/null
@@ -0,0 +1,141 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  convertfiles.c
+ *
+ *      Conversion to 1 bpp
+ *          l_int32    convertFilesTo1bpp()
+ *
+ *  These are utility functions that will perform depth conversion
+ *  on selected files, writing the results to a specified directory.
+ *  We start with conversion to 1 bpp.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+
+/*------------------------------------------------------------------*
+ *                        Conversion to 1 bpp                       *
+ *------------------------------------------------------------------*/
+/*!
+ *  convertFilesTo1bpp()
+ *
+ *      Input:  dirin
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              upscaling (1, 2 or 4; only for input color or grayscale)
+ *              thresh  (global threshold for binarization; use 0 for default)
+ *              firstpage
+ *              npages (use 0 to do all from @firstpage to the end)
+ *              dirout
+ *              outformat (IFF_PNG, IFF_TIFF_G4)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Images are sorted lexicographically, and the names in the
+ *          output directory are retained except for the extension.
+ */
+l_int32
+convertFilesTo1bpp(const char  *dirin,
+                   const char  *substr,
+                   l_int32      upscaling,
+                   l_int32      thresh,
+                   l_int32      firstpage,
+                   l_int32      npages,
+                   const char  *dirout,
+                   l_int32      outformat)
+{
+l_int32  i, nfiles;
+char     buf[512];
+char    *fname, *tail, *basename;
+PIX     *pixs, *pixg1, *pixg2, *pixb;
+SARRAY  *safiles;
+
+    PROCNAME("convertFilesTo1bpp");
+
+    if (!dirin)
+        return ERROR_INT("dirin", procName, 1);
+    if (!dirout)
+        return ERROR_INT("dirout", procName, 1);
+    if (upscaling != 1 && upscaling != 2 && upscaling != 4)
+        return ERROR_INT("invalid upscaling factor", procName, 1);
+    if (thresh <= 0) thresh = 180;
+    if (firstpage < 0) firstpage = 0;
+    if (npages < 0) npages = 0;
+    if (outformat != IFF_TIFF_G4)
+        outformat = IFF_PNG;
+
+    safiles = getSortedPathnamesInDirectory(dirin, substr, firstpage, npages);
+    if (!safiles)
+        return ERROR_INT("safiles not made", procName, 1);
+    if ((nfiles = sarrayGetCount(safiles)) == 0) {
+        sarrayDestroy(&safiles);
+        return ERROR_INT("no matching files in the directory", procName, 1);
+    }
+
+    for (i = 0; i < nfiles; i++) {
+        fname = sarrayGetString(safiles, i, L_NOCOPY);
+        if ((pixs = pixRead(fname)) == NULL) {
+            L_WARNING("Couldn't read file %s\n", procName, fname);
+            continue;
+        }
+        if (pixGetDepth(pixs) == 32)
+            pixg1 = pixConvertRGBToLuminance(pixs);
+        else
+            pixg1 = pixClone(pixs);
+        pixg2 = pixRemoveColormap(pixg1, REMOVE_CMAP_TO_GRAYSCALE);
+        if (pixGetDepth(pixg2) == 1) {
+            pixb = pixClone(pixg2);
+        } else {
+            if (upscaling == 1)
+                pixb = pixThresholdToBinary(pixg2, thresh);
+            else if (upscaling == 2)
+                pixb = pixScaleGray2xLIThresh(pixg2, thresh);
+            else  /* upscaling == 4 */
+                pixb = pixScaleGray4xLIThresh(pixg2, thresh);
+        }
+        pixDestroy(&pixs);
+        pixDestroy(&pixg1);
+        pixDestroy(&pixg2);
+
+        splitPathAtDirectory(fname, NULL, &tail);
+        splitPathAtExtension(tail, &basename, NULL);
+        if (outformat == IFF_TIFF_G4) {
+            snprintf(buf, sizeof(buf), "%s/%s.tif", dirout, basename);
+            pixWrite(buf, pixb, IFF_TIFF_G4);
+        } else {
+            snprintf(buf, sizeof(buf), "%s/%s.png", dirout, basename);
+            pixWrite(buf, pixb, IFF_PNG);
+        }
+        pixDestroy(&pixb);
+        LEPT_FREE(tail);
+        LEPT_FREE(basename);
+    }
+
+    sarrayDestroy(&safiles);
+    return 0;
+}
diff --git a/src/convolve.c b/src/convolve.c
new file mode 100644 (file)
index 0000000..830da4f
--- /dev/null
@@ -0,0 +1,2497 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  convolve.c
+ *
+ *      Top level grayscale or color block convolution
+ *          PIX          *pixBlockconv()
+ *
+ *      Grayscale block convolution
+ *          PIX          *pixBlockconvGray()
+ *          static void   blockconvLow()
+ *
+ *      Accumulator for 1, 8 and 32 bpp convolution
+ *          PIX          *pixBlockconvAccum()
+ *          static void   blockconvAccumLow()
+ *
+ *      Un-normalized grayscale block convolution
+ *          PIX          *pixBlockconvGrayUnnormalized()
+ *
+ *      Tiled grayscale or color block convolution
+ *          PIX          *pixBlockconvTiled()
+ *          PIX          *pixBlockconvGrayTile()
+ *
+ *      Convolution for mean, mean square, variance and rms deviation
+ *      in specified window
+ *          l_int32       pixWindowedStats()
+ *          PIX          *pixWindowedMean()
+ *          PIX          *pixWindowedMeanSquare()
+ *          l_int32       pixWindowedVariance()
+ *          DPIX         *pixMeanSquareAccum()
+ *
+ *      Binary block sum and rank filter
+ *          PIX          *pixBlockrank()
+ *          PIX          *pixBlocksum()
+ *          static void   blocksumLow()
+ *
+ *      Census transform
+ *          PIX          *pixCensusTransform()
+ *
+ *      Generic convolution (with Pix)
+ *          PIX          *pixConvolve()
+ *          PIX          *pixConvolveSep()
+ *          PIX          *pixConvolveRGB()
+ *          PIX          *pixConvolveRGBSep()
+ *
+ *      Generic convolution (with float arrays)
+ *          FPIX         *fpixConvolve()
+ *          FPIX         *fpixConvolveSep()
+ *
+ *      Convolution with bias (for non-negative output)
+ *          PIX          *pixConvolveWithBias()
+ *
+ *      Set parameter for convolution subsampling
+ *          void          l_setConvolveSampling()
+ *
+ *      Additive gaussian noise
+ *          PIX          *pixAddGaussNoise()
+ *          l_float32     gaussDistribSampling()
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+    /* These globals determine the subsampling factors for
+     * generic convolution of pix and fpix.  Declare extern to use.
+     * To change the values, use l_setConvolveSampling(). */
+LEPT_DLL l_int32  ConvolveSamplingFactX = 1;
+LEPT_DLL l_int32  ConvolveSamplingFactY = 1;
+
+    /* Low-level static functions */
+static void blockconvLow(l_uint32 *data, l_int32 w, l_int32 h, l_int32 wpl,
+                         l_uint32 *dataa, l_int32 wpla, l_int32 wc,
+                         l_int32 hc);
+static void blockconvAccumLow(l_uint32 *datad, l_int32 w, l_int32 h,
+                              l_int32 wpld, l_uint32 *datas, l_int32 d,
+                              l_int32 wpls);
+static void blocksumLow(l_uint32 *datad, l_int32 w, l_int32 h, l_int32 wpl,
+                        l_uint32 *dataa, l_int32 wpla, l_int32 wc, l_int32 hc);
+
+
+/*----------------------------------------------------------------------*
+ *             Top-level grayscale or color block convolution           *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixBlockconv()
+ *
+ *      Input:  pix (8 or 32 bpp; or 2, 4 or 8 bpp with colormap)
+ *              wc, hc   (half width/height of convolution kernel)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The full width and height of the convolution kernel
+ *          are (2 * wc + 1) and (2 * hc + 1)
+ *      (2) Returns a copy if both wc and hc are 0
+ *      (3) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
+ *          where (w,h) are the dimensions of pixs.
+ */
+PIX  *
+pixBlockconv(PIX     *pix,
+             l_int32  wc,
+             l_int32  hc)
+{
+l_int32  w, h, d;
+PIX     *pixs, *pixd, *pixr, *pixrc, *pixg, *pixgc, *pixb, *pixbc;
+
+    PROCNAME("pixBlockconv");
+
+    if (!pix)
+        return (PIX *)ERROR_PTR("pix not defined", procName, NULL);
+    if (wc < 0) wc = 0;
+    if (hc < 0) hc = 0;
+    pixGetDimensions(pix, &w, &h, &d);
+    if (w < 2 * wc + 1 || h < 2 * hc + 1) {
+        wc = L_MIN(wc, (w - 1) / 2);
+        hc = L_MIN(hc, (h - 1) / 2);
+        L_WARNING("kernel too large; reducing!\n", procName);
+        L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
+    }
+    if (wc == 0 && hc == 0)   /* no-op */
+        return pixCopy(NULL, pix);
+
+        /* Remove colormap if necessary */
+    if ((d == 2 || d == 4 || d == 8) && pixGetColormap(pix)) {
+        L_WARNING("pix has colormap; removing\n", procName);
+        pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+        d = pixGetDepth(pixs);
+    } else {
+        pixs = pixClone(pix);
+    }
+
+    if (d != 8 && d != 32) {
+        pixDestroy(&pixs);
+        return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, NULL);
+    }
+
+    if (d == 8) {
+        pixd = pixBlockconvGray(pixs, NULL, wc, hc);
+    } else { /* d == 32 */
+        pixr = pixGetRGBComponent(pixs, COLOR_RED);
+        pixrc = pixBlockconvGray(pixr, NULL, wc, hc);
+        pixDestroy(&pixr);
+        pixg = pixGetRGBComponent(pixs, COLOR_GREEN);
+        pixgc = pixBlockconvGray(pixg, NULL, wc, hc);
+        pixDestroy(&pixg);
+        pixb = pixGetRGBComponent(pixs, COLOR_BLUE);
+        pixbc = pixBlockconvGray(pixb, NULL, wc, hc);
+        pixDestroy(&pixb);
+        pixd = pixCreateRGBImage(pixrc, pixgc, pixbc);
+        pixDestroy(&pixrc);
+        pixDestroy(&pixgc);
+        pixDestroy(&pixbc);
+    }
+
+    pixDestroy(&pixs);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                     Grayscale block convolution                      *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixBlockconvGray()
+ *
+ *      Input:  pix (8 bpp)
+ *              accum pix (32 bpp; can be null)
+ *              wc, hc   (half width/height of convolution kernel)
+ *      Return: pix (8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) If accum pix is null, make one and destroy it before
+ *          returning; otherwise, just use the input accum pix.
+ *      (2) The full width and height of the convolution kernel
+ *          are (2 * wc + 1) and (2 * hc + 1).
+ *      (3) Returns a copy if both wc and hc are 0.
+ *      (4) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
+ *          where (w,h) are the dimensions of pixs.
+ */
+PIX *
+pixBlockconvGray(PIX     *pixs,
+                 PIX     *pixacc,
+                 l_int32  wc,
+                 l_int32  hc)
+{
+l_int32    w, h, d, wpl, wpla;
+l_uint32  *datad, *dataa;
+PIX       *pixd, *pixt;
+
+    PROCNAME("pixBlockconvGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (wc < 0) wc = 0;
+    if (hc < 0) hc = 0;
+    if (w < 2 * wc + 1 || h < 2 * hc + 1) {
+        wc = L_MIN(wc, (w - 1) / 2);
+        hc = L_MIN(hc, (h - 1) / 2);
+        L_WARNING("kernel too large; reducing!\n", procName);
+        L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
+    }
+    if (wc == 0 && hc == 0)   /* no-op */
+        return pixCopy(NULL, pixs);
+
+    if (pixacc) {
+        if (pixGetDepth(pixacc) == 32) {
+            pixt = pixClone(pixacc);
+        } else {
+            L_WARNING("pixacc not 32 bpp; making new one\n", procName);
+            if ((pixt = pixBlockconvAccum(pixs)) == NULL)
+                return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        }
+    } else {
+        if ((pixt = pixBlockconvAccum(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+    }
+
+    if ((pixd = pixCreateTemplate(pixs)) == NULL) {
+        pixDestroy(&pixt);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+
+    wpl = pixGetWpl(pixs);
+    wpla = pixGetWpl(pixt);
+    datad = pixGetData(pixd);
+    dataa = pixGetData(pixt);
+    blockconvLow(datad, w, h, wpl, dataa, wpla, wc, hc);
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  blockconvLow()
+ *
+ *      Input:  data   (data of input image, to be convolved)
+ *              w, h, wpl
+ *              dataa    (data of 32 bpp accumulator)
+ *              wpla     (accumulator)
+ *              wc      (convolution "half-width")
+ *              hc      (convolution "half-height")
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) The full width and height of the convolution kernel
+ *          are (2 * wc + 1) and (2 * hc + 1).
+ *      (2) The lack of symmetry between the handling of the
+ *          first (hc + 1) lines and the last (hc) lines,
+ *          and similarly with the columns, is due to fact that
+ *          for the pixel at (x,y), the accumulator values are
+ *          taken at (x + wc, y + hc), (x - wc - 1, y + hc),
+ *          (x + wc, y - hc - 1) and (x - wc - 1, y - hc - 1).
+ *      (3) We compute sums, normalized as if there were no reduced
+ *          area at the boundary.  This under-estimates the value
+ *          of the boundary pixels, so we multiply them by another
+ *          normalization factor that is greater than 1.
+ *      (4) This second normalization is done first for the first
+ *          hc + 1 lines; then for the last hc lines; and finally
+ *          for the first wc + 1 and last wc columns in the intermediate
+ *          lines.
+ *      (5) The caller should verify that wc < w and hc < h.
+ *          Under those conditions, illegal reads and writes can occur.
+ *      (6) Implementation note: to get the same results in the interior
+ *          between this function and pixConvolve(), it is necessary to
+ *          add 0.5 for roundoff in the main loop that runs over all pixels.
+ *          However, if we do that and have white (255) pixels near the
+ *          image boundary, some overflow occurs for pixels very close
+ *          to the boundary.  We can't fix this by subtracting from the
+ *          normalized values for the boundary pixels, because this results
+ *          in underflow if the boundary pixels are black (0).  Empirically,
+ *          adding 0.25 (instead of 0.5) before truncating in the main
+ *          loop will not cause overflow, but this gives some
+ *          off-by-1-level errors in interior pixel values.  So we add
+ *          0.5 for roundoff in the main loop, and for pixels within a
+ *          half filter width of the boundary, use a L_MIN of the
+ *          computed value and 255 to avoid overflow during normalization.
+ */
+static void
+blockconvLow(l_uint32  *data,
+             l_int32    w,
+             l_int32    h,
+             l_int32    wpl,
+             l_uint32  *dataa,
+             l_int32    wpla,
+             l_int32    wc,
+             l_int32    hc)
+{
+l_int32    i, j, imax, imin, jmax, jmin;
+l_int32    wn, hn, fwc, fhc, wmwc, hmhc;
+l_float32  norm, normh, normw;
+l_uint32   val;
+l_uint32  *linemina, *linemaxa, *line;
+
+    PROCNAME("blockconvLow");
+
+    wmwc = w - wc;
+    hmhc = h - hc;
+    if (wmwc <= 0 || hmhc <= 0) {
+        L_ERROR("wc >= w || hc >=h\n", procName);
+        return;
+    }
+    fwc = 2 * wc + 1;
+    fhc = 2 * hc + 1;
+    norm = 1. / (fwc * fhc);
+
+        /*------------------------------------------------------------*
+         *  Compute, using b.c. only to set limits on the accum image *
+         *------------------------------------------------------------*/
+    for (i = 0; i < h; i++) {
+        imin = L_MAX(i - 1 - hc, 0);
+        imax = L_MIN(i + hc, h - 1);
+        line = data + wpl * i;
+        linemina = dataa + wpla * imin;
+        linemaxa = dataa + wpla * imax;
+        for (j = 0; j < w; j++) {
+            jmin = L_MAX(j - 1 - wc, 0);
+            jmax = L_MIN(j + wc, w - 1);
+            val = linemaxa[jmax] - linemaxa[jmin]
+                  + linemina[jmin] - linemina[jmax];
+            val = (l_uint8)(norm * val + 0.5);  /* see comment above */
+            SET_DATA_BYTE(line, j, val);
+        }
+    }
+
+        /*------------------------------------------------------------*
+         *             Fix normalization for boundary pixels          *
+         *------------------------------------------------------------*/
+    for (i = 0; i <= hc; i++) {    /* first hc + 1 lines */
+        hn = hc + i;
+        normh = (l_float32)fhc / (l_float32)hn;   /* > 1 */
+        line = data + wpl * i;
+        for (j = 0; j <= wc; j++) {
+            wn = wc + j;
+            normw = (l_float32)fwc / (l_float32)wn;   /* > 1 */
+            val = GET_DATA_BYTE(line, j);
+            val = (l_uint8)L_MIN(val * normh * normw, 255);
+            SET_DATA_BYTE(line, j, val);
+        }
+        for (j = wc + 1; j < wmwc; j++) {
+            val = GET_DATA_BYTE(line, j);
+            val = (l_uint8)L_MIN(val * normh, 255);
+            SET_DATA_BYTE(line, j, val);
+        }
+        for (j = wmwc; j < w; j++) {
+            wn = wc + w - j;
+            normw = (l_float32)fwc / (l_float32)wn;   /* > 1 */
+            val = GET_DATA_BYTE(line, j);
+            val = (l_uint8)L_MIN(val * normh * normw, 255);
+            SET_DATA_BYTE(line, j, val);
+        }
+    }
+
+    for (i = hmhc; i < h; i++) {  /* last hc lines */
+        hn = hc + h - i;
+        normh = (l_float32)fhc / (l_float32)hn;   /* > 1 */
+        line = data + wpl * i;
+        for (j = 0; j <= wc; j++) {
+            wn = wc + j;
+            normw = (l_float32)fwc / (l_float32)wn;   /* > 1 */
+            val = GET_DATA_BYTE(line, j);
+            val = (l_uint8)L_MIN(val * normh * normw, 255);
+            SET_DATA_BYTE(line, j, val);
+        }
+        for (j = wc + 1; j < wmwc; j++) {
+            val = GET_DATA_BYTE(line, j);
+            val = (l_uint8)L_MIN(val * normh, 255);
+            SET_DATA_BYTE(line, j, val);
+        }
+        for (j = wmwc; j < w; j++) {
+            wn = wc + w - j;
+            normw = (l_float32)fwc / (l_float32)wn;   /* > 1 */
+            val = GET_DATA_BYTE(line, j);
+            val = (l_uint8)L_MIN(val * normh * normw, 255);
+            SET_DATA_BYTE(line, j, val);
+        }
+    }
+
+    for (i = hc + 1; i < hmhc; i++) {    /* intermediate lines */
+        line = data + wpl * i;
+        for (j = 0; j <= wc; j++) {   /* first wc + 1 columns */
+            wn = wc + j;
+            normw = (l_float32)fwc / (l_float32)wn;   /* > 1 */
+            val = GET_DATA_BYTE(line, j);
+            val = (l_uint8)L_MIN(val * normw, 255);
+            SET_DATA_BYTE(line, j, val);
+        }
+        for (j = wmwc; j < w; j++) {   /* last wc columns */
+            wn = wc + w - j;
+            normw = (l_float32)fwc / (l_float32)wn;   /* > 1 */
+            val = GET_DATA_BYTE(line, j);
+            val = (l_uint8)L_MIN(val * normw, 255);
+            SET_DATA_BYTE(line, j, val);
+        }
+    }
+
+    return;
+}
+
+
+/*----------------------------------------------------------------------*
+ *              Accumulator for 1, 8 and 32 bpp convolution             *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixBlockconvAccum()
+ *
+ *      Input:  pixs (1, 8 or 32 bpp)
+ *      Return: accum pix (32 bpp), or null on error.
+ *
+ *  Notes:
+ *      (1) The general recursion relation is
+ *            a(i,j) = v(i,j) + a(i-1, j) + a(i, j-1) - a(i-1, j-1)
+ *          For the first line, this reduces to the special case
+ *            a(i,j) = v(i,j) + a(i, j-1)
+ *          For the first column, the special case is
+ *            a(i,j) = v(i,j) + a(i-1, j)
+ */
+PIX *
+pixBlockconvAccum(PIX  *pixs)
+{
+l_int32    w, h, d, wpls, wpld;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixBlockconvAccum");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 1, 8 or 32 bpp", procName, NULL);
+    if ((pixd = pixCreate(w, h, 32)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    blockconvAccumLow(datad, w, h, wpld, datas, d, wpls);
+
+    return pixd;
+}
+
+
+/*
+ *  blockconvAccumLow()
+ *
+ *      Input:  datad  (32 bpp dest)
+ *              w, h, wpld (of 32 bpp dest)
+ *              datas (1, 8 or 32 bpp src)
+ *              d (bpp of src)
+ *              wpls (of src)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) The general recursion relation is
+ *             a(i,j) = v(i,j) + a(i-1, j) + a(i, j-1) - a(i-1, j-1)
+ *          For the first line, this reduces to the special case
+ *             a(0,j) = v(0,j) + a(0, j-1), j > 0
+ *          For the first column, the special case is
+ *             a(i,0) = v(i,0) + a(i-1, 0), i > 0
+ */
+static void
+blockconvAccumLow(l_uint32  *datad,
+                  l_int32    w,
+                  l_int32    h,
+                  l_int32    wpld,
+                  l_uint32  *datas,
+                  l_int32    d,
+                  l_int32    wpls)
+{
+l_uint8    val;
+l_int32    i, j;
+l_uint32   val32;
+l_uint32  *lines, *lined, *linedp;
+
+    PROCNAME("blockconvAccumLow");
+
+    lines = datas;
+    lined = datad;
+
+    if (d == 1) {
+            /* Do the first line */
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BIT(lines, j);
+            if (j == 0)
+                lined[0] = val;
+            else
+                lined[j] = lined[j - 1] + val;
+        }
+
+            /* Do the other lines */
+        for (i = 1; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;  /* curr dest line */
+            linedp = lined - wpld;   /* prev dest line */
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_BIT(lines, j);
+                if (j == 0)
+                    lined[0] = val + linedp[0];
+                else
+                    lined[j] = val + lined[j - 1] + linedp[j] - linedp[j - 1];
+            }
+        }
+    } else if (d == 8) {
+            /* Do the first line */
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(lines, j);
+            if (j == 0)
+                lined[0] = val;
+            else
+                lined[j] = lined[j - 1] + val;
+        }
+
+            /* Do the other lines */
+        for (i = 1; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;  /* curr dest line */
+            linedp = lined - wpld;   /* prev dest line */
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_BYTE(lines, j);
+                if (j == 0)
+                    lined[0] = val + linedp[0];
+                else
+                    lined[j] = val + lined[j - 1] + linedp[j] - linedp[j - 1];
+            }
+        }
+    } else if (d == 32) {
+            /* Do the first line */
+        for (j = 0; j < w; j++) {
+            val32 = lines[j];
+            if (j == 0)
+                lined[0] = val32;
+            else
+                lined[j] = lined[j - 1] + val32;
+        }
+
+            /* Do the other lines */
+        for (i = 1; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;  /* curr dest line */
+            linedp = lined - wpld;   /* prev dest line */
+            for (j = 0; j < w; j++) {
+                val32 = lines[j];
+                if (j == 0)
+                    lined[0] = val32 + linedp[0];
+                else
+                    lined[j] = val32 + lined[j - 1] + linedp[j] - linedp[j - 1];
+            }
+        }
+    } else {
+        L_ERROR("depth not 1, 8 or 32 bpp\n", procName);
+    }
+
+    return;
+}
+
+
+/*----------------------------------------------------------------------*
+ *               Un-normalized grayscale block convolution              *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixBlockconvGrayUnnormalized()
+ *
+ *      Input:  pixs (8 bpp)
+ *              wc, hc   (half width/height of convolution kernel)
+ *      Return: pix (32 bpp; containing the convolution without normalizing
+ *                   for the window size), or null on error
+ *
+ *  Notes:
+ *      (1) The full width and height of the convolution kernel
+ *          are (2 * wc + 1) and (2 * hc + 1).
+ *      (2) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
+ *          where (w,h) are the dimensions of pixs.
+ *      (3) Returns a copy if both wc and hc are 0.
+ *      (3) Adds mirrored border to avoid treating the boundary pixels
+ *          specially.  Note that we add wc + 1 pixels to the left
+ *          and wc to the right.  The added width is 2 * wc + 1 pixels,
+ *          and the particular choice simplifies the indexing in the loop.
+ *          Likewise, add hc + 1 pixels to the top and hc to the bottom.
+ *      (4) To get the normalized result, divide by the area of the
+ *          convolution kernel: (2 * wc + 1) * (2 * hc + 1)
+ *          Specifically, do this:
+ *               pixc = pixBlockconvGrayUnnormalized(pixs, wc, hc);
+ *               fract = 1. / ((2 * wc + 1) * (2 * hc + 1));
+ *               pixMultConstantGray(pixc, fract);
+ *               pixd = pixGetRGBComponent(pixc, L_ALPHA_CHANNEL);
+ *      (5) Unlike pixBlockconvGray(), this always computes the accumulation
+ *          pix because its size is tied to wc and hc.
+ *      (6) Compare this implementation with pixBlockconvGray(), where
+ *          most of the code in blockconvLow() is special casing for
+ *          efficiently handling the boundary.  Here, the use of
+ *          mirrored borders and destination indexing makes the
+ *          implementation very simple.
+ */
+PIX *
+pixBlockconvGrayUnnormalized(PIX     *pixs,
+                             l_int32  wc,
+                             l_int32  hc)
+{
+l_int32    i, j, w, h, d, wpla, wpld, jmax;
+l_uint32  *linemina, *linemaxa, *lined, *dataa, *datad;
+PIX       *pixsb, *pixacc, *pixd;
+
+    PROCNAME("pixBlockconvGrayUnnormalized");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (wc < 0) wc = 0;
+    if (hc < 0) hc = 0;
+    if (w < 2 * wc + 1 || h < 2 * hc + 1) {
+        wc = L_MIN(wc, (w - 1) / 2);
+        hc = L_MIN(hc, (h - 1) / 2);
+        L_WARNING("kernel too large; reducing!\n", procName);
+        L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
+    }
+    if (wc == 0 && hc == 0)   /* no-op */
+        return pixCopy(NULL, pixs);
+
+    if ((pixsb = pixAddMirroredBorder(pixs, wc + 1, wc, hc + 1, hc)) == NULL)
+        return (PIX *)ERROR_PTR("pixsb not made", procName, NULL);
+    pixacc = pixBlockconvAccum(pixsb);
+    pixDestroy(&pixsb);
+    if (!pixacc)
+        return (PIX *)ERROR_PTR("pixacc not made", procName, NULL);
+    if ((pixd = pixCreate(w, h, 32)) == NULL) {
+        pixDestroy(&pixacc);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+
+    wpla = pixGetWpl(pixacc);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    dataa = pixGetData(pixacc);
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        linemina = dataa + i * wpla;
+        linemaxa = dataa + (i + 2 * hc + 1) * wpla;
+        for (j = 0; j < w; j++) {
+            jmax = j + 2 * wc + 1;
+            lined[j] = linemaxa[jmax] - linemaxa[j] -
+                       linemina[jmax] + linemina[j];
+        }
+    }
+
+    pixDestroy(&pixacc);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *               Tiled grayscale or color block convolution             *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixBlockconvTiled()
+ *
+ *      Input:  pix (8 or 32 bpp; or 2, 4 or 8 bpp with colormap)
+ *              wc, hc   (half width/height of convolution kernel)
+ *              nx, ny  (subdivision into tiles)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The full width and height of the convolution kernel
+ *          are (2 * wc + 1) and (2 * hc + 1)
+ *      (2) Returns a copy if both wc and hc are 0
+ *      (3) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
+ *          where (w,h) are the dimensions of pixs.
+ *      (4) For nx == ny == 1, this defaults to pixBlockconv(), which
+ *          is typically about twice as fast, and gives nearly
+ *          identical results as pixBlockconvGrayTile().
+ *      (5) If the tiles are too small, nx and/or ny are reduced
+ *          a minimum amount so that the tiles are expanded to the
+ *          smallest workable size in the problematic direction(s).
+ *      (6) Why a tiled version?  Three reasons:
+ *          (a) Because the accumulator is a uint32, overflow can occur
+ *              for an image with more than 16M pixels.
+ *          (b) The accumulator array for 16M pixels is 64 MB; using
+ *              tiles reduces the size of this array.
+ *          (c) Each tile can be processed independently, in parallel,
+ *              on a multicore processor.
+ */
+PIX *
+pixBlockconvTiled(PIX     *pix,
+                  l_int32  wc,
+                  l_int32  hc,
+                  l_int32  nx,
+                  l_int32  ny)
+{
+l_int32     i, j, w, h, d, xrat, yrat;
+PIX        *pixs, *pixd, *pixc, *pixt;
+PIX        *pixr, *pixrc, *pixg, *pixgc, *pixb, *pixbc;
+PIXTILING  *pt;
+
+    PROCNAME("pixBlockconvTiled");
+
+    if (!pix)
+        return (PIX *)ERROR_PTR("pix not defined", procName, NULL);
+    if (wc < 0) wc = 0;
+    if (hc < 0) hc = 0;
+    pixGetDimensions(pix, &w, &h, &d);
+    if (w < 2 * wc + 3 || h < 2 * hc + 3) {
+        wc = L_MAX(0, L_MIN(wc, (w - 3) / 2));
+        hc = L_MAX(0, L_MIN(hc, (h - 3) / 2));
+        L_WARNING("kernel too large; reducing!\n", procName);
+        L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
+    }
+    if (wc == 0 && hc == 0)   /* no-op */
+        return pixCopy(NULL, pix);
+    if (nx <= 1 && ny <= 1)
+        return pixBlockconv(pix, wc, hc);
+
+        /* Test to see if the tiles are too small.  The required
+         * condition is that the tile dimensions must be at least
+         * (wc + 2) x (hc + 2). */
+    xrat = w / nx;
+    yrat = h / ny;
+    if (xrat < wc + 2) {
+        nx = w / (wc + 2);
+        L_WARNING("tile width too small; nx reduced to %d\n", procName, nx);
+    }
+    if (yrat < hc + 2) {
+        ny = h / (hc + 2);
+        L_WARNING("tile height too small; ny reduced to %d\n", procName, ny);
+    }
+
+        /* Remove colormap if necessary */
+    if ((d == 2 || d == 4 || d == 8) && pixGetColormap(pix)) {
+        L_WARNING("pix has colormap; removing\n", procName);
+        pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+        d = pixGetDepth(pixs);
+    } else {
+        pixs = pixClone(pix);
+    }
+
+    if (d != 8 && d != 32) {
+        pixDestroy(&pixs);
+        return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, NULL);
+    }
+
+       /* Note that the overlaps in the width and height that
+        * are added to the tile are (wc + 2) and (hc + 2).
+        * These overlaps are removed by pixTilingPaintTile().
+        * They are larger than the extent of the filter because
+        * although the filter is symmetric with respect to its origin,
+        * the implementation is asymmetric -- see the implementation in
+        * pixBlockconvGrayTile(). */
+    if ((pixd = pixCreateTemplateNoInit(pixs)) == NULL) {
+        pixDestroy(&pixs);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    pt = pixTilingCreate(pixs, nx, ny, 0, 0, wc + 2, hc + 2);
+    for (i = 0; i < ny; i++) {
+        for (j = 0; j < nx; j++) {
+            pixt = pixTilingGetTile(pt, i, j);
+
+                /* Convolve over the tile */
+            if (d == 8) {
+                pixc = pixBlockconvGrayTile(pixt, NULL, wc, hc);
+            } else { /* d == 32 */
+                pixr = pixGetRGBComponent(pixt, COLOR_RED);
+                pixrc = pixBlockconvGrayTile(pixr, NULL, wc, hc);
+                pixDestroy(&pixr);
+                pixg = pixGetRGBComponent(pixt, COLOR_GREEN);
+                pixgc = pixBlockconvGrayTile(pixg, NULL, wc, hc);
+                pixDestroy(&pixg);
+                pixb = pixGetRGBComponent(pixt, COLOR_BLUE);
+                pixbc = pixBlockconvGrayTile(pixb, NULL, wc, hc);
+                pixDestroy(&pixb);
+                pixc = pixCreateRGBImage(pixrc, pixgc, pixbc);
+                pixDestroy(&pixrc);
+                pixDestroy(&pixgc);
+                pixDestroy(&pixbc);
+            }
+
+            pixTilingPaintTile(pixd, i, j, pixc, pt);
+            pixDestroy(&pixt);
+            pixDestroy(&pixc);
+        }
+    }
+
+    pixDestroy(&pixs);
+    pixTilingDestroy(&pt);
+    return pixd;
+}
+
+
+/*!
+ *  pixBlockconvGrayTile()
+ *
+ *      Input:  pixs (8 bpp gray)
+ *              pixacc (32 bpp accum pix)
+ *              wc, hc   (half width/height of convolution kernel)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The full width and height of the convolution kernel
+ *          are (2 * wc + 1) and (2 * hc + 1)
+ *      (2) Assumes that the input pixs is padded with (wc + 1) pixels on
+ *          left and right, and with (hc + 1) pixels on top and bottom.
+ *          The returned pix has these stripped off; they are only used
+ *          for computation.
+ *      (3) Returns a copy if both wc and hc are 0
+ *      (4) Require that w > 2 * wc + 1 and h > 2 * hc + 1,
+ *          where (w,h) are the dimensions of pixs.
+ */
+PIX *
+pixBlockconvGrayTile(PIX     *pixs,
+                     PIX     *pixacc,
+                     l_int32  wc,
+                     l_int32  hc)
+{
+l_int32    w, h, d, wd, hd, i, j, imin, imax, jmin, jmax, wplt, wpld;
+l_float32  norm;
+l_uint32   val;
+l_uint32  *datat, *datad, *lined, *linemint, *linemaxt;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixBlockconvGrayTile");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pix not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (wc < 0) wc = 0;
+    if (hc < 0) hc = 0;
+    if (w < 2 * wc + 3 || h < 2 * hc + 3) {
+        wc = L_MAX(0, L_MIN(wc, (w - 3) / 2));
+        hc = L_MAX(0, L_MIN(hc, (h - 3) / 2));
+        L_WARNING("kernel too large; reducing!\n", procName);
+        L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
+    }
+    if (wc == 0 && hc == 0)
+        return pixCopy(NULL, pixs);
+    wd = w - 2 * wc;
+    hd = h - 2 * hc;
+
+    if (pixacc) {
+        if (pixGetDepth(pixacc) == 32) {
+            pixt = pixClone(pixacc);
+        } else {
+            L_WARNING("pixacc not 32 bpp; making new one\n", procName);
+            if ((pixt = pixBlockconvAccum(pixs)) == NULL)
+                return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        }
+    } else {
+        if ((pixt = pixBlockconvAccum(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+    }
+
+    if ((pixd = pixCreateTemplate(pixs)) == NULL) {
+        pixDestroy(&pixt);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    norm = 1. / (l_float32)((2 * wc + 1) * (2 * hc + 1));
+
+        /* Do the convolution over the subregion of size (wd - 2, hd - 2),
+         * which exactly corresponds to the size of the subregion that
+         * will be extracted by pixTilingPaintTile().  Note that the
+         * region in which points are computed is not symmetric about
+         * the center of the images; instead the computation in
+         * the accumulator image is shifted up and to the left by 1,
+         * relative to the center, because the 4 accumulator sampling
+         * points are taken at the LL corner of the filter and at 3 other
+         * points that are shifted -wc and -hc to the left and above.  */
+    for (i = hc; i < hc + hd - 2; i++) {
+        imin = L_MAX(i - hc - 1, 0);
+        imax = L_MIN(i + hc, h - 1);
+        lined = datad + i * wpld;
+        linemint = datat + imin * wplt;
+        linemaxt = datat + imax * wplt;
+        for (j = wc; j < wc + wd - 2; j++) {
+            jmin = L_MAX(j - wc - 1, 0);
+            jmax = L_MIN(j + wc, w - 1);
+            val = linemaxt[jmax] - linemaxt[jmin]
+                  + linemint[jmin] - linemint[jmax];
+            val = (l_uint8)(norm * val + 0.5);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *     Convolution for mean, mean square, variance and rms deviation    *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixWindowedStats()
+ *
+ *      Input:  pixs (8 bpp grayscale)
+ *              wc, hc   (half width/height of convolution kernel)
+ *              hasborder (use 1 if it already has (wc + 1) border pixels
+ *                         on left and right, and (hc + 1) on top and bottom;
+ *                         use 0 to add kernel-dependent border)
+ *              &pixm (<optional return> 8 bpp mean value in window)
+ *              &pixms (<optional return> 32 bpp mean square value in window)
+ *              &fpixv (<optional return> float variance in window)
+ *              &fpixrv (<optional return> float rms deviation from the mean)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a high-level convenience function for calculating
+ *          any or all of these derived images.
+ *      (2) If @hasborder = 0, a border is added and the result is
+ *          computed over all pixels in pixs.  Otherwise, no border is
+ *          added and the border pixels are removed from the output images.
+ *      (3) These statistical measures over the pixels in the
+ *          rectangular window are:
+ *            - average value: <p>  (pixm)
+ *            - average squared value: <p*p> (pixms)
+ *            - variance: <(p - <p>)*(p - <p>)> = <p*p> - <p>*<p>  (pixv)
+ *            - square-root of variance: (pixrv)
+ *          where the brackets < .. > indicate that the average value is
+ *          to be taken over the window.
+ *      (4) Note that the variance is just the mean square difference from
+ *          the mean value; and the square root of the variance is the
+ *          root mean square difference from the mean, sometimes also
+ *          called the 'standard deviation'.
+ *      (5) The added border, along with the use of an accumulator array,
+ *          allows computation without special treatment of pixels near
+ *          the image boundary, and runs in a time that is independent
+ *          of the size of the convolution kernel.
+ */
+l_int32
+pixWindowedStats(PIX     *pixs,
+                 l_int32  wc,
+                 l_int32  hc,
+                 l_int32  hasborder,
+                 PIX    **ppixm,
+                 PIX    **ppixms,
+                 FPIX   **pfpixv,
+                 FPIX   **pfpixrv)
+{
+PIX  *pixb, *pixm, *pixms;
+
+    PROCNAME("pixWindowedStats");
+
+    if (!ppixm && !ppixms && !pfpixv && !pfpixrv)
+        return ERROR_INT("no output requested", procName, 1);
+    if (ppixm) *ppixm = NULL;
+    if (ppixms) *ppixms = NULL;
+    if (pfpixv) *pfpixv = NULL;
+    if (pfpixrv) *pfpixrv = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (wc < 2 || hc < 2)
+        return ERROR_INT("wc and hc not >= 2", procName, 1);
+
+        /* Add border if requested */
+    if (!hasborder)
+        pixb = pixAddBorderGeneral(pixs, wc + 1, wc + 1, hc + 1, hc + 1, 0);
+    else
+        pixb = pixClone(pixs);
+
+    if (!pfpixv && !pfpixrv) {
+        if (ppixm) *ppixm = pixWindowedMean(pixb, wc, hc, 1, 1);
+        if (ppixms) *ppixms = pixWindowedMeanSquare(pixb, wc, hc, 1);
+        pixDestroy(&pixb);
+        return 0;
+    }
+
+    pixm = pixWindowedMean(pixb, wc, hc, 1, 1);
+    pixms = pixWindowedMeanSquare(pixb, wc, hc, 1);
+    pixWindowedVariance(pixm, pixms, pfpixv, pfpixrv);
+    if (ppixm)
+        *ppixm = pixm;
+    else
+        pixDestroy(&pixm);
+    if (ppixms)
+        *ppixms = pixms;
+    else
+        pixDestroy(&pixms);
+    pixDestroy(&pixb);
+    return 0;
+}
+
+
+/*!
+ *  pixWindowedMean()
+ *
+ *      Input:  pixs (8 or 32 bpp grayscale)
+ *              wc, hc   (half width/height of convolution kernel)
+ *              hasborder (use 1 if it already has (wc + 1) border pixels
+ *                         on left and right, and (hc + 1) on top and bottom;
+ *                         use 0 to add kernel-dependent border)
+ *              normflag (1 for normalization to get average in window;
+ *                        0 for the sum in the window (un-normalized))
+ *      Return: pixd (8 or 32 bpp, average over kernel window)
+ *
+ *  Notes:
+ *      (1) The input and output depths are the same.
+ *      (2) A set of border pixels of width (wc + 1) on left and right,
+ *          and of height (hc + 1) on top and bottom, must be on the
+ *          pix before the accumulator is found.  The output pixd
+ *          (after convolution) has this border removed.
+ *          If @hasborder = 0, the required border is added.
+ *      (3) Typically, @normflag == 1.  However, if you want the sum
+ *          within the window, rather than a normalized convolution,
+ *          use @normflag == 0.
+ *      (4) This builds a block accumulator pix, uses it here, and
+ *          destroys it.
+ *      (5) The added border, along with the use of an accumulator array,
+ *          allows computation without special treatment of pixels near
+ *          the image boundary, and runs in a time that is independent
+ *          of the size of the convolution kernel.
+ */
+PIX *
+pixWindowedMean(PIX     *pixs,
+                l_int32  wc,
+                l_int32  hc,
+                l_int32  hasborder,
+                l_int32  normflag)
+{
+l_int32    i, j, w, h, d, wd, hd, wplc, wpld, wincr, hincr;
+l_uint32   val;
+l_uint32  *datac, *datad, *linec1, *linec2, *lined;
+l_float32  norm;
+PIX       *pixb, *pixc, *pixd;
+
+    PROCNAME("pixWindowedMean");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+    if (wc < 2 || hc < 2)
+        return (PIX *)ERROR_PTR("wc and hc not >= 2", procName, NULL);
+
+        /* Add border if requested */
+    if (!hasborder)
+        pixb = pixAddBorderGeneral(pixs, wc + 1, wc + 1, hc + 1, hc + 1, 0);
+    else
+        pixb = pixClone(pixs);
+
+        /* The output has wc + 1 border pixels stripped from each side
+         * of pixb, and hc + 1 border pixels stripped from top and bottom. */
+    pixGetDimensions(pixb, &w, &h, NULL);
+    wd = w - 2 * (wc + 1);
+    hd = h - 2 * (hc + 1);
+    if (wd < 2 || hd < 2)
+        return (PIX *)ERROR_PTR("w or h too small for kernel", procName, NULL);
+    if ((pixd = pixCreate(wd, hd, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+        /* Make the accumulator pix from pixb */
+    if ((pixc = pixBlockconvAccum(pixb)) == NULL) {
+        pixDestroy(&pixb);
+        pixDestroy(&pixd);
+        return (PIX *)ERROR_PTR("pixc not made", procName, NULL);
+    }
+    wplc = pixGetWpl(pixc);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    datac = pixGetData(pixc);
+
+    wincr = 2 * wc + 1;
+    hincr = 2 * hc + 1;
+    norm = 1.0;  /* use this for sum-in-window */
+    if (normflag)
+        norm = 1.0 / (wincr * hincr);
+    for (i = 0; i < hd; i++) {
+        linec1 = datac + i * wplc;
+        linec2 = datac + (i + hincr) * wplc;
+        lined = datad + i * wpld;
+        for (j = 0; j < wd; j++) {
+            val = linec2[j + wincr] - linec2[j] - linec1[j + wincr] + linec1[j];
+            if (d == 8) {
+                val = (l_uint8)(norm * val);
+                SET_DATA_BYTE(lined, j, val);
+            } else {  /* d == 32 */
+                val = (l_uint32)(norm * val);
+                lined[j] = val;
+            }
+        }
+    }
+
+    pixDestroy(&pixc);
+    pixDestroy(&pixb);
+    return pixd;
+}
+
+
+/*!
+ *  pixWindowedMeanSquare()
+ *
+ *      Input:  pixs (8 bpp grayscale)
+ *              wc, hc   (half width/height of convolution kernel)
+ *              hasborder (use 1 if it already has (wc + 1) border pixels
+ *                         on left and right, and (hc + 1) on top and bottom;
+ *                         use 0 to add kernel-dependent border)
+ *      Return: pixd (32 bpp, average over rectangular window of
+ *                    width = 2 * wc + 1 and height = 2 * hc + 1)
+ *
+ *  Notes:
+ *      (1) A set of border pixels of width (wc + 1) on left and right,
+ *          and of height (hc + 1) on top and bottom, must be on the
+ *          pix before the accumulator is found.  The output pixd
+ *          (after convolution) has this border removed.
+ *          If @hasborder = 0, the required border is added.
+ *      (2) The advantage is that we are unaffected by the boundary, and
+ *          it is not necessary to treat pixels within @wc and @hc of the
+ *          border differently.  This is because processing for pixd
+ *          only takes place for pixels in pixs for which the
+ *          kernel is entirely contained in pixs.
+ *      (3) Why do we have an added border of width (@wc + 1) and
+ *          height (@hc + 1), when we only need @wc and @hc pixels
+ *          to satisfy this condition?  Answer: the accumulators
+ *          are asymmetric, requiring an extra row and column of
+ *          pixels at top and left to work accurately.
+ *      (4) The added border, along with the use of an accumulator array,
+ *          allows computation without special treatment of pixels near
+ *          the image boundary, and runs in a time that is independent
+ *          of the size of the convolution kernel.
+ */
+PIX *
+pixWindowedMeanSquare(PIX     *pixs,
+                      l_int32  wc,
+                      l_int32  hc,
+                      l_int32  hasborder)
+{
+l_int32     i, j, w, h, wd, hd, wpl, wpld, wincr, hincr;
+l_uint32    ival;
+l_uint32   *datad, *lined;
+l_float64   norm;
+l_float64   val;
+l_float64  *data, *line1, *line2;
+DPIX       *dpix;
+PIX        *pixb, *pixd;
+
+    PROCNAME("pixWindowedMeanSquare");
+
+    if (!pixs || (pixGetDepth(pixs) != 8))
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+    if (wc < 2 || hc < 2)
+        return (PIX *)ERROR_PTR("wc and hc not >= 2", procName, NULL);
+
+        /* Add border if requested */
+    if (!hasborder)
+        pixb = pixAddBorderGeneral(pixs, wc + 1, wc + 1, hc + 1, hc + 1, 0);
+    else
+        pixb = pixClone(pixs);
+
+    if ((dpix = pixMeanSquareAccum(pixb)) == NULL)
+        return (PIX *)ERROR_PTR("dpix not made", procName, NULL);
+    wpl = dpixGetWpl(dpix);
+    data = dpixGetData(dpix);
+
+        /* The output has wc + 1 border pixels stripped from each side
+         * of pixb, and hc + 1 border pixels stripped from top and bottom. */
+    pixGetDimensions(pixb, &w, &h, NULL);
+    wd = w - 2 * (wc + 1);
+    hd = h - 2 * (hc + 1);
+    if (wd < 2 || hd < 2)
+        return (PIX *)ERROR_PTR("w or h too small for kernel", procName, NULL);
+    if ((pixd = pixCreate(wd, hd, 32)) == NULL) {
+        dpixDestroy(&dpix);
+        pixDestroy(&pixb);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+
+    wincr = 2 * wc + 1;
+    hincr = 2 * hc + 1;
+    norm = 1.0 / (wincr * hincr);
+    for (i = 0; i < hd; i++) {
+        line1 = data + i * wpl;
+        line2 = data + (i + hincr) * wpl;
+        lined = datad + i * wpld;
+        for (j = 0; j < wd; j++) {
+            val = line2[j + wincr] - line2[j] - line1[j + wincr] + line1[j];
+            ival = (l_uint32)(norm * val);
+            lined[j] = ival;
+        }
+    }
+
+    dpixDestroy(&dpix);
+    pixDestroy(&pixb);
+    return pixd;
+}
+
+
+/*!
+ *  pixWindowedVariance()
+ *
+ *      Input:  pixm (mean over window; 8 or 32 bpp grayscale)
+ *              pixms (mean square over window; 32 bpp)
+ *              &fpixv (<optional return> float variance -- the ms deviation
+ *                      from the mean)
+ *              &fpixrv (<optional return> float rms deviation from the mean)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The mean and mean square values are precomputed, using
+ *          pixWindowedMean() and pixWindowedMeanSquare().
+ *      (2) Either or both of the variance and square-root of variance
+ *          are returned as an fpix, where the variance is the
+ *          average over the window of the mean square difference of
+ *          the pixel value from the mean:
+ *                <(p - <p>)*(p - <p>)> = <p*p> - <p>*<p>
+ *      (3) To visualize the results:
+ *            - for both, use fpixDisplayMaxDynamicRange().
+ *            - for rms deviation, simply convert the output fpix to pix,
+ */
+l_int32
+pixWindowedVariance(PIX    *pixm,
+                    PIX    *pixms,
+                    FPIX  **pfpixv,
+                    FPIX  **pfpixrv)
+{
+l_int32     i, j, w, h, ws, hs, ds, wplm, wplms, wplv, wplrv, valm, valms;
+l_float32   var;
+l_uint32   *linem, *linems, *datam, *datams;
+l_float32  *linev, *linerv, *datav, *datarv;
+FPIX       *fpixv, *fpixrv;  /* variance and square root of variance */
+
+    PROCNAME("pixWindowedVariance");
+
+    if (!pfpixv && !pfpixrv)
+        return ERROR_INT("no output requested", procName, 1);
+    if (pfpixv) *pfpixv = NULL;
+    if (pfpixrv) *pfpixrv = NULL;
+    if (!pixm || pixGetDepth(pixm) != 8)
+        return ERROR_INT("pixm undefined or not 8 bpp", procName, 1);
+    if (!pixms || pixGetDepth(pixms) != 32)
+        return ERROR_INT("pixms undefined or not 32 bpp", procName, 1);
+    pixGetDimensions(pixm, &w, &h, NULL);
+    pixGetDimensions(pixms, &ws, &hs, &ds);
+    if (w != ws || h != hs)
+        return ERROR_INT("pixm and pixms sizes differ", procName, 1);
+
+    if (pfpixv) {
+        fpixv = fpixCreate(w, h);
+        *pfpixv = fpixv;
+        wplv = fpixGetWpl(fpixv);
+        datav = fpixGetData(fpixv);
+    }
+    if (pfpixrv) {
+        fpixrv = fpixCreate(w, h);
+        *pfpixrv = fpixrv;
+        wplrv = fpixGetWpl(fpixrv);
+        datarv = fpixGetData(fpixrv);
+    }
+
+    wplm = pixGetWpl(pixm);
+    wplms = pixGetWpl(pixms);
+    datam = pixGetData(pixm);
+    datams = pixGetData(pixms);
+    for (i = 0; i < h; i++) {
+        linem = datam + i * wplm;
+        linems = datams + i * wplms;
+        if (pfpixv)
+            linev = datav + i * wplv;
+        if (pfpixrv)
+            linerv = datarv + i * wplrv;
+        for (j = 0; j < w; j++) {
+            valm = GET_DATA_BYTE(linem, j);
+            if (ds == 8)
+                valms = GET_DATA_BYTE(linems, j);
+            else  /* ds == 32 */
+                valms = (l_int32)linems[j];
+            var = (l_float32)valms - (l_float32)valm * valm;
+            if (pfpixv)
+                linev[j] = var;
+            if (pfpixrv)
+                linerv[j] = (l_float32)sqrt(var);
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixMeanSquareAccum()
+ *
+ *      Input:  pixs (8 bpp grayscale)
+ *      Return: dpix (64 bit array), or null on error
+ *
+ *  Notes:
+ *      (1) Similar to pixBlockconvAccum(), this computes the
+ *          sum of the squares of the pixel values in such a way
+ *          that the value at (i,j) is the sum of all squares in
+ *          the rectangle from the origin to (i,j).
+ *      (2) The general recursion relation (v are squared pixel values) is
+ *            a(i,j) = v(i,j) + a(i-1, j) + a(i, j-1) - a(i-1, j-1)
+ *          For the first line, this reduces to the special case
+ *            a(i,j) = v(i,j) + a(i, j-1)
+ *          For the first column, the special case is
+ *            a(i,j) = v(i,j) + a(i-1, j)
+ */
+DPIX *
+pixMeanSquareAccum(PIX  *pixs)
+{
+l_int32     i, j, w, h, wpl, wpls, val;
+l_uint32   *datas, *lines;
+l_float64  *data, *line, *linep;
+DPIX       *dpix;
+
+    PROCNAME("pixMeanSquareAccum");
+
+
+    if (!pixs || (pixGetDepth(pixs) != 8))
+        return (DPIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((dpix = dpixCreate(w, h)) ==  NULL)
+        return (DPIX *)ERROR_PTR("dpix not made", procName, NULL);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    data = dpixGetData(dpix);
+    wpl = dpixGetWpl(dpix);
+
+    lines = datas;
+    line = data;
+    for (j = 0; j < w; j++) {   /* first line */
+        val = GET_DATA_BYTE(lines, j);
+        if (j == 0)
+            line[0] = val * val;
+        else
+            line[j] = line[j - 1] + val * val;
+    }
+
+        /* Do the other lines */
+    for (i = 1; i < h; i++) {
+        lines = datas + i * wpls;
+        line = data + i * wpl;  /* current dest line */
+        linep = line - wpl;;  /* prev dest line */
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(lines, j);
+            if (j == 0)
+                line[0] = linep[0] + val * val;
+            else
+                line[j] = line[j - 1] + linep[j] - linep[j - 1] + val * val;
+        }
+    }
+
+    return dpix;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                        Binary block sum/rank                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixBlockrank()
+ *
+ *      Input:  pixs (1 bpp)
+ *              accum pix (<optional> 32 bpp)
+ *              wc, hc   (half width/height of block sum/rank kernel)
+ *              rank   (between 0.0 and 1.0; 0.5 is median filter)
+ *      Return: pixd (1 bpp)
+ *
+ *  Notes:
+ *      (1) The full width and height of the convolution kernel
+ *          are (2 * wc + 1) and (2 * hc + 1)
+ *      (2) This returns a pixd where each pixel is a 1 if the
+ *          neighborhood (2 * wc + 1) x (2 * hc + 1)) pixels
+ *          contains the rank fraction of 1 pixels.  Otherwise,
+ *          the returned pixel is 0.  Note that the special case
+ *          of rank = 0.0 is always satisfied, so the returned
+ *          pixd has all pixels with value 1.
+ *      (3) If accum pix is null, make one, use it, and destroy it
+ *          before returning; otherwise, just use the input accum pix
+ *      (4) If both wc and hc are 0, returns a copy unless rank == 0.0,
+ *          in which case this returns an all-ones image.
+ *      (5) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
+ *          where (w,h) are the dimensions of pixs.
+ */
+PIX *
+pixBlockrank(PIX       *pixs,
+             PIX       *pixacc,
+             l_int32    wc,
+             l_int32    hc,
+             l_float32  rank)
+{
+l_int32  w, h, d, thresh;
+PIX     *pixt, *pixd;
+
+    PROCNAME("pixBlockrank");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (rank < 0.0 || rank > 1.0)
+        return (PIX *)ERROR_PTR("rank must be in [0.0, 1.0]", procName, NULL);
+
+    if (rank == 0.0) {
+        pixd = pixCreateTemplate(pixs);
+        pixSetAll(pixd);
+        return pixd;
+    }
+
+    if (wc < 0) wc = 0;
+    if (hc < 0) hc = 0;
+    if (w < 2 * wc + 1 || h < 2 * hc + 1) {
+        wc = L_MIN(wc, (w - 1) / 2);
+        hc = L_MIN(hc, (h - 1) / 2);
+        L_WARNING("kernel too large; reducing!\n", procName);
+        L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
+    }
+    if (wc == 0 && hc == 0)
+        return pixCopy(NULL, pixs);
+
+    if ((pixt = pixBlocksum(pixs, pixacc, wc, hc)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+        /* 1 bpp block rank filter output.
+         * Must invert because threshold gives 1 for values < thresh,
+         * but we need a 1 if the value is >= thresh. */
+    thresh = (l_int32)(255. * rank);
+    pixd = pixThresholdToBinary(pixt, thresh);
+    pixInvert(pixd, pixd);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixBlocksum()
+ *
+ *      Input:  pixs (1 bpp)
+ *              accum pix (<optional> 32 bpp)
+ *              wc, hc   (half width/height of block sum/rank kernel)
+ *      Return: pixd (8 bpp)
+ *
+ *  Notes:
+ *      (1) If accum pix is null, make one and destroy it before
+ *          returning; otherwise, just use the input accum pix
+ *      (2) The full width and height of the convolution kernel
+ *          are (2 * wc + 1) and (2 * hc + 1)
+ *      (3) Use of wc = hc = 1, followed by pixInvert() on the
+ *          8 bpp result, gives a nice anti-aliased, and somewhat
+ *          darkened, result on text.
+ *      (4) Require that w >= 2 * wc + 1 and h >= 2 * hc + 1,
+ *          where (w,h) are the dimensions of pixs.
+ *      (5) Returns in each dest pixel the sum of all src pixels
+ *          that are within a block of size of the kernel, centered
+ *          on the dest pixel.  This sum is the number of src ON
+ *          pixels in the block at each location, normalized to 255
+ *          for a block containing all ON pixels.  For pixels near
+ *          the boundary, where the block is not entirely contained
+ *          within the image, we then multiply by a second normalization
+ *          factor that is greater than one, so that all results
+ *          are normalized by the number of participating pixels
+ *          within the block.
+ */
+PIX *
+pixBlocksum(PIX     *pixs,
+            PIX     *pixacc,
+            l_int32  wc,
+            l_int32  hc)
+{
+l_int32    w, h, d, wplt, wpld;
+l_uint32  *datat, *datad;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixBlocksum");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (wc < 0) wc = 0;
+    if (hc < 0) hc = 0;
+    if (w < 2 * wc + 1 || h < 2 * hc + 1) {
+        wc = L_MIN(wc, (w - 1) / 2);
+        hc = L_MIN(hc, (h - 1) / 2);
+        L_WARNING("kernel too large; reducing!\n", procName);
+        L_INFO("wc = %d, hc = %d\n", procName, wc, hc);
+    }
+    if (wc == 0 && hc == 0)
+        return pixCopy(NULL, pixs);
+
+    if (pixacc) {
+        if (pixGetDepth(pixacc) != 32)
+            return (PIX *)ERROR_PTR("pixacc not 32 bpp", procName, NULL);
+        pixt = pixClone(pixacc);
+    } else {
+        if ((pixt = pixBlockconvAccum(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+    }
+
+        /* 8 bpp block sum output */
+    if ((pixd = pixCreate(w, h, 8)) == NULL) {
+        pixDestroy(&pixt);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    pixCopyResolution(pixd, pixs);
+
+    wpld = pixGetWpl(pixd);
+    wplt = pixGetWpl(pixt);
+    datad = pixGetData(pixd);
+    datat = pixGetData(pixt);
+    blocksumLow(datad, w, h, wpld, datat, wplt, wc, hc);
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  blocksumLow()
+ *
+ *      Input:  datad  (of 8 bpp dest)
+ *              w, h, wpl  (of 8 bpp dest)
+ *              dataa (of 32 bpp accum)
+ *              wpla  (of 32 bpp accum)
+ *              wc, hc  (convolution "half-width" and "half-height")
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) The full width and height of the convolution kernel
+ *          are (2 * wc + 1) and (2 * hc + 1).
+ *      (2) The lack of symmetry between the handling of the
+ *          first (hc + 1) lines and the last (hc) lines,
+ *          and similarly with the columns, is due to fact that
+ *          for the pixel at (x,y), the accumulator values are
+ *          taken at (x + wc, y + hc), (x - wc - 1, y + hc),
+ *          (x + wc, y - hc - 1) and (x - wc - 1, y - hc - 1).
+ *      (3) Compute sums of ON pixels within the block filter size,
+ *          normalized between 0 and 255, as if there were no reduced
+ *          area at the boundary.  This under-estimates the value
+ *          of the boundary pixels, so we multiply them by another
+ *          normalization factor that is greater than 1.
+ *      (4) This second normalization is done first for the first
+ *          hc + 1 lines; then for the last hc lines; and finally
+ *          for the first wc + 1 and last wc columns in the intermediate
+ *          lines.
+ *      (5) Required constraints are: wc < w and hc < h.
+ */
+static void
+blocksumLow(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpl,
+            l_uint32  *dataa,
+            l_int32    wpla,
+            l_int32    wc,
+            l_int32    hc)
+{
+l_int32    i, j, imax, imin, jmax, jmin;
+l_int32    wn, hn, fwc, fhc, wmwc, hmhc;
+l_float32  norm, normh, normw;
+l_uint32   val;
+l_uint32  *linemina, *linemaxa, *lined;
+
+    PROCNAME("blocksumLow");
+
+    wmwc = w - wc;
+    hmhc = h - hc;
+    if (wmwc <= 0 || hmhc <= 0) {
+        L_ERROR("wc >= w || hc >=h\n", procName);
+        return;
+    }
+    fwc = 2 * wc + 1;
+    fhc = 2 * hc + 1;
+    norm = 255. / (fwc * fhc);
+
+        /*------------------------------------------------------------*
+         *  Compute, using b.c. only to set limits on the accum image *
+         *------------------------------------------------------------*/
+    for (i = 0; i < h; i++) {
+        imin = L_MAX(i - 1 - hc, 0);
+        imax = L_MIN(i + hc, h - 1);
+        lined = datad + wpl * i;
+        linemina = dataa + wpla * imin;
+        linemaxa = dataa + wpla * imax;
+        for (j = 0; j < w; j++) {
+            jmin = L_MAX(j - 1 - wc, 0);
+            jmax = L_MIN(j + wc, w - 1);
+            val = linemaxa[jmax] - linemaxa[jmin]
+                  - linemina[jmax] + linemina[jmin];
+            val = (l_uint8)(norm * val);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+        /*------------------------------------------------------------*
+         *             Fix normalization for boundary pixels          *
+         *------------------------------------------------------------*/
+    for (i = 0; i <= hc; i++) {    /* first hc + 1 lines */
+        hn = hc + i;
+        normh = (l_float32)fhc / (l_float32)hn;   /* > 1 */
+        lined = datad + wpl * i;
+        for (j = 0; j <= wc; j++) {
+            wn = wc + j;
+            normw = (l_float32)fwc / (l_float32)wn;   /* > 1 */
+            val = GET_DATA_BYTE(lined, j);
+            val = (l_uint8)(val * normh * normw);
+            SET_DATA_BYTE(lined, j, val);
+        }
+        for (j = wc + 1; j < wmwc; j++) {
+            val = GET_DATA_BYTE(lined, j);
+            val = (l_uint8)(val * normh);
+            SET_DATA_BYTE(lined, j, val);
+        }
+        for (j = wmwc; j < w; j++) {
+            wn = wc + w - j;
+            normw = (l_float32)fwc / (l_float32)wn;   /* > 1 */
+            val = GET_DATA_BYTE(lined, j);
+            val = (l_uint8)(val * normh * normw);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    for (i = hmhc; i < h; i++) {  /* last hc lines */
+        hn = hc + h - i;
+        normh = (l_float32)fhc / (l_float32)hn;   /* > 1 */
+        lined = datad + wpl * i;
+        for (j = 0; j <= wc; j++) {
+            wn = wc + j;
+            normw = (l_float32)fwc / (l_float32)wn;   /* > 1 */
+            val = GET_DATA_BYTE(lined, j);
+            val = (l_uint8)(val * normh * normw);
+            SET_DATA_BYTE(lined, j, val);
+        }
+        for (j = wc + 1; j < wmwc; j++) {
+            val = GET_DATA_BYTE(lined, j);
+            val = (l_uint8)(val * normh);
+            SET_DATA_BYTE(lined, j, val);
+        }
+        for (j = wmwc; j < w; j++) {
+            wn = wc + w - j;
+            normw = (l_float32)fwc / (l_float32)wn;   /* > 1 */
+            val = GET_DATA_BYTE(lined, j);
+            val = (l_uint8)(val * normh * normw);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    for (i = hc + 1; i < hmhc; i++) {    /* intermediate lines */
+        lined = datad + wpl * i;
+        for (j = 0; j <= wc; j++) {   /* first wc + 1 columns */
+            wn = wc + j;
+            normw = (l_float32)fwc / (l_float32)wn;   /* > 1 */
+            val = GET_DATA_BYTE(lined, j);
+            val = (l_uint8)(val * normw);
+            SET_DATA_BYTE(lined, j, val);
+        }
+        for (j = wmwc; j < w; j++) {   /* last wc columns */
+            wn = wc + w - j;
+            normw = (l_float32)fwc / (l_float32)wn;   /* > 1 */
+            val = GET_DATA_BYTE(lined, j);
+            val = (l_uint8)(val * normw);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    return;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                          Census transform                            *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixCensusTransform()
+ *
+ *      Input:  pixs (8 bpp)
+ *              halfsize (of square over which neighbors are averaged)
+ *              accum pix (<optional> 32 bpp)
+ *      Return: pixd (1 bpp)
+ *
+ *  Notes:
+ *      (1) The Census transform was invented by Ramin Zabih and John Woodfill
+ *          ("Non-parametric local transforms for computing visual
+ *          correspondence", Third European Conference on Computer Vision,
+ *          Stockholm, Sweden, May 1994); see publications at
+ *             http://www.cs.cornell.edu/~rdz/index.htm
+ *          This compares each pixel against the average of its neighbors,
+ *          in a square of odd dimension centered on the pixel.
+ *          If the pixel is greater than the average of its neighbors,
+ *          the output pixel value is 1; otherwise it is 0.
+ *      (2) This can be used as an encoding for an image that is
+ *          fairly robust against slow illumination changes, with
+ *          applications in image comparison and mosaicing.
+ *      (3) The size of the convolution kernel is (2 * halfsize + 1)
+ *          on a side.  The halfsize parameter must be >= 1.
+ *      (4) If accum pix is null, make one, use it, and destroy it
+ *          before returning; otherwise, just use the input accum pix
+ */
+PIX *
+pixCensusTransform(PIX     *pixs,
+                   l_int32  halfsize,
+                   PIX     *pixacc)
+{
+l_int32    i, j, w, h, wpls, wplv, wpld;
+l_int32    vals, valv;
+l_uint32  *datas, *datav, *datad, *lines, *linev, *lined;
+PIX       *pixav, *pixd;
+
+    PROCNAME("pixCensusTransform");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (halfsize < 1)
+        return (PIX *)ERROR_PTR("halfsize must be >= 1", procName, NULL);
+
+        /* Get the average of each pixel with its neighbors */
+    if ((pixav = pixBlockconvGray(pixs, pixacc, halfsize, halfsize))
+          == NULL)
+        return (PIX *)ERROR_PTR("pixav not made", procName, NULL);
+
+        /* Subtract the pixel from the average, and then compare
+         * the pixel value with the remaining average */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w, h, 1)) == NULL) {
+        pixDestroy(&pixav);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    datas = pixGetData(pixs);
+    datav = pixGetData(pixav);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wplv = pixGetWpl(pixav);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        linev = datav + i * wplv;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            vals = GET_DATA_BYTE(lines, j);
+            valv = GET_DATA_BYTE(linev, j);
+            if (vals > valv)
+                SET_DATA_BIT(lined, j);
+        }
+    }
+
+    pixDestroy(&pixav);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                         Generic convolution                          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixConvolve()
+ *
+ *      Input:  pixs (8, 16, 32 bpp; no colormap)
+ *              kernel
+ *              outdepth (of pixd: 8, 16 or 32)
+ *              normflag (1 to normalize kernel to unit sum; 0 otherwise)
+ *      Return: pixd (8, 16 or 32 bpp)
+ *
+ *  Notes:
+ *      (1) This gives a convolution with an arbitrary kernel.
+ *      (2) The input pixs must have only one sample/pixel.
+ *          To do a convolution on an RGB image, use pixConvolveRGB().
+ *      (3) The parameter @outdepth determines the depth of the result.
+ *          If the kernel is normalized to unit sum, the output values
+ *          can never exceed 255, so an output depth of 8 bpp is sufficient.
+ *          If the kernel is not normalized, it may be necessary to use
+ *          16 or 32 bpp output to avoid overflow.
+ *      (4) If normflag == 1, the result is normalized by scaling all
+ *          kernel values for a unit sum.  If the sum of kernel values
+ *          is very close to zero, the kernel can not be normalized and
+ *          the convolution will not be performed.  A warning is issued.
+ *      (5) The kernel values can be positive or negative, but the
+ *          result for the convolution can only be stored as a positive
+ *          number.  Consequently, if it goes negative, the choices are
+ *          to clip to 0 or take the absolute value.  We're choosing
+ *          to take the absolute value.  (Another possibility would be
+ *          to output a second unsigned image for the negative values.)
+ *          If you want to get a clipped result, or to keep the negative
+ *          values in the result, use fpixConvolve(), with the
+ *          converters in fpix2.c between pix and fpix.
+ *      (6) This uses a mirrored border to avoid special casing on
+ *          the boundaries.
+ *      (7) To get a subsampled output, call l_setConvolveSampling().
+ *          The time to make a subsampled output is reduced by the
+ *          product of the sampling factors.
+ *      (8) The function is slow, running at about 12 machine cycles for
+ *          each pixel-op in the convolution.  For example, with a 3 GHz
+ *          cpu, a 1 Mpixel grayscale image, and a kernel with
+ *          (sx * sy) = 25 elements, the convolution takes about 100 msec.
+ */
+PIX *
+pixConvolve(PIX       *pixs,
+            L_KERNEL  *kel,
+            l_int32    outdepth,
+            l_int32    normflag)
+{
+l_int32    i, j, id, jd, k, m, w, h, d, wd, hd, sx, sy, cx, cy, wplt, wpld;
+l_int32    val;
+l_uint32  *datat, *datad, *linet, *lined;
+l_float32  sum;
+L_KERNEL  *keli, *keln;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixConvolve");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8, 16, or 32 bpp", procName, NULL);
+    if (!kel)
+        return (PIX *)ERROR_PTR("kel not defined", procName, NULL);
+
+    keli = kernelInvert(kel);
+    kernelGetParameters(keli, &sy, &sx, &cy, &cx);
+    if (normflag)
+        keln = kernelNormalize(keli, 1.0);
+    else
+        keln = kernelCopy(keli);
+
+    if ((pixt = pixAddMirroredBorder(pixs, cx, sx - cx, cy, sy - cy)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+    wd = (w + ConvolveSamplingFactX - 1) / ConvolveSamplingFactX;
+    hd = (h + ConvolveSamplingFactY - 1) / ConvolveSamplingFactY;
+    pixd = pixCreate(wd, hd, outdepth);
+    datat = pixGetData(pixt);
+    datad = pixGetData(pixd);
+    wplt = pixGetWpl(pixt);
+    wpld = pixGetWpl(pixd);
+    for (i = 0, id = 0; id < hd; i += ConvolveSamplingFactY, id++) {
+        lined = datad + id * wpld;
+        for (j = 0, jd = 0; jd < wd; j += ConvolveSamplingFactX, jd++) {
+            sum = 0.0;
+            for (k = 0; k < sy; k++) {
+                linet = datat + (i + k) * wplt;
+                if (d == 8) {
+                    for (m = 0; m < sx; m++) {
+                        val = GET_DATA_BYTE(linet, j + m);
+                        sum += val * keln->data[k][m];
+                    }
+                } else if (d == 16) {
+                    for (m = 0; m < sx; m++) {
+                        val = GET_DATA_TWO_BYTES(linet, j + m);
+                        sum += val * keln->data[k][m];
+                    }
+                } else {  /* d == 32 */
+                    for (m = 0; m < sx; m++) {
+                        val = *(linet + j + m);
+                        sum += val * keln->data[k][m];
+                    }
+                }
+            }
+            if (sum < 0.0) sum = -sum;  /* make it non-negative */
+            if (outdepth == 8)
+                SET_DATA_BYTE(lined, jd, (l_int32)(sum + 0.5));
+            else if (outdepth == 16)
+                SET_DATA_TWO_BYTES(lined, jd, (l_int32)(sum + 0.5));
+            else  /* outdepth == 32 */
+                *(lined + jd) = (l_uint32)(sum + 0.5);
+        }
+    }
+
+    kernelDestroy(&keli);
+    kernelDestroy(&keln);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvolveSep()
+ *
+ *      Input:  pixs (8, 16, 32 bpp; no colormap)
+ *              kelx (x-dependent kernel)
+ *              kely (y-dependent kernel)
+ *              outdepth (of pixd: 8, 16 or 32)
+ *              normflag (1 to normalize kernel to unit sum; 0 otherwise)
+ *      Return: pixd (8, 16 or 32 bpp)
+ *
+ *  Notes:
+ *      (1) This does a convolution with a separable kernel that is
+ *          is a sequence of convolutions in x and y.  The two
+ *          one-dimensional kernel components must be input separately;
+ *          the full kernel is the product of these components.
+ *          The support for the full kernel is thus a rectangular region.
+ *      (2) The input pixs must have only one sample/pixel.
+ *          To do a convolution on an RGB image, use pixConvolveSepRGB().
+ *      (3) The parameter @outdepth determines the depth of the result.
+ *          If the kernel is normalized to unit sum, the output values
+ *          can never exceed 255, so an output depth of 8 bpp is sufficient.
+ *          If the kernel is not normalized, it may be necessary to use
+ *          16 or 32 bpp output to avoid overflow.
+ *      (2) The @normflag parameter is used as in pixConvolve().
+ *      (4) The kernel values can be positive or negative, but the
+ *          result for the convolution can only be stored as a positive
+ *          number.  Consequently, if it goes negative, the choices are
+ *          to clip to 0 or take the absolute value.  We're choosing
+ *          the former for now.  Another possibility would be to output
+ *          a second unsigned image for the negative values.
+ *      (5) Warning: if you use l_setConvolveSampling() to get a
+ *          subsampled output, and the sampling factor is larger than
+ *          the kernel half-width, it is faster to use the non-separable
+ *          version pixConvolve().  This is because the first convolution
+ *          here must be done on every raster line, regardless of the
+ *          vertical sampling factor.  If the sampling factor is smaller
+ *          than kernel half-width, it's faster to use the separable
+ *          convolution.
+ *      (6) This uses mirrored borders to avoid special casing on
+ *          the boundaries.
+ */
+PIX *
+pixConvolveSep(PIX       *pixs,
+               L_KERNEL  *kelx,
+               L_KERNEL  *kely,
+               l_int32    outdepth,
+               l_int32    normflag)
+{
+l_int32    d, xfact, yfact;
+L_KERNEL  *kelxn, *kelyn;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixConvolveSep");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8, 16, or 32 bpp", procName, NULL);
+    if (!kelx)
+        return (PIX *)ERROR_PTR("kelx not defined", procName, NULL);
+    if (!kely)
+        return (PIX *)ERROR_PTR("kely not defined", procName, NULL);
+
+    xfact = ConvolveSamplingFactX;
+    yfact = ConvolveSamplingFactY;
+    if (normflag) {
+        kelxn = kernelNormalize(kelx, 1000.0);
+        kelyn = kernelNormalize(kely, 0.001);
+        l_setConvolveSampling(xfact, 1);
+        pixt = pixConvolve(pixs, kelxn, 32, 0);
+        l_setConvolveSampling(1, yfact);
+        pixd = pixConvolve(pixt, kelyn, outdepth, 0);
+        l_setConvolveSampling(xfact, yfact);  /* restore */
+        kernelDestroy(&kelxn);
+        kernelDestroy(&kelyn);
+    } else {  /* don't normalize */
+        l_setConvolveSampling(xfact, 1);
+        pixt = pixConvolve(pixs, kelx, 32, 0);
+        l_setConvolveSampling(1, yfact);
+        pixd = pixConvolve(pixt, kely, outdepth, 0);
+        l_setConvolveSampling(xfact, yfact);
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvolveRGB()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              kernel
+ *      Return: pixd (32 bpp rgb)
+ *
+ *  Notes:
+ *      (1) This gives a convolution on an RGB image using an
+ *          arbitrary kernel (which we normalize to keep each
+ *          component within the range [0 ... 255].
+ *      (2) The input pixs must be RGB.
+ *      (3) The kernel values can be positive or negative, but the
+ *          result for the convolution can only be stored as a positive
+ *          number.  Consequently, if it goes negative, we clip the
+ *          result to 0.
+ *      (4) To get a subsampled output, call l_setConvolveSampling().
+ *          The time to make a subsampled output is reduced by the
+ *          product of the sampling factors.
+ *      (5) This uses a mirrored border to avoid special casing on
+ *          the boundaries.
+ */
+PIX *
+pixConvolveRGB(PIX       *pixs,
+               L_KERNEL  *kel)
+{
+PIX  *pixt, *pixr, *pixg, *pixb, *pixd;
+
+    PROCNAME("pixConvolveRGB");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs is not 32 bpp", procName, NULL);
+    if (!kel)
+        return (PIX *)ERROR_PTR("kel not defined", procName, NULL);
+
+    pixt = pixGetRGBComponent(pixs, COLOR_RED);
+    pixr = pixConvolve(pixt, kel, 8, 1);
+    pixDestroy(&pixt);
+    pixt = pixGetRGBComponent(pixs, COLOR_GREEN);
+    pixg = pixConvolve(pixt, kel, 8, 1);
+    pixDestroy(&pixt);
+    pixt = pixGetRGBComponent(pixs, COLOR_BLUE);
+    pixb = pixConvolve(pixt, kel, 8, 1);
+    pixDestroy(&pixt);
+    pixd = pixCreateRGBImage(pixr, pixg, pixb);
+
+    pixDestroy(&pixr);
+    pixDestroy(&pixg);
+    pixDestroy(&pixb);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvolveRGBSep()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              kelx (x-dependent kernel)
+ *              kely (y-dependent kernel)
+ *      Return: pixd (32 bpp rgb)
+ *
+ *  Notes:
+ *      (1) This does a convolution on an RGB image using a separable
+ *          kernel that is a sequence of convolutions in x and y.  The two
+ *          one-dimensional kernel components must be input separately;
+ *          the full kernel is the product of these components.
+ *          The support for the full kernel is thus a rectangular region.
+ *      (2) The kernel values can be positive or negative, but the
+ *          result for the convolution can only be stored as a positive
+ *          number.  Consequently, if it goes negative, we clip the
+ *          result to 0.
+ *      (3) To get a subsampled output, call l_setConvolveSampling().
+ *          The time to make a subsampled output is reduced by the
+ *          product of the sampling factors.
+ *      (4) This uses a mirrored border to avoid special casing on
+ *          the boundaries.
+ */
+PIX *
+pixConvolveRGBSep(PIX       *pixs,
+                  L_KERNEL  *kelx,
+                  L_KERNEL  *kely)
+{
+PIX  *pixt, *pixr, *pixg, *pixb, *pixd;
+
+    PROCNAME("pixConvolveRGBSep");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs is not 32 bpp", procName, NULL);
+    if (!kelx || !kely)
+        return (PIX *)ERROR_PTR("kelx, kely not both defined", procName, NULL);
+
+    pixt = pixGetRGBComponent(pixs, COLOR_RED);
+    pixr = pixConvolveSep(pixt, kelx, kely, 8, 1);
+    pixDestroy(&pixt);
+    pixt = pixGetRGBComponent(pixs, COLOR_GREEN);
+    pixg = pixConvolveSep(pixt, kelx, kely, 8, 1);
+    pixDestroy(&pixt);
+    pixt = pixGetRGBComponent(pixs, COLOR_BLUE);
+    pixb = pixConvolveSep(pixt, kelx, kely, 8, 1);
+    pixDestroy(&pixt);
+    pixd = pixCreateRGBImage(pixr, pixg, pixb);
+
+    pixDestroy(&pixr);
+    pixDestroy(&pixg);
+    pixDestroy(&pixb);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                  Generic convolution with float array                *
+ *----------------------------------------------------------------------*/
+/*!
+ *  fpixConvolve()
+ *
+ *      Input:  fpixs (32 bit float array)
+ *              kernel
+ *              normflag (1 to normalize kernel to unit sum; 0 otherwise)
+ *      Return: fpixd (32 bit float array)
+ *
+ *  Notes:
+ *      (1) This gives a float convolution with an arbitrary kernel.
+ *      (2) If normflag == 1, the result is normalized by scaling all
+ *          kernel values for a unit sum.  If the sum of kernel values
+ *          is very close to zero, the kernel can not be normalized and
+ *          the convolution will not be performed.  A warning is issued.
+ *      (3) With the FPix, there are no issues about negative
+ *          array or kernel values.  The convolution is performed
+ *          with single precision arithmetic.
+ *      (4) To get a subsampled output, call l_setConvolveSampling().
+ *          The time to make a subsampled output is reduced by the
+ *          product of the sampling factors.
+ *      (5) This uses a mirrored border to avoid special casing on
+ *          the boundaries.
+ */
+FPIX *
+fpixConvolve(FPIX      *fpixs,
+             L_KERNEL  *kel,
+             l_int32    normflag)
+{
+l_int32     i, j, id, jd, k, m, w, h, wd, hd, sx, sy, cx, cy, wplt, wpld;
+l_float32   val;
+l_float32  *datat, *datad, *linet, *lined;
+l_float32   sum;
+L_KERNEL   *keli, *keln;
+FPIX       *fpixt, *fpixd;
+
+    PROCNAME("fpixConvolve");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+    if (!kel)
+        return (FPIX *)ERROR_PTR("kel not defined", procName, NULL);
+
+    keli = kernelInvert(kel);
+    kernelGetParameters(keli, &sy, &sx, &cy, &cx);
+    if (normflag)
+        keln = kernelNormalize(keli, 1.0);
+    else
+        keln = kernelCopy(keli);
+
+    fpixGetDimensions(fpixs, &w, &h);
+    fpixt = fpixAddMirroredBorder(fpixs, cx, sx - cx, cy, sy - cy);
+    if (!fpixt)
+        return (FPIX *)ERROR_PTR("fpixt not made", procName, NULL);
+
+    wd = (w + ConvolveSamplingFactX - 1) / ConvolveSamplingFactX;
+    hd = (h + ConvolveSamplingFactY - 1) / ConvolveSamplingFactY;
+    fpixd = fpixCreate(wd, hd);
+    datat = fpixGetData(fpixt);
+    datad = fpixGetData(fpixd);
+    wplt = fpixGetWpl(fpixt);
+    wpld = fpixGetWpl(fpixd);
+    for (i = 0, id = 0; id < hd; i += ConvolveSamplingFactY, id++) {
+        lined = datad + id * wpld;
+        for (j = 0, jd = 0; jd < wd; j += ConvolveSamplingFactX, jd++) {
+            sum = 0.0;
+            for (k = 0; k < sy; k++) {
+                linet = datat + (i + k) * wplt;
+                for (m = 0; m < sx; m++) {
+                    val = *(linet + j + m);
+                    sum += val * keln->data[k][m];
+                }
+            }
+            *(lined + jd) = sum;
+        }
+    }
+
+    kernelDestroy(&keli);
+    kernelDestroy(&keln);
+    fpixDestroy(&fpixt);
+    return fpixd;
+}
+
+
+/*!
+ *  fpixConvolveSep()
+ *
+ *      Input:  fpixs (32 bit float array)
+ *              kelx (x-dependent kernel)
+ *              kely (y-dependent kernel)
+ *              normflag (1 to normalize kernel to unit sum; 0 otherwise)
+ *      Return: fpixd (32 bit float array)
+ *
+ *  Notes:
+ *      (1) This does a convolution with a separable kernel that is
+ *          is a sequence of convolutions in x and y.  The two
+ *          one-dimensional kernel components must be input separately;
+ *          the full kernel is the product of these components.
+ *          The support for the full kernel is thus a rectangular region.
+ *      (2) The normflag parameter is used as in fpixConvolve().
+ *      (3) Warning: if you use l_setConvolveSampling() to get a
+ *          subsampled output, and the sampling factor is larger than
+ *          the kernel half-width, it is faster to use the non-separable
+ *          version pixConvolve().  This is because the first convolution
+ *          here must be done on every raster line, regardless of the
+ *          vertical sampling factor.  If the sampling factor is smaller
+ *          than kernel half-width, it's faster to use the separable
+ *          convolution.
+ *      (4) This uses mirrored borders to avoid special casing on
+ *          the boundaries.
+ */
+FPIX *
+fpixConvolveSep(FPIX      *fpixs,
+                L_KERNEL  *kelx,
+                L_KERNEL  *kely,
+                l_int32    normflag)
+{
+l_int32    xfact, yfact;
+L_KERNEL  *kelxn, *kelyn;
+FPIX      *fpixt, *fpixd;
+
+    PROCNAME("fpixConvolveSep");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!kelx)
+        return (FPIX *)ERROR_PTR("kelx not defined", procName, NULL);
+    if (!kely)
+        return (FPIX *)ERROR_PTR("kely not defined", procName, NULL);
+
+    xfact = ConvolveSamplingFactX;
+    yfact = ConvolveSamplingFactY;
+    if (normflag) {
+        kelxn = kernelNormalize(kelx, 1.0);
+        kelyn = kernelNormalize(kely, 1.0);
+        l_setConvolveSampling(xfact, 1);
+        fpixt = fpixConvolve(fpixs, kelxn, 0);
+        l_setConvolveSampling(1, yfact);
+        fpixd = fpixConvolve(fpixt, kelyn, 0);
+        l_setConvolveSampling(xfact, yfact);  /* restore */
+        kernelDestroy(&kelxn);
+        kernelDestroy(&kelyn);
+    } else {  /* don't normalize */
+        l_setConvolveSampling(xfact, 1);
+        fpixt = fpixConvolve(fpixs, kelx, 0);
+        l_setConvolveSampling(1, yfact);
+        fpixd = fpixConvolve(fpixt, kely, 0);
+        l_setConvolveSampling(xfact, yfact);
+    }
+
+    fpixDestroy(&fpixt);
+    return fpixd;
+}
+
+
+/*------------------------------------------------------------------------*
+ *              Convolution with bias (for non-negative output)           *
+ *------------------------------------------------------------------------*/
+/*!
+ *  pixConvolveWithBias()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              kel1
+ *              kel2  (can be null; use if separable)
+ *              force8 (if 1, force output to 8 bpp; otherwise, determine
+ *                      output depth by the dynamic range of pixel values)
+ *              &bias (<return> applied bias)
+ *      Return: pixd (8 or 16 bpp)
+ *
+ *  Notes:
+ *      (1) This does a convolution with either a single kernel or
+ *          a pair of separable kernels, and automatically applies whatever
+ *          bias (shift) is required so that the resulting pixel values
+ *          are non-negative.
+ *      (2) The kernel is always normalized.  If there are no negative
+ *          values in the kernel, a standard normalized convolution is
+ *          performed, with 8 bpp output.  If the sum of kernel values is
+ *          very close to zero, the kernel can not be normalized and
+ *          the convolution will not be performed.  An error message results.
+ *      (3) If there are negative values in the kernel, the pix is
+ *          converted to an fpix, the convolution is done on the fpix, and
+ *          a bias (shift) may need to be applied.
+ *      (4) If force8 == TRUE and the range of values after the convolution
+ *          is > 255, the output values will be scaled to fit in [0 ... 255].
+ *          If force8 == FALSE, the output will be either 8 or 16 bpp,
+ *          to accommodate the dynamic range of output values without scaling.
+ */
+PIX *
+pixConvolveWithBias(PIX       *pixs,
+                    L_KERNEL  *kel1,
+                    L_KERNEL  *kel2,
+                    l_int32    force8,
+                    l_int32   *pbias)
+{
+l_int32    outdepth;
+l_float32  min1, min2, min, minval, maxval, range;
+FPIX      *fpix1, *fpix2;
+PIX       *pixd;
+
+    PROCNAME("pixConvolveWithBias");
+
+    if (!pbias)
+        return (PIX *)ERROR_PTR("&bias not defined", procName, NULL);
+    *pbias = 0;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
+    if (!kel1)
+        return (PIX *)ERROR_PTR("kel1 not defined", procName, NULL);
+
+        /* Determine if negative values can be produced in the convolution */
+    kernelGetMinMax(kel1, &min1, NULL);
+    min2 = 0.0;
+    if (kel2)
+        kernelGetMinMax(kel2, &min2, NULL);
+    min = L_MIN(min1, min2);
+
+    if (min >= 0.0) {
+        if (!kel2)
+            return pixConvolve(pixs, kel1, 8, 1);
+        else
+            return pixConvolveSep(pixs, kel1, kel2, 8, 1);
+    }
+
+        /* Bias may need to be applied; convert to fpix and convolve */
+    fpix1 = pixConvertToFPix(pixs, 1);
+    if (!kel2)
+        fpix2 = fpixConvolve(fpix1, kel1, 1);
+    else
+        fpix2 = fpixConvolveSep(fpix1, kel1, kel2, 1);
+    fpixDestroy(&fpix1);
+
+        /* Determine the bias and the dynamic range.
+         * If the dynamic range is <= 255, just shift the values by the
+         * bias, if any.
+         * If the dynamic range is > 255, there are two cases:
+         *    (1) the output depth is not forced to 8 bpp
+         *           ==> apply the bias without scaling; outdepth = 16
+         *    (2) the output depth is forced to 8
+         *           ==> linearly map the pixel values to [0 ... 255].  */
+    fpixGetMin(fpix2, &minval, NULL, NULL);
+    fpixGetMax(fpix2, &maxval, NULL, NULL);
+    range = maxval - minval;
+    *pbias = (minval < 0.0) ? -minval : 0.0;
+    fpixAddMultConstant(fpix2, *pbias, 1.0);  /* shift: min val ==> 0 */
+    if (range <= 255 || !force8) {  /* no scaling of output values */
+        outdepth = (range > 255) ? 16 : 8;
+    } else {  /* scale output values to fit in 8 bpp */
+        fpixAddMultConstant(fpix2, 0.0, (255.0 / range));
+        outdepth = 8;
+    }
+
+        /* Convert back to pix; it won't do any clipping */
+    pixd = fpixConvertToPix(fpix2, outdepth, L_CLIP_TO_ZERO, 0);
+    fpixDestroy(&fpix2);
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                Set parameter for convolution subsampling               *
+ *------------------------------------------------------------------------*/
+/*!
+ *  l_setConvolveSampling()
+
+ *
+ *      Input:  xfact, yfact (integer >= 1)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This sets the x and y output subsampling factors for generic pix
+ *          and fpix convolution.  The default values are 1 (no subsampling).
+ */
+void
+l_setConvolveSampling(l_int32  xfact,
+                      l_int32  yfact)
+{
+    if (xfact < 1) xfact = 1;
+    if (yfact < 1) yfact = 1;
+    ConvolveSamplingFactX = xfact;
+    ConvolveSamplingFactY = yfact;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                          Additive gaussian noise                       *
+ *------------------------------------------------------------------------*/
+/*!
+ *  pixAddGaussianNoise()
+ *
+ *      Input:  pixs (8 bpp gray or 32 bpp rgb; no colormap)
+ *              stdev (of noise)
+ *      Return: pixd (8 or 32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This adds noise to each pixel, taken from a normal
+ *          distribution with zero mean and specified standard deviation.
+ */
+PIX *
+pixAddGaussianNoise(PIX       *pixs,
+                    l_float32  stdev)
+{
+l_int32    i, j, w, h, d, wpls, wpld, val, rval, gval, bval;
+l_uint32   pixel;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixAddGaussianNoise");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+
+    pixd = pixCreateTemplateNoInit(pixs);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            if (d == 8) {
+                val = GET_DATA_BYTE(lines, j);
+                val += (l_int32)(stdev * gaussDistribSampling() + 0.5);
+                val = L_MIN(255, L_MAX(0, val));
+                SET_DATA_BYTE(lined, j, val);
+            } else {  /* d = 32 */
+                pixel = *(lines + j);
+                extractRGBValues(pixel, &rval, &gval, &bval);
+                rval += (l_int32)(stdev * gaussDistribSampling() + 0.5);
+                rval = L_MIN(255, L_MAX(0, rval));
+                gval += (l_int32)(stdev * gaussDistribSampling() + 0.5);
+                gval = L_MIN(255, L_MAX(0, gval));
+                bval += (l_int32)(stdev * gaussDistribSampling() + 0.5);
+                bval = L_MIN(255, L_MAX(0, bval));
+                composeRGBPixel(rval, gval, bval, lined + j);
+            }
+        }
+    }
+    return pixd;
+}
+
+
+/*!
+ *  gaussDistribSampling()
+ *
+ *      Return: gaussian distributed variable with zero mean and unit stdev
+ *
+ *  Notes:
+ *      (1) For an explanation of the Box-Muller method for generating
+ *          a normally distributed random variable with zero mean and
+ *          unit standard deviation, see Numerical Recipes in C,
+ *          2nd edition, p. 288ff.
+ *      (2) This can be called sequentially to get samples that can be
+ *          used for adding noise to each pixel of an image, for example.
+ */
+l_float32
+gaussDistribSampling()
+{
+static l_int32    select = 0;  /* flips between 0 and 1 on successive calls */
+static l_float32  saveval;
+l_float32         frand, xval, yval, rsq, factor;
+
+    if (select == 0) {
+        while (1) {  /* choose a point in a 2x2 square, centered at origin */
+            frand = (l_float32)rand() / (l_float32)RAND_MAX;
+            xval = 2.0 * frand - 1.0;
+            frand = (l_float32)rand() / (l_float32)RAND_MAX;
+            yval = 2.0 * frand - 1.0;
+            rsq = xval * xval + yval * yval;
+            if (rsq > 0.0 && rsq < 1.0)  /* point is inside the unit circle */
+                break;
+        }
+        factor = sqrt(-2.0 * log(rsq) / rsq);
+        saveval = xval * factor;
+        select = 1;
+        return yval * factor;
+    }
+    else {
+        select = 0;
+        return saveval;
+    }
+}
diff --git a/src/correlscore.c b/src/correlscore.c
new file mode 100644 (file)
index 0000000..7fa86eb
--- /dev/null
@@ -0,0 +1,869 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ * correlscore.c
+ *
+ *     These are functions for computing correlation between
+ *     pairs of 1 bpp images.
+ *
+ *     Optimized 2 pix correlators (for jbig2 clustering)
+ *         l_int32     pixCorrelationScore()
+ *         l_int32     pixCorrelationScoreThresholded()
+ *
+ *     Simple 2 pix correlators
+ *         l_int32     pixCorrelationScoreSimple()
+ *         l_int32     pixCorrelationScoreShifted()
+ *
+ *     There are other, more application-oriented functions, that
+ *     compute the correlation between two binary images, taking into
+ *     account small translational shifts, between two binary images.
+ *     These are:
+ *         compare.c:     pixBestCorrelation()
+ *                        Uses coarse-to-fine translations of full image
+ *         recogident.c:  pixCorrelationBestShift()
+ *                        Uses small shifts between c.c. centroids.
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+
+/* -------------------------------------------------------------------- *
+ *           Optimized 2 pix correlators (for jbig2 clustering)         *
+ * -------------------------------------------------------------------- */
+/*!
+ *  pixCorrelationScore()
+ *
+ *      Input:  pix1   (test pix, 1 bpp)
+ *              pix2   (exemplar pix, 1 bpp)
+ *              area1  (number of on pixels in pix1)
+ *              area2  (number of on pixels in pix2)
+ *              delx   (x comp of centroid difference)
+ *              dely   (y comp of centroid difference)
+ *              maxdiffw (max width difference of pix1 and pix2)
+ *              maxdiffh (max height difference of pix1 and pix2)
+ *              tab    (sum tab for byte)
+ *              &score (<return> correlation score)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Note: we check first that the two pix are roughly the same size.
+ *  For jbclass (jbig2) applications at roughly 300 ppi, maxdiffw and
+ *  maxdiffh should be at least 2.
+ *
+ *  Only if they meet that criterion do we compare the bitmaps.
+ *  The centroid difference is used to align the two images to the
+ *  nearest integer for the correlation.
+ *
+ *  The correlation score is the ratio of the square of the number of
+ *  pixels in the AND of the two bitmaps to the product of the number
+ *  of ON pixels in each.  Denote the number of ON pixels in pix1
+ *  by |1|, the number in pix2 by |2|, and the number in the AND
+ *  of pix1 and pix2 by |1 & 2|.  The correlation score is then
+ *  (|1 & 2|)**2 / (|1|*|2|).
+ *
+ *  This score is compared with an input threshold, which can
+ *  be modified depending on the weight of the template.
+ *  The modified threshold is
+ *     thresh + (1.0 - thresh) * weight * R
+ *  where
+ *     weight is a fixed input factor between 0.0 and 1.0
+ *     R = |2| / area(2)
+ *  and area(2) is the total number of pixels in 2 (i.e., width x height).
+ *
+ *  To understand why a weight factor is useful, consider what happens
+ *  with thick, sans-serif characters that look similar and have a value
+ *  of R near 1.  Different characters can have a high correlation value,
+ *  and the classifier will make incorrect substitutions.  The weight
+ *  factor raises the threshold for these characters.
+ *
+ *  Yet another approach to reduce such substitutions is to run the classifier
+ *  in a non-greedy way, matching to the template with the highest
+ *  score, not the first template with a score satisfying the matching
+ *  constraint.  However, this is not particularly effective.
+ *
+ *  The implementation here gives the same result as in
+ *  pixCorrelationScoreSimple(), where a temporary Pix is made to hold
+ *  the AND and implementation uses rasterop:
+ *      pixt = pixCreateTemplate(pix1);
+ *      pixRasterop(pixt, idelx, idely, wt, ht, PIX_SRC, pix2, 0, 0);
+ *      pixRasterop(pixt, 0, 0, wi, hi, PIX_SRC & PIX_DST, pix1, 0, 0);
+ *      pixCountPixels(pixt, &count, tab);
+ *      pixDestroy(&pixt);
+ *  However, here it is done in a streaming fashion, counting as it goes,
+ *  and touching memory exactly once, giving a 3-4x speedup over the
+ *  simple implementation.  This very fast correlation matcher was
+ *  contributed by William Rucklidge.
+ */
+l_int32
+pixCorrelationScore(PIX        *pix1,
+                    PIX        *pix2,
+                    l_int32     area1,
+                    l_int32     area2,
+                    l_float32   delx,   /* x(1) - x(3) */
+                    l_float32   dely,   /* y(1) - y(3) */
+                    l_int32     maxdiffw,
+                    l_int32     maxdiffh,
+                    l_int32    *tab,
+                    l_float32  *pscore)
+{
+l_int32    wi, hi, wt, ht, delw, delh, idelx, idely, count;
+l_int32    wpl1, wpl2, lorow, hirow, locol, hicol;
+l_int32    x, y, pix1lskip, pix2lskip, rowwords1, rowwords2;
+l_uint32   word1, word2, andw;
+l_uint32  *row1, *row2;
+
+    PROCNAME("pixCorrelationScore");
+
+    if (!pscore)
+        return ERROR_INT("&score not defined", procName, 1);
+    *pscore = 0.0;
+    if (!pix1 || pixGetDepth(pix1) != 1)
+        return ERROR_INT("pix1 undefined or not 1 bpp", procName, 1);
+    if (!pix2 || pixGetDepth(pix2) != 1)
+        return ERROR_INT("pix2 undefined or not 1 bpp", procName, 1);
+    if (!tab)
+        return ERROR_INT("tab not defined", procName, 1);
+    if (area1 <= 0 || area2 <= 0)
+        return ERROR_INT("areas must be > 0", procName, 1);
+
+        /* Eliminate based on size difference */
+    pixGetDimensions(pix1, &wi, &hi, NULL);
+    pixGetDimensions(pix2, &wt, &ht, NULL);
+    delw = L_ABS(wi - wt);
+    if (delw > maxdiffw)
+        return 0;
+    delh = L_ABS(hi - ht);
+    if (delh > maxdiffh)
+        return 0;
+
+        /* Round difference to nearest integer */
+    if (delx >= 0)
+        idelx = (l_int32)(delx + 0.5);
+    else
+        idelx = (l_int32)(delx - 0.5);
+    if (dely >= 0)
+        idely = (l_int32)(dely + 0.5);
+    else
+        idely = (l_int32)(dely - 0.5);
+
+    count = 0;
+    wpl1 = pixGetWpl(pix1);
+    wpl2 = pixGetWpl(pix2);
+    rowwords2 = wpl2;
+
+        /* What rows of pix1 need to be considered?  Only those underlying the
+         * shifted pix2. */
+    lorow = L_MAX(idely, 0);
+    hirow = L_MIN(ht + idely, hi);
+
+        /* Get the pointer to the first row of each image that will be
+         * considered. */
+    row1 = pixGetData(pix1) + wpl1 * lorow;
+    row2 = pixGetData(pix2) + wpl2 * (lorow - idely);
+
+        /* Similarly, figure out which columns of pix1 will be considered. */
+    locol = L_MAX(idelx, 0);
+    hicol = L_MIN(wt + idelx, wi);
+
+    if (idelx >= 32) {
+            /* pix2 is shifted far enough to the right that pix1's first
+             * word(s) won't contribute to the count.  Increment its
+             * pointer to point to the first word that will contribute,
+             * and adjust other values accordingly. */
+        pix1lskip = idelx >> 5;  /* # of words to skip on left */
+        row1 += pix1lskip;
+        locol -= pix1lskip << 5;
+        hicol -= pix1lskip << 5;
+        idelx &= 31;
+    } else if (idelx <= -32) {
+            /* pix2 is shifted far enough to the left that its first word(s)
+             * won't contribute to the count.  Increment its pointer
+             * to point to the first word that will contribute,
+             * and adjust other values accordingly. */
+        pix2lskip = -((idelx + 31) >> 5);  /* # of words to skip on left */
+        row2 += pix2lskip;
+        rowwords2 -= pix2lskip;
+        idelx += pix2lskip << 5;
+    }
+
+    if ((locol >= hicol) || (lorow >= hirow)) {  /* there is no overlap */
+        count = 0;
+    } else {
+            /* How many words of each row of pix1 need to be considered? */
+        rowwords1 = (hicol + 31) >> 5;
+
+        if (idelx == 0) {
+                /* There's no lateral offset; simple case. */
+            for (y = lorow; y < hirow; y++, row1 += wpl1, row2 += wpl2) {
+                for (x = 0; x < rowwords1; x++) {
+                    andw = row1[x] & row2[x];
+                    count += tab[andw & 0xff] +
+                        tab[(andw >> 8) & 0xff] +
+                        tab[(andw >> 16) & 0xff] +
+                        tab[andw >> 24];
+                }
+            }
+        } else if (idelx > 0) {
+                /* pix2 is shifted to the right.  word 0 of pix1 is touched by
+                 * word 0 of pix2; word 1 of pix1 is touched by word 0 and word
+                 * 1 of pix2, and so on up to the last word of pix1 (word N),
+                 * which is touched by words N-1 and N of pix1... if there is a
+                 * word N.  Handle the two cases (pix2 has N-1 words and pix2
+                 * has at least N words) separately.
+                 *
+                 * Note: we know that pix2 has at least N-1 words (i.e.,
+                 * rowwords2 >= rowwords1 - 1) by the following logic.
+                 * We can pretend that idelx <= 31 because the >= 32 logic
+                 * above adjusted everything appropriately.  Then
+                 * hicol <= wt + idelx <= wt + 31, so
+                 * hicol + 31 <= wt + 62
+                 * rowwords1 = (hicol + 31) >> 5 <= (wt + 62) >> 5
+                 * rowwords2 == (wt + 31) >> 5, so
+                 * rowwords1 <= rowwords2 + 1 */
+            if (rowwords2 < rowwords1) {
+                for (y = lorow; y < hirow; y++, row1 += wpl1, row2 += wpl2) {
+                        /* Do the first iteration so the loop can be
+                         * branch-free. */
+                    word1 = row1[0];
+                    word2 = row2[0] >> idelx;
+                    andw = word1 & word2;
+                    count += tab[andw & 0xff] +
+                        tab[(andw >> 8) & 0xff] +
+                        tab[(andw >> 16) & 0xff] +
+                        tab[andw >> 24];
+
+                    for (x = 1; x < rowwords2; x++) {
+                        word1 = row1[x];
+                        word2 = (row2[x] >> idelx) |
+                            (row2[x - 1] << (32 - idelx));
+                        andw = word1 & word2;
+                        count += tab[andw & 0xff] +
+                            tab[(andw >> 8) & 0xff] +
+                            tab[(andw >> 16) & 0xff] +
+                            tab[andw >> 24];
+                    }
+
+                        /* Now the last iteration - we know that this is safe
+                         * (i.e.  rowwords1 >= 2) because rowwords1 > rowwords2
+                         * > 0 (if it was 0, we'd have taken the "count = 0"
+                         * fast-path out of here). */
+                    word1 = row1[x];
+                    word2 = row2[x - 1] << (32 - idelx);
+                    andw = word1 & word2;
+                    count += tab[andw & 0xff] +
+                        tab[(andw >> 8) & 0xff] +
+                        tab[(andw >> 16) & 0xff] +
+                        tab[andw >> 24];
+                }
+            } else {
+                for (y = lorow; y < hirow; y++, row1 += wpl1, row2 += wpl2) {
+                        /* Do the first iteration so the loop can be
+                         * branch-free.  This section is the same as above
+                         * except for the different limit on the loop, since
+                         * the last iteration is the same as all the other
+                         * iterations (beyond the first). */
+                    word1 = row1[0];
+                    word2 = row2[0] >> idelx;
+                    andw = word1 & word2;
+                    count += tab[andw & 0xff] +
+                        tab[(andw >> 8) & 0xff] +
+                        tab[(andw >> 16) & 0xff] +
+                        tab[andw >> 24];
+
+                    for (x = 1; x < rowwords1; x++) {
+                        word1 = row1[x];
+                        word2 = (row2[x] >> idelx) |
+                            (row2[x - 1] << (32 - idelx));
+                        andw = word1 & word2;
+                        count += tab[andw & 0xff] +
+                            tab[(andw >> 8) & 0xff] +
+                            tab[(andw >> 16) & 0xff] +
+                            tab[andw >> 24];
+                    }
+                }
+            }
+        } else {
+                /* pix2 is shifted to the left.  word 0 of pix1 is touched by
+                 * word 0 and word 1 of pix2, and so on up to the last word of
+                 * pix1 (word N), which is touched by words N and N+1 of
+                 * pix2... if there is a word N+1.  Handle the two cases (pix2
+                 * has N words and pix2 has at least N+1 words) separately. */
+            if (rowwords1 < rowwords2) {
+                    /* pix2 has at least N+1 words, so every iteration through
+                     * the loop can be the same. */
+                for (y = lorow; y < hirow; y++, row1 += wpl1, row2 += wpl2) {
+                    for (x = 0; x < rowwords1; x++) {
+                        word1 = row1[x];
+                        word2 = row2[x] << -idelx;
+                        word2 |= row2[x + 1] >> (32 + idelx);
+                        andw = word1 & word2;
+                        count += tab[andw & 0xff] +
+                            tab[(andw >> 8) & 0xff] +
+                            tab[(andw >> 16) & 0xff] +
+                            tab[andw >> 24];
+                    }
+                }
+            } else {
+                    /* pix2 has only N words, so the last iteration is broken
+                     * out. */
+                for (y = lorow; y < hirow; y++, row1 += wpl1, row2 += wpl2) {
+                    for (x = 0; x < rowwords1 - 1; x++) {
+                        word1 = row1[x];
+                        word2 = row2[x] << -idelx;
+                        word2 |= row2[x + 1] >> (32 + idelx);
+                        andw = word1 & word2;
+                        count += tab[andw & 0xff] +
+                            tab[(andw >> 8) & 0xff] +
+                            tab[(andw >> 16) & 0xff] +
+                            tab[andw >> 24];
+                    }
+
+                    word1 = row1[x];
+                    word2 = row2[x] << -idelx;
+                    andw = word1 & word2;
+                    count += tab[andw & 0xff] +
+                        tab[(andw >> 8) & 0xff] +
+                        tab[(andw >> 16) & 0xff] +
+                        tab[andw >> 24];
+                }
+            }
+        }
+    }
+
+    *pscore = (l_float32)count * (l_float32)count /
+              ((l_float32)area1 * (l_float32)area2);
+/*    fprintf(stderr, "score = %5.3f, count = %d, area1 = %d, area2 = %d\n",
+             *pscore, count, area1, area2); */
+    return 0;
+}
+
+
+/*!
+ *  pixCorrelationScoreThresholded()
+ *
+ *      Input:  pix1   (test pix, 1 bpp)
+ *              pix2   (exemplar pix, 1 bpp)
+ *              area1  (number of on pixels in pix1)
+ *              area2  (number of on pixels in pix2)
+ *              delx   (x comp of centroid difference)
+ *              dely   (y comp of centroid difference)
+ *              maxdiffw (max width difference of pix1 and pix2)
+ *              maxdiffh (max height difference of pix1 and pix2)
+ *              tab    (sum tab for byte)
+ *              downcount (count of 1 pixels below each row of pix1)
+ *              score_threshold
+ *      Return: whether the correlation score is >= score_threshold
+ *
+ *
+ *  Note: we check first that the two pix are roughly the same size.
+ *  Only if they meet that criterion do we compare the bitmaps.
+ *  The centroid difference is used to align the two images to the
+ *  nearest integer for the correlation.
+ *
+ *  The correlation score is the ratio of the square of the number of
+ *  pixels in the AND of the two bitmaps to the product of the number
+ *  of ON pixels in each.  Denote the number of ON pixels in pix1
+ *  by |1|, the number in pix2 by |2|, and the number in the AND
+ *  of pix1 and pix2 by |1 & 2|.  The correlation score is then
+ *  (|1 & 2|)**2 / (|1|*|2|).
+ *
+ *  This score is compared with an input threshold, which can
+ *  be modified depending on the weight of the template.
+ *  The modified threshold is
+ *     thresh + (1.0 - thresh) * weight * R
+ *  where
+ *     weight is a fixed input factor between 0.0 and 1.0
+ *     R = |2| / area(2)
+ *  and area(2) is the total number of pixels in 2 (i.e., width x height).
+ *
+ *  To understand why a weight factor is useful, consider what happens
+ *  with thick, sans-serif characters that look similar and have a value
+ *  of R near 1.  Different characters can have a high correlation value,
+ *  and the classifier will make incorrect substitutions.  The weight
+ *  factor raises the threshold for these characters.
+ *
+ *  Yet another approach to reduce such substitutions is to run the classifier
+ *  in a non-greedy way, matching to the template with the highest
+ *  score, not the first template with a score satisfying the matching
+ *  constraint.  However, this is not particularly effective.
+ *
+ *  This very fast correlation matcher was contributed by William Rucklidge.
+ */
+l_int32
+pixCorrelationScoreThresholded(PIX       *pix1,
+                               PIX       *pix2,
+                               l_int32    area1,
+                               l_int32    area2,
+                               l_float32  delx,   /* x(1) - x(3) */
+                               l_float32  dely,   /* y(1) - y(3) */
+                               l_int32    maxdiffw,
+                               l_int32    maxdiffh,
+                               l_int32   *tab,
+                               l_int32   *downcount,
+                               l_float32  score_threshold)
+{
+l_int32    wi, hi, wt, ht, delw, delh, idelx, idely, count;
+l_int32    wpl1, wpl2, lorow, hirow, locol, hicol, untouchable;
+l_int32    x, y, pix1lskip, pix2lskip, rowwords1, rowwords2;
+l_uint32   word1, word2, andw;
+l_uint32  *row1, *row2;
+l_float32  score;
+l_int32    threshold;
+
+    PROCNAME("pixCorrelationScoreThresholded");
+
+    if (!pix1 || pixGetDepth(pix1) != 1)
+        return ERROR_INT("pix1 undefined or not 1 bpp", procName, 0);
+    if (!pix2 || pixGetDepth(pix2) != 1)
+        return ERROR_INT("pix2 undefined or not 1 bpp", procName, 0);
+    if (!tab)
+        return ERROR_INT("tab not defined", procName, 0);
+    if (area1 <= 0 || area2 <= 0)
+        return ERROR_INT("areas must be > 0", procName, 0);
+
+        /* Eliminate based on size difference */
+    pixGetDimensions(pix1, &wi, &hi, NULL);
+    pixGetDimensions(pix2, &wt, &ht, NULL);
+    delw = L_ABS(wi - wt);
+    if (delw > maxdiffw)
+        return FALSE;
+    delh = L_ABS(hi - ht);
+    if (delh > maxdiffh)
+        return FALSE;
+
+        /* Round difference to nearest integer */
+    if (delx >= 0)
+        idelx = (l_int32)(delx + 0.5);
+    else
+        idelx = (l_int32)(delx - 0.5);
+    if (dely >= 0)
+        idely = (l_int32)(dely + 0.5);
+    else
+        idely = (l_int32)(dely - 0.5);
+
+        /* Compute the correlation count that is needed so that
+         * count * count / (area1 * area2) >= score_threshold */
+    threshold = (l_int32)ceil(sqrt(score_threshold * area1 * area2));
+
+    count = 0;
+    wpl1 = pixGetWpl(pix1);
+    wpl2 = pixGetWpl(pix2);
+    rowwords2 = wpl2;
+
+        /* What rows of pix1 need to be considered?  Only those underlying the
+         * shifted pix2. */
+    lorow = L_MAX(idely, 0);
+    hirow = L_MIN(ht + idely, hi);
+
+        /* Get the pointer to the first row of each image that will be
+         * considered. */
+    row1 = pixGetData(pix1) + wpl1 * lorow;
+    row2 = pixGetData(pix2) + wpl2 * (lorow - idely);
+    if (hirow <= hi) {
+            /* Some rows of pix1 will never contribute to count */
+        untouchable = downcount[hirow - 1];
+    }
+
+        /* Similarly, figure out which columns of pix1 will be considered. */
+    locol = L_MAX(idelx, 0);
+    hicol = L_MIN(wt + idelx, wi);
+
+    if (idelx >= 32) {
+            /* pix2 is shifted far enough to the right that pix1's first
+             * word(s) won't contribute to the count.  Increment its
+             * pointer to point to the first word that will contribute,
+             * and adjust other values accordingly. */
+        pix1lskip = idelx >> 5;  /* # of words to skip on left */
+        row1 += pix1lskip;
+        locol -= pix1lskip << 5;
+        hicol -= pix1lskip << 5;
+        idelx &= 31;
+    } else if (idelx <= -32) {
+            /* pix2 is shifted far enough to the left that its first word(s)
+             * won't contribute to the count.  Increment its pointer
+             * to point to the first word that will contribute,
+             * and adjust other values accordingly. */
+        pix2lskip = -((idelx + 31) >> 5);  /* # of words to skip on left */
+        row2 += pix2lskip;
+        rowwords2 -= pix2lskip;
+        idelx += pix2lskip << 5;
+    }
+
+    if ((locol >= hicol) || (lorow >= hirow)) {  /* there is no overlap */
+        count = 0;
+    } else {
+            /* How many words of each row of pix1 need to be considered? */
+        rowwords1 = (hicol + 31) >> 5;
+
+        if (idelx == 0) {
+                /* There's no lateral offset; simple case. */
+            for (y = lorow; y < hirow; y++, row1 += wpl1, row2 += wpl2) {
+                for (x = 0; x < rowwords1; x++) {
+                    andw = row1[x] & row2[x];
+                    count += tab[andw & 0xff] +
+                        tab[(andw >> 8) & 0xff] +
+                        tab[(andw >> 16) & 0xff] +
+                        tab[andw >> 24];
+                }
+
+                    /* If the count is over the threshold, no need to
+                     * calculate any further.  Likewise, return early if the
+                     * count plus the maximum count attainable from further
+                     * rows is below the threshold. */
+                if (count >= threshold) return TRUE;
+                if (count + downcount[y] - untouchable < threshold) {
+                    return FALSE;
+                }
+            }
+        } else if (idelx > 0) {
+                /* pix2 is shifted to the right.  word 0 of pix1 is touched by
+                 * word 0 of pix2; word 1 of pix1 is touched by word 0 and word
+                 * 1 of pix2, and so on up to the last word of pix1 (word N),
+                 * which is touched by words N-1 and N of pix1... if there is a
+                 * word N.  Handle the two cases (pix2 has N-1 words and pix2
+                 * has at least N words) separately.
+                 *
+                 * Note: we know that pix2 has at least N-1 words (i.e.,
+                 * rowwords2 >= rowwords1 - 1) by the following logic.
+                 * We can pretend that idelx <= 31 because the >= 32 logic
+                 * above adjusted everything appropriately.  Then
+                 * hicol <= wt + idelx <= wt + 31, so
+                 * hicol + 31 <= wt + 62
+                 * rowwords1 = (hicol + 31) >> 5 <= (wt + 62) >> 5
+                 * rowwords2 == (wt + 31) >> 5, so
+                 * rowwords1 <= rowwords2 + 1 */
+            if (rowwords2 < rowwords1) {
+                for (y = lorow; y < hirow; y++, row1 += wpl1, row2 += wpl2) {
+                        /* Do the first iteration so the loop can be
+                         * branch-free. */
+                    word1 = row1[0];
+                    word2 = row2[0] >> idelx;
+                    andw = word1 & word2;
+                    count += tab[andw & 0xff] +
+                        tab[(andw >> 8) & 0xff] +
+                        tab[(andw >> 16) & 0xff] +
+                        tab[andw >> 24];
+
+                    for (x = 1; x < rowwords2; x++) {
+                        word1 = row1[x];
+                        word2 = (row2[x] >> idelx) |
+                            (row2[x - 1] << (32 - idelx));
+                        andw = word1 & word2;
+                        count += tab[andw & 0xff] +
+                            tab[(andw >> 8) & 0xff] +
+                            tab[(andw >> 16) & 0xff] +
+                            tab[andw >> 24];
+                    }
+
+                        /* Now the last iteration - we know that this is safe
+                         * (i.e.  rowwords1 >= 2) because rowwords1 > rowwords2
+                         * > 0 (if it was 0, we'd have taken the "count = 0"
+                         * fast-path out of here). */
+                    word1 = row1[x];
+                    word2 = row2[x - 1] << (32 - idelx);
+                    andw = word1 & word2;
+                    count += tab[andw & 0xff] +
+                        tab[(andw >> 8) & 0xff] +
+                        tab[(andw >> 16) & 0xff] +
+                        tab[andw >> 24];
+
+                    if (count >= threshold) return TRUE;
+                    if (count + downcount[y] - untouchable < threshold) {
+                        return FALSE;
+                    }
+                }
+            } else {
+                for (y = lorow; y < hirow; y++, row1 += wpl1, row2 += wpl2) {
+                        /* Do the first iteration so the loop can be
+                         * branch-free.  This section is the same as above
+                         * except for the different limit on the loop, since
+                         * the last iteration is the same as all the other
+                         * iterations (beyond the first). */
+                    word1 = row1[0];
+                    word2 = row2[0] >> idelx;
+                    andw = word1 & word2;
+                    count += tab[andw & 0xff] +
+                        tab[(andw >> 8) & 0xff] +
+                        tab[(andw >> 16) & 0xff] +
+                        tab[andw >> 24];
+
+                    for (x = 1; x < rowwords1; x++) {
+                        word1 = row1[x];
+                        word2 = (row2[x] >> idelx) |
+                            (row2[x - 1] << (32 - idelx));
+                        andw = word1 & word2;
+                        count += tab[andw & 0xff] +
+                            tab[(andw >> 8) & 0xff] +
+                            tab[(andw >> 16) & 0xff] +
+                            tab[andw >> 24];
+                    }
+
+                    if (count >= threshold) return TRUE;
+                    if (count + downcount[y] - untouchable < threshold) {
+                        return FALSE;
+                    }
+                }
+            }
+        } else {
+                /* pix2 is shifted to the left.  word 0 of pix1 is touched by
+                 * word 0 and word 1 of pix2, and so on up to the last word of
+                 * pix1 (word N), which is touched by words N and N+1 of
+                 * pix2... if there is a word N+1.  Handle the two cases (pix2
+                 * has N words and pix2 has at least N+1 words) separately. */
+            if (rowwords1 < rowwords2) {
+                    /* pix2 has at least N+1 words, so every iteration through
+                     * the loop can be the same. */
+                for (y = lorow; y < hirow; y++, row1 += wpl1, row2 += wpl2) {
+                    for (x = 0; x < rowwords1; x++) {
+                        word1 = row1[x];
+                        word2 = row2[x] << -idelx;
+                        word2 |= row2[x + 1] >> (32 + idelx);
+                        andw = word1 & word2;
+                        count += tab[andw & 0xff] +
+                            tab[(andw >> 8) & 0xff] +
+                            tab[(andw >> 16) & 0xff] +
+                            tab[andw >> 24];
+                    }
+
+                    if (count >= threshold) return TRUE;
+                    if (count + downcount[y] - untouchable < threshold) {
+                        return FALSE;
+                    }
+                }
+            } else {
+                    /* pix2 has only N words, so the last iteration is broken
+                     * out. */
+                for (y = lorow; y < hirow; y++, row1 += wpl1, row2 += wpl2) {
+                    for (x = 0; x < rowwords1 - 1; x++) {
+                        word1 = row1[x];
+                        word2 = row2[x] << -idelx;
+                        word2 |= row2[x + 1] >> (32 + idelx);
+                        andw = word1 & word2;
+                        count += tab[andw & 0xff] +
+                            tab[(andw >> 8) & 0xff] +
+                            tab[(andw >> 16) & 0xff] +
+                            tab[andw >> 24];
+                    }
+
+                    word1 = row1[x];
+                    word2 = row2[x] << -idelx;
+                    andw = word1 & word2;
+                    count += tab[andw & 0xff] +
+                        tab[(andw >> 8) & 0xff] +
+                        tab[(andw >> 16) & 0xff] +
+                        tab[andw >> 24];
+
+                    if (count >= threshold) return TRUE;
+                    if (count + downcount[y] - untouchable < threshold) {
+                        return FALSE;
+                    }
+                }
+            }
+        }
+    }
+
+    score = (l_float32)count * (l_float32)count /
+             ((l_float32)area1 * (l_float32)area2);
+    if (score >= score_threshold) {
+        fprintf(stderr, "count %d < threshold %d but score %g >= score_threshold %g\n",
+                count, threshold, score, score_threshold);
+    }
+    return FALSE;
+}
+
+
+/* -------------------------------------------------------------------- *
+ *             Simple 2 pix correlators (for jbig2 clustering)          *
+ * -------------------------------------------------------------------- */
+/*!
+ *  pixCorrelationScoreSimple()
+ *
+ *      Input:  pix1   (test pix, 1 bpp)
+ *              pix2   (exemplar pix, 1 bpp)
+ *              area1  (number of on pixels in pix1)
+ *              area2  (number of on pixels in pix2)
+ *              delx   (x comp of centroid difference)
+ *              dely   (y comp of centroid difference)
+ *              maxdiffw (max width difference of pix1 and pix2)
+ *              maxdiffh (max height difference of pix1 and pix2)
+ *              tab    (sum tab for byte)
+ *              &score (<return> correlation score, in range [0.0 ... 1.0])
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This calculates exactly the same value as pixCorrelationScore().
+ *          It is 2-3x slower, but much simpler to understand.
+ *      (2) The returned correlation score is 0.0 if the width or height
+ *          exceed @maxdiffw or @maxdiffh.
+ */
+l_int32
+pixCorrelationScoreSimple(PIX        *pix1,
+                          PIX        *pix2,
+                          l_int32     area1,
+                          l_int32     area2,
+                          l_float32   delx,   /* x(1) - x(3) */
+                          l_float32   dely,   /* y(1) - y(3) */
+                          l_int32     maxdiffw,
+                          l_int32     maxdiffh,
+                          l_int32    *tab,
+                          l_float32  *pscore)
+{
+l_int32  wi, hi, wt, ht, delw, delh, idelx, idely, count;
+PIX     *pixt;
+
+    PROCNAME("pixCorrelationScoreSimple");
+
+    if (!pscore)
+        return ERROR_INT("&score not defined", procName, 1);
+    *pscore = 0.0;
+    if (!pix1 || pixGetDepth(pix1) != 1)
+        return ERROR_INT("pix1 undefined or not 1 bpp", procName, 1);
+    if (!pix2 || pixGetDepth(pix2) != 1)
+        return ERROR_INT("pix2 undefined or not 1 bpp", procName, 1);
+    if (!tab)
+        return ERROR_INT("tab not defined", procName, 1);
+    if (!area1 || !area2)
+        return ERROR_INT("areas must be > 0", procName, 1);
+
+        /* Eliminate based on size difference */
+    pixGetDimensions(pix1, &wi, &hi, NULL);
+    pixGetDimensions(pix2, &wt, &ht, NULL);
+    delw = L_ABS(wi - wt);
+    if (delw > maxdiffw)
+        return 0;
+    delh = L_ABS(hi - ht);
+    if (delh > maxdiffh)
+        return 0;
+
+        /* Round difference to nearest integer */
+    if (delx >= 0)
+        idelx = (l_int32)(delx + 0.5);
+    else
+        idelx = (l_int32)(delx - 0.5);
+    if (dely >= 0)
+        idely = (l_int32)(dely + 0.5);
+    else
+        idely = (l_int32)(dely - 0.5);
+
+        /*  pixt = pixAnd(NULL, pix1, pix2), including shift.
+         *  To insure that pixels are ON only within the
+         *  intersection of pix1 and the shifted pix2:
+         *  (1) Start with pixt cleared and equal in size to pix1.
+         *  (2) Blit the shifted pix2 onto pixt.  Then all ON pixels
+         *      are within the intersection of pix1 and the shifted pix2.
+         *  (3) AND pix1 with pixt. */
+    pixt = pixCreateTemplate(pix1);
+    pixRasterop(pixt, idelx, idely, wt, ht, PIX_SRC, pix2, 0, 0);
+    pixRasterop(pixt, 0, 0, wi, hi, PIX_SRC & PIX_DST, pix1, 0, 0);
+    pixCountPixels(pixt, &count, tab);
+    pixDestroy(&pixt);
+
+    *pscore = (l_float32)count * (l_float32)count /
+               ((l_float32)area1 * (l_float32)area2);
+/*    fprintf(stderr, "score = %5.3f, count = %d, area1 = %d, area2 = %d\n",
+             *pscore, count, area1, area2); */
+    return 0;
+}
+
+
+/*!
+ *  pixCorrelationScoreShifted()
+ *
+ *      Input:  pix1   (1 bpp)
+ *              pix2   (1 bpp)
+ *              area1  (number of on pixels in pix1)
+ *              area2  (number of on pixels in pix2)
+ *              delx (x translation of pix2 relative to pix1)
+ *              dely (y translation of pix2 relative to pix1)
+ *              tab    (sum tab for byte)
+ *              &score (<return> correlation score)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This finds the correlation between two 1 bpp images,
+ *          when pix2 is shifted by (delx, dely) with respect
+ *          to each other.
+ *      (2) This is implemented by starting with a copy of pix1 and
+ *          ANDing its pixels with those of a shifted pix2.
+ *      (3) Get the pixel counts for area1 and area2 using piCountPixels().
+ *      (4) A good estimate for a shift that would maximize the correlation
+ *          is to align the centroids (cx1, cy1; cx2, cy2), giving the
+ *          relative translations etransx and etransy:
+ *             etransx = cx1 - cx2
+ *             etransy = cy1 - cy2
+ *          Typically delx is chosen to be near etransx; ditto for dely.
+ *          This function is used in pixBestCorrelation(), where the
+ *          translations delx and dely are varied to find the best alignment.
+ *      (5) We do not check the sizes of pix1 and pix2, because they should
+ *          be comparable.
+ */
+l_int32
+pixCorrelationScoreShifted(PIX        *pix1,
+                           PIX        *pix2,
+                           l_int32     area1,
+                           l_int32     area2,
+                           l_int32     delx,
+                           l_int32     dely,
+                           l_int32    *tab,
+                           l_float32  *pscore)
+{
+l_int32  w1, h1, w2, h2, count;
+PIX     *pixt;
+
+    PROCNAME("pixCorrelationScoreShifted");
+
+    if (!pscore)
+        return ERROR_INT("&score not defined", procName, 1);
+    *pscore = 0.0;
+    if (!pix1 || pixGetDepth(pix1) != 1)
+        return ERROR_INT("pix1 undefined or not 1 bpp", procName, 1);
+    if (!pix2 || pixGetDepth(pix2) != 1)
+        return ERROR_INT("pix2 undefined or not 1 bpp", procName, 1);
+    if (!tab)
+        return ERROR_INT("tab not defined", procName, 1);
+    if (!area1 || !area2)
+        return ERROR_INT("areas must be > 0", procName, 1);
+
+    pixGetDimensions(pix1, &w1, &h1, NULL);
+    pixGetDimensions(pix2, &w2, &h2, NULL);
+
+        /*  To insure that pixels are ON only within the
+         *  intersection of pix1 and the shifted pix2:
+         *  (1) Start with pixt cleared and equal in size to pix1.
+         *  (2) Blit the shifted pix2 onto pixt.  Then all ON pixels
+         *      are within the intersection of pix1 and the shifted pix2.
+         *  (3) AND pix1 with pixt. */
+    pixt = pixCreateTemplate(pix1);
+    pixRasterop(pixt, delx, dely, w2, h2, PIX_SRC, pix2, 0, 0);
+    pixRasterop(pixt, 0, 0, w1, h1, PIX_SRC & PIX_DST, pix1, 0, 0);
+    pixCountPixels(pixt, &count, tab);
+    pixDestroy(&pixt);
+
+    *pscore = (l_float32)count * (l_float32)count /
+               ((l_float32)area1 * (l_float32)area2);
+    return 0;
+}
diff --git a/src/dewarp.h b/src/dewarp.h
new file mode 100644 (file)
index 0000000..f390c52
--- /dev/null
@@ -0,0 +1,165 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_DEWARP_H
+#define  LEPTONICA_DEWARP_H
+
+/*
+ *  dewarp.h
+ *
+ *     Data structure to hold arrays and results for generating
+ *     horizontal and vertical disparity arrays based on textlines.
+ *     Each disparity array is two-dimensional.  The vertical disparity
+ *     array gives a vertical displacement, relative to the lowest point
+ *     in the textlines.  The horizontal disparty array gives a horizontal
+ *     displacement, relative to the minimum values (for even pages)
+ *     or maximum values (for odd pages) of the left and right ends of
+ *     full textlines.  Horizontal alignment always involves translations
+ *     away from the book gutter.
+ *
+ *     We have intentionally separated the process of building models
+ *     from the rendering process that uses the models.  For any page,
+ *     the building operation either creates an actual model (that is,
+ *     a model with at least the vertical disparity being computed, and
+ *     for which the 'success' flag is set) or fails to create a model.
+ *     However, at rendering time, a page can have one of two different
+ *     types of models.
+ *     (1) A valid model is an actual model that meets the rendering
+ *         constraints, which are limits on model curvature parameters.
+ *         See dewarpaTestForValidModel() for details.
+ *         Valid models are identified by dewarpaInsertRefModels(),
+ *         which sets the 'vvalid' and 'hvalid' fields.  Only valid
+ *         models are used for rendering.
+ *     (2) A reference model is used by a page that doesn't have
+ *         a valid model, but has a nearby valid model of the same
+ *         parity (even/odd page) that it can use.  The range in pages
+ *         to search for a valid model is given by the 'maxdist' field.
+ *
+ *     If a valid vertical disparity model (VDM) is not available,
+ *     just use the input image.  Otherwise, assuming the VDM is available:
+ *       (a) with useboth == 0, we use only the VDM.
+ *       (b) with useboth == 1, we require using the VDM and, if a valid
+ *           horizontal disparity model (HDM) is available, we also use it.
+ *
+ *     The 'maxdist' parameter is input when the dewarpa is created.
+ *     The other rendering parameters have default values given in dewarp.c.
+ *     All parameters used by rendering can be set (or reset) using accessors.
+ *
+ *     After dewarping, use of the VDM will cause all points on each
+ *     altered curve to have a y-value equal to the minimum.  Use of
+ *     the HDA will cause the left and right edges of the textlines
+ *     to be vertically aligned if they had been typeset flush-left
+ *     and flush-right, respectively.
+ *
+ *     The sampled disparity arrays are expanded to full resolution,
+ *     using linear interpolation, and this is further expanded
+ *     by slope continuation to the right and below if the image
+ *     is larger than the full resolution disparity arrays.  Then
+ *     the disparity correction can be applied to the input image.
+ *     If the input pix are 2x reduced, the expansion from sampled
+ *     to full res uses the product of (sampling) * (redfactor).
+ *
+ *     The most accurate results are produced at full resolution, and
+ *     this is generally recommended.
+ */
+
+    /* Note on versioning of the serialization of this data structure:
+     * The dewarping utility and the stored data can be expected to change.
+     * In most situations, the serialized version is ephemeral -- it is
+     * not needed after being used.  No functions will be provided to
+     * convert between different versions. */
+#define  DEWARP_VERSION_NUMBER      4
+
+struct L_Dewarpa
+{
+    l_int32            nalloc;        /* size of dewarp ptr array            */
+    l_int32            maxpage;       /* maximum page number in array        */
+    struct L_Dewarp  **dewarp;        /* array of ptrs to page dewarp        */
+    struct L_Dewarp  **dewarpcache;   /* array of ptrs to cached dewarps     */
+    struct Numa       *namodels;      /* list of page numbers for pages      */
+                                      /* with page models                    */
+    struct Numa       *napages;       /* list of page numbers with either    */
+                                      /* page models or ref page models      */
+    l_int32            redfactor;     /* reduction factor of input: 1 or 2   */
+    l_int32            sampling;      /* disparity arrays sampling factor    */
+    l_int32            minlines;      /* min number of long lines required   */
+    l_int32            maxdist;       /* max distance for getting ref pages  */
+    l_int32            max_linecurv;  /* maximum abs line curvature,         */
+                                      /* in micro-units                      */
+    l_int32            min_diff_linecurv; /* minimum abs diff line curvature */
+                                      /* in micro-units                      */
+    l_int32            max_diff_linecurv; /* maximum abs diff line curvature */
+                                      /* in micro-units                      */
+    l_int32            max_edgeslope; /* maximum abs left or right edge      */
+                                      /* slope, in milli-units               */
+    l_int32            max_edgecurv;  /* maximum abs left or right edge      */
+                                      /* curvature, in micro-units           */
+    l_int32            max_diff_edgecurv; /* maximum abs diff left-right     */
+                                      /* edge curvature, in micro-units      */
+    l_int32            useboth;       /* use both disparity arrays if        */
+                                      /* available; just vertical otherwise  */
+    l_int32            modelsready;   /* invalid models have been removed    */
+                                      /* and refs built against valid set    */
+};
+typedef struct L_Dewarpa L_DEWARPA;
+
+
+struct L_Dewarp
+{
+    struct L_Dewarpa  *dewa;         /* ptr to parent (not owned)            */
+    struct Pix        *pixs;         /* source pix, 1 bpp                    */
+    struct FPix       *sampvdispar;  /* sampled vert disparity array         */
+    struct FPix       *samphdispar;  /* sampled horiz disparity array        */
+    struct FPix       *fullvdispar;  /* full vert disparity array            */
+    struct FPix       *fullhdispar;  /* full horiz disparity array           */
+    struct Numa       *namidys;      /* sorted y val of midpoint each line   */
+    struct Numa       *nacurves;     /* sorted curvature of each line        */
+    l_int32            w;            /* width of source image                */
+    l_int32            h;            /* height of source image               */
+    l_int32            pageno;       /* page number; important for reuse     */
+    l_int32            sampling;     /* sampling factor of disparity arrays  */
+    l_int32            redfactor;    /* reduction factor of pixs: 1 or 2     */
+    l_int32            minlines;     /* min number of long lines required    */
+    l_int32            nlines;       /* number of long lines found           */
+    l_int32            mincurv;      /* min line curvature in micro-units    */
+    l_int32            maxcurv;      /* max line curvature in micro-units    */
+    l_int32            leftslope;    /* left edge slope in milli-units       */
+    l_int32            rightslope;   /* right edge slope in milli-units      */
+    l_int32            leftcurv;     /* left edge curvature in micro-units   */
+    l_int32            rightcurv;    /* right edge curvature in micro-units  */
+    l_int32            nx;           /* number of sampling pts in x-dir      */
+    l_int32            ny;           /* number of sampling pts in y-dir      */
+    l_int32            hasref;       /* 0 if normal; 1 if has a refpage      */
+    l_int32            refpage;      /* page with disparity model to use     */
+    l_int32            vsuccess;     /* sets to 1 if vert disparity builds   */
+    l_int32            hsuccess;     /* sets to 1 if horiz disparity builds  */
+    l_int32            vvalid;       /* sets to 1 if valid vert disparity    */
+    l_int32            hvalid;       /* sets to 1 if valid horiz disparity   */
+    l_int32            debug;        /* sets to 1 if debug output requested  */
+};
+typedef struct L_Dewarp L_DEWARP;
+
+#endif  /* LEPTONICA_DEWARP_H */
diff --git a/src/dewarp1.c b/src/dewarp1.c
new file mode 100644 (file)
index 0000000..961d32a
--- /dev/null
@@ -0,0 +1,1451 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  dewarp1.c
+ *
+ *    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            dewarpaSetMaxDistance()
+ *
+ *      Dewarp serialized I/O
+ *          L_DEWARP          *dewarpRead()
+ *          L_DEWARP          *dewarpReadStream()
+ *          l_int32            dewarpWrite()
+ *          l_int32            dewarpWriteStream()
+ *
+ *      Dewarpa serialized I/O
+ *          L_DEWARPA         *dewarpaRead()
+ *          L_DEWARPA         *dewarpaReadStream()
+ *          l_int32            dewarpaWrite()
+ *          l_int32            dewarpaWriteStream()
+ *
+ *
+ *  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:
+ *     // Make the Dewarpa for the pages
+ *     L_Dewarpa *dewa = dewarpaCreate(1, 30, 1, 15, 50);
+ *     dewarpaSetCurvatures(dewa, -1, 5, -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);
+ *
+ *  Basic functioning to dewarp many pages:
+ *     // 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, 10, -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 ...)
+ *         }
+ *     }
+ *
+ *  Basic functioning to dewarp a small set of pages, potentially
+ *  using models from nearby pages:
+ *     // (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);
+ *     }
+ *
+ *  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:
+ *     // 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);
+ *
+ *  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:
+ *     Pix *pixd;
+ *     dewarpaApplyDisparity(dewa, pageno, undercropped_pix, 255,
+ *                           left, top, &pixd, NULL);
+ *
+ *
+ *  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, determine if the lines
+ *      are left and right-justified, and if so, 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.
+ */
+
+#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     INITIAL_PTR_ARRAYSIZE = 20;   /* n'import quoi */
+static const l_int32     MAX_PTR_ARRAYSIZE = 10000;
+static const l_int32     DEFAULT_ARRAY_SAMPLING = 30;
+static const l_int32     MIN_ARRAY_SAMPLING = 8;
+static const l_int32     DEFAULT_MIN_LINES = 15;
+static const l_int32     MIN_MIN_LINES = 4;
+static const l_int32     DEFAULT_MAX_REF_DIST = 16;
+
+    /* Parameter values used in dewarpaSetCurvatures() */
+static const l_int32     DEFAULT_MAX_LINECURV = 180;
+static const l_int32     DEFAULT_MIN_DIFF_LINECURV = 0;
+static const l_int32     DEFAULT_MAX_DIFF_LINECURV = 200;
+static const l_int32     DEFAULT_MAX_EDGECURV = 50;
+static const l_int32     DEFAULT_MAX_DIFF_EDGECURV = 40;
+static const l_int32     DEFAULT_MAX_EDGESLOPE = 80;
+
+
+/*----------------------------------------------------------------------*
+ *                           Create/destroy Dewarp                      *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpCreate()
+ *
+ *     Input: pixs (1 bpp)
+ *            pageno (page number)
+ *     Return: dew (or null on error)
+ *
+ *  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.
+ */
+L_DEWARP *
+dewarpCreate(PIX     *pixs,
+             l_int32  pageno)
+{
+L_DEWARP  *dew;
+
+    PROCNAME("dewarpCreate");
+
+    if (!pixs)
+        return (L_DEWARP *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (L_DEWARP *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+
+    if ((dew = (L_DEWARP *)LEPT_CALLOC(1, sizeof(L_DEWARP))) == NULL)
+        return (L_DEWARP *)ERROR_PTR("dew not made", procName, NULL);
+    dew->pixs = pixClone(pixs);
+    dew->pageno = pageno;
+    dew->w = pixGetWidth(pixs);
+    dew->h = pixGetHeight(pixs);
+    return dew;
+}
+
+
+/*!
+ *  dewarpCreateRef()
+ *
+ *     Input:  pageno (this page number)
+ *             refpage (page number of dewarp disparity arrays to be used)
+ *     Return: dew (or null on error)
+ *
+ *  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.
+ */
+L_DEWARP *
+dewarpCreateRef(l_int32  pageno,
+                l_int32  refpage)
+{
+L_DEWARP  *dew;
+
+    PROCNAME("dewarpCreateRef");
+
+    if ((dew = (L_DEWARP *)LEPT_CALLOC(1, sizeof(L_DEWARP))) == NULL)
+        return (L_DEWARP *)ERROR_PTR("dew not made", procName, NULL);
+    dew->pageno = pageno;
+    dew->hasref = 1;
+    dew->refpage = refpage;
+    return dew;
+}
+
+
+/*!
+ *  dewarpDestroy()
+ *
+ *      Input:  &dew (<will be set to null before returning>)
+ *      Return: void
+ */
+void
+dewarpDestroy(L_DEWARP  **pdew)
+{
+L_DEWARP  *dew;
+
+    PROCNAME("dewarpDestroy");
+
+    if (pdew == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+    if ((dew = *pdew) == NULL)
+        return;
+
+    pixDestroy(&dew->pixs);
+    fpixDestroy(&dew->sampvdispar);
+    fpixDestroy(&dew->samphdispar);
+    fpixDestroy(&dew->fullvdispar);
+    fpixDestroy(&dew->fullhdispar);
+    numaDestroy(&dew->namidys);
+    numaDestroy(&dew->nacurves);
+    LEPT_FREE(dew);
+    *pdew = NULL;
+    return;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                          Create/destroy Dewarpa                      *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpaCreate()
+ *
+ *     Input: nptrs (number of dewarp page ptrs; typically the number of pages)
+ *            sampling (use 0 for default value; the minimum allowed is 8)
+ *            redfactor (of input images: 1 is full resolution; 2 is 2x reduced)
+ *            minlines (minimum number of lines to accept; use 0 for default)
+ *            maxdist (for locating reference disparity; use -1 for default)
+ *     Return: dewa (or null on error)
+ *
+ *  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.
+ */
+L_DEWARPA *
+dewarpaCreate(l_int32  nptrs,
+              l_int32  sampling,
+              l_int32  redfactor,
+              l_int32  minlines,
+              l_int32  maxdist)
+{
+L_DEWARPA  *dewa;
+
+    PROCNAME("dewarpaCreate");
+
+    if (nptrs <= 0)
+        nptrs = INITIAL_PTR_ARRAYSIZE;
+    if (nptrs > MAX_PTR_ARRAYSIZE)
+        return (L_DEWARPA *)ERROR_PTR("too many pages", procName, NULL);
+    if (redfactor != 1 && redfactor != 2)
+        return (L_DEWARPA *)ERROR_PTR("redfactor not in {1,2}",
+                                      procName, NULL);
+    if (sampling == 0) {
+         sampling = DEFAULT_ARRAY_SAMPLING;
+    } else if (sampling < MIN_ARRAY_SAMPLING) {
+         L_WARNING("sampling too small; setting to %d\n", procName,
+                   MIN_ARRAY_SAMPLING);
+         sampling = MIN_ARRAY_SAMPLING;
+    }
+    if (minlines == 0) {
+        minlines = DEFAULT_MIN_LINES;
+    } else if (minlines < MIN_MIN_LINES) {
+        L_WARNING("minlines too small; setting to %d\n", procName,
+                  MIN_MIN_LINES);
+        minlines = DEFAULT_MIN_LINES;
+    }
+    if (maxdist < 0)
+         maxdist = DEFAULT_MAX_REF_DIST;
+
+    if ((dewa = (L_DEWARPA *)LEPT_CALLOC(1, sizeof(L_DEWARPA))) == NULL)
+        return (L_DEWARPA *)ERROR_PTR("dewa not made", procName, NULL);
+    if ((dewa->dewarp =
+         (L_DEWARP **)LEPT_CALLOC(nptrs, sizeof(L_DEWARPA *))) == NULL)
+        return (L_DEWARPA *)ERROR_PTR("dewarp ptrs not made", procName, NULL);
+    if ((dewa->dewarpcache =
+        (L_DEWARP **)LEPT_CALLOC(nptrs, sizeof(L_DEWARPA *))) == NULL)
+        return (L_DEWARPA *)ERROR_PTR("dewarpcache ptrs not made",
+                                      procName, NULL);
+    dewa->nalloc = nptrs;
+    dewa->sampling = sampling;
+    dewa->redfactor = redfactor;
+    dewa->minlines = minlines;
+    dewa->maxdist = maxdist;
+    dewa->max_linecurv = DEFAULT_MAX_LINECURV;
+    dewa->min_diff_linecurv = DEFAULT_MIN_DIFF_LINECURV;
+    dewa->max_diff_linecurv = DEFAULT_MAX_DIFF_LINECURV;
+    dewa->max_edgeslope = DEFAULT_MAX_EDGESLOPE;
+    dewa->max_edgecurv = DEFAULT_MAX_EDGECURV;
+    dewa->max_diff_edgecurv = DEFAULT_MAX_DIFF_EDGECURV;
+
+    return dewa;
+}
+
+
+/*!
+ *  dewarpaCreateFromPixacomp()
+ *
+ *     Input: pixac (pixacomp of G4, 1 bpp images; with 1x1x1 placeholders)
+ *            useboth (0 for vert disparity; 1 for both vert and horiz)
+ *            sampling (use -1 or 0 for default value; otherwise minimum of 5)
+ *            minlines (minimum number of lines to accept; e.g., 10)
+ *            maxdist (for locating reference disparity; use -1 for default)
+ *     Return: dewa (or null on error)
+ *
+ *  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.
+ */
+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;
+
+    PROCNAME("dewarpaCreateFromPixacomp");
+
+    if (!pixac)
+        return (L_DEWARPA *)ERROR_PTR("pixac not defined", procName, NULL);
+
+    nptrs = pixacompGetCount(pixac);
+    if ((dewa = dewarpaCreate(pixacompGetOffset(pixac) + nptrs,
+                              sampling, 1, minlines, maxdist)) == NULL)
+        return (L_DEWARPA *)ERROR_PTR("dewa not made", procName, 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!", procName, 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", procName, i);
+                continue;
+            }
+                /* Remove all extraneous data */
+            dewarpMinimize(dew);
+        }
+        pixDestroy(&pixt);
+    }
+    dewarpaInsertRefModels(dewa, 0, 0);
+
+    return dewa;
+}
+
+
+/*!
+ *  dewarpaDestroy()
+ *
+ *      Input:  &dewa (<will be set to null before returning>)
+ *      Return: void
+ */
+void
+dewarpaDestroy(L_DEWARPA  **pdewa)
+{
+l_int32     i;
+L_DEWARP   *dew;
+L_DEWARPA  *dewa;
+
+    PROCNAME("dewarpaDestroy");
+
+    if (pdewa == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        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;
+    return;
+}
+
+
+/*!
+ *  dewarpaDestroyDewarp()
+ *
+ *      Input:  dewa
+ *              pageno (of dew to be destroyed)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+dewarpaDestroyDewarp(L_DEWARPA  *dewa,
+                     l_int32     pageno)
+{
+L_DEWARP   *dew;
+
+    PROCNAME("dewarpaDestroyDewarp");
+
+    if (!dewa)
+        return ERROR_INT("dewa or dew not defined", procName, 1);
+    if (pageno < 0 || pageno > dewa->maxpage)
+        return ERROR_INT("page out of bounds", procName, 1);
+    if ((dew = dewa->dewarp[pageno]) == NULL)
+        return ERROR_INT("dew not defined", procName, 1);
+
+    dewarpDestroy(&dew);
+    dewa->dewarp[pageno] = NULL;
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                       Dewarpa insertion/extraction                   *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpaInsertDewarp()
+ *
+ *      Input:  dewarpa
+ *              dewarp  (to be added)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  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.
+ */
+l_int32
+dewarpaInsertDewarp(L_DEWARPA  *dewa,
+                    L_DEWARP   *dew)
+{
+l_int32    pageno, n, newsize;
+L_DEWARP  *prevdew;
+
+    PROCNAME("dewarpaInsertDewarp");
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+    if (!dew)
+        return ERROR_INT("dew not defined", procName, 1);
+
+    dew->dewa = dewa;
+    pageno = dew->pageno;
+    if (pageno > MAX_PTR_ARRAYSIZE)
+        return ERROR_INT("too many pages", procName, 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)
+        dewarpaExtendArraysToSize(dewa, newsize);
+
+    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;
+}
+
+
+/*!
+ *  dewarpaExtendArraysToSize()
+ *
+ *      Input:  dewa
+ *              size (new size of dewarpa array)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) If necessary, reallocs main and cache dewarpa ptr arrays to @size.
+ */
+static l_int32
+dewarpaExtendArraysToSize(L_DEWARPA  *dewa,
+                         l_int32     size)
+{
+    PROCNAME("dewarpaExtendArraysToSize");
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 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", procName, 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", procName, 1);
+        dewa->nalloc = size;
+    }
+    return 0;
+}
+
+
+/*!
+ *  dewarpaGetDewarp()
+ *
+ *      Input:  dewa (populated with dewarp structs for pages)
+ *              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)
+{
+    PROCNAME("dewarpaGetDewarp");
+
+    if (!dewa)
+        return (L_DEWARP *)ERROR_PTR("dewa not defined", procName, NULL);
+    if (index < 0 || index > dewa->maxpage) {
+        L_ERROR("index = %d is invalid; max index = %d\n",
+                procName, index, dewa->maxpage);
+        return NULL;
+    }
+
+    return dewa->dewarp[index];
+}
+
+
+/*----------------------------------------------------------------------*
+ *         Setting parameters to control rendering from the model       *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpaSetCurvatures()
+ *
+ *      Input:  dewa
+ *              max_linecurv (-1 for default)
+ *              min_diff_linecurv (-1 for default; 0 to accept all models)
+ *              max_diff_linecurv (-1 for default)
+ *              max_edgecurv (-1 for default)
+ *              max_diff_edgecurv (-1 for default)
+ *              max_edgeslope (-1 for default)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Approximating the line by a quadratic, the coefficent
+ *          of the quadratic term is the curvature, and distance
+ *          units are in pixels (of course).  The curvature is very
+ *          small, so we multiply by 10^6 and express the constraints
+ *          on the model curvatures in micro-units.
+ *      (2) This sets five curvature thresholds and a slope threshold:
+ *          * the maximum absolute value of the vertical disparity
+ *            line curvatures
+ *          * the minimum absolute value of the largest difference in
+ *            vertical disparity line curvatures (Use a value of 0
+ *            to accept all models.)
+ *          * the maximum absolute value of the largest difference in
+ *            vertical disparity line curvatures
+ *          * the maximum absolute value of the left and right edge
+ *            curvature for the horizontal disparity
+ *          * the maximum absolute value of the difference between
+ *            left and right edge curvature for the horizontal disparity
+ *          all in micro-units, for dewarping to take place.
+ *          Use -1 for default values.
+ *      (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 100 micro-units should probably not be used.
+ */
+l_int32
+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)
+{
+    PROCNAME("dewarpaSetCurvatures");
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+
+    if (max_linecurv == -1)
+        dewa->max_linecurv = DEFAULT_MAX_LINECURV;
+    else
+        dewa->max_linecurv = L_ABS(max_linecurv);
+
+    if (min_diff_linecurv == -1)
+        dewa->min_diff_linecurv = DEFAULT_MIN_DIFF_LINECURV;
+    else
+        dewa->min_diff_linecurv = L_ABS(min_diff_linecurv);
+
+    if (max_diff_linecurv == -1)
+        dewa->max_diff_linecurv = DEFAULT_MAX_DIFF_LINECURV;
+    else
+        dewa->max_diff_linecurv = L_ABS(max_diff_linecurv);
+
+    if (max_edgecurv == -1)
+        dewa->max_edgecurv = DEFAULT_MAX_EDGECURV;
+    else
+        dewa->max_edgecurv = L_ABS(max_edgecurv);
+
+    if (max_diff_edgecurv == -1)
+        dewa->max_diff_edgecurv = DEFAULT_MAX_DIFF_EDGECURV;
+    else
+        dewa->max_diff_edgecurv = L_ABS(max_diff_edgecurv);
+
+    if (max_edgeslope == -1)
+        dewa->max_edgeslope = DEFAULT_MAX_EDGESLOPE;
+    else
+        dewa->max_edgeslope = L_ABS(max_edgeslope);
+
+    dewa->modelsready = 0;  /* force validation */
+    return 0;
+}
+
+
+/*!
+ *  dewarpaUseBothArrays()
+ *
+ *      Input:  dewa
+ *              useboth (0 for false, 1 for true)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  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.
+ */
+l_int32
+dewarpaUseBothArrays(L_DEWARPA  *dewa,
+                     l_int32     useboth)
+{
+    PROCNAME("dewarpaUseBothArrays");
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+
+    dewa->useboth = useboth;
+    dewa->modelsready = 0;  /* force validation */
+    return 0;
+}
+
+
+/*!
+ *  dewarpaSetMaxDistance()
+ *
+ *      Input:  dewa
+ *              maxdist (for using ref models)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This sets the maxdist field.
+ */
+l_int32
+dewarpaSetMaxDistance(L_DEWARPA  *dewa,
+                      l_int32     maxdist)
+{
+    PROCNAME("dewarpaSetMaxDistance");
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+
+    dewa->maxdist = maxdist;
+    dewa->modelsready = 0;  /* force validation */
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                       Dewarp serialized I/O                          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpRead()
+ *
+ *      Input:  filename
+ *      Return: dew, or null on error
+ */
+L_DEWARP *
+dewarpRead(const char  *filename)
+{
+FILE      *fp;
+L_DEWARP  *dew;
+
+    PROCNAME("dewarpRead");
+
+    if (!filename)
+        return (L_DEWARP *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (L_DEWARP *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((dew = dewarpReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (L_DEWARP *)ERROR_PTR("dew not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return dew;
+}
+
+
+/*!
+ *  dewarpReadStream()
+ *
+ *      Input:  stream
+ *      Return: dew, or null on error
+ *
+ *  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.
+ */
+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, *fpixh;
+
+    PROCNAME("dewarpReadStream");
+
+    if (!fp)
+        return (L_DEWARP *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nDewarp Version %d\n", &version) != 1)
+        return (L_DEWARP *)ERROR_PTR("not a dewarp file", procName, NULL);
+    if (version != DEWARP_VERSION_NUMBER)
+        return (L_DEWARP *)ERROR_PTR("invalid dewarp version", procName, NULL);
+    if (fscanf(fp, "pageno = %d\n", &pageno) != 1)
+        return (L_DEWARP *)ERROR_PTR("read fail for pageno", procName, NULL);
+    if (fscanf(fp, "hasref = %d, refpage = %d\n", &hasref, &refpage) != 2)
+        return (L_DEWARP *)ERROR_PTR("read fail for hasref, refpage",
+                                     procName, NULL);
+    if (fscanf(fp, "sampling = %d, redfactor = %d\n", &sampling, &redfactor)
+               != 2)
+        return (L_DEWARP *)ERROR_PTR("read fail for sampling/redfactor",
+                                     procName, NULL);
+    if (fscanf(fp, "nlines = %d, minlines = %d\n", &nlines, &minlines) != 2)
+        return (L_DEWARP *)ERROR_PTR("read fail for nlines/minlines",
+                                     procName, NULL);
+    if (fscanf(fp, "w = %d, h = %d\n", &w, &h) != 2)
+        return (L_DEWARP *)ERROR_PTR("read fail for w, h", procName, NULL);
+    if (fscanf(fp, "nx = %d, ny = %d\n", &nx, &ny) != 2)
+        return (L_DEWARP *)ERROR_PTR("read fail for nx, ny", procName, NULL);
+    if (fscanf(fp, "vert_dispar = %d, horiz_dispar = %d\n", &vdispar, &hdispar)
+               != 2)
+        return (L_DEWARP *)ERROR_PTR("read fail for flags", procName, 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",
+                                         procName, 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",
+                                         procName, 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",
+                                         procName, NULL);
+    }
+    if (vdispar) {
+        if ((fpixv = fpixReadStream(fp)) == NULL)
+            return (L_DEWARP *)ERROR_PTR("read fail for vdispar",
+                                         procName, NULL);
+    }
+    if (hdispar) {
+        if ((fpixh = fpixReadStream(fp)) == NULL)
+            return (L_DEWARP *)ERROR_PTR("read fail for hdispar",
+                                         procName, 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;
+}
+
+
+/*!
+ *  dewarpWrite()
+ *
+ *      Input:  filename
+ *              dew
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+dewarpWrite(const char  *filename,
+            L_DEWARP    *dew)
+{
+FILE  *fp;
+
+    PROCNAME("dewarpWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!dew)
+        return ERROR_INT("dew not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (dewarpWriteStream(fp, dew))
+        return ERROR_INT("dew not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  dewarpWriteStream()
+ *
+ *      Input:  stream (opened for "wb")
+ *              dew
+ *      Return: 0 if OK, 1 on error
+ *
+ *  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.
+ */
+l_int32
+dewarpWriteStream(FILE      *fp,
+                  L_DEWARP  *dew)
+{
+l_int32  vdispar, hdispar;
+
+    PROCNAME("dewarpWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!dew)
+        return ERROR_INT("dew not defined", procName, 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", procName);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                       Dewarpa serialized I/O                          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpaRead()
+ *
+ *      Input:  filename
+ *      Return: dewa, or null on error
+ */
+L_DEWARPA *
+dewarpaRead(const char  *filename)
+{
+FILE       *fp;
+L_DEWARPA  *dewa;
+
+    PROCNAME("dewarpaRead");
+
+    if (!filename)
+        return (L_DEWARPA *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (L_DEWARPA *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((dewa = dewarpaReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (L_DEWARPA *)ERROR_PTR("dewa not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return dewa;
+}
+
+
+/*!
+ *  dewarpaReadStream()
+ *
+ *      Input:  stream
+ *      Return: dewa, or null on error
+ *
+ *  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.
+ */
+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;
+
+    PROCNAME("dewarpaReadStream");
+
+    if (!fp)
+        return (L_DEWARPA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nDewarpa Version %d\n", &version) != 1)
+        return (L_DEWARPA *)ERROR_PTR("not a dewarpa file", procName, NULL);
+    if (version != DEWARP_VERSION_NUMBER)
+        return (L_DEWARPA *)ERROR_PTR("invalid dewarp version", procName, NULL);
+
+    if (fscanf(fp, "ndewarp = %d, maxpage = %d\n", &ndewarp, &maxpage) != 2)
+        return (L_DEWARPA *)ERROR_PTR("read fail for maxpage+", procName, 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", procName, 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", procName, 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", procName, NULL);
+    if (fscanf(fp, "fullmodel = %d\n", &useboth) != 1)
+        return (L_DEWARPA *)ERROR_PTR("read fail for useboth", procName, 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", procName, i);
+            return NULL;
+        }
+        dewarpaInsertDewarp(dewa, dew);
+        numaAddNumber(namodels, dew->pageno);
+    }
+
+        /* Validate the models and insert reference models */
+    dewarpaInsertRefModels(dewa, 0, 0);
+
+    return dewa;
+}
+
+
+/*!
+ *  dewarpaWrite()
+ *
+ *      Input:  filename
+ *              dewa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+dewarpaWrite(const char  *filename,
+             L_DEWARPA   *dewa)
+{
+FILE  *fp;
+
+    PROCNAME("dewarpaWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (dewarpaWriteStream(fp, dewa))
+        return ERROR_INT("dewa not written to stream", procName, 1);
+    fclose(fp);
+    return 0;
+}
+
+
+/*!
+ *  dewarpaWriteStream()
+ *
+ *      Input:  stream (opened for "wb")
+ *              dewa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+dewarpaWriteStream(FILE       *fp,
+                   L_DEWARPA  *dewa)
+{
+l_int32  ndewarp, i, pageno;
+
+    PROCNAME("dewarpaWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 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", procName, 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;
+}
+
diff --git a/src/dewarp2.c b/src/dewarp2.c
new file mode 100644 (file)
index 0000000..e49f221
--- /dev/null
@@ -0,0 +1,1566 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  dewarp2.c
+ *
+ *    Build the page disparity model
+ *
+ *      Build page disparity model
+ *          l_int32            dewarpBuildPageModel()
+ *          l_int32            dewarpFindVertDisparity()
+ *          l_int32            dewarpFindHorizDisparity()
+ *          PTAA              *dewarpGetTextlineCenters()
+ *          static PTA        *dewarpGetMeanVerticals()
+ *          PTAA              *dewarpRemoveShortLines()
+ *          static l_int32     dewarpGetLineEndpoints()
+ *          static l_int32     dewarpFindLongLines()
+ *          static l_int32     dewarpIsLineCoverageValid()
+ *          static l_int32     dewarpQuadraticLSF()
+ *
+ *      Build the line disparity model
+ *          l_int32            dewarpBuildLineModel()
+ *
+ *      Query model status
+ *          l_int32            dewarpaModelStatus()
+ *
+ *      Rendering helpers
+ *          static l_int32     pixRenderFlats()
+ *          static l_int32     pixRenderHorizEndPoints
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+static PTA *dewarpGetMeanVerticals(PIX *pixs, l_int32 x, l_int32 y);
+static l_int32 dewarpGetLineEndpoints(l_int32 h, PTAA *ptaa, PTA **pptal,
+                                      PTA **pptar);
+static l_int32 dewarpFindLongLines(PTA *ptal, PTA *ptar, l_float32 minfract,
+                                   PTA **pptald, PTA **pptard);
+static l_int32 dewarpIsLineCoverageValid(PTAA *ptaa2, l_int32 h,
+                                         l_int32 *ptopline, l_int32 *pbotline);
+static l_int32 dewarpQuadraticLSF(PTA *ptad, l_float32 *pa, l_float32 *pb,
+                                  l_float32 *pc, l_float32 *pmederr);
+static l_int32 pixRenderMidYs(PIX *pixs, NUMA *namidys, l_int32 linew);
+static l_int32 pixRenderHorizEndPoints(PIX *pixs, PTA *ptal, PTA *ptar,
+                                       l_uint32 color);
+
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_TEXTLINE_CENTERS    0   /* set this to 1 for debuging */
+#define  DEBUG_SHORT_LINES         0   /* ditto */
+#else
+#define  DEBUG_TEXTLINE_CENTERS    0   /* always must be 0 */
+#define  DEBUG_SHORT_LINES         0   /* ditto */
+#endif  /* !NO_CONSOLE_IO */
+
+    /* Special parameter values */
+static const l_float32   MIN_RATIO_LINES_TO_HEIGHT = 0.45;
+
+
+/*----------------------------------------------------------------------*
+ *                      Build page disparity model                      *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpBuildPageModel()
+ *
+ *      Input:  dew
+ *              debugfile (use null to skip writing this)
+ *      Return: 0 if OK, 1 if unable to build the model or on error
+ *
+ *  Notes:
+ *      (1) This is the basic function that builds the horizontal and
+ *          vertical disparity arrays, which allow determination of the
+ *          src pixel in the input image corresponding to each
+ *          dest pixel in the dewarped image.
+ *      (2) Sets vsuccess = 1 if the vertical disparity array builds.
+ *          Always attempts to build the horizontal disparity array,
+ *          even if it will not be requested (useboth == 0).
+ *          Sets hsuccess = 1 if horizontal disparity builds.
+ *      (3) The method is as follows:
+ *          (a) Estimate the points along the centers of all the
+ *              long textlines.  If there are too few lines, no
+ *              disparity models are built.
+ *          (b) From the vertical deviation of the lines, estimate
+ *              the vertical disparity.
+ *          (c) From the ends of the lines, estimate the horizontal
+ *              disparity, assuming that the text is made of lines
+ *              that are left and right justified.
+ *          (d) One can also compute an additional contribution to the
+ *              horizontal disparity, inferred from slopes of the top
+ *              and bottom lines.  We do not do this.
+ *      (4) In more detail for the vertical disparity:
+ *          (a) Fit a LS quadratic to center locations along each line.
+ *              This smooths the curves.
+ *          (b) Sample each curve at a regular interval, find the y-value
+ *              of the mid-point on each curve, and subtract the sampled
+ *              curve value from this value.  This is the vertical
+ *              disparity at sampled points along each curve.
+ *          (c) Fit a LS quadratic to each set of vertically aligned
+ *              disparity samples.  This smooths the disparity values
+ *              in the vertical direction.  Then resample at the same
+ *              regular interval.  We now have a regular grid of smoothed
+ *              vertical disparity valuels.
+ *      (5) Once the sampled vertical disparity array is found, it can be
+ *          interpolated to get a full resolution vertical disparity map.
+ *          This can be applied directly to the src image pixels
+ *          to dewarp the image in the vertical direction, making
+ *          all textlines horizontal.  Likewise, the horizontal
+ *          disparity array is used to left- and right-align the
+ *          longest textlines.
+ */
+l_int32
+dewarpBuildPageModel(L_DEWARP    *dew,
+                     const char  *debugfile)
+{
+l_int32  linecount, topline, botline, ret;
+PIX     *pixs, *pix1, *pix2, *pix3;
+PTA     *pta;
+PTAA    *ptaa1, *ptaa2;
+
+    PROCNAME("dewarpBuildPageModel");
+
+    if (!dew)
+        return ERROR_INT("dew not defined", procName, 1);
+
+    dew->debug = (debugfile) ? 1 : 0;
+    dew->vsuccess = dew->hsuccess = 0;
+    pixs = dew->pixs;
+    if (debugfile) {
+        lept_rmdir("lept/dewmod");  /* erase previous images */
+        lept_mkdir("lept/dewmod");
+        pixDisplayWithTitle(pixs, 0, 0, "pixs", 1);
+        pixWrite("/tmp/lept/dewmod/0010.png", pixs, IFF_PNG);
+    }
+
+        /* Make initial estimate of centers of textlines */
+    ptaa1 = dewarpGetTextlineCenters(pixs, debugfile || DEBUG_TEXTLINE_CENTERS);
+    if (!ptaa1) {
+        L_WARNING("textline centers not found; model not built\n", procName);
+        return 1;
+    }
+    if (debugfile) {
+        pix1 = pixConvertTo32(pixs);
+        pta = generatePtaFilledCircle(1);
+        pix2 = pixGenerateFromPta(pta, 5, 5);
+        pix3 = pixDisplayPtaaPattern(NULL, pix1, ptaa1, pix2, 2, 2);
+        pixWrite("/tmp/lept/dewmod/0020.png", pix3, IFF_PNG);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+        pixDestroy(&pix3);
+        ptaDestroy(&pta);
+    }
+
+        /* Remove all lines that are not at least 0.8 times the length
+         * of the longest line. */
+    ptaa2 = dewarpRemoveShortLines(pixs, ptaa1, 0.8,
+                                   debugfile || DEBUG_SHORT_LINES);
+    if (debugfile) {
+        pix1 = pixConvertTo32(pixs);
+        pta = generatePtaFilledCircle(1);
+        pix2 = pixGenerateFromPta(pta, 5, 5);
+        pix3 = pixDisplayPtaaPattern(NULL, pix1, ptaa2, pix2, 2, 2);
+        pixWrite("/tmp/lept/dewmod/0030.png", pix3, IFF_PNG);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+        pixDestroy(&pix3);
+        ptaDestroy(&pta);
+    }
+    ptaaDestroy(&ptaa1);
+
+        /* Verify that there are sufficient "long" lines */
+    linecount = ptaaGetCount(ptaa2);
+    if (linecount < dew->minlines) {
+        ptaaDestroy(&ptaa2);
+        L_WARNING("linecount %d < min req'd number of lines (%d) for model\n",
+                  procName, linecount, dew->minlines);
+        return 1;
+    }
+
+        /* Verify that the lines have a reasonable coverage of the
+         * vertical extent of the image foreground. */
+    if (dewarpIsLineCoverageValid(ptaa2, pixGetHeight(pixs),
+                                  &topline, &botline) == FALSE) {
+        ptaaDestroy(&ptaa2);
+        L_WARNING("invalid line coverage: [%d ... %d] in height %d\n",
+                  procName, topline, botline, pixGetHeight(pixs));
+        return 1;
+    }
+
+        /* Get the sampled vertical disparity from the textline centers.
+         * The disparity array will push pixels vertically so that each
+         * textline is flat and centered at the y-position of the mid-point. */
+    if (dewarpFindVertDisparity(dew, ptaa2, 0) != 0) {
+        L_WARNING("vertical disparity not built\n", procName);
+        ptaaDestroy(&ptaa2);
+        return 1;
+    }
+
+        /* Get the sampled horizontal disparity from the left and right
+         * edges of the text.  The disparity array will expand the image
+         * linearly outward to align the text edges vertically.
+         * Do this even if useboth == 0; we still calculate it even
+         * if we don't plan to use it. */
+    if ((ret = dewarpFindHorizDisparity(dew, ptaa2)) == 0)
+        L_INFO("hsuccess = 1\n", procName);
+
+        /* Debug output */
+    if (debugfile) {
+        dewarpPopulateFullRes(dew, NULL, 0, 0);
+        pix1 = fpixRenderContours(dew->fullvdispar, 3.0, 0.15);
+        pixWrite("/tmp/lept/dewmod/0060.png", pix1, IFF_PNG);
+        pixDisplay(pix1, 1000, 0);
+        pixDestroy(&pix1);
+        if (ret == 0) {
+            pix1 = fpixRenderContours(dew->fullhdispar, 3.0, 0.15);
+            pixWrite("/tmp/lept/dewmod/0070.png", pix1, IFF_PNG);
+            pixDisplay(pix1, 1000, 0);
+            pixDestroy(&pix1);
+        }
+        convertFilesToPdf("/tmp/lept/dewmod", NULL, 135, 1.0, 0, 0,
+                          "Dewarp Build Model", debugfile);
+        fprintf(stderr, "pdf file: %s\n", debugfile);
+    }
+
+    ptaaDestroy(&ptaa2);
+    return 0;
+}
+
+
+/*!
+ *  dewarpFindVertDisparity()
+ *
+ *      Input:  dew
+ *              ptaa (unsmoothed lines, not vertically ordered)
+ *              rotflag (0 if using dew->pixs; 1 if rotated by 90 degrees cw)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This starts with points along the centers of textlines.
+ *          It does quadratic fitting (and smoothing), first along the
+ *          lines and then in the vertical direction, to generate
+ *          the sampled vertical disparity map.  This can then be
+ *          interpolated to full resolution and used to remove
+ *          the vertical line warping.
+ *      (2) Use @rotflag == 1 if you are dewarping vertical lines, as
+ *          is done in dewarpBuildLineModel().  The usual case is for
+ *          @rotflag == 0.
+ *      (3) The model fails to build if the vertical disparity fails.
+ *          This sets the vsuccess flag to 1 on success.
+ *      (4) Pix debug output goes to /tmp/dewvert/ for collection into
+ *          a pdf.  Non-pix debug output goes to /tmp.
+ */
+l_int32
+dewarpFindVertDisparity(L_DEWARP  *dew,
+                        PTAA      *ptaa,
+                        l_int32    rotflag)
+{
+l_int32     i, j, nlines, npts, nx, ny, sampling;
+l_float32   c0, c1, c2, x, y, midy, val, medval, medvar, minval, maxval;
+l_float32  *famidys;
+NUMA       *nax, *nafit, *nacurve0, *nacurve1, *nacurves;
+NUMA       *namidy, *namidys, *namidysi;
+PIX        *pix1, *pix2, *pixcirc, *pixdb;
+PTA        *pta, *ptad, *ptacirc;
+PTAA       *ptaa0, *ptaa1, *ptaa2, *ptaa3, *ptaa4, *ptaa5, *ptaat;
+FPIX       *fpix;
+
+    PROCNAME("dewarpFindVertDisparity");
+
+    if (!dew)
+        return ERROR_INT("dew not defined", procName, 1);
+    dew->vsuccess = 0;
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 1);
+
+    lept_mkdir("lept/dewdebug");
+    lept_mkdir("lept/dewarp");
+    if (dew->debug) L_INFO("finding vertical disparity\n", procName);
+
+        /* Do quadratic fit to smooth each line.  A single quadratic
+         * over the entire width of the line appears to be sufficient.
+         * Quartics tend to overfit to noise.  Each line is thus
+         * represented by three coefficients: y(x) = c2 * x^2 + c1 * x + c0.
+         * Using the coefficients, sample each fitted curve uniformly
+         * across the full width of the image.  The result is in ptaa0.  */
+    sampling = dew->sampling;
+    nx = (rotflag) ? dew->ny : dew->nx;
+    ny = (rotflag) ? dew->nx : dew->ny;
+    nlines = ptaaGetCount(ptaa);
+    dew->nlines = nlines;
+    ptaa0 = ptaaCreate(nlines);
+    nacurve0 = numaCreate(nlines);  /* stores curvature coeff c2 */
+    pixdb = (rotflag) ? pixRotateOrth(dew->pixs, 1) : pixClone(dew->pixs);
+    for (i = 0; i < nlines; i++) {  /* for each line */
+        pta = ptaaGetPta(ptaa, i, L_CLONE);
+        ptaGetQuadraticLSF(pta, &c2, &c1, &c0, NULL);
+        numaAddNumber(nacurve0, c2);
+        ptad = ptaCreate(nx);
+        for (j = 0; j < nx; j++) {  /* uniformly sampled in x */
+             x = j * sampling;
+             applyQuadraticFit(c2, c1, c0, x, &y);
+             ptaAddPt(ptad, x, y);
+        }
+        ptaaAddPta(ptaa0, ptad, L_INSERT);
+        ptaDestroy(&pta);
+    }
+    if (dew->debug) {
+        ptaat = ptaaCreate(nlines);
+        for (i = 0; i < nlines; i++) {
+            pta = ptaaGetPta(ptaa, i, L_CLONE);
+            ptaGetArrays(pta, &nax, NULL);
+            ptaGetQuadraticLSF(pta, NULL, NULL, NULL, &nafit);
+            ptad = ptaCreateFromNuma(nax, nafit);
+            ptaaAddPta(ptaat, ptad, L_INSERT);
+            ptaDestroy(&pta);
+            numaDestroy(&nax);
+            numaDestroy(&nafit);
+        }
+        pix1 = pixConvertTo32(pixdb);
+        pta = generatePtaFilledCircle(1);
+        pixcirc = pixGenerateFromPta(pta, 5, 5);
+        pix2 = pixDisplayPtaaPattern(NULL, pix1, ptaat, pixcirc, 2, 2);
+        pixWrite("/tmp/lept/dewmod/0041.png", pix2, IFF_PNG);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+        pixDestroy(&pixcirc);
+        ptaaDestroy(&ptaat);
+    }
+
+        /* Remove lines with outlier curvatures.
+         * Note that this is just looking for internal consistency in
+         * the line curvatures.  It is not rejecting lines based on
+         * the magnitude of the curvature.  That is done when constraints
+         * are applied for valid models. */
+    numaGetMedianVariation(nacurve0, &medval, &medvar);
+    L_INFO("\nPage %d\n", procName, dew->pageno);
+    L_INFO("Pass 1: Curvature: medval = %f, medvar = %f\n",
+           procName, medval, medvar);
+    ptaa1 = ptaaCreate(nlines);
+    nacurve1 = numaCreate(nlines);
+    for (i = 0; i < nlines; i++) {  /* for each line */
+        numaGetFValue(nacurve0, i, &val);
+        if (L_ABS(val - medval) > 7.0 * medvar)  /* TODO: reduce to ~ 3.0 */
+            continue;
+        pta = ptaaGetPta(ptaa0, i, L_CLONE);
+        ptaaAddPta(ptaa1, pta, L_INSERT);
+        numaAddNumber(nacurve1, val);
+    }
+    nlines = ptaaGetCount(ptaa1);
+    numaDestroy(&nacurve0);
+
+        /* Save the min and max curvature (in micro-units) */
+    numaGetMin(nacurve1, &minval, NULL);
+    numaGetMax(nacurve1, &maxval, NULL);
+    dew->mincurv = lept_roundftoi(1000000. * minval);
+    dew->maxcurv = lept_roundftoi(1000000. * maxval);
+    L_INFO("Pass 2: Min/max curvature = (%d, %d)\n", procName,
+           dew->mincurv, dew->maxcurv);
+
+        /* Find and save the y values at the mid-points in each curve.
+         * If the slope is zero anywhere, it will typically be here. */
+    namidy = numaCreate(nlines);
+    for (i = 0; i < nlines; i++) {
+        pta = ptaaGetPta(ptaa1, i, L_CLONE);
+        npts = ptaGetCount(pta);
+        ptaGetPt(pta, npts / 2, NULL, &midy);
+        numaAddNumber(namidy, midy);
+        ptaDestroy(&pta);
+    }
+
+        /* Sort the lines in ptaa1c by their vertical position, going down */
+    namidysi = numaGetSortIndex(namidy, L_SORT_INCREASING);
+    namidys = numaSortByIndex(namidy, namidysi);
+    nacurves = numaSortByIndex(nacurve1, namidysi);
+    numaDestroy(&dew->namidys);  /* in case previously made */
+    numaDestroy(&dew->nacurves);
+    dew->namidys = namidys;
+    dew->nacurves = nacurves;
+    ptaa2 = ptaaSortByIndex(ptaa1, namidysi);
+    numaDestroy(&namidy);
+    numaDestroy(&nacurve1);
+    numaDestroy(&namidysi);
+    if (dew->debug) {
+        numaWrite("/tmp/lept/dewdebug/midys.na", namidys);
+        numaWrite("/tmp/lept/dewdebug/curves.na", nacurves);
+        pix1 = pixConvertTo32(pixdb);
+        ptacirc = generatePtaFilledCircle(5);
+        pixcirc = pixGenerateFromPta(ptacirc, 11, 11);
+        srand(3);
+        pixDisplayPtaaPattern(pix1, pix1, ptaa2, pixcirc, 5, 5);
+        srand(3);  /* use the same colors for text and reference lines */
+        pixRenderMidYs(pix1, namidys, 2);
+        pix2 = (rotflag) ? pixRotateOrth(pix1, 3) : pixClone(pix1);
+        pixWrite("/tmp/lept/dewmod/0042.png", pix2, IFF_PNG);
+        pixDisplay(pix2, 0, 0);
+        ptaDestroy(&ptacirc);
+        pixDestroy(&pixcirc);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+    pixDestroy(&pixdb);
+
+        /* Convert the sampled points in ptaa2 to a sampled disparity with
+         * with respect to the y value at the mid point in the curve.
+         * The disparity is the distance the point needs to move;
+         * plus is downward.  */
+    ptaa3 = ptaaCreate(nlines);
+    for (i = 0; i < nlines; i++) {
+        pta = ptaaGetPta(ptaa2, i, L_CLONE);
+        numaGetFValue(namidys, i, &midy);
+        ptad = ptaCreate(nx);
+        for (j = 0; j < nx; j++) {
+            ptaGetPt(pta, j, &x, &y);
+            ptaAddPt(ptad, x, midy - y);
+        }
+        ptaaAddPta(ptaa3, ptad, L_INSERT);
+        ptaDestroy(&pta);
+    }
+    if (dew->debug) {
+        ptaaWrite("/tmp/lept/dewdebug/ptaa3.ptaa", ptaa3, 0);
+    }
+
+        /* Generate ptaa4 by taking vertical 'columns' from ptaa3.
+         * We want to fit the vertical disparity on the column to the
+         * vertical position of the line, which we call 'y' here and
+         * obtain from namidys.  So each pta in ptaa4 is the set of
+         * vertical disparities down a column of points.  The columns
+         * in ptaa4 are equally spaced in x. */
+    ptaa4 = ptaaCreate(nx);
+    famidys = numaGetFArray(namidys, L_NOCOPY);
+    for (j = 0; j < nx; j++) {
+        pta = ptaCreate(nlines);
+        for (i = 0; i < nlines; i++) {
+            y = famidys[i];
+            ptaaGetPt(ptaa3, i, j, NULL, &val);  /* disparity value */
+            ptaAddPt(pta, y, val);
+        }
+        ptaaAddPta(ptaa4, pta, L_INSERT);
+    }
+    if (dew->debug) {
+        ptaaWrite("/tmp/lept/dewdebug/ptaa4.ptaa", ptaa4, 0);
+    }
+
+        /* Do quadratic fit vertically on each of the pixel columns
+         * in ptaa4, for the vertical displacement (which identifies the
+         * src pixel(s) for each dest pixel) as a function of y (the
+         * y value of the mid-points for each line).  Then generate
+         * ptaa5 by sampling the fitted vertical displacement on a
+         * regular grid in the vertical direction.  Each pta in ptaa5
+         * gives the vertical displacement for regularly sampled y values
+         * at a fixed x. */
+    ptaa5 = ptaaCreate(nx);  /* uniformly sampled across full height of image */
+    for (j = 0; j < nx; j++) {  /* for each column */
+        pta = ptaaGetPta(ptaa4, j, L_CLONE);
+        ptaGetQuadraticLSF(pta, &c2, &c1, &c0, NULL);
+        ptad = ptaCreate(ny);
+        for (i = 0; i < ny; i++) {  /* uniformly sampled in y */
+             y = i * sampling;
+             applyQuadraticFit(c2, c1, c0, y, &val);
+             ptaAddPt(ptad, y, val);
+        }
+        ptaaAddPta(ptaa5, ptad, L_INSERT);
+        ptaDestroy(&pta);
+    }
+    if (dew->debug) {
+        ptaaWrite("/tmp/lept/dewdebug/ptaa5.ptaa", ptaa5, 0);
+        convertFilesToPdf("/tmp/lept/dewmod", "004", 135, 1.0, 0, 0,
+                          "Dewarp Vert Disparity",
+                          "/tmp/lept/dewarp/vert_disparity.pdf");
+        fprintf(stderr, "pdf file: /tmp/lept/dewarp/vert_disparity.pdf\n");
+    }
+
+        /* Save the result in a fpix at the specified subsampling  */
+    fpix = fpixCreate(nx, ny);
+    for (i = 0; i < ny; i++) {
+        for (j = 0; j < nx; j++) {
+            ptaaGetPt(ptaa5, j, i, NULL, &val);
+            fpixSetPixel(fpix, j, i, val);
+        }
+    }
+    dew->sampvdispar = fpix;
+    dew->vsuccess = 1;
+
+    ptaaDestroy(&ptaa0);
+    ptaaDestroy(&ptaa1);
+    ptaaDestroy(&ptaa2);
+    ptaaDestroy(&ptaa3);
+    ptaaDestroy(&ptaa4);
+    ptaaDestroy(&ptaa5);
+    return 0;
+}
+
+
+/*!
+ *  dewarpFindHorizDisparity()
+ *
+ *      Input:  dew
+ *              ptaa (unsmoothed lines, not vertically ordered)
+ *      Return: 0 if OK, 1 if vertical disparity array is no built or on error
+ *
+ *      (1) This is not required for a successful model; only the vertical
+ *          disparity is required.  This will not be called if the
+ *          function to build the vertical disparity fails.
+ *      (2) Debug output goes to /tmp/lept/dewmod/ for collection into a pdf.
+ */
+l_int32
+dewarpFindHorizDisparity(L_DEWARP  *dew,
+                         PTAA      *ptaa)
+{
+l_int32    i, j, h, nx, ny, sampling, ret;
+l_float32  c0, c1, cl0, cl1, cl2, cr0, cr1, cr2;
+l_float32  x, y, refl, refr;
+l_float32  val, mederr;
+NUMA      *nald, *nard;
+PIX       *pix1;
+PTA       *ptal, *ptar;  /* left and right end points of lines */
+PTA       *ptalf, *ptarf;  /* left and right block, fitted, uniform spacing */
+PTA       *pta, *ptat, *pta1, *pta2, *ptald, *ptard;
+PTAA      *ptaah;
+FPIX      *fpix;
+
+    PROCNAME("dewarpFindHorizDisparity");
+
+    if (!dew)
+        return ERROR_INT("dew not defined", procName, 1);
+    dew->hsuccess = 0;
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 1);
+
+    lept_mkdir("lept/dewdebug");
+    lept_mkdir("lept/dewarp");
+    if (dew->debug) L_INFO("finding horizontal disparity\n", procName);
+
+        /* Get the endpoints of the lines */
+    h = pixGetHeight(dew->pixs);
+    ret = dewarpGetLineEndpoints(h, ptaa, &ptal, &ptar);
+    if (ret) {
+        L_INFO("Horiz disparity not built\n", procName);
+        return 1;
+    }
+    if (dew->debug) {
+        ptaWrite("/tmp/lept/dewdebug/endpts_left.pta", ptal, 1);
+        ptaWrite("/tmp/lept/dewdebug/endpts_right.pta", ptar, 1);
+    }
+
+        /* Do a quadratic fit to the left and right endpoints of the
+         * longest lines.  Each line is represented by 3 coefficients:
+         *     x(y) = c2 * y^2 + c1 * y + c0.
+         * Using the coefficients, sample each fitted curve uniformly
+         * along the full height of the image.
+         * TODO: Set right edge disparity to 0 if not flush-right aligned */
+    sampling = dew->sampling;
+    nx = dew->nx;
+    ny = dew->ny;
+
+        /* Find the top and bottom set of long lines, defined by being
+         * at least 0.95 of the length of the longest line in each set.
+         * Quit if there are not at least 3 lines in each set. */
+    ptald = ptard = NULL;  /* end points of longest lines */
+    ret = dewarpFindLongLines(ptal, ptar, 0.95, &ptald, &ptard);
+    if (ret) {
+        L_INFO("Horiz disparity not built\n", procName);
+        ptaDestroy(&ptal);
+        ptaDestroy(&ptar);
+        return 1;
+    }
+
+        /* Fit the left side, using quadratic LSF on the set of long
+         * lines.  It is not necessary to use the noisy LSF fit
+         * function, because we've removed outlier end points by
+         * selecting the long lines.  Then uniformly sample along
+         * this fitted curve. */
+    dewarpQuadraticLSF(ptald, &cl2, &cl1, &cl0, &mederr);
+    dew->leftslope = lept_roundftoi(1000. * cl1);  /* milli-units */
+    dew->leftcurv = lept_roundftoi(1000000. * cl2);  /* micro-units */
+    L_INFO("Left quad LSF median error = %5.2f\n", procName,  mederr);
+    L_INFO("Left edge slope = %d\n", procName, dew->leftslope);
+    L_INFO("Left edge curvature = %d\n", procName, dew->leftcurv);
+    ptalf = ptaCreate(ny);
+    for (i = 0; i < ny; i++) {  /* uniformly sampled in y */
+        y = i * sampling;
+        applyQuadraticFit(cl2, cl1, cl0, y, &x);
+        ptaAddPt(ptalf, x, y);
+    }
+
+        /* Fit the right side in the same way. */
+    dewarpQuadraticLSF(ptard, &cr2, &cr1, &cr0, &mederr);
+    dew->rightslope = lept_roundftoi(1000.0 * cr1);  /* milli-units */
+    dew->rightcurv = lept_roundftoi(1000000. * cr2);  /* micro-units */
+    L_INFO("Right quad LSF median error = %5.2f\n", procName,  mederr);
+    L_INFO("Right edge slope = %d\n", procName, dew->rightslope);
+    L_INFO("Right edge curvature = %d\n", procName, dew->rightcurv);
+    ptarf = ptaCreate(ny);
+    for (i = 0; i < ny; i++) {  /* uniformly sampled in y */
+        y = i * sampling;
+        applyQuadraticFit(cr2, cr1, cr0, y, &x);
+        ptaAddPt(ptarf, x, y);
+    }
+
+    if (dew->debug) {
+        PTA  *ptalft, *ptarft;
+        h = pixGetHeight(dew->pixs);
+        pta1 = ptaCreate(h);
+        pta2 = ptaCreate(h);
+        for (i = 0; i < h; i++) {
+            applyQuadraticFit(cl2, cl1, cl0, i, &x);
+            ptaAddPt(pta1, x, i);
+            applyQuadraticFit(cr2, cr1, cr0, i, &x);
+            ptaAddPt(pta2, x, i);
+        }
+        pix1 = pixDisplayPta(NULL, dew->pixs, pta1);
+        pixDisplayPta(pix1, pix1, pta2);
+        pixRenderHorizEndPoints(pix1, ptald, ptard, 0xff000000);
+        pixDisplay(pix1, 600, 800);
+        pixWrite("/tmp/lept/dewmod/0051.png", pix1, IFF_PNG);
+        pixDestroy(&pix1);
+
+        pix1 = pixDisplayPta(NULL, dew->pixs, pta1);
+        pixDisplayPta(pix1, pix1, pta2);
+        ptalft = ptaTranspose(ptalf);
+        ptarft = ptaTranspose(ptarf);
+        pixRenderHorizEndPoints(pix1, ptalft, ptarft, 0x0000ff00);
+        pixDisplay(pix1, 800, 800);
+        pixWrite("/tmp/lept/dewmod/0052.png", pix1, IFF_PNG);
+        convertFilesToPdf("/tmp/lept/dewmod", "005", 135, 1.0, 0, 0,
+                          "Dewarp Horiz Disparity",
+                          "/tmp/lept/dewarp/horiz_disparity.pdf");
+        fprintf(stderr, "pdf file: /tmp/lept/dewarp/horiz_disparity.pdf\n");
+        pixDestroy(&pix1);
+        ptaDestroy(&pta1);
+        ptaDestroy(&pta2);
+        ptaDestroy(&ptalft);
+        ptaDestroy(&ptarft);
+    }
+
+        /* Find the x value at the midpoints (in y) of the two vertical lines,
+         * ptalf and ptarf.  These are the reference values for each of the
+         * lines.  Then use the difference between the these midpoint
+         * values and the actual x coordinates of the lines to represent
+         * the horizontal disparity (nald, nard) on the vertical lines
+         * for the sampled y values. */
+    ptaGetPt(ptalf, ny / 2, &refl, NULL);
+    ptaGetPt(ptarf, ny / 2, &refr, NULL);
+    nald = numaCreate(ny);
+    nard = numaCreate(ny);
+    for (i = 0; i < ny; i++) {
+        ptaGetPt(ptalf, i, &x, NULL);
+        numaAddNumber(nald, refl - x);
+        ptaGetPt(ptarf, i, &x, NULL);
+        numaAddNumber(nard, refr - x);
+    }
+
+        /* Now for each pair of sampled values of the two lines (at the
+         * same value of y), do a linear interpolation to generate
+         * the horizontal disparity on all sampled points between them.  */
+    ptaah = ptaaCreate(ny);
+    for (i = 0; i < ny; i++) {
+        pta = ptaCreate(2);
+        numaGetFValue(nald, i, &val);
+        ptaAddPt(pta, refl, val);
+        numaGetFValue(nard, i, &val);
+        ptaAddPt(pta, refr, val);
+        ptaGetLinearLSF(pta, &c1, &c0, NULL);  /* horiz disparity along line */
+        ptat = ptaCreate(nx);
+        for (j = 0; j < nx; j++) {
+            x = j * sampling;
+            applyLinearFit(c1, c0, x, &val);
+            ptaAddPt(ptat, x, val);
+        }
+        ptaaAddPta(ptaah, ptat, L_INSERT);
+        ptaDestroy(&pta);
+    }
+    numaDestroy(&nald);
+    numaDestroy(&nard);
+
+        /* Save the result in a fpix at the specified subsampling  */
+    fpix = fpixCreate(nx, ny);
+    for (i = 0; i < ny; i++) {
+        for (j = 0; j < nx; j++) {
+            ptaaGetPt(ptaah, i, j, NULL, &val);
+            fpixSetPixel(fpix, j, i, val);
+        }
+    }
+    dew->samphdispar = fpix;
+    dew->hsuccess = 1;
+
+    ptaDestroy(&ptal);
+    ptaDestroy(&ptar);
+    ptaDestroy(&ptald);
+    ptaDestroy(&ptard);
+    ptaDestroy(&ptalf);
+    ptaDestroy(&ptarf);
+    ptaDestroy(&ptard);
+    ptaaDestroy(&ptaah);
+    return 0;
+}
+
+
+/*!
+ *  dewarpGetTextlineCenters()
+ *
+ *      Input:  pixs (1 bpp)
+ *              debugflag (1 for debug output)
+ *      Return: ptaa (of center values of textlines)
+ *
+ *  Notes:
+ *      (1) This in general does not have a point for each value
+ *          of x, because there will be gaps between words.
+ *          It doesn't matter because we will fit a quadratic to the
+ *          points that we do have.
+ */
+PTAA *
+dewarpGetTextlineCenters(PIX     *pixs,
+                         l_int32  debugflag)
+{
+char      buf[64];
+l_int32   i, w, h, bx, by, nsegs, csize1, csize2;
+BOXA     *boxa;
+PIX      *pix1, *pix2;
+PIXA     *pixa1, *pixa2;
+PTA      *pta;
+PTAA     *ptaa;
+
+    PROCNAME("dewarpGetTextlineCenters");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PTAA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+
+    lept_mkdir("lept/dewmod");
+    if (debugflag) L_INFO("finding text line centers\n", procName);
+
+        /* Filter to solidify the text lines within the x-height region,
+         * and to remove most of the ascenders and descenders.
+         * We start with a small vertical opening to remove noise beyond
+         * the line that can cause an error in the line end points.
+         * The small closing (csize1) is used to bridge the gaps between
+         * letters.  The large closing (csize2) bridges the gaps between
+         * words; using 1/30 of the page width usually suffices. */
+    csize1 = L_MAX(15, w / 80);
+    csize2 = L_MAX(40, w / 30);
+    snprintf(buf, sizeof(buf), "o1.3 + c%d.1 + o%d.1 + c%d.1",
+             csize1, csize1, csize2);
+    pix1 = pixMorphSequence(pixs, buf, 0);
+
+        /* Remove the components (e.g., embedded images) that have
+         * long vertical runs (>= 50 pixels).  You can't use bounding
+         * boxes because connected component b.b. of lines can be quite
+         * tall due to slope and curvature.  */
+    pix2 = pixMorphSequence(pix1, "e1.50", 0);  /* seed */
+    pixSeedfillBinary(pix2, pix2, pix1, 8);  /* tall components */
+    pixXor(pix2, pix2, pix1);  /* remove tall */
+
+    if (debugflag) {
+        pixWrite("/tmp/lept/dewmod/0011.tif", pix1, IFF_TIFF_G4);
+        pixDisplayWithTitle(pix1, 0, 600, "pix1", 1);
+        pixWrite("/tmp/lept/dewmod/0012.tif", pix2, IFF_TIFF_G4);
+        pixDisplayWithTitle(pix2, 0, 800, "pix2", 1);
+    }
+    pixDestroy(&pix1);
+
+        /* Get the 8-connected components ... */
+    boxa = pixConnComp(pix2, &pixa1, 8);
+    pixDestroy(&pix2);
+    boxaDestroy(&boxa);
+    if (pixaGetCount(pixa1) == 0) {
+        pixaDestroy(&pixa1);
+        return NULL;
+    }
+
+        /* ... and remove the short width and very short height c.c */
+    pixa2 = pixaSelectBySize(pixa1, 100, 4, L_SELECT_IF_BOTH,
+                                   L_SELECT_IF_GT, NULL);
+    if ((nsegs = pixaGetCount(pixa2)) == 0) {
+        pixaDestroy(&pixa1);
+        pixaDestroy(&pixa2);
+        return NULL;
+    }
+    if (debugflag) {
+        pix2 = pixaDisplay(pixa2, w, h);
+        pixWrite("/tmp/lept/dewmod/0013.tif", pix2, IFF_TIFF_G4);
+        pixDisplayWithTitle(pix2, 0, 1000, "pix2", 1);
+        pixDestroy(&pix2);
+    }
+
+        /* For each c.c., get the weighted center of each vertical column.
+         * The result is a set of points going approximately through
+         * the center of the x-height part of the text line.  */
+    ptaa = ptaaCreate(nsegs);
+    for (i = 0; i < nsegs; i++) {
+        pixaGetBoxGeometry(pixa2, i, &bx, &by, NULL, NULL);
+        pix2 = pixaGetPix(pixa2, i, L_CLONE);
+        pta = dewarpGetMeanVerticals(pix2, bx, by);
+        ptaaAddPta(ptaa, pta, L_INSERT);
+        pixDestroy(&pix2);
+    }
+    if (debugflag) {
+        pix1 = pixCreateTemplate(pixs);
+        pix2 = pixDisplayPtaa(pix1, ptaa);
+        pixWrite("/tmp/lept/dewmod/0014.tif", pix2, IFF_PNG);
+        pixDisplayWithTitle(pix2, 0, 1200, "pix3", 1);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    pixaDestroy(&pixa1);
+    pixaDestroy(&pixa2);
+    return ptaa;
+}
+
+
+/*!
+ *  dewarpGetMeanVerticals()
+ *
+ *      Input:  pixs (1 bpp, single c.c.)
+ *              x,y (location of UL corner of pixs with respect to page image
+ *      Return: pta (mean y-values in component for each x-value,
+ *                   both translated by (x,y)
+ */
+static PTA *
+dewarpGetMeanVerticals(PIX     *pixs,
+                       l_int32  x,
+                       l_int32  y)
+{
+l_int32    w, h, i, j, wpl, sum, count;
+l_uint32  *line, *data;
+PTA       *pta;
+
+    PROCNAME("pixGetMeanVerticals");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PTA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pta = ptaCreate(w);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    for (j = 0; j < w; j++) {
+        line = data;
+        sum = count = 0;
+        for (i = 0; i < h; i++) {
+            if (GET_DATA_BIT(line, j) == 1) {
+                sum += i;
+                count += 1;
+            }
+            line += wpl;
+        }
+        if (count == 0) continue;
+        ptaAddPt(pta, x + j, y + (sum / count));
+    }
+
+    return pta;
+}
+
+
+/*!
+ *  dewarpRemoveShortLines()
+ *
+ *      Input:  pixs (1 bpp)
+ *              ptaas (input lines)
+ *              fract (minimum fraction of longest line to keep)
+ *              debugflag
+ *      Return: ptaad (containing only lines of sufficient length),
+ *                     or null on error
+ */
+PTAA *
+dewarpRemoveShortLines(PIX       *pixs,
+                       PTAA      *ptaas,
+                       l_float32  fract,
+                       l_int32    debugflag)
+{
+l_int32    w, n, i, index, maxlen, len;
+l_float32  minx, maxx;
+NUMA      *na, *naindex;
+PIX       *pix1, *pix2;
+PTA       *pta;
+PTAA      *ptaad;
+
+    PROCNAME("dewarpRemoveShortLines");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PTAA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (!ptaas)
+        return (PTAA *)ERROR_PTR("ptaas undefined", procName, NULL);
+
+    lept_mkdir("lept/dewmod");
+
+    pixGetDimensions(pixs, &w, NULL, NULL);
+    n = ptaaGetCount(ptaas);
+    ptaad = ptaaCreate(n);
+    na = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        pta = ptaaGetPta(ptaas, i, L_CLONE);
+        ptaGetRange(pta, &minx, &maxx, NULL, NULL);
+        numaAddNumber(na, maxx - minx + 1);
+        ptaDestroy(&pta);
+    }
+
+        /* Sort by length and find all that are long enough */
+    naindex = numaGetSortIndex(na, L_SORT_DECREASING);
+    numaGetIValue(naindex, 0, &index);
+    numaGetIValue(na, index, &maxlen);
+    if (maxlen < 0.5 * w)
+        L_WARNING("lines are relatively short\n", procName);
+    pta = ptaaGetPta(ptaas, index, L_CLONE);
+    ptaaAddPta(ptaad, pta, L_INSERT);
+    for (i = 1; i < n; i++) {
+        numaGetIValue(naindex, i, &index);
+        numaGetIValue(na, index, &len);
+        if (len < fract * maxlen) break;
+        pta = ptaaGetPta(ptaas, index, L_CLONE);
+        ptaaAddPta(ptaad, pta, L_INSERT);
+    }
+
+    if (debugflag) {
+        pix1 = pixCopy(NULL, pixs);
+        pix2 = pixDisplayPtaa(pix1, ptaad);
+        pixDisplayWithTitle(pix2, 0, 200, "pix4", 1);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    numaDestroy(&na);
+    numaDestroy(&naindex);
+    return ptaad;
+}
+
+
+/*!
+ *  dewarpGetLineEndpoints()
+ *
+ *      Input:  h (height of pixs)
+ *              ptaa (lines)
+ *              &ptal (<return> left end points of each line)
+ *              &ptar (<return> right end points of each line)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) We require that the set of end points extends over 45% of the
+ *          height of the input image, to insure good coverage and
+ *          avoid extrapolating the curvature too far beyond the
+ *          actual textlines.  Large extrapolations are particularly
+ *          dangerous if used as a reference model.
+ *      (2) For fitting the endpoints, x = f(y), we transpose x and y.
+ *          Thus all these ptas have x and y swapped!
+ */
+static l_int32
+dewarpGetLineEndpoints(l_int32  h,
+                       PTAA    *ptaa,
+                       PTA    **pptal,
+                       PTA    **pptar)
+{
+l_int32    i, n, npt, x, y;
+l_float32  miny, maxy, ratio;
+PTA       *pta, *ptal, *ptar;
+
+    PROCNAME("dewarpGetLineEndpoints");
+
+    if (!pptal || !pptar)
+        return ERROR_INT("&ptal and &ptar not both defined", procName, 1);
+    *pptal = *pptar = NULL;
+    if (!ptaa)
+        return ERROR_INT("ptaa undefined", procName, 1);
+
+    n = ptaaGetCount(ptaa);
+    ptal = ptaCreate(n);
+    ptar = ptaCreate(n);
+    for (i = 0; i < n; i++) {
+        pta = ptaaGetPta(ptaa, i, L_CLONE);
+        ptaGetIPt(pta, 0, &x, &y);
+        ptaAddPt(ptal, y, x);
+        npt = ptaGetCount(pta);
+        ptaGetIPt(pta, npt - 1, &x, &y);
+        ptaAddPt(ptar, y, x);
+        ptaDestroy(&pta);
+    }
+
+        /* Use the min and max of the y value on the left side. */
+    ptaGetRange(ptal, &miny, &maxy, NULL, NULL);
+    ratio = (maxy - miny) / (l_float32)h;
+    if (ratio < MIN_RATIO_LINES_TO_HEIGHT) {
+        L_INFO("ratio lines to height, %f, too small\n", procName, ratio);
+        ptaDestroy(&ptal);
+        ptaDestroy(&ptar);
+        return 1;
+    }
+
+    *pptal = ptal;
+    *pptar = ptar;
+    return 0;
+}
+
+
+/*!
+ *  dewarpFindLongLines()
+ *
+ *      Input:  ptal (left end points of lines)
+ *              ptar (right end points of lines)
+ *              minfract (minimum allowed fraction of longest line)
+ *              &ptald (<return> left end points of longest lines)
+ *              &ptard (<return> right end points of longest lines)
+ *      Return: 0 if OK, 1 on error or if there aren't enough long lines
+ *
+ *  Notes:
+ *      (1) We do the following:
+ *         (a) Sort the lines from top to bottom, and divide equally
+ *             into Top and Bottom sets.
+ *         (b) For each set, select the lines that are at least @minfract
+ *             of the length of the longest line in the set.
+ *             Typically choose @minfract around 0.95.
+ *         (c) Accumulate the left and right end points from both
+ *             sets into the two returned ptas.
+ */
+static l_int32
+dewarpFindLongLines(PTA       *ptal,
+                    PTA       *ptar,
+                    l_float32  minfract,
+                    PTA      **pptald,
+                    PTA      **pptard)
+{
+l_int32    i, n, ntop, nt, nb;
+l_float32  xl, xr, yl, yr, len, maxtoplen, maxbotlen, tbratio;
+NUMA      *nalen, *naindex;
+PTA       *ptals, *ptars, *ptald, *ptard;
+
+    PROCNAME("dewarpFindLongLines");
+
+    if (!pptald || !pptard)
+        return ERROR_INT("&ptald and &ptard are not both defined", procName, 1);
+    *pptald = *pptard = NULL;
+    if (!ptal || !ptar)
+        return ERROR_INT("ptal and ptar are not both defined", procName, 1);
+    if (minfract < 0.8 || minfract > 1.0)
+        return ERROR_INT("typ minfract is in [0.90 - 0.95]", procName, 1);
+
+        /* Sort from top to bottom, remembering that x <--> y in the pta */
+    n = ptaGetCount(ptal);
+    ptaGetSortIndex(ptal, L_SORT_BY_X, L_SORT_INCREASING, &naindex);
+    ptals = ptaSortByIndex(ptal, naindex);
+    ptars = ptaSortByIndex(ptar, naindex);
+    numaDestroy(&naindex);
+
+    ptald = ptaCreate(n);  /* output of long lines */
+    ptard = ptaCreate(n);  /* ditto */
+
+        /* Find all lines in the top half that are within typically
+         * about 5 percent of the length of the longest line in that set. */
+    ntop = n / 2;
+    nalen = numaCreate(n / 2);  /* lengths of top lines */
+    for (i = 0; i < ntop; i++) {
+        ptaGetPt(ptals, i, NULL, &xl);
+        ptaGetPt(ptars, i, NULL, &xr);
+        numaAddNumber(nalen, xr - xl);
+    }
+    numaGetMax(nalen, &maxtoplen, NULL);
+    L_INFO("Top: maxtoplen = %8.3f\n", procName, maxtoplen);
+    for (i = 0; i < ntop; i++) {
+        numaGetFValue(nalen, i, &len);
+        if (len >= minfract * maxtoplen) {
+            ptaGetPt(ptals, i, &yl, &xl);
+            ptaAddPt(ptald, yl, xl);
+            ptaGetPt(ptars, i, &yr, &xr);
+            ptaAddPt(ptard, yr, xr);
+        }
+    }
+    numaDestroy(&nalen);
+
+    nt = ptaGetCount(ptald);
+    if (nt < 3) {
+        L_INFO("too few long lines at top: %d\n", procName, nt);
+        ptaDestroy(&ptals);
+        ptaDestroy(&ptars);
+        ptaDestroy(&ptald);
+        ptaDestroy(&ptard);
+        return 1;
+    }
+
+        /* Find all lines in the bottom half that are within 8 percent
+         * of the length of the longest line in that set. */
+    nalen = numaCreate(0);  /* lengths of bottom lines */
+    for (i = ntop; i < n; i++) {
+        ptaGetPt(ptals, i, NULL, &xl);
+        ptaGetPt(ptars, i, NULL, &xr);
+        numaAddNumber(nalen, xr - xl);
+    }
+    numaGetMax(nalen, &maxbotlen, NULL);
+    L_INFO("Bottom: maxbotlen = %8.3f\n", procName, maxbotlen);
+    for (i = 0; i < n - ntop; i++) {
+        numaGetFValue(nalen, i, &len);
+        if (len >= minfract * maxbotlen) {
+            ptaGetPt(ptals, ntop + i, &yl, &xl);
+            ptaAddPt(ptald, yl, xl);
+            ptaGetPt(ptars, ntop + i, &yr, &xr);
+            ptaAddPt(ptard, yr, xr);
+        }
+    }
+    numaDestroy(&nalen);
+    ptaDestroy(&ptals);
+    ptaDestroy(&ptars);
+
+        /* Impose another condition: the top and bottom max lengths must
+         * be within 15% of each other. */
+    tbratio = (maxtoplen >= maxbotlen) ?  maxbotlen / maxtoplen :
+        maxtoplen / maxbotlen;
+    nb = ptaGetCount(ptald) - nt;
+    if (nb < 3 || tbratio < 0.85) {
+        if (nb < 3) L_INFO("too few long lines at bottom: %d\n", procName, nb);
+        if (tbratio < 0.85) L_INFO("big length diff: ratio = %4.2f\n",
+                                   procName, tbratio);
+        ptaDestroy(&ptald);
+        ptaDestroy(&ptard);
+        return 1;
+    } else {
+        *pptald = ptald;
+        *pptard = ptard;
+    }
+    return 0;
+}
+
+
+/*!
+ *  dewarpIsLineCoverageValid()
+ *
+ *      Input:  ptaa (of validated lines)
+ *              h (height of pix)
+ *              &topline (<return> location of top line)
+ *              &botline (<return> location of bottom line)
+ *      Return: 1 if coverage is valid, 0 if not or on error.
+ *
+ *  Notes:
+ *      (1) The criterion for valid coverage is:
+ *          (a) there must be lines in both halves (top and bottom)
+ *              of the image.
+ *          (b) the coverage must be at least 40% of the image height
+ */
+static l_int32
+dewarpIsLineCoverageValid(PTAA     *ptaa,
+                          l_int32   h,
+                          l_int32  *ptopline,
+                          l_int32  *pbotline)
+{
+l_int32    i, n, both_halves;
+l_float32  top, bot, y, fraction;
+
+    PROCNAME("dewarpIsLineCoverageValid");
+
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 0);
+    if ((n = ptaaGetCount(ptaa)) == 0)
+        return ERROR_INT("ptaa empty", procName, 0);
+    if (h <= 0)
+        return ERROR_INT("invalid h", procName, 0);
+    if (!ptopline || !pbotline)
+        return ERROR_INT("&topline and &botline not defined", procName, 0);
+
+    top = 100000.0;
+    bot = 0.0;
+    for (i = 0; i < n; i++) {
+        ptaaGetPt(ptaa, i, 0, NULL, &y);
+        if (y < top) top = y;
+        if (y > bot) bot = y;
+    }
+    *ptopline = (l_int32)top;
+    *pbotline = (l_int32)bot;
+    both_halves = top < 0.5 * h && bot > 0.5 * h;
+    fraction = (bot - top) / h;
+    if (both_halves && fraction > 0.40)
+        return 1;
+    return 0;
+}
+
+
+/*!
+ *  dewarpQuadraticLSF()
+ *
+ *      Input:  ptad (left or right end points of longest lines)
+ *              &a  (<return> coeff a of LSF: y = ax^2 + bx + c)
+ *              &b  (<return> coeff b of LSF: y = ax^2 + bx + c)
+ *              &c  (<return> coeff c of LSF: y = ax^2 + bx + c)
+ *              &mederr (<optional return> median error)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) This is used for finding the left or right sides of
+ *          the text block, computed as a quadratic curve.
+ *          Only the longest lines are input, so there are
+ *          no outliers.
+ *      (2) The ptas for the end points all have x and y swapped.
+ */
+static l_int32
+dewarpQuadraticLSF(PTA        *ptad,
+                   l_float32  *pa,
+                   l_float32  *pb,
+                   l_float32  *pc,
+                   l_float32  *pmederr)
+{
+l_int32    i, n;
+l_float32  x, y, xp, c0, c1, c2;
+NUMA      *naerr;
+
+    PROCNAME("dewarpQuadraticLSF");
+
+    if (pmederr) *pmederr = 0.0;
+    if (!pa || !pb || !pc)
+        return ERROR_INT("not all ptrs are defined", procName, 1);
+    *pa = *pb = *pc = 0.0;
+    if (!ptad)
+        return ERROR_INT("ptad not defined", procName, 1);
+
+        /* Fit to the longest lines */
+    ptaGetQuadraticLSF(ptad, &c2, &c1, &c0, NULL);
+    *pa = c2;
+    *pb = c1;
+    *pc = c0;
+
+        /* Optionally, find the median error */
+    if (pmederr) {
+        n = ptaGetCount(ptad);
+        naerr = numaCreate(n);
+        for (i = 0; i < n; i++) {
+            ptaGetPt(ptad, i, &y, &xp);
+            applyQuadraticFit(c2, c1, c0, y, &x);
+            numaAddNumber(naerr, L_ABS(x - xp));
+        }
+        numaGetMedian(naerr, pmederr);
+        numaDestroy(&naerr);
+    }
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                      Build line disparity model                     *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpBuildLineModel()
+ *
+ *      Input:  dew
+ *              opensize (size of opening to remove perpendicular lines)
+ *              debugfile (use null to skip writing this)
+ *      Return: 0 if OK, 1 if unable to build the model or on error
+ *
+ *  Notes:
+ *      (1) This builds the horizontal and vertical disparity arrays
+ *          for an input of ruled lines, typically for calibration.
+ *          In book scanning, you could lay the ruled paper over a page.
+ *          Then for that page and several below it, you can use the
+ *          disparity correction of the line model to dewarp the pages.
+ *      (2) The dew has been initialized with the image of ruled lines.
+ *          These lines must be continuous, but we do a small amount
+ *          of pre-processing here to insure that.
+ *      (3) @opensize is typically about 8.  It must be larger than
+ *          the thickness of the lines to be extracted.  This is the
+ *          default value, which is applied if @opensize < 3.
+ *      (4) Sets vsuccess = 1 and hsuccess = 1 if the vertical and/or
+ *          horizontal disparity arrays build.
+ *      (5) Similar to dewarpBuildPageModel(), except here the vertical
+ *          and horizontal disparity arrays are both built from ruled lines.
+ *          See notes there.
+ */
+l_int32
+dewarpBuildLineModel(L_DEWARP    *dew,
+                     l_int32      opensize,
+                     const char  *debugfile)
+{
+char     buf[64];
+l_int32  i, j, bx, by, ret, nlines;
+BOXA    *boxa;
+PIX     *pixs, *pixh, *pixv, *pix, *pix1, *pix2;
+PIXA    *pixa1, *pixa2;
+PTA     *pta;
+PTAA    *ptaa1, *ptaa2;
+
+    PROCNAME("dewarpBuildLineModel");
+
+    if (!dew)
+        return ERROR_INT("dew not defined", procName, 1);
+    if (opensize < 3) {
+        L_WARNING("opensize should be >= 3; setting to 8\n", procName);
+        opensize = 8;  /* default */
+    }
+
+    dew->debug = (debugfile) ? 1 : 0;
+    dew->vsuccess = dew->hsuccess = 0;
+    pixs = dew->pixs;
+    if (debugfile) {
+        lept_rmdir("lept/dewline");  /* erase previous images */
+        lept_mkdir("lept/dewline");
+        lept_rmdir("lept/dewmod");  /* erase previous images */
+        lept_mkdir("lept/dewmod");
+        lept_mkdir("lept/dewarp");
+        pixDisplayWithTitle(pixs, 0, 0, "pixs", 1);
+        pixWrite("/tmp/lept/dewline/001.png", pixs, IFF_PNG);
+    }
+
+        /* Extract and solidify the horizontal and vertical lines.  We use
+         * the horizontal lines to derive the vertical disparity, and v.v.
+         * Both disparities are computed using the vertical disparity
+         * algorithm; the horizontal disparity is found from the
+         * vertical lines by rotating them clockwise by 90 degrees.
+         * On the first pass, we compute the horizontal disparity, from
+         * the vertical lines, by rotating them by 90 degrees (so they
+         * are horizontal) and computing the vertical disparity on them;
+         * we rotate the resulting fpix array for the horizontal disparity
+         * back by -90 degrees.  On the second pass, we compute the vertical
+         * disparity from the horizontal lines in the usual fashion. */
+    snprintf(buf, sizeof(buf), "d1.3 + c%d.1 + o%d.1", opensize - 2, opensize);
+    pixh = pixMorphSequence(pixs, buf, 0);  /* horiz */
+    snprintf(buf, sizeof(buf), "d3.1 + c1.%d + o1.%d", opensize - 2, opensize);
+    pix1 = pixMorphSequence(pixs, buf, 0);  /* vert */
+    pixv = pixRotateOrth(pix1, 1); /* vert rotated to horizontal */
+    pixa1 = pixaCreate(2);
+    pixaAddPix(pixa1, pixv, L_INSERT);  /* get horizontal disparity first */
+    pixaAddPix(pixa1, pixh, L_INSERT);
+    pixDestroy(&pix1);
+
+        /*--------------------------------------------------------------*/
+        /*    Process twice: first for horiz disparity, then for vert   */
+        /*--------------------------------------------------------------*/
+    for (i = 0; i < 2; i++) {
+        pix = pixaGetPix(pixa1, i, L_CLONE);
+        pixDisplay(pix, 0, 900);
+        boxa = pixConnComp(pix, &pixa2, 8);
+        nlines = boxaGetCount(boxa);
+        boxaDestroy(&boxa);
+        if (nlines < dew->minlines) {
+            L_WARNING("only found %d lines\n", procName, nlines);
+            pixDestroy(&pix);
+            pixaDestroy(&pixa1);
+            continue;
+        }
+
+            /* Identify the pixels along the skeleton of each line */
+        ptaa1 = ptaaCreate(nlines);
+        for (j = 0; j < nlines; j++) {
+            pixaGetBoxGeometry(pixa2, j, &bx, &by, NULL, NULL);
+            pix1 = pixaGetPix(pixa2, j, L_CLONE);
+            pta = dewarpGetMeanVerticals(pix1, bx, by);
+            ptaaAddPta(ptaa1, pta, L_INSERT);
+            pixDestroy(&pix1);
+        }
+        pixaDestroy(&pixa2);
+        if (debugfile) {
+            pix1 = pixConvertTo32(pix);
+            pix2 = pixDisplayPtaa(pix1, ptaa1);
+            snprintf(buf, sizeof(buf), "/tmp/lept/dewline/%03d.png", 2 + 2 * i);
+            pixWrite(buf, pix2, IFF_PNG);
+            pixDestroy(&pix1);
+            pixDestroy(&pix2);
+        }
+
+            /* Remove all lines that are not at least 0.75 times the length
+             * of the longest line. */
+        ptaa2 = dewarpRemoveShortLines(pix, ptaa1, 0.75, DEBUG_SHORT_LINES);
+        if (debugfile) {
+            pix1 = pixConvertTo32(pix);
+            pix2 = pixDisplayPtaa(pix1, ptaa2);
+            snprintf(buf, sizeof(buf), "/tmp/lept/dewline/%03d.png", 3 + 2 * i);
+            pixWrite(buf, pix2, IFF_PNG);
+            pixDestroy(&pix1);
+            pixDestroy(&pix2);
+        }
+        ptaaDestroy(&ptaa1);
+        nlines = ptaaGetCount(ptaa2);
+        if (nlines < dew->minlines) {
+            pixDestroy(&pix);
+            ptaaDestroy(&ptaa2);
+            L_WARNING("%d lines: too few to build model\n", procName, nlines);
+            continue;
+        }
+
+            /* Get the sampled 'vertical' disparity from the textline
+             * centers.  The disparity array will push pixels vertically
+             * so that each line is flat and centered at the y-position
+             * of the mid-point. */
+        ret = dewarpFindVertDisparity(dew, ptaa2, 1 - i);
+
+            /* If i == 0, move the result to the horizontal disparity,
+             * rotating it back by -90 degrees. */
+        if (i == 0) {  /* horizontal disparity, really */
+            if (ret) {
+                L_WARNING("horizontal disparity not built\n", procName);
+            } else {
+                L_INFO("hsuccess = 1\n", procName);
+                dew->samphdispar = fpixRotateOrth(dew->sampvdispar, 3);
+                fpixDestroy(&dew->sampvdispar);
+                if (debugfile)
+                    lept_mv("/tmp/lept/dewarp/vert_disparity.pdf",
+                            "lept/dewarp", "horiz_disparity.pdf", NULL);
+            }
+            dew->hsuccess = dew->vsuccess;
+            dew->vsuccess = 0;
+        } else {  /* i == 1 */
+            if (ret)
+                L_WARNING("vertical disparity not built\n", procName);
+            else
+                L_INFO("vsuccess = 1\n", procName);
+        }
+        ptaaDestroy(&ptaa2);
+        pixDestroy(&pix);
+    }
+    pixaDestroy(&pixa1);
+
+        /* Debug output */
+    if (debugfile) {
+        if (dew->vsuccess == 1) {
+            dewarpPopulateFullRes(dew, NULL, 0, 0);
+            pix1 = fpixRenderContours(dew->fullvdispar, 3.0, 0.15);
+            pixWrite("/tmp/lept/dewline/006.png", pix1, IFF_PNG);
+            pixDisplay(pix1, 1000, 0);
+            pixDestroy(&pix1);
+        }
+        if (dew->hsuccess == 1) {
+            pix1 = fpixRenderContours(dew->fullhdispar, 3.0, 0.15);
+            pixWrite("/tmp/lept/dewline/007.png", pix1, IFF_PNG);
+            pixDisplay(pix1, 1000, 0);
+            pixDestroy(&pix1);
+        }
+        convertFilesToPdf("/tmp/lept/dewline", NULL, 135, 1.0, 0, 0,
+                          "Dewarp Build Line Model", debugfile);
+        fprintf(stderr, "pdf file: %s\n", debugfile);
+    }
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                         Query model status                           *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpaModelStatus()
+ *
+ *      Input:  dewa
+ *              pageno
+ *              &vsuccess (<optional return> 1 on success)
+ *              &hsuccess (<optional return> 1 on success)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This tests if a model has been built, not if it is valid.
+ */
+l_int32
+dewarpaModelStatus(L_DEWARPA  *dewa,
+                   l_int32     pageno,
+                   l_int32    *pvsuccess,
+                   l_int32    *phsuccess)
+{
+L_DEWARP  *dew;
+
+    PROCNAME("dewarpaModelStatus");
+
+    if (pvsuccess) *pvsuccess = 0;
+    if (phsuccess) *phsuccess = 0;
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+
+    if ((dew = dewarpaGetDewarp(dewa, pageno)) == NULL)
+        return ERROR_INT("dew not retrieved", procName, 1);
+    if (pvsuccess) *pvsuccess = dew->vsuccess;
+    if (phsuccess) *phsuccess = dew->hsuccess;
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                          Rendering helpers                           *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixRenderMidYs()
+ *
+ *      Input:  pixs (32 bpp)
+ *              namidys (y location of reference lines for vertical disparity)
+ *              linew (width of rendered line; typ 2)
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+pixRenderMidYs(PIX     *pixs,
+               NUMA    *namidys,
+               l_int32  linew)
+{
+l_int32   i, n, w, yval, rval, gval, bval;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixRenderMidYs");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!namidys)
+        return ERROR_INT("namidys not defined", procName, 1);
+
+    w = pixGetWidth(pixs);
+    n = numaGetCount(namidys);
+    cmap = pixcmapCreateRandom(8, 0, 0);
+    for (i = 0; i < n; i++) {
+        pixcmapGetColor(cmap, i % 256, &rval, &gval, &bval);
+        numaGetIValue(namidys, i, &yval);
+        pixRenderLineArb(pixs, 0, yval, w, yval, linew, rval, gval, bval);
+    }
+    pixcmapDestroy(&cmap);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderHorizEndPoints()
+ *
+ *      Input:  pixs (32 bpp)
+ *              ptal (left side line end points)
+ *              ptar (right side line end points)
+ *              color (0xrrggbb00)
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+pixRenderHorizEndPoints(PIX      *pixs,
+                        PTA      *ptal,
+                        PTA      *ptar,
+                        l_uint32  color)
+{
+PIX      *pixcirc;
+PTA      *ptalt, *ptart, *ptacirc;
+
+    PROCNAME("pixRenderHorizEndPoints");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!ptal || !ptar)
+        return ERROR_INT("ptal and ptar not both defined", procName, 1);
+
+    ptacirc = generatePtaFilledCircle(5);
+    pixcirc = pixGenerateFromPta(ptacirc, 11, 11);
+    ptalt = ptaTranspose(ptal);
+    ptart = ptaTranspose(ptar);
+
+    pixDisplayPtaPattern(pixs, pixs, ptalt, pixcirc, 5, 5, color);
+    pixDisplayPtaPattern(pixs, pixs, ptart, pixcirc, 5, 5, color);
+    ptaDestroy(&ptacirc);
+    ptaDestroy(&ptalt);
+    ptaDestroy(&ptart);
+    pixDestroy(&pixcirc);
+    return 0;
+}
+
diff --git a/src/dewarp3.c b/src/dewarp3.c
new file mode 100644 (file)
index 0000000..4090f92
--- /dev/null
@@ -0,0 +1,973 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  dewarp3.c
+ *
+ *    Applying and stripping the page disparity model
+ *
+ *      Apply disparity array to pix
+ *          l_int32            dewarpaApplyDisparity()
+ *          static l_int32     dewarpaApplyInit()
+ *          static PIX        *pixApplyVertDisparity()
+ *          static PIX        *pixApplyHorizDisparity()
+ *
+ *      Apply disparity array to boxa
+ *          l_int32            dewarpaApplyDisparityBoxa()
+ *          static BOXA       *boxaApplyDisparity()
+ *
+ *      Stripping out data and populating full res disparity
+ *          l_int32            dewarpMinimize()
+ *          l_int32            dewarpPopulateFullRes()
+ *
+ *      Static functions not presently in use
+ *          static FPIX       *fpixSampledDisparity()
+ *          static FPIX       *fpixExtraHorizDisparity()
+ *
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+static l_int32 dewarpaApplyInit(L_DEWARPA *dewa, l_int32 pageno, PIX *pixs,
+                                l_int32 x, l_int32 y, L_DEWARP **pdew,
+                                const char *debugfile);
+static PIX *pixApplyVertDisparity(L_DEWARP *dew, PIX *pixs, l_int32 grayin);
+static PIX * pixApplyHorizDisparity(L_DEWARP *dew, PIX *pixs, l_int32 grayin);
+static BOXA *boxaApplyDisparity(L_DEWARP *dew, BOXA *boxa, l_int32 direction,
+                                l_int32 mapdir);
+
+
+
+/*----------------------------------------------------------------------*
+ *                 Apply warping disparity array to pixa                *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpaApplyDisparity()
+ *
+ *      Input:  dewa
+ *              pageno (of page model to be used; may be a ref model)
+ *              pixs (image to be modified; can be 1, 8 or 32 bpp)
+ *              grayin (gray value, from 0 to 255, for pixels brought in;
+ *                      use -1 to use pixels on the boundary of pixs)
+ *              x, y (origin for generation of disparity arrays)
+ *              &pixd (<return> disparity corrected image)
+ *              debugfile (use null to skip writing this)
+ *      Return: 0 if OK, 1 on error (no models or ref models available)
+ *
+ *  Notes:
+ *      (1) This applies the disparity arrays to the specified image.
+ *      (2) Specify gray color for pixels brought in from the outside:
+ *          0 is black, 255 is white.  Use -1 to select pixels from the
+ *          boundary of the source image.
+ *      (3) If the models and ref models have not been validated, this
+ *          will do so by calling dewarpaInsertRefModels().
+ *      (4) This works with both stripped and full resolution page models.
+ *          If the full res disparity array(s) are missing, they are remade.
+ *      (5) The caller must handle errors that are returned because there
+ *          are no valid models or ref models for the page -- typically
+ *          by using the input pixs.
+ *      (6) If there is no model for @pageno, this will use the model for
+ *          'refpage' and put the result in the dew for @pageno.
+ *      (7) This populates the full resolution disparity arrays if
+ *          necessary.  If x and/or y are positive, they are used,
+ *          in conjunction with pixs, to determine the required
+ *          slope-based extension of the full resolution disparity
+ *          arrays in each direction.  When (x,y) == (0,0), all
+ *          extension is to the right and down.  Nonzero values of (x,y)
+ *          are useful for dewarping when pixs is deliberately undercropped.
+ *      (8) Important: when applying disparity to a number of images,
+ *          after calling this function and saving the resulting pixd,
+ *          you should call dewarpMinimize(dew) on the dew for @pageno.
+ *          This will remove pixs and pixd (or their clones) stored in dew,
+ *          as well as the full resolution disparity arrays.  Together,
+ *          these hold approximately 16 bytes for each pixel in pixs.
+ */
+l_int32
+dewarpaApplyDisparity(L_DEWARPA   *dewa,
+                      l_int32      pageno,
+                      PIX         *pixs,
+                      l_int32      grayin,
+                      l_int32      x,
+                      l_int32      y,
+                      PIX        **ppixd,
+                      const char  *debugfile)
+{
+L_DEWARP  *dew1, *dew;
+PIX       *pixv, *pixh;
+
+    PROCNAME("dewarpaApplyDisparity");
+
+        /* Initialize the output with the input, so we'll have that
+         * in case we can't apply the page model. */
+    if (!ppixd)
+        return ERROR_INT("&pixd not defined", procName, 1);
+    *ppixd = pixClone(pixs);
+    if (grayin > 255) {
+        L_WARNING("invalid grayin = %d; clipping at 255\n", procName, grayin);
+        grayin = 255;
+    }
+
+        /* Find the appropriate dew to use and fully populate its array(s) */
+    if (dewarpaApplyInit(dewa, pageno, pixs, x, y, &dew, debugfile))
+        return ERROR_INT("no model available", procName, 1);
+
+        /* Correct for vertical disparity and save the result */
+    if ((pixv = pixApplyVertDisparity(dew, pixs, grayin)) == NULL) {
+        dewarpMinimize(dew);
+        return ERROR_INT("pixv not made", procName, 1);
+    }
+    pixDestroy(ppixd);
+    *ppixd = pixv;
+    if (debugfile) {
+        pixDisplayWithTitle(pixv, 300, 0, "pixv", 1);
+        lept_rmdir("lept/dewapply");  /* remove previous images */
+        lept_mkdir("lept/dewapply");
+        pixWrite("/tmp/lept/dewapply/001.png", pixs, IFF_PNG);
+        pixWrite("/tmp/lept/dewapply/002.png", pixv, IFF_PNG);
+    }
+
+        /* Optionally, correct for horizontal disparity */
+    if (dewa->useboth && dew->hsuccess) {
+        if (dew->hvalid == FALSE) {
+            L_INFO("invalid horiz model for page %d\n", procName, pageno);
+        } else {
+            if ((pixh = pixApplyHorizDisparity(dew, pixv, grayin)) != NULL) {
+                pixDestroy(ppixd);
+                *ppixd = pixh;
+                if (debugfile) {
+                    pixDisplayWithTitle(pixh, 600, 0, "pixh", 1);
+                    pixWrite("/tmp/lept/dewapply/003.png", pixh, IFF_PNG);
+                }
+            } else {
+                L_ERROR("horiz disparity failed on page %d\n",
+                        procName, pageno);
+            }
+        }
+    }
+
+    if (debugfile) {
+        dew1 = dewarpaGetDewarp(dewa, pageno);
+        dewarpDebug(dew1, "lept/dewapply", 0);
+        convertFilesToPdf("/tmp/lept/dewapply", NULL, 135, 1.0, 0, 0,
+                         "Dewarp Apply Disparity", debugfile);
+        fprintf(stderr, "pdf file: %s\n", debugfile);
+    }
+
+        /* Get rid of the large full res disparity arrays */
+    dewarpMinimize(dew);
+
+    return 0;
+}
+
+
+/*!
+ *  dewarpaApplyInit()
+ *
+ *      Input:  dewa
+ *              pageno (of page model to be used; may be a ref model)
+ *              pixs (image to be modified; can be 1, 8 or 32 bpp)
+ *              x, y (origin for generation of disparity arrays)
+ *              &pdew (<return> dewarp to be used for this page
+ *              debugfile (use null to skip writing this)
+ *      Return: 0 if OK, 1 on error (no models or ref models available)
+ *
+ *  Notes:
+ *      (1) This prepares pixs for being dewarped.  It returns 1 if
+ *          no dewarping model exists.
+ *      (2) The returned @dew contains the model to be used for this page
+ *          image.  The @dew is owned by dewa; do not destroy.
+ *      (3) See dewarpApplyDisparity() for other details on inputs.
+ */
+static l_int32
+dewarpaApplyInit(L_DEWARPA   *dewa,
+                 l_int32      pageno,
+                 PIX         *pixs,
+                 l_int32      x,
+                 l_int32      y,
+                 L_DEWARP   **pdew,
+                 const char  *debugfile)
+{
+l_int32    debug;
+L_DEWARP  *dew1, *dew2;
+
+    PROCNAME("dewarpaApplyInit");
+
+    if (!pdew)
+        return ERROR_INT("&dew not defined", procName, 1);
+    *pdew = NULL;
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+    if (pageno < 0 || pageno > dewa->maxpage)
+        return ERROR_INT("invalid pageno", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (x < 0) x = 0;
+    if (y < 0) y = 0;
+    debug = (debugfile) ? 1 : 0;
+
+        /* Make sure all models are valid and all refmodels have
+         * been added to dewa */
+    if (dewa->modelsready == FALSE)
+        dewarpaInsertRefModels(dewa, 0, debug);
+
+        /* Check for the existence of a valid model; we don't expect
+         * all pages to have them. */
+    if ((dew1 = dewarpaGetDewarp(dewa, pageno)) == NULL) {
+        L_INFO("no valid dew model for page %d\n", procName, pageno);
+        return 1;
+    }
+
+        /* Get the page model that we will use and sanity-check that
+         * it is valid.  The ultimate result will be put in dew1->pixd. */
+    if (dew1->hasref)  /* point to another page with a model */
+        dew2 = dewarpaGetDewarp(dewa, dew1->refpage);
+    else
+        dew2 = dew1;
+    if (dew2->vvalid == FALSE)
+        return ERROR_INT("no model; shouldn't happen", procName, 1);
+    *pdew = dew2;
+
+        /* Generate the full res disparity arrays if they don't exist
+         * (e.g., if they've been minimized or read from file), or if
+         * they are too small for the current image.  */
+    dewarpPopulateFullRes(dew2, pixs, x, y);
+    return 0;
+}
+
+
+/*!
+ *  pixApplyVertDisparity()
+ *
+ *      Input:  dew
+ *              pixs (1, 8 or 32 bpp)
+ *              grayin (gray value, from 0 to 255, for pixels brought in;
+ *                      use -1 to use pixels on the boundary of pixs)
+ *      Return: pixd (modified to remove vertical disparity), or null on error
+ *
+ *  Notes:
+ *      (1) This applies the vertical disparity array to the specified
+ *          image.  For src pixels above the image, we use the pixels
+ *          in the first raster line.
+ *      (2) Specify gray color for pixels brought in from the outside:
+ *          0 is black, 255 is white.  Use -1 to select pixels from the
+ *          boundary of the source image.
+ */
+static PIX *
+pixApplyVertDisparity(L_DEWARP  *dew,
+                      PIX       *pixs,
+                      l_int32    grayin)
+{
+l_int32     i, j, w, h, d, fw, fh, wpld, wplf, isrc, val8;
+l_uint32   *datad, *lined;
+l_float32  *dataf, *linef;
+void      **lineptrs;
+FPIX       *fpix;
+PIX        *pixd;
+
+    PROCNAME("pixApplyVertDisparity");
+
+    if (!dew)
+        return (PIX *)ERROR_PTR("dew not defined", procName, NULL);
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pix not 1, 8 or 32 bpp", procName, NULL);
+    if ((fpix = dew->fullvdispar) == NULL)
+        return (PIX *)ERROR_PTR("fullvdispar not defined", procName, NULL);
+    fpixGetDimensions(fpix, &fw, &fh);
+    if (fw < w || fh < h) {
+        fprintf(stderr, "fw = %d, w = %d, fh = %d, h = %d\n", fw, w, fh, h);
+        return (PIX *)ERROR_PTR("invalid fpix size", procName, NULL);
+    }
+
+        /* Two choices for requested pixels outside pixs: (1) use pixels'
+         * from the boundary of pixs; use white or light gray pixels. */
+    pixd = pixCreateTemplate(pixs);
+    if (grayin >= 0)
+        pixSetAllGray(pixd, grayin);
+    datad = pixGetData(pixd);
+    dataf = fpixGetData(fpix);
+    wpld = pixGetWpl(pixd);
+    wplf = fpixGetWpl(fpix);
+    if (d == 1) {
+        lineptrs = pixGetLinePtrs(pixs, NULL);
+        for (i = 0; i < h; i++) {
+            lined = datad + i * wpld;
+            linef = dataf + i * wplf;
+            for (j = 0; j < w; j++) {
+                isrc = (l_int32)(i - linef[j] + 0.5);
+                if (grayin < 0)  /* use value at boundary if outside */
+                    isrc = L_MIN(L_MAX(isrc, 0), h - 1);
+                if (isrc >= 0 && isrc < h) {  /* remains gray if outside */
+                    if (GET_DATA_BIT(lineptrs[isrc], j))
+                        SET_DATA_BIT(lined, j);
+                }
+            }
+        }
+    } else if (d == 8) {
+        lineptrs = pixGetLinePtrs(pixs, NULL);
+        for (i = 0; i < h; i++) {
+            lined = datad + i * wpld;
+            linef = dataf + i * wplf;
+            for (j = 0; j < w; j++) {
+                isrc = (l_int32)(i - linef[j] + 0.5);
+                if (grayin < 0)
+                    isrc = L_MIN(L_MAX(isrc, 0), h - 1);
+                if (isrc >= 0 && isrc < h) {
+                    val8 = GET_DATA_BYTE(lineptrs[isrc], j);
+                    SET_DATA_BYTE(lined, j, val8);
+                }
+            }
+        }
+    } else {  /* d == 32 */
+        lineptrs = pixGetLinePtrs(pixs, NULL);
+        for (i = 0; i < h; i++) {
+            lined = datad + i * wpld;
+            linef = dataf + i * wplf;
+            for (j = 0; j < w; j++) {
+                isrc = (l_int32)(i - linef[j] + 0.5);
+                if (grayin < 0)
+                    isrc = L_MIN(L_MAX(isrc, 0), h - 1);
+                if (isrc >= 0 && isrc < h)
+                    lined[j] = GET_DATA_FOUR_BYTES(lineptrs[isrc], j);
+            }
+        }
+    }
+
+    LEPT_FREE(lineptrs);
+    return pixd;
+}
+
+
+/*!
+ *  pixApplyHorizDisparity()
+ *
+ *      Input:  dew
+ *              pixs (1, 8 or 32 bpp)
+ *              grayin (gray value, from 0 to 255, for pixels brought in;
+ *                      use -1 to use pixels on the boundary of pixs)
+ *      Return: pixd (modified to remove horizontal disparity if possible),
+ *              or null on error.
+ *
+ *  Notes:
+ *      (1) This applies the horizontal disparity array to the specified
+ *          image.
+ *      (2) Specify gray color for pixels brought in from the outside:
+ *          0 is black, 255 is white.  Use -1 to select pixels from the
+ *          boundary of the source image.
+ *      (3) The input pixs has already been corrected for vertical disparity.
+ *          If the horizontal disparity array doesn't exist, this returns
+ *          a clone of @pixs.
+ */
+static PIX *
+pixApplyHorizDisparity(L_DEWARP  *dew,
+                       PIX       *pixs,
+                       l_int32    grayin)
+{
+l_int32     i, j, w, h, d, fw, fh, wpls, wpld, wplf, jsrc, val8;
+l_uint32   *datas, *lines, *datad, *lined;
+l_float32  *dataf, *linef;
+FPIX       *fpix;
+PIX        *pixd;
+
+    PROCNAME("pixApplyHorizDisparity");
+
+    if (!dew)
+        return (PIX *)ERROR_PTR("dew not defined", procName, pixs);
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pix not 1, 8 or 32 bpp", procName, NULL);
+    if ((fpix = dew->fullhdispar) == NULL)
+        return (PIX *)ERROR_PTR("fullhdispar not defined", procName, NULL);
+    fpixGetDimensions(fpix, &fw, &fh);
+    if (fw < w || fh < h) {
+        fprintf(stderr, "fw = %d, w = %d, fh = %d, h = %d\n", fw, w, fh, h);
+        return (PIX *)ERROR_PTR("invalid fpix size", procName, NULL);
+    }
+
+        /* Two choices for requested pixels outside pixs: (1) use pixels'
+         * from the boundary of pixs; use white or light gray pixels. */
+    pixd = pixCreateTemplate(pixs);
+    if (grayin >= 0)
+        pixSetAllGray(pixd, grayin);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    dataf = fpixGetData(fpix);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    wplf = fpixGetWpl(fpix);
+    if (d == 1) {
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            linef = dataf + i * wplf;
+            for (j = 0; j < w; j++) {
+                jsrc = (l_int32)(j - linef[j] + 0.5);
+                if (grayin < 0)  /* use value at boundary if outside */
+                    jsrc = L_MIN(L_MAX(jsrc, 0), w - 1);
+                if (jsrc >= 0 && jsrc < w) {  /* remains gray if outside */
+                    if (GET_DATA_BIT(lines, jsrc))
+                        SET_DATA_BIT(lined, j);
+                }
+            }
+        }
+    } else if (d == 8) {
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            linef = dataf + i * wplf;
+            for (j = 0; j < w; j++) {
+                jsrc = (l_int32)(j - linef[j] + 0.5);
+                if (grayin < 0)
+                    jsrc = L_MIN(L_MAX(jsrc, 0), w - 1);
+                if (jsrc >= 0 && jsrc < w) {
+                    val8 = GET_DATA_BYTE(lines, jsrc);
+                    SET_DATA_BYTE(lined, j, val8);
+                }
+            }
+        }
+    } else {  /* d == 32 */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            linef = dataf + i * wplf;
+            for (j = 0; j < w; j++) {
+                jsrc = (l_int32)(j - linef[j] + 0.5);
+                if (grayin < 0)
+                    jsrc = L_MIN(L_MAX(jsrc, 0), w - 1);
+                if (jsrc >= 0 && jsrc < w)
+                    lined[j] = lines[jsrc];
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                 Apply warping disparity array to boxa                *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpaApplyDisparityBoxa()
+ *
+ *      Input:  dewa
+ *              pageno (of page model to be used; may be a ref model)
+ *              pixs (initial pix reference; for alignment and debugging)
+ *              boxas (boxa to be mapped)
+ *              mapdir (1 if mapping forward from original to dewarped;
+ *                      0 if backward)
+ *              x, y (origin for generation of disparity arrays with
+ *                    respect to the source region)
+ *              &boxad (<return> disparity corrected boxa)
+ *              debugfile (use null to skip writing this)
+ *      Return: 0 if OK, 1 on error (no models or ref models available)
+ *
+ *  Notes:
+ *      (1) This applies the disparity arrays in one of two mapping directions
+ *          to the specified boxa.  It can be used in the backward direction
+ *          to locate a box in the original coordinates that would have
+ *          been dewarped to to the specified image.
+ *      (2) If there is no model for @pageno, this will use the model for
+ *          'refpage' and put the result in the dew for @pageno.
+ *      (3) This works with both stripped and full resolution page models.
+ *          If the full res disparity array(s) are missing, they are remade.
+ *      (4) If an error occurs, a copy of the input boxa is returned.
+ */
+l_int32
+dewarpaApplyDisparityBoxa(L_DEWARPA   *dewa,
+                          l_int32      pageno,
+                          PIX         *pixs,
+                          BOXA        *boxas,
+                          l_int32      mapdir,
+                          l_int32      x,
+                          l_int32      y,
+                          BOXA       **pboxad,
+                          const char  *debugfile)
+{
+l_int32    debug_out;
+L_DEWARP  *dew1, *dew;
+BOXA      *boxav, *boxah;
+PIX       *pixv, *pixh;
+
+    PROCNAME("dewarpaApplyDisparityBoxa");
+
+        /* Initialize the output with the input, so we'll have that
+         * in case we can't apply the page model. */
+    if (!pboxad)
+        return ERROR_INT("&boxad not defined", procName, 1);
+    *pboxad = boxaCopy(boxas, L_CLONE);
+
+        /* Find the appropriate dew to use and fully populate its array(s) */
+    if (dewarpaApplyInit(dewa, pageno, pixs, x, y, &dew, debugfile))
+        return ERROR_INT("no model available", procName, 1);
+
+        /* Correct for vertical disparity and save the result */
+    if ((boxav = boxaApplyDisparity(dew, boxas, L_VERT, mapdir)) == NULL) {
+        dewarpMinimize(dew);
+        return ERROR_INT("boxa1 not made", procName, 1);
+    }
+    boxaDestroy(pboxad);
+    *pboxad = boxav;
+    pixv = NULL;
+    pixh = NULL;
+    if (debugfile && mapdir != 1)
+        L_INFO("Reverse map direction; no debug output\n", procName);
+    debug_out = debugfile && (mapdir == 1);
+    if (debug_out) {
+        PIX  *pix1;
+        lept_rmdir("lept/dewboxa");  /* remove previous images */
+        lept_mkdir("lept/dewboxa");
+        pix1 = pixConvertTo32(pixs);
+        pixRenderBoxaArb(pix1, boxas, 2, 255, 0, 0);
+        pixWrite("/tmp/lept/dewboxa/01.png", pix1, IFF_PNG);
+        pixDestroy(&pix1);
+        pixv = pixApplyVertDisparity(dew, pixs, 255);
+        pix1 = pixConvertTo32(pixv);
+        pixRenderBoxaArb(pix1, boxav, 2, 0, 255, 0);
+        pixWrite("/tmp/lept/dewboxa/02.png", pix1, IFF_PNG);
+        pixDestroy(&pix1);
+    }
+
+        /* Optionally, correct for horizontal disparity */
+    if (dewa->useboth && dew->hsuccess) {
+        if (dew->hvalid == FALSE) {
+            L_INFO("invalid horiz model for page %d\n", procName, pageno);
+        } else {
+            boxah = boxaApplyDisparity(dew, boxav, L_HORIZ, mapdir);
+            if (!boxah) {
+                L_ERROR("horiz disparity fails on page %d\n", procName, pageno);
+            } else {
+                boxaDestroy(pboxad);
+                *pboxad = boxah;
+                if (debug_out) {
+                    PIX  *pix1;
+                    pixh = pixApplyHorizDisparity(dew, pixv, 255);
+                    pix1 = pixConvertTo32(pixh);
+                    pixRenderBoxaArb(pix1, boxah, 2, 0, 0, 255);
+                    pixWrite("/tmp/lept/dewboxa/03.png", pix1, IFF_PNG);
+                    pixDestroy(&pixh);
+                    pixDestroy(&pix1);
+                }
+            }
+        }
+    }
+
+    if (debug_out) {
+        pixDestroy(&pixv);
+        dew1 = dewarpaGetDewarp(dewa, pageno);
+        dewarpDebug(dew1, "lept/dewapply", 0);
+        convertFilesToPdf("/tmp/lept/dewboxa", NULL, 135, 1.0, 0, 0,
+                         "Dewarp Apply Disparity Boxa", debugfile);
+        fprintf(stderr, "Dewarp Apply Disparity Boxa pdf file: %s\n",
+                debugfile);
+    }
+
+        /* Get rid of the large full res disparity arrays */
+    dewarpMinimize(dew);
+
+    return 0;
+}
+
+
+/*!
+ *  boxaApplyDisparity()
+ *
+ *      Input:  dew
+ *              boxa
+ *              direction (L_HORIZ or L_VERT)
+ *              mapdir (1 if mapping forward from original to dewarped;
+ *                      0 if backward)
+ *      Return: boxad (modified by the disparity), or null on error
+ */
+static BOXA *
+boxaApplyDisparity(L_DEWARP  *dew,
+                   BOXA      *boxa,
+                   l_int32    direction,
+                   l_int32    mapdir)
+{
+l_int32     x, y, w, h, ib, ip, nbox, wpl;
+l_float32   xn, yn;
+l_float32  *data, *line;
+BOX        *boxs, *boxd;
+BOXA       *boxad;
+FPIX       *fpix;
+PTA        *ptas, *ptad;
+
+    PROCNAME("boxaApplyDisparity");
+
+    if (!dew)
+        return (BOXA *)ERROR_PTR("dew not defined", procName, NULL);
+    if (!boxa)
+        return (BOXA *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (direction == L_VERT)
+        fpix = dew->fullvdispar;
+    else if (direction == L_HORIZ)
+        fpix = dew->fullhdispar;
+    else
+        return (BOXA *)ERROR_PTR("invalid direction", procName, NULL);
+    if (!fpix)
+        return (BOXA *)ERROR_PTR("full disparity not defined", procName, NULL);
+    fpixGetDimensions(fpix, &w, &h);
+
+        /* Clip the output to the positive quadrant because all box
+         * coordinates must be non-negative. */
+    data = fpixGetData(fpix);
+    wpl = fpixGetWpl(fpix);
+    nbox = boxaGetCount(boxa);
+    boxad = boxaCreate(nbox);
+    for (ib = 0; ib < nbox; ib++) {
+        boxs = boxaGetBox(boxa, ib, L_COPY);
+        ptas = boxConvertToPta(boxs, 4);
+        ptad = ptaCreate(4);
+        for (ip = 0; ip < 4; ip++) {
+            ptaGetIPt(ptas, ip, &x, &y);
+            line = data + y * wpl;
+            if (direction == L_VERT) {
+                if (mapdir == 0)
+                    yn = y - line[x];
+                else
+                    yn = y + line[x];
+                yn = L_MAX(0, yn);
+                ptaAddPt(ptad, x, yn);
+            } else {  /* direction == L_HORIZ */
+                if (mapdir == 0)
+                    xn = x - line[x];
+                else
+                    xn = x + line[x];
+                xn = L_MAX(0, xn);
+                ptaAddPt(ptad, xn, y);
+            }
+        }
+        boxd = ptaConvertToBox(ptad);
+        boxaAddBox(boxad, boxd, L_INSERT);
+        boxDestroy(&boxs);
+        ptaDestroy(&ptas);
+        ptaDestroy(&ptad);
+    }
+
+    return boxad;
+}
+
+
+/*----------------------------------------------------------------------*
+ *          Stripping out data and populating full res disparity        *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpMinimize()
+ *
+ *      Input:  dew
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This removes all data that is not needed for serialization.
+ *          It keeps the subsampled disparity array(s), so the full
+ *          resolution arrays can be reconstructed.
+ */
+l_int32
+dewarpMinimize(L_DEWARP  *dew)
+{
+L_DEWARP  *dewt;
+
+    PROCNAME("dewarpMinimize");
+
+    if (!dew)
+        return ERROR_INT("dew not defined", procName, 1);
+
+        /* If dew is a ref, minimize the actual dewarp */
+    if (dew->hasref)
+        dewt = dewarpaGetDewarp(dew->dewa, dew->refpage);
+    else
+        dewt = dew;
+    if (!dewt)
+        return ERROR_INT("dewt not found", procName, 1);
+
+    pixDestroy(&dewt->pixs);
+    fpixDestroy(&dewt->fullvdispar);
+    fpixDestroy(&dewt->fullhdispar);
+    numaDestroy(&dewt->namidys);
+    numaDestroy(&dewt->nacurves);
+    return 0;
+}
+
+
+/*!
+ *  dewarpPopulateFullRes()
+ *
+ *      Input:  dew
+ *              pix (<optional>, to give size of actual image)
+ *              x, y (origin for generation of disparity arrays)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If the full resolution vertical and horizontal disparity
+ *          arrays do not exist, they are built from the subsampled ones.
+ *      (2) If pixs is not given, the size of the arrays is determined
+ *          by the original image from which the sampled version was
+ *          generated.  Any values of (x,y) are ignored.
+ *      (3) If pixs is given, the full resolution disparity arrays must
+ *          be large enough to accommodate it.
+ *          (a) If the arrays do not exist, the value of (x,y) determines
+ *              the origin of the full resolution arrays without extension,
+ *              relative to pixs.  Thus, (x,y) gives the amount of
+ *              slope extension in (left, top).  The (right, bottom)
+ *              extension is then determined by the size of pixs and
+ *              (x,y); the values should never be < 0.
+ *          (b) If the arrays exist and pixs is too large, the existing
+ *              full res arrays are destroyed and new ones are made,
+ *              again using (x,y) to determine the extension in the
+ *              four directions.
+ */
+l_int32
+dewarpPopulateFullRes(L_DEWARP  *dew,
+                      PIX       *pix,
+                      l_int32    x,
+                      l_int32    y)
+{
+l_int32     width, height, fw, fh, deltaw, deltah, redfactor;
+FPIX       *fpixt1, *fpixt2;
+
+    PROCNAME("dewarpPopulateFullRes");
+
+    if (!dew)
+        return ERROR_INT("dew not defined", procName, 1);
+    if (!dew->sampvdispar)
+        return ERROR_INT("no sampled vert disparity", procName, 1);
+    if (x < 0) x = 0;
+    if (y < 0) y = 0;
+
+        /* Establish the target size for the full res arrays */
+    if (pix)
+        pixGetDimensions(pix, &width, &height, NULL);
+    else {
+        width = dew->w;
+        height = dew->h;
+    }
+
+        /* Destroy the existing arrays if they are too small */
+    if (dew->fullvdispar) {
+        fpixGetDimensions(dew->fullvdispar, &fw, &fh);
+        if (width > fw || height > fw)
+            fpixDestroy(&dew->fullvdispar);
+    }
+    if (dew->fullhdispar) {
+        fpixGetDimensions(dew->fullhdispar, &fw, &fh);
+        if (width > fw || height > fw)
+            fpixDestroy(&dew->fullhdispar);
+    }
+
+        /* Find the required width and height expansion deltas */
+    deltaw = width - dew->sampling * (dew->nx - 1) + 2;
+    deltah = height - dew->sampling * (dew->ny - 1) + 2;
+    redfactor = dew->redfactor;
+    deltaw = redfactor * L_MAX(0, deltaw);
+    deltah = redfactor * L_MAX(0, deltah);
+
+        /* Generate the full res vertical array if it doesn't exist,
+         * extending it as required to make it big enough.  Use x,y
+         * to determine the amounts on each side. */
+    if (!dew->fullvdispar) {
+        fpixt1 = fpixCopy(NULL, dew->sampvdispar);
+        if (redfactor == 2)
+            fpixAddMultConstant(fpixt1, 0.0, (l_float32)redfactor);
+        fpixt2 = fpixScaleByInteger(fpixt1, dew->sampling * redfactor);
+        fpixDestroy(&fpixt1);
+        if (deltah == 0 && deltaw == 0) {
+            dew->fullvdispar = fpixt2;
+        }
+        else {
+            dew->fullvdispar = fpixAddSlopeBorder(fpixt2, x, deltaw - x,
+                                                  y, deltah - y);
+            fpixDestroy(&fpixt2);
+        }
+    }
+
+        /* Similarly, generate the full res horizontal array if it
+         * doesn't exist.  Do this even if useboth == 0. */
+    if (!dew->fullhdispar && dew->samphdispar) {
+        fpixt1 = fpixCopy(NULL, dew->samphdispar);
+        if (redfactor == 2)
+            fpixAddMultConstant(fpixt1, 0.0, (l_float32)redfactor);
+        fpixt2 = fpixScaleByInteger(fpixt1, dew->sampling * redfactor);
+        fpixDestroy(&fpixt1);
+        if (deltah == 0 && deltaw == 0) {
+            dew->fullhdispar = fpixt2;
+        }
+        else {
+            dew->fullhdispar = fpixAddSlopeBorder(fpixt2, x, deltaw - x,
+                                                  y, deltah - y);
+            fpixDestroy(&fpixt2);
+        }
+    }
+
+    return 0;
+}
+
+
+#if 0
+/*----------------------------------------------------------------------*
+ *                Static functions not presently in use                 *
+ *----------------------------------------------------------------------*/
+/*!
+ *  fpixSampledDisparity()
+ *
+ *      Input:  fpixs (full resolution disparity model)
+ *              sampling (sampling factor)
+ *      Return: fpixd (sampled disparity model), or null on error
+ *
+ *  Notes:
+ *      (1) This converts full to sampled disparity.
+ *      (2) The input array is sampled at the right and top edges, and
+ *          at every @sampling pixels horizontally and vertically.
+ *      (3) The sampled array may not extend to the right and bottom
+ *          pixels in fpixs.  This will occur if fpixs was generated
+ *          with slope extension because the image on that page was
+ *          larger than normal.  This is fine, because in use the
+ *          sampled array will be interpolated back to full resolution
+ *          and then extended as required.  So the operations of
+ *          sampling and interpolation will be idempotent.
+ *      (4) There must be at least 3 sampled points horizontally and
+ *          vertically.
+ */
+static FPIX *
+fpixSampledDisparity(FPIX    *fpixs,
+                     l_int32  sampling)
+{
+l_int32    w, h, wd, hd, i, j, is, js;
+l_float32  val;
+FPIX      *fpixd;
+
+    PROCNAME("fpixSampledDisparity");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+    if (sampling < 1)
+        return (FPIX *)ERROR_PTR("sampling < 1", procName, NULL);
+
+    fpixGetDimensions(fpixs, &w, &h);
+    wd = 1 + (w + sampling - 2) / sampling;
+    hd = 1 + (h + sampling - 2) / sampling;
+    if (wd < 3 || hd < 3)
+        return (FPIX *)ERROR_PTR("wd < 3 or hd < 3", procName, NULL);
+    fpixd = fpixCreate(wd, hd);
+    for (i = 0; i < hd; i++) {
+        is = sampling * i;
+        if (is >= h) continue;
+        for (j = 0; j < wd; j++) {
+            js = sampling * j;
+            if (js >= w) continue;
+            fpixGetPixel(fpixs, js, is, &val);
+            fpixSetPixel(fpixd, j, i, val);
+        }
+    }
+
+    return fpixd;
+}
+
+
+/*!
+ *  fpixExtraHorizDisparity()
+ *
+ *      Input:  fpixv (vertical disparity model)
+ *              factor (conversion factor for vertical disparity slope;
+ *                      use 0 for default)
+ *              &xwid (<return> extra width to be added to dewarped pix)
+ *      Return: fpixh, or null on error
+ *
+ *  Notes:
+ *      (1) This takes the difference in vertical disparity at top
+ *          and bottom of the image, and converts it to an assumed
+ *          horizontal disparity.  In use, we add this to the
+ *          horizontal disparity determined by the left and right
+ *          ends of textlines.
+ *      (2) Usage:
+ *            l_int32 xwid = [extra width to be added to fpix and image]
+ *            FPix *fpix = fpixExtraHorizDisparity(dew->fullvdispar, 0, &xwid);
+ *            fpixLinearCombination(dew->fullhdispar, dew->fullhdispar,
+ *                                  fpix, 1.0, 1.0);
+ */
+static FPIX *
+fpixExtraHorizDisparity(FPIX      *fpixv,
+                        l_float32  factor,
+                        l_int32   *pxwid)
+{
+l_int32     w, h, i, j, fw, wpl, maxloc;
+l_float32   val1, val2, vdisp, vdisp0, maxval;
+l_float32  *data, *line, *fadiff;
+NUMA       *nadiff;
+FPIX       *fpixh;
+
+    PROCNAME("fpixExtraHorizDisparity");
+
+    if (!fpixv)
+        return (FPIX *)ERROR_PTR("fpixv not defined", procName, NULL);
+    if (!pxwid)
+        return (FPIX *)ERROR_PTR("&xwid not defined", procName, NULL);
+    if (factor == 0.0)
+        factor = DEFAULT_SLOPE_FACTOR;
+
+        /* Estimate horizontal disparity from the vertical disparity
+         * difference between the top and bottom, normalized to the
+         * image height.  Add the maximum value to the width of the
+         * output image, so that all src pixels can be mapped
+         * into the dest. */
+    fpixGetDimensions(fpixv, &w, &h);
+    nadiff = numaCreate(w);
+    for (j = 0; j < w; j++) {
+        fpixGetPixel(fpixv, j, 0, &val1);
+        fpixGetPixel(fpixv, j, h - 1, &val2);
+        vdisp = factor * (val2 - val1) / (l_float32)h;
+        if (j == 0) vdisp0 = vdisp;
+        vdisp = vdisp0 - vdisp;
+        numaAddNumber(nadiff, vdisp);
+    }
+    numaGetMax(nadiff, &maxval, &maxloc);
+    *pxwid = (l_int32)(maxval + 0.5);
+
+    fw = w + *pxwid;
+    fpixh = fpixCreate(fw, h);
+    data = fpixGetData(fpixh);
+    wpl = fpixGetWpl(fpixh);
+    fadiff = numaGetFArray(nadiff, L_NOCOPY);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < fw; j++) {
+            if (j < maxloc)   /* this may not work for even pages */
+                line[j] = fadiff[j];
+            else  /* keep it at the max value the rest of the way across */
+                line[j] = maxval;
+        }
+    }
+
+    numaDestroy(&nadiff);
+    return fpixh;
+}
+#endif
diff --git a/src/dewarp4.c b/src/dewarp4.c
new file mode 100644 (file)
index 0000000..a36e474
--- /dev/null
@@ -0,0 +1,1142 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  dewarp4.c
+ *
+ *    Single page dewarper
+ *
+ *    Reference model (book-level, dewarpa) operations and debugging output
+ *
+ *      Top-level single page dewarper
+ *          l_int32            dewarpSinglePage()
+ *          l_int32            dewarpSinglePageInit()
+ *          l_int32            dewarpSinglePageRun()
+ *
+ *      Operations on dewarpa
+ *          l_int32            dewarpaListPages()
+ *          l_int32            dewarpaSetValidModels()
+ *          l_int32            dewarpaInsertRefModels()
+ *          l_int32            dewarpaStripRefModels()
+ *          l_int32            dewarpaRestoreModels()
+ *
+ *      Dewarp debugging output
+ *          l_int32            dewarpaInfo()
+ *          l_int32            dewarpaModelStats()
+ *          static l_int32     dewarpaTestForValidModel()
+ *          l_int32            dewarpaShowArrays()
+ *          l_int32            dewarpDebug()
+ *          l_int32            dewarpShowResults()
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+static l_int32 dewarpaTestForValidModel(L_DEWARPA *dewa, L_DEWARP *dew,
+                                        l_int32 notests);
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_INVALID_MODELS      0   /* set this to 1 for debuging */
+#endif  /* !NO_CONSOLE_IO */
+
+    /* Special parameter values */
+static const l_int32     GRAYIN_VALUE = 200;
+
+/*----------------------------------------------------------------------*
+ *                   Top-level single page dewarper                     *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpSinglePage()
+ *
+ *      Input:  pixs (with text, any depth)
+ *              thresh (for global thresholding to 1 bpp; ignored otherwise)
+ *              adaptive (1 for adaptive thresholding; 0 for global threshold)
+ *              use_both (1 for horizontal and vertical; 0 for vertical only)
+ *              &pixd (<return> dewarped result)
+ *              &dewa (<optional return> dewa with single page; NULL to skip)
+ *              debug (1 for debugging output, 0 otherwise)
+ *      Return: 0 if OK, 1 on error (list of page numbers), or null on error
+ *
+ *  Notes:
+ *      (1) Dewarps pixs and returns the result in &pixd.
+ *      (2) This uses default values for all model parameters.
+ *      (3) If pixs is 1 bpp, the parameters @adaptive and @thresh are ignored.
+ *      (4) If it can't build a model, returns a copy of pixs in &pixd.
+ */
+l_int32
+dewarpSinglePage(PIX         *pixs,
+                 l_int32      thresh,
+                 l_int32      adaptive,
+                 l_int32      use_both,
+                 PIX        **ppixd,
+                 L_DEWARPA  **pdewa,
+                 l_int32      debug)
+{
+L_DEWARPA  *dewa;
+PIX        *pixb;
+
+    PROCNAME("dewarpSinglePage");
+
+    if (!ppixd)
+        return ERROR_INT("&pixd not defined", procName, 1);
+    *ppixd = NULL;
+    if (pdewa) *pdewa = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    dewarpSinglePageInit(pixs, thresh, adaptive, use_both, &pixb, &dewa);
+    if (!pixb) {
+        dewarpaDestroy(&dewa);
+        return ERROR_INT("pixb not made", procName, 1);
+    }
+
+    dewarpSinglePageRun(pixs, pixb, dewa, ppixd, debug);
+
+    if (pdewa)
+        *pdewa = dewa;
+    else
+        dewarpaDestroy(&dewa);
+    pixDestroy(&pixb);
+    return 0;
+}
+
+
+/*!
+ *  dewarpSinglePageInit()
+ *
+ *      Input:  pixs (with text, any depth)
+ *              thresh (for global thresholding to 1 bpp; ignored otherwise)
+ *              adaptive (1 for adaptive thresholding; 0 for global threshold)
+ *              use_both (1 for horizontal and vertical; 0 for vertical only)
+ *              &pixb (<return> 1 bpp image)
+ *              &dewa (<return> initialized dewa)
+ *      Return: 0 if OK, 1 on error (list of page numbers), or null on error
+ *
+ *  Notes:
+ *      (1) This binarizes the input pixs if necessary, returning the
+ *          binarized image.  It also initializes the dewa to default values
+ *          for the model parameters.
+ *      (2) If pixs is 1 bpp, the parameters @adaptive and @thresh are ignored.
+ *      (3) To change the model parameters, call dewarpaSetCurvatures()
+ *          before running dewarpSinglePageRun().  For example:
+ *             dewarpSinglePageInit(pixs, 0, 1, 1, &pixb, &dewa);
+ *             dewarpaSetCurvatures(dewa, 250, -1, -1, 80, 70, 150);
+ *             dewarpSinglePageRun(pixs, pixb, dewa, &pixd, 0);
+ *             dewarpaDestroy(&dewa);
+ *             pixDestroy(&pixb);
+ */
+l_int32
+dewarpSinglePageInit(PIX         *pixs,
+                     l_int32      thresh,
+                     l_int32      adaptive,
+                     l_int32      use_both,
+                     PIX        **ppixb,
+                     L_DEWARPA  **pdewa)
+{
+PIX  *pix1;
+
+    PROCNAME("dewarpSinglePageInit");
+
+    if (ppixb) *ppixb = NULL;
+    if (pdewa) *pdewa = NULL;
+    if (!ppixb || !pdewa)
+        return ERROR_INT("&pixb and &dewa not both defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    *pdewa = dewarpaCreate(1, 0, 1, 0, -1);
+    dewarpaUseBothArrays(*pdewa, use_both);
+
+         /* Generate a binary image, if necessary */
+    if (pixGetDepth(pixs) > 1) {
+        pix1 = pixConvertTo8(pixs, 0);
+        if (adaptive)
+            *ppixb = pixAdaptThresholdToBinary(pix1, NULL, 1.0);
+        else
+            *ppixb = pixThresholdToBinary(pix1, thresh);
+        pixDestroy(&pix1);
+    } else {
+        *ppixb = pixClone(pixs);
+    }
+    return 0;
+}
+
+
+/*!
+ *  dewarpSinglePageRun()
+ *
+ *      Input:  pixs (any depth)
+ *              pixb (1 bpp)
+ *              dewa (initialized)
+ *              &pixd (<return> dewarped result)
+ *              debug (1 for debugging output, 0 otherwise)
+ *      Return: 0 if OK, 1 on error (list of page numbers), or null on error
+ *
+ *  Notes:
+ *      (1) Dewarps pixs and returns the result in &pixd.
+ *      (2) The model parameters must be set before calling this.
+ *      (3) If a model cannot be built, this returns a copy of pixs in &pixd.
+ */
+l_int32
+dewarpSinglePageRun(PIX        *pixs,
+                    PIX        *pixb,
+                    L_DEWARPA  *dewa,
+                    PIX       **ppixd,
+                    l_int32     debug)
+{
+const char  *debugfile;
+l_int32      vsuccess, ret;
+L_DEWARP    *dew;
+
+    PROCNAME("dewarpSinglePageRun");
+
+    if (!ppixd)
+        return ERROR_INT("&pixd not defined", procName, 1);
+    *ppixd = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixb)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+
+        /* Generate the page model */
+    lept_mkdir("lept/dewarp");
+    dew = dewarpCreate(pixb, 0);
+    dewarpaInsertDewarp(dewa, dew);
+    debugfile = (debug) ? "/tmp/lept/dewarp/singlepage_model.pdf" : NULL;
+    dewarpBuildPageModel(dew, debugfile);
+    dewarpaModelStatus(dewa, 0, &vsuccess, NULL);
+    if (vsuccess == 0) {
+        L_ERROR("failure to build model for vertical disparity\n", procName);
+        *ppixd = pixCopy(NULL, pixs);
+        return 0;
+    }
+
+        /* Apply the page model */
+    debugfile = (debug) ? "/tmp/lept/dewarp/singlepage_apply.pdf" : NULL;
+    ret = dewarpaApplyDisparity(dewa, 0, pixs, 255, 0, 0, ppixd, debugfile);
+    if (ret)
+        L_ERROR("invalid model; failure to apply disparity\n", procName);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                        Operations on dewarpa                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpaListPages()
+ *
+ *      Input:  dewa (populated with dewarp structs for pages)
+ *      Return: 0 if OK, 1 on error (list of page numbers), or null on error
+ *
+ *  Notes:
+ *      (1) This generates two numas, stored in the dewarpa, that give:
+ *          (a) the page number for each dew that has a page model.
+ *          (b) the page number for each dew that has either a page
+ *              model or a reference model.
+ *          It can be called at any time.
+ *      (2) It is called by the dewarpa serializer before writing.
+ */
+l_int32
+dewarpaListPages(L_DEWARPA  *dewa)
+{
+l_int32    i;
+L_DEWARP  *dew;
+NUMA      *namodels, *napages;
+
+    PROCNAME("dewarpaListPages");
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+
+    numaDestroy(&dewa->namodels);
+    numaDestroy(&dewa->napages);
+    namodels = numaCreate(dewa->maxpage + 1);
+    napages = numaCreate(dewa->maxpage + 1);
+    dewa->namodels = namodels;
+    dewa->napages = napages;
+    for (i = 0; i <= dewa->maxpage; i++) {
+        if ((dew = dewarpaGetDewarp(dewa, i)) != NULL) {
+            if (dew->hasref == 0)
+                numaAddNumber(namodels, dew->pageno);
+            numaAddNumber(napages, dew->pageno);
+        }
+    }
+    return 0;
+}
+
+
+/*!
+ *  dewarpaSetValidModels()
+ *
+ *      Input:  dewa
+ *              notests
+ *              debug (1 to output information on invalid page models)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) A valid model must meet the rendering requirements, which
+ *          include whether or not a vertical disparity model exists
+ *          and conditions on curvatures for vertical and horizontal
+ *          disparity models.
+ *      (2) If @notests == 1, this ignores the curvature constraints
+ *          and assumes that all successfully built models are valid.
+ *      (3) This function does not need to be called by the application.
+ *          It is called by dewarpaInsertRefModels(), which
+ *          will destroy all invalid dewarps.  Consequently, to inspect
+ *          an invalid dewarp model, it must be done before calling
+ *          dewarpaInsertRefModels().
+ */
+l_int32
+dewarpaSetValidModels(L_DEWARPA  *dewa,
+                      l_int32     notests,
+                      l_int32     debug)
+{
+l_int32    i, n, maxcurv, diffcurv, diffedge;
+L_DEWARP  *dew;
+
+    PROCNAME("dewarpaSetValidModels");
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+
+    n = dewa->maxpage + 1;
+    for (i = 0; i < n; i++) {
+        if ((dew = dewarpaGetDewarp(dewa, i)) == NULL)
+            continue;
+
+        if (debug) {
+            if (dew->hasref == 1) {
+                L_INFO("page %d: has only a ref model\n", procName, i);
+            } else if (dew->vsuccess == 0) {
+                L_INFO("page %d: no model successfully built\n",
+                       procName, i);
+            } else if (!notests) {
+                maxcurv = L_MAX(L_ABS(dew->mincurv), L_ABS(dew->maxcurv));
+                diffcurv = dew->maxcurv - dew->mincurv;
+                if (dewa->useboth && !dew->hsuccess)
+                    L_INFO("page %d: useboth, but no horiz disparity\n",
+                               procName, i);
+                if (maxcurv > dewa->max_linecurv)
+                    L_INFO("page %d: max curvature %d > max_linecurv\n",
+                                procName, i, diffcurv);
+                if (diffcurv < dewa->min_diff_linecurv)
+                    L_INFO("page %d: diff curv %d < min_diff_linecurv\n",
+                                procName, i, diffcurv);
+                if (diffcurv > dewa->max_diff_linecurv)
+                    L_INFO("page %d: abs diff curv %d > max_diff_linecurv\n",
+                                procName, i, diffcurv);
+                if (dew->hsuccess) {
+                    if (L_ABS(dew->leftslope) > dewa->max_edgeslope)
+                        L_INFO("page %d: abs left slope %d > max_edgeslope\n",
+                                    procName, i, dew->leftslope);
+                    if (L_ABS(dew->rightslope) > dewa->max_edgeslope)
+                        L_INFO("page %d: abs right slope %d > max_edgeslope\n",
+                                    procName, i, dew->rightslope);
+                    diffedge = L_ABS(dew->leftcurv - dew->rightcurv);
+                    if (L_ABS(dew->leftcurv) > dewa->max_edgecurv)
+                        L_INFO("page %d: left curvature %d > max_edgecurv\n",
+                                    procName, i, dew->leftcurv);
+                    if (L_ABS(dew->rightcurv) > dewa->max_edgecurv)
+                        L_INFO("page %d: right curvature %d > max_edgecurv\n",
+                               procName, i, dew->rightcurv);
+                    if (diffedge > dewa->max_diff_edgecurv)
+                        L_INFO("page %d: abs diff left-right curv %d > "
+                               "max_diff_edgecurv\n", procName, i, diffedge);
+                }
+            }
+        }
+
+        dewarpaTestForValidModel(dewa, dew, notests);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  dewarpaInsertRefModels()
+ *
+ *      Input:  dewa
+ *              notests (if 1, ignore curvature constraints on model)
+ *              debug (1 to output information on invalid page models)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This destroys all dewarp models that are invalid, and then
+ *          inserts reference models where possible.
+ *      (2) If @notests == 1, this ignores the curvature constraints
+ *          and assumes that all successfully built models are valid.
+ *      (3) If useboth == 0, it uses the closest valid model within the
+ *          distance and parity constraints.  If useboth == 1, it tries
+ *          to use the closest allowed hvalid model; if it doesn't find
+ *          an hvalid model, it uses the closest valid model.
+ *      (4) For all pages without a model, this clears out any existing
+ *          invalid and reference dewarps, finds the nearest valid model
+ *          with the same parity, and inserts an empty dewarp with the
+ *          reference page.
+ *      (5) Then if it is requested to use both vertical and horizontal
+ *          disparity arrays (useboth == 1), it tries to replace any
+ *          hvalid == 0 model or reference with an hvalid == 1 reference.
+ *      (6) The distance constraint is that any reference model must
+ *          be within maxdist.  Note that with the parity constraint,
+ *          no reference models will be used if maxdist < 2.
+ *      (7) This function must be called, even if reference models will
+ *          not be used.  It should be called after building models on all
+ *          available pages, and after setting the rendering parameters.
+ *      (8) If the dewa has been serialized, this function is called by
+ *          dewarpaRead() when it is read back.  It is also called
+ *          any time the rendering parameters are changed.
+ *      (9) Note: if this has been called with useboth == 1, and useboth
+ *          is reset to 0, you should first call dewarpRestoreModels()
+ *          to bring real models from the cache back to the primary array.
+ */
+l_int32
+dewarpaInsertRefModels(L_DEWARPA  *dewa,
+                       l_int32     notests,
+                       l_int32     debug)
+{
+l_int32    i, j, n, val, min, distdown, distup;
+L_DEWARP  *dew;
+NUMA      *na, *nah;
+
+    PROCNAME("dewarpaInsertRefModels");
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+    if (dewa->maxdist < 2)
+        L_INFO("maxdist < 2; no ref models can be used\n", procName);
+
+        /* Make an indicator numa for pages with valid models. */
+    dewarpaSetValidModels(dewa, notests, debug);
+    n = dewa->maxpage + 1;
+    na = numaMakeConstant(0, n);
+    for (i = 0; i < n; i++) {
+        dew = dewarpaGetDewarp(dewa, i);
+        if (dew && dew->vvalid)
+            numaReplaceNumber(na, i, 1);
+    }
+
+        /* Remove all existing ref models and restore models from cache */
+    dewarpaRestoreModels(dewa);
+
+        /* Move invalid models to the cache, and insert reference dewarps
+         * for pages that need to borrow a model.
+         * First, try to find a valid model for each page. */
+    for (i = 0; i < n; i++) {
+        numaGetIValue(na, i, &val);
+        if (val == 1) continue;  /* already has a valid model */
+        if ((dew = dewa->dewarp[i]) != NULL) {  /* exists but is not valid; */
+            dewa->dewarpcache[i] = dew;  /* move it to the cache */
+            dewa->dewarp[i] = NULL;
+        }
+        if (dewa->maxdist < 2) continue;  /* can't use a ref model */
+            /* Look back for nearest model */
+        distdown = distup = dewa->maxdist + 1;
+        for (j = i - 2; j >= 0 && distdown > dewa->maxdist; j -= 2) {
+            numaGetIValue(na, j, &val);
+            if (val == 1) distdown = i - j;
+        }
+            /* Look ahead for nearest model */
+        for (j = i + 2; j < n && distup > dewa->maxdist; j += 2) {
+            numaGetIValue(na, j, &val);
+            if (val == 1) distup = j - i;
+        }
+        min = L_MIN(distdown, distup);
+        if (min > dewa->maxdist) continue;  /* no valid model in range */
+        if (distdown <= distup)
+            dewarpaInsertDewarp(dewa, dewarpCreateRef(i, i - distdown));
+        else
+            dewarpaInsertDewarp(dewa, dewarpCreateRef(i, i + distup));
+    }
+    numaDestroy(&na);
+
+        /* If a valid model will do, we're finished. */
+    if (dewa->useboth == 0) {
+        dewa->modelsready = 1;  /* validated */
+        return 0;
+    }
+
+        /* The request is useboth == 1.  Now try to find an hvalid model */
+    nah = numaMakeConstant(0, n);
+    for (i = 0; i < n; i++) {
+        dew = dewarpaGetDewarp(dewa, i);
+        if (dew && dew->hvalid)
+            numaReplaceNumber(nah, i, 1);
+    }
+    for (i = 0; i < n; i++) {
+        numaGetIValue(nah, i, &val);
+        if (val == 1) continue;  /* already has a hvalid model */
+        if (dewa->maxdist < 2) continue;  /* can't use a ref model */
+        distdown = distup = 100000;
+        for (j = i - 2; j >= 0; j -= 2) {  /* look back for nearest model */
+            numaGetIValue(nah, j, &val);
+            if (val == 1) {
+                distdown = i - j;
+                break;
+            }
+        }
+        for (j = i + 2; j < n; j += 2) {  /* look ahead for nearest model */
+            numaGetIValue(nah, j, &val);
+            if (val == 1) {
+                distup = j - i;
+                break;
+            }
+        }
+        min = L_MIN(distdown, distup);
+        if (min > dewa->maxdist) continue;  /* no hvalid model within range */
+
+            /* We can replace the existing valid model with an hvalid model.
+             * If it's not a reference, save it in the cache. */
+        if ((dew = dewarpaGetDewarp(dewa, i)) == NULL) {
+            L_ERROR("dew is null for page %d!\n", procName, i);
+        } else {
+            if (dew->hasref == 0) {  /* not a ref model */
+                dewa->dewarpcache[i] = dew;  /* move it to the cache */
+                dewa->dewarp[i] = NULL;  /* must null the ptr */
+            }
+        }
+        if (distdown <= distup)  /* insert the hvalid ref model */
+            dewarpaInsertDewarp(dewa, dewarpCreateRef(i, i - distdown));
+        else
+            dewarpaInsertDewarp(dewa, dewarpCreateRef(i, i + distup));
+    }
+    numaDestroy(&nah);
+
+    dewa->modelsready = 1;  /* validated */
+    return 0;
+}
+
+
+/*!
+ *  dewarpaStripRefModels()
+ *
+ *      Input:  dewa (populated with dewarp structs for pages)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This examines each dew in a dewarpa, and removes
+ *          all that don't have their own page model (i.e., all
+ *          that have "references" to nearby pages with valid models).
+ *          These references were generated by dewarpaInsertRefModels(dewa).
+ */
+l_int32
+dewarpaStripRefModels(L_DEWARPA  *dewa)
+{
+l_int32    i;
+L_DEWARP  *dew;
+
+    PROCNAME("dewarpaStripRefModels");
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+
+    for (i = 0; i <= dewa->maxpage; i++) {
+        if ((dew = dewarpaGetDewarp(dewa, i)) != NULL) {
+            if (dew->hasref)
+                dewarpDestroy(&dewa->dewarp[i]);
+        }
+    }
+    dewa->modelsready = 0;
+
+        /* Regenerate the page lists */
+    dewarpaListPages(dewa);
+    return 0;
+}
+
+
+/*!
+ *  dewarpaRestoreModels()
+ *
+ *      Input:  dewa (populated with dewarp structs for pages)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This puts all real models (and only real models) in the
+ *          primary dewarp array.  First remove all dewarps that are
+ *          only references to other page models.  Then move all models
+ *          that had been cached back into the primary dewarp array.
+ *      (2) After this is done, we still need to recompute and insert
+ *          the reference models before dewa->modelsready is true.
+ */
+l_int32
+dewarpaRestoreModels(L_DEWARPA  *dewa)
+{
+l_int32    i;
+L_DEWARP  *dew;
+
+    PROCNAME("dewarpaRestoreModels");
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+
+        /* Strip out ref models.  Then only real models will be in the
+         * primary dewarp array. */
+    dewarpaStripRefModels(dewa);
+
+        /* The cache holds only real models, which are not necessarily valid. */
+    for (i = 0; i <= dewa->maxpage; i++) {
+        if ((dew = dewa->dewarpcache[i]) != NULL) {
+            if (dewa->dewarp[i]) {
+                L_ERROR("dew in both cache and main array!: page %d\n",
+                        procName, i);
+            } else {
+                dewa->dewarp[i] = dew;
+                dewa->dewarpcache[i] = NULL;
+            }
+        }
+    }
+    dewa->modelsready = 0;  /* new ref models not yet inserted */
+
+        /* Regenerate the page lists */
+    dewarpaListPages(dewa);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                      Dewarp debugging output                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  dewarpaInfo()
+ *
+ *      Input:  fp
+ *              dewa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+dewarpaInfo(FILE       *fp,
+            L_DEWARPA  *dewa)
+{
+l_int32    i, n, pageno, nnone, nvsuccess, nvvalid, nhsuccess, nhvalid, nref;
+L_DEWARP  *dew;
+
+    PROCNAME("dewarpaInfo");
+
+    if (!fp)
+        return ERROR_INT("dewa not defined", procName, 1);
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+
+    fprintf(fp, "\nDewarpaInfo: %p\n", dewa);
+    fprintf(fp, "nalloc = %d, maxpage = %d\n", dewa->nalloc, dewa->maxpage);
+    fprintf(fp, "sampling = %d, redfactor = %d, minlines = %d\n",
+            dewa->sampling, dewa->redfactor, dewa->minlines);
+    fprintf(fp, "maxdist = %d, useboth = %d\n",
+            dewa->maxdist, dewa->useboth);
+
+    dewarpaModelStats(dewa, &nnone, &nvsuccess, &nvvalid,
+                      &nhsuccess, &nhvalid, &nref);
+    n = numaGetCount(dewa->napages);
+    fprintf(stderr, "Total number of pages with a dew = %d\n", n);
+    fprintf(stderr, "Number of pages without any models = %d\n", nnone);
+    fprintf(stderr, "Number of pages with a vert model = %d\n", nvsuccess);
+    fprintf(stderr, "Number of pages with a valid vert model = %d\n", nvvalid);
+    fprintf(stderr, "Number of pages with both models = %d\n", nhsuccess);
+    fprintf(stderr, "Number of pages with both models valid = %d\n", nhvalid);
+    fprintf(stderr, "Number of pages with a ref model = %d\n", nref);
+
+    for (i = 0; i < n; i++) {
+        numaGetIValue(dewa->napages, i, &pageno);
+        if ((dew = dewarpaGetDewarp(dewa, pageno)) == NULL)
+            continue;
+        fprintf(stderr, "Page: %d\n", dew->pageno);
+        fprintf(stderr, "  hasref = %d, refpage = %d\n",
+                dew->hasref, dew->refpage);
+        fprintf(stderr, "  nlines = %d\n", dew->nlines);
+        fprintf(stderr, "  w = %d, h = %d, nx = %d, ny = %d\n",
+                dew->w, dew->h, dew->nx, dew->ny);
+        if (dew->sampvdispar)
+            fprintf(stderr, "  Vertical disparity builds:\n"
+                    "    (min,max,abs-diff) line curvature = (%d,%d,%d)\n",
+                    dew->mincurv, dew->maxcurv, dew->maxcurv - dew->mincurv);
+        if (dew->samphdispar)
+            fprintf(stderr, "  Horizontal disparity builds:\n"
+                    "    left edge slope = %d, right edge slope = %d\n"
+                    "    (left,right,abs-diff) edge curvature = (%d,%d,%d)\n",
+                    dew->leftslope, dew->rightslope, dew->leftcurv,
+                    dew->rightcurv, L_ABS(dew->leftcurv - dew->rightcurv));
+    }
+    return 0;
+}
+
+
+/*!
+ *  dewarpaModelStats()
+ *
+ *      Input:  dewa
+ *              &nnone (<optional return> number without any model)
+ *              &nvsuccess (<optional return> number with a vert model)
+ *              &nvvalid (<optional return> number with a valid vert model)
+ *              &nhsuccess (<optional return> number with both models)
+ *              &nhvalid (<optional return> number with both models valid)
+ *              &nref (<optional return> number with a reference model)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) A page without a model has no dew.  It most likely failed to
+ *          generate a vertical model, and has not been assigned a ref
+ *          model from a neighboring page with a valid vertical model.
+ *      (2) A page has vsuccess == 1 if there is at least a model of the
+ *          vertical disparity.  The model may be invalid, in which case
+ *          dewarpaInsertRefModels() will stash it in the cache and
+ *          attempt to replace it by a valid ref model.
+ *      (3) A vvvalid model is a vertical disparity model whose parameters
+ *          satisfy the constraints given in dewarpaSetValidModels().
+ *      (4) A page has hsuccess == 1 if both the vertical and horizontal
+ *          disparity arrays have been constructed.
+ *      (5) An  hvalid model has vertical and horizontal disparity
+ *          models whose parameters satisfy the constraints given
+ *          in dewarpaSetValidModels().
+ *      (6) A page has a ref model if it failed to generate a valid
+ *          model but was assigned a vvalid or hvalid model on another
+ *          page (within maxdist) by dewarpaInsertRefModel().
+ *      (7) This calls dewarpaTestForValidModel(); it ignores the vvalid
+ *          and hvalid fields.
+ */
+l_int32
+dewarpaModelStats(L_DEWARPA  *dewa,
+                  l_int32    *pnnone,
+                  l_int32    *pnvsuccess,
+                  l_int32    *pnvvalid,
+                  l_int32    *pnhsuccess,
+                  l_int32    *pnhvalid,
+                  l_int32    *pnref)
+{
+l_int32    i, n, pageno, nnone, nvsuccess, nvvalid, nhsuccess, nhvalid, nref;
+L_DEWARP  *dew;
+
+    PROCNAME("dewarpaModelStats");
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+
+    dewarpaListPages(dewa);
+    n = numaGetCount(dewa->napages);
+    nnone = nref = nvsuccess = nvvalid = nhsuccess = nhvalid = 0;
+    for (i = 0; i < n; i++) {
+        numaGetIValue(dewa->napages, i, &pageno);
+        dew = dewarpaGetDewarp(dewa, pageno);
+        if (!dew) {
+            nnone++;
+            continue;
+        }
+        if (dew->hasref == 1)
+            nref++;
+        if (dew->vsuccess == 1)
+            nvsuccess++;
+        if (dew->hsuccess == 1)
+            nhsuccess++;
+        dewarpaTestForValidModel(dewa, dew, 0);
+        if (dew->vvalid == 1)
+            nvvalid++;
+        if (dew->hvalid == 1)
+            nhvalid++;
+    }
+
+    if (pnnone) *pnnone = nnone;
+    if (pnref) *pnref = nref;
+    if (pnvsuccess) *pnvsuccess = nvsuccess;
+    if (pnvvalid) *pnvvalid = nvvalid;
+    if (pnhsuccess) *pnhsuccess = nhsuccess;
+    if (pnhvalid) *pnhvalid = nhvalid;
+    return 0;
+}
+
+
+/*!
+ *  dewarpaTestForValidModel()
+ *
+ *      Input:  dewa
+ *              dew
+ *              notests
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Computes validity of vertical (vvalid) model and both
+ *          vertical and horizontal (hvalid) models.
+ *      (2) If @notests == 1, this ignores the curvature constraints
+ *          and assumes that all successfully built models are valid.
+ *      (3) This is just about the models, not the rendering process,
+ *          so the value of useboth is not considered here.
+ */
+static l_int32
+dewarpaTestForValidModel(L_DEWARPA  *dewa,
+                         L_DEWARP   *dew,
+                         l_int32     notests)
+{
+l_int32  maxcurv, diffcurv, diffedge;
+
+    PROCNAME("dewarpaTestForValidModel");
+
+    if (!dewa || !dew)
+        return ERROR_INT("dewa and dew not both defined", procName, 1);
+
+    if (notests) {
+       dew->vvalid = dew->vsuccess;
+       dew->hvalid = dew->hsuccess;
+       return 0;
+    }
+
+        /* No actual model was built */
+    if (dew->vsuccess == 0) return 0;
+
+        /* Was previously found not to have a valid model  */
+    if (dew->hasref == 1) return 0;
+
+        /* vsuccess == 1; a vertical (line) model exists.
+         * First test that the vertical curvatures are within allowed
+         * bounds.  Note that all curvatures are signed.*/
+    maxcurv = L_MAX(L_ABS(dew->mincurv), L_ABS(dew->maxcurv));
+    diffcurv = dew->maxcurv - dew->mincurv;
+    if (maxcurv <= dewa->max_linecurv &&
+        diffcurv >= dewa->min_diff_linecurv &&
+        diffcurv <= dewa->max_diff_linecurv) {
+        dew->vvalid = 1;
+    } else {
+        L_INFO("invalid vert model for page %d:\n", procName, dew->pageno);
+#if DEBUG_INVALID_MODELS
+        fprintf(stderr, "  max line curv = %d, max allowed = %d\n",
+                maxcurv, dewa->max_linecurv);
+        fprintf(stderr, "  diff line curv = %d, max allowed = %d\n",
+                diffcurv, dewa->max_diff_linecurv);
+#endif  /* DEBUG_INVALID_MODELS */
+    }
+
+        /* If a horizontal (edge) model exists, test for validity. */
+    if (dew->hsuccess) {
+        diffedge = L_ABS(dew->leftcurv - dew->rightcurv);
+        if (L_ABS(dew->leftslope) <= dewa->max_edgeslope &&
+            L_ABS(dew->rightslope) <= dewa->max_edgeslope &&
+            L_ABS(dew->leftcurv) <= dewa->max_edgecurv &&
+            L_ABS(dew->rightcurv) <= dewa->max_edgecurv &&
+            diffedge <= dewa->max_diff_edgecurv) {
+            dew->hvalid = 1;
+        } else {
+            L_INFO("invalid horiz model for page %d:\n", procName, dew->pageno);
+#if DEBUG_INVALID_MODELS
+            fprintf(stderr, "  left edge slope = %d, max allowed = %d\n",
+                    dew->leftslope, dewa->max_edgeslope);
+            fprintf(stderr, "  right edge slope = %d, max allowed = %d\n",
+                    dew->rightslope, dewa->max_edgeslope);
+            fprintf(stderr, "  left edge curv = %d, max allowed = %d\n",
+                    dew->leftcurv, dewa->max_edgecurv);
+            fprintf(stderr, "  right edge curv = %d, max allowed = %d\n",
+                    dew->rightcurv, dewa->max_edgecurv);
+            fprintf(stderr, "  diff edge curv = %d, max allowed = %d\n",
+                    diffedge, dewa->max_diff_edgecurv);
+#endif  /* DEBUG_INVALID_MODELS */
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  dewarpaShowArrays()
+ *
+ *      Input:  dewa
+ *              scalefact (on contour images; typ. 0.5)
+ *              first (first page model to render)
+ *              last (last page model to render; use 0 to go to end)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Generates a pdf of contour plots of the disparity arrays.
+ *      (2) This only shows actual models; not ref models
+ */
+l_int32
+dewarpaShowArrays(L_DEWARPA   *dewa,
+                  l_float32    scalefact,
+                  l_int32      first,
+                  l_int32      last)
+{
+char       buf[256];
+char      *pathname;
+l_int32    i, svd, shd;
+L_BMF     *bmf;
+L_DEWARP  *dew;
+PIX       *pixv, *pixvs, *pixh, *pixhs, *pixt, *pixd;
+PIXA      *pixa;
+
+    PROCNAME("dewarpaShowArrays");
+
+    if (!dewa)
+        return ERROR_INT("dew not defined", procName, 1);
+    if (first < 0 || first > dewa->maxpage)
+        return ERROR_INT("first out of bounds", procName, 1);
+    if (last <= 0 || last > dewa->maxpage) last = dewa->maxpage;
+    if (last < first)
+        return ERROR_INT("last < first", procName, 1);
+
+    lept_rmdir("lept/dewarp1");  /* temp directory for contour plots */
+    lept_mkdir("lept/dewarp1");
+    if ((bmf = bmfCreate(NULL, 8)) == NULL)
+        L_ERROR("bmf not made; page info not displayed", procName);
+
+    fprintf(stderr, "Generating contour plots\n");
+    for (i = first; i <= last; i++) {
+        if (i && ((i % 10) == 0))
+            fprintf(stderr, " .. %d", i);
+        dew = dewarpaGetDewarp(dewa, i);
+        if (!dew) continue;
+        if (dew->hasref == 1) continue;
+        svd = shd = 0;
+        if (dew->sampvdispar) svd = 1;
+        if (dew->samphdispar) shd = 1;
+        if (!svd) {
+            L_ERROR("sampvdispar not made for page %d!\n", procName, i);
+            continue;
+        }
+
+            /* Generate contour plots at reduced resolution */
+        dewarpPopulateFullRes(dew, NULL, 0, 0);
+        pixv = fpixRenderContours(dew->fullvdispar, 3.0, 0.15);
+        pixvs = pixScaleBySampling(pixv, scalefact, scalefact);
+        pixDestroy(&pixv);
+        if (shd) {
+            pixh = fpixRenderContours(dew->fullhdispar, 3.0, 0.15);
+            pixhs = pixScaleBySampling(pixh, scalefact, scalefact);
+            pixDestroy(&pixh);
+        }
+        dewarpMinimize(dew);
+
+            /* Save side-by-side */
+        pixa = pixaCreate(2);
+        pixaAddPix(pixa, pixvs, L_INSERT);
+        if (shd)
+            pixaAddPix(pixa, pixhs, L_INSERT);
+        pixt = pixaDisplayTiledInRows(pixa, 32, 1500, 1.0, 0, 30, 2);
+        snprintf(buf, sizeof(buf), "Page %d", i);
+        pixd = pixAddSingleTextblock(pixt, bmf, buf, 0x0000ff00,
+                                     L_ADD_BELOW, NULL);
+        snprintf(buf, sizeof(buf), "arrays_%04d.png", i);
+        pathname = genPathname("/tmp/lept/dewarp1", buf);
+        pixWrite(pathname, pixd, IFF_PNG);
+        pixaDestroy(&pixa);
+        pixDestroy(&pixt);
+        pixDestroy(&pixd);
+        LEPT_FREE(pathname);
+    }
+    bmfDestroy(&bmf);
+    fprintf(stderr, "\n");
+
+    fprintf(stderr, "Generating pdf of contour plots\n");
+    convertFilesToPdf("/tmp/lept/dewarp1", "arrays_", 90, 1.0, L_FLATE_ENCODE,
+                      0, "Disparity arrays", "/tmp/lept/disparity_arrays.pdf");
+    fprintf(stderr, "Output written to: /tmp/lept/disparity_arrays.pdf\n");
+    return 0;
+}
+
+
+/*!
+ *  dewarpDebug()
+ *
+ *      Input:  dew
+ *              subdirs (one or more subdirectories of /tmp; e.g., "dew1")
+ *              index (to help label output images; e.g., the page number)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Prints dewarp fields and generates disparity array contour images.
+ *          The contour images are written to file:
+ *                /tmp/[subdirs]/pixv_[index].png
+ */
+l_int32
+dewarpDebug(L_DEWARP    *dew,
+            const char  *subdirs,
+            l_int32      index)
+{
+char     fname[64];
+char    *outdir, *pathname;
+l_int32  svd, shd;
+PIX     *pixv, *pixh;
+
+    PROCNAME("dewarpDebug");
+
+    if (!dew)
+        return ERROR_INT("dew not defined", procName, 1);
+    if (!subdirs)
+        return ERROR_INT("subdirs not defined", procName, 1);
+
+    fprintf(stderr, "pageno = %d, hasref = %d, refpage = %d\n",
+            dew->pageno, dew->hasref, dew->refpage);
+    fprintf(stderr, "sampling = %d, redfactor = %d, minlines = %d\n",
+            dew->sampling, dew->redfactor, dew->minlines);
+    svd = shd = 0;
+    if (!dew->hasref) {
+        if (dew->sampvdispar) svd = 1;
+        if (dew->samphdispar) shd = 1;
+        fprintf(stderr, "sampv = %d, samph = %d\n", svd, shd);
+        fprintf(stderr, "w = %d, h = %d\n", dew->w, dew->h);
+        fprintf(stderr, "nx = %d, ny = %d\n", dew->nx, dew->ny);
+        fprintf(stderr, "nlines = %d\n", dew->nlines);
+        if (svd) {
+            fprintf(stderr, "(min,max,abs-diff) line curvature = (%d,%d,%d)\n",
+                    dew->mincurv, dew->maxcurv, dew->maxcurv - dew->mincurv);
+        }
+        if (shd) {
+            fprintf(stderr, "(left edge slope = %d, right edge slope = %d\n",
+                    dew->leftslope, dew->rightslope);
+            fprintf(stderr, "(left,right,abs-diff) edge curvature = "
+                    "(%d,%d,%d)\n", dew->leftcurv, dew->rightcurv,
+                    L_ABS(dew->leftcurv - dew->rightcurv));
+        }
+    }
+    if (!svd && !shd) {
+        fprintf(stderr, "No disparity arrays\n");
+        return 0;
+    }
+
+    dewarpPopulateFullRes(dew, NULL, 0, 0);
+    lept_mkdir(subdirs);
+    outdir = pathJoin("/tmp", subdirs);
+    if (svd) {
+        pixv = fpixRenderContours(dew->fullvdispar, 3.0, 0.15);
+        snprintf(fname, sizeof(fname), "pixv_%d.png", index);
+        pathname = genPathname(outdir, fname);
+        pixWrite(pathname, pixv, IFF_PNG);
+        pixDestroy(&pixv);
+        LEPT_FREE(pathname);
+    }
+    if (shd) {
+        pixh = fpixRenderContours(dew->fullhdispar, 3.0, 0.15);
+        snprintf(fname, sizeof(fname), "pixh_%d.png", index);
+        pathname = genPathname(outdir, fname);
+        pixWrite(pathname, pixh, IFF_PNG);
+        pixDestroy(&pixh);
+        LEPT_FREE(pathname);
+    }
+    LEPT_FREE(outdir);
+    return 0;
+}
+
+
+/*!
+ *  dewarpShowResults()
+ *
+ *      Input:  dewa
+ *              sarray (of indexed input images)
+ *              boxa (crop boxes for input images; can be null)
+ *              firstpage, lastpage
+ *              pdfout (filename)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This generates a pdf of image pairs (before, after) for
+ *          the designated set of input pages.
+ *      (2) If the boxa exists, its elements are aligned with numbers
+ *          in the filenames in @sa.  It is used to crop the input images.
+ *          It is assumed that the dewa was generated from the cropped
+ *          images.  No undercropping is applied before rendering.
+ */
+l_int32
+dewarpShowResults(L_DEWARPA   *dewa,
+                  SARRAY      *sa,
+                  BOXA        *boxa,
+                  l_int32      firstpage,
+                  l_int32      lastpage,
+                  const char  *pdfout)
+{
+char       bufstr[256];
+char      *outpath;
+l_int32    i, modelpage;
+L_BMF     *bmf;
+BOX       *box;
+L_DEWARP  *dew;
+PIX       *pixs, *pixc, *pixd, *pixt1, *pixt2;
+PIXA      *pixa;
+
+    PROCNAME("dewarpShowResults");
+
+    if (!dewa)
+        return ERROR_INT("dewa not defined", procName, 1);
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!pdfout)
+        return ERROR_INT("pdfout not defined", procName, 1);
+    if (firstpage > lastpage)
+        return ERROR_INT("invalid first/last page numbers", procName, 1);
+
+    lept_rmdir("lept/dewarp_pdfout");
+    lept_mkdir("lept/dewarp_pdfout");
+    bmf = bmfCreate(NULL, 6);
+
+    fprintf(stderr, "Dewarping and generating s/by/s view\n");
+    for (i = firstpage; i <= lastpage; i++) {
+        if (i && (i % 10 == 0)) fprintf(stderr, ".. %d ", i);
+        pixs = pixReadIndexed(sa, i);
+        if (boxa) {
+            box = boxaGetBox(boxa, i, L_CLONE);
+            pixc = pixClipRectangle(pixs, box, NULL);
+            boxDestroy(&box);
+        }
+        else
+            pixc = pixClone(pixs);
+        dew = dewarpaGetDewarp(dewa, i);
+        pixd = NULL;
+        if (dew) {
+            dewarpaApplyDisparity(dewa, dew->pageno, pixc,
+                                        GRAYIN_VALUE, 0, 0, &pixd, NULL);
+            dewarpMinimize(dew);
+        }
+        pixa = pixaCreate(2);
+        pixaAddPix(pixa, pixc, L_INSERT);
+        if (pixd)
+            pixaAddPix(pixa, pixd, L_INSERT);
+        pixt1 = pixaDisplayTiledAndScaled(pixa, 32, 500, 2, 0, 35, 2);
+        if (dew) {
+            modelpage = (dew->hasref) ? dew->refpage : dew->pageno;
+            snprintf(bufstr, sizeof(bufstr), "Page %d; using %d\n",
+                     i, modelpage);
+        }
+        else
+            snprintf(bufstr, sizeof(bufstr), "Page %d; no dewarp\n", i);
+        pixt2 = pixAddSingleTextblock(pixt1, bmf, bufstr, 0x0000ff00,
+                                      L_ADD_BELOW, 0);
+        snprintf(bufstr, sizeof(bufstr), "/tmp/lept/dewarp_pdfout/%05d", i);
+        pixWrite(bufstr, pixt2, IFF_JFIF_JPEG);
+        pixaDestroy(&pixa);
+        pixDestroy(&pixs);
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt2);
+    }
+    fprintf(stderr, "\n");
+
+    fprintf(stderr, "Generating pdf of result\n");
+    convertFilesToPdf("/tmp/lept/dewarp_pdfout", NULL, 100, 1.0, L_JPEG_ENCODE,
+                      0, "Dewarp sequence", pdfout);
+    outpath = genPathname(pdfout, NULL);
+    fprintf(stderr, "Output written to: %s\n", outpath);
+    LEPT_FREE(outpath);
+    bmfDestroy(&bmf);
+    return 0;
+}
+
diff --git a/src/dnabasic.c b/src/dnabasic.c
new file mode 100644 (file)
index 0000000..78f85b7
--- /dev/null
@@ -0,0 +1,2267 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   dnabasic.c
+ *
+ *      Dna creation, destruction, copy, clone, etc.
+ *          L_DNA       *l_dnaCreate()
+ *          L_DNA       *l_dnaCreateFromIArray()
+ *          L_DNA       *l_dnaCreateFromDArray()
+ *          L_DNA       *l_dnaMakeSequence()
+ *          void        *l_dnaDestroy()
+ *          L_DNA       *l_dnaCopy()
+ *          L_DNA       *l_dnaClone()
+ *          l_int32      l_dnaEmpty()
+ *
+ *      Dna: add/remove number and extend array
+ *          l_int32      l_dnaAddNumber()
+ *          static l_int32  l_dnaExtendArray()
+ *          l_int32      l_dnaInsertNumber()
+ *          l_int32      l_dnaRemoveNumber()
+ *          l_int32      l_dnaReplaceNumber()
+ *
+ *      Dna accessors
+ *          l_int32      l_dnaGetCount()
+ *          l_int32      l_dnaSetCount()
+ *          l_int32      l_dnaGetIValue()
+ *          l_int32      l_dnaGetDValue()
+ *          l_int32      l_dnaSetValue()
+ *          l_int32      l_dnaShiftValue()
+ *          l_int32     *l_dnaGetIArray()
+ *          l_float64   *l_dnaGetDArray()
+ *          l_int32      l_dnaGetRefcount()
+ *          l_int32      l_dnaChangeRefcount()
+ *          l_int32      l_dnaGetParameters()
+ *          l_int32      l_dnaSetParameters()
+ *          l_int32      l_dnaCopyParameters()
+ *
+ *      Serialize Dna for I/O
+ *          L_DNA       *l_dnaRead()
+ *          L_DNA       *l_dnaReadStream()
+ *          l_int32      l_dnaWrite()
+ *          l_int32      l_dnaWriteStream()
+ *
+ *      Dnaa creation, destruction
+ *          L_DNAA      *l_dnaaCreate()
+ *          L_DNAA      *l_dnaaCreateFull()
+ *          l_int32      l_dnaaTruncate()
+ *          void        *l_dnaaDestroy()
+ *
+ *      Add Dna to Dnaa
+ *          l_int32      l_dnaaAddDna()
+ *          static l_int32  l_dnaaExtendArray()
+ *
+ *      Dnaa accessors
+ *          l_int32      l_dnaaGetCount()
+ *          l_int32      l_dnaaGetDnaCount()
+ *          l_int32      l_dnaaGetNumberCount()
+ *          L_DNA       *l_dnaaGetDna()
+ *          L_DNA       *l_dnaaReplaceDna()
+ *          l_int32      l_dnaaGetValue()
+ *          l_int32      l_dnaaAddNumber()
+ *
+ *      Serialize Dnaa for I/O
+ *          L_DNAA      *l_dnaaRead()
+ *          L_DNAA      *l_dnaaReadStream()
+ *          l_int32      l_dnaaWrite()
+ *          l_int32      l_dnaaWriteStream()
+ *
+ *      DnaHash creation, destruction
+ *          L_DNAHASH   *l_dnaHashCreate()
+ *          void        *l_dnaHashDestroy()
+ *
+ *      DnaHash: Accessors and modifiers                      *
+ *          l_int32     *l_dnaHashGetCount()
+ *          l_int32     *l_dnaHashGetTotalCount()
+ *          L_DNA       *l_dnaHashGetDna()
+ *          void        *l_dnaHashAdd()
+ *
+ *      DnaHash: Operations on Dna
+ *          L_DNAHASH   *l_dnaHashCreateFromDna()
+ *          l_int32      l_dnaRemoveDupsByHash()
+ *          l_int32      l_dnaMakeHistoByHash()
+ *          L_DNA       *l_dnaIntersectionByHash()
+ *          l_int32      l_dnaFindValByHash()
+ *
+ *      Other Dna functions
+ *          L_DNA       *l_dnaMakeDelta()
+ *          NUMA        *l_dnaConvertToNuma()
+ *          L_DNA       *numaConvertToDna()
+ *          l_int32     *l_dnaJoin()
+ *
+ *    (1) The Dna is a struct holding an array of doubles.  It can also
+ *        be used to store l_int32 values, up to the full precision
+ *        of int32.  Always use it whenever integers larger than a
+ *        few million need to be stored.
+ *
+ *    (2) Always use the accessors in this file, never the fields directly.
+ *
+ *    (3) Storing and retrieving numbers:
+ *
+ *       * to append a new number to the array, use l_dnaAddNumber().  If
+ *         the number is an int, it will will automatically be converted
+ *         to l_float64 and stored.
+ *
+ *       * to reset a value stored in the array, use l_dnaSetValue().
+ *
+ *       * to increment or decrement a value stored in the array,
+ *         use l_dnaShiftValue().
+ *
+ *       * to obtain a value from the array, use either l_dnaGetIValue()
+ *         or l_dnaGetDValue(), depending on whether you are retrieving
+ *         an integer or a float64.  This avoids doing an explicit cast,
+ *         such as
+ *           (a) return a l_float64 and cast it to an l_int32
+ *           (b) cast the return directly to (l_float64 *) to
+ *               satisfy the function prototype, as in
+ *                 l_dnaGetDValue(da, index, (l_float64 *)&ival);   [ugly!]
+ *
+ *    (4) int <--> double conversions:
+ *
+ *        Conversions go automatically from l_int32 --> l_float64,
+ *        without loss of precision.  You must cast (l_int32)
+ *        to go from l_float64 --> l_int32 because you're truncating
+ *        to the integer value.
+ *
+ *    (5) As with other arrays in leptonica, the l_dna has both an allocated
+ *        size and a count of the stored numbers.  When you add a number, it
+ *        goes on the end of the array, and causes a realloc if the array
+ *        is already filled.  However, in situations where you want to
+ *        add numbers randomly into an array, such as when you build a
+ *        histogram, you must set the count of stored numbers in advance.
+ *        This is done with l_dnaSetCount().  If you set a count larger
+ *        than the allocated array, it does a realloc to the size requested.
+ *
+ *    (6) In situations where the data in a l_dna correspond to a function
+ *        y(x), the values can be either at equal spacings in x or at
+ *        arbitrary spacings.  For the former, we can represent all x values
+ *        by two parameters: startx (corresponding to y[0]) and delx
+ *        for the change in x for adjacent values y[i] and y[i+1].
+ *        startx and delx are initialized to 0.0 and 1.0, rsp.
+ *        For arbitrary spacings, we use a second l_dna, and the two
+ *        l_dnas are typically denoted dnay and dnax.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+static const l_int32 INITIAL_PTR_ARRAYSIZE = 50;      /* n'importe quoi */
+
+    /* Static functions */
+static l_int32 l_dnaExtendArray(L_DNA *da);
+static l_int32 l_dnaaExtendArray(L_DNAA *daa);
+
+
+/*--------------------------------------------------------------------------*
+ *                 Dna creation, destruction, copy, clone, etc.             *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  l_dnaCreate()
+ *
+ *      Input:  size of number array to be alloc'd (0 for default)
+ *      Return: da, or null on error
+ */
+L_DNA *
+l_dnaCreate(l_int32  n)
+{
+L_DNA  *da;
+
+    PROCNAME("l_dnaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((da = (L_DNA *)LEPT_CALLOC(1, sizeof(L_DNA))) == NULL)
+        return (L_DNA *)ERROR_PTR("da not made", procName, NULL);
+    if ((da->array = (l_float64 *)LEPT_CALLOC(n, sizeof(l_float64))) == NULL)
+        return (L_DNA *)ERROR_PTR("double array not made", procName, NULL);
+
+    da->nalloc = n;
+    da->n = 0;
+    da->refcount = 1;
+    da->startx = 0.0;
+    da->delx = 1.0;
+
+    return da;
+}
+
+
+/*!
+ *  l_dnaCreateFromIArray()
+ *
+ *      Input:  iarray (integer)
+ *              size (of the array)
+ *      Return: da, or null on error
+ *
+ *  Notes:
+ *      (1) We can't insert this int array into the l_dna, because a l_dna
+ *          takes a double array.  So this just copies the data from the
+ *          input array into the l_dna.  The input array continues to be
+ *          owned by the caller.
+ */
+L_DNA *
+l_dnaCreateFromIArray(l_int32  *iarray,
+                      l_int32   size)
+{
+l_int32  i;
+L_DNA   *da;
+
+    PROCNAME("l_dnaCreateFromIArray");
+
+    if (!iarray)
+        return (L_DNA *)ERROR_PTR("iarray not defined", procName, NULL);
+    if (size <= 0)
+        return (L_DNA *)ERROR_PTR("size must be > 0", procName, NULL);
+
+    da = l_dnaCreate(size);
+    for (i = 0; i < size; i++)
+        l_dnaAddNumber(da, iarray[i]);
+
+    return da;
+}
+
+
+/*!
+ *  l_dnaCreateFromDArray()
+ *
+ *      Input:  da (float)
+ *              size (of the array)
+ *              copyflag (L_INSERT or L_COPY)
+ *      Return: da, or null on error
+ *
+ *  Notes:
+ *      (1) With L_INSERT, ownership of the input array is transferred
+ *          to the returned l_dna, and all @size elements are considered
+ *          to be valid.
+ */
+L_DNA *
+l_dnaCreateFromDArray(l_float64  *darray,
+                      l_int32     size,
+                      l_int32     copyflag)
+{
+l_int32  i;
+L_DNA   *da;
+
+    PROCNAME("l_dnaCreateFromDArray");
+
+    if (!darray)
+        return (L_DNA *)ERROR_PTR("darray not defined", procName, NULL);
+    if (size <= 0)
+        return (L_DNA *)ERROR_PTR("size must be > 0", procName, NULL);
+    if (copyflag != L_INSERT && copyflag != L_COPY)
+        return (L_DNA *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    da = l_dnaCreate(size);
+    if (copyflag == L_INSERT) {
+        if (da->array) LEPT_FREE(da->array);
+        da->array = darray;
+        da->n = size;
+    } else {  /* just copy the contents */
+        for (i = 0; i < size; i++)
+            l_dnaAddNumber(da, darray[i]);
+    }
+
+    return da;
+}
+
+
+/*!
+ *  l_dnaMakeSequence()
+ *
+ *      Input:  startval
+ *              increment
+ *              size (of sequence)
+ *      Return: l_dna of sequence of evenly spaced values, or null on error
+ */
+L_DNA *
+l_dnaMakeSequence(l_float64  startval,
+                  l_float64  increment,
+                  l_int32    size)
+{
+l_int32    i;
+l_float64  val;
+L_DNA     *da;
+
+    PROCNAME("l_dnaMakeSequence");
+
+    if ((da = l_dnaCreate(size)) == NULL)
+        return (L_DNA *)ERROR_PTR("da not made", procName, NULL);
+
+    for (i = 0; i < size; i++) {
+        val = startval + i * increment;
+        l_dnaAddNumber(da, val);
+    }
+
+    return da;
+}
+
+
+/*!
+ *  l_dnaDestroy()
+ *
+ *      Input:  &da (<to be nulled if it exists>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Decrements the ref count and, if 0, destroys the l_dna.
+ *      (2) Always nulls the input ptr.
+ */
+void
+l_dnaDestroy(L_DNA  **pda)
+{
+L_DNA  *da;
+
+    PROCNAME("l_dnaDestroy");
+
+    if (pda == NULL) {
+        L_WARNING("ptr address is NULL\n", procName);
+        return;
+    }
+
+    if ((da = *pda) == NULL)
+        return;
+
+        /* Decrement the ref count.  If it is 0, destroy the l_dna. */
+    l_dnaChangeRefcount(da, -1);
+    if (l_dnaGetRefcount(da) <= 0) {
+        if (da->array)
+            LEPT_FREE(da->array);
+        LEPT_FREE(da);
+    }
+
+    *pda = NULL;
+    return;
+}
+
+
+/*!
+ *  l_dnaCopy()
+ *
+ *      Input:  da
+ *      Return: copy of l_dna, or null on error
+ *
+ *  Notes:
+ *      (1) This removes unused ptrs above da->n.
+ */
+L_DNA *
+l_dnaCopy(L_DNA  *da)
+{
+l_int32  i;
+L_DNA   *dac;
+
+    PROCNAME("l_dnaCopy");
+
+    if (!da)
+        return (L_DNA *)ERROR_PTR("da not defined", procName, NULL);
+
+    if ((dac = l_dnaCreate(da->n)) == NULL)
+        return (L_DNA *)ERROR_PTR("dac not made", procName, NULL);
+    dac->startx = da->startx;
+    dac->delx = da->delx;
+
+    for (i = 0; i < da->n; i++)
+        l_dnaAddNumber(dac, da->array[i]);
+
+    return dac;
+}
+
+
+/*!
+ *  l_dnaClone()
+ *
+ *      Input:  da
+ *      Return: ptr to same l_dna, or null on error
+ */
+L_DNA *
+l_dnaClone(L_DNA  *da)
+{
+    PROCNAME("l_dnaClone");
+
+    if (!da)
+        return (L_DNA *)ERROR_PTR("da not defined", procName, NULL);
+
+    l_dnaChangeRefcount(da, 1);
+    return da;
+}
+
+
+/*!
+ *  l_dnaEmpty()
+ *
+ *      Input:  da
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This does not change the allocation of the array.
+ *          It just clears the number of stored numbers, so that
+ *          the array appears to be empty.
+ */
+l_int32
+l_dnaEmpty(L_DNA  *da)
+{
+    PROCNAME("l_dnaEmpty");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+
+    da->n = 0;
+    return 0;
+}
+
+
+
+/*--------------------------------------------------------------------------*
+ *                  Dna: add/remove number and extend array                 *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  l_dnaAddNumber()
+ *
+ *      Input:  da
+ *              val  (float or int to be added; stored as a float)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_dnaAddNumber(L_DNA     *da,
+               l_float64  val)
+{
+l_int32  n;
+
+    PROCNAME("l_dnaAddNumber");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+
+    n = l_dnaGetCount(da);
+    if (n >= da->nalloc)
+        l_dnaExtendArray(da);
+    da->array[n] = val;
+    da->n++;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaExtendArray()
+ *
+ *      Input:  da
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+l_dnaExtendArray(L_DNA  *da)
+{
+    PROCNAME("l_dnaExtendArray");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+
+    if ((da->array = (l_float64 *)reallocNew((void **)&da->array,
+                                sizeof(l_float64) * da->nalloc,
+                                2 * sizeof(l_float64) * da->nalloc)) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+
+    da->nalloc *= 2;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaInsertNumber()
+ *
+ *      Input:  da
+ *              index (location in da to insert new value)
+ *              val  (float64 or integer to be added)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This shifts da[i] --> da[i + 1] for all i >= index,
+ *          and then inserts val as da[index].
+ *      (2) It should not be used repeatedly on large arrays,
+ *          because the function is O(n).
+ *
+ */
+l_int32
+l_dnaInsertNumber(L_DNA      *da,
+                  l_int32    index,
+                  l_float64  val)
+{
+l_int32  i, n;
+
+    PROCNAME("l_dnaInsertNumber");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+    n = l_dnaGetCount(da);
+    if (index < 0 || index > n)
+        return ERROR_INT("index not in {0...n}", procName, 1);
+
+    if (n >= da->nalloc)
+        l_dnaExtendArray(da);
+    for (i = n; i > index; i--)
+        da->array[i] = da->array[i - 1];
+    da->array[index] = val;
+    da->n++;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaRemoveNumber()
+ *
+ *      Input:  da
+ *              index (element to be removed)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This shifts da[i] --> da[i - 1] for all i > index.
+ *      (2) It should not be used repeatedly on large arrays,
+ *          because the function is O(n).
+ */
+l_int32
+l_dnaRemoveNumber(L_DNA   *da,
+                  l_int32  index)
+{
+l_int32  i, n;
+
+    PROCNAME("l_dnaRemoveNumber");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+    n = l_dnaGetCount(da);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not in {0...n - 1}", procName, 1);
+
+    for (i = index + 1; i < n; i++)
+        da->array[i - 1] = da->array[i];
+    da->n--;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaReplaceNumber()
+ *
+ *      Input:  da
+ *              index (element to be replaced)
+ *              val (new value to replace old one)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_dnaReplaceNumber(L_DNA     *da,
+                   l_int32    index,
+                   l_float64  val)
+{
+l_int32  n;
+
+    PROCNAME("l_dnaReplaceNumber");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+    n = l_dnaGetCount(da);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not in {0...n - 1}", procName, 1);
+
+    da->array[index] = val;
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                             Dna accessors                            *
+ *----------------------------------------------------------------------*/
+/*!
+ *  l_dnaGetCount()
+ *
+ *      Input:  da
+ *      Return: count, or 0 if no numbers or on error
+ */
+l_int32
+l_dnaGetCount(L_DNA  *da)
+{
+    PROCNAME("l_dnaGetCount");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, 0);
+    return da->n;
+}
+
+
+/*!
+ *  l_dnaSetCount()
+ *
+ *      Input:  da
+ *              newcount
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If newcount <= da->nalloc, this resets da->n.
+ *          Using newcount = 0 is equivalent to l_dnaEmpty().
+ *      (2) If newcount > da->nalloc, this causes a realloc
+ *          to a size da->nalloc = newcount.
+ *      (3) All the previously unused values in da are set to 0.0.
+ */
+l_int32
+l_dnaSetCount(L_DNA   *da,
+              l_int32  newcount)
+{
+    PROCNAME("l_dnaSetCount");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+    if (newcount > da->nalloc) {
+        if ((da->array = (l_float64 *)reallocNew((void **)&da->array,
+                         sizeof(l_float64) * da->nalloc,
+                         sizeof(l_float64) * newcount)) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+        da->nalloc = newcount;
+    }
+    da->n = newcount;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaGetDValue()
+ *
+ *      Input:  da
+ *              index (into l_dna)
+ *              &val  (<return> double value; 0.0 on error)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Caller may need to check the function return value to
+ *          decide if a 0.0 in the returned ival is valid.
+ */
+l_int32
+l_dnaGetDValue(L_DNA      *da,
+               l_int32     index,
+               l_float64  *pval)
+{
+    PROCNAME("l_dnaGetDValue");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0.0;
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+
+    if (index < 0 || index >= da->n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    *pval = da->array[index];
+    return 0;
+}
+
+
+/*!
+ *  l_dnaGetIValue()
+ *
+ *      Input:  da
+ *              index (into l_dna)
+ *              &ival  (<return> integer value; 0 on error)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Caller may need to check the function return value to
+ *          decide if a 0 in the returned ival is valid.
+ */
+l_int32
+l_dnaGetIValue(L_DNA    *da,
+               l_int32   index,
+               l_int32  *pival)
+{
+l_float64  val;
+
+    PROCNAME("l_dnaGetIValue");
+
+    if (!pival)
+        return ERROR_INT("&ival not defined", procName, 1);
+    *pival = 0;
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+
+    if (index < 0 || index >= da->n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    val = da->array[index];
+    *pival = (l_int32)(val + L_SIGN(val) * 0.5);
+    return 0;
+}
+
+
+/*!
+ *  l_dnaSetValue()
+ *
+ *      Input:  da
+ *              index  (to element to be set)
+ *              val  (to set element)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+l_dnaSetValue(L_DNA     *da,
+              l_int32    index,
+              l_float64  val)
+{
+    PROCNAME("l_dnaSetValue");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+    if (index < 0 || index >= da->n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    da->array[index] = val;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaShiftValue()
+ *
+ *      Input:  da
+ *              index (to element to change relative to the current value)
+ *              diff  (increment if diff > 0 or decrement if diff < 0)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+l_dnaShiftValue(L_DNA     *da,
+                l_int32    index,
+                l_float64  diff)
+{
+    PROCNAME("l_dnaShiftValue");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+    if (index < 0 || index >= da->n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    da->array[index] += diff;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaGetIArray()
+ *
+ *      Input:  da
+ *      Return: a copy of the bare internal array, integerized
+ *              by rounding, or null on error
+ *  Notes:
+ *      (1) A copy of the array is made, because we need to
+ *          generate an integer array from the bare double array.
+ *          The caller is responsible for freeing the array.
+ *      (2) The array size is determined by the number of stored numbers,
+ *          not by the size of the allocated array in the l_dna.
+ *      (3) This function is provided to simplify calculations
+ *          using the bare internal array, rather than continually
+ *          calling accessors on the l_dna.  It is typically used
+ *          on an array of size 256.
+ */
+l_int32 *
+l_dnaGetIArray(L_DNA  *da)
+{
+l_int32   i, n, ival;
+l_int32  *array;
+
+    PROCNAME("l_dnaGetIArray");
+
+    if (!da)
+        return (l_int32 *)ERROR_PTR("da not defined", procName, NULL);
+
+    n = l_dnaGetCount(da);
+    if ((array = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32))) == NULL)
+        return (l_int32 *)ERROR_PTR("array not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        l_dnaGetIValue(da, i, &ival);
+        array[i] = ival;
+    }
+
+    return array;
+}
+
+
+/*!
+ *  l_dnaGetDArray()
+ *
+ *      Input:  da
+ *              copyflag (L_NOCOPY or L_COPY)
+ *      Return: either the bare internal array or a copy of it,
+ *              or null on error
+ *
+ *  Notes:
+ *      (1) If copyflag == L_COPY, it makes a copy which the caller
+ *          is responsible for freeing.  Otherwise, it operates
+ *          directly on the bare array of the l_dna.
+ *      (2) Very important: for L_NOCOPY, any writes to the array
+ *          will be in the l_dna.  Do not write beyond the size of
+ *          the count field, because it will not be accessible
+ *          from the l_dna!  If necessary, be sure to set the count
+ *          field to a larger number (such as the alloc size)
+ *          BEFORE calling this function.  Creating with l_dnaMakeConstant()
+ *          is another way to insure full initialization.
+ */
+l_float64 *
+l_dnaGetDArray(L_DNA   *da,
+               l_int32  copyflag)
+{
+l_int32     i, n;
+l_float64  *array;
+
+    PROCNAME("l_dnaGetDArray");
+
+    if (!da)
+        return (l_float64 *)ERROR_PTR("da not defined", procName, NULL);
+
+    if (copyflag == L_NOCOPY) {
+        array = da->array;
+    } else {  /* copyflag == L_COPY */
+        n = l_dnaGetCount(da);
+        if ((array = (l_float64 *)LEPT_CALLOC(n, sizeof(l_float64))) == NULL)
+            return (l_float64 *)ERROR_PTR("array not made", procName, NULL);
+        for (i = 0; i < n; i++)
+            array[i] = da->array[i];
+    }
+
+    return array;
+}
+
+
+/*!
+ *  l_dnaGetRefCount()
+ *
+ *      Input:  da
+ *      Return: refcount, or UNDEF on error
+ */
+l_int32
+l_dnaGetRefcount(L_DNA  *da)
+{
+    PROCNAME("l_dnaGetRefcount");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, UNDEF);
+    return da->refcount;
+}
+
+
+/*!
+ *  l_dnaChangeRefCount()
+ *
+ *      Input:  da
+ *              delta (change to be applied)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_dnaChangeRefcount(L_DNA   *da,
+                    l_int32  delta)
+{
+    PROCNAME("l_dnaChangeRefcount");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+    da->refcount += delta;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaGetParameters()
+ *
+ *      Input:  da
+ *              &startx (<optional return> startx)
+ *              &delx (<optional return> delx)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_dnaGetParameters(L_DNA     *da,
+                   l_float64  *pstartx,
+                   l_float64  *pdelx)
+{
+    PROCNAME("l_dnaGetParameters");
+
+    if (pstartx) *pstartx = 0.0;
+    if (pdelx) *pdelx = 1.0;
+    if (!pstartx && !pdelx)
+        return ERROR_INT("neither &startx nor &delx are defined", procName, 1);
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+
+    if (pstartx) *pstartx = da->startx;
+    if (pdelx) *pdelx = da->delx;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaSetParameters()
+ *
+ *      Input:  da
+ *              startx (x value corresponding to da[0])
+ *              delx (difference in x values for the situation where the
+ *                    elements of da correspond to the evaulation of a
+ *                    function at equal intervals of size @delx)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_dnaSetParameters(L_DNA     *da,
+                   l_float64  startx,
+                   l_float64  delx)
+{
+    PROCNAME("l_dnaSetParameters");
+
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+
+    da->startx = startx;
+    da->delx = delx;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaCopyParameters()
+ *
+ *      Input:  dad (destination DNuma)
+ *              das (source DNuma)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_dnaCopyParameters(L_DNA  *dad,
+                    L_DNA  *das)
+{
+l_float64  start, binsize;
+
+    PROCNAME("l_dnaCopyParameters");
+
+    if (!das || !dad)
+        return ERROR_INT("das and dad not both defined", procName, 1);
+
+    l_dnaGetParameters(das, &start, &binsize);
+    l_dnaSetParameters(dad, start, binsize);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                        Serialize Dna for I/O                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  l_dnaRead()
+ *
+ *      Input:  filename
+ *      Return: da, or null on error
+ */
+L_DNA *
+l_dnaRead(const char  *filename)
+{
+FILE   *fp;
+L_DNA  *da;
+
+    PROCNAME("l_dnaRead");
+
+    if (!filename)
+        return (L_DNA *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (L_DNA *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((da = l_dnaReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (L_DNA *)ERROR_PTR("da not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return da;
+}
+
+
+/*!
+ *  l_dnaReadStream()
+ *
+ *      Input:  stream
+ *      Return: da, or null on error
+ *
+ *  Notes:
+ *      (1) fscanf takes %lf to read a double; fprintf takes %f to write it.
+ */
+L_DNA *
+l_dnaReadStream(FILE  *fp)
+{
+l_int32    i, n, index, ret, version;
+l_float64  val, startx, delx;
+L_DNA     *da;
+
+    PROCNAME("l_dnaReadStream");
+
+    if (!fp)
+        return (L_DNA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    ret = fscanf(fp, "\nL_Dna Version %d\n", &version);
+    if (ret != 1)
+        return (L_DNA *)ERROR_PTR("not a l_dna file", procName, NULL);
+    if (version != DNA_VERSION_NUMBER)
+        return (L_DNA *)ERROR_PTR("invalid l_dna version", procName, NULL);
+    if (fscanf(fp, "Number of numbers = %d\n", &n) != 1)
+        return (L_DNA *)ERROR_PTR("invalid number of numbers", procName, NULL);
+
+    if ((da = l_dnaCreate(n)) == NULL)
+        return (L_DNA *)ERROR_PTR("da not made", procName, NULL);
+
+    for (i = 0; i < n; i++) {
+        if (fscanf(fp, "  [%d] = %lf\n", &index, &val) != 2)
+            return (L_DNA *)ERROR_PTR("bad input data", procName, NULL);
+        l_dnaAddNumber(da, val);
+    }
+
+        /* Optional data */
+    if (fscanf(fp, "startx = %lf, delx = %lf\n", &startx, &delx) == 2)
+        l_dnaSetParameters(da, startx, delx);
+
+    return da;
+}
+
+
+/*!
+ *  l_dnaWrite()
+ *
+ *      Input:  filename, da
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_dnaWrite(const char  *filename,
+           L_DNA       *da)
+{
+FILE  *fp;
+
+    PROCNAME("l_dnaWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "w")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (l_dnaWriteStream(fp, da))
+        return ERROR_INT("da not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  l_dnaWriteStream()
+ *
+ *      Input:  stream, da
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_dnaWriteStream(FILE   *fp,
+                 L_DNA  *da)
+{
+l_int32    i, n;
+l_float64  startx, delx;
+
+    PROCNAME("l_dnaWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+
+    n = l_dnaGetCount(da);
+    fprintf(fp, "\nL_Dna Version %d\n", DNA_VERSION_NUMBER);
+    fprintf(fp, "Number of numbers = %d\n", n);
+    for (i = 0; i < n; i++)
+        fprintf(fp, "  [%d] = %f\n", i, da->array[i]);
+    fprintf(fp, "\n");
+
+        /* Optional data */
+    l_dnaGetParameters(da, &startx, &delx);
+    if (startx != 0.0 || delx != 1.0)
+        fprintf(fp, "startx = %f, delx = %f\n", startx, delx);
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                       Dnaa creation, destruction                         *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  l_dnaaCreate()
+ *
+ *      Input:  size of l_dna ptr array to be alloc'd (0 for default)
+ *      Return: daa, or null on error
+ *
+ */
+L_DNAA *
+l_dnaaCreate(l_int32  n)
+{
+L_DNAA  *daa;
+
+    PROCNAME("l_dnaaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((daa = (L_DNAA *)LEPT_CALLOC(1, sizeof(L_DNAA))) == NULL)
+        return (L_DNAA *)ERROR_PTR("daa not made", procName, NULL);
+    if ((daa->dna = (L_DNA **)LEPT_CALLOC(n, sizeof(L_DNA *))) == NULL)
+        return (L_DNAA *)ERROR_PTR("l_dna ptr array not made", procName, NULL);
+
+    daa->nalloc = n;
+    daa->n = 0;
+
+    return daa;
+}
+
+
+/*!
+ *  l_dnaaCreateFull()
+ *
+ *      Input:  nptr: size of dna ptr array to be alloc'd
+ *              n: size of individual dna arrays to be alloc'd (0 for default)
+ *      Return: daa, or null on error
+ *
+ *  Notes:
+ *      (1) This allocates a dnaa and fills the array with allocated dnas.
+ *          In use, after calling this function, use
+ *              l_dnaaAddNumber(dnaa, index, val);
+ *          to add val to the index-th dna in dnaa.
+ */
+L_DNAA *
+l_dnaaCreateFull(l_int32  nptr,
+                 l_int32  n)
+{
+l_int32  i;
+L_DNAA  *daa;
+L_DNA   *da;
+
+    daa = l_dnaaCreate(nptr);
+    for (i = 0; i < nptr; i++) {
+        da = l_dnaCreate(n);
+        l_dnaaAddDna(daa, da, L_INSERT);
+    }
+
+    return daa;
+}
+
+
+/*!
+ *  l_dnaaTruncate()
+ *
+ *      Input:  daa
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This identifies the largest index containing a dna that
+ *          has any numbers within it, destroys all dna beyond that
+ *          index, and resets the count.
+ */
+l_int32
+l_dnaaTruncate(L_DNAA  *daa)
+{
+l_int32  i, n, nn;
+L_DNA   *da;
+
+    PROCNAME("l_dnaaTruncate");
+
+    if (!daa)
+        return ERROR_INT("daa not defined", procName, 1);
+
+    n = l_dnaaGetCount(daa);
+    for (i = n - 1; i >= 0; i--) {
+        da = l_dnaaGetDna(daa, i, L_CLONE);
+        if (!da)
+            continue;
+        nn = l_dnaGetCount(da);
+        l_dnaDestroy(&da);  /* the clone */
+        if (nn == 0)
+            l_dnaDestroy(&daa->dna[i]);
+        else
+            break;
+    }
+    daa->n = i + 1;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaaDestroy()
+ *
+ *      Input: &dnaa <to be nulled if it exists>
+ *      Return: void
+ */
+void
+l_dnaaDestroy(L_DNAA  **pdaa)
+{
+l_int32  i;
+L_DNAA  *daa;
+
+    PROCNAME("l_dnaaDestroy");
+
+    if (pdaa == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+
+    if ((daa = *pdaa) == NULL)
+        return;
+
+    for (i = 0; i < daa->n; i++)
+        l_dnaDestroy(&daa->dna[i]);
+    LEPT_FREE(daa->dna);
+    LEPT_FREE(daa);
+    *pdaa = NULL;
+
+    return;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                             Add Dna to Dnaa                              *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  l_dnaaAddDna()
+ *
+ *      Input:  daa
+ *              da   (to be added)
+ *              copyflag  (L_INSERT, L_COPY, L_CLONE)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_dnaaAddDna(L_DNAA  *daa,
+             L_DNA   *da,
+             l_int32  copyflag)
+{
+l_int32  n;
+L_DNA   *dac;
+
+    PROCNAME("l_dnaaAddDna");
+
+    if (!daa)
+        return ERROR_INT("daa not defined", procName, 1);
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+
+    if (copyflag == L_INSERT) {
+        dac = da;
+    } else if (copyflag == L_COPY) {
+        if ((dac = l_dnaCopy(da)) == NULL)
+            return ERROR_INT("dac not made", procName, 1);
+    } else if (copyflag == L_CLONE) {
+        dac = l_dnaClone(da);
+    } else {
+        return ERROR_INT("invalid copyflag", procName, 1);
+    }
+
+    n = l_dnaaGetCount(daa);
+    if (n >= daa->nalloc)
+        l_dnaaExtendArray(daa);
+    daa->dna[n] = dac;
+    daa->n++;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaaExtendArray()
+ *
+ *      Input:  daa
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+l_dnaaExtendArray(L_DNAA  *daa)
+{
+    PROCNAME("l_dnaaExtendArray");
+
+    if (!daa)
+        return ERROR_INT("daa not defined", procName, 1);
+
+    if ((daa->dna = (L_DNA **)reallocNew((void **)&daa->dna,
+                              sizeof(L_DNA *) * daa->nalloc,
+                              2 * sizeof(L_DNA *) * daa->nalloc)) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+
+    daa->nalloc *= 2;
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                           DNumaa accessors                           *
+ *----------------------------------------------------------------------*/
+/*!
+ *  l_dnaaGetCount()
+ *
+ *      Input:  daa
+ *      Return: count (number of l_dna), or 0 if no l_dna or on error
+ */
+l_int32
+l_dnaaGetCount(L_DNAA  *daa)
+{
+    PROCNAME("l_dnaaGetCount");
+
+    if (!daa)
+        return ERROR_INT("daa not defined", procName, 0);
+    return daa->n;
+}
+
+
+/*!
+ *  l_dnaaGetDnaCount()
+ *
+ *      Input:  daa
+ *              index (of l_dna in daa)
+ *      Return: count of numbers in the referenced l_dna, or 0 on error.
+ */
+l_int32
+l_dnaaGetDnaCount(L_DNAA   *daa,
+                    l_int32  index)
+{
+    PROCNAME("l_dnaaGetDnaCount");
+
+    if (!daa)
+        return ERROR_INT("daa not defined", procName, 0);
+    if (index < 0 || index >= daa->n)
+        return ERROR_INT("invalid index into daa", procName, 0);
+    return l_dnaGetCount(daa->dna[index]);
+}
+
+
+/*!
+ *  l_dnaaGetNumberCount()
+ *
+ *      Input:  daa
+ *      Return: count (total number of numbers in the l_dnaa),
+ *                     or 0 if no numbers or on error
+ */
+l_int32
+l_dnaaGetNumberCount(L_DNAA  *daa)
+{
+L_DNA   *da;
+l_int32  n, sum, i;
+
+    PROCNAME("l_dnaaGetNumberCount");
+
+    if (!daa)
+        return ERROR_INT("daa not defined", procName, 0);
+
+    n = l_dnaaGetCount(daa);
+    for (sum = 0, i = 0; i < n; i++) {
+        da = l_dnaaGetDna(daa, i, L_CLONE);
+        sum += l_dnaGetCount(da);
+        l_dnaDestroy(&da);
+    }
+
+    return sum;
+}
+
+
+/*!
+ *  l_dnaaGetDna()
+ *
+ *      Input:  daa
+ *              index  (to the index-th l_dna)
+ *              accessflag   (L_COPY or L_CLONE)
+ *      Return: l_dna, or null on error
+ */
+L_DNA *
+l_dnaaGetDna(L_DNAA  *daa,
+             l_int32  index,
+             l_int32  accessflag)
+{
+    PROCNAME("l_dnaaGetDna");
+
+    if (!daa)
+        return (L_DNA *)ERROR_PTR("daa not defined", procName, NULL);
+    if (index < 0 || index >= daa->n)
+        return (L_DNA *)ERROR_PTR("index not valid", procName, NULL);
+
+    if (accessflag == L_COPY)
+        return l_dnaCopy(daa->dna[index]);
+    else if (accessflag == L_CLONE)
+        return l_dnaClone(daa->dna[index]);
+    else
+        return (L_DNA *)ERROR_PTR("invalid accessflag", procName, NULL);
+}
+
+
+/*!
+ *  l_dnaaReplaceDna()
+ *
+ *      Input:  daa
+ *              index  (to the index-th l_dna)
+ *              l_dna (insert and replace any existing one)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Any existing l_dna is destroyed, and the input one
+ *          is inserted in its place.
+ *      (2) If the index is invalid, return 1 (error)
+ */
+l_int32
+l_dnaaReplaceDna(L_DNAA  *daa,
+                 l_int32  index,
+                 L_DNA   *da)
+{
+l_int32  n;
+
+    PROCNAME("l_dnaaReplaceDna");
+
+    if (!daa)
+        return ERROR_INT("daa not defined", procName, 1);
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+    n = l_dnaaGetCount(daa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    l_dnaDestroy(&daa->dna[index]);
+    daa->dna[index] = da;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaaGetValue()
+ *
+ *      Input:  daa
+ *              i (index of l_dna within l_dnaa)
+ *              j (index into l_dna)
+ *              val (<return> double value)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_dnaaGetValue(L_DNAA     *daa,
+               l_int32     i,
+               l_int32     j,
+               l_float64  *pval)
+{
+l_int32  n;
+L_DNA   *da;
+
+    PROCNAME("l_dnaaGetValue");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0.0;
+    if (!daa)
+        return ERROR_INT("daa not defined", procName, 1);
+    n = l_dnaaGetCount(daa);
+    if (i < 0 || i >= n)
+        return ERROR_INT("invalid index into daa", procName, 1);
+    da = daa->dna[i];
+    if (j < 0 || j >= da->n)
+        return ERROR_INT("invalid index into da", procName, 1);
+    *pval = da->array[j];
+    return 0;
+}
+
+
+/*!
+ *  l_dnaaAddNumber()
+ *
+ *      Input:  daa
+ *              index (of l_dna within l_dnaa)
+ *              val  (number to be added; stored as a double)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Adds to an existing l_dna only.
+ */
+l_int32
+l_dnaaAddNumber(L_DNAA    *daa,
+                l_int32    index,
+                l_float64  val)
+{
+l_int32  n;
+L_DNA   *da;
+
+    PROCNAME("l_dnaaAddNumber");
+
+    if (!daa)
+        return ERROR_INT("daa not defined", procName, 1);
+    n = l_dnaaGetCount(daa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("invalid index in daa", procName, 1);
+
+    da = l_dnaaGetDna(daa, index, L_CLONE);
+    l_dnaAddNumber(da, val);
+    l_dnaDestroy(&da);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                        Serialize Dna for I/O                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  l_dnaaRead()
+ *
+ *      Input:  filename
+ *      Return: daa, or null on error
+ */
+L_DNAA *
+l_dnaaRead(const char  *filename)
+{
+FILE    *fp;
+L_DNAA  *daa;
+
+    PROCNAME("l_dnaaRead");
+
+    if (!filename)
+        return (L_DNAA *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (L_DNAA *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((daa = l_dnaaReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (L_DNAA *)ERROR_PTR("daa not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return daa;
+}
+
+
+/*!
+ *  l_dnaaReadStream()
+ *
+ *      Input:  stream
+ *      Return: daa, or null on error
+ */
+L_DNAA *
+l_dnaaReadStream(FILE  *fp)
+{
+l_int32    i, n, index, ret, version;
+L_DNA     *da;
+L_DNAA    *daa;
+
+    PROCNAME("l_dnaaReadStream");
+
+    if (!fp)
+        return (L_DNAA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    ret = fscanf(fp, "\nL_Dnaa Version %d\n", &version);
+    if (ret != 1)
+        return (L_DNAA *)ERROR_PTR("not a l_dna file", procName, NULL);
+    if (version != DNA_VERSION_NUMBER)
+        return (L_DNAA *)ERROR_PTR("invalid l_dnaa version", procName, NULL);
+    if (fscanf(fp, "Number of L_Dna = %d\n\n", &n) != 1)
+        return (L_DNAA *)ERROR_PTR("invalid number of l_dna", procName, NULL);
+    if ((daa = l_dnaaCreate(n)) == NULL)
+        return (L_DNAA *)ERROR_PTR("daa not made", procName, NULL);
+
+    for (i = 0; i < n; i++) {
+        if (fscanf(fp, "L_Dna[%d]:", &index) != 1)
+            return (L_DNAA *)ERROR_PTR("invalid l_dna header", procName, NULL);
+        if ((da = l_dnaReadStream(fp)) == NULL)
+            return (L_DNAA *)ERROR_PTR("da not made", procName, NULL);
+        l_dnaaAddDna(daa, da, L_INSERT);
+    }
+
+    return daa;
+}
+
+
+/*!
+ *  l_dnaaWrite()
+ *
+ *      Input:  filename, daa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_dnaaWrite(const char  *filename,
+            L_DNAA      *daa)
+{
+FILE  *fp;
+
+    PROCNAME("l_dnaaWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!daa)
+        return ERROR_INT("daa not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "w")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (l_dnaaWriteStream(fp, daa))
+        return ERROR_INT("daa not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  l_dnaaWriteStream()
+ *
+ *      Input:  stream, daa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_dnaaWriteStream(FILE    *fp,
+                  L_DNAA  *daa)
+{
+l_int32  i, n;
+L_DNA   *da;
+
+    PROCNAME("l_dnaaWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!daa)
+        return ERROR_INT("daa not defined", procName, 1);
+
+    n = l_dnaaGetCount(daa);
+    fprintf(fp, "\nL_Dnaa Version %d\n", DNA_VERSION_NUMBER);
+    fprintf(fp, "Number of L_Dna = %d\n\n", n);
+    for (i = 0; i < n; i++) {
+        if ((da = l_dnaaGetDna(daa, i, L_CLONE)) == NULL)
+            return ERROR_INT("da not found", procName, 1);
+        fprintf(fp, "L_Dna[%d]:", i);
+        l_dnaWriteStream(fp, da);
+        l_dnaDestroy(&da);
+    }
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                     Dna hash: Creation and destruction                   *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  l_dnaHashCreate()
+ *
+ *      Input: nbuckets (the number of buckets in the hash table,
+ *                       which should be prime.)
+ *             initsize (initial size of each allocated numa; 0 for default)
+ *      Return: ptr to new dnahash, or null on error
+ *
+ *  Note: actual dna are created only as required by l_dnaHashAdd()
+ */
+L_DNAHASH *
+l_dnaHashCreate(l_int32  nbuckets,
+                l_int32  initsize)
+{
+L_DNAHASH  *dahash;
+
+    PROCNAME("l_dnaHashCreate");
+
+    if (nbuckets <= 0)
+        return (L_DNAHASH *)ERROR_PTR("negative hash size", procName, NULL);
+    if ((dahash = (L_DNAHASH *)LEPT_CALLOC(1, sizeof(L_DNAHASH))) == NULL)
+        return (L_DNAHASH *)ERROR_PTR("dahash not made", procName, NULL);
+    if ((dahash->dna = (L_DNA **)LEPT_CALLOC(nbuckets, sizeof(L_DNA *)))
+        == NULL) {
+        LEPT_FREE(dahash);
+        return (L_DNAHASH *)ERROR_PTR("dna ptr array not made", procName, NULL);
+    }
+
+    dahash->nbuckets = nbuckets;
+    dahash->initsize = initsize;
+    return dahash;
+}
+
+
+/*!
+ *  l_dnaHashDestroy()
+ *
+ *      Input:  &dahash (<to be nulled, if it exists>)
+ *      Return: void
+ */
+void
+l_dnaHashDestroy(L_DNAHASH **pdahash)
+{
+L_DNAHASH  *dahash;
+l_int32    i;
+
+    PROCNAME("l_dnaHashDestroy");
+
+    if (pdahash == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+
+    if ((dahash = *pdahash) == NULL)
+        return;
+
+    for (i = 0; i < dahash->nbuckets; i++)
+        l_dnaDestroy(&dahash->dna[i]);
+    LEPT_FREE(dahash->dna);
+    LEPT_FREE(dahash);
+    *pdahash = NULL;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                   Dna hash: Accessors and modifiers                      *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  l_dnaHashGetCount()
+ *
+ *      Input:  dahash
+ *      Return: nbuckets (allocated, or 0 on error)
+ */
+l_int32
+l_dnaHashGetCount(L_DNAHASH  *dahash)
+{
+l_int32  nbuckets;
+
+    PROCNAME("l_dnaHashGetCount");
+
+    if (!dahash)
+        return ERROR_INT("dahash not defined", procName, 0);
+    return dahash->nbuckets;
+}
+
+
+/*!
+ *  l_dnaHashGetTotalCount()
+ *
+ *      Input:  dahash
+ *      Return: n (number of numbers in all dna, or 0 on error)
+ */
+l_int32
+l_dnaHashGetTotalCount(L_DNAHASH  *dahash)
+{
+l_int32  nbuckets, i, n;
+L_DNA   *da;
+
+    PROCNAME("l_dnaHashGetTotalCount");
+
+    if (!dahash)
+        return ERROR_INT("dahash not defined", procName, 0);
+
+    for (i = 0, n = 0; i < dahash->nbuckets; i++) {
+        da = l_dnaHashGetDna(dahash, i, L_NOCOPY);
+        if (da)
+            n += l_dnaGetCount(da);
+    }
+
+    return n;
+}
+
+
+/*!
+ *  l_dnaHashGetDna()
+ *
+ *      Input:  dahash
+ *              key  (key to be hashed into a bucket number)
+ *              copyflag (L_NOCOPY, L_COPY, L_CLONE)
+ *      Return: ptr to numa
+ */
+L_DNA *
+l_dnaHashGetDna(L_DNAHASH  *dahash,
+                l_uint64    key,
+                l_int32     copyflag)
+{
+l_int32  bucket;
+L_DNA   *da;
+
+    PROCNAME("l_dnaHashGetDna");
+
+    if (!dahash)
+        return (L_DNA *)ERROR_PTR("dahash not defined", procName, NULL);
+    bucket = key % dahash->nbuckets;
+    da = dahash->dna[bucket];
+    if (da) {
+        if (copyflag == L_NOCOPY)
+            return da;
+        else if (copyflag == L_COPY)
+            return l_dnaCopy(da);
+        else
+            return l_dnaClone(da);
+    }
+    else
+        return NULL;
+}
+
+
+/*!
+ *  l_dnaHashAdd()
+ *
+ *      Input:  dahash
+ *              key  (key to be hashed into a bucket number)
+ *              value  (float value to be appended to the specific dna)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+l_dnaHashAdd(L_DNAHASH  *dahash,
+             l_uint64    key,
+             l_float64   value)
+{
+l_int32  bucket;
+L_DNA   *da;
+
+    PROCNAME("l_dnaHashAdd");
+
+    if (!dahash)
+        return ERROR_INT("dahash not defined", procName, 1);
+    bucket = key % dahash->nbuckets;
+    da = dahash->dna[bucket];
+    if (!da) {
+        if ((da = l_dnaCreate(dahash->initsize)) == NULL)
+            return ERROR_INT("da not made", procName, 1);
+        dahash->dna[bucket] = da;
+    }
+    l_dnaAddNumber(da, value);
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                      DnaHash: Operations on Dna                          *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  l_dnaHashCreateFromDna()
+ *
+ *      Input:  da
+ *      Return: dahash if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The values stored in the @dahash are indices into @da;
+ *          @dahash has no use without @da.
+ */
+L_DNAHASH *
+l_dnaHashCreateFromDna(L_DNA  *da)
+{
+l_int32     i, n;
+l_uint32    nsize;
+l_uint64    key;
+l_float64   val;
+L_DNAHASH  *dahash;
+
+    PROCNAME("l_dnaHashCreateFromDna");
+
+    if (!da)
+        return (L_DNAHASH *)ERROR_PTR("da not defined", procName, NULL);
+
+    n = l_dnaGetCount(da);
+    findNextLargerPrime(n / 20, &nsize);  /* buckets in hash table */
+
+    dahash = l_dnaHashCreate(nsize, 8);
+    for (i = 0; i < n; i++) {
+        l_dnaGetDValue(da, i, &val);
+        l_hashFloat64ToUint64(nsize, val, &key);
+        l_dnaHashAdd(dahash, key, (l_float64)i);
+    }
+
+    return dahash;
+}
+
+
+/*!
+ *  l_dnaRemoveDupsByHash()
+ *
+ *      Input:  das
+ *              &dad (<return> hash set)
+ *              &dahash (<optional return> dnahash used for lookup)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Generates a dna with unique values.
+ *      (2) The dnahash is built up with dad to assure uniqueness.
+ *          It can be used to find if an element is in the set:
+ *              l_dnaFindValByHash(dad, dahash, val, &index)
+ */
+l_int32
+l_dnaRemoveDupsByHash(L_DNA       *das,
+                      L_DNA      **pdad,
+                      L_DNAHASH  **pdahash)
+{
+l_int32     i, n, index, items;
+l_uint32    nsize;
+l_uint64    key;
+l_float64   val;
+L_DNA      *dad;
+L_DNAHASH  *dahash;
+
+    PROCNAME("l_dnaRemoveDupsByHash");
+
+    if (pdahash) *pdahash = NULL;
+    if (!pdad)
+        return ERROR_INT("&dad not defined", procName, 1);
+    *pdad = NULL;
+    if (!das)
+        return ERROR_INT("das not defined", procName, 1);
+
+    n = l_dnaGetCount(das);
+    findNextLargerPrime(n / 20, &nsize);  /* buckets in hash table */
+    dahash = l_dnaHashCreate(nsize, 8);
+    dad = l_dnaCreate(n);
+    *pdad = dad;
+    for (i = 0, items = 0; i < n; i++) {
+        l_dnaGetDValue(das, i, &val);
+        l_dnaFindValByHash(dad, dahash, val, &index);
+        if (index < 0) {  /* not found */
+            l_hashFloat64ToUint64(nsize, val, &key);
+            l_dnaHashAdd(dahash, key, (l_float64)items);
+            l_dnaAddNumber(dad, val);
+            items++;
+        }
+    }
+
+    if (pdahash)
+        *pdahash = dahash;
+    else
+        l_dnaHashDestroy(&dahash);
+    return 0;
+}
+
+
+/*!
+ *  l_dnaMakeHistoByHash()
+ *
+ *      Input:  das
+ *              &dahash (<return> hash map: val --> index)
+ *              &dav (<return> array of values: index --> val)
+ *              &dac (<return> histo array of counts: index --> count)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Generates and returns a dna of occurrences (histogram),
+ *          an aligned dna of values, and an associated hashmap.
+ *          The hashmap takes @dav and a value, and points into the
+ *          histogram in @dac.
+ *      (2) The dna of values, @dav, is aligned with the histogram @dac,
+ *          and is needed for fast lookup.  It is a hash set, because
+ *          the values are unique.
+ *      (3) Lookup is simple:
+ *              l_dnaFindValByHash(dav, dahash, val, &index);
+ *              if (index >= 0)
+ *                  l_dnaGetIValue(dac, index, &icount);
+ *              else
+ *                  icount = 0;
+ */
+l_int32
+l_dnaMakeHistoByHash(L_DNA       *das,
+                     L_DNAHASH  **pdahash,
+                     L_DNA      **pdav,
+                     L_DNA      **pdac)
+{
+l_int32     i, n, nitems, index, count;
+l_uint32    nsize;
+l_uint64    key;
+l_float64   val;
+L_DNA      *dac, *dav;
+L_DNAHASH  *dahash;
+
+    PROCNAME("l_dnaMakeHistoByHash");
+
+    if (pdahash) *pdahash = NULL;
+    if (pdac) *pdac = NULL;
+    if (pdav) *pdav = NULL;
+    if (!pdahash || !pdac || !pdav)
+        return ERROR_INT("&dahash, &dac, &dav not all defined", procName, 1);
+    if (!das)
+        return ERROR_INT("das not defined", procName, 1);
+    if ((n = l_dnaGetCount(das)) == 0)
+        return ERROR_INT("no data in das", procName, 1);
+
+    findNextLargerPrime(n / 20, &nsize);  /* buckets in hash table */
+    dahash = l_dnaHashCreate(nsize, 8);
+    dac = l_dnaCreate(n);  /* histogram */
+    dav = l_dnaCreate(n);  /* the values */
+    for (i = 0, nitems = 0; i < n; i++) {
+        l_dnaGetDValue(das, i, &val);
+            /* Is this value already stored in dav? */
+        l_dnaFindValByHash(dav, dahash, val, &index);
+        if (index >= 0) {  /* found */
+            l_dnaGetIValue(dac, (l_float64)index, &count);
+            l_dnaSetValue(dac, (l_float64)index, count + 1);
+        } else {  /* not found */
+            l_hashFloat64ToUint64(nsize, val, &key);
+            l_dnaHashAdd(dahash, key, (l_float64)nitems);
+            l_dnaAddNumber(dav, val);
+            l_dnaAddNumber(dac, 1);
+            nitems++;
+        }
+    }
+
+    *pdahash = dahash;
+    *pdac = dac;
+    *pdav = dav;
+    return 0;
+}
+
+
+/*!
+ *  l_dnaIntersectionByHash()
+ *
+ *      Input:  da1, da2
+ *      Return: dad (intersection of the number arrays), or null on error
+ *
+ *  Notes:
+ *      (1) This uses the same method for building the intersection set
+ *          as ptaIntersectionByHash() and sarrayIntersectionByHash().
+ */
+L_DNA *
+l_dnaIntersectionByHash(L_DNA  *da1,
+                        L_DNA  *da2)
+{
+l_int32     n1, n2, nsmall, nbuckets, i, index1, index2;
+l_uint32    nsize2;
+l_uint64    key;
+l_float64   val;
+L_DNAHASH  *dahash1, *dahash2;
+L_DNA      *da_small, *da_big, *dad;
+
+    PROCNAME("l_dnaIntersectionByHash");
+
+    if (!da1)
+        return (L_DNA *)ERROR_PTR("da1 not defined", procName, NULL);
+    if (!da2)
+        return (L_DNA *)ERROR_PTR("da2 not defined", procName, NULL);
+
+        /* Put the elements of the biggest array into a dnahash */
+    n1 = l_dnaGetCount(da1);
+    n2 = l_dnaGetCount(da2);
+    da_small = (n1 < n2) ? da1 : da2;   /* do not destroy da_small */
+    da_big = (n1 < n2) ? da2 : da1;   /* do not destroy da_big */
+    dahash1 = l_dnaHashCreateFromDna(da_big);
+
+        /* Build up the intersection of numbers.  Add to @dad
+         * if the number is in da_big (using dahash1) but hasn't
+         * yet been seen in the traversal of da_small (using dahash2). */
+    dad = l_dnaCreate(0);
+    nsmall = l_dnaGetCount(da_small);
+    findNextLargerPrime(nsmall / 20, &nsize2);  /* buckets in hash table */
+    dahash2 = l_dnaHashCreate(nsize2, 0);
+    nbuckets = l_dnaHashGetCount(dahash2);
+    for (i = 0; i < nsmall; i++) {
+        l_dnaGetDValue(da_small, i, &val);
+        l_dnaFindValByHash(da_big, dahash1, val, &index1);
+        if (index1 >= 0) {  /* found */
+            l_dnaFindValByHash(da_small, dahash2, val, &index2);
+            if (index2 == -1) {  /* not found */
+                l_dnaAddNumber(dad, val);
+                l_hashFloat64ToUint64(nbuckets, val, &key);
+                l_dnaHashAdd(dahash2, key, (l_float64)i);
+            }
+        }
+    }
+
+    l_dnaHashDestroy(&dahash1);
+    l_dnaHashDestroy(&dahash2);
+    return dad;
+}
+
+
+/*!
+ *  l_dnaFindValByHash()
+ *
+ *      Input:  da
+ *              dahash (containing indices into @da)
+ *              val  (searching for this number in @da)
+ *              &index (<return> index into da if found; -1 otherwise)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Algo: hash @val into a key; hash the key to get the dna
+ *                in @dahash (that holds indices into @da); traverse
+ *                the dna of indices looking for @val in @da.
+ */
+l_int32
+l_dnaFindValByHash(L_DNA      *da,
+                   L_DNAHASH  *dahash,
+                   l_float64   val,
+                   l_int32    *pindex)
+{
+l_int32    i, nbuckets, nvals, indexval;
+l_float64  vali;
+l_uint64   key;
+L_DNA     *da1;
+
+    PROCNAME("l_dnaFindValByHash");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = -1;
+    if (!da)
+        return ERROR_INT("da not defined", procName, 1);
+    if (!dahash)
+        return ERROR_INT("dahash not defined", procName, 1);
+
+    nbuckets = l_dnaHashGetCount(dahash);
+    l_hashFloat64ToUint64(nbuckets, val, &key);
+    da1 = l_dnaHashGetDna(dahash, key, L_NOCOPY);
+    if (!da1) return 0;
+
+        /* Run through da1, looking for this @val */
+    nvals = l_dnaGetCount(da1);
+    for (i = 0; i < nvals; i++) {
+        l_dnaGetIValue(da1, i, &indexval);
+        l_dnaGetDValue(da, indexval, &vali);
+        if (val == vali) {
+            *pindex = indexval;
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                         Other Dna functions                          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  l_dnaMakeDelta()
+ *
+ *      Input:  das (input l_dna)
+ *      Return: dad (of difference values val[i+1] - val[i]),
+ *                   or null on error
+ */
+L_DNA *
+l_dnaMakeDelta(L_DNA  *das)
+{
+l_int32  i, n, prev, cur;
+L_DNA   *dad;
+
+    PROCNAME("l_dnaMakeDelta");
+
+    if (!das)
+        return (L_DNA *)ERROR_PTR("das not defined", procName, NULL);
+    n = l_dnaGetCount(das);
+    dad = l_dnaCreate(n - 1);
+    prev = 0;
+    for (i = 1; i < n; i++) {
+        l_dnaGetIValue(das, i, &cur);
+        l_dnaAddNumber(dad, cur - prev);
+        prev = cur;
+    }
+    return dad;
+}
+
+
+/*!
+ *  l_dnaConvertToNuma()
+ *
+ *      Input:  da
+ *      Return: na, or null on error
+ */
+NUMA *
+l_dnaConvertToNuma(L_DNA  *da)
+{
+l_int32    i, n;
+l_float64  val;
+NUMA      *na;
+
+    PROCNAME("l_dnaConvertToNuma");
+
+    if (!da)
+        return (NUMA *)ERROR_PTR("da not defined", procName, NULL);
+
+    n = l_dnaGetCount(da);
+    na = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        l_dnaGetDValue(da, i, &val);
+        numaAddNumber(na, val);
+    }
+    return na;
+}
+
+
+/*!
+ *  numaConvertToDna
+ *
+ *      Input:  na
+ *      Return: da, or null on error
+ */
+L_DNA *
+numaConvertToDna(NUMA  *na)
+{
+l_int32    i, n;
+l_float32  val;
+L_DNA     *da;
+
+    PROCNAME("numaConvertToDna");
+
+    if (!na)
+        return (L_DNA *)ERROR_PTR("na not defined", procName, NULL);
+
+    n = numaGetCount(na);
+    da = l_dnaCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        l_dnaAddNumber(da, val);
+    }
+    return da;
+}
+
+
+/*!
+ *  l_dnaJoin()
+ *
+ *      Input:  dad  (dest dna; add to this one)
+ *              das  (<optional> source dna; add from this one)
+ *              istart  (starting index in das)
+ *              iend  (ending index in das; use -1 to cat all)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) istart < 0 is taken to mean 'read from the start' (istart = 0)
+ *      (2) iend < 0 means 'read to the end'
+ *      (3) if das == NULL, this is a no-op
+ */
+l_int32
+l_dnaJoin(L_DNA   *dad,
+          L_DNA   *das,
+          l_int32  istart,
+          l_int32  iend)
+{
+l_int32    n, i;
+l_float64  val;
+
+    PROCNAME("l_dnaJoin");
+
+    if (!dad)
+        return ERROR_INT("dad not defined", procName, 1);
+    if (!das)
+        return 0;
+
+    if (istart < 0)
+        istart = 0;
+    n = l_dnaGetCount(das);
+    if (iend < 0 || iend >= n)
+        iend = n - 1;
+    if (istart > iend)
+        return ERROR_INT("istart > iend; nothing to add", procName, 1);
+
+    for (i = istart; i <= iend; i++) {
+        l_dnaGetDValue(das, i, &val);
+        l_dnaAddNumber(dad, val);
+    }
+
+    return 0;
+}
+
+
diff --git a/src/dwacomb.2.c b/src/dwacomb.2.c
new file mode 100644 (file)
index 0000000..24df2c9
--- /dev/null
@@ -0,0 +1,291 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*!
+ *      Top-level fast binary morphology with auto-generated sels
+ *
+ *             PIX     *pixMorphDwa_2()
+ *             PIX     *pixFMorphopGen_2()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+PIX *pixMorphDwa_2(PIX *pixd, PIX *pixs, l_int32 operation, char *selname);
+PIX *pixFMorphopGen_2(PIX *pixd, PIX *pixs, l_int32 operation, char *selname);
+l_int32 fmorphopgen_low_2(l_uint32 *datad, l_int32 w,
+                          l_int32 h, l_int32 wpld,
+                          l_uint32 *datas, l_int32 wpls,
+                          l_int32 index);
+
+static l_int32   NUM_SELS_GENERATED = 76;
+static char  SEL_NAMES[][80] = {
+                             "sel_comb_4h",
+                             "sel_comb_4v",
+                             "sel_comb_5h",
+                             "sel_comb_5v",
+                             "sel_comb_6h",
+                             "sel_comb_6v",
+                             "sel_comb_7h",
+                             "sel_comb_7v",
+                             "sel_comb_8h",
+                             "sel_comb_8v",
+                             "sel_comb_9h",
+                             "sel_comb_9v",
+                             "sel_comb_10h",
+                             "sel_comb_10v",
+                             "sel_comb_12h",
+                             "sel_comb_12v",
+                             "sel_comb_14h",
+                             "sel_comb_14v",
+                             "sel_comb_15h",
+                             "sel_comb_15v",
+                             "sel_comb_16h",
+                             "sel_comb_16v",
+                             "sel_comb_18h",
+                             "sel_comb_18v",
+                             "sel_comb_20h",
+                             "sel_comb_20v",
+                             "sel_comb_21h",
+                             "sel_comb_21v",
+                             "sel_comb_22h",
+                             "sel_comb_22v",
+                             "sel_comb_24h",
+                             "sel_comb_24v",
+                             "sel_comb_25h",
+                             "sel_comb_25v",
+                             "sel_comb_27h",
+                             "sel_comb_27v",
+                             "sel_comb_28h",
+                             "sel_comb_28v",
+                             "sel_comb_30h",
+                             "sel_comb_30v",
+                             "sel_comb_32h",
+                             "sel_comb_32v",
+                             "sel_comb_33h",
+                             "sel_comb_33v",
+                             "sel_comb_35h",
+                             "sel_comb_35v",
+                             "sel_comb_36h",
+                             "sel_comb_36v",
+                             "sel_comb_39h",
+                             "sel_comb_39v",
+                             "sel_comb_40h",
+                             "sel_comb_40v",
+                             "sel_comb_42h",
+                             "sel_comb_42v",
+                             "sel_comb_44h",
+                             "sel_comb_44v",
+                             "sel_comb_45h",
+                             "sel_comb_45v",
+                             "sel_comb_48h",
+                             "sel_comb_48v",
+                             "sel_comb_49h",
+                             "sel_comb_49v",
+                             "sel_comb_50h",
+                             "sel_comb_50v",
+                             "sel_comb_52h",
+                             "sel_comb_52v",
+                             "sel_comb_54h",
+                             "sel_comb_54v",
+                             "sel_comb_55h",
+                             "sel_comb_55v",
+                             "sel_comb_56h",
+                             "sel_comb_56v",
+                             "sel_comb_60h",
+                             "sel_comb_60v",
+                             "sel_comb_63h",
+                             "sel_comb_63v"};
+
+/*!
+ *  pixMorphDwa_2()
+ *
+ *      Input:  pixd (usual 3 choices: null, == pixs, != pixs)
+ *              pixs (1 bpp)
+ *              operation  (L_MORPH_DILATE, L_MORPH_ERODE,
+ *                          L_MORPH_OPEN, L_MORPH_CLOSE)
+ *              sel name
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This simply adds a border, calls the appropriate
+ *          pixFMorphopGen_*(), and removes the border.
+ *          See the notes for that function.
+ *      (2) The size of the border depends on the operation
+ *          and the boundary conditions.
+ */
+PIX *
+pixMorphDwa_2(PIX     *pixd,
+              PIX     *pixs,
+              l_int32  operation,
+              char    *selname)
+{
+l_int32  bordercolor, bordersize;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixMorphDwa_2");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, pixd);
+
+        /* Set the border size */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    bordersize = 32;
+    if (bordercolor == 0 && operation == L_MORPH_CLOSE)
+        bordersize += 32;
+
+    pixt1 = pixAddBorder(pixs, bordersize, 0);
+    pixt2 = pixFMorphopGen_2(NULL, pixt1, operation, selname);
+    pixt3 = pixRemoveBorder(pixt2, bordersize);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixCopy(pixd, pixt3);
+    pixDestroy(&pixt3);
+    return pixd;
+}
+
+
+/*!
+ *  pixFMorphopGen_2()
+ *
+ *      Input:  pixd (usual 3 choices: null, == pixs, != pixs)
+ *              pixs (1 bpp)
+ *              operation  (L_MORPH_DILATE, L_MORPH_ERODE,
+ *                          L_MORPH_OPEN, L_MORPH_CLOSE)
+ *              sel name
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This is a dwa operation, and the Sels must be limited in
+ *          size to not more than 31 pixels about the origin.
+ *      (2) A border of appropriate size (32 pixels, or 64 pixels
+ *          for safe closing with asymmetric b.c.) must be added before
+ *          this function is called.
+ *      (3) This handles all required setting of the border pixels
+ *          before erosion and dilation.
+ *      (4) The closing operation is safe; no pixels can be removed
+ *          near the boundary.
+ */
+PIX *
+pixFMorphopGen_2(PIX     *pixd,
+                 PIX     *pixs,
+                 l_int32  operation,
+                 char    *selname)
+{
+l_int32    i, index, found, w, h, wpls, wpld, bordercolor, erodeop, borderop;
+l_uint32  *datad, *datas, *datat;
+PIX       *pixt;
+
+    PROCNAME("pixFMorphopGen_2");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, pixd);
+
+        /* Get boundary colors to use */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    if (bordercolor == 1)
+        erodeop = PIX_SET;
+    else
+        erodeop = PIX_CLR;
+
+    found = FALSE;
+    for (i = 0; i < NUM_SELS_GENERATED; i++) {
+        if (strcmp(selname, SEL_NAMES[i]) == 0) {
+            found = TRUE;
+            index = 2 * i;
+            break;
+        }
+    }
+    if (found == FALSE)
+        return (PIX *)ERROR_PTR("sel index not found", procName, pixd);
+
+    if (!pixd) {
+        if ((pixd = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    else  /* for in-place or pre-allocated */
+        pixResizeImageData(pixd, pixs);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+        /* The images must be surrounded, in advance, with a border of
+         * size 32 pixels (or 64, for closing), that we'll read from.
+         * Fabricate a "proper" image as the subimage within the 32
+         * pixel border, having the following parameters:  */
+    w = pixGetWidth(pixs) - 64;
+    h = pixGetHeight(pixs) - 64;
+    datas = pixGetData(pixs) + 32 * wpls + 1;
+    datad = pixGetData(pixd) + 32 * wpld + 1;
+
+    if (operation == L_MORPH_DILATE || operation == L_MORPH_ERODE) {
+        borderop = PIX_CLR;
+        if (operation == L_MORPH_ERODE) {
+            borderop = erodeop;
+            index++;
+        }
+        if (pixd == pixs) {  /* in-place; generate a temp image */
+            if ((pixt = pixCopy(NULL, pixs)) == NULL)
+                return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+            datat = pixGetData(pixt) + 32 * wpls + 1;
+            pixSetOrClearBorder(pixt, 32, 32, 32, 32, borderop);
+            fmorphopgen_low_2(datad, w, h, wpld, datat, wpls, index);
+            pixDestroy(&pixt);
+        }
+        else { /* not in-place */
+            pixSetOrClearBorder(pixs, 32, 32, 32, 32, borderop);
+            fmorphopgen_low_2(datad, w, h, wpld, datas, wpls, index);
+        }
+    }
+    else {  /* opening or closing; generate a temp image */
+        if ((pixt = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+        datat = pixGetData(pixt) + 32 * wpls + 1;
+        if (operation == L_MORPH_OPEN) {
+            pixSetOrClearBorder(pixs, 32, 32, 32, 32, erodeop);
+            fmorphopgen_low_2(datat, w, h, wpls, datas, wpls, index+1);
+            pixSetOrClearBorder(pixt, 32, 32, 32, 32, PIX_CLR);
+            fmorphopgen_low_2(datad, w, h, wpld, datat, wpls, index);
+        }
+        else {  /* closing */
+            pixSetOrClearBorder(pixs, 32, 32, 32, 32, PIX_CLR);
+            fmorphopgen_low_2(datat, w, h, wpls, datas, wpls, index);
+            pixSetOrClearBorder(pixt, 32, 32, 32, 32, erodeop);
+            fmorphopgen_low_2(datad, w, h, wpld, datat, wpls, index+1);
+        }
+        pixDestroy(&pixt);
+    }
+
+    return pixd;
+}
+
diff --git a/src/dwacomblow.2.c b/src/dwacomblow.2.c
new file mode 100644 (file)
index 0000000..d081361
--- /dev/null
@@ -0,0 +1,4966 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*!
+ *     Low-level fast binary morphology with auto-generated sels
+ *
+ *      Dispatcher:
+ *             l_int32    fmorphopgen_low_2()
+ *
+ *      Static Low-level:
+ *             void       fdilate_2_*()
+ *             void       ferode_2_*()
+ */
+
+#include "allheaders.h"
+
+static void  fdilate_2_0(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_0(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_1(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_1(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_2(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_2(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_3(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_3(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_4(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_4(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_5(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_5(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_6(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_6(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_7(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_7(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_8(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_8(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_9(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_9(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_10(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_10(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_11(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_11(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_12(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_12(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_13(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_13(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_14(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_14(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_15(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_15(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_16(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_16(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_17(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_17(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_18(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_18(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_19(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_19(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_20(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_20(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_21(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_21(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_22(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_22(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_23(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_23(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_24(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_24(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_25(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_25(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_26(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_26(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_27(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_27(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_28(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_28(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_29(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_29(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_30(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_30(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_31(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_31(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_32(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_32(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_33(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_33(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_34(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_34(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_35(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_35(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_36(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_36(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_37(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_37(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_38(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_38(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_39(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_39(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_40(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_40(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_41(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_41(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_42(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_42(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_43(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_43(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_44(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_44(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_45(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_45(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_46(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_46(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_47(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_47(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_48(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_48(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_49(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_49(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_50(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_50(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_51(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_51(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_52(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_52(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_53(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_53(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_54(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_54(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_55(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_55(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_56(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_56(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_57(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_57(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_58(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_58(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_59(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_59(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_60(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_60(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_61(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_61(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_62(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_62(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_63(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_63(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_64(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_64(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_65(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_65(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_66(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_66(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_67(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_67(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_68(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_68(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_69(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_69(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_70(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_70(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_71(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_71(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_72(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_72(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_73(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_73(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_74(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_74(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_2_75(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_2_75(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+
+
+/*---------------------------------------------------------------------*
+ *                          Fast morph dispatcher                      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  fmorphopgen_low_2()
+ *
+ *       a dispatcher to appropriate low-level code
+ */
+l_int32
+fmorphopgen_low_2(l_uint32  *datad,
+                  l_int32    w,
+                  l_int32    h,
+                  l_int32    wpld,
+                  l_uint32  *datas,
+                  l_int32    wpls,
+                  l_int32    index)
+{
+
+    switch (index)
+    {
+    case 0:
+        fdilate_2_0(datad, w, h, wpld, datas, wpls);
+        break;
+    case 1:
+        ferode_2_0(datad, w, h, wpld, datas, wpls);
+        break;
+    case 2:
+        fdilate_2_1(datad, w, h, wpld, datas, wpls);
+        break;
+    case 3:
+        ferode_2_1(datad, w, h, wpld, datas, wpls);
+        break;
+    case 4:
+        fdilate_2_2(datad, w, h, wpld, datas, wpls);
+        break;
+    case 5:
+        ferode_2_2(datad, w, h, wpld, datas, wpls);
+        break;
+    case 6:
+        fdilate_2_3(datad, w, h, wpld, datas, wpls);
+        break;
+    case 7:
+        ferode_2_3(datad, w, h, wpld, datas, wpls);
+        break;
+    case 8:
+        fdilate_2_4(datad, w, h, wpld, datas, wpls);
+        break;
+    case 9:
+        ferode_2_4(datad, w, h, wpld, datas, wpls);
+        break;
+    case 10:
+        fdilate_2_5(datad, w, h, wpld, datas, wpls);
+        break;
+    case 11:
+        ferode_2_5(datad, w, h, wpld, datas, wpls);
+        break;
+    case 12:
+        fdilate_2_6(datad, w, h, wpld, datas, wpls);
+        break;
+    case 13:
+        ferode_2_6(datad, w, h, wpld, datas, wpls);
+        break;
+    case 14:
+        fdilate_2_7(datad, w, h, wpld, datas, wpls);
+        break;
+    case 15:
+        ferode_2_7(datad, w, h, wpld, datas, wpls);
+        break;
+    case 16:
+        fdilate_2_8(datad, w, h, wpld, datas, wpls);
+        break;
+    case 17:
+        ferode_2_8(datad, w, h, wpld, datas, wpls);
+        break;
+    case 18:
+        fdilate_2_9(datad, w, h, wpld, datas, wpls);
+        break;
+    case 19:
+        ferode_2_9(datad, w, h, wpld, datas, wpls);
+        break;
+    case 20:
+        fdilate_2_10(datad, w, h, wpld, datas, wpls);
+        break;
+    case 21:
+        ferode_2_10(datad, w, h, wpld, datas, wpls);
+        break;
+    case 22:
+        fdilate_2_11(datad, w, h, wpld, datas, wpls);
+        break;
+    case 23:
+        ferode_2_11(datad, w, h, wpld, datas, wpls);
+        break;
+    case 24:
+        fdilate_2_12(datad, w, h, wpld, datas, wpls);
+        break;
+    case 25:
+        ferode_2_12(datad, w, h, wpld, datas, wpls);
+        break;
+    case 26:
+        fdilate_2_13(datad, w, h, wpld, datas, wpls);
+        break;
+    case 27:
+        ferode_2_13(datad, w, h, wpld, datas, wpls);
+        break;
+    case 28:
+        fdilate_2_14(datad, w, h, wpld, datas, wpls);
+        break;
+    case 29:
+        ferode_2_14(datad, w, h, wpld, datas, wpls);
+        break;
+    case 30:
+        fdilate_2_15(datad, w, h, wpld, datas, wpls);
+        break;
+    case 31:
+        ferode_2_15(datad, w, h, wpld, datas, wpls);
+        break;
+    case 32:
+        fdilate_2_16(datad, w, h, wpld, datas, wpls);
+        break;
+    case 33:
+        ferode_2_16(datad, w, h, wpld, datas, wpls);
+        break;
+    case 34:
+        fdilate_2_17(datad, w, h, wpld, datas, wpls);
+        break;
+    case 35:
+        ferode_2_17(datad, w, h, wpld, datas, wpls);
+        break;
+    case 36:
+        fdilate_2_18(datad, w, h, wpld, datas, wpls);
+        break;
+    case 37:
+        ferode_2_18(datad, w, h, wpld, datas, wpls);
+        break;
+    case 38:
+        fdilate_2_19(datad, w, h, wpld, datas, wpls);
+        break;
+    case 39:
+        ferode_2_19(datad, w, h, wpld, datas, wpls);
+        break;
+    case 40:
+        fdilate_2_20(datad, w, h, wpld, datas, wpls);
+        break;
+    case 41:
+        ferode_2_20(datad, w, h, wpld, datas, wpls);
+        break;
+    case 42:
+        fdilate_2_21(datad, w, h, wpld, datas, wpls);
+        break;
+    case 43:
+        ferode_2_21(datad, w, h, wpld, datas, wpls);
+        break;
+    case 44:
+        fdilate_2_22(datad, w, h, wpld, datas, wpls);
+        break;
+    case 45:
+        ferode_2_22(datad, w, h, wpld, datas, wpls);
+        break;
+    case 46:
+        fdilate_2_23(datad, w, h, wpld, datas, wpls);
+        break;
+    case 47:
+        ferode_2_23(datad, w, h, wpld, datas, wpls);
+        break;
+    case 48:
+        fdilate_2_24(datad, w, h, wpld, datas, wpls);
+        break;
+    case 49:
+        ferode_2_24(datad, w, h, wpld, datas, wpls);
+        break;
+    case 50:
+        fdilate_2_25(datad, w, h, wpld, datas, wpls);
+        break;
+    case 51:
+        ferode_2_25(datad, w, h, wpld, datas, wpls);
+        break;
+    case 52:
+        fdilate_2_26(datad, w, h, wpld, datas, wpls);
+        break;
+    case 53:
+        ferode_2_26(datad, w, h, wpld, datas, wpls);
+        break;
+    case 54:
+        fdilate_2_27(datad, w, h, wpld, datas, wpls);
+        break;
+    case 55:
+        ferode_2_27(datad, w, h, wpld, datas, wpls);
+        break;
+    case 56:
+        fdilate_2_28(datad, w, h, wpld, datas, wpls);
+        break;
+    case 57:
+        ferode_2_28(datad, w, h, wpld, datas, wpls);
+        break;
+    case 58:
+        fdilate_2_29(datad, w, h, wpld, datas, wpls);
+        break;
+    case 59:
+        ferode_2_29(datad, w, h, wpld, datas, wpls);
+        break;
+    case 60:
+        fdilate_2_30(datad, w, h, wpld, datas, wpls);
+        break;
+    case 61:
+        ferode_2_30(datad, w, h, wpld, datas, wpls);
+        break;
+    case 62:
+        fdilate_2_31(datad, w, h, wpld, datas, wpls);
+        break;
+    case 63:
+        ferode_2_31(datad, w, h, wpld, datas, wpls);
+        break;
+    case 64:
+        fdilate_2_32(datad, w, h, wpld, datas, wpls);
+        break;
+    case 65:
+        ferode_2_32(datad, w, h, wpld, datas, wpls);
+        break;
+    case 66:
+        fdilate_2_33(datad, w, h, wpld, datas, wpls);
+        break;
+    case 67:
+        ferode_2_33(datad, w, h, wpld, datas, wpls);
+        break;
+    case 68:
+        fdilate_2_34(datad, w, h, wpld, datas, wpls);
+        break;
+    case 69:
+        ferode_2_34(datad, w, h, wpld, datas, wpls);
+        break;
+    case 70:
+        fdilate_2_35(datad, w, h, wpld, datas, wpls);
+        break;
+    case 71:
+        ferode_2_35(datad, w, h, wpld, datas, wpls);
+        break;
+    case 72:
+        fdilate_2_36(datad, w, h, wpld, datas, wpls);
+        break;
+    case 73:
+        ferode_2_36(datad, w, h, wpld, datas, wpls);
+        break;
+    case 74:
+        fdilate_2_37(datad, w, h, wpld, datas, wpls);
+        break;
+    case 75:
+        ferode_2_37(datad, w, h, wpld, datas, wpls);
+        break;
+    case 76:
+        fdilate_2_38(datad, w, h, wpld, datas, wpls);
+        break;
+    case 77:
+        ferode_2_38(datad, w, h, wpld, datas, wpls);
+        break;
+    case 78:
+        fdilate_2_39(datad, w, h, wpld, datas, wpls);
+        break;
+    case 79:
+        ferode_2_39(datad, w, h, wpld, datas, wpls);
+        break;
+    case 80:
+        fdilate_2_40(datad, w, h, wpld, datas, wpls);
+        break;
+    case 81:
+        ferode_2_40(datad, w, h, wpld, datas, wpls);
+        break;
+    case 82:
+        fdilate_2_41(datad, w, h, wpld, datas, wpls);
+        break;
+    case 83:
+        ferode_2_41(datad, w, h, wpld, datas, wpls);
+        break;
+    case 84:
+        fdilate_2_42(datad, w, h, wpld, datas, wpls);
+        break;
+    case 85:
+        ferode_2_42(datad, w, h, wpld, datas, wpls);
+        break;
+    case 86:
+        fdilate_2_43(datad, w, h, wpld, datas, wpls);
+        break;
+    case 87:
+        ferode_2_43(datad, w, h, wpld, datas, wpls);
+        break;
+    case 88:
+        fdilate_2_44(datad, w, h, wpld, datas, wpls);
+        break;
+    case 89:
+        ferode_2_44(datad, w, h, wpld, datas, wpls);
+        break;
+    case 90:
+        fdilate_2_45(datad, w, h, wpld, datas, wpls);
+        break;
+    case 91:
+        ferode_2_45(datad, w, h, wpld, datas, wpls);
+        break;
+    case 92:
+        fdilate_2_46(datad, w, h, wpld, datas, wpls);
+        break;
+    case 93:
+        ferode_2_46(datad, w, h, wpld, datas, wpls);
+        break;
+    case 94:
+        fdilate_2_47(datad, w, h, wpld, datas, wpls);
+        break;
+    case 95:
+        ferode_2_47(datad, w, h, wpld, datas, wpls);
+        break;
+    case 96:
+        fdilate_2_48(datad, w, h, wpld, datas, wpls);
+        break;
+    case 97:
+        ferode_2_48(datad, w, h, wpld, datas, wpls);
+        break;
+    case 98:
+        fdilate_2_49(datad, w, h, wpld, datas, wpls);
+        break;
+    case 99:
+        ferode_2_49(datad, w, h, wpld, datas, wpls);
+        break;
+    case 100:
+        fdilate_2_50(datad, w, h, wpld, datas, wpls);
+        break;
+    case 101:
+        ferode_2_50(datad, w, h, wpld, datas, wpls);
+        break;
+    case 102:
+        fdilate_2_51(datad, w, h, wpld, datas, wpls);
+        break;
+    case 103:
+        ferode_2_51(datad, w, h, wpld, datas, wpls);
+        break;
+    case 104:
+        fdilate_2_52(datad, w, h, wpld, datas, wpls);
+        break;
+    case 105:
+        ferode_2_52(datad, w, h, wpld, datas, wpls);
+        break;
+    case 106:
+        fdilate_2_53(datad, w, h, wpld, datas, wpls);
+        break;
+    case 107:
+        ferode_2_53(datad, w, h, wpld, datas, wpls);
+        break;
+    case 108:
+        fdilate_2_54(datad, w, h, wpld, datas, wpls);
+        break;
+    case 109:
+        ferode_2_54(datad, w, h, wpld, datas, wpls);
+        break;
+    case 110:
+        fdilate_2_55(datad, w, h, wpld, datas, wpls);
+        break;
+    case 111:
+        ferode_2_55(datad, w, h, wpld, datas, wpls);
+        break;
+    case 112:
+        fdilate_2_56(datad, w, h, wpld, datas, wpls);
+        break;
+    case 113:
+        ferode_2_56(datad, w, h, wpld, datas, wpls);
+        break;
+    case 114:
+        fdilate_2_57(datad, w, h, wpld, datas, wpls);
+        break;
+    case 115:
+        ferode_2_57(datad, w, h, wpld, datas, wpls);
+        break;
+    case 116:
+        fdilate_2_58(datad, w, h, wpld, datas, wpls);
+        break;
+    case 117:
+        ferode_2_58(datad, w, h, wpld, datas, wpls);
+        break;
+    case 118:
+        fdilate_2_59(datad, w, h, wpld, datas, wpls);
+        break;
+    case 119:
+        ferode_2_59(datad, w, h, wpld, datas, wpls);
+        break;
+    case 120:
+        fdilate_2_60(datad, w, h, wpld, datas, wpls);
+        break;
+    case 121:
+        ferode_2_60(datad, w, h, wpld, datas, wpls);
+        break;
+    case 122:
+        fdilate_2_61(datad, w, h, wpld, datas, wpls);
+        break;
+    case 123:
+        ferode_2_61(datad, w, h, wpld, datas, wpls);
+        break;
+    case 124:
+        fdilate_2_62(datad, w, h, wpld, datas, wpls);
+        break;
+    case 125:
+        ferode_2_62(datad, w, h, wpld, datas, wpls);
+        break;
+    case 126:
+        fdilate_2_63(datad, w, h, wpld, datas, wpls);
+        break;
+    case 127:
+        ferode_2_63(datad, w, h, wpld, datas, wpls);
+        break;
+    case 128:
+        fdilate_2_64(datad, w, h, wpld, datas, wpls);
+        break;
+    case 129:
+        ferode_2_64(datad, w, h, wpld, datas, wpls);
+        break;
+    case 130:
+        fdilate_2_65(datad, w, h, wpld, datas, wpls);
+        break;
+    case 131:
+        ferode_2_65(datad, w, h, wpld, datas, wpls);
+        break;
+    case 132:
+        fdilate_2_66(datad, w, h, wpld, datas, wpls);
+        break;
+    case 133:
+        ferode_2_66(datad, w, h, wpld, datas, wpls);
+        break;
+    case 134:
+        fdilate_2_67(datad, w, h, wpld, datas, wpls);
+        break;
+    case 135:
+        ferode_2_67(datad, w, h, wpld, datas, wpls);
+        break;
+    case 136:
+        fdilate_2_68(datad, w, h, wpld, datas, wpls);
+        break;
+    case 137:
+        ferode_2_68(datad, w, h, wpld, datas, wpls);
+        break;
+    case 138:
+        fdilate_2_69(datad, w, h, wpld, datas, wpls);
+        break;
+    case 139:
+        ferode_2_69(datad, w, h, wpld, datas, wpls);
+        break;
+    case 140:
+        fdilate_2_70(datad, w, h, wpld, datas, wpls);
+        break;
+    case 141:
+        ferode_2_70(datad, w, h, wpld, datas, wpls);
+        break;
+    case 142:
+        fdilate_2_71(datad, w, h, wpld, datas, wpls);
+        break;
+    case 143:
+        ferode_2_71(datad, w, h, wpld, datas, wpls);
+        break;
+    case 144:
+        fdilate_2_72(datad, w, h, wpld, datas, wpls);
+        break;
+    case 145:
+        ferode_2_72(datad, w, h, wpld, datas, wpls);
+        break;
+    case 146:
+        fdilate_2_73(datad, w, h, wpld, datas, wpls);
+        break;
+    case 147:
+        ferode_2_73(datad, w, h, wpld, datas, wpls);
+        break;
+    case 148:
+        fdilate_2_74(datad, w, h, wpld, datas, wpls);
+        break;
+    case 149:
+        ferode_2_74(datad, w, h, wpld, datas, wpls);
+        break;
+    case 150:
+        fdilate_2_75(datad, w, h, wpld, datas, wpls);
+        break;
+    case 151:
+        ferode_2_75(datad, w, h, wpld, datas, wpls);
+        break;
+    }
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                 Low-level auto-generated static routines                 *
+ *--------------------------------------------------------------------------*/
+/*
+ *  N.B.  In all the low-level routines, the part of the image
+ *        that is accessed has been clipped by 32 pixels on
+ *        all four sides.  This is done in the higher level
+ *        code by redefining w and h smaller and by moving the
+ *        start-of-image pointers up to the beginning of this
+ *        interior rectangle.
+ */
+static void
+fdilate_2_0(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31));
+        }
+    }
+}
+
+static void
+ferode_2_0(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31));
+        }
+    }
+}
+
+static void
+fdilate_2_1(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls)) |
+                    (*(sptr - wpls));
+        }
+    }
+}
+
+static void
+ferode_2_1(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls)) &
+                    (*(sptr + wpls));
+        }
+    }
+}
+
+static void
+fdilate_2_2(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*sptr);
+        }
+    }
+}
+
+static void
+ferode_2_2(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*sptr);
+        }
+    }
+}
+
+static void
+fdilate_2_3(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*sptr);
+        }
+    }
+}
+
+static void
+ferode_2_3(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*sptr);
+        }
+    }
+}
+
+static void
+fdilate_2_4(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31));
+        }
+    }
+}
+
+static void
+ferode_2_4(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31));
+        }
+    }
+}
+
+static void
+fdilate_2_5(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls2)) |
+                    (*(sptr - wpls));
+        }
+    }
+}
+
+static void
+ferode_2_5(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls2)) &
+                    (*(sptr + wpls));
+        }
+    }
+}
+
+static void
+fdilate_2_6(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*sptr);
+        }
+    }
+}
+
+static void
+ferode_2_6(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*sptr);
+        }
+    }
+}
+
+static void
+fdilate_2_7(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*sptr);
+        }
+    }
+}
+
+static void
+ferode_2_7(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*sptr);
+        }
+    }
+}
+
+static void
+fdilate_2_8(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30));
+        }
+    }
+}
+
+static void
+ferode_2_8(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30));
+        }
+    }
+}
+
+static void
+fdilate_2_9(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls2)) |
+                    (*(sptr - wpls2));
+        }
+    }
+}
+
+static void
+ferode_2_9(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls2)) &
+                    (*(sptr + wpls2));
+        }
+    }
+}
+
+static void
+fdilate_2_10(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    (*sptr) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29));
+        }
+    }
+}
+
+static void
+ferode_2_10(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    (*sptr) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29));
+        }
+    }
+}
+
+static void
+fdilate_2_11(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls3;
+
+    wpls3 = 3 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls3)) |
+                    (*sptr) |
+                    (*(sptr - wpls3));
+        }
+    }
+}
+
+static void
+ferode_2_11(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls3;
+
+    wpls3 = 3 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls3)) &
+                    (*sptr) &
+                    (*(sptr + wpls3));
+        }
+    }
+}
+
+static void
+fdilate_2_12(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30));
+        }
+    }
+}
+
+static void
+ferode_2_12(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30));
+        }
+    }
+}
+
+static void
+fdilate_2_13(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+l_int32             wpls3;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls3)) |
+                    (*(sptr - wpls2));
+        }
+    }
+}
+
+static void
+ferode_2_13(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+l_int32             wpls3;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls3)) &
+                    (*(sptr + wpls2));
+        }
+    }
+}
+
+static void
+fdilate_2_14(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    (*sptr) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28));
+        }
+    }
+}
+
+static void
+ferode_2_14(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    (*sptr) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28));
+        }
+    }
+}
+
+static void
+fdilate_2_15(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls4;
+
+    wpls4 = 4 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls4)) |
+                    (*sptr) |
+                    (*(sptr - wpls4));
+        }
+    }
+}
+
+static void
+ferode_2_15(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls4;
+
+    wpls4 = 4 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls4)) &
+                    (*sptr) &
+                    (*(sptr + wpls4));
+        }
+    }
+}
+
+static void
+fdilate_2_16(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29));
+        }
+    }
+}
+
+static void
+ferode_2_16(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29));
+        }
+    }
+}
+
+static void
+fdilate_2_17(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls3;
+l_int32             wpls4;
+
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls4)) |
+                    (*(sptr - wpls3));
+        }
+    }
+}
+
+static void
+ferode_2_17(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls3;
+l_int32             wpls4;
+
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls4)) &
+                    (*(sptr + wpls3));
+        }
+    }
+}
+
+static void
+fdilate_2_18(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    (*sptr) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27));
+        }
+    }
+}
+
+static void
+ferode_2_18(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    (*sptr) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27));
+        }
+    }
+}
+
+static void
+fdilate_2_19(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls5;
+
+    wpls5 = 5 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls5)) |
+                    (*sptr) |
+                    (*(sptr - wpls5));
+        }
+    }
+}
+
+static void
+ferode_2_19(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls5;
+
+    wpls5 = 5 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls5)) &
+                    (*sptr) &
+                    (*(sptr + wpls5));
+        }
+    }
+}
+
+static void
+fdilate_2_20(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26));
+        }
+    }
+}
+
+static void
+ferode_2_20(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26));
+        }
+    }
+}
+
+static void
+fdilate_2_21(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+l_int32             wpls6;
+
+    wpls2 = 2 * wpls;
+    wpls6 = 6 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls6)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls6));
+        }
+    }
+}
+
+static void
+ferode_2_21(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+l_int32             wpls6;
+
+    wpls2 = 2 * wpls;
+    wpls6 = 6 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls6)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls6));
+        }
+    }
+}
+
+static void
+fdilate_2_22(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    (*sptr) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26));
+        }
+    }
+}
+
+static void
+ferode_2_22(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    (*sptr) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26));
+        }
+    }
+}
+
+static void
+fdilate_2_23(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls6;
+
+    wpls6 = 6 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls6)) |
+                    (*sptr) |
+                    (*(sptr - wpls6));
+        }
+    }
+}
+
+static void
+ferode_2_23(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls6;
+
+    wpls6 = 6 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls6)) &
+                    (*sptr) &
+                    (*(sptr + wpls6));
+        }
+    }
+}
+
+static void
+fdilate_2_24(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25));
+        }
+    }
+}
+
+static void
+ferode_2_24(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25));
+        }
+    }
+}
+
+static void
+fdilate_2_25(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+l_int32             wpls3;
+l_int32             wpls7;
+l_int32             wpls8;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls8)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls7));
+        }
+    }
+}
+
+static void
+ferode_2_25(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+l_int32             wpls3;
+l_int32             wpls7;
+l_int32             wpls8;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls8)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls7));
+        }
+    }
+}
+
+static void
+fdilate_2_26(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    (*sptr) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25));
+        }
+    }
+}
+
+static void
+ferode_2_26(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    (*sptr) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25));
+        }
+    }
+}
+
+static void
+fdilate_2_27(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls7;
+
+    wpls7 = 7 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls7)) |
+                    (*sptr) |
+                    (*(sptr - wpls7));
+        }
+    }
+}
+
+static void
+ferode_2_27(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls7;
+
+    wpls7 = 7 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls7)) &
+                    (*sptr) &
+                    (*(sptr + wpls7));
+        }
+    }
+}
+
+static void
+fdilate_2_28(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27));
+        }
+    }
+}
+
+static void
+ferode_2_28(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27));
+        }
+    }
+}
+
+static void
+fdilate_2_29(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls5;
+l_int32             wpls6;
+
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls6)) |
+                    (*(sptr - wpls5));
+        }
+    }
+}
+
+static void
+ferode_2_29(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls5;
+l_int32             wpls6;
+
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls6)) &
+                    (*(sptr + wpls5));
+        }
+    }
+}
+
+static void
+fdilate_2_30(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23));
+        }
+    }
+}
+
+static void
+ferode_2_30(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23));
+        }
+    }
+}
+
+static void
+fdilate_2_31(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls3;
+l_int32             wpls9;
+
+    wpls3 = 3 * wpls;
+    wpls9 = 9 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls9)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls9));
+        }
+    }
+}
+
+static void
+ferode_2_31(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls3;
+l_int32             wpls9;
+
+    wpls3 = 3 * wpls;
+    wpls9 = 9 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls9)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls9));
+        }
+    }
+}
+
+static void
+fdilate_2_32(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    (*sptr) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22));
+        }
+    }
+}
+
+static void
+ferode_2_32(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    (*sptr) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22));
+        }
+    }
+}
+
+static void
+fdilate_2_33(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls5;
+l_int32             wpls10;
+
+    wpls5 = 5 * wpls;
+    wpls10 = 10 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls10)) |
+                    (*(sptr + wpls5)) |
+                    (*sptr) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls10));
+        }
+    }
+}
+
+static void
+ferode_2_33(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls5;
+l_int32             wpls10;
+
+    wpls5 = 5 * wpls;
+    wpls10 = 10 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls10)) &
+                    (*(sptr - wpls5)) &
+                    (*sptr) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls10));
+        }
+    }
+}
+
+static void
+fdilate_2_34(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    (*sptr) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23));
+        }
+    }
+}
+
+static void
+ferode_2_34(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    (*sptr) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23));
+        }
+    }
+}
+
+static void
+fdilate_2_35(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls9;
+
+    wpls9 = 9 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls9)) |
+                    (*sptr) |
+                    (*(sptr - wpls9));
+        }
+    }
+}
+
+static void
+ferode_2_35(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls9;
+
+    wpls9 = 9 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls9)) &
+                    (*sptr) &
+                    (*(sptr + wpls9));
+        }
+    }
+}
+
+static void
+fdilate_2_36(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22));
+        }
+    }
+}
+
+static void
+ferode_2_36(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22));
+        }
+    }
+}
+
+static void
+fdilate_2_37(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls3;
+l_int32             wpls4;
+l_int32             wpls10;
+l_int32             wpls11;
+
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls11)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls10));
+        }
+    }
+}
+
+static void
+ferode_2_37(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls3;
+l_int32             wpls4;
+l_int32             wpls10;
+l_int32             wpls11;
+
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls11)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls10));
+        }
+    }
+}
+
+static void
+fdilate_2_38(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 12) | (*(sptr + 1) >> 20)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    (*sptr) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20));
+        }
+    }
+}
+
+static void
+ferode_2_38(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 12) | (*(sptr - 1) << 20)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    (*sptr) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20));
+        }
+    }
+}
+
+static void
+fdilate_2_39(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls6;
+l_int32             wpls12;
+
+    wpls6 = 6 * wpls;
+    wpls12 = 12 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls12)) |
+                    (*(sptr + wpls6)) |
+                    (*sptr) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls12));
+        }
+    }
+}
+
+static void
+ferode_2_39(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls6;
+l_int32             wpls12;
+
+    wpls6 = 6 * wpls;
+    wpls12 = 12 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls12)) &
+                    (*(sptr - wpls6)) &
+                    (*sptr) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls12));
+        }
+    }
+}
+
+static void
+fdilate_2_40(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 12) | (*(sptr + 1) >> 20)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20));
+        }
+    }
+}
+
+static void
+ferode_2_40(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 12) | (*(sptr - 1) << 20)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20));
+        }
+    }
+}
+
+static void
+fdilate_2_41(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls4;
+l_int32             wpls12;
+
+    wpls4 = 4 * wpls;
+    wpls12 = 12 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls12)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls12));
+        }
+    }
+}
+
+static void
+ferode_2_41(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls4;
+l_int32             wpls12;
+
+    wpls4 = 4 * wpls;
+    wpls12 = 12 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls12)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls12));
+        }
+    }
+}
+
+static void
+fdilate_2_42(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    (*sptr) |
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21));
+        }
+    }
+}
+
+static void
+ferode_2_42(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    (*sptr) &
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21));
+        }
+    }
+}
+
+static void
+fdilate_2_43(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls11;
+
+    wpls11 = 11 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls11)) |
+                    (*sptr) |
+                    (*(sptr - wpls11));
+        }
+    }
+}
+
+static void
+ferode_2_43(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls11;
+
+    wpls11 = 11 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls11)) &
+                    (*sptr) &
+                    (*(sptr + wpls11));
+        }
+    }
+}
+
+static void
+fdilate_2_44(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 14) | (*(sptr + 1) >> 18)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    (*sptr) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18));
+        }
+    }
+}
+
+static void
+ferode_2_44(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 14) | (*(sptr - 1) << 18)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    (*sptr) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18));
+        }
+    }
+}
+
+static void
+fdilate_2_45(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls7;
+l_int32             wpls14;
+
+    wpls7 = 7 * wpls;
+    wpls14 = 14 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls14)) |
+                    (*(sptr + wpls7)) |
+                    (*sptr) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls14));
+        }
+    }
+}
+
+static void
+ferode_2_45(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls7;
+l_int32             wpls14;
+
+    wpls7 = 7 * wpls;
+    wpls14 = 14 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls14)) &
+                    (*(sptr - wpls7)) &
+                    (*sptr) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls14));
+        }
+    }
+}
+
+static void
+fdilate_2_46(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 15) | (*(sptr + 1) >> 17)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17));
+        }
+    }
+}
+
+static void
+ferode_2_46(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 15) | (*(sptr - 1) << 17)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17));
+        }
+    }
+}
+
+static void
+fdilate_2_47(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls3;
+l_int32             wpls9;
+l_int32             wpls15;
+
+    wpls3 = 3 * wpls;
+    wpls9 = 9 * wpls;
+    wpls15 = 15 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls15)) |
+                    (*(sptr + wpls9)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls15));
+        }
+    }
+}
+
+static void
+ferode_2_47(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls3;
+l_int32             wpls9;
+l_int32             wpls15;
+
+    wpls3 = 3 * wpls;
+    wpls9 = 9 * wpls;
+    wpls15 = 15 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls15)) &
+                    (*(sptr - wpls9)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls15));
+        }
+    }
+}
+
+static void
+fdilate_2_48(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 13) | (*(sptr + 1) >> 19)) |
+                    (*sptr) |
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19));
+        }
+    }
+}
+
+static void
+ferode_2_48(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 13) | (*(sptr - 1) << 19)) &
+                    (*sptr) &
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19));
+        }
+    }
+}
+
+static void
+fdilate_2_49(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls13;
+
+    wpls13 = 13 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls13)) |
+                    (*sptr) |
+                    (*(sptr - wpls13));
+        }
+    }
+}
+
+static void
+ferode_2_49(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls13;
+
+    wpls13 = 13 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls13)) &
+                    (*sptr) &
+                    (*(sptr + wpls13));
+        }
+    }
+}
+
+static void
+fdilate_2_50(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 16) | (*(sptr + 1) >> 16)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    (*sptr) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16));
+        }
+    }
+}
+
+static void
+ferode_2_50(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 16) | (*(sptr - 1) << 16)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    (*sptr) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16));
+        }
+    }
+}
+
+static void
+fdilate_2_51(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls8;
+l_int32             wpls16;
+
+    wpls8 = 8 * wpls;
+    wpls16 = 16 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls16)) |
+                    (*(sptr + wpls8)) |
+                    (*sptr) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls16));
+        }
+    }
+}
+
+static void
+ferode_2_51(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls8;
+l_int32             wpls16;
+
+    wpls8 = 8 * wpls;
+    wpls16 = 16 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls16)) &
+                    (*(sptr - wpls8)) &
+                    (*sptr) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls16));
+        }
+    }
+}
+
+static void
+fdilate_2_52(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 18) | (*(sptr + 1) >> 14)) |
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) |
+                    ((*(sptr) >> 17) | (*(sptr - 1) << 15));
+        }
+    }
+}
+
+static void
+ferode_2_52(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 18) | (*(sptr - 1) << 14)) &
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) &
+                    ((*(sptr) << 17) | (*(sptr + 1) >> 15));
+        }
+    }
+}
+
+static void
+fdilate_2_53(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls3;
+l_int32             wpls4;
+l_int32             wpls10;
+l_int32             wpls11;
+l_int32             wpls17;
+l_int32             wpls18;
+
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls17 = 17 * wpls;
+    wpls18 = 18 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls18)) |
+                    (*(sptr + wpls11)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls10)) |
+                    (*(sptr - wpls17));
+        }
+    }
+}
+
+static void
+ferode_2_53(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls3;
+l_int32             wpls4;
+l_int32             wpls10;
+l_int32             wpls11;
+l_int32             wpls17;
+l_int32             wpls18;
+
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls17 = 17 * wpls;
+    wpls18 = 18 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls18)) &
+                    (*(sptr - wpls11)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls10)) &
+                    (*(sptr + wpls17));
+        }
+    }
+}
+
+static void
+fdilate_2_54(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 17) | (*(sptr + 1) >> 15)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16));
+        }
+    }
+}
+
+static void
+ferode_2_54(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 17) | (*(sptr - 1) << 15)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16));
+        }
+    }
+}
+
+static void
+fdilate_2_55(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls5;
+l_int32             wpls6;
+l_int32             wpls16;
+l_int32             wpls17;
+
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls17)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls16));
+        }
+    }
+}
+
+static void
+ferode_2_55(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls5;
+l_int32             wpls6;
+l_int32             wpls16;
+l_int32             wpls17;
+
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls17)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls16));
+        }
+    }
+}
+
+static void
+fdilate_2_56(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 18) | (*(sptr + 1) >> 14)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    (*sptr) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14));
+        }
+    }
+}
+
+static void
+ferode_2_56(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 18) | (*(sptr - 1) << 14)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    (*sptr) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14));
+        }
+    }
+}
+
+static void
+fdilate_2_57(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls9;
+l_int32             wpls18;
+
+    wpls9 = 9 * wpls;
+    wpls18 = 18 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls18)) |
+                    (*(sptr + wpls9)) |
+                    (*sptr) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls18));
+        }
+    }
+}
+
+static void
+ferode_2_57(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls9;
+l_int32             wpls18;
+
+    wpls9 = 9 * wpls;
+    wpls18 = 18 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls18)) &
+                    (*(sptr - wpls9)) &
+                    (*sptr) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls18));
+        }
+    }
+}
+
+static void
+fdilate_2_58(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 20) | (*(sptr + 1) >> 12)) |
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) |
+                    ((*(sptr) >> 20) | (*(sptr - 1) << 12));
+        }
+    }
+}
+
+static void
+ferode_2_58(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 20) | (*(sptr - 1) << 12)) &
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) &
+                    ((*(sptr) << 20) | (*(sptr + 1) >> 12));
+        }
+    }
+}
+
+static void
+fdilate_2_59(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls4;
+l_int32             wpls12;
+l_int32             wpls20;
+
+    wpls4 = 4 * wpls;
+    wpls12 = 12 * wpls;
+    wpls20 = 20 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls20)) |
+                    (*(sptr + wpls12)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls12)) |
+                    (*(sptr - wpls20));
+        }
+    }
+}
+
+static void
+ferode_2_59(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls4;
+l_int32             wpls12;
+l_int32             wpls20;
+
+    wpls4 = 4 * wpls;
+    wpls12 = 12 * wpls;
+    wpls20 = 20 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls20)) &
+                    (*(sptr - wpls12)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls12)) &
+                    (*(sptr + wpls20));
+        }
+    }
+}
+
+static void
+fdilate_2_60(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 21) | (*(sptr + 1) >> 11)) |
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    (*sptr) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) |
+                    ((*(sptr) >> 21) | (*(sptr - 1) << 11));
+        }
+    }
+}
+
+static void
+ferode_2_60(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 21) | (*(sptr - 1) << 11)) &
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    (*sptr) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) &
+                    ((*(sptr) << 21) | (*(sptr + 1) >> 11));
+        }
+    }
+}
+
+static void
+fdilate_2_61(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls7;
+l_int32             wpls14;
+l_int32             wpls21;
+
+    wpls7 = 7 * wpls;
+    wpls14 = 14 * wpls;
+    wpls21 = 21 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls21)) |
+                    (*(sptr + wpls14)) |
+                    (*(sptr + wpls7)) |
+                    (*sptr) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls14)) |
+                    (*(sptr - wpls21));
+        }
+    }
+}
+
+static void
+ferode_2_61(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls7;
+l_int32             wpls14;
+l_int32             wpls21;
+
+    wpls7 = 7 * wpls;
+    wpls14 = 14 * wpls;
+    wpls21 = 21 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls21)) &
+                    (*(sptr - wpls14)) &
+                    (*(sptr - wpls7)) &
+                    (*sptr) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls14)) &
+                    (*(sptr + wpls21));
+        }
+    }
+}
+
+static void
+fdilate_2_62(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 20) | (*(sptr + 1) >> 12)) |
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    (*sptr) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) |
+                    ((*(sptr) >> 20) | (*(sptr - 1) << 12));
+        }
+    }
+}
+
+static void
+ferode_2_62(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 20) | (*(sptr - 1) << 12)) &
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    (*sptr) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) &
+                    ((*(sptr) << 20) | (*(sptr + 1) >> 12));
+        }
+    }
+}
+
+static void
+fdilate_2_63(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls10;
+l_int32             wpls20;
+
+    wpls10 = 10 * wpls;
+    wpls20 = 20 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls20)) |
+                    (*(sptr + wpls10)) |
+                    (*sptr) |
+                    (*(sptr - wpls10)) |
+                    (*(sptr - wpls20));
+        }
+    }
+}
+
+static void
+ferode_2_63(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls10;
+l_int32             wpls20;
+
+    wpls10 = 10 * wpls;
+    wpls20 = 20 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls20)) &
+                    (*(sptr - wpls10)) &
+                    (*sptr) &
+                    (*(sptr + wpls10)) &
+                    (*(sptr + wpls20));
+        }
+    }
+}
+
+static void
+fdilate_2_64(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 20) | (*(sptr + 1) >> 12)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 19) | (*(sptr - 1) << 13));
+        }
+    }
+}
+
+static void
+ferode_2_64(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 20) | (*(sptr - 1) << 12)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 19) | (*(sptr + 1) >> 13));
+        }
+    }
+}
+
+static void
+fdilate_2_65(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls6;
+l_int32             wpls7;
+l_int32             wpls19;
+l_int32             wpls20;
+
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls19 = 19 * wpls;
+    wpls20 = 20 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls20)) |
+                    (*(sptr + wpls7)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls19));
+        }
+    }
+}
+
+static void
+ferode_2_65(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls6;
+l_int32             wpls7;
+l_int32             wpls19;
+l_int32             wpls20;
+
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls19 = 19 * wpls;
+    wpls20 = 20 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls20)) &
+                    (*(sptr - wpls7)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls19));
+        }
+    }
+}
+
+static void
+fdilate_2_66(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 23) | (*(sptr + 1) >> 9)) |
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) |
+                    ((*(sptr) >> 22) | (*(sptr - 1) << 10));
+        }
+    }
+}
+
+static void
+ferode_2_66(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 23) | (*(sptr - 1) << 9)) &
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) &
+                    ((*(sptr) << 22) | (*(sptr + 1) >> 10));
+        }
+    }
+}
+
+static void
+fdilate_2_67(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls4;
+l_int32             wpls5;
+l_int32             wpls13;
+l_int32             wpls14;
+l_int32             wpls22;
+l_int32             wpls23;
+
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls22 = 22 * wpls;
+    wpls23 = 23 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls23)) |
+                    (*(sptr + wpls14)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls13)) |
+                    (*(sptr - wpls22));
+        }
+    }
+}
+
+static void
+ferode_2_67(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls4;
+l_int32             wpls5;
+l_int32             wpls13;
+l_int32             wpls14;
+l_int32             wpls22;
+l_int32             wpls23;
+
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls22 = 22 * wpls;
+    wpls23 = 23 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls23)) &
+                    (*(sptr - wpls14)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls13)) &
+                    (*(sptr + wpls22));
+        }
+    }
+}
+
+static void
+fdilate_2_68(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 22) | (*(sptr + 1) >> 10)) |
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    (*sptr) |
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) |
+                    ((*(sptr) >> 22) | (*(sptr - 1) << 10));
+        }
+    }
+}
+
+static void
+ferode_2_68(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 22) | (*(sptr - 1) << 10)) &
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    (*sptr) &
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) &
+                    ((*(sptr) << 22) | (*(sptr + 1) >> 10));
+        }
+    }
+}
+
+static void
+fdilate_2_69(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls11;
+l_int32             wpls22;
+
+    wpls11 = 11 * wpls;
+    wpls22 = 22 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls22)) |
+                    (*(sptr + wpls11)) |
+                    (*sptr) |
+                    (*(sptr - wpls11)) |
+                    (*(sptr - wpls22));
+        }
+    }
+}
+
+static void
+ferode_2_69(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls11;
+l_int32             wpls22;
+
+    wpls11 = 11 * wpls;
+    wpls22 = 22 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls22)) &
+                    (*(sptr - wpls11)) &
+                    (*sptr) &
+                    (*(sptr + wpls11)) &
+                    (*(sptr + wpls22));
+        }
+    }
+}
+
+static void
+fdilate_2_70(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 24) | (*(sptr + 1) >> 8)) |
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    (*sptr) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) |
+                    ((*(sptr) >> 24) | (*(sptr - 1) << 8));
+        }
+    }
+}
+
+static void
+ferode_2_70(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 24) | (*(sptr - 1) << 8)) &
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    (*sptr) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) &
+                    ((*(sptr) << 24) | (*(sptr + 1) >> 8));
+        }
+    }
+}
+
+static void
+fdilate_2_71(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls8;
+l_int32             wpls16;
+l_int32             wpls24;
+
+    wpls8 = 8 * wpls;
+    wpls16 = 16 * wpls;
+    wpls24 = 24 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls24)) |
+                    (*(sptr + wpls16)) |
+                    (*(sptr + wpls8)) |
+                    (*sptr) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls16)) |
+                    (*(sptr - wpls24));
+        }
+    }
+}
+
+static void
+ferode_2_71(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls8;
+l_int32             wpls16;
+l_int32             wpls24;
+
+    wpls8 = 8 * wpls;
+    wpls16 = 16 * wpls;
+    wpls24 = 24 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls24)) &
+                    (*(sptr - wpls16)) &
+                    (*(sptr - wpls8)) &
+                    (*sptr) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls16)) &
+                    (*(sptr + wpls24));
+        }
+    }
+}
+
+static void
+fdilate_2_72(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 25) | (*(sptr + 1) >> 7)) |
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) |
+                    ((*(sptr) >> 25) | (*(sptr - 1) << 7));
+        }
+    }
+}
+
+static void
+ferode_2_72(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 25) | (*(sptr - 1) << 7)) &
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) &
+                    ((*(sptr) << 25) | (*(sptr + 1) >> 7));
+        }
+    }
+}
+
+static void
+fdilate_2_73(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls5;
+l_int32             wpls15;
+l_int32             wpls25;
+
+    wpls5 = 5 * wpls;
+    wpls15 = 15 * wpls;
+    wpls25 = 25 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls25)) |
+                    (*(sptr + wpls15)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls15)) |
+                    (*(sptr - wpls25));
+        }
+    }
+}
+
+static void
+ferode_2_73(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls5;
+l_int32             wpls15;
+l_int32             wpls25;
+
+    wpls5 = 5 * wpls;
+    wpls15 = 15 * wpls;
+    wpls25 = 25 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls25)) &
+                    (*(sptr - wpls15)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls15)) &
+                    (*(sptr + wpls25));
+        }
+    }
+}
+
+static void
+fdilate_2_74(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 27) | (*(sptr + 1) >> 5)) |
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    (*sptr) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14)) |
+                    ((*(sptr) >> 27) | (*(sptr - 1) << 5));
+        }
+    }
+}
+
+static void
+ferode_2_74(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 27) | (*(sptr - 1) << 5)) &
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    (*sptr) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14)) &
+                    ((*(sptr) << 27) | (*(sptr + 1) >> 5));
+        }
+    }
+}
+
+static void
+fdilate_2_75(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls9;
+l_int32             wpls18;
+l_int32             wpls27;
+
+    wpls9 = 9 * wpls;
+    wpls18 = 18 * wpls;
+    wpls27 = 27 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls27)) |
+                    (*(sptr + wpls18)) |
+                    (*(sptr + wpls9)) |
+                    (*sptr) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls18)) |
+                    (*(sptr - wpls27));
+        }
+    }
+}
+
+static void
+ferode_2_75(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls9;
+l_int32             wpls18;
+l_int32             wpls27;
+
+    wpls9 = 9 * wpls;
+    wpls18 = 18 * wpls;
+    wpls27 = 27 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls27)) &
+                    (*(sptr - wpls18)) &
+                    (*(sptr - wpls9)) &
+                    (*sptr) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls18)) &
+                    (*(sptr + wpls27));
+        }
+    }
+}
+
diff --git a/src/edge.c b/src/edge.c
new file mode 100644 (file)
index 0000000..3962739
--- /dev/null
@@ -0,0 +1,632 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  edge.c
+ *
+ *      Sobel edge detecting filter
+ *          PIX      *pixSobelEdgeFilter()
+ *
+ *      Two-sided edge gradient filter
+ *          PIX      *pixTwoSidedEdgeFilter()
+ *
+ *      Measurement of edge smoothness
+ *          l_int32   pixMeasureEdgeSmoothness()
+ *          NUMA     *pixGetEdgeProfile()
+ *          l_int32   pixGetLastOffPixelInRun()
+ *          l_int32   pixGetLastOnPixelInRun()
+ *
+ *
+ *  The Sobel edge detector uses these two simple gradient filters.
+ *
+ *       1    2    1             1    0   -1
+ *       0    0    0             2    0   -2
+ *      -1   -2   -1             1    0   -1
+ *
+ *      (horizontal)             (vertical)
+ *
+ *  To use both the vertical and horizontal filters, set the orientation
+ *  flag to L_ALL_EDGES; this sums the abs. value of their outputs,
+ *  clipped to 255.
+ *
+ *  See comments below for displaying the resulting image with
+ *  the edges dark, both for 8 bpp and 1 bpp.
+ */
+
+#include "allheaders.h"
+
+
+/*----------------------------------------------------------------------*
+ *                    Sobel edge detecting filter                       *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixSobelEdgeFilter()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              orientflag (L_HORIZONTAL_EDGES, L_VERTICAL_EDGES, L_ALL_EDGES)
+ *      Return: pixd (8 bpp, edges are brighter), or null on error
+ *
+ *  Notes:
+ *      (1) Invert pixd to see larger gradients as darker (grayscale).
+ *      (2) To generate a binary image of the edges, threshold
+ *          the result using pixThresholdToBinary().  If the high
+ *          edge values are to be fg (1), invert after running
+ *          pixThresholdToBinary().
+ *      (3) Label the pixels as follows:
+ *              1    4    7
+ *              2    5    8
+ *              3    6    9
+ *          Read the data incrementally across the image and unroll
+ *          the loop.
+ *      (4) This runs at about 45 Mpix/sec on a 3 GHz processor.
+ */
+PIX *
+pixSobelEdgeFilter(PIX     *pixs,
+                   l_int32  orientflag)
+{
+l_int32    w, h, d, i, j, wplt, wpld, gx, gy, vald;
+l_int32    val1, val2, val3, val4, val5, val6, val7, val8, val9;
+l_uint32  *datat, *linet, *datad, *lined;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixSobelEdgeFilter");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (orientflag != L_HORIZONTAL_EDGES && orientflag != L_VERTICAL_EDGES &&
+        orientflag != L_ALL_EDGES)
+        return (PIX *)ERROR_PTR("invalid orientflag", procName, NULL);
+
+        /* Add 1 pixel (mirrored) to each side of the image. */
+    if ((pixt = pixAddMirroredBorder(pixs, 1, 1, 1, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+        /* Compute filter output at each location. */
+    pixd = pixCreateTemplate(pixs);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            if (j == 0) {  /* start a new row */
+                val1 = GET_DATA_BYTE(linet, j);
+                val2 = GET_DATA_BYTE(linet + wplt, j);
+                val3 = GET_DATA_BYTE(linet + 2 * wplt, j);
+                val4 = GET_DATA_BYTE(linet, j + 1);
+                val5 = GET_DATA_BYTE(linet + wplt, j + 1);
+                val6 = GET_DATA_BYTE(linet + 2 * wplt, j + 1);
+                val7 = GET_DATA_BYTE(linet, j + 2);
+                val8 = GET_DATA_BYTE(linet + wplt, j + 2);
+                val9 = GET_DATA_BYTE(linet + 2 * wplt, j + 2);
+            } else {  /* shift right by 1 pixel; update incrementally */
+                val1 = val4;
+                val2 = val5;
+                val3 = val6;
+                val4 = val7;
+                val5 = val8;
+                val6 = val9;
+                val7 = GET_DATA_BYTE(linet, j + 2);
+                val8 = GET_DATA_BYTE(linet + wplt, j + 2);
+                val9 = GET_DATA_BYTE(linet + 2 * wplt, j + 2);
+            }
+            if (orientflag == L_HORIZONTAL_EDGES)
+                vald = L_ABS(val1 + 2 * val4 + val7
+                             - val3 - 2 * val6 - val9) >> 3;
+            else if (orientflag == L_VERTICAL_EDGES)
+                vald = L_ABS(val1 + 2 * val2 + val3 - val7
+                             - 2 * val8 - val9) >> 3;
+            else {  /* L_ALL_EDGES */
+                gx = L_ABS(val1 + 2 * val2 + val3 - val7
+                           - 2 * val8 - val9) >> 3;
+                gy = L_ABS(val1 + 2 * val4 + val7
+                             - val3 - 2 * val6 - val9) >> 3;
+                vald = L_MIN(255, gx + gy);
+            }
+            SET_DATA_BYTE(lined, j, vald);
+        }
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                   Two-sided edge gradient filter                     *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixTwoSidedEdgeFilter()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              orientflag (L_HORIZONTAL_EDGES, L_VERTICAL_EDGES)
+ *      Return: pixd (8 bpp, edges are brighter), or null on error
+ *
+ *  Notes:
+ *      (1) For detecting vertical edges, this considers the
+ *          difference of the central pixel from those on the left
+ *          and right.  For situations where the gradient is the same
+ *          sign on both sides, this computes and stores the minimum
+ *          (absolute value of the) difference.  The reason for
+ *          checking the sign is that we are looking for pixels within
+ *          a transition.  By contrast, for single pixel noise, the pixel
+ *          value is either larger than or smaller than its neighbors,
+ *          so the gradient would change direction on each side.  Horizontal
+ *          edges are handled similarly, looking for vertical gradients.
+ *      (2) To generate a binary image of the edges, threshold
+ *          the result using pixThresholdToBinary().  If the high
+ *          edge values are to be fg (1), invert after running
+ *          pixThresholdToBinary().
+ *      (3) This runs at about 60 Mpix/sec on a 3 GHz processor.
+ *          It is about 30% faster than Sobel, and the results are
+ *          similar.
+ */
+PIX *
+pixTwoSidedEdgeFilter(PIX     *pixs,
+                      l_int32  orientflag)
+{
+l_int32    w, h, d, i, j, wpls, wpld;
+l_int32    cval, rval, bval, val, lgrad, rgrad, tgrad, bgrad;
+l_uint32  *datas, *lines, *datad, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixTwoSidedEdgeFilter");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (orientflag != L_HORIZONTAL_EDGES && orientflag != L_VERTICAL_EDGES)
+        return (PIX *)ERROR_PTR("invalid orientflag", procName, NULL);
+
+    pixd = pixCreateTemplate(pixs);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    if (orientflag == L_VERTICAL_EDGES) {
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            cval = GET_DATA_BYTE(lines, 1);
+            lgrad = cval - GET_DATA_BYTE(lines, 0);
+            for (j = 1; j < w - 1; j++) {
+                rval = GET_DATA_BYTE(lines, j + 1);
+                rgrad = rval - cval;
+                if (lgrad * rgrad > 0) {
+                    if (lgrad < 0)
+                        val = -L_MAX(lgrad, rgrad);
+                    else
+                        val = L_MIN(lgrad, rgrad);
+                    SET_DATA_BYTE(lined, j, val);
+                }
+                lgrad = rgrad;
+                cval = rval;
+            }
+        }
+    }
+    else {  /* L_HORIZONTAL_EDGES) */
+        for (j = 0; j < w; j++) {
+            lines = datas + wpls;
+            cval = GET_DATA_BYTE(lines, j);  /* for line 1 */
+            tgrad = cval - GET_DATA_BYTE(datas, j);
+            for (i = 1; i < h - 1; i++) {
+                lines += wpls;  /* for line i + 1 */
+                lined = datad + i * wpld;
+                bval = GET_DATA_BYTE(lines, j);
+                bgrad = bval - cval;
+                if (tgrad * bgrad > 0) {
+                    if (tgrad < 0)
+                        val = -L_MAX(tgrad, bgrad);
+                    else
+                        val = L_MIN(tgrad, bgrad);
+                    SET_DATA_BYTE(lined, j, val);
+                }
+                tgrad = bgrad;
+                cval = bval;
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                   Measurement of edge smoothness                     *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixMeasureEdgeSmoothness()
+ *
+ *      Input:  pixs (1 bpp)
+ *              side (L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT)
+ *              minjump (minimum jump to be counted; >= 1)
+ *              minreversal (minimum reversal size for new peak or valley)
+ *              &jpl (<optional return> jumps/length: number of jumps,
+ *                    normalized to length of component side)
+ *              &jspl (<optional return> jumpsum/length: sum of all
+ *                     sufficiently large jumps, normalized to length
+ *                     of component side)
+ *              &rpl (<optional return> reversals/length: number of
+ *                    peak-to-valley or valley-to-peak reversals,
+ *                    normalized to length of component side)
+ *              debugfile (<optional> displays constructed edge; use NULL
+ *                         for no output)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This computes three measures of smoothness of the edge of a
+ *          connected component:
+ *            * jumps/length: (jpl) the number of jumps of size >= @minjump,
+ *              normalized to the length of the side
+ *            * jump sum/length: (jspl) the sum of all jump lengths of
+ *              size >= @minjump, normalized to the length of the side
+ *            * reversals/length: (rpl) the number of peak <--> valley
+ *              reversals, using @minreverse as a minimum deviation of
+ *              the peak or valley from its preceding extremum,
+ *              normalized to the length of the side
+ *      (2) The input pix should be a single connected component, but
+ *          this is not required.
+ */
+l_int32
+pixMeasureEdgeSmoothness(PIX         *pixs,
+                         l_int32      side,
+                         l_int32      minjump,
+                         l_int32      minreversal,
+                         l_float32   *pjpl,
+                         l_float32   *pjspl,
+                         l_float32   *prpl,
+                         const char  *debugfile)
+{
+l_int32  i, n, val, nval, diff, njumps, jumpsum, nreversal;
+NUMA    *na, *nae;
+
+    PROCNAME("pixMeasureEdgeSmoothness");
+
+    if (pjpl) *pjpl = 0.0;
+    if (pjspl) *pjspl = 0.0;
+    if (prpl) *prpl = 0.0;
+    if (!pjpl && !pjspl && !prpl && !debugfile)
+        return ERROR_INT("no output requested", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (side != L_FROM_LEFT && side != L_FROM_RIGHT &&
+        side != L_FROM_TOP && side != L_FROM_BOT)
+        return ERROR_INT("invalid side", procName, 1);
+    if (minjump < 1)
+        return ERROR_INT("invalid minjump; must be >= 1", procName, 1);
+    if (minreversal < 1)
+        return ERROR_INT("invalid minreversal; must be >= 1", procName, 1);
+
+    if ((na = pixGetEdgeProfile(pixs, side, debugfile)) == NULL)
+        return ERROR_INT("edge profile not made", procName, 1);
+    if ((n = numaGetCount(na)) < 2) {
+        numaDestroy(&na);
+        return 0;
+    }
+
+    if (pjpl || pjspl) {
+        jumpsum = 0;
+        njumps = 0;
+        numaGetIValue(na, 0, &val);
+        for (i = 1; i < n; i++) {
+            numaGetIValue(na, i, &nval);
+            diff = L_ABS(nval - val);
+            if (diff >= minjump) {
+                njumps++;
+                jumpsum += diff;
+            }
+            val = nval;
+        }
+        if (pjpl)
+            *pjpl = (l_float32)njumps / (l_float32)(n - 1);
+        if (pjspl)
+            *pjspl = (l_float32)jumpsum / (l_float32)(n - 1);
+    }
+
+    if (prpl) {
+        nae = numaFindExtrema(na, minreversal);
+        nreversal = numaGetCount(nae) - 1;
+        *prpl = (l_float32)nreversal / (l_float32)(n - 1);
+        numaDestroy(&nae);
+    }
+
+    numaDestroy(&na);
+    return 0;
+}
+
+
+/*!
+ *  pixGetEdgeProfile()
+ *
+ *      Input:  pixs (1 bpp)
+ *              side (L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT)
+ *              debugfile (<optional> displays constructed edge; use NULL
+ *                         for no output)
+ *      Return: na (of fg edge pixel locations), or null on error
+ */
+NUMA *
+pixGetEdgeProfile(PIX         *pixs,
+                  l_int32      side,
+                  const char  *debugfile)
+{
+l_int32   x, y, w, h, loc, index, ival;
+l_uint32  val;
+NUMA     *na;
+PIX      *pixt;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixGetEdgeProfile");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (NUMA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (side != L_FROM_LEFT && side != L_FROM_RIGHT &&
+        side != L_FROM_TOP && side != L_FROM_BOT)
+        return (NUMA *)ERROR_PTR("invalid side", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (side == L_FROM_LEFT || side == L_FROM_RIGHT)
+        na = numaCreate(h);
+    else
+        na = numaCreate(w);
+    if (side == L_FROM_LEFT) {
+        pixGetLastOffPixelInRun(pixs, 0, 0, L_FROM_LEFT, &loc);
+        loc = (loc == w - 1) ? 0 : loc + 1;  /* back to the left edge */
+        numaAddNumber(na, loc);
+        for (y = 1; y < h; y++) {
+            pixGetPixel(pixs, loc, y, &val);
+            if (val == 1) {
+                pixGetLastOnPixelInRun(pixs, loc, y, L_FROM_RIGHT, &loc);
+            } else {
+                pixGetLastOffPixelInRun(pixs, loc, y, L_FROM_LEFT, &loc);
+                loc = (loc == w - 1) ? 0 : loc + 1;
+            }
+            numaAddNumber(na, loc);
+        }
+    }
+    else if (side == L_FROM_RIGHT) {
+        pixGetLastOffPixelInRun(pixs, w - 1, 0, L_FROM_RIGHT, &loc);
+        loc = (loc == 0) ? w - 1 : loc - 1;  /* back to the right edge */
+        numaAddNumber(na, loc);
+        for (y = 1; y < h; y++) {
+            pixGetPixel(pixs, loc, y, &val);
+            if (val == 1) {
+                pixGetLastOnPixelInRun(pixs, loc, y, L_FROM_LEFT, &loc);
+            } else {
+                pixGetLastOffPixelInRun(pixs, loc, y, L_FROM_RIGHT, &loc);
+                loc = (loc == 0) ? w - 1 : loc - 1;
+            }
+            numaAddNumber(na, loc);
+        }
+    }
+    else if (side == L_FROM_TOP) {
+        pixGetLastOffPixelInRun(pixs, 0, 0, L_FROM_TOP, &loc);
+        loc = (loc == h - 1) ? 0 : loc + 1;  /* back to the top edge */
+        numaAddNumber(na, loc);
+        for (x = 1; x < w; x++) {
+            pixGetPixel(pixs, x, loc, &val);
+            if (val == 1) {
+                pixGetLastOnPixelInRun(pixs, x, loc, L_FROM_BOT, &loc);
+            } else {
+                pixGetLastOffPixelInRun(pixs, x, loc, L_FROM_TOP, &loc);
+                loc = (loc == h - 1) ? 0 : loc + 1;
+            }
+            numaAddNumber(na, loc);
+        }
+    }
+    else {  /* side == L_FROM_BOT */
+        pixGetLastOffPixelInRun(pixs, 0, h - 1, L_FROM_BOT, &loc);
+        loc = (loc == 0) ? h - 1 : loc - 1;  /* back to the bottom edge */
+        numaAddNumber(na, loc);
+        for (x = 1; x < w; x++) {
+            pixGetPixel(pixs, x, loc, &val);
+            if (val == 1) {
+                pixGetLastOnPixelInRun(pixs, x, loc, L_FROM_TOP, &loc);
+            } else {
+                pixGetLastOffPixelInRun(pixs, x, loc, L_FROM_BOT, &loc);
+                loc = (loc == 0) ? h - 1 : loc - 1;
+            }
+            numaAddNumber(na, loc);
+        }
+    }
+
+    if (debugfile) {
+        pixt = pixConvertTo8(pixs, TRUE);
+        cmap = pixGetColormap(pixt);
+        pixcmapAddColor(cmap, 255, 0, 0);
+        index = pixcmapGetCount(cmap) - 1;
+        if (side == L_FROM_LEFT || side == L_FROM_RIGHT) {
+            for (y = 0; y < h; y++) {
+                numaGetIValue(na, y, &ival);
+                pixSetPixel(pixt, ival, y, index);
+            }
+        } else {  /* L_FROM_TOP or L_FROM_BOT */
+            for (x = 0; x < w; x++) {
+                numaGetIValue(na, x, &ival);
+                pixSetPixel(pixt, x, ival, index);
+            }
+        }
+        pixWrite(debugfile, pixt, IFF_PNG);
+        pixDestroy(&pixt);
+    }
+
+    return na;
+}
+
+
+/*
+ *  pixGetLastOffPixelInRun()
+ *
+ *      Input:  pixs (1 bpp)
+ *              x, y (starting location)
+ *              direction (L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT)
+ *              &loc (<return> location in scan direction coordinate
+ *                    of last OFF pixel found)
+ *      Return: na (of fg edge pixel locations), or null on error
+ *
+ *  Notes:
+ *      (1) Search starts from the pixel at (x, y), which is OFF.
+ *      (2) It returns the location in the scan direction of the last
+ *          pixel in the current run that is OFF.
+ *      (3) The interface for these pixel run functions is cleaner when
+ *          you ask for the last pixel in the current run, rather than the
+ *          first pixel of opposite polarity that is found, because the
+ *          current run may go to the edge of the image, in which case
+ *          no pixel of opposite polarity is found.
+ */
+l_int32
+pixGetLastOffPixelInRun(PIX      *pixs,
+                        l_int32   x,
+                        l_int32   y,
+                        l_int32   direction,
+                        l_int32  *ploc)
+{
+l_int32   loc, w, h;
+l_uint32  val;
+
+    PROCNAME("pixGetLastOffPixelInRun");
+
+    if (!ploc)
+        return ERROR_INT("&loc not defined", procName, 1);
+    *ploc = 0;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs undefined or not 1 bpp", procName, 1);
+    if (direction != L_FROM_LEFT && direction != L_FROM_RIGHT &&
+        direction != L_FROM_TOP && direction != L_FROM_BOT)
+        return ERROR_INT("invalid side", procName, 1);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (direction == L_FROM_LEFT) {
+        for (loc = x; loc < w; loc++) {
+            pixGetPixel(pixs, loc, y, &val);
+            if (val == 1)
+                break;
+        }
+        *ploc = loc - 1;
+    } else if (direction == L_FROM_RIGHT) {
+        for (loc = x; loc >= 0; loc--) {
+            pixGetPixel(pixs, loc, y, &val);
+            if (val == 1)
+                break;
+        }
+        *ploc = loc + 1;
+    }
+    else if (direction == L_FROM_TOP) {
+        for (loc = y; loc < h; loc++) {
+            pixGetPixel(pixs, x, loc, &val);
+            if (val == 1)
+                break;
+        }
+        *ploc = loc - 1;
+    }
+    else if (direction == L_FROM_BOT) {
+        for (loc = y; loc >= 0; loc--) {
+            pixGetPixel(pixs, x, loc, &val);
+            if (val == 1)
+                break;
+        }
+        *ploc = loc + 1;
+    }
+    return 0;
+}
+
+
+/*
+ *  pixGetLastOnPixelInRun()
+ *
+ *      Input:  pixs (1 bpp)
+ *              x, y (starting location)
+ *              direction (L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT)
+ *              &loc (<return> location in scan direction coordinate
+ *                    of first ON pixel found)
+ *      Return: na (of fg edge pixel locations), or null on error
+ *
+ *  Notes:
+ *      (1) Search starts from the pixel at (x, y), which is ON.
+ *      (2) It returns the location in the scan direction of the last
+ *          pixel in the current run that is ON.
+ */
+l_int32
+pixGetLastOnPixelInRun(PIX      *pixs,
+                       l_int32   x,
+                       l_int32   y,
+                       l_int32   direction,
+                       l_int32  *ploc)
+{
+l_int32   loc, w, h;
+l_uint32  val;
+
+    PROCNAME("pixLastOnPixelInRun");
+
+    if (!ploc)
+        return ERROR_INT("&loc not defined", procName, 1);
+    *ploc = 0;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs undefined or not 1 bpp", procName, 1);
+    if (direction != L_FROM_LEFT && direction != L_FROM_RIGHT &&
+        direction != L_FROM_TOP && direction != L_FROM_BOT)
+        return ERROR_INT("invalid side", procName, 1);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (direction == L_FROM_LEFT) {
+        for (loc = x; loc < w; loc++) {
+            pixGetPixel(pixs, loc, y, &val);
+            if (val == 0)
+                break;
+        }
+        *ploc = loc - 1;
+    } else if (direction == L_FROM_RIGHT) {
+        for (loc = x; loc >= 0; loc--) {
+            pixGetPixel(pixs, loc, y, &val);
+            if (val == 0)
+                break;
+        }
+        *ploc = loc + 1;
+    }
+    else if (direction == L_FROM_TOP) {
+        for (loc = y; loc < h; loc++) {
+            pixGetPixel(pixs, x, loc, &val);
+            if (val == 0)
+                break;
+        }
+        *ploc = loc - 1;
+    }
+    else if (direction == L_FROM_BOT) {
+        for (loc = y; loc >= 0; loc--) {
+            pixGetPixel(pixs, x, loc, &val);
+            if (val == 0)
+                break;
+        }
+        *ploc = loc + 1;
+    }
+    return 0;
+}
diff --git a/src/encoding.c b/src/encoding.c
new file mode 100644 (file)
index 0000000..90dd9ab
--- /dev/null
@@ -0,0 +1,639 @@
+/*====================================================================*
+ -  Copyright (C) 2001 Leptonica.  All rights reserved.
+ -  This software is distributed in the hope that it will be
+ -  useful, but with NO WARRANTY OF ANY KIND.
+ -  No author or distributor accepts responsibility to anyone for the
+ -  consequences of using this software, or for whether it serves any
+ -  particular purpose or works at all, unless he or she says so in
+ -  writing.  Everyone is granted permission to copy, modify and
+ -  redistribute this source code, for commercial or non-commercial
+ -  purposes, with the following restrictions: (1) the origin of this
+ -  source code must not be misrepresented; (2) modified versions must
+ -  be plainly marked as such; and (3) this notice may not be removed
+ -  or altered from any source or modified source distribution.
+ *====================================================================*/
+
+/*
+ *  encodings.c
+ *
+ *    Base64
+ *        char           *encodeBase64()
+ *        l_uint8        *decodeBase64()
+ *        static l_int32  isBase64()
+ *        static l_int32 *genReverseTab64()
+ *        static void     byteConvert3to4()
+ *        static void     byteConvert4to3()
+ *
+ *    Ascii85
+ *        char           *encodeAscii85()
+ *        l_uint8        *decodeAscii85()
+ *        static l_int32  convertChunkToAscii85()
+ *
+ *    String reformatting for base 64 encoded data
+ *        char           *reformatPacked64()
+ *
+ *  Base64 encoding is useful for encding binary data in a restricted set of
+ *  64 printable ascii symbols, that includes the 62 alphanumerics and '+'
+ *  and '/'.  Notably it does not include quotes, so that base64 encoded
+ *  strings can be used in situations where quotes are used for formatting.
+ *  64 symbols was chosen because it is the smallest number that can be used
+ *  in 4-for-3 byte encoding of binary data:
+ *         log2(64) / log2(256) = 0.75 = 3/4
+ *
+ *  Ascii85 encoding is used in PostScript and some pdf files for
+ *  representing binary data (for example, a compressed image) in printable
+ *  ascii symbols.  It has a dictionary of 85 symbols; 85 was chosen because
+ *  it is the smallest number that can be used in 5-for-4 byte encoding
+ *  of binary data (256 possible input values).  This can be seen from
+ *  the max information content in such a sequence:
+ *         log2(84) / log2(256) = 0.799 < 4/5
+ *         log2(85) / log2(256) = 0.801 > 4/5
+ */
+
+#include <ctype.h>
+#include "allheaders.h"
+
+    /* Base64 encoding table in string representation */
+static const l_int32  MAX_BASE64_LINE   = 72;  /* max line length base64 */
+static const char *tablechar64 =
+             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+             "abcdefghijklmnopqrstuvwxyz"
+             "0123456789+/";
+
+static l_int32 isBase64(char);
+static l_int32 *genReverseTab64(void);
+static void byteConvert3to4(l_uint8 *in3, l_uint8 *out4);
+static void byteConvert4to3(l_uint8 *in4, l_uint8 *out3);
+
+    /* Ascii85 encoding */
+static const l_int32  MAX_ASCII85_LINE   = 64;  /* max line length ascii85 */
+static const l_uint32  power85[5] = {1,
+                                     85,
+                                     85 * 85,
+                                     85 * 85 * 85,
+                                     85 * 85 * 85 * 85};
+
+static l_int32 convertChunkToAscii85(l_uint8 *inarray, l_int32 insize,
+                                     l_int32 *pindex, char *outbuf,
+                                     l_int32 *pnbout);
+
+
+/*-------------------------------------------------------------*
+ *      Utility for encoding and decoding data with base64     *
+ *-------------------------------------------------------------*/
+/*!
+ *  encodeBase64()
+ *
+ *      Input:  inarray (input binary data)
+ *              insize (number of bytes in input array)
+ *              &outsize (<return> number of bytes in output char array)
+ *      Return: chara (with MAX_BASE64_LINE characters + \n in each line)
+ *
+ *  Notes:
+ *      (1) The input character data is unrestricted binary.
+ *          The ouput encoded data consists of the 64 characters
+ *          in the base64 set, plus newlines and the pad character '='.
+ */
+char *
+encodeBase64(l_uint8  *inarray,
+             l_int32   insize,
+             l_int32  *poutsize)
+{
+char     *chara;
+l_uint8  *bytea;
+l_uint8   array3[3], array4[4];
+l_int32   outsize, i, j, index, linecount;
+
+    PROCNAME("encodeBase64");
+
+    if (!poutsize)
+        return (char *)ERROR_PTR("&outsize not defined", procName, NULL);
+    *poutsize = 0;
+    if (!inarray)
+        return (char *)ERROR_PTR("inarray not defined", procName, NULL);
+    if (insize <= 0)
+        return (char *)ERROR_PTR("insize not > 0", procName, NULL);
+
+        /* The output array is padded to a multiple of 4 bytes, not
+         * counting the newlines.  We just need to allocate a large
+         * enough array, and add 4 bytes to make sure it is big enough. */
+    outsize = 4 * ((insize + 2) / 3);  /* without newlines */
+    outsize += outsize / MAX_BASE64_LINE + 4;  /* with the newlines */
+    if ((chara = (char *)LEPT_CALLOC(outsize, sizeof(char))) == NULL)
+        return (char *)ERROR_PTR("chara not made", procName, NULL);
+
+        /* Read all the input data, and convert in sets of 3 input
+         * bytes --> 4 output bytes. */
+    i = index = linecount = 0;
+    bytea = inarray;
+    while (insize--) {
+        if (linecount == MAX_BASE64_LINE) {
+            chara[index++] = '\n';
+            linecount = 0;
+        }
+        array3[i++] = *bytea++;
+        if (i == 3) {  /* convert 3 to 4 and save */
+            byteConvert3to4(array3, array4);
+            for (j = 0; j < 4; j++)
+                chara[index++] = tablechar64[array4[j]];
+            i = 0;
+            linecount += 4;
+        }
+    }
+
+        /* Suppose 1 or 2 bytes has been read but not yet processed.
+         * If 1 byte has been read, this will generate 2 bytes of
+         * output, with 6 bits to the first byte and 2 bits to the second.
+         * We will add two bytes of '=' for padding.
+         * If 2 bytes has been read, this will generate 3 bytes of output,
+         * with 6 bits to the first 2 bytes and 4 bits to the third, and
+         * we add a fourth padding byte ('='). */
+    if (i > 0) {  /* left-over 1 or 2 input bytes */
+        for (j = i; j < 3; j++)
+            array3[j] = '\0';  /* zero the remaining input bytes */
+        byteConvert3to4(array3, array4);
+        for (j = 0; j <= i; j++)
+            chara[index++] = tablechar64[array4[j]];
+        for (j = i + 1; j < 4; j++)
+            chara[index++] = '=';
+    }
+    *poutsize = index;
+
+    return chara;
+}
+
+
+/*!
+ *  decodeBase64()
+ *
+ *      Input:  inarray (input encoded char data, with 72 chars/line))
+ *              insize (number of bytes in input array)
+ *              &outsize (<return> number of bytes in output byte array)
+ *      Return: bytea (decoded byte data), or null on error
+ *
+ *  Notes:
+ *      (1) The input character data should have only 66 different characters:
+ *          The 64 character set for base64 encoding, plus the pad
+ *          character '=' and newlines for formatting with fixed line
+ *          lengths.  If there are any other characters, the decoder
+ *          will declare the input data to be invalid and return NULL.
+ *      (2) The decoder ignores newlines and, for a valid input string,
+ *          stops reading input when a pad byte is found.
+ */
+l_uint8 *
+decodeBase64(const char  *inarray,
+             l_int32      insize,
+             l_int32     *poutsize)
+{
+char      inchar;
+l_uint8  *bytea;
+l_uint8   array3[3], array4[4];
+l_int32  *rtable64;
+l_int32   i, j, outsize, in_index, out_index;
+
+    PROCNAME("decodeBase64");
+
+    if (!poutsize)
+        return (l_uint8 *)ERROR_PTR("&outsize not defined", procName, NULL);
+    *poutsize = 0;
+    if (!inarray)
+        return (l_uint8 *)ERROR_PTR("inarray not defined", procName, NULL);
+    if (insize <= 0)
+        return (l_uint8 *)ERROR_PTR("insize not > 0", procName, NULL);
+
+        /* Validate the input data */
+    for (i = 0; i < insize; i++) {
+        inchar = inarray[i];
+        if (inchar == '\n') continue;
+        if (isBase64(inchar) == 0 && inchar != '=')
+            return (l_uint8 *)ERROR_PTR("invalid char in inarray",
+                                        procName, NULL);
+    }
+
+        /* The input array typically is made with a newline every
+         * MAX_BASE64_LINE input bytes.  However, as a printed string, the
+         * newlines would be stripped.  So when we allocate the output
+         * array, assume the input array is all data, but strip
+         * out the newlines during decoding.  This guarantees that
+         * the allocated array is large enough. */
+    outsize = 3 * ((insize + 3) / 4) + 4;
+    if ((bytea = (l_uint8 *)LEPT_CALLOC(outsize, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("bytea not made", procName, NULL);
+
+        /* The number of encoded input data bytes is always a multiple of 4.
+         * Read all the data, until you reach either the end or
+         * the first pad character '='.  The data is processed in
+         * units of 4 input bytes, generating 3 output decoded bytes
+         * of binary data.  Newlines are ignored.  If there are no
+         * pad bytes, i == 0 at the end of this section. */
+    rtable64 = genReverseTab64();
+    i = in_index = out_index = 0;
+    for (in_index = 0; in_index < insize; in_index++) {
+        inchar = inarray[in_index];
+        if (inchar == '\n') continue;
+        if (inchar == '=') break;
+        array4[i++] = rtable64[(unsigned char)inchar];
+        if (i < 4) {
+            continue;
+        } else {  /* i == 4; convert 4 to 3 and save */
+            byteConvert4to3(array4, array3);
+            for (j = 0; j < 3; j++)
+                bytea[out_index++] = array3[j];
+            i = 0;
+        }
+    }
+
+        /* If i > 0, we ran into pad bytes ('=').  If i == 2, there are
+         * two input pad bytes and one output data byte.  If i == 3,
+         * there is one input pad byte and two output data bytes. */
+    if (i > 0) {
+        for (j = i; j < 4; j++)
+            array4[j] = '\0';  /* zero the remaining input bytes */
+        byteConvert4to3(array4, array3);
+        for (j = 0; j < i - 1; j++)
+            bytea[out_index++] = array3[j];
+    }
+    *poutsize = out_index;
+
+    LEPT_FREE(rtable64);
+    return bytea;
+}
+
+
+/*!
+ *  isBase64()
+ */
+static l_int32
+isBase64(char  c)
+{
+    return (isalnum(((int)c)) || ((c) == '+') || ((c) == '/')) ? 1 : 0;
+}
+
+/*!
+ *  genReverseTab64()
+ */
+static l_int32 *
+genReverseTab64()
+{
+l_int32   i;
+l_int32  *rtable64;
+
+    rtable64 = (l_int32 *)LEPT_CALLOC(128, sizeof(l_int32));
+    for (i = 0; i < 64; i++) {
+        rtable64[(unsigned char)tablechar64[i]] = i;
+    }
+    return rtable64;
+}
+
+/*!
+ *  byteConvert3to4()
+ */
+static void
+byteConvert3to4(l_uint8  *in3,
+                l_uint8  *out4)
+{
+    out4[0] = in3[0] >> 2;
+    out4[1] = ((in3[0] & 0x03) << 4) | (in3[1] >> 4);
+    out4[2] = ((in3[1] & 0x0f) << 2) | (in3[2] >> 6);
+    out4[3] = in3[2] & 0x3f;
+    return;
+}
+
+/*!
+ *  byteConvert4to3()
+ */
+static void
+byteConvert4to3(l_uint8  *in4,
+                l_uint8  *out3)
+{
+    out3[0] = (in4[0] << 2) | (in4[1] >> 4);
+    out3[1] = ((in4[1] & 0x0f) << 4) | (in4[2] >> 2);
+    out3[2] = ((in4[2] & 0x03) << 6) | in4[3];
+    return;
+}
+
+
+/*-------------------------------------------------------------*
+ *      Utility for encoding and decoding data with ascii85    *
+ *-------------------------------------------------------------*/
+/*!
+ *  encodeAscii85()
+ *
+ *      Input:  inarray (input data)
+ *              insize (number of bytes in input array)
+ *              &outsize (<return> number of bytes in output char array)
+ *      Return: chara (with 64 characters + \n in each line)
+ *
+ *  Notes:
+ *      (1) Ghostscript has a stack break if the last line of
+ *          data only has a '>', so we avoid the problem by
+ *          always putting '~>' on the last line.
+ */
+char *
+encodeAscii85(l_uint8  *inarray,
+              l_int32   insize,
+              l_int32  *poutsize)
+{
+char    *chara;
+char    *outbuf;
+l_int32  maxsize, i, index, outindex, linecount, nbout, eof;
+
+    PROCNAME("encodeAscii85");
+
+    if (!poutsize)
+        return (char *)ERROR_PTR("&outsize not defined", procName, NULL);
+    *poutsize = 0;
+    if (!inarray)
+        return (char *)ERROR_PTR("inarray not defined", procName, NULL);
+    if (insize <= 0)
+        return (char *)ERROR_PTR("insize not > 0", procName, NULL);
+
+        /* Accumulate results in char array */
+    maxsize = (l_int32)(80. + (insize * 5. / 4.) *
+                        (1. + 2. / MAX_ASCII85_LINE));
+    if ((chara = (char *)LEPT_CALLOC(maxsize, sizeof(char))) == NULL)
+        return (char *)ERROR_PTR("chara not made", procName, NULL);
+    if ((outbuf = (char *)LEPT_CALLOC(8, sizeof(char))) == NULL)
+        return (char *)ERROR_PTR("outbuf not made", procName, NULL);
+
+    linecount = 0;
+    index = 0;
+    outindex = 0;
+    while (1) {
+        eof = convertChunkToAscii85(inarray, insize, &index, outbuf, &nbout);
+        for (i = 0; i < nbout; i++) {
+            chara[outindex++] = outbuf[i];
+            linecount++;
+            if (linecount >= MAX_ASCII85_LINE) {
+                chara[outindex++] = '\n';
+                linecount = 0;
+            }
+        }
+        if (eof == TRUE) {
+            if (linecount != 0)
+                chara[outindex++] = '\n';
+            chara[outindex++] = '~';
+            chara[outindex++] = '>';
+            chara[outindex++] = '\n';
+            break;
+        }
+    }
+
+    LEPT_FREE(outbuf);
+    *poutsize = outindex;
+    return chara;
+}
+
+
+/*!
+ *  convertChunkToAscii85()
+ *
+ *      Input:  inarray (input data)
+ *              insize  (number of bytes in input array)
+ *              &index (use and <return> -- ptr)
+ *              outbuf (holds 8 ascii chars; we use no more than 7)
+ *              &nbsout (<return> number of bytes written to outbuf)
+ *      Return: boolean for eof (0 if more data, 1 if end of file)
+ *
+ *  Notes:
+ *      (1) Attempts to read 4 bytes and write 5.
+ *      (2) Writes 1 byte if the value is 0.
+ */
+static l_int32
+convertChunkToAscii85(l_uint8  *inarray,
+                      l_int32   insize,
+                      l_int32  *pindex,
+                      char     *outbuf,
+                      l_int32  *pnbout)
+{
+l_uint8   inbyte;
+l_uint32  inword, val;
+l_int32   eof, index, nread, nbout, i;
+
+    eof = FALSE;
+    index = *pindex;
+    nread = L_MIN(4, (insize - index));
+    if (insize == index + nread)
+        eof = TRUE;
+    *pindex += nread;  /* save new index */
+
+        /* Read input data and save in l_uint32 */
+    inword = 0;
+    for (i = 0; i < nread; i++) {
+        inbyte = inarray[index + i];
+        inword += inbyte << (8 * (3 - i));
+    }
+
+#if 0
+    fprintf(stderr, "index = %d, nread = %d\n", index, nread);
+    fprintf(stderr, "inword = %x\n", inword);
+    fprintf(stderr, "eof = %d\n", eof);
+#endif
+
+        /* Special case: output 1 byte only */
+    if (inword == 0) {
+        outbuf[0] = 'z';
+        nbout = 1;
+    } else { /* output nread + 1 bytes */
+        for (i = 4; i >= 4 - nread; i--) {
+            val = inword / power85[i];
+            outbuf[4 - i] = (l_uint8)(val + '!');
+            inword -= val * power85[i];
+        }
+        nbout = nread + 1;
+    }
+    *pnbout = nbout;
+
+    return eof;
+}
+
+
+/*!
+ *  decodeAscii85()
+ *
+ *      Input:  inarray (ascii85 input data)
+ *              insize (number of bytes in input array)
+ *              &outsize (<return> number of bytes in output l_uint8 array)
+ *      Return: outarray (binary)
+ *
+ *  Notes:
+ *      (1) We assume the data is properly encoded, so we do not check
+ *          for invalid characters or the final '>' character.
+ *      (2) We permit whitespace to be added to the encoding in an
+ *          arbitrary way.
+ */
+l_uint8 *
+decodeAscii85(char     *inarray,
+              l_int32   insize,
+              l_int32  *poutsize)
+{
+char      inc;
+char     *pin;
+l_uint8   val;
+l_uint8  *outa;
+l_int32   maxsize, ocount, bytecount, index;
+l_uint32  oword;
+
+    PROCNAME("decodeAscii85");
+
+    if (!poutsize)
+        return (l_uint8 *)ERROR_PTR("&outsize not defined", procName, NULL);
+    *poutsize = 0;
+    if (!inarray)
+        return (l_uint8 *)ERROR_PTR("inarray not defined", procName, NULL);
+    if (insize <= 0)
+        return (l_uint8 *)ERROR_PTR("insize not > 0", procName, NULL);
+
+        /* Accumulate results in outa */
+    maxsize = (l_int32)(80. + (insize * 4. / 5.));  /* plenty big */
+    if ((outa = (l_uint8 *)LEPT_CALLOC(maxsize, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("outa not made", procName, NULL);
+
+    pin = inarray;
+    ocount = 0;  /* byte index into outa */
+    oword = 0;
+    for (index = 0, bytecount = 0; index < insize; index++, pin++) {
+        inc = *pin;
+
+        if (inc == ' ' || inc == '\t' || inc == '\n' ||
+            inc == '\f' || inc == '\r' || inc == '\v')  /* ignore white space */
+            continue;
+
+        val = inc - '!';
+        if (val < 85) {
+            oword = oword * 85 + val;
+            if (bytecount < 4) {
+                bytecount++;
+            } else {  /* we have all 5 input chars for the oword */
+                outa[ocount] = (oword >> 24) & 0xff;
+                outa[ocount + 1] = (oword >> 16) & 0xff;
+                outa[ocount + 2] = (oword >> 8) & 0xff;
+                outa[ocount + 3] = oword & 0xff;
+                ocount += 4;
+                bytecount = 0;
+                oword = 0;
+            }
+        } else if (inc == 'z' && bytecount == 0) {
+            outa[ocount] = 0;
+            outa[ocount + 1] = 0;
+            outa[ocount + 2] = 0;
+            outa[ocount + 3] = 0;
+            ocount += 4;
+        } else if (inc == '~') {  /* end of data */
+            L_INFO(" %d extra bytes output\n", procName, bytecount - 1);
+            switch (bytecount) {
+            case 0:   /* normal eof */
+            case 1:   /* error */
+                break;
+            case 2:   /* 1 extra byte */
+                oword = oword * power85[3] + 0xffffff;
+                outa[ocount] = (oword >> 24) & 0xff;
+                break;
+            case 3:   /* 2 extra bytes */
+                oword = oword * power85[2] + 0xffff;
+                outa[ocount] = (oword >> 24) & 0xff;
+                outa[ocount + 1] = (oword >> 16) & 0xff;
+                break;
+            case 4:   /* 3 extra bytes */
+                oword = oword * 85 + 0xff;
+                outa[ocount] = (oword >> 24) & 0xff;
+                outa[ocount + 1] = (oword >> 16) & 0xff;
+                outa[ocount + 2] = (oword >> 8) & 0xff;
+                break;
+            }
+            if (bytecount > 1)
+                ocount += (bytecount - 1);
+            break;
+        }
+    }
+    *poutsize = ocount;
+
+    return outa;
+}
+
+
+/*-------------------------------------------------------------*
+ *       String reformatting for base 64 encoded data          *
+ *-------------------------------------------------------------*/
+/*!
+ *  reformatPacked64()
+ *
+ *      Input:  inarray (base64 encoded string with newlines)
+ *              insize (number of bytes in input array)
+ *              leadspace (number of spaces in each line before the data)
+ *              linechars (number of bytes of data in each line; multiple of 4)
+ *              addquotes (1 to add quotes to each line of data; 0 to skip)
+ *              &outsize (<return> number of bytes in output char array)
+ *      Return: outarray (ascii)
+ *
+ *  Notes:
+ *      (1) Each line in the output array has @leadspace space characters,
+ *          followed optionally by a double-quote, followed by @linechars
+ *          bytes of base64 data, followed optionally by a double-quote,
+ *          followed by a newline.
+ *      (2) This can be used to convert a base64 encoded string to a
+ *          string formatted for inclusion in a C source file.
+ */
+char *
+reformatPacked64(char     *inarray,
+                 l_int32   insize,
+                 l_int32   leadspace,
+                 l_int32   linechars,
+                 l_int32   addquotes,
+                 l_int32  *poutsize)
+{
+char    *flata, *outa;
+l_int32  i, j, flatindex, flatsize, outindex, nlines, linewithpad, linecount;
+
+    PROCNAME("reformatPacked64");
+
+    if (!poutsize)
+        return (char *)ERROR_PTR("&outsize not defined", procName, NULL);
+    *poutsize = 0;
+    if (!inarray)
+        return (char *)ERROR_PTR("inarray not defined", procName, NULL);
+    if (insize <= 0)
+        return (char *)ERROR_PTR("insize not > 0", procName, NULL);
+    if (leadspace < 0)
+        return (char *)ERROR_PTR("leadspace must be >= 0", procName, NULL);
+    if (linechars % 4)
+        return (char *)ERROR_PTR("linechars % 4 must be 0", procName, NULL);
+
+        /* Remove all white space */
+    if ((flata = (char *)LEPT_CALLOC(insize, sizeof(char))) == NULL)
+        return (char *)ERROR_PTR("flata not made", procName, NULL);
+    for (i = 0, flatindex = 0; i < insize; i++) {
+        if (isBase64(inarray[i]) || inarray[i] == '=')
+            flata[flatindex++] = inarray[i];
+    }
+
+        /* Generate output string */
+    flatsize = flatindex;
+    nlines = (flatsize + linechars - 1) / linechars;
+    linewithpad = leadspace + linechars + 1;  /* including newline */
+    if (addquotes) linewithpad += 2;
+    if ((outa = (char *)LEPT_CALLOC(nlines * linewithpad, sizeof(char)))
+        == NULL)
+        return (char *)ERROR_PTR("outa not made", procName, NULL);
+    for (j = 0, outindex = 0; j < leadspace; j++)
+        outa[outindex++] = ' ';
+    if (addquotes) outa[outindex++] = '"';
+    for (i = 0, linecount = 0; i < flatsize; i++) {
+        if (linecount == linechars) {
+            if (addquotes) outa[outindex++] = '"';
+            outa[outindex++] = '\n';
+            for (j = 0; j < leadspace; j++)
+                outa[outindex++] = ' ';
+            if (addquotes) outa[outindex++] = '"';
+            linecount = 0;
+        }
+        outa[outindex++] = flata[i];
+        linecount++;
+    }
+    if (addquotes) outa[outindex++] = '"';
+    *poutsize = outindex;
+
+    LEPT_FREE(flata);
+    return outa;
+}
+
diff --git a/src/endianness.h b/src/endianness.h
new file mode 100644 (file)
index 0000000..e9eaba9
--- /dev/null
@@ -0,0 +1,11 @@
+#if !defined (L_BIG_ENDIAN) && !defined (L_LITTLE_ENDIAN)
+# if 0
+#  ifdef __BIG_ENDIAN__
+#   define L_BIG_ENDIAN
+#  else
+#   define L_LITTLE_ENDIAN
+#  endif
+# else
+#  define L_LITTLE_ENDIAN
+# endif
+#endif
diff --git a/src/endianness.h.dist b/src/endianness.h.dist
new file mode 100644 (file)
index 0000000..f9c2bf7
--- /dev/null
@@ -0,0 +1,4 @@
+#if !defined (L_BIG_ENDIAN) && !defined (L_LITTLE_ENDIAN)
+# define L_LITTLE_ENDIAN
+/* # define L_BIG_ENDIAN */
+#endif
diff --git a/src/endianness.h.in b/src/endianness.h.in
new file mode 100644 (file)
index 0000000..e6b1d45
--- /dev/null
@@ -0,0 +1,11 @@
+#if !defined (L_BIG_ENDIAN) && !defined (L_LITTLE_ENDIAN)
+# if @APPLE_UNIVERSAL_BUILD@
+#  ifdef __BIG_ENDIAN__
+#   define L_BIG_ENDIAN
+#  else
+#   define L_LITTLE_ENDIAN
+#  endif
+# else
+#  define @ENDIANNESS@
+# endif
+#endif
diff --git a/src/endiantest.c b/src/endiantest.c
new file mode 100644 (file)
index 0000000..e45f27f
--- /dev/null
@@ -0,0 +1,50 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *    endiantest.c
+ *
+ *    This test was contributed by Bill Janssen.  When used with the
+ *    gnu compiler, it allows efficient computation of the endian
+ *    flag as part of the normal compilation process.  As a result,
+ *    it is not necessary to set this flag either manually or
+ *    through the configure Makefile generator.
+ */
+
+#include <stdio.h>
+
+int main()
+{
+/* fprintf(stderr, "doing the test\n"); */
+    long v = 0x04030201;
+    if (*((unsigned char *)(&v)) == 0x04)
+        printf("L_BIG_ENDIAN\n");
+    else
+        printf("L_LITTLE_ENDIAN\n");
+    return 0;
+}
+
+
diff --git a/src/enhance.c b/src/enhance.c
new file mode 100644 (file)
index 0000000..11073d3
--- /dev/null
@@ -0,0 +1,2014 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  enhance.c
+ *
+ *      Gamma TRC (tone reproduction curve) mapping
+ *           PIX     *pixGammaTRC()
+ *           PIX     *pixGammaTRCMasked()
+ *           PIX     *pixGammaTRCWithAlpha()
+ *           NUMA    *numaGammaTRC()
+ *
+ *      Contrast enhancement
+ *           PIX     *pixContrastTRC()
+ *           PIX     *pixContrastTRCMasked()
+ *           NUMA    *numaContrastTRC()
+ *
+ *      Histogram equalization
+ *           PIX     *pixEqualizeTRC()
+ *           NUMA    *numaEqualizeTRC()
+ *
+ *      Generic TRC mapper
+ *           PIX     *pixTRCMap()
+ *
+ *      Unsharp-masking
+ *           PIX     *pixUnsharpMasking()
+ *           PIX     *pixUnsharpMaskingGray()
+ *           PIX     *pixUnsharpMaskingFast()
+ *           PIX     *pixUnsharpMaskingGrayFast()
+ *           PIX     *pixUnsharpMaskingGray1D()
+ *           PIX     *pixUnsharpMaskingGray2D()
+ *
+ *      Hue and saturation modification
+ *           PIX     *pixModifyHue()
+ *           PIX     *pixModifySaturation()
+ *           l_int32  pixMeasureSaturation()
+ *           PIX     *pixModifyBrightness()
+ *
+ *      Color shifting
+ *           PIX     *pixColorShiftRGB()
+ *
+ *      General multiplicative constant color transform
+ *           PIX     *pixMultConstantColor()
+ *           PIX     *pixMultMatrixColor()
+ *
+ *      Edge by bandpass
+ *           PIX     *pixHalfEdgeByBandpass()
+ *
+ *      Gamma correction, contrast enhancement and histogram equalization
+ *      apply a simple mapping function to each pixel (or, for color
+ *      images, to each sample (i.e., r,g,b) of the pixel).
+ *
+ *       - Gamma correction either lightens the image or darkens
+ *         it, depending on whether the gamma factor is greater
+ *         or less than 1.0, respectively.
+ *
+ *       - Contrast enhancement darkens the pixels that are already
+ *         darker than the middle of the dynamic range (128)
+ *         and lightens pixels that are lighter than 128.
+ *
+ *       - Histogram equalization remaps to have the same number
+ *         of image pixels at each of 256 intensity values.  This is
+ *         a quick and dirty method of adjusting contrast and brightness
+ *         to bring out details in both light and dark regions.
+ *
+ *      Unsharp masking is a more complicated enhancement.
+ *      A "high frequency" image, generated by subtracting
+ *      the smoothed ("low frequency") part of the image from
+ *      itself, has all the energy at the edges.  This "edge image"
+ *      has 0 average value.  A fraction of the edge image is
+ *      then added to the original, enhancing the differences
+ *      between pixel values at edges.  Because we represent
+ *      images as l_uint8 arrays, we preserve dynamic range and
+ *      handle negative values by doing all the arithmetic on
+ *      shifted l_uint16 arrays; the l_uint8 values are recovered
+ *      at the end.
+ *
+ *      Hue and saturation modification work in HSV space.  Because
+ *      this is too large for efficient table lookup, each pixel value
+ *      is transformed to HSV, modified, and transformed back.
+ *      It's not the fastest way to do this, but the method is
+ *      easily understood.
+ *
+ *      Unsharp masking is never in-place, and returns a clone if no
+ *      operation is to be performed.
+ */
+
+
+#include <math.h>
+#include "allheaders.h"
+
+    /* Scales contrast enhancement factor to have a useful range
+     * between 0.0 and 1.0 */
+static const l_float32  ENHANCE_SCALE_FACTOR = 5.;
+
+    /* Default number of pixels sampled to determine histogram */
+static const l_int32  DEFAULT_HISTO_SAMPLES = 100000;
+
+
+/*-------------------------------------------------------------*
+ *         Gamma TRC (tone reproduction curve) mapping         *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixGammaTRC()
+ *
+ *      Input:  pixd (<optional> null or equal to pixs)
+ *              pixs (8 or 32 bpp; or 2, 4 or 8 bpp with colormap)
+ *              gamma (gamma correction; must be > 0.0)
+ *              minval  (input value that gives 0 for output; can be < 0)
+ *              maxval  (input value that gives 255 for output; can be > 255)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) pixd must either be null or equal to pixs.
+ *          For in-place operation, set pixd == pixs:
+ *             pixGammaTRC(pixs, pixs, ...);
+ *          To get a new image, set pixd == null:
+ *             pixd = pixGammaTRC(NULL, pixs, ...);
+ *      (2) If pixs is colormapped, the colormap is transformed,
+ *          either in-place or in a copy of pixs.
+ *      (3) We use a gamma mapping between minval and maxval.
+ *      (4) If gamma < 1.0, the image will appear darker;
+ *          if gamma > 1.0, the image will appear lighter;
+ *      (5) If gamma = 1.0 and minval = 0 and maxval = 255, no
+ *          enhancement is performed; return a copy unless in-place,
+ *          in which case this is a no-op.
+ *      (6) For color images that are not colormapped, the mapping
+ *          is applied to each component.
+ *      (7) minval and maxval are not restricted to the interval [0, 255].
+ *          If minval < 0, an input value of 0 is mapped to a
+ *          nonzero output.  This will turn black to gray.
+ *          If maxval > 255, an input value of 255 is mapped to
+ *          an output value less than 255.  This will turn
+ *          white (e.g., in the background) to gray.
+ *      (8) Increasing minval darkens the image.
+ *      (9) Decreasing maxval bleaches the image.
+ *      (10) Simultaneously increasing minval and decreasing maxval
+ *           will darken the image and make the colors more intense;
+ *           e.g., minval = 50, maxval = 200.
+ *      (11) See numaGammaTRC() for further examples of use.
+ */
+PIX *
+pixGammaTRC(PIX       *pixd,
+            PIX       *pixs,
+            l_float32  gamma,
+            l_int32    minval,
+            l_int32    maxval)
+{
+l_int32   d;
+NUMA     *nag;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixGammaTRC");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd);
+    if (gamma <= 0.0) {
+        L_WARNING("gamma must be > 0.0; setting to 1.0\n", procName);
+        gamma = 1.0;
+    }
+    if (minval >= maxval)
+        return (PIX *)ERROR_PTR("minval not < maxval", procName, pixd);
+    cmap = pixGetColormap(pixs);
+    d = pixGetDepth(pixs);
+    if (!cmap && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, pixd);
+
+    if (gamma == 1.0 && minval == 0 && maxval == 255)  /* no-op */
+        return pixCopy(pixd, pixs);
+
+    if (!pixd)  /* start with a copy if not in-place */
+        pixd = pixCopy(NULL, pixs);
+
+    if (cmap) {
+        pixcmapGammaTRC(pixGetColormap(pixd), gamma, minval, maxval);
+        return pixd;
+    }
+
+        /* pixd is 8 or 32 bpp */
+    if ((nag = numaGammaTRC(gamma, minval, maxval)) == NULL)
+        return (PIX *)ERROR_PTR("nag not made", procName, pixd);
+    pixTRCMap(pixd, NULL, nag);
+    numaDestroy(&nag);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixGammaTRCMasked()
+ *
+ *      Input:  pixd (<optional> null or equal to pixs)
+ *              pixs (8 or 32 bpp; not colormapped)
+ *              pixm (<optional> null or 1 bpp)
+ *              gamma (gamma correction; must be > 0.0)
+ *              minval  (input value that gives 0 for output; can be < 0)
+ *              maxval  (input value that gives 255 for output; can be > 255)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) Same as pixGammaTRC() except mapping is optionally over
+ *          a subset of pixels described by pixm.
+ *      (2) Masking does not work for colormapped images.
+ *      (3) See pixGammaTRC() for details on how to use the parameters.
+ */
+PIX *
+pixGammaTRCMasked(PIX       *pixd,
+                  PIX       *pixs,
+                  PIX       *pixm,
+                  l_float32  gamma,
+                  l_int32    minval,
+                  l_int32    maxval)
+{
+l_int32  d;
+NUMA    *nag;
+
+    PROCNAME("pixGammaTRCMasked");
+
+    if (!pixm)
+        return pixGammaTRC(pixd, pixs, gamma, minval, maxval);
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("invalid: pixs has a colormap", procName, pixd);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, pixd);
+    if (minval >= maxval)
+        return (PIX *)ERROR_PTR("minval not < maxval", procName, pixd);
+    if (gamma <= 0.0) {
+        L_WARNING("gamma must be > 0.0; setting to 1.0\n", procName);
+        gamma = 1.0;
+    }
+
+    if (gamma == 1.0 && minval == 0 && maxval == 255)
+        return pixCopy(pixd, pixs);
+
+    if (!pixd)  /* start with a copy if not in-place */
+        pixd = pixCopy(NULL, pixs);
+
+    if ((nag = numaGammaTRC(gamma, minval, maxval)) == NULL)
+        return (PIX *)ERROR_PTR("nag not made", procName, pixd);
+    pixTRCMap(pixd, pixm, nag);
+    numaDestroy(&nag);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixGammaTRCWithAlpha()
+ *
+ *      Input:  pixd (<optional> null or equal to pixs)
+ *              pixs (32 bpp)
+ *              gamma (gamma correction; must be > 0.0)
+ *              minval  (input value that gives 0 for output; can be < 0)
+ *              maxval  (input value that gives 255 for output; can be > 255)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) See usage notes in pixGammaTRC().
+ *      (2) This version saves the alpha channel.  It is only valid
+ *          for 32 bpp (no colormap), and is a bit slower.
+ */
+PIX *
+pixGammaTRCWithAlpha(PIX       *pixd,
+                     PIX       *pixs,
+                     l_float32  gamma,
+                     l_int32    minval,
+                     l_int32    maxval)
+{
+NUMA  *nag;
+PIX   *pixalpha;
+
+    PROCNAME("pixGammaTRCWithAlpha");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, pixd);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd);
+    if (gamma <= 0.0) {
+        L_WARNING("gamma must be > 0.0; setting to 1.0\n", procName);
+        gamma = 1.0;
+    }
+    if (minval >= maxval)
+        return (PIX *)ERROR_PTR("minval not < maxval", procName, pixd);
+
+    if (gamma == 1.0 && minval == 0 && maxval == 255)
+        return pixCopy(pixd, pixs);
+    if (!pixd)  /* start with a copy if not in-place */
+        pixd = pixCopy(NULL, pixs);
+
+    pixalpha = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);  /* save */
+    if ((nag = numaGammaTRC(gamma, minval, maxval)) == NULL)
+        return (PIX *)ERROR_PTR("nag not made", procName, pixd);
+    pixTRCMap(pixd, NULL, nag);
+    pixSetRGBComponent(pixd, pixalpha, L_ALPHA_CHANNEL);  /* restore */
+    pixSetSpp(pixd, 4);
+
+    numaDestroy(&nag);
+    pixDestroy(&pixalpha);
+    return pixd;
+}
+
+
+/*!
+ *  numaGammaTRC()
+ *
+ *      Input:  gamma   (gamma factor; must be > 0.0)
+ *              minval  (input value that gives 0 for output)
+ *              maxval  (input value that gives 255 for output)
+ *      Return: na, or null on error
+ *
+ *  Notes:
+ *      (1) The map is returned as a numa; values are clipped to [0, 255].
+ *      (2) To force all intensities into a range within fraction delta
+ *          of white, use: minval = -256 * (1 - delta) / delta
+ *                         maxval = 255
+ *      (3) To force all intensities into a range within fraction delta
+ *          of black, use: minval = 0
+ *                         maxval = 256 * (1 - delta) / delta
+ */
+NUMA *
+numaGammaTRC(l_float32  gamma,
+             l_int32    minval,
+             l_int32    maxval)
+{
+l_int32    i, val;
+l_float32  x, invgamma;
+NUMA      *na;
+
+    PROCNAME("numaGammaTRC");
+
+    if (minval >= maxval)
+        return (NUMA *)ERROR_PTR("minval not < maxval", procName, NULL);
+    if (gamma <= 0.0) {
+        L_WARNING("gamma must be > 0.0; setting to 1.0\n", procName);
+        gamma = 1.0;
+    }
+
+    invgamma = 1. / gamma;
+    na = numaCreate(256);
+    for (i = 0; i < minval; i++)
+        numaAddNumber(na, 0);
+    for (i = minval; i <= maxval; i++) {
+        if (i < 0) continue;
+        if (i > 255) continue;
+        x = (l_float32)(i - minval) / (l_float32)(maxval - minval);
+        val = (l_int32)(255. * powf(x, invgamma) + 0.5);
+        val = L_MAX(val, 0);
+        val = L_MIN(val, 255);
+        numaAddNumber(na, val);
+    }
+    for (i = maxval + 1; i < 256; i++)
+        numaAddNumber(na, 255);
+
+    return na;
+}
+
+
+/*-------------------------------------------------------------*
+ *                      Contrast enhancement                   *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixContrastTRC()
+ *
+ *      Input:  pixd (<optional> null or equal to pixs)
+ *              pixs (8 or 32 bpp; or 2, 4 or 8 bpp with colormap)
+ *              factor  (0.0 is no enhancement)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) pixd must either be null or equal to pixs.
+ *          For in-place operation, set pixd == pixs:
+ *             pixContrastTRC(pixs, pixs, ...);
+ *          To get a new image, set pixd == null:
+ *             pixd = pixContrastTRC(NULL, pixs, ...);
+ *      (2) If pixs is colormapped, the colormap is transformed,
+ *          either in-place or in a copy of pixs.
+ *      (3) Contrast is enhanced by mapping each color component
+ *          using an atan function with maximum slope at 127.
+ *          Pixels below 127 are lowered in intensity and pixels
+ *          above 127 are increased.
+ *      (4) The useful range for the contrast factor is scaled to
+ *          be in (0.0 to 1.0), but larger values can also be used.
+ *      (5) If factor == 0.0, no enhancement is performed; return a copy
+ *          unless in-place, in which case this is a no-op.
+ *      (6) For color images that are not colormapped, the mapping
+ *          is applied to each component.
+ */
+PIX *
+pixContrastTRC(PIX       *pixd,
+               PIX       *pixs,
+               l_float32  factor)
+{
+l_int32   d;
+NUMA     *nac;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixContrastTRC");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd);
+    if (factor < 0.0) {
+        L_WARNING("factor must be >= 0.0; using 0.0\n", procName);
+        factor = 0.0;
+    }
+    if (factor == 0.0)
+        return pixCopy(pixd, pixs);
+
+    cmap = pixGetColormap(pixs);
+    d = pixGetDepth(pixs);
+    if (!cmap && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, pixd);
+
+    if (!pixd)  /* start with a copy if not in-place */
+        pixd = pixCopy(NULL, pixs);
+
+    if (cmap) {
+        pixcmapContrastTRC(pixGetColormap(pixd), factor);
+        return pixd;
+    }
+
+        /* pixd is 8 or 32 bpp */
+    if ((nac = numaContrastTRC(factor)) == NULL)
+        return (PIX *)ERROR_PTR("nac not made", procName, pixd);
+    pixTRCMap(pixd, NULL, nac);
+    numaDestroy(&nac);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixContrastTRCMasked()
+ *
+ *      Input:  pixd (<optional> null or equal to pixs)
+ *              pixs (8 or 32 bpp; or 2, 4 or 8 bpp with colormap)
+ *              pixm (<optional> null or 1 bpp)
+ *              factor  (0.0 is no enhancement)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) Same as pixContrastTRC() except mapping is optionally over
+ *          a subset of pixels described by pixm.
+ *      (2) Masking does not work for colormapped images.
+ *      (3) See pixContrastTRC() for details on how to use the parameters.
+ */
+PIX *
+pixContrastTRCMasked(PIX       *pixd,
+                     PIX       *pixs,
+                     PIX       *pixm,
+                     l_float32  factor)
+{
+l_int32  d;
+NUMA    *nac;
+
+    PROCNAME("pixContrastTRCMasked");
+
+    if (!pixm)
+        return pixContrastTRC(pixd, pixs, factor);
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("invalid: pixs has a colormap", procName, pixd);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, pixd);
+
+    if (factor < 0.0) {
+        L_WARNING("factor must be >= 0.0; using 0.0\n", procName);
+        factor = 0.0;
+    }
+    if (factor == 0.0)
+        return pixCopy(pixd, pixs);
+
+    if (!pixd)  /* start with a copy if not in-place */
+        pixd = pixCopy(NULL, pixs);
+
+    if ((nac = numaContrastTRC(factor)) == NULL)
+        return (PIX *)ERROR_PTR("nac not made", procName, pixd);
+    pixTRCMap(pixd, pixm, nac);
+    numaDestroy(&nac);
+
+    return pixd;
+}
+
+
+/*!
+ *  numaContrastTRC()
+ *
+ *      Input:  factor (generally between 0.0 (no enhancement)
+ *              and 1.0, but can be larger than 1.0)
+ *      Return: na, or null on error
+ *
+ *  Notes:
+ *      (1) The mapping is monotonic increasing, where 0 is mapped
+ *          to 0 and 255 is mapped to 255.
+ *      (2) As 'factor' is increased from 0.0 (where the mapping is linear),
+ *          the map gets closer to its limit as a step function that
+ *          jumps from 0 to 255 at the center (input value = 127).
+ */
+NUMA *
+numaContrastTRC(l_float32  factor)
+{
+l_int32    i, val;
+l_float64  x, ymax, ymin, dely, scale;
+NUMA      *na;
+
+    PROCNAME("numaContrastTRC");
+
+    if (factor < 0.0) {
+        L_WARNING("factor must be >= 0.0; using 0.0; no enhancement\n",
+                  procName);
+        factor = 0.0;
+    }
+    if (factor == 0.0)
+        return numaMakeSequence(0, 1, 256);  /* linear map */
+
+    scale = ENHANCE_SCALE_FACTOR;
+    ymax = atan((l_float64)(1.0 * factor * scale));
+    ymin = atan((l_float64)(-127. * factor * scale / 128.));
+    dely = ymax - ymin;
+    na = numaCreate(256);
+    for (i = 0; i < 256; i++) {
+        x = (l_float64)i;
+        val = (l_int32)((255. / dely) *
+             (-ymin + atan((l_float64)(factor * scale * (x - 127.) / 128.))) +
+                 0.5);
+        numaAddNumber(na, val);
+    }
+
+    return na;
+}
+
+
+/*-------------------------------------------------------------*
+ *                     Histogram equalization                  *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixEqualizeTRC()
+ *
+ *      Input:  pixd (<optional> null or equal to pixs)
+ *              pixs (8 bpp gray, 32 bpp rgb, or colormapped)
+ *              fract (fraction of equalization movement of pixel values)
+ *              factor (subsampling factor; integer >= 1)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) pixd must either be null or equal to pixs.
+ *          For in-place operation, set pixd == pixs:
+ *             pixEqualizeTRC(pixs, pixs, ...);
+ *          To get a new image, set pixd == null:
+ *             pixd = pixEqualizeTRC(NULL, pixs, ...);
+ *      (2) In histogram equalization, a tone reproduction curve
+ *          mapping is used to make the number of pixels at each
+ *          intensity equal.
+ *      (3) If fract == 0.0, no equalization is performed; return a copy
+ *          unless in-place, in which case this is a no-op.
+ *          If fract == 1.0, equalization is complete.
+ *      (4) Set the subsampling factor > 1 to reduce the amount of computation.
+ *      (5) If pixs is colormapped, the colormap is removed and
+ *          converted to rgb or grayscale.
+ *      (6) If pixs has color, equalization is done in each channel
+ *          separately.
+ *      (7) Note that even if there is a colormap, we can get an
+ *          in-place operation because the intermediate image pixt
+ *          is copied back to pixs (which for in-place is the same
+ *          as pixd).
+ */
+PIX *
+pixEqualizeTRC(PIX       *pixd,
+               PIX       *pixs,
+               l_float32  fract,
+               l_int32    factor)
+{
+l_int32   d;
+NUMA     *na;
+PIX      *pixt, *pix8;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixEqualizeTRC");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd);
+    cmap = pixGetColormap(pixs);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32 && !cmap)
+        return (PIX *)ERROR_PTR("pixs not 8/32 bpp or cmapped", procName, NULL);
+    if (fract < 0.0 || fract > 1.0)
+        return (PIX *)ERROR_PTR("fract not in [0.0 ... 1.0]", procName, NULL);
+    if (factor < 1)
+        return (PIX *)ERROR_PTR("sampling factor < 1", procName, NULL);
+
+    if (fract == 0.0)
+        return pixCopy(pixd, pixs);
+
+        /* If there is a colormap, remove it. */
+    if (cmap)
+        pixt = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    else
+        pixt = pixClone(pixs);
+
+        /* Make a copy if necessary */
+    pixd = pixCopy(pixd, pixt);
+    pixDestroy(&pixt);
+
+    d = pixGetDepth(pixd);
+    if (d == 8) {
+        na = numaEqualizeTRC(pixd, fract, factor);
+        pixTRCMap(pixd, NULL, na);
+        numaDestroy(&na);
+    } else {  /* 32 bpp */
+        pix8 = pixGetRGBComponent(pixd, COLOR_RED);
+        na = numaEqualizeTRC(pix8, fract, factor);
+        pixTRCMap(pix8, NULL, na);
+        pixSetRGBComponent(pixd, pix8, COLOR_RED);
+        numaDestroy(&na);
+        pixDestroy(&pix8);
+        pix8 = pixGetRGBComponent(pixd, COLOR_GREEN);
+        na = numaEqualizeTRC(pix8, fract, factor);
+        pixTRCMap(pix8, NULL, na);
+        pixSetRGBComponent(pixd, pix8, COLOR_GREEN);
+        numaDestroy(&na);
+        pixDestroy(&pix8);
+        pix8 = pixGetRGBComponent(pixd, COLOR_BLUE);
+        na = numaEqualizeTRC(pix8, fract, factor);
+        pixTRCMap(pix8, NULL, na);
+        pixSetRGBComponent(pixd, pix8, COLOR_BLUE);
+        numaDestroy(&na);
+        pixDestroy(&pix8);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  numaEqualizeTRC()
+ *
+ *      Input:  pix (8 bpp, no colormap)
+ *              fract (fraction of equalization movement of pixel values)
+ *              factor (subsampling factor; integer >= 1)
+ *      Return: nad, or null on error
+ *
+ *  Notes:
+ *      (1) If fract == 0.0, no equalization will be performed.
+ *          If fract == 1.0, equalization is complete.
+ *      (2) Set the subsampling factor > 1 to reduce the amount of computation.
+ *      (3) The map is returned as a numa with 256 values, specifying
+ *          the equalized value (array value) for every input value
+ *          (the array index).
+ */
+NUMA *
+numaEqualizeTRC(PIX       *pix,
+                l_float32  fract,
+                l_int32    factor)
+{
+l_int32    iin, iout, itarg;
+l_float32  val, sum;
+NUMA      *nah, *nasum, *nad;
+
+    PROCNAME("numaEqualizeTRC");
+
+    if (!pix)
+        return (NUMA *)ERROR_PTR("pix not defined", procName, NULL);
+    if (pixGetDepth(pix) != 8)
+        return (NUMA *)ERROR_PTR("pix not 8 bpp", procName, NULL);
+    if (fract < 0.0 || fract > 1.0)
+        return (NUMA *)ERROR_PTR("fract not in [0.0 ... 1.0]", procName, NULL);
+    if (factor < 1)
+        return (NUMA *)ERROR_PTR("sampling factor < 1", procName, NULL);
+
+    if (fract == 0.0)
+        L_WARNING("fract = 0.0; no equalization requested\n", procName);
+
+    if ((nah = pixGetGrayHistogram(pix, factor)) == NULL)
+        return (NUMA *)ERROR_PTR("histogram not made", procName, NULL);
+    numaGetSum(nah, &sum);
+    nasum = numaGetPartialSums(nah);
+
+    nad = numaCreate(256);
+    for (iin = 0; iin < 256; iin++) {
+        numaGetFValue(nasum, iin, &val);
+        itarg = (l_int32)(255. * val / sum + 0.5);
+        iout = iin + (l_int32)(fract * (itarg - iin));
+        iout = L_MIN(iout, 255);  /* to be safe */
+        numaAddNumber(nad, iout);
+    }
+
+    numaDestroy(&nah);
+    numaDestroy(&nasum);
+    return nad;
+}
+
+
+/*-------------------------------------------------------------*
+ *                       Generic TRC mapping                   *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixTRCMap()
+ *
+ *      Input:  pixs (8 grayscale or 32 bpp rgb; not colormapped)
+ *              pixm (<optional> 1 bpp mask)
+ *              na (mapping array)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This operation is in-place on pixs.
+ *      (2) For 32 bpp, this applies the same map to each of the r,g,b
+ *          components.
+ *      (3) The mapping array is of size 256, and it maps the input
+ *          index into values in the range [0, 255].
+ *      (4) If defined, the optional 1 bpp mask pixm has its origin
+ *          aligned with pixs, and the map function is applied only
+ *          to pixels in pixs under the fg of pixm.
+ *      (5) For 32 bpp, this does not save the alpha channel.
+ */
+l_int32
+pixTRCMap(PIX   *pixs,
+          PIX   *pixm,
+          NUMA  *na)
+{
+l_int32    w, h, d, wm, hm, wpl, wplm, i, j, sval8, dval8;
+l_int32   *tab;
+l_uint32   sval32, dval32;
+l_uint32  *data, *datam, *line, *linem;
+
+    PROCNAME("pixTRCMap");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetColormap(pixs))
+        return ERROR_INT("pixs is colormapped", procName, 1);
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (numaGetCount(na) != 256)
+        return ERROR_INT("na not of size 256", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 32)
+        return ERROR_INT("pixs not 8 or 32 bpp", procName, 1);
+    if (pixm) {
+        if (pixGetDepth(pixm) != 1)
+            return ERROR_INT("pixm not 1 bpp", procName, 1);
+    }
+
+    tab = numaGetIArray(na);  /* get the array for efficiency */
+    wpl = pixGetWpl(pixs);
+    data = pixGetData(pixs);
+    if (!pixm) {
+        if (d == 8) {
+            for (i = 0; i < h; i++) {
+                line = data + i * wpl;
+                for (j = 0; j < w; j++) {
+                    sval8 = GET_DATA_BYTE(line, j);
+                    dval8 = tab[sval8];
+                    SET_DATA_BYTE(line, j, dval8);
+                }
+            }
+        } else {  /* d == 32 */
+            for (i = 0; i < h; i++) {
+                line = data + i * wpl;
+                for (j = 0; j < w; j++) {
+                    sval32 = *(line + j);
+                    dval32 =
+                        tab[(sval32 >> L_RED_SHIFT) & 0xff] << L_RED_SHIFT |
+                        tab[(sval32 >> L_GREEN_SHIFT) & 0xff] << L_GREEN_SHIFT |
+                        tab[(sval32 >> L_BLUE_SHIFT) & 0xff] << L_BLUE_SHIFT;
+                    *(line + j) = dval32;
+                }
+            }
+        }
+    } else {
+        datam = pixGetData(pixm);
+        wplm = pixGetWpl(pixm);
+        pixGetDimensions(pixm, &wm, &hm, NULL);
+        if (d == 8) {
+            for (i = 0; i < h; i++) {
+                if (i >= hm)
+                    break;
+                line = data + i * wpl;
+                linem = datam + i * wplm;
+                for (j = 0; j < w; j++) {
+                    if (j >= wm)
+                        break;
+                    if (GET_DATA_BIT(linem, j) == 0)
+                        continue;
+                    sval8 = GET_DATA_BYTE(line, j);
+                    dval8 = tab[sval8];
+                    SET_DATA_BYTE(line, j, dval8);
+                }
+            }
+        } else {  /* d == 32 */
+            for (i = 0; i < h; i++) {
+                if (i >= hm)
+                    break;
+                line = data + i * wpl;
+                linem = datam + i * wplm;
+                for (j = 0; j < w; j++) {
+                    if (j >= wm)
+                        break;
+                    if (GET_DATA_BIT(linem, j) == 0)
+                        continue;
+                    sval32 = *(line + j);
+                    dval32 =
+                        tab[(sval32 >> L_RED_SHIFT) & 0xff] << L_RED_SHIFT |
+                        tab[(sval32 >> L_GREEN_SHIFT) & 0xff] << L_GREEN_SHIFT |
+                        tab[(sval32 >> L_BLUE_SHIFT) & 0xff] << L_BLUE_SHIFT;
+                    *(line + j) = dval32;
+                }
+            }
+        }
+    }
+
+    LEPT_FREE(tab);
+    return 0;
+}
+
+
+
+/*-----------------------------------------------------------------------*
+ *                             Unsharp masking                           *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixUnsharpMasking()
+ *
+ *      Input:  pixs (all depths except 1 bpp; with or without colormaps)
+ *              halfwidth  ("half-width" of smoothing filter)
+ *              fract  (fraction of edge added back into image)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) We use symmetric smoothing filters of odd dimension,
+ *          typically use sizes of 3, 5, 7, etc.  The @halfwidth parameter
+ *          for these is (size - 1)/2; i.e., 1, 2, 3, etc.
+ *      (2) The fract parameter is typically taken in the
+ *          range:  0.2 < fract < 0.7
+ *      (3) Returns a clone if no sharpening is requested.
+ */
+PIX *
+pixUnsharpMasking(PIX       *pixs,
+                  l_int32    halfwidth,
+                  l_float32  fract)
+{
+l_int32  d;
+PIX     *pixt, *pixd, *pixr, *pixrs, *pixg, *pixgs, *pixb, *pixbs;
+
+    PROCNAME("pixUnsharpMasking");
+
+    if (!pixs || (pixGetDepth(pixs) == 1))
+        return (PIX *)ERROR_PTR("pixs not defined or 1 bpp", procName, NULL);
+    if (fract <= 0.0 || halfwidth <= 0) {
+        L_WARNING("no sharpening requested; clone returned\n", procName);
+        return pixClone(pixs);
+    }
+
+    if (halfwidth == 1 || halfwidth == 2)
+        return pixUnsharpMaskingFast(pixs, halfwidth, fract, L_BOTH_DIRECTIONS);
+
+        /* Remove colormap; clone if possible; result is either 8 or 32 bpp */
+    if ((pixt = pixConvertTo8Or32(pixs, 0, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+        /* Sharpen */
+    d = pixGetDepth(pixt);
+    if (d == 8) {
+        pixd = pixUnsharpMaskingGray(pixt, halfwidth, fract);
+    } else {  /* d == 32 */
+        pixr = pixGetRGBComponent(pixs, COLOR_RED);
+        pixrs = pixUnsharpMaskingGray(pixr, halfwidth, fract);
+        pixDestroy(&pixr);
+        pixg = pixGetRGBComponent(pixs, COLOR_GREEN);
+        pixgs = pixUnsharpMaskingGray(pixg, halfwidth, fract);
+        pixDestroy(&pixg);
+        pixb = pixGetRGBComponent(pixs, COLOR_BLUE);
+        pixbs = pixUnsharpMaskingGray(pixb, halfwidth, fract);
+        pixDestroy(&pixb);
+        pixd = pixCreateRGBImage(pixrs, pixgs, pixbs);
+        pixDestroy(&pixrs);
+        pixDestroy(&pixgs);
+        pixDestroy(&pixbs);
+        if (pixGetSpp(pixs) == 4)
+            pixScaleAndTransferAlpha(pixd, pixs, 1.0, 1.0);
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixUnsharpMaskingGray()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              halfwidth  ("half-width" of smoothing filter)
+ *              fract  (fraction of edge added back into image)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) We use symmetric smoothing filters of odd dimension,
+ *          typically use sizes of 3, 5, 7, etc.  The @halfwidth parameter
+ *          for these is (size - 1)/2; i.e., 1, 2, 3, etc.
+ *      (2) The fract parameter is typically taken in the range:
+ *          0.2 < fract < 0.7
+ *      (3) Returns a clone if no sharpening is requested.
+ */
+PIX *
+pixUnsharpMaskingGray(PIX       *pixs,
+                      l_int32    halfwidth,
+                      l_float32  fract)
+{
+l_int32  w, h, d;
+PIX     *pixc, *pixd;
+PIXACC  *pixacc;
+
+    PROCNAME("pixUnsharpMaskingGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 || pixGetColormap(pixs) != NULL)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp or has cmap", procName, NULL);
+    if (fract <= 0.0 || halfwidth <= 0) {
+        L_WARNING("no sharpening requested; clone returned\n", procName);
+        return pixClone(pixs);
+    }
+    if (halfwidth == 1 || halfwidth == 2)
+        return pixUnsharpMaskingGrayFast(pixs, halfwidth, fract,
+                                         L_BOTH_DIRECTIONS);
+
+    if ((pixc = pixBlockconvGray(pixs, NULL, halfwidth, halfwidth)) == NULL)
+        return (PIX *)ERROR_PTR("pixc not made", procName, NULL);
+
+        /* Steps:
+         *    (1) edge image is pixs - pixc  (this is highpass part)
+         *    (2) multiply edge image by fract
+         *    (3) add fraction of edge to pixs
+         *
+         * To show how this is done with both interfaces to arithmetic
+         * on integer Pix, here is the implementation in the lower-level
+         * function calls:
+         *    pixt = pixInitAccumulate(w, h, 0x10000000)) == NULL)
+         *    pixAccumulate(pixt, pixs, L_ARITH_ADD);
+         *    pixAccumulate(pixt, pixc, L_ARITH_SUBTRACT);
+         *    pixMultConstAccumulate(pixt, fract, 0x10000000);
+         *    pixAccumulate(pixt, pixs, L_ARITH_ADD);
+         *    pixd = pixFinalAccumulate(pixt, 0x10000000, 8)) == NULL)
+         *    pixDestroy(&pixt);
+         *
+         * The code below does the same thing using the Pixacc accumulator,
+         * hiding the details of the offset that is needed for subtraction.
+         */
+    pixacc = pixaccCreate(w, h, 1);
+    pixaccAdd(pixacc, pixs);
+    pixaccSubtract(pixacc, pixc);
+    pixaccMultConst(pixacc, fract);
+    pixaccAdd(pixacc, pixs);
+    pixd = pixaccFinal(pixacc, 8);
+    pixaccDestroy(&pixacc);
+
+    pixDestroy(&pixc);
+    return pixd;
+}
+
+
+/*!
+ *  pixUnsharpMaskingFast()
+ *
+ *      Input:  pixs (all depths except 1 bpp; with or without colormaps)
+ *              halfwidth  ("half-width" of smoothing filter; 1 and 2 only)
+ *              fract  (fraction of high frequency added to image)
+ *              direction (L_HORIZ, L_VERT, L_BOTH_DIRECTIONS)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The fast version uses separable 1-D filters directly on
+ *          the input image.  The halfwidth is either 1 (full width = 3)
+ *          or 2 (full width = 5).
+ *      (2) The fract parameter is typically taken in the
+ *            range:  0.2 < fract < 0.7
+ *      (3) To skip horizontal sharpening, use @fracth = 0.0; ditto for @fractv
+ *      (4) For one dimensional filtering (as an example):
+ *          For @halfwidth = 1, the low-pass filter is
+ *              L:    1/3    1/3   1/3
+ *          and the high-pass filter is
+ *              H = I - L:   -1/3   2/3   -1/3
+ *          For @halfwidth = 2, the low-pass filter is
+ *              L:    1/5    1/5   1/5    1/5    1/5
+ *          and the high-pass filter is
+ *              H = I - L:   -1/5  -1/5   4/5  -1/5   -1/5
+ *          The new sharpened pixel value is found by adding some fraction
+ *          of the high-pass filter value (which sums to 0) to the
+ *          initial pixel value:
+ *              N = I + fract * H
+ *      (5) For 2D, the sharpening filter is not separable, because the
+ *          vertical filter depends on the horizontal location relative
+ *          to the filter origin, and v.v.   So we either do the full
+ *          2D filter (for @halfwidth == 1) or do the low-pass
+ *          convolution separably and then compose with the original pix.
+ *      (6) Returns a clone if no sharpening is requested.
+ */
+PIX *
+pixUnsharpMaskingFast(PIX       *pixs,
+                      l_int32    halfwidth,
+                      l_float32  fract,
+                      l_int32    direction)
+{
+l_int32  d;
+PIX     *pixt, *pixd, *pixr, *pixrs, *pixg, *pixgs, *pixb, *pixbs;
+
+    PROCNAME("pixUnsharpMaskingFast");
+
+    if (!pixs || (pixGetDepth(pixs) == 1))
+        return (PIX *)ERROR_PTR("pixs not defined or 1 bpp", procName, NULL);
+    if (fract <= 0.0 || halfwidth <= 0) {
+        L_WARNING("no sharpening requested; clone returned\n", procName);
+        return pixClone(pixs);
+    }
+    if (halfwidth != 1 && halfwidth != 2)
+        return (PIX *)ERROR_PTR("halfwidth must be 1 or 2", procName, NULL);
+    if (direction != L_HORIZ && direction != L_VERT &&
+        direction != L_BOTH_DIRECTIONS)
+        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
+
+        /* Remove colormap; clone if possible; result is either 8 or 32 bpp */
+    if ((pixt = pixConvertTo8Or32(pixs, 0, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+        /* Sharpen */
+    d = pixGetDepth(pixt);
+    if (d == 8) {
+        pixd = pixUnsharpMaskingGrayFast(pixt, halfwidth, fract, direction);
+    } else {  /* d == 32 */
+        pixr = pixGetRGBComponent(pixs, COLOR_RED);
+        pixrs = pixUnsharpMaskingGrayFast(pixr, halfwidth, fract, direction);
+        pixDestroy(&pixr);
+        pixg = pixGetRGBComponent(pixs, COLOR_GREEN);
+        pixgs = pixUnsharpMaskingGrayFast(pixg, halfwidth, fract, direction);
+        pixDestroy(&pixg);
+        pixb = pixGetRGBComponent(pixs, COLOR_BLUE);
+        pixbs = pixUnsharpMaskingGrayFast(pixb, halfwidth, fract, direction);
+        pixDestroy(&pixb);
+        pixd = pixCreateRGBImage(pixrs, pixgs, pixbs);
+        if (pixGetSpp(pixs) == 4)
+            pixScaleAndTransferAlpha(pixd, pixs, 1.0, 1.0);
+        pixDestroy(&pixrs);
+        pixDestroy(&pixgs);
+        pixDestroy(&pixbs);
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+
+/*!
+ *  pixUnsharpMaskingGrayFast()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              halfwidth  ("half-width" of smoothing filter: 1 or 2)
+ *              fract  (fraction of high frequency added to image)
+ *              direction (L_HORIZ, L_VERT, L_BOTH_DIRECTIONS)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) For usage and explanation of the algorithm, see notes
+ *          in pixUnsharpMaskingFast().
+ *      (2) Returns a clone if no sharpening is requested.
+ */
+PIX *
+pixUnsharpMaskingGrayFast(PIX       *pixs,
+                          l_int32    halfwidth,
+                          l_float32  fract,
+                          l_int32    direction)
+{
+PIX  *pixd;
+
+    PROCNAME("pixUnsharpMaskingGrayFast");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8 || pixGetColormap(pixs) != NULL)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp or has cmap", procName, NULL);
+    if (fract <= 0.0 || halfwidth <= 0) {
+        L_WARNING("no sharpening requested; clone returned\n", procName);
+        return pixClone(pixs);
+    }
+    if (halfwidth != 1 && halfwidth != 2)
+        return (PIX *)ERROR_PTR("halfwidth must be 1 or 2", procName, NULL);
+    if (direction != L_HORIZ && direction != L_VERT &&
+        direction != L_BOTH_DIRECTIONS)
+        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
+
+    if (direction != L_BOTH_DIRECTIONS)
+        pixd = pixUnsharpMaskingGray1D(pixs, halfwidth, fract, direction);
+    else  /* 2D sharpening */
+        pixd = pixUnsharpMaskingGray2D(pixs, halfwidth, fract);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixUnsharpMaskingGray1D()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              halfwidth  ("half-width" of smoothing filter: 1 or 2)
+ *              fract  (fraction of high frequency added to image)
+ *              direction (of filtering; use L_HORIZ or L_VERT)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) For usage and explanation of the algorithm, see notes
+ *          in pixUnsharpMaskingFast().
+ *      (2) Returns a clone if no sharpening is requested.
+ */
+PIX *
+pixUnsharpMaskingGray1D(PIX       *pixs,
+                        l_int32    halfwidth,
+                        l_float32  fract,
+                        l_int32    direction)
+{
+l_int32    w, h, d, wpls, wpld, i, j, ival;
+l_uint32  *datas, *datad;
+l_uint32  *lines, *lines0, *lines1, *lines2, *lines3, *lines4, *lined;
+l_float32  val, a[5];
+PIX       *pixd;
+
+    PROCNAME("pixUnsharpMaskingGray1D");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 || pixGetColormap(pixs) != NULL)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp or has cmap", procName, NULL);
+    if (fract <= 0.0 || halfwidth <= 0) {
+        L_WARNING("no sharpening requested; clone returned\n", procName);
+        return pixClone(pixs);
+    }
+    if (halfwidth != 1 && halfwidth != 2)
+        return (PIX *)ERROR_PTR("halfwidth must be 1 or 2", procName, NULL);
+
+        /* Initialize pixd with pixels from pixs that will not be
+         * set when computing the sharpened values. */
+    pixd = pixCopyBorder(NULL, pixs, halfwidth, halfwidth,
+                         halfwidth, halfwidth);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+    if (halfwidth == 1) {
+        a[0] = -fract / 3.0;
+        a[1] = 1.0 + fract * 2.0 / 3.0;
+        a[2] = a[0];
+    } else {  /* halfwidth == 2 */
+        a[0] = -fract / 5.0;
+        a[1] = a[0];
+        a[2] = 1.0 + fract * 4.0 / 5.0;
+        a[3] = a[0];
+        a[4] = a[0];
+    }
+
+    if (direction == L_HORIZ) {
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            if (halfwidth == 1) {
+                for (j = 1; j < w - 1; j++) {
+                    val = a[0] * GET_DATA_BYTE(lines, j - 1) +
+                          a[1] * GET_DATA_BYTE(lines, j) +
+                          a[2] * GET_DATA_BYTE(lines, j + 1);
+                    ival = (l_int32)val;
+                    ival = L_MAX(0, ival);
+                    ival = L_MIN(255, ival);
+                    SET_DATA_BYTE(lined, j, ival);
+                }
+            } else {  /* halfwidth == 2 */
+                for (j = 2; j < w - 2; j++) {
+                    val = a[0] * GET_DATA_BYTE(lines, j - 2) +
+                          a[1] * GET_DATA_BYTE(lines, j - 1) +
+                          a[2] * GET_DATA_BYTE(lines, j) +
+                          a[3] * GET_DATA_BYTE(lines, j + 1) +
+                          a[4] * GET_DATA_BYTE(lines, j + 2);
+                    ival = (l_int32)val;
+                    ival = L_MAX(0, ival);
+                    ival = L_MIN(255, ival);
+                    SET_DATA_BYTE(lined, j, ival);
+                }
+            }
+        }
+    } else {  /* direction == L_VERT */
+        if (halfwidth == 1) {
+            for (i = 1; i < h - 1; i++) {
+                lines0 = datas + (i - 1) * wpls;
+                lines1 = datas + i * wpls;
+                lines2 = datas + (i + 1) * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    val = a[0] * GET_DATA_BYTE(lines0, j) +
+                          a[1] * GET_DATA_BYTE(lines1, j) +
+                          a[2] * GET_DATA_BYTE(lines2, j);
+                    ival = (l_int32)val;
+                    ival = L_MAX(0, ival);
+                    ival = L_MIN(255, ival);
+                    SET_DATA_BYTE(lined, j, ival);
+                }
+            }
+        } else {  /* halfwidth == 2 */
+            for (i = 2; i < h - 2; i++) {
+                lines0 = datas + (i - 2) * wpls;
+                lines1 = datas + (i - 1) * wpls;
+                lines2 = datas + i * wpls;
+                lines3 = datas + (i + 1) * wpls;
+                lines4 = datas + (i + 2) * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    val = a[0] * GET_DATA_BYTE(lines0, j) +
+                          a[1] * GET_DATA_BYTE(lines1, j) +
+                          a[2] * GET_DATA_BYTE(lines2, j) +
+                          a[3] * GET_DATA_BYTE(lines3, j) +
+                          a[4] * GET_DATA_BYTE(lines4, j);
+                    ival = (l_int32)val;
+                    ival = L_MAX(0, ival);
+                    ival = L_MIN(255, ival);
+                    SET_DATA_BYTE(lined, j, ival);
+                }
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixUnsharpMaskingGray2D()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              halfwidth  ("half-width" of smoothing filter: 1 or 2)
+ *              fract  (fraction of high frequency added to image)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) For halfwidth == 1, we implement the full sharpening filter
+ *          directly.  For halfwidth == 2, we implement the the lowpass
+ *          filter separably and then compute the sharpening result locally.
+ *      (2) Returns a clone if no sharpening is requested.
+ */
+PIX *
+pixUnsharpMaskingGray2D(PIX       *pixs,
+                        l_int32    halfwidth,
+                        l_float32  fract)
+{
+l_int32     w, h, d, wpls, wpld, wplf, i, j, ival, sval;
+l_uint32   *datas, *datad, *lines, *lines0, *lines1, *lines2, *lined;
+l_float32   val, a[9];
+l_float32  *dataf, *linef, *linef0, *linef1, *linef2, *linef3, *linef4;
+PIX        *pixd;
+FPIX       *fpix;
+
+    PROCNAME("pixUnsharpMaskingGray2D");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 || pixGetColormap(pixs) != NULL)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp or has cmap", procName, NULL);
+    if (fract <= 0.0 || halfwidth <= 0) {
+        L_WARNING("no sharpening requested; clone returned\n", procName);
+        return pixClone(pixs);
+    }
+    if (halfwidth != 1 && halfwidth != 2)
+        return (PIX *)ERROR_PTR("halfwidth must be 1 or 2", procName, NULL);
+
+    pixd = pixCopyBorder(NULL, pixs, halfwidth, halfwidth,
+                         halfwidth, halfwidth);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+
+    if (halfwidth == 1) {
+        for (i = 0; i < 9; i++)
+            a[i] = -fract / 9.0;
+        a[4] = 1.0 + fract * 8.0 / 9.0;
+        for (i = 1; i < h - 1; i++) {
+            lines0 = datas + (i - 1) * wpls;
+            lines1 = datas + i * wpls;
+            lines2 = datas + (i + 1) * wpls;
+            lined = datad + i * wpld;
+            for (j = 1; j < w - 1; j++) {
+                val = a[0] * GET_DATA_BYTE(lines0, j - 1) +
+                      a[1] * GET_DATA_BYTE(lines0, j) +
+                      a[2] * GET_DATA_BYTE(lines0, j + 1) +
+                      a[3] * GET_DATA_BYTE(lines1, j - 1) +
+                      a[4] * GET_DATA_BYTE(lines1, j) +
+                      a[5] * GET_DATA_BYTE(lines1, j + 1) +
+                      a[6] * GET_DATA_BYTE(lines2, j - 1) +
+                      a[7] * GET_DATA_BYTE(lines2, j) +
+                      a[8] * GET_DATA_BYTE(lines2, j + 1);
+                ival = (l_int32)(val + 0.5);
+                ival = L_MAX(0, ival);
+                ival = L_MIN(255, ival);
+                SET_DATA_BYTE(lined, j, ival);
+            }
+        }
+
+        return pixd;
+    }
+
+        /* For halfwidth == 2, do the low pass separably.  Store
+         * the result of horizontal smoothing in an intermediate fpix. */
+    fpix = fpixCreate(w, h);
+    dataf = fpixGetData(fpix);
+    wplf = fpixGetWpl(fpix);
+    for (i = 2; i < h - 2; i++) {
+        lines = datas + i * wpls;
+        linef = dataf + i * wplf;
+        for (j = 2; j < w - 2; j++) {
+            val = GET_DATA_BYTE(lines, j - 2) +
+                  GET_DATA_BYTE(lines, j - 1) +
+                  GET_DATA_BYTE(lines, j) +
+                  GET_DATA_BYTE(lines, j + 1) +
+                  GET_DATA_BYTE(lines, j + 2);
+            linef[j] = val;
+        }
+    }
+
+        /* Do vertical smoothing to finish the low-pass filter.
+         * At each pixel, if L is the lowpass value, I is the
+         * src pixel value and f is the fraction of highpass to
+         * be added to I, then the highpass filter value is
+         *     H = I - L
+         * and the new sharpened value is
+         *     N = I + f * H.
+         */
+    for (i = 2; i < h - 2; i++) {
+        linef0 = dataf + (i - 2) * wplf;
+        linef1 = dataf + (i - 1) * wplf;
+        linef2 = dataf + i * wplf;
+        linef3 = dataf + (i + 1) * wplf;
+        linef4 = dataf + (i + 2) * wplf;
+        lined = datad + i * wpld;
+        lines = datas + i * wpls;
+        for (j = 2; j < w - 2; j++) {
+            val = 0.04 * (linef0[j] + linef1[j] + linef2[j] +
+                          linef3[j] + linef4[j]);  /* L: lowpass filter value */
+            sval = GET_DATA_BYTE(lines, j);   /* I: source pixel */
+            ival = (l_int32)(sval + fract * (sval - val) + 0.5);
+            ival = L_MAX(0, ival);
+            ival = L_MIN(255, ival);
+            SET_DATA_BYTE(lined, j, ival);
+        }
+    }
+
+    fpixDestroy(&fpix);
+    return pixd;
+}
+
+
+
+/*-----------------------------------------------------------------------*
+ *                    Hue and saturation modification                    *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixModifyHue()
+ *
+ *      Input:  pixd (<optional> can be null or equal to pixs)
+ *              pixs (32 bpp rgb)
+ *              fract (between -1.0 and 1.0)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) pixd must either be null or equal to pixs.
+ *          For in-place operation, set pixd == pixs:
+ *             pixEqualizeTRC(pixs, pixs, ...);
+ *          To get a new image, set pixd == null:
+ *             pixd = pixEqualizeTRC(NULL, pixs, ...);
+ *      (1) Use fract > 0.0 to increase hue value; < 0.0 to decrease it.
+ *          1.0 (or -1.0) represents a 360 degree rotation; i.e., no change.
+ *      (2) If no modification is requested (fract = -1.0 or 0 or 1.0),
+ *          return a copy unless in-place, in which case this is a no-op.
+ *      (3) See discussion of color-modification methods, in coloring.c.
+ */
+PIX  *
+pixModifyHue(PIX       *pixd,
+             PIX       *pixs,
+             l_float32  fract)
+{
+l_int32    w, h, d, i, j, wpl, delhue;
+l_int32    rval, gval, bval, hval, sval, vval;
+l_uint32  *data, *line;
+
+    PROCNAME("pixModifyHue");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs) != NULL)
+        return (PIX *)ERROR_PTR("pixs colormapped", procName, NULL);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd not null or pixs", procName, pixd);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (L_ABS(fract) > 1.0)
+        return (PIX *)ERROR_PTR("fract not in [-1.0 ... 1.0]", procName, NULL);
+
+    pixd = pixCopy(pixd, pixs);
+
+    delhue = (l_int32)(240 * fract);
+    if (delhue == 0 || delhue == 240 || delhue == -240) {
+        L_WARNING("no change requested in hue\n", procName);
+        return pixd;
+    }
+    if (delhue < 0)
+        delhue += 240;
+
+    data = pixGetData(pixd);
+    wpl = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(line[j], &rval, &gval, &bval);
+            convertRGBToHSV(rval, gval, bval, &hval, &sval, &vval);
+            hval = (hval + delhue) % 240;
+            convertHSVToRGB(hval, sval, vval, &rval, &gval, &bval);
+            composeRGBPixel(rval, gval, bval, line + j);
+        }
+    }
+    if (pixGetSpp(pixs) == 4)
+        pixScaleAndTransferAlpha(pixd, pixs, 1.0, 1.0);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixModifySaturation()
+ *
+ *      Input:  pixd (<optional> can be null, existing or equal to pixs)
+ *              pixs (32 bpp rgb)
+ *              fract (between -1.0 and 1.0)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) If fract > 0.0, it gives the fraction that the pixel
+ *          saturation is moved from its initial value toward 255.
+ *          If fract < 0.0, it gives the fraction that the pixel
+ *          saturation is moved from its initial value toward 0.
+ *          The limiting values for fract = -1.0 (1.0) thus set the
+ *          saturation to 0 (255).
+ *      (2) If fract = 0, no modification is requested; return a copy
+ *          unless in-place, in which case this is a no-op.
+ *      (3) See discussion of color-modification methods, in coloring.c.
+ */
+PIX  *
+pixModifySaturation(PIX       *pixd,
+                    PIX       *pixs,
+                    l_float32  fract)
+{
+l_int32    w, h, d, i, j, wpl;
+l_int32    rval, gval, bval, hval, sval, vval;
+l_uint32  *data, *line;
+
+    PROCNAME("pixModifySaturation");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (L_ABS(fract) > 1.0)
+        return (PIX *)ERROR_PTR("fract not in [-1.0 ... 1.0]", procName, NULL);
+
+    pixd = pixCopy(pixd, pixs);
+    if (fract == 0.0) {
+        L_WARNING("no change requested in saturation\n", procName);
+        return pixd;
+    }
+
+    data = pixGetData(pixd);
+    wpl = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(line[j], &rval, &gval, &bval);
+            convertRGBToHSV(rval, gval, bval, &hval, &sval, &vval);
+            if (fract < 0.0)
+                sval = (l_int32)(sval * (1.0 + fract));
+            else
+                sval = (l_int32)(sval + fract * (255 - sval));
+            convertHSVToRGB(hval, sval, vval, &rval, &gval, &bval);
+            composeRGBPixel(rval, gval, bval, line + j);
+        }
+    }
+    if (pixGetSpp(pixs) == 4)
+        pixScaleAndTransferAlpha(pixd, pixs, 1.0, 1.0);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixMeasureSaturation()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              factor (subsampling factor; integer >= 1)
+ *              &sat (<return> average saturation)
+ *      Return: pixd, or null on error
+ */
+l_int32
+pixMeasureSaturation(PIX        *pixs,
+                     l_int32     factor,
+                     l_float32  *psat)
+{
+l_int32    w, h, d, i, j, wpl, sum, count;
+l_int32    rval, gval, bval, hval, sval, vval;
+l_uint32  *data, *line;
+
+    PROCNAME("pixMeasureSaturation");
+
+    if (!psat)
+        return ERROR_INT("pixs not defined", procName, 1);
+    *psat = 0.0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32)
+        return ERROR_INT("pixs not 32 bpp", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("subsampling factor < 1", procName, 1);
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    for (i = 0, sum = 0, count = 0; i < h; i += factor) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j += factor) {
+            extractRGBValues(line[j], &rval, &gval, &bval);
+            convertRGBToHSV(rval, gval, bval, &hval, &sval, &vval);
+            sum += sval;
+            count++;
+        }
+    }
+
+    if (count > 0)
+        *psat = (l_float32)sum / (l_float32)count;
+    return 0;
+}
+
+
+/*!
+ *  pixModifyBrightness()
+ *
+ *      Input:  pixd (<optional> can be null, existing or equal to pixs)
+ *              pixs (32 bpp rgb)
+ *              fract (between -1.0 and 1.0)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) If fract > 0.0, it gives the fraction that the v-parameter,
+ *          which is max(r,g,b), is moved from its initial value toward 255.
+ *          If fract < 0.0, it gives the fraction that the v-parameter
+ *          is moved from its initial value toward 0.
+ *          The limiting values for fract = -1.0 (1.0) thus set the
+ *          v-parameter to 0 (255).
+ *      (2) If fract = 0, no modification is requested; return a copy
+ *          unless in-place, in which case this is a no-op.
+ *      (3) See discussion of color-modification methods, in coloring.c.
+ */
+PIX  *
+pixModifyBrightness(PIX       *pixd,
+                    PIX       *pixs,
+                    l_float32  fract)
+{
+l_int32    w, h, d, i, j, wpl;
+l_int32    rval, gval, bval, hval, sval, vval;
+l_uint32  *data, *line;
+
+    PROCNAME("pixModifyBrightness");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (L_ABS(fract) > 1.0)
+        return (PIX *)ERROR_PTR("fract not in [-1.0 ... 1.0]", procName, NULL);
+
+    pixd = pixCopy(pixd, pixs);
+    if (fract == 0.0) {
+        L_WARNING("no change requested in brightness\n", procName);
+        return pixd;
+    }
+
+    data = pixGetData(pixd);
+    wpl = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(line[j], &rval, &gval, &bval);
+            convertRGBToHSV(rval, gval, bval, &hval, &sval, &vval);
+            if (fract > 0.0)
+                vval = (l_int32)(vval + fract * (255.0 - vval));
+            else
+                vval = (l_int32)(vval * (1.0 + fract));
+            convertHSVToRGB(hval, sval, vval, &rval, &gval, &bval);
+            composeRGBPixel(rval, gval, bval, line + j);
+        }
+    }
+    if (pixGetSpp(pixs) == 4)
+        pixScaleAndTransferAlpha(pixd, pixs, 1.0, 1.0);
+
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                             Color shifting                            *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixColorShiftRGB()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              rfract (fractional shift in red component)
+ *              gfract (fractional shift in green component)
+ *              bfract (fractional shift in blue component)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This allows independent fractional shifts of the r,g and b
+ *          components.  A positive shift pushes to saturation (255);
+ *          a negative shift pushes toward 0 (black).
+ *      (2) The effect can be imagined using a color wheel that consists
+ *          (for our purposes) of these 6 colors, separated by 60 degrees:
+ *             red, magenta, blue, cyan, green, yellow
+ *      (3) So, for example, a negative shift of the blue component
+ *          (bfract < 0) could be accompanied by positive shifts
+ *          of red and green to make an image more yellow.
+ *      (4) Examples of limiting cases:
+ *            rfract = 1 ==> r = 255
+ *            rfract = -1 ==> r = 0
+ */
+PIX *
+pixColorShiftRGB(PIX       *pixs,
+                 l_float32  rfract,
+                 l_float32  gfract,
+                 l_float32  bfract)
+{
+l_int32    w, h, i, j, wpls, wpld, rval, gval, bval;
+l_int32   *rlut, *glut, *blut;
+l_uint32  *datas, *datad, *lines, *lined;
+l_float32  fi;
+PIX       *pixd;
+
+    PROCNAME("pixColorShiftRGB");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (rfract < -1.0 || rfract > 1.0)
+        return (PIX *)ERROR_PTR("rfract not in [-1.0,...,1.0]", procName, NULL);
+    if (gfract < -1.0 || gfract > 1.0)
+        return (PIX *)ERROR_PTR("gfract not in [-1.0,...,1.0]", procName, NULL);
+    if (bfract < -1.0 || bfract > 1.0)
+        return (PIX *)ERROR_PTR("bfract not in [-1.0,...,1.0]", procName, NULL);
+    if (rfract == 0.0 && gfract == 0.0 && bfract == 0.0)
+        return pixCopy(NULL, pixs);
+
+    rlut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    glut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    blut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    for (i = 0; i < 256; i++) {
+        fi = i;
+        if (rfract >= 0) {
+            rlut[i] = (l_int32)(fi + (255.0 - fi) * rfract);
+        } else {
+            rlut[i] = (l_int32)(fi * (1.0 + rfract));
+        }
+        if (gfract >= 0) {
+            glut[i] = (l_int32)(fi + (255.0 - fi) * gfract);
+        } else {
+            glut[i] = (l_int32)(fi * (1.0 + gfract));
+        }
+        if (bfract >= 0) {
+            blut[i] = (l_int32)(fi + (255.0 - fi) * bfract);
+        } else {
+            blut[i] = (l_int32)(fi * (1.0 + bfract));
+        }
+    }
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreate(w, h, 32);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            composeRGBPixel(rlut[rval], glut[gval], blut[bval], lined + j);
+        }
+    }
+
+    LEPT_FREE(rlut);
+    LEPT_FREE(glut);
+    LEPT_FREE(blut);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *            General multiplicative constant color transform            *
+ *-----------------------------------------------------------------------*/
+/*
+ *  pixMultConstantColor()
+ *
+ *      Input:  pixs (colormapped or rgb)
+ *              rfact, gfact, bfact (multiplicative factors on each component)
+ *      Return: pixd (colormapped or rgb, with colors scaled), or null on error
+ *
+ *  Notes:
+ *      (1) rfact, gfact and bfact can only have non-negative values.
+ *          They can be greater than 1.0.  All transformed component
+ *          values are clipped to the interval [0, 255].
+ *      (2) For multiplication with a general 3x3 matrix of constants,
+ *          use pixMultMatrixColor().
+ */
+PIX *
+pixMultConstantColor(PIX       *pixs,
+                     l_float32  rfact,
+                     l_float32  gfact,
+                     l_float32  bfact)
+{
+l_int32    i, j, w, h, d, wpls, wpld;
+l_int32    ncolors, rval, gval, bval, nrval, ngval, nbval;
+l_uint32   nval;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixMultConstantColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    cmap = pixGetColormap(pixs);
+    if (!cmap && d != 32)
+        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
+    rfact = L_MAX(0.0, rfact);
+    gfact = L_MAX(0.0, gfact);
+    bfact = L_MAX(0.0, bfact);
+
+    if (cmap) {
+        if ((pixd = pixCopy(NULL, pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+        cmap = pixGetColormap(pixd);
+        ncolors = pixcmapGetCount(cmap);
+        for (i = 0; i < ncolors; i++) {
+            pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+            nrval = (l_int32)(rfact * rval);
+            ngval = (l_int32)(gfact * gval);
+            nbval = (l_int32)(bfact * bval);
+            nrval = L_MIN(255, nrval);
+            ngval = L_MIN(255, ngval);
+            nbval = L_MIN(255, nbval);
+            pixcmapResetColor(cmap, i, nrval, ngval, nbval);
+        }
+        return pixd;
+    }
+
+    if ((pixd = pixCreateTemplateNoInit(pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            nrval = (l_int32)(rfact * rval);
+            ngval = (l_int32)(gfact * gval);
+            nbval = (l_int32)(bfact * bval);
+            nrval = L_MIN(255, nrval);
+            ngval = L_MIN(255, ngval);
+            nbval = L_MIN(255, nbval);
+            composeRGBPixel(nrval, ngval, nbval, &nval);
+            *(lined + j) = nval;
+        }
+    }
+
+    return pixd;
+}
+
+
+/*
+ *  pixMultMatrixColor()
+ *
+ *      Input:  pixs (colormapped or rgb)
+ *              kernel (3x3 matrix of floats)
+ *      Return: pixd (colormapped or rgb), or null on error
+ *
+ *  Notes:
+ *      (1) The kernel is a data structure used mostly for floating point
+ *          convolution.  Here it is a 3x3 matrix of floats that are used
+ *          to transform the pixel values by matrix multiplication:
+ *            nrval = a[0,0] * rval + a[0,1] * gval + a[0,2] * bval
+ *            ngval = a[1,0] * rval + a[1,1] * gval + a[1,2] * bval
+ *            nbval = a[2,0] * rval + a[2,1] * gval + a[2,2] * bval
+ *      (2) The matrix can be generated in several ways.
+ *          See kernel.c for details.  Here are two of them:
+ *            (a) kel = kernelCreate(3, 3);
+ *                kernelSetElement(kel, 0, 0, val00);
+ *                kernelSetElement(kel, 0, 1, val01);
+ *                ...
+ *            (b) from a static string; e.g.,:
+ *                const char *kdata = " 0.6  0.3 -0.2 "
+ *                                    " 0.1  1.2  0.4 "
+ *                                    " -0.4 0.2  0.9 ";
+ *                kel = kernelCreateFromString(3, 3, 0, 0, kdata);
+ *      (3) For the special case where the matrix is diagonal, it is easier
+ *          to use pixMultConstantColor().
+ *      (4) Matrix entries can have positive and negative values, and can
+ *          be larger than 1.0.  All transformed component values
+ *          are clipped to [0, 255].
+ */
+PIX *
+pixMultMatrixColor(PIX       *pixs,
+                   L_KERNEL  *kel)
+{
+l_int32    i, j, index, kw, kh, w, h, d, wpls, wpld;
+l_int32    ncolors, rval, gval, bval, nrval, ngval, nbval;
+l_uint32   nval;
+l_uint32  *datas, *datad, *lines, *lined;
+l_float32  v[9];  /* use linear array for convenience */
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixMultMatrixColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!kel)
+        return (PIX *)ERROR_PTR("kel not defined", procName, NULL);
+    kernelGetParameters(kel, &kw, &kh, NULL, NULL);
+    if (kw != 3 || kh != 3)
+        return (PIX *)ERROR_PTR("matrix not 3x3", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    cmap = pixGetColormap(pixs);
+    if (!cmap && d != 32)
+        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
+
+    for (i = 0, index = 0; i < 3; i++)
+        for (j = 0; j < 3; j++, index++)
+            kernelGetElement(kel, i, j, v + index);
+
+    if (cmap) {
+        if ((pixd = pixCopy(NULL, pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+        cmap = pixGetColormap(pixd);
+        ncolors = pixcmapGetCount(cmap);
+        for (i = 0; i < ncolors; i++) {
+            pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+            nrval = (l_int32)(v[0] * rval + v[1] * gval + v[2] * bval);
+            ngval = (l_int32)(v[3] * rval + v[4] * gval + v[5] * bval);
+            nbval = (l_int32)(v[6] * rval + v[7] * gval + v[8] * bval);
+            nrval = L_MAX(0, L_MIN(255, nrval));
+            ngval = L_MAX(0, L_MIN(255, ngval));
+            nbval = L_MAX(0, L_MIN(255, nbval));
+            pixcmapResetColor(cmap, i, nrval, ngval, nbval);
+        }
+        return pixd;
+    }
+
+    if ((pixd = pixCreateTemplateNoInit(pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            nrval = (l_int32)(v[0] * rval + v[1] * gval + v[2] * bval);
+            ngval = (l_int32)(v[3] * rval + v[4] * gval + v[5] * bval);
+            nbval = (l_int32)(v[6] * rval + v[7] * gval + v[8] * bval);
+            nrval = L_MAX(0, L_MIN(255, nrval));
+            ngval = L_MAX(0, L_MIN(255, ngval));
+            nbval = L_MAX(0, L_MIN(255, nbval));
+            composeRGBPixel(nrval, ngval, nbval, &nval);
+            *(lined + j) = nval;
+        }
+    }
+
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ *                    Half-edge by bandpass                    *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixHalfEdgeByBandpass()
+ *
+ *      Input:  pixs (8 bpp gray or 32 bpp rgb)
+ *              sm1h, sm1v ("half-widths" of smoothing filter sm1)
+ *              sm2h, sm2v ("half-widths" of smoothing filter sm2)
+ *                      (require sm2 != sm1)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) We use symmetric smoothing filters of odd dimension,
+ *          typically use 3, 5, 7, etc.  The smoothing parameters
+ *          for these are 1, 2, 3, etc.  The filter size is related
+ *          to the smoothing parameter by
+ *               size = 2 * smoothing + 1
+ *      (2) Because we take the difference of two lowpass filters,
+ *          this is actually a bandpass filter.
+ *      (3) We allow both filters to be anisotropic.
+ *      (4) Consider either the h or v component of the 2 filters.
+ *          Depending on whether sm1 > sm2 or sm2 > sm1, we get
+ *          different halves of the smoothed gradients (or "edges").
+ *          This difference of smoothed signals looks more like
+ *          a second derivative of a transition, which we rectify
+ *          by not allowing the signal to go below zero.  If sm1 < sm2,
+ *          the sm2 transition is broader, so the difference between
+ *          sm1 and sm2 signals is positive on the upper half of
+ *          the transition.  Likewise, if sm1 > sm2, the sm1 - sm2
+ *          signal difference is positive on the lower half of
+ *          the transition.
+ */
+PIX *
+pixHalfEdgeByBandpass(PIX     *pixs,
+                      l_int32  sm1h,
+                      l_int32  sm1v,
+                      l_int32  sm2h,
+                      l_int32  sm2v)
+{
+l_int32  d;
+PIX     *pixg, *pixacc, *pixc1, *pixc2;
+
+    PROCNAME("pixHalfEdgeByBandpass");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (sm1h == sm2h && sm1v == sm2v)
+        return (PIX *)ERROR_PTR("sm2 = sm1", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+    if (d == 32)
+        pixg = pixConvertRGBToLuminance(pixs);
+    else   /* d == 8 */
+        pixg = pixClone(pixs);
+
+        /* Make a convolution accumulator and use it twice */
+    if ((pixacc = pixBlockconvAccum(pixg)) == NULL)
+        return (PIX *)ERROR_PTR("pixacc not made", procName, NULL);
+    if ((pixc1 = pixBlockconvGray(pixg, pixacc, sm1h, sm1v)) == NULL)
+        return (PIX *)ERROR_PTR("pixc1 not made", procName, NULL);
+    if ((pixc2 = pixBlockconvGray(pixg, pixacc, sm2h, sm2v)) == NULL)
+        return (PIX *)ERROR_PTR("pixc2 not made", procName, NULL);
+    pixDestroy(&pixacc);
+
+        /* Compute the half-edge using pixc1 - pixc2.  */
+    pixSubtractGray(pixc1, pixc1, pixc2);
+
+    pixDestroy(&pixg);
+    pixDestroy(&pixc2);
+    return pixc1;
+}
diff --git a/src/environ.h b/src/environ.h
new file mode 100644 (file)
index 0000000..446e3ea
--- /dev/null
@@ -0,0 +1,478 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_ENVIRON_H
+#define  LEPTONICA_ENVIRON_H
+
+/*------------------------------------------------------------------------*
+ *  Defines and includes differ for Unix and Windows.  Also for Windows,  *
+ *  differentiate between conditionals based on platform and compiler.    *
+ *      For platforms:                                                    *
+ *          _WIN32       =>     Windows, 32- or 64-bit                    *
+ *          _WIN64       =>     Windows, 64-bit only                      *
+ *          __CYGWIN__   =>     Cygwin                                    *
+ *      For compilers:                                                    *
+ *          __GNUC__     =>     gcc                                       *
+ *          _MSC_VER     =>     msvc                                      *
+ *------------------------------------------------------------------------*/
+
+/* MS VC++ does not provide stdint.h, so define the missing types here */
+
+
+#ifndef _MSC_VER
+#include <stdint.h>
+
+#else
+/* Note that _WIN32 is defined for both 32 and 64 bit applications,
+   whereas _WIN64 is defined only for the latter */
+
+#ifdef _WIN64
+typedef __int64 intptr_t;
+typedef unsigned __int64 uintptr_t;
+#else
+typedef int intptr_t;
+typedef unsigned int uintptr_t;
+#endif
+
+/* VC++6 doesn't seem to have powf, expf. */
+#if (_MSC_VER < 1400)
+#define powf(x, y) (float)pow((double)(x), (double)(y))
+#define expf(x) (float)exp((double)(x))
+#endif
+
+#endif /* _MSC_VER */
+
+/* Windows specifics */
+#ifdef _WIN32
+  /* DLL EXPORTS and IMPORTS */
+  #if defined(LIBLEPT_EXPORTS)
+    #define LEPT_DLL __declspec(dllexport)
+  #elif defined(LIBLEPT_IMPORTS)
+    #define LEPT_DLL __declspec(dllimport)
+  #else
+    #define LEPT_DLL
+  #endif
+#else  /* non-Windows specifics */
+  #include <stdint.h>
+  #define LEPT_DLL
+#endif  /* _WIN32 */
+
+typedef intptr_t l_intptr_t;
+typedef uintptr_t l_uintptr_t;
+
+
+/*--------------------------------------------------------------------*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*
+ *                          USER CONFIGURABLE                         *
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*
+ *               Environment variables with I/O libraries             *
+ *               Manual Configuration Only: NOT AUTO_CONF             *
+ *--------------------------------------------------------------------*/
+/*
+ *  Leptonica provides interfaces to link to several external image
+ *  I/O libraries, plus zlib.  Setting any of these to 0 here causes
+ *  non-functioning stubs to be linked.
+ */
+#if !defined(HAVE_CONFIG_H) && !defined(ANDROID_BUILD)
+#define  HAVE_LIBJPEG     1
+#define  HAVE_LIBTIFF     1
+#define  HAVE_LIBPNG      1
+#define  HAVE_LIBZ        1
+#define  HAVE_LIBGIF      0
+#define  HAVE_LIBUNGIF    0
+#define  HAVE_LIBWEBP     0
+#define  HAVE_LIBJP2K     0
+
+    /* Leptonica supports both OpenJPEG 2.0 and 2.1.  If you have a
+     * version of openjpeg (HAVE_LIBJP2K) that is not 2.1, set the
+     * path to the openjpeg.h header in angle brackets here. */
+#define  LIBJP2K_HEADER   <openjpeg-2.1/openjpeg.h>
+#endif  /* ! HAVE_CONFIG_H etc. */
+
+/*
+ * On linux systems, you can do I/O between Pix and memory.  Specifically,
+ * you can compress (write compressed data to memory from a Pix) and
+ * uncompress (read from compressed data in memory to a Pix).
+ * For jpeg, png, jp2k, gif, pnm and bmp, these use the non-posix GNU
+ * functions fmemopen() and open_memstream().  These functions are not
+ * available on other systems.
+ * To use these functions in linux, you must define HAVE_FMEMOPEN to 1.
+ * To use them on MacOS, which does not support these functions, set it to 0.
+ */
+#if !defined(HAVE_CONFIG_H) && !defined(ANDROID_BUILD) && !defined(_MSC_VER)
+#define  HAVE_FMEMOPEN    1
+#endif  /* ! HAVE_CONFIG_H etc. */
+
+
+/*--------------------------------------------------------------------*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*
+ *                          USER CONFIGURABLE                         *
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*
+ *     Environ variables for image I/O without external libraries     *
+ *--------------------------------------------------------------------*/
+/*
+ *  Leptonica supplies I/O support without using external libraries for:
+ *     * image read/write for bmp, pnm
+ *     * header read for jp2k
+ *     * image wrapping write for pdf and ps.
+ *  Setting any of these to 0 causes non-functioning stubs to be linked.
+ */
+#define  USE_BMPIO        1
+#define  USE_PNMIO        1
+#define  USE_JP2KHEADER   1
+#define  USE_PDFIO        1
+#define  USE_PSIO         1
+
+
+/*--------------------------------------------------------------------*
+ * It is desirable on Windows to have all temp files written to the same
+ * subdirectory of the Windows <Temp> directory, because files under <Temp>
+ * persist after reboot, and the regression tests write a lot of files.
+ * We write all test files to /tmp/lept or subdirectories of /tmp/lept.
+ * Windows temp files are specified as in unix, but have the translation
+ *        /tmp/lept/xxx  -->   <Temp>/lept/xxx
+ *--------------------------------------------------------------------*/
+
+
+/*--------------------------------------------------------------------*
+ *                          Built-in types                            *
+ *--------------------------------------------------------------------*/
+typedef signed char             l_int8;
+typedef unsigned char           l_uint8;
+typedef short                   l_int16;
+typedef unsigned short          l_uint16;
+typedef int                     l_int32;
+typedef unsigned int            l_uint32;
+typedef float                   l_float32;
+typedef double                  l_float64;
+#ifdef COMPILER_MSVC
+typedef __int64                 l_int64;
+typedef unsigned __int64        l_uint64;
+#else
+typedef long long               l_int64;
+typedef unsigned long long      l_uint64;
+#endif  /* COMPILER_MSVC */
+
+
+/*------------------------------------------------------------------------*
+ *                            Standard macros                             *
+ *------------------------------------------------------------------------*/
+#ifndef L_MIN
+#define L_MIN(x,y)   (((x) < (y)) ? (x) : (y))
+#endif
+
+#ifndef L_MAX
+#define L_MAX(x,y)   (((x) > (y)) ? (x) : (y))
+#endif
+
+#ifndef L_ABS
+#define L_ABS(x)     (((x) < 0) ? (-1 * (x)) : (x))
+#endif
+
+#ifndef L_SIGN
+#define L_SIGN(x)    (((x) < 0) ? -1 : 1)
+#endif
+
+#ifndef UNDEF
+#define UNDEF        -1
+#endif
+
+#ifndef NULL
+#define NULL          0
+#endif
+
+#ifndef TRUE
+#define TRUE          1
+#endif
+
+#ifndef FALSE
+#define FALSE         0
+#endif
+
+
+/*--------------------------------------------------------------------*
+ *            Environment variables for endian dependence             *
+ *--------------------------------------------------------------------*/
+/*
+ *  To control conditional compilation, one of two variables
+ *
+ *       L_LITTLE_ENDIAN  (e.g., for Intel X86)
+ *       L_BIG_ENDIAN     (e.g., for Sun SPARC, Mac Power PC)
+ *
+ *  is defined when the GCC compiler is invoked.
+ *  All code should compile properly for both hardware architectures.
+ */
+
+
+/*------------------------------------------------------------------------*
+ *                    Simple search state variables                       *
+ *------------------------------------------------------------------------*/
+enum {
+    L_NOT_FOUND = 0,
+    L_FOUND = 1
+};
+
+
+/*------------------------------------------------------------------------*
+ *                     Path separator conversion                          *
+ *------------------------------------------------------------------------*/
+enum {
+    UNIX_PATH_SEPCHAR = 0,
+    WIN_PATH_SEPCHAR = 1
+};
+
+
+/*------------------------------------------------------------------------*
+ *                          Timing structs                                *
+ *------------------------------------------------------------------------*/
+typedef void *L_TIMER;
+struct L_WallTimer {
+    l_int32  start_sec;
+    l_int32  start_usec;
+    l_int32  stop_sec;
+    l_int32  stop_usec;
+};
+typedef struct L_WallTimer  L_WALLTIMER;
+
+
+/*------------------------------------------------------------------------*
+ *                      Standard memory allocation                        *
+ *                                                                        *
+ *  These specify the memory management functions that are used           *
+ *  on all heap data except for Pix.  Memory management for Pix           *
+ *  also defaults to malloc and free.  See pix1.c for details.            *
+ *------------------------------------------------------------------------*/
+#define LEPT_MALLOC(blocksize)           malloc(blocksize)
+#define LEPT_CALLOC(numelem, elemsize)   calloc(numelem, elemsize)
+#define LEPT_REALLOC(ptr, blocksize)     realloc(ptr, blocksize)
+#define LEPT_FREE(ptr)                   free(ptr)
+
+
+/*------------------------------------------------------------------------*
+ *         Control printing of error, warning, and info messages          *
+ *                                                                        *
+ *  To omit all messages to stderr, simply define NO_CONSOLE_IO on the    *
+ *  command line.  For finer grained control, we have a mechanism         *
+ *  based on the message severity level.  The following assumes that      *
+ *  NO_CONSOLE_IO is not defined.                                         *
+ *                                                                        *
+ *  Messages are printed if the message severity is greater than or equal *
+ *  to the current severity threshold.  The current severity threshold    *
+ *  is the greater of the compile-time severity, which is the minimum     *
+ *  severity that can be reported, and the run-time severity, which is    *
+ *  the severity threshold at the moment.                                 *
+ *                                                                        *
+ *  The compile-time threshold determines which messages are compiled     *
+ *  into the library for potential printing.  Messages below the          *
+ *  compile-time threshold are omitted and can never be printed.  The     *
+ *  default compile-time threshold is L_SEVERITY_INFO, but this may be    *
+ *  overridden by defining MINIMUM_SEVERITY to the desired enumeration    *
+ *  identifier on the compiler command line.  Defining NO_CONSOLE_IO on   *
+ *  the command line is the same as setting MINIMUM_SEVERITY to           *
+ *  L_SEVERITY_NONE.                                                      *
+ *                                                                        *
+ *  The run-time threshold determines which messages are printed during   *
+ *  library execution.  It defaults to the compile-time threshold but     *
+ *  may be changed either statically by defining DEFAULT_SEVERITY to      *
+ *  the desired enumeration identifier on the compiler command line, or   *
+ *  dynamically by calling setMsgSeverity() to specify a new threshold.   *
+ *  The run-time threshold may also be set from the value of the          *
+ *  environment variable LEPT_MSG_SEVERITY by calling setMsgSeverity()   *
+ *  and specifying L_SEVERITY_EXTERNAL.                                   *
+ *                                                                        *
+ *  In effect, the compile-time threshold setting says, "Generate code    *
+ *  to permit messages of equal or greater severity than this to be       *
+ *  printed, if desired," whereas the run-time threshold setting says,    *
+ *  "Print messages that have an equal or greater severity than this."    *
+ *------------------------------------------------------------------------*/
+enum {
+    L_SEVERITY_EXTERNAL = 0,   /* Get the severity from the environment   */
+    L_SEVERITY_ALL      = 1,   /* Lowest severity: print all messages     */
+    L_SEVERITY_DEBUG    = 2,   /* Print debugging and higher messages     */
+    L_SEVERITY_INFO     = 3,   /* Print informational and higher messages */
+    L_SEVERITY_WARNING  = 4,   /* Print warning and higher messages       */
+    L_SEVERITY_ERROR    = 5,   /* Print error and higher messages         */
+    L_SEVERITY_NONE     = 6    /* Highest severity: print no messages     */
+};
+
+/*  No message less than the compile-time threshold will ever be
+ *  reported, regardless of the current run-time threshold.  This allows
+ *  selection of the set of messages to include in the library.  For
+ *  example, setting the threshold to L_SEVERITY_WARNING eliminates all
+ *  informational messages from the library.  With that setting, both
+ *  warning and error messages would be printed unless setMsgSeverity()
+ *  was called, or DEFAULT_SEVERITY was redefined, to set the run-time
+ *  severity to L_SEVERITY_ERROR.  In that case, only error messages
+ *  would be printed.
+ *
+ *  This mechanism makes the library smaller and faster, by eliminating
+ *  undesired message reporting and the associated run-time overhead for
+ *  message threshold checking, because code for messages whose severity
+ *  is lower than MINIMUM_SEVERITY won't be generated.
+ *
+ *  A production library might typically permit WARNING and higher
+ *  messages to be generated, and a development library might permit
+ *  DEBUG and higher.  The actual messages printed (as opposed to
+ *  generated) would depend on the current run-time severity threshold.
+ */
+
+#ifdef  NO_CONSOLE_IO
+  #undef MINIMUM_SEVERITY
+  #undef DEFAULT_SEVERITY
+
+  #define MINIMUM_SEVERITY      L_SEVERITY_NONE
+  #define DEFAULT_SEVERITY      L_SEVERITY_NONE
+
+#else
+  #ifndef MINIMUM_SEVERITY
+    #define MINIMUM_SEVERITY    L_SEVERITY_INFO    /* Compile-time default */
+  #endif
+
+  #ifndef DEFAULT_SEVERITY
+    #define DEFAULT_SEVERITY    MINIMUM_SEVERITY   /* Run-time default */
+  #endif
+#endif
+
+
+/*  The run-time message severity threshold is defined in utils.c.  */
+LEPT_DLL extern l_int32  LeptMsgSeverity;
+
+/*
+ *  Usage
+ *  =====
+ *  Messages are of two types.
+ *
+ *  (1) The messages
+ *      ERROR_INT(a,b,c)       : returns l_int32
+ *      ERROR_FLOAT(a,b,c)     : returns l_float32
+ *      ERROR_PTR(a,b,c)       : returns void*
+ *  are used to return from functions and take a fixed set of parameters:
+ *      a : <message string>
+ *      b : procName
+ *      c : <return value from function>
+ *  where procName is the name of the local variable naming the function.
+ *
+ *  (2) The purely informational L_* messages
+ *      L_ERROR(a,...)
+ *      L_WARNING(a,...)
+ *      L_INFO(a,...)
+ *  do not take a return value, but they take at least two parameters:
+ *      a  :  <message string> with optional format conversions
+ *      v1 : procName    (this must be included as the first vararg)
+ *      v2, ... :  optional varargs to match format converters in the message
+ *
+ *  To return an error from a function that returns void, use:
+ *      L_ERROR(<message string>, procName, [...])
+ *      return;
+ *
+ *  Implementation details
+ *  ======================
+ *  Messages are defined with the IF_SEV macro.  The first parameter is
+ *  the message severity, the second is the function to call if the
+ *  message is to be printed, and the third is the return value if the
+ *  message is to be suppressed.  For example, we might have an
+ *  informational message defined as:
+ *
+ *    IF_SEV(L_SEVERITY_INFO, fprintf(.......), 0)
+ *
+ *  The macro expands into a conditional.  Because the first comparison
+ *  is between two constants, an optimizing compiler will remove either
+ *  the comparison (if it's true) or the entire macro expansion (if it
+ *  is false).  This means that there is no run-time overhead for
+ *  messages whose severity falls below the minimum specified at compile
+ *  time, and for others the overhead is one (not two) comparisons.
+ *
+ *  The L_nnn() macros below do not return a value, but because the
+ *  conditional operator requires one for the false condition, we
+ *  specify a void expression.
+ */
+
+#ifdef  NO_CONSOLE_IO
+
+  #define PROCNAME(name)
+  #define ERROR_INT(a,b,c)            ((l_int32)(c))
+  #define ERROR_FLOAT(a,b,c)          ((l_float32)(c))
+  #define ERROR_PTR(a,b,c)            ((void *)(c))
+  #define L_ERROR(a,...)
+  #define L_WARNING(a,...)
+  #define L_INFO(a,...)
+
+#else
+
+  #define PROCNAME(name)              static const char procName[] = name
+  #define IF_SEV(l,t,f) \
+      ((l) >= MINIMUM_SEVERITY && (l) >= LeptMsgSeverity ? (t) : (f))
+
+  #define ERROR_INT(a,b,c) \
+      IF_SEV(L_SEVERITY_ERROR, returnErrorInt((a),(b),(c)), (l_int32)(c))
+  #define ERROR_FLOAT(a,b,c) \
+      IF_SEV(L_SEVERITY_ERROR, returnErrorFloat((a),(b),(c)), (l_float32)(c))
+  #define ERROR_PTR(a,b,c) \
+      IF_SEV(L_SEVERITY_ERROR, returnErrorPtr((a),(b),(c)), (void *)(c))
+
+  #define L_ERROR(a,...) \
+      IF_SEV(L_SEVERITY_ERROR, \
+             (void)fprintf(stderr, "Error in %s: " a, __VA_ARGS__), \
+             (void)0)
+  #define L_WARNING(a,...) \
+      IF_SEV(L_SEVERITY_WARNING, \
+             (void)fprintf(stderr, "Warning in %s: " a, __VA_ARGS__), \
+             (void)0)
+  #define L_INFO(a,...) \
+      IF_SEV(L_SEVERITY_INFO, \
+             (void)fprintf(stderr, "Info in %s: " a, __VA_ARGS__), \
+             (void)0)
+
+#if 0  /* Alternative method for controlling L_* message output */
+  #define L_ERROR(a,...) \
+    { if (L_SEVERITY_ERROR >= MINIMUM_SEVERITY && \
+          L_SEVERITY_ERROR >= LeptMsgSeverity) \
+          fprintf(stderr, "Error in %s: " a, __VA_ARGS__) \
+    }
+  #define L_WARNING(a,...) \
+    { if (L_SEVERITY_WARNING >= MINIMUM_SEVERITY && \
+          L_SEVERITY_WARNING >= LeptMsgSeverity) \
+          fprintf(stderr, "Warning in %s: " a, __VA_ARGS__) \
+    }
+  #define L_INFO(a,...) \
+    { if (L_SEVERITY_INFO >= MINIMUM_SEVERITY && \
+          L_SEVERITY_INFO >= LeptMsgSeverity) \
+             fprintf(stderr, "Info in %s: " a, __VA_ARGS__) \
+    }
+#endif
+
+#endif  /* NO_CONSOLE_IO */
+
+
+/*------------------------------------------------------------------------*
+ *                        snprintf() renamed in MSVC                      *
+ *------------------------------------------------------------------------*/
+#ifdef _MSC_VER
+#define snprintf(buf, size, ...)  _snprintf_s(buf, size, _TRUNCATE, __VA_ARGS__)
+#endif
+
+
+#endif /* LEPTONICA_ENVIRON_H */
diff --git a/src/fhmtauto.c b/src/fhmtauto.c
new file mode 100644 (file)
index 0000000..9f103e6
--- /dev/null
@@ -0,0 +1,797 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  fhmtauto.c
+ *
+ *    Main function calls:
+ *       l_int32             fhmtautogen()
+ *       l_int32             fhmtautogen1()
+ *       l_int32             fhmtautogen2()
+ *
+ *    Static helpers:
+ *       static SARRAY      *sarrayMakeWplsCode()
+ *       static SARRAY      *sarrayMakeInnerLoopDWACode()
+ *       static char        *makeBarrelshiftString()
+ *
+ *    This automatically generates dwa code for the hit-miss transform.
+ *    Here's a road map for how it all works.
+ *
+ *    (1) You generate an array (a SELA) of hit-miss transform SELs.
+ *        This can be done in several ways, including
+ *           (a) calling the function selaAddHitMiss() for
+ *               pre-compiled SELs
+ *           (b) generating the SELA in code in line
+ *           (c) reading in a SELA from file, using selaRead()
+ *               or various other formats.
+ *
+ *    (2) You call fhmtautogen1() and fhmtautogen2() on this SELA.
+ *        This uses the text files hmttemplate1.txt and
+ *        hmttemplate2.txt for building up the source code.  See the file
+ *        prog/fhmtautogen.c for an example of how this is done.
+ *        The output is written to files named fhmtgen.*.c
+ *        and fhmtgenlow.*.c, where "*" is an integer that you
+ *        input to this function.  That integer labels both
+ *        the output files, as well as all the functions that
+ *        are generated.  That way, using different integers,
+ *        you can invoke fhmtautogen() any number of times
+ *        to get functions that all have different names so that
+ *        they can be linked into one program.
+ *
+ *    (3) You copy the generated source code back to your src
+ *        directory for compilation.  Put their names in the
+ *        Makefile, regnerate the prototypes, and recompile
+ *        the libraries.  Look at the Makefile to see how I've
+ *        included fhmtgen.1.c and fhmtgenlow.1.c.  These files
+ *        provide the high-level interfaces for the hmt, and
+ *        the low-level interfaces to do the actual work.
+ *
+ *    (4) In an application, you now use this interface.  Again
+ *        for the example files generated, using integer "1":
+ *
+ *           PIX   *pixHMTDwa_1(PIX *pixd, PIX *pixs, const char *selname);
+ *
+ *              or
+ *
+ *           PIX   *pixFHMTGen_1(PIX *pixd, PIX *pixs, const char *selname);
+ *
+ *        where the selname is one of the set that were defined
+ *        as the name field of sels.  This set is listed at the
+ *        beginning of the file fhmtgen.1.c.
+ *        As an example, see the file prog/fmtauto_reg.c, which
+ *        verifies the correctness of the implementation by
+ *        comparing the dwa result with that of full-image
+ *        rasterops.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+#define   OUTROOT         "fhmtgen"
+#define   TEMPLATE1       "hmttemplate1.txt"
+#define   TEMPLATE2       "hmttemplate2.txt"
+
+#define   PROTOARGS   "(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);"
+
+static const l_int32  L_BUF_SIZE = 512;
+
+static char * makeBarrelshiftString(l_int32 delx, l_int32 dely, l_int32 type);
+static SARRAY * sarrayMakeInnerLoopDWACode(SEL *sel, l_int32 nhits, l_int32 nmisses);
+static SARRAY * sarrayMakeWplsCode(SEL *sel);
+
+static char wpldecls[][60] = {
+            "l_int32             wpls2;",
+            "l_int32             wpls2, wpls3;",
+            "l_int32             wpls2, wpls3, wpls4;",
+            "l_int32             wpls5;",
+            "l_int32             wpls5, wpls6;",
+            "l_int32             wpls5, wpls6, wpls7;",
+            "l_int32             wpls5, wpls6, wpls7, wpls8;",
+            "l_int32             wpls9;",
+            "l_int32             wpls9, wpls10;",
+            "l_int32             wpls9, wpls10, wpls11;",
+            "l_int32             wpls9, wpls10, wpls11, wpls12;",
+            "l_int32             wpls13;",
+            "l_int32             wpls13, wpls14;",
+            "l_int32             wpls13, wpls14, wpls15;",
+            "l_int32             wpls13, wpls14, wpls15, wpls16;",
+            "l_int32             wpls17;",
+            "l_int32             wpls17, wpls18;",
+            "l_int32             wpls17, wpls18, wpls19;",
+            "l_int32             wpls17, wpls18, wpls19, wpls20;",
+            "l_int32             wpls21;",
+            "l_int32             wpls21, wpls22;",
+            "l_int32             wpls21, wpls22, wpls23;",
+            "l_int32             wpls21, wpls22, wpls23, wpls24;",
+            "l_int32             wpls25;",
+            "l_int32             wpls25, wpls26;",
+            "l_int32             wpls25, wpls26, wpls27;",
+            "l_int32             wpls25, wpls26, wpls27, wpls28;",
+            "l_int32             wpls29;",
+            "l_int32             wpls29, wpls30;",
+            "l_int32             wpls29, wpls30, wpls31;"};
+
+static char wpldefs[][24] = {
+            "    wpls2 = 2 * wpls;",
+            "    wpls3 = 3 * wpls;",
+            "    wpls4 = 4 * wpls;",
+            "    wpls5 = 5 * wpls;",
+            "    wpls6 = 6 * wpls;",
+            "    wpls7 = 7 * wpls;",
+            "    wpls8 = 8 * wpls;",
+            "    wpls9 = 9 * wpls;",
+            "    wpls10 = 10 * wpls;",
+            "    wpls11 = 11 * wpls;",
+            "    wpls12 = 12 * wpls;",
+            "    wpls13 = 13 * wpls;",
+            "    wpls14 = 14 * wpls;",
+            "    wpls15 = 15 * wpls;",
+            "    wpls16 = 16 * wpls;",
+            "    wpls17 = 17 * wpls;",
+            "    wpls18 = 18 * wpls;",
+            "    wpls19 = 19 * wpls;",
+            "    wpls20 = 20 * wpls;",
+            "    wpls21 = 21 * wpls;",
+            "    wpls22 = 22 * wpls;",
+            "    wpls23 = 23 * wpls;",
+            "    wpls24 = 24 * wpls;",
+            "    wpls25 = 25 * wpls;",
+            "    wpls26 = 26 * wpls;",
+            "    wpls27 = 27 * wpls;",
+            "    wpls28 = 28 * wpls;",
+            "    wpls29 = 29 * wpls;",
+            "    wpls30 = 30 * wpls;",
+            "    wpls31 = 31 * wpls;"};
+
+static char wplstrp[][10] = {"+ wpls", "+ wpls2", "+ wpls3", "+ wpls4",
+                             "+ wpls5", "+ wpls6", "+ wpls7", "+ wpls8",
+                             "+ wpls9", "+ wpls10", "+ wpls11", "+ wpls12",
+                             "+ wpls13", "+ wpls14", "+ wpls15", "+ wpls16",
+                             "+ wpls17", "+ wpls18", "+ wpls19", "+ wpls20",
+                             "+ wpls21", "+ wpls22", "+ wpls23", "+ wpls24",
+                             "+ wpls25", "+ wpls26", "+ wpls27", "+ wpls28",
+                             "+ wpls29", "+ wpls30", "+ wpls31"};
+
+static char wplstrm[][10] = {"- wpls", "- wpls2", "- wpls3", "- wpls4",
+                             "- wpls5", "- wpls6", "- wpls7", "- wpls8",
+                             "- wpls9", "- wpls10", "- wpls11", "- wpls12",
+                             "- wpls13", "- wpls14", "- wpls15", "- wpls16",
+                             "- wpls17", "- wpls18", "- wpls19", "- wpls20",
+                             "- wpls21", "- wpls22", "- wpls23", "- wpls24",
+                             "- wpls25", "- wpls26", "- wpls27", "- wpls28",
+                             "- wpls29", "- wpls30", "- wpls31"};
+
+
+/*!
+ *  fhmtautogen()
+ *
+ *      Input:  sela
+ *              fileindex
+ *              filename (<optional>; can be null)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This function generates all the code for implementing
+ *          dwa morphological operations using all the sels in the sela.
+ *      (2) See fhmtautogen1() and fhmtautogen2() for details.
+ */
+l_int32
+fhmtautogen(SELA        *sela,
+            l_int32      fileindex,
+            const char  *filename)
+{
+l_int32  ret1, ret2;
+
+    PROCNAME("fhmtautogen");
+
+    if (!sela)
+        return ERROR_INT("sela not defined", procName, 1);
+    ret1 = fhmtautogen1(sela, fileindex, filename);
+    ret2 = fhmtautogen2(sela, fileindex, filename);
+    if (ret1 || ret2)
+        return ERROR_INT("code generation problem", procName, 1);
+    return 0;
+}
+
+
+/*!
+ *  fhmtautogen1()
+ *
+ *      Input:  sel array
+ *              fileindex
+ *              filename (<optional>; can be null)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This function uses hmttemplate1.txt to create a
+ *          top-level file that contains two functions that carry
+ *          out the hit-miss transform for any of the sels in
+ *          the input sela.
+ *      (2) The fileindex parameter is inserted into the output
+ *          filename, as described below.
+ *      (3) If filename == NULL, the output file is fhmtgen.<n>.c,
+ *          where <n> is equal to the 'fileindex' parameter.
+ *      (4) If filename != NULL, the output file is <filename>.<n>.c.
+ *      (5) Each sel must have at least one hit.  A sel with only misses
+ *          generates code that will abort the operation if it is called.
+ */
+l_int32
+fhmtautogen1(SELA        *sela,
+             l_int32      fileindex,
+             const char  *filename)
+{
+char    *filestr;
+char    *str_proto1, *str_proto2, *str_proto3;
+char    *str_doc1, *str_doc2, *str_doc3, *str_doc4;
+char    *str_def1, *str_def2, *str_proc1, *str_proc2;
+char    *str_dwa1, *str_low_dt, *str_low_ds;
+char     bigbuf[L_BUF_SIZE];
+l_int32  i, nsels, nbytes, actstart, end, newstart;
+size_t   size;
+SARRAY  *sa1, *sa2, *sa3;
+
+    PROCNAME("fhmtautogen1");
+
+    if (!sela)
+        return ERROR_INT("sela not defined", procName, 1);
+    if (fileindex < 0)
+        fileindex = 0;
+    if ((nsels = selaGetCount(sela)) == 0)
+        return ERROR_INT("no sels in sela", procName, 1);
+
+        /* Make array of sel names */
+    sa1 = selaGetSelnames(sela);
+
+        /* Make array of textlines from from hmttemplate1.txt */
+    if ((filestr = (char *)l_binaryRead(TEMPLATE1, &size)) == NULL)
+        return ERROR_INT("filestr not made", procName, 1);
+    if ((sa2 = sarrayCreateLinesFromString(filestr, 1)) == NULL)
+        return ERROR_INT("sa2 not made", procName, 1);
+    LEPT_FREE(filestr);
+
+        /* Make strings containing function call names */
+    sprintf(bigbuf, "PIX *pixHMTDwa_%d(PIX *pixd, PIX *pixs, "
+                    "const char *selname);", fileindex);
+    str_proto1 = stringNew(bigbuf);
+    sprintf(bigbuf, "PIX *pixFHMTGen_%d(PIX *pixd, PIX *pixs, "
+                    "const char *selname);", fileindex);
+    str_proto2 = stringNew(bigbuf);
+    sprintf(bigbuf, "l_int32 fhmtgen_low_%d(l_uint32 *datad, l_int32 w,\n"
+            "                      l_int32 h, l_int32 wpld,\n"
+            "                      l_uint32 *datas, l_int32 wpls,\n"
+            "                      l_int32 index);", fileindex);
+    str_proto3 = stringNew(bigbuf);
+    sprintf(bigbuf, " *             PIX     *pixHMTDwa_%d()", fileindex);
+    str_doc1 = stringNew(bigbuf);
+    sprintf(bigbuf, " *             PIX     *pixFHMTGen_%d()", fileindex);
+    str_doc2 = stringNew(bigbuf);
+    sprintf(bigbuf, " *  pixHMTDwa_%d()", fileindex);
+    str_doc3 = stringNew(bigbuf);
+    sprintf(bigbuf, " *  pixFHMTGen_%d()", fileindex);
+    str_doc4 = stringNew(bigbuf);
+    sprintf(bigbuf, "pixHMTDwa_%d(PIX         *pixd,", fileindex);
+    str_def1 = stringNew(bigbuf);
+    sprintf(bigbuf, "pixFHMTGen_%d(PIX         *pixd,", fileindex);
+    str_def2 = stringNew(bigbuf);
+    sprintf(bigbuf, "    PROCNAME(\"pixHMTDwa_%d\");", fileindex);
+    str_proc1 = stringNew(bigbuf);
+    sprintf(bigbuf, "    PROCNAME(\"pixFHMTGen_%d\");", fileindex);
+    str_proc2 = stringNew(bigbuf);
+    sprintf(bigbuf, "    pixt2 = pixFHMTGen_%d(NULL, pixt1, selname);",
+            fileindex);
+    str_dwa1 = stringNew(bigbuf);
+    sprintf(bigbuf,
+            "        fhmtgen_low_%d(datad, w, h, wpld, datat, wpls, index);",
+            fileindex);
+    str_low_dt = stringNew(bigbuf);
+    sprintf(bigbuf,
+            "        fhmtgen_low_%d(datad, w, h, wpld, datas, wpls, index);",
+            fileindex);
+    str_low_ds = stringNew(bigbuf);
+
+        /* Make the output sa */
+    if ((sa3 = sarrayCreate(0)) == NULL)
+        return ERROR_INT("sa3 not made", procName, 1);
+
+        /* Copyright notice and info header */
+    sarrayParseRange(sa2, 0, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Insert function names as documentation */
+    sarrayAddString(sa3, str_doc1, L_INSERT);
+    sarrayAddString(sa3, str_doc2, L_INSERT);
+
+        /* Add '#include's */
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Insert function prototypes */
+    sarrayAddString(sa3, str_proto1, L_INSERT);
+    sarrayAddString(sa3, str_proto2, L_INSERT);
+    sarrayAddString(sa3, str_proto3, L_INSERT);
+
+        /* Add static globals */
+    sprintf(bigbuf, "\nstatic l_int32   NUM_SELS_GENERATED = %d;", nsels);
+    sarrayAddString(sa3, bigbuf, L_COPY);
+    sprintf(bigbuf, "static char  SEL_NAMES[][80] = {");
+    sarrayAddString(sa3, bigbuf, L_COPY);
+    for (i = 0; i < nsels - 1; i++) {
+        sprintf(bigbuf, "                             \"%s\",",
+                sarrayGetString(sa1, i, L_NOCOPY));
+        sarrayAddString(sa3, bigbuf, L_COPY);
+    }
+    sprintf(bigbuf, "                             \"%s\"};",
+            sarrayGetString(sa1, i, L_NOCOPY));
+    sarrayAddString(sa3, bigbuf, L_COPY);
+
+        /* Start pixHMTDwa_*() function description */
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_doc3, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Finish pixHMTDwa_*() function definition */
+    sarrayAddString(sa3, str_def1, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_proc1, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_dwa1, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Start pixFHMTGen_*() function description */
+    sarrayAddString(sa3, str_doc4, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Finish pixFHMTGen_*() function description */
+    sarrayAddString(sa3, str_def2, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_proc2, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_low_dt, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_low_ds, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+    if ((filestr = sarrayToString(sa3, 1)) == NULL)
+        return ERROR_INT("filestr from sa3 not made", procName, 1);
+    nbytes = strlen(filestr);
+    if (filename)
+        sprintf(bigbuf, "%s.%d.c", filename, fileindex);
+    else
+        sprintf(bigbuf, "%s.%d.c", OUTROOT, fileindex);
+    l_binaryWrite(bigbuf, "w", filestr, nbytes);
+    sarrayDestroy(&sa1);
+    sarrayDestroy(&sa2);
+    sarrayDestroy(&sa3);
+    LEPT_FREE(filestr);
+    return 0;
+}
+
+
+/*!
+ *  fhmtautogen2()
+ *
+ *      Input:  sel array
+ *              fileindex
+ *              filename (<optional>; can be null)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This function uses hmttemplate2.txt to create a
+ *          low-level file that contains the low-level functions for
+ *          implementing the hit-miss transform for every sel
+ *          in the input sela.
+ *      (2) The fileindex parameter is inserted into the output
+ *          filename, as described below.
+ *      (3) If filename == NULL, the output file is fhmtgenlow.<n>.c,
+ *          where <n> is equal to the 'fileindex' parameter.
+ *      (4) If filename != NULL, the output file is <filename>low.<n>.c.
+ */
+l_int32
+fhmtautogen2(SELA        *sela,
+             l_int32      fileindex,
+             const char  *filename)
+{
+char    *filestr, *fname, *linestr;
+char    *str_doc1, *str_doc2, *str_doc3, *str_def1;
+char     bigbuf[L_BUF_SIZE];
+char     breakstring[] = "        break;";
+char     staticstring[] = "static void";
+l_int32  i, k, l, nsels, nbytes, nhits, nmisses;
+l_int32  actstart, end, newstart;
+l_int32  argstart, argend, loopstart, loopend, finalstart, finalend;
+size_t   size;
+SARRAY  *sa1, *sa2, *sa3, *sa4, *sa5, *sa6;
+SEL     *sel;
+
+    PROCNAME("fhmtautogen2");
+
+    if (!sela)
+        return ERROR_INT("sela not defined", procName, 1);
+    if (fileindex < 0)
+        fileindex = 0;
+    if ((nsels = selaGetCount(sela)) == 0)
+        return ERROR_INT("no sels in sela", procName, 1);
+
+        /* Make the array of textlines from hmttemplate2.txt */
+    if ((filestr = (char *)l_binaryRead(TEMPLATE2, &size)) == NULL)
+        return ERROR_INT("filestr not made", procName, 1);
+    if ((sa1 = sarrayCreateLinesFromString(filestr, 1)) == NULL)
+        return ERROR_INT("sa1 not made", procName, 1);
+    LEPT_FREE(filestr);
+
+        /* Make the array of static function names */
+    if ((sa2 = sarrayCreate(nsels)) == NULL)
+        return ERROR_INT("sa2 not made", procName, 1);
+    for (i = 0; i < nsels; i++) {
+        sprintf(bigbuf, "fhmt_%d_%d", fileindex, i);
+        sarrayAddString(sa2, bigbuf, L_COPY);
+    }
+
+        /* Make the static prototype strings */
+    if ((sa3 = sarrayCreate(2 * nsels)) == NULL)
+        return ERROR_INT("sa3 not made", procName, 1);
+    for (i = 0; i < nsels; i++) {
+        fname = sarrayGetString(sa2, i, L_NOCOPY);
+        sprintf(bigbuf, "static void  %s%s", fname, PROTOARGS);
+        sarrayAddString(sa3, bigbuf, L_COPY);
+    }
+
+        /* Make strings containing function names */
+    sprintf(bigbuf, " *             l_int32    fhmtgen_low_%d()",
+            fileindex);
+    str_doc1 = stringNew(bigbuf);
+    sprintf(bigbuf, " *             void       fhmt_%d_*()", fileindex);
+    str_doc2 = stringNew(bigbuf);
+    sprintf(bigbuf, " *  fhmtgen_low_%d()", fileindex);
+    str_doc3 = stringNew(bigbuf);
+    sprintf(bigbuf, "fhmtgen_low_%d(l_uint32  *datad,", fileindex);
+    str_def1 = stringNew(bigbuf);
+
+        /* Output to this sa */
+    if ((sa4 = sarrayCreate(0)) == NULL)
+        return ERROR_INT("sa4 not made", procName, 1);
+
+        /* Copyright notice and info header */
+    sarrayParseRange(sa1, 0, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+
+        /* Insert function names as documentation */
+    sarrayAddString(sa4, str_doc1, L_INSERT);
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+    sarrayAddString(sa4, str_doc2, L_INSERT);
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+
+        /* Insert static protos */
+    for (i = 0; i < nsels; i++) {
+        if ((linestr = sarrayGetString(sa3, i, L_COPY)) == NULL)
+            return ERROR_INT("linestr not retrieved", procName, 1);
+        sarrayAddString(sa4, linestr, L_INSERT);
+    }
+
+        /* Insert function header */
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+    sarrayAddString(sa4, str_doc3, L_INSERT);
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+    sarrayAddString(sa4, str_def1, L_INSERT);
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+
+        /* Generate and insert the dispatcher code */
+    for (i = 0; i < nsels; i++) {
+        sprintf(bigbuf, "    case %d:", i);
+        sarrayAddString(sa4, bigbuf, L_COPY);
+        sprintf(bigbuf, "        %s(datad, w, h, wpld, datas, wpls);",
+               sarrayGetString(sa2, i, L_NOCOPY));
+        sarrayAddString(sa4, bigbuf, L_COPY);
+        sarrayAddString(sa4, breakstring, L_COPY);
+    }
+
+        /* Finish the dispatcher and introduce the low-level code */
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+
+        /* Get the range for the args common to all functions */
+    sarrayParseRange(sa1, newstart, &argstart, &argend, &newstart, "--", 0);
+
+        /* Get the range for the loop code common to all functions */
+    sarrayParseRange(sa1, newstart, &loopstart, &loopend, &newstart, "--", 0);
+
+        /* Get the range for the ending code common to all functions */
+    sarrayParseRange(sa1, newstart, &finalstart, &finalend, &newstart, "--", 0);
+
+        /* Do all the static functions */
+    for (i = 0; i < nsels; i++) {
+            /* Generate the function header and add the common args */
+        sarrayAddString(sa4, staticstring, L_COPY);
+        fname = sarrayGetString(sa2, i, L_NOCOPY);
+        sprintf(bigbuf, "%s(l_uint32  *datad,", fname);
+        sarrayAddString(sa4, bigbuf, L_COPY);
+        sarrayAppendRange(sa4, sa1, argstart, argend);
+
+            /* Declare and define wplsN args, as necessary */
+        if ((sel = selaGetSel(sela, i)) == NULL)
+            return ERROR_INT("sel not returned", procName, 1);
+        if ((sa5 = sarrayMakeWplsCode(sel)) == NULL)
+            return ERROR_INT("sa5 not made", procName, 1);
+        sarrayJoin(sa4, sa5);
+        sarrayDestroy(&sa5);
+
+            /* Make sure sel has at least one hit */
+        nhits = 0;
+        nmisses = 0;
+        for (k = 0; k < sel->sy; k++) {
+            for (l = 0; l < sel->sx; l++) {
+                if (sel->data[k][l] == 1)
+                    nhits++;
+                else if (sel->data[k][l] == 2)
+                    nmisses++;
+            }
+        }
+        if (nhits == 0) {
+            linestr = stringNew("    fprintf(stderr, \"Error in HMT: no hits in sel!\\n\");\n}\n\n");
+            sarrayAddString(sa4, linestr, L_INSERT);
+            continue;
+        }
+
+            /* Add the function loop code */
+        sarrayAppendRange(sa4, sa1, loopstart, loopend);
+
+            /* Insert barrel-op code for *dptr */
+        if ((sa6 = sarrayMakeInnerLoopDWACode(sel, nhits, nmisses)) == NULL)
+            return ERROR_INT("sa6 not made", procName, 1);
+        sarrayJoin(sa4, sa6);
+        sarrayDestroy(&sa6);
+
+            /* Finish the function code */
+        sarrayAppendRange(sa4, sa1, finalstart, finalend);
+    }
+
+        /* Output to file */
+    if ((filestr = sarrayToString(sa4, 1)) == NULL)
+        return ERROR_INT("filestr from sa4 not made", procName, 1);
+    nbytes = strlen(filestr);
+    if (filename)
+        sprintf(bigbuf, "%slow.%d.c", filename, fileindex);
+    else
+        sprintf(bigbuf, "%slow.%d.c", OUTROOT, fileindex);
+    l_binaryWrite(bigbuf, "w", filestr, nbytes);
+    sarrayDestroy(&sa1);
+    sarrayDestroy(&sa2);
+    sarrayDestroy(&sa3);
+    sarrayDestroy(&sa4);
+    LEPT_FREE(filestr);
+
+    return 0;
+}
+
+
+
+/*--------------------------------------------------------------------------*
+ *                            Helper code for sel                           *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  sarrayMakeWplsCode()
+ */
+static SARRAY *
+sarrayMakeWplsCode(SEL  *sel)
+{
+char     emptystring[] = "";
+l_int32  i, j, ymax, dely;
+SARRAY  *sa;
+
+    PROCNAME("sarrayMakeWplsCode");
+
+    if (!sel)
+        return (SARRAY *)ERROR_PTR("sel not defined", procName, NULL);
+
+    ymax = 0;
+    for (i = 0; i < sel->sy; i++) {
+        for (j = 0; j < sel->sx; j++) {
+            if (sel->data[i][j] == 1 || sel->data[i][j] == 2) {
+                dely = L_ABS(i - sel->cy);
+                ymax = L_MAX(ymax, dely);
+            }
+        }
+    }
+    if (ymax > 31) {
+        L_WARNING("ymax > 31; truncating to 31\n", procName);
+        ymax = 31;
+    }
+
+    if ((sa = sarrayCreate(0)) == NULL)
+        return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+
+        /* Declarations */
+    if (ymax > 4)
+        sarrayAddString(sa, wpldecls[2], L_COPY);
+    if (ymax > 8)
+        sarrayAddString(sa, wpldecls[6], L_COPY);
+    if (ymax > 12)
+        sarrayAddString(sa, wpldecls[10], L_COPY);
+    if (ymax > 16)
+        sarrayAddString(sa, wpldecls[14], L_COPY);
+    if (ymax > 20)
+        sarrayAddString(sa, wpldecls[18], L_COPY);
+    if (ymax > 24)
+        sarrayAddString(sa, wpldecls[22], L_COPY);
+    if (ymax > 28)
+        sarrayAddString(sa, wpldecls[26], L_COPY);
+    if (ymax > 1)
+        sarrayAddString(sa, wpldecls[ymax - 2], L_COPY);
+
+    sarrayAddString(sa, emptystring, L_COPY);
+
+        /* Definitions */
+    for (i = 2; i <= ymax; i++)
+        sarrayAddString(sa, wpldefs[i - 2], L_COPY);
+
+    return sa;
+}
+
+
+/*!
+ *  sarrayMakeInnerLoopDWACode()
+ */
+static SARRAY *
+sarrayMakeInnerLoopDWACode(SEL     *sel,
+                           l_int32  nhits,
+                           l_int32  nmisses)
+{
+char    *string;
+char     land[] = "&";
+char     bigbuf[L_BUF_SIZE];
+l_int32  i, j, ntot, nfound, type, delx, dely;
+SARRAY  *sa;
+
+    PROCNAME("sarrayMakeInnerLoopDWACode");
+
+    if (!sel)
+        return (SARRAY *)ERROR_PTR("sel not defined", procName, NULL);
+
+    if ((sa = sarrayCreate(0)) == NULL)
+        return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+
+    ntot = nhits + nmisses;
+    nfound = 0;
+    for (i = 0; i < sel->sy; i++) {
+        for (j = 0; j < sel->sx; j++) {
+            type = sel->data[i][j];
+            if (type == SEL_HIT || type == SEL_MISS) {
+                nfound++;
+                dely = i - sel->cy;
+                delx = j - sel->cx;
+                if ((string = makeBarrelshiftString(delx, dely, type))
+                        == NULL) {
+                    L_WARNING("barrel shift string not made\n", procName);
+                    continue;
+                }
+                if (ntot == 1)  /* just one item */
+                    sprintf(bigbuf, "            *dptr = %s;", string);
+                else if (nfound == 1)
+                    sprintf(bigbuf, "            *dptr = %s %s", string, land);
+                else if (nfound < ntot)
+                    sprintf(bigbuf, "                    %s %s", string, land);
+                else  /* nfound == ntot */
+                    sprintf(bigbuf, "                    %s;", string);
+                sarrayAddString(sa, bigbuf, L_COPY);
+                LEPT_FREE(string);
+            }
+        }
+    }
+
+    return sa;
+}
+
+
+/*!
+ *  makeBarrelshiftString()
+ */
+static char *
+makeBarrelshiftString(l_int32  delx,    /* j - cx */
+                      l_int32  dely,    /* i - cy */
+                      l_int32  type)    /* SEL_HIT or SEL_MISS */
+{
+l_int32  absx, absy;
+char     bigbuf[L_BUF_SIZE];
+
+    PROCNAME("makeBarrelshiftString");
+
+    if (delx < -31 || delx > 31)
+        return (char *)ERROR_PTR("delx out of bounds", procName, NULL);
+    if (dely < -31 || dely > 31)
+        return (char *)ERROR_PTR("dely out of bounds", procName, NULL);
+    absx = L_ABS(delx);
+    absy = L_ABS(dely);
+
+    if (type == SEL_HIT) {
+        if ((delx == 0) && (dely == 0))
+            sprintf(bigbuf, "(*sptr)");
+        else if ((delx == 0) && (dely < 0))
+            sprintf(bigbuf, "(*(sptr %s))", wplstrm[absy - 1]);
+        else if ((delx == 0) && (dely > 0))
+            sprintf(bigbuf, "(*(sptr %s))", wplstrp[absy - 1]);
+        else if ((delx < 0) && (dely == 0))
+            sprintf(bigbuf, "((*(sptr) >> %d) | (*(sptr - 1) << %d))",
+                  absx, 32 - absx);
+        else if ((delx > 0) && (dely == 0))
+            sprintf(bigbuf, "((*(sptr) << %d) | (*(sptr + 1) >> %d))",
+                  absx, 32 - absx);
+        else if ((delx < 0) && (dely < 0))
+            sprintf(bigbuf, "((*(sptr %s) >> %d) | (*(sptr %s - 1) << %d))",
+                  wplstrm[absy - 1], absx, wplstrm[absy - 1], 32 - absx);
+        else if ((delx > 0) && (dely < 0))
+            sprintf(bigbuf, "((*(sptr %s) << %d) | (*(sptr %s + 1) >> %d))",
+                  wplstrm[absy - 1], absx, wplstrm[absy - 1], 32 - absx);
+        else if ((delx < 0) && (dely > 0))
+            sprintf(bigbuf, "((*(sptr %s) >> %d) | (*(sptr %s - 1) << %d))",
+                  wplstrp[absy - 1], absx, wplstrp[absy - 1], 32 - absx);
+        else  /*  ((delx > 0) && (dely > 0))  */
+            sprintf(bigbuf, "((*(sptr %s) << %d) | (*(sptr %s + 1) >> %d))",
+                  wplstrp[absy - 1], absx, wplstrp[absy - 1], 32 - absx);
+    } else {  /* type == SEL_MISS */
+        if ((delx == 0) && (dely == 0))
+            sprintf(bigbuf, "(~*sptr)");
+        else if ((delx == 0) && (dely < 0))
+            sprintf(bigbuf, "(~*(sptr %s))", wplstrm[absy - 1]);
+        else if ((delx == 0) && (dely > 0))
+            sprintf(bigbuf, "(~*(sptr %s))", wplstrp[absy - 1]);
+        else if ((delx < 0) && (dely == 0))
+            sprintf(bigbuf, "((~*(sptr) >> %d) | (~*(sptr - 1) << %d))",
+                  absx, 32 - absx);
+        else if ((delx > 0) && (dely == 0))
+            sprintf(bigbuf, "((~*(sptr) << %d) | (~*(sptr + 1) >> %d))",
+                  absx, 32 - absx);
+        else if ((delx < 0) && (dely < 0))
+            sprintf(bigbuf, "((~*(sptr %s) >> %d) | (~*(sptr %s - 1) << %d))",
+                  wplstrm[absy - 1], absx, wplstrm[absy - 1], 32 - absx);
+        else if ((delx > 0) && (dely < 0))
+            sprintf(bigbuf, "((~*(sptr %s) << %d) | (~*(sptr %s + 1) >> %d))",
+                  wplstrm[absy - 1], absx, wplstrm[absy - 1], 32 - absx);
+        else if ((delx < 0) && (dely > 0))
+            sprintf(bigbuf, "((~*(sptr %s) >> %d) | (~*(sptr %s - 1) << %d))",
+                  wplstrp[absy - 1], absx, wplstrp[absy - 1], 32 - absx);
+        else  /*  ((delx > 0) && (dely > 0))  */
+            sprintf(bigbuf, "((~*(sptr %s) << %d) | (~*(sptr %s + 1) >> %d))",
+                  wplstrp[absy - 1], absx, wplstrp[absy - 1], 32 - absx);
+    }
+
+    return stringNew(bigbuf);
+}
diff --git a/src/fhmtgen.1.c b/src/fhmtgen.1.c
new file mode 100644 (file)
index 0000000..2d0231d
--- /dev/null
@@ -0,0 +1,174 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*!
+ *      Top-level fast hit-miss transform with auto-generated sels
+ *
+ *             PIX     *pixHMTDwa_1()
+ *             PIX     *pixFHMTGen_1()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+PIX *pixHMTDwa_1(PIX *pixd, PIX *pixs, const char *selname);
+PIX *pixFHMTGen_1(PIX *pixd, PIX *pixs, const char *selname);
+l_int32 fhmtgen_low_1(l_uint32 *datad, l_int32 w,
+                      l_int32 h, l_int32 wpld,
+                      l_uint32 *datas, l_int32 wpls,
+                      l_int32 index);
+
+static l_int32   NUM_SELS_GENERATED = 10;
+static char  SEL_NAMES[][80] = {
+                             "sel_3hm",
+                             "sel_3de",
+                             "sel_3ue",
+                             "sel_3re",
+                             "sel_3le",
+                             "sel_sl1",
+                             "sel_ulc",
+                             "sel_urc",
+                             "sel_llc",
+                             "sel_lrc"};
+
+/*!
+ *  pixHMTDwa_1()
+ *
+ *      Input:  pixd (usual 3 choices: null, == pixs, != pixs)
+ *              pixs (1 bpp)
+ *              sel name
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This simply adds a 32 pixel border, calls the appropriate
+ *          pixFHMTGen_*(), and removes the border.
+ *          See notes below for that function.
+ */
+PIX *
+pixHMTDwa_1(PIX         *pixd,
+            PIX         *pixs,
+            const char  *selname)
+{
+PIX  *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixHMTDwa_1");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, pixd);
+
+    pixt1 = pixAddBorder(pixs, 32, 0);
+    pixt2 = pixFHMTGen_1(NULL, pixt1, selname);
+    pixt3 = pixRemoveBorder(pixt2, 32);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixCopy(pixd, pixt3);
+    pixDestroy(&pixt3);
+    return pixd;
+}
+
+
+/*!
+ *  pixFHMTGen_1()
+ *
+ *      Input:  pixd (usual 3 choices: null, == pixs, != pixs)
+ *              pixs (1 bpp)
+ *              sel name
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This is a dwa implementation of the hit-miss transform
+ *          on pixs by the sel.
+ *      (2) The sel must be limited in size to not more than 31 pixels
+ *          about the origin.  It must have at least one hit, and it
+ *          can have any number of misses.
+ *      (3) This handles all required setting of the border pixels
+ *          before erosion and dilation.
+ */
+PIX *
+pixFHMTGen_1(PIX         *pixd,
+             PIX         *pixs,
+             const char  *selname)
+{
+l_int32    i, index, found, w, h, wpls, wpld;
+l_uint32  *datad, *datas, *datat;
+PIX       *pixt;
+
+    PROCNAME("pixFHMTGen_1");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, pixd);
+
+    found = FALSE;
+    for (i = 0; i < NUM_SELS_GENERATED; i++) {
+        if (strcmp(selname, SEL_NAMES[i]) == 0) {
+            found = TRUE;
+            index = i;
+            break;
+        }
+    }
+    if (found == FALSE)
+        return (PIX *)ERROR_PTR("sel index not found", procName, pixd);
+
+    if (!pixd) {
+        if ((pixd = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    else  /* for in-place or pre-allocated */
+        pixResizeImageData(pixd, pixs);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+        /*  The images must be surrounded with 32 additional border
+         *  pixels, that we'll read from.  We fabricate a "proper"
+         *  image as the subimage within the border, having the
+         *  following parameters:  */
+    w = pixGetWidth(pixs) - 64;
+    h = pixGetHeight(pixs) - 64;
+    datas = pixGetData(pixs) + 32 * wpls + 1;
+    datad = pixGetData(pixd) + 32 * wpld + 1;
+
+    if (pixd == pixs) {  /* need temp image if in-place */
+        if ((pixt = pixCopy(NULL, pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+        datat = pixGetData(pixt) + 32 * wpls + 1;
+        fhmtgen_low_1(datad, w, h, wpld, datat, wpls, index);
+        pixDestroy(&pixt);
+    }
+    else {  /* not in-place */
+        fhmtgen_low_1(datad, w, h, wpld, datas, wpls, index);
+    }
+
+    return pixd;
+}
+
diff --git a/src/fhmtgenlow.1.c b/src/fhmtgenlow.1.c
new file mode 100644 (file)
index 0000000..a308a76
--- /dev/null
@@ -0,0 +1,445 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*!
+ *     Low-level fast hit-miss transform with auto-generated sels
+ *
+ *      Dispatcher:
+ *             l_int32    fhmtgen_low_1()
+ *
+ *      Static Low-level:
+ *             void       fhmt_1_*()
+ */
+
+#include "allheaders.h"
+
+static void  fhmt_1_0(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fhmt_1_1(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fhmt_1_2(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fhmt_1_3(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fhmt_1_4(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fhmt_1_5(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fhmt_1_6(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fhmt_1_7(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fhmt_1_8(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fhmt_1_9(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+
+
+/*---------------------------------------------------------------------*
+ *                           Fast hmt dispatcher                       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  fhmtgen_low_1()
+ *
+ *       a dispatcher to appropriate low-level code
+ */
+l_int32
+fhmtgen_low_1(l_uint32  *datad,
+              l_int32    w,
+              l_int32    h,
+              l_int32    wpld,
+              l_uint32  *datas,
+              l_int32    wpls,
+              l_int32    index)
+{
+
+    switch (index)
+    {
+    case 0:
+        fhmt_1_0(datad, w, h, wpld, datas, wpls);
+        break;
+    case 1:
+        fhmt_1_1(datad, w, h, wpld, datas, wpls);
+        break;
+    case 2:
+        fhmt_1_2(datad, w, h, wpld, datas, wpls);
+        break;
+    case 3:
+        fhmt_1_3(datad, w, h, wpld, datas, wpls);
+        break;
+    case 4:
+        fhmt_1_4(datad, w, h, wpld, datas, wpls);
+        break;
+    case 5:
+        fhmt_1_5(datad, w, h, wpld, datas, wpls);
+        break;
+    case 6:
+        fhmt_1_6(datad, w, h, wpld, datas, wpls);
+        break;
+    case 7:
+        fhmt_1_7(datad, w, h, wpld, datas, wpls);
+        break;
+    case 8:
+        fhmt_1_8(datad, w, h, wpld, datas, wpls);
+        break;
+    case 9:
+        fhmt_1_9(datad, w, h, wpld, datas, wpls);
+        break;
+    }
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                 Low-level auto-generated static routines                 *
+ *--------------------------------------------------------------------------*/
+/*
+ *  N.B.  In all the low-level routines, the part of the image
+ *        that is accessed has been clipped by 32 pixels on
+ *        all four sides.  This is done in the higher level
+ *        code by redefining w and h smaller and by moving the
+ *        start-of-image pointers up to the beginning of this
+ *        interior rectangle.
+ */
+static void
+fhmt_1_0(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((~*(sptr - wpls) >> 1) | (~*(sptr - wpls - 1) << 31)) &
+                    (~*(sptr - wpls)) &
+                    ((~*(sptr - wpls) << 1) | (~*(sptr - wpls + 1) >> 31)) &
+                    ((~*(sptr) >> 1) | (~*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((~*(sptr) << 1) | (~*(sptr + 1) >> 31)) &
+                    ((~*(sptr + wpls) >> 1) | (~*(sptr + wpls - 1) << 31)) &
+                    (~*(sptr + wpls)) &
+                    ((~*(sptr + wpls) << 1) | (~*(sptr + wpls + 1) >> 31));
+        }
+    }
+}
+
+static void
+fhmt_1_1(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((~*(sptr + wpls) >> 1) | (~*(sptr + wpls - 1) << 31)) &
+                    (~*(sptr + wpls)) &
+                    ((~*(sptr + wpls) << 1) | (~*(sptr + wpls + 1) >> 31));
+        }
+    }
+}
+
+static void
+fhmt_1_2(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((~*(sptr - wpls) >> 1) | (~*(sptr - wpls - 1) << 31)) &
+                    (~*(sptr - wpls)) &
+                    ((~*(sptr - wpls) << 1) | (~*(sptr - wpls + 1) >> 31)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31));
+        }
+    }
+}
+
+static void
+fhmt_1_3(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls)) &
+                    ((~*(sptr - wpls) << 1) | (~*(sptr - wpls + 1) >> 31)) &
+                    (*sptr) &
+                    ((~*(sptr) << 1) | (~*(sptr + 1) >> 31)) &
+                    (*(sptr + wpls)) &
+                    ((~*(sptr + wpls) << 1) | (~*(sptr + wpls + 1) >> 31));
+        }
+    }
+}
+
+static void
+fhmt_1_4(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((~*(sptr - wpls) >> 1) | (~*(sptr - wpls - 1) << 31)) &
+                    (*(sptr - wpls)) &
+                    ((~*(sptr) >> 1) | (~*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((~*(sptr + wpls) >> 1) | (~*(sptr + wpls - 1) << 31)) &
+                    (*(sptr + wpls));
+        }
+    }
+}
+
+static void
+fhmt_1_5(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((~*(sptr - wpls6) << 1) | (~*(sptr - wpls6 + 1) >> 31)) &
+                    ((*(sptr - wpls6) << 3) | (*(sptr - wpls6 + 1) >> 29)) &
+                    (~*(sptr - wpls2)) &
+                    ((*(sptr - wpls2) << 2) | (*(sptr - wpls2 + 1) >> 30)) &
+                    ((~*(sptr + wpls2) >> 1) | (~*(sptr + wpls2 - 1) << 31)) &
+                    ((*(sptr + wpls2) << 1) | (*(sptr + wpls2 + 1) >> 31)) &
+                    ((~*(sptr + wpls6) >> 2) | (~*(sptr + wpls6 - 1) << 30)) &
+                    (*(sptr + wpls6));
+        }
+    }
+}
+
+static void
+fhmt_1_6(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((~*(sptr - wpls) >> 1) | (~*(sptr - wpls - 1) << 31)) &
+                    (~*(sptr - wpls)) &
+                    ((~*(sptr - wpls) << 1) | (~*(sptr - wpls + 1) >> 31)) &
+                    ((~*(sptr - wpls) << 2) | (~*(sptr - wpls + 1) >> 30)) &
+                    ((~*(sptr) >> 1) | (~*(sptr - 1) << 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((~*(sptr + wpls) >> 1) | (~*(sptr + wpls - 1) << 31)) &
+                    ((*(sptr + wpls) << 1) | (*(sptr + wpls + 1) >> 31)) &
+                    ((*(sptr + wpls) << 2) | (*(sptr + wpls + 1) >> 30)) &
+                    ((~*(sptr + wpls2) >> 1) | (~*(sptr + wpls2 - 1) << 31)) &
+                    (*(sptr + wpls2)) &
+                    ((*(sptr + wpls2) << 1) | (*(sptr + wpls2 + 1) >> 31)) &
+                    ((*(sptr + wpls2) << 2) | (*(sptr + wpls2 + 1) >> 30));
+        }
+    }
+}
+
+static void
+fhmt_1_7(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((~*(sptr - wpls) >> 2) | (~*(sptr - wpls - 1) << 30)) &
+                    ((~*(sptr - wpls) >> 1) | (~*(sptr - wpls - 1) << 31)) &
+                    (~*(sptr - wpls)) &
+                    ((~*(sptr - wpls) << 1) | (~*(sptr - wpls + 1) >> 31)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((~*(sptr) << 1) | (~*(sptr + 1) >> 31)) &
+                    ((*(sptr + wpls) >> 2) | (*(sptr + wpls - 1) << 30)) &
+                    ((*(sptr + wpls) >> 1) | (*(sptr + wpls - 1) << 31)) &
+                    ((~*(sptr + wpls) << 1) | (~*(sptr + wpls + 1) >> 31)) &
+                    ((*(sptr + wpls2) >> 2) | (*(sptr + wpls2 - 1) << 30)) &
+                    ((*(sptr + wpls2) >> 1) | (*(sptr + wpls2 - 1) << 31)) &
+                    (*(sptr + wpls2)) &
+                    ((~*(sptr + wpls2) << 1) | (~*(sptr + wpls2 + 1) >> 31));
+        }
+    }
+}
+
+static void
+fhmt_1_8(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((~*(sptr - wpls2) >> 1) | (~*(sptr - wpls2 - 1) << 31)) &
+                    (*(sptr - wpls2)) &
+                    ((*(sptr - wpls2) << 1) | (*(sptr - wpls2 + 1) >> 31)) &
+                    ((*(sptr - wpls2) << 2) | (*(sptr - wpls2 + 1) >> 30)) &
+                    ((~*(sptr - wpls) >> 1) | (~*(sptr - wpls - 1) << 31)) &
+                    ((*(sptr - wpls) << 1) | (*(sptr - wpls + 1) >> 31)) &
+                    ((*(sptr - wpls) << 2) | (*(sptr - wpls + 1) >> 30)) &
+                    ((~*(sptr) >> 1) | (~*(sptr - 1) << 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((~*(sptr + wpls) >> 1) | (~*(sptr + wpls - 1) << 31)) &
+                    (~*(sptr + wpls)) &
+                    ((~*(sptr + wpls) << 1) | (~*(sptr + wpls + 1) >> 31)) &
+                    ((~*(sptr + wpls) << 2) | (~*(sptr + wpls + 1) >> 30));
+        }
+    }
+}
+
+static void
+fhmt_1_9(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr - wpls2) >> 2) | (*(sptr - wpls2 - 1) << 30)) &
+                    ((*(sptr - wpls2) >> 1) | (*(sptr - wpls2 - 1) << 31)) &
+                    (*(sptr - wpls2)) &
+                    ((~*(sptr - wpls2) << 1) | (~*(sptr - wpls2 + 1) >> 31)) &
+                    ((*(sptr - wpls) >> 2) | (*(sptr - wpls - 1) << 30)) &
+                    ((*(sptr - wpls) >> 1) | (*(sptr - wpls - 1) << 31)) &
+                    ((~*(sptr - wpls) << 1) | (~*(sptr - wpls + 1) >> 31)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((~*(sptr) << 1) | (~*(sptr + 1) >> 31)) &
+                    ((~*(sptr + wpls) >> 2) | (~*(sptr + wpls - 1) << 30)) &
+                    ((~*(sptr + wpls) >> 1) | (~*(sptr + wpls - 1) << 31)) &
+                    (~*(sptr + wpls)) &
+                    ((~*(sptr + wpls) << 1) | (~*(sptr + wpls + 1) >> 31));
+        }
+    }
+}
+
diff --git a/src/finditalic.c b/src/finditalic.c
new file mode 100644 (file)
index 0000000..f16110f
--- /dev/null
@@ -0,0 +1,231 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ * finditalic.c
+ *
+ *      l_int32   pixItalicWords()
+ *
+ *    Locate italic words.  This is an example of the use of
+ *    hit-miss binary morphology with binary reconstruction
+ *    (filling from a seed into a mask).
+ *
+ *    To see how this works, run with prog/italic.png.
+ */
+
+#include "allheaders.h"
+
+    /* ---------------------------------------------------------------  *
+     * These hit-miss sels match the slanted edge of italic characters  *
+     * ---------------------------------------------------------------  */
+static const char *str_ital1 = "   o x"
+                               "      "
+                               "      "
+                               "      "
+                               "  o x "
+                               "      "
+                               "  C   "
+                               "      "
+                               " o x  "
+                               "      "
+                               "      "
+                               "      "
+                               "o x   ";
+
+static const char *str_ital2 = "   o x"
+                               "      "
+                               "      "
+                               "  o x "
+                               "  C   "
+                               "      "
+                               " o x  "
+                               "      "
+                               "      "
+                               "o x   ";
+
+    /* ------------------------------------------------------------- *
+     * This sel removes noise that is not oriented as a slanted edge *
+     * ------------------------------------------------------------- */
+static const char *str_ital3 = " x"
+                               "Cx"
+                               "x "
+                               "x ";
+
+/*!
+ *  pixItalicWords()
+ *
+ *      Input:  pixs (1 bpp)
+ *              boxaw (<optional> word bounding boxes; can be NULL)
+ *              pixw (<optional> word box mask; can be NULL)
+ *              &boxa (<return> boxa of italic words)
+ *              debugflag (1 for debug output; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) You can input the bounding boxes for the words in one of
+ *          two forms: as bounding boxes (@boxaw) or as a word mask with
+ *          the word bounding boxes filled (@pixw).  For example,
+ *          to compute @pixw, you can use pixWordMaskByDilation().
+ *      (2) Alternatively, you can set both of these inputs to NULL,
+ *          in which case the word mask is generated here.  This is
+ *          done by dilating and closing the input image to connect
+ *          letters within a word, while leaving the words separated.
+ *          The parameters are chosen under the assumption that the
+ *          input is 10 to 12 pt text, scanned at about 300 ppi.
+ *      (3) sel_ital1 and sel_ital2 detect the right edges that are
+ *          nearly vertical, at approximately the angle of italic
+ *          strokes.  We use the right edge to avoid getting seeds
+ *          from lower-case 'y'.  The typical italic slant has a smaller
+ *          angle with the vertical than the 'W', so in most cases we
+ *          will not trigger on the slanted lines in the 'W'.
+ *      (4) Note that sel_ital2 is shorter than sel_ital1.  It is
+ *          more appropriate for a typical font scanned at 200 ppi.
+ */
+l_int32
+pixItalicWords(PIX     *pixs,
+               BOXA    *boxaw,
+               PIX     *pixw,
+               BOXA   **pboxa,
+               l_int32  debugflag)
+{
+char     opstring[32];
+l_int32  size;
+BOXA    *boxa;
+PIX     *pixsd, *pixm, *pixd;
+SEL     *sel_ital1, *sel_ital2, *sel_ital3;
+
+    PROCNAME("pixItalicWords");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pboxa)
+        return ERROR_INT("&boxa not defined", procName, 1);
+    if (boxaw && pixw)
+        return ERROR_INT("both boxaw and pixw are defined", procName, 1);
+
+    sel_ital1 = selCreateFromString(str_ital1, 13, 6, NULL);
+    sel_ital2 = selCreateFromString(str_ital2, 10, 6, NULL);
+    sel_ital3 = selCreateFromString(str_ital3, 4, 2, NULL);
+
+        /* Make the italic seed: extract with HMT; remove noise.
+         * The noise removal close/open is important to exclude
+         * situations where a small slanted line accidentally
+         * matches sel_ital1. */
+    pixsd = pixHMT(NULL, pixs, sel_ital1);
+    pixClose(pixsd, pixsd, sel_ital3);
+    pixOpen(pixsd, pixsd, sel_ital3);
+
+        /* Make the word mask.  Use input boxes or mask if given. */
+    if (boxaw) {
+        pixm = pixCreateTemplate(pixs);
+        pixMaskBoxa(pixm, pixm, boxaw, L_SET_PIXELS);
+    } else if (pixw) {
+        pixm = pixClone(pixw);
+    } else {
+        pixWordMaskByDilation(pixs, 20, NULL, &size);
+        L_INFO("dilation size = %d\n", procName, size);
+        snprintf(opstring, sizeof(opstring), "d1.5 + c%d.1", size);
+        pixm = pixMorphSequence(pixs, opstring, 0);
+    }
+
+        /* Binary reconstruction to fill in those word mask
+         * components for which there is at least one seed pixel. */
+    pixd = pixSeedfillBinary(NULL, pixsd, pixm, 8);
+    boxa = pixConnComp(pixd, NULL, 8);
+    *pboxa = boxa;
+
+    if (debugflag) {
+            /* Save results at at 2x reduction */
+        l_int32  res, upper;
+        BOXA  *boxat;
+        GPLOT *gplot;
+        NUMA  *na;
+        PIXA  *pad;
+        PIX   *pix1, *pix2, *pix3;
+        pad = pixaCreate(0);
+        boxat = pixConnComp(pixm, NULL, 8);
+        boxaWrite("/tmp/ital.ba", boxat);
+        pixSaveTiledOutline(pixs, pad, 0.5, 1, 20, 2, 32);  /* orig */
+        pixSaveTiledOutline(pixsd, pad, 0.5, 1, 20, 2, 0);  /* seed */
+        pix1 = pixConvertTo32(pixm);
+        pixRenderBoxaArb(pix1, boxat, 3, 255, 0, 0);
+        pixSaveTiledOutline(pix1, pad, 0.5, 1, 20, 2, 0);  /* mask + outline */
+        pixDestroy(&pix1);
+        pixSaveTiledOutline(pixd, pad, 0.5, 1, 20, 2, 0);  /* ital mask */
+        pix1 = pixConvertTo32(pixs);
+        pixRenderBoxaArb(pix1, boxa, 3, 255, 0, 0);
+        pixSaveTiledOutline(pix1, pad, 0.5, 1, 20, 2, 0);  /* orig + outline */
+        pixDestroy(&pix1);
+        pix1 = pixCreateTemplate(pixs);
+        pix2 = pixSetBlackOrWhiteBoxa(pix1, boxa, L_SET_BLACK);
+        pixCopy(pix1, pixs);
+        pix3 = pixDilateBrick(NULL, pixs, 3, 3);
+        pixCombineMasked(pix1, pix3, pix2);
+        pixSaveTiledOutline(pix1, pad, 0.5, 1, 20, 2, 0);  /* ital bolded */
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+        pixDestroy(&pix3);
+        pix2 = pixaDisplay(pad, 0, 0);
+        pixWrite("/tmp/ital.png", pix2, IFF_PNG);
+        pixDestroy(&pix2);
+
+            /* Assuming the image represents 6 inches of actual page width,
+             * the pixs resolution is approximately
+             *    (width of pixs in pixels) / 6
+             * and the images have been saved at half this resolution.   */
+        res = pixGetWidth(pixs) / 12;
+        L_INFO("resolution = %d\n", procName, res);
+        pixaConvertToPdf(pad, res, 1.0, L_FLATE_ENCODE, 75, "Italic Finder",
+                         "/tmp/ital.pdf");
+        pixaDestroy(&pad);
+        boxaDestroy(&boxat);
+
+            /* Plot histogram of horizontal white run sizes.  A small
+             * initial vertical dilation removes most runs that are neither
+             * inter-character nor inter-word.  The larger first peak is
+             * from inter-character runs, and the smaller second peak is
+             * from inter-word runs. */
+        pix1 = pixDilateBrick(NULL, pixs, 1, 15);
+        upper = L_MAX(30, 3 * size);
+        na = pixRunHistogramMorph(pix1, L_RUN_OFF, L_HORIZ, upper);
+        pixDestroy(&pix1);
+        gplot = gplotCreate("/tmp/runhisto", GPLOT_PNG,
+                "Histogram of horizontal runs of white pixels, vs length",
+                "run length", "number of runs");
+        gplotAddPlot(gplot, NULL, na, GPLOT_LINES, "plot1");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+        numaDestroy(&na);
+    }
+
+    selDestroy(&sel_ital1);
+    selDestroy(&sel_ital2);
+    selDestroy(&sel_ital3);
+    pixDestroy(&pixsd);
+    pixDestroy(&pixm);
+    pixDestroy(&pixd);
+    return 0;
+}
diff --git a/src/flipdetect.c b/src/flipdetect.c
new file mode 100644 (file)
index 0000000..e959bd2
--- /dev/null
@@ -0,0 +1,1000 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  flipdetect.c
+ *
+ *      Page orientation detection (pure rotation by 90 degree increments):
+ *          l_int32      pixOrientDetect()
+ *          l_int32      makeOrientDecision()
+ *          l_int32      pixUpDownDetect()
+ *          l_int32      pixUpDownDetectGeneral()
+ *          l_int32      pixOrientDetectDwa()
+ *          l_int32      pixUpDownDetectDwa()
+ *          l_int32      pixUpDownDetectGeneralDwa()
+ *
+ *      Page mirror detection (flip 180 degrees about line in plane of image):
+ *          l_int32      pixMirrorDetect()
+ *          l_int32      pixMirrorDetectDwa()
+ *
+ *      Static debug helper
+ *          void         pixDebugFlipDetect()
+ *
+ *  ===================================================================
+ *
+ *  Page transformation detection:
+ *
+ *  Once a page is deskewed, there are 8 possible states that it
+ *  can be in, shown symbolically below.  Suppose state 0 is correct.
+ *
+ *      0: correct     1          2          3
+ *      +------+   +------+   +------+   +------+
+ *      | **** |   | *    |   | **** |   |    * |
+ *      | *    |   | *    |   |    * |   |    * |
+ *      | *    |   | **** |   |    * |   | **** |
+ *      +------+   +------+   +------+   +------+
+ *
+ *         4          5          6          7
+ *      +-----+    +-----+    +-----+    +-----+
+ *      | *** |    |   * |    | *** |    | *   |
+ *      |   * |    |   * |    | *   |    | *   |
+ *      |   * |    |   * |    | *   |    | *   |
+ *      |   * |    | *** |    | *   |    | *** |
+ *      +-----+    +-----+    +-----+    +-----+
+ *
+ *  Each of the other seven can be derived from state 0 by applying some
+ *  combination of a 90 degree clockwise rotation, a flip about
+ *  a horizontal line, and a flip about a vertical line,
+ *  all abbreviated as:
+ *      R = Rotation (about a line perpendicular to the image)
+ *      H = Horizontal flip (about a vertical line in the plane of the image)
+ *      V = Vertical flip (about a horizontal line in the plane of the image)
+ *
+ *  We get these transformations:
+ *      RHV
+ *      000  -> 0
+ *      001  -> 1
+ *      010  -> 2
+ *      011  -> 3
+ *      100  -> 4
+ *      101  -> 5
+ *      110  -> 6
+ *      111  -> 7
+ *
+ *  Note that in four of these, the sum of H and V is 1 (odd).
+ *  For these four, we have a change in parity (handedness) of
+ *  the image, and the transformation cannot be performed by
+ *  rotation about a vertical line out of the page.   Under
+ *  rotation R, the set of 8 transformations decomposes into
+ *  two subgroups linking {0, 3, 4, 7} and {1, 2, 5, 6} independently.
+ *
+ *  pixOrientDetect*() tests for a pure rotation (0, 90, 180, 270 degrees).
+ *  It doesn't change parity.
+ *
+ *  pixMirrorDetect*() tests for a horizontal flip about the vertical axis.
+ *  It changes parity.
+ *
+ *  The landscape/portrait rotation can be detected in two ways:
+ *
+ *    (1) Compute the deskew confidence for an image segment,
+ *        both as is and rotated 90 degrees  (see skew.c).
+ *
+ *    (2) Compute the ascender/descender signal for the image,
+ *        both as is and rotated 90 degrees  (implemented here).
+ *
+ *  The ascender/descender signal is useful for determining text
+ *  orientation in Roman alphabets because the incidence of letters
+ *  with straight-line ascenders (b, d, h, k, l, <t>) outnumber
+ *  those with descenders (<g>, p, q).  The letters <t> and <g>
+ *  will respond variably to the filter, depending on the type face.
+ *
+ *  What about the mirror image situations?  These aren't common
+ *  unless you're dealing with film, for example.
+ *  But you can reliably test if the image has undergone a
+ *  parity-changing flip once about some axis in the plane
+ *  of the image, using pixMirrorDetect*().  This works ostensibly by
+ *  counting the number of characters with ascenders that
+ *  stick out to the left and right of the ascender.  Characters
+ *  that are not mirror flipped are more likely to extend to the
+ *  right (b, h, k) than to the left (d).  Of course, that is for
+ *  text that is rightside-up.  So before you apply the mirror
+ *  test, it is necessary to insure that the text has the ascenders
+ *  going up, and not down or to the left or right.  But here's
+ *  what *really* happens.  It turns out that the pre-filtering before
+ *  the hit-miss transform (HMT) is crucial, and surprisingly, when
+ *  the pre-filtering is chosen to generate a large signal, the majority
+ *  of the signal comes from open regions of common lower-case
+ *  letters such as 'e', 'c' and 'f'.
+ *
+ *  All operations are given in two implementations whose results are
+ *  identical: rasterop morphology and dwa morphology.  The dwa
+ *  implementations are between 2x and 3x faster.
+ *
+ *  The set of operations you actually use depends on your prior knowledge:
+ *
+ *  (1) If the page is known to be either rightside-up or upside-down, use
+ *      either pixOrientDetect*() with pleftconf = NULL, or
+ *      pixUpDownDetect*().   [The '*' refers to either the rasterop
+ *      or dwa versions.]
+ *
+ *  (2) If any of the four orientations are possible, use pixOrientDetect*().
+ *
+ *  (3) If the text is horizontal and rightside-up, the only remaining
+ *      degree of freedom is a left-right mirror flip: use
+ *      pixMirrorDetect*().
+ *
+ *  (4) If you have a relatively large amount of numbers on the page,
+ *      us the slower pixUpDownDetectGeneral().
+ *
+ *  We summarize the full orientation and mirror flip detection process:
+ *
+ *  (1) First determine which of the four 90 degree rotations
+ *      causes the text to be rightside-up.  This can be done
+ *      with either skew confidence or the pixOrientDetect*()
+ *      signals.  For the latter, see the table for pixOrientDetect().
+ *
+ *  (2) Then, with ascenders pointing up, apply pixMirrorDetect*().
+ *      In the normal situation the confidence confidence will be
+ *      large and positive.  However, if mirror flipped, the
+ *      confidence will be large and negative.
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+    /* Sels for pixOrientDetect() and pixMirrorDetect() */
+static const char *textsel1 = "x  oo "
+                              "x oOo "
+                              "x  o  "
+                              "x     "
+                              "xxxxxx";
+
+static const char *textsel2 = " oo  x"
+                              " oOo x"
+                              "  o  x"
+                              "     x"
+                              "xxxxxx";
+
+static const char *textsel3 = "xxxxxx"
+                              "x     "
+                              "x  o  "
+                              "x oOo "
+                              "x  oo ";
+
+static const char *textsel4 = "xxxxxx"
+                              "     x"
+                              "  o  x"
+                              " oOo x"
+                              " oo  x";
+
+    /* Parameters for determining orientation */
+static const l_int32  DEFAULT_MIN_UP_DOWN_COUNT = 70;
+static const l_float32  DEFAULT_MIN_UP_DOWN_CONF = 7.0;
+static const l_float32  DEFAULT_MIN_UP_DOWN_RATIO = 2.5;
+
+    /* Parameters for determining mirror flip */
+static const l_int32  DEFAULT_MIN_MIRROR_FLIP_COUNT = 100;
+static const l_float32  DEFAULT_MIN_MIRROR_FLIP_CONF = 5.0;
+
+    /* Static debug function */
+static void pixDebugFlipDetect(const char *filename, PIX *pixs,
+                               PIX *pixhm, l_int32 enable);
+
+
+/*----------------------------------------------------------------*
+ *         Orientation detection (four 90 degree angles)          *
+ *                      Rasterop implementation                   *
+ *----------------------------------------------------------------*/
+/*!
+ *  pixOrientDetect()
+ *
+ *      Input:  pixs (1 bpp, deskewed, English text, 150 - 300 ppi)
+ *              &upconf (<optional return> ; may be null)
+ *              &leftconf (<optional return> ; may be null)
+ *              mincount (min number of up + down; use 0 for default)
+ *              debug (1 for debug output; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See "Measuring document image skew and orientation"
+ *          Dan S. Bloomberg, Gary E. Kopec and Lakshmi Dasari
+ *          IS&T/SPIE EI'95, Conference 2422: Document Recognition II
+ *          pp 302-316, Feb 6-7, 1995, San Jose, CA
+ *      (2) upconf is the normalized difference between up ascenders
+ *          and down ascenders.  The image is analyzed without rotation
+ *          for being rightside-up or upside-down.  Set &upconf to null
+ *          to skip this operation.
+ *      (3) leftconf is the normalized difference between up ascenders
+ *          and down ascenders in the image after it has been
+ *          rotated 90 degrees clockwise.  With that rotation, ascenders
+ *          projecting to the left in the source image will project up
+ *          in the rotated image.  We compute this by rotating 90 degrees
+ *          clockwise and testing for up and down ascenders.  Set
+ *          &leftconf to null to skip this operation.
+ *      (4) Note that upconf and leftconf are not linear measures of
+ *          confidence, e.g., in a range between 0 and 100.  They
+ *          measure how far you are out on the tail of a (presumably)
+ *          normal distribution.  For example, a confidence of 10 means
+ *          that it is nearly certain that the difference did not
+ *          happen at random.  However, these values must be interpreted
+ *          cautiously, taking into consideration the estimated prior
+ *          for a particular orientation or mirror flip.   The up-down
+ *          signal is very strong if applied to text with ascenders
+ *          up and down, and relatively weak for text at 90 degrees,
+ *          but even at 90 degrees, the difference can look significant.
+ *          For example, suppose the ascenders are oriented horizontally,
+ *          but the test is done vertically.  Then upconf can
+ *          be < -MIN_CONF_FOR_UP_DOWN, suggesting the text may be
+ *          upside-down.  However, if instead the test were done
+ *          horizontally, leftconf will be very much larger
+ *          (in absolute value), giving the correct orientation.
+ *      (5) If you compute both upconf and leftconf, and there is
+ *          sufficient signal, the following table determines the
+ *          cw angle necessary to rotate pixs so that the text is
+ *          rightside-up:
+ *             0 deg :           upconf >> 1,    abs(upconf) >> abs(leftconf)
+ *             90 deg :          leftconf >> 1,  abs(leftconf) >> abs(upconf)
+ *             180 deg :         upconf << -1,   abs(upconf) >> abs(leftconf)
+ *             270 deg :         leftconf << -1, abs(leftconf) >> abs(upconf)
+ *      (6) One should probably not interpret the direction unless
+ *          there are a sufficient number of counts for both orientations,
+ *          in which case neither upconf nor leftconf will be 0.0.
+ *      (7) Uses rasterop implementation of HMT.
+ */
+l_int32
+pixOrientDetect(PIX        *pixs,
+                l_float32  *pupconf,
+                l_float32  *pleftconf,
+                l_int32     mincount,
+                l_int32     debug)
+{
+PIX  *pixt;
+
+    PROCNAME("pixOrientDetect");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not 1 bpp", procName, 1);
+    if (!pupconf && !pleftconf)
+        return ERROR_INT("nothing to do", procName, 1);
+    if (mincount == 0)
+        mincount = DEFAULT_MIN_UP_DOWN_COUNT;
+
+    if (pupconf)
+        pixUpDownDetect(pixs, pupconf, mincount, debug);
+    if (pleftconf) {
+        pixt = pixRotate90(pixs, 1);
+        pixUpDownDetect(pixt, pleftconf, mincount, debug);
+        pixDestroy(&pixt);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  makeOrientDecision()
+ *
+ *      Input:  upconf (nonzero)
+ *              leftconf (nonzero)
+ *              minupconf (minimum value for which a decision can be made)
+ *              minratio (minimum conf ratio required for a decision)
+ *              &orient (<return> text orientation enum {0,1,2,3,4})
+ *              debug (1 for debug output; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This can be run after pixOrientDetect()
+ *      (2) Both upconf and leftconf must be nonzero; otherwise the
+ *          orientation cannot be determined.
+ *      (3) The abs values of the input confidences are compared to
+ *          minupconf.
+ *      (4) The abs value of the largest of (upconf/leftconf) and
+ *          (leftconf/upconf) is compared with minratio.
+ *      (5) Input 0.0 for the default values for minupconf and minratio.
+ *      (6) The return value of orient is interpreted thus:
+ *            L_TEXT_ORIENT_UNKNOWN:  not enough evidence to determine
+ *            L_TEXT_ORIENT_UP:       text rightside-up
+ *            L_TEXT_ORIENT_LEFT:     landscape, text up facing left
+ *            L_TEXT_ORIENT_DOWN:     text upside-down
+ *            L_TEXT_ORIENT_RIGHT:    landscape, text up facing right
+ */
+l_int32
+makeOrientDecision(l_float32  upconf,
+                   l_float32  leftconf,
+                   l_float32  minupconf,
+                   l_float32  minratio,
+                   l_int32   *porient,
+                   l_int32    debug)
+{
+l_float32  absupconf, absleftconf;
+
+    PROCNAME("makeOrientDecision");
+
+    if (!porient)
+        return ERROR_INT("&orient not defined", procName, 1);
+    *porient = L_TEXT_ORIENT_UNKNOWN;  /* default: no decision */
+    if (upconf == 0.0 || leftconf == 0.0)
+        return ERROR_INT("not enough conf to get orientation", procName, 1);
+
+    if (minupconf == 0.0)
+        minupconf = DEFAULT_MIN_UP_DOWN_CONF;
+    if (minratio == 0.0)
+        minratio = DEFAULT_MIN_UP_DOWN_RATIO;
+    absupconf = L_ABS(upconf);
+    absleftconf = L_ABS(leftconf);
+
+        /* Here are the four possible orientation decisions, based
+         * on satisfaction of two threshold constraints. */
+    if (upconf > minupconf && absupconf > minratio * absleftconf)
+        *porient = L_TEXT_ORIENT_UP;
+    else if (leftconf > minupconf && absleftconf > minratio * absupconf)
+        *porient = L_TEXT_ORIENT_LEFT;
+    else if (upconf < -minupconf && absupconf > minratio * absleftconf)
+        *porient = L_TEXT_ORIENT_DOWN;
+    else if (leftconf < -minupconf && absleftconf > minratio * absupconf)
+        *porient = L_TEXT_ORIENT_RIGHT;
+
+    if (debug) {
+        fprintf(stderr, "upconf = %7.3f, leftconf = %7.3f\n", upconf, leftconf);
+        if (*porient == L_TEXT_ORIENT_UNKNOWN)
+            fprintf(stderr, "Confidence is low; no determination is made\n");
+        else if (*porient == L_TEXT_ORIENT_UP)
+            fprintf(stderr, "Text is rightside-up\n");
+        else if (*porient == L_TEXT_ORIENT_LEFT)
+            fprintf(stderr, "Text is rotated 90 deg ccw\n");
+        else if (*porient == L_TEXT_ORIENT_DOWN)
+            fprintf(stderr, "Text is upside-down\n");
+        else   /* *porient == L_TEXT_ORIENT_RIGHT */
+            fprintf(stderr, "Text is rotated 90 deg cw\n");
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixUpDownDetect()
+ *
+ *      Input:  pixs (1 bpp, deskewed, English text, 150 - 300 ppi)
+ *              &conf (<return> confidence that text is rightside-up)
+ *              mincount (min number of up + down; use 0 for default)
+ *              debug (1 for debug output; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Special (typical, slightly faster) case, where the pixels
+ *          identified through the HMT (hit-miss transform) are not
+ *          clipped by a truncated word mask pixm.  See pixOrientDetect()
+ *          and pixUpDownDetectGeneral() for details.
+ *      (2) The returned confidence is the normalized difference
+ *          between the number of detected up and down ascenders,
+ *          assuming that the text is either rightside-up or upside-down
+ *          and not rotated at a 90 degree angle.
+ */
+l_int32
+pixUpDownDetect(PIX        *pixs,
+                l_float32  *pconf,
+                l_int32     mincount,
+                l_int32     debug)
+{
+    return pixUpDownDetectGeneral(pixs, pconf, mincount, 0, debug);
+}
+
+
+/*!
+ *  pixUpDownDetectGeneral()
+ *
+ *      Input:  pixs (1 bpp, deskewed, English text, 150 - 300 ppi)
+ *              &conf (<return> confidence that text is rightside-up)
+ *              mincount (min number of up + down; use 0 for default)
+ *              npixels (number of pixels removed from each side of word box)
+ *              debug (1 for debug output; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See pixOrientDetect() for other details.
+ *      (2) @conf is the normalized difference between the number of
+ *          detected up and down ascenders, assuming that the text
+ *          is either rightside-up or upside-down and not rotated
+ *          at a 90 degree angle.
+ *      (3) The typical mode of operation is @npixels == 0.
+ *          If @npixels > 0, this removes HMT matches at the
+ *          beginning and ending of "words."  This is useful for
+ *          pages that may have mostly digits, because if npixels == 0,
+ *          leading "1" and "3" digits can register as having
+ *          ascenders or descenders, and "7" digits can match descenders.
+ *          Consequently, a page image of only digits may register
+ *          as being upside-down.
+ *      (4) We want to count the number of instances found using the HMT.
+ *          An expensive way to do this would be to count the
+ *          number of connected components.  A cheap way is to do a rank
+ *          reduction cascade that reduces each component to a single
+ *          pixel, and results (after two or three 2x reductions)
+ *          in one pixel for each of the original components.
+ *          After the reduction, you have a much smaller pix over
+ *          which to count pixels.  We do only 2 reductions, because
+ *          this function is designed to work for input pix between
+ *          150 and 300 ppi, and an 8x reduction on a 150 ppi image
+ *          is going too far -- components will get merged.
+ */
+l_int32
+pixUpDownDetectGeneral(PIX        *pixs,
+                       l_float32  *pconf,
+                       l_int32     mincount,
+                       l_int32     npixels,
+                       l_int32     debug)
+{
+l_int32    countup, countdown, nmax;
+l_float32  nup, ndown;
+PIX       *pixt0, *pixt1, *pixt2, *pixt3, *pixm;
+SEL       *sel1, *sel2, *sel3, *sel4;
+
+    PROCNAME("pixUpDownDetectGeneral");
+
+    if (!pconf)
+        return ERROR_INT("&conf not defined", procName, 1);
+    *pconf = 0.0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (mincount == 0)
+        mincount = DEFAULT_MIN_UP_DOWN_COUNT;
+    if (npixels < 0)
+        npixels = 0;
+
+    sel1 = selCreateFromString(textsel1, 5, 6, NULL);
+    sel2 = selCreateFromString(textsel2, 5, 6, NULL);
+    sel3 = selCreateFromString(textsel3, 5, 6, NULL);
+    sel4 = selCreateFromString(textsel4, 5, 6, NULL);
+
+        /* One of many reasonable pre-filtering sequences: (1, 8) and (30, 1).
+         * This closes holes in x-height characters and joins them at
+         * the x-height.  There is more noise in the descender detection
+         * from this, but it works fairly well. */
+    pixt0 = pixMorphCompSequence(pixs, "c1.8 + c30.1", 0);
+
+        /* Optionally, make a mask of the word bounding boxes, shortening
+         * each of them by a fixed amount at each end. */
+    pixm = NULL;
+    if (npixels > 0) {
+        l_int32  i, nbox, x, y, w, h;
+        BOX   *box;
+        BOXA  *boxa;
+        pixt1 = pixMorphSequence(pixt0, "o10.1", 0);
+        boxa = pixConnComp(pixt1, NULL, 8);
+        pixm = pixCreateTemplate(pixt1);
+        pixDestroy(&pixt1);
+        nbox = boxaGetCount(boxa);
+        for (i = 0; i < nbox; i++) {
+            box = boxaGetBox(boxa, i, L_CLONE);
+            boxGetGeometry(box, &x, &y, &w, &h);
+            if (w > 2 * npixels)
+                pixRasterop(pixm, x + npixels, y - 6, w - 2 * npixels, h + 13,
+                            PIX_SET, NULL, 0, 0);
+            boxDestroy(&box);
+        }
+        boxaDestroy(&boxa);
+    }
+
+        /* Find the ascenders and optionally filter with pixm.
+         * For an explanation of the procedure used for counting the result
+         * of the HMT, see comments at the beginning of this function. */
+    pixt1 = pixHMT(NULL, pixt0, sel1);
+    pixt2 = pixHMT(NULL, pixt0, sel2);
+    pixOr(pixt1, pixt1, pixt2);
+    if (pixm)
+        pixAnd(pixt1, pixt1, pixm);
+    pixt3 = pixReduceRankBinaryCascade(pixt1, 1, 1, 0, 0);
+    pixCountPixels(pixt3, &countup, NULL);
+    pixDebugFlipDetect("junkpixup", pixs, pixt1, debug);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+
+        /* Find the ascenders and optionally filter with pixm. */
+    pixt1 = pixHMT(NULL, pixt0, sel3);
+    pixt2 = pixHMT(NULL, pixt0, sel4);
+    pixOr(pixt1, pixt1, pixt2);
+    if (pixm)
+        pixAnd(pixt1, pixt1, pixm);
+    pixt3 = pixReduceRankBinaryCascade(pixt1, 1, 1, 0, 0);
+    pixCountPixels(pixt3, &countdown, NULL);
+    pixDebugFlipDetect("junkpixdown", pixs, pixt1, debug);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+
+        /* Evaluate statistically, generating a confidence that is
+         * related to the probability with a gaussian distribution. */
+    nup = (l_float32)(countup);
+    ndown = (l_float32)(countdown);
+    nmax = L_MAX(countup, countdown);
+    if (nmax > mincount)
+        *pconf = 2. * ((nup - ndown) / sqrt(nup + ndown));
+
+    if (debug) {
+        if (pixm) pixWrite("junkpixm1", pixm, IFF_PNG);
+        fprintf(stderr, "nup = %7.3f, ndown = %7.3f, conf = %7.3f\n",
+                nup, ndown, *pconf);
+        if (*pconf > DEFAULT_MIN_UP_DOWN_CONF)
+            fprintf(stderr, "Text is rightside-up\n");
+        if (*pconf < -DEFAULT_MIN_UP_DOWN_CONF)
+            fprintf(stderr, "Text is upside-down\n");
+    }
+
+    pixDestroy(&pixt0);
+    pixDestroy(&pixm);
+    selDestroy(&sel1);
+    selDestroy(&sel2);
+    selDestroy(&sel3);
+    selDestroy(&sel4);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------*
+ *         Orientation detection (four 90 degree angles)          *
+ *                         DWA implementation                     *
+ *----------------------------------------------------------------*/
+/*!
+ *  pixOrientDetectDwa()
+ *
+ *      Input:  pixs (1 bpp, deskewed, English text)
+ *              &upconf (<optional return> ; may be null)
+ *              &leftconf (<optional return> ; may be null)
+ *              mincount (min number of up + down; use 0 for default)
+ *              debug (1 for debug output; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Same interface as for pixOrientDetect().  See notes
+ *          there for usage.
+ *      (2) Uses auto-gen'd code for the Sels defined at the
+ *          top of this file, with some renaming of functions.
+ *          The auto-gen'd code is in fliphmtgen.c, and can
+ *          be generated by a simple executable; see prog/flipselgen.c.
+ *      (3) This runs about 2.5 times faster than the pixOrientDetect().
+ */
+l_int32
+pixOrientDetectDwa(PIX        *pixs,
+                   l_float32  *pupconf,
+                   l_float32  *pleftconf,
+                   l_int32     mincount,
+                   l_int32     debug)
+{
+PIX  *pixt;
+
+    PROCNAME("pixOrientDetectDwa");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not 1 bpp", procName, 1);
+    if (!pupconf && !pleftconf)
+        return ERROR_INT("nothing to do", procName, 1);
+    if (mincount == 0)
+        mincount = DEFAULT_MIN_UP_DOWN_COUNT;
+
+    if (pupconf)
+        pixUpDownDetectDwa(pixs, pupconf, mincount, debug);
+    if (pleftconf) {
+        pixt = pixRotate90(pixs, 1);
+        pixUpDownDetectDwa(pixt, pleftconf, mincount, debug);
+        pixDestroy(&pixt);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixUpDownDetectDwa()
+ *
+ *      Input:  pixs (1 bpp, deskewed, English text, 150 - 300 ppi)
+ *              &conf (<return> confidence that text is rightside-up)
+ *              mincount (min number of up + down; use 0 for default)
+ *              debug (1 for debug output; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Faster (DWA) version of pixUpDownDetect().
+ *      (2) This is a special case (but typical and slightly faster) of
+ *          pixUpDownDetectGeneralDwa(), where the pixels identified
+ *          through the HMT (hit-miss transform) are not clipped by
+ *          a truncated word mask pixm.  See pixUpDownDetectGeneral()
+ *          for usage and other details.
+ *      (3) The returned confidence is the normalized difference
+ *          between the number of detected up and down ascenders,
+ *          assuming that the text is either rightside-up or upside-down
+ *          and not rotated at a 90 degree angle.
+ */
+l_int32
+pixUpDownDetectDwa(PIX        *pixs,
+                  l_float32  *pconf,
+                  l_int32     mincount,
+                  l_int32     debug)
+{
+    return pixUpDownDetectGeneralDwa(pixs, pconf, mincount, 0, debug);
+}
+
+
+/*!
+ *  pixUpDownDetectGeneralDwa()
+ *
+ *      Input:  pixs (1 bpp, deskewed, English text)
+ *              &conf (<return> confidence that text is rightside-up)
+ *              mincount (min number of up + down; use 0 for default)
+ *              npixels (number of pixels removed from each side of word box)
+ *              debug (1 for debug output; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See the notes in pixUpDownDetectGeneral() for usage.
+ */
+l_int32
+pixUpDownDetectGeneralDwa(PIX        *pixs,
+                          l_float32  *pconf,
+                          l_int32     mincount,
+                          l_int32     npixels,
+                          l_int32     debug)
+{
+char       flipsel1[] = "flipsel1";
+char       flipsel2[] = "flipsel2";
+char       flipsel3[] = "flipsel3";
+char       flipsel4[] = "flipsel4";
+l_int32    countup, countdown, nmax;
+l_float32  nup, ndown;
+PIX       *pixt, *pixt0, *pixt1, *pixt2, *pixt3, *pixm;
+
+    PROCNAME("pixUpDownDetectGeneralDwa");
+
+    if (!pconf)
+        return ERROR_INT("&conf not defined", procName, 1);
+    *pconf = 0.0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (mincount == 0)
+        mincount = DEFAULT_MIN_UP_DOWN_COUNT;
+    if (npixels < 0)
+        npixels = 0;
+
+        /* One of many reasonable pre-filtering sequences: (1, 8) and (30, 1).
+         * This closes holes in x-height characters and joins them at
+         * the x-height.  There is more noise in the descender detection
+         * from this, but it works fairly well. */
+    pixt = pixMorphSequenceDwa(pixs, "c1.8 + c30.1", 0);
+
+        /* Be sure to add the border before the flip DWA operations! */
+    pixt0 = pixAddBorderGeneral(pixt, ADDED_BORDER, ADDED_BORDER,
+                                ADDED_BORDER, ADDED_BORDER, 0);
+    pixDestroy(&pixt);
+
+        /* Optionally, make a mask of the word bounding boxes, shortening
+         * each of them by a fixed amount at each end. */
+    pixm = NULL;
+    if (npixels > 0) {
+        l_int32  i, nbox, x, y, w, h;
+        BOX   *box;
+        BOXA  *boxa;
+        pixt1 = pixMorphSequenceDwa(pixt0, "o10.1", 0);
+        boxa = pixConnComp(pixt1, NULL, 8);
+        pixm = pixCreateTemplate(pixt1);
+        pixDestroy(&pixt1);
+        nbox = boxaGetCount(boxa);
+        for (i = 0; i < nbox; i++) {
+            box = boxaGetBox(boxa, i, L_CLONE);
+            boxGetGeometry(box, &x, &y, &w, &h);
+            if (w > 2 * npixels)
+                pixRasterop(pixm, x + npixels, y - 6, w - 2 * npixels, h + 13,
+                            PIX_SET, NULL, 0, 0);
+            boxDestroy(&box);
+        }
+        boxaDestroy(&boxa);
+    }
+
+        /* Find the ascenders and optionally filter with pixm.
+         * For an explanation of the procedure used for counting the result
+         * of the HMT, see comments in pixUpDownDetectGeneral().  */
+    pixt1 = pixFlipFHMTGen(NULL, pixt0, flipsel1);
+    pixt2 = pixFlipFHMTGen(NULL, pixt0, flipsel2);
+    pixOr(pixt1, pixt1, pixt2);
+    if (pixm)
+        pixAnd(pixt1, pixt1, pixm);
+    pixt3 = pixReduceRankBinaryCascade(pixt1, 1, 1, 0, 0);
+    pixCountPixels(pixt3, &countup, NULL);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+
+        /* Find the ascenders and optionally filter with pixm. */
+    pixt1 = pixFlipFHMTGen(NULL, pixt0, flipsel3);
+    pixt2 = pixFlipFHMTGen(NULL, pixt0, flipsel4);
+    pixOr(pixt1, pixt1, pixt2);
+    if (pixm)
+        pixAnd(pixt1, pixt1, pixm);
+    pixt3 = pixReduceRankBinaryCascade(pixt1, 1, 1, 0, 0);
+    pixCountPixels(pixt3, &countdown, NULL);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+
+        /* Evaluate statistically, generating a confidence that is
+         * related to the probability with a gaussian distribution. */
+    nup = (l_float32)(countup);
+    ndown = (l_float32)(countdown);
+    nmax = L_MAX(countup, countdown);
+    if (nmax > mincount)
+        *pconf = 2. * ((nup - ndown) / sqrt(nup + ndown));
+
+    if (debug) {
+        if (pixm) pixWrite("junkpixm2", pixm, IFF_PNG);
+        fprintf(stderr, "nup = %7.3f, ndown = %7.3f, conf = %7.3f\n",
+                nup, ndown, *pconf);
+        if (*pconf > DEFAULT_MIN_UP_DOWN_CONF)
+            fprintf(stderr, "Text is rightside-up\n");
+        if (*pconf < -DEFAULT_MIN_UP_DOWN_CONF)
+            fprintf(stderr, "Text is upside-down\n");
+    }
+
+    pixDestroy(&pixt0);
+    pixDestroy(&pixm);
+    return 0;
+}
+
+
+
+/*----------------------------------------------------------------*
+ *                     Left-right mirror detection                *
+ *                       Rasterop implementation                  *
+ *----------------------------------------------------------------*/
+/*!
+ *  pixMirrorDetect()
+ *
+ *      Input:  pixs (1 bpp, deskewed, English text)
+ *              &conf (<return> confidence that text is not LR mirror reversed)
+ *              mincount (min number of left + right; use 0 for default)
+ *              debug (1 for debug output; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For this test, it is necessary that the text is horizontally
+ *          oriented, with ascenders going up.
+ *      (2) conf is the normalized difference between the number of
+ *          right and left facing characters with ascenders.
+ *          Left-facing are {d}; right-facing are {b, h, k}.
+ *          At least that was the expectation.  In practice, we can
+ *          really just say that it is the normalized difference in
+ *          hits using two specific hit-miss filters, textsel1 and textsel2,
+ *          after the image has been suitably pre-filtered so that
+ *          these filters are effective.  See (4) for what's really happening.
+ *      (3) A large positive conf value indicates normal text, whereas
+ *          a large negative conf value means the page is mirror reversed.
+ *      (4) The implementation is a bit tricky.  The general idea is
+ *          to fill the x-height part of characters, but not the space
+ *          between them, before doing the HMT.  This is done by
+ *          finding pixels added using two different operations -- a
+ *          horizontal close and a vertical dilation -- and adding
+ *          the intersection of these sets to the original.  It turns
+ *          out that the original intuition about the signal was largely
+ *          in error: much of the signal for right-facing characters
+ *          comes from the lower part of common x-height characters, like
+ *          the e and c, that remain open after these operations.
+ *          So it's important that the operations to close the x-height
+ *          parts of the characters are purposely weakened sufficiently
+ *          to allow these characters to remain open.  The wonders
+ *          of morphology!
+ */
+l_int32
+pixMirrorDetect(PIX        *pixs,
+                l_float32  *pconf,
+                l_int32     mincount,
+                l_int32     debug)
+{
+l_int32    count1, count2, nmax;
+l_float32  nleft, nright;
+PIX       *pixt0, *pixt1, *pixt2, *pixt3;
+SEL       *sel1, *sel2;
+
+    PROCNAME("pixMirrorDetect");
+
+    if (!pconf)
+        return ERROR_INT("&conf not defined", procName, 1);
+    *pconf = 0.0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (mincount == 0)
+        mincount = DEFAULT_MIN_MIRROR_FLIP_COUNT;
+
+    sel1 = selCreateFromString(textsel1, 5, 6, NULL);
+    sel2 = selCreateFromString(textsel2, 5, 6, NULL);
+
+        /* Fill x-height characters but not space between them, sort of. */
+    pixt3 = pixMorphCompSequence(pixs, "d1.30", 0);
+    pixXor(pixt3, pixt3, pixs);
+    pixt0 = pixMorphCompSequence(pixs, "c15.1", 0);
+    pixXor(pixt0, pixt0, pixs);
+    pixAnd(pixt0, pixt0, pixt3);
+    pixOr(pixt0, pixt0, pixs);
+    pixDestroy(&pixt3);
+/*    pixDisplayWrite(pixt0, 1); */
+
+        /* Filter the right-facing characters. */
+    pixt1 = pixHMT(NULL, pixt0, sel1);
+    pixt3 = pixReduceRankBinaryCascade(pixt1, 1, 1, 0, 0);
+    pixCountPixels(pixt3, &count1, NULL);
+    pixDebugFlipDetect("junkpixright", pixs, pixt1, debug);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt3);
+
+        /* Filter the left-facing characters. */
+    pixt2 = pixHMT(NULL, pixt0, sel2);
+    pixt3 = pixReduceRankBinaryCascade(pixt2, 1, 1, 0, 0);
+    pixCountPixels(pixt3, &count2, NULL);
+    pixDebugFlipDetect("junkpixleft", pixs, pixt2, debug);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+
+    nright = (l_float32)count1;
+    nleft = (l_float32)count2;
+    nmax = L_MAX(count1, count2);
+    pixDestroy(&pixt0);
+    selDestroy(&sel1);
+    selDestroy(&sel2);
+
+    if (nmax > mincount)
+        *pconf = 2. * ((nright - nleft) / sqrt(nright + nleft));
+
+    if (debug) {
+        fprintf(stderr, "nright = %f, nleft = %f\n", nright, nleft);
+        if (*pconf > DEFAULT_MIN_MIRROR_FLIP_CONF)
+            fprintf(stderr, "Text is not mirror reversed\n");
+        if (*pconf < -DEFAULT_MIN_MIRROR_FLIP_CONF)
+            fprintf(stderr, "Text is mirror reversed\n");
+    }
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------*
+ *                     Left-right mirror detection                *
+ *                          DWA implementation                    *
+ *----------------------------------------------------------------*/
+/*!
+ *  pixMirrorDetectDwa()
+ *
+ *      Input:  pixs (1 bpp, deskewed, English text)
+ *              &conf (<return> confidence that text is not LR mirror reversed)
+ *              mincount (min number of left + right; use 0 for default)
+ *              debug (1 for debug output; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) We assume the text is horizontally oriented, with
+ *          ascenders going up.
+ *      (2) See notes in pixMirrorDetect().
+ */
+l_int32
+pixMirrorDetectDwa(PIX        *pixs,
+                   l_float32  *pconf,
+                   l_int32     mincount,
+                   l_int32     debug)
+{
+char       flipsel1[] = "flipsel1";
+char       flipsel2[] = "flipsel2";
+l_int32    count1, count2, nmax;
+l_float32  nleft, nright;
+PIX       *pixt0, *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixMirrorDetectDwa");
+
+    if (!pconf)
+        return ERROR_INT("&conf not defined", procName, 1);
+    *pconf = 0.0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (mincount == 0)
+        mincount = DEFAULT_MIN_MIRROR_FLIP_COUNT;
+
+        /* Fill x-height characters but not space between them, sort of. */
+    pixt3 = pixMorphSequenceDwa(pixs, "d1.30", 0);
+    pixXor(pixt3, pixt3, pixs);
+    pixt0 = pixMorphSequenceDwa(pixs, "c15.1", 0);
+    pixXor(pixt0, pixt0, pixs);
+    pixAnd(pixt0, pixt0, pixt3);
+    pixOr(pixt3, pixt0, pixs);
+    pixDestroy(&pixt0);
+    pixt0 = pixAddBorderGeneral(pixt3, ADDED_BORDER, ADDED_BORDER,
+                                ADDED_BORDER, ADDED_BORDER, 0);
+    pixDestroy(&pixt3);
+
+        /* Filter the right-facing characters. */
+    pixt1 = pixFlipFHMTGen(NULL, pixt0, flipsel1);
+    pixt3 = pixReduceRankBinaryCascade(pixt1, 1, 1, 0, 0);
+    pixCountPixels(pixt3, &count1, NULL);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt3);
+
+        /* Filter the left-facing characters. */
+    pixt2 = pixFlipFHMTGen(NULL, pixt0, flipsel2);
+    pixt3 = pixReduceRankBinaryCascade(pixt2, 1, 1, 0, 0);
+    pixCountPixels(pixt3, &count2, NULL);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+
+    pixDestroy(&pixt0);
+    nright = (l_float32)count1;
+    nleft = (l_float32)count2;
+    nmax = L_MAX(count1, count2);
+
+    if (nmax > mincount)
+        *pconf = 2. * ((nright - nleft) / sqrt(nright + nleft));
+
+    if (debug) {
+        fprintf(stderr, "nright = %f, nleft = %f\n", nright, nleft);
+        if (*pconf > DEFAULT_MIN_MIRROR_FLIP_CONF)
+            fprintf(stderr, "Text is not mirror reversed\n");
+        if (*pconf < -DEFAULT_MIN_MIRROR_FLIP_CONF)
+            fprintf(stderr, "Text is mirror reversed\n");
+    }
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------*
+ *                        Static debug helper                     *
+ *----------------------------------------------------------------*/
+/*
+ *  pixDebugFlipDetect()
+ *
+ *      Input:  filename (for output debug file)
+ *              pixs (input to pix*Detect)
+ *              pixhm (hit-miss result from ascenders or descenders)
+ *              enable (1 to enable this function; 0 to disable)
+ *      Return: void
+ */
+static void
+pixDebugFlipDetect(const char *filename,
+                   PIX        *pixs,
+                   PIX        *pixhm,
+                   l_int32     enable)
+{
+PIX  *pixt, *pixthm;
+
+   if (!enable) return;
+
+        /* Display with red dot at counted locations */
+    pixt = pixConvert1To4Cmap(pixs);
+    pixthm = pixMorphSequence(pixhm, "d5.5", 0);
+    pixSetMaskedCmap(pixt, pixthm, 0, 0, 255, 0, 0);
+
+    pixWrite(filename, pixt, IFF_PNG);
+    pixDestroy(&pixthm);
+    pixDestroy(&pixt);
+    return;
+}
diff --git a/src/fliphmtgen.c b/src/fliphmtgen.c
new file mode 100644 (file)
index 0000000..3ca6f6b
--- /dev/null
@@ -0,0 +1,351 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *    fliphmtgen.c
+ *
+ *       DWA implementation of hit-miss transforms with auto-generated sels
+ *       for pixOrientDetectDwa() and pixUpDownDetectDwa() in flipdetect.c
+ *
+ *            PIX             *pixFlipFHMTGen()
+ *              static l_int32   flipfhmtgen_low()  -- dispatcher
+ *                static void      fhmt_1_0()
+ *                static void      fhmt_1_1()
+ *                static void      fhmt_1_2()
+ *                static void      fhmt_1_3()
+ *
+ *       The code (rearranged) was generated by prog/flipselgen.c
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static l_int32   NUM_SELS_GENERATED = 4;
+static char  SEL_NAMES[][10] = {"flipsel1",
+                                "flipsel2",
+                                "flipsel3",
+                                "flipsel4"};
+
+static l_int32 flipfhmtgen_low(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32, l_int32);
+
+static void  fhmt_1_0(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fhmt_1_1(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fhmt_1_2(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fhmt_1_3(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+
+
+/*---------------------------------------------------------------------*
+ *                          Top-level hmt functions                    *
+ *---------------------------------------------------------------------*/
+/*
+ *  pixFlipFHMTGen()
+ *
+ *     Input:  pixd (usual 3 choices: null, == pixs, != pixs)
+ *             pixs
+ *             sel name (one of four defined in SEL_NAMES[])
+ *     Return: pixd
+ *
+ *     Action: hit-miss transform on pixs by the sel
+ *     N.B.: the sel must have at least one hit, and it
+ *           can have any number of misses.
+ */
+PIX *
+pixFlipFHMTGen(PIX   *pixd,
+               PIX   *pixs,
+               char  *selname)
+{
+l_int32    i, index, found, w, h, wpls, wpld;
+l_uint32  *datad, *datas, *datat;
+PIX       *pixt;
+
+    PROCNAME("pixFlipFHMTGen");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, pixd);
+
+    found = FALSE;
+    for (i = 0; i < NUM_SELS_GENERATED; i++) {
+        if (strcmp(selname, SEL_NAMES[i]) == 0) {
+            found = TRUE;
+            index = i;
+            break;
+        }
+    }
+    if (found == FALSE)
+        return (PIX *)ERROR_PTR("sel index not found", procName, pixd);
+
+    if (pixd) {
+        if (!pixSizesEqual(pixs, pixd))
+            return (PIX *)ERROR_PTR("sizes not equal", procName, pixd);
+    } else {
+        if ((pixd = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+        /*  The images must be surrounded with ADDED_BORDER white pixels,
+         *  that we'll read from.  We fabricate a "proper"
+         *  image as the subimage within the border, having the
+         *  following parameters:  */
+    w = pixGetWidth(pixs) - 2 * ADDED_BORDER;
+    h = pixGetHeight(pixs) - 2 * ADDED_BORDER;
+    datas = pixGetData(pixs) + ADDED_BORDER * wpls + ADDED_BORDER / 32;
+    datad = pixGetData(pixd) + ADDED_BORDER * wpld + ADDED_BORDER / 32;
+
+    if (pixd == pixs) {  /* need temp image if in-place */
+        if ((pixt = pixCopy(NULL, pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+        datat = pixGetData(pixt) + ADDED_BORDER * wpls + ADDED_BORDER / 32;
+        flipfhmtgen_low(datad, w, h, wpld, datat, wpls, index);
+        pixDestroy(&pixt);
+    } else {  /* simple and not in-place */
+        flipfhmtgen_low(datad, w, h, wpld, datas, wpls, index);
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                           Fast hmt dispatcher                       *
+ *---------------------------------------------------------------------*/
+/*
+ *  flipfhmtgen_low()
+ *
+ *       A dispatcher to appropriate low-level code for flip hmt ops
+ */
+static l_int32
+flipfhmtgen_low(l_uint32  *datad,
+                l_int32    w,
+                l_int32    h,
+                l_int32    wpld,
+                l_uint32  *datas,
+                l_int32    wpls,
+                l_int32    index)
+{
+
+    switch (index)
+    {
+    case 0:
+        fhmt_1_0(datad, w, h, wpld, datas, wpls);
+        break;
+    case 1:
+        fhmt_1_1(datad, w, h, wpld, datas, wpls);
+        break;
+    case 2:
+        fhmt_1_2(datad, w, h, wpld, datas, wpls);
+        break;
+    case 3:
+        fhmt_1_3(datad, w, h, wpld, datas, wpls);
+        break;
+    }
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                  Low-level auto-generated hmt routines                   *
+ *--------------------------------------------------------------------------*/
+/*
+ *  N.B.  in all the low-level routines, the part of the image
+ *        that is accessed has been clipped by ADDED_BORDER pixels
+ *        on all four sides.  This is done in the higher level
+ *        code by redefining w and h smaller and by moving the
+ *        start-of-image pointers up to the beginning of this
+ *        interior rectangle.
+ */
+
+static void
+fhmt_1_0(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32              i;
+register l_int32     j, pwpls;
+register l_uint32   *sptr, *dptr;
+l_int32              wpls2, wpls3;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr - wpls) >> 3) | (*(sptr - wpls - 1) << 29)) &
+                    (~*(sptr - wpls)) &
+                    ((~*(sptr - wpls) << 1) | (~*(sptr - wpls + 1) >> 31)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((~*(sptr) >> 1) | (~*(sptr - 1) << 31)) &
+                    (~*sptr) &
+                    ((~*(sptr) << 1) | (~*(sptr + 1) >> 31)) &
+                    ((*(sptr + wpls) >> 3) | (*(sptr + wpls - 1) << 29)) &
+                    (~*(sptr + wpls)) &
+                    ((*(sptr + wpls2) >> 3) | (*(sptr + wpls2 - 1) << 29)) &
+                    ((*(sptr + wpls3) >> 3) | (*(sptr + wpls3 - 1) << 29)) &
+                    ((*(sptr + wpls3) >> 2) | (*(sptr + wpls3 - 1) << 30)) &
+                    ((*(sptr + wpls3) >> 1) | (*(sptr + wpls3 - 1) << 31)) &
+                    (*(sptr + wpls3)) &
+                    ((*(sptr + wpls3) << 1) | (*(sptr + wpls3 + 1) >> 31)) &
+                    ((*(sptr + wpls3) << 2) | (*(sptr + wpls3 + 1) >> 30));
+        }
+    }
+}
+
+
+static void
+fhmt_1_1(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32              i;
+register l_int32     j, pwpls;
+register l_uint32   *sptr, *dptr;
+l_int32              wpls2, wpls3;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((~*(sptr - wpls) >> 1) | (~*(sptr - wpls - 1) << 31)) &
+                    (~*(sptr - wpls)) &
+                    ((*(sptr - wpls) << 3) | (*(sptr - wpls + 1) >> 29)) &
+                    ((~*(sptr) >> 1) | (~*(sptr - 1) << 31)) &
+                    (~*sptr) &
+                    ((~*(sptr) << 1) | (~*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    (~*(sptr + wpls)) &
+                    ((*(sptr + wpls) << 3) | (*(sptr + wpls + 1) >> 29)) &
+                    ((*(sptr + wpls2) << 3) | (*(sptr + wpls2 + 1) >> 29)) &
+                    ((*(sptr + wpls3) >> 2) | (*(sptr + wpls3 - 1) << 30)) &
+                    ((*(sptr + wpls3) >> 1) | (*(sptr + wpls3 - 1) << 31)) &
+                    (*(sptr + wpls3)) &
+                    ((*(sptr + wpls3) << 1) | (*(sptr + wpls3 + 1) >> 31)) &
+                    ((*(sptr + wpls3) << 2) | (*(sptr + wpls3 + 1) >> 30)) &
+                    ((*(sptr + wpls3) << 3) | (*(sptr + wpls3 + 1) >> 29));
+        }
+    }
+}
+
+
+static void
+fhmt_1_2(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32              i;
+register l_int32     j, pwpls;
+register l_uint32   *sptr, *dptr;
+l_int32              wpls2, wpls3;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr - wpls3) >> 3) | (*(sptr - wpls3 - 1) << 29)) &
+                    ((*(sptr - wpls3) >> 2) | (*(sptr - wpls3 - 1) << 30)) &
+                    ((*(sptr - wpls3) >> 1) | (*(sptr - wpls3 - 1) << 31)) &
+                    (*(sptr - wpls3)) &
+                    ((*(sptr - wpls3) << 1) | (*(sptr - wpls3 + 1) >> 31)) &
+                    ((*(sptr - wpls3) << 2) | (*(sptr - wpls3 + 1) >> 30)) &
+                    ((*(sptr - wpls2) >> 3) | (*(sptr - wpls2 - 1) << 29)) &
+                    ((*(sptr - wpls) >> 3) | (*(sptr - wpls - 1) << 29)) &
+                    (~*(sptr - wpls)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((~*(sptr) >> 1) | (~*(sptr - 1) << 31)) &
+                    (~*sptr) &
+                    ((~*(sptr) << 1) | (~*(sptr + 1) >> 31)) &
+                    ((*(sptr + wpls) >> 3) | (*(sptr + wpls - 1) << 29)) &
+                    (~*(sptr + wpls)) &
+                    ((~*(sptr + wpls) << 1) | (~*(sptr + wpls + 1) >> 31));
+        }
+    }
+}
+
+
+static void
+fhmt_1_3(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32              i;
+register l_int32     j, pwpls;
+register l_uint32   *sptr, *dptr;
+l_int32              wpls2, wpls3;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr - wpls3) >> 2) | (*(sptr - wpls3 - 1) << 30)) &
+                    ((*(sptr - wpls3) >> 1) | (*(sptr - wpls3 - 1) << 31)) &
+                    (*(sptr - wpls3)) &
+                    ((*(sptr - wpls3) << 1) | (*(sptr - wpls3 + 1) >> 31)) &
+                    ((*(sptr - wpls3) << 2) | (*(sptr - wpls3 + 1) >> 30)) &
+                    ((*(sptr - wpls3) << 3) | (*(sptr - wpls3 + 1) >> 29)) &
+                    ((*(sptr - wpls2) << 3) | (*(sptr - wpls2 + 1) >> 29)) &
+                    (~*(sptr - wpls)) &
+                    ((*(sptr - wpls) << 3) | (*(sptr - wpls + 1) >> 29)) &
+                    ((~*(sptr) >> 1) | (~*(sptr - 1) << 31)) &
+                    (~*sptr) &
+                    ((~*(sptr) << 1) | (~*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((~*(sptr + wpls) >> 1) | (~*(sptr + wpls - 1) << 31)) &
+                    (~*(sptr + wpls)) &
+                    ((*(sptr + wpls) << 3) | (*(sptr + wpls + 1) >> 29));
+        }
+    }
+}
diff --git a/src/fmorphauto.c b/src/fmorphauto.c
new file mode 100644 (file)
index 0000000..fed614f
--- /dev/null
@@ -0,0 +1,862 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  fmorphauto.c
+ *
+ *    Main function calls:
+ *       l_int32             fmorphautogen()
+ *       l_int32             fmorphautogen1()
+ *       l_int32             fmorphautogen2()
+ *
+ *    Static helpers:
+ *       static SARRAY      *sarrayMakeWplsCode()
+ *       static SARRAY      *sarrayMakeInnerLoopDWACode()
+ *       static char        *makeBarrelshiftString()
+ *
+ *
+ *    This automatically generates dwa code for erosion and dilation.
+ *    Here's a road map for how it all works.
+ *
+ *    (1) You generate an array (a SELA) of structuring elements (SELs).
+ *        This can be done in several ways, including
+ *           (a) calling the function selaAddBasic() for
+ *               pre-compiled SELs
+ *           (b) generating the SELA in code in line
+ *           (c) reading in a SELA from file, using selaRead() or
+ *               various other formats.
+ *
+ *    (2) You call fmorphautogen1() and fmorphautogen2() on this SELA.
+ *        These use the text files morphtemplate1.txt and
+ *        morphtemplate2.txt for building up the source code.  See the file
+ *        prog/fmorphautogen.c for an example of how this is done.
+ *        The output is written to files named fmorphgen.*.c
+ *        and fmorphgenlow.*.c, where "*" is an integer that you
+ *        input to this function.  That integer labels both
+ *        the output files, as well as all the functions that
+ *        are generated.  That way, using different integers,
+ *        you can invoke fmorphautogen() any number of times
+ *        to get functions that all have different names so that
+ *        they can be linked into one program.
+ *
+ *    (3) You copy the generated source files back to your src
+ *        directory for compilation.  Put their names in the
+ *        Makefile, regenerate the prototypes, and recompile
+ *        the library.  Look at the Makefile to see how I've
+ *        included morphgen.1.c and fmorphgenlow.1.c.  These files
+ *        provide the high-level interfaces for erosion, dilation,
+ *        opening and closing, and the low-level interfaces to
+ *        do the actual work, for all 58 SELs in the SEL array.
+ *
+ *    (4) In an application, you now use this interface.  Again
+ *        for the example files in the library, using integer "1":
+ *
+ *            PIX   *pixMorphDwa_1(PIX *pixd, PIX, *pixs,
+ *                                 l_int32 operation, char *selname);
+ *
+ *                 or
+ *
+ *            PIX   *pixFMorphopGen_1(PIX *pixd, PIX *pixs,
+ *                                    l_int32 operation, char *selname);
+ *
+ *        where the operation is one of {L_MORPH_DILATE, L_MORPH_ERODE.
+ *        L_MORPH_OPEN, L_MORPH_CLOSE}, and the selname is one
+ *        of the set that were defined as the name field of sels.
+ *        This set is listed at the beginning of the file fmorphgen.1.c.
+ *        For examples of use, see the file prog/binmorph_reg1.c, which
+ *        verifies the consistency of the various implementations by
+ *        comparing the dwa result with that of full-image rasterops.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+#define   OUTROOT         "fmorphgen"
+#define   TEMPLATE1       "morphtemplate1.txt"
+#define   TEMPLATE2       "morphtemplate2.txt"
+
+#define   PROTOARGS   "(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);"
+
+static const l_int32  L_BUF_SIZE = 512;
+
+static char * makeBarrelshiftString(l_int32 delx, l_int32 dely);
+static SARRAY * sarrayMakeInnerLoopDWACode(SEL *sel, l_int32 index);
+static SARRAY * sarrayMakeWplsCode(SEL *sel);
+
+static char wpldecls[][53] = {
+            "l_int32             wpls2;",
+            "l_int32             wpls2, wpls3;",
+            "l_int32             wpls2, wpls3, wpls4;",
+            "l_int32             wpls5;",
+            "l_int32             wpls5, wpls6;",
+            "l_int32             wpls5, wpls6, wpls7;",
+            "l_int32             wpls5, wpls6, wpls7, wpls8;",
+            "l_int32             wpls9;",
+            "l_int32             wpls9, wpls10;",
+            "l_int32             wpls9, wpls10, wpls11;",
+            "l_int32             wpls9, wpls10, wpls11, wpls12;",
+            "l_int32             wpls13;",
+            "l_int32             wpls13, wpls14;",
+            "l_int32             wpls13, wpls14, wpls15;",
+            "l_int32             wpls13, wpls14, wpls15, wpls16;",
+            "l_int32             wpls17;",
+            "l_int32             wpls17, wpls18;",
+            "l_int32             wpls17, wpls18, wpls19;",
+            "l_int32             wpls17, wpls18, wpls19, wpls20;",
+            "l_int32             wpls21;",
+            "l_int32             wpls21, wpls22;",
+            "l_int32             wpls21, wpls22, wpls23;",
+            "l_int32             wpls21, wpls22, wpls23, wpls24;",
+            "l_int32             wpls25;",
+            "l_int32             wpls25, wpls26;",
+            "l_int32             wpls25, wpls26, wpls27;",
+            "l_int32             wpls25, wpls26, wpls27, wpls28;",
+            "l_int32             wpls29;",
+            "l_int32             wpls29, wpls30;",
+            "l_int32             wpls29, wpls30, wpls31;"};
+
+static char wplgendecls[][30] = {
+            "l_int32             wpls2;",
+            "l_int32             wpls3;",
+            "l_int32             wpls4;",
+            "l_int32             wpls5;",
+            "l_int32             wpls6;",
+            "l_int32             wpls7;",
+            "l_int32             wpls8;",
+            "l_int32             wpls9;",
+            "l_int32             wpls10;",
+            "l_int32             wpls11;",
+            "l_int32             wpls12;",
+            "l_int32             wpls13;",
+            "l_int32             wpls14;",
+            "l_int32             wpls15;",
+            "l_int32             wpls16;",
+            "l_int32             wpls17;",
+            "l_int32             wpls18;",
+            "l_int32             wpls19;",
+            "l_int32             wpls20;",
+            "l_int32             wpls21;",
+            "l_int32             wpls22;",
+            "l_int32             wpls23;",
+            "l_int32             wpls24;",
+            "l_int32             wpls25;",
+            "l_int32             wpls26;",
+            "l_int32             wpls27;",
+            "l_int32             wpls28;",
+            "l_int32             wpls29;",
+            "l_int32             wpls30;",
+            "l_int32             wpls31;"};
+
+static char wpldefs[][25] = {
+            "    wpls2 = 2 * wpls;",
+            "    wpls3 = 3 * wpls;",
+            "    wpls4 = 4 * wpls;",
+            "    wpls5 = 5 * wpls;",
+            "    wpls6 = 6 * wpls;",
+            "    wpls7 = 7 * wpls;",
+            "    wpls8 = 8 * wpls;",
+            "    wpls9 = 9 * wpls;",
+            "    wpls10 = 10 * wpls;",
+            "    wpls11 = 11 * wpls;",
+            "    wpls12 = 12 * wpls;",
+            "    wpls13 = 13 * wpls;",
+            "    wpls14 = 14 * wpls;",
+            "    wpls15 = 15 * wpls;",
+            "    wpls16 = 16 * wpls;",
+            "    wpls17 = 17 * wpls;",
+            "    wpls18 = 18 * wpls;",
+            "    wpls19 = 19 * wpls;",
+            "    wpls20 = 20 * wpls;",
+            "    wpls21 = 21 * wpls;",
+            "    wpls22 = 22 * wpls;",
+            "    wpls23 = 23 * wpls;",
+            "    wpls24 = 24 * wpls;",
+            "    wpls25 = 25 * wpls;",
+            "    wpls26 = 26 * wpls;",
+            "    wpls27 = 27 * wpls;",
+            "    wpls28 = 28 * wpls;",
+            "    wpls29 = 29 * wpls;",
+            "    wpls30 = 30 * wpls;",
+            "    wpls31 = 31 * wpls;"};
+
+static char wplstrp[][10] = {"+ wpls", "+ wpls2", "+ wpls3", "+ wpls4",
+                             "+ wpls5", "+ wpls6", "+ wpls7", "+ wpls8",
+                             "+ wpls9", "+ wpls10", "+ wpls11", "+ wpls12",
+                             "+ wpls13", "+ wpls14", "+ wpls15", "+ wpls16",
+                             "+ wpls17", "+ wpls18", "+ wpls19", "+ wpls20",
+                             "+ wpls21", "+ wpls22", "+ wpls23", "+ wpls24",
+                             "+ wpls25", "+ wpls26", "+ wpls27", "+ wpls28",
+                             "+ wpls29", "+ wpls30", "+ wpls31"};
+
+static char wplstrm[][10] = {"- wpls", "- wpls2", "- wpls3", "- wpls4",
+                             "- wpls5", "- wpls6", "- wpls7", "- wpls8",
+                             "- wpls9", "- wpls10", "- wpls11", "- wpls12",
+                             "- wpls13", "- wpls14", "- wpls15", "- wpls16",
+                             "- wpls17", "- wpls18", "- wpls19", "- wpls20",
+                             "- wpls21", "- wpls22", "- wpls23", "- wpls24",
+                             "- wpls25", "- wpls26", "- wpls27", "- wpls28",
+                             "- wpls29", "- wpls30", "- wpls31"};
+
+
+/*!
+ *  fmorphautogen()
+ *
+ *      Input:  sela
+ *              fileindex
+ *              filename (<optional>; can be null)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This function generates all the code for implementing
+ *          dwa morphological operations using all the sels in the sela.
+ *      (2) See fmorphautogen1() and fmorphautogen2() for details.
+ */
+l_int32
+fmorphautogen(SELA        *sela,
+              l_int32      fileindex,
+              const char  *filename)
+{
+l_int32  ret1, ret2;
+
+    PROCNAME("fmorphautogen");
+
+    if (!sela)
+        return ERROR_INT("sela not defined", procName, 1);
+    ret1 = fmorphautogen1(sela, fileindex, filename);
+    ret2 = fmorphautogen2(sela, fileindex, filename);
+    if (ret1 || ret2)
+        return ERROR_INT("code generation problem", procName, 1);
+    return 0;
+}
+
+
+/*!
+ *  fmorphautogen1()
+ *
+ *      Input:  sela
+ *              fileindex
+ *              filename (<optional>; can be null)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This function uses morphtemplate1.txt to create a
+ *          top-level file that contains two functions.  These
+ *          functions will carry out dilation, erosion,
+ *          opening or closing for any of the sels in the input sela.
+ *      (2) The fileindex parameter is inserted into the output
+ *          filename, as described below.
+ *      (3) If filename == NULL, the output file is fmorphgen.<n>.c,
+ *          where <n> is equal to the 'fileindex' parameter.
+ *      (4) If filename != NULL, the output file is <filename>.<n>.c.
+ */
+l_int32
+fmorphautogen1(SELA        *sela,
+               l_int32      fileindex,
+               const char  *filename)
+{
+char    *filestr;
+char    *str_proto1, *str_proto2, *str_proto3;
+char    *str_doc1, *str_doc2, *str_doc3, *str_doc4;
+char    *str_def1, *str_def2, *str_proc1, *str_proc2;
+char    *str_dwa1, *str_low_dt, *str_low_ds, *str_low_ts;
+char    *str_low_tsp1, *str_low_dtp1;
+char     bigbuf[L_BUF_SIZE];
+l_int32  i, nsels, nbytes, actstart, end, newstart;
+size_t   size;
+SARRAY  *sa1, *sa2, *sa3;
+
+    PROCNAME("fmorphautogen1");
+
+    if (!sela)
+        return ERROR_INT("sela not defined", procName, 1);
+    if (fileindex < 0)
+        fileindex = 0;
+    if ((nsels = selaGetCount(sela)) == 0)
+        return ERROR_INT("no sels in sela", procName, 1);
+
+        /* Make array of sel names */
+    sa1 = selaGetSelnames(sela);
+
+        /* Make array of textlines from morphtemplate1.txt */
+    if ((filestr = (char *)l_binaryRead(TEMPLATE1, &size)) == NULL)
+        return ERROR_INT("filestr not made", procName, 1);
+    if ((sa2 = sarrayCreateLinesFromString(filestr, 1)) == NULL)
+        return ERROR_INT("sa2 not made", procName, 1);
+    LEPT_FREE(filestr);
+/*    sarrayWriteStream(stderr, sa2); */
+
+        /* Make strings containing function call names */
+    sprintf(bigbuf, "PIX *pixMorphDwa_%d(PIX *pixd, PIX *pixs, "
+                    "l_int32 operation, char *selname);", fileindex);
+    str_proto1 = stringNew(bigbuf);
+    sprintf(bigbuf, "PIX *pixFMorphopGen_%d(PIX *pixd, PIX *pixs, "
+                    "l_int32 operation, char *selname);", fileindex);
+    str_proto2 = stringNew(bigbuf);
+    sprintf(bigbuf, "l_int32 fmorphopgen_low_%d(l_uint32 *datad, l_int32 w,\n"
+        "                          l_int32 h, l_int32 wpld,\n"
+        "                          l_uint32 *datas, l_int32 wpls,\n"
+        "                          l_int32 index);", fileindex);
+    str_proto3 = stringNew(bigbuf);
+    sprintf(bigbuf, " *             PIX     *pixMorphDwa_%d()", fileindex);
+    str_doc1 = stringNew(bigbuf);
+    sprintf(bigbuf, " *             PIX     *pixFMorphopGen_%d()", fileindex);
+    str_doc2 = stringNew(bigbuf);
+    sprintf(bigbuf, " *  pixMorphDwa_%d()", fileindex);
+    str_doc3 = stringNew(bigbuf);
+    sprintf(bigbuf, " *  pixFMorphopGen_%d()", fileindex);
+    str_doc4 = stringNew(bigbuf);
+    sprintf(bigbuf, "pixMorphDwa_%d(PIX     *pixd,", fileindex);
+    str_def1 = stringNew(bigbuf);
+    sprintf(bigbuf, "pixFMorphopGen_%d(PIX     *pixd,", fileindex);
+    str_def2 = stringNew(bigbuf);
+    sprintf(bigbuf, "    PROCNAME(\"pixMorphDwa_%d\");", fileindex);
+    str_proc1 = stringNew(bigbuf);
+    sprintf(bigbuf, "    PROCNAME(\"pixFMorphopGen_%d\");", fileindex);
+    str_proc2 = stringNew(bigbuf);
+    sprintf(bigbuf,
+            "    pixt2 = pixFMorphopGen_%d(NULL, pixt1, operation, selname);",
+            fileindex);
+    str_dwa1 = stringNew(bigbuf);
+    sprintf(bigbuf,
+      "            fmorphopgen_low_%d(datad, w, h, wpld, datat, wpls, index);",
+      fileindex);
+    str_low_dt = stringNew(bigbuf);
+    sprintf(bigbuf,
+      "            fmorphopgen_low_%d(datad, w, h, wpld, datas, wpls, index);",
+      fileindex);
+    str_low_ds = stringNew(bigbuf);
+    sprintf(bigbuf,
+     "            fmorphopgen_low_%d(datat, w, h, wpls, datas, wpls, index+1);",
+      fileindex);
+    str_low_tsp1 = stringNew(bigbuf);
+    sprintf(bigbuf,
+      "            fmorphopgen_low_%d(datat, w, h, wpls, datas, wpls, index);",
+      fileindex);
+    str_low_ts = stringNew(bigbuf);
+    sprintf(bigbuf,
+     "            fmorphopgen_low_%d(datad, w, h, wpld, datat, wpls, index+1);",
+      fileindex);
+    str_low_dtp1 = stringNew(bigbuf);
+
+        /* Make the output sa */
+    if ((sa3 = sarrayCreate(0)) == NULL)
+        return ERROR_INT("sa3 not made", procName, 1);
+
+        /* Copyright notice and info header */
+    sarrayParseRange(sa2, 0, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Insert function names as documentation */
+    sarrayAddString(sa3, str_doc1, L_INSERT);
+    sarrayAddString(sa3, str_doc2, L_INSERT);
+
+        /* Add '#include's */
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Insert function prototypes */
+    sarrayAddString(sa3, str_proto1, L_INSERT);
+    sarrayAddString(sa3, str_proto2, L_INSERT);
+    sarrayAddString(sa3, str_proto3, L_INSERT);
+
+        /* Add static globals */
+    sprintf(bigbuf, "\nstatic l_int32   NUM_SELS_GENERATED = %d;", nsels);
+    sarrayAddString(sa3, bigbuf, L_COPY);
+    sprintf(bigbuf, "static char  SEL_NAMES[][80] = {");
+    sarrayAddString(sa3, bigbuf, L_COPY);
+    for (i = 0; i < nsels - 1; i++) {
+        sprintf(bigbuf, "                             \"%s\",",
+                sarrayGetString(sa1, i, L_NOCOPY));
+        sarrayAddString(sa3, bigbuf, L_COPY);
+    }
+    sprintf(bigbuf, "                             \"%s\"};",
+            sarrayGetString(sa1, i, L_NOCOPY));
+    sarrayAddString(sa3, bigbuf, L_COPY);
+
+        /* Start pixMorphDwa_*() function description */
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_doc3, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Finish pixMorphDwa_*() function definition */
+    sarrayAddString(sa3, str_def1, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_proc1, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_dwa1, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Start pixFMorphopGen_*() function description */
+    sarrayAddString(sa3, str_doc4, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Finish pixFMorphopGen_*() function definition */
+    sarrayAddString(sa3, str_def2, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_proc2, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_low_dt, L_COPY);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_low_ds, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_low_tsp1, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_low_dt, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_low_ts, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+    sarrayAddString(sa3, str_low_dtp1, L_INSERT);
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Output to file */
+    if ((filestr = sarrayToString(sa3, 1)) == NULL)
+        return ERROR_INT("filestr from sa3 not made", procName, 1);
+    nbytes = strlen(filestr);
+    if (filename)
+        sprintf(bigbuf, "%s.%d.c", filename, fileindex);
+    else
+        sprintf(bigbuf, "%s.%d.c", OUTROOT, fileindex);
+    l_binaryWrite(bigbuf, "w", filestr, nbytes);
+    sarrayDestroy(&sa1);
+    sarrayDestroy(&sa2);
+    sarrayDestroy(&sa3);
+    LEPT_FREE(filestr);
+    return 0;
+}
+
+
+/*
+ *  fmorphautogen2()
+ *
+ *      Input:  sela
+ *              fileindex
+ *              filename (<optional>; can be null)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This function uses morphtemplate2.txt to create a
+ *          low-level file that contains the low-level functions for
+ *          implementing dilation and erosion for every sel
+ *          in the input sela.
+ *      (2) The fileindex parameter is inserted into the output
+ *          filename, as described below.
+ *      (3) If filename == NULL, the output file is fmorphgenlow.<n>.c,
+ *          where <n> is equal to the 'fileindex' parameter.
+ *      (4) If filename != NULL, the output file is <filename>low.<n>.c.
+ */
+l_int32
+fmorphautogen2(SELA        *sela,
+               l_int32      fileindex,
+               const char  *filename)
+{
+char    *filestr, *linestr, *fname;
+char    *str_doc1, *str_doc2, *str_doc3, *str_doc4, *str_def1;
+char     bigbuf[L_BUF_SIZE];
+char     breakstring[] = "        break;";
+char     staticstring[] = "static void";
+l_int32  i, nsels, nbytes, actstart, end, newstart;
+l_int32  argstart, argend, loopstart, loopend, finalstart, finalend;
+size_t   size;
+SARRAY  *sa1, *sa2, *sa3, *sa4, *sa5, *sa6;
+SEL     *sel;
+
+    PROCNAME("fmorphautogen2");
+
+    if (!sela)
+        return ERROR_INT("sela not defined", procName, 1);
+    if (fileindex < 0)
+        fileindex = 0;
+    if ((nsels = selaGetCount(sela)) == 0)
+        return ERROR_INT("no sels in sela", procName, 1);
+
+        /* Make the array of textlines from morphtemplate2.txt */
+    if ((filestr = (char *)l_binaryRead(TEMPLATE2, &size)) == NULL)
+        return ERROR_INT("filestr not made", procName, 1);
+    if ((sa1 = sarrayCreateLinesFromString(filestr, 1)) == NULL)
+        return ERROR_INT("sa1 not made", procName, 1);
+    LEPT_FREE(filestr);
+
+        /* Make the array of static function names */
+    if ((sa2 = sarrayCreate(2 * nsels)) == NULL)
+        return ERROR_INT("sa2 not made", procName, 1);
+    for (i = 0; i < nsels; i++) {
+        sprintf(bigbuf, "fdilate_%d_%d", fileindex, i);
+        sarrayAddString(sa2, bigbuf, L_COPY);
+        sprintf(bigbuf, "ferode_%d_%d", fileindex, i);
+        sarrayAddString(sa2, bigbuf, L_COPY);
+    }
+
+        /* Make the static prototype strings */
+    if ((sa3 = sarrayCreate(2 * nsels)) == NULL)
+        return ERROR_INT("sa3 not made", procName, 1);
+    for (i = 0; i < 2 * nsels; i++) {
+        fname = sarrayGetString(sa2, i, 0);
+        sprintf(bigbuf, "static void  %s%s", fname, PROTOARGS);
+        sarrayAddString(sa3, bigbuf, L_COPY);
+    }
+
+        /* Make strings containing function names */
+    sprintf(bigbuf, " *             l_int32    fmorphopgen_low_%d()",
+            fileindex);
+    str_doc1 = stringNew(bigbuf);
+    sprintf(bigbuf, " *             void       fdilate_%d_*()", fileindex);
+    str_doc2 = stringNew(bigbuf);
+    sprintf(bigbuf, " *             void       ferode_%d_*()", fileindex);
+    str_doc3 = stringNew(bigbuf);
+    sprintf(bigbuf, " *  fmorphopgen_low_%d()", fileindex);
+    str_doc4 = stringNew(bigbuf);
+    sprintf(bigbuf, "fmorphopgen_low_%d(l_uint32  *datad,", fileindex);
+    str_def1 = stringNew(bigbuf);
+
+        /* Output to this sa */
+    if ((sa4 = sarrayCreate(0)) == NULL)
+        return ERROR_INT("sa4 not made", procName, 1);
+
+        /* Copyright notice and info header */
+    sarrayParseRange(sa1, 0, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+
+        /* Insert function names as documentation */
+    sarrayAddString(sa4, str_doc1, L_INSERT);
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+    sarrayAddString(sa4, str_doc2, L_INSERT);
+    sarrayAddString(sa4, str_doc3, L_INSERT);
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+
+        /* Insert static protos */
+    for (i = 0; i < 2 * nsels; i++) {
+        if ((linestr = sarrayGetString(sa3, i, L_COPY)) == NULL)
+            return ERROR_INT("linestr not retrieved", procName, 1);
+        sarrayAddString(sa4, linestr, L_INSERT);
+    }
+
+        /* Insert function header */
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+    sarrayAddString(sa4, str_doc4, L_INSERT);
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+    sarrayAddString(sa4, str_def1, L_INSERT);
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+
+        /* Generate and insert the dispatcher code */
+    for (i = 0; i < 2 * nsels; i++) {
+        sprintf(bigbuf, "    case %d:", i);
+        sarrayAddString(sa4, bigbuf, L_COPY);
+        sprintf(bigbuf, "        %s(datad, w, h, wpld, datas, wpls);",
+               sarrayGetString(sa2, i, L_NOCOPY));
+        sarrayAddString(sa4, bigbuf, L_COPY);
+        sarrayAddString(sa4, breakstring, L_COPY);
+    }
+
+        /* Finish the dispatcher and introduce the low-level code */
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa4, sa1, actstart, end);
+
+        /* Get the range for the args common to all functions */
+    sarrayParseRange(sa1, newstart, &argstart, &argend, &newstart, "--", 0);
+
+        /* Get the range for the loop code common to all functions */
+    sarrayParseRange(sa1, newstart, &loopstart, &loopend, &newstart, "--", 0);
+
+        /* Get the range for the ending code common to all functions */
+    sarrayParseRange(sa1, newstart, &finalstart, &finalend, &newstart, "--", 0);
+
+        /* Do all the static functions */
+    for (i = 0; i < 2 * nsels; i++) {
+            /* Generate the function header and add the common args */
+        sarrayAddString(sa4, staticstring, L_COPY);
+        fname = sarrayGetString(sa2, i, L_NOCOPY);
+        sprintf(bigbuf, "%s(l_uint32  *datad,", fname);
+        sarrayAddString(sa4, bigbuf, L_COPY);
+        sarrayAppendRange(sa4, sa1, argstart, argend);
+
+            /* Declare and define wplsN args, as necessary */
+        if ((sel = selaGetSel(sela, i/2)) == NULL)
+            return ERROR_INT("sel not returned", procName, 1);
+        if ((sa5 = sarrayMakeWplsCode(sel)) == NULL)
+            return ERROR_INT("sa5 not made", procName, 1);
+        sarrayJoin(sa4, sa5);
+        sarrayDestroy(&sa5);
+
+            /* Add the function loop code */
+        sarrayAppendRange(sa4, sa1, loopstart, loopend);
+
+            /* Insert barrel-op code for *dptr */
+        if ((sa6 = sarrayMakeInnerLoopDWACode(sel, i)) == NULL)
+            return ERROR_INT("sa6 not made", procName, 1);
+        sarrayJoin(sa4, sa6);
+        sarrayDestroy(&sa6);
+
+            /* Finish the function code */
+        sarrayAppendRange(sa4, sa1, finalstart, finalend);
+    }
+
+        /* Output to file */
+    if ((filestr = sarrayToString(sa4, 1)) == NULL)
+        return ERROR_INT("filestr from sa4 not made", procName, 1);
+    nbytes = strlen(filestr);
+    if (filename)
+        sprintf(bigbuf, "%slow.%d.c", filename, fileindex);
+    else
+        sprintf(bigbuf, "%slow.%d.c", OUTROOT, fileindex);
+    l_binaryWrite(bigbuf, "w", filestr, nbytes);
+    sarrayDestroy(&sa1);
+    sarrayDestroy(&sa2);
+    sarrayDestroy(&sa3);
+    sarrayDestroy(&sa4);
+    LEPT_FREE(filestr);
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                            Helper code for sel                           *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  sarrayMakeWplsCode()
+ */
+static SARRAY *
+sarrayMakeWplsCode(SEL  *sel)
+{
+char     emptystring[] = "";
+l_int32  i, j, ymax, dely, allvshifts;
+l_int32  vshift[32];
+SARRAY  *sa;
+
+    PROCNAME("sarrayMakeWplsCode");
+
+    if (!sel)
+        return (SARRAY *)ERROR_PTR("sel not defined", procName, NULL);
+
+    for (i = 0; i < 32; i++)
+        vshift[i] = 0;
+    ymax = 0;
+    for (i = 0; i < sel->sy; i++) {
+        for (j = 0; j < sel->sx; j++) {
+            if (sel->data[i][j] == 1) {
+                dely = L_ABS(i - sel->cy);
+                if (dely < 32)
+                    vshift[dely] = 1;
+                ymax = L_MAX(ymax, dely);
+            }
+        }
+    }
+    if (ymax > 31) {
+        L_WARNING("ymax > 31; truncating to 31\n", procName);
+        ymax = 31;
+    }
+
+        /* Test if this is a vertical brick */
+    allvshifts = TRUE;
+    for (i = 0; i < ymax; i++) {
+        if (vshift[i] == 0) {
+            allvshifts = FALSE;
+            break;
+        }
+    }
+
+    if ((sa = sarrayCreate(0)) == NULL)
+        return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+
+        /* Add declarations */
+    if (allvshifts == TRUE) {   /* packs them as well as possible */
+        if (ymax > 4)
+            sarrayAddString(sa, wpldecls[2], L_COPY);
+        if (ymax > 8)
+            sarrayAddString(sa, wpldecls[6], L_COPY);
+        if (ymax > 12)
+            sarrayAddString(sa, wpldecls[10], L_COPY);
+        if (ymax > 16)
+            sarrayAddString(sa, wpldecls[14], L_COPY);
+        if (ymax > 20)
+            sarrayAddString(sa, wpldecls[18], L_COPY);
+        if (ymax > 24)
+            sarrayAddString(sa, wpldecls[22], L_COPY);
+        if (ymax > 28)
+            sarrayAddString(sa, wpldecls[26], L_COPY);
+        if (ymax > 1)
+            sarrayAddString(sa, wpldecls[ymax - 2], L_COPY);
+    } else {  /* puts them one/line */
+        for (i = 2; i <= ymax; i++) {
+            if (vshift[i])
+                sarrayAddString(sa, wplgendecls[i - 2], L_COPY);
+        }
+    }
+
+    sarrayAddString(sa, emptystring, L_COPY);
+
+        /* Add definitions */
+    for (i = 2; i <= ymax; i++) {
+        if (vshift[i])
+            sarrayAddString(sa, wpldefs[i - 2], L_COPY);
+    }
+
+    return sa;
+}
+
+
+/*!
+ *  sarrayMakeInnerLoopDWACode()
+ */
+static SARRAY *
+sarrayMakeInnerLoopDWACode(SEL     *sel,
+                           l_int32  index)
+{
+char    *tstr, *string;
+char     logicalor[] = "|";
+char     logicaland[] = "&";
+char     bigbuf[L_BUF_SIZE];
+l_int32  i, j, optype, count, nfound, delx, dely;
+SARRAY  *sa;
+
+    PROCNAME("sarrayMakeInnerLoopDWACode");
+
+    if (!sel)
+        return (SARRAY *)ERROR_PTR("sel not defined", procName, NULL);
+
+    if (index % 2 == 0) {
+        optype = L_MORPH_DILATE;
+        tstr = logicalor;
+    } else {
+        optype = L_MORPH_ERODE;
+        tstr = logicaland;
+    }
+
+    count = 0;
+    for (i = 0; i < sel->sy; i++) {
+        for (j = 0; j < sel->sx; j++) {
+            if (sel->data[i][j] == 1)
+                count++;
+        }
+    }
+
+    if ((sa = sarrayCreate(0)) == NULL)
+        return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+    if (count == 0) {
+        L_WARNING("no hits in Sel %d\n", procName, index);
+        return sa;  /* no code inside! */
+    }
+
+    nfound = 0;
+    for (i = 0; i < sel->sy; i++) {
+        for (j = 0; j < sel->sx; j++) {
+            if (sel->data[i][j] == 1) {
+                nfound++;
+                if (optype == L_MORPH_DILATE) {
+                    dely = sel->cy - i;
+                    delx = sel->cx - j;
+                } else if (optype == L_MORPH_ERODE) {
+                    dely = i - sel->cy;
+                    delx = j - sel->cx;
+                }
+                if ((string = makeBarrelshiftString(delx, dely)) == NULL) {
+                    L_WARNING("barrel shift string not made\n", procName);
+                    continue;
+                }
+                if (count == 1)  /* just one item */
+                    sprintf(bigbuf, "            *dptr = %s;", string);
+                else if (nfound == 1)
+                    sprintf(bigbuf, "            *dptr = %s %s", string, tstr);
+                else if (nfound < count)
+                    sprintf(bigbuf, "                    %s %s", string, tstr);
+                else  /* nfound == count */
+                    sprintf(bigbuf, "                    %s;", string);
+                sarrayAddString(sa, bigbuf, L_COPY);
+                LEPT_FREE(string);
+            }
+        }
+    }
+
+    return sa;
+}
+
+
+/*!
+ *  makeBarrelshiftString()
+ */
+static char *
+makeBarrelshiftString(l_int32  delx,    /* j - cx */
+                      l_int32  dely)    /* i - cy */
+{
+l_int32  absx, absy;
+char     bigbuf[L_BUF_SIZE];
+
+    PROCNAME("makeBarrelshiftString");
+
+    if (delx < -31 || delx > 31)
+        return (char *)ERROR_PTR("delx out of bounds", procName, NULL);
+    if (dely < -31 || dely > 31)
+        return (char *)ERROR_PTR("dely out of bounds", procName, NULL);
+    absx = L_ABS(delx);
+    absy = L_ABS(dely);
+
+    if ((delx == 0) && (dely == 0))
+        sprintf(bigbuf, "(*sptr)");
+    else if ((delx == 0) && (dely < 0))
+        sprintf(bigbuf, "(*(sptr %s))", wplstrm[absy - 1]);
+    else if ((delx == 0) && (dely > 0))
+        sprintf(bigbuf, "(*(sptr %s))", wplstrp[absy - 1]);
+    else if ((delx < 0) && (dely == 0))
+        sprintf(bigbuf, "((*(sptr) >> %d) | (*(sptr - 1) << %d))",
+              absx, 32 - absx);
+    else if ((delx > 0) && (dely == 0))
+        sprintf(bigbuf, "((*(sptr) << %d) | (*(sptr + 1) >> %d))",
+              absx, 32 - absx);
+    else if ((delx < 0) && (dely < 0))
+        sprintf(bigbuf, "((*(sptr %s) >> %d) | (*(sptr %s - 1) << %d))",
+              wplstrm[absy - 1], absx, wplstrm[absy - 1], 32 - absx);
+    else if ((delx > 0) && (dely < 0))
+        sprintf(bigbuf, "((*(sptr %s) << %d) | (*(sptr %s + 1) >> %d))",
+              wplstrm[absy - 1], absx, wplstrm[absy - 1], 32 - absx);
+    else if ((delx < 0) && (dely > 0))
+        sprintf(bigbuf, "((*(sptr %s) >> %d) | (*(sptr %s - 1) << %d))",
+              wplstrp[absy - 1], absx, wplstrp[absy - 1], 32 - absx);
+    else  /*  ((delx > 0) && (dely > 0))  */
+        sprintf(bigbuf, "((*(sptr %s) << %d) | (*(sptr %s + 1) >> %d))",
+              wplstrp[absy - 1], absx, wplstrp[absy - 1], 32 - absx);
+
+    return stringNew(bigbuf);
+}
diff --git a/src/fmorphgen.1.c b/src/fmorphgen.1.c
new file mode 100644 (file)
index 0000000..4fbc6ca
--- /dev/null
@@ -0,0 +1,273 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*!
+ *      Top-level fast binary morphology with auto-generated sels
+ *
+ *             PIX     *pixMorphDwa_1()
+ *             PIX     *pixFMorphopGen_1()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+PIX *pixMorphDwa_1(PIX *pixd, PIX *pixs, l_int32 operation, char *selname);
+PIX *pixFMorphopGen_1(PIX *pixd, PIX *pixs, l_int32 operation, char *selname);
+l_int32 fmorphopgen_low_1(l_uint32 *datad, l_int32 w,
+                          l_int32 h, l_int32 wpld,
+                          l_uint32 *datas, l_int32 wpls,
+                          l_int32 index);
+
+static l_int32   NUM_SELS_GENERATED = 58;
+static char  SEL_NAMES[][80] = {
+                             "sel_2h",
+                             "sel_3h",
+                             "sel_4h",
+                             "sel_5h",
+                             "sel_6h",
+                             "sel_7h",
+                             "sel_8h",
+                             "sel_9h",
+                             "sel_10h",
+                             "sel_11h",
+                             "sel_12h",
+                             "sel_13h",
+                             "sel_14h",
+                             "sel_15h",
+                             "sel_20h",
+                             "sel_21h",
+                             "sel_25h",
+                             "sel_30h",
+                             "sel_31h",
+                             "sel_35h",
+                             "sel_40h",
+                             "sel_41h",
+                             "sel_45h",
+                             "sel_50h",
+                             "sel_51h",
+                             "sel_2v",
+                             "sel_3v",
+                             "sel_4v",
+                             "sel_5v",
+                             "sel_6v",
+                             "sel_7v",
+                             "sel_8v",
+                             "sel_9v",
+                             "sel_10v",
+                             "sel_11v",
+                             "sel_12v",
+                             "sel_13v",
+                             "sel_14v",
+                             "sel_15v",
+                             "sel_20v",
+                             "sel_21v",
+                             "sel_25v",
+                             "sel_30v",
+                             "sel_31v",
+                             "sel_35v",
+                             "sel_40v",
+                             "sel_41v",
+                             "sel_45v",
+                             "sel_50v",
+                             "sel_51v",
+                             "sel_2",
+                             "sel_3",
+                             "sel_4",
+                             "sel_5",
+                             "sel_2dp",
+                             "sel_2dm",
+                             "sel_5dp",
+                             "sel_5dm"};
+
+/*!
+ *  pixMorphDwa_1()
+ *
+ *      Input:  pixd (usual 3 choices: null, == pixs, != pixs)
+ *              pixs (1 bpp)
+ *              operation  (L_MORPH_DILATE, L_MORPH_ERODE,
+ *                          L_MORPH_OPEN, L_MORPH_CLOSE)
+ *              sel name
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This simply adds a border, calls the appropriate
+ *          pixFMorphopGen_*(), and removes the border.
+ *          See the notes for that function.
+ *      (2) The size of the border depends on the operation
+ *          and the boundary conditions.
+ */
+PIX *
+pixMorphDwa_1(PIX     *pixd,
+              PIX     *pixs,
+              l_int32  operation,
+              char    *selname)
+{
+l_int32  bordercolor, bordersize;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixMorphDwa_1");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, pixd);
+
+        /* Set the border size */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    bordersize = 32;
+    if (bordercolor == 0 && operation == L_MORPH_CLOSE)
+        bordersize += 32;
+
+    pixt1 = pixAddBorder(pixs, bordersize, 0);
+    pixt2 = pixFMorphopGen_1(NULL, pixt1, operation, selname);
+    pixt3 = pixRemoveBorder(pixt2, bordersize);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixCopy(pixd, pixt3);
+    pixDestroy(&pixt3);
+    return pixd;
+}
+
+
+/*!
+ *  pixFMorphopGen_1()
+ *
+ *      Input:  pixd (usual 3 choices: null, == pixs, != pixs)
+ *              pixs (1 bpp)
+ *              operation  (L_MORPH_DILATE, L_MORPH_ERODE,
+ *                          L_MORPH_OPEN, L_MORPH_CLOSE)
+ *              sel name
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This is a dwa operation, and the Sels must be limited in
+ *          size to not more than 31 pixels about the origin.
+ *      (2) A border of appropriate size (32 pixels, or 64 pixels
+ *          for safe closing with asymmetric b.c.) must be added before
+ *          this function is called.
+ *      (3) This handles all required setting of the border pixels
+ *          before erosion and dilation.
+ *      (4) The closing operation is safe; no pixels can be removed
+ *          near the boundary.
+ */
+PIX *
+pixFMorphopGen_1(PIX     *pixd,
+                 PIX     *pixs,
+                 l_int32  operation,
+                 char    *selname)
+{
+l_int32    i, index, found, w, h, wpls, wpld, bordercolor, erodeop, borderop;
+l_uint32  *datad, *datas, *datat;
+PIX       *pixt;
+
+    PROCNAME("pixFMorphopGen_1");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, pixd);
+
+        /* Get boundary colors to use */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    if (bordercolor == 1)
+        erodeop = PIX_SET;
+    else
+        erodeop = PIX_CLR;
+
+    found = FALSE;
+    for (i = 0; i < NUM_SELS_GENERATED; i++) {
+        if (strcmp(selname, SEL_NAMES[i]) == 0) {
+            found = TRUE;
+            index = 2 * i;
+            break;
+        }
+    }
+    if (found == FALSE)
+        return (PIX *)ERROR_PTR("sel index not found", procName, pixd);
+
+    if (!pixd) {
+        if ((pixd = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    else  /* for in-place or pre-allocated */
+        pixResizeImageData(pixd, pixs);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+        /* The images must be surrounded, in advance, with a border of
+         * size 32 pixels (or 64, for closing), that we'll read from.
+         * Fabricate a "proper" image as the subimage within the 32
+         * pixel border, having the following parameters:  */
+    w = pixGetWidth(pixs) - 64;
+    h = pixGetHeight(pixs) - 64;
+    datas = pixGetData(pixs) + 32 * wpls + 1;
+    datad = pixGetData(pixd) + 32 * wpld + 1;
+
+    if (operation == L_MORPH_DILATE || operation == L_MORPH_ERODE) {
+        borderop = PIX_CLR;
+        if (operation == L_MORPH_ERODE) {
+            borderop = erodeop;
+            index++;
+        }
+        if (pixd == pixs) {  /* in-place; generate a temp image */
+            if ((pixt = pixCopy(NULL, pixs)) == NULL)
+                return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+            datat = pixGetData(pixt) + 32 * wpls + 1;
+            pixSetOrClearBorder(pixt, 32, 32, 32, 32, borderop);
+            fmorphopgen_low_1(datad, w, h, wpld, datat, wpls, index);
+            pixDestroy(&pixt);
+        }
+        else { /* not in-place */
+            pixSetOrClearBorder(pixs, 32, 32, 32, 32, borderop);
+            fmorphopgen_low_1(datad, w, h, wpld, datas, wpls, index);
+        }
+    }
+    else {  /* opening or closing; generate a temp image */
+        if ((pixt = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+        datat = pixGetData(pixt) + 32 * wpls + 1;
+        if (operation == L_MORPH_OPEN) {
+            pixSetOrClearBorder(pixs, 32, 32, 32, 32, erodeop);
+            fmorphopgen_low_1(datat, w, h, wpls, datas, wpls, index+1);
+            pixSetOrClearBorder(pixt, 32, 32, 32, 32, PIX_CLR);
+            fmorphopgen_low_1(datad, w, h, wpld, datat, wpls, index);
+        }
+        else {  /* closing */
+            pixSetOrClearBorder(pixs, 32, 32, 32, 32, PIX_CLR);
+            fmorphopgen_low_1(datat, w, h, wpls, datas, wpls, index);
+            pixSetOrClearBorder(pixt, 32, 32, 32, 32, erodeop);
+            fmorphopgen_low_1(datad, w, h, wpld, datat, wpls, index+1);
+        }
+        pixDestroy(&pixt);
+    }
+
+    return pixd;
+}
+
diff --git a/src/fmorphgenlow.1.c b/src/fmorphgenlow.1.c
new file mode 100644 (file)
index 0000000..26a7f6a
--- /dev/null
@@ -0,0 +1,5862 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*!
+ *     Low-level fast binary morphology with auto-generated sels
+ *
+ *      Dispatcher:
+ *             l_int32    fmorphopgen_low_1()
+ *
+ *      Static Low-level:
+ *             void       fdilate_1_*()
+ *             void       ferode_1_*()
+ */
+
+#include "allheaders.h"
+
+static void  fdilate_1_0(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_0(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_1(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_1(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_2(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_2(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_3(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_3(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_4(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_4(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_5(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_5(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_6(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_6(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_7(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_7(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_8(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_8(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_9(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_9(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_10(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_10(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_11(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_11(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_12(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_12(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_13(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_13(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_14(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_14(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_15(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_15(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_16(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_16(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_17(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_17(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_18(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_18(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_19(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_19(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_20(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_20(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_21(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_21(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_22(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_22(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_23(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_23(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_24(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_24(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_25(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_25(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_26(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_26(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_27(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_27(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_28(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_28(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_29(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_29(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_30(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_30(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_31(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_31(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_32(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_32(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_33(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_33(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_34(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_34(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_35(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_35(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_36(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_36(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_37(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_37(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_38(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_38(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_39(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_39(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_40(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_40(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_41(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_41(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_42(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_42(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_43(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_43(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_44(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_44(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_45(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_45(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_46(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_46(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_47(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_47(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_48(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_48(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_49(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_49(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_50(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_50(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_51(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_51(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_52(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_52(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_53(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_53(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_54(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_54(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_55(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_55(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_56(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_56(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  fdilate_1_57(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+static void  ferode_1_57(l_uint32 *, l_int32, l_int32, l_int32, l_uint32 *, l_int32);
+
+
+/*---------------------------------------------------------------------*
+ *                          Fast morph dispatcher                      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  fmorphopgen_low_1()
+ *
+ *       a dispatcher to appropriate low-level code
+ */
+l_int32
+fmorphopgen_low_1(l_uint32  *datad,
+                  l_int32    w,
+                  l_int32    h,
+                  l_int32    wpld,
+                  l_uint32  *datas,
+                  l_int32    wpls,
+                  l_int32    index)
+{
+
+    switch (index)
+    {
+    case 0:
+        fdilate_1_0(datad, w, h, wpld, datas, wpls);
+        break;
+    case 1:
+        ferode_1_0(datad, w, h, wpld, datas, wpls);
+        break;
+    case 2:
+        fdilate_1_1(datad, w, h, wpld, datas, wpls);
+        break;
+    case 3:
+        ferode_1_1(datad, w, h, wpld, datas, wpls);
+        break;
+    case 4:
+        fdilate_1_2(datad, w, h, wpld, datas, wpls);
+        break;
+    case 5:
+        ferode_1_2(datad, w, h, wpld, datas, wpls);
+        break;
+    case 6:
+        fdilate_1_3(datad, w, h, wpld, datas, wpls);
+        break;
+    case 7:
+        ferode_1_3(datad, w, h, wpld, datas, wpls);
+        break;
+    case 8:
+        fdilate_1_4(datad, w, h, wpld, datas, wpls);
+        break;
+    case 9:
+        ferode_1_4(datad, w, h, wpld, datas, wpls);
+        break;
+    case 10:
+        fdilate_1_5(datad, w, h, wpld, datas, wpls);
+        break;
+    case 11:
+        ferode_1_5(datad, w, h, wpld, datas, wpls);
+        break;
+    case 12:
+        fdilate_1_6(datad, w, h, wpld, datas, wpls);
+        break;
+    case 13:
+        ferode_1_6(datad, w, h, wpld, datas, wpls);
+        break;
+    case 14:
+        fdilate_1_7(datad, w, h, wpld, datas, wpls);
+        break;
+    case 15:
+        ferode_1_7(datad, w, h, wpld, datas, wpls);
+        break;
+    case 16:
+        fdilate_1_8(datad, w, h, wpld, datas, wpls);
+        break;
+    case 17:
+        ferode_1_8(datad, w, h, wpld, datas, wpls);
+        break;
+    case 18:
+        fdilate_1_9(datad, w, h, wpld, datas, wpls);
+        break;
+    case 19:
+        ferode_1_9(datad, w, h, wpld, datas, wpls);
+        break;
+    case 20:
+        fdilate_1_10(datad, w, h, wpld, datas, wpls);
+        break;
+    case 21:
+        ferode_1_10(datad, w, h, wpld, datas, wpls);
+        break;
+    case 22:
+        fdilate_1_11(datad, w, h, wpld, datas, wpls);
+        break;
+    case 23:
+        ferode_1_11(datad, w, h, wpld, datas, wpls);
+        break;
+    case 24:
+        fdilate_1_12(datad, w, h, wpld, datas, wpls);
+        break;
+    case 25:
+        ferode_1_12(datad, w, h, wpld, datas, wpls);
+        break;
+    case 26:
+        fdilate_1_13(datad, w, h, wpld, datas, wpls);
+        break;
+    case 27:
+        ferode_1_13(datad, w, h, wpld, datas, wpls);
+        break;
+    case 28:
+        fdilate_1_14(datad, w, h, wpld, datas, wpls);
+        break;
+    case 29:
+        ferode_1_14(datad, w, h, wpld, datas, wpls);
+        break;
+    case 30:
+        fdilate_1_15(datad, w, h, wpld, datas, wpls);
+        break;
+    case 31:
+        ferode_1_15(datad, w, h, wpld, datas, wpls);
+        break;
+    case 32:
+        fdilate_1_16(datad, w, h, wpld, datas, wpls);
+        break;
+    case 33:
+        ferode_1_16(datad, w, h, wpld, datas, wpls);
+        break;
+    case 34:
+        fdilate_1_17(datad, w, h, wpld, datas, wpls);
+        break;
+    case 35:
+        ferode_1_17(datad, w, h, wpld, datas, wpls);
+        break;
+    case 36:
+        fdilate_1_18(datad, w, h, wpld, datas, wpls);
+        break;
+    case 37:
+        ferode_1_18(datad, w, h, wpld, datas, wpls);
+        break;
+    case 38:
+        fdilate_1_19(datad, w, h, wpld, datas, wpls);
+        break;
+    case 39:
+        ferode_1_19(datad, w, h, wpld, datas, wpls);
+        break;
+    case 40:
+        fdilate_1_20(datad, w, h, wpld, datas, wpls);
+        break;
+    case 41:
+        ferode_1_20(datad, w, h, wpld, datas, wpls);
+        break;
+    case 42:
+        fdilate_1_21(datad, w, h, wpld, datas, wpls);
+        break;
+    case 43:
+        ferode_1_21(datad, w, h, wpld, datas, wpls);
+        break;
+    case 44:
+        fdilate_1_22(datad, w, h, wpld, datas, wpls);
+        break;
+    case 45:
+        ferode_1_22(datad, w, h, wpld, datas, wpls);
+        break;
+    case 46:
+        fdilate_1_23(datad, w, h, wpld, datas, wpls);
+        break;
+    case 47:
+        ferode_1_23(datad, w, h, wpld, datas, wpls);
+        break;
+    case 48:
+        fdilate_1_24(datad, w, h, wpld, datas, wpls);
+        break;
+    case 49:
+        ferode_1_24(datad, w, h, wpld, datas, wpls);
+        break;
+    case 50:
+        fdilate_1_25(datad, w, h, wpld, datas, wpls);
+        break;
+    case 51:
+        ferode_1_25(datad, w, h, wpld, datas, wpls);
+        break;
+    case 52:
+        fdilate_1_26(datad, w, h, wpld, datas, wpls);
+        break;
+    case 53:
+        ferode_1_26(datad, w, h, wpld, datas, wpls);
+        break;
+    case 54:
+        fdilate_1_27(datad, w, h, wpld, datas, wpls);
+        break;
+    case 55:
+        ferode_1_27(datad, w, h, wpld, datas, wpls);
+        break;
+    case 56:
+        fdilate_1_28(datad, w, h, wpld, datas, wpls);
+        break;
+    case 57:
+        ferode_1_28(datad, w, h, wpld, datas, wpls);
+        break;
+    case 58:
+        fdilate_1_29(datad, w, h, wpld, datas, wpls);
+        break;
+    case 59:
+        ferode_1_29(datad, w, h, wpld, datas, wpls);
+        break;
+    case 60:
+        fdilate_1_30(datad, w, h, wpld, datas, wpls);
+        break;
+    case 61:
+        ferode_1_30(datad, w, h, wpld, datas, wpls);
+        break;
+    case 62:
+        fdilate_1_31(datad, w, h, wpld, datas, wpls);
+        break;
+    case 63:
+        ferode_1_31(datad, w, h, wpld, datas, wpls);
+        break;
+    case 64:
+        fdilate_1_32(datad, w, h, wpld, datas, wpls);
+        break;
+    case 65:
+        ferode_1_32(datad, w, h, wpld, datas, wpls);
+        break;
+    case 66:
+        fdilate_1_33(datad, w, h, wpld, datas, wpls);
+        break;
+    case 67:
+        ferode_1_33(datad, w, h, wpld, datas, wpls);
+        break;
+    case 68:
+        fdilate_1_34(datad, w, h, wpld, datas, wpls);
+        break;
+    case 69:
+        ferode_1_34(datad, w, h, wpld, datas, wpls);
+        break;
+    case 70:
+        fdilate_1_35(datad, w, h, wpld, datas, wpls);
+        break;
+    case 71:
+        ferode_1_35(datad, w, h, wpld, datas, wpls);
+        break;
+    case 72:
+        fdilate_1_36(datad, w, h, wpld, datas, wpls);
+        break;
+    case 73:
+        ferode_1_36(datad, w, h, wpld, datas, wpls);
+        break;
+    case 74:
+        fdilate_1_37(datad, w, h, wpld, datas, wpls);
+        break;
+    case 75:
+        ferode_1_37(datad, w, h, wpld, datas, wpls);
+        break;
+    case 76:
+        fdilate_1_38(datad, w, h, wpld, datas, wpls);
+        break;
+    case 77:
+        ferode_1_38(datad, w, h, wpld, datas, wpls);
+        break;
+    case 78:
+        fdilate_1_39(datad, w, h, wpld, datas, wpls);
+        break;
+    case 79:
+        ferode_1_39(datad, w, h, wpld, datas, wpls);
+        break;
+    case 80:
+        fdilate_1_40(datad, w, h, wpld, datas, wpls);
+        break;
+    case 81:
+        ferode_1_40(datad, w, h, wpld, datas, wpls);
+        break;
+    case 82:
+        fdilate_1_41(datad, w, h, wpld, datas, wpls);
+        break;
+    case 83:
+        ferode_1_41(datad, w, h, wpld, datas, wpls);
+        break;
+    case 84:
+        fdilate_1_42(datad, w, h, wpld, datas, wpls);
+        break;
+    case 85:
+        ferode_1_42(datad, w, h, wpld, datas, wpls);
+        break;
+    case 86:
+        fdilate_1_43(datad, w, h, wpld, datas, wpls);
+        break;
+    case 87:
+        ferode_1_43(datad, w, h, wpld, datas, wpls);
+        break;
+    case 88:
+        fdilate_1_44(datad, w, h, wpld, datas, wpls);
+        break;
+    case 89:
+        ferode_1_44(datad, w, h, wpld, datas, wpls);
+        break;
+    case 90:
+        fdilate_1_45(datad, w, h, wpld, datas, wpls);
+        break;
+    case 91:
+        ferode_1_45(datad, w, h, wpld, datas, wpls);
+        break;
+    case 92:
+        fdilate_1_46(datad, w, h, wpld, datas, wpls);
+        break;
+    case 93:
+        ferode_1_46(datad, w, h, wpld, datas, wpls);
+        break;
+    case 94:
+        fdilate_1_47(datad, w, h, wpld, datas, wpls);
+        break;
+    case 95:
+        ferode_1_47(datad, w, h, wpld, datas, wpls);
+        break;
+    case 96:
+        fdilate_1_48(datad, w, h, wpld, datas, wpls);
+        break;
+    case 97:
+        ferode_1_48(datad, w, h, wpld, datas, wpls);
+        break;
+    case 98:
+        fdilate_1_49(datad, w, h, wpld, datas, wpls);
+        break;
+    case 99:
+        ferode_1_49(datad, w, h, wpld, datas, wpls);
+        break;
+    case 100:
+        fdilate_1_50(datad, w, h, wpld, datas, wpls);
+        break;
+    case 101:
+        ferode_1_50(datad, w, h, wpld, datas, wpls);
+        break;
+    case 102:
+        fdilate_1_51(datad, w, h, wpld, datas, wpls);
+        break;
+    case 103:
+        ferode_1_51(datad, w, h, wpld, datas, wpls);
+        break;
+    case 104:
+        fdilate_1_52(datad, w, h, wpld, datas, wpls);
+        break;
+    case 105:
+        ferode_1_52(datad, w, h, wpld, datas, wpls);
+        break;
+    case 106:
+        fdilate_1_53(datad, w, h, wpld, datas, wpls);
+        break;
+    case 107:
+        ferode_1_53(datad, w, h, wpld, datas, wpls);
+        break;
+    case 108:
+        fdilate_1_54(datad, w, h, wpld, datas, wpls);
+        break;
+    case 109:
+        ferode_1_54(datad, w, h, wpld, datas, wpls);
+        break;
+    case 110:
+        fdilate_1_55(datad, w, h, wpld, datas, wpls);
+        break;
+    case 111:
+        ferode_1_55(datad, w, h, wpld, datas, wpls);
+        break;
+    case 112:
+        fdilate_1_56(datad, w, h, wpld, datas, wpls);
+        break;
+    case 113:
+        ferode_1_56(datad, w, h, wpld, datas, wpls);
+        break;
+    case 114:
+        fdilate_1_57(datad, w, h, wpld, datas, wpls);
+        break;
+    case 115:
+        ferode_1_57(datad, w, h, wpld, datas, wpls);
+        break;
+    }
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                 Low-level auto-generated static routines                 *
+ *--------------------------------------------------------------------------*/
+/*
+ *  N.B.  In all the low-level routines, the part of the image
+ *        that is accessed has been clipped by 32 pixels on
+ *        all four sides.  This is done in the higher level
+ *        code by redefining w and h smaller and by moving the
+ *        start-of-image pointers up to the beginning of this
+ *        interior rectangle.
+ */
+static void
+fdilate_1_0(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr);
+        }
+    }
+}
+
+static void
+ferode_1_0(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr);
+        }
+    }
+}
+
+static void
+fdilate_1_1(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31));
+        }
+    }
+}
+
+static void
+ferode_1_1(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31));
+        }
+    }
+}
+
+static void
+fdilate_1_2(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31));
+        }
+    }
+}
+
+static void
+ferode_1_2(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31));
+        }
+    }
+}
+
+static void
+fdilate_1_3(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30));
+        }
+    }
+}
+
+static void
+ferode_1_3(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30));
+        }
+    }
+}
+
+static void
+fdilate_1_4(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30));
+        }
+    }
+}
+
+static void
+ferode_1_4(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30));
+        }
+    }
+}
+
+static void
+fdilate_1_5(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29));
+        }
+    }
+}
+
+static void
+ferode_1_5(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29));
+        }
+    }
+}
+
+static void
+fdilate_1_6(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29));
+        }
+    }
+}
+
+static void
+ferode_1_6(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29));
+        }
+    }
+}
+
+static void
+fdilate_1_7(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28));
+        }
+    }
+}
+
+static void
+ferode_1_7(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28));
+        }
+    }
+}
+
+static void
+fdilate_1_8(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28));
+        }
+    }
+}
+
+static void
+ferode_1_8(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28));
+        }
+    }
+}
+
+static void
+fdilate_1_9(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27));
+        }
+    }
+}
+
+static void
+ferode_1_9(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27));
+        }
+    }
+}
+
+static void
+fdilate_1_10(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27));
+        }
+    }
+}
+
+static void
+ferode_1_10(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27));
+        }
+    }
+}
+
+static void
+fdilate_1_11(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26));
+        }
+    }
+}
+
+static void
+ferode_1_11(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26));
+        }
+    }
+}
+
+static void
+fdilate_1_12(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26));
+        }
+    }
+}
+
+static void
+ferode_1_12(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26));
+        }
+    }
+}
+
+static void
+fdilate_1_13(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25));
+        }
+    }
+}
+
+static void
+ferode_1_13(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25));
+        }
+    }
+}
+
+static void
+fdilate_1_14(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23));
+        }
+    }
+}
+
+static void
+ferode_1_14(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23));
+        }
+    }
+}
+
+static void
+fdilate_1_15(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22));
+        }
+    }
+}
+
+static void
+ferode_1_15(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22));
+        }
+    }
+}
+
+static void
+fdilate_1_16(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 12) | (*(sptr + 1) >> 20)) |
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) |
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) |
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20));
+        }
+    }
+}
+
+static void
+ferode_1_16(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 12) | (*(sptr - 1) << 20)) &
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) &
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) &
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20));
+        }
+    }
+}
+
+static void
+fdilate_1_17(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 15) | (*(sptr + 1) >> 17)) |
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) |
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) |
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) |
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) |
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) |
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) |
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) |
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18));
+        }
+    }
+}
+
+static void
+ferode_1_17(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 15) | (*(sptr - 1) << 17)) &
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) &
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) &
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) &
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) &
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) &
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) &
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) &
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18));
+        }
+    }
+}
+
+static void
+fdilate_1_18(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 15) | (*(sptr + 1) >> 17)) |
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) |
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) |
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) |
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) |
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) |
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) |
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) |
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) |
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17));
+        }
+    }
+}
+
+static void
+ferode_1_18(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 15) | (*(sptr - 1) << 17)) &
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) &
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) &
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) &
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) &
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) &
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) &
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) &
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) &
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17));
+        }
+    }
+}
+
+static void
+fdilate_1_19(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 17) | (*(sptr + 1) >> 15)) |
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) |
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) |
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) |
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) |
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) |
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) |
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) |
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) |
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) |
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) |
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) |
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) |
+                    ((*(sptr) >> 17) | (*(sptr - 1) << 15));
+        }
+    }
+}
+
+static void
+ferode_1_19(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 17) | (*(sptr - 1) << 15)) &
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) &
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) &
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) &
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) &
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) &
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) &
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) &
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) &
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) &
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) &
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) &
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) &
+                    ((*(sptr) << 17) | (*(sptr + 1) >> 15));
+        }
+    }
+}
+
+static void
+fdilate_1_20(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 20) | (*(sptr + 1) >> 12)) |
+                    ((*(sptr) << 19) | (*(sptr + 1) >> 13)) |
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14)) |
+                    ((*(sptr) << 17) | (*(sptr + 1) >> 15)) |
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) |
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) |
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) |
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) |
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) |
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) |
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) |
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) |
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) |
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) |
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) |
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) |
+                    ((*(sptr) >> 17) | (*(sptr - 1) << 15)) |
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14)) |
+                    ((*(sptr) >> 19) | (*(sptr - 1) << 13));
+        }
+    }
+}
+
+static void
+ferode_1_20(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 20) | (*(sptr - 1) << 12)) &
+                    ((*(sptr) >> 19) | (*(sptr - 1) << 13)) &
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14)) &
+                    ((*(sptr) >> 17) | (*(sptr - 1) << 15)) &
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) &
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) &
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) &
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) &
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) &
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) &
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) &
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) &
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) &
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) &
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) &
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) &
+                    ((*(sptr) << 17) | (*(sptr + 1) >> 15)) &
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14)) &
+                    ((*(sptr) << 19) | (*(sptr + 1) >> 13));
+        }
+    }
+}
+
+static void
+fdilate_1_21(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 20) | (*(sptr + 1) >> 12)) |
+                    ((*(sptr) << 19) | (*(sptr + 1) >> 13)) |
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14)) |
+                    ((*(sptr) << 17) | (*(sptr + 1) >> 15)) |
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) |
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) |
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) |
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) |
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) |
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) |
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) |
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) |
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) |
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) |
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) |
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) |
+                    ((*(sptr) >> 17) | (*(sptr - 1) << 15)) |
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14)) |
+                    ((*(sptr) >> 19) | (*(sptr - 1) << 13)) |
+                    ((*(sptr) >> 20) | (*(sptr - 1) << 12));
+        }
+    }
+}
+
+static void
+ferode_1_21(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 20) | (*(sptr - 1) << 12)) &
+                    ((*(sptr) >> 19) | (*(sptr - 1) << 13)) &
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14)) &
+                    ((*(sptr) >> 17) | (*(sptr - 1) << 15)) &
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) &
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) &
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) &
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) &
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) &
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) &
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) &
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) &
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) &
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) &
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) &
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) &
+                    ((*(sptr) << 17) | (*(sptr + 1) >> 15)) &
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14)) &
+                    ((*(sptr) << 19) | (*(sptr + 1) >> 13)) &
+                    ((*(sptr) << 20) | (*(sptr + 1) >> 12));
+        }
+    }
+}
+
+static void
+fdilate_1_22(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 22) | (*(sptr + 1) >> 10)) |
+                    ((*(sptr) << 21) | (*(sptr + 1) >> 11)) |
+                    ((*(sptr) << 20) | (*(sptr + 1) >> 12)) |
+                    ((*(sptr) << 19) | (*(sptr + 1) >> 13)) |
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14)) |
+                    ((*(sptr) << 17) | (*(sptr + 1) >> 15)) |
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) |
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) |
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) |
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) |
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) |
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) |
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) |
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) |
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) |
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) |
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) |
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) |
+                    ((*(sptr) >> 17) | (*(sptr - 1) << 15)) |
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14)) |
+                    ((*(sptr) >> 19) | (*(sptr - 1) << 13)) |
+                    ((*(sptr) >> 20) | (*(sptr - 1) << 12)) |
+                    ((*(sptr) >> 21) | (*(sptr - 1) << 11)) |
+                    ((*(sptr) >> 22) | (*(sptr - 1) << 10));
+        }
+    }
+}
+
+static void
+ferode_1_22(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 22) | (*(sptr - 1) << 10)) &
+                    ((*(sptr) >> 21) | (*(sptr - 1) << 11)) &
+                    ((*(sptr) >> 20) | (*(sptr - 1) << 12)) &
+                    ((*(sptr) >> 19) | (*(sptr - 1) << 13)) &
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14)) &
+                    ((*(sptr) >> 17) | (*(sptr - 1) << 15)) &
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) &
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) &
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) &
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) &
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) &
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) &
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) &
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) &
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) &
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) &
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) &
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) &
+                    ((*(sptr) << 17) | (*(sptr + 1) >> 15)) &
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14)) &
+                    ((*(sptr) << 19) | (*(sptr + 1) >> 13)) &
+                    ((*(sptr) << 20) | (*(sptr + 1) >> 12)) &
+                    ((*(sptr) << 21) | (*(sptr + 1) >> 11)) &
+                    ((*(sptr) << 22) | (*(sptr + 1) >> 10));
+        }
+    }
+}
+
+static void
+fdilate_1_23(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 25) | (*(sptr + 1) >> 7)) |
+                    ((*(sptr) << 24) | (*(sptr + 1) >> 8)) |
+                    ((*(sptr) << 23) | (*(sptr + 1) >> 9)) |
+                    ((*(sptr) << 22) | (*(sptr + 1) >> 10)) |
+                    ((*(sptr) << 21) | (*(sptr + 1) >> 11)) |
+                    ((*(sptr) << 20) | (*(sptr + 1) >> 12)) |
+                    ((*(sptr) << 19) | (*(sptr + 1) >> 13)) |
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14)) |
+                    ((*(sptr) << 17) | (*(sptr + 1) >> 15)) |
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) |
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) |
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) |
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) |
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) |
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) |
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) |
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) |
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) |
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) |
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) |
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) |
+                    ((*(sptr) >> 17) | (*(sptr - 1) << 15)) |
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14)) |
+                    ((*(sptr) >> 19) | (*(sptr - 1) << 13)) |
+                    ((*(sptr) >> 20) | (*(sptr - 1) << 12)) |
+                    ((*(sptr) >> 21) | (*(sptr - 1) << 11)) |
+                    ((*(sptr) >> 22) | (*(sptr - 1) << 10)) |
+                    ((*(sptr) >> 23) | (*(sptr - 1) << 9)) |
+                    ((*(sptr) >> 24) | (*(sptr - 1) << 8));
+        }
+    }
+}
+
+static void
+ferode_1_23(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 25) | (*(sptr - 1) << 7)) &
+                    ((*(sptr) >> 24) | (*(sptr - 1) << 8)) &
+                    ((*(sptr) >> 23) | (*(sptr - 1) << 9)) &
+                    ((*(sptr) >> 22) | (*(sptr - 1) << 10)) &
+                    ((*(sptr) >> 21) | (*(sptr - 1) << 11)) &
+                    ((*(sptr) >> 20) | (*(sptr - 1) << 12)) &
+                    ((*(sptr) >> 19) | (*(sptr - 1) << 13)) &
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14)) &
+                    ((*(sptr) >> 17) | (*(sptr - 1) << 15)) &
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) &
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) &
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) &
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) &
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) &
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) &
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) &
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) &
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) &
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) &
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) &
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) &
+                    ((*(sptr) << 17) | (*(sptr + 1) >> 15)) &
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14)) &
+                    ((*(sptr) << 19) | (*(sptr + 1) >> 13)) &
+                    ((*(sptr) << 20) | (*(sptr + 1) >> 12)) &
+                    ((*(sptr) << 21) | (*(sptr + 1) >> 11)) &
+                    ((*(sptr) << 22) | (*(sptr + 1) >> 10)) &
+                    ((*(sptr) << 23) | (*(sptr + 1) >> 9)) &
+                    ((*(sptr) << 24) | (*(sptr + 1) >> 8));
+        }
+    }
+}
+
+static void
+fdilate_1_24(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 25) | (*(sptr + 1) >> 7)) |
+                    ((*(sptr) << 24) | (*(sptr + 1) >> 8)) |
+                    ((*(sptr) << 23) | (*(sptr + 1) >> 9)) |
+                    ((*(sptr) << 22) | (*(sptr + 1) >> 10)) |
+                    ((*(sptr) << 21) | (*(sptr + 1) >> 11)) |
+                    ((*(sptr) << 20) | (*(sptr + 1) >> 12)) |
+                    ((*(sptr) << 19) | (*(sptr + 1) >> 13)) |
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14)) |
+                    ((*(sptr) << 17) | (*(sptr + 1) >> 15)) |
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) |
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) |
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) |
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) |
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) |
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) |
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) |
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) |
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) |
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) |
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) |
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) |
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) |
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) |
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) |
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) |
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) |
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) |
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) |
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) |
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) |
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) |
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) |
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) |
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) |
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) |
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) |
+                    ((*(sptr) >> 17) | (*(sptr - 1) << 15)) |
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14)) |
+                    ((*(sptr) >> 19) | (*(sptr - 1) << 13)) |
+                    ((*(sptr) >> 20) | (*(sptr - 1) << 12)) |
+                    ((*(sptr) >> 21) | (*(sptr - 1) << 11)) |
+                    ((*(sptr) >> 22) | (*(sptr - 1) << 10)) |
+                    ((*(sptr) >> 23) | (*(sptr - 1) << 9)) |
+                    ((*(sptr) >> 24) | (*(sptr - 1) << 8)) |
+                    ((*(sptr) >> 25) | (*(sptr - 1) << 7));
+        }
+    }
+}
+
+static void
+ferode_1_24(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 25) | (*(sptr - 1) << 7)) &
+                    ((*(sptr) >> 24) | (*(sptr - 1) << 8)) &
+                    ((*(sptr) >> 23) | (*(sptr - 1) << 9)) &
+                    ((*(sptr) >> 22) | (*(sptr - 1) << 10)) &
+                    ((*(sptr) >> 21) | (*(sptr - 1) << 11)) &
+                    ((*(sptr) >> 20) | (*(sptr - 1) << 12)) &
+                    ((*(sptr) >> 19) | (*(sptr - 1) << 13)) &
+                    ((*(sptr) >> 18) | (*(sptr - 1) << 14)) &
+                    ((*(sptr) >> 17) | (*(sptr - 1) << 15)) &
+                    ((*(sptr) >> 16) | (*(sptr - 1) << 16)) &
+                    ((*(sptr) >> 15) | (*(sptr - 1) << 17)) &
+                    ((*(sptr) >> 14) | (*(sptr - 1) << 18)) &
+                    ((*(sptr) >> 13) | (*(sptr - 1) << 19)) &
+                    ((*(sptr) >> 12) | (*(sptr - 1) << 20)) &
+                    ((*(sptr) >> 11) | (*(sptr - 1) << 21)) &
+                    ((*(sptr) >> 10) | (*(sptr - 1) << 22)) &
+                    ((*(sptr) >> 9) | (*(sptr - 1) << 23)) &
+                    ((*(sptr) >> 8) | (*(sptr - 1) << 24)) &
+                    ((*(sptr) >> 7) | (*(sptr - 1) << 25)) &
+                    ((*(sptr) >> 6) | (*(sptr - 1) << 26)) &
+                    ((*(sptr) >> 5) | (*(sptr - 1) << 27)) &
+                    ((*(sptr) >> 4) | (*(sptr - 1) << 28)) &
+                    ((*(sptr) >> 3) | (*(sptr - 1) << 29)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr) << 3) | (*(sptr + 1) >> 29)) &
+                    ((*(sptr) << 4) | (*(sptr + 1) >> 28)) &
+                    ((*(sptr) << 5) | (*(sptr + 1) >> 27)) &
+                    ((*(sptr) << 6) | (*(sptr + 1) >> 26)) &
+                    ((*(sptr) << 7) | (*(sptr + 1) >> 25)) &
+                    ((*(sptr) << 8) | (*(sptr + 1) >> 24)) &
+                    ((*(sptr) << 9) | (*(sptr + 1) >> 23)) &
+                    ((*(sptr) << 10) | (*(sptr + 1) >> 22)) &
+                    ((*(sptr) << 11) | (*(sptr + 1) >> 21)) &
+                    ((*(sptr) << 12) | (*(sptr + 1) >> 20)) &
+                    ((*(sptr) << 13) | (*(sptr + 1) >> 19)) &
+                    ((*(sptr) << 14) | (*(sptr + 1) >> 18)) &
+                    ((*(sptr) << 15) | (*(sptr + 1) >> 17)) &
+                    ((*(sptr) << 16) | (*(sptr + 1) >> 16)) &
+                    ((*(sptr) << 17) | (*(sptr + 1) >> 15)) &
+                    ((*(sptr) << 18) | (*(sptr + 1) >> 14)) &
+                    ((*(sptr) << 19) | (*(sptr + 1) >> 13)) &
+                    ((*(sptr) << 20) | (*(sptr + 1) >> 12)) &
+                    ((*(sptr) << 21) | (*(sptr + 1) >> 11)) &
+                    ((*(sptr) << 22) | (*(sptr + 1) >> 10)) &
+                    ((*(sptr) << 23) | (*(sptr + 1) >> 9)) &
+                    ((*(sptr) << 24) | (*(sptr + 1) >> 8)) &
+                    ((*(sptr) << 25) | (*(sptr + 1) >> 7));
+        }
+    }
+}
+
+static void
+fdilate_1_25(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls)) |
+                    (*sptr);
+        }
+    }
+}
+
+static void
+ferode_1_25(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls)) &
+                    (*sptr);
+        }
+    }
+}
+
+static void
+fdilate_1_26(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls));
+        }
+    }
+}
+
+static void
+ferode_1_26(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls));
+        }
+    }
+}
+
+static void
+fdilate_1_27(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls));
+        }
+    }
+}
+
+static void
+ferode_1_27(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls));
+        }
+    }
+}
+
+static void
+fdilate_1_28(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2));
+        }
+    }
+}
+
+static void
+ferode_1_28(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2));
+        }
+    }
+}
+
+static void
+fdilate_1_29(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2));
+        }
+    }
+}
+
+static void
+ferode_1_29(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2));
+        }
+    }
+}
+
+static void
+fdilate_1_30(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3));
+        }
+    }
+}
+
+static void
+ferode_1_30(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3));
+        }
+    }
+}
+
+static void
+fdilate_1_31(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3));
+        }
+    }
+}
+
+static void
+ferode_1_31(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3));
+        }
+    }
+}
+
+static void
+fdilate_1_32(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4));
+        }
+    }
+}
+
+static void
+ferode_1_32(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4));
+        }
+    }
+}
+
+static void
+fdilate_1_33(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4));
+        }
+    }
+}
+
+static void
+ferode_1_33(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4));
+        }
+    }
+}
+
+static void
+fdilate_1_34(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5));
+        }
+    }
+}
+
+static void
+ferode_1_34(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5));
+        }
+    }
+}
+
+static void
+fdilate_1_35(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5));
+        }
+    }
+}
+
+static void
+ferode_1_35(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5));
+        }
+    }
+}
+
+static void
+fdilate_1_36(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6));
+        }
+    }
+}
+
+static void
+ferode_1_36(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6));
+        }
+    }
+}
+
+static void
+fdilate_1_37(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6));
+        }
+    }
+}
+
+static void
+ferode_1_37(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6));
+        }
+    }
+}
+
+static void
+fdilate_1_38(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls7));
+        }
+    }
+}
+
+static void
+ferode_1_38(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls7));
+        }
+    }
+}
+
+static void
+fdilate_1_39(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls10)) |
+                    (*(sptr + wpls9)) |
+                    (*(sptr + wpls8)) |
+                    (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls9));
+        }
+    }
+}
+
+static void
+ferode_1_39(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls10)) &
+                    (*(sptr - wpls9)) &
+                    (*(sptr - wpls8)) &
+                    (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls9));
+        }
+    }
+}
+
+static void
+fdilate_1_40(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls10)) |
+                    (*(sptr + wpls9)) |
+                    (*(sptr + wpls8)) |
+                    (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls10));
+        }
+    }
+}
+
+static void
+ferode_1_40(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls10)) &
+                    (*(sptr - wpls9)) &
+                    (*(sptr - wpls8)) &
+                    (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls10));
+        }
+    }
+}
+
+static void
+fdilate_1_41(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls12)) |
+                    (*(sptr + wpls11)) |
+                    (*(sptr + wpls10)) |
+                    (*(sptr + wpls9)) |
+                    (*(sptr + wpls8)) |
+                    (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls10)) |
+                    (*(sptr - wpls11)) |
+                    (*(sptr - wpls12));
+        }
+    }
+}
+
+static void
+ferode_1_41(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls12)) &
+                    (*(sptr - wpls11)) &
+                    (*(sptr - wpls10)) &
+                    (*(sptr - wpls9)) &
+                    (*(sptr - wpls8)) &
+                    (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls10)) &
+                    (*(sptr + wpls11)) &
+                    (*(sptr + wpls12));
+        }
+    }
+}
+
+static void
+fdilate_1_42(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls15)) |
+                    (*(sptr + wpls14)) |
+                    (*(sptr + wpls13)) |
+                    (*(sptr + wpls12)) |
+                    (*(sptr + wpls11)) |
+                    (*(sptr + wpls10)) |
+                    (*(sptr + wpls9)) |
+                    (*(sptr + wpls8)) |
+                    (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls10)) |
+                    (*(sptr - wpls11)) |
+                    (*(sptr - wpls12)) |
+                    (*(sptr - wpls13)) |
+                    (*(sptr - wpls14));
+        }
+    }
+}
+
+static void
+ferode_1_42(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls15)) &
+                    (*(sptr - wpls14)) &
+                    (*(sptr - wpls13)) &
+                    (*(sptr - wpls12)) &
+                    (*(sptr - wpls11)) &
+                    (*(sptr - wpls10)) &
+                    (*(sptr - wpls9)) &
+                    (*(sptr - wpls8)) &
+                    (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls10)) &
+                    (*(sptr + wpls11)) &
+                    (*(sptr + wpls12)) &
+                    (*(sptr + wpls13)) &
+                    (*(sptr + wpls14));
+        }
+    }
+}
+
+static void
+fdilate_1_43(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls15)) |
+                    (*(sptr + wpls14)) |
+                    (*(sptr + wpls13)) |
+                    (*(sptr + wpls12)) |
+                    (*(sptr + wpls11)) |
+                    (*(sptr + wpls10)) |
+                    (*(sptr + wpls9)) |
+                    (*(sptr + wpls8)) |
+                    (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls10)) |
+                    (*(sptr - wpls11)) |
+                    (*(sptr - wpls12)) |
+                    (*(sptr - wpls13)) |
+                    (*(sptr - wpls14)) |
+                    (*(sptr - wpls15));
+        }
+    }
+}
+
+static void
+ferode_1_43(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls15)) &
+                    (*(sptr - wpls14)) &
+                    (*(sptr - wpls13)) &
+                    (*(sptr - wpls12)) &
+                    (*(sptr - wpls11)) &
+                    (*(sptr - wpls10)) &
+                    (*(sptr - wpls9)) &
+                    (*(sptr - wpls8)) &
+                    (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls10)) &
+                    (*(sptr + wpls11)) &
+                    (*(sptr + wpls12)) &
+                    (*(sptr + wpls13)) &
+                    (*(sptr + wpls14)) &
+                    (*(sptr + wpls15));
+        }
+    }
+}
+
+static void
+fdilate_1_44(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15, wpls16;
+l_int32             wpls17;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls17)) |
+                    (*(sptr + wpls16)) |
+                    (*(sptr + wpls15)) |
+                    (*(sptr + wpls14)) |
+                    (*(sptr + wpls13)) |
+                    (*(sptr + wpls12)) |
+                    (*(sptr + wpls11)) |
+                    (*(sptr + wpls10)) |
+                    (*(sptr + wpls9)) |
+                    (*(sptr + wpls8)) |
+                    (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls10)) |
+                    (*(sptr - wpls11)) |
+                    (*(sptr - wpls12)) |
+                    (*(sptr - wpls13)) |
+                    (*(sptr - wpls14)) |
+                    (*(sptr - wpls15)) |
+                    (*(sptr - wpls16)) |
+                    (*(sptr - wpls17));
+        }
+    }
+}
+
+static void
+ferode_1_44(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15, wpls16;
+l_int32             wpls17;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls17)) &
+                    (*(sptr - wpls16)) &
+                    (*(sptr - wpls15)) &
+                    (*(sptr - wpls14)) &
+                    (*(sptr - wpls13)) &
+                    (*(sptr - wpls12)) &
+                    (*(sptr - wpls11)) &
+                    (*(sptr - wpls10)) &
+                    (*(sptr - wpls9)) &
+                    (*(sptr - wpls8)) &
+                    (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls10)) &
+                    (*(sptr + wpls11)) &
+                    (*(sptr + wpls12)) &
+                    (*(sptr + wpls13)) &
+                    (*(sptr + wpls14)) &
+                    (*(sptr + wpls15)) &
+                    (*(sptr + wpls16)) &
+                    (*(sptr + wpls17));
+        }
+    }
+}
+
+static void
+fdilate_1_45(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15, wpls16;
+l_int32             wpls17, wpls18, wpls19, wpls20;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    wpls18 = 18 * wpls;
+    wpls19 = 19 * wpls;
+    wpls20 = 20 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls20)) |
+                    (*(sptr + wpls19)) |
+                    (*(sptr + wpls18)) |
+                    (*(sptr + wpls17)) |
+                    (*(sptr + wpls16)) |
+                    (*(sptr + wpls15)) |
+                    (*(sptr + wpls14)) |
+                    (*(sptr + wpls13)) |
+                    (*(sptr + wpls12)) |
+                    (*(sptr + wpls11)) |
+                    (*(sptr + wpls10)) |
+                    (*(sptr + wpls9)) |
+                    (*(sptr + wpls8)) |
+                    (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls10)) |
+                    (*(sptr - wpls11)) |
+                    (*(sptr - wpls12)) |
+                    (*(sptr - wpls13)) |
+                    (*(sptr - wpls14)) |
+                    (*(sptr - wpls15)) |
+                    (*(sptr - wpls16)) |
+                    (*(sptr - wpls17)) |
+                    (*(sptr - wpls18)) |
+                    (*(sptr - wpls19));
+        }
+    }
+}
+
+static void
+ferode_1_45(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15, wpls16;
+l_int32             wpls17, wpls18, wpls19, wpls20;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    wpls18 = 18 * wpls;
+    wpls19 = 19 * wpls;
+    wpls20 = 20 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls20)) &
+                    (*(sptr - wpls19)) &
+                    (*(sptr - wpls18)) &
+                    (*(sptr - wpls17)) &
+                    (*(sptr - wpls16)) &
+                    (*(sptr - wpls15)) &
+                    (*(sptr - wpls14)) &
+                    (*(sptr - wpls13)) &
+                    (*(sptr - wpls12)) &
+                    (*(sptr - wpls11)) &
+                    (*(sptr - wpls10)) &
+                    (*(sptr - wpls9)) &
+                    (*(sptr - wpls8)) &
+                    (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls10)) &
+                    (*(sptr + wpls11)) &
+                    (*(sptr + wpls12)) &
+                    (*(sptr + wpls13)) &
+                    (*(sptr + wpls14)) &
+                    (*(sptr + wpls15)) &
+                    (*(sptr + wpls16)) &
+                    (*(sptr + wpls17)) &
+                    (*(sptr + wpls18)) &
+                    (*(sptr + wpls19));
+        }
+    }
+}
+
+static void
+fdilate_1_46(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15, wpls16;
+l_int32             wpls17, wpls18, wpls19, wpls20;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    wpls18 = 18 * wpls;
+    wpls19 = 19 * wpls;
+    wpls20 = 20 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls20)) |
+                    (*(sptr + wpls19)) |
+                    (*(sptr + wpls18)) |
+                    (*(sptr + wpls17)) |
+                    (*(sptr + wpls16)) |
+                    (*(sptr + wpls15)) |
+                    (*(sptr + wpls14)) |
+                    (*(sptr + wpls13)) |
+                    (*(sptr + wpls12)) |
+                    (*(sptr + wpls11)) |
+                    (*(sptr + wpls10)) |
+                    (*(sptr + wpls9)) |
+                    (*(sptr + wpls8)) |
+                    (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls10)) |
+                    (*(sptr - wpls11)) |
+                    (*(sptr - wpls12)) |
+                    (*(sptr - wpls13)) |
+                    (*(sptr - wpls14)) |
+                    (*(sptr - wpls15)) |
+                    (*(sptr - wpls16)) |
+                    (*(sptr - wpls17)) |
+                    (*(sptr - wpls18)) |
+                    (*(sptr - wpls19)) |
+                    (*(sptr - wpls20));
+        }
+    }
+}
+
+static void
+ferode_1_46(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15, wpls16;
+l_int32             wpls17, wpls18, wpls19, wpls20;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    wpls18 = 18 * wpls;
+    wpls19 = 19 * wpls;
+    wpls20 = 20 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls20)) &
+                    (*(sptr - wpls19)) &
+                    (*(sptr - wpls18)) &
+                    (*(sptr - wpls17)) &
+                    (*(sptr - wpls16)) &
+                    (*(sptr - wpls15)) &
+                    (*(sptr - wpls14)) &
+                    (*(sptr - wpls13)) &
+                    (*(sptr - wpls12)) &
+                    (*(sptr - wpls11)) &
+                    (*(sptr - wpls10)) &
+                    (*(sptr - wpls9)) &
+                    (*(sptr - wpls8)) &
+                    (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls10)) &
+                    (*(sptr + wpls11)) &
+                    (*(sptr + wpls12)) &
+                    (*(sptr + wpls13)) &
+                    (*(sptr + wpls14)) &
+                    (*(sptr + wpls15)) &
+                    (*(sptr + wpls16)) &
+                    (*(sptr + wpls17)) &
+                    (*(sptr + wpls18)) &
+                    (*(sptr + wpls19)) &
+                    (*(sptr + wpls20));
+        }
+    }
+}
+
+static void
+fdilate_1_47(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15, wpls16;
+l_int32             wpls17, wpls18, wpls19, wpls20;
+l_int32             wpls21, wpls22;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    wpls18 = 18 * wpls;
+    wpls19 = 19 * wpls;
+    wpls20 = 20 * wpls;
+    wpls21 = 21 * wpls;
+    wpls22 = 22 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls22)) |
+                    (*(sptr + wpls21)) |
+                    (*(sptr + wpls20)) |
+                    (*(sptr + wpls19)) |
+                    (*(sptr + wpls18)) |
+                    (*(sptr + wpls17)) |
+                    (*(sptr + wpls16)) |
+                    (*(sptr + wpls15)) |
+                    (*(sptr + wpls14)) |
+                    (*(sptr + wpls13)) |
+                    (*(sptr + wpls12)) |
+                    (*(sptr + wpls11)) |
+                    (*(sptr + wpls10)) |
+                    (*(sptr + wpls9)) |
+                    (*(sptr + wpls8)) |
+                    (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls10)) |
+                    (*(sptr - wpls11)) |
+                    (*(sptr - wpls12)) |
+                    (*(sptr - wpls13)) |
+                    (*(sptr - wpls14)) |
+                    (*(sptr - wpls15)) |
+                    (*(sptr - wpls16)) |
+                    (*(sptr - wpls17)) |
+                    (*(sptr - wpls18)) |
+                    (*(sptr - wpls19)) |
+                    (*(sptr - wpls20)) |
+                    (*(sptr - wpls21)) |
+                    (*(sptr - wpls22));
+        }
+    }
+}
+
+static void
+ferode_1_47(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15, wpls16;
+l_int32             wpls17, wpls18, wpls19, wpls20;
+l_int32             wpls21, wpls22;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    wpls18 = 18 * wpls;
+    wpls19 = 19 * wpls;
+    wpls20 = 20 * wpls;
+    wpls21 = 21 * wpls;
+    wpls22 = 22 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls22)) &
+                    (*(sptr - wpls21)) &
+                    (*(sptr - wpls20)) &
+                    (*(sptr - wpls19)) &
+                    (*(sptr - wpls18)) &
+                    (*(sptr - wpls17)) &
+                    (*(sptr - wpls16)) &
+                    (*(sptr - wpls15)) &
+                    (*(sptr - wpls14)) &
+                    (*(sptr - wpls13)) &
+                    (*(sptr - wpls12)) &
+                    (*(sptr - wpls11)) &
+                    (*(sptr - wpls10)) &
+                    (*(sptr - wpls9)) &
+                    (*(sptr - wpls8)) &
+                    (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls10)) &
+                    (*(sptr + wpls11)) &
+                    (*(sptr + wpls12)) &
+                    (*(sptr + wpls13)) &
+                    (*(sptr + wpls14)) &
+                    (*(sptr + wpls15)) &
+                    (*(sptr + wpls16)) &
+                    (*(sptr + wpls17)) &
+                    (*(sptr + wpls18)) &
+                    (*(sptr + wpls19)) &
+                    (*(sptr + wpls20)) &
+                    (*(sptr + wpls21)) &
+                    (*(sptr + wpls22));
+        }
+    }
+}
+
+static void
+fdilate_1_48(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15, wpls16;
+l_int32             wpls17, wpls18, wpls19, wpls20;
+l_int32             wpls21, wpls22, wpls23, wpls24;
+l_int32             wpls25;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    wpls18 = 18 * wpls;
+    wpls19 = 19 * wpls;
+    wpls20 = 20 * wpls;
+    wpls21 = 21 * wpls;
+    wpls22 = 22 * wpls;
+    wpls23 = 23 * wpls;
+    wpls24 = 24 * wpls;
+    wpls25 = 25 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls25)) |
+                    (*(sptr + wpls24)) |
+                    (*(sptr + wpls23)) |
+                    (*(sptr + wpls22)) |
+                    (*(sptr + wpls21)) |
+                    (*(sptr + wpls20)) |
+                    (*(sptr + wpls19)) |
+                    (*(sptr + wpls18)) |
+                    (*(sptr + wpls17)) |
+                    (*(sptr + wpls16)) |
+                    (*(sptr + wpls15)) |
+                    (*(sptr + wpls14)) |
+                    (*(sptr + wpls13)) |
+                    (*(sptr + wpls12)) |
+                    (*(sptr + wpls11)) |
+                    (*(sptr + wpls10)) |
+                    (*(sptr + wpls9)) |
+                    (*(sptr + wpls8)) |
+                    (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls10)) |
+                    (*(sptr - wpls11)) |
+                    (*(sptr - wpls12)) |
+                    (*(sptr - wpls13)) |
+                    (*(sptr - wpls14)) |
+                    (*(sptr - wpls15)) |
+                    (*(sptr - wpls16)) |
+                    (*(sptr - wpls17)) |
+                    (*(sptr - wpls18)) |
+                    (*(sptr - wpls19)) |
+                    (*(sptr - wpls20)) |
+                    (*(sptr - wpls21)) |
+                    (*(sptr - wpls22)) |
+                    (*(sptr - wpls23)) |
+                    (*(sptr - wpls24));
+        }
+    }
+}
+
+static void
+ferode_1_48(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15, wpls16;
+l_int32             wpls17, wpls18, wpls19, wpls20;
+l_int32             wpls21, wpls22, wpls23, wpls24;
+l_int32             wpls25;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    wpls18 = 18 * wpls;
+    wpls19 = 19 * wpls;
+    wpls20 = 20 * wpls;
+    wpls21 = 21 * wpls;
+    wpls22 = 22 * wpls;
+    wpls23 = 23 * wpls;
+    wpls24 = 24 * wpls;
+    wpls25 = 25 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls25)) &
+                    (*(sptr - wpls24)) &
+                    (*(sptr - wpls23)) &
+                    (*(sptr - wpls22)) &
+                    (*(sptr - wpls21)) &
+                    (*(sptr - wpls20)) &
+                    (*(sptr - wpls19)) &
+                    (*(sptr - wpls18)) &
+                    (*(sptr - wpls17)) &
+                    (*(sptr - wpls16)) &
+                    (*(sptr - wpls15)) &
+                    (*(sptr - wpls14)) &
+                    (*(sptr - wpls13)) &
+                    (*(sptr - wpls12)) &
+                    (*(sptr - wpls11)) &
+                    (*(sptr - wpls10)) &
+                    (*(sptr - wpls9)) &
+                    (*(sptr - wpls8)) &
+                    (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls10)) &
+                    (*(sptr + wpls11)) &
+                    (*(sptr + wpls12)) &
+                    (*(sptr + wpls13)) &
+                    (*(sptr + wpls14)) &
+                    (*(sptr + wpls15)) &
+                    (*(sptr + wpls16)) &
+                    (*(sptr + wpls17)) &
+                    (*(sptr + wpls18)) &
+                    (*(sptr + wpls19)) &
+                    (*(sptr + wpls20)) &
+                    (*(sptr + wpls21)) &
+                    (*(sptr + wpls22)) &
+                    (*(sptr + wpls23)) &
+                    (*(sptr + wpls24));
+        }
+    }
+}
+
+static void
+fdilate_1_49(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15, wpls16;
+l_int32             wpls17, wpls18, wpls19, wpls20;
+l_int32             wpls21, wpls22, wpls23, wpls24;
+l_int32             wpls25;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    wpls18 = 18 * wpls;
+    wpls19 = 19 * wpls;
+    wpls20 = 20 * wpls;
+    wpls21 = 21 * wpls;
+    wpls22 = 22 * wpls;
+    wpls23 = 23 * wpls;
+    wpls24 = 24 * wpls;
+    wpls25 = 25 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr + wpls25)) |
+                    (*(sptr + wpls24)) |
+                    (*(sptr + wpls23)) |
+                    (*(sptr + wpls22)) |
+                    (*(sptr + wpls21)) |
+                    (*(sptr + wpls20)) |
+                    (*(sptr + wpls19)) |
+                    (*(sptr + wpls18)) |
+                    (*(sptr + wpls17)) |
+                    (*(sptr + wpls16)) |
+                    (*(sptr + wpls15)) |
+                    (*(sptr + wpls14)) |
+                    (*(sptr + wpls13)) |
+                    (*(sptr + wpls12)) |
+                    (*(sptr + wpls11)) |
+                    (*(sptr + wpls10)) |
+                    (*(sptr + wpls9)) |
+                    (*(sptr + wpls8)) |
+                    (*(sptr + wpls7)) |
+                    (*(sptr + wpls6)) |
+                    (*(sptr + wpls5)) |
+                    (*(sptr + wpls4)) |
+                    (*(sptr + wpls3)) |
+                    (*(sptr + wpls2)) |
+                    (*(sptr + wpls)) |
+                    (*sptr) |
+                    (*(sptr - wpls)) |
+                    (*(sptr - wpls2)) |
+                    (*(sptr - wpls3)) |
+                    (*(sptr - wpls4)) |
+                    (*(sptr - wpls5)) |
+                    (*(sptr - wpls6)) |
+                    (*(sptr - wpls7)) |
+                    (*(sptr - wpls8)) |
+                    (*(sptr - wpls9)) |
+                    (*(sptr - wpls10)) |
+                    (*(sptr - wpls11)) |
+                    (*(sptr - wpls12)) |
+                    (*(sptr - wpls13)) |
+                    (*(sptr - wpls14)) |
+                    (*(sptr - wpls15)) |
+                    (*(sptr - wpls16)) |
+                    (*(sptr - wpls17)) |
+                    (*(sptr - wpls18)) |
+                    (*(sptr - wpls19)) |
+                    (*(sptr - wpls20)) |
+                    (*(sptr - wpls21)) |
+                    (*(sptr - wpls22)) |
+                    (*(sptr - wpls23)) |
+                    (*(sptr - wpls24)) |
+                    (*(sptr - wpls25));
+        }
+    }
+}
+
+static void
+ferode_1_49(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2, wpls3, wpls4;
+l_int32             wpls5, wpls6, wpls7, wpls8;
+l_int32             wpls9, wpls10, wpls11, wpls12;
+l_int32             wpls13, wpls14, wpls15, wpls16;
+l_int32             wpls17, wpls18, wpls19, wpls20;
+l_int32             wpls21, wpls22, wpls23, wpls24;
+l_int32             wpls25;
+
+    wpls2 = 2 * wpls;
+    wpls3 = 3 * wpls;
+    wpls4 = 4 * wpls;
+    wpls5 = 5 * wpls;
+    wpls6 = 6 * wpls;
+    wpls7 = 7 * wpls;
+    wpls8 = 8 * wpls;
+    wpls9 = 9 * wpls;
+    wpls10 = 10 * wpls;
+    wpls11 = 11 * wpls;
+    wpls12 = 12 * wpls;
+    wpls13 = 13 * wpls;
+    wpls14 = 14 * wpls;
+    wpls15 = 15 * wpls;
+    wpls16 = 16 * wpls;
+    wpls17 = 17 * wpls;
+    wpls18 = 18 * wpls;
+    wpls19 = 19 * wpls;
+    wpls20 = 20 * wpls;
+    wpls21 = 21 * wpls;
+    wpls22 = 22 * wpls;
+    wpls23 = 23 * wpls;
+    wpls24 = 24 * wpls;
+    wpls25 = 25 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*(sptr - wpls25)) &
+                    (*(sptr - wpls24)) &
+                    (*(sptr - wpls23)) &
+                    (*(sptr - wpls22)) &
+                    (*(sptr - wpls21)) &
+                    (*(sptr - wpls20)) &
+                    (*(sptr - wpls19)) &
+                    (*(sptr - wpls18)) &
+                    (*(sptr - wpls17)) &
+                    (*(sptr - wpls16)) &
+                    (*(sptr - wpls15)) &
+                    (*(sptr - wpls14)) &
+                    (*(sptr - wpls13)) &
+                    (*(sptr - wpls12)) &
+                    (*(sptr - wpls11)) &
+                    (*(sptr - wpls10)) &
+                    (*(sptr - wpls9)) &
+                    (*(sptr - wpls8)) &
+                    (*(sptr - wpls7)) &
+                    (*(sptr - wpls6)) &
+                    (*(sptr - wpls5)) &
+                    (*(sptr - wpls4)) &
+                    (*(sptr - wpls3)) &
+                    (*(sptr - wpls2)) &
+                    (*(sptr - wpls)) &
+                    (*sptr) &
+                    (*(sptr + wpls)) &
+                    (*(sptr + wpls2)) &
+                    (*(sptr + wpls3)) &
+                    (*(sptr + wpls4)) &
+                    (*(sptr + wpls5)) &
+                    (*(sptr + wpls6)) &
+                    (*(sptr + wpls7)) &
+                    (*(sptr + wpls8)) &
+                    (*(sptr + wpls9)) &
+                    (*(sptr + wpls10)) &
+                    (*(sptr + wpls11)) &
+                    (*(sptr + wpls12)) &
+                    (*(sptr + wpls13)) &
+                    (*(sptr + wpls14)) &
+                    (*(sptr + wpls15)) &
+                    (*(sptr + wpls16)) &
+                    (*(sptr + wpls17)) &
+                    (*(sptr + wpls18)) &
+                    (*(sptr + wpls19)) &
+                    (*(sptr + wpls20)) &
+                    (*(sptr + wpls21)) &
+                    (*(sptr + wpls22)) &
+                    (*(sptr + wpls23)) &
+                    (*(sptr + wpls24)) &
+                    (*(sptr + wpls25));
+        }
+    }
+}
+
+static void
+fdilate_1_50(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr + wpls) << 1) | (*(sptr + wpls + 1) >> 31)) |
+                    (*(sptr + wpls)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr);
+        }
+    }
+}
+
+static void
+ferode_1_50(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr - wpls) >> 1) | (*(sptr - wpls - 1) << 31)) &
+                    (*(sptr - wpls)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr);
+        }
+    }
+}
+
+static void
+fdilate_1_51(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr + wpls) << 1) | (*(sptr + wpls + 1) >> 31)) |
+                    (*(sptr + wpls)) |
+                    ((*(sptr + wpls) >> 1) | (*(sptr + wpls - 1) << 31)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr - wpls) << 1) | (*(sptr - wpls + 1) >> 31)) |
+                    (*(sptr - wpls)) |
+                    ((*(sptr - wpls) >> 1) | (*(sptr - wpls - 1) << 31));
+        }
+    }
+}
+
+static void
+ferode_1_51(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr - wpls) >> 1) | (*(sptr - wpls - 1) << 31)) &
+                    (*(sptr - wpls)) &
+                    ((*(sptr - wpls) << 1) | (*(sptr - wpls + 1) >> 31)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr + wpls) >> 1) | (*(sptr + wpls - 1) << 31)) &
+                    (*(sptr + wpls)) &
+                    ((*(sptr + wpls) << 1) | (*(sptr + wpls + 1) >> 31));
+        }
+    }
+}
+
+static void
+fdilate_1_52(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr + wpls2) << 2) | (*(sptr + wpls2 + 1) >> 30)) |
+                    ((*(sptr + wpls2) << 1) | (*(sptr + wpls2 + 1) >> 31)) |
+                    (*(sptr + wpls2)) |
+                    ((*(sptr + wpls2) >> 1) | (*(sptr + wpls2 - 1) << 31)) |
+                    ((*(sptr + wpls) << 2) | (*(sptr + wpls + 1) >> 30)) |
+                    ((*(sptr + wpls) << 1) | (*(sptr + wpls + 1) >> 31)) |
+                    (*(sptr + wpls)) |
+                    ((*(sptr + wpls) >> 1) | (*(sptr + wpls - 1) << 31)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr - wpls) << 2) | (*(sptr - wpls + 1) >> 30)) |
+                    ((*(sptr - wpls) << 1) | (*(sptr - wpls + 1) >> 31)) |
+                    (*(sptr - wpls)) |
+                    ((*(sptr - wpls) >> 1) | (*(sptr - wpls - 1) << 31));
+        }
+    }
+}
+
+static void
+ferode_1_52(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr - wpls2) >> 2) | (*(sptr - wpls2 - 1) << 30)) &
+                    ((*(sptr - wpls2) >> 1) | (*(sptr - wpls2 - 1) << 31)) &
+                    (*(sptr - wpls2)) &
+                    ((*(sptr - wpls2) << 1) | (*(sptr - wpls2 + 1) >> 31)) &
+                    ((*(sptr - wpls) >> 2) | (*(sptr - wpls - 1) << 30)) &
+                    ((*(sptr - wpls) >> 1) | (*(sptr - wpls - 1) << 31)) &
+                    (*(sptr - wpls)) &
+                    ((*(sptr - wpls) << 1) | (*(sptr - wpls + 1) >> 31)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr + wpls) >> 2) | (*(sptr + wpls - 1) << 30)) &
+                    ((*(sptr + wpls) >> 1) | (*(sptr + wpls - 1) << 31)) &
+                    (*(sptr + wpls)) &
+                    ((*(sptr + wpls) << 1) | (*(sptr + wpls + 1) >> 31));
+        }
+    }
+}
+
+static void
+fdilate_1_53(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr + wpls2) << 2) | (*(sptr + wpls2 + 1) >> 30)) |
+                    ((*(sptr + wpls2) << 1) | (*(sptr + wpls2 + 1) >> 31)) |
+                    (*(sptr + wpls2)) |
+                    ((*(sptr + wpls2) >> 1) | (*(sptr + wpls2 - 1) << 31)) |
+                    ((*(sptr + wpls2) >> 2) | (*(sptr + wpls2 - 1) << 30)) |
+                    ((*(sptr + wpls) << 2) | (*(sptr + wpls + 1) >> 30)) |
+                    ((*(sptr + wpls) << 1) | (*(sptr + wpls + 1) >> 31)) |
+                    (*(sptr + wpls)) |
+                    ((*(sptr + wpls) >> 1) | (*(sptr + wpls - 1) << 31)) |
+                    ((*(sptr + wpls) >> 2) | (*(sptr + wpls - 1) << 30)) |
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) |
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) |
+                    ((*(sptr - wpls) << 2) | (*(sptr - wpls + 1) >> 30)) |
+                    ((*(sptr - wpls) << 1) | (*(sptr - wpls + 1) >> 31)) |
+                    (*(sptr - wpls)) |
+                    ((*(sptr - wpls) >> 1) | (*(sptr - wpls - 1) << 31)) |
+                    ((*(sptr - wpls) >> 2) | (*(sptr - wpls - 1) << 30)) |
+                    ((*(sptr - wpls2) << 2) | (*(sptr - wpls2 + 1) >> 30)) |
+                    ((*(sptr - wpls2) << 1) | (*(sptr - wpls2 + 1) >> 31)) |
+                    (*(sptr - wpls2)) |
+                    ((*(sptr - wpls2) >> 1) | (*(sptr - wpls2 - 1) << 31)) |
+                    ((*(sptr - wpls2) >> 2) | (*(sptr - wpls2 - 1) << 30));
+        }
+    }
+}
+
+static void
+ferode_1_53(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr - wpls2) >> 2) | (*(sptr - wpls2 - 1) << 30)) &
+                    ((*(sptr - wpls2) >> 1) | (*(sptr - wpls2 - 1) << 31)) &
+                    (*(sptr - wpls2)) &
+                    ((*(sptr - wpls2) << 1) | (*(sptr - wpls2 + 1) >> 31)) &
+                    ((*(sptr - wpls2) << 2) | (*(sptr - wpls2 + 1) >> 30)) &
+                    ((*(sptr - wpls) >> 2) | (*(sptr - wpls - 1) << 30)) &
+                    ((*(sptr - wpls) >> 1) | (*(sptr - wpls - 1) << 31)) &
+                    (*(sptr - wpls)) &
+                    ((*(sptr - wpls) << 1) | (*(sptr - wpls + 1) >> 31)) &
+                    ((*(sptr - wpls) << 2) | (*(sptr - wpls + 1) >> 30)) &
+                    ((*(sptr) >> 2) | (*(sptr - 1) << 30)) &
+                    ((*(sptr) >> 1) | (*(sptr - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    ((*(sptr) << 2) | (*(sptr + 1) >> 30)) &
+                    ((*(sptr + wpls) >> 2) | (*(sptr + wpls - 1) << 30)) &
+                    ((*(sptr + wpls) >> 1) | (*(sptr + wpls - 1) << 31)) &
+                    (*(sptr + wpls)) &
+                    ((*(sptr + wpls) << 1) | (*(sptr + wpls + 1) >> 31)) &
+                    ((*(sptr + wpls) << 2) | (*(sptr + wpls + 1) >> 30)) &
+                    ((*(sptr + wpls2) >> 2) | (*(sptr + wpls2 - 1) << 30)) &
+                    ((*(sptr + wpls2) >> 1) | (*(sptr + wpls2 - 1) << 31)) &
+                    (*(sptr + wpls2)) &
+                    ((*(sptr + wpls2) << 1) | (*(sptr + wpls2 + 1) >> 31)) &
+                    ((*(sptr + wpls2) << 2) | (*(sptr + wpls2 + 1) >> 30));
+        }
+    }
+}
+
+static void
+fdilate_1_54(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) >> 1) | (*(sptr - 1) << 31)) |
+                    (*(sptr - wpls));
+        }
+    }
+}
+
+static void
+ferode_1_54(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr) << 1) | (*(sptr + 1) >> 31)) &
+                    (*(sptr + wpls));
+        }
+    }
+}
+
+static void
+fdilate_1_55(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*sptr) |
+                    ((*(sptr - wpls) >> 1) | (*(sptr - wpls - 1) << 31));
+        }
+    }
+}
+
+static void
+ferode_1_55(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = (*sptr) &
+                    ((*(sptr + wpls) << 1) | (*(sptr + wpls + 1) >> 31));
+        }
+    }
+}
+
+static void
+fdilate_1_56(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr + wpls2) >> 2) | (*(sptr + wpls2 - 1) << 30)) |
+                    ((*(sptr + wpls) >> 1) | (*(sptr + wpls - 1) << 31)) |
+                    (*sptr) |
+                    ((*(sptr - wpls) << 1) | (*(sptr - wpls + 1) >> 31)) |
+                    ((*(sptr - wpls2) << 2) | (*(sptr - wpls2 + 1) >> 30));
+        }
+    }
+}
+
+static void
+ferode_1_56(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr - wpls2) << 2) | (*(sptr - wpls2 + 1) >> 30)) &
+                    ((*(sptr - wpls) << 1) | (*(sptr - wpls + 1) >> 31)) &
+                    (*sptr) &
+                    ((*(sptr + wpls) >> 1) | (*(sptr + wpls - 1) << 31)) &
+                    ((*(sptr + wpls2) >> 2) | (*(sptr + wpls2 - 1) << 30));
+        }
+    }
+}
+
+static void
+fdilate_1_57(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr + wpls2) << 2) | (*(sptr + wpls2 + 1) >> 30)) |
+                    ((*(sptr + wpls) << 1) | (*(sptr + wpls + 1) >> 31)) |
+                    (*sptr) |
+                    ((*(sptr - wpls) >> 1) | (*(sptr - wpls - 1) << 31)) |
+                    ((*(sptr - wpls2) >> 2) | (*(sptr - wpls2 - 1) << 30));
+        }
+    }
+}
+
+static void
+ferode_1_57(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+l_int32             wpls2;
+
+    wpls2 = 2 * wpls;
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+            *dptr = ((*(sptr - wpls2) >> 2) | (*(sptr - wpls2 - 1) << 30)) &
+                    ((*(sptr - wpls) >> 1) | (*(sptr - wpls - 1) << 31)) &
+                    (*sptr) &
+                    ((*(sptr + wpls) << 1) | (*(sptr + wpls + 1) >> 31)) &
+                    ((*(sptr + wpls2) << 2) | (*(sptr + wpls2 + 1) >> 30));
+        }
+    }
+}
+
diff --git a/src/fpix1.c b/src/fpix1.c
new file mode 100644 (file)
index 0000000..251d3c4
--- /dev/null
@@ -0,0 +1,1991 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  fpix1.c
+ *
+ *    This file has basic constructors, destructors and field accessors
+ *    for FPix, FPixa and DPix.  It also has uncompressed read/write.
+ *
+ *    FPix Create/copy/destroy
+ *          FPIX          *fpixCreate()
+ *          FPIX          *fpixCreateTemplate()
+ *          FPIX          *fpixClone()
+ *          FPIX          *fpixCopy()
+ *          l_int32        fpixResizeImageData()
+ *          void           fpixDestroy()
+ *
+ *    FPix accessors
+ *          l_int32        fpixGetDimensions()
+ *          l_int32        fpixSetDimensions()
+ *          l_int32        fpixGetWpl()
+ *          l_int32        fpixSetWpl()
+ *          l_int32        fpixGetRefcount()
+ *          l_int32        fpixChangeRefcount()
+ *          l_int32        fpixGetResolution()
+ *          l_int32        fpixSetResolution()
+ *          l_int32        fpixCopyResolution()
+ *          l_float32     *fpixGetData()
+ *          l_int32        fpixSetData()
+ *          l_int32        fpixGetPixel()
+ *          l_int32        fpixSetPixel()
+ *
+ *    FPixa Create/copy/destroy
+ *          FPIXA         *fpixaCreate()
+ *          FPIXA         *fpixaCopy()
+ *          void           fpixaDestroy()
+ *
+ *    FPixa addition
+ *          l_int32        fpixaAddFPix()
+ *          static l_int32 fpixaExtendArray()
+ *          static l_int32 fpixaExtendArrayToSize()
+ *
+ *    FPixa accessors
+ *          l_int32        fpixaGetCount()
+ *          l_int32        fpixaChangeRefcount()
+ *          FPIX          *fpixaGetFPix()
+ *          l_int32        fpixaGetFPixDimensions()
+ *          l_float32     *fpixaGetData()
+ *          l_int32        fpixaGetPixel()
+ *          l_int32        fpixaSetPixel()
+ *
+ *    DPix Create/copy/destroy
+ *          DPIX          *dpixCreate()
+ *          DPIX          *dpixCreateTemplate()
+ *          DPIX          *dpixClone()
+ *          DPIX          *dpixCopy()
+ *          l_int32        dpixResizeImageData()
+ *          void           dpixDestroy()
+ *
+ *    DPix accessors
+ *          l_int32        dpixGetDimensions()
+ *          l_int32        dpixSetDimensions()
+ *          l_int32        dpixGetWpl()
+ *          l_int32        dpixSetWpl()
+ *          l_int32        dpixGetRefcount()
+ *          l_int32        dpixChangeRefcount()
+ *          l_int32        dpixGetResolution()
+ *          l_int32        dpixSetResolution()
+ *          l_int32        dpixCopyResolution()
+ *          l_float64     *dpixGetData()
+ *          l_int32        dpixSetData()
+ *          l_int32        dpixGetPixel()
+ *          l_int32        dpixSetPixel()
+ *
+ *    FPix serialized I/O
+ *          FPIX          *fpixRead()
+ *          FPIX          *fpixReadStream()
+ *          l_int32        fpixWrite()
+ *          l_int32        fpixWriteStream()
+ *          FPIX          *fpixEndianByteSwap()
+ *
+ *    DPix serialized I/O
+ *          DPIX          *dpixRead()
+ *          DPIX          *dpixReadStream()
+ *          l_int32        dpixWrite()
+ *          l_int32        dpixWriteStream()
+ *          DPIX          *dpixEndianByteSwap()
+ *
+ *    Print FPix (subsampled, for debugging)
+ *          l_int32        fpixPrintStream()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;   /* must be > 0 */
+
+    /* Static functions */
+static l_int32 fpixaExtendArray(FPIXA *fpixa);
+static l_int32 fpixaExtendArrayToSize(FPIXA *fpixa, l_int32 size);
+
+
+/*--------------------------------------------------------------------*
+ *                     FPix Create/copy/destroy                       *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixCreate()
+ *
+ *      Input:  width, height
+ *      Return: fpixd (with data allocated and initialized to 0),
+ *                     or null on error
+ *
+ *  Notes:
+ *      (1) Makes a FPix of specified size, with the data array
+ *          allocated and initialized to 0.
+ */
+FPIX *
+fpixCreate(l_int32  width,
+           l_int32  height)
+{
+l_float32  *data;
+l_uint64    bignum;
+FPIX       *fpixd;
+
+    PROCNAME("fpixCreate");
+
+    if (width <= 0)
+        return (FPIX *)ERROR_PTR("width must be > 0", procName, NULL);
+    if (height <= 0)
+        return (FPIX *)ERROR_PTR("height must be > 0", procName, NULL);
+
+        /* Avoid overflow in malloc arg, malicious or otherwise */
+    bignum = 4L * width * height;   /* max number of bytes requested */
+    if (bignum > ((1LL << 31) - 1)) {
+        L_ERROR("requested w = %d, h = %d\n", procName, width, height);
+        return (FPIX *)ERROR_PTR("requested bytes >= 2^31", procName, NULL);
+    }
+
+    if ((fpixd = (FPIX *)LEPT_CALLOC(1, sizeof(FPIX))) == NULL)
+        return (FPIX *)ERROR_PTR("LEPT_CALLOC fail for fpixd", procName, NULL);
+    fpixSetDimensions(fpixd, width, height);
+    fpixSetWpl(fpixd, width);  /* 4-byte words */
+    fpixd->refcount = 1;
+
+    data = (l_float32 *)LEPT_CALLOC(width * height, sizeof(l_float32));
+    if (!data)
+        return (FPIX *)ERROR_PTR("LEPT_CALLOC fail for data", procName, NULL);
+    fpixSetData(fpixd, data);
+
+    return fpixd;
+}
+
+
+/*!
+ *  fpixCreateTemplate()
+ *
+ *      Input:  fpixs
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) Makes a FPix of the same size as the input FPix, with the
+ *          data array allocated and initialized to 0.
+ *      (2) Copies the resolution.
+ */
+FPIX *
+fpixCreateTemplate(FPIX  *fpixs)
+{
+l_int32  w, h;
+FPIX    *fpixd;
+
+    PROCNAME("fpixCreateTemplate");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+
+    fpixGetDimensions(fpixs, &w, &h);
+    fpixd = fpixCreate(w, h);
+    fpixCopyResolution(fpixd, fpixs);
+    return fpixd;
+}
+
+
+/*!
+ *  fpixClone()
+ *
+ *      Input:  fpix
+ *      Return: same fpix (ptr), or null on error
+ *
+ *  Notes:
+ *      (1) See pixClone() for definition and usage.
+ */
+FPIX *
+fpixClone(FPIX  *fpix)
+{
+    PROCNAME("fpixClone");
+
+    if (!fpix)
+        return (FPIX *)ERROR_PTR("fpix not defined", procName, NULL);
+    fpixChangeRefcount(fpix, 1);
+
+    return fpix;
+}
+
+
+/*!
+ *  fpixCopy()
+ *
+ *      Input:  fpixd (<optional>; can be null, or equal to fpixs,
+ *                    or different from fpixs)
+ *              fpixs
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) There are three cases:
+ *            (a) fpixd == null  (makes a new fpix; refcount = 1)
+ *            (b) fpixd == fpixs  (no-op)
+ *            (c) fpixd != fpixs  (data copy; no change in refcount)
+ *          If the refcount of fpixd > 1, case (c) will side-effect
+ *          these handles.
+ *      (2) The general pattern of use is:
+ *             fpixd = fpixCopy(fpixd, fpixs);
+ *          This will work for all three cases.
+ *          For clarity when the case is known, you can use:
+ *            (a) fpixd = fpixCopy(NULL, fpixs);
+ *            (c) fpixCopy(fpixd, fpixs);
+ *      (3) For case (c), we check if fpixs and fpixd are the same size.
+ *          If so, the data is copied directly.
+ *          Otherwise, the data is reallocated to the correct size
+ *          and the copy proceeds.  The refcount of fpixd is unchanged.
+ *      (4) This operation, like all others that may involve a pre-existing
+ *          fpixd, will side-effect any existing clones of fpixd.
+ */
+FPIX *
+fpixCopy(FPIX  *fpixd,   /* can be null */
+         FPIX  *fpixs)
+{
+l_int32     w, h, bytes;
+l_float32  *datas, *datad;
+
+    PROCNAME("fpixCopy");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+    if (fpixs == fpixd)
+        return fpixd;
+
+        /* Total bytes in image data */
+    fpixGetDimensions(fpixs, &w, &h);
+    bytes = 4 * w * h;
+
+        /* If we're making a new fpix ... */
+    if (!fpixd) {
+        if ((fpixd = fpixCreateTemplate(fpixs)) == NULL)
+            return (FPIX *)ERROR_PTR("fpixd not made", procName, NULL);
+        datas = fpixGetData(fpixs);
+        datad = fpixGetData(fpixd);
+        memcpy((char *)datad, (char *)datas, bytes);
+        return fpixd;
+    }
+
+        /* Reallocate image data if sizes are different */
+    fpixResizeImageData(fpixd, fpixs);
+
+        /* Copy data */
+    fpixCopyResolution(fpixd, fpixs);
+    datas = fpixGetData(fpixs);
+    datad = fpixGetData(fpixd);
+    memcpy((char*)datad, (char*)datas, bytes);
+    return fpixd;
+}
+
+
+/*!
+ *  fpixResizeImageData()
+ *
+ *      Input:  fpixd, fpixs
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If the data sizes differ, this destroys the existing
+ *          data in fpixd and allocates a new, uninitialized, data array
+ *          of the same size as the data in fpixs.  Otherwise, this
+ *          doesn't do anything.
+ */
+l_int32
+fpixResizeImageData(FPIX  *fpixd,
+                    FPIX  *fpixs)
+{
+l_int32     ws, hs, wd, hd, bytes;
+l_float32  *data;
+
+    PROCNAME("fpixResizeImageData");
+
+    if (!fpixs)
+        return ERROR_INT("fpixs not defined", procName, 1);
+    if (!fpixd)
+        return ERROR_INT("fpixd not defined", procName, 1);
+
+    fpixGetDimensions(fpixs, &ws, &hs);
+    fpixGetDimensions(fpixd, &wd, &hd);
+    if (ws == wd && hs == hd)  /* nothing to do */
+        return 0;
+
+    fpixSetDimensions(fpixd, ws, hs);
+    fpixSetWpl(fpixd, ws);
+    bytes = 4 * ws * hs;
+    data = fpixGetData(fpixd);
+    if (data) LEPT_FREE(data);
+    if ((data = (l_float32 *)LEPT_MALLOC(bytes)) == NULL)
+        return ERROR_INT("LEPT_MALLOC fail for data", procName, 1);
+    fpixSetData(fpixd, data);
+    return 0;
+}
+
+
+/*!
+ *  fpixDestroy()
+ *
+ *      Input:  &fpix <will be nulled>
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Decrements the ref count and, if 0, destroys the fpix.
+ *      (2) Always nulls the input ptr.
+ */
+void
+fpixDestroy(FPIX  **pfpix)
+{
+l_float32  *data;
+FPIX       *fpix;
+
+    PROCNAME("fpixDestroy");
+
+    if (!pfpix) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((fpix = *pfpix) == NULL)
+        return;
+
+        /* Decrement the ref count.  If it is 0, destroy the fpix. */
+    fpixChangeRefcount(fpix, -1);
+    if (fpixGetRefcount(fpix) <= 0) {
+        if ((data = fpixGetData(fpix)) != NULL)
+            LEPT_FREE(data);
+        LEPT_FREE(fpix);
+    }
+
+    *pfpix = NULL;
+    return;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                          FPix  Accessors                           *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixGetDimensions()
+ *
+ *      Input:  fpix
+ *              &w, &h (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+fpixGetDimensions(FPIX     *fpix,
+                  l_int32  *pw,
+                  l_int32  *ph)
+{
+    PROCNAME("fpixGetDimensions");
+
+    if (!pw && !ph)
+        return ERROR_INT("no return val requested", procName, 1);
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+    if (pw) *pw = fpix->w;
+    if (ph) *ph = fpix->h;
+    return 0;
+}
+
+
+/*!
+ *  fpixSetDimensions()
+ *
+ *      Input:  fpix
+ *              w, h
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+fpixSetDimensions(FPIX     *fpix,
+                  l_int32   w,
+                  l_int32   h)
+{
+    PROCNAME("fpixSetDimensions");
+
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+    fpix->w = w;
+    fpix->h = h;
+    return 0;
+}
+
+
+l_int32
+fpixGetWpl(FPIX  *fpix)
+{
+    PROCNAME("fpixGetWpl");
+
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, UNDEF);
+    return fpix->wpl;
+}
+
+
+l_int32
+fpixSetWpl(FPIX    *fpix,
+           l_int32  wpl)
+{
+    PROCNAME("fpixSetWpl");
+
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+    fpix->wpl = wpl;
+    return 0;
+}
+
+
+l_int32
+fpixGetRefcount(FPIX  *fpix)
+{
+    PROCNAME("fpixGetRefcount");
+
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, UNDEF);
+    return fpix->refcount;
+}
+
+
+l_int32
+fpixChangeRefcount(FPIX    *fpix,
+                   l_int32  delta)
+{
+    PROCNAME("fpixChangeRefcount");
+
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+    fpix->refcount += delta;
+    return 0;
+}
+
+
+l_int32
+fpixGetResolution(FPIX     *fpix,
+                  l_int32  *pxres,
+                  l_int32  *pyres)
+{
+    PROCNAME("fpixGetResolution");
+
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+    if (pxres) *pxres = fpix->xres;
+    if (pyres) *pyres = fpix->yres;
+    return 0;
+}
+
+
+l_int32
+fpixSetResolution(FPIX    *fpix,
+                  l_int32  xres,
+                  l_int32  yres)
+{
+    PROCNAME("fpixSetResolution");
+
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+    fpix->xres = xres;
+    fpix->yres = yres;
+    return 0;
+}
+
+
+l_int32
+fpixCopyResolution(FPIX  *fpixd,
+                   FPIX  *fpixs)
+{
+l_int32  xres, yres;
+    PROCNAME("fpixCopyResolution");
+
+    if (!fpixs || !fpixd)
+        return ERROR_INT("fpixs and fpixd not both defined", procName, 1);
+
+    fpixGetResolution(fpixs, &xres, &yres);
+    fpixSetResolution(fpixd, xres, yres);
+    return 0;
+}
+
+
+l_float32 *
+fpixGetData(FPIX  *fpix)
+{
+    PROCNAME("fpixGetData");
+
+    if (!fpix)
+        return (l_float32 *)ERROR_PTR("fpix not defined", procName, NULL);
+    return fpix->data;
+}
+
+
+l_int32
+fpixSetData(FPIX       *fpix,
+            l_float32  *data)
+{
+    PROCNAME("fpixSetData");
+
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+    fpix->data = data;
+    return 0;
+}
+
+
+/*!
+ *  fpixGetPixel()
+ *
+ *      Input:  fpix
+ *              (x,y) pixel coords
+ *              &val (<return> pixel value)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+fpixGetPixel(FPIX       *fpix,
+             l_int32     x,
+             l_int32     y,
+             l_float32  *pval)
+{
+l_int32  w, h;
+
+    PROCNAME("fpixGetPixel");
+
+    if (!pval)
+        return ERROR_INT("pval not defined", procName, 1);
+    *pval = 0.0;
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+    fpixGetDimensions(fpix, &w, &h);
+    if (x < 0 || x >= w)
+        return ERROR_INT("x out of bounds", procName, 1);
+    if (y < 0 || y >= h)
+        return ERROR_INT("y out of bounds", procName, 1);
+
+    *pval = *(fpix->data + y * w + x);
+    return 0;
+}
+
+
+/*!
+ *  fpixSetPixel()
+ *
+ *      Input:  fpix
+ *              (x,y) pixel coords
+ *              val (pixel value)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+fpixSetPixel(FPIX      *fpix,
+             l_int32    x,
+             l_int32    y,
+             l_float32  val)
+{
+l_int32  w, h;
+
+    PROCNAME("fpixSetPixel");
+
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+    fpixGetDimensions(fpix, &w, &h);
+    if (x < 0 || x >= w)
+        return ERROR_INT("x out of bounds", procName, 1);
+    if (y < 0 || y >= h)
+        return ERROR_INT("y out of bounds", procName, 1);
+
+    *(fpix->data + y * w + x) = val;
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                     FPixa Create/copy/destroy                      *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixaCreate()
+ *
+ *      Input:  n  (initial number of ptrs)
+ *      Return: fpixa, or null on error
+ */
+FPIXA *
+fpixaCreate(l_int32  n)
+{
+FPIXA  *fpixa;
+
+    PROCNAME("fpixaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((fpixa = (FPIXA *)LEPT_CALLOC(1, sizeof(FPIXA))) == NULL)
+        return (FPIXA *)ERROR_PTR("pixa not made", procName, NULL);
+    fpixa->n = 0;
+    fpixa->nalloc = n;
+    fpixa->refcount = 1;
+
+    if ((fpixa->fpix = (FPIX **)LEPT_CALLOC(n, sizeof(FPIX *))) == NULL)
+        return (FPIXA *)ERROR_PTR("fpix ptrs not made", procName, NULL);
+
+    return fpixa;
+}
+
+
+/*!
+ *  fpixaCopy()
+ *
+ *      Input:  fpixas
+ *              copyflag:
+ *                L_COPY makes a new fpixa and copies each fpix
+ *                L_CLONE gives a new ref-counted handle to the input fpixa
+ *                L_COPY_CLONE makes a new fpixa with clones of all fpix
+ *      Return: new fpixa, or null on error
+ */
+FPIXA *
+fpixaCopy(FPIXA   *fpixa,
+          l_int32  copyflag)
+{
+l_int32  i;
+FPIX    *fpixc;
+FPIXA   *fpixac;
+
+    PROCNAME("fpixaCopy");
+
+    if (!fpixa)
+        return (FPIXA *)ERROR_PTR("fpixa not defined", procName, NULL);
+
+    if (copyflag == L_CLONE) {
+        fpixaChangeRefcount(fpixa, 1);
+        return fpixa;
+    }
+
+    if (copyflag != L_COPY && copyflag != L_COPY_CLONE)
+        return (FPIXA *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    if ((fpixac = fpixaCreate(fpixa->n)) == NULL)
+        return (FPIXA *)ERROR_PTR("fpixac not made", procName, NULL);
+    for (i = 0; i < fpixa->n; i++) {
+        if (copyflag == L_COPY)
+            fpixc = fpixaGetFPix(fpixa, i, L_COPY);
+        else  /* copy-clone */
+            fpixc = fpixaGetFPix(fpixa, i, L_CLONE);
+        fpixaAddFPix(fpixac, fpixc, L_INSERT);
+    }
+
+    return fpixac;
+}
+
+
+/*!
+ *  fpixaDestroy()
+ *
+ *      Input:  &fpixa (<can be nulled>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Decrements the ref count and, if 0, destroys the fpixa.
+ *      (2) Always nulls the input ptr.
+ */
+void
+fpixaDestroy(FPIXA  **pfpixa)
+{
+l_int32  i;
+FPIXA   *fpixa;
+
+    PROCNAME("fpixaDestroy");
+
+    if (pfpixa == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+
+    if ((fpixa = *pfpixa) == NULL)
+        return;
+
+        /* Decrement the refcount.  If it is 0, destroy the pixa. */
+    fpixaChangeRefcount(fpixa, -1);
+    if (fpixa->refcount <= 0) {
+        for (i = 0; i < fpixa->n; i++)
+            fpixDestroy(&fpixa->fpix[i]);
+        LEPT_FREE(fpixa->fpix);
+        LEPT_FREE(fpixa);
+    }
+
+    *pfpixa = NULL;
+    return;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                           FPixa addition                           *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixaAddFPix()
+ *
+ *      Input:  fpixa
+ *              fpix  (to be added)
+ *              copyflag (L_INSERT, L_COPY, L_CLONE)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+fpixaAddFPix(FPIXA   *fpixa,
+             FPIX    *fpix,
+             l_int32  copyflag)
+{
+l_int32  n;
+FPIX    *fpixc;
+
+    PROCNAME("fpixaAddFPix");
+
+    if (!fpixa)
+        return ERROR_INT("fpixa not defined", procName, 1);
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+    if (copyflag == L_INSERT)
+        fpixc = fpix;
+    else if (copyflag == L_COPY)
+        fpixc = fpixCopy(NULL, fpix);
+    else if (copyflag == L_CLONE)
+        fpixc = fpixClone(fpix);
+    else
+        return ERROR_INT("invalid copyflag", procName, 1);
+    if (!fpixc)
+        return ERROR_INT("fpixc not made", procName, 1);
+
+    n = fpixaGetCount(fpixa);
+    if (n >= fpixa->nalloc)
+        fpixaExtendArray(fpixa);
+    fpixa->fpix[n] = fpixc;
+    fpixa->n++;
+
+    return 0;
+}
+
+
+/*!
+ *  fpixaExtendArray()
+ *
+ *      Input:  fpixa
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Doubles the size of the fpixa ptr array.
+ */
+static l_int32
+fpixaExtendArray(FPIXA  *fpixa)
+{
+    PROCNAME("fpixaExtendArray");
+
+    if (!fpixa)
+        return ERROR_INT("fpixa not defined", procName, 1);
+
+    return fpixaExtendArrayToSize(fpixa, 2 * fpixa->nalloc);
+}
+
+
+/*!
+ *  fpixaExtendArrayToSize()
+ *
+ *      Input:  fpixa
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) If necessary, reallocs new fpixa ptrs array to @size.
+ */
+static l_int32
+fpixaExtendArrayToSize(FPIXA   *fpixa,
+                       l_int32  size)
+{
+    PROCNAME("fpixaExtendArrayToSize");
+
+    if (!fpixa)
+        return ERROR_INT("fpixa not defined", procName, 1);
+
+    if (size > fpixa->nalloc) {
+        if ((fpixa->fpix = (FPIX **)reallocNew((void **)&fpixa->fpix,
+                                 sizeof(FPIX *) * fpixa->nalloc,
+                                 size * sizeof(FPIX *))) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+        fpixa->nalloc = size;
+    }
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                          FPixa accessors                           *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixaGetCount()
+ *
+ *      Input:  fpixa
+ *      Return: count, or 0 if no pixa
+ */
+l_int32
+fpixaGetCount(FPIXA  *fpixa)
+{
+    PROCNAME("fpixaGetCount");
+
+    if (!fpixa)
+        return ERROR_INT("fpixa not defined", procName, 0);
+
+    return fpixa->n;
+}
+
+
+/*!
+ *  fpixaChangeRefcount()
+ *
+ *      Input:  fpixa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+fpixaChangeRefcount(FPIXA   *fpixa,
+                    l_int32  delta)
+{
+    PROCNAME("fpixaChangeRefcount");
+
+    if (!fpixa)
+        return ERROR_INT("fpixa not defined", procName, 1);
+
+    fpixa->refcount += delta;
+    return 0;
+}
+
+
+/*!
+ *  fpixaGetFPix()
+ *
+ *      Input:  fpixa
+ *              index  (to the index-th fpix)
+ *              accesstype  (L_COPY or L_CLONE)
+ *      Return: fpix, or null on error
+ */
+FPIX *
+fpixaGetFPix(FPIXA   *fpixa,
+             l_int32  index,
+             l_int32  accesstype)
+{
+    PROCNAME("fpixaGetFPix");
+
+    if (!fpixa)
+        return (FPIX *)ERROR_PTR("fpixa not defined", procName, NULL);
+    if (index < 0 || index >= fpixa->n)
+        return (FPIX *)ERROR_PTR("index not valid", procName, NULL);
+
+    if (accesstype == L_COPY)
+        return fpixCopy(NULL, fpixa->fpix[index]);
+    else if (accesstype == L_CLONE)
+        return fpixClone(fpixa->fpix[index]);
+    else
+        return (FPIX *)ERROR_PTR("invalid accesstype", procName, NULL);
+}
+
+
+/*!
+ *  fpixaGetFPixDimensions()
+ *
+ *      Input:  fpixa
+ *              index  (to the index-th box)
+ *              &w, &h (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+fpixaGetFPixDimensions(FPIXA    *fpixa,
+                       l_int32   index,
+                       l_int32  *pw,
+                       l_int32  *ph)
+{
+FPIX  *fpix;
+
+    PROCNAME("fpixaGetFPixDimensions");
+
+    if (!pw && !ph)
+        return ERROR_INT("no return val requested", procName, 1);
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (!fpixa)
+        return ERROR_INT("fpixa not defined", procName, 1);
+    if (index < 0 || index >= fpixa->n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    if ((fpix = fpixaGetFPix(fpixa, index, L_CLONE)) == NULL)
+        return ERROR_INT("fpix not found!", procName, 1);
+    fpixGetDimensions(fpix, pw, ph);
+    fpixDestroy(&fpix);
+    return 0;
+}
+
+
+/*!
+ *  fpixaGetData()
+ *
+ *      Input:  fpixa
+ *              index (into fpixa array)
+ *      Return: data (not a copy), or null on error
+ */
+l_float32 *
+fpixaGetData(FPIXA      *fpixa,
+             l_int32     index)
+{
+l_int32     n;
+l_float32  *data;
+FPIX       *fpix;
+
+    PROCNAME("fpixaGetData");
+
+    if (!fpixa)
+        return (l_float32 *)ERROR_PTR("fpixa not defined", procName, NULL);
+    n = fpixaGetCount(fpixa);
+    if (index < 0 || index >= n)
+        return (l_float32 *)ERROR_PTR("invalid index", procName, NULL);
+
+    fpix = fpixaGetFPix(fpixa, index, L_CLONE);
+    data = fpixGetData(fpix);
+    fpixDestroy(&fpix);
+    return data;
+}
+
+
+/*!
+ *  fpixaGetPixel()
+ *
+ *      Input:  fpixa
+ *              index (into fpixa array)
+ *              (x,y) pixel coords
+ *              &val (<return> pixel value)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+fpixaGetPixel(FPIXA      *fpixa,
+              l_int32     index,
+              l_int32     x,
+              l_int32     y,
+              l_float32  *pval)
+{
+l_int32  n, ret;
+FPIX    *fpix;
+
+    PROCNAME("fpixaGetPixel");
+
+    if (!pval)
+        return ERROR_INT("pval not defined", procName, 1);
+    *pval = 0.0;
+    if (!fpixa)
+        return ERROR_INT("fpixa not defined", procName, 1);
+    n = fpixaGetCount(fpixa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("invalid index into fpixa", procName, 1);
+
+    fpix = fpixaGetFPix(fpixa, index, L_CLONE);
+    ret = fpixGetPixel(fpix, x, y, pval);
+    fpixDestroy(&fpix);
+    return ret;
+}
+
+
+/*!
+ *  fpixaSetPixel()
+ *
+ *      Input:  fpixa
+ *              index (into fpixa array)
+ *              (x,y) pixel coords
+ *              val (pixel value)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+fpixaSetPixel(FPIXA     *fpixa,
+              l_int32    index,
+              l_int32    x,
+              l_int32    y,
+              l_float32  val)
+{
+l_int32  n, ret;
+FPIX    *fpix;
+
+    PROCNAME("fpixaSetPixel");
+
+    if (!fpixa)
+        return ERROR_INT("fpixa not defined", procName, 1);
+    n = fpixaGetCount(fpixa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("invalid index into fpixa", procName, 1);
+
+    fpix = fpixaGetFPix(fpixa, index, L_CLONE);
+    ret = fpixSetPixel(fpix, x, y, val);
+    fpixDestroy(&fpix);
+    return ret;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                     DPix Create/copy/destroy                       *
+ *--------------------------------------------------------------------*/
+/*!
+ *  dpixCreate()
+ *
+ *      Input:  width, height
+ *      Return: dpix (with data allocated and initialized to 0),
+ *                     or null on error
+ *
+ *  Notes:
+ *      (1) Makes a DPix of specified size, with the data array
+ *          allocated and initialized to 0.
+ */
+DPIX *
+dpixCreate(l_int32  width,
+           l_int32  height)
+{
+l_float64  *data;
+l_uint64    bignum;
+DPIX       *dpix;
+
+    PROCNAME("dpixCreate");
+
+    if (width <= 0)
+        return (DPIX *)ERROR_PTR("width must be > 0", procName, NULL);
+    if (height <= 0)
+        return (DPIX *)ERROR_PTR("height must be > 0", procName, NULL);
+
+        /* Avoid overflow in malloc arg, malicious or otherwise */
+    bignum = 8L * width * height;   /* max number of bytes requested */
+    if (bignum > ((1LL << 31) - 1)) {
+        L_ERROR("requested w = %d, h = %d\n", procName, width, height);
+        return (DPIX *)ERROR_PTR("requested bytes >= 2^31", procName, NULL);
+    }
+
+    if ((dpix = (DPIX *)LEPT_CALLOC(1, sizeof(DPIX))) == NULL)
+        return (DPIX *)ERROR_PTR("LEPT_CALLOC fail for dpix", procName, NULL);
+    dpixSetDimensions(dpix, width, height);
+    dpixSetWpl(dpix, width);  /* 8 byte words */
+    dpix->refcount = 1;
+
+    data = (l_float64 *)LEPT_CALLOC(width * height, sizeof(l_float64));
+    if (!data)
+        return (DPIX *)ERROR_PTR("LEPT_CALLOC fail for data", procName, NULL);
+    dpixSetData(dpix, data);
+
+    return dpix;
+}
+
+
+/*!
+ *  dpixCreateTemplate()
+ *
+ *      Input:  dpixs
+ *      Return: dpixd, or null on error
+ *
+ *  Notes:
+ *      (1) Makes a DPix of the same size as the input DPix, with the
+ *          data array allocated and initialized to 0.
+ *      (2) Copies the resolution.
+ */
+DPIX *
+dpixCreateTemplate(DPIX  *dpixs)
+{
+l_int32  w, h;
+DPIX    *dpixd;
+
+    PROCNAME("dpixCreateTemplate");
+
+    if (!dpixs)
+        return (DPIX *)ERROR_PTR("dpixs not defined", procName, NULL);
+
+    dpixGetDimensions(dpixs, &w, &h);
+    dpixd = dpixCreate(w, h);
+    dpixCopyResolution(dpixd, dpixs);
+    return dpixd;
+}
+
+
+/*!
+ *  dpixClone()
+ *
+ *      Input:  dpix
+ *      Return: same dpix (ptr), or null on error
+ *
+ *  Notes:
+ *      (1) See pixClone() for definition and usage.
+ */
+DPIX *
+dpixClone(DPIX  *dpix)
+{
+    PROCNAME("dpixClone");
+
+    if (!dpix)
+        return (DPIX *)ERROR_PTR("dpix not defined", procName, NULL);
+    dpixChangeRefcount(dpix, 1);
+
+    return dpix;
+}
+
+
+/*!
+ *  dpixCopy()
+ *
+ *      Input:  dpixd (<optional>; can be null, or equal to dpixs,
+ *                    or different from dpixs)
+ *              dpixs
+ *      Return: dpixd, or null on error
+ *
+ *  Notes:
+ *      (1) There are three cases:
+ *            (a) dpixd == null  (makes a new dpix; refcount = 1)
+ *            (b) dpixd == dpixs  (no-op)
+ *            (c) dpixd != dpixs  (data copy; no change in refcount)
+ *          If the refcount of dpixd > 1, case (c) will side-effect
+ *          these handles.
+ *      (2) The general pattern of use is:
+ *             dpixd = dpixCopy(dpixd, dpixs);
+ *          This will work for all three cases.
+ *          For clarity when the case is known, you can use:
+ *            (a) dpixd = dpixCopy(NULL, dpixs);
+ *            (c) dpixCopy(dpixd, dpixs);
+ *      (3) For case (c), we check if dpixs and dpixd are the same size.
+ *          If so, the data is copied directly.
+ *          Otherwise, the data is reallocated to the correct size
+ *          and the copy proceeds.  The refcount of dpixd is unchanged.
+ *      (4) This operation, like all others that may involve a pre-existing
+ *          dpixd, will side-effect any existing clones of dpixd.
+ */
+DPIX *
+dpixCopy(DPIX  *dpixd,   /* can be null */
+         DPIX  *dpixs)
+{
+l_int32     w, h, bytes;
+l_float64  *datas, *datad;
+
+    PROCNAME("dpixCopy");
+
+    if (!dpixs)
+        return (DPIX *)ERROR_PTR("dpixs not defined", procName, NULL);
+    if (dpixs == dpixd)
+        return dpixd;
+
+        /* Total bytes in image data */
+    dpixGetDimensions(dpixs, &w, &h);
+    bytes = 8 * w * h;
+
+        /* If we're making a new dpix ... */
+    if (!dpixd) {
+        if ((dpixd = dpixCreateTemplate(dpixs)) == NULL)
+            return (DPIX *)ERROR_PTR("dpixd not made", procName, NULL);
+        datas = dpixGetData(dpixs);
+        datad = dpixGetData(dpixd);
+        memcpy((char *)datad, (char *)datas, bytes);
+        return dpixd;
+    }
+
+        /* Reallocate image data if sizes are different */
+    dpixResizeImageData(dpixd, dpixs);
+
+        /* Copy data */
+    dpixCopyResolution(dpixd, dpixs);
+    datas = dpixGetData(dpixs);
+    datad = dpixGetData(dpixd);
+    memcpy((char*)datad, (char*)datas, bytes);
+    return dpixd;
+}
+
+
+/*!
+ *  dpixResizeImageData()
+ *
+ *      Input:  dpixd, dpixs
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+dpixResizeImageData(DPIX  *dpixd,
+                    DPIX  *dpixs)
+{
+l_int32     ws, hs, wd, hd, bytes;
+l_float64  *data;
+
+    PROCNAME("dpixResizeImageData");
+
+    if (!dpixs)
+        return ERROR_INT("dpixs not defined", procName, 1);
+    if (!dpixd)
+        return ERROR_INT("dpixd not defined", procName, 1);
+
+    dpixGetDimensions(dpixs, &ws, &hs);
+    dpixGetDimensions(dpixd, &wd, &hd);
+    if (ws == wd && hs == hd)  /* nothing to do */
+        return 0;
+
+    dpixSetDimensions(dpixd, ws, hs);
+    dpixSetWpl(dpixd, ws);  /* 8 byte words */
+    bytes = 8 * ws * hs;
+    data = dpixGetData(dpixd);
+    if (data) LEPT_FREE(data);
+    if ((data = (l_float64 *)LEPT_MALLOC(bytes)) == NULL)
+        return ERROR_INT("LEPT_MALLOC fail for data", procName, 1);
+    dpixSetData(dpixd, data);
+    return 0;
+}
+
+
+/*!
+ *  dpixDestroy()
+ *
+ *      Input:  &dpix <will be nulled>
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Decrements the ref count and, if 0, destroys the dpix.
+ *      (2) Always nulls the input ptr.
+ */
+void
+dpixDestroy(DPIX  **pdpix)
+{
+l_float64  *data;
+DPIX       *dpix;
+
+    PROCNAME("dpixDestroy");
+
+    if (!pdpix) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((dpix = *pdpix) == NULL)
+        return;
+
+        /* Decrement the ref count.  If it is 0, destroy the dpix. */
+    dpixChangeRefcount(dpix, -1);
+    if (dpixGetRefcount(dpix) <= 0) {
+        if ((data = dpixGetData(dpix)) != NULL)
+            LEPT_FREE(data);
+        LEPT_FREE(dpix);
+    }
+
+    *pdpix = NULL;
+    return;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                          DPix  Accessors                           *
+ *--------------------------------------------------------------------*/
+/*!
+ *  dpixGetDimensions()
+ *
+ *      Input:  dpix
+ *              &w, &h (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+dpixGetDimensions(DPIX     *dpix,
+                  l_int32  *pw,
+                  l_int32  *ph)
+{
+    PROCNAME("dpixGetDimensions");
+
+    if (!pw && !ph)
+        return ERROR_INT("no return val requested", procName, 1);
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+    if (pw) *pw = dpix->w;
+    if (ph) *ph = dpix->h;
+    return 0;
+}
+
+
+/*!
+ *  dpixSetDimensions()
+ *
+ *      Input:  dpix
+ *              w, h
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+dpixSetDimensions(DPIX     *dpix,
+                  l_int32   w,
+                  l_int32   h)
+{
+    PROCNAME("dpixSetDimensions");
+
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+    dpix->w = w;
+    dpix->h = h;
+    return 0;
+}
+
+
+l_int32
+dpixGetWpl(DPIX  *dpix)
+{
+    PROCNAME("dpixGetWpl");
+
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+    return dpix->wpl;
+}
+
+
+l_int32
+dpixSetWpl(DPIX    *dpix,
+           l_int32  wpl)
+{
+    PROCNAME("dpixSetWpl");
+
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+
+    dpix->wpl = wpl;
+    return 0;
+}
+
+
+l_int32
+dpixGetRefcount(DPIX  *dpix)
+{
+    PROCNAME("dpixGetRefcount");
+
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, UNDEF);
+    return dpix->refcount;
+}
+
+
+l_int32
+dpixChangeRefcount(DPIX    *dpix,
+                   l_int32  delta)
+{
+    PROCNAME("dpixChangeRefcount");
+
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+
+    dpix->refcount += delta;
+    return 0;
+}
+
+
+l_int32
+dpixGetResolution(DPIX     *dpix,
+                  l_int32  *pxres,
+                  l_int32  *pyres)
+{
+    PROCNAME("dpixGetResolution");
+
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+    if (pxres) *pxres = dpix->xres;
+    if (pyres) *pyres = dpix->yres;
+    return 0;
+}
+
+
+l_int32
+dpixSetResolution(DPIX    *dpix,
+                  l_int32  xres,
+                  l_int32  yres)
+{
+    PROCNAME("dpixSetResolution");
+
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+
+    dpix->xres = xres;
+    dpix->yres = yres;
+    return 0;
+}
+
+
+l_int32
+dpixCopyResolution(DPIX  *dpixd,
+                   DPIX  *dpixs)
+{
+l_int32  xres, yres;
+    PROCNAME("dpixCopyResolution");
+
+    if (!dpixs || !dpixd)
+        return ERROR_INT("dpixs and dpixd not both defined", procName, 1);
+
+    dpixGetResolution(dpixs, &xres, &yres);
+    dpixSetResolution(dpixd, xres, yres);
+    return 0;
+}
+
+
+l_float64 *
+dpixGetData(DPIX  *dpix)
+{
+    PROCNAME("dpixGetData");
+
+    if (!dpix)
+        return (l_float64 *)ERROR_PTR("dpix not defined", procName, NULL);
+    return dpix->data;
+}
+
+
+l_int32
+dpixSetData(DPIX       *dpix,
+            l_float64  *data)
+{
+    PROCNAME("dpixSetData");
+
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+
+    dpix->data = data;
+    return 0;
+}
+
+
+/*!
+ *  dpixGetPixel()
+ *
+ *      Input:  dpix
+ *              (x,y) pixel coords
+ *              &val (<return> pixel value)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+dpixGetPixel(DPIX       *dpix,
+             l_int32     x,
+             l_int32     y,
+             l_float64  *pval)
+{
+l_int32  w, h;
+
+    PROCNAME("dpixGetPixel");
+
+    if (!pval)
+        return ERROR_INT("pval not defined", procName, 1);
+    *pval = 0.0;
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+
+    dpixGetDimensions(dpix, &w, &h);
+    if (x < 0 || x >= w)
+        return ERROR_INT("x out of bounds", procName, 1);
+    if (y < 0 || y >= h)
+        return ERROR_INT("y out of bounds", procName, 1);
+
+    *pval = *(dpix->data + y * w + x);
+    return 0;
+}
+
+
+/*!
+ *  dpixSetPixel()
+ *
+ *      Input:  dpix
+ *              (x,y) pixel coords
+ *              val (pixel value)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+dpixSetPixel(DPIX      *dpix,
+             l_int32    x,
+             l_int32    y,
+             l_float64  val)
+{
+l_int32  w, h;
+
+    PROCNAME("dpixSetPixel");
+
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+
+    dpixGetDimensions(dpix, &w, &h);
+    if (x < 0 || x >= w)
+        return ERROR_INT("x out of bounds", procName, 1);
+    if (y < 0 || y >= h)
+        return ERROR_INT("y out of bounds", procName, 1);
+
+    *(dpix->data + y * w + x) = val;
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                       FPix serialized I/O                          *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixRead()
+ *
+ *      Input:  filename
+ *      Return: fpix, or null on error
+ */
+FPIX *
+fpixRead(const char  *filename)
+{
+FILE  *fp;
+FPIX  *fpix;
+
+    PROCNAME("fpixRead");
+
+    if (!filename)
+        return (FPIX *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (FPIX *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((fpix = fpixReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (FPIX *)ERROR_PTR("fpix not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return fpix;
+}
+
+
+/*!
+ *  fpixReadStream()
+ *
+ *      Input:  stream
+ *      Return: fpix, or null on error
+ */
+FPIX *
+fpixReadStream(FILE  *fp)
+{
+char        buf[256];
+l_int32     w, h, nbytes, xres, yres, version;
+l_float32  *data;
+FPIX       *fpix;
+
+    PROCNAME("fpixReadStream");
+
+    if (!fp)
+        return (FPIX *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nFPix Version %d\n", &version) != 1)
+        return (FPIX *)ERROR_PTR("not a fpix file", procName, NULL);
+    if (version != FPIX_VERSION_NUMBER)
+        return (FPIX *)ERROR_PTR("invalid fpix version", procName, NULL);
+    if (fscanf(fp, "w = %d, h = %d, nbytes = %d\n", &w, &h, &nbytes) != 3)
+        return (FPIX *)ERROR_PTR("read fail for data size", procName, NULL);
+
+        /* Use fgets() and sscanf(); not fscanf(), for the last
+         * bit of header data before the float data.  The reason is
+         * that fscanf throws away white space, and if the float data
+         * happens to begin with ascii character(s) that are white
+         * space, it will swallow them and all will be lost!  */
+    if (fgets(buf, sizeof(buf), fp) == NULL)
+        return (FPIX *)ERROR_PTR("fgets read fail", procName, NULL);
+    if (sscanf(buf, "xres = %d, yres = %d\n", &xres, &yres) != 2)
+        return (FPIX *)ERROR_PTR("read fail for xres, yres", procName, NULL);
+
+    if ((fpix = fpixCreate(w, h)) == NULL)
+        return (FPIX *)ERROR_PTR("fpix not made", procName, NULL);
+    fpixSetResolution(fpix, xres, yres);
+    data = fpixGetData(fpix);
+    if (fread(data, 1, nbytes, fp) != nbytes)
+        return (FPIX *)ERROR_PTR("read error for nbytes", procName, NULL);
+    fgetc(fp);  /* ending nl */
+
+        /* Convert to little-endian if necessary */
+    fpixEndianByteSwap(fpix, fpix);
+    return fpix;
+}
+
+
+/*!
+ *  fpixWrite()
+ *
+ *      Input:  filename
+ *              fpix
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+fpixWrite(const char  *filename,
+          FPIX        *fpix)
+{
+FILE  *fp;
+
+    PROCNAME("fpixWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (fpixWriteStream(fp, fpix))
+        return ERROR_INT("fpix not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  fpixWriteStream()
+ *
+ *      Input:  stream (opened for "wb")
+ *              fpix
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+fpixWriteStream(FILE  *fp,
+                FPIX  *fpix)
+{
+l_int32     w, h, nbytes, xres, yres;
+l_float32  *data;
+FPIX       *fpixt;
+
+    PROCNAME("fpixWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+        /* Convert to little-endian if necessary */
+    fpixt = fpixEndianByteSwap(NULL, fpix);
+
+    fpixGetDimensions(fpixt, &w, &h);
+    data = fpixGetData(fpixt);
+    nbytes = w * h * sizeof(l_float32);
+    fpixGetResolution(fpixt, &xres, &yres);
+    fprintf(fp, "\nFPix Version %d\n", FPIX_VERSION_NUMBER);
+    fprintf(fp, "w = %d, h = %d, nbytes = %d\n", w, h, nbytes);
+    fprintf(fp, "xres = %d, yres = %d\n", xres, yres);
+    fwrite(data, 1, nbytes, fp);
+    fprintf(fp, "\n");
+
+    fpixDestroy(&fpixt);
+    return 0;
+}
+
+
+/*!
+ *  fpixEndianByteSwap()
+ *
+ *      Input:  fpixd (can be equal to fpixs or NULL)
+ *              fpixs
+ *      Return: fpixd always
+ *
+ *  Notes:
+ *      (1) On big-endian hardware, this does byte-swapping on each of
+ *          the 4-byte floats in the fpix data.  On little-endians,
+ *          the data is unchanged.  This is used for serialization
+ *          of fpix; the data is serialized in little-endian byte
+ *          order because most hardware is little-endian.
+ *      (2) The operation can be either in-place or, if fpixd == NULL,
+ *          a new fpix is made.  If not in-place, caller must catch
+ *          the returned pointer.
+ */
+FPIX *
+fpixEndianByteSwap(FPIX  *fpixd,
+                   FPIX  *fpixs)
+{
+    PROCNAME("fpixEndianByteSwap");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, fpixd);
+    if (fpixd && (fpixs != fpixd))
+        return (FPIX *)ERROR_PTR("fpixd != fpixs", procName, fpixd);
+
+#ifdef L_BIG_ENDIAN
+    {
+    l_uint32  *data;
+    l_int32    i, j, w, h;
+    l_uint32   word;
+
+        fpixGetDimensions(fpixs, &w, &h);
+        fpixd = fpixCopy(fpixd, fpixs);  /* no copy if fpixd == fpixs */
+
+        data = (l_uint32 *)fpixGetData(fpixd);
+        for (i = 0; i < h; i++) {
+            for (j = 0; j < w; j++, data++) {
+                word = *data;
+                *data = (word >> 24) |
+                        ((word >> 8) & 0x0000ff00) |
+                        ((word << 8) & 0x00ff0000) |
+                        (word << 24);
+            }
+        }
+        return fpixd;
+    }
+#else   /* L_LITTLE_ENDIAN */
+
+    if (fpixd)
+        return fpixd;  /* no-op */
+    else
+        return fpixClone(fpixs);
+
+#endif   /* L_BIG_ENDIAN */
+}
+
+
+/*--------------------------------------------------------------------*
+ *                       DPix serialized I/O                          *
+ *--------------------------------------------------------------------*/
+/*!
+ *  dpixRead()
+ *
+ *      Input:  filename
+ *      Return: dpix, or null on error
+ */
+DPIX *
+dpixRead(const char  *filename)
+{
+FILE  *fp;
+DPIX  *dpix;
+
+    PROCNAME("dpixRead");
+
+    if (!filename)
+        return (DPIX *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (DPIX *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((dpix = dpixReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (DPIX *)ERROR_PTR("dpix not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return dpix;
+}
+
+
+/*!
+ *  dpixReadStream()
+ *
+ *      Input:  stream
+ *      Return: dpix, or null on error
+ */
+DPIX *
+dpixReadStream(FILE  *fp)
+{
+char        buf[256];
+l_int32     w, h, nbytes, version, xres, yres;
+l_float64  *data;
+DPIX       *dpix;
+
+    PROCNAME("dpixReadStream");
+
+    if (!fp)
+        return (DPIX *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nDPix Version %d\n", &version) != 1)
+        return (DPIX *)ERROR_PTR("not a dpix file", procName, NULL);
+    if (version != DPIX_VERSION_NUMBER)
+        return (DPIX *)ERROR_PTR("invalid dpix version", procName, NULL);
+    if (fscanf(fp, "w = %d, h = %d, nbytes = %d\n", &w, &h, &nbytes) != 3)
+        return (DPIX *)ERROR_PTR("read fail for data size", procName, NULL);
+
+        /* Use fgets() and sscanf(); not fscanf(), for the last
+         * bit of header data before the float data.  The reason is
+         * that fscanf throws away white space, and if the float data
+         * happens to begin with ascii character(s) that are white
+         * space, it will swallow them and all will be lost!  */
+    if (fgets(buf, sizeof(buf), fp) == NULL)
+        return (DPIX *)ERROR_PTR("fgets read fail", procName, NULL);
+    if (sscanf(buf, "xres = %d, yres = %d\n", &xres, &yres) != 2)
+        return (DPIX *)ERROR_PTR("read fail for xres, yres", procName, NULL);
+
+    if ((dpix = dpixCreate(w, h)) == NULL)
+        return (DPIX *)ERROR_PTR("dpix not made", procName, NULL);
+    dpixSetResolution(dpix, xres, yres);
+    data = dpixGetData(dpix);
+    if (fread(data, 1, nbytes, fp) != nbytes)
+        return (DPIX *)ERROR_PTR("read error for nbytes", procName, NULL);
+    fgetc(fp);  /* ending nl */
+
+        /* Convert to little-endian if necessary */
+    dpixEndianByteSwap(dpix, dpix);
+    return dpix;
+}
+
+
+/*!
+ *  dpixWrite()
+ *
+ *      Input:  filename
+ *              dpix
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+dpixWrite(const char  *filename,
+          DPIX        *dpix)
+{
+FILE  *fp;
+
+    PROCNAME("dpixWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (dpixWriteStream(fp, dpix))
+        return ERROR_INT("dpix not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  dpixWriteStream()
+ *
+ *      Input:  stream (opened for "wb")
+ *              dpix
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+dpixWriteStream(FILE  *fp,
+                DPIX  *dpix)
+{
+l_int32     w, h, nbytes, xres, yres;
+l_float64  *data;
+DPIX       *dpixt;
+
+    PROCNAME("dpixWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+
+        /* Convert to little-endian if necessary */
+    dpixt = dpixEndianByteSwap(NULL, dpix);
+
+    dpixGetDimensions(dpixt, &w, &h);
+    dpixGetResolution(dpixt, &xres, &yres);
+    data = dpixGetData(dpixt);
+    nbytes = w * h * sizeof(l_float64);
+    fprintf(fp, "\nDPix Version %d\n", DPIX_VERSION_NUMBER);
+    fprintf(fp, "w = %d, h = %d, nbytes = %d\n", w, h, nbytes);
+    fprintf(fp, "xres = %d, yres = %d\n", xres, yres);
+    fwrite(data, 1, nbytes, fp);
+    fprintf(fp, "\n");
+
+    dpixDestroy(&dpixt);
+    return 0;
+}
+
+
+/*!
+ *  dpixEndianByteSwap()
+ *
+ *      Input:  dpixd (can be equal to dpixs or NULL)
+ *              dpixs
+ *      Return: dpixd always
+ *
+ *  Notes:
+ *      (1) On big-endian hardware, this does byte-swapping on each of
+ *          the 4-byte words in the dpix data.  On little-endians,
+ *          the data is unchanged.  This is used for serialization
+ *          of dpix; the data is serialized in little-endian byte
+ *          order because most hardware is little-endian.
+ *      (2) The operation can be either in-place or, if dpixd == NULL,
+ *          a new dpix is made.  If not in-place, caller must catch
+ *          the returned pointer.
+ */
+DPIX *
+dpixEndianByteSwap(DPIX  *dpixd,
+                   DPIX  *dpixs)
+{
+    PROCNAME("dpixEndianByteSwap");
+
+    if (!dpixs)
+        return (DPIX *)ERROR_PTR("dpixs not defined", procName, dpixd);
+    if (dpixd && (dpixs != dpixd))
+        return (DPIX *)ERROR_PTR("dpixd != dpixs", procName, dpixd);
+
+#ifdef L_BIG_ENDIAN
+    {
+    l_uint32  *data;
+    l_int32    i, j, w, h;
+    l_uint32   word;
+
+        dpixGetDimensions(dpixs, &w, &h);
+        dpixd = dpixCopy(dpixd, dpixs);  /* no copy if dpixd == dpixs */
+
+        data = (l_uint32 *)dpixGetData(dpixd);
+        for (i = 0; i < h; i++) {
+            for (j = 0; j < 2 * w; j++, data++) {
+                word = *data;
+                *data = (word >> 24) |
+                        ((word >> 8) & 0x0000ff00) |
+                        ((word << 8) & 0x00ff0000) |
+                        (word << 24);
+            }
+        }
+        return dpixd;
+    }
+#else   /* L_LITTLE_ENDIAN */
+
+    if (dpixd)
+        return dpixd;  /* no-op */
+    else
+        return dpixClone(dpixs);
+
+#endif   /* L_BIG_ENDIAN */
+}
+
+
+/*--------------------------------------------------------------------*
+ *                 Print FPix (subsampled, for debugging)             *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixPrintStream()
+ *
+ *      Input:  stream
+ *              fpix
+ *              factor (subsampled)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Subsampled printout of fpix for debugging.
+ */
+l_int32
+fpixPrintStream(FILE    *fp,
+                FPIX    *fpix,
+                l_int32  factor)
+{
+l_int32    i, j, w, h, count;
+l_float32  val;
+
+    PROCNAME("fpixPrintStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("sampling factor < 1f", procName, 1);
+
+    fpixGetDimensions(fpix, &w, &h);
+    fprintf(fp, "\nFPix: w = %d, h = %d\n", w, h);
+    for (i = 0; i < h; i += factor) {
+        for (count = 0, j = 0; j < w; j += factor, count++) {
+            fpixGetPixel(fpix, j, i, &val);
+            fprintf(fp, "val[%d, %d] = %f   ", i, j, val);
+            if ((count + 1) % 3 == 0) fprintf(fp, "\n");
+        }
+        if (count % 3) fprintf(fp, "\n");
+     }
+     fprintf(fp, "\n");
+     return 0;
+}
diff --git a/src/fpix2.c b/src/fpix2.c
new file mode 100644 (file)
index 0000000..6f16a3d
--- /dev/null
@@ -0,0 +1,2400 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  fpix2.c
+ *
+ *    This file has these FPix utilities:
+ *       - interconversions with pix, fpix, dpix
+ *       - min and max values
+ *       - integer scaling
+ *       - arithmetic operations
+ *       - set all
+ *       - border functions
+ *       - simple rasterop (source --> dest)
+ *       - geometric transforms
+ *
+ *    Interconversions between Pix, FPix and DPix
+ *          FPIX          *pixConvertToFPix()
+ *          DPIX          *pixConvertToDPix()
+ *          PIX           *fpixConvertToPix()
+ *          PIX           *fpixDisplayMaxDynamicRange()  [useful for debugging]
+ *          DPIX          *fpixConvertToDPix()
+ *          PIX           *dpixConvertToPix()
+ *          FPIX          *dpixConvertToFPix()
+ *
+ *    Min/max value
+ *          l_int32        fpixGetMin()
+ *          l_int32        fpixGetMax()
+ *          l_int32        dpixGetMin()
+ *          l_int32        dpixGetMax()
+ *
+ *    Integer scaling
+ *          FPIX          *fpixScaleByInteger()
+ *          DPIX          *dpixScaleByInteger()
+ *
+ *    Arithmetic operations
+ *          FPIX          *fpixLinearCombination()
+ *          l_int32        fpixAddMultConstant()
+ *          DPIX          *dpixLinearCombination()
+ *          l_int32        dpixAddMultConstant()
+ *
+ *    Set all
+ *          l_int32        fpixSetAllArbitrary()
+ *          l_int32        dpixSetAllArbitrary()
+ *
+ *    FPix border functions
+ *          FPIX          *fpixAddBorder()
+ *          FPIX          *fpixRemoveBorder()
+ *          FPIX          *fpixAddMirroredBorder()
+ *          FPIX          *fpixAddContinuedBorder()
+ *          FPIX          *fpixAddSlopeBorder()
+ *
+ *    FPix simple rasterop
+ *          l_int32        fpixRasterop()
+ *
+ *    FPix rotation by multiples of 90 degrees
+ *          FPIX          *fpixRotateOrth()
+ *          FPIX          *fpixRotate180()
+ *          FPIX          *fpixRotate90()
+ *          FPIX          *fpixFlipLR()
+ *          FPIX          *fpixFlipTB()
+ *
+ *    FPix affine and projective interpolated transforms
+ *          FPIX          *fpixAffinePta()
+ *          FPIX          *fpixAffine()
+ *          FPIX          *fpixProjectivePta()
+ *          FPIX          *fpixProjective()
+ *          l_int32        linearInterpolatePixelFloat()
+ *
+ *    Thresholding to 1 bpp Pix
+ *          PIX           *fpixThresholdToPix()
+ *
+ *    Generate function from components
+ *          FPIX          *pixComponentFunction()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*--------------------------------------------------------------------*
+ *                     FPix  <-->  Pix conversions                    *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixConvertToFPix()
+ *
+ *      Input:  pix (1, 2, 4, 8, 16 or 32 bpp)
+ *              ncomps (number of components: 3 for RGB, 1 otherwise)
+ *      Return: fpix, or null on error
+ *
+ *  Notes:
+ *      (1) If colormapped, remove to grayscale.
+ *      (2) If 32 bpp and @ncomps == 3, this is RGB; convert to luminance.
+ *          In all other cases the src image is treated as having a single
+ *          component of pixel values.
+ */
+FPIX *
+pixConvertToFPix(PIX     *pixs,
+                 l_int32  ncomps)
+{
+l_int32     w, h, d, i, j, val, wplt, wpld;
+l_uint32    uval;
+l_uint32   *datat, *linet;
+l_float32  *datad, *lined;
+PIX        *pixt;
+FPIX       *fpixd;
+
+    PROCNAME("pixConvertToFPix");
+
+    if (!pixs)
+        return (FPIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+           /* Convert to a single component */
+    if (pixGetColormap(pixs))
+        pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else if (pixGetDepth(pixs) == 32 && ncomps == 3)
+        pixt = pixConvertRGBToLuminance(pixs);
+    else
+        pixt = pixClone(pixs);
+    pixGetDimensions(pixt, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32) {
+        pixDestroy(&pixt);
+        return (FPIX *)ERROR_PTR("invalid depth", procName, NULL);
+    }
+
+    if ((fpixd = fpixCreate(w, h)) == NULL)
+        return (FPIX *)ERROR_PTR("fpixd not made", procName, NULL);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+    datad = fpixGetData(fpixd);
+    wpld = fpixGetWpl(fpixd);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        if (d == 1) {
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_BIT(linet, j);
+                lined[j] = (l_float32)val;
+            }
+        } else if (d == 2) {
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_DIBIT(linet, j);
+                lined[j] = (l_float32)val;
+            }
+        } else if (d == 4) {
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_QBIT(linet, j);
+                lined[j] = (l_float32)val;
+            }
+        } else if (d == 8) {
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_BYTE(linet, j);
+                lined[j] = (l_float32)val;
+            }
+        } else if (d == 16) {
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_TWO_BYTES(linet, j);
+                lined[j] = (l_float32)val;
+            }
+        } else {  /* d == 32 */
+            for (j = 0; j < w; j++) {
+                uval = GET_DATA_FOUR_BYTES(linet, j);
+                lined[j] = (l_float32)uval;
+            }
+        }
+    }
+
+    pixDestroy(&pixt);
+    return fpixd;
+}
+
+
+/*!
+ *  pixConvertToDPix()
+ *
+ *      Input:  pix (1, 2, 4, 8, 16 or 32 bpp)
+ *              ncomps (number of components: 3 for RGB, 1 otherwise)
+ *      Return: dpix, or null on error
+ *
+ *  Notes:
+ *      (1) If colormapped, remove to grayscale.
+ *      (2) If 32 bpp and @ncomps == 3, this is RGB; convert to luminance.
+ *          In all other cases the src image is treated as having a single
+ *          component of pixel values.
+ */
+DPIX *
+pixConvertToDPix(PIX     *pixs,
+                 l_int32  ncomps)
+{
+l_int32     w, h, d, i, j, val, wplt, wpld;
+l_uint32    uval;
+l_uint32   *datat, *linet;
+l_float64  *datad, *lined;
+PIX        *pixt;
+DPIX       *dpixd;
+
+    PROCNAME("pixConvertToDPix");
+
+    if (!pixs)
+        return (DPIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+           /* Convert to a single component */
+    if (pixGetColormap(pixs))
+        pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else if (pixGetDepth(pixs) == 32 && ncomps == 3)
+        pixt = pixConvertRGBToLuminance(pixs);
+    else
+        pixt = pixClone(pixs);
+    pixGetDimensions(pixt, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32) {
+        pixDestroy(&pixt);
+        return (DPIX *)ERROR_PTR("invalid depth", procName, NULL);
+    }
+
+    if ((dpixd = dpixCreate(w, h)) == NULL)
+        return (DPIX *)ERROR_PTR("dpixd not made", procName, NULL);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+    datad = dpixGetData(dpixd);
+    wpld = dpixGetWpl(dpixd);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        if (d == 1) {
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_BIT(linet, j);
+                lined[j] = (l_float64)val;
+            }
+        } else if (d == 2) {
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_DIBIT(linet, j);
+                lined[j] = (l_float64)val;
+            }
+        } else if (d == 4) {
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_QBIT(linet, j);
+                lined[j] = (l_float64)val;
+            }
+        } else if (d == 8) {
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_BYTE(linet, j);
+                lined[j] = (l_float64)val;
+            }
+        } else if (d == 16) {
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_TWO_BYTES(linet, j);
+                lined[j] = (l_float64)val;
+            }
+        } else {  /* d == 32 */
+            for (j = 0; j < w; j++) {
+                uval = GET_DATA_FOUR_BYTES(linet, j);
+                lined[j] = (l_float64)uval;
+            }
+        }
+    }
+
+    pixDestroy(&pixt);
+    return dpixd;
+}
+
+
+/*!
+ *  fpixConvertToPix()
+ *
+ *      Input:  fpixs
+ *              outdepth (0, 8, 16 or 32 bpp)
+ *              negvals (L_CLIP_TO_ZERO, L_TAKE_ABSVAL)
+ *              errorflag (1 to output error stats; 0 otherwise)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Use @outdepth = 0 to programmatically determine the
+ *          output depth.  If no values are greater than 255,
+ *          it will set outdepth = 8; otherwise to 16 or 32.
+ *      (2) Because we are converting a float to an unsigned int
+ *          with a specified dynamic range (8, 16 or 32 bits), errors
+ *          can occur.  If errorflag == TRUE, output the number
+ *          of values out of range, both negative and positive.
+ *      (3) If a pixel value is positive and out of range, clip to
+ *          the maximum value represented at the outdepth of 8, 16
+ *          or 32 bits.
+ */
+PIX *
+fpixConvertToPix(FPIX    *fpixs,
+                 l_int32  outdepth,
+                 l_int32  negvals,
+                 l_int32  errorflag)
+{
+l_int32     w, h, i, j, wpls, wpld, maxval;
+l_uint32    vald;
+l_float32   val;
+l_float32  *datas, *lines;
+l_uint32   *datad, *lined;
+PIX        *pixd;
+
+    PROCNAME("fpixConvertToPix");
+
+    if (!fpixs)
+        return (PIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+    if (negvals != L_CLIP_TO_ZERO && negvals != L_TAKE_ABSVAL)
+        return (PIX *)ERROR_PTR("invalid negvals", procName, NULL);
+    if (outdepth != 0 && outdepth != 8 && outdepth != 16 && outdepth != 32)
+        return (PIX *)ERROR_PTR("outdepth not in {0,8,16,32}", procName, NULL);
+
+    fpixGetDimensions(fpixs, &w, &h);
+    datas = fpixGetData(fpixs);
+    wpls = fpixGetWpl(fpixs);
+
+        /* Adaptive determination of output depth */
+    if (outdepth == 0) {
+        outdepth = 8;
+        for (i = 0; i < h && outdepth < 32; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < w && outdepth < 32; j++) {
+                if (lines[j] > 65535.5)
+                    outdepth = 32;
+                else if (lines[j] > 255.5)
+                    outdepth = 16;
+            }
+        }
+    }
+    maxval = (1 << outdepth) - 1;
+
+        /* Gather statistics if @errorflag = TRUE */
+    if (errorflag) {
+        l_int32  negs = 0;
+        l_int32  overvals = 0;
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < w; j++) {
+                val = lines[j];
+                if (val < 0.0)
+                    negs++;
+                else if (val > maxval)
+                    overvals++;
+            }
+        }
+        if (negs > 0)
+            L_ERROR("Number of negative values: %d\n", procName, negs);
+        if (overvals > 0)
+            L_ERROR("Number of too-large values: %d\n", procName, overvals);
+    }
+
+        /* Make the pix and convert the data */
+    if ((pixd = pixCreate(w, h, outdepth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = lines[j];
+            if (val >= 0.0)
+                vald = (l_uint32)(val + 0.5);
+            else if (negvals == L_CLIP_TO_ZERO)  /* and val < 0.0 */
+                vald = 0;
+            else
+                vald = (l_uint32)(-val + 0.5);
+            if (vald > maxval)
+                vald = maxval;
+
+            if (outdepth == 8)
+                SET_DATA_BYTE(lined, j, vald);
+            else if (outdepth == 16)
+                SET_DATA_TWO_BYTES(lined, j, vald);
+            else  /* outdepth == 32 */
+                SET_DATA_FOUR_BYTES(lined, j, vald);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  fpixDisplayMaxDynamicRange()
+ *
+ *      Input:  fpixs
+ *      Return: pixd (8 bpp), or null on error
+ */
+PIX *
+fpixDisplayMaxDynamicRange(FPIX  *fpixs)
+{
+l_uint8     dval;
+l_int32     i, j, w, h, wpls, wpld;
+l_float32   factor, sval, maxval;
+l_float32  *lines, *datas;
+l_uint32   *lined, *datad;
+PIX        *pixd;
+
+    PROCNAME("fpixDisplayMaxDynamicRange");
+
+    if (!fpixs)
+        return (PIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+
+    fpixGetDimensions(fpixs, &w, &h);
+    datas = fpixGetData(fpixs);
+    wpls = fpixGetWpl(fpixs);
+
+    maxval = 0.0;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        for (j = 0; j < w; j++) {
+            sval = *(lines + j);
+            if (sval > maxval)
+                maxval = sval;
+        }
+    }
+
+    pixd = pixCreate(w, h, 8);
+    if (maxval == 0.0)
+        return pixd;  /* all pixels are 0 */
+
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    factor = 255. / maxval;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            sval = *(lines + j);
+            if (sval < 0.0) sval = 0.0;
+            dval = (l_uint8)(factor * sval + 0.5);
+            SET_DATA_BYTE(lined, j, dval);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  fpixConvertToDPix()
+ *
+ *      Input:  fpix
+ *      Return: dpix, or null on error
+ */
+DPIX *
+fpixConvertToDPix(FPIX  *fpix)
+{
+l_int32     w, h, i, j, wpls, wpld;
+l_float32   val;
+l_float32  *datas, *lines;
+l_float64  *datad, *lined;
+DPIX       *dpix;
+
+    PROCNAME("fpixConvertToDPix");
+
+    if (!fpix)
+        return (DPIX *)ERROR_PTR("fpix not defined", procName, NULL);
+
+    fpixGetDimensions(fpix, &w, &h);
+    if ((dpix = dpixCreate(w, h)) == NULL)
+        return (DPIX *)ERROR_PTR("dpix not made", procName, NULL);
+
+    datas = fpixGetData(fpix);
+    datad = dpixGetData(dpix);
+    wpls = fpixGetWpl(fpix);
+    wpld = dpixGetWpl(dpix);  /* 8 byte words */
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = lines[j];
+            lined[j] = val;
+        }
+    }
+
+    return dpix;
+}
+
+
+/*!
+ *  dpixConvertToPix()
+ *
+ *      Input:  dpixs
+ *              outdepth (0, 8, 16 or 32 bpp)
+ *              negvals (L_CLIP_TO_ZERO, L_TAKE_ABSVAL)
+ *              errorflag (1 to output error stats; 0 otherwise)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Use @outdepth = 0 to programmatically determine the
+ *          output depth.  If no values are greater than 255,
+ *          it will set outdepth = 8; otherwise to 16 or 32.
+ *      (2) Because we are converting a float to an unsigned int
+ *          with a specified dynamic range (8, 16 or 32 bits), errors
+ *          can occur.  If errorflag == TRUE, output the number
+ *          of values out of range, both negative and positive.
+ *      (3) If a pixel value is positive and out of range, clip to
+ *          the maximum value represented at the outdepth of 8, 16
+ *          or 32 bits.
+ */
+PIX *
+dpixConvertToPix(DPIX    *dpixs,
+                 l_int32  outdepth,
+                 l_int32  negvals,
+                 l_int32  errorflag)
+{
+l_int32     w, h, i, j, wpls, wpld, maxval;
+l_uint32    vald;
+l_float64   val;
+l_float64  *datas, *lines;
+l_uint32   *datad, *lined;
+PIX        *pixd;
+
+    PROCNAME("dpixConvertToPix");
+
+    if (!dpixs)
+        return (PIX *)ERROR_PTR("dpixs not defined", procName, NULL);
+    if (negvals != L_CLIP_TO_ZERO && negvals != L_TAKE_ABSVAL)
+        return (PIX *)ERROR_PTR("invalid negvals", procName, NULL);
+    if (outdepth != 0 && outdepth != 8 && outdepth != 16 && outdepth != 32)
+        return (PIX *)ERROR_PTR("outdepth not in {0,8,16,32}", procName, NULL);
+
+    dpixGetDimensions(dpixs, &w, &h);
+    datas = dpixGetData(dpixs);
+    wpls = dpixGetWpl(dpixs);
+
+        /* Adaptive determination of output depth */
+    if (outdepth == 0) {
+        outdepth = 8;
+        for (i = 0; i < h && outdepth < 32; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < w && outdepth < 32; j++) {
+                if (lines[j] > 65535.5)
+                    outdepth = 32;
+                else if (lines[j] > 255.5)
+                    outdepth = 16;
+            }
+        }
+    }
+    maxval = (1 << outdepth) - 1;
+
+        /* Gather statistics if @errorflag = TRUE */
+    if (errorflag) {
+        l_int32  negs = 0;
+        l_int32  overvals = 0;
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < w; j++) {
+                val = lines[j];
+                if (val < 0.0)
+                    negs++;
+                else if (val > maxval)
+                    overvals++;
+            }
+        }
+        if (negs > 0)
+            L_ERROR("Number of negative values: %d\n", procName, negs);
+        if (overvals > 0)
+            L_ERROR("Number of too-large values: %d\n", procName, overvals);
+    }
+
+        /* Make the pix and convert the data */
+    if ((pixd = pixCreate(w, h, outdepth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = lines[j];
+            if (val >= 0.0) {
+                vald = (l_uint32)(val + 0.5);
+            } else {  /* val < 0.0 */
+                if (negvals == L_CLIP_TO_ZERO)
+                    vald = 0;
+                else
+                    vald = (l_uint32)(-val + 0.5);
+            }
+            if (vald > maxval)
+                vald = maxval;
+            if (outdepth == 8)
+                SET_DATA_BYTE(lined, j, vald);
+            else if (outdepth == 16)
+                SET_DATA_TWO_BYTES(lined, j, vald);
+            else  /* outdepth == 32 */
+                SET_DATA_FOUR_BYTES(lined, j, vald);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  dpixConvertToFPix()
+ *
+ *      Input:  dpix
+ *      Return: fpix, or null on error
+ */
+FPIX *
+dpixConvertToFPix(DPIX  *dpix)
+{
+l_int32     w, h, i, j, wpls, wpld;
+l_float64   val;
+l_float32  *datad, *lined;
+l_float64  *datas, *lines;
+FPIX       *fpix;
+
+    PROCNAME("dpixConvertToFPix");
+
+    if (!dpix)
+        return (FPIX *)ERROR_PTR("dpix not defined", procName, NULL);
+
+    dpixGetDimensions(dpix, &w, &h);
+    if ((fpix = fpixCreate(w, h)) == NULL)
+        return (FPIX *)ERROR_PTR("fpix not made", procName, NULL);
+
+    datas = dpixGetData(dpix);
+    datad = fpixGetData(fpix);
+    wpls = dpixGetWpl(dpix);  /* 8 byte words */
+    wpld = fpixGetWpl(fpix);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = lines[j];
+            lined[j] = (l_float32)val;
+        }
+    }
+
+    return fpix;
+}
+
+
+
+/*--------------------------------------------------------------------*
+ *                           Min/max value                            *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixGetMin()
+ *
+ *      Input:  fpix
+ *              &minval (<optional return> min value)
+ *              &xminloc (<optional return> x location of min)
+ *              &yminloc (<optional return> y location of min)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+fpixGetMin(FPIX       *fpix,
+           l_float32  *pminval,
+           l_int32    *pxminloc,
+           l_int32    *pyminloc)
+{
+l_int32     i, j, w, h, wpl, xminloc, yminloc;
+l_float32  *data, *line;
+l_float32   minval;
+
+    PROCNAME("fpixGetMin");
+
+    if (!pminval && !pxminloc && !pyminloc)
+        return ERROR_INT("no return val requested", procName, 1);
+    if (pminval) *pminval = 0.0;
+    if (pxminloc) *pxminloc = 0;
+    if (pyminloc) *pyminloc = 0;
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+    minval = +1.0e20;
+    xminloc = 0;
+    yminloc = 0;
+    fpixGetDimensions(fpix, &w, &h);
+    data = fpixGetData(fpix);
+    wpl = fpixGetWpl(fpix);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            if (line[j] < minval) {
+                minval = line[j];
+                xminloc = j;
+                yminloc = i;
+            }
+        }
+    }
+
+    if (pminval) *pminval = minval;
+    if (pxminloc) *pxminloc = xminloc;
+    if (pyminloc) *pyminloc = yminloc;
+    return 0;
+}
+
+
+/*!
+ *  fpixGetMax()
+ *
+ *      Input:  fpix
+ *              &maxval (<optional return> max value)
+ *              &xmaxloc (<optional return> x location of max)
+ *              &ymaxloc (<optional return> y location of max)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+fpixGetMax(FPIX       *fpix,
+           l_float32  *pmaxval,
+           l_int32    *pxmaxloc,
+           l_int32    *pymaxloc)
+{
+l_int32     i, j, w, h, wpl, xmaxloc, ymaxloc;
+l_float32  *data, *line;
+l_float32   maxval;
+
+    PROCNAME("fpixGetMax");
+
+    if (!pmaxval && !pxmaxloc && !pymaxloc)
+        return ERROR_INT("no return val requested", procName, 1);
+    if (pmaxval) *pmaxval = 0.0;
+    if (pxmaxloc) *pxmaxloc = 0;
+    if (pymaxloc) *pymaxloc = 0;
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+    maxval = -1.0e20;
+    xmaxloc = 0;
+    ymaxloc = 0;
+    fpixGetDimensions(fpix, &w, &h);
+    data = fpixGetData(fpix);
+    wpl = fpixGetWpl(fpix);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            if (line[j] > maxval) {
+                maxval = line[j];
+                xmaxloc = j;
+                ymaxloc = i;
+            }
+        }
+    }
+
+    if (pmaxval) *pmaxval = maxval;
+    if (pxmaxloc) *pxmaxloc = xmaxloc;
+    if (pymaxloc) *pymaxloc = ymaxloc;
+    return 0;
+}
+
+
+/*!
+ *  dpixGetMin()
+ *
+ *      Input:  dpix
+ *              &minval (<optional return> min value)
+ *              &xminloc (<optional return> x location of min)
+ *              &yminloc (<optional return> y location of min)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+dpixGetMin(DPIX       *dpix,
+           l_float64  *pminval,
+           l_int32    *pxminloc,
+           l_int32    *pyminloc)
+{
+l_int32     i, j, w, h, wpl, xminloc, yminloc;
+l_float64  *data, *line;
+l_float64   minval;
+
+    PROCNAME("dpixGetMin");
+
+    if (!pminval && !pxminloc && !pyminloc)
+        return ERROR_INT("no return val requested", procName, 1);
+    if (pminval) *pminval = 0.0;
+    if (pxminloc) *pxminloc = 0;
+    if (pyminloc) *pyminloc = 0;
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+
+    minval = +1.0e300;
+    xminloc = 0;
+    yminloc = 0;
+    dpixGetDimensions(dpix, &w, &h);
+    data = dpixGetData(dpix);
+    wpl = dpixGetWpl(dpix);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            if (line[j] < minval) {
+                minval = line[j];
+                xminloc = j;
+                yminloc = i;
+            }
+        }
+    }
+
+    if (pminval) *pminval = minval;
+    if (pxminloc) *pxminloc = xminloc;
+    if (pyminloc) *pyminloc = yminloc;
+    return 0;
+}
+
+
+/*!
+ *  dpixGetMax()
+ *
+ *      Input:  dpix
+ *              &maxval (<optional return> max value)
+ *              &xmaxloc (<optional return> x location of max)
+ *              &ymaxloc (<optional return> y location of max)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+dpixGetMax(DPIX       *dpix,
+           l_float64  *pmaxval,
+           l_int32    *pxmaxloc,
+           l_int32    *pymaxloc)
+{
+l_int32     i, j, w, h, wpl, xmaxloc, ymaxloc;
+l_float64  *data, *line;
+l_float64   maxval;
+
+    PROCNAME("dpixGetMax");
+
+    if (!pmaxval && !pxmaxloc && !pymaxloc)
+        return ERROR_INT("no return val requested", procName, 1);
+    if (pmaxval) *pmaxval = 0.0;
+    if (pxmaxloc) *pxmaxloc = 0;
+    if (pymaxloc) *pymaxloc = 0;
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+
+    maxval = -1.0e20;
+    xmaxloc = 0;
+    ymaxloc = 0;
+    dpixGetDimensions(dpix, &w, &h);
+    data = dpixGetData(dpix);
+    wpl = dpixGetWpl(dpix);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            if (line[j] > maxval) {
+                maxval = line[j];
+                xmaxloc = j;
+                ymaxloc = i;
+            }
+        }
+    }
+
+    if (pmaxval) *pmaxval = maxval;
+    if (pxmaxloc) *pxmaxloc = xmaxloc;
+    if (pymaxloc) *pymaxloc = ymaxloc;
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                       Special integer scaling                      *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixScaleByInteger()
+ *
+ *      Input:  fpixs (low resolution, subsampled)
+ *              factor (scaling factor)
+ *      Return: fpixd (interpolated result), or null on error
+ *
+ *  Notes:
+ *      (1) The width wd of fpixd is related to ws of fpixs by:
+ *              wd = factor * (ws - 1) + 1   (and ditto for the height)
+ *          We avoid special-casing boundary pixels in the interpolation
+ *          by constructing fpixd by inserting (factor - 1) interpolated
+ *          pixels between each pixel in fpixs.  Then
+ *               wd = ws + (ws - 1) * (factor - 1)    (same as above)
+ *          This also has the advantage that if we subsample by @factor,
+ *          throwing out all the interpolated pixels, we regain the
+ *          original low resolution fpix.
+ */
+FPIX *
+fpixScaleByInteger(FPIX    *fpixs,
+                   l_int32  factor)
+{
+l_int32     i, j, k, m, ws, hs, wd, hd, wpls, wpld;
+l_float32   val0, val1, val2, val3;
+l_float32  *datas, *datad, *lines, *lined, *fract;
+FPIX       *fpixd;
+
+    PROCNAME("fpixScaleByInteger");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+
+    fpixGetDimensions(fpixs, &ws, &hs);
+    wd = factor * (ws - 1) + 1;
+    hd = factor * (hs - 1) + 1;
+    fpixd = fpixCreate(wd, hd);
+    datas = fpixGetData(fpixs);
+    datad = fpixGetData(fpixd);
+    wpls = fpixGetWpl(fpixs);
+    wpld = fpixGetWpl(fpixd);
+    fract = (l_float32 *)LEPT_CALLOC(factor, sizeof(l_float32));
+    for (i = 0; i < factor; i++)
+        fract[i] = i / (l_float32)factor;
+    for (i = 0; i < hs - 1; i++) {
+        lines = datas + i * wpls;
+        for (j = 0; j < ws - 1; j++) {
+            val0 = lines[j];
+            val1 = lines[j + 1];
+            val2 = lines[wpls + j];
+            val3 = lines[wpls + j + 1];
+            for (k = 0; k < factor; k++) {  /* rows of sub-block */
+                lined = datad + (i * factor + k) * wpld;
+                for (m = 0; m < factor; m++) {  /* cols of sub-block */
+                     lined[j * factor + m] =
+                            val0 * (1.0 - fract[m]) * (1.0 - fract[k]) +
+                            val1 * fract[m] * (1.0 - fract[k]) +
+                            val2 * (1.0 - fract[m]) * fract[k] +
+                            val3 * fract[m] * fract[k];
+                }
+            }
+        }
+    }
+
+        /* Do the right-most column of fpixd, skipping LR corner */
+    for (i = 0; i < hs - 1; i++) {
+        lines = datas + i * wpls;
+        val0 = lines[ws - 1];
+        val1 = lines[wpls + ws - 1];
+        for (k = 0; k < factor; k++) {
+            lined = datad + (i * factor + k) * wpld;
+            lined[wd - 1] = val0 * (1.0 - fract[k]) + val1 * fract[k];
+        }
+    }
+
+        /* Do the bottom-most row of fpixd */
+    lines = datas + (hs - 1) * wpls;
+    lined = datad + (hd - 1) * wpld;
+    for (j = 0; j < ws - 1; j++) {
+        val0 = lines[j];
+        val1 = lines[j + 1];
+        for (m = 0; m < factor; m++)
+            lined[j * factor + m] = val0 * (1.0 - fract[m]) + val1 * fract[m];
+        lined[wd - 1] = lines[ws - 1];  /* LR corner */
+    }
+
+    LEPT_FREE(fract);
+    return fpixd;
+}
+
+
+/*!
+ *  dpixScaleByInteger()
+ *
+ *      Input:  dpixs (low resolution, subsampled)
+ *              factor (scaling factor)
+ *      Return: dpixd (interpolated result), or null on error
+ *
+ *  Notes:
+ *      (1) The width wd of dpixd is related to ws of dpixs by:
+ *              wd = factor * (ws - 1) + 1   (and ditto for the height)
+ *          We avoid special-casing boundary pixels in the interpolation
+ *          by constructing fpixd by inserting (factor - 1) interpolated
+ *          pixels between each pixel in fpixs.  Then
+ *               wd = ws + (ws - 1) * (factor - 1)    (same as above)
+ *          This also has the advantage that if we subsample by @factor,
+ *          throwing out all the interpolated pixels, we regain the
+ *          original low resolution dpix.
+ */
+DPIX *
+dpixScaleByInteger(DPIX    *dpixs,
+                   l_int32  factor)
+{
+l_int32     i, j, k, m, ws, hs, wd, hd, wpls, wpld;
+l_float64   val0, val1, val2, val3;
+l_float64  *datas, *datad, *lines, *lined, *fract;
+DPIX       *dpixd;
+
+    PROCNAME("dpixScaleByInteger");
+
+    if (!dpixs)
+        return (DPIX *)ERROR_PTR("dpixs not defined", procName, NULL);
+
+    dpixGetDimensions(dpixs, &ws, &hs);
+    wd = factor * (ws - 1) + 1;
+    hd = factor * (hs - 1) + 1;
+    dpixd = dpixCreate(wd, hd);
+    datas = dpixGetData(dpixs);
+    datad = dpixGetData(dpixd);
+    wpls = dpixGetWpl(dpixs);
+    wpld = dpixGetWpl(dpixd);
+    fract = (l_float64 *)LEPT_CALLOC(factor, sizeof(l_float64));
+    for (i = 0; i < factor; i++)
+        fract[i] = i / (l_float64)factor;
+    for (i = 0; i < hs - 1; i++) {
+        lines = datas + i * wpls;
+        for (j = 0; j < ws - 1; j++) {
+            val0 = lines[j];
+            val1 = lines[j + 1];
+            val2 = lines[wpls + j];
+            val3 = lines[wpls + j + 1];
+            for (k = 0; k < factor; k++) {  /* rows of sub-block */
+                lined = datad + (i * factor + k) * wpld;
+                for (m = 0; m < factor; m++) {  /* cols of sub-block */
+                     lined[j * factor + m] =
+                            val0 * (1.0 - fract[m]) * (1.0 - fract[k]) +
+                            val1 * fract[m] * (1.0 - fract[k]) +
+                            val2 * (1.0 - fract[m]) * fract[k] +
+                            val3 * fract[m] * fract[k];
+                }
+            }
+        }
+    }
+
+        /* Do the right-most column of dpixd, skipping LR corner */
+    for (i = 0; i < hs - 1; i++) {
+        lines = datas + i * wpls;
+        val0 = lines[ws - 1];
+        val1 = lines[wpls + ws - 1];
+        for (k = 0; k < factor; k++) {
+            lined = datad + (i * factor + k) * wpld;
+            lined[wd - 1] = val0 * (1.0 - fract[k]) + val1 * fract[k];
+        }
+    }
+
+        /* Do the bottom-most row of dpixd */
+    lines = datas + (hs - 1) * wpls;
+    lined = datad + (hd - 1) * wpld;
+    for (j = 0; j < ws - 1; j++) {
+        val0 = lines[j];
+        val1 = lines[j + 1];
+        for (m = 0; m < factor; m++)
+            lined[j * factor + m] = val0 * (1.0 - fract[m]) + val1 * fract[m];
+        lined[wd - 1] = lines[ws - 1];  /* LR corner */
+    }
+
+    LEPT_FREE(fract);
+    return dpixd;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                        Arithmetic operations                       *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixLinearCombination()
+ *
+ *      Input:  fpixd (<optional>; this can be null, equal to fpixs1, or
+ *                     different from fpixs1)
+ *              fpixs1 (can be == to fpixd)
+ *              fpixs2
+ *              a, b (multiplication factors on fpixs1 and fpixs2, rsp.)
+ *      Return: fpixd always
+ *
+ *  Notes:
+ *      (1) Computes pixelwise linear combination: a * src1 + b * src2
+ *      (2) Alignment is to UL corner.
+ *      (3) There are 3 cases.  The result can go to a new dest,
+ *          in-place to fpixs1, or to an existing input dest:
+ *          * fpixd == null:   (src1 + src2) --> new fpixd
+ *          * fpixd == fpixs1:  (src1 + src2) --> src1  (in-place)
+ *          * fpixd != fpixs1: (src1 + src2) --> input fpixd
+ *      (4) fpixs2 must be different from both fpixd and fpixs1.
+ */
+FPIX *
+fpixLinearCombination(FPIX      *fpixd,
+                      FPIX      *fpixs1,
+                      FPIX      *fpixs2,
+                      l_float32  a,
+                      l_float32  b)
+{
+l_int32     i, j, ws, hs, w, h, wpls, wpld;
+l_float32  *datas, *datad, *lines, *lined;
+
+    PROCNAME("fpixLinearCombination");
+
+    if (!fpixs1)
+        return (FPIX *)ERROR_PTR("fpixs1 not defined", procName, fpixd);
+    if (!fpixs2)
+        return (FPIX *)ERROR_PTR("fpixs2 not defined", procName, fpixd);
+    if (fpixs1 == fpixs2)
+        return (FPIX *)ERROR_PTR("fpixs1 == fpixs2", procName, fpixd);
+    if (fpixs2 == fpixd)
+        return (FPIX *)ERROR_PTR("fpixs2 == fpixd", procName, fpixd);
+
+    if (fpixs1 != fpixd)
+        fpixd = fpixCopy(fpixd, fpixs1);
+
+    datas = fpixGetData(fpixs2);
+    datad = fpixGetData(fpixd);
+    wpls = fpixGetWpl(fpixs2);
+    wpld = fpixGetWpl(fpixd);
+    fpixGetDimensions(fpixs2, &ws, &hs);
+    fpixGetDimensions(fpixd, &w, &h);
+    w = L_MIN(ws, w);
+    h = L_MIN(hs, h);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++)
+            lined[j] = a * lined[j] + b * lines[j];
+    }
+
+    return fpixd;
+}
+
+
+/*!
+ *  fpixAddMultConstant()
+ *
+ *      Input:  fpix
+ *              addc  (use 0.0 to skip the operation)
+ *              multc (use 1.0 to skip the operation)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place operation.
+ *      (2) It can be used to multiply each pixel by a constant,
+ *          and also to add a constant to each pixel.  Multiplication
+ *          is done first.
+ */
+l_int32
+fpixAddMultConstant(FPIX      *fpix,
+                    l_float32  addc,
+                    l_float32  multc)
+{
+l_int32     i, j, w, h, wpl;
+l_float32  *line, *data;
+
+    PROCNAME("fpixAddMultConstant");
+
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+    if (addc == 0.0 && multc == 1.0)
+        return 0;
+
+    fpixGetDimensions(fpix, &w, &h);
+    data = fpixGetData(fpix);
+    wpl = fpixGetWpl(fpix);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        if (addc == 0.0) {
+            for (j = 0; j < w; j++)
+                line[j] *= multc;
+        } else if (multc == 1.0) {
+            for (j = 0; j < w; j++)
+                line[j] += addc;
+        } else {
+            for (j = 0; j < w; j++) {
+                line[j] = multc * line[j] + addc;
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  dpixLinearCombination()
+ *
+ *      Input:  dpixd (<optional>; this can be null, equal to dpixs1, or
+ *                     different from dpixs1)
+ *              dpixs1 (can be == to dpixd)
+ *              dpixs2
+ *              a, b (multiplication factors on dpixs1 and dpixs2, rsp.)
+ *      Return: dpixd always
+ *
+ *  Notes:
+ *      (1) Computes pixelwise linear combination: a * src1 + b * src2
+ *      (2) Alignment is to UL corner.
+ *      (3) There are 3 cases.  The result can go to a new dest,
+ *          in-place to dpixs1, or to an existing input dest:
+ *          * dpixd == null:   (src1 + src2) --> new dpixd
+ *          * dpixd == dpixs1:  (src1 + src2) --> src1  (in-place)
+ *          * dpixd != dpixs1: (src1 + src2) --> input dpixd
+ *      (4) dpixs2 must be different from both dpixd and dpixs1.
+ */
+DPIX *
+dpixLinearCombination(DPIX      *dpixd,
+                      DPIX      *dpixs1,
+                      DPIX      *dpixs2,
+                      l_float32  a,
+                      l_float32  b)
+{
+l_int32     i, j, ws, hs, w, h, wpls, wpld;
+l_float64  *datas, *datad, *lines, *lined;
+
+    PROCNAME("dpixLinearCombination");
+
+    if (!dpixs1)
+        return (DPIX *)ERROR_PTR("dpixs1 not defined", procName, dpixd);
+    if (!dpixs2)
+        return (DPIX *)ERROR_PTR("dpixs2 not defined", procName, dpixd);
+    if (dpixs1 == dpixs2)
+        return (DPIX *)ERROR_PTR("dpixs1 == dpixs2", procName, dpixd);
+    if (dpixs2 == dpixd)
+        return (DPIX *)ERROR_PTR("dpixs2 == dpixd", procName, dpixd);
+
+    if (dpixs1 != dpixd)
+        dpixd = dpixCopy(dpixd, dpixs1);
+
+    datas = dpixGetData(dpixs2);
+    datad = dpixGetData(dpixd);
+    wpls = dpixGetWpl(dpixs2);
+    wpld = dpixGetWpl(dpixd);
+    dpixGetDimensions(dpixs2, &ws, &hs);
+    dpixGetDimensions(dpixd, &w, &h);
+    w = L_MIN(ws, w);
+    h = L_MIN(hs, h);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++)
+            lined[j] = a * lined[j] + b * lines[j];
+    }
+
+    return dpixd;
+}
+
+
+/*!
+ *  dpixAddMultConstant()
+ *
+ *      Input:  dpix
+ *              addc  (use 0.0 to skip the operation)
+ *              multc (use 1.0 to skip the operation)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place operation.
+ *      (2) It can be used to multiply each pixel by a constant,
+ *          and also to add a constant to each pixel.  Multiplication
+ *          is done first.
+ */
+l_int32
+dpixAddMultConstant(DPIX      *dpix,
+                    l_float64  addc,
+                    l_float64  multc)
+{
+l_int32     i, j, w, h, wpl;
+l_float64  *line, *data;
+
+    PROCNAME("dpixAddMultConstant");
+
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+
+    if (addc == 0.0 && multc == 1.0)
+        return 0;
+
+    dpixGetDimensions(dpix, &w, &h);
+    data = dpixGetData(dpix);
+    wpl = dpixGetWpl(dpix);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        if (addc == 0.0) {
+            for (j = 0; j < w; j++)
+                line[j] *= multc;
+        } else if (multc == 1.0) {
+            for (j = 0; j < w; j++)
+                line[j] += addc;
+        } else {
+            for (j = 0; j < w; j++)
+                line[j] = multc * line[j] + addc;
+        }
+    }
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                              Set all                               *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixSetAllArbitrary()
+ *
+ *      Input:  fpix
+ *              val (to set at each pixel)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+fpixSetAllArbitrary(FPIX      *fpix,
+                    l_float32  inval)
+{
+l_int32     i, j, w, h;
+l_float32  *data, *line;
+
+    PROCNAME("fpixSetAllArbitrary");
+
+    if (!fpix)
+        return ERROR_INT("fpix not defined", procName, 1);
+
+    fpixGetDimensions(fpix, &w, &h);
+    data = fpixGetData(fpix);
+    for (i = 0; i < h; i++) {
+        line = data + i * w;
+        for (j = 0; j < w; j++)
+            *(line + j) = inval;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  dpixSetAllArbitrary()
+ *
+ *      Input:  dpix
+ *              val (to set at each pixel)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+dpixSetAllArbitrary(DPIX      *dpix,
+                    l_float64  inval)
+{
+l_int32     i, j, w, h;
+l_float64  *data, *line;
+
+    PROCNAME("dpixSetAllArbitrary");
+
+    if (!dpix)
+        return ERROR_INT("dpix not defined", procName, 1);
+
+    dpixGetDimensions(dpix, &w, &h);
+    data = dpixGetData(dpix);
+    for (i = 0; i < h; i++) {
+        line = data + i * w;
+        for (j = 0; j < w; j++)
+            *(line + j) = inval;
+    }
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                          Border functions                          *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixAddBorder()
+ *
+ *      Input:  fpixs
+ *              left, right, top, bot (pixels on each side to be added)
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) Adds border of '0' 32-bit pixels
+ */
+FPIX *
+fpixAddBorder(FPIX    *fpixs,
+              l_int32  left,
+              l_int32  right,
+              l_int32  top,
+              l_int32  bot)
+{
+l_int32  ws, hs, wd, hd;
+FPIX    *fpixd;
+
+    PROCNAME("fpixAddBorder");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+
+    if (left <= 0 && right <= 0 && top <= 0 && bot <= 0)
+        return fpixCopy(NULL, fpixs);
+    fpixGetDimensions(fpixs, &ws, &hs);
+    wd = ws + left + right;
+    hd = hs + top + bot;
+    if ((fpixd = fpixCreate(wd, hd)) == NULL)
+        return (FPIX *)ERROR_PTR("fpixd not made", procName, NULL);
+
+    fpixCopyResolution(fpixd, fpixs);
+    fpixRasterop(fpixd, left, top, ws, hs, fpixs, 0, 0);
+    return fpixd;
+}
+
+
+/*!
+ *  fpixRemoveBorder()
+ *
+ *      Input:  fpixs
+ *              left, right, top, bot (pixels on each side to be removed)
+ *      Return: fpixd, or null on error
+ */
+FPIX *
+fpixRemoveBorder(FPIX    *fpixs,
+                 l_int32  left,
+                 l_int32  right,
+                 l_int32  top,
+                 l_int32  bot)
+{
+l_int32  ws, hs, wd, hd;
+FPIX    *fpixd;
+
+    PROCNAME("fpixRemoveBorder");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+
+    if (left <= 0 && right <= 0 && top <= 0 && bot <= 0)
+        return fpixCopy(NULL, fpixs);
+    fpixGetDimensions(fpixs, &ws, &hs);
+    wd = ws - left - right;
+    hd = hs - top - bot;
+    if (wd <= 0 || hd <= 0)
+        return (FPIX *)ERROR_PTR("width & height not both > 0", procName, NULL);
+    if ((fpixd = fpixCreate(wd, hd)) == NULL)
+        return (FPIX *)ERROR_PTR("fpixd not made", procName, NULL);
+
+    fpixCopyResolution(fpixd, fpixs);
+    fpixRasterop(fpixd, 0, 0, wd, hd, fpixs, left, top);
+    return fpixd;
+}
+
+
+
+/*!
+ *  fpixAddMirroredBorder()
+ *
+ *      Input:  fpixs
+ *              left, right, top, bot (pixels on each side to be added)
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) See pixAddMirroredBorder() for situations of usage.
+ */
+FPIX *
+fpixAddMirroredBorder(FPIX    *fpixs,
+                      l_int32  left,
+                      l_int32  right,
+                      l_int32  top,
+                      l_int32  bot)
+{
+l_int32  i, j, w, h;
+FPIX    *fpixd;
+
+    PROCNAME("fpixAddMirroredBorder");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+
+    fpixd = fpixAddBorder(fpixs, left, right, top, bot);
+    fpixGetDimensions(fpixs, &w, &h);
+    for (j = 0; j < left; j++)
+        fpixRasterop(fpixd, left - 1 - j, top, 1, h,
+                     fpixd, left + j, top);
+    for (j = 0; j < right; j++)
+        fpixRasterop(fpixd, left + w + j, top, 1, h,
+                     fpixd, left + w - 1 - j, top);
+    for (i = 0; i < top; i++)
+        fpixRasterop(fpixd, 0, top - 1 - i, left + w + right, 1,
+                     fpixd, 0, top + i);
+    for (i = 0; i < bot; i++)
+        fpixRasterop(fpixd, 0, top + h + i, left + w + right, 1,
+                     fpixd, 0, top + h - 1 - i);
+
+    return fpixd;
+}
+
+
+/*!
+ *  fpixAddContinuedBorder()
+ *
+ *      Input:  fpixs
+ *              left, right, top, bot (pixels on each side to be added)
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) This adds pixels on each side whose values are equal to
+ *          the value on the closest boundary pixel.
+ */
+FPIX *
+fpixAddContinuedBorder(FPIX    *fpixs,
+                       l_int32  left,
+                       l_int32  right,
+                       l_int32  top,
+                       l_int32  bot)
+{
+l_int32  i, j, w, h;
+FPIX    *fpixd;
+
+    PROCNAME("fpixAddContinuedBorder");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+
+    fpixd = fpixAddBorder(fpixs, left, right, top, bot);
+    fpixGetDimensions(fpixs, &w, &h);
+    for (j = 0; j < left; j++)
+        fpixRasterop(fpixd, j, top, 1, h, fpixd, left, top);
+    for (j = 0; j < right; j++)
+        fpixRasterop(fpixd, left + w + j, top, 1, h, fpixd, left + w - 1, top);
+    for (i = 0; i < top; i++)
+        fpixRasterop(fpixd, 0, i, left + w + right, 1, fpixd, 0, top);
+    for (i = 0; i < bot; i++)
+        fpixRasterop(fpixd, 0, top + h + i, left + w + right, 1,
+                     fpixd, 0, top + h - 1);
+
+    return fpixd;
+}
+
+
+/*!
+ *  fpixAddSlopeBorder()
+ *
+ *      Input:  fpixs
+ *              left, right, top, bot (pixels on each side to be added)
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) This adds pixels on each side whose values have a normal
+ *          derivative equal to the normal derivative at the boundary
+ *          of fpixs.
+ */
+FPIX *
+fpixAddSlopeBorder(FPIX    *fpixs,
+                   l_int32  left,
+                   l_int32  right,
+                   l_int32  top,
+                   l_int32  bot)
+{
+l_int32    i, j, w, h, fullw, fullh;
+l_float32  val1, val2, del;
+FPIX      *fpixd;
+
+    PROCNAME("fpixAddSlopeBorder");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+
+    fpixd = fpixAddBorder(fpixs, left, right, top, bot);
+    fpixGetDimensions(fpixs, &w, &h);
+
+        /* Left */
+    for (i = top; i < top + h; i++) {
+        fpixGetPixel(fpixd, left, i, &val1);
+        fpixGetPixel(fpixd, left + 1, i, &val2);
+        del = val1 - val2;
+        for (j = 0; j < left; j++)
+            fpixSetPixel(fpixd, j, i, val1 + del * (left - j));
+    }
+
+        /* Right */
+    fullw = left + w + right;
+    for (i = top; i < top + h; i++) {
+        fpixGetPixel(fpixd, left + w - 1, i, &val1);
+        fpixGetPixel(fpixd, left + w - 2, i, &val2);
+        del = val1 - val2;
+        for (j = left + w; j < fullw; j++)
+            fpixSetPixel(fpixd, j, i, val1 + del * (j - left - w + 1));
+    }
+
+        /* Top */
+    for (j = 0; j < fullw; j++) {
+        fpixGetPixel(fpixd, j, top, &val1);
+        fpixGetPixel(fpixd, j, top + 1, &val2);
+        del = val1 - val2;
+        for (i = 0; i < top; i++)
+            fpixSetPixel(fpixd, j, i, val1 + del * (top - i));
+    }
+
+        /* Bottom */
+    fullh = top + h + bot;
+    for (j = 0; j < fullw; j++) {
+        fpixGetPixel(fpixd, j, top + h - 1, &val1);
+        fpixGetPixel(fpixd, j, top + h - 2, &val2);
+        del = val1 - val2;
+        for (i = top + h; i < fullh; i++)
+            fpixSetPixel(fpixd, j, i, val1 + del * (i - top - h + 1));
+    }
+
+    return fpixd;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                          Simple rasterop                           *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixRasterop()
+ *
+ *      Input:  fpixd  (dest fpix)
+ *              dx     (x val of UL corner of dest rectangle)
+ *              dy     (y val of UL corner of dest rectangle)
+ *              dw     (width of dest rectangle)
+ *              dh     (height of dest rectangle)
+ *              fpixs  (src fpix)
+ *              sx     (x val of UL corner of src rectangle)
+ *              sy     (y val of UL corner of src rectangle)
+ *      Return: 0 if OK; 1 on error.
+ *
+ *  Notes:
+ *      (1) This is similar in structure to pixRasterop(), except
+ *          it only allows copying from the source into the destination.
+ *          For that reason, no op code is necessary.  Additionally,
+ *          all pixels are 32 bit words (float values), which makes
+ *          the copy very simple.
+ *      (2) Clipping of both src and dest fpix are done automatically.
+ *      (3) This allows in-place copying, without checking to see if
+ *          the result is valid:  use for in-place with caution!
+ */
+l_int32
+fpixRasterop(FPIX    *fpixd,
+             l_int32  dx,
+             l_int32  dy,
+             l_int32  dw,
+             l_int32  dh,
+             FPIX    *fpixs,
+             l_int32  sx,
+             l_int32  sy)
+{
+l_int32     fsw, fsh, fdw, fdh, dhangw, shangw, dhangh, shangh;
+l_int32     i, j, wpls, wpld;
+l_float32  *datas, *datad, *lines, *lined;
+
+    PROCNAME("fpixRasterop");
+
+    if (!fpixs)
+        return ERROR_INT("fpixs not defined", procName, 1);
+    if (!fpixd)
+        return ERROR_INT("fpixd not defined", procName, 1);
+
+    /* -------------------------------------------------------- *
+     *      Clip to maximum rectangle with both src and dest    *
+     * -------------------------------------------------------- */
+    fpixGetDimensions(fpixs, &fsw, &fsh);
+    fpixGetDimensions(fpixd, &fdw, &fdh);
+
+        /* First clip horizontally (sx, dx, dw) */
+    if (dx < 0) {
+        sx -= dx;  /* increase sx */
+        dw += dx;  /* reduce dw */
+        dx = 0;
+    }
+    if (sx < 0) {
+        dx -= sx;  /* increase dx */
+        dw += sx;  /* reduce dw */
+        sx = 0;
+    }
+    dhangw = dx + dw - fdw;  /* rect overhang of dest to right */
+    if (dhangw > 0)
+        dw -= dhangw;  /* reduce dw */
+    shangw = sx + dw - fsw;   /* rect overhang of src to right */
+    if (shangw > 0)
+        dw -= shangw;  /* reduce dw */
+
+        /* Then clip vertically (sy, dy, dh) */
+    if (dy < 0) {
+        sy -= dy;  /* increase sy */
+        dh += dy;  /* reduce dh */
+        dy = 0;
+    }
+    if (sy < 0) {
+        dy -= sy;  /* increase dy */
+        dh += sy;  /* reduce dh */
+        sy = 0;
+    }
+    dhangh = dy + dh - fdh;  /* rect overhang of dest below */
+    if (dhangh > 0)
+        dh -= dhangh;  /* reduce dh */
+    shangh = sy + dh - fsh;  /* rect overhang of src below */
+    if (shangh > 0)
+        dh -= shangh;  /* reduce dh */
+
+        /* if clipped entirely, quit */
+    if ((dw <= 0) || (dh <= 0))
+        return 0;
+
+    /* -------------------------------------------------------- *
+     *                    Copy block of data                    *
+     * -------------------------------------------------------- */
+    datas = fpixGetData(fpixs);
+    datad = fpixGetData(fpixd);
+    wpls = fpixGetWpl(fpixs);
+    wpld = fpixGetWpl(fpixd);
+    datas += sy * wpls + sx;  /* at UL corner of block */
+    datad += dy * wpld + dx;  /* at UL corner of block */
+    for (i = 0; i < dh; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < dw; j++) {
+            *lined = *lines;
+            lines++;
+            lined++;
+        }
+    }
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                   Rotation by multiples of 90 degrees              *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixRotateOrth()
+ *
+ *      Input:  fpixs
+ *              quads (0-3; number of 90 degree cw rotations)
+ *      Return: fpixd, or null on error
+ */
+FPIX *
+fpixRotateOrth(FPIX     *fpixs,
+               l_int32  quads)
+{
+    PROCNAME("fpixRotateOrth");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+    if (quads < 0 || quads > 3)
+        return (FPIX *)ERROR_PTR("quads not in {0,1,2,3}", procName, NULL);
+
+    if (quads == 0)
+        return fpixCopy(NULL, fpixs);
+    else if (quads == 1)
+        return fpixRotate90(fpixs, 1);
+    else if (quads == 2)
+        return fpixRotate180(NULL, fpixs);
+    else /* quads == 3 */
+        return fpixRotate90(fpixs, -1);
+}
+
+
+/*!
+ *  fpixRotate180()
+ *
+ *      Input:  fpixd  (<optional>; can be null, equal to fpixs,
+ *                      or different from fpixs)
+ *              fpixs
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) This does a 180 rotation of the image about the center,
+ *          which is equivalent to a left-right flip about a vertical
+ *          line through the image center, followed by a top-bottom
+ *          flip about a horizontal line through the image center.
+ *      (2) There are 3 cases for input:
+ *          (a) fpixd == null (creates a new fpixd)
+ *          (b) fpixd == fpixs (in-place operation)
+ *          (c) fpixd != fpixs (existing fpixd)
+ *      (3) For clarity, use these three patterns, respectively:
+ *          (a) fpixd = fpixRotate180(NULL, fpixs);
+ *          (b) fpixRotate180(fpixs, fpixs);
+ *          (c) fpixRotate180(fpixd, fpixs);
+ */
+FPIX *
+fpixRotate180(FPIX  *fpixd,
+              FPIX  *fpixs)
+{
+    PROCNAME("fpixRotate180");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+
+        /* Prepare pixd for in-place operation */
+    if ((fpixd = fpixCopy(fpixd, fpixs)) == NULL)
+        return (FPIX *)ERROR_PTR("fpixd not made", procName, NULL);
+
+    fpixFlipLR(fpixd, fpixd);
+    fpixFlipTB(fpixd, fpixd);
+    return fpixd;
+}
+
+
+/*!
+ *  fpixRotate90()
+ *
+ *      Input:  fpixs
+ *              direction (1 = clockwise,  -1 = counter-clockwise)
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) This does a 90 degree rotation of the image about the center,
+ *          either cw or ccw, returning a new pix.
+ *      (2) The direction must be either 1 (cw) or -1 (ccw).
+ */
+FPIX *
+fpixRotate90(FPIX    *fpixs,
+             l_int32  direction)
+{
+l_int32     i, j, wd, hd, wpls, wpld;
+l_float32  *datas, *datad, *lines, *lined;
+FPIX       *fpixd;
+
+    PROCNAME("fpixRotate90");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+    if (direction != 1 && direction != -1)
+        return (FPIX *)ERROR_PTR("invalid direction", procName, NULL);
+
+    fpixGetDimensions(fpixs, &hd, &wd);
+    if ((fpixd = fpixCreate(wd, hd)) == NULL)
+        return (FPIX *)ERROR_PTR("fpixd not made", procName, NULL);
+    fpixCopyResolution(fpixd, fpixs);
+
+    datas = fpixGetData(fpixs);
+    wpls = fpixGetWpl(fpixs);
+    datad = fpixGetData(fpixd);
+    wpld = fpixGetWpl(fpixd);
+    if (direction == 1) {  /* clockwise */
+        for (i = 0; i < hd; i++) {
+            lined = datad + i * wpld;
+            lines = datas + (wd - 1) * wpls;
+            for (j = 0; j < wd; j++) {
+                lined[j] = lines[i];
+                lines -= wpls;
+            }
+        }
+    } else {  /* ccw */
+        for (i = 0; i < hd; i++) {
+            lined = datad + i * wpld;
+            lines = datas;
+            for (j = 0; j < wd; j++) {
+                lined[j] = lines[hd - 1 - i];
+                lines += wpls;
+            }
+        }
+    }
+
+    return fpixd;
+}
+
+
+/*!
+ *  pixFlipLR()
+ *
+ *      Input:  fpixd (<optional>; can be null, equal to fpixs,
+ *                     or different from fpixs)
+ *              fpixs
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) This does a left-right flip of the image, which is
+ *          equivalent to a rotation out of the plane about a
+ *          vertical line through the image center.
+ *      (2) There are 3 cases for input:
+ *          (a) fpixd == null (creates a new fpixd)
+ *          (b) fpixd == fpixs (in-place operation)
+ *          (c) fpixd != fpixs (existing fpixd)
+ *      (3) For clarity, use these three patterns, respectively:
+ *          (a) fpixd = fpixFlipLR(NULL, fpixs);
+ *          (b) fpixFlipLR(fpixs, fpixs);
+ *          (c) fpixFlipLR(fpixd, fpixs);
+ *      (4) If an existing fpixd is not the same size as fpixs, the
+ *          image data will be reallocated.
+ */
+FPIX *
+fpixFlipLR(FPIX  *fpixd,
+           FPIX  *fpixs)
+{
+l_int32     i, j, w, h, wpl, bpl;
+l_float32  *line, *data, *buffer;
+
+    PROCNAME("fpixFlipLR");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+
+    fpixGetDimensions(fpixs, &w, &h);
+
+        /* Prepare fpixd for in-place operation */
+    if ((fpixd = fpixCopy(fpixd, fpixs)) == NULL)
+        return (FPIX *)ERROR_PTR("fpixd not made", procName, NULL);
+
+    data = fpixGetData(fpixd);
+    wpl = fpixGetWpl(fpixd);  /* 4-byte words */
+    bpl = 4 * wpl;
+    if ((buffer = (l_float32 *)LEPT_CALLOC(wpl, sizeof(l_float32))) == NULL)
+        return (FPIX *)ERROR_PTR("buffer not made", procName, NULL);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        memcpy(buffer, line, bpl);
+        for (j = 0; j < w; j++)
+            line[j] = buffer[w - 1 - j];
+    }
+    LEPT_FREE(buffer);
+    return fpixd;
+}
+
+
+/*!
+ *  fpixFlipTB()
+ *
+ *      Input:  fpixd (<optional>; can be null, equal to fpixs,
+ *                     or different from fpixs)
+ *              fpixs
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) This does a top-bottom flip of the image, which is
+ *          equivalent to a rotation out of the plane about a
+ *          horizontal line through the image center.
+ *      (2) There are 3 cases for input:
+ *          (a) fpixd == null (creates a new fpixd)
+ *          (b) fpixd == fpixs (in-place operation)
+ *          (c) fpixd != fpixs (existing fpixd)
+ *      (3) For clarity, use these three patterns, respectively:
+ *          (a) fpixd = fpixFlipTB(NULL, fpixs);
+ *          (b) fpixFlipTB(fpixs, fpixs);
+ *          (c) fpixFlipTB(fpixd, fpixs);
+ *      (4) If an existing fpixd is not the same size as fpixs, the
+ *          image data will be reallocated.
+ */
+FPIX *
+fpixFlipTB(FPIX  *fpixd,
+           FPIX  *fpixs)
+{
+l_int32     i, k, h, h2, wpl, bpl;
+l_float32  *linet, *lineb, *data, *buffer;
+
+    PROCNAME("fpixFlipTB");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+
+        /* Prepare fpixd for in-place operation */
+    if ((fpixd = fpixCopy(fpixd, fpixs)) == NULL)
+        return (FPIX *)ERROR_PTR("fpixd not made", procName, NULL);
+
+    data = fpixGetData(fpixd);
+    wpl = fpixGetWpl(fpixd);
+    fpixGetDimensions(fpixd, NULL, &h);
+    if ((buffer = (l_float32 *)LEPT_CALLOC(wpl, sizeof(l_float32))) == NULL)
+        return (FPIX *)ERROR_PTR("buffer not made", procName, NULL);
+    h2 = h / 2;
+    bpl = 4 * wpl;
+    for (i = 0, k = h - 1; i < h2; i++, k--) {
+        linet = data + i * wpl;
+        lineb = data + k * wpl;
+        memcpy(buffer, linet, bpl);
+        memcpy(linet, lineb, bpl);
+        memcpy(lineb, buffer, bpl);
+    }
+    LEPT_FREE(buffer);
+    return fpixd;
+}
+
+
+/*--------------------------------------------------------------------*
+ *            Affine and projective interpolated transforms           *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixAffinePta()
+ *
+ *      Input:  fpixs (8 bpp)
+ *              ptad  (4 pts of final coordinate space)
+ *              ptas  (4 pts of initial coordinate space)
+ *              border (size of extension with constant normal derivative)
+ *              inval (value brought in; typ. 0)
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) If @border > 0, all four sides are extended by that distance,
+ *          and removed after the transformation is finished.  Pixels
+ *          that would be brought in to the trimmed result from outside
+ *          the extended region are assigned @inval.  The purpose of
+ *          extending the image is to avoid such assignments.
+ *      (2) On the other hand, you may want to give all pixels that
+ *          are brought in from outside fpixs a specific value.  In that
+ *          case, set @border == 0.
+ */
+FPIX *
+fpixAffinePta(FPIX      *fpixs,
+              PTA       *ptad,
+              PTA       *ptas,
+              l_int32    border,
+              l_float32  inval)
+{
+l_float32  *vc;
+PTA        *ptas2, *ptad2;
+FPIX       *fpixs2, *fpixd, *fpixd2;
+
+    PROCNAME("fpixAffinePta");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+    if (!ptas)
+        return (FPIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (FPIX *)ERROR_PTR("ptad not defined", procName, NULL);
+
+        /* If a border is to be added, also translate the ptas */
+    if (border > 0) {
+        ptas2 = ptaTransform(ptas, border, border, 1.0, 1.0);
+        ptad2 = ptaTransform(ptad, border, border, 1.0, 1.0);
+        fpixs2 = fpixAddSlopeBorder(fpixs, border, border, border, border);
+    } else {
+        ptas2 = ptaClone(ptas);
+        ptad2 = ptaClone(ptad);
+        fpixs2 = fpixClone(fpixs);
+    }
+
+        /* Get backwards transform from dest to src, and apply it */
+    getAffineXformCoeffs(ptad2, ptas2, &vc);
+    fpixd2 = fpixAffine(fpixs2, vc, inval);
+    fpixDestroy(&fpixs2);
+    ptaDestroy(&ptas2);
+    ptaDestroy(&ptad2);
+    LEPT_FREE(vc);
+
+    if (border == 0)
+        return fpixd2;
+
+        /* Remove the added border */
+    fpixd = fpixRemoveBorder(fpixd2, border, border, border, border);
+    fpixDestroy(&fpixd2);
+    return fpixd;
+}
+
+
+/*!
+ *  fpixAffine()
+ *
+ *      Input:  fpixs (8 bpp)
+ *              vc  (vector of 8 coefficients for projective transformation)
+ *              inval (value brought in; typ. 0)
+ *      Return: fpixd, or null on error
+ */
+FPIX *
+fpixAffine(FPIX       *fpixs,
+           l_float32  *vc,
+           l_float32   inval)
+{
+l_int32     i, j, w, h, wpld;
+l_float32   val;
+l_float32  *datas, *datad, *lined;
+l_float32   x, y;
+FPIX       *fpixd;
+
+    PROCNAME("fpixAffine");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+    fpixGetDimensions(fpixs, &w, &h);
+    if (!vc)
+        return (FPIX *)ERROR_PTR("vc not defined", procName, NULL);
+
+    datas = fpixGetData(fpixs);
+    fpixd = fpixCreateTemplate(fpixs);
+    fpixSetAllArbitrary(fpixd, inval);
+    datad = fpixGetData(fpixd);
+    wpld = fpixGetWpl(fpixd);
+
+        /* Iterate over destination pixels */
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+                /* Compute float src pixel location corresponding to (i,j) */
+            affineXformPt(vc, j, i, &x, &y);
+            linearInterpolatePixelFloat(datas, w, h, x, y, inval, &val);
+            *(lined + j) = val;
+        }
+    }
+
+    return fpixd;
+}
+
+
+/*!
+ *  fpixProjectivePta()
+ *
+ *      Input:  fpixs (8 bpp)
+ *              ptad  (4 pts of final coordinate space)
+ *              ptas  (4 pts of initial coordinate space)
+ *              border (size of extension with constant normal derivative)
+ *              inval (value brought in; typ. 0)
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) If @border > 0, all four sides are extended by that distance,
+ *          and removed after the transformation is finished.  Pixels
+ *          that would be brought in to the trimmed result from outside
+ *          the extended region are assigned @inval.  The purpose of
+ *          extending the image is to avoid such assignments.
+ *      (2) On the other hand, you may want to give all pixels that
+ *          are brought in from outside fpixs a specific value.  In that
+ *          case, set @border == 0.
+ */
+FPIX *
+fpixProjectivePta(FPIX      *fpixs,
+                  PTA       *ptad,
+                  PTA       *ptas,
+                  l_int32    border,
+                  l_float32  inval)
+{
+l_float32  *vc;
+PTA        *ptas2, *ptad2;
+FPIX       *fpixs2, *fpixd, *fpixd2;
+
+    PROCNAME("fpixProjectivePta");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+    if (!ptas)
+        return (FPIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (FPIX *)ERROR_PTR("ptad not defined", procName, NULL);
+
+        /* If a border is to be added, also translate the ptas */
+    if (border > 0) {
+        ptas2 = ptaTransform(ptas, border, border, 1.0, 1.0);
+        ptad2 = ptaTransform(ptad, border, border, 1.0, 1.0);
+        fpixs2 = fpixAddSlopeBorder(fpixs, border, border, border, border);
+    } else {
+        ptas2 = ptaClone(ptas);
+        ptad2 = ptaClone(ptad);
+        fpixs2 = fpixClone(fpixs);
+    }
+
+        /* Get backwards transform from dest to src, and apply it */
+    getProjectiveXformCoeffs(ptad2, ptas2, &vc);
+    fpixd2 = fpixProjective(fpixs2, vc, inval);
+    fpixDestroy(&fpixs2);
+    ptaDestroy(&ptas2);
+    ptaDestroy(&ptad2);
+    LEPT_FREE(vc);
+
+    if (border == 0)
+        return fpixd2;
+
+        /* Remove the added border */
+    fpixd = fpixRemoveBorder(fpixd2, border, border, border, border);
+    fpixDestroy(&fpixd2);
+    return fpixd;
+}
+
+
+/*!
+ *  fpixProjective()
+ *
+ *      Input:  fpixs (8 bpp)
+ *              vc  (vector of 8 coefficients for projective transformation)
+ *              inval (value brought in; typ. 0)
+ *      Return: fpixd, or null on error
+ */
+FPIX *
+fpixProjective(FPIX       *fpixs,
+               l_float32  *vc,
+               l_float32   inval)
+{
+l_int32     i, j, w, h, wpld;
+l_float32   val;
+l_float32  *datas, *datad, *lined;
+l_float32   x, y;
+FPIX       *fpixd;
+
+    PROCNAME("fpixProjective");
+
+    if (!fpixs)
+        return (FPIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+    fpixGetDimensions(fpixs, &w, &h);
+    if (!vc)
+        return (FPIX *)ERROR_PTR("vc not defined", procName, NULL);
+
+    datas = fpixGetData(fpixs);
+    fpixd = fpixCreateTemplate(fpixs);
+    fpixSetAllArbitrary(fpixd, inval);
+    datad = fpixGetData(fpixd);
+    wpld = fpixGetWpl(fpixd);
+
+        /* Iterate over destination pixels */
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+                /* Compute float src pixel location corresponding to (i,j) */
+            projectiveXformPt(vc, j, i, &x, &y);
+            linearInterpolatePixelFloat(datas, w, h, x, y, inval, &val);
+            *(lined + j) = val;
+        }
+    }
+
+    return fpixd;
+}
+
+
+/*!
+ *  linearInterpolatePixelFloat()
+ *
+ *      Input:  datas (ptr to beginning of float image data)
+ *              wpls (32-bit word/line for this data array)
+ *              w, h (of image)
+ *              x, y (floating pt location for evaluation)
+ *              inval (float value brought in from the outside when the
+ *                     input x,y location is outside the image)
+ *              &val (<return> interpolated float value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a standard linear interpolation function.  It is
+ *          equivalent to area weighting on each component, and
+ *          avoids "jaggies" when rendering sharp edges.
+ */
+l_int32
+linearInterpolatePixelFloat(l_float32  *datas,
+                            l_int32     w,
+                            l_int32     h,
+                            l_float32   x,
+                            l_float32   y,
+                            l_float32   inval,
+                            l_float32  *pval)
+{
+l_int32     xpm, ypm, xp, yp, xf, yf;
+l_float32   v00, v01, v10, v11;
+l_float32  *lines;
+
+    PROCNAME("linearInterpolatePixelFloat");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = inval;
+    if (!datas)
+        return ERROR_INT("datas not defined", procName, 1);
+
+        /* Skip if off the edge */
+    if (x < 0.0 || y < 0.0 || x > w - 2.0 || y > h - 2.0)
+        return 0;
+
+    xpm = (l_int32)(16.0 * x + 0.5);
+    ypm = (l_int32)(16.0 * y + 0.5);
+    xp = xpm >> 4;
+    yp = ypm >> 4;
+    xf = xpm & 0x0f;
+    yf = ypm & 0x0f;
+
+#if  DEBUG
+    if (xf < 0 || yf < 0)
+        fprintf(stderr, "xp = %d, yp = %d, xf = %d, yf = %d\n", xp, yp, xf, yf);
+#endif  /* DEBUG */
+
+        /* Interpolate by area weighting. */
+    lines = datas + yp * w;
+    v00 = (16.0 - xf) * (16.0 - yf) * (*(lines + xp));
+    v10 = xf * (16.0 - yf) * (*(lines + xp + 1));
+    v01 = (16.0 - xf) * yf * (*(lines + w + xp));
+    v11 = xf * yf * (*(lines + w + xp + 1));
+    *pval = (v00 + v01 + v10 + v11) / 256.0;
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                      Thresholding to 1 bpp Pix                     *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fpixThresholdToPix()
+ *
+ *      Input:  fpix
+ *              thresh
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) For all values of fpix that are <= thresh, sets the pixel
+ *          in pixd to 1.
+ */
+PIX *
+fpixThresholdToPix(FPIX      *fpix,
+                   l_float32  thresh)
+{
+l_int32     i, j, w, h, wpls, wpld;
+l_float32  *datas, *lines;
+l_uint32   *datad, *lined;
+PIX        *pixd;
+
+    PROCNAME("fpixThresholdToPix");
+
+    if (!fpix)
+        return (PIX *)ERROR_PTR("fpix not defined", procName, NULL);
+
+    fpixGetDimensions(fpix, &w, &h);
+    datas = fpixGetData(fpix);
+    wpls = fpixGetWpl(fpix);
+    pixd = pixCreate(w, h, 1);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            if (lines[j] <= thresh)
+                SET_DATA_BIT(lined, j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                Generate function from components                   *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixComponentFunction()
+ *
+ *      Input:  pix (32 bpp rgb)
+ *              rnum, gnum, bnum (coefficients for numerator)
+ *              rdenom, gdenom, bdenom (coefficients for denominator)
+ *      Return: fpixd, or null on error
+ *
+ *  Notes:
+ *      (1) This stores a function of the component values of each
+ *          input pixel in @fpixd.
+ *      (2) The function is a ratio of linear combinations of component values.
+ *          There are two special cases for denominator coefficients:
+ *          (a) The denominator is 1.0: input 0 for all denominator coefficients
+ *          (b) Only one component is used in the denominator: input 1.0
+ *              for that denominator component and 0.0 for the other two.
+ *      (3) If the denominator is 0, multiply by an arbitrary number that
+ *          is much larger than 1.  Choose 256 "arbitrarily".
+ *
+ */
+FPIX *
+pixComponentFunction(PIX       *pix,
+                     l_float32  rnum,
+                     l_float32  gnum,
+                     l_float32  bnum,
+                     l_float32  rdenom,
+                     l_float32  gdenom,
+                     l_float32  bdenom)
+{
+l_int32     i, j, w, h, wpls, wpld, rval, gval, bval, zerodenom, onedenom;
+l_float32   fnum, fdenom;
+l_uint32   *datas, *lines;
+l_float32  *datad, *lined, *recip;
+FPIX       *fpixd;
+
+    PROCNAME("pixComponentFunction");
+
+    if (!pix || pixGetDepth(pix) != 32)
+        return (FPIX *)ERROR_PTR("pix undefined or not 32 bpp", procName, NULL);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    datas = pixGetData(pix);
+    wpls = pixGetWpl(pix);
+    fpixd = fpixCreate(w, h);
+    datad = fpixGetData(fpixd);
+    wpld = fpixGetWpl(fpixd);
+    zerodenom = (rdenom == 0.0 && gdenom == 0.0 && bdenom == 0.0) ? 1: 0;
+    onedenom = ((rdenom == 1.0 && gdenom == 0.0 && bdenom == 0.0) ||
+                (rdenom == 0.0 && gdenom == 1.0 && bdenom == 0.0) ||
+                (rdenom == 0.0 && gdenom == 0.0 && bdenom == 1.0)) ? 1 : 0;
+    recip = NULL;
+    if (onedenom) {
+        recip = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32));
+        recip[0] = 256;  /* arbitrary large number */
+        for (i = 1; i < 256; i++)
+            recip[i] = 1.0 / (l_float32)i;
+    }
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        if (zerodenom) {
+            for (j = 0; j < w; j++) {
+                extractRGBValues(lines[j], &rval, &gval, &bval);
+                lined[j] = rnum * rval + gnum * gval + bnum * bval;
+            }
+        } else if (onedenom && rdenom == 1.0) {
+            for (j = 0; j < w; j++) {
+                extractRGBValues(lines[j], &rval, &gval, &bval);
+                lined[j]
+                    = recip[rval] * (rnum * rval + gnum * gval + bnum * bval);
+            }
+        } else if (onedenom && gdenom == 1.0) {
+            for (j = 0; j < w; j++) {
+                extractRGBValues(lines[j], &rval, &gval, &bval);
+                lined[j]
+                    = recip[gval] * (rnum * rval + gnum * gval + bnum * bval);
+            }
+        } else if (onedenom && bdenom == 1.0) {
+            for (j = 0; j < w; j++) {
+                extractRGBValues(lines[j], &rval, &gval, &bval);
+                lined[j]
+                    = recip[bval] * (rnum * rval + gnum * gval + bnum * bval);
+            }
+        } else {  /* general case */
+            for (j = 0; j < w; j++) {
+                extractRGBValues(lines[j], &rval, &gval, &bval);
+                fnum = rnum * rval + gnum * gval + bnum * bval;
+                fdenom = rdenom * rval + gdenom * gval + bdenom * bval;
+                lined[j] = (fdenom == 0) ? 256.0 * fnum : fnum / fdenom;
+            }
+        }
+    }
+
+    LEPT_FREE(recip);
+    return fpixd;
+}
+
diff --git a/src/freetype.c b/src/freetype.c
new file mode 100644 (file)
index 0000000..7c2dce0
--- /dev/null
@@ -0,0 +1,436 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ * freetype.c
+ *      static l_int32       ftUtfToUniChar()
+ *      static PIX          *ftDrawBitmap()
+ *             FT_LIBRARY   *ftInitLibrary()
+ *             void          ftShutdownLibrary()
+ *             PIX          *pixWriteTTFText()
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+#include <ft2build.h>
+#include FT_LEPT_FREETYPE_H
+#include FT_GLYPH_H
+
+#undef MAX
+#define MAX(a, b) (((a)>(b))?(a):(b))
+
+#define ROUNDUPDOWN(val, updown) (!updown) ? (val < 0 ? ((val - 63) >> 6) : val >> 6) : (val > 0 ? ((val + 63) >> 6) : val >> 6)
+
+struct ft_library_st {
+    FT_Library library;
+};
+
+static l_int32
+ftUtfToUniChar(char     *str,
+               l_int32  *chPtr)
+{
+    l_int32 byte;
+
+        /* HTML4.0 entities in decimal form, e.g. &#197; {{{ */
+    byte = *((unsigned char *) str);
+    if (byte == '&') {
+        l_int32 i, n = 0;
+
+        byte = *((unsigned char *) (str+1));
+        if (byte == '#') {
+            for (i = 2; i < 8; i++) {
+                byte = *((unsigned char *) (str+i));
+                if (byte >= '0' && byte <= '9') {
+                    n = (n * 10) + (byte - '0');
+                } else {
+                    break;
+                }
+            }
+            if (byte == ';') {
+                *chPtr = (l_int32) n;
+                return ++i;
+            }
+        }
+    }
+    /* }}} */
+
+        /* Unroll 1 to 3 byte UTF-8 sequences */
+
+    byte = *((unsigned char *) str);
+    if (byte < 0xC0) {
+            /* Handles properly formed UTF-8 characters between 0x01 and 0x7F.
+             * Also treats \0 and naked trail bytes 0x80 to 0xBF as valid
+             * characters representing themselves. */
+        *chPtr = (l_int32) byte;
+        return 1;
+    } else if (byte < 0xE0) {
+        if ((str[1] & 0xC0) == 0x80) {
+                /* Two-byte-character lead-byte followed by a trail-byte. */
+            *chPtr = (l_int32) (((byte & 0x1F) << 6) | (str[1] & 0x3F));
+            return 2;
+        }
+            /* A two-byte-character lead-byte not followed by trail-byte
+             * represents itself.  */
+        *chPtr = (l_int32) byte;
+        return 1;
+    } else if (byte < 0xF0) {
+        if (((str[1] & 0xC0) == 0x80) && ((str[2] & 0xC0) == 0x80)) {
+                /* Three-byte-character lead byte followed by 2 trail bytes. */
+            *chPtr = (l_int32) (((byte & 0x0F) << 12) |
+                                ((str[1] & 0x3F) << 6) | (str[2] & 0x3F));
+            return 3;
+        }
+
+            /* A three-byte-character lead-byte not followed by two
+             * trail-bytes represents itself. */
+        *chPtr = (l_int32) byte;
+        return 1;
+    }
+
+    *chPtr = (l_int32)byte;
+    return 1;
+}
+/* }}} */
+
+
+static PIX *
+ftDrawBitmap(l_uint32  *datad,
+             l_uint32   color,
+             FT_Bitmap  bitmap,
+             l_int32    pen_x,
+             l_int32    pen_y,
+             l_int32    width,
+             l_int32    height)
+{
+l_uint32 *ppixel = NULL, pixel;
+l_int32 x, y, row, col, pc, pcr, i;
+l_uint8 tmp;
+
+    PROCNAME("ftDrawBitmap");
+
+    for (row = 0; row < bitmap.rows; row++) {
+        pc = row * bitmap.pitch;
+        pcr = pc;
+        y = pen_y + row;
+
+            /* Clip if out of bounds */
+        if (y >= height || y < 0) {
+            continue;
+        }
+
+        for (col = 0; col < bitmap.width; col++, pc++) {
+            int level;
+            if (bitmap.pixel_mode == ft_pixel_mode_grays) {
+                level = (bitmap.buffer[pc] * 127/ (bitmap.num_grays - 1));
+            } else if (bitmap.pixel_mode == ft_pixel_mode_mono) {
+                level = ((bitmap.buffer[(col>>3)+pcr]) & (1<<(~col&0x07))) ?
+                    127 : 0;
+            } else {
+                return (PIX *)ERROR_PTR("unsupported ft_pixel mode",
+                                        procName, NULL);
+            }
+
+            if (color >= 0) {
+                level = level * (127 - GET_DATA_BYTE(&color, L_ALPHA_CHANNEL))
+                    / 127;
+            }
+
+            level = 127 - level;
+            x = pen_x + col;
+
+                /* Clip if out of bounds */
+            if (x >= width || x < 0) {
+                continue;
+            }
+            ppixel = datad + y*width + x;
+
+                /* Mix 2 colors using level as alpha */
+            if (level != 127) {
+                l_uint8 new, old;
+
+                pixel = *ppixel;
+                for (i = 0; i < 3; i++) {
+                    new = GET_DATA_BYTE(&color, i);
+                    old = GET_DATA_BYTE(&pixel, i);
+                    tmp = (double)old * ((double)level/127) +
+                           (double)new * ((double)(127 - level)/127);
+                    SET_DATA_BYTE(ppixel, i, tmp);
+                }
+           }
+        }
+    }
+    return NULL;
+}
+
+
+FT_LIBRARY *
+ftInitLibrary(void)
+{
+FT_Error err;
+FT_LIBRARY *lib_ptr;
+
+    lib_ptr = CALLOC(1, sizeof(FT_LIBRARY));
+
+    err = FT_Init_FreeType(&lib_ptr->library);
+    if (err) {
+        LEPT_FREE(lib_ptr);
+        return NULL;
+    }
+    return lib_ptr;
+}
+
+
+void
+ftShutdownLibrary(FT_LIBRARY  *lib_ptr)
+{
+    if (lib_ptr) {
+        FT_Done_FreeType(lib_ptr->library);
+        LEPT_FREE(lib_ptr);
+    }
+}
+
+
+PIX *
+pixWriteTTFText(FT_LIBRARY  *lib_ptr,
+                PIX         *pixs,
+                l_float32    size,
+                l_float32    angle,
+                l_int32      x,
+                l_int32      y,
+                l_int32      letter_space,
+                l_uint32     color,
+                l_uint8     *fontfile,
+                l_uint8     *text,
+                l_int32      text_len,
+                l_int32     *brect)
+{
+PIX *pixd, *pixt = NULL;
+FT_Error err;
+FT_Face face;
+FT_Glyph image;
+FT_BitmapGlyph bitmap;
+FT_CharMap charmap;
+FT_Matrix matrix;
+FT_Vector pen, penf;
+FT_UInt glyph_index, previous;
+FT_BBox char_bbox, bbox;
+l_uint32 *datad, letter_space_x, letter_space_y;
+l_int32 i, found, len, ch, x1 = 0, y1 = 0, width, height;
+l_uint16 platform, encoding;
+char *next;
+l_float32 cos_a, sin_a;
+
+    PROCNAME("pixWriteTTFText");
+
+    if (pixGetDepth(pixs) != 32) {
+        pixt = pixConvertTo32(pixs);
+        if (!pixt) {
+            return (PIX *)ERROR_PTR("failed to convert pixs to 32bpp image",
+                                    procName, NULL);
+        }
+        pixd = pixCopy(NULL, pixt);
+    } else {
+        pixd = pixCopy(NULL, pixs);
+    }
+    if (!pixd) {
+        pixDestroy(&pixt);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+
+    datad = pixGetData(pixd);
+    pixGetDimensions(pixd, &width, &height, NULL);
+    err = FT_New_Face (lib_ptr->library, (char *)fontfile, 0, &face);
+    if (err) {
+        pixDestroy(&pixt);
+        pixDestroy(&pixd);
+        return (PIX *)ERROR_PTR("failed to load font file", procName, NULL);
+    }
+
+    err = FT_Set_Char_Size(face, 0, (FT_F26Dot6) (size * 64),
+                           LEPTONICA_FT_RESOLUTION, LEPTONICA_FT_RESOLUTION);
+    if (err) {
+        pixDestroy(&pixt);
+        pixDestroy(&pixd);
+        FT_Done_Face(face);
+        return (PIX *)ERROR_PTR("failed to set font size", procName, NULL);
+    }
+
+    found = 0;
+    for (i = 0; i < face->num_charmaps; i++) {
+        charmap = face->charmaps[i];
+        platform = charmap->platform_id;
+        encoding = charmap->encoding_id;
+        if ((platform == 3 && encoding == 1)    /* Windows Unicode */
+            || (platform == 3 && encoding == 0) /* Windows Symbol */
+            || (platform == 2 && encoding == 1) /* ISO Unicode */
+            || (platform == 0)) {               /* Apple Unicode */
+            found = 1;
+            break;
+        }
+    }
+
+    if (!found) {
+        pixDestroy(&pixt);
+        pixDestroy(&pixd);
+        FT_Done_Face(face);
+        return (PIX *)ERROR_PTR("could not find Unicode charmap",
+                                procName, NULL);
+    }
+
+        /* Degrees to radians */
+    angle = angle * (M_PI/180);
+    sin_a = sin(angle);
+    cos_a = cos(angle);
+    matrix.xx = (FT_Fixed) (cos_a * (1 << 16));
+    matrix.yx = (FT_Fixed) (sin_a * (1 << 16));
+    matrix.xy = -matrix.yx;
+    matrix.yy = matrix.xx;
+    FT_Set_Transform(face, &matrix, NULL);
+    penf.x = penf.y = 0;    /* running position of non-rotated string */
+    pen.x = pen.y = 0;      /* running position of rotated string */
+
+    previous = 0;
+    next = (char *)text;
+    i = 0;
+    while (*next) {
+        if (i == 0) { /* use char spacing for 1+ characters */
+            letter_space_x = 0;
+            letter_space_y = 0;
+        } else {
+            letter_space_x = cos_a * letter_space * i;
+            letter_space_y = -sin_a * letter_space * i;
+        }
+
+        len = ftUtfToUniChar(next, &ch);
+//        ch |= 0xf000;
+        next += len;
+
+        glyph_index = FT_Get_Char_Index(face, ch);
+
+        err = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
+        if (err) {
+            pixDestroy(&pixt);
+            pixDestroy(&pixd);
+            FT_Done_Face(face);
+            return (PIX *)ERROR_PTR("could not load glyph into the slot",
+                                    procName, NULL);
+        }
+
+        err = FT_Get_Glyph(face->glyph, &image);
+        if (err) {
+            pixDestroy(&pixt);
+            pixDestroy(&pixd);
+            FT_Done_Face(face);
+            return (PIX *)ERROR_PTR("could not extract glyph from a slot",
+                                    procName, NULL);
+        }
+
+        if (brect) {
+            FT_Glyph_Get_CBox(image, ft_glyph_bbox_gridfit, &char_bbox);
+            char_bbox.xMin += penf.x;
+            char_bbox.yMin += penf.y;
+            char_bbox.xMax += penf.x;
+            char_bbox.yMax += penf.y;
+
+            if (i == 0) {
+                bbox.xMin = char_bbox.xMin;
+                bbox.yMin = char_bbox.yMin;
+                bbox.xMax = char_bbox.xMax;
+                bbox.yMax = char_bbox.yMax;
+            } else {
+                if (bbox.xMin > char_bbox.xMin) {
+                    bbox.xMin = char_bbox.xMin;
+                }
+                if (bbox.yMin > char_bbox.yMin) {
+                    bbox.yMin = char_bbox.yMin;
+                }
+                if (bbox.xMax < char_bbox.xMax) {
+                     bbox.xMax = char_bbox.xMax;
+                }
+                if (bbox.yMax < char_bbox.yMax) {
+                     bbox.yMax = char_bbox.yMax;
+                }
+            }
+        }
+
+        if (image->format != ft_glyph_format_bitmap &&
+            FT_Glyph_To_Bitmap(&image, ft_render_mode_normal, 0, 1)) {
+            pixDestroy(&pixt);
+            pixDestroy(&pixd);
+            FT_Done_Face(face);
+            return (PIX *)ERROR_PTR("could not convert glyph to bitmap",
+                                    procName, NULL);
+        }
+
+            /* Now, draw to our target surface */
+        bitmap = (FT_BitmapGlyph) image;
+        ftDrawBitmap(datad, color, bitmap->bitmap,
+                  letter_space_x + x + x1 + ((pen.x + 31) >> 6) + bitmap->left,
+                  letter_space_y + y - y1 + ((pen.y + 31) >> 6) - bitmap->top,
+                  width, height);
+
+            /* Record current glyph index for kerning */
+        previous = glyph_index;
+
+            /* Increment pen position */
+        pen.x += image->advance.x >> 10;
+        pen.y -= image->advance.y >> 10;
+        penf.x += face->glyph->metrics.horiAdvance;
+
+        FT_Done_Glyph(image);
+        i++;
+    }
+
+    if (brect) {
+        double d1 = sin (angle + 0.78539816339744830962);
+        double d2 = sin (angle - 0.78539816339744830962);
+
+            /* Rotate bounding rectangle */
+        brect[0] = (int) (bbox.xMin * cos_a - bbox.yMin * sin_a);
+        brect[1] = (int) (bbox.xMin * sin_a + bbox.yMin * cos_a);
+        brect[2] = (int) (bbox.xMax * cos_a - bbox.yMin * sin_a);
+        brect[3] = (int) (bbox.xMax * sin_a + bbox.yMin * cos_a);
+        brect[4] = (int) (bbox.xMax * cos_a - bbox.yMax * sin_a);
+        brect[5] = (int) (bbox.xMax * sin_a + bbox.yMax * cos_a);
+        brect[6] = (int) (bbox.xMin * cos_a - bbox.yMax * sin_a);
+        brect[7] = (int) (bbox.xMin * sin_a + bbox.yMax * cos_a);
+
+            /* Scale, round and offset brect */
+        brect[0] = x + ROUNDUPDOWN(brect[0], d2 > 0);
+        brect[1] = y - ROUNDUPDOWN(brect[1], d1 < 0);
+        brect[2] = x + ROUNDUPDOWN(brect[2], d1 > 0);
+        brect[3] = y - ROUNDUPDOWN(brect[3], d2 > 0);
+        brect[4] = x + ROUNDUPDOWN(brect[4], d2 < 0);
+        brect[5] = y - ROUNDUPDOWN(brect[5], d1 > 0);
+        brect[6] = x + ROUNDUPDOWN(brect[6], d1 < 0);
+        brect[7] = y - ROUNDUPDOWN(brect[7], d2 < 0);
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
diff --git a/src/freetype.h b/src/freetype.h
new file mode 100644 (file)
index 0000000..6969d41
--- /dev/null
@@ -0,0 +1,34 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_FREETYPE_H
+#define  LEPTONICA_FREETYPE_H
+
+#define  LEPTONICA_FT_RESOLUTION 96
+
+typedef struct ft_library_st FT_LIBRARY;
+
+#endif  /* LEPTONICA_FREETYPE_H */
diff --git a/src/gifio.c b/src/gifio.c
new file mode 100644 (file)
index 0000000..3c06ec7
--- /dev/null
@@ -0,0 +1,581 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  gifio.c
+ *
+ *    Read gif from file
+ *          PIX        *pixReadStreamGif()
+ *          static PIX *pixUninterlaceGIF()
+ *
+ *    Write gif to file
+ *          l_int32     pixWriteStreamGif()
+ *
+ *    Read/write from/to memory (see warning)
+ *          PIX        *pixReadMemGif()
+ *          l_int32     pixWriteMemGif()
+ *
+ *    The initial version of this module was generously contribued by
+ *    Antony Dovgal.  He can be contacted at:  tony *AT* daylessday.org
+ *
+ *    Important version information:
+ *
+ *    (1) This uses the gif library, version 4.1.6 or later.
+ *        Do not use 4.1.4.  It has serious problems handling 1 bpp images.
+ *    (2) There are some issues with version 5.0:
+ *        - valgrind detects uninitialized values used used for writing
+ *          and conditionally jumping in EGifPutScreenDesc().  This has
+ *          not been fixed as of 1/20/2016.
+ *        - DGifSlurp() crashes on some images, apparently triggered by
+ *          by some GIF extension records.  This has been fixed as of 5.1.
+ *    (3) E. Raymond has changed the high-level interface with 5.0
+ *        and again with 5.1, and to keep up we have used macros
+ *        determined by the major and minor version numbers.  Note that
+ *        tiff, jpeg, png and webp have maintained a consistent high-level
+ *        interface through all versions; version-dependent code paths
+ *        are not required to use them.
+ *    (4) Version 5.1.2 came out on Jan 7, 2016.  Leptonica cannot
+ *        successfully read gif files that it writes with this version;
+ *        DGifSlurp() gets an internal error from an uninitialized array
+ *        and returns failure.  E. Raymond fixed the problem for 5.1.3,
+ *        and we disable leptonica with 5.1.2.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <sys/types.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#else
+#include <io.h>
+#endif  /* _MSC_VER */
+#include "allheaders.h"
+
+/* --------------------------------------------------------------------*/
+#if  HAVE_LIBGIF  || HAVE_LIBUNGIF             /* defined in environ.h */
+/* --------------------------------------------------------------------*/
+
+#include "gif_lib.h"
+
+    /* GIF supports 4-way interlacing by raster lines.
+     * Leptonica restores interlaced data to normal raster order. */
+static PIX * pixUninterlaceGIF(PIX  *pixs);
+static const l_int32 InterlacedOffset[] = {0, 4, 2, 1};
+static const l_int32 InterlacedJumps[] = {8, 8, 4, 2};
+
+    /* Basic interface changed in 5.0 (!).  We have to do this for
+     * backward compatibililty with 4.1.6. */
+#if GIFLIB_MAJOR < 5
+#define GifMakeMapObject         MakeMapObject
+#define GifFreeMapObject         FreeMapObject
+#define DGifOpenFileHandle(a,b)  DGifOpenFileHandle(a)
+#define EGifOpenFileHandle(a,b)  EGifOpenFileHandle(a)
+#endif  /* GIFLIB_MAJOR */
+
+    /* Basic interface changed again in 5.1 (!) */
+#if GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0)
+#define DGifCloseFile(a,b)       DGifCloseFile(a)
+#define EGifCloseFile(a,b)       EGifCloseFile(a)
+#endif
+
+/*---------------------------------------------------------------------*
+ *                       Reading gif from file                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixReadStreamGif()
+ *
+ *      Input:  stream
+ *      Return: pix, or null on error
+ */
+PIX *
+pixReadStreamGif(FILE  *fp)
+{
+l_int32          fd, wpl, i, j, w, h, d, cindex, ncolors;
+l_int32          rval, gval, bval;
+l_uint32        *data, *line;
+GifFileType     *gif;
+PIX             *pixd, *pixdi;
+PIXCMAP         *cmap;
+ColorMapObject  *gif_cmap;
+SavedImage       si;
+#if GIFLIB_MAJOR == 5 && GIFLIB_MINOR > 0
+int              giferr;
+#endif
+
+    PROCNAME("pixReadStreamGif");
+
+#if GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 1 && GIFLIB_RELEASE == 2  /* 5.1.2 */
+    L_ERROR("Can't use giflib-5.1.2; suggest 5.1.1 or earlier\n", procName);
+    return NULL;
+#endif  /* 5.1.2 */
+
+    if ((fd = fileno(fp)) < 0)
+        return (PIX *)ERROR_PTR("invalid file descriptor", procName, NULL);
+#ifdef _WIN32
+    fd = _dup(fd);
+#endif /* _WIN32 */
+
+#ifndef _MSC_VER
+    lseek(fd, 0, SEEK_SET);
+#else
+    _lseek(fd, 0, SEEK_SET);
+#endif  /* _MSC_VER */
+
+    if ((gif = DGifOpenFileHandle(fd, NULL)) == NULL)
+        return (PIX *)ERROR_PTR("invalid file or file not found",
+                                procName, NULL);
+
+        /* Read all the data, but use only the first image found */
+    if (DGifSlurp(gif) != GIF_OK) {
+        DGifCloseFile(gif, &giferr);
+        return (PIX *)ERROR_PTR("failed to read GIF data", procName, NULL);
+    }
+
+    if (gif->SavedImages == NULL) {
+        DGifCloseFile(gif, &giferr);
+        return (PIX *)ERROR_PTR("no images found in GIF", procName, NULL);
+    }
+
+    si = gif->SavedImages[0];
+    w = si.ImageDesc.Width;
+    h = si.ImageDesc.Height;
+    if (w <= 0 || h <= 0) {
+        DGifCloseFile(gif, &giferr);
+        return (PIX *)ERROR_PTR("invalid image dimensions", procName, NULL);
+    }
+
+    if (si.RasterBits == NULL) {
+        DGifCloseFile(gif, &giferr);
+        return (PIX *)ERROR_PTR("no raster data in GIF", procName, NULL);
+    }
+
+    if (si.ImageDesc.ColorMap) {
+            /* private cmap for this image */
+        gif_cmap = si.ImageDesc.ColorMap;
+    } else if (gif->SColorMap) {
+            /* global cmap for whole picture */
+        gif_cmap = gif->SColorMap;
+    } else {
+            /* don't know where to take cmap from */
+        DGifCloseFile(gif, &giferr);
+        return (PIX *)ERROR_PTR("color map is missing", procName, NULL);
+    }
+
+    ncolors = gif_cmap->ColorCount;
+    if (ncolors <= 2)
+        d = 1;
+    else if (ncolors <= 4)
+        d = 2;
+    else if (ncolors <= 16)
+        d = 4;
+    else
+        d = 8;
+    if ((cmap = pixcmapCreate(d)) == NULL) {
+        DGifCloseFile(gif, &giferr);
+        return (PIX *)ERROR_PTR("cmap creation failed", procName, NULL);
+    }
+
+    for (cindex = 0; cindex < ncolors; cindex++) {
+        rval = gif_cmap->Colors[cindex].Red;
+        gval = gif_cmap->Colors[cindex].Green;
+        bval = gif_cmap->Colors[cindex].Blue;
+        pixcmapAddColor(cmap, rval, gval, bval);
+    }
+
+    if ((pixd = pixCreate(w, h, d)) == NULL) {
+        DGifCloseFile(gif, &giferr);
+        pixcmapDestroy(&cmap);
+        return (PIX *)ERROR_PTR("failed to allocate pixd", procName, NULL);
+    }
+    pixSetInputFormat(pixd, IFF_GIF);
+    pixSetColormap(pixd, cmap);
+
+    wpl = pixGetWpl(pixd);
+    data = pixGetData(pixd);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        if (d == 1) {
+            for (j = 0; j < w; j++) {
+                if (si.RasterBits[i * w + j])
+                    SET_DATA_BIT(line, j);
+            }
+        } else if (d == 2) {
+            for (j = 0; j < w; j++)
+                SET_DATA_DIBIT(line, j, si.RasterBits[i * w + j]);
+        } else if (d == 4) {
+            for (j = 0; j < w; j++)
+                SET_DATA_QBIT(line, j, si.RasterBits[i * w + j]);
+        } else {  /* d == 8 */
+            for (j = 0; j < w; j++)
+                SET_DATA_BYTE(line, j, si.RasterBits[i * w + j]);
+        }
+    }
+
+        /* If the image has been interlaced (for viewing in a browser),
+         * this restores the raster lines to normal order. */
+    if (gif->Image.Interlace) {
+        pixdi = pixUninterlaceGIF(pixd);
+        pixTransferAllData(pixd, &pixdi, 0, 0);
+    }
+
+    DGifCloseFile(gif, &giferr);
+    return pixd;
+}
+
+
+static PIX *
+pixUninterlaceGIF(PIX  *pixs)
+{
+l_int32    w, h, d, wpl, j, k, srow, drow;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixUninterlaceGIF");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, &d);
+    wpl = pixGetWpl(pixs);
+    pixd = pixCreateTemplate(pixs);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    for (k = 0, srow = 0; k < 4; k++) {
+        for (drow = InterlacedOffset[k]; drow < h;
+             drow += InterlacedJumps[k], srow++) {
+            lines = datas + srow * wpl;
+            lined = datad + drow * wpl;
+            for (j = 0; j < w; j++)
+                memcpy(lined, lines, 4 * wpl);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Writing gif to file                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixWriteStreamGif()
+ *
+ *      Input:  stream
+ *              pix (1, 2, 4, 8, 16 or 32 bpp)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) All output gif have colormaps.  If the pix is 32 bpp rgb,
+ *          this quantizes the colors and writes out 8 bpp.
+ *          If the pix is 16 bpp grayscale, it converts to 8 bpp first.
+ *      (2) We can't write to memory using open_memstream() because
+ *          the gif functions write through a file descriptor, not a
+ *          file stream.
+ */
+l_int32
+pixWriteStreamGif(FILE  *fp,
+                  PIX   *pix)
+{
+char            *text;
+l_int32          fd, wpl, i, j, w, h, d, ncolor, rval, gval, bval;
+l_int32          gif_ncolor = 0;
+l_uint32        *data, *line;
+PIX             *pixd;
+PIXCMAP         *cmap;
+GifFileType     *gif;
+ColorMapObject  *gif_cmap;
+GifByteType     *gif_line;
+#if GIFLIB_MAJOR == 5 && GIFLIB_MINOR > 0
+int              giferr;
+#endif
+
+    PROCNAME("pixWriteStreamGif");
+
+        /* See version information at top of this file */
+#if GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 1 && GIFLIB_RELEASE == 2  /* 5.1.2 */
+    return ERROR_INT("Can't use giflib-5.1.2; suggest 5.1.1 or earlier\n",
+                     procName, 1);
+#endif  /* 5.1.2 */
+
+    if (!fp)
+        return ERROR_INT("stream not open", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    rewind(fp);
+
+    if ((fd = fileno(fp)) < 0)
+        return ERROR_INT("invalid file descriptor", procName, 1);
+#ifdef _WIN32
+    fd = _dup(fd);
+#endif /* _WIN32 */
+
+    d = pixGetDepth(pix);
+    if (d == 32) {
+        pixd = pixConvertRGBToColormap(pix, 1);
+    } else if (d > 1) {
+        pixd = pixConvertTo8(pix, TRUE);
+    } else {  /* d == 1; make sure there's a colormap */
+        pixd = pixClone(pix);
+        if (!pixGetColormap(pixd)) {
+            cmap = pixcmapCreate(1);
+            pixcmapAddColor(cmap, 255, 255, 255);
+            pixcmapAddColor(cmap, 0, 0, 0);
+            pixSetColormap(pixd, cmap);
+        }
+    }
+
+    if (!pixd)
+        return ERROR_INT("failed to convert image to indexed", procName, 1);
+    d = pixGetDepth(pixd);
+
+    if ((cmap = pixGetColormap(pixd)) == NULL) {
+        pixDestroy(&pixd);
+        return ERROR_INT("cmap is missing", procName, 1);
+    }
+
+        /* 'Round' the number of gif colors up to a power of 2 */
+    ncolor = pixcmapGetCount(cmap);
+    for (i = 0; i <= 8; i++) {
+        if ((1 << i) >= ncolor) {
+            gif_ncolor = (1 << i);
+            break;
+        }
+    }
+    if (gif_ncolor < 1) {
+        pixDestroy(&pixd);
+        return ERROR_INT("number of colors is invalid", procName, 1);
+    }
+
+        /* Save the cmap colors in a gif_cmap */
+    if ((gif_cmap = GifMakeMapObject(gif_ncolor, NULL)) == NULL) {
+        pixDestroy(&pixd);
+        return ERROR_INT("failed to create GIF color map", procName, 1);
+    }
+    for (i = 0; i < gif_ncolor; i++) {
+        rval = gval = bval = 0;
+        if (ncolor > 0) {
+            if (pixcmapGetColor(cmap, i, &rval, &gval, &bval) != 0) {
+                pixDestroy(&pixd);
+                GifFreeMapObject(gif_cmap);
+                return ERROR_INT("failed to get color from color map",
+                                 procName, 1);
+            }
+            ncolor--;
+        }
+        gif_cmap->Colors[i].Red = rval;
+        gif_cmap->Colors[i].Green = gval;
+        gif_cmap->Colors[i].Blue = bval;
+    }
+
+        /* Get the gif file handle */
+    if ((gif = EGifOpenFileHandle(fd, NULL)) == NULL) {
+        GifFreeMapObject(gif_cmap);
+        pixDestroy(&pixd);
+        return ERROR_INT("failed to create GIF image handle", procName, 1);
+    }
+
+    pixGetDimensions(pixd, &w, &h, NULL);
+    if (EGifPutScreenDesc(gif, w, h, gif_cmap->BitsPerPixel, 0, gif_cmap)
+        != GIF_OK) {
+        pixDestroy(&pixd);
+        GifFreeMapObject(gif_cmap);
+        EGifCloseFile(gif, &giferr);
+        return ERROR_INT("failed to write screen description", procName, 1);
+    }
+    GifFreeMapObject(gif_cmap); /* not needed after this point */
+
+    if (EGifPutImageDesc(gif, 0, 0, w, h, FALSE, NULL) != GIF_OK) {
+        pixDestroy(&pixd);
+        EGifCloseFile(gif, &giferr);
+        return ERROR_INT("failed to image screen description", procName, 1);
+    }
+
+    data = pixGetData(pixd);
+    wpl = pixGetWpl(pixd);
+    if (d != 1 && d != 2 && d != 4 && d != 8) {
+        pixDestroy(&pixd);
+        EGifCloseFile(gif, &giferr);
+        return ERROR_INT("image depth is not in {1, 2, 4, 8}", procName, 1);
+    }
+
+    if ((gif_line = (GifByteType *)LEPT_CALLOC(sizeof(GifByteType), w))
+        == NULL) {
+        pixDestroy(&pixd);
+        EGifCloseFile(gif, &giferr);
+        return ERROR_INT("mem alloc fail for data line", procName, 1);
+    }
+
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+            /* Gif's way of setting the raster line up for compression */
+        for (j = 0; j < w; j++) {
+            switch(d)
+            {
+            case 8:
+                gif_line[j] = GET_DATA_BYTE(line, j);
+                break;
+            case 4:
+                gif_line[j] = GET_DATA_QBIT(line, j);
+                break;
+            case 2:
+                gif_line[j] = GET_DATA_DIBIT(line, j);
+                break;
+            case 1:
+                gif_line[j] = GET_DATA_BIT(line, j);
+                break;
+            }
+        }
+
+            /* Compress and save the line */
+        if (EGifPutLine(gif, gif_line, w) != GIF_OK) {
+            LEPT_FREE(gif_line);
+            pixDestroy(&pixd);
+            EGifCloseFile(gif, &giferr);
+            return ERROR_INT("failed to write data line into GIF", procName, 1);
+        }
+    }
+
+        /* Write a text comment.  This must be placed after writing the
+         * data (!!)  Note that because libgif does not provide a function
+         * for reading comments from file, you will need another way
+         * to read comments. */
+    if ((text = pixGetText(pix)) != NULL) {
+        if (EGifPutComment(gif, text) != GIF_OK)
+            L_WARNING("gif comment not written\n", procName);
+    }
+
+    LEPT_FREE(gif_line);
+    pixDestroy(&pixd);
+    EGifCloseFile(gif, &giferr);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                      Read/write from/to memory                      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixReadMemGif()
+ *
+ *      Input:  data (const; gif-encoded)
+ *              size (of data)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) Of course, we are cheating here -- writing the data to file
+ *          in gif format and reading it back in.  We can't use the
+ *          GNU runtime extension fmemopen() to avoid writing to a file
+ *          because libgif doesn't have a file stream interface!
+ *      (2) This should not be assumed to be safe from a sophisticated
+ *          attack, even though we have attempted to make the filename
+ *          difficult to guess by embedding the process number and the
+ *          current time in microseconds.  The best way to handle
+ *          temporary files is to use file descriptors (capabilities)
+ *          or file handles.  However, I know of no way to do this
+ *          for gif files because of the way that libgif handles the
+ *          file descriptors.  The canonical approach would be to do this:
+ *              char templ[] = "hiddenfilenameXXXXXX";
+ *              l_int32 fd = mkstemp(templ);
+ *              FILE *fp = fdopen(fd, "w+b");
+ *              fwrite(data, 1, size, fp);
+ *              rewind(fp);
+ *              Pix *pix = pixReadStreamGif(fp);
+ *          but this fails because fp is in a bad state after writing.
+ */
+PIX *
+pixReadMemGif(const l_uint8  *cdata,
+              size_t          size)
+{
+char  *fname;
+PIX   *pix;
+
+    PROCNAME("pixReadMemGif");
+
+    if (!cdata)
+        return (PIX *)ERROR_PTR("cdata not defined", procName, NULL);
+    L_WARNING("writing to a temp file, not directly to memory\n", procName);
+
+        /* Write to a temp file */
+    fname = genTempFilename("/tmp/", "mem.gif", 1, 1);
+    l_binaryWrite(fname, "w", (l_uint8 *)cdata, size);
+
+        /* Read back from the file */
+    pix = pixRead(fname);
+    lept_rmfile(fname);
+    lept_free(fname);
+    if (!pix) L_ERROR("pix not read\n", procName);
+    return pix;
+}
+
+
+/*!
+ *  pixWriteMemGif()
+ *
+ *      Input:  &data (<return> data of gif compressed image)
+ *              &size (<return> size of returned data)
+ *              pix
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See comments in pixReadMemGif()
+ */
+l_int32
+pixWriteMemGif(l_uint8  **pdata,
+               size_t    *psize,
+               PIX       *pix)
+{
+char  *fname;
+
+    PROCNAME("pixWriteMemGif");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1 );
+    *pdata = NULL;
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1 );
+    *psize = 0;
+    if (!pix)
+        return ERROR_INT("&pix not defined", procName, 1 );
+    L_WARNING("writing to a temp file, not directly to memory\n", procName);
+
+        /* Write to a temp file */
+    fname = genTempFilename("/tmp/", "mem.gif", 1, 1);
+    pixWrite(fname, pix, IFF_GIF);
+
+        /* Read back into memory */
+    *pdata = l_binaryRead(fname, psize);
+    lept_rmfile(fname);
+    lept_free(fname);
+    return 0;
+}
+
+
+/* -----------------------------------------------------------------*/
+#endif    /* HAVE_LIBGIF || HAVE_LIBUNGIF  */
+/* -----------------------------------------------------------------*/
diff --git a/src/gifiostub.c b/src/gifiostub.c
new file mode 100644 (file)
index 0000000..301fe02
--- /dev/null
@@ -0,0 +1,71 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  gifiostub.c
+ *
+ *     Stubs for gifio.c functions
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* -----------------------------------------------------------------*/
+#if  (!HAVE_LIBGIF) && (!HAVE_LIBUNGIF)     /* defined in environ.h */
+/* -----------------------------------------------------------------*/
+
+PIX * pixReadStreamGif(FILE *fp)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadStreamGif", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteStreamGif(FILE *fp, PIX *pix)
+{
+    return ERROR_INT("function not present", "pixWriteStreamGif", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadMemGif(const l_uint8 *cdata, size_t size)
+{
+    return (PIX *)ERROR_PTR("function not present", "pixReadMemGif", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteMemGif(l_uint8 **pdata, size_t *psize, PIX *pix)
+{
+    return ERROR_INT("function not present", "pixWriteMemGif", 1);
+}
+
+/* -----------------------------------------------------------------*/
+#endif      /* !HAVE_LIBGIF && !HAVE_LIBUNGIF */
+/* -----------------------------------------------------------------*/
diff --git a/src/gplot.c b/src/gplot.c
new file mode 100644 (file)
index 0000000..1014d82
--- /dev/null
@@ -0,0 +1,938 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  gplot.c
+ *
+ *     Basic plotting functions
+ *          GPLOT      *gplotCreate()
+ *          void        gplotDestroy()
+ *          l_int32     gplotAddPlot()
+ *          l_int32     gplotSetScaling()
+ *          l_int32     gplotMakeOutput()
+ *          l_int32     gplotGenCommandFile()
+ *          l_int32     gplotGenDataFiles()
+ *
+ *     Quick and dirty plots
+ *          l_int32     gplotSimple1()
+ *          l_int32     gplotSimple2()
+ *          l_int32     gplotSimpleN()
+ *          l_int32     gplotSimpleXY1()
+ *          l_int32     gplotSimpleXY2()
+ *          l_int32     gplotSimpleXYN()
+ *
+ *     Serialize for I/O
+ *          GPLOT      *gplotRead()
+ *          l_int32     gplotWrite()
+ *
+ *
+ *     Utility for programmatic plotting using gnuplot 7.3.2 or later
+ *     Enabled:
+ *         - output to png (color), ps (mono), x11 (color), latex (mono)
+ *         - optional title for graph
+ *         - optional x and y axis labels
+ *         - multiple plots on one frame
+ *         - optional title for each plot on the frame
+ *         - optional log scaling on either or both axes
+ *         - choice of 5 plot styles for each plot
+ *         - choice of 2 plot modes, either using one input array
+ *           (Y vs index) or two input arrays (Y vs X).  This
+ *           choice is made implicitly depending on the number of
+ *           input arrays.
+ *
+ *     Usage:
+ *         gplotCreate() initializes for plotting
+ *         gplotAddPlot() for each plot on the frame
+ *         gplotMakeOutput() to generate all output files and run gnuplot
+ *         gplotDestroy() to clean up
+ *
+ *     Example of use:
+ *         gplot = gplotCreate("tempskew", GPLOT_PNG, "Skew score vs angle",
+ *                    "angle (deg)", "score");
+ *         gplotAddPlot(gplot, natheta, nascore1, GPLOT_LINES, "plot 1");
+ *         gplotAddPlot(gplot, natheta, nascore2, GPLOT_POINTS, "plot 2");
+ *         gplotSetScaling(gplot, GPLOT_LOG_SCALE_Y);
+ *         gplotMakeOutput(gplot);
+ *         gplotDestroy(&gplot);
+ *
+ *     Note for output to GPLOT_LATEX:
+ *         This creates latex output of the plot, named <rootname>.tex.
+ *         It needs to be placed in a latex file <latexname>.tex
+ *         that precedes the plot output with, at a minimum:
+ *           \documentclass{article}
+ *           \begin{document}
+ *         and ends with
+ *           \end{document}
+ *         You can then generate a dvi file <latexname>.dvi using
+ *           latex <latexname>.tex
+ *         and a PostScript file <psname>.ps from that using
+ *           dvips -o <psname>.ps <latexname>.dvi
+ *
+ *     N.B. To generate plots, it is necessary to have gnuplot installed on
+ *          your Unix system, or wgnuplot on Windows.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  L_BUF_SIZE = 512;
+
+const char  *gplotstylenames[] = {"with lines",
+                                  "with points",
+                                  "with impulses",
+                                  "with linespoints",
+                                  "with dots"};
+const char  *gplotfilestyles[] = {"LINES",
+                                  "POINTS",
+                                  "IMPULSES",
+                                  "LINESPOINTS",
+                                  "DOTS"};
+const char  *gplotfileoutputs[] = {"",
+                                   "PNG",
+                                   "PS",
+                                   "EPS",
+                                   "X11",
+                                   "LATEX"};
+
+
+/*-----------------------------------------------------------------*
+ *                       Basic Plotting Functions                  *
+ *-----------------------------------------------------------------*/
+/*!
+ *  gplotCreate()
+ *
+ *      Input:  rootname (root for all output files)
+ *              outformat (GPLOT_PNG, GPLOT_PS, GPLOT_EPS, GPLOT_X11,
+ *                         GPLOT_LATEX)
+ *              title  (<optional> overall title)
+ *              xlabel (<optional> x axis label)
+ *              ylabel (<optional> y axis label)
+ *      Return: gplot, or null on error
+ *
+ *  Notes:
+ *      (1) This initializes the plot.
+ *      (2) The 'title', 'xlabel' and 'ylabel' strings can have spaces,
+ *          double quotes and backquotes, but not single quotes.
+ */
+GPLOT  *
+gplotCreate(const char  *rootname,
+            l_int32      outformat,
+            const char  *title,
+            const char  *xlabel,
+            const char  *ylabel)
+{
+char   *newroot;
+char    buf[L_BUF_SIZE];
+GPLOT  *gplot;
+
+    PROCNAME("gplotCreate");
+
+    if (!rootname)
+        return (GPLOT *)ERROR_PTR("rootname not defined", procName, NULL);
+    if (outformat != GPLOT_PNG && outformat != GPLOT_PS &&
+        outformat != GPLOT_EPS && outformat != GPLOT_X11 &&
+        outformat != GPLOT_LATEX)
+        return (GPLOT *)ERROR_PTR("outformat invalid", procName, NULL);
+
+    if ((gplot = (GPLOT *)LEPT_CALLOC(1, sizeof(GPLOT))) == NULL)
+        return (GPLOT *)ERROR_PTR("gplot not made", procName, NULL);
+    gplot->cmddata = sarrayCreate(0);
+    gplot->datanames = sarrayCreate(0);
+    gplot->plotdata = sarrayCreate(0);
+    gplot->plottitles = sarrayCreate(0);
+    gplot->plotstyles = numaCreate(0);
+
+        /* Save title, labels, rootname, outformat, cmdname, outname */
+    newroot = genPathname(rootname, NULL);
+    gplot->rootname = newroot;
+    gplot->outformat = outformat;
+    snprintf(buf, L_BUF_SIZE, "%s.cmd", newroot);
+    gplot->cmdname = stringNew(buf);
+    if (outformat == GPLOT_PNG)
+        snprintf(buf, L_BUF_SIZE, "%s.png", newroot);
+    else if (outformat == GPLOT_PS)
+        snprintf(buf, L_BUF_SIZE, "%s.ps", newroot);
+    else if (outformat == GPLOT_EPS)
+        snprintf(buf, L_BUF_SIZE, "%s.eps", newroot);
+    else if (outformat == GPLOT_LATEX)
+        snprintf(buf, L_BUF_SIZE, "%s.tex", newroot);
+    else  /* outformat == GPLOT_X11 */
+        buf[0] = '\0';
+    gplot->outname = stringNew(buf);
+    if (title) gplot->title = stringNew(title);
+    if (xlabel) gplot->xlabel = stringNew(xlabel);
+    if (ylabel) gplot->ylabel = stringNew(ylabel);
+
+    return gplot;
+}
+
+
+/*!
+ *   gplotDestroy()
+ *
+ *        Input: &gplot (<to be nulled>)
+ *        Return: void
+ */
+void
+gplotDestroy(GPLOT  **pgplot)
+{
+GPLOT  *gplot;
+
+    PROCNAME("gplotDestroy");
+
+    if (pgplot == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((gplot = *pgplot) == NULL)
+        return;
+
+    LEPT_FREE(gplot->rootname);
+    LEPT_FREE(gplot->cmdname);
+    sarrayDestroy(&gplot->cmddata);
+    sarrayDestroy(&gplot->datanames);
+    sarrayDestroy(&gplot->plotdata);
+    sarrayDestroy(&gplot->plottitles);
+    numaDestroy(&gplot->plotstyles);
+    LEPT_FREE(gplot->outname);
+    if (gplot->title)
+        LEPT_FREE(gplot->title);
+    if (gplot->xlabel)
+        LEPT_FREE(gplot->xlabel);
+    if (gplot->ylabel)
+        LEPT_FREE(gplot->ylabel);
+
+    LEPT_FREE(gplot);
+    *pgplot = NULL;
+    return;
+}
+
+
+/*!
+ *  gplotAddPlot()
+ *
+ *      Input:  gplot
+ *              nax (<optional> numa: set to null for Y_VS_I;
+ *                   required for Y_VS_X)
+ *              nay (numa: required for both Y_VS_I and Y_VS_X)
+ *              plotstyle (GPLOT_LINES, GPLOT_POINTS, GPLOT_IMPULSES,
+ *                         GPLOT_LINESPOINTS, GPLOT_DOTS)
+ *              plottitle  (<optional> title for individual plot)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) There are 2 options for (x,y) values:
+ *            o  To plot an array vs a linear function of the
+ *               index, set nax = NULL.
+ *            o  To plot one array vs another, use both nax and nay.
+ *      (2) If nax is NULL, the x value corresponding to the i-th
+ *          value of nay is found from the startx and delx fields
+ *          in nay:
+ *               x = startx + i * delx
+ *          These are set with numaSetParameters().  Their default
+ *          values are startx = 0.0, delx = 1.0.
+ *      (3) If nax is defined, it must be the same size as nay.
+ *      (4) The 'plottitle' string can have spaces, double
+ *          quotes and backquotes, but not single quotes.
+ */
+l_int32
+gplotAddPlot(GPLOT       *gplot,
+             NUMA        *nax,
+             NUMA        *nay,
+             l_int32      plotstyle,
+             const char  *plottitle)
+{
+char       buf[L_BUF_SIZE];
+char       emptystring[] = "";
+char      *datastr, *title;
+l_int32    n, i;
+l_float32  valx, valy, startx, delx;
+SARRAY    *sa;
+
+    PROCNAME("gplotAddPlot");
+
+    if (!gplot)
+        return ERROR_INT("gplot not defined", procName, 1);
+    if (!nay)
+        return ERROR_INT("nay not defined", procName, 1);
+    if (plotstyle != GPLOT_LINES && plotstyle != GPLOT_POINTS &&
+        plotstyle != GPLOT_IMPULSES && plotstyle != GPLOT_LINESPOINTS &&
+        plotstyle != GPLOT_DOTS)
+        return ERROR_INT("invalid plotstyle", procName, 1);
+
+    n = numaGetCount(nay);
+    numaGetParameters(nay, &startx, &delx);
+    if (nax) {
+        if (n != numaGetCount(nax))
+            return ERROR_INT("nax and nay sizes differ", procName, 1);
+    }
+
+        /* Save plotstyle and plottitle */
+    numaAddNumber(gplot->plotstyles, plotstyle);
+    if (plottitle) {
+        title = stringNew(plottitle);
+        sarrayAddString(gplot->plottitles, title, L_INSERT);
+    } else {
+        sarrayAddString(gplot->plottitles, emptystring, L_COPY);
+    }
+
+        /* Generate and save data filename */
+    gplot->nplots++;
+    snprintf(buf, L_BUF_SIZE, "%s.data.%d", gplot->rootname, gplot->nplots);
+    sarrayAddString(gplot->datanames, buf, L_COPY);
+
+        /* Generate data and save as a string */
+    sa = sarrayCreate(n);
+    for (i = 0; i < n; i++) {
+        if (nax)
+            numaGetFValue(nax, i, &valx);
+        else
+            valx = startx + i * delx;
+        numaGetFValue(nay, i, &valy);
+        snprintf(buf, L_BUF_SIZE, "%f %f\n", valx, valy);
+        sarrayAddString(sa, buf, L_COPY);
+    }
+    datastr = sarrayToString(sa, 0);
+    sarrayAddString(gplot->plotdata, datastr, L_INSERT);
+    sarrayDestroy(&sa);
+
+    return 0;
+}
+
+
+/*!
+ *  gplotSetScaling()
+ *
+ *      Input:  gplot
+ *              scaling (GPLOT_LINEAR_SCALE, GPLOT_LOG_SCALE_X,
+ *                       GPLOT_LOG_SCALE_Y, GPLOT_LOG_SCALE_X_Y)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) By default, the x and y axis scaling is linear.
+ *      (2) Call this function to set semi-log or log-log scaling.
+ */
+l_int32
+gplotSetScaling(GPLOT   *gplot,
+                l_int32  scaling)
+{
+    PROCNAME("gplotSetScaling");
+
+    if (!gplot)
+        return ERROR_INT("gplot not defined", procName, 1);
+    if (scaling != GPLOT_LINEAR_SCALE &&
+        scaling != GPLOT_LOG_SCALE_X &&
+        scaling != GPLOT_LOG_SCALE_Y &&
+        scaling != GPLOT_LOG_SCALE_X_Y)
+        return ERROR_INT("invalid gplot scaling", procName, 1);
+    gplot->scaling = scaling;
+    return 0;
+}
+
+
+/*!
+ *  gplotMakeOutput()
+ *
+ *      Input:  gplot
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This uses gplot and the new arrays to add a plot
+ *          to the output, by writing a new data file and appending
+ *          the appropriate plot commands to the command file.
+ *      (2) This is the only function in this file that requires the
+ *          gnuplot executable, to actually generate the plot.
+ *      (3) The gnuplot program for windows is wgnuplot.exe.  The
+ *          standard gp426win32 distribution does not have a X11 terminal.
+ */
+l_int32
+gplotMakeOutput(GPLOT  *gplot)
+{
+char     buf[L_BUF_SIZE];
+l_int32  ignore;
+
+    PROCNAME("gplotMakeOutput");
+
+    if (!gplot)
+        return ERROR_INT("gplot not defined", procName, 1);
+
+    gplotGenCommandFile(gplot);
+    gplotGenDataFiles(gplot);
+
+#ifndef _WIN32
+    if (gplot->outformat != GPLOT_X11)
+        snprintf(buf, L_BUF_SIZE, "gnuplot %s", gplot->cmdname);
+    else
+        snprintf(buf, L_BUF_SIZE,
+                 "gnuplot -persist -geometry +10+10 %s &", gplot->cmdname);
+#else
+   if (gplot->outformat != GPLOT_X11)
+       snprintf(buf, L_BUF_SIZE, "wgnuplot %s", gplot->cmdname);
+   else
+       snprintf(buf, L_BUF_SIZE,
+               "wgnuplot -persist %s", gplot->cmdname);
+#endif  /* _WIN32 */
+    ignore = system(buf);  /* gnuplot || wgnuplot */
+    return 0;
+}
+
+
+/*!
+ *  gplotGenCommandFile()
+ *
+ *      Input:  gplot
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+gplotGenCommandFile(GPLOT  *gplot)
+{
+char     buf[L_BUF_SIZE];
+char    *cmdstr, *plottitle, *dataname;
+l_int32  i, plotstyle, nplots;
+FILE    *fp;
+
+    PROCNAME("gplotGenCommandFile");
+
+    if (!gplot)
+        return ERROR_INT("gplot not defined", procName, 1);
+
+        /* Remove any previous command data */
+    sarrayClear(gplot->cmddata);
+
+        /* Generate command data instructions */
+    if (gplot->title) {   /* set title */
+        snprintf(buf, L_BUF_SIZE, "set title '%s'", gplot->title);
+        sarrayAddString(gplot->cmddata, buf, L_COPY);
+    }
+    if (gplot->xlabel) {   /* set xlabel */
+        snprintf(buf, L_BUF_SIZE, "set xlabel '%s'", gplot->xlabel);
+        sarrayAddString(gplot->cmddata, buf, L_COPY);
+    }
+    if (gplot->ylabel) {   /* set ylabel */
+        snprintf(buf, L_BUF_SIZE, "set ylabel '%s'", gplot->ylabel);
+        sarrayAddString(gplot->cmddata, buf, L_COPY);
+    }
+
+    if (gplot->outformat == GPLOT_PNG)    /* set terminal type and output */
+        snprintf(buf, L_BUF_SIZE, "set terminal png; set output '%s'",
+                 gplot->outname);
+    else if (gplot->outformat == GPLOT_PS)
+        snprintf(buf, L_BUF_SIZE, "set terminal postscript; set output '%s'",
+                 gplot->outname);
+    else if (gplot->outformat == GPLOT_EPS)
+        snprintf(buf, L_BUF_SIZE,
+                "set terminal postscript eps; set output '%s'",
+                gplot->outname);
+    else if (gplot->outformat == GPLOT_LATEX)
+        snprintf(buf, L_BUF_SIZE, "set terminal latex; set output '%s'",
+                 gplot->outname);
+    else  /* gplot->outformat == GPLOT_X11 */
+#ifndef _WIN32
+        snprintf(buf, L_BUF_SIZE, "set terminal x11");
+#else
+        snprintf(buf, L_BUF_SIZE, "set terminal windows");
+#endif  /* _WIN32 */
+    sarrayAddString(gplot->cmddata, buf, L_COPY);
+
+    if (gplot->scaling == GPLOT_LOG_SCALE_X ||
+        gplot->scaling == GPLOT_LOG_SCALE_X_Y) {
+        snprintf(buf, L_BUF_SIZE, "set logscale x");
+        sarrayAddString(gplot->cmddata, buf, L_COPY);
+    }
+    if (gplot->scaling == GPLOT_LOG_SCALE_Y ||
+        gplot->scaling == GPLOT_LOG_SCALE_X_Y) {
+        snprintf(buf, L_BUF_SIZE, "set logscale y");
+        sarrayAddString(gplot->cmddata, buf, L_COPY);
+    }
+
+    nplots = sarrayGetCount(gplot->datanames);
+    for (i = 0; i < nplots; i++) {
+        plottitle = sarrayGetString(gplot->plottitles, i, L_NOCOPY);
+        dataname = sarrayGetString(gplot->datanames, i, L_NOCOPY);
+        numaGetIValue(gplot->plotstyles, i, &plotstyle);
+        if (nplots == 1) {
+            snprintf(buf, L_BUF_SIZE, "plot '%s' title '%s' %s",
+                     dataname, plottitle, gplotstylenames[plotstyle]);
+        } else {
+            if (i == 0)
+                snprintf(buf, L_BUF_SIZE, "plot '%s' title '%s' %s, \\",
+                     dataname, plottitle, gplotstylenames[plotstyle]);
+            else if (i < nplots - 1)
+                snprintf(buf, L_BUF_SIZE, " '%s' title '%s' %s, \\",
+                     dataname, plottitle, gplotstylenames[plotstyle]);
+            else
+                snprintf(buf, L_BUF_SIZE, " '%s' title '%s' %s",
+                     dataname, plottitle, gplotstylenames[plotstyle]);
+        }
+        sarrayAddString(gplot->cmddata, buf, L_COPY);
+    }
+
+        /* Write command data to file */
+    cmdstr = sarrayToString(gplot->cmddata, 1);
+    if ((fp = fopenWriteStream(gplot->cmdname, "w")) == NULL)
+        return ERROR_INT("cmd stream not opened", procName, 1);
+    fwrite(cmdstr, 1, strlen(cmdstr), fp);
+    fclose(fp);
+    LEPT_FREE(cmdstr);
+    return 0;
+}
+
+
+/*!
+ *  gplotGenDataFiles()
+ *
+ *      Input:  gplot
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+gplotGenDataFiles(GPLOT  *gplot)
+{
+char    *plotdata, *dataname;
+l_int32  i, nplots;
+FILE    *fp;
+
+    PROCNAME("gplotGenDataFiles");
+
+    if (!gplot)
+        return ERROR_INT("gplot not defined", procName, 1);
+
+    nplots = sarrayGetCount(gplot->datanames);
+    for (i = 0; i < nplots; i++) {
+        plotdata = sarrayGetString(gplot->plotdata, i, L_NOCOPY);
+        dataname = sarrayGetString(gplot->datanames, i, L_NOCOPY);
+        if ((fp = fopenWriteStream(dataname, "w")) == NULL)
+            return ERROR_INT("datafile stream not opened", procName, 1);
+        fwrite(plotdata, 1, strlen(plotdata), fp);
+        fclose(fp);
+    }
+
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------*
+ *                       Quick and Dirty Plots                     *
+ *-----------------------------------------------------------------*/
+/*!
+ *  gplotSimple1()
+ *
+ *      Input:  na (numa; plot Y_VS_I)
+ *              outformat (GPLOT_PNG, GPLOT_PS, GPLOT_EPS, GPLOT_X11,
+ *                         GPLOT_LATEX)
+ *              outroot (root of output files)
+ *              title  (<optional>, can be NULL)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This gives a line plot of a numa, where the array value
+ *          is plotted vs the array index.  The plot is generated
+ *          in the specified output format; the title  is optional.
+ *      (2) When calling these simple plot functions more than once, use
+ *          different @outroot to avoid overwriting the output files.
+ */
+l_int32
+gplotSimple1(NUMA        *na,
+             l_int32      outformat,
+             const char  *outroot,
+             const char  *title)
+{
+    return gplotSimpleXY1(NULL, na, GPLOT_LINES, outformat, outroot, title);
+}
+
+
+/*!
+ *  gplotSimple2()
+ *
+ *      Input:  na1 (numa; plotted with Y_VS_I)
+ *              na2 (ditto)
+ *              outformat (GPLOT_PNG, GPLOT_PS, GPLOT_EPS, GPLOT_X11,
+ *                         GPLOT_LATEX)
+ *              outroot (root of output files)
+ *              title  (<optional>)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This gives a line plot of two numa, where the array values
+ *          are each plotted vs the array index.  The plot is generated
+ *          in the specified output format; the title  is optional.
+ *      (2) When calling these simple plot functions more than once, use
+ *          different @outroot to avoid overwriting the output files.
+ */
+l_int32
+gplotSimple2(NUMA        *na1,
+             NUMA        *na2,
+             l_int32      outformat,
+             const char  *outroot,
+             const char  *title)
+{
+    return gplotSimpleXY2(NULL, na1, na2, GPLOT_LINES,
+                          outformat, outroot, title);
+}
+
+
+/*!
+ *  gplotSimpleN()
+ *
+ *      Input:  naa (numaa; we plotted with Y_VS_I for each numa)
+ *              outformat (GPLOT_PNG, GPLOT_PS, GPLOT_EPS, GPLOT_X11,
+ *                         GPLOT_LATEX)
+ *              outroot (root of output files)
+ *              title (<optional>)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This gives a line plot of all numas in a numaa (array of numa),
+ *          where the array values are each plotted vs the array index.
+ *          The plot is generated in the specified output format;
+ *          the title  is optional.
+ *      (2) When calling these simple plot functions more than once, use
+ *          different @outroot to avoid overwriting the output files.
+ */
+l_int32
+gplotSimpleN(NUMAA       *naa,
+             l_int32      outformat,
+             const char  *outroot,
+             const char  *title)
+{
+    return gplotSimpleXYN(NULL, naa, GPLOT_LINES, outformat, outroot, title);
+}
+
+
+/*!
+ *  gplotSimpleXY1()
+ *
+ *      Input:  nax (<optional>)
+ *              nay
+ *              plotstyle (GPLOT_LINES, GPLOT_POINTS, GPLOT_IMPULSES,
+ *                         GPLOT_LINESPOINTS, GPLOT_DOTS)
+ *              outformat (GPLOT_PNG, GPLOT_PS, GPLOT_EPS, GPLOT_X11,
+ *                         GPLOT_LATEX)
+ *              outroot (root of output files)
+ *              title  (<optional>, can be NULL)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This gives a plot of a @nay vs @nax, generated in
+ *          the specified output format.  The title is optional.
+ *      (2) Use 0 for default plotstyle (lines).
+ *      (3) @nax is optional.  If NULL, @nay is plotted against
+ *          the array index.
+ *      (4) When calling these simple plot functions more than once, use
+ *          different @outroot to avoid overwriting the output files.
+ */
+l_int32
+gplotSimpleXY1(NUMA        *nax,
+               NUMA        *nay,
+               l_int32      plotstyle,
+               l_int32      outformat,
+               const char  *outroot,
+               const char  *title)
+{
+GPLOT  *gplot;
+
+    PROCNAME("gplotSimpleXY1");
+
+    if (!nay)
+        return ERROR_INT("nay not defined", procName, 1);
+    if (plotstyle != GPLOT_LINES && plotstyle != GPLOT_POINTS &&
+        plotstyle != GPLOT_IMPULSES && plotstyle != GPLOT_LINESPOINTS &&
+        plotstyle != GPLOT_DOTS)
+        return ERROR_INT("invalid plotstyle", procName, 1);
+    if (outformat != GPLOT_PNG && outformat != GPLOT_PS &&
+        outformat != GPLOT_EPS && outformat != GPLOT_X11 &&
+        outformat != GPLOT_LATEX)
+        return ERROR_INT("invalid outformat", procName, 1);
+    if (!outroot)
+        return ERROR_INT("outroot not specified", procName, 1);
+
+    if ((gplot = gplotCreate(outroot, outformat, title, NULL, NULL)) == 0)
+        return ERROR_INT("gplot not made", procName, 1);
+    gplotAddPlot(gplot, nax, nay, plotstyle, NULL);
+    gplotMakeOutput(gplot);
+    gplotDestroy(&gplot);
+    return 0;
+}
+
+
+/*!
+ *  gplotSimpleXY2()
+ *
+ *      Input:  nax (<optional; can be NULL)
+ *              nay1
+ *              nay2
+ *              plotstyle (GPLOT_LINES, GPLOT_POINTS, GPLOT_IMPULSES,
+ *                         GPLOT_LINESPOINTS, GPLOT_DOTS)
+ *              outformat (GPLOT_PNG, GPLOT_PS, GPLOT_EPS, GPLOT_X11,
+ *                         GPLOT_LATEX)
+ *              outroot (root of output files)
+ *              title  (<optional>)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This gives plots of @nay1 and @nay2 against nax, generated
+ *          in the specified output format.  The title is optional.
+ *      (2) Use 0 for default plotstyle (lines).
+ *      (3) @nax is optional.  If NULL, @nay1 and @nay2 are plotted
+ *          against the array index.
+ *      (4) When calling these simple plot functions more than once, use
+ *          different @outroot to avoid overwriting the output files.
+ */
+l_int32
+gplotSimpleXY2(NUMA        *nax,
+               NUMA        *nay1,
+               NUMA        *nay2,
+               l_int32      plotstyle,
+               l_int32      outformat,
+               const char  *outroot,
+               const char  *title)
+{
+GPLOT  *gplot;
+
+    PROCNAME("gplotSimpleXY2");
+
+    if (!nay1 || !nay2)
+        return ERROR_INT("nay1 and nay2 not both defined", procName, 1);
+    if (plotstyle != GPLOT_LINES && plotstyle != GPLOT_POINTS &&
+        plotstyle != GPLOT_IMPULSES && plotstyle != GPLOT_LINESPOINTS &&
+        plotstyle != GPLOT_DOTS)
+        return ERROR_INT("invalid plotstyle", procName, 1);
+    if (outformat != GPLOT_PNG && outformat != GPLOT_PS &&
+        outformat != GPLOT_EPS && outformat != GPLOT_X11 &&
+        outformat != GPLOT_LATEX)
+        return ERROR_INT("invalid outformat", procName, 1);
+    if (!outroot)
+        return ERROR_INT("outroot not specified", procName, 1);
+
+    if ((gplot = gplotCreate(outroot, outformat, title, NULL, NULL)) == 0)
+        return ERROR_INT("gplot not made", procName, 1);
+    gplotAddPlot(gplot, nax, nay1, plotstyle, NULL);
+    gplotAddPlot(gplot, nax, nay2, plotstyle, NULL);
+    gplotMakeOutput(gplot);
+    gplotDestroy(&gplot);
+    return 0;
+}
+
+
+/*!
+ *  gplotSimpleXYN()
+ *
+ *      Input:  nax (<optional>; can be NULL)
+ *              naay (numaa of arrays to plot against @nax)
+ *              plotstyle (GPLOT_LINES, GPLOT_POINTS, GPLOT_IMPULSES,
+ *                         GPLOT_LINESPOINTS, GPLOT_DOTS)
+ *              outformat (GPLOT_PNG, GPLOT_PS, GPLOT_EPS, GPLOT_X11,
+ *                         GPLOT_LATEX)
+ *              outroot (root of output files)
+ *              title (<optional>)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This gives plots of each Numa in @naa against nax,
+ *          generated in the specified output format.  The title is optional.
+ *      (2) Use 0 for default plotstyle (lines).
+ *      (3) @nax is optional.  If NULL, each Numa array is plotted against
+ *          the array index.
+ *      (4) When calling these simple plot functions more than once, use
+ *          different @outroot to avoid overwriting the output files.
+ */
+l_int32
+gplotSimpleXYN(NUMA        *nax,
+               NUMAA       *naay,
+               l_int32      plotstyle,
+               l_int32      outformat,
+               const char  *outroot,
+               const char  *title)
+{
+l_int32  i, n;
+GPLOT   *gplot;
+NUMA    *nay;
+
+    PROCNAME("gplotSimpleXYN");
+
+    if (!naay)
+        return ERROR_INT("naay not defined", procName, 1);
+    if ((n = numaaGetCount(naay)) == 0)
+        return ERROR_INT("no numa in array", procName, 1);
+    if (plotstyle != GPLOT_LINES && plotstyle != GPLOT_POINTS &&
+        plotstyle != GPLOT_IMPULSES && plotstyle != GPLOT_LINESPOINTS &&
+        plotstyle != GPLOT_DOTS)
+        return ERROR_INT("invalid plotstyle", procName, 1);
+    if (outformat != GPLOT_PNG && outformat != GPLOT_PS &&
+        outformat != GPLOT_EPS && outformat != GPLOT_X11 &&
+        outformat != GPLOT_LATEX)
+        return ERROR_INT("invalid outformat", procName, 1);
+    if (!outroot)
+        return ERROR_INT("outroot not specified", procName, 1);
+
+    if ((gplot = gplotCreate(outroot, outformat, title, NULL, NULL)) == 0)
+        return ERROR_INT("gplot not made", procName, 1);
+    for (i = 0; i < n; i++) {
+        nay = numaaGetNuma(naay, i, L_CLONE);
+        gplotAddPlot(gplot, nax, nay, plotstyle, NULL);
+        numaDestroy(&nay);
+    }
+    gplotMakeOutput(gplot);
+    gplotDestroy(&gplot);
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------*
+ *                           Serialize for I/O                     *
+ *-----------------------------------------------------------------*/
+/*!
+ *  gplotRead()
+ *
+ *      Input:  filename
+ *      Return: gplot, or NULL on error
+ */
+GPLOT *
+gplotRead(const char  *filename)
+{
+char     buf[L_BUF_SIZE];
+char    *rootname, *title, *xlabel, *ylabel, *ignores;
+l_int32  outformat, ret, version, ignore;
+FILE    *fp;
+GPLOT   *gplot;
+
+    PROCNAME("gplotRead");
+
+    if (!filename)
+        return (GPLOT *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (GPLOT *)ERROR_PTR("stream not opened", procName, NULL);
+
+    ret = fscanf(fp, "Gplot Version %d\n", &version);
+    if (ret != 1) {
+        fclose(fp);
+        return (GPLOT *)ERROR_PTR("not a gplot file", procName, NULL);
+    }
+    if (version != GPLOT_VERSION_NUMBER) {
+        fclose(fp);
+        return (GPLOT *)ERROR_PTR("invalid gplot version", procName, NULL);
+    }
+
+    ignore = fscanf(fp, "Rootname: %s\n", buf);
+    rootname = stringNew(buf);
+    ignore = fscanf(fp, "Output format: %d\n", &outformat);
+    ignores = fgets(buf, L_BUF_SIZE, fp);   /* Title: ... */
+    title = stringNew(buf + 7);
+    title[strlen(title) - 1] = '\0';
+    ignores = fgets(buf, L_BUF_SIZE, fp);   /* X axis label: ... */
+    xlabel = stringNew(buf + 14);
+    xlabel[strlen(xlabel) - 1] = '\0';
+    ignores = fgets(buf, L_BUF_SIZE, fp);   /* Y axis label: ... */
+    ylabel = stringNew(buf + 14);
+    ylabel[strlen(ylabel) - 1] = '\0';
+
+    if (!(gplot = gplotCreate(rootname, outformat, title, xlabel, ylabel))) {
+        fclose(fp);
+        return (GPLOT *)ERROR_PTR("gplot not made", procName, NULL);
+    }
+    LEPT_FREE(rootname);
+    LEPT_FREE(title);
+    LEPT_FREE(xlabel);
+    LEPT_FREE(ylabel);
+    sarrayDestroy(&gplot->cmddata);
+    sarrayDestroy(&gplot->datanames);
+    sarrayDestroy(&gplot->plotdata);
+    sarrayDestroy(&gplot->plottitles);
+    numaDestroy(&gplot->plotstyles);
+
+    ignore = fscanf(fp, "Commandfile name: %s\n", buf);
+    stringReplace(&gplot->cmdname, buf);
+    ignore = fscanf(fp, "\nCommandfile data:");
+    gplot->cmddata = sarrayReadStream(fp);
+    ignore = fscanf(fp, "\nDatafile names:");
+    gplot->datanames = sarrayReadStream(fp);
+    ignore = fscanf(fp, "\nPlot data:");
+    gplot->plotdata = sarrayReadStream(fp);
+    ignore = fscanf(fp, "\nPlot titles:");
+    gplot->plottitles = sarrayReadStream(fp);
+    ignore = fscanf(fp, "\nPlot styles:");
+    gplot->plotstyles = numaReadStream(fp);
+
+    ignore = fscanf(fp, "Number of plots: %d\n", &gplot->nplots);
+    ignore = fscanf(fp, "Output file name: %s\n", buf);
+    stringReplace(&gplot->outname, buf);
+    ignore = fscanf(fp, "Axis scaling: %d\n", &gplot->scaling);
+
+    fclose(fp);
+    return gplot;
+}
+
+
+/*!
+ *  gplotWrite()
+ *
+ *      Input:  filename
+ *              gplot
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+gplotWrite(const char  *filename,
+           GPLOT       *gplot)
+{
+FILE  *fp;
+
+    PROCNAME("gplotWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!gplot)
+        return ERROR_INT("gplot not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+
+    fprintf(fp, "Gplot Version %d\n", GPLOT_VERSION_NUMBER);
+    fprintf(fp, "Rootname: %s\n", gplot->rootname);
+    fprintf(fp, "Output format: %d\n", gplot->outformat);
+    fprintf(fp, "Title: %s\n", gplot->title);
+    fprintf(fp, "X axis label: %s\n", gplot->xlabel);
+    fprintf(fp, "Y axis label: %s\n", gplot->ylabel);
+
+    fprintf(fp, "Commandfile name: %s\n", gplot->cmdname);
+    fprintf(fp, "\nCommandfile data:");
+    sarrayWriteStream(fp, gplot->cmddata);
+    fprintf(fp, "\nDatafile names:");
+    sarrayWriteStream(fp, gplot->datanames);
+    fprintf(fp, "\nPlot data:");
+    sarrayWriteStream(fp, gplot->plotdata);
+    fprintf(fp, "\nPlot titles:");
+    sarrayWriteStream(fp, gplot->plottitles);
+    fprintf(fp, "\nPlot styles:");
+    numaWriteStream(fp, gplot->plotstyles);
+
+    fprintf(fp, "Number of plots: %d\n", gplot->nplots);
+    fprintf(fp, "Output file name: %s\n", gplot->outname);
+    fprintf(fp, "Axis scaling: %d\n", gplot->scaling);
+
+    fclose(fp);
+    return 0;
+}
diff --git a/src/gplot.h b/src/gplot.h
new file mode 100644 (file)
index 0000000..5efd6f0
--- /dev/null
@@ -0,0 +1,88 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_GPLOT_H
+#define  LEPTONICA_GPLOT_H
+
+/*
+ *   gplot.h
+ *
+ *       Data structures and parameters for generating gnuplot files
+ */
+
+#define  GPLOT_VERSION_NUMBER    1
+
+#define  NUM_GPLOT_STYLES      5
+enum GPLOT_STYLE {
+    GPLOT_LINES       = 0,
+    GPLOT_POINTS      = 1,
+    GPLOT_IMPULSES    = 2,
+    GPLOT_LINESPOINTS = 3,
+    GPLOT_DOTS        = 4
+};
+
+#define  NUM_GPLOT_OUTPUTS     6
+enum GPLOT_OUTPUT {
+    GPLOT_NONE  = 0,
+    GPLOT_PNG   = 1,
+    GPLOT_PS    = 2,
+    GPLOT_EPS   = 3,
+    GPLOT_X11   = 4,
+    GPLOT_LATEX = 5
+};
+
+enum GPLOT_SCALING {
+    GPLOT_LINEAR_SCALE  = 0,   /* default */
+    GPLOT_LOG_SCALE_X   = 1,
+    GPLOT_LOG_SCALE_Y   = 2,
+    GPLOT_LOG_SCALE_X_Y = 3
+};
+
+extern const char  *gplotstylenames[];  /* used in gnuplot cmd file */
+extern const char  *gplotfilestyles[];  /* used in simple file input */
+extern const char  *gplotfileoutputs[]; /* used in simple file input */
+
+struct GPlot
+{
+    char          *rootname;   /* for cmd, data, output            */
+    char          *cmdname;    /* command file name                */
+    struct Sarray *cmddata;    /* command file contents            */
+    struct Sarray *datanames;  /* data file names                  */
+    struct Sarray *plotdata;   /* plot data (1 string/file)        */
+    struct Sarray *plottitles; /* title for each individual plot   */
+    struct Numa   *plotstyles; /* plot style for individual plots  */
+    l_int32        nplots;     /* current number of plots          */
+    char          *outname;    /* output file name                 */
+    l_int32        outformat;  /* GPLOT_OUTPUT values              */
+    l_int32        scaling;    /* GPLOT_SCALING values             */
+    char          *title;      /* optional                         */
+    char          *xlabel;     /* optional x axis label            */
+    char          *ylabel;     /* optional y axis label            */
+};
+typedef struct GPlot  GPLOT;
+
+
+#endif /* LEPTONICA_GPLOT_H */
diff --git a/src/graphics.c b/src/graphics.c
new file mode 100644 (file)
index 0000000..8f2b0ef
--- /dev/null
@@ -0,0 +1,2744 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  graphics.c
+ *
+ *      Pta generation for arbitrary shapes built with lines
+ *          PTA        *generatePtaLine()
+ *          PTA        *generatePtaWideLine()
+ *          PTA        *generatePtaBox()
+ *          PTA        *generatePtaBoxa()
+ *          PTA        *generatePtaHashBox()
+ *          PTA        *generatePtaHashBoxa()
+ *          PTAA       *generatePtaaBoxa()
+ *          PTAA       *generatePtaaHashBoxa()
+ *          PTA        *generatePtaPolyline()
+ *          PTA        *generatePtaGrid()
+ *          PTA        *convertPtaLineTo4cc()
+ *          PTA        *generatePtaFilledCircle()
+ *          PTA        *generatePtaFilledSquare()
+ *          PTA        *generatePtaLineFromPt()
+ *          l_int32     locatePtRadially()
+ *
+ *      Rendering function plots directly on images
+ *          l_int32     pixRenderPlotFromNuma()
+ *          l_int32     pixRenderPlotFromNumaGen()
+ *          PTA        *makePlotPtaFromNuma()
+ *          PTA        *makePlotPtaFromNumaGen()
+ *
+ *      Pta rendering
+ *          l_int32     pixRenderPta()
+ *          l_int32     pixRenderPtaArb()
+ *          l_int32     pixRenderPtaBlend()
+ *
+ *      Rendering of arbitrary shapes built with lines
+ *          l_int32     pixRenderLine()
+ *          l_int32     pixRenderLineArb()
+ *          l_int32     pixRenderLineBlend()
+ *
+ *          l_int32     pixRenderBox()
+ *          l_int32     pixRenderBoxArb()
+ *          l_int32     pixRenderBoxBlend()
+ *
+ *          l_int32     pixRenderBoxa()
+ *          l_int32     pixRenderBoxaArb()
+ *          l_int32     pixRenderBoxaBlend()
+ *
+ *          l_int32     pixRenderHashBox()
+ *          l_int32     pixRenderHashBoxArb()
+ *          l_int32     pixRenderHashBoxBlend()
+ *
+ *          l_int32     pixRenderHashBoxa()
+ *          l_int32     pixRenderHashBoxaArb()
+ *          l_int32     pixRenderHashBoxaBlend()
+ *
+ *          l_int32     pixRenderPolyline()
+ *          l_int32     pixRenderPolylineArb()
+ *          l_int32     pixRenderPolylineBlend()
+ *
+ *          l_int32     pixRenderGrid()
+ *
+ *          l_int32     pixRenderRandomCmapPtaa()
+ *
+ *      Rendering and filling of polygons
+ *          PIX        *pixRenderPolygon()
+ *          PIX        *pixFillPolygon()
+ *
+ *      Contour rendering on grayscale images
+ *          PIX        *pixRenderContours()
+ *          PIX        *fpixAutoRenderContours()
+ *          PIX        *fpixRenderContours()
+ *
+ *      Boundary pt generation on 1 bpp images
+ *          PTA        *pixGeneratePtaBoundary()
+ *
+ *  The line rendering functions are relatively crude, but they
+ *  get the job done for most simple situations.  We use the pta
+ *  (array of points) as an intermediate data structure.  For example,
+ *  to render a line we first generate a pta.
+ *
+ *  Some rendering functions come in sets of three.  For example
+ *       pixRenderLine() -- render on 1 bpp pix
+ *       pixRenderLineArb() -- render on 32 bpp pix with arbitrary (r,g,b)
+ *       pixRenderLineBlend() -- render on 32 bpp pix, blending the
+ *               (r,g,b) graphic object with the underlying rgb pixels.
+ *
+ *  There are also procedures for plotting a function, computed
+ *  from the row or column pixels, directly on the image.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+
+/*------------------------------------------------------------------*
+ *        Pta generation for arbitrary shapes built with lines      *
+ *------------------------------------------------------------------*/
+/*!
+ *  generatePtaLine()
+ *
+ *      Input:  x1, y1  (end point 1)
+ *              x2, y2  (end point 2)
+ *      Return: pta, or null on error
+ *
+ *  Notes:
+ *      (1) Uses Bresenham line drawing, which results in an 8-connected line.
+ */
+PTA  *
+generatePtaLine(l_int32  x1,
+                l_int32  y1,
+                l_int32  x2,
+                l_int32  y2)
+{
+l_int32    npts, diff, getyofx, sign, i, x, y;
+l_float32  slope;
+PTA       *pta;
+
+    PROCNAME("generatePtaLine");
+
+        /* Generate line parameters */
+    if (x1 == x2 && y1 == y2) {  /* same point */
+        npts = 1;
+    } else if (L_ABS(x2 - x1) >= L_ABS(y2 - y1)) {
+        getyofx = TRUE;
+        npts = L_ABS(x2 - x1) + 1;
+        diff = x2 - x1;
+        sign = L_SIGN(x2 - x1);
+        slope = (l_float32)(sign * (y2 - y1)) / (l_float32)diff;
+    } else {
+        getyofx = FALSE;
+        npts = L_ABS(y2 - y1) + 1;
+        diff = y2 - y1;
+        sign = L_SIGN(y2 - y1);
+        slope = (l_float32)(sign * (x2 - x1)) / (l_float32)diff;
+    }
+
+    if ((pta = ptaCreate(npts)) == NULL)
+        return (PTA *)ERROR_PTR("pta not made", procName, NULL);
+
+    if (npts == 1) {  /* degenerate case */
+        ptaAddPt(pta, x1, y1);
+        return pta;
+    }
+
+        /* Generate the set of points */
+    if (getyofx) {  /* y = y(x) */
+        for (i = 0; i < npts; i++) {
+            x = x1 + sign * i;
+            y = (l_int32)(y1 + (l_float32)i * slope + 0.5);
+            ptaAddPt(pta, x, y);
+        }
+    } else {   /* x = x(y) */
+        for (i = 0; i < npts; i++) {
+            x = (l_int32)(x1 + (l_float32)i * slope + 0.5);
+            y = y1 + sign * i;
+            ptaAddPt(pta, x, y);
+        }
+    }
+
+    return pta;
+}
+
+
+/*!
+ *  generatePtaWideLine()
+ *
+ *      Input:  x1, y1  (end point 1)
+ *              x2, y2  (end point 2)
+ *              width
+ *      Return: ptaj, or null on error
+ */
+PTA  *
+generatePtaWideLine(l_int32  x1,
+                    l_int32  y1,
+                    l_int32  x2,
+                    l_int32  y2,
+                    l_int32  width)
+{
+l_int32  i, x1a, x2a, y1a, y2a;
+PTA     *pta, *ptaj;
+
+    PROCNAME("generatePtaWideLine");
+
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    if ((ptaj = generatePtaLine(x1, y1, x2, y2)) == NULL)
+        return (PTA *)ERROR_PTR("ptaj not made", procName, NULL);
+    if (width == 1)
+        return ptaj;
+
+        /* width > 1; estimate line direction & join */
+    if (L_ABS(x1 - x2) > L_ABS(y1 - y2)) {  /* "horizontal" line  */
+        for (i = 1; i < width; i++) {
+            if ((i & 1) == 1) {   /* place above */
+                y1a = y1 - (i + 1) / 2;
+                y2a = y2 - (i + 1) / 2;
+            } else {  /* place below */
+                y1a = y1 + (i + 1) / 2;
+                y2a = y2 + (i + 1) / 2;
+            }
+            if ((pta = generatePtaLine(x1, y1a, x2, y2a)) == NULL)
+                return (PTA *)ERROR_PTR("pta not made", procName, NULL);
+            ptaJoin(ptaj, pta, 0, -1);
+            ptaDestroy(&pta);
+        }
+    } else  {  /* "vertical" line  */
+        for (i = 1; i < width; i++) {
+            if ((i & 1) == 1) {   /* place to left */
+                x1a = x1 - (i + 1) / 2;
+                x2a = x2 - (i + 1) / 2;
+            } else {  /* place to right */
+                x1a = x1 + (i + 1) / 2;
+                x2a = x2 + (i + 1) / 2;
+            }
+            if ((pta = generatePtaLine(x1a, y1, x2a, y2)) == NULL)
+                return (PTA *)ERROR_PTR("pta not made", procName, NULL);
+            ptaJoin(ptaj, pta, 0, -1);
+            ptaDestroy(&pta);
+        }
+    }
+
+    return ptaj;
+}
+
+
+/*!
+ *  generatePtaBox()
+ *
+ *      Input:  box
+ *              width (of line)
+ *      Return: ptad, or null on error
+ *
+ *  Notes:
+ *      (1) Because the box is constructed so that we don't have any
+ *          overlapping lines, there is no need to remove duplicates.
+ */
+PTA  *
+generatePtaBox(BOX     *box,
+               l_int32  width)
+{
+l_int32  x, y, w, h;
+PTA     *ptad, *pta;
+
+    PROCNAME("generatePtaBox");
+
+    if (!box)
+        return (PTA *)ERROR_PTR("box not defined", procName, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+        /* Generate line points and add them to the pta. */
+    boxGetGeometry(box, &x, &y, &w, &h);
+    if (w == 0 || h == 0)
+        return (PTA *)ERROR_PTR("box has w = 0 or h = 0", procName, NULL);
+    ptad = ptaCreate(0);
+    if ((width & 1) == 1) {   /* odd width */
+        pta = generatePtaWideLine(x - width / 2, y,
+                                  x + w - 1 + width / 2, y, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+        pta = generatePtaWideLine(x + w - 1, y + 1 + width / 2,
+                                  x + w - 1, y + h - 2 - width / 2, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+        pta = generatePtaWideLine(x + w - 1 + width / 2, y + h - 1,
+                                  x - width / 2, y + h - 1, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+        pta = generatePtaWideLine(x, y + h - 2 - width / 2,
+                                  x, y + 1 + width / 2, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+    } else {   /* even width */
+        pta = generatePtaWideLine(x - width / 2, y,
+                                  x + w - 2 + width / 2, y, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+        pta = generatePtaWideLine(x + w - 1, y + 0 + width / 2,
+                                  x + w - 1, y + h - 2 - width / 2, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+        pta = generatePtaWideLine(x + w - 2 + width / 2, y + h - 1,
+                                  x - width / 2, y + h - 1, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+        pta = generatePtaWideLine(x, y + h - 2 - width / 2,
+                                  x, y + 0 + width / 2, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  generatePtaBoxa()
+ *
+ *      Input:  boxa
+ *              width
+ *              removedups  (1 to remove, 0 to leave)
+ *      Return: ptad, or null on error
+ *
+ *  Notes:
+ *      (1) If the boxa has overlapping boxes, and if blending will
+ *          be used to give a transparent effect, transparency
+ *          artifacts at line intersections can be removed using
+ *          removedups = 1.
+ */
+PTA  *
+generatePtaBoxa(BOXA    *boxa,
+                l_int32  width,
+                l_int32  removedups)
+{
+l_int32  i, n;
+BOX     *box;
+PTA     *ptad, *ptat, *pta;
+
+    PROCNAME("generatePtaBoxa");
+
+    if (!boxa)
+        return (PTA *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    n = boxaGetCount(boxa);
+    ptat = ptaCreate(0);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        pta = generatePtaBox(box, width);
+        ptaJoin(ptat, pta, 0, -1);
+        ptaDestroy(&pta);
+        boxDestroy(&box);
+    }
+
+    if (removedups)
+        ptad = ptaRemoveDupsByAset(ptat);
+    else
+        ptad = ptaClone(ptat);
+
+    ptaDestroy(&ptat);
+    return ptad;
+}
+
+
+/*!
+ *  generatePtaHashBox()
+ *
+ *      Input:  box
+ *              spacing (spacing between lines; must be > 1)
+ *              width  (of line)
+ *              orient  (orientation of lines: L_HORIZONTAL_LINE, ...)
+ *              outline  (0 to skip drawing box outline)
+ *      Return: ptad, or null on error
+ *
+ *  Notes:
+ *      (1) The orientation takes on one of 4 orientations (horiz, vertical,
+ *          slope +1, slope -1).
+ *      (2) The full outline is also drawn if @outline = 1.
+ */
+PTA  *
+generatePtaHashBox(BOX     *box,
+                   l_int32  spacing,
+                   l_int32  width,
+                   l_int32  orient,
+                   l_int32  outline)
+{
+l_int32  bx, by, bh, bw, x, y, x1, y1, x2, y2, i, n, npts;
+PTA     *ptad, *pta;
+
+    PROCNAME("generatePtaHashBox");
+
+    if (!box)
+        return (PTA *)ERROR_PTR("box not defined", procName, NULL);
+    if (spacing <= 1)
+        return (PTA *)ERROR_PTR("spacing not > 1", procName, NULL);
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return (PTA *)ERROR_PTR("invalid line orientation", procName, NULL);
+    boxGetGeometry(box, &bx, &by, &bw, &bh);
+    if (bw == 0 || bh == 0)
+        return (PTA *)ERROR_PTR("box has bw = 0 or bh = 0", procName, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+        /* Generate line points and add them to the pta. */
+    ptad = ptaCreate(0);
+    if (outline) {
+        pta = generatePtaBox(box, width);
+        ptaJoin(ptad, pta, 0, -1);
+        ptaDestroy(&pta);
+    }
+    if (orient == L_HORIZONTAL_LINE) {
+        n = 1 + bh / spacing;
+        for (i = 0; i < n; i++) {
+            y = by + (i * (bh - 1)) / (n - 1);
+            pta = generatePtaWideLine(bx, y, bx + bw - 1, y, width);
+            ptaJoin(ptad, pta, 0, -1);
+            ptaDestroy(&pta);
+        }
+    } else if (orient == L_VERTICAL_LINE) {
+        n = 1 + bw / spacing;
+        for (i = 0; i < n; i++) {
+            x = bx + (i * (bw - 1)) / (n - 1);
+            pta = generatePtaWideLine(x, by, x, by + bh - 1, width);
+            ptaJoin(ptad, pta, 0, -1);
+            ptaDestroy(&pta);
+        }
+    } else if (orient == L_POS_SLOPE_LINE) {
+        n = 2 + (l_int32)((bw + bh) / (1.4 * spacing));
+        for (i = 0; i < n; i++) {
+            x = (l_int32)(bx + (i + 0.5) * 1.4 * spacing);
+            boxIntersectByLine(box, x, by - 1, 1.0, &x1, &y1, &x2, &y2, &npts);
+            if (npts == 2) {
+                pta = generatePtaWideLine(x1, y1, x2, y2, width);
+                ptaJoin(ptad, pta, 0, -1);
+                ptaDestroy(&pta);
+            }
+        }
+    } else {  /* orient == L_NEG_SLOPE_LINE */
+        n = 2 + (l_int32)((bw + bh) / (1.4 * spacing));
+        for (i = 0; i < n; i++) {
+            x = (l_int32)(bx - bh + (i + 0.5) * 1.4 * spacing);
+            boxIntersectByLine(box, x, by - 1, -1.0, &x1, &y1, &x2, &y2, &npts);
+            if (npts == 2) {
+                pta = generatePtaWideLine(x1, y1, x2, y2, width);
+                ptaJoin(ptad, pta, 0, -1);
+                ptaDestroy(&pta);
+            }
+        }
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  generatePtaHashBoxa()
+ *
+ *      Input:  boxa
+ *              spacing (spacing between lines; must be > 1)
+ *              width  (of line)
+ *              orient  (orientation of lines: L_HORIZONTAL_LINE, ...)
+ *              outline  (0 to skip drawing box outline)
+ *              removedups  (1 to remove, 0 to leave)
+ *      Return: ptad, or null on error
+ *
+ *  Notes:
+ *      (1) The orientation takes on one of 4 orientations (horiz, vertical,
+ *          slope +1, slope -1).
+ *      (2) The full outline is also drawn if @outline = 1.
+ *      (3) If the boxa has overlapping boxes, and if blending will
+ *          be used to give a transparent effect, transparency
+ *          artifacts at line intersections can be removed using
+ *          removedups = 1.
+ */
+PTA  *
+generatePtaHashBoxa(BOXA    *boxa,
+                    l_int32  spacing,
+                    l_int32  width,
+                    l_int32  orient,
+                    l_int32  outline,
+                    l_int32  removedups)
+{
+l_int32  i, n;
+BOX     *box;
+PTA     *ptad, *ptat, *pta;
+
+    PROCNAME("generatePtaHashBoxa");
+
+    if (!boxa)
+        return (PTA *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (spacing <= 1)
+        return (PTA *)ERROR_PTR("spacing not > 1", procName, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return (PTA *)ERROR_PTR("invalid line orientation", procName, NULL);
+
+    n = boxaGetCount(boxa);
+    ptat = ptaCreate(0);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        pta = generatePtaHashBox(box, spacing, width, orient, outline);
+        ptaJoin(ptat, pta, 0, -1);
+        ptaDestroy(&pta);
+        boxDestroy(&box);
+    }
+
+    if (removedups)
+        ptad = ptaRemoveDupsByAset(ptat);
+    else
+        ptad = ptaClone(ptat);
+
+    ptaDestroy(&ptat);
+    return ptad;
+}
+
+
+/*!
+ *  generatePtaaBoxa()
+ *
+ *      Input:  boxa
+ *      Return: ptaa, or null on error
+ *
+ *  Notes:
+ *      (1) This generates a pta of the four corners for each box in
+ *          the boxa.
+ *      (2) Each of these pta can be rendered onto a pix with random colors,
+ *          by using pixRenderRandomCmapPtaa() with closeflag = 1.
+ */
+PTAA  *
+generatePtaaBoxa(BOXA  *boxa)
+{
+l_int32  i, n, x, y, w, h;
+BOX     *box;
+PTA     *pta;
+PTAA    *ptaa;
+
+    PROCNAME("generatePtaaBoxa");
+
+    if (!boxa)
+        return (PTAA *)ERROR_PTR("boxa not defined", procName, NULL);
+
+    n = boxaGetCount(boxa);
+    ptaa = ptaaCreate(n);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        boxGetGeometry(box, &x, &y, &w, &h);
+        pta = ptaCreate(4);
+        ptaAddPt(pta, x, y);
+        ptaAddPt(pta, x + w - 1, y);
+        ptaAddPt(pta, x + w - 1, y + h - 1);
+        ptaAddPt(pta, x, y + h - 1);
+        ptaaAddPta(ptaa, pta, L_INSERT);
+        boxDestroy(&box);
+    }
+
+    return ptaa;
+}
+
+
+/*!
+ *  generatePtaaHashBoxa()
+ *
+ *      Input:  boxa
+ *              spacing (spacing between hash lines; must be > 1)
+ *              width  (hash line width)
+ *              orient  (orientation of lines: L_HORIZONTAL_LINE, ...)
+ *              outline  (0 to skip drawing box outline)
+ *      Return: ptaa, or null on error
+ *
+ *  Notes:
+ *      (1) The orientation takes on one of 4 orientations (horiz, vertical,
+ *          slope +1, slope -1).
+ *      (2) The full outline is also drawn if @outline = 1.
+ *      (3) Each of these pta can be rendered onto a pix with random colors,
+ *          by using pixRenderRandomCmapPtaa() with closeflag = 1.
+ *
+ */
+PTAA *
+generatePtaaHashBoxa(BOXA    *boxa,
+                     l_int32  spacing,
+                     l_int32  width,
+                     l_int32  orient,
+                     l_int32  outline)
+{
+l_int32  i, n;
+BOX     *box;
+PTA     *pta;
+PTAA    *ptaa;
+
+    PROCNAME("generatePtaaHashBoxa");
+
+    if (!boxa)
+        return (PTAA *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (spacing <= 1)
+        return (PTAA *)ERROR_PTR("spacing not > 1", procName, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return (PTAA *)ERROR_PTR("invalid line orientation", procName, NULL);
+
+    n = boxaGetCount(boxa);
+    ptaa = ptaaCreate(n);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        pta = generatePtaHashBox(box, spacing, width, orient, outline);
+        ptaaAddPta(ptaa, pta, L_INSERT);
+        boxDestroy(&box);
+    }
+
+    return ptaa;
+}
+
+
+/*!
+ *  generatePtaPolyline()
+ *
+ *      Input:  pta (vertices of polyline)
+ *              width
+ *              closeflag (1 to close the contour; 0 otherwise)
+ *              removedups  (1 to remove, 0 to leave)
+ *      Return: ptad, or null on error
+ */
+PTA *
+generatePtaPolyline(PTA     *ptas,
+                    l_int32  width,
+                    l_int32  closeflag,
+                    l_int32  removedups)
+{
+l_int32  i, n, x1, y1, x2, y2;
+PTA     *ptad, *ptat, *pta;
+
+    PROCNAME("generatePtaPolyline");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    n = ptaGetCount(ptas);
+    ptat = ptaCreate(0);
+    if (n < 2)  /* nothing to do */
+        return ptat;
+
+    ptaGetIPt(ptas, 0, &x1, &y1);
+    for (i = 1; i < n; i++) {
+        ptaGetIPt(ptas, i, &x2, &y2);
+        pta = generatePtaWideLine(x1, y1, x2, y2, width);
+        ptaJoin(ptat, pta, 0, -1);
+        ptaDestroy(&pta);
+        x1 = x2;
+        y1 = y2;
+    }
+
+    if (closeflag) {
+        ptaGetIPt(ptas, 0, &x2, &y2);
+        pta = generatePtaWideLine(x1, y1, x2, y2, width);
+        ptaJoin(ptat, pta, 0, -1);
+        ptaDestroy(&pta);
+    }
+
+    if (removedups)
+        ptad = ptaRemoveDupsByAset(ptat);
+    else
+        ptad = ptaClone(ptat);
+
+    ptaDestroy(&ptat);
+    return ptad;
+}
+
+
+/*!
+ *  generatePtaGrid()
+ *
+ *      Input:  w, h (of region where grid will be displayed)
+ *              nx, ny  (number of rectangles in each direction in grid)
+ *              width (of rendered lines)
+ *      Return: ptad, or null on error
+ */
+PTA  *
+generatePtaGrid(l_int32  w,
+                l_int32  h,
+                l_int32  nx,
+                l_int32  ny,
+                l_int32  width)
+{
+l_int32  i, j, bx, by, x1, x2, y1, y2;
+BOX     *box;
+BOXA    *boxa;
+PTA     *pta;
+
+    PROCNAME("generatePtaGrid");
+
+    if (nx < 1 || ny < 1)
+        return (PTA *)ERROR_PTR("nx and ny must be > 0", procName, NULL);
+    if (w < 2 * nx || h < 2 * ny)
+        return (PTA *)ERROR_PTR("w and/or h too small", procName, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    boxa = boxaCreate(nx * ny);
+    bx = (w + nx - 1) / nx;
+    by = (h + ny - 1) / ny;
+    for (i = 0; i < ny; i++) {
+        y1 = by * i;
+        y2 = L_MIN(y1 + by, h - 1);
+        for (j = 0; j < nx; j++) {
+            x1 = bx * j;
+            x2 = L_MIN(x1 + bx, w - 1);
+            box = boxCreate(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
+            boxaAddBox(boxa, box, L_INSERT);
+        }
+    }
+
+    pta = generatePtaBoxa(boxa, width, 1);
+    boxaDestroy(&boxa);
+    return pta;
+}
+
+
+/*!
+ *  convertPtaLineTo4cc()
+ *
+ *      Input:  ptas (8-connected line of points)
+ *      Return: ptad (4-connected line), or null on error
+ *
+ *  Notes:
+ *      (1) When a polyline is generated with width = 1, the resulting
+ *          line is not 4-connected in general.  This function adds
+ *          points as necessary to convert the line to 4-cconnected.
+ *          It is useful when rendering 1 bpp on a pix.
+ *      (2) Do not use this for lines generated with width > 1.
+ */
+PTA *
+convertPtaLineTo4cc(PTA  *ptas)
+{
+l_int32  i, n, x, y, xp, yp;
+PTA     *ptad;
+
+    PROCNAME("convertPtaLineTo4cc");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+
+    n = ptaGetCount(ptas);
+    ptad = ptaCreate(n);
+    ptaGetIPt(ptas, 0, &xp, &yp);
+    ptaAddPt(ptad, xp, yp);
+    for (i = 1; i < n; i++) {
+        ptaGetIPt(ptas, i, &x, &y);
+        if (x != xp && y != yp)  /* diagonal */
+            ptaAddPt(ptad, x, yp);
+        ptaAddPt(ptad, x, y);
+        xp = x;
+        yp = y;
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  generatePtaFilledCircle()
+ *
+ *      Input:  radius
+ *      Return: pta, or null on error
+ *
+ *  Notes:
+ *      (1) The circle is has diameter = 2 * radius + 1.
+ *      (2) It is located with the center of the circle at the
+ *          point (radius, radius).
+ *      (3) Consequently, it typically must be translated if
+ *          it is to represent a set of pixels in an image.
+ */
+PTA *
+generatePtaFilledCircle(l_int32  radius)
+{
+l_int32    x, y;
+l_float32  radthresh, sqdist;
+PTA       *pta;
+
+    PROCNAME("generatePtaFilledCircle");
+
+    if (radius < 1)
+        return (PTA *)ERROR_PTR("radius must be >= 1", procName, NULL);
+
+    pta = ptaCreate(0);
+    radthresh = (radius + 0.5) * (radius + 0.5);
+    for (y = 0; y <= 2 * radius; y++) {
+        for (x = 0; x <= 2 * radius; x++) {
+            sqdist = (l_float32)((y - radius) * (y - radius) +
+                                 (x - radius) * (x - radius));
+            if (sqdist <= radthresh)
+                ptaAddPt(pta, x, y);
+        }
+    }
+
+    return pta;
+}
+
+
+/*!
+ *  generatePtaFilledSquare()
+ *
+ *      Input:  side
+ *      Return: pta, or null on error
+ *
+ *  Notes:
+ *      (1) The center of the square can be chosen to be at
+ *          (side / 2, side / 2).  It must be translated by this amount
+ *          when used for replication.
+ */
+PTA *
+generatePtaFilledSquare(l_int32  side)
+{
+l_int32  x, y;
+PTA     *pta;
+
+    PROCNAME("generatePtaFilledSquare");
+    if (side < 1)
+        return (PTA *)ERROR_PTR("side must be > 0", procName, NULL);
+
+    pta = ptaCreate(0);
+    for (y = 0; y < side; y++)
+        for (x = 0; x < side; x++)
+            ptaAddPt(pta, x, y);
+
+    return pta;
+}
+
+
+/*!
+ *  generatePtaLineFromPt()
+ *
+ *      Input:  x, y  (point of origination)
+ *              length (of line, including starting point)
+ *              radang (angle in radians, CW from horizontal)
+ *      Return: pta, or null on error
+ *
+ *  Notes:
+ *      (1) The @length of the line is 1 greater than the distance
+ *          used in locatePtRadially().  Example: a distance of 1
+ *          gives rise to a length of 2.
+ */
+PTA *
+generatePtaLineFromPt(l_int32    x,
+                      l_int32    y,
+                      l_float64  length,
+                      l_float64  radang)
+{
+l_int32  x2, y2;  /* the point at the other end of the line */
+
+    x2 = x + (l_int32)((length - 1.0) * cos(radang));
+    y2 = y + (l_int32)((length - 1.0) * sin(radang));
+    return generatePtaLine(x, y, x2, y2);
+}
+
+
+/*!
+ *  locatePtRadially()
+ *
+ *      Input:  xr, yr  (reference point)
+ *              radang (angle in radians, CW from horizontal)
+ *              dist (distance of point from reference point along line
+ *                    given by the specified angle)
+ *              &x, &y (<return> location of point)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+locatePtRadially(l_int32     xr,
+                 l_int32     yr,
+                 l_float64   dist,
+                 l_float64   radang,
+                 l_float64  *px,
+                 l_float64  *py)
+{
+    PROCNAME("locatePtRadially");
+
+    if (!px || !py)
+        return ERROR_INT("&x and &y not both defined", procName, 1);
+
+    *px = xr + dist * cos(radang);
+    *py = yr + dist * sin(radang);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *            Rendering function plots directly on images           *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRenderPlotFromNuma()
+ *
+ *      Input:  &pix (any type; replaced if not 32 bpp rgb)
+ *              numa (to be plotted)
+ *              plotloc (location of plot: L_PLOT_AT_TOP, etc)
+ *              linewidth (width of "line" that is drawn; between 1 and 7)
+ *              max (maximum excursion in pixels from baseline)
+ *              color (plot color: 0xrrggbb00)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Simplified interface for plotting row or column aligned data
+ *          on a pix.
+ *      (2) This replaces @pix with a 32 bpp rgb version if it is not
+ *          already 32 bpp.  It then draws the plot on the pix.
+ *      (3) See makePlotPtaFromNumaGen() for more details.
+ */
+l_int32
+pixRenderPlotFromNuma(PIX     **ppix,
+                      NUMA     *na,
+                      l_int32   plotloc,
+                      l_int32   linewidth,
+                      l_int32   max,
+                      l_uint32  color)
+{
+l_int32  w, h, size, rval, gval, bval;
+PIX     *pix1;
+PTA     *pta;
+
+    PROCNAME("pixRenderPlotFromNuma");
+
+    if (!ppix)
+        return ERROR_INT("&pix not defined", procName, 1);
+    if (*ppix == NULL)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pixGetDimensions(*ppix, &w, &h, NULL);
+    size = (plotloc == L_PLOT_AT_TOP || plotloc == L_PLOT_AT_MID_HORIZ ||
+            plotloc == L_PLOT_AT_BOT) ? h : w;
+    pta = makePlotPtaFromNuma(na, size, plotloc, linewidth, max);
+    if (!pta)
+        return ERROR_INT("pta not made", procName, 1);
+
+    if (pixGetDepth(*ppix) != 32) {
+        pix1 = pixConvertTo32(*ppix);
+        pixDestroy(ppix);
+        *ppix = pix1;
+    }
+    extractRGBValues(color, &rval, &gval, &bval);
+    pixRenderPtaArb(*ppix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  makePlotPtaFromNuma()
+ *
+ *      Input:  numa
+ *              size (pix height for horizontal plot; width for vertical plot)
+ *              plotloc (location of plot: L_PLOT_AT_TOP, etc)
+ *              linewidth (width of "line" that is drawn; between 1 and 7)
+ *              max (maximum excursion in pixels from baseline)
+ *      Return: ptad, or null on error
+ *
+ *  Notes:
+ *      (1) This generates points from @numa representing y(x) or x(y)
+ *          with respect to a pix.  A horizontal plot y(x) is drawn for
+ *          a function of column position, and a vertical plot is drawn
+ *          for a function x(y) of row position.  The baseline is located
+ *          so that all plot points will fit in the pix.
+ *      (2) See makePlotPtaFromNumaGen() for more details.
+ */
+PTA *
+makePlotPtaFromNuma(NUMA    *na,
+                    l_int32  size,
+                    l_int32  plotloc,
+                    l_int32  linewidth,
+                    l_int32  max)
+{
+l_int32  orient, refpos;
+
+    PROCNAME("makePlotPtaFromNuma");
+
+    if (!na)
+        return (PTA *)ERROR_PTR("na not defined", procName, NULL);
+    if (plotloc == L_PLOT_AT_TOP || plotloc == L_PLOT_AT_MID_HORIZ ||
+        plotloc == L_PLOT_AT_BOT) {
+        orient = L_HORIZONTAL_LINE;
+    } else if (plotloc == L_PLOT_AT_LEFT || plotloc == L_PLOT_AT_MID_VERT ||
+               plotloc == L_PLOT_AT_RIGHT) {
+        orient = L_VERTICAL_LINE;
+    } else {
+        return (PTA *)ERROR_PTR("invalid plotloc", procName, NULL);
+    }
+
+    if (plotloc == L_PLOT_AT_LEFT || plotloc == L_PLOT_AT_TOP)
+        refpos = max;
+    else if (plotloc == L_PLOT_AT_MID_VERT || plotloc == L_PLOT_AT_MID_HORIZ)
+        refpos = size / 2;
+    else  /* L_PLOT_AT_RIGHT || L_PLOT_AT_BOT */
+        refpos = size - max - 1;
+
+    return makePlotPtaFromNumaGen(na, orient, linewidth, refpos, max, 1);
+}
+
+
+/*!
+ *  pixRenderPlotFromNumaGen()
+ *
+ *      Input:  &pix (any type; replaced if not 32 bpp rgb)
+ *              numa (to be plotted)
+ *              orient (L_HORIZONTAL_LINE, L_VERTICAL_LINE)
+ *              linewidth (width of "line" that is drawn; between 1 and 7)
+ *              refpos (reference position: y for horizontal and x for vertical)
+ *              max (maximum excursion in pixels from baseline)
+ *              drawref (1 to draw the reference line and the normal to it)
+ *              color (plot color: 0xrrggbb00)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) General interface for plotting row or column aligned data
+ *          on a pix.
+ *      (2) This replaces @pix with a 32 bpp rgb version if it is not
+ *          already 32 bpp.  It then draws the plot on the pix.
+ *      (3) See makePlotPtaFromNumaGen() for other input parameters.
+ */
+l_int32
+pixRenderPlotFromNumaGen(PIX     **ppix,
+                         NUMA     *na,
+                         l_int32   orient,
+                         l_int32   linewidth,
+                         l_int32   refpos,
+                         l_int32   max,
+                         l_int32   drawref,
+                         l_uint32  color)
+{
+l_int32  rval, gval, bval;
+PIX     *pix1;
+PTA     *pta;
+
+    PROCNAME("pixRenderPlotFromNumaGen");
+
+    if (!ppix)
+        return ERROR_INT("&pix not defined", procName, 1);
+    if (*ppix == NULL)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pta = makePlotPtaFromNumaGen(na, orient, linewidth, refpos, max, drawref);
+    if (!pta)
+        return ERROR_INT("pta not made", procName, 1);
+
+    if (pixGetDepth(*ppix) != 32) {
+        pix1 = pixConvertTo32(*ppix);
+        pixDestroy(ppix);
+        *ppix = pix1;
+    }
+    extractRGBValues(color, &rval, &gval, &bval);
+    pixRenderPtaArb(*ppix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  makePlotPtaFromNumaGen()
+ *
+ *      Input:  numa
+ *              orient (L_HORIZONTAL_LINE, L_VERTICAL_LINE)
+ *              linewidth (width of "line" that is drawn; between 1 and 7)
+ *              refpos (reference position: y for horizontal and x for vertical)
+ *              max (maximum excursion in pixels from baseline)
+ *              drawref (1 to draw the reference line and the normal to it)
+ *      Return: ptad, or null on error
+ *
+ *  Notes:
+ *      (1) This generates points from @numa representing y(x) or x(y)
+ *          with respect to a pix.  For y(x), we draw a horizontal line
+ *          at the reference position and a vertical line at the edge; then
+ *          we draw the values of @numa, scaled so that the maximum
+ *          excursion from the reference position is @max pixels.
+ *      (2) The start and delx parameters of @numa are used to refer
+ *          its values to the raster lines (L_VERTICAL_LINE) or columns
+ *          (L_HORIZONTAL_LINE).
+ *      (3) The linewidth is chosen in the interval [1 ... 7].
+ *      (4) @refpos should be chosen so the plot is entirely within the pix
+ *          that it will be painted onto.
+ *      (5) This would typically be used to plot, in place, a function
+ *          computed along pixel rows or columns.
+ */
+PTA *
+makePlotPtaFromNumaGen(NUMA    *na,
+                       l_int32  orient,
+                       l_int32  linewidth,
+                       l_int32  refpos,
+                       l_int32  max,
+                       l_int32  drawref)
+{
+l_int32    i, n, maxw, maxh;
+l_float32  minval, maxval, absval, val, scale, start, del;
+PTA       *pta1, *pta2, *ptad;
+
+    PROCNAME("makePlotPtaFromNumaGen");
+
+    if (!na)
+        return (PTA *)ERROR_PTR("na not defined", procName, NULL);
+    if (orient != L_HORIZONTAL_LINE && orient != L_VERTICAL_LINE)
+        return (PTA *)ERROR_PTR("invalid orient", procName, NULL);
+    if (linewidth < 1) {
+        L_WARNING("linewidth < 1; setting to 1\n", procName);
+        linewidth = 1;
+    }
+    if (linewidth > 7) {
+        L_WARNING("linewidth > 7; setting to 7\n", procName);
+        linewidth = 7;
+    }
+
+    numaGetMin(na, &minval, NULL);
+    numaGetMax(na, &maxval, NULL);
+    absval = L_MAX(L_ABS(minval), L_ABS(maxval));
+    scale = (l_float32)max / (l_float32)absval;
+    n = numaGetCount(na);
+    numaGetParameters(na, &start, &del);
+
+        /* Generate the plot points */
+    pta1 = ptaCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        if (orient == L_HORIZONTAL_LINE) {
+            ptaAddPt(pta1, start + i * del, refpos + scale * val);
+            maxw = (del >= 0) ? start + n * del + linewidth
+                              : start + linewidth;
+            maxh = refpos + max + linewidth;
+        } else {  /* vertical line */
+            ptaAddPt(pta1, refpos + scale * val, start + i * del);
+            maxw = refpos + max + linewidth;
+            maxh = (del >= 0) ? start + n * del + linewidth
+                              : start + linewidth;
+        }
+    }
+
+        /* Optionally, widen the plot */
+    if (linewidth > 1) {
+        if (linewidth % 2 == 0)  /* even linewidth; use side of a square */
+            pta2 = generatePtaFilledSquare(linewidth);
+        else  /* odd linewidth; use radius of a circle */
+            pta2 = generatePtaFilledCircle(linewidth / 2);
+        ptad = ptaReplicatePattern(pta1, NULL, pta2, linewidth / 2,
+                                   linewidth / 2, maxw, maxh);
+        ptaDestroy(&pta2);
+    } else {
+        ptad = ptaClone(pta1);
+    }
+    ptaDestroy(&pta1);
+
+        /* Optionally, add the reference lines */
+    if (drawref) {
+        if (orient == L_HORIZONTAL_LINE) {
+            pta1 = generatePtaLine(start, refpos, start + n * del, refpos);
+            ptaJoin(ptad, pta1, 0, -1);
+            ptaDestroy(&pta1);
+            pta1 = generatePtaLine(start, refpos - max,
+                                   start, refpos + max);
+            ptaJoin(ptad, pta1, 0, -1);
+        } else {  /* vertical line */
+            pta1 = generatePtaLine(refpos, start, refpos, start + n * del);
+            ptaJoin(ptad, pta1, 0, -1);
+            ptaDestroy(&pta1);
+            pta1 = generatePtaLine(refpos - max, start,
+                                   refpos + max, start);
+            ptaJoin(ptad, pta1, 0, -1);
+        }
+        ptaDestroy(&pta1);
+    }
+
+    return ptad;
+}
+
+
+/*------------------------------------------------------------------*
+ *        Pta generation for arbitrary shapes built with lines      *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRenderPta()
+ *
+ *      Input:  pix
+ *              pta (arbitrary set of points)
+ *              op   (one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) L_SET_PIXELS puts all image bits in each pixel to 1
+ *          (black for 1 bpp; white for depth > 1)
+ *      (2) L_CLEAR_PIXELS puts all image bits in each pixel to 0
+ *          (white for 1 bpp; black for depth > 1)
+ *      (3) L_FLIP_PIXELS reverses all image bits in each pixel
+ *      (4) This function clips the rendering to the pix.  It performs
+ *          clipping for functions such as pixRenderLine(),
+ *          pixRenderBox() and pixRenderBoxa(), that call pixRenderPta().
+ */
+l_int32
+pixRenderPta(PIX     *pix,
+             PTA     *pta,
+             l_int32  op)
+{
+l_int32  i, n, x, y, w, h, d, maxval;
+
+    PROCNAME("pixRenderPta");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", procName, 1);
+
+    pixGetDimensions(pix, &w, &h, &d);
+    maxval = 1;
+    if (op == L_SET_PIXELS) {
+        switch (d)
+        {
+        case 2:
+            maxval = 0x3;
+            break;
+        case 4:
+            maxval = 0xf;
+            break;
+        case 8:
+            maxval = 0xff;
+            break;
+        case 16:
+            maxval = 0xffff;
+            break;
+        case 32:
+            maxval = 0xffffffff;
+            break;
+        }
+    }
+
+    n = ptaGetCount(pta);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        if (x < 0 || x >= w)
+            continue;
+        if (y < 0 || y >= h)
+            continue;
+        switch (op)
+        {
+        case L_SET_PIXELS:
+            pixSetPixel(pix, x, y, maxval);
+            break;
+        case L_CLEAR_PIXELS:
+            pixClearPixel(pix, x, y);
+            break;
+        case L_FLIP_PIXELS:
+            pixFlipPixel(pix, x, y);
+            break;
+        default:
+            break;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixRenderPtaArb()
+ *
+ *      Input:  pix (any depth, cmapped ok)
+ *              pta (arbitrary set of points)
+ *              rval, gval, bval
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If pix is colormapped, render this color (or the nearest
+ *          color if the cmap is full) on each pixel.
+ *      (2) If pix is not colormapped, do the best job you can using
+ *          the input colors:
+ *          - d = 1: set the pixels
+ *          - d = 2, 4, 8: average the input rgb value
+ *          - d = 32: use the input rgb value
+ *      (3) This function clips the rendering to the pix.
+ */
+l_int32
+pixRenderPtaArb(PIX     *pix,
+                PTA     *pta,
+                l_uint8  rval,
+                l_uint8  gval,
+                l_uint8  bval)
+{
+l_int32   i, n, x, y, w, h, d, index;
+l_uint8   val;
+l_uint32  val32;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixRenderPtaArb");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    d = pixGetDepth(pix);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 32)
+        return ERROR_INT("depth not in {1,2,4,8,32}", procName, 1);
+
+    if (d == 1) {
+        pixRenderPta(pix, pta, L_SET_PIXELS);
+        return 0;
+    }
+
+    cmap = pixGetColormap(pix);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (cmap) {
+        pixcmapAddNearestColor(cmap, rval, gval, bval, &index);
+    } else {
+        if (d == 2)
+            val = (rval + gval + bval) / (3 * 64);
+        else if (d == 4)
+            val = (rval + gval + bval) / (3 * 16);
+        else if (d == 8)
+            val = (rval + gval + bval) / 3;
+        else  /* d == 32 */
+            composeRGBPixel(rval, gval, bval, &val32);
+    }
+
+    n = ptaGetCount(pta);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        if (x < 0 || x >= w)
+            continue;
+        if (y < 0 || y >= h)
+            continue;
+        if (cmap)
+            pixSetPixel(pix, x, y, index);
+        else if (d == 32)
+            pixSetPixel(pix, x, y, val32);
+        else
+            pixSetPixel(pix, x, y, val);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixRenderPtaBlend()
+ *
+ *      Input:  pix (32 bpp rgb)
+ *              pta  (arbitrary set of points)
+ *              rval, gval, bval
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function clips the rendering to the pix.
+ */
+l_int32
+pixRenderPtaBlend(PIX     *pix,
+                  PTA     *pta,
+                  l_uint8  rval,
+                  l_uint8  gval,
+                  l_uint8  bval,
+                  l_float32 fract)
+{
+l_int32    i, n, x, y, w, h;
+l_uint8    nrval, ngval, nbval;
+l_uint32   val32;
+l_float32  frval, fgval, fbval;
+
+    PROCNAME("pixRenderPtaBlend");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    if (pixGetDepth(pix) != 32)
+        return ERROR_INT("depth not 32 bpp", procName, 1);
+    if (fract < 0.0 || fract > 1.0) {
+        L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", procName);
+        fract = 0.5;
+    }
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    n = ptaGetCount(pta);
+    frval = fract * rval;
+    fgval = fract * gval;
+    fbval = fract * bval;
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        if (x < 0 || x >= w)
+            continue;
+        if (y < 0 || y >= h)
+            continue;
+        pixGetPixel(pix, x, y, &val32);
+        nrval = GET_DATA_BYTE(&val32, COLOR_RED);
+        nrval = (l_uint8)((1. - fract) * nrval + frval);
+        ngval = GET_DATA_BYTE(&val32, COLOR_GREEN);
+        ngval = (l_uint8)((1. - fract) * ngval + fgval);
+        nbval = GET_DATA_BYTE(&val32, COLOR_BLUE);
+        nbval = (l_uint8)((1. - fract) * nbval + fbval);
+        composeRGBPixel(nrval, ngval, nbval, &val32);
+        pixSetPixel(pix, x, y, val32);
+    }
+
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *           Rendering of arbitrary shapes built with lines         *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRenderLine()
+ *
+ *      Input:  pix
+ *              x1, y1
+ *              x2, y2
+ *              width  (thickness of line)
+ *              op  (one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderLine(PIX     *pix,
+              l_int32  x1,
+              l_int32  y1,
+              l_int32  x2,
+              l_int32  y2,
+              l_int32  width,
+              l_int32  op)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderLine");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (width < 1) {
+        L_WARNING("width must be > 0; setting to 1\n", procName);
+        width = 1;
+    }
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", procName, 1);
+
+    if ((pta = generatePtaWideLine(x1, y1, x2, y2, width)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPta(pix, pta, op);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderLineArb()
+ *
+ *      Input:  pix
+ *              x1, y1
+ *              x2, y2
+ *              width  (thickness of line)
+ *              rval, gval, bval
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderLineArb(PIX     *pix,
+                 l_int32  x1,
+                 l_int32  y1,
+                 l_int32  x2,
+                 l_int32  y2,
+                 l_int32  width,
+                 l_uint8  rval,
+                 l_uint8  gval,
+                 l_uint8  bval)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderLineArb");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (width < 1) {
+        L_WARNING("width must be > 0; setting to 1\n", procName);
+        width = 1;
+    }
+
+    if ((pta = generatePtaWideLine(x1, y1, x2, y2, width)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderLineBlend()
+ *
+ *      Input:  pix
+ *              x1, y1
+ *              x2, y2
+ *              width  (thickness of line)
+ *              rval, gval, bval
+ *              fract
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderLineBlend(PIX       *pix,
+                   l_int32    x1,
+                   l_int32    y1,
+                   l_int32    x2,
+                   l_int32    y2,
+                   l_int32    width,
+                   l_uint8    rval,
+                   l_uint8    gval,
+                   l_uint8    bval,
+                   l_float32  fract)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderLineBlend");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (width < 1) {
+        L_WARNING("width must be > 0; setting to 1\n", procName);
+        width = 1;
+    }
+
+    if ((pta = generatePtaWideLine(x1, y1, x2, y2, width)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderBox()
+ *
+ *      Input:  pix
+ *              box
+ *              width  (thickness of box lines)
+ *              op  (one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderBox(PIX     *pix,
+             BOX     *box,
+             l_int32  width,
+             l_int32  op)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderBox");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", procName, 1);
+
+    if ((pta = generatePtaBox(box, width)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPta(pix, pta, op);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderBoxArb()
+ *
+ *      Input:  pix (any depth, cmapped ok)
+ *              box
+ *              width  (thickness of box lines)
+ *              rval, gval, bval
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderBoxArb(PIX     *pix,
+                BOX     *box,
+                l_int32  width,
+                l_uint8  rval,
+                l_uint8  gval,
+                l_uint8  bval)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderBoxArb");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    if ((pta = generatePtaBox(box, width)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderBoxBlend()
+ *
+ *      Input:  pix
+ *              box
+ *              width  (thickness of box lines)
+ *              rval, gval, bval
+ *              fract (in [0.0 - 1.0]; complete transparency (no effect)
+ *                     if 0.0; no transparency if 1.0)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderBoxBlend(PIX       *pix,
+                  BOX       *box,
+                  l_int32    width,
+                  l_uint8    rval,
+                  l_uint8    gval,
+                  l_uint8    bval,
+                  l_float32  fract)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderBoxBlend");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    if ((pta = generatePtaBox(box, width)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderBoxa()
+ *
+ *      Input:  pix
+ *              boxa
+ *              width  (thickness of line)
+ *              op  (one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderBoxa(PIX     *pix,
+              BOXA    *boxa,
+              l_int32  width,
+              l_int32  op)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderBoxa");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", procName, 1);
+
+    if ((pta = generatePtaBoxa(boxa, width, 0)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPta(pix, pta, op);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderBoxaArb()
+ *
+ *      Input:  pix
+ *              boxa
+ *              width  (thickness of line)
+ *              rval, gval, bval
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderBoxaArb(PIX     *pix,
+                 BOXA    *boxa,
+                 l_int32  width,
+                 l_uint8  rval,
+                 l_uint8  gval,
+                 l_uint8  bval)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderBoxaArb");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    if ((pta = generatePtaBoxa(boxa, width, 0)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderBoxaBlend()
+ *
+ *      Input:  pix
+ *              boxa
+ *              width  (thickness of line)
+ *              rval, gval, bval
+ *              fract (in [0.0 - 1.0]; complete transparency (no effect)
+ *                     if 0.0; no transparency if 1.0)
+ *              removedups  (1 to remove; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderBoxaBlend(PIX       *pix,
+                   BOXA      *boxa,
+                   l_int32    width,
+                   l_uint8    rval,
+                   l_uint8    gval,
+                   l_uint8    bval,
+                   l_float32  fract,
+                   l_int32    removedups)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderBoxaBlend");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    if ((pta = generatePtaBoxa(boxa, width, removedups)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderHashBox()
+ *
+ *      Input:  pix
+ *              box
+ *              spacing (spacing between lines; must be > 1)
+ *              width  (thickness of box and hash lines)
+ *              orient  (orientation of lines: L_HORIZONTAL_LINE, ...)
+ *              outline  (0 to skip drawing box outline)
+ *              op  (one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderHashBox(PIX     *pix,
+                 BOX     *box,
+                 l_int32  spacing,
+                 l_int32  width,
+                 l_int32  orient,
+                 l_int32  outline,
+                 l_int32  op)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderHashBox");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", procName, 1);
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", procName, 1);
+
+    pta = generatePtaHashBox(box, spacing, width, orient, outline);
+    if (!pta)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPta(pix, pta, op);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderHashBoxArb()
+ *
+ *      Input:  pix
+ *              box
+ *              spacing (spacing between lines; must be > 1)
+ *              width  (thickness of box and hash lines)
+ *              orient  (orientation of lines: L_HORIZONTAL_LINE, ...)
+ *              outline  (0 to skip drawing box outline)
+ *              rval, gval, bval
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderHashBoxArb(PIX     *pix,
+                    BOX     *box,
+                    l_int32  spacing,
+                    l_int32  width,
+                    l_int32  orient,
+                    l_int32  outline,
+                    l_int32  rval,
+                    l_int32  gval,
+                    l_int32  bval)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderHashBoxArb");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", procName, 1);
+
+    pta = generatePtaHashBox(box, spacing, width, orient, outline);
+    if (!pta)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderHashBoxBlend()
+ *
+ *      Input:  pix
+ *              box
+ *              spacing (spacing between lines; must be > 1)
+ *              width  (thickness of box and hash lines)
+ *              orient  (orientation of lines: L_HORIZONTAL_LINE, ...)
+ *              outline  (0 to skip drawing box outline)
+ *              rval, gval, bval
+ *              fract (in [0.0 - 1.0]; complete transparency (no effect)
+ *                     if 0.0; no transparency if 1.0)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderHashBoxBlend(PIX       *pix,
+                      BOX       *box,
+                      l_int32    spacing,
+                      l_int32    width,
+                      l_int32    orient,
+                      l_int32    outline,
+                      l_int32    rval,
+                      l_int32    gval,
+                      l_int32    bval,
+                      l_float32  fract)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderHashBoxBlend");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", procName, 1);
+
+    pta = generatePtaHashBox(box, spacing, width, orient, outline);
+    if (!pta)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderHashBoxa()
+ *
+ *      Input:  pix
+ *              boxa
+ *              spacing (spacing between lines; must be > 1)
+ *              width  (thickness of box and hash lines)
+ *              orient  (orientation of lines: L_HORIZONTAL_LINE, ...)
+ *              outline  (0 to skip drawing box outline)
+ *              op  (one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderHashBoxa(PIX     *pix,
+                  BOXA    *boxa,
+                  l_int32  spacing,
+                  l_int32  width,
+                  l_int32  orient,
+                  l_int32  outline,
+                  l_int32  op)
+ {
+PTA  *pta;
+
+    PROCNAME("pixRenderHashBoxa");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", procName, 1);
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", procName, 1);
+
+    pta = generatePtaHashBoxa(boxa, spacing, width, orient, outline, 1);
+    if (!pta)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPta(pix, pta, op);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderHashBoxaArb()
+ *
+ *      Input:  pix
+ *              boxa
+ *              spacing (spacing between lines; must be > 1)
+ *              width  (thickness of box and hash lines)
+ *              orient  (orientation of lines: L_HORIZONTAL_LINE, ...)
+ *              outline  (0 to skip drawing box outline)
+ *              rval, gval, bval
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderHashBoxaArb(PIX     *pix,
+                     BOXA    *boxa,
+                     l_int32  spacing,
+                     l_int32  width,
+                     l_int32  orient,
+                     l_int32  outline,
+                     l_int32  rval,
+                     l_int32  gval,
+                     l_int32  bval)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderHashBoxArb");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", procName, 1);
+
+    pta = generatePtaHashBoxa(boxa, spacing, width, orient, outline, 1);
+    if (!pta)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderHashBoxaBlend()
+ *
+ *      Input:  pix
+ *              boxa
+ *              spacing (spacing between lines; must be > 1)
+ *              width  (thickness of box and hash lines)
+ *              orient  (orientation of lines: L_HORIZONTAL_LINE, ...)
+ *              outline  (0 to skip drawing box outline)
+ *              rval, gval, bval
+ *              fract (in [0.0 - 1.0]; complete transparency (no effect)
+ *                     if 0.0; no transparency if 1.0)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderHashBoxaBlend(PIX       *pix,
+                       BOXA      *boxa,
+                       l_int32    spacing,
+                       l_int32    width,
+                       l_int32    orient,
+                       l_int32    outline,
+                       l_int32    rval,
+                       l_int32    gval,
+                       l_int32    bval,
+                       l_float32  fract)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderHashBoxaBlend");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (spacing <= 1)
+        return ERROR_INT("spacing not > 1", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+    if (orient != L_HORIZONTAL_LINE && orient != L_POS_SLOPE_LINE &&
+        orient != L_VERTICAL_LINE && orient != L_NEG_SLOPE_LINE)
+        return ERROR_INT("invalid line orientation", procName, 1);
+
+    pta = generatePtaHashBoxa(boxa, spacing, width, orient, outline, 1);
+    if (!pta)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderPolyline()
+ *
+ *      Input:  pix
+ *              ptas
+ *              width  (thickness of line)
+ *              op  (one of L_SET_PIXELS, L_CLEAR_PIXELS, L_FLIP_PIXELS)
+ *              closeflag (1 to close the contour; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Note: this renders a closed contour.
+ */
+l_int32
+pixRenderPolyline(PIX     *pix,
+                  PTA     *ptas,
+                  l_int32  width,
+                  l_int32  op,
+                  l_int32  closeflag)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderPolyline");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!ptas)
+        return ERROR_INT("ptas not defined", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+    if (op != L_SET_PIXELS && op != L_CLEAR_PIXELS && op != L_FLIP_PIXELS)
+        return ERROR_INT("invalid op", procName, 1);
+
+    if ((pta = generatePtaPolyline(ptas, width, closeflag, 0)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPta(pix, pta, op);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderPolylineArb()
+ *
+ *      Input:  pix
+ *              ptas
+ *              width  (thickness of line)
+ *              rval, gval, bval
+ *              closeflag (1 to close the contour; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Note: this renders a closed contour.
+ */
+l_int32
+pixRenderPolylineArb(PIX     *pix,
+                     PTA     *ptas,
+                     l_int32  width,
+                     l_uint8  rval,
+                     l_uint8  gval,
+                     l_uint8  bval,
+                     l_int32  closeflag)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderPolylineArb");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!ptas)
+        return ERROR_INT("ptas not defined", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    if ((pta = generatePtaPolyline(ptas, width, closeflag, 0)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderPolylineBlend()
+ *
+ *      Input:  pix
+ *              ptas
+ *              width  (thickness of line)
+ *              rval, gval, bval
+ *              fract (in [0.0 - 1.0]; complete transparency (no effect)
+ *                     if 0.0; no transparency if 1.0)
+ *              closeflag (1 to close the contour; 0 otherwise)
+ *              removedups  (1 to remove; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderPolylineBlend(PIX       *pix,
+                       PTA       *ptas,
+                       l_int32    width,
+                       l_uint8    rval,
+                       l_uint8    gval,
+                       l_uint8    bval,
+                       l_float32  fract,
+                       l_int32    closeflag,
+                       l_int32    removedups)
+{
+PTA  *pta;
+
+    PROCNAME("pixRenderPolylineBlend");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!ptas)
+        return ERROR_INT("ptas not defined", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    if ((pta = generatePtaPolyline(ptas, width, closeflag, removedups)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaBlend(pix, pta, rval, gval, bval, fract);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderGridArb()
+ *
+ *      Input:  pix (any depth, cmapped ok)
+ *              nx, ny (number of rectangles in each direction)
+ *              width  (thickness of grid lines)
+ *              rval, gval, bval
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRenderGridArb(PIX     *pix,
+                 l_int32  nx,
+                 l_int32  ny,
+                 l_int32  width,
+                 l_uint8  rval,
+                 l_uint8  gval,
+                 l_uint8  bval)
+{
+l_int32  w, h;
+PTA     *pta;
+
+    PROCNAME("pixRenderGridArb");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (nx < 1 || ny < 1)
+        return ERROR_INT("nx, ny must be > 0", procName, 1);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    if ((pta = generatePtaGrid(w, h, nx, ny, width)) == NULL)
+        return ERROR_INT("pta not made", procName, 1);
+    pixRenderPtaArb(pix, pta, rval, gval, bval);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  pixRenderRandomCmapPtaa()
+ *
+ *      Input:  pix (1, 2, 4, 8, 16, 32 bpp)
+ *              ptaa
+ *              polyflag (1 to interpret each Pta as a polyline; 0 to simply
+ *                        render the Pta as a set of pixels)
+ *              width  (thickness of line; use only for polyline)
+ *              closeflag (1 to close the contour; 0 otherwise;
+ *                         use only for polyline mode)
+ *      Return: pixd (cmapped, 8 bpp) or null on error
+ *
+ *  Notes:
+ *      (1) This is a debugging routine, that displays a set of
+ *          pixels, selected by the set of Ptas in a Ptaa,
+ *          in a random color in a pix.
+ *      (2) If @polyflag == 1, each Pta is considered to be a polyline,
+ *          and is rendered using @width and @closeflag.  Each polyline
+ *          is rendered in a random color.
+ *      (3) If @polyflag == 0, all points in each Pta are rendered in a
+ *          random color.  The @width and @closeflag parameters are ignored.
+ *      (4) The output pix is 8 bpp and colormapped.  Up to 254
+ *          different, randomly selected colors, can be used.
+ *      (5) The rendered pixels replace the input pixels.  They will
+ *          be clipped silently to the input pix.
+ */
+PIX  *
+pixRenderRandomCmapPtaa(PIX     *pix,
+                        PTAA    *ptaa,
+                        l_int32  polyflag,
+                        l_int32  width,
+                        l_int32  closeflag)
+{
+l_int32   i, n, index, rval, gval, bval;
+PIXCMAP  *cmap;
+PTA      *pta, *ptat;
+PIX      *pixd;
+
+    PROCNAME("pixRenderRandomCmapPtaa");
+
+    if (!pix)
+        return (PIX *)ERROR_PTR("pix not defined", procName, NULL);
+    if (!ptaa)
+        return (PIX *)ERROR_PTR("ptaa not defined", procName, NULL);
+    if (polyflag != 0 && width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    pixd = pixConvertTo8(pix, FALSE);
+    cmap = pixcmapCreateRandom(8, 1, 1);
+    pixSetColormap(pixd, cmap);
+
+    if ((n = ptaaGetCount(ptaa)) == 0)
+        return pixd;
+
+    for (i = 0; i < n; i++) {
+        index = 1 + (i % 254);
+        pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+        pta = ptaaGetPta(ptaa, i, L_CLONE);
+        if (polyflag)
+            ptat = generatePtaPolyline(pta, width, closeflag, 0);
+        else
+            ptat = ptaClone(pta);
+        pixRenderPtaArb(pixd, ptat, rval, gval, bval);
+        ptaDestroy(&pta);
+        ptaDestroy(&ptat);
+    }
+
+    return pixd;
+}
+
+
+
+/*------------------------------------------------------------------*
+ *                Rendering and filling of polygons                 *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRenderPolygon()
+ *
+ *      Input:  ptas (of vertices, none repeated)
+ *              width (of polygon outline)
+ *              &xmin (<optional return> min x value of input pts)
+ *              &ymin (<optional return> min y value of input pts)
+ *      Return: pix (1 bpp, with outline generated), or null on error
+ *
+ *  Notes:
+ *      (1) The pix is the minimum size required to contain the origin
+ *          and the polygon.  For example, the max x value of the input
+ *          points is w - 1, where w is the pix width.
+ *      (2) The rendered line is 4-connected, so that an interior or
+ *          exterior 8-c.c. flood fill operation works properly.
+ */
+PIX *
+pixRenderPolygon(PTA      *ptas,
+                 l_int32   width,
+                 l_int32  *pxmin,
+                 l_int32  *pymin)
+{
+l_float32  fxmin, fxmax, fymin, fymax;
+PIX       *pixd;
+PTA       *pta1, *pta2;
+
+    PROCNAME("pixRenderPolygon");
+
+    if (pxmin) *pxmin = 0;
+    if (pymin) *pymin = 0;
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+
+        /* Generate a 4-connected polygon line */
+    if ((pta1 = generatePtaPolyline(ptas, width, 1, 0)) == NULL)
+        return (PIX *)ERROR_PTR("pta1 not made", procName, NULL);
+    if (width < 2)
+        pta2 = convertPtaLineTo4cc(pta1);
+    else
+        pta2 = ptaClone(pta1);
+
+        /* Render onto a minimum-sized pix */
+    ptaGetRange(pta2, &fxmin, &fxmax, &fymin, &fymax);
+    if (pxmin) *pxmin = (l_int32)(fxmin + 0.5);
+    if (pymin) *pymin = (l_int32)(fymin + 0.5);
+    pixd = pixCreate((l_int32)(fxmax + 0.5) + 1, (l_int32)(fymax + 0.5) + 1, 1);
+    pixRenderPolyline(pixd, pta2, width, L_SET_PIXELS, 1);
+    ptaDestroy(&pta1);
+    ptaDestroy(&pta2);
+    return pixd;
+}
+
+
+/*!
+ *  pixFillPolygon()
+ *
+ *      Input:  pixs (1 bpp, with 4-connected polygon outline)
+ *              pta (vertices of the polygon)
+ *              xmin, ymin (min values of vertices of polygon)
+ *      Return: pixd (with outline filled), or null on error
+ *
+ *  Notes:
+ *      (1) This fills the interior of the polygon, returning a
+ *          new pix.  It works for both convex and non-convex polygons.
+ *      (2) To generate a filled polygon from a pta:
+ *            PIX *pixt = pixRenderPolygon(pta, 1, &xmin, &ymin);
+ *            PIX *pixd = pixFillPolygon(pixt, pta, xmin, ymin);
+ *            pixDestroy(&pixt);
+ */
+PIX *
+pixFillPolygon(PIX     *pixs,
+               PTA     *pta,
+               l_int32  xmin,
+               l_int32  ymin)
+{
+l_int32   w, h, i, n, inside, found;
+l_int32  *xstart, *xend;
+PIX      *pixi, *pixd;
+
+    PROCNAME("pixFillPolygon");
+
+    if (!pixs || (pixGetDepth(pixs) != 1))
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (!pta)
+        return (PIX *)ERROR_PTR("pta not defined", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    xstart = (l_int32 *)LEPT_CALLOC(w / 2, sizeof(l_int32));
+    xend = (l_int32 *)LEPT_CALLOC(w / 2, sizeof(l_int32));
+
+        /* Find a raster with 2 or more black runs.  The first background
+         * pixel after the end of the first run is likely to be inside
+         * the polygon, and can be used as a seed pixel. */
+    found = FALSE;
+    for (i = ymin + 1; i < h; i++) {
+        pixFindHorizontalRuns(pixs, i, xstart, xend, &n);
+        if (n > 1) {
+            ptaPtInsidePolygon(pta, xend[0] + 1, i, &inside);
+            if (inside) {
+                found = TRUE;
+                break;
+            }
+        }
+    }
+    if (!found) {
+        L_WARNING("nothing found to fill\n", procName);
+        LEPT_FREE(xstart);
+        LEPT_FREE(xend);
+        return 0;
+    }
+
+        /* Place the seed pixel in the output image */
+    pixd = pixCreateTemplate(pixs);
+    pixSetPixel(pixd, xend[0] + 1, i, 1);
+
+        /* Invert pixs to make a filling mask, and fill from the seed */
+    pixi = pixInvert(NULL, pixs);
+    pixSeedfillBinary(pixd, pixd, pixi, 4);
+
+        /* Add the pixels of the original polygon outline */
+    pixOr(pixd, pixd, pixs);
+
+    pixDestroy(&pixi);
+    LEPT_FREE(xstart);
+    LEPT_FREE(xend);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *             Contour rendering on grayscale images                *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRenderContours()
+ *
+ *      Input:  pixs (8 or 16 bpp; no colormap)
+ *              startval (value of lowest contour; must be in [0 ... maxval])
+ *              incr  (increment to next contour; must be > 0)
+ *              outdepth (either 1 or depth of pixs)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The output can be either 1 bpp, showing just the contour
+ *          lines, or a copy of the input pixs with the contour lines
+ *          superposed.
+ */
+PIX *
+pixRenderContours(PIX     *pixs,
+                  l_int32  startval,
+                  l_int32  incr,
+                  l_int32  outdepth)
+{
+l_int32    w, h, d, maxval, wpls, wpld, i, j, val, test;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixRenderContours");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 16)
+        return (PIX *)ERROR_PTR("pixs not 8 or 16 bpp", procName, NULL);
+    if (outdepth != 1 && outdepth != d) {
+        L_WARNING("invalid outdepth; setting to 1\n", procName);
+        outdepth = 1;
+    }
+    maxval = (1 << d) - 1;
+    if (startval < 0 || startval > maxval)
+        return (PIX *)ERROR_PTR("startval not in [0 ... maxval]",
+               procName, NULL);
+    if (incr < 1)
+        return (PIX *)ERROR_PTR("incr < 1", procName, NULL);
+
+    if (outdepth == d)
+        pixd = pixCopy(NULL, pixs);
+    else
+        pixd = pixCreate(w, h, 1);
+
+    pixCopyResolution(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+
+    switch (d)
+    {
+    case 8:
+        if (outdepth == 1) {
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    val = GET_DATA_BYTE(lines, j);
+                    if (val < startval)
+                        continue;
+                    test = (val - startval) % incr;
+                    if (!test)
+                        SET_DATA_BIT(lined, j);
+                }
+            }
+        } else {  /* outdepth == d */
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    val = GET_DATA_BYTE(lines, j);
+                    if (val < startval)
+                        continue;
+                    test = (val - startval) % incr;
+                    if (!test)
+                        SET_DATA_BYTE(lined, j, 0);
+                }
+            }
+        }
+        break;
+
+    case 16:
+        if (outdepth == 1) {
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    val = GET_DATA_TWO_BYTES(lines, j);
+                    if (val < startval)
+                        continue;
+                    test = (val - startval) % incr;
+                    if (!test)
+                        SET_DATA_BIT(lined, j);
+                }
+            }
+        } else {  /* outdepth == d */
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    val = GET_DATA_TWO_BYTES(lines, j);
+                    if (val < startval)
+                        continue;
+                    test = (val - startval) % incr;
+                    if (!test)
+                        SET_DATA_TWO_BYTES(lined, j, 0);
+                }
+            }
+        }
+        break;
+
+    default:
+        return (PIX *)ERROR_PTR("pixs not 8 or 16 bpp", procName, NULL);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  fpixAutoRenderContours()
+ *
+ *      Input:  fpix
+ *              ncontours (> 1, < 500, typ. about 50)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) The increment is set to get approximately @ncontours.
+ *      (2) The proximity to the target value for contour display
+ *          is set to 0.15.
+ *      (3) Negative values are rendered in red; positive values as black.
+ */
+PIX *
+fpixAutoRenderContours(FPIX    *fpix,
+                       l_int32  ncontours)
+{
+l_float32  minval, maxval, incr;
+
+    PROCNAME("fpixAutoRenderContours");
+
+    if (!fpix)
+        return (PIX *)ERROR_PTR("fpix not defined", procName, NULL);
+    if (ncontours < 2 || ncontours > 500)
+        return (PIX *)ERROR_PTR("ncontours < 2 or > 500", procName, NULL);
+
+    fpixGetMin(fpix, &minval, NULL, NULL);
+    fpixGetMax(fpix, &maxval, NULL, NULL);
+    if (minval == maxval)
+        return (PIX *)ERROR_PTR("all values in fpix are equal", procName, NULL);
+    incr = (maxval - minval) / ((l_float32)ncontours - 1);
+    return fpixRenderContours(fpix, incr, 0.15);
+}
+
+
+/*!
+ *  fpixRenderContours()
+ *
+ *      Input:  fpixs
+ *              incr  (increment between contours; must be > 0.0)
+ *              proxim (required proximity to target value; default 0.15)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) Values are displayed when val/incr is within +-proxim
+ *          to an integer.  The default value is 0.15; smaller values
+ *          result in thinner contour lines.
+ *      (2) Negative values are rendered in red; positive values as black.
+ */
+PIX *
+fpixRenderContours(FPIX      *fpixs,
+                   l_float32  incr,
+                   l_float32  proxim)
+{
+l_int32     i, j, w, h, wpls, wpld;
+l_float32   val, invincr, finter, above, below, diff;
+l_uint32   *datad, *lined;
+l_float32  *datas, *lines;
+PIX        *pixd;
+PIXCMAP    *cmap;
+
+    PROCNAME("fpixRenderContours");
+
+    if (!fpixs)
+        return (PIX *)ERROR_PTR("fpixs not defined", procName, NULL);
+    if (incr <= 0.0)
+        return (PIX *)ERROR_PTR("incr <= 0.0", procName, NULL);
+    if (proxim <= 0.0)
+        proxim = 0.15;  /* default */
+
+    fpixGetDimensions(fpixs, &w, &h);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    cmap = pixcmapCreate(8);
+    pixSetColormap(pixd, cmap);
+    pixcmapAddColor(cmap, 255, 255, 255);  /* white */
+    pixcmapAddColor(cmap, 0, 0, 0);  /* black */
+    pixcmapAddColor(cmap, 255, 0, 0);  /* red */
+
+    datas = fpixGetData(fpixs);
+    wpls = fpixGetWpl(fpixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    invincr = 1.0 / incr;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = lines[j];
+            finter = invincr * val;
+            above = finter - floorf(finter);
+            below = ceilf(finter) - finter;
+            diff = L_MIN(above, below);
+            if (diff <= proxim) {
+                if (val < 0.0)
+                    SET_DATA_BYTE(lined, j, 2);
+                else
+                    SET_DATA_BYTE(lined, j, 1);
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *             Boundary pt generation on 1 bpp images               *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixGeneratePtaBoundary()
+ *
+ *      Input:  pixs (1 bpp)
+ *              width (of boundary line)
+ *      Return: pta, or null on error
+ *
+ *  Notes:
+ *      (1) Similar to ptaGetBoundaryPixels(), except here:
+ *          * we only get pixels in the foreground
+ *          * we can have a "line" width greater than 1 pixel.
+ *      (2) Once generated, this can be applied to a random 1 bpp image
+ *          to add a color boundary as follows:
+ *             Pta *pta = pixGeneratePtaBoundary(pixs, width);
+ *             Pix *pix1 = pixConvert1To8Cmap(pixs);
+ *             pixRenderPtaArb(pix1, pta, rval, gval, bval);
+ */
+PTA  *
+pixGeneratePtaBoundary(PIX     *pixs,
+                       l_int32  width)
+{
+PIX  *pix1;
+PTA  *pta;
+
+    PROCNAME("pixGeneratePtaBoundary");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PTA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (width < 1) {
+        L_WARNING("width < 1; setting to 1\n", procName);
+        width = 1;
+    }
+
+    pix1 = pixErodeBrick(NULL, pixs, 2 * width + 1, 2 * width + 1);
+    pixXor(pix1, pix1, pixs);
+    pta = ptaGetPixelsFromPix(pix1, NULL);
+    pixDestroy(&pix1);
+    return pta;
+}
+
diff --git a/src/graymorph.c b/src/graymorph.c
new file mode 100644 (file)
index 0000000..112bf94
--- /dev/null
@@ -0,0 +1,1312 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  graymorph.c
+ *
+ *      Top-level grayscale morphological operations (van Herk / Gil-Werman)
+ *            PIX           *pixErodeGray()
+ *            PIX           *pixDilateGray()
+ *            PIX           *pixOpenGray()
+ *            PIX           *pixCloseGray()
+ *
+ *      Special operations for 1x3, 3x1 and 3x3 Sels  (direct)
+ *            PIX           *pixErodeGray3()
+ *            static PIX    *pixErodeGray3h()
+ *            static PIX    *pixErodeGray3v()
+ *            PIX           *pixDilateGray3()
+ *            static PIX    *pixDilateGray3h()
+ *            static PIX    *pixDilateGray3v()
+ *            PIX           *pixOpenGray3()
+ *            PIX           *pixCloseGray3()
+ *
+ *      Low-level grayscale morphological operations
+ *            static void    dilateGrayLow()
+ *            static void    erodeGrayLow()
+ *
+ *
+ *      Method: Algorithm by van Herk and Gil and Werman, 1992
+ *
+ *      Measured speed of the vH/G-W implementation is about 1 output
+ *      pixel per 120 PIII clock cycles, for a horizontal or vertical
+ *      erosion or dilation.  The computation time doubles for opening
+ *      or closing, or for a square SE, as expected, and is independent
+ *      of the size of the SE.
+ *
+ *      A faster implementation can be made directly for brick Sels
+ *      of maximum size 3.  We unroll the computation for sets of 8 bytes.
+ *      It needs to be called explicitly; the general functions do not
+ *      default for the size 3 brick Sels.
+ *
+ *      We use the van Herk/Gil-Werman (vHGW) algorithm, [van Herk,
+ *      Patt. Recog. Let. 13, pp. 517-521, 1992; Gil and Werman,
+ *      IEEE Trans PAMI 15(5), pp. 504-507, 1993.]
+ *      This was the first grayscale morphology
+ *      algorithm to compute dilation and erosion with
+ *      complexity independent of the size of the structuring
+ *      element.  It is simple and elegant, and surprising that
+ *      it was discovered as recently as 1992.  It works for
+ *      SEs composed of horizontal and/or vertical lines.  The
+ *      general case requires finding the Min or Max over an
+ *      arbitrary set of pixels, and this requires a number of
+ *      pixel comparisons equal to the SE "size" at each pixel
+ *      in the image.  The vHGW algorithm requires not
+ *      more than 3 comparisons at each point.  The algorithm has been
+ *      recently refined by Gil and Kimmel ("Efficient Dilation
+ *      Erosion, Opening and Closing Algorithms", in "Mathematical
+ *      Morphology and its Applications to Image and Signal Processing",
+ *      the proceedings of the International Symposium on Mathematical
+ *      Morphology, Palo Alto, CA, June 2000, Kluwer Academic
+ *      Publishers, pp. 301-310).  They bring this number down below
+ *      1.5 comparisons per output pixel but at a cost of significantly
+ *      increased complexity, so I don't bother with that here.
+ *
+ *      In brief, the method is as follows.  We evaluate the dilation
+ *      in groups of "size" pixels, equal to the size of the SE.
+ *      For horizontal, we start at x = "size"/2 and go
+ *      (w - 2 * ("size"/2))/"size" steps.  This means that
+ *      we don't evaluate the first 0.5 * "size" pixels and, worst
+ *      case, the last 1.5 * "size" pixels.  Thus we embed the
+ *      image in a larger image with these augmented dimensions, where
+ *      the new border pixels are appropriately initialized (0 for
+ *      dilation; 255 for erosion), and remove the boundary at the end.
+ *      (For vertical, use h instead of w.)   Then for each group
+ *      of "size" pixels, we form an array of length 2 * "size" + 1,
+ *      consisting of backward and forward partial maxima (for
+ *      dilation) or minima (for erosion).  This represents a
+ *      jumping window computed from the source image, over which
+ *      the SE will slide.  The center of the array gets the source
+ *      pixel at the center of the SE.  Call this the center pixel
+ *      of the window.  Array values to left of center get
+ *      the maxima(minima) of the pixels from the center
+ *      one and going to the left an equal distance.  Array
+ *      values to the right of center get the maxima(minima) to
+ *      the pixels from the center one and going to the right
+ *      an equal distance.  These are computed sequentially starting
+ *      from the center one.  The SE (of length "size") can slide over this
+ *      window (of length 2 * "size + 1) at "size" different places.
+ *      At each place, the maxima(minima) of the values in the window
+ *      that correspond to the end points of the SE give the extremal
+ *      values over that interval, and these are stored at the dest
+ *      pixel corresponding to the SE center.  A picture is worth
+ *      at least this many words, so if this isn't clear, see the
+ *      leptonica documentation on grayscale morphology.
+ */
+
+#include "allheaders.h"
+
+    /* Special static operations for 3x1, 1x3 and 3x3 structuring elements */
+static PIX *pixErodeGray3h(PIX *pixs);
+static PIX *pixErodeGray3v(PIX *pixs);
+static PIX *pixDilateGray3h(PIX *pixs);
+static PIX *pixDilateGray3v(PIX *pixs);
+
+    /*  Low-level gray morphological operations */
+static void dilateGrayLow(l_uint32 *datad, l_int32 w, l_int32 h,
+                          l_int32 wpld, l_uint32 *datas, l_int32 wpls,
+                          l_int32 size, l_int32 direction, l_uint8 *buffer,
+                          l_uint8 *maxarray);
+static void erodeGrayLow(l_uint32 *datad, l_int32 w, l_int32 h,
+                         l_int32 wpld, l_uint32 *datas, l_int32 wpls,
+                         l_int32 size, l_int32 direction, l_uint8 *buffer,
+                         l_uint8 *minarray);
+
+/*-----------------------------------------------------------------*
+ *           Top-level grayscale morphological operations          *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixErodeGray()
+ *
+ *      Input:  pixs
+ *              hsize  (of Sel; must be odd; origin implicitly in center)
+ *              vsize  (ditto)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) If hsize = vsize = 1, just returns a copy.
+ */
+PIX *
+pixErodeGray(PIX     *pixs,
+             l_int32  hsize,
+             l_int32  vsize)
+{
+l_uint8   *buffer, *minarray;
+l_int32    w, h, wplb, wplt;
+l_int32    leftpix, rightpix, toppix, bottompix, maxsize;
+l_uint32  *datab, *datat;
+PIX       *pixb, *pixt, *pixd;
+
+    PROCNAME("pixErodeGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize or vsize < 1", procName, NULL);
+    if ((hsize & 1) == 0 ) {
+        L_WARNING("horiz sel size must be odd; increasing by 1\n", procName);
+        hsize++;
+    }
+    if ((vsize & 1) == 0 ) {
+        L_WARNING("vert sel size must be odd; increasing by 1\n", procName);
+        vsize++;
+    }
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(NULL, pixs);
+
+    if (vsize == 1) {  /* horizontal sel */
+        leftpix = (hsize + 1) / 2;
+        rightpix = (3 * hsize + 1) / 2;
+        toppix = 0;
+        bottompix = 0;
+    } else if (hsize == 1) {  /* vertical sel */
+        leftpix = 0;
+        rightpix = 0;
+        toppix = (vsize + 1) / 2;
+        bottompix = (3 * vsize + 1) / 2;
+    } else {
+        leftpix = (hsize + 1) / 2;
+        rightpix = (3 * hsize + 1) / 2;
+        toppix = (vsize + 1) / 2;
+        bottompix = (3 * vsize + 1) / 2;
+    }
+
+    if ((pixb = pixAddBorderGeneral(pixs,
+                leftpix, rightpix, toppix, bottompix, 255)) == NULL)
+        return (PIX *)ERROR_PTR("pixb not made", procName, NULL);
+    if ((pixt = pixCreateTemplate(pixb)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+    pixGetDimensions(pixt, &w, &h, NULL);
+    datab = pixGetData(pixb);
+    datat = pixGetData(pixt);
+    wplb = pixGetWpl(pixb);
+    wplt = pixGetWpl(pixt);
+
+    if ((buffer = (l_uint8 *)LEPT_CALLOC(L_MAX(w, h), sizeof(l_uint8))) == NULL)
+        return (PIX *)ERROR_PTR("buffer not made", procName, NULL);
+    maxsize = L_MAX(hsize, vsize);
+    if ((minarray = (l_uint8 *)LEPT_CALLOC(2 * maxsize, sizeof(l_uint8)))
+        == NULL)
+        return (PIX *)ERROR_PTR("minarray not made", procName, NULL);
+
+    if (vsize == 1) {
+        erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
+                     buffer, minarray);
+    } else if (hsize == 1) {
+        erodeGrayLow(datat, w, h, wplt, datab, wplb, vsize, L_VERT,
+                     buffer, minarray);
+    } else {
+        erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
+                     buffer, minarray);
+        pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
+                            PIX_SET);
+        erodeGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
+                     buffer, minarray);
+        pixDestroy(&pixt);
+        pixt = pixClone(pixb);
+    }
+
+    if ((pixd = pixRemoveBorderGeneral(pixt,
+                leftpix, rightpix, toppix, bottompix)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    LEPT_FREE(buffer);
+    LEPT_FREE(minarray);
+    pixDestroy(&pixb);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixDilateGray()
+ *
+ *      Input:  pixs
+ *              hsize  (of Sel; must be odd; origin implicitly in center)
+ *              vsize  (ditto)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) If hsize = vsize = 1, just returns a copy.
+ */
+PIX *
+pixDilateGray(PIX     *pixs,
+              l_int32  hsize,
+              l_int32  vsize)
+{
+l_uint8   *buffer, *maxarray;
+l_int32    w, h, wplb, wplt;
+l_int32    leftpix, rightpix, toppix, bottompix, maxsize;
+l_uint32  *datab, *datat;
+PIX       *pixb, *pixt, *pixd;
+
+    PROCNAME("pixDilateGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize or vsize < 1", procName, NULL);
+    if ((hsize & 1) == 0 ) {
+        L_WARNING("horiz sel size must be odd; increasing by 1\n", procName);
+        hsize++;
+    }
+    if ((vsize & 1) == 0 ) {
+        L_WARNING("vert sel size must be odd; increasing by 1\n", procName);
+        vsize++;
+    }
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(NULL, pixs);
+
+    if (vsize == 1) {  /* horizontal sel */
+        leftpix = (hsize + 1) / 2;
+        rightpix = (3 * hsize + 1) / 2;
+        toppix = 0;
+        bottompix = 0;
+    } else if (hsize == 1) {  /* vertical sel */
+        leftpix = 0;
+        rightpix = 0;
+        toppix = (vsize + 1) / 2;
+        bottompix = (3 * vsize + 1) / 2;
+    } else {
+        leftpix = (hsize + 1) / 2;
+        rightpix = (3 * hsize + 1) / 2;
+        toppix = (vsize + 1) / 2;
+        bottompix = (3 * vsize + 1) / 2;
+    }
+
+    if ((pixb = pixAddBorderGeneral(pixs,
+                leftpix, rightpix, toppix, bottompix, 0)) == NULL)
+        return (PIX *)ERROR_PTR("pixb not made", procName, NULL);
+    if ((pixt = pixCreateTemplate(pixb)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+    pixGetDimensions(pixt, &w, &h, NULL);
+    datab = pixGetData(pixb);
+    datat = pixGetData(pixt);
+    wplb = pixGetWpl(pixb);
+    wplt = pixGetWpl(pixt);
+
+    if ((buffer = (l_uint8 *)LEPT_CALLOC(L_MAX(w, h), sizeof(l_uint8))) == NULL)
+        return (PIX *)ERROR_PTR("buffer not made", procName, NULL);
+    maxsize = L_MAX(hsize, vsize);
+    if ((maxarray = (l_uint8 *)LEPT_CALLOC(2 * maxsize, sizeof(l_uint8)))
+        == NULL)
+        return (PIX *)ERROR_PTR("buffer not made", procName, NULL);
+
+    if (vsize == 1) {
+        dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
+                      buffer, maxarray);
+    } else if (hsize == 1) {
+        dilateGrayLow(datat, w, h, wplt, datab, wplb, vsize, L_VERT,
+                      buffer, maxarray);
+    } else {
+        dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
+                      buffer, maxarray);
+        pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
+                            PIX_CLR);
+        dilateGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
+                      buffer, maxarray);
+        pixDestroy(&pixt);
+        pixt = pixClone(pixb);
+    }
+
+    if ((pixd = pixRemoveBorderGeneral(pixt,
+                leftpix, rightpix, toppix, bottompix)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    LEPT_FREE(buffer);
+    LEPT_FREE(maxarray);
+    pixDestroy(&pixb);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixOpenGray()
+ *
+ *      Input:  pixs
+ *              hsize  (of Sel; must be odd; origin implicitly in center)
+ *              vsize  (ditto)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) If hsize = vsize = 1, just returns a copy.
+ */
+PIX *
+pixOpenGray(PIX     *pixs,
+            l_int32  hsize,
+            l_int32  vsize)
+{
+l_uint8   *buffer;
+l_uint8   *array;  /* used to find either min or max in interval */
+l_int32    w, h, wplb, wplt;
+l_int32    leftpix, rightpix, toppix, bottompix, maxsize;
+l_uint32  *datab, *datat;
+PIX       *pixb, *pixt, *pixd;
+
+    PROCNAME("pixOpenGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize or vsize < 1", procName, NULL);
+    if ((hsize & 1) == 0 ) {
+        L_WARNING("horiz sel size must be odd; increasing by 1\n", procName);
+        hsize++;
+    }
+    if ((vsize & 1) == 0 ) {
+        L_WARNING("vert sel size must be odd; increasing by 1\n", procName);
+        vsize++;
+    }
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(NULL, pixs);
+
+    if (vsize == 1) {  /* horizontal sel */
+        leftpix = (hsize + 1) / 2;
+        rightpix = (3 * hsize + 1) / 2;
+        toppix = 0;
+        bottompix = 0;
+    } else if (hsize == 1) {  /* vertical sel */
+        leftpix = 0;
+        rightpix = 0;
+        toppix = (vsize + 1) / 2;
+        bottompix = (3 * vsize + 1) / 2;
+    } else {
+        leftpix = (hsize + 1) / 2;
+        rightpix = (3 * hsize + 1) / 2;
+        toppix = (vsize + 1) / 2;
+        bottompix = (3 * vsize + 1) / 2;
+    }
+
+    if ((pixb = pixAddBorderGeneral(pixs,
+                leftpix, rightpix, toppix, bottompix, 255)) == NULL)
+        return (PIX *)ERROR_PTR("pixb not made", procName, NULL);
+    if ((pixt = pixCreateTemplate(pixb)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+    pixGetDimensions(pixt, &w, &h, NULL);
+    datab = pixGetData(pixb);
+    datat = pixGetData(pixt);
+    wplb = pixGetWpl(pixb);
+    wplt = pixGetWpl(pixt);
+
+    if ((buffer = (l_uint8 *)LEPT_CALLOC(L_MAX(w, h), sizeof(l_uint8))) == NULL)
+        return (PIX *)ERROR_PTR("buffer not made", procName, NULL);
+    maxsize = L_MAX(hsize, vsize);
+    if ((array = (l_uint8 *)LEPT_CALLOC(2 * maxsize, sizeof(l_uint8))) == NULL)
+        return (PIX *)ERROR_PTR("array not made", procName, NULL);
+
+    if (vsize == 1) {
+        erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
+                     buffer, array);
+        pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
+                            PIX_CLR);
+        dilateGrayLow(datab, w, h, wplb, datat, wplt, hsize, L_HORIZ,
+                      buffer, array);
+    }
+    else if (hsize == 1) {
+        erodeGrayLow(datat, w, h, wplt, datab, wplb, vsize, L_VERT,
+                     buffer, array);
+        pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
+                            PIX_CLR);
+        dilateGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
+                      buffer, array);
+    } else {
+        erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
+                     buffer, array);
+        pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
+                            PIX_SET);
+        erodeGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
+                     buffer, array);
+        pixSetOrClearBorder(pixb, leftpix, rightpix, toppix, bottompix,
+                            PIX_CLR);
+        dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
+                      buffer, array);
+        pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
+                            PIX_CLR);
+        dilateGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
+                      buffer, array);
+    }
+
+    if ((pixd = pixRemoveBorderGeneral(pixb,
+                leftpix, rightpix, toppix, bottompix)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    LEPT_FREE(buffer);
+    LEPT_FREE(array);
+    pixDestroy(&pixb);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixCloseGray()
+ *
+ *      Input:  pixs
+ *              hsize  (of Sel; must be odd; origin implicitly in center)
+ *              vsize  (ditto)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) If hsize = vsize = 1, just returns a copy.
+ */
+PIX *
+pixCloseGray(PIX     *pixs,
+             l_int32  hsize,
+             l_int32  vsize)
+{
+l_uint8   *buffer;
+l_uint8   *array;  /* used to find either min or max in interval */
+l_int32    w, h, wplb, wplt;
+l_int32    leftpix, rightpix, toppix, bottompix, maxsize;
+l_uint32  *datab, *datat;
+PIX       *pixb, *pixt, *pixd;
+
+    PROCNAME("pixCloseGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize or vsize < 1", procName, NULL);
+    if ((hsize & 1) == 0 ) {
+        L_WARNING("horiz sel size must be odd; increasing by 1\n", procName);
+        hsize++;
+    }
+    if ((vsize & 1) == 0 ) {
+        L_WARNING("vert sel size must be odd; increasing by 1\n", procName);
+        vsize++;
+    }
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(NULL, pixs);
+
+    if (vsize == 1) {  /* horizontal sel */
+        leftpix = (hsize + 1) / 2;
+        rightpix = (3 * hsize + 1) / 2;
+        toppix = 0;
+        bottompix = 0;
+    } else if (hsize == 1) {  /* vertical sel */
+        leftpix = 0;
+        rightpix = 0;
+        toppix = (vsize + 1) / 2;
+        bottompix = (3 * vsize + 1) / 2;
+    } else {
+        leftpix = (hsize + 1) / 2;
+        rightpix = (3 * hsize + 1) / 2;
+        toppix = (vsize + 1) / 2;
+        bottompix = (3 * vsize + 1) / 2;
+    }
+
+    if ((pixb = pixAddBorderGeneral(pixs,
+                leftpix, rightpix, toppix, bottompix, 0)) == NULL)
+        return (PIX *)ERROR_PTR("pixb not made", procName, NULL);
+    if ((pixt = pixCreateTemplate(pixb)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+    pixGetDimensions(pixt, &w, &h, NULL);
+    datab = pixGetData(pixb);
+    datat = pixGetData(pixt);
+    wplb = pixGetWpl(pixb);
+    wplt = pixGetWpl(pixt);
+
+    if ((buffer = (l_uint8 *)LEPT_CALLOC(L_MAX(w, h), sizeof(l_uint8))) == NULL)
+        return (PIX *)ERROR_PTR("buffer not made", procName, NULL);
+    maxsize = L_MAX(hsize, vsize);
+    if ((array = (l_uint8 *)LEPT_CALLOC(2 * maxsize, sizeof(l_uint8))) == NULL)
+        return (PIX *)ERROR_PTR("array not made", procName, NULL);
+
+    if (vsize == 1) {
+        dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
+                     buffer, array);
+        pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
+                            PIX_SET);
+        erodeGrayLow(datab, w, h, wplb, datat, wplt, hsize, L_HORIZ,
+                      buffer, array);
+    } else if (hsize == 1) {
+        dilateGrayLow(datat, w, h, wplt, datab, wplb, vsize, L_VERT,
+                     buffer, array);
+        pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
+                            PIX_SET);
+        erodeGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
+                      buffer, array);
+    } else {
+        dilateGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
+                      buffer, array);
+        pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
+                            PIX_CLR);
+        dilateGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
+                      buffer, array);
+        pixSetOrClearBorder(pixb, leftpix, rightpix, toppix, bottompix,
+                            PIX_SET);
+        erodeGrayLow(datat, w, h, wplt, datab, wplb, hsize, L_HORIZ,
+                     buffer, array);
+        pixSetOrClearBorder(pixt, leftpix, rightpix, toppix, bottompix,
+                            PIX_SET);
+        erodeGrayLow(datab, w, h, wplb, datat, wplt, vsize, L_VERT,
+                     buffer, array);
+    }
+
+    if ((pixd = pixRemoveBorderGeneral(pixb,
+                leftpix, rightpix, toppix, bottompix)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    LEPT_FREE(buffer);
+    LEPT_FREE(array);
+    pixDestroy(&pixb);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *           Special operations for 1x3, 3x1 and 3x3 Sels          *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixErodeGray3()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *              hsize  (1 or 3)
+ *              vsize  (1 or 3)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
+ *      (2) If hsize = vsize = 1, just returns a copy.
+ *      (3) It would be nice not to add a border, but it is required
+ *          if we want the same results as from the general case.
+ *          We add 4 bytes on the left to speed up the copying, and
+ *          8 bytes at the right and bottom to allow unrolling of
+ *          the computation of 8 pixels.
+ */
+PIX *
+pixErodeGray3(PIX     *pixs,
+              l_int32  hsize,
+              l_int32  vsize)
+{
+PIX  *pixt, *pixb, *pixbd, *pixd;
+
+    PROCNAME("pixErodeGray3");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pix has colormap", procName, NULL);
+    if ((hsize != 1 && hsize != 3) ||
+        (vsize != 1 && vsize != 3))
+        return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", procName, NULL);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(NULL, pixs);
+
+    pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 255);
+
+    if (vsize == 1)
+        pixbd = pixErodeGray3h(pixb);
+    else if (hsize == 1)
+        pixbd = pixErodeGray3v(pixb);
+    else {  /* vize == hsize == 3 */
+        pixt = pixErodeGray3h(pixb);
+        pixbd = pixErodeGray3v(pixt);
+        pixDestroy(&pixt);
+    }
+
+    pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8);
+    pixDestroy(&pixb);
+    pixDestroy(&pixbd);
+    return pixd;
+}
+
+
+/*!
+ *  pixErodeGray3h()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Special case for horizontal 3x1 brick Sel;
+ *          also used as the first step for the 3x3 brick Sel.
+ */
+static PIX *
+pixErodeGray3h(PIX  *pixs)
+{
+l_uint32  *datas, *datad, *lines, *lined;
+l_int32    w, h, wpl, i, j;
+l_int32    val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, minval;
+PIX       *pixd;
+
+    PROCNAME("pixErodeGray3h");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+
+    pixd = pixCreateTemplateNoInit(pixs);
+    pixSetBorderVal(pixd, 4, 8, 2, 8, 0);  /* only to silence valgrind */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpl = pixGetWpl(pixs);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpl;
+        lined = datad + i * wpl;
+        for (j = 1; j < w - 8; j += 8) {
+            val0 = GET_DATA_BYTE(lines, j - 1);
+            val1 = GET_DATA_BYTE(lines, j);
+            val2 = GET_DATA_BYTE(lines, j + 1);
+            val3 = GET_DATA_BYTE(lines, j + 2);
+            val4 = GET_DATA_BYTE(lines, j + 3);
+            val5 = GET_DATA_BYTE(lines, j + 4);
+            val6 = GET_DATA_BYTE(lines, j + 5);
+            val7 = GET_DATA_BYTE(lines, j + 6);
+            val8 = GET_DATA_BYTE(lines, j + 7);
+            val9 = GET_DATA_BYTE(lines, j + 8);
+            minval = L_MIN(val1, val2);
+            SET_DATA_BYTE(lined, j, L_MIN(val0, minval));
+            SET_DATA_BYTE(lined, j + 1, L_MIN(minval, val3));
+            minval = L_MIN(val3, val4);
+            SET_DATA_BYTE(lined, j + 2, L_MIN(val2, minval));
+            SET_DATA_BYTE(lined, j + 3, L_MIN(minval, val5));
+            minval = L_MIN(val5, val6);
+            SET_DATA_BYTE(lined, j + 4, L_MIN(val4, minval));
+            SET_DATA_BYTE(lined, j + 5, L_MIN(minval, val7));
+            minval = L_MIN(val7, val8);
+            SET_DATA_BYTE(lined, j + 6, L_MIN(val6, minval));
+            SET_DATA_BYTE(lined, j + 7, L_MIN(minval, val9));
+        }
+    }
+    return pixd;
+}
+
+
+/*!
+ *  pixErodeGray3v()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Special case for vertical 1x3 brick Sel;
+ *          also used as the second step for the 3x3 brick Sel.
+ *      (2) Surprisingly, this is faster than setting up the
+ *          lineptrs array and accessing into it; e.g.,
+ *              val4 = GET_DATA_BYTE(lines8[i + 3], j);
+ */
+static PIX *
+pixErodeGray3v(PIX  *pixs)
+{
+l_uint32  *datas, *datad, *linesi, *linedi;
+l_int32    w, h, wpl, i, j;
+l_int32    val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, minval;
+PIX       *pixd;
+
+    PROCNAME("pixErodeGray3v");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+
+    pixd = pixCreateTemplateNoInit(pixs);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpl = pixGetWpl(pixs);
+    for (j = 0; j < w; j++) {
+        for (i = 1; i < h - 8; i += 8) {
+            linesi = datas + i * wpl;
+            linedi = datad + i * wpl;
+            val0 = GET_DATA_BYTE(linesi - wpl, j);
+            val1 = GET_DATA_BYTE(linesi, j);
+            val2 = GET_DATA_BYTE(linesi + wpl, j);
+            val3 = GET_DATA_BYTE(linesi + 2 * wpl, j);
+            val4 = GET_DATA_BYTE(linesi + 3 * wpl, j);
+            val5 = GET_DATA_BYTE(linesi + 4 * wpl, j);
+            val6 = GET_DATA_BYTE(linesi + 5 * wpl, j);
+            val7 = GET_DATA_BYTE(linesi + 6 * wpl, j);
+            val8 = GET_DATA_BYTE(linesi + 7 * wpl, j);
+            val9 = GET_DATA_BYTE(linesi + 8 * wpl, j);
+            minval = L_MIN(val1, val2);
+            SET_DATA_BYTE(linedi, j, L_MIN(val0, minval));
+            SET_DATA_BYTE(linedi + wpl, j, L_MIN(minval, val3));
+            minval = L_MIN(val3, val4);
+            SET_DATA_BYTE(linedi + 2 * wpl, j, L_MIN(val2, minval));
+            SET_DATA_BYTE(linedi + 3 * wpl, j, L_MIN(minval, val5));
+            minval = L_MIN(val5, val6);
+            SET_DATA_BYTE(linedi + 4 * wpl, j, L_MIN(val4, minval));
+            SET_DATA_BYTE(linedi + 5 * wpl, j, L_MIN(minval, val7));
+            minval = L_MIN(val7, val8);
+            SET_DATA_BYTE(linedi + 6 * wpl, j, L_MIN(val6, minval));
+            SET_DATA_BYTE(linedi + 7 * wpl, j, L_MIN(minval, val9));
+        }
+    }
+    return pixd;
+}
+
+
+/*!
+ *  pixDilateGray3()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *              hsize  (1 or 3)
+ *              vsize  (1 or 3)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
+ *      (2) If hsize = vsize = 1, just returns a copy.
+ */
+PIX *
+pixDilateGray3(PIX     *pixs,
+               l_int32  hsize,
+               l_int32  vsize)
+{
+PIX  *pixt, *pixb, *pixbd, *pixd;
+
+    PROCNAME("pixDilateGray3");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pix has colormap", procName, NULL);
+    if ((hsize != 1 && hsize != 3) ||
+        (vsize != 1 && vsize != 3))
+        return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", procName, NULL);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(NULL, pixs);
+
+    pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 0);
+
+    if (vsize == 1)
+        pixbd = pixDilateGray3h(pixb);
+    else if (hsize == 1)
+        pixbd = pixDilateGray3v(pixb);
+    else {  /* vize == hsize == 3 */
+        pixt = pixDilateGray3h(pixb);
+        pixbd = pixDilateGray3v(pixt);
+        pixDestroy(&pixt);
+    }
+
+    pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8);
+    pixDestroy(&pixb);
+    pixDestroy(&pixbd);
+    return pixd;
+}
+
+
+/*!
+ *  pixDilateGray3h()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Special case for horizontal 3x1 brick Sel;
+ *          also used as the first step for the 3x3 brick Sel.
+ */
+static PIX *
+pixDilateGray3h(PIX  *pixs)
+{
+l_uint32  *datas, *datad, *lines, *lined;
+l_int32    w, h, wpl, i, j;
+l_int32    val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, maxval;
+PIX       *pixd;
+
+    PROCNAME("pixDilateGray3h");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+
+    pixd = pixCreateTemplateNoInit(pixs);
+    pixSetBorderVal(pixd, 4, 8, 2, 8, 0);  /* only to silence valgrind */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpl = pixGetWpl(pixs);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpl;
+        lined = datad + i * wpl;
+        for (j = 1; j < w - 8; j += 8) {
+            val0 = GET_DATA_BYTE(lines, j - 1);
+            val1 = GET_DATA_BYTE(lines, j);
+            val2 = GET_DATA_BYTE(lines, j + 1);
+            val3 = GET_DATA_BYTE(lines, j + 2);
+            val4 = GET_DATA_BYTE(lines, j + 3);
+            val5 = GET_DATA_BYTE(lines, j + 4);
+            val6 = GET_DATA_BYTE(lines, j + 5);
+            val7 = GET_DATA_BYTE(lines, j + 6);
+            val8 = GET_DATA_BYTE(lines, j + 7);
+            val9 = GET_DATA_BYTE(lines, j + 8);
+            maxval = L_MAX(val1, val2);
+            SET_DATA_BYTE(lined, j, L_MAX(val0, maxval));
+            SET_DATA_BYTE(lined, j + 1, L_MAX(maxval, val3));
+            maxval = L_MAX(val3, val4);
+            SET_DATA_BYTE(lined, j + 2, L_MAX(val2, maxval));
+            SET_DATA_BYTE(lined, j + 3, L_MAX(maxval, val5));
+            maxval = L_MAX(val5, val6);
+            SET_DATA_BYTE(lined, j + 4, L_MAX(val4, maxval));
+            SET_DATA_BYTE(lined, j + 5, L_MAX(maxval, val7));
+            maxval = L_MAX(val7, val8);
+            SET_DATA_BYTE(lined, j + 6, L_MAX(val6, maxval));
+            SET_DATA_BYTE(lined, j + 7, L_MAX(maxval, val9));
+        }
+    }
+    return pixd;
+}
+
+
+/*!
+ *  pixDilateGray3v()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Special case for vertical 1x3 brick Sel;
+ *          also used as the second step for the 3x3 brick Sel.
+ */
+static PIX *
+pixDilateGray3v(PIX  *pixs)
+{
+l_uint32  *datas, *datad, *linesi, *linedi;
+l_int32    w, h, wpl, i, j;
+l_int32    val0, val1, val2, val3, val4, val5, val6, val7, val8, val9, maxval;
+PIX       *pixd;
+
+    PROCNAME("pixDilateGray3v");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+
+    pixd = pixCreateTemplateNoInit(pixs);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpl = pixGetWpl(pixs);
+    for (j = 0; j < w; j++) {
+        for (i = 1; i < h - 8; i += 8) {
+            linesi = datas + i * wpl;
+            linedi = datad + i * wpl;
+            val0 = GET_DATA_BYTE(linesi - wpl, j);
+            val1 = GET_DATA_BYTE(linesi, j);
+            val2 = GET_DATA_BYTE(linesi + wpl, j);
+            val3 = GET_DATA_BYTE(linesi + 2 * wpl, j);
+            val4 = GET_DATA_BYTE(linesi + 3 * wpl, j);
+            val5 = GET_DATA_BYTE(linesi + 4 * wpl, j);
+            val6 = GET_DATA_BYTE(linesi + 5 * wpl, j);
+            val7 = GET_DATA_BYTE(linesi + 6 * wpl, j);
+            val8 = GET_DATA_BYTE(linesi + 7 * wpl, j);
+            val9 = GET_DATA_BYTE(linesi + 8 * wpl, j);
+            maxval = L_MAX(val1, val2);
+            SET_DATA_BYTE(linedi, j, L_MAX(val0, maxval));
+            SET_DATA_BYTE(linedi + wpl, j, L_MAX(maxval, val3));
+            maxval = L_MAX(val3, val4);
+            SET_DATA_BYTE(linedi + 2 * wpl, j, L_MAX(val2, maxval));
+            SET_DATA_BYTE(linedi + 3 * wpl, j, L_MAX(maxval, val5));
+            maxval = L_MAX(val5, val6);
+            SET_DATA_BYTE(linedi + 4 * wpl, j, L_MAX(val4, maxval));
+            SET_DATA_BYTE(linedi + 5 * wpl, j, L_MAX(maxval, val7));
+            maxval = L_MAX(val7, val8);
+            SET_DATA_BYTE(linedi + 6 * wpl, j, L_MAX(val6, maxval));
+            SET_DATA_BYTE(linedi + 7 * wpl, j, L_MAX(maxval, val9));
+        }
+    }
+    return pixd;
+}
+
+
+/*!
+ *  pixOpenGray3()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *              hsize  (1 or 3)
+ *              vsize  (1 or 3)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
+ *      (2) If hsize = vsize = 1, just returns a copy.
+ *      (3) It would be nice not to add a border, but it is required
+ *          to get the same results as for the general case.
+ */
+PIX *
+pixOpenGray3(PIX     *pixs,
+             l_int32  hsize,
+             l_int32  vsize)
+{
+PIX  *pixt, *pixb, *pixbd, *pixd;
+
+    PROCNAME("pixOpenGray3");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pix has colormap", procName, NULL);
+    if ((hsize != 1 && hsize != 3) ||
+        (vsize != 1 && vsize != 3))
+        return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", procName, NULL);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(NULL, pixs);
+
+    pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 255);  /* set to max */
+
+    if (vsize == 1) {
+        pixt = pixErodeGray3h(pixb);
+        pixSetBorderVal(pixt, 4, 8, 2, 8, 0);  /* set to min */
+        pixbd = pixDilateGray3h(pixt);
+        pixDestroy(&pixt);
+    } else if (hsize == 1) {
+        pixt = pixErodeGray3v(pixb);
+        pixSetBorderVal(pixt, 4, 8, 2, 8, 0);
+        pixbd = pixDilateGray3v(pixt);
+        pixDestroy(&pixt);
+    } else {  /* vize == hsize == 3 */
+        pixt = pixErodeGray3h(pixb);
+        pixbd = pixErodeGray3v(pixt);
+        pixDestroy(&pixt);
+        pixSetBorderVal(pixbd, 4, 8, 2, 8, 0);
+        pixt = pixDilateGray3h(pixbd);
+        pixDestroy(&pixbd);
+        pixbd = pixDilateGray3v(pixt);
+        pixDestroy(&pixt);
+    }
+
+    pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8);
+    pixDestroy(&pixb);
+    pixDestroy(&pixbd);
+    return pixd;
+}
+
+
+/*!
+ *  pixCloseGray3()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *              hsize  (1 or 3)
+ *              vsize  (1 or 3)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Special case for 1x3, 3x1 or 3x3 brick sel (all hits)
+ *      (2) If hsize = vsize = 1, just returns a copy.
+ */
+PIX *
+pixCloseGray3(PIX     *pixs,
+              l_int32  hsize,
+              l_int32  vsize)
+{
+PIX  *pixt, *pixb, *pixbd, *pixd;
+
+    PROCNAME("pixCloseGray3");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pix has colormap", procName, NULL);
+    if ((hsize != 1 && hsize != 3) ||
+        (vsize != 1 && vsize != 3))
+        return (PIX *)ERROR_PTR("invalid size: must be 1 or 3", procName, NULL);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(NULL, pixs);
+
+    pixb = pixAddBorderGeneral(pixs, 4, 8, 2, 8, 0);  /* set to min */
+
+    if (vsize == 1) {
+        pixt = pixDilateGray3h(pixb);
+        pixSetBorderVal(pixt, 4, 8, 2, 8, 255);  /* set to max */
+        pixbd = pixErodeGray3h(pixt);
+        pixDestroy(&pixt);
+    } else if (hsize == 1) {
+        pixt = pixDilateGray3v(pixb);
+        pixSetBorderVal(pixt, 4, 8, 2, 8, 255);
+        pixbd = pixErodeGray3v(pixt);
+        pixDestroy(&pixt);
+    } else {  /* vize == hsize == 3 */
+        pixt = pixDilateGray3h(pixb);
+        pixbd = pixDilateGray3v(pixt);
+        pixDestroy(&pixt);
+        pixSetBorderVal(pixbd, 4, 8, 2, 8, 255);
+        pixt = pixErodeGray3h(pixbd);
+        pixDestroy(&pixbd);
+        pixbd = pixErodeGray3v(pixt);
+        pixDestroy(&pixt);
+    }
+
+    pixd = pixRemoveBorderGeneral(pixbd, 4, 8, 2, 8);
+    pixDestroy(&pixb);
+    pixDestroy(&pixbd);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *              Low-level gray morphological operations            *
+ *-----------------------------------------------------------------*/
+/*!
+ *  dilateGrayLow()
+ *
+ *    Input:  datad, w, h, wpld (8 bpp image)
+ *            datas, wpls  (8 bpp image, of same dimensions)
+ *            size  (full length of SEL; restricted to odd numbers)
+ *            direction  (L_HORIZ or L_VERT)
+ *            buffer  (holds full line or column of src image pixels)
+ *            maxarray  (array of dimension 2*size+1)
+ *    Return: void
+ *
+ *    Notes:
+ *        (1) To eliminate border effects on the actual image, these images
+ *            are prepared with an additional border of dimensions:
+ *               leftpix = 0.5 * size
+ *               rightpix = 1.5 * size
+ *               toppix = 0.5 * size
+ *               bottompix = 1.5 * size
+ *            and we initialize the src border pixels to 0.
+ *            This allows full processing over the actual image; at
+ *            the end the border is removed.
+ *        (2) Uses algorithm of van Herk, Gil and Werman
+ */
+static void
+dilateGrayLow(l_uint32  *datad,
+              l_int32    w,
+              l_int32    h,
+              l_int32    wpld,
+              l_uint32  *datas,
+              l_int32    wpls,
+              l_int32    size,
+              l_int32    direction,
+              l_uint8   *buffer,
+              l_uint8   *maxarray)
+{
+l_int32    i, j, k;
+l_int32    hsize, nsteps, startmax, startx, starty;
+l_uint8    maxval;
+l_uint32  *lines, *lined;
+
+    if (direction == L_HORIZ) {
+        hsize = size / 2;
+        nsteps = (w - 2 * hsize) / size;
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+
+                /* fill buffer with pixels in byte order */
+            for (j = 0; j < w; j++)
+                buffer[j] = GET_DATA_BYTE(lines, j);
+
+            for (j = 0; j < nsteps; j++) {
+                    /* refill the minarray */
+                startmax = (j + 1) * size - 1;
+                maxarray[size - 1] = buffer[startmax];
+                for (k = 1; k < size; k++) {
+                    maxarray[size - 1 - k] =
+                        L_MAX(maxarray[size - k], buffer[startmax - k]);
+                    maxarray[size - 1 + k] =
+                        L_MAX(maxarray[size + k - 2], buffer[startmax + k]);
+                }
+
+                    /* compute dilation values */
+                startx = hsize + j * size;
+                SET_DATA_BYTE(lined, startx, maxarray[0]);
+                SET_DATA_BYTE(lined, startx + size - 1, maxarray[2 * size - 2]);
+                for (k = 1; k < size - 1; k++) {
+                    maxval = L_MAX(maxarray[k], maxarray[k + size - 1]);
+                    SET_DATA_BYTE(lined, startx + k, maxval);
+                }
+            }
+        }
+    } else {  /* direction == L_VERT */
+        hsize = size / 2;
+        nsteps = (h - 2 * hsize) / size;
+        for (j = 0; j < w; j++) {
+                /* fill buffer with pixels in byte order */
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                buffer[i] = GET_DATA_BYTE(lines, j);
+            }
+
+            for (i = 0; i < nsteps; i++) {
+                    /* refill the minarray */
+                startmax = (i + 1) * size - 1;
+                maxarray[size - 1] = buffer[startmax];
+                for (k = 1; k < size; k++) {
+                    maxarray[size - 1 - k] =
+                        L_MAX(maxarray[size - k], buffer[startmax - k]);
+                    maxarray[size - 1 + k] =
+                        L_MAX(maxarray[size + k - 2], buffer[startmax + k]);
+                }
+
+                    /* compute dilation values */
+                starty = hsize + i * size;
+                lined = datad + starty * wpld;
+                SET_DATA_BYTE(lined, j, maxarray[0]);
+                SET_DATA_BYTE(lined + (size - 1) * wpld, j,
+                        maxarray[2 * size - 2]);
+                for (k = 1; k < size - 1; k++) {
+                    maxval = L_MAX(maxarray[k], maxarray[k + size - 1]);
+                    SET_DATA_BYTE(lined + wpld * k, j, maxval);
+                }
+            }
+        }
+    }
+
+    return;
+}
+
+
+/*!
+ *  erodeGrayLow()
+ *
+ *    Input:  datad, w, h, wpld (8 bpp image)
+ *            datas, wpls  (8 bpp image, of same dimensions)
+ *            size  (full length of SEL; restricted to odd numbers)
+ *            direction  (L_HORIZ or L_VERT)
+ *            buffer  (holds full line or column of src image pixels)
+ *            minarray  (array of dimension 2*size+1)
+ *    Return: void
+ *
+ *    Notes:
+ *        (1) See notes in dilateGrayLow()
+ */
+static void
+erodeGrayLow(l_uint32  *datad,
+             l_int32    w,
+             l_int32    h,
+             l_int32    wpld,
+             l_uint32  *datas,
+             l_int32    wpls,
+             l_int32    size,
+             l_int32    direction,
+             l_uint8   *buffer,
+             l_uint8   *minarray)
+{
+l_int32    i, j, k;
+l_int32    hsize, nsteps, startmin, startx, starty;
+l_uint8    minval;
+l_uint32  *lines, *lined;
+
+    if (direction == L_HORIZ) {
+        hsize = size / 2;
+        nsteps = (w - 2 * hsize) / size;
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+
+                /* fill buffer with pixels in byte order */
+            for (j = 0; j < w; j++)
+                buffer[j] = GET_DATA_BYTE(lines, j);
+
+            for (j = 0; j < nsteps; j++) {
+                    /* refill the minarray */
+                startmin = (j + 1) * size - 1;
+                minarray[size - 1] = buffer[startmin];
+                for (k = 1; k < size; k++) {
+                    minarray[size - 1 - k] =
+                        L_MIN(minarray[size - k], buffer[startmin - k]);
+                    minarray[size - 1 + k] =
+                        L_MIN(minarray[size + k - 2], buffer[startmin + k]);
+                }
+
+                    /* compute erosion values */
+                startx = hsize + j * size;
+                SET_DATA_BYTE(lined, startx, minarray[0]);
+                SET_DATA_BYTE(lined, startx + size - 1, minarray[2 * size - 2]);
+                for (k = 1; k < size - 1; k++) {
+                    minval = L_MIN(minarray[k], minarray[k + size - 1]);
+                    SET_DATA_BYTE(lined, startx + k, minval);
+                }
+            }
+        }
+    } else {  /* direction == L_VERT */
+        hsize = size / 2;
+        nsteps = (h - 2 * hsize) / size;
+        for (j = 0; j < w; j++) {
+                /* fill buffer with pixels in byte order */
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                buffer[i] = GET_DATA_BYTE(lines, j);
+            }
+
+            for (i = 0; i < nsteps; i++) {
+                    /* refill the minarray */
+                startmin = (i + 1) * size - 1;
+                minarray[size - 1] = buffer[startmin];
+                for (k = 1; k < size; k++) {
+                    minarray[size - 1 - k] =
+                        L_MIN(minarray[size - k], buffer[startmin - k]);
+                    minarray[size - 1 + k] =
+                        L_MIN(minarray[size + k - 2], buffer[startmin + k]);
+                }
+
+                    /* compute erosion values */
+                starty = hsize + i * size;
+                lined = datad + starty * wpld;
+                SET_DATA_BYTE(lined, j, minarray[0]);
+                SET_DATA_BYTE(lined + (size - 1) * wpld, j,
+                        minarray[2 * size - 2]);
+                for (k = 1; k < size - 1; k++) {
+                    minval = L_MIN(minarray[k], minarray[k + size - 1]);
+                    SET_DATA_BYTE(lined + wpld * k, j, minval);
+                }
+            }
+        }
+    }
+
+    return;
+}
diff --git a/src/grayquant.c b/src/grayquant.c
new file mode 100644 (file)
index 0000000..6e259d8
--- /dev/null
@@ -0,0 +1,2014 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  grayquant.c
+ *
+ *      Thresholding from 8 bpp to 1 bpp
+ *
+ *          Floyd-Steinberg dithering to binary
+ *              PIX    *pixDitherToBinary()
+ *              PIX    *pixDitherToBinarySpec()
+ *
+ *          Simple (pixelwise) binarization with fixed threshold
+ *              PIX    *pixThresholdToBinary()
+ *
+ *          Binarization with variable threshold
+ *              PIX    *pixVarThresholdToBinary()
+ *
+ *          Binarization by adaptive mapping
+ *              PIX    *pixAdaptThresholdToBinary()
+ *              PIX    *pixAdaptThresholdToBinaryGen()
+ *
+ *          Slower implementation of Floyd-Steinberg dithering, using LUTs
+ *              PIX    *pixDitherToBinaryLUT()
+ *
+ *          Generate a binary mask from pixels of particular values
+ *              PIX    *pixGenerateMaskByValue()
+ *              PIX    *pixGenerateMaskByBand()
+ *
+ *      Thresholding from 8 bpp to 2 bpp
+ *
+ *          Dithering to 2 bpp
+ *              PIX      *pixDitherTo2bpp()
+ *              PIX      *pixDitherTo2bppSpec()
+ *
+ *          Simple (pixelwise) thresholding to 2 bpp with optional cmap
+ *              PIX      *pixThresholdTo2bpp()
+ *
+ *      Simple (pixelwise) thresholding from 8 bpp to 4 bpp
+ *              PIX      *pixThresholdTo4bpp()
+ *
+ *      Simple (pixelwise) quantization on 8 bpp grayscale
+ *              PIX      *pixThresholdOn8bpp()
+ *
+ *      Arbitrary (pixelwise) thresholding from 8 bpp to 2, 4 or 8 bpp
+ *              PIX      *pixThresholdGrayArb()
+ *
+ *      Quantization tables for linear thresholds of grayscale images
+ *              l_int32  *makeGrayQuantIndexTable()
+ *              l_int32  *makeGrayQuantTargetTable()
+ *
+ *      Quantization table for arbitrary thresholding of grayscale images
+ *              l_int32   makeGrayQuantTableArb()
+ *              l_int32   makeGrayQuantColormapArb()
+ *
+ *      Thresholding from 32 bpp rgb to 1 bpp
+ *      (really color quantization, but it's better placed in this file)
+ *              PIX      *pixGenerateMaskByBand32()
+ *              PIX      *pixGenerateMaskByDiscr32()
+ *
+ *      Histogram-based grayscale quantization
+ *              PIX      *pixGrayQuantFromHisto()
+ *       static l_int32   numaFillCmapFromHisto()
+ *
+ *      Color quantize grayscale image using existing colormap
+ *              PIX      *pixGrayQuantFromCmap()
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+
+static l_int32 numaFillCmapFromHisto(NUMA *na, PIXCMAP *cmap,
+                                     l_float32 minfract, l_int32 maxsize,
+                                     l_int32 **plut);
+
+
+/*------------------------------------------------------------------*
+ *             Binarization by Floyd-Steinberg dithering            *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixDitherToBinary()
+ *
+ *      Input:  pixs
+ *      Return: pixd (dithered binary), or null on error
+ *
+ *  The Floyd-Steinberg error diffusion dithering algorithm
+ *  binarizes an 8 bpp grayscale image to a threshold of 128.
+ *  If a pixel has a value above 127, it is binarized to white
+ *  and the excess (below 255) is subtracted from three
+ *  neighboring pixels in the fractions 3/8 to (i, j+1),
+ *  3/8 to (i+1, j) and 1/4 to (i+1,j+1), truncating to 0
+ *  if necessary.  Likewise, if it the pixel has a value
+ *  below 128, it is binarized to black and the excess above 0
+ *  is added to the neighboring pixels, truncating to 255 if necessary.
+ *
+ *  This function differs from straight dithering in that it allows
+ *  clipping of grayscale to 0 or 255 if the values are
+ *  sufficiently close, without distribution of the excess.
+ *  This uses default values to specify the range of lower
+ *  and upper values (near 0 and 255, rsp) that are clipped
+ *  to black and white without propagating the excess.
+ *  Not propagating the excess has the effect of reducing the
+ *  snake patterns in parts of the image that are nearly black or white;
+ *  however, it also prevents the attempt to reproduce gray for those values.
+ *
+ *  The implementation is straightforward.  It uses a pair of
+ *  line buffers to avoid changing pixs.  It is about 2x faster
+ *  than the implementation using LUTs.
+ */
+PIX *
+pixDitherToBinary(PIX  *pixs)
+{
+    PROCNAME("pixDitherToBinary");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("must be 8 bpp for dithering", procName, NULL);
+
+    return pixDitherToBinarySpec(pixs, DEFAULT_CLIP_LOWER_1,
+                                 DEFAULT_CLIP_UPPER_1);
+}
+
+
+/*!
+ *  pixDitherToBinarySpec()
+ *
+ *      Input:  pixs
+ *              lowerclip (lower clip distance to black; use 0 for default)
+ *              upperclip (upper clip distance to white; use 0 for default)
+ *      Return: pixd (dithered binary), or null on error
+ *
+ *  Notes:
+ *      (1) See comments above in pixDitherToBinary() for details.
+ *      (2) The input parameters lowerclip and upperclip specify the range
+ *          of lower and upper values (near 0 and 255, rsp) that are
+ *          clipped to black and white without propagating the excess.
+ *          For that reason, lowerclip and upperclip should be small numbers.
+ */
+PIX *
+pixDitherToBinarySpec(PIX     *pixs,
+                      l_int32  lowerclip,
+                      l_int32  upperclip)
+{
+l_int32    w, h, d, wplt, wpld;
+l_uint32  *datat, *datad;
+l_uint32  *bufs1, *bufs2;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixDitherToBinarySpec");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("must be 8 bpp for dithering", procName, NULL);
+    if (lowerclip < 0 || lowerclip > 255)
+        return (PIX *)ERROR_PTR("invalid value for lowerclip", procName, NULL);
+    if (upperclip < 0 || upperclip > 255)
+        return (PIX *)ERROR_PTR("invalid value for upperclip", procName, NULL);
+
+    if ((pixd = pixCreate(w, h, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* Remove colormap if it exists */
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+
+        /* Two line buffers, 1 for current line and 2 for next line */
+    if ((bufs1 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("bufs1 not made", procName, NULL);
+    if ((bufs2 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("bufs2 not made", procName, NULL);
+
+    ditherToBinaryLow(datad, w, h, wpld, datat, wplt, bufs1, bufs2,
+                      lowerclip, upperclip);
+
+    LEPT_FREE(bufs1);
+    LEPT_FREE(bufs2);
+    pixDestroy(&pixt);
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *       Simple (pixelwise) binarization with fixed threshold       *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixThresholdToBinary()
+ *
+ *      Input:  pixs (4 or 8 bpp)
+ *              threshold value
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) If the source pixel is less than the threshold value,
+ *          the dest will be 1; otherwise, it will be 0
+ */
+PIX *
+pixThresholdToBinary(PIX     *pixs,
+                     l_int32  thresh)
+{
+l_int32    d, w, h, wplt, wpld;
+l_uint32  *datat, *datad;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixThresholdToBinary");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 4 && d != 8)
+        return (PIX *)ERROR_PTR("pixs must be 4 or 8 bpp", procName, NULL);
+    if (thresh < 0)
+        return (PIX *)ERROR_PTR("thresh must be non-negative", procName, NULL);
+    if (d == 4 && thresh > 16)
+        return (PIX *)ERROR_PTR("4 bpp thresh not in {0-16}", procName, NULL);
+    if (d == 8 && thresh > 256)
+        return (PIX *)ERROR_PTR("8 bpp thresh not in {0-256}", procName, NULL);
+
+    if ((pixd = pixCreate(w, h, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* Remove colormap if it exists.  If there is a colormap,
+         * pixt will be 8 bpp regardless of the depth of pixs. */
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+    if (pixGetColormap(pixs) && d == 4) {  /* promoted to 8 bpp */
+        d = 8;
+        thresh *= 16;
+    }
+
+    thresholdToBinaryLow(datad, w, h, wpld, datat, d, wplt, thresh);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                Binarization with variable threshold              *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixVarThresholdToBinary()
+ *
+ *      Input:  pixs (8 bpp)
+ *              pixg (8 bpp; contains threshold values for each pixel)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) If the pixel in pixs is less than the corresponding pixel
+ *          in pixg, the dest will be 1; otherwise it will be 0.
+ */
+PIX *
+pixVarThresholdToBinary(PIX  *pixs,
+                        PIX  *pixg)
+{
+l_int32    i, j, vals, valg, w, h, d, wpls, wplg, wpld;
+l_uint32  *datas, *datag, *datad, *lines, *lineg, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixVarThresholdToBinary");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!pixg)
+        return (PIX *)ERROR_PTR("pixg not defined", procName, NULL);
+    if (!pixSizesEqual(pixs, pixg))
+        return (PIX *)ERROR_PTR("pix sizes not equal", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs must be 8 bpp", procName, NULL);
+
+    pixd = pixCreate(w, h, 1);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datag = pixGetData(pixg);
+    wplg = pixGetWpl(pixg);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lineg = datag + i * wplg;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            vals = GET_DATA_BYTE(lines, j);
+            valg = GET_DATA_BYTE(lineg, j);
+            if (vals < valg)
+                SET_DATA_BIT(lined, j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                  Binarization by adaptive mapping                *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixAdaptThresholdToBinary()
+ *
+ *      Input:  pixs (8 bpp)
+ *              pixm (<optional> 1 bpp image mask; can be null)
+ *              gamma (gamma correction; must be > 0.0; typically ~1.0)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This is a simple convenience function for doing adaptive
+ *          thresholding on a grayscale image with variable background.
+ *          It uses default parameters appropriate for typical text images.
+ *      (2) @pixm is a 1 bpp mask over "image" regions, which are not
+ *          expected to have a white background.  The mask inhibits
+ *          background finding under the fg pixels of the mask.  For
+ *          images with both text and image, the image regions would
+ *          be binarized (or quantized) by a different set of operations.
+ *      (3) As @gamma is increased, the foreground pixels are reduced.
+ *      (4) Under the covers:  The default background value for normalization
+ *          is 200, so we choose 170 for 'maxval' in pixGammaTRC.  Likewise,
+ *          the default foreground threshold for normalization is 60,
+ *          so we choose 50 for 'minval' in pixGammaTRC.  Because
+ *          170 was mapped to 255, choosing 200 for the threshold is
+ *          quite safe for avoiding speckle noise from the background.
+ */
+PIX *
+pixAdaptThresholdToBinary(PIX       *pixs,
+                          PIX       *pixm,
+                          l_float32  gamma)
+{
+    PROCNAME("pixAdaptThresholdToBinary");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+
+    return pixAdaptThresholdToBinaryGen(pixs, pixm, gamma, 50, 170, 200);
+}
+
+
+/*!
+ *  pixAdaptThresholdToBinaryGen()
+ *
+ *      Input:  pixs (8 bpp)
+ *              pixm (<optional> 1 bpp image mask; can be null)
+ *              gamma (gamma correction; must be > 0.0; typically ~1.0)
+ *              blackval (dark value to set to black (0))
+ *              whiteval (light value to set to white (255))
+ *              thresh (final threshold for binarization)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This is a convenience function for doing adaptive thresholding
+ *          on a grayscale image with variable background.  Also see notes
+ *          in pixAdaptThresholdToBinary().
+ *      (2) Reducing @gamma increases the foreground (text) pixels.
+ *          Use a low value (e.g., 0.5) for images with light text.
+ *      (3) For normal images, see default args in pixAdaptThresholdToBinary().
+ *          For images with very light text, these values are appropriate:
+ *             gamma     ~0.5
+ *             blackval  ~70
+ *             whiteval  ~190
+ *             thresh    ~200
+ */
+PIX *
+pixAdaptThresholdToBinaryGen(PIX       *pixs,
+                             PIX       *pixm,
+                             l_float32  gamma,
+                             l_int32    blackval,
+                             l_int32    whiteval,
+                             l_int32    thresh)
+{
+PIX  *pix1, *pixd;
+
+    PROCNAME("pixAdaptThresholdToBinaryGen");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+
+    pix1 = pixBackgroundNormSimple(pixs, pixm, NULL);
+    pixGammaTRC(pix1, pix1, gamma, blackval, whiteval);
+    pixd = pixThresholdToBinary(pix1, thresh);
+    pixDestroy(&pix1);
+    return pixd;
+}
+
+
+/*--------------------------------------------------------------------*
+ *    Slower implementation of binarization by dithering using LUTs   *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixDitherToBinaryLUT()
+ *
+ *      Input:  pixs
+ *              lowerclip (lower clip distance to black; use -1 for default)
+ *              upperclip (upper clip distance to white; use -1 for default)
+ *      Return: pixd (dithered binary), or null on error
+ *
+ *  This implementation is deprecated.  You should use pixDitherToBinary().
+ *
+ *  See comments in pixDitherToBinary()
+ *
+ *  This implementation additionally uses three lookup tables to
+ *  generate the output pixel value and the excess or deficit
+ *  carried over to the neighboring pixels.
+ */
+PIX *
+pixDitherToBinaryLUT(PIX     *pixs,
+                     l_int32  lowerclip,
+                     l_int32  upperclip)
+{
+l_int32    w, h, d, wplt, wpld;
+l_int32   *tabval, *tab38, *tab14;
+l_uint32  *datat, *datad;
+l_uint32  *bufs1, *bufs2;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixDitherToBinaryLUT");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("must be 8 bpp for dithering", procName, NULL);
+    if (lowerclip < 0)
+        lowerclip = DEFAULT_CLIP_LOWER_1;
+    if (upperclip < 0)
+        upperclip = DEFAULT_CLIP_UPPER_1;
+
+    if ((pixd = pixCreate(w, h, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* Remove colormap if it exists */
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+
+        /* Two line buffers, 1 for current line and 2 for next line */
+    if ((bufs1 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("bufs1 not made", procName, NULL);
+    if ((bufs2 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("bufs2 not made", procName, NULL);
+
+        /* 3 lookup tables: 1-bit value, (3/8)excess, and (1/4)excess */
+    make8To1DitherTables(&tabval, &tab38, &tab14, lowerclip, upperclip);
+
+    ditherToBinaryLUTLow(datad, w, h, wpld, datat, wplt, bufs1, bufs2,
+                         tabval, tab38, tab14);
+
+    LEPT_FREE(bufs1);
+    LEPT_FREE(bufs2);
+    LEPT_FREE(tabval);
+    LEPT_FREE(tab38);
+    LEPT_FREE(tab14);
+    pixDestroy(&pixt);
+
+    return pixd;
+}
+
+
+/*--------------------------------------------------------------------*
+ *       Generate a binary mask from pixels of particular value(s)    *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixGenerateMaskByValue()
+ *
+ *      Input:  pixs (2, 4 or 8 bpp, or colormapped)
+ *              val (of pixels for which we set 1 in dest)
+ *              usecmap (1 to retain cmap values; 0 to convert to gray)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) @val is the pixel value that we are selecting.  It can be
+ *          either a gray value or a colormap index.
+ *      (2) If pixs is colormapped, @usecmap determines if the colormap
+ *          index values are used, or if the colormap is removed to gray and
+ *          the gray values are used.  For the latter, it generates
+ *          an approximate grayscale value for each pixel, and then looks
+ *          for gray pixels with the value @val.
+ */
+PIX *
+pixGenerateMaskByValue(PIX     *pixs,
+                       l_int32  val,
+                       l_int32  usecmap)
+{
+l_int32    i, j, w, h, d, wplg, wpld;
+l_uint32  *datag, *datad, *lineg, *lined;
+PIX       *pixg, *pixd;
+
+    PROCNAME("pixGenerateMaskByValue");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 2 && d != 4 && d != 8)
+        return (PIX *)ERROR_PTR("not 2, 4 or 8 bpp", procName, NULL);
+
+    if (!usecmap && pixGetColormap(pixs))
+        pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else
+        pixg = pixClone(pixs);
+    pixGetDimensions(pixg, &w, &h, &d);
+    if (d == 8 && (val < 0 || val > 255)) {
+        pixDestroy(&pixg);
+        return (PIX *)ERROR_PTR("val out of 8 bpp range", procName, NULL);
+    }
+    if (d == 4 && (val < 0 || val > 15)) {
+        pixDestroy(&pixg);
+        return (PIX *)ERROR_PTR("val out of 4 bpp range", procName, NULL);
+    }
+    if (d == 2 && (val < 0 || val > 3)) {
+        pixDestroy(&pixg);
+        return (PIX *)ERROR_PTR("val out of 2 bpp range", procName, NULL);
+    }
+
+    pixd = pixCreate(w, h, 1);
+    pixCopyResolution(pixd, pixg);
+    pixCopyInputFormat(pixd, pixs);
+    datag = pixGetData(pixg);
+    wplg = pixGetWpl(pixg);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lineg = datag + i * wplg;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            if (d == 8) {
+                if (GET_DATA_BYTE(lineg, j) == val)
+                    SET_DATA_BIT(lined, j);
+            } else if (d == 4) {
+                if (GET_DATA_QBIT(lineg, j) == val)
+                    SET_DATA_BIT(lined, j);
+            } else {  /* d == 2 */
+                if (GET_DATA_DIBIT(lineg, j) == val)
+                    SET_DATA_BIT(lined, j);
+            }
+        }
+    }
+
+    pixDestroy(&pixg);
+    return pixd;
+}
+
+
+/*!
+ *  pixGenerateMaskByBand()
+ *
+ *      Input:  pixs (2, 4 or 8 bpp, or colormapped)
+ *              lower, upper (two pixel values from which a range, either
+ *                            between (inband) or outside of (!inband),
+ *                            determines which pixels in pixs cause us to
+ *                            set a 1 in the dest mask)
+ *              inband (1 for finding pixels in [lower, upper];
+ *                      0 for finding pixels in [0, lower) union (upper, 255])
+ *              usecmap (1 to retain cmap values; 0 to convert to gray)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) Generates a 1 bpp mask pixd, the same size as pixs, where
+ *          the fg pixels in the mask are those either within the specified
+ *          band (for inband == 1) or outside the specified band
+ *          (for inband == 0).
+ *      (2) If pixs is colormapped, @usecmap determines if the colormap
+ *          values are used, or if the colormap is removed to gray and
+ *          the gray values are used.  For the latter, it generates
+ *          an approximate grayscale value for each pixel, and then looks
+ *          for gray pixels with the value @val.
+ */
+PIX *
+pixGenerateMaskByBand(PIX     *pixs,
+                      l_int32  lower,
+                      l_int32  upper,
+                      l_int32  inband,
+                      l_int32  usecmap)
+{
+l_int32    i, j, w, h, d, wplg, wpld, val;
+l_uint32  *datag, *datad, *lineg, *lined;
+PIX       *pixg, *pixd;
+
+    PROCNAME("pixGenerateMaskByBand");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 2 && d != 4 && d != 8)
+        return (PIX *)ERROR_PTR("not 2, 4 or 8 bpp", procName, NULL);
+    if (lower < 0 || lower > upper)
+        return (PIX *)ERROR_PTR("lower < 0 or lower > upper!", procName, NULL);
+
+    if (!usecmap && pixGetColormap(pixs))
+        pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else
+        pixg = pixClone(pixs);
+    pixGetDimensions(pixg, &w, &h, &d);
+    if (d == 8 && upper > 255) {
+        pixDestroy(&pixg);
+        return (PIX *)ERROR_PTR("d == 8 and upper > 255", procName, NULL);
+    }
+    if (d == 4 && upper > 15) {
+        pixDestroy(&pixg);
+        return (PIX *)ERROR_PTR("d == 4 and upper > 15", procName, NULL);
+    }
+    if (d == 2 && upper > 3) {
+        pixDestroy(&pixg);
+        return (PIX *)ERROR_PTR("d == 2 and upper > 3", procName, NULL);
+    }
+
+    pixd = pixCreate(w, h, 1);
+    pixCopyResolution(pixd, pixg);
+    pixCopyInputFormat(pixd, pixs);
+    datag = pixGetData(pixg);
+    wplg = pixGetWpl(pixg);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lineg = datag + i * wplg;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            if (d == 8)
+                val = GET_DATA_BYTE(lineg, j);
+            else if (d == 4)
+                val = GET_DATA_QBIT(lineg, j);
+            else  /* d == 2 */
+                val = GET_DATA_DIBIT(lineg, j);
+            if (inband) {
+                if (val >= lower && val <= upper)
+                    SET_DATA_BIT(lined, j);
+            } else {  /* out of band */
+                if (val < lower || val > upper)
+                    SET_DATA_BIT(lined, j);
+            }
+        }
+    }
+
+    pixDestroy(&pixg);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                Thresholding to 2 bpp by dithering                *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixDitherTo2bpp()
+ *
+ *      Input:  pixs (8 bpp)
+ *              cmapflag (1 to generate a colormap)
+ *      Return: pixd (dithered 2 bpp), or null on error
+ *
+ *  An analog of the Floyd-Steinberg error diffusion dithering
+ *  algorithm is used to "dibitize" an 8 bpp grayscale image
+ *  to 2 bpp, using equally spaced gray values of 0, 85, 170, and 255,
+ *  which are served by thresholds of 43, 128 and 213.
+ *  If cmapflag == 1, the colormap values are set to 0, 85, 170 and 255.
+ *  If a pixel has a value between 0 and 42, it is dibitized
+ *  to 0, and the excess (above 0) is added to the
+ *  three neighboring pixels, in the fractions 3/8 to (i, j+1),
+ *  3/8 to (i+1, j) and 1/4 to (i+1, j+1), truncating to 255 if
+ *  necessary.  If a pixel has a value between 43 and 127, it is
+ *  dibitized to 1, and the excess (above 85) is added to the three
+ *  neighboring pixels as before.  If the value is below 85, the
+ *  excess is subtracted.  With a value between 128
+ *  and 212, it is dibitized to 2, with the excess on either side
+ *  of 170 distributed as before.  Finally, with a value between
+ *  213 and 255, it is dibitized to 3, with the excess (below 255)
+ *  subtracted from the neighbors.  We always truncate to 0 or 255.
+ *  The details can be seen in the lookup table generation.
+ *
+ *  This function differs from straight dithering in that it allows
+ *  clipping of grayscale to 0 or 255 if the values are
+ *  sufficiently close, without distribution of the excess.
+ *  This uses default values (from pix.h) to specify the range of lower
+ *  and upper values (near 0 and 255, rsp) that are clipped to black
+ *  and white without propagating the excess.
+ *  Not propagating the excess has the effect of reducing the snake
+ *  patterns in parts of the image that are nearly black or white;
+ *  however, it also prevents any attempt to reproduce gray for those values.
+ *
+ *  The implementation uses 3 lookup tables for simplicity, and
+ *  a pair of line buffers to avoid modifying pixs.
+ */
+PIX *
+pixDitherTo2bpp(PIX     *pixs,
+                l_int32  cmapflag)
+{
+    PROCNAME("pixDitherTo2bpp");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("must be 8 bpp for dithering", procName, NULL);
+
+    return pixDitherTo2bppSpec(pixs, DEFAULT_CLIP_LOWER_2,
+                               DEFAULT_CLIP_UPPER_2, cmapflag);
+}
+
+
+/*!
+ *  pixDitherTo2bppSpec()
+ *
+ *      Input:  pixs (8 bpp)
+ *              lowerclip (lower clip distance to black; use 0 for default)
+ *              upperclip (upper clip distance to white; use 0 for default)
+ *              cmapflag (1 to generate a colormap)
+ *      Return: pixd (dithered 2 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) See comments above in pixDitherTo2bpp() for details.
+ *      (2) The input parameters lowerclip and upperclip specify the range
+ *          of lower and upper values (near 0 and 255, rsp) that are
+ *          clipped to black and white without propagating the excess.
+ *          For that reason, lowerclip and upperclip should be small numbers.
+ */
+PIX *
+pixDitherTo2bppSpec(PIX     *pixs,
+                    l_int32  lowerclip,
+                    l_int32  upperclip,
+                    l_int32  cmapflag)
+{
+l_int32    w, h, d, wplt, wpld;
+l_int32   *tabval, *tab38, *tab14;
+l_uint32  *datat, *datad;
+l_uint32  *bufs1, *bufs2;
+PIX       *pixt, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixDitherTo2bppSpec");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("must be 8 bpp for dithering", procName, NULL);
+    if (lowerclip < 0 || lowerclip > 255)
+        return (PIX *)ERROR_PTR("invalid value for lowerclip", procName, NULL);
+    if (upperclip < 0 || upperclip > 255)
+        return (PIX *)ERROR_PTR("invalid value for upperclip", procName, NULL);
+
+    if ((pixd = pixCreate(w, h, 2)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* If there is a colormap, remove it */
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+
+        /* Two line buffers, 1 for current line and 2 for next line */
+    if ((bufs1 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("bufs1 not made", procName, NULL);
+    if ((bufs2 = (l_uint32 *)LEPT_CALLOC(wplt, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("bufs2 not made", procName, NULL);
+
+        /* 3 lookup tables: 2-bit value, (3/8)excess, and (1/4)excess */
+    make8To2DitherTables(&tabval, &tab38, &tab14, lowerclip, upperclip);
+
+    ditherTo2bppLow(datad, w, h, wpld, datat, wplt, bufs1, bufs2,
+                    tabval, tab38, tab14);
+
+    if (cmapflag) {
+        cmap = pixcmapCreateLinear(2, 4);
+        pixSetColormap(pixd, cmap);
+    }
+
+    LEPT_FREE(bufs1);
+    LEPT_FREE(bufs2);
+    LEPT_FREE(tabval);
+    LEPT_FREE(tab38);
+    LEPT_FREE(tab14);
+    pixDestroy(&pixt);
+
+    return pixd;
+}
+
+
+/*--------------------------------------------------------------------*
+ *  Simple (pixelwise) thresholding to 2 bpp with optional colormap   *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixThresholdTo2bpp()
+ *
+ *      Input:  pixs (8 bpp)
+ *              nlevels (equally spaced; must be between 2 and 4)
+ *              cmapflag (1 to build colormap; 0 otherwise)
+ *      Return: pixd (2 bpp, optionally with colormap), or null on error
+ *
+ *  Notes:
+ *      (1) Valid values for nlevels is the set {2, 3, 4}.
+ *      (2) Any colormap on the input pixs is removed to 8 bpp grayscale.
+ *      (3) This function is typically invoked with cmapflag == 1.
+ *          In the situation where no colormap is desired, nlevels is
+ *          ignored and pixs is thresholded to 4 levels.
+ *      (4) The target output colors are equally spaced, with the
+ *          darkest at 0 and the lightest at 255.  The thresholds are
+ *          chosen halfway between adjacent output values.  A table
+ *          is built that specifies the mapping from src to dest.
+ *      (5) If cmapflag == 1, a colormap of size 'nlevels' is made,
+ *          and the pixel values in pixs are replaced by their
+ *          appropriate color indices.  The number of holdouts,
+ *          4 - nlevels, will be between 0 and 2.
+ *      (6) If you don't want the thresholding to be equally spaced,
+ *          either first transform the 8 bpp src using pixGammaTRC().
+ *          or, if cmapflag == 1, after calling this function you can use
+ *          pixcmapResetColor() to change any individual colors.
+ *      (7) If a colormap is generated, it will specify (to display
+ *          programs) exactly how each level is to be represented in RGB
+ *          space.  When representing text, 3 levels is far better than
+ *          2 because of the antialiasing of the single gray level,
+ *          and 4 levels (black, white and 2 gray levels) is getting
+ *          close to the perceptual quality of a (nearly continuous)
+ *          grayscale image.  With 2 bpp, you can set up a colormap
+ *          and allocate from 2 to 4 levels to represent antialiased text.
+ *          Any left over colormap entries can be used for coloring regions.
+ *          For the same number of levels, the file size of a 2 bpp image
+ *          is about 10% smaller than that of a 4 bpp result for the same
+ *          number of levels.  For both 2 bpp and 4 bpp, using 4 levels you
+ *          get compression far better than that of jpeg, because the
+ *          quantization to 4 levels will remove the jpeg ringing in the
+ *          background near character edges.
+ */
+PIX *
+pixThresholdTo2bpp(PIX     *pixs,
+                   l_int32  nlevels,
+                   l_int32  cmapflag)
+{
+l_int32   *qtab;
+l_int32    w, h, d, wplt, wpld;
+l_uint32  *datat, *datad;
+PIX       *pixt, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixThresholdTo2bpp");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (nlevels < 2 || nlevels > 4)
+        return (PIX *)ERROR_PTR("nlevels not in {2, 3, 4}", procName, NULL);
+
+        /* Make the appropriate table */
+    if (cmapflag)
+        qtab = makeGrayQuantIndexTable(nlevels);
+    else
+        qtab = makeGrayQuantTargetTable(4, 2);
+
+    if ((pixd = pixCreate(w, h, 2)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    if (cmapflag) {   /* hold out (4 - nlevels) cmap entries */
+        cmap = pixcmapCreateLinear(2, nlevels);
+        pixSetColormap(pixd, cmap);
+    }
+
+        /* If there is a colormap in the src, remove it */
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+
+    thresholdTo2bppLow(datad, h, wpld, datat, wplt, qtab);
+
+    if (qtab) LEPT_FREE(qtab);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *               Simple (pixelwise) thresholding to 4 bpp               *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixThresholdTo4bpp()
+ *
+ *      Input:  pixs (8 bpp, can have colormap)
+ *              nlevels (equally spaced; must be between 2 and 16)
+ *              cmapflag (1 to build colormap; 0 otherwise)
+ *      Return: pixd (4 bpp, optionally with colormap), or null on error
+ *
+ *  Notes:
+ *      (1) Valid values for nlevels is the set {2, ... 16}.
+ *      (2) Any colormap on the input pixs is removed to 8 bpp grayscale.
+ *      (3) This function is typically invoked with cmapflag == 1.
+ *          In the situation where no colormap is desired, nlevels is
+ *          ignored and pixs is thresholded to 16 levels.
+ *      (4) The target output colors are equally spaced, with the
+ *          darkest at 0 and the lightest at 255.  The thresholds are
+ *          chosen halfway between adjacent output values.  A table
+ *          is built that specifies the mapping from src to dest.
+ *      (5) If cmapflag == 1, a colormap of size 'nlevels' is made,
+ *          and the pixel values in pixs are replaced by their
+ *          appropriate color indices.  The number of holdouts,
+ *          16 - nlevels, will be between 0 and 14.
+ *      (6) If you don't want the thresholding to be equally spaced,
+ *          either first transform the 8 bpp src using pixGammaTRC().
+ *          or, if cmapflag == 1, after calling this function you can use
+ *          pixcmapResetColor() to change any individual colors.
+ *      (7) If a colormap is generated, it will specify, to display
+ *          programs, exactly how each level is to be represented in RGB
+ *          space.  When representing text, 3 levels is far better than
+ *          2 because of the antialiasing of the single gray level,
+ *          and 4 levels (black, white and 2 gray levels) is getting
+ *          close to the perceptual quality of a (nearly continuous)
+ *          grayscale image.  Therefore, with 4 bpp, you can set up a
+ *          colormap, allocate a relatively small fraction of the 16
+ *          possible values to represent antialiased text, and use the
+ *          other colormap entries for other things, such as coloring
+ *          text or background.  Two other reasons for using a small number
+ *          of gray values for antialiased text are (1) PNG compression
+ *          gets worse as the number of levels that are used is increased,
+ *          and (2) using a small number of levels will filter out most of
+ *          the jpeg ringing that is typically introduced near sharp edges
+ *          of text.  This filtering is partly responsible for the improved
+ *          compression.
+ */
+PIX *
+pixThresholdTo4bpp(PIX     *pixs,
+                   l_int32  nlevels,
+                   l_int32  cmapflag)
+{
+l_int32   *qtab;
+l_int32    w, h, d, wplt, wpld;
+l_uint32  *datat, *datad;
+PIX       *pixt, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixThresholdTo4bpp");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (nlevels < 2 || nlevels > 16)
+        return (PIX *)ERROR_PTR("nlevels not in [2,...,16]", procName, NULL);
+
+        /* Make the appropriate table */
+    if (cmapflag)
+        qtab = makeGrayQuantIndexTable(nlevels);
+    else
+        qtab = makeGrayQuantTargetTable(16, 4);
+
+    if ((pixd = pixCreate(w, h, 4)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    if (cmapflag) {   /* hold out (16 - nlevels) cmap entries */
+        cmap = pixcmapCreateLinear(4, nlevels);
+        pixSetColormap(pixd, cmap);
+    }
+
+        /* If there is a colormap in the src, remove it */
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+
+    thresholdTo4bppLow(datad, h, wpld, datat, wplt, qtab);
+
+    if (qtab) LEPT_FREE(qtab);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *    Simple (pixelwise) thresholding on 8 bpp with optional colormap   *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixThresholdOn8bpp()
+ *
+ *      Input:  pixs (8 bpp, can have colormap)
+ *              nlevels (equally spaced; must be between 2 and 256)
+ *              cmapflag (1 to build colormap; 0 otherwise)
+ *      Return: pixd (8 bpp, optionally with colormap), or null on error
+ *
+ *  Notes:
+ *      (1) Valid values for nlevels is the set {2,...,256}.
+ *      (2) Any colormap on the input pixs is removed to 8 bpp grayscale.
+ *      (3) If cmapflag == 1, a colormap of size 'nlevels' is made,
+ *          and the pixel values in pixs are replaced by their
+ *          appropriate color indices.  Otherwise, the pixel values
+ *          are the actual thresholded (i.e., quantized) grayscale values.
+ *      (4) If you don't want the thresholding to be equally spaced,
+ *          first transform the input 8 bpp src using pixGammaTRC().
+ */
+PIX *
+pixThresholdOn8bpp(PIX     *pixs,
+                   l_int32  nlevels,
+                   l_int32  cmapflag)
+{
+l_int32   *qtab;  /* quantization table */
+l_int32    i, j, w, h, wpld, val, newval;
+l_uint32  *datad, *lined;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixThresholdOn8bpp");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (nlevels < 2 || nlevels > 256)
+        return (PIX *)ERROR_PTR("nlevels not in [2,...,256]", procName, NULL);
+
+    if (cmapflag)
+        qtab = makeGrayQuantIndexTable(nlevels);
+    else
+        qtab = makeGrayQuantTargetTable(nlevels, 8);
+
+        /* Get a new pixd; if there is a colormap in the src, remove it */
+    if (pixGetColormap(pixs))
+        pixd = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else
+        pixd = pixCopy(NULL, pixs);
+
+    if (cmapflag) {   /* hold out (256 - nlevels) cmap entries */
+        cmap = pixcmapCreateLinear(8, nlevels);
+        pixSetColormap(pixd, cmap);
+    }
+
+    pixGetDimensions(pixd, &w, &h, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(lined, j);
+            newval = qtab[val];
+            SET_DATA_BYTE(lined, j, newval);
+        }
+    }
+
+    if (qtab) LEPT_FREE(qtab);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *    Arbitrary (pixelwise) thresholding from 8 bpp to 2, 4 or 8 bpp    *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixThresholdGrayArb()
+ *
+ *      Input:  pixs (8 bpp grayscale; can have colormap)
+ *              edgevals (string giving edge value of each bin)
+ *              outdepth (0, 2, 4 or 8 bpp; 0 is default for min depth)
+ *              use_average (1 if use the average pixel value in colormap)
+ *              setblack (1 if darkest color is set to black)
+ *              setwhite (1 if lightest color is set to white)
+ *      Return: pixd (2, 4 or 8 bpp quantized image with colormap),
+ *                    or null on error
+ *
+ *  Notes:
+ *      (1) This function allows exact specification of the quantization bins.
+ *          The string @edgevals is a space-separated set of values
+ *          specifying the dividing points between output quantization bins.
+ *          These threshold values are assigned to the bin with higher
+ *          values, so that each of them is the smallest value in their bin.
+ *      (2) The output image (pixd) depth is specified by @outdepth.  The
+ *          number of bins is the number of edgevals + 1.  The
+ *          relation between outdepth and the number of bins is:
+ *               outdepth = 2       nbins <= 4
+ *               outdepth = 4       nbins <= 16
+ *               outdepth = 8       nbins <= 256
+ *          With @outdepth == 0, the minimum required depth for the
+ *          given number of bins is used.
+ *          The output pixd has a colormap.
+ *      (3) The last 3 args determine the specific values that go into
+ *          the colormap.
+ *      (4) For @use_average:
+ *            - if TRUE, the average value of pixels falling in the bin is
+ *              chosen as the representative gray value.  Otherwise,
+ *            - if FALSE, the central value of each bin is chosen as
+ *              the representative value.
+ *          The colormap holds the representative value.
+ *      (5) For @setblack, if TRUE the darkest color is set to (0,0,0).
+ *      (6) For @setwhite, if TRUE the lightest color is set to (255,255,255).
+ *      (7) An alternative to using this function to quantize to
+ *          unequally-spaced bins is to first transform the 8 bpp pixs
+ *          using pixGammaTRC(), and follow this with pixThresholdTo4bpp().
+ */
+PIX *
+pixThresholdGrayArb(PIX         *pixs,
+                    const char  *edgevals,
+                    l_int32      outdepth,
+                    l_int32      use_average,
+                    l_int32      setblack,
+                    l_int32      setwhite)
+{
+l_int32   *qtab;
+l_int32    w, h, d, i, j, n, wplt, wpld, val, newval;
+l_uint32  *datat, *datad, *linet, *lined;
+NUMA      *na;
+PIX       *pixt, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixThresholdGrayArb");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (!edgevals)
+        return (PIX *)ERROR_PTR("edgevals not defined", procName, NULL);
+    if (outdepth != 0 && outdepth != 2 && outdepth != 4 && outdepth != 8)
+        return (PIX *)ERROR_PTR("invalid outdepth", procName, NULL);
+
+        /* Parse and sort (if required) the bin edge values */
+    na = parseStringForNumbers(edgevals, " \t\n,");
+    n = numaGetCount(na);
+    if (n > 255)
+        return (PIX *)ERROR_PTR("more than 256 levels", procName, NULL);
+    if (outdepth == 0) {
+        if (n <= 3)
+            outdepth = 2;
+        else if (n <= 15)
+            outdepth = 4;
+        else
+            outdepth = 8;
+    } else if (n + 1 > (1 << outdepth)) {
+        L_WARNING("outdepth too small; setting to 8 bpp\n", procName);
+        outdepth = 8;
+    }
+    numaSort(na, na, L_SORT_INCREASING);
+
+        /* Make the quantization LUT and the colormap */
+    makeGrayQuantTableArb(na, outdepth, &qtab, &cmap);
+    if (use_average) {  /* use the average value in each bin */
+        pixcmapDestroy(&cmap);
+        makeGrayQuantColormapArb(pixs, qtab, outdepth, &cmap);
+    }
+    pixcmapSetBlackAndWhite(cmap, setblack, setwhite);
+    numaDestroy(&na);
+
+    if ((pixd = pixCreate(w, h, outdepth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    pixSetColormap(pixd, cmap);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* If there is a colormap in the src, remove it */
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+
+    if (outdepth == 2) {
+        thresholdTo2bppLow(datad, h, wpld, datat, wplt, qtab);
+    } else if (outdepth == 4) {
+        thresholdTo4bppLow(datad, h, wpld, datat, wplt, qtab);
+    } else {
+        for (i = 0; i < h; i++) {
+            lined = datad + i * wpld;
+            linet = datat + i * wplt;
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_BYTE(linet, j);
+                newval = qtab[val];
+                SET_DATA_BYTE(lined, j, newval);
+            }
+        }
+    }
+
+    LEPT_FREE(qtab);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *     Quantization tables for linear thresholds of grayscale images    *
+ *----------------------------------------------------------------------*/
+/*!
+ *  makeGrayQuantIndexTable()
+ *
+ *      Input:  nlevels (number of output levels)
+ *      Return: table (maps input gray level to colormap index,
+ *                     or null on error)
+ *  Notes:
+ *      (1) 'nlevels' is some number between 2 and 256 (typically 8 or less).
+ *      (2) The table is typically used for quantizing 2, 4 and 8 bpp
+ *          grayscale src pix, and generating a colormapped dest pix.
+ */
+l_int32 *
+makeGrayQuantIndexTable(l_int32  nlevels)
+{
+l_int32   *tab;
+l_int32    i, j, thresh;
+
+    PROCNAME("makeGrayQuantIndexTable");
+
+    if ((tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return (l_int32 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+    for (i = 0; i < 256; i++) {
+        for (j = 0; j < nlevels; j++) {
+            thresh = 255 * (2 * j + 1) / (2 * nlevels - 2);
+            if (i <= thresh) {
+                tab[i] = j;
+/*                fprintf(stderr, "tab[%d] = %d\n", i, j); */
+                break;
+            }
+        }
+    }
+    return tab;
+}
+
+
+/*!
+ *  makeGrayQuantTargetTable()
+ *
+ *      Input:  nlevels (number of output levels)
+ *              depth (of dest pix, in bpp; 2, 4 or 8 bpp)
+ *      Return: table (maps input gray level to thresholded gray level,
+ *                     or null on error)
+ *
+ *  Notes:
+ *      (1) nlevels is some number between 2 and 2^(depth)
+ *      (2) The table is used in two similar ways:
+ *           - for 8 bpp, it quantizes to a given number of target levels
+ *           - for 2 and 4 bpp, it thresholds to appropriate target values
+ *             that will use the full dynamic range of the dest pix.
+ *      (3) For depth = 8, the number of thresholds chosen is
+ *          ('nlevels' - 1), and the 'nlevels' values stored in the
+ *          table are at the two at the extreme ends, (0, 255), plus
+ *          plus ('nlevels' - 2) values chosen at equal intervals between.
+ *          For example, for depth = 8 and 'nlevels' = 3, the two
+ *          threshold values are 3f and bf, and the three target pixel
+ *          values are 0, 7f and ff.
+ *      (4) For depth < 8, we ignore nlevels, and always use the maximum
+ *          number of levels, which is 2^(depth).
+ *          If you want nlevels < the maximum number, you should always
+ *          use a colormap.
+ */
+l_int32 *
+makeGrayQuantTargetTable(l_int32  nlevels,
+                         l_int32  depth)
+{
+l_int32   *tab;
+l_int32    i, j, thresh, maxval, quantval;
+
+    PROCNAME("makeGrayQuantTargetTable");
+
+    if ((tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return (l_int32 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+    maxval = (1 << depth) - 1;
+    if (depth < 8)
+        nlevels = 1 << depth;
+    for (i = 0; i < 256; i++) {
+        for (j = 0; j < nlevels; j++) {
+            thresh = 255 * (2 * j + 1) / (2 * nlevels - 2);
+            if (i <= thresh) {
+                quantval = maxval * j / (nlevels - 1);
+                tab[i] = quantval;
+/*                fprintf(stderr, "tab[%d] = %d\n", i, tab[i]); */
+                break;
+            }
+        }
+    }
+    return tab;
+}
+
+
+/*----------------------------------------------------------------------*
+ *   Quantization table for arbitrary thresholding of grayscale images  *
+ *----------------------------------------------------------------------*/
+/*!
+ *  makeGrayQuantTableArb()
+ *
+ *      Input:  na (numa of bin boundaries)
+ *              outdepth (of colormap: 1, 2, 4 or 8)
+ *              &tab (<return> table mapping input gray level to cmap index)
+ *              &cmap (<return> colormap)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The number of bins is the count of @na + 1.
+ *      (2) The bin boundaries in na must be sorted in increasing order.
+ *      (3) The table is an inverse colormap: it maps input gray level
+ *          to colormap index (the bin number).
+ *      (4) The colormap generated here has quantized values at the
+ *          center of each bin.  If you want to use the average gray
+ *          value of pixels within the bin, discard the colormap and
+ *          compute it using makeGrayQuantColormapArb().
+ *      (5) Returns an error if there are not enough levels in the
+ *          output colormap for the number of bins.  The number
+ *          of bins must not exceed 2^outdepth.
+ */
+l_int32
+makeGrayQuantTableArb(NUMA      *na,
+                      l_int32    outdepth,
+                      l_int32  **ptab,
+                      PIXCMAP  **pcmap)
+{
+l_int32   i, j, n, jstart, ave, val;
+l_int32  *tab;
+PIXCMAP  *cmap;
+
+    PROCNAME("makeGrayQuantTableArb");
+
+    if (!ptab)
+        return ERROR_INT("&tab not defined", procName, 1);
+    *ptab = NULL;
+    if (!pcmap)
+        return ERROR_INT("&cmap not defined", procName, 1);
+    *pcmap = NULL;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    n = numaGetCount(na);
+    if (n + 1 > (1 << outdepth))
+        return ERROR_INT("more bins than cmap levels", procName, 1);
+
+    if ((tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return ERROR_INT("calloc fail for tab", procName, 1);
+    if ((cmap = pixcmapCreate(outdepth)) == NULL)
+        return ERROR_INT("cmap not made", procName, 1);
+    *ptab = tab;
+    *pcmap = cmap;
+
+        /* First n bins */
+    jstart = 0;
+    for (i = 0; i < n; i++) {
+        numaGetIValue(na, i, &val);
+        ave = (jstart + val) / 2;
+        pixcmapAddColor(cmap, ave, ave, ave);
+        for (j = jstart; j < val; j++)
+            tab[j] = i;
+        jstart = val;
+    }
+
+        /* Last bin */
+    ave = (jstart + 255) / 2;
+    pixcmapAddColor(cmap, ave, ave, ave);
+    for (j = jstart; j < 256; j++)
+        tab[j] = n;
+
+    return 0;
+}
+
+
+/*!
+ *  makeGrayQuantColormapArb()
+ *
+ *      Input:  pixs (8 bpp)
+ *              tab (table mapping input gray level to cmap index)
+ *              outdepth (of colormap: 1, 2, 4 or 8)
+ *              &cmap (<return> colormap)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The table is a 256-entry inverse colormap: it maps input gray
+ *          level to colormap index (the bin number).  It is computed
+ *          using makeGrayQuantTableArb().
+ *      (2) The colormap generated here has quantized values at the
+ *          average gray value of the pixels that are in each bin.
+ *      (3) Returns an error if there are not enough levels in the
+ *          output colormap for the number of bins.  The number
+ *          of bins must not exceed 2^outdepth.
+ */
+l_int32
+makeGrayQuantColormapArb(PIX       *pixs,
+                         l_int32   *tab,
+                         l_int32    outdepth,
+                         PIXCMAP  **pcmap)
+{
+l_int32    i, j, index, w, h, d, nbins, wpl, factor, val;
+l_int32   *bincount, *binave, *binstart;
+l_uint32  *line, *data;
+
+    PROCNAME("makeGrayQuantColormapArb");
+
+    if (!pcmap)
+        return ERROR_INT("&cmap not defined", procName, 1);
+    *pcmap = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return ERROR_INT("pixs not 8 bpp", procName, 1);
+    if (!tab)
+        return ERROR_INT("tab not defined", procName, 1);
+    nbins = tab[255] + 1;
+    if (nbins > (1 << outdepth))
+        return ERROR_INT("more bins than cmap levels", procName, 1);
+
+        /* Find the count and weighted count for each bin */
+    if ((bincount = (l_int32 *)LEPT_CALLOC(nbins, sizeof(l_int32))) == NULL)
+        return ERROR_INT("calloc fail for bincount", procName, 1);
+    if ((binave = (l_int32 *)LEPT_CALLOC(nbins, sizeof(l_int32))) == NULL)
+        return ERROR_INT("calloc fail for binave", procName, 1);
+    factor = (l_int32)(sqrt((l_float64)(w * h) / 30000.) + 0.5);
+    factor = L_MAX(1, factor);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    for (i = 0; i < h; i += factor) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j += factor) {
+            val = GET_DATA_BYTE(line, j);
+            bincount[tab[val]]++;
+            binave[tab[val]] += val;
+        }
+    }
+
+        /* Find the smallest gray values in each bin */
+    if ((binstart = (l_int32 *)LEPT_CALLOC(nbins, sizeof(l_int32))) == NULL)
+        return ERROR_INT("calloc fail for binstart", procName, 1);
+    for (i = 1, index = 1; i < 256; i++) {
+        if (tab[i] < index) continue;
+        if (tab[i] == index)
+            binstart[index++] = i;
+    }
+
+        /* Get the averages.  If there are no samples in a bin, use
+         * the center value of the bin. */
+    *pcmap = pixcmapCreate(outdepth);
+    for (i = 0; i < nbins; i++) {
+        if (bincount[i]) {
+            val = binave[i] / bincount[i];
+        } else {  /* no samples in the bin */
+            if (i < nbins - 1)
+                val = (binstart[i] + binstart[i + 1]) / 2;
+            else  /* last bin */
+                val = (binstart[i] + 255) / 2;
+        }
+        pixcmapAddColor(*pcmap, val, val, val);
+    }
+
+    LEPT_FREE(bincount);
+    LEPT_FREE(binave);
+    LEPT_FREE(binstart);
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                 Thresholding from 32 bpp rgb to 1 bpp              *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixGenerateMaskByBand32()
+ *
+ *      Input:  pixs (32 bpp)
+ *              refval (reference rgb value)
+ *              delm (max amount below the ref value for any component)
+ *              delp (max amount above the ref value for any component)
+ *              fractm (fractional amount below ref value for all components)
+ *              fractp (fractional amount above ref value for all components)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) Generates a 1 bpp mask pixd, the same size as pixs, where
+ *          the fg pixels in the mask within a band of rgb values
+ *          surrounding @refval.  The band can be chosen in two ways
+ *          for each component:
+ *          (a) Use (@delm, @delp) to specify how many levels down and up
+ *          (b) Use (@fractm, @fractp) to specify the fractional
+ *              distance toward 0 and 255, respectively.
+ *          Note that @delm and @delp must be in [0 ... 255], whereas
+ *          @fractm and @fractp must be in [0.0 - 1.0].
+ *      (2) Either (@delm, @delp) or (@fractm, @fractp) can be used.
+ *          Set each value in the other pair to 0.
+ */
+PIX *
+pixGenerateMaskByBand32(PIX       *pixs,
+                        l_uint32   refval,
+                        l_int32    delm,
+                        l_int32    delp,
+                        l_float32  fractm,
+                        l_float32  fractp)
+{
+l_int32    i, j, w, h, d, wpls, wpld;
+l_int32    rref, gref, bref, rval, gval, bval;
+l_int32    rmin, gmin, bmin, rmax, gmax, bmax;
+l_uint32   pixel;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixGenerateMaskByBand32");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32)
+        return (PIX *)ERROR_PTR("not 32 bpp", procName, NULL);
+    if (delm < 0 || delp < 0)
+        return (PIX *)ERROR_PTR("delm and delp must be >= 0", procName, NULL);
+    if (fractm < 0.0 || fractm > 1.0 || fractp < 0.0 || fractp > 1.0)
+        return (PIX *)ERROR_PTR("fractm and/or fractp invalid", procName, NULL);
+
+    extractRGBValues(refval, &rref, &gref, &bref);
+    if (fractm == 0.0 && fractp == 0.0) {
+        rmin = rref - delm;
+        gmin = gref - delm;
+        bmin = bref - delm;
+        rmax = rref + delm;
+        gmax = gref + delm;
+        bmax = bref + delm;
+    } else if (delm == 0 && delp == 0) {
+        rmin = (l_int32)((1.0 - fractm) * rref);
+        gmin = (l_int32)((1.0 - fractm) * gref);
+        bmin = (l_int32)((1.0 - fractm) * bref);
+        rmax = rref + (l_int32)(fractp * (255 - rref));
+        gmax = gref + (l_int32)(fractp * (255 - gref));
+        bmax = bref + (l_int32)(fractp * (255 - bref));
+    } else {
+        L_ERROR("bad input: either (delm, delp) or (fractm, fractp) "
+                "must be 0\n", procName);
+        return NULL;
+    }
+
+    pixd = pixCreate(w, h, 1);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            pixel = lines[j];
+            rval = (pixel >> L_RED_SHIFT) & 0xff;
+            if (rval < rmin || rval > rmax)
+                continue;
+            gval = (pixel >> L_GREEN_SHIFT) & 0xff;
+            if (gval < gmin || gval > gmax)
+                continue;
+            bval = (pixel >> L_BLUE_SHIFT) & 0xff;
+            if (bval < bmin || bval > bmax)
+                continue;
+            SET_DATA_BIT(lined, j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixGenerateMaskByDiscr32()
+ *
+ *      Input:  pixs (32 bpp)
+ *              refval1 (reference rgb value)
+ *              refval2 (reference rgb value)
+ *              distflag (L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) Generates a 1 bpp mask pixd, the same size as pixs, where
+ *          the fg pixels in the mask are those where the pixel in pixs
+ *          is "closer" to refval1 than to refval2.
+ *      (2) "Closer" can be defined in several ways, such as:
+ *            - manhattan distance (L1)
+ *            - euclidean distance (L2)
+ *            - majority vote of the individual components
+ *          Here, we have a choice of L1 or L2.
+ */
+PIX *
+pixGenerateMaskByDiscr32(PIX      *pixs,
+                         l_uint32  refval1,
+                         l_uint32  refval2,
+                         l_int32   distflag)
+{
+l_int32    i, j, w, h, d, wpls, wpld;
+l_int32    rref1, gref1, bref1, rref2, gref2, bref2, rval, gval, bval;
+l_uint32   pixel, dist1, dist2;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixGenerateMaskByDiscr32");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32)
+        return (PIX *)ERROR_PTR("not 32 bpp", procName, NULL);
+    if (distflag != L_MANHATTAN_DISTANCE && distflag != L_EUCLIDEAN_DISTANCE)
+        return (PIX *)ERROR_PTR("invalid distflag", procName, NULL);
+
+    extractRGBValues(refval1, &rref1, &gref1, &bref1);
+    extractRGBValues(refval2, &rref2, &gref2, &bref2);
+    pixd = pixCreate(w, h, 1);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            pixel = lines[j];
+            extractRGBValues(pixel, &rval, &gval, &bval);
+            if (distflag == L_MANHATTAN_DISTANCE) {
+                dist1 = L_ABS(rref1 - rval);
+                dist2 = L_ABS(rref2 - rval);
+                dist1 += L_ABS(gref1 - gval);
+                dist2 += L_ABS(gref2 - gval);
+                dist1 += L_ABS(bref1 - bval);
+                dist2 += L_ABS(bref2 - bval);
+            } else {
+                dist1 = (rref1 - rval) * (rref1 - rval);
+                dist2 = (rref2 - rval) * (rref2 - rval);
+                dist1 += (gref1 - gval) * (gref1 - gval);
+                dist2 += (gref2 - gval) * (gref2 - gval);
+                dist1 += (bref1 - bval) * (bref1 - bval);
+                dist2 += (bref2 - bval) * (bref2 - bval);
+            }
+            if (dist1 < dist2)
+                SET_DATA_BIT(lined, j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                Histogram-based grayscale quantization                *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixGrayQuantFromHisto()
+ *
+ *      Input:  pixd (<optional> quantized pix with cmap; can be null)
+ *              pixs (8 bpp gray input pix; not cmapped)
+ *              pixm (<optional> mask over pixels in pixs to quantize)
+ *              minfract (minimum fraction of pixels in a set of adjacent
+ *                        histo bins that causes the set to be automatically
+ *                        set aside as a color in the colormap; must be
+ *                        at least 0.01)
+ *              maxsize (maximum number of adjacent bins allowed to represent
+ *                       a color, regardless of the population of pixels
+ *                       in the bins; must be at least 2)
+ *      Return: pixd (8 bpp, cmapped), or null on error
+ *
+ *  Notes:
+ *      (1) This is useful for quantizing images with relatively few
+ *          colors, but which may have both color and gray pixels.
+ *          If there are color pixels, it is assumed that an input
+ *          rgb image has been color quantized first so that:
+ *            - pixd has a colormap describing the color pixels
+ *            - pixm is a mask over the non-color pixels in pixd
+ *            - the colormap in pixd, and the color pixels in pixd,
+ *              have been repacked to go from 0 to n-1 (n colors)
+ *          If there are no color pixels, pixd and pixm are both null,
+ *          and all pixels in pixs are quantized to gray.
+ *      (2) A 256-entry histogram is built of the gray values in pixs.
+ *          If pixm exists, the pixels contributing to the histogram are
+ *          restricted to the fg of pixm.  A colormap and LUT are generated
+ *          from this histogram.  We break up the array into a set
+ *          of intervals, each one constituting a color in the colormap:
+ *          An interval is identified by summing histogram bins until
+ *          either the sum equals or exceeds the @minfract of the total
+ *          number of pixels, or the span itself equals or exceeds @maxsize.
+ *          The color of each bin is always an average of the pixels
+ *          that constitute it.
+ *      (3) Note that we do not specify the number of gray colors in
+ *          the colormap.  Instead, we specify two parameters that
+ *          describe the accuracy of the color assignments; this and
+ *          the actual image determine the number of resulting colors.
+ *      (4) If a mask exists and it is not the same size as pixs, make
+ *          a new mask the same size as pixs, with the original mask
+ *          aligned at the UL corners.  Set all additional pixels
+ *          in the (larger) new mask set to 1, causing those pixels
+ *          in pixd to be set as gray.
+ *      (5) We estimate the total number of colors (color plus gray);
+ *          if it exceeds 255, return null.
+ */
+PIX *
+pixGrayQuantFromHisto(PIX       *pixd,
+                      PIX       *pixs,
+                      PIX       *pixm,
+                      l_float32  minfract,
+                      l_int32    maxsize)
+{
+l_int32    w, h, wd, hd, wm, hm, wpls, wplm, wpld;
+l_int32    nc, nestim, i, j, vals, vald;
+l_int32   *lut;
+l_uint32  *datas, *datam, *datad, *lines, *linem, *lined;
+NUMA      *na;
+PIX       *pixmr;  /* resized mask */
+PIXCMAP   *cmap;
+
+    PROCNAME("pixGrayQuantFromHisto");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+    if (minfract < 0.01) {
+        L_WARNING("minfract < 0.01; setting to 0.05\n", procName);
+        minfract = 0.05;
+    }
+    if (maxsize < 2) {
+        L_WARNING("maxsize < 2; setting to 10\n", procName);
+        maxsize = 10;
+    }
+    if ((pixd && !pixm) || (!pixd && pixm))
+        return (PIX *)ERROR_PTR("(pixd,pixm) not defined together",
+                                procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (pixd) {
+        if (pixGetDepth(pixm) != 1)
+            return (PIX *)ERROR_PTR("pixm not 1 bpp", procName, NULL);
+        if ((cmap = pixGetColormap(pixd)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not cmapped", procName, NULL);
+        pixGetDimensions(pixd, &wd, &hd, NULL);
+        if (w != wd || h != hd)
+            return (PIX *)ERROR_PTR("pixs, pixd sizes differ", procName, NULL);
+        nc = pixcmapGetCount(cmap);
+        nestim = nc + (l_int32)(1.5 * 255 / maxsize);
+        fprintf(stderr, "nestim = %d\n", nestim);
+        if (nestim > 255) {
+            L_ERROR("Estimate %d colors!\n", procName, nestim);
+            return (PIX *)ERROR_PTR("probably too many colors", procName, NULL);
+        }
+        pixGetDimensions(pixm, &wm, &hm, NULL);
+        if (w != wm || h != hm) {  /* resize the mask */
+            L_WARNING("mask and dest sizes not equal\n", procName);
+            pixmr = pixCreateNoInit(w, h, 1);
+            pixRasterop(pixmr, 0, 0, wm, hm, PIX_SRC, pixm, 0, 0);
+            pixRasterop(pixmr, wm, 0, w - wm, h, PIX_SET, NULL, 0, 0);
+            pixRasterop(pixmr, 0, hm, wm, h - hm, PIX_SET, NULL, 0, 0);
+        } else {
+            pixmr = pixClone(pixm);
+        }
+    } else {
+        pixd = pixCreateTemplate(pixs);
+        cmap = pixcmapCreate(8);
+        pixSetColormap(pixd, cmap);
+    }
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+        /* Use original mask, if it exists, to select gray pixels */
+    na = pixGetGrayHistogramMasked(pixs, pixm, 0, 0, 1);
+
+        /* Fill out the cmap with gray colors, and generate the lut
+         * for pixel assignment.  Issue a warning on failure.  */
+    if (numaFillCmapFromHisto(na, cmap, minfract, maxsize, &lut))
+        L_ERROR("ran out of colors in cmap!\n", procName);
+    numaDestroy(&na);
+
+        /* Assign the gray pixels to their cmap indices */
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    if (!pixm) {
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                vals = GET_DATA_BYTE(lines, j);
+                vald = lut[vals];
+                SET_DATA_BYTE(lined, j, vald);
+            }
+        }
+        LEPT_FREE(lut);
+        return pixd;
+    }
+
+    datam = pixGetData(pixmr);
+    wplm = pixGetWpl(pixmr);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        linem = datam + i * wplm;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            if (!GET_DATA_BIT(linem, j))
+                continue;
+            vals = GET_DATA_BYTE(lines, j);
+            vald = lut[vals];
+            SET_DATA_BYTE(lined, j, vald);
+        }
+    }
+    pixDestroy(&pixmr);
+    LEPT_FREE(lut);
+    return pixd;
+}
+
+
+/*!
+ *  numaFillCmapFromHisto()
+ *
+ *      Input:  na (histogram of gray values)
+ *              cmap (8 bpp cmap, possibly initialized with color value)
+ *              minfract (minimum fraction of pixels in a set of adjacent
+ *                        histo bins that causes the set to be automatically
+ *                        set aside as a color in the colormap; must be
+ *                        at least 0.01)
+ *              maxsize (maximum number of adjacent bins allowed to represent
+ *                       a color, regardless of the population of pixels
+ *                       in the bins; must be at least 2)
+ *             &lut (<return> lookup table from gray value to colormap index)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This static function must be called from pixGrayQuantFromHisto()
+ */
+static l_int32
+numaFillCmapFromHisto(NUMA      *na,
+                      PIXCMAP   *cmap,
+                      l_float32  minfract,
+                      l_int32    maxsize,
+                      l_int32  **plut)
+{
+l_int32    mincount, index, sum, wtsum, span, istart, i, val, ret;
+l_int32   *iahisto, *lut;
+l_float32  total;
+
+    PROCNAME("numaFillCmapFromHisto");
+
+    if (!plut)
+        return ERROR_INT("&lut not defined", procName, 1);
+    *plut = NULL;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+
+    numaGetSum(na, &total);
+    mincount = (l_int32)(minfract * total);
+    iahisto = numaGetIArray(na);
+    if ((lut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return ERROR_INT("lut not made", procName, 1);
+    *plut = lut;
+    index = pixcmapGetCount(cmap);  /* start with number of colors
+                                     * already reserved */
+
+        /* March through, associating colors with sets of adjacent
+         * gray levels.  During the process, the LUT that gives
+         * the colormap index for each gray level is computed.
+         * To complete a color, either the total count must equal
+         * or exceed @mincount, or the current span of colors must
+         * equal or exceed @maxsize.  An empty span is not converted
+         * into a color; it is simply ignored.  When a span is completed for a
+         * color, the weighted color in the span is added to the colormap. */
+    sum = 0;
+    wtsum = 0;
+    istart = 0;
+    ret = 0;
+    for (i = 0; i < 256; i++) {
+        lut[i] = index;
+        sum += iahisto[i];
+        wtsum += i * iahisto[i];
+        span = i - istart + 1;
+        if (sum < mincount && span < maxsize)
+            continue;
+
+        if (sum == 0) {  /* empty span; don't save */
+            istart = i + 1;
+            continue;
+        }
+
+            /* Found new color; sum > 0 */
+        val = (l_int32)((l_float32)wtsum / (l_float32)sum + 0.5);
+        ret = pixcmapAddColor(cmap, val, val, val);
+        istart = i + 1;
+        sum = 0;
+        wtsum = 0;
+        index++;
+    }
+    if (istart < 256 && sum > 0) {  /* last one */
+        span = 256 - istart;
+        val = (l_int32)((l_float32)wtsum / (l_float32)sum + 0.5);
+        ret = pixcmapAddColor(cmap, val, val, val);
+    }
+
+    LEPT_FREE(iahisto);
+    return ret;
+}
+
+
+/*----------------------------------------------------------------------*
+ *        Color quantize grayscale image using existing colormap        *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixGrayQuantFromCmap()
+ *
+ *      Input:  pixs (8 bpp grayscale without cmap)
+ *              cmap (to quantize to; of dest pix)
+ *              mindepth (minimum depth of pixd: can be 2, 4 or 8 bpp)
+ *      Return: pixd (2, 4 or 8 bpp, colormapped), or null on error
+ *
+ *  Notes:
+ *      (1) In use, pixs is an 8 bpp grayscale image without a colormap.
+ *          If there is an existing colormap, a warning is issued and
+ *          a copy of the input pixs is returned.
+ */
+PIX *
+pixGrayQuantFromCmap(PIX      *pixs,
+                     PIXCMAP  *cmap,
+                     l_int32   mindepth)
+{
+l_int32    i, j, index, w, h, d, depth, wpls, wpld;
+l_int32    hascolor, vals, vald;
+l_int32   *tab;
+l_uint32  *datas, *datad, *lines, *lined;
+PIXCMAP   *cmapd;
+PIX       *pixd;
+
+    PROCNAME("pixGrayQuantFromCmap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs) != NULL) {
+        L_WARNING("pixs already has a colormap; returning a copy\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (!cmap)
+        return (PIX *)ERROR_PTR("cmap not defined", procName, NULL);
+    if (mindepth != 2 && mindepth != 4 && mindepth != 8)
+        return (PIX *)ERROR_PTR("invalid mindepth", procName, NULL);
+
+        /* Make sure the colormap is gray */
+    pixcmapHasColor(cmap, &hascolor);
+    if (hascolor) {
+        L_WARNING("Converting colormap colors to gray\n", procName);
+        cmapd = pixcmapColorToGray(cmap, 0.3, 0.5, 0.2);
+    } else {
+        cmapd = pixcmapCopy(cmap);
+    }
+
+        /* Make LUT into colormap */
+    if ((tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("tab not made", procName, NULL);
+    for (i = 0; i < 256; i++) {
+        pixcmapGetNearestGrayIndex(cmapd, i, &index);
+        tab[i] = index;
+    }
+
+    pixcmapGetMinDepth(cmap, &depth);
+    depth = L_MAX(depth, mindepth);
+    pixd = pixCreate(w, h, depth);
+    pixSetColormap(pixd, cmapd);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            vals = GET_DATA_BYTE(lines, j);
+            vald = tab[vals];
+            if (depth == 2)
+                SET_DATA_DIBIT(lined, j, vald);
+            else if (depth == 4)
+                SET_DATA_QBIT(lined, j, vald);
+            else  /* depth == 8 */
+                SET_DATA_BYTE(lined, j, vald);
+        }
+    }
+
+    LEPT_FREE(tab);
+    return pixd;
+}
diff --git a/src/grayquantlow.c b/src/grayquantlow.c
new file mode 100644 (file)
index 0000000..fe31b24
--- /dev/null
@@ -0,0 +1,900 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  grayquantlow.c
+ *
+ *      Thresholding from 8 bpp to 1 bpp
+ *
+ *          Floyd-Steinberg dithering to binary
+ *              void       ditherToBinaryLow()
+ *              void       ditherToBinaryLineLow()
+ *
+ *          Simple (pixelwise) binarization
+ *              void       thresholdToBinaryLow()
+ *              void       thresholdToBinaryLineLow()
+ *
+ *          A slower version of Floyd-Steinberg dithering that uses LUTs
+ *              void       ditherToBinaryLUTLow()
+ *              void       ditherToBinaryLineLUTLow()
+ *              l_int32    make8To1DitherTables()
+ *
+ *      Thresholding from 8 bpp to 2 bpp
+ *
+ *          Floyd-Steinberg-like dithering to 2 bpp
+ *              void       ditherTo2bppLow()
+ *              void       ditherTo2bppLineLow()
+ *              l_int32    make8To2DitherTables()
+ *
+ *          Simple thresholding to 2 bpp
+ *              void       thresholdTo2bppLow()
+ *
+ *      Thresholding from 8 bpp to 4 bpp
+ *
+ *          Simple thresholding to 4 bpp
+ *              void       thresholdTo4bppLow()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+#ifndef  NO_CONSOLE_IO
+#define DEBUG_UNROLLING 0
+#endif   /* ~NO_CONSOLE_IO */
+
+
+/*------------------------------------------------------------------*
+ *             Binarization by Floyd-Steinberg Dithering            *
+ *------------------------------------------------------------------*/
+/*
+ *  ditherToBinaryLow()
+ *
+ *  See comments in pixDitherToBinary() in binarize.c
+ */
+void
+ditherToBinaryLow(l_uint32  *datad,
+                  l_int32    w,
+                  l_int32    h,
+                  l_int32    wpld,
+                  l_uint32  *datas,
+                  l_int32    wpls,
+                  l_uint32  *bufs1,
+                  l_uint32  *bufs2,
+                  l_int32    lowerclip,
+                  l_int32    upperclip)
+{
+l_int32      i;
+l_uint32    *lined;
+
+        /* do all lines except last line */
+    memcpy(bufs2, datas, 4 * wpls);  /* prime the buffer */
+    for (i = 0; i < h - 1; i++) {
+        memcpy(bufs1, bufs2, 4 * wpls);
+        memcpy(bufs2, datas + (i + 1) * wpls, 4 * wpls);
+        lined = datad + i * wpld;
+        ditherToBinaryLineLow(lined, w, bufs1, bufs2, lowerclip, upperclip, 0);
+    }
+
+        /* do last line */
+    memcpy(bufs1, bufs2, 4 * wpls);
+    lined = datad + (h - 1) * wpld;
+    ditherToBinaryLineLow(lined, w, bufs1, bufs2, lowerclip, upperclip, 1);
+    return;
+}
+
+
+/*
+ *  ditherToBinaryLineLow()
+ *
+ *      Input:  lined  (ptr to beginning of dest line
+ *              w   (width of image in pixels)
+ *              bufs1 (buffer of current source line)
+ *              bufs2 (buffer of next source line)
+ *              lowerclip (lower clip distance to black)
+ *              upperclip (upper clip distance to white)
+ *              lastlineflag  (0 if not last dest line, 1 if last dest line)
+ *      Return: void
+ *
+ *  Dispatches FS error diffusion dithering for
+ *  a single line of the image.  If lastlineflag == 0,
+ *  both source buffers are used; otherwise, only bufs1
+ *  is used.  We use source buffers because the error
+ *  is propagated into them, and we don't want to change
+ *  the input src image.
+ *
+ *  We break dithering out line by line to make it
+ *  easier to combine functions like interpolative
+ *  scaling and error diffusion dithering, as such a
+ *  combination of operations obviates the need to
+ *  generate a 2x grayscale image as an intermediary.
+ */
+void
+ditherToBinaryLineLow(l_uint32  *lined,
+                      l_int32    w,
+                      l_uint32  *bufs1,
+                      l_uint32  *bufs2,
+                      l_int32    lowerclip,
+                      l_int32    upperclip,
+                      l_int32    lastlineflag)
+{
+l_int32   j;
+l_int32   oval, eval;
+l_uint8   fval1, fval2, rval, bval, dval;
+
+    if (lastlineflag == 0) {
+        for (j = 0; j < w - 1; j++) {
+            oval = GET_DATA_BYTE(bufs1, j);
+            if (oval > 127) {   /* binarize to OFF */
+                if ((eval = 255 - oval) > upperclip) {
+                        /* subtract from neighbors */
+                    fval1 = (3 * eval) / 8;
+                    fval2 = eval / 4;
+                    rval = GET_DATA_BYTE(bufs1, j + 1);
+                    rval = L_MAX(0, rval - fval1);
+                    SET_DATA_BYTE(bufs1, j + 1, rval);
+                    bval = GET_DATA_BYTE(bufs2, j);
+                    bval = L_MAX(0, bval - fval1);
+                    SET_DATA_BYTE(bufs2, j, bval);
+                    dval = GET_DATA_BYTE(bufs2, j + 1);
+                    dval = L_MAX(0, dval - fval2);
+                    SET_DATA_BYTE(bufs2, j + 1, dval);
+                }
+            } else {   /* oval <= 127; binarize to ON  */
+                SET_DATA_BIT(lined, j);   /* ON pixel */
+                if (oval > lowerclip) {
+                        /* add to neighbors */
+                    fval1 = (3 * oval) / 8;
+                    fval2 = oval / 4;
+                    rval = GET_DATA_BYTE(bufs1, j + 1);
+                    rval = L_MIN(255, rval + fval1);
+                    SET_DATA_BYTE(bufs1, j + 1, rval);
+                    bval = GET_DATA_BYTE(bufs2, j);
+                    bval = L_MIN(255, bval + fval1);
+                    SET_DATA_BYTE(bufs2, j, bval);
+                    dval = GET_DATA_BYTE(bufs2, j + 1);
+                    dval = L_MIN(255, dval + fval2);
+                    SET_DATA_BYTE(bufs2, j + 1, dval);
+                }
+            }
+        }
+
+            /* do last column: j = w - 1 */
+        oval = GET_DATA_BYTE(bufs1, j);
+        if (oval > 127) {  /* binarize to OFF */
+            if ((eval = 255 - oval) > upperclip) {
+                    /* subtract from neighbors */
+                fval1 = (3 * eval) / 8;
+                bval = GET_DATA_BYTE(bufs2, j);
+                bval = L_MAX(0, bval - fval1);
+                SET_DATA_BYTE(bufs2, j, bval);
+            }
+        } else {  /*oval <= 127; binarize to ON */
+            SET_DATA_BIT(lined, j);   /* ON pixel */
+            if (oval > lowerclip) {
+                    /* add to neighbors */
+                fval1 = (3 * oval) / 8;
+                bval = GET_DATA_BYTE(bufs2, j);
+                bval = L_MIN(255, bval + fval1);
+                SET_DATA_BYTE(bufs2, j, bval);
+            }
+        }
+    } else {   /* lastlineflag == 1 */
+        for (j = 0; j < w - 1; j++) {
+            oval = GET_DATA_BYTE(bufs1, j);
+            if (oval > 127) {   /* binarize to OFF */
+                if ((eval = 255 - oval) > upperclip) {
+                        /* subtract from neighbors */
+                    fval1 = (3 * eval) / 8;
+                    rval = GET_DATA_BYTE(bufs1, j + 1);
+                    rval = L_MAX(0, rval - fval1);
+                    SET_DATA_BYTE(bufs1, j + 1, rval);
+                }
+            } else {   /* oval <= 127; binarize to ON  */
+                SET_DATA_BIT(lined, j);   /* ON pixel */
+                if (oval > lowerclip) {
+                        /* add to neighbors */
+                    fval1 = (3 * oval) / 8;
+                    rval = GET_DATA_BYTE(bufs1, j + 1);
+                    rval = L_MIN(255, rval + fval1);
+                    SET_DATA_BYTE(bufs1, j + 1, rval);
+                }
+            }
+        }
+
+            /* do last pixel: (i, j) = (h - 1, w - 1) */
+        oval = GET_DATA_BYTE(bufs1, j);
+        if (oval < 128)
+            SET_DATA_BIT(lined, j);   /* ON pixel */
+    }
+
+    return;
+}
+
+
+
+/*------------------------------------------------------------------*
+ *             Simple binarization with fixed threshold             *
+ *------------------------------------------------------------------*/
+/*
+ *  thresholdToBinaryLow()
+ *
+ *  If the source pixel is less than thresh,
+ *  the dest will be 1; otherwise, it will be 0
+ */
+void
+thresholdToBinaryLow(l_uint32  *datad,
+                     l_int32    w,
+                     l_int32    h,
+                     l_int32    wpld,
+                     l_uint32  *datas,
+                     l_int32    d,
+                     l_int32    wpls,
+                     l_int32    thresh)
+{
+l_int32    i;
+l_uint32  *lines, *lined;
+
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        thresholdToBinaryLineLow(lined, w, lines, d, thresh);
+    }
+    return;
+}
+
+
+/*
+ *  thresholdToBinaryLineLow()
+ *
+ */
+void
+thresholdToBinaryLineLow(l_uint32  *lined,
+                         l_int32    w,
+                         l_uint32  *lines,
+                         l_int32    d,
+                         l_int32    thresh)
+{
+l_int32  j, k, gval, scount, dcount;
+l_uint32 sword, dword;
+
+    PROCNAME("thresholdToBinaryLineLow");
+
+    switch (d)
+    {
+    case 4:
+            /* Unrolled as 4 source words, 1 dest word */
+        for (j = 0, scount = 0, dcount = 0; j + 31 < w; j += 32) {
+            dword = 0;
+            for (k = 0; k < 4; k++) {
+                sword = lines[scount++];
+                dword <<= 8;
+                gval = (sword >> 28) & 0xf;
+                    /* Trick used here and below: if gval < thresh then
+                     * gval - thresh < 0, so its high-order bit is 1, and
+                     * ((gval - thresh) >> 31) & 1 == 1; likewise, if
+                     * gval >= thresh, then ((gval - thresh) >> 31) & 1 == 0
+                     * Doing it this way avoids a random (and thus easily
+                     * mispredicted) branch on each pixel. */
+                dword |= ((gval - thresh) >> 24) & 128;
+                gval = (sword >> 24) & 0xf;
+                dword |= ((gval - thresh) >> 25) & 64;
+                gval = (sword >> 20) & 0xf;
+                dword |= ((gval - thresh) >> 26) & 32;
+                gval = (sword >> 16) & 0xf;
+                dword |= ((gval - thresh) >> 27) & 16;
+                gval = (sword >> 12) & 0xf;
+                dword |= ((gval - thresh) >> 28) & 8;
+                gval = (sword >> 8) & 0xf;
+                dword |= ((gval - thresh) >> 29) & 4;
+                gval = (sword >> 4) & 0xf;
+                dword |= ((gval - thresh) >> 30) & 2;
+                gval = sword & 0xf;
+                dword |= ((gval - thresh) >> 31) & 1;
+            }
+            lined[dcount++] = dword;
+        }
+
+        if (j < w) {
+          dword = 0;
+          for (; j < w; j++) {
+              if ((j & 7) == 0) {
+                  sword = lines[scount++];
+              }
+              gval = (sword >> 28) & 0xf;
+              sword <<= 4;
+              dword |= (((gval - thresh) >> 31) & 1) << (31 - (j & 31));
+          }
+          lined[dcount] = dword;
+        }
+#if DEBUG_UNROLLING
+#define CHECK_BIT(a, b, c) if (GET_DATA_BIT(a, b) != c) { \
+    fprintf(stderr, "Error: mismatch at %d/%d(%d), %d vs %d\n", \
+            j, w, d, GET_DATA_BIT(a, b), c); }
+        for (j = 0; j < w; j++) {
+            gval = GET_DATA_QBIT(lines, j);
+            CHECK_BIT(lined, j, gval < thresh ? 1 : 0);
+        }
+#endif
+        break;
+    case 8:
+            /* Unrolled as 8 source words, 1 dest word */
+        for (j = 0, scount = 0, dcount = 0; j + 31 < w; j += 32) {
+            dword = 0;
+            for (k = 0; k < 8; k++) {
+                sword = lines[scount++];
+                dword <<= 4;
+                gval = (sword >> 24) & 0xff;
+                dword |= ((gval - thresh) >> 28) & 8;
+                gval = (sword >> 16) & 0xff;
+                dword |= ((gval - thresh) >> 29) & 4;
+                gval = (sword >> 8) & 0xff;
+                dword |= ((gval - thresh) >> 30) & 2;
+                gval = sword & 0xff;
+                dword |= ((gval - thresh) >> 31) & 1;
+            }
+            lined[dcount++] = dword;
+        }
+
+        if (j < w) {
+            dword = 0;
+            for (; j < w; j++) {
+                if ((j & 3) == 0) {
+                    sword = lines[scount++];
+                }
+                gval = (sword >> 24) & 0xff;
+                sword <<= 8;
+                dword |= (((gval - thresh) >> 31) & 1) << (31 - (j & 31));
+            }
+            lined[dcount] = dword;
+        }
+#if DEBUG_UNROLLING
+        for (j = 0; j < w; j++) {
+            gval = GET_DATA_BYTE(lines, j);
+            CHECK_BIT(lined, j, gval < thresh ? 1 : 0);
+        }
+#undef CHECK_BIT
+#endif
+        break;
+    default:
+        L_ERROR("src depth not 4 or 8 bpp\n", procName);
+        break;
+    }
+    return;
+}
+
+
+/*---------------------------------------------------------------------*
+ *    Alternate implementation of dithering that uses lookup tables.   *
+ *    This is analogous to the method used in dithering to 2 bpp.      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ditherToBinaryLUTLow()
+ *
+ *  Low-level function for doing Floyd-Steinberg error diffusion
+ *  dithering from 8 bpp (datas) to 1 bpp (datad).  Two source
+ *  line buffers, bufs1 and bufs2, are provided, along with three
+ *  256-entry lookup tables: tabval gives the output pixel value,
+ *  tab38 gives the extra (plus or minus) transferred to the pixels
+ *  directly to the left and below, and tab14 gives the extra
+ *  transferred to the diagonal below.  The choice of 3/8 and 1/4
+ *  is traditional but arbitrary when you use a lookup table; the
+ *  only constraint is that the sum is 1.  See other comments below.
+ */
+void
+ditherToBinaryLUTLow(l_uint32  *datad,
+                     l_int32    w,
+                     l_int32    h,
+                     l_int32    wpld,
+                     l_uint32  *datas,
+                     l_int32    wpls,
+                     l_uint32  *bufs1,
+                     l_uint32  *bufs2,
+                     l_int32   *tabval,
+                     l_int32   *tab38,
+                     l_int32   *tab14)
+{
+l_int32      i;
+l_uint32    *lined;
+
+        /* do all lines except last line */
+    memcpy(bufs2, datas, 4 * wpls);  /* prime the buffer */
+    for (i = 0; i < h - 1; i++) {
+        memcpy(bufs1, bufs2, 4 * wpls);
+        memcpy(bufs2, datas + (i + 1) * wpls, 4 * wpls);
+        lined = datad + i * wpld;
+        ditherToBinaryLineLUTLow(lined, w, bufs1, bufs2,
+                                 tabval, tab38, tab14, 0);
+    }
+
+        /* do last line */
+    memcpy(bufs1, bufs2, 4 * wpls);
+    lined = datad + (h - 1) * wpld;
+    ditherToBinaryLineLUTLow(lined, w, bufs1, bufs2, tabval, tab38, tab14,  1);
+    return;
+}
+
+
+/*!
+ *  ditherToBinaryLineLUTLow()
+ *
+ *      Input:  lined  (ptr to beginning of dest line
+ *              w   (width of image in pixels)
+ *              bufs1 (buffer of current source line)
+ *              bufs2 (buffer of next source line)
+ *              tabval (value to assign for current pixel)
+ *              tab38 (excess value to give to neighboring 3/8 pixels)
+ *              tab14 (excess value to give to neighboring 1/4 pixel)
+ *              lastlineflag  (0 if not last dest line, 1 if last dest line)
+ *      Return: void
+ */
+void
+ditherToBinaryLineLUTLow(l_uint32  *lined,
+                         l_int32    w,
+                         l_uint32  *bufs1,
+                         l_uint32  *bufs2,
+                         l_int32   *tabval,
+                         l_int32   *tab38,
+                         l_int32   *tab14,
+                         l_int32    lastlineflag)
+{
+l_int32  j;
+l_int32  oval, tab38val, tab14val;
+l_uint8  rval, bval, dval;
+
+    if (lastlineflag == 0) {
+        for (j = 0; j < w - 1; j++) {
+            oval = GET_DATA_BYTE(bufs1, j);
+            if (tabval[oval])
+                SET_DATA_BIT(lined, j);
+            rval = GET_DATA_BYTE(bufs1, j + 1);
+            bval = GET_DATA_BYTE(bufs2, j);
+            dval = GET_DATA_BYTE(bufs2, j + 1);
+            tab38val = tab38[oval];
+            if (tab38val == 0)
+                continue;
+            tab14val = tab14[oval];
+            if (tab38val < 0) {
+                rval = L_MAX(0, rval + tab38val);
+                bval = L_MAX(0, bval + tab38val);
+                dval = L_MAX(0, dval + tab14val);
+            } else {
+                rval = L_MIN(255, rval + tab38val);
+                bval = L_MIN(255, bval + tab38val);
+                dval = L_MIN(255, dval + tab14val);
+            }
+            SET_DATA_BYTE(bufs1, j + 1, rval);
+            SET_DATA_BYTE(bufs2, j, bval);
+            SET_DATA_BYTE(bufs2, j + 1, dval);
+        }
+
+            /* do last column: j = w - 1 */
+        oval = GET_DATA_BYTE(bufs1, j);
+        if (tabval[oval])
+            SET_DATA_BIT(lined, j);
+        bval = GET_DATA_BYTE(bufs2, j);
+        tab38val = tab38[oval];
+        if (tab38val < 0) {
+            bval = L_MAX(0, bval + tab38val);
+            SET_DATA_BYTE(bufs2, j, bval);
+        } else if (tab38val > 0 ) {
+            bval = L_MIN(255, bval + tab38val);
+            SET_DATA_BYTE(bufs2, j, bval);
+        }
+    } else {   /* lastlineflag == 1 */
+        for (j = 0; j < w - 1; j++) {
+            oval = GET_DATA_BYTE(bufs1, j);
+            if (tabval[oval])
+                SET_DATA_BIT(lined, j);
+            rval = GET_DATA_BYTE(bufs1, j + 1);
+            tab38val = tab38[oval];
+            if (tab38val == 0)
+                continue;
+            if (tab38val < 0)
+                rval = L_MAX(0, rval + tab38val);
+            else
+                rval = L_MIN(255, rval + tab38val);
+            SET_DATA_BYTE(bufs1, j + 1, rval);
+        }
+
+            /* do last pixel: (i, j) = (h - 1, w - 1) */
+        oval = GET_DATA_BYTE(bufs1, j);
+        if (tabval[oval])
+            SET_DATA_BIT(lined, j);
+    }
+
+    return;
+}
+
+
+/*!
+ *  make8To1DitherTables()
+ *
+ *      Input: &tabval (value assigned to output pixel; 0 or 1)
+ *             &tab38  (amount propagated to pixels left and below)
+ *             &tab14  (amount propagated to pixel to left and down)
+ *             lowerclip (values near 0 where the excess is not propagated)
+ *             upperclip (values near 255 where the deficit is not propagated)
+ *
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+make8To1DitherTables(l_int32 **ptabval,
+                     l_int32 **ptab38,
+                     l_int32 **ptab14,
+                     l_int32   lowerclip,
+                     l_int32   upperclip)
+{
+l_int32   i;
+l_int32  *tabval, *tab38, *tab14;
+
+    PROCNAME("make8To1DitherTables");
+
+    if (!ptabval || !ptab38 || !ptab14)
+        return ERROR_INT("table ptrs not all defined", procName, 1);
+
+        /* 3 lookup tables: 1-bit value, (3/8)excess, and (1/4)excess */
+    if ((tabval = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return ERROR_INT("tabval not made", procName, 1);
+    if ((tab38 = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return ERROR_INT("tab38 not made", procName, 1);
+    if ((tab14 = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return ERROR_INT("tab14 not made", procName, 1);
+    *ptabval = tabval;
+    *ptab38 = tab38;
+    *ptab14 = tab14;
+
+    for (i = 0; i < 256; i++) {
+        if (i <= lowerclip) {
+            tabval[i] = 1;
+            tab38[i] = 0;
+            tab14[i] = 0;
+        } else if (i < 128) {
+            tabval[i] = 1;
+            tab38[i] = (3 * i + 4) / 8;
+            tab14[i] = (i + 2) / 4;
+        } else if (i < 255 - upperclip) {
+            tabval[i] = 0;
+            tab38[i] = (3 * (i - 255) + 4) / 8;
+            tab14[i] = ((i - 255) + 2) / 4;
+        } else {  /* i >= 255 - upperclip */
+            tabval[i] = 0;
+            tab38[i] = 0;
+            tab14[i] = 0;
+        }
+    }
+
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *                         Dithering to 2 bpp                       *
+ *------------------------------------------------------------------*/
+/*
+ *  ditherTo2bppLow()
+ *
+ *  Low-level function for doing Floyd-Steinberg error diffusion
+ *  dithering from 8 bpp (datas) to 2 bpp (datad).  Two source
+ *  line buffers, bufs1 and bufs2, are provided, along with three
+ *  256-entry lookup tables: tabval gives the output pixel value,
+ *  tab38 gives the extra (plus or minus) transferred to the pixels
+ *  directly to the left and below, and tab14 gives the extra
+ *  transferred to the diagonal below.  The choice of 3/8 and 1/4
+ *  is traditional but arbitrary when you use a lookup table; the
+ *  only constraint is that the sum is 1.  See other comments
+ *  below and in grayquant.c.
+ */
+void
+ditherTo2bppLow(l_uint32  *datad,
+                l_int32    w,
+                l_int32    h,
+                l_int32    wpld,
+                l_uint32  *datas,
+                l_int32    wpls,
+                l_uint32  *bufs1,
+                l_uint32  *bufs2,
+                l_int32   *tabval,
+                l_int32   *tab38,
+                l_int32   *tab14)
+{
+l_int32      i;
+l_uint32    *lined;
+
+        /* do all lines except last line */
+    memcpy(bufs2, datas, 4 * wpls);  /* prime the buffer */
+    for (i = 0; i < h - 1; i++) {
+        memcpy(bufs1, bufs2, 4 * wpls);
+        memcpy(bufs2, datas + (i + 1) * wpls, 4 * wpls);
+        lined = datad + i * wpld;
+        ditherTo2bppLineLow(lined, w, bufs1, bufs2, tabval, tab38, tab14, 0);
+    }
+
+        /* do last line */
+    memcpy(bufs1, bufs2, 4 * wpls);
+    lined = datad + (h - 1) * wpld;
+    ditherTo2bppLineLow(lined, w, bufs1, bufs2, tabval, tab38, tab14, 1);
+    return;
+}
+
+
+/*
+ *  ditherTo2bppLineLow()
+ *
+ *      Input:  lined  (ptr to beginning of dest line
+ *              w   (width of image in pixels)
+ *              bufs1 (buffer of current source line)
+ *              bufs2 (buffer of next source line)
+ *              tabval (value to assign for current pixel)
+ *              tab38 (excess value to give to neighboring 3/8 pixels)
+ *              tab14 (excess value to give to neighboring 1/4 pixel)
+ *              lastlineflag  (0 if not last dest line, 1 if last dest line)
+ *      Return: void
+ *
+ *  Dispatches error diffusion dithering for
+ *  a single line of the image.  If lastlineflag == 0,
+ *  both source buffers are used; otherwise, only bufs1
+ *  is used.  We use source buffers because the error
+ *  is propagated into them, and we don't want to change
+ *  the input src image.
+ *
+ *  We break dithering out line by line to make it
+ *  easier to combine functions like interpolative
+ *  scaling and error diffusion dithering, as such a
+ *  combination of operations obviates the need to
+ *  generate a 2x grayscale image as an intermediary.
+ */
+void
+ditherTo2bppLineLow(l_uint32  *lined,
+                    l_int32    w,
+                    l_uint32  *bufs1,
+                    l_uint32  *bufs2,
+                    l_int32   *tabval,
+                    l_int32   *tab38,
+                    l_int32   *tab14,
+                    l_int32    lastlineflag)
+{
+l_int32  j;
+l_int32  oval, tab38val, tab14val;
+l_uint8  rval, bval, dval;
+
+    if (lastlineflag == 0) {
+        for (j = 0; j < w - 1; j++) {
+            oval = GET_DATA_BYTE(bufs1, j);
+            SET_DATA_DIBIT(lined, j, tabval[oval]);
+            rval = GET_DATA_BYTE(bufs1, j + 1);
+            bval = GET_DATA_BYTE(bufs2, j);
+            dval = GET_DATA_BYTE(bufs2, j + 1);
+            tab38val = tab38[oval];
+            tab14val = tab14[oval];
+            if (tab38val < 0) {
+                rval = L_MAX(0, rval + tab38val);
+                bval = L_MAX(0, bval + tab38val);
+                dval = L_MAX(0, dval + tab14val);
+            } else {
+                rval = L_MIN(255, rval + tab38val);
+                bval = L_MIN(255, bval + tab38val);
+                dval = L_MIN(255, dval + tab14val);
+            }
+            SET_DATA_BYTE(bufs1, j + 1, rval);
+            SET_DATA_BYTE(bufs2, j, bval);
+            SET_DATA_BYTE(bufs2, j + 1, dval);
+        }
+
+            /* do last column: j = w - 1 */
+        oval = GET_DATA_BYTE(bufs1, j);
+        SET_DATA_DIBIT(lined, j, tabval[oval]);
+        bval = GET_DATA_BYTE(bufs2, j);
+        tab38val = tab38[oval];
+        if (tab38val < 0)
+            bval = L_MAX(0, bval + tab38val);
+        else
+            bval = L_MIN(255, bval + tab38val);
+        SET_DATA_BYTE(bufs2, j, bval);
+    } else {   /* lastlineflag == 1 */
+        for (j = 0; j < w - 1; j++) {
+            oval = GET_DATA_BYTE(bufs1, j);
+            SET_DATA_DIBIT(lined, j, tabval[oval]);
+            rval = GET_DATA_BYTE(bufs1, j + 1);
+            tab38val = tab38[oval];
+            if (tab38val < 0)
+                rval = L_MAX(0, rval + tab38val);
+            else
+                rval = L_MIN(255, rval + tab38val);
+            SET_DATA_BYTE(bufs1, j + 1, rval);
+        }
+
+            /* do last pixel: (i, j) = (h - 1, w - 1) */
+        oval = GET_DATA_BYTE(bufs1, j);
+        SET_DATA_DIBIT(lined, j, tabval[oval]);
+    }
+
+    return;
+}
+
+
+/*!
+ *  make8To2DitherTables()
+ *
+ *      Input: &tabval (value assigned to output pixel; 0, 1, 2 or 3)
+ *             &tab38  (amount propagated to pixels left and below)
+ *             &tab14  (amount propagated to pixel to left and down)
+ *             cliptoblack (values near 0 where the excess is not propagated)
+ *             cliptowhite (values near 255 where the deficit is not propagated)
+ *
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+make8To2DitherTables(l_int32 **ptabval,
+                     l_int32 **ptab38,
+                     l_int32 **ptab14,
+                     l_int32   cliptoblack,
+                     l_int32   cliptowhite)
+{
+l_int32   i;
+l_int32  *tabval, *tab38, *tab14;
+
+    PROCNAME("make8To2DitherTables");
+
+        /* 3 lookup tables: 2-bit value, (3/8)excess, and (1/4)excess */
+    if ((tabval = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return ERROR_INT("tabval not made", procName, 1);
+    if ((tab38 = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return ERROR_INT("tab38 not made", procName, 1);
+    if ((tab14 = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return ERROR_INT("tab14 not made", procName, 1);
+    *ptabval = tabval;
+    *ptab38 = tab38;
+    *ptab14 = tab14;
+
+    for (i = 0; i < 256; i++) {
+        if (i <= cliptoblack) {
+            tabval[i] = 0;
+            tab38[i] = 0;
+            tab14[i] = 0;
+        } else if (i < 43) {
+            tabval[i] = 0;
+            tab38[i] = (3 * i + 4) / 8;
+            tab14[i] = (i + 2) / 4;
+        } else if (i < 85) {
+            tabval[i] = 1;
+            tab38[i] = (3 * (i - 85) - 4) / 8;
+            tab14[i] = ((i - 85) - 2) / 4;
+        } else if (i < 128) {
+            tabval[i] = 1;
+            tab38[i] = (3 * (i - 85) + 4) / 8;
+            tab14[i] = ((i - 85) + 2) / 4;
+        } else if (i < 170) {
+            tabval[i] = 2;
+            tab38[i] = (3 * (i - 170) - 4) / 8;
+            tab14[i] = ((i - 170) - 2) / 4;
+        } else if (i < 213) {
+            tabval[i] = 2;
+            tab38[i] = (3 * (i - 170) + 4) / 8;
+            tab14[i] = ((i - 170) + 2) / 4;
+        } else if (i < 255 - cliptowhite) {
+            tabval[i] = 3;
+            tab38[i] = (3 * (i - 255) - 4) / 8;
+            tab14[i] = ((i - 255) - 2) / 4;
+        } else {  /* i >= 255 - cliptowhite */
+            tabval[i] = 3;
+            tab38[i] = 0;
+            tab14[i] = 0;
+        }
+    }
+
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *                   Simple thresholding to 2 bpp                   *
+ *------------------------------------------------------------------*/
+/*
+ *  thresholdTo2bppLow()
+ *
+ *  Low-level function for thresholding from 8 bpp (datas) to
+ *  2 bpp (datad), using thresholds implicitly defined through @tab,
+ *  a 256-entry lookup table that gives a 2-bit output value
+ *  for each possible input.
+ *
+ *  For each line, unroll the loop so that for each 32 bit src word,
+ *  representing four consecutive 8-bit pixels, we compose one byte
+ *  of output consisiting of four 2-bit pixels.
+ */
+void
+thresholdTo2bppLow(l_uint32  *datad,
+                   l_int32    h,
+                   l_int32    wpld,
+                   l_uint32  *datas,
+                   l_int32    wpls,
+                   l_int32   *tab)
+{
+l_uint8    sval1, sval2, sval3, sval4, dval;
+l_int32    i, j, k;
+l_uint32  *lines, *lined;
+
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < wpls; j++) {
+            k = 4 * j;
+            sval1 = GET_DATA_BYTE(lines, k);
+            sval2 = GET_DATA_BYTE(lines, k + 1);
+            sval3 = GET_DATA_BYTE(lines, k + 2);
+            sval4 = GET_DATA_BYTE(lines, k + 3);
+            dval = (tab[sval1] << 6) | (tab[sval2] << 4) |
+                   (tab[sval3] << 2) | tab[sval4];
+            SET_DATA_BYTE(lined, j, dval);
+        }
+    }
+    return;
+}
+
+
+/*------------------------------------------------------------------*
+ *                   Simple thresholding to 4 bpp                   *
+ *------------------------------------------------------------------*/
+/*
+ *  thresholdTo4bppLow()
+ *
+ *  Low-level function for thresholding from 8 bpp (datas) to
+ *  4 bpp (datad), using thresholds implicitly defined through @tab,
+ *  a 256-entry lookup table that gives a 4-bit output value
+ *  for each possible input.
+ *
+ *  For each line, unroll the loop so that for each 32 bit src word,
+ *  representing four consecutive 8-bit pixels, we compose two bytes
+ *  of output consisiting of four 4-bit pixels.
+ */
+void
+thresholdTo4bppLow(l_uint32  *datad,
+                   l_int32    h,
+                   l_int32    wpld,
+                   l_uint32  *datas,
+                   l_int32    wpls,
+                   l_int32   *tab)
+{
+l_uint8    sval1, sval2, sval3, sval4;
+l_uint16   dval;
+l_int32    i, j, k;
+l_uint32  *lines, *lined;
+
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < wpls; j++) {
+            k = 4 * j;
+            sval1 = GET_DATA_BYTE(lines, k);
+            sval2 = GET_DATA_BYTE(lines, k + 1);
+            sval3 = GET_DATA_BYTE(lines, k + 2);
+            sval4 = GET_DATA_BYTE(lines, k + 3);
+            dval = (tab[sval1] << 12) | (tab[sval2] << 8) |
+                   (tab[sval3] << 4) | tab[sval4];
+            SET_DATA_TWO_BYTES(lined, j, dval);
+        }
+    }
+    return;
+}
diff --git a/src/heap.c b/src/heap.c
new file mode 100644 (file)
index 0000000..91374c9
--- /dev/null
@@ -0,0 +1,528 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   heap.c
+ *
+ *      Create/Destroy L_Heap
+ *          L_HEAP         *lheapCreate()
+ *          void           *lheapDestroy()
+ *
+ *      Operations to add/remove to/from the heap
+ *          l_int32         lheapAdd()
+ *          static l_int32  lheapExtendArray()
+ *          void           *lheapRemove()
+ *
+ *      Heap operations
+ *          l_int32         lheapSwapUp()
+ *          l_int32         lheapSwapDown()
+ *          l_int32         lheapSort()
+ *          l_int32         lheapSortStrictOrder()
+ *
+ *      Accessors
+ *          l_int32         lheapGetCount()
+ *
+ *      Debug output
+ *          l_int32         lheapPrint()
+ *
+ *    The L_Heap is useful to implement a priority queue, that is sorted
+ *    on a key in each element of the heap.  The heap is an array
+ *    of nearly arbitrary structs, with a l_float32 the first field.
+ *    This field is the key on which the heap is sorted.
+ *
+ *    Internally, we keep track of the heap size, n.  The item at the
+ *    root of the heap is at the head of the array.  Items are removed
+ *    from the head of the array and added to the end of the array.
+ *    When an item is removed from the head, the item at the end
+ *    of the array is moved to the head.  When items are either
+ *    added or removed, it is usually necessary to swap array items
+ *    to restore the heap order.  It is guaranteed that the number
+ *    of swaps does not exceed log(n).
+ *
+ *    --------------------------  N.B.  ------------------------------
+ *    The items on the heap (or, equivalently, in the array) are cast
+ *    to void*.  Their key is a l_float32, and it is REQUIRED that the
+ *    key be the first field in the struct.  That allows us to get the
+ *    key by simply dereferencing the struct.  Alternatively, we could
+ *    choose (but don't) to pass an application-specific comparison
+ *    function into the heap operation functions.
+ *    --------------------------  N.B.  ------------------------------
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  MIN_BUFFER_SIZE = 20;             /* n'importe quoi */
+static const l_int32  INITIAL_BUFFER_ARRAYSIZE = 128;   /* n'importe quoi */
+
+#define SWAP_ITEMS(i, j)       { void *tempitem = lh->array[(i)]; \
+                                 lh->array[(i)] = lh->array[(j)]; \
+                                 lh->array[(j)] = tempitem; }
+
+    /* Static function */
+static l_int32 lheapExtendArray(L_HEAP *lh);
+
+
+/*--------------------------------------------------------------------------*
+ *                          L_Heap create/destroy                           *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  lheapCreate()
+ *
+ *      Input:  size of ptr array to be alloc'd (0 for default)
+ *              direction (L_SORT_INCREASING, L_SORT_DECREASING)
+ *      Return: lheap, or null on error
+ */
+L_HEAP *
+lheapCreate(l_int32  nalloc,
+            l_int32  direction)
+{
+L_HEAP  *lh;
+
+    PROCNAME("lheapCreate");
+
+    if (nalloc < MIN_BUFFER_SIZE)
+        nalloc = MIN_BUFFER_SIZE;
+
+        /* Allocate ptr array and initialize counters. */
+    if ((lh = (L_HEAP *)LEPT_CALLOC(1, sizeof(L_HEAP))) == NULL)
+        return (L_HEAP *)ERROR_PTR("lh not made", procName, NULL);
+    if ((lh->array = (void **)LEPT_CALLOC(nalloc, sizeof(void *))) == NULL)
+        return (L_HEAP *)ERROR_PTR("ptr array not made", procName, NULL);
+    lh->nalloc = nalloc;
+    lh->n = 0;
+    lh->direction = direction;
+    return lh;
+}
+
+
+/*!
+ *  lheapDestroy()
+ *
+ *      Input:  &lheap  (<to be nulled>)
+ *              freeflag (TRUE to free each remaining struct in the array)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Use freeflag == TRUE when the items in the array can be
+ *          simply destroyed using free.  If those items require their
+ *          own destroy function, they must be destroyed before
+ *          calling this function, and then this function is called
+ *          with freeflag == FALSE.
+ *      (2) To destroy the lheap, we destroy the ptr array, then
+ *          the lheap, and then null the contents of the input ptr.
+ */
+void
+lheapDestroy(L_HEAP  **plh,
+             l_int32   freeflag)
+{
+l_int32  i;
+L_HEAP  *lh;
+
+    PROCNAME("lheapDestroy");
+
+    if (plh == NULL) {
+        L_WARNING("ptr address is NULL\n", procName);
+        return;
+    }
+    if ((lh = *plh) == NULL)
+        return;
+
+    if (freeflag) {  /* free each struct in the array */
+        for (i = 0; i < lh->n; i++)
+            LEPT_FREE(lh->array[i]);
+    } else if (lh->n > 0) {  /* freeflag == FALSE but elements exist on array */
+        L_WARNING("memory leak of %d items in lheap!\n", procName, lh->n);
+    }
+
+    if (lh->array)
+        LEPT_FREE(lh->array);
+    LEPT_FREE(lh);
+    *plh = NULL;
+
+    return;
+}
+
+/*--------------------------------------------------------------------------*
+ *                                  Accessors                               *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  lheapAdd()
+ *
+ *      Input:  lheap
+ *              item to be added to the tail of the heap
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+lheapAdd(L_HEAP  *lh,
+         void    *item)
+{
+    PROCNAME("lheapAdd");
+
+    if (!lh)
+        return ERROR_INT("lh not defined", procName, 1);
+    if (!item)
+        return ERROR_INT("item not defined", procName, 1);
+
+        /* If necessary, expand the allocated array by a factor of 2 */
+    if (lh->n >= lh->nalloc)
+        lheapExtendArray(lh);
+
+        /* Add the item */
+    lh->array[lh->n] = item;
+    lh->n++;
+
+        /* Restore the heap */
+    lheapSwapUp(lh, lh->n - 1);
+    return 0;
+}
+
+
+/*!
+ *  lheapExtendArray()
+ *
+ *      Input:  lheap
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+lheapExtendArray(L_HEAP  *lh)
+{
+    PROCNAME("lheapExtendArray");
+
+    if (!lh)
+        return ERROR_INT("lh not defined", procName, 1);
+
+    if ((lh->array = (void **)reallocNew((void **)&lh->array,
+                                sizeof(void *) * lh->nalloc,
+                                2 * sizeof(void *) * lh->nalloc)) == NULL)
+        return ERROR_INT("new ptr array not returned", procName, 1);
+
+    lh->nalloc = 2 * lh->nalloc;
+    return 0;
+}
+
+
+/*!
+ *  lheapRemove()
+ *
+ *      Input:  lheap
+ *      Return: ptr to item popped from the root of the heap,
+ *              or null if the heap is empty or on error
+ */
+void *
+lheapRemove(L_HEAP  *lh)
+{
+void   *item;
+
+    PROCNAME("lheapRemove");
+
+    if (!lh)
+        return (void *)ERROR_PTR("lh not defined", procName, NULL);
+
+    if (lh->n == 0)
+        return NULL;
+
+    item = lh->array[0];
+    lh->array[0] = lh->array[lh->n - 1];  /* move last to the head */
+    lh->array[lh->n - 1] = NULL;  /* set ptr to null */
+    lh->n--;
+
+    lheapSwapDown(lh);  /* restore the heap */
+    return item;
+}
+
+
+/*!
+ *  lheapGetCount()
+ *
+ *      Input:  lheap
+ *      Return: count, or 0 on error
+ */
+l_int32
+lheapGetCount(L_HEAP  *lh)
+{
+    PROCNAME("lheapGetCount");
+
+    if (!lh)
+        return ERROR_INT("lh not defined", procName, 0);
+
+    return lh->n;
+}
+
+
+
+/*--------------------------------------------------------------------------*
+ *                               Heap operations                            *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  lheapSwapUp()
+ *
+ *      Input:  lh (heap)
+ *              index (of array corresponding to node to be swapped up)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is called after a new item is put on the heap, at the
+ *          bottom of a complete tree.
+ *      (2) To regain the heap order, we let it bubble up,
+ *          iteratively swapping with its parent, until it either
+ *          reaches the root of the heap or it finds a parent that
+ *          is in the correct position already vis-a-vis the child.
+ */
+l_int32
+lheapSwapUp(L_HEAP  *lh,
+            l_int32  index)
+{
+l_int32    ip;  /* index to heap for parent; 1 larger than array index */
+l_int32    ic;  /* index into heap for child */
+l_float32  valp, valc;
+
+  PROCNAME("lheapSwapUp");
+
+  if (!lh)
+      return ERROR_INT("lh not defined", procName, 1);
+  if (index < 0 || index >= lh->n)
+      return ERROR_INT("invalid index", procName, 1);
+
+  ic = index + 1;  /* index into heap: add 1 to array index */
+  if (lh->direction == L_SORT_INCREASING) {
+      while (1) {
+          if (ic == 1)  /* root of heap */
+              break;
+          ip = ic / 2;
+          valc = *(l_float32 *)(lh->array[ic - 1]);
+          valp = *(l_float32 *)(lh->array[ip - 1]);
+          if (valp <= valc)
+             break;
+          SWAP_ITEMS(ip - 1, ic - 1);
+          ic = ip;
+      }
+  } else {  /* lh->direction == L_SORT_DECREASING */
+      while (1) {
+          if (ic == 1)  /* root of heap */
+              break;
+          ip = ic / 2;
+          valc = *(l_float32 *)(lh->array[ic - 1]);
+          valp = *(l_float32 *)(lh->array[ip - 1]);
+          if (valp >= valc)
+             break;
+          SWAP_ITEMS(ip - 1, ic - 1);
+          ic = ip;
+      }
+  }
+  return 0;
+}
+
+
+/*!
+ *  lheapSwapDown()
+ *
+ *      Input:  lh (heap)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is called after an item has been popped off the
+ *          root of the heap, and the last item in the heap has
+ *          been placed at the root.
+ *      (2) To regain the heap order, we let it bubble down,
+ *          iteratively swapping with one of its children.  For a
+ *          decreasing sort, it swaps with the largest child; for
+ *          an increasing sort, the smallest.  This continues until
+ *          it either reaches the lowest level in the heap, or the
+ *          parent finds that neither child should swap with it
+ *          (e.g., for a decreasing heap, the parent is larger
+ *          than or equal to both children).
+ */
+l_int32
+lheapSwapDown(L_HEAP  *lh)
+{
+l_int32    ip;  /* index to heap for parent; 1 larger than array index */
+l_int32    icr, icl;  /* index into heap for left/right children */
+l_float32  valp, valcl, valcr;
+
+  PROCNAME("lheapSwapDown");
+
+  if (!lh)
+      return ERROR_INT("lh not defined", procName, 1);
+  if (lheapGetCount(lh) < 1)
+      return 0;
+
+  ip = 1;  /* index into top of heap: corresponds to array[0] */
+  if (lh->direction == L_SORT_INCREASING) {
+      while (1) {
+          icl = 2 * ip;
+          if (icl > lh->n)
+             break;
+          valp = *(l_float32 *)(lh->array[ip - 1]);
+          valcl = *(l_float32 *)(lh->array[icl - 1]);
+          icr = icl + 1;
+          if (icr > lh->n) {  /* only a left child; no iters below */
+              if (valp > valcl)
+                  SWAP_ITEMS(ip - 1, icl - 1);
+              break;
+          } else {  /* both children exist; swap with the smallest if bigger */
+              valcr = *(l_float32 *)(lh->array[icr - 1]);
+              if (valp <= valcl && valp <= valcr)  /* smaller than both */
+                  break;
+              if (valcl <= valcr) {  /* left smaller; swap */
+                  SWAP_ITEMS(ip - 1, icl - 1);
+                  ip = icl;
+              } else { /* right smaller; swap */
+                  SWAP_ITEMS(ip - 1, icr - 1);
+                  ip = icr;
+              }
+          }
+      }
+  } else {  /* lh->direction == L_SORT_DECREASING */
+      while (1) {
+          icl = 2 * ip;
+          if (icl > lh->n)
+             break;
+          valp = *(l_float32 *)(lh->array[ip - 1]);
+          valcl = *(l_float32 *)(lh->array[icl - 1]);
+          icr = icl + 1;
+          if (icr > lh->n) {  /* only a left child; no iters below */
+              if (valp < valcl)
+                  SWAP_ITEMS(ip - 1, icl - 1);
+              break;
+          } else {  /* both children exist; swap with the biggest if smaller */
+              valcr = *(l_float32 *)(lh->array[icr - 1]);
+              if (valp >= valcl && valp >= valcr)  /* bigger than both */
+                  break;
+              if (valcl >= valcr) {  /* left bigger; swap */
+                  SWAP_ITEMS(ip - 1, icl - 1);
+                  ip = icl;
+              } else {  /* right bigger; swap */
+                  SWAP_ITEMS(ip - 1, icr - 1);
+                  ip = icr;
+              }
+          }
+      }
+  }
+
+  return 0;
+}
+
+
+/*!
+ *  lheapSort()
+ *
+ *      Input:  lh (heap, with internal array)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This sorts an array into heap order.  If the heap is already
+ *          in heap order for the direction given, this has no effect.
+ */
+l_int32
+lheapSort(L_HEAP  *lh)
+{
+l_int32  i;
+
+  PROCNAME("lheapSort");
+
+  if (!lh)
+      return ERROR_INT("lh not defined", procName, 1);
+
+  for (i = 0; i < lh->n; i++)
+      lheapSwapUp(lh, i);
+
+  return 0;
+}
+
+
+/*!
+ *  lheapSortStrictOrder()
+ *
+ *      Input:  lh (heap, with internal array)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This sorts a heap into strict order.
+ *      (2) For each element, starting at the end of the array and
+ *          working forward, the element is swapped with the head
+ *          element and then allowed to swap down onto a heap of
+ *          size reduced by one.  The result is that the heap is
+ *          reversed but in strict order.  The array elements are
+ *          then reversed to put it in the original order.
+ */
+l_int32
+lheapSortStrictOrder(L_HEAP  *lh)
+{
+l_int32  i, index, size;
+
+  PROCNAME("lheapSortStrictOrder");
+
+  if (!lh)
+      return ERROR_INT("lh not defined", procName, 1);
+
+  size = lh->n;  /* save the actual size */
+  for (i = 0; i < size; i++) {
+      index = size - i;
+      SWAP_ITEMS(0, index - 1);
+      lh->n--;  /* reduce the apparent heap size by 1 */
+      lheapSwapDown(lh);
+  }
+  lh->n = size;  /* restore the size */
+
+  for (i = 0; i < size / 2; i++)  /* reverse */
+      SWAP_ITEMS(i, size - i - 1);
+
+  return 0;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                            Debug output                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  lheapPrint()
+ *
+ *      Input:  stream
+ *              lheap
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+lheapPrint(FILE    *fp,
+           L_HEAP  *lh)
+{
+l_int32  i;
+
+    PROCNAME("lheapPrint");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!lh)
+        return ERROR_INT("lh not defined", procName, 1);
+
+    fprintf(fp, "\n L_Heap: nalloc = %d, n = %d, array = %p\n",
+            lh->nalloc, lh->n, lh->array);
+    for (i = 0; i < lh->n; i++)
+        fprintf(fp,   "keyval[%d] = %f\n", i, *(l_float32 *)lh->array[i]);
+
+    return 0;
+}
diff --git a/src/heap.h b/src/heap.h
new file mode 100644 (file)
index 0000000..b53911a
--- /dev/null
@@ -0,0 +1,84 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_HEAP_H
+#define  LEPTONICA_HEAP_H
+
+/*
+ *  heap.h
+ *
+ *      Expandable priority queue configured as a heap for arbitrary void* data
+ *
+ *      The L_Heap is used to implement a priority queue.  The elements
+ *      in the heap are ordered in either increasing or decreasing key value.
+ *      The key is a float field 'keyval' that is required to be
+ *      contained in the elements of the queue.
+ *
+ *      The heap is a simple binary tree with the following constraints:
+ *         - the key of each node is >= the keys of the two children
+ *         - the tree is complete, meaning that each level (1, 2, 4, ...)
+ *           is filled and the last level is filled from left to right
+ *
+ *      The tree structure is implicit in the queue array, with the
+ *      array elements numbered as a breadth-first search of the tree
+ *      from left to right.  It is thus guaranteed that the largest
+ *      (or smallest) key belongs to the first element in the array.
+ *
+ *      Heap sort is used to sort the array.  Once an array has been
+ *      sorted as a heap, it is convenient to use it as a priority queue,
+ *      because the min (or max) elements are always at the root of
+ *      the tree (element 0), and once removed, the heap can be
+ *      resorted in not more than log[n] steps, where n is the number
+ *      of elements on the heap.  Likewise, if an arbitrary element is
+ *      added to the end of the array A, the sorted heap can be restored
+ *      in not more than log[n] steps.
+ *
+ *      A L_Heap differs from a L_Queue in that the elements in the former
+ *      are sorted by a key.  Internally, the array is maintained
+ *      as a queue, with a pointer to the end of the array.  The
+ *      head of the array always remains at array[0].  The array is
+ *      maintained (sorted) as a heap.  When an item is removed from
+ *      the head, the last item takes its place (thus reducing the
+ *      array length by 1), and this is followed by array element
+ *      swaps to restore the heap property.   When an item is added,
+ *      it goes at the end of the array, and is swapped up to restore
+ *      the heap.  If the ptr array is full, adding another item causes
+ *      the ptr array size to double.
+ *
+ *      For further implementation details, see heap.c.
+ */
+
+struct L_Heap
+{
+    l_int32      nalloc;      /* size of allocated ptr array                 */
+    l_int32      n;           /* number of elements stored in the heap       */
+    void       **array;       /* ptr array                                   */
+    l_int32      direction;   /* L_SORT_INCREASING or L_SORT_DECREASING      */
+};
+typedef struct L_Heap  L_HEAP;
+
+
+#endif  /* LEPTONICA_HEAP_H */
diff --git a/src/hmttemplate1.txt b/src/hmttemplate1.txt
new file mode 100644 (file)
index 0000000..96a473d
--- /dev/null
@@ -0,0 +1,167 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*!
+ *      Top-level fast hit-miss transform with auto-generated sels
+ *
+--- *            PIX      *pixHMTDwa_*()
+--- *            PIX      *pixFHMTGen_*()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+---               This file is:  hmttemplate1.txt
+---
+---           We need to include these prototypes:
+---    PIX *pixHMTDwa_*(PIX *pixd, PIX *pixs, l_int32 operation);
+---    PIX *pixFHMTGen_*(PIX *pixd, PIX *pixs, l_int32 operation);
+---    l_int32 fhmtgen_low_*(l_uint32 *datad, l_int32 w, l_int32 h,
+---                          l_int32 wpld, l_uint32 *datas,
+---                          l_int32  wpls, l_int32 index);
+---
+---             We need to input two static globals here:
+---   static l_int32     NUM_SELS_GENERATED =  <some number>;
+---   static char  SEL_NAMES[][80] =    {"<string1>", "<string2>", ...};
+
+/*!
+--- *  pixHMTDwa_*()
+ *
+ *      Input:  pixd (usual 3 choices: null, == pixs, != pixs)
+ *              pixs (1 bpp)
+ *              sel name
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This simply adds a 32 pixel border, calls the appropriate
+ *          pixFHMTGen_*(), and removes the border.
+ *          See notes below for that function.
+ */
+PIX *
+---     pixHMTDwa_*(PIX   *pixd,
+            PIX         *pixs,
+            const char  *selname)
+{
+PIX  *pixt1, *pixt2, *pixt3;
+
+--- PROCNAME("pixHMTDwa_*");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, pixd);
+
+    pixt1 = pixAddBorder(pixs, 32, 0);
+--- pixt2 = pixFHMTGen_*(NULL, pixt1, selname);
+    pixt3 = pixRemoveBorder(pixt2, 32);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixCopy(pixd, pixt3);
+    pixDestroy(&pixt3);
+    return pixd;
+}
+
+
+/*!
+--- *  pixFHMTGen_*()
+ *
+ *      Input:  pixd (usual 3 choices: null, == pixs, != pixs)
+ *              pixs (1 bpp)
+ *              sel name
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This is a dwa implementation of the hit-miss transform
+ *          on pixs by the sel.
+ *      (2) The sel must be limited in size to not more than 31 pixels
+ *          about the origin.  It must have at least one hit, and it
+ *          can have any number of misses.
+ *      (3) This handles all required setting of the border pixels
+ *          before erosion and dilation.
+ */
+PIX *
+---     pixFHMTGen_*(PIX   *pixd,
+             PIX         *pixs,
+             const char  *selname)
+{
+l_int32    i, index, found, w, h, wpls, wpld;
+l_uint32  *datad, *datas, *datat;
+PIX       *pixt;
+
+--- PROCNAME("pixFHMTGen_*");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, pixd);
+
+    found = FALSE;
+    for (i = 0; i < NUM_SELS_GENERATED; i++) {
+        if (strcmp(selname, SEL_NAMES[i]) == 0) {
+            found = TRUE;
+            index = i;
+            break;
+        }
+    }
+    if (found == FALSE)
+        return (PIX *)ERROR_PTR("sel index not found", procName, pixd);
+
+    if (!pixd) {
+        if ((pixd = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    else  /* for in-place or pre-allocated */
+        pixResizeImageData(pixd, pixs);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+        /*  The images must be surrounded with 32 additional border
+         *  pixels, that we'll read from.  We fabricate a "proper"
+         *  image as the subimage within the border, having the
+         *  following parameters:  */
+    w = pixGetWidth(pixs) - 64;
+    h = pixGetHeight(pixs) - 64;
+    datas = pixGetData(pixs) + 32 * wpls + 1;
+    datad = pixGetData(pixd) + 32 * wpld + 1;
+
+    if (pixd == pixs) {  /* need temp image if in-place */
+        if ((pixt = pixCopy(NULL, pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+        datat = pixGetData(pixt) + 32 * wpls + 1;
+---     fhmtgen_low_*(datad, w, h, wpld, datat, wpls, index);
+        pixDestroy(&pixt);
+    }
+    else {  /* not in-place */
+---     fhmtgen_low_*(datad, w, h, wpld, datas, wpls, index);
+    }
+
+    return pixd;
+}
+
diff --git a/src/hmttemplate2.txt b/src/hmttemplate2.txt
new file mode 100644 (file)
index 0000000..9972702
--- /dev/null
@@ -0,0 +1,103 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*!
+ *     Low-level fast hit-miss transform with auto-generated sels
+ *
+ *      Dispatcher:
+--- *            l_int32   fhmtgen_low_*()
+ *
+ *      Static Low-level:
+--- *            void      fhmt_*_*()
+ */
+
+#include "allheaders.h"
+
+---                This file is:  hmttemplate2.txt
+---
+---      insert static protos here
+
+
+/*---------------------------------------------------------------------*
+ *                           Fast hmt dispatcher                       *
+ *---------------------------------------------------------------------*/
+/*!
+--- *  fhmtgen_low_*()
+ *
+ *       a dispatcher to appropriate low-level code
+ */
+l_int32
+---   fhmtgen_low_*(l_uint32  *datad,
+              l_int32    w,
+              l_int32    h,
+              l_int32    wpld,
+              l_uint32  *datas,
+              l_int32    wpls,
+              l_int32    index)
+{
+
+    switch (index)
+    {
+---    insert dispatcher code for fhmt* routines
+    }
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                 Low-level auto-generated static routines                 *
+ *--------------------------------------------------------------------------*/
+/*
+ *  N.B.  In all the low-level routines, the part of the image
+ *        that is accessed has been clipped by 32 pixels on
+ *        all four sides.  This is done in the higher level
+ *        code by redefining w and h smaller and by moving the
+ *        start-of-image pointers up to the beginning of this
+ *        interior rectangle.
+ */
+---   static void fhmt_*_*(l_uint32  *datad,
+         l_int32    w,
+         l_int32    h,
+         l_int32    wpld,
+         l_uint32  *datas,
+         l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+---     declare wplsN args as necessary ----------------------
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+---        insert barrel-op code for *dptr here ...
+        }
+    }
+}
+
diff --git a/src/imageio.h b/src/imageio.h
new file mode 100644 (file)
index 0000000..b9229fb
--- /dev/null
@@ -0,0 +1,206 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  General features of image I/O in leptonica
+ *
+ *  At present, there are 9 file formats for images that can be read
+ *  and written:
+ *      png (requires libpng, libz)
+ *      jpeg (requires libjpeg)
+ *      tiff (requires libtiff, libz)
+ *      gif (requires libgif)
+ *      webp (requires libwebp)
+ *      jp2 (requires libopenjp2)
+ *      bmp (no library required)
+ *      pnm (no library required)
+ *      spix (no library required)
+ *  Additionally, there are two file formats for writing (only) images:
+ *      PostScript (requires libpng, libz, libjpeg, libtiff)
+ *      pdf (requires libpng, libz, libjpeg, libtiff)
+ *
+ *  For all 9 read/write formats, leptonica provides interconversion
+ *  between pix (with raster data) and formatted image data:
+ *      Conversion from pix (typically compression):
+ *          pixWrite():        pix --> file
+ *          pixWriteStream():  pix --> filestream (aka FILE*)
+ *          pixWriteMem():     pix --> memory buffer
+ *      Conversion to pix (typically decompression):
+ *          pixRead():         file --> pix
+ *          pixReadStream():   filestream --> pix
+ *          pixReadMem():      memory buffer --> pix
+ *
+ *  Conversions for which the image data is not compressed are:
+ *     * uncompressed tiff   (IFF_TIFF)
+ *     * bmp
+ *     * pnm
+ *     * spix (fast serialization that copies the pix raster data)
+ *
+ *  The image header (metadata) information can be read from either
+ *  the compressed file or a memory buffer, for all 9 formats.
+ */
+
+#ifndef  LEPTONICA_IMAGEIO_H
+#define  LEPTONICA_IMAGEIO_H
+
+/* ------------------ Image file format types -------------- */
+/*
+ *  The IFF_DEFAULT flag is used to write the file out in the
+ *  same (input) file format that the pix was read from.  If the pix
+ *  was not read from file, the input format field will be
+ *  IFF_UNKNOWN and the output file format will be chosen to
+ *  be compressed and lossless; namely, IFF_TIFF_G4 for d = 1
+ *  and IFF_PNG for everything else.   IFF_JP2 is for jpeg2000, which
+ *  is not supported in leptonica.
+ *
+ *  In the future, new format types that have defined extensions
+ *  will be added before IFF_DEFAULT, and will be kept in sync with
+ *  the file format extensions in writefile.c.  The positions of
+ *  file formats before IFF_DEFAULT will remain invariant.
+ */
+enum {
+    IFF_UNKNOWN        = 0,
+    IFF_BMP            = 1,
+    IFF_JFIF_JPEG      = 2,
+    IFF_PNG            = 3,
+    IFF_TIFF           = 4,
+    IFF_TIFF_PACKBITS  = 5,
+    IFF_TIFF_RLE       = 6,
+    IFF_TIFF_G3        = 7,
+    IFF_TIFF_G4        = 8,
+    IFF_TIFF_LZW       = 9,
+    IFF_TIFF_ZIP       = 10,
+    IFF_PNM            = 11,
+    IFF_PS             = 12,
+    IFF_GIF            = 13,
+    IFF_JP2            = 14,
+    IFF_WEBP           = 15,
+    IFF_LPDF           = 16,
+    IFF_DEFAULT        = 17,
+    IFF_SPIX           = 18
+};
+
+
+/* ---------------------- Format header ids --------------------- */
+enum {
+    BMP_ID             = 0x4d42,
+    TIFF_BIGEND_ID     = 0x4d4d,     /* MM - for 'motorola' */
+    TIFF_LITTLEEND_ID  = 0x4949      /* II - for 'intel'    */
+};
+
+
+/* ------------- Hinting bit flags in jpeg reader --------------- */
+enum {
+    L_JPEG_READ_LUMINANCE = 1,  /* only want luminance data; no chroma */
+    L_JPEG_FAIL_ON_BAD_DATA = 2  /* don't return possibly damaged pix */
+};
+
+
+/* ------------------ Pdf formatted encoding types -------------- */
+enum {
+    L_DEFAULT_ENCODE  = 0,  /* use default encoding based on image        */
+    L_JPEG_ENCODE     = 1,  /* use dct encoding: 8 and 32 bpp, no cmap    */
+    L_G4_ENCODE       = 2,  /* use ccitt g4 fax encoding: 1 bpp           */
+    L_FLATE_ENCODE    = 3,  /* use flate encoding: any depth, cmap ok     */
+    L_JP2K_ENCODE     = 4   /* use jp2k encoding: 8 and 32 bpp, no cmap   */
+};
+
+
+/* ------------------ Compressed image data --------------------- */
+/*
+ *  In use, either datacomp or data85 will be produced, depending
+ *  on whether the data needs to be ascii85 encoded.  PostScript
+ *  requires ascii85 encoding; pdf does not.
+ *
+ *  For the colormap (flate compression only), PostScript uses ascii85
+ *  encoding and pdf uses a bracketed array of space-separated
+ *  hex-encoded rgb triples.  Only tiff g4 (type == L_G4_ENCODE) uses
+ *  the minisblack field.
+ */
+struct L_Compressed_Data
+{
+    l_int32            type;         /* encoding type: L_JPEG_ENCODE, etc   */
+    l_uint8           *datacomp;     /* gzipped raster data                 */
+    size_t             nbytescomp;   /* number of compressed bytes          */
+    char              *data85;       /* ascii85-encoded gzipped raster data */
+    size_t             nbytes85;     /* number of ascii85 encoded bytes     */
+    char              *cmapdata85;   /* ascii85-encoded uncompressed cmap   */
+    char              *cmapdatahex;  /* hex pdf array for the cmap          */
+    l_int32            ncolors;      /* number of colors in cmap            */
+    l_int32            w;            /* image width                         */
+    l_int32            h;            /* image height                        */
+    l_int32            bps;          /* bits/sample; typ. 1, 2, 4 or 8      */
+    l_int32            spp;          /* samples/pixel; typ. 1 or 3          */
+    l_int32            minisblack;   /* tiff g4 photometry                  */
+    l_int32            predictor;    /* flate data has PNG predictors       */
+    size_t             nbytes;       /* number of uncompressed raster bytes */
+    l_int32            res;          /* resolution (ppi)                    */
+};
+typedef struct L_Compressed_Data  L_COMP_DATA;
+
+
+/* ------------------------ Pdf multi-image flags ------------------------ */
+enum {
+    L_FIRST_IMAGE   = 1,    /* first image to be used                      */
+    L_NEXT_IMAGE    = 2,    /* intermediate image; not first or last       */
+    L_LAST_IMAGE    = 3     /* last image to be used                       */
+};
+
+
+/* ------------------ Intermediate pdf generation data -------------------- */
+/*
+ *  This accumulates data for generating a pdf of a single page consisting
+ *  of an arbitrary number of images.
+ *
+ *  None of the strings have a trailing newline.
+ */
+struct L_Pdf_Data
+{
+    char              *title;        /* optional title for pdf              */
+    l_int32            n;            /* number of images                    */
+    l_int32            ncmap;        /* number of colormaps                 */
+    struct L_Ptra     *cida;         /* array of compressed image data      */
+    char              *id;           /* %PDF-1.2 id string                  */
+    char              *obj1;         /* catalog string                      */
+    char              *obj2;         /* metadata string                     */
+    char              *obj3;         /* pages string                        */
+    char              *obj4;         /* page string (variable data)         */
+    char              *obj5;         /* content string (variable data)      */
+    char              *poststream;   /* post-binary-stream string           */
+    char              *trailer;      /* trailer string (variable data)      */
+    struct Pta        *xy;           /* store (xpt, ypt) array              */
+    struct Pta        *wh;           /* store (wpt, hpt) array              */
+    struct Box        *mediabox;     /* bounding region for all images      */
+    struct Sarray     *saprex;       /* pre-binary-stream xobject strings   */
+    struct Sarray     *sacmap;       /* colormap pdf object strings         */
+    struct L_Dna      *objsize;      /* sizes of each pdf string object     */
+    struct L_Dna      *objloc;       /* location of each pdf string object  */
+    l_int32            xrefloc;      /* location of xref                    */
+};
+typedef struct L_Pdf_Data  L_PDF_DATA;
+
+
+#endif  /* LEPTONICA_IMAGEIO_H */
diff --git a/src/jbclass.c b/src/jbclass.c
new file mode 100644 (file)
index 0000000..e031870
--- /dev/null
@@ -0,0 +1,2497 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ * jbclass.c
+ *
+ *     These are functions for unsupervised classification of
+ *     collections of connected components -- either characters or
+ *     words -- in binary images.  They can be used as image
+ *     processing steps in jbig2 compression.
+ *
+ *     Initialization
+ *
+ *         JBCLASSER         *jbRankHausInit()      [rank hausdorff encoder]
+ *         JBCLASSER         *jbCorrelationInit()   [correlation encoder]
+ *         JBCLASSER         *jbCorrelationInitWithoutComponents()  [ditto]
+ *         static JBCLASSER  *jbCorrelationInitInternal()
+ *
+ *     Classify the pages
+ *
+ *         l_int32     jbAddPages()
+ *         l_int32     jbAddPage()
+ *         l_int32     jbAddPageComponents()
+ *
+ *     Rank hausdorff classifier
+ *
+ *         l_int32     jbClassifyRankHaus()
+ *         l_int32     pixHaustest()
+ *         l_int32     pixRankHaustest()
+ *
+ *     Binary correlation classifier
+ *
+ *         l_int32     jbClassifyCorrelation()
+ *
+ *     Determine the image components we start with
+ *
+ *         l_int32     jbGetComponents()
+ *         l_int32     pixWordMaskByDilation()
+ *         l_int32     pixWordBoxesByDilation()
+ *
+ *     Build grayscale composites (templates)
+ *
+ *         PIXA       *jbAccumulateComposites
+ *         PIXA       *jbTemplatesFromComposites
+ *
+ *     Utility functions for Classer
+ *
+ *         JBCLASSER  *jbClasserCreate()
+ *         void        jbClasserDestroy()
+ *
+ *     Utility functions for Data
+ *
+ *         JBDATA     *jbDataSave()
+ *         void        jbDataDestroy()
+ *         l_int32     jbDataWrite()
+ *         JBDATA     *jbDataRead()
+ *         PIXA       *jbDataRender()
+ *         l_int32     jbGetULCorners()
+ *         l_int32     jbGetLLCorners()
+ *
+ *     Static helpers
+ *
+ *         static JBFINDCTX *findSimilarSizedTemplatesInit()
+ *         static l_int32    findSimilarSizedTemplatesNext()
+ *         static void       findSimilarSizedTemplatesDestroy()
+ *         static l_int32    finalPositioningForAlignment()
+ *
+ *     Note: this is NOT an implementation of the JPEG jbig2
+ *     proposed standard encoder, the specifications for which
+ *     can be found at http://www.jpeg.org/jbigpt2.html.
+ *     (See below for a full implementation.)
+ *     It is an implementation of the lower-level part of an encoder that:
+ *
+ *        (1) identifies connected components that are going to be used
+ *        (2) puts them in similarity classes (this is an unsupervised
+ *            classifier), and
+ *        (3) stores the result in a simple file format (2 files,
+ *            one for templates and one for page/coordinate/template-index
+ *            quartets).
+ *
+ *     An actual implementation of the official jbig2 encoder could
+ *     start with parts (1) and (2), and would then compress the quartets
+ *     according to the standards requirements (e.g., Huffman or
+ *     arithmetic coding of coordinate differences and image templates).
+ *
+ *     The low-level part of the encoder provided here has the
+ *     following useful features:
+ *
+ *         - It is accurate in the identification of templates
+ *           and classes because it uses a windowed hausdorff
+ *           distance metric.
+ *         - It is accurate in the placement of the connected
+ *           components, doing a two step process of first aligning
+ *           the the centroids of the template with those of each instance,
+ *           and then making a further correction of up to +- 1 pixel
+ *           in each direction to best align the templates.
+ *         - It is fast because it uses a morphologically based
+ *           matching algorithm to implement the hausdorff criterion,
+ *           and it selects the patterns that are possible matches
+ *           based on their size.
+ *
+ *     We provide two different matching functions, one using Hausdorff
+ *     distance and one using a simple image correlation.
+ *     The Hausdorff method sometimes produces better results for the
+ *     same number of classes, because it gives a relatively small
+ *     effective weight to foreground pixels near the boundary,
+ *     and a relatively  large weight to foreground pixels that are
+ *     not near the boundary.  By effectively ignoring these boundary
+ *     pixels, Hausdorff weighting corresponds better to the expected
+ *     probabilities of the pixel values in a scanned image, where the
+ *     variations in instances of the same printed character are much
+ *     more likely to be in pixels near the boundary.  By contrast,
+ *     the correlation method gives equal weight to all foreground pixels.
+ *
+ *     For best results, use the correlation method.  Correlation takes
+ *     the number of fg pixels in the AND of instance and template,
+ *     divided by the product of the number of fg pixels in instance
+ *     and template.  It compares this with a threshold that, in
+ *     general, depends on the fractional coverage of the template.
+ *     For heavy text, the threshold is raised above that for light
+ *     text,  By using both these parameters (basic threshold and
+ *     adjustment factor for text weight), one has more flexibility
+ *     and can arrive at the fewest substitution errors, although
+ *     this comes at the price of more templates.
+ *
+ *     The strict Hausdorff scoring is not a rank weighting, because a
+ *     single pixel beyond the given distance will cause a match
+ *     failure.  A rank Hausdorff is more robust to non-boundary noise,
+ *     but it is also more susceptible to confusing components that
+ *     should be in different classes.  For implementing a jbig2
+ *     application for visually lossless binary image compression,
+ *     you have two choices:
+ *
+ *        (1) use a 3x3 structuring element (size = 3) and a strict
+ *            Hausdorff comparison (rank = 1.0 in the rank Hausdorff
+ *            function).  This will result in a minimal number of classes,
+ *            but confusion of small characters, such as italic and
+ *            non-italic lower-case 'o', can still occur.
+ *        (2) use the correlation method with a threshold of 0.85
+ *            and a weighting factor of about 0.7.  This will result in
+ *            a larger number of classes, but should not be confused
+ *            either by similar small characters or by extremely
+ *            thick sans serif characters, such as in prog/cootoots.png.
+ *
+ *     As mentioned above, if visual substitution errors must be
+ *     avoided, you should use the correlation method.
+ *
+ *     We provide executables that show how to do the encoding:
+ *         prog/jbrankhaus.c
+ *         prog/jbcorrelation.c
+ *
+ *     The basic flow for correlation classification goes as follows,
+ *     where specific choices have been made for parameters (Hausdorff
+ *     is the same except for initialization):
+ *
+ *             // Initialize and save data in the classer
+ *         JBCLASSER *classer =
+ *             jbCorrelationInit(JB_CONN_COMPS, 0, 0, 0.8, 0.7);
+ *         SARRAY *safiles = getSortedPathnamesInDirectory(directory,
+ *                                                         NULL, 0, 0);
+ *         jbAddPages(classer, safiles);
+ *
+ *             // Save the data in a data structure for serialization,
+ *             // and write it into two files.
+ *         JBDATA *data = jbDataSave(classer);
+ *         jbDataWrite(rootname, data);
+ *
+ *             // Reconstruct (render) the pages from the encoded data.
+ *         PIXA *pixa = jbDataRender(data, FALSE);
+ *
+ *     Adam Langley has built a jbig2 standards-compliant encoder, the
+ *     first one to appear in open source.  You can get this encoder at:
+ *          http://www.imperialviolet.org/jbig2.html
+ *
+ *     It uses arithmetic encoding throughout.  It encodes binary images
+ *     losslessly with a single arithmetic coding over the full image.
+ *     It also does both lossy and lossless encoding from connected
+ *     components, using leptonica to generate the templates representing
+ *     each cluster.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+static const l_int32  L_BUF_SIZE = 512;
+
+    /* For jbClassifyRankHaus(): size of border added around
+     * pix of each c.c., to allow further processing.  This
+     * should be at least the sum of the MAX_DIFF_HEIGHT
+     * (or MAX_DIFF_WIDTH) and one-half the size of the Sel  */
+static const l_int32  JB_ADDED_PIXELS = 6;
+
+    /* For pixHaustest(), pixRankHaustest() and pixCorrelationScore():
+     * choose these to be 2 or greater */
+static const l_int32  MAX_DIFF_WIDTH = 2;  /* use at least 2 */
+static const l_int32  MAX_DIFF_HEIGHT = 2;  /* use at least 2 */
+
+    /* In initialization, you have the option to discard components
+     * (cc, characters or words) that have either width or height larger
+     * than a given size.  This is convenient for jbDataSave(), because
+     * the components are placed onto a regular lattice with cell
+     * dimension equal to the maximum component size.  The default
+     * values are given here.  If you want to save all components,
+     * use a sufficiently large set of dimensions. */
+static const l_int32  MAX_CONN_COMP_WIDTH = 350;  /* default max cc width */
+static const l_int32  MAX_CHAR_COMP_WIDTH = 350;  /* default max char width */
+static const l_int32  MAX_WORD_COMP_WIDTH = 1000;  /* default max word width */
+static const l_int32  MAX_COMP_HEIGHT = 120;  /* default max component height */
+
+    /* Max allowed dilation to merge characters into words */
+static const l_int32  MAX_ALLOWED_DILATION = 25;
+
+    /* This stores the state of a state machine which fetches
+     * similar sized templates */
+struct JbFindTemplatesState
+{
+    JBCLASSER       *classer;    /* classer                               */
+    l_int32          w;          /* desired width                         */
+    l_int32          h;          /* desired height                        */
+    l_int32          i;          /* index into two_by_two step array      */
+    L_DNA           *dna;        /* current number array                  */
+    l_int32          n;          /* current element of dna                */
+};
+typedef struct JbFindTemplatesState JBFINDCTX;
+
+    /* Static initialization function */
+static JBCLASSER * jbCorrelationInitInternal(l_int32 components,
+                       l_int32 maxwidth, l_int32 maxheight, l_float32 thresh,
+                       l_float32 weightfactor, l_int32 keep_components);
+
+    /* Static helper functions */
+static JBFINDCTX * findSimilarSizedTemplatesInit(JBCLASSER *classer, PIX *pixs);
+static l_int32 findSimilarSizedTemplatesNext(JBFINDCTX *context);
+static void findSimilarSizedTemplatesDestroy(JBFINDCTX **pcontext);
+static l_int32 finalPositioningForAlignment(PIX *pixs, l_int32 x, l_int32 y,
+                             l_int32 idelx, l_int32 idely, PIX *pixt,
+                             l_int32 *sumtab, l_int32 *pdx, l_int32 *pdy);
+
+#ifndef NO_CONSOLE_IO
+#define  DEBUG_PLOT_CC             0
+#define  DEBUG_CORRELATION_SCORE   0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*----------------------------------------------------------------------*
+ *                            Initialization                            *
+ *----------------------------------------------------------------------*/
+/*!
+ *  jbRankHausInit()
+ *
+ *      Input:  components (JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS)
+ *              maxwidth (of component; use 0 for default)
+ *              maxheight (of component; use 0 for default)
+ *              size  (of square structuring element; 2, representing
+ *                     2x2 sel, is necessary for reasonable accuracy of
+ *                     small components; combine this with rank ~ 0.97
+ *                     to avoid undue class expansion)
+ *              rank (rank val of match, each way; in [0.5 - 1.0];
+ *                    when using size = 2, 0.97 is a reasonable value)
+ *      Return: jbclasser if OK; NULL on error
+ */
+JBCLASSER *
+jbRankHausInit(l_int32    components,
+               l_int32    maxwidth,
+               l_int32    maxheight,
+               l_int32    size,
+               l_float32  rank)
+{
+JBCLASSER  *classer;
+
+    PROCNAME("jbRankHausInit");
+
+    if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
+        components != JB_WORDS)
+        return (JBCLASSER *)ERROR_PTR("invalid components", procName, NULL);
+    if (size < 1 || size > 10)
+        return (JBCLASSER *)ERROR_PTR("size not reasonable", procName, NULL);
+    if (rank < 0.5 || rank > 1.0)
+        return (JBCLASSER *)ERROR_PTR("rank not in [0.5-1.0]", procName, NULL);
+    if (maxwidth == 0) {
+        if (components == JB_CONN_COMPS)
+            maxwidth = MAX_CONN_COMP_WIDTH;
+        else if (components == JB_CHARACTERS)
+            maxwidth = MAX_CHAR_COMP_WIDTH;
+        else  /* JB_WORDS */
+            maxwidth = MAX_WORD_COMP_WIDTH;
+    }
+    if (maxheight == 0)
+        maxheight = MAX_COMP_HEIGHT;
+
+    if ((classer = jbClasserCreate(JB_RANKHAUS, components)) == NULL)
+        return (JBCLASSER *)ERROR_PTR("classer not made", procName, NULL);
+    classer->maxwidth = maxwidth;
+    classer->maxheight = maxheight;
+    classer->sizehaus = size;
+    classer->rankhaus = rank;
+    classer->dahash = l_dnaHashCreate(5507, 4);  /* 5507 is prime */
+    return classer;
+}
+
+
+/*!
+ *  jbCorrelationInit()
+ *
+ *      Input:  components (JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS)
+ *              maxwidth (of component; use 0 for default)
+ *              maxheight (of component; use 0 for default)
+ *              thresh (value for correlation score: in [0.4 - 0.98])
+ *              weightfactor (corrects thresh for thick characters [0.0 - 1.0])
+ *      Return: jbclasser if OK; NULL on error
+ *
+ *  Notes:
+ *      (1) For scanned text, suggested input values are:
+ *            thresh ~ [0.8 - 0.85]
+ *            weightfactor ~ [0.5 - 0.6]
+ *      (2) For electronically generated fonts (e.g., rasterized pdf),
+ *          a very high thresh (e.g., 0.95) will not cause a significant
+ *          increase in the number of classes.
+ */
+JBCLASSER *
+jbCorrelationInit(l_int32    components,
+                  l_int32    maxwidth,
+                  l_int32    maxheight,
+                  l_float32  thresh,
+                  l_float32  weightfactor)
+{
+    return jbCorrelationInitInternal(components, maxwidth, maxheight, thresh,
+                                     weightfactor, 1);
+}
+
+/*!
+ *  jbCorrelationInitWithoutComponents()
+ *
+ *      Input:  same as jbCorrelationInit
+ *      Output: same as jbCorrelationInit
+ *
+ *  Note: acts the same as jbCorrelationInit(), but the resulting
+ *        object doesn't keep a list of all the components.
+ */
+JBCLASSER *
+jbCorrelationInitWithoutComponents(l_int32    components,
+                                   l_int32    maxwidth,
+                                   l_int32    maxheight,
+                                   l_float32  thresh,
+                                   l_float32  weightfactor)
+{
+    return jbCorrelationInitInternal(components, maxwidth, maxheight, thresh,
+                                     weightfactor, 0);
+}
+
+
+static JBCLASSER *
+jbCorrelationInitInternal(l_int32    components,
+                          l_int32    maxwidth,
+                          l_int32    maxheight,
+                          l_float32  thresh,
+                          l_float32  weightfactor,
+                          l_int32    keep_components)
+{
+JBCLASSER  *classer;
+
+    PROCNAME("jbCorrelationInitInternal");
+
+    if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
+        components != JB_WORDS)
+        return (JBCLASSER *)ERROR_PTR("invalid components", procName, NULL);
+    if (thresh < 0.4 || thresh > 0.98)
+        return (JBCLASSER *)ERROR_PTR("thresh not in range [0.4 - 0.98]",
+                procName, NULL);
+    if (weightfactor < 0.0 || weightfactor > 1.0)
+        return (JBCLASSER *)ERROR_PTR("weightfactor not in range [0.0 - 1.0]",
+                procName, NULL);
+    if (maxwidth == 0) {
+        if (components == JB_CONN_COMPS)
+            maxwidth = MAX_CONN_COMP_WIDTH;
+        else if (components == JB_CHARACTERS)
+            maxwidth = MAX_CHAR_COMP_WIDTH;
+        else  /* JB_WORDS */
+            maxwidth = MAX_WORD_COMP_WIDTH;
+    }
+    if (maxheight == 0)
+        maxheight = MAX_COMP_HEIGHT;
+
+
+    if ((classer = jbClasserCreate(JB_CORRELATION, components)) == NULL)
+        return (JBCLASSER *)ERROR_PTR("classer not made", procName, NULL);
+    classer->maxwidth = maxwidth;
+    classer->maxheight = maxheight;
+    classer->thresh = thresh;
+    classer->weightfactor = weightfactor;
+    classer->dahash = l_dnaHashCreate(5507, 4);  /* 5507 is prime */
+    classer->keep_pixaa = keep_components;
+    return classer;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                       Classify the pages                             *
+ *----------------------------------------------------------------------*/
+/*!
+ *  jbAddPages()
+ *
+ *      Input:  jbclasser
+ *              safiles (of page image file names)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Note:
+ *      (1) jbclasser makes a copy of the array of file names.
+ *      (2) The caller is still responsible for destroying the input array.
+ */
+l_int32
+jbAddPages(JBCLASSER  *classer,
+           SARRAY     *safiles)
+{
+l_int32  i, nfiles;
+char    *fname;
+PIX     *pix;
+
+    PROCNAME("jbAddPages");
+
+    if (!classer)
+        return ERROR_INT("classer not defined", procName, 1);
+    if (!safiles)
+        return ERROR_INT("safiles not defined", procName, 1);
+
+    classer->safiles = sarrayCopy(safiles);
+    nfiles = sarrayGetCount(safiles);
+    for (i = 0; i < nfiles; i++) {
+        fname = sarrayGetString(safiles, i, L_NOCOPY);
+        if ((pix = pixRead(fname)) == NULL) {
+            L_WARNING("image file %d not read\n", procName, i);
+            continue;
+        }
+        if (pixGetDepth(pix) != 1) {
+            L_WARNING("image file %d not 1 bpp\n", procName, i);
+            continue;
+        }
+        jbAddPage(classer, pix);
+        pixDestroy(&pix);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  jbAddPage()
+ *
+ *      Input:  jbclasser
+ *              pixs (of input page)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+jbAddPage(JBCLASSER  *classer,
+          PIX        *pixs)
+{
+BOXA  *boxas;
+PIXA  *pixas;
+
+    PROCNAME("jbAddPage");
+
+    if (!classer)
+        return ERROR_INT("classer not defined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+    classer->w = pixGetWidth(pixs);
+    classer->h = pixGetHeight(pixs);
+
+        /* Get the appropriate components and their bounding boxes */
+    if (jbGetComponents(pixs, classer->components, classer->maxwidth,
+                        classer->maxheight, &boxas, &pixas)) {
+        return ERROR_INT("components not made", procName, 1);
+    }
+
+    jbAddPageComponents(classer, pixs, boxas, pixas);
+    boxaDestroy(&boxas);
+    pixaDestroy(&pixas);
+    return 0;
+}
+
+
+/*!
+ *  jbAddPageComponents()
+ *
+ *      Input:  jbclasser
+ *              pixs (of input page)
+ *              boxas (b.b. of components for this page)
+ *              pixas (components for this page)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) If there are no components on the page, we don't require input
+ *          of empty boxas or pixas, although that's the typical situation.
+ */
+l_int32
+jbAddPageComponents(JBCLASSER  *classer,
+                    PIX        *pixs,
+                    BOXA       *boxas,
+                    PIXA       *pixas)
+{
+l_int32  n;
+
+    PROCNAME("jbAddPageComponents");
+
+    if (!classer)
+        return ERROR_INT("classer not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pix not defined", procName, 1);
+
+        /* Test for no components on the current page.  Always update the
+         * number of pages processed, even if nothing is on it. */
+    if (!boxas || !pixas || (boxaGetCount(boxas) == 0)) {
+        classer->npages++;
+        return 0;
+    }
+
+        /* Get classes.  For hausdorff, it uses a specified size of
+         * structuring element and specified rank.  For correlation,
+         * it uses a specified threshold. */
+    if (classer->method == JB_RANKHAUS) {
+        if (jbClassifyRankHaus(classer, boxas, pixas))
+            return ERROR_INT("rankhaus classification failed", procName, 1);
+    } else {  /* classer->method == JB_CORRELATION */
+        if (jbClassifyCorrelation(classer, boxas, pixas))
+            return ERROR_INT("correlation classification failed", procName, 1);
+    }
+
+        /* Find the global UL corners, adjusted for each instance so
+         * that the class template and instance will have their
+         * centroids in the same place.  Then the template can be
+         * used to replace the instance. */
+    if (jbGetULCorners(classer, pixs, boxas))
+        return ERROR_INT("UL corners not found", procName, 1);
+
+        /* Update total component counts and number of pages processed. */
+    n = boxaGetCount(boxas);
+    classer->baseindex += n;
+    numaAddNumber(classer->nacomps, n);
+    classer->npages++;
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *         Classification using windowed rank hausdorff metric          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  jbClassifyRankHaus()
+ *
+ *      Input:  jbclasser
+ *              boxa (of new components for classification)
+ *              pixas (of new components for classification)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+jbClassifyRankHaus(JBCLASSER  *classer,
+                   BOXA       *boxa,
+                   PIXA       *pixas)
+{
+l_int32     n, nt, i, wt, ht, iclass, size, found, testval;
+l_int32    *sumtab;
+l_int32     npages, area1, area3;
+l_int32    *tab8;
+l_float32   rank, x1, y1, x2, y2;
+BOX        *box;
+NUMA       *naclass, *napage;
+NUMA       *nafg;   /* fg area of all instances */
+NUMA       *nafgt;  /* fg area of all templates */
+JBFINDCTX  *findcontext;
+L_DNAHASH  *dahash;
+PIX        *pix, *pix1, *pix2, *pix3, *pix4;
+PIXA       *pixa, *pixa1, *pixa2, *pixat, *pixatd;
+PIXAA      *pixaa;
+PTA        *pta, *ptac, *ptact;
+SEL        *sel;
+
+    PROCNAME("jbClassifyRankHaus");
+
+    if (!classer)
+        return ERROR_INT("classer not found", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not found", procName, 1);
+    if (!pixas)
+        return ERROR_INT("pixas not found", procName, 1);
+
+    npages = classer->npages;
+    size = classer->sizehaus;
+    sel = selCreateBrick(size, size, size / 2, size / 2, SEL_HIT);
+
+        /* Generate the bordered pixa, with and without dilation.
+         * pixa1 and pixa2 contain all the input components. */
+    n = pixaGetCount(pixas);
+    pixa1 = pixaCreate(n);
+    pixa2 = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixas, i, L_CLONE);
+        pix1 = pixAddBorderGeneral(pix, JB_ADDED_PIXELS, JB_ADDED_PIXELS,
+                    JB_ADDED_PIXELS, JB_ADDED_PIXELS, 0);
+        pix2 = pixDilate(NULL, pix1, sel);
+        pixaAddPix(pixa1, pix1, L_INSERT);   /* un-dilated */
+        pixaAddPix(pixa2, pix2, L_INSERT);   /* dilated */
+        pixDestroy(&pix);
+    }
+
+        /* Get the centroids of all the bordered images.
+         * These are relative to the UL corner of each (bordered) pix.  */
+    pta = pixaCentroids(pixa1);  /* centroids for this page; use here */
+    ptac = classer->ptac;  /* holds centroids of components up to this page */
+    ptaJoin(ptac, pta, 0, -1);  /* save centroids of all components */
+    ptact = classer->ptact;  /* holds centroids of templates */
+
+        /* Use these to save the class and page of each component. */
+    naclass = classer->naclass;
+    napage = classer->napage;
+    sumtab = makePixelSumTab8();
+
+        /* Store the unbordered pix in a pixaa, in a hierarchical
+         * set of arrays.  There is one pixa for each class,
+         * and the pix in each pixa are all the instances found
+         * of that class.  This is actually more than one would need
+         * for a jbig2 encoder, but there are two reasons to keep
+         * them around: (1) the set of instances for each class
+         * can be used to make an improved binary (or, better,
+         * a grayscale) template, rather than simply using the first
+         * one in the set; (2) we can investigate the failures
+         * of the classifier.  This pixaa grows as we process
+         * successive pages. */
+    pixaa = classer->pixaa;
+
+        /* arrays to store class exemplars (templates) */
+    pixat = classer->pixat;   /* un-dilated */
+    pixatd = classer->pixatd;   /* dilated */
+
+        /* Fill up the pixaa tree with the template exemplars as
+         * the first pix in each pixa.  As we add each pix,
+         * we also add the associated box to the pixa.
+         * We also keep track of the centroid of each pix,
+         * and use the difference between centroids (of the
+         * pix with the exemplar we are checking it with)
+         * to align the two when checking that the Hausdorff
+         * distance does not exceed a threshold.
+         * The threshold is set by the Sel used for dilating.
+         * For example, a 3x3 brick, sel_3, corresponds to a
+         * Hausdorff distance of 1.  In general, for an NxN brick,
+         * with N odd, corresponds to a Hausdorff distance of (N - 1)/2.
+         * It turns out that we actually need to use a sel of size 2x2
+         * to avoid small bad components when there is a halftone image
+         * from which components can be chosen.
+         * The larger the Sel you use, the fewer the number of classes,
+         * and the greater the likelihood of putting semantically
+         * different objects in the same class.  For simplicity,
+         * we do this separately for the case of rank == 1.0 (exact
+         * match within the Hausdorff distance) and rank < 1.0.  */
+    rank = classer->rankhaus;
+    dahash = classer->dahash;
+    if (rank == 1.0) {
+        for (i = 0; i < n; i++) {
+            pix1 = pixaGetPix(pixa1, i, L_CLONE);
+            pix2 = pixaGetPix(pixa2, i, L_CLONE);
+            ptaGetPt(pta, i, &x1, &y1);
+            nt = pixaGetCount(pixat);  /* number of templates */
+            found = FALSE;
+            findcontext = findSimilarSizedTemplatesInit(classer, pix1);
+            while ((iclass = findSimilarSizedTemplatesNext(findcontext)) > -1) {
+                    /* Find score for this template */
+                pix3 = pixaGetPix(pixat, iclass, L_CLONE);
+                pix4 = pixaGetPix(pixatd, iclass, L_CLONE);
+                ptaGetPt(ptact, iclass, &x2, &y2);
+                testval = pixHaustest(pix1, pix2, pix3, pix4, x1 - x2, y1 - y2,
+                                      MAX_DIFF_WIDTH, MAX_DIFF_HEIGHT);
+                pixDestroy(&pix3);
+                pixDestroy(&pix4);
+                if (testval == 1) {
+                    found = TRUE;
+                    numaAddNumber(naclass, iclass);
+                    numaAddNumber(napage, npages);
+                    if (classer->keep_pixaa) {
+                        pixa = pixaaGetPixa(pixaa, iclass, L_CLONE);
+                        pix = pixaGetPix(pixas, i, L_CLONE);
+                        pixaAddPix(pixa, pix, L_INSERT);
+                        box = boxaGetBox(boxa, i, L_CLONE);
+                        pixaAddBox(pixa, box, L_INSERT);
+                        pixaDestroy(&pixa);
+                    }
+                    break;
+                }
+            }
+            findSimilarSizedTemplatesDestroy(&findcontext);
+            if (found == FALSE) {  /* new class */
+                numaAddNumber(naclass, nt);
+                numaAddNumber(napage, npages);
+                pixa = pixaCreate(0);
+                pix = pixaGetPix(pixas, i, L_CLONE);  /* unbordered instance */
+                pixaAddPix(pixa, pix, L_INSERT);
+                wt = pixGetWidth(pix);
+                ht = pixGetHeight(pix);
+                l_dnaHashAdd(dahash, ht * wt, nt);
+                box = boxaGetBox(boxa, i, L_CLONE);
+                pixaAddBox(pixa, box, L_INSERT);
+                pixaaAddPixa(pixaa, pixa, L_INSERT);  /* unbordered instance */
+                ptaAddPt(ptact, x1, y1);
+                pixaAddPix(pixat, pix1, L_INSERT);  /* bordered template */
+                pixaAddPix(pixatd, pix2, L_INSERT);  /* bordered dil template */
+            } else {  /* don't save them */
+                pixDestroy(&pix1);
+                pixDestroy(&pix2);
+            }
+        }
+    } else {  /* rank < 1.0 */
+        if ((nafg = pixaCountPixels(pixas)) == NULL)  /* areas for this page */
+            return ERROR_INT("nafg not made", procName, 1);
+        nafgt = classer->nafgt;
+        tab8 = makePixelSumTab8();
+        for (i = 0; i < n; i++) {   /* all instances on this page */
+            pix1 = pixaGetPix(pixa1, i, L_CLONE);
+            numaGetIValue(nafg, i, &area1);
+            pix2 = pixaGetPix(pixa2, i, L_CLONE);
+            ptaGetPt(pta, i, &x1, &y1);   /* use pta for this page */
+            nt = pixaGetCount(pixat);  /* number of templates */
+            found = FALSE;
+            findcontext = findSimilarSizedTemplatesInit(classer, pix1);
+            while ((iclass = findSimilarSizedTemplatesNext(findcontext)) > -1) {
+                    /* Find score for this template */
+                pix3 = pixaGetPix(pixat, iclass, L_CLONE);
+                numaGetIValue(nafgt, iclass, &area3);
+                pix4 = pixaGetPix(pixatd, iclass, L_CLONE);
+                ptaGetPt(ptact, iclass, &x2, &y2);
+                testval = pixRankHaustest(pix1, pix2, pix3, pix4,
+                                          x1 - x2, y1 - y2,
+                                          MAX_DIFF_WIDTH, MAX_DIFF_HEIGHT,
+                                          area1, area3, rank, tab8);
+                pixDestroy(&pix3);
+                pixDestroy(&pix4);
+                if (testval == 1) {  /* greedy match; take the first */
+                    found = TRUE;
+                    numaAddNumber(naclass, iclass);
+                    numaAddNumber(napage, npages);
+                    if (classer->keep_pixaa) {
+                        pixa = pixaaGetPixa(pixaa, iclass, L_CLONE);
+                        pix = pixaGetPix(pixas, i, L_CLONE);
+                        pixaAddPix(pixa, pix, L_INSERT);
+                        box = boxaGetBox(boxa, i, L_CLONE);
+                        pixaAddBox(pixa, box, L_INSERT);
+                        pixaDestroy(&pixa);
+                    }
+                    break;
+                }
+            }
+            findSimilarSizedTemplatesDestroy(&findcontext);
+            if (found == FALSE) {  /* new class */
+                numaAddNumber(naclass, nt);
+                numaAddNumber(napage, npages);
+                pixa = pixaCreate(0);
+                pix = pixaGetPix(pixas, i, L_CLONE);  /* unbordered instance */
+                pixaAddPix(pixa, pix, L_INSERT);
+                wt = pixGetWidth(pix);
+                ht = pixGetHeight(pix);
+                l_dnaHashAdd(dahash, ht * wt, nt);
+                box = boxaGetBox(boxa, i, L_CLONE);
+                pixaAddBox(pixa, box, L_INSERT);
+                pixaaAddPixa(pixaa, pixa, L_INSERT);  /* unbordered instance */
+                ptaAddPt(ptact, x1, y1);
+                pixaAddPix(pixat, pix1, L_INSERT);  /* bordered template */
+                pixaAddPix(pixatd, pix2, L_INSERT);  /* ditto */
+                numaAddNumber(nafgt, area1);
+            } else {  /* don't save them */
+                pixDestroy(&pix1);
+                pixDestroy(&pix2);
+            }
+        }
+        LEPT_FREE(tab8);
+        numaDestroy(&nafg);
+    }
+    classer->nclass = pixaGetCount(pixat);
+
+    LEPT_FREE(sumtab);
+    ptaDestroy(&pta);
+    pixaDestroy(&pixa1);
+    pixaDestroy(&pixa2);
+    selDestroy(&sel);
+    return 0;
+}
+
+
+/*!
+ *  pixHaustest()
+ *
+ *      Input:  pix1   (new pix, not dilated)
+ *              pix2   (new pix, dilated)
+ *              pix3   (exemplar pix, not dilated)
+ *              pix4   (exemplar pix, dilated)
+ *              delx   (x comp of centroid difference)
+ *              dely   (y comp of centroid difference)
+ *              maxdiffw (max width difference of pix1 and pix2)
+ *              maxdiffh (max height difference of pix1 and pix2)
+ *      Return: 0 (FALSE) if no match, 1 (TRUE) if the new
+ *              pix is in the same class as the exemplar.
+ *
+ *  Note: we check first that the two pix are roughly
+ *  the same size.  Only if they meet that criterion do
+ *  we compare the bitmaps.  The Hausdorff is a 2-way
+ *  check.  The centroid difference is used to align the two
+ *  images to the nearest integer for each of the checks.
+ *  These check that the dilated image of one contains
+ *  ALL the pixels of the undilated image of the other.
+ *  Checks are done in both direction.  A single pixel not
+ *  contained in either direction results in failure of the test.
+ */
+l_int32
+pixHaustest(PIX       *pix1,
+            PIX       *pix2,
+            PIX       *pix3,
+            PIX       *pix4,
+            l_float32  delx,   /* x(1) - x(3) */
+            l_float32  dely,   /* y(1) - y(3) */
+            l_int32    maxdiffw,
+            l_int32    maxdiffh)
+{
+l_int32  wi, hi, wt, ht, delw, delh, idelx, idely, boolmatch;
+PIX     *pixt;
+
+        /* Eliminate possible matches based on size difference */
+    wi = pixGetWidth(pix1);
+    hi = pixGetHeight(pix1);
+    wt = pixGetWidth(pix3);
+    ht = pixGetHeight(pix3);
+    delw = L_ABS(wi - wt);
+    if (delw > maxdiffw)
+        return FALSE;
+    delh = L_ABS(hi - ht);
+    if (delh > maxdiffh)
+        return FALSE;
+
+        /* Round difference in centroid location to nearest integer;
+         * use this as a shift when doing the matching. */
+    if (delx >= 0)
+        idelx = (l_int32)(delx + 0.5);
+    else
+        idelx = (l_int32)(delx - 0.5);
+    if (dely >= 0)
+        idely = (l_int32)(dely + 0.5);
+    else
+        idely = (l_int32)(dely - 0.5);
+
+        /*  Do 1-direction hausdorff, checking that every pixel in pix1
+         *  is within a dilation distance of some pixel in pix3.  Namely,
+         *  that pix4 entirely covers pix1:
+         *       pixt = pixSubtract(NULL, pix1, pix4), including shift
+         *  where pixt has no ON pixels.  */
+    pixt = pixCreateTemplate(pix1);
+    pixRasterop(pixt, 0, 0, wi, hi, PIX_SRC, pix1, 0, 0);
+    pixRasterop(pixt, idelx, idely, wi, hi, PIX_DST & PIX_NOT(PIX_SRC),
+                pix4, 0, 0);
+    pixZero(pixt, &boolmatch);
+    if (boolmatch == 0) {
+        pixDestroy(&pixt);
+        return FALSE;
+    }
+
+        /*  Do 1-direction hausdorff, checking that every pixel in pix3
+         *  is within a dilation distance of some pixel in pix1.  Namely,
+         *  that pix2 entirely covers pix3:
+         *      pixSubtract(pixt, pix3, pix2), including shift
+         *  where pixt has no ON pixels. */
+    pixRasterop(pixt, idelx, idely, wt, ht, PIX_SRC, pix3, 0, 0);
+    pixRasterop(pixt, 0, 0, wt, ht, PIX_DST & PIX_NOT(PIX_SRC), pix2, 0, 0);
+    pixZero(pixt, &boolmatch);
+    pixDestroy(&pixt);
+    return boolmatch;
+}
+
+
+/*!
+ *  pixRankHaustest()
+ *
+ *      Input:  pix1   (new pix, not dilated)
+ *              pix2   (new pix, dilated)
+ *              pix3   (exemplar pix, not dilated)
+ *              pix4   (exemplar pix, dilated)
+ *              delx   (x comp of centroid difference)
+ *              dely   (y comp of centroid difference)
+ *              maxdiffw (max width difference of pix1 and pix2)
+ *              maxdiffh (max height difference of pix1 and pix2)
+ *              area1  (fg pixels in pix1)
+ *              area3  (fg pixels in pix3)
+ *              rank   (rank value of test, each way)
+ *              tab8   (table of pixel sums for byte)
+ *      Return: 0 (FALSE) if no match, 1 (TRUE) if the new
+ *                 pix is in the same class as the exemplar.
+ *
+ *  Note: we check first that the two pix are roughly
+ *  the same size.  Only if they meet that criterion do
+ *  we compare the bitmaps.  We convert the rank value to
+ *  a number of pixels by multiplying the rank fraction by the number
+ *  of pixels in the undilated image.  The Hausdorff is a 2-way
+ *  check.  The centroid difference is used to align the two
+ *  images to the nearest integer for each of the checks.
+ *  The rank hausdorff checks that the dilated image of one
+ *  contains the rank fraction of the pixels of the undilated
+ *  image of the other.   Checks are done in both direction.
+ *  Failure of the test in either direction results in failure
+ *  of the test.
+ */
+l_int32
+pixRankHaustest(PIX       *pix1,
+                PIX       *pix2,
+                PIX       *pix3,
+                PIX       *pix4,
+                l_float32  delx,   /* x(1) - x(3) */
+                l_float32  dely,   /* y(1) - y(3) */
+                l_int32    maxdiffw,
+                l_int32    maxdiffh,
+                l_int32    area1,
+                l_int32    area3,
+                l_float32  rank,
+                l_int32   *tab8)
+{
+l_int32  wi, hi, wt, ht, delw, delh, idelx, idely, boolmatch;
+l_int32  thresh1, thresh3;
+PIX     *pixt;
+
+        /* Eliminate possible matches based on size difference */
+    wi = pixGetWidth(pix1);
+    hi = pixGetHeight(pix1);
+    wt = pixGetWidth(pix3);
+    ht = pixGetHeight(pix3);
+    delw = L_ABS(wi - wt);
+    if (delw > maxdiffw)
+        return FALSE;
+    delh = L_ABS(hi - ht);
+    if (delh > maxdiffh)
+        return FALSE;
+
+        /* Upper bounds in remaining pixels for allowable match */
+    thresh1 = (l_int32)(area1 * (1. - rank) + 0.5);
+    thresh3 = (l_int32)(area3 * (1. - rank) + 0.5);
+
+        /* Round difference in centroid location to nearest integer;
+         * use this as a shift when doing the matching. */
+    if (delx >= 0)
+        idelx = (l_int32)(delx + 0.5);
+    else
+        idelx = (l_int32)(delx - 0.5);
+    if (dely >= 0)
+        idely = (l_int32)(dely + 0.5);
+    else
+        idely = (l_int32)(dely - 0.5);
+
+        /*  Do 1-direction hausdorff, checking that every pixel in pix1
+         *  is within a dilation distance of some pixel in pix3.  Namely,
+         *  that pix4 entirely covers pix1:
+         *       pixt = pixSubtract(NULL, pix1, pix4), including shift
+         *  where pixt has no ON pixels.  */
+    pixt = pixCreateTemplate(pix1);
+    pixRasterop(pixt, 0, 0, wi, hi, PIX_SRC, pix1, 0, 0);
+    pixRasterop(pixt, idelx, idely, wi, hi, PIX_DST & PIX_NOT(PIX_SRC),
+                pix4, 0, 0);
+    pixThresholdPixelSum(pixt, thresh1, &boolmatch, tab8);
+    if (boolmatch == 1) { /* above thresh1 */
+        pixDestroy(&pixt);
+        return FALSE;
+    }
+
+        /*  Do 1-direction hausdorff, checking that every pixel in pix3
+         *  is within a dilation distance of some pixel in pix1.  Namely,
+         *  that pix2 entirely covers pix3:
+         *      pixSubtract(pixt, pix3, pix2), including shift
+         *  where pixt has no ON pixels. */
+    pixRasterop(pixt, idelx, idely, wt, ht, PIX_SRC, pix3, 0, 0);
+    pixRasterop(pixt, 0, 0, wt, ht, PIX_DST & PIX_NOT(PIX_SRC), pix2, 0, 0);
+    pixThresholdPixelSum(pixt, thresh3, &boolmatch, tab8);
+    pixDestroy(&pixt);
+    if (boolmatch == 1)  /* above thresh3 */
+        return FALSE;
+    else
+        return TRUE;
+}
+
+
+/*----------------------------------------------------------------------*
+ *            Classification using windowed correlation score           *
+ *----------------------------------------------------------------------*/
+/*!
+ *  jbClassifyCorrelation()
+ *
+ *      Input:  jbclasser
+ *              boxa (of new components for classification)
+ *              pixas (of new components for classification)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+jbClassifyCorrelation(JBCLASSER  *classer,
+                      BOXA       *boxa,
+                      PIXA       *pixas)
+{
+l_int32     n, nt, i, iclass, wt, ht, found, area, area1, area2, npages,
+            overthreshold;
+l_int32    *sumtab, *centtab;
+l_uint32   *row, word;
+l_float32   x1, y1, x2, y2, xsum, ysum;
+l_float32   thresh, weight, threshold;
+BOX        *box;
+NUMA       *naclass, *napage;
+NUMA       *nafgt;   /* fg area of all templates */
+NUMA       *naarea;   /* w * h area of all templates */
+JBFINDCTX  *findcontext;
+L_DNAHASH  *dahash;
+PIX        *pix, *pix1, *pix2;
+PIXA       *pixa, *pixa1, *pixat;
+PIXAA      *pixaa;
+PTA        *pta, *ptac, *ptact;
+l_int32    *pixcts;  /* pixel counts of each pixa */
+l_int32   **pixrowcts;  /* row-by-row pixel counts of each pixa */
+l_int32     x, y, rowcount, downcount, wpl;
+l_uint8     byte;
+
+    PROCNAME("jbClassifyCorrelation");
+
+    if (!classer)
+        return ERROR_INT("classer not found", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not found", procName, 1);
+    if (!pixas)
+        return ERROR_INT("pixas not found", procName, 1);
+
+    npages = classer->npages;
+
+        /* Generate the bordered pixa, which contains all the the
+         * input components.  This will not be saved.   */
+    n = pixaGetCount(pixas);
+    pixa1 = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixas, i, L_CLONE);
+        pix1 = pixAddBorderGeneral(pix, JB_ADDED_PIXELS, JB_ADDED_PIXELS,
+                    JB_ADDED_PIXELS, JB_ADDED_PIXELS, 0);
+        pixaAddPix(pixa1, pix1, L_INSERT);
+        pixDestroy(&pix);
+    }
+
+        /* Use these to save the class and page of each component. */
+    naclass = classer->naclass;
+    napage = classer->napage;
+
+        /* Get the number of fg pixels in each component.  */
+    nafgt = classer->nafgt;    /* holds fg areas of the templates */
+    sumtab = makePixelSumTab8();
+
+    pixcts = (l_int32 *)LEPT_CALLOC(n, sizeof(*pixcts));
+    pixrowcts = (l_int32 **)LEPT_CALLOC(n, sizeof(*pixrowcts));
+    centtab = makePixelCentroidTab8();
+    if (!pixcts || !pixrowcts || !centtab)
+        return ERROR_INT("calloc fail in pix*cts or centtab", procName, 1);
+
+        /* Count the "1" pixels in each row of the pix in pixa1; this
+         * allows pixCorrelationScoreThresholded to abort early if a match
+         * is impossible.  This loop merges three calculations: the total
+         * number of "1" pixels, the number of "1" pixels in each row, and
+         * the centroid.  The centroids are relative to the UL corner of
+         * each (bordered) pix.  The pixrowcts[i][y] are the total number
+         * of fg pixels in pixa[i] below row y. */
+    pta = ptaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa1, i, L_CLONE);
+        pixrowcts[i] = (l_int32 *)LEPT_CALLOC(pixGetHeight(pix),
+                                         sizeof(**pixrowcts));
+        xsum = 0;
+        ysum = 0;
+        wpl = pixGetWpl(pix);
+        row = pixGetData(pix) + (pixGetHeight(pix) - 1) * wpl;
+        downcount = 0;
+        for (y = pixGetHeight(pix) - 1; y >= 0; y--, row -= wpl) {
+            pixrowcts[i][y] = downcount;
+            rowcount = 0;
+            for (x = 0; x < wpl; x++) {
+                word = row[x];
+                byte = word & 0xff;
+                rowcount += sumtab[byte];
+                xsum += centtab[byte] + (x * 32 + 24) * sumtab[byte];
+                byte = (word >> 8) & 0xff;
+                rowcount += sumtab[byte];
+                xsum += centtab[byte] + (x * 32 + 16) * sumtab[byte];
+                byte = (word >> 16) & 0xff;
+                rowcount += sumtab[byte];
+                xsum += centtab[byte] + (x * 32 + 8) * sumtab[byte];
+                byte = (word >> 24) & 0xff;
+                rowcount += sumtab[byte];
+                xsum += centtab[byte] + x * 32 * sumtab[byte];
+            }
+            downcount += rowcount;
+            ysum += rowcount * y;
+        }
+        pixcts[i] = downcount;
+        ptaAddPt(pta,
+                 xsum / (l_float32)downcount, ysum / (l_float32)downcount);
+        pixDestroy(&pix);
+    }
+
+    ptac = classer->ptac;  /* holds centroids of components up to this page */
+    ptaJoin(ptac, pta, 0, -1);  /* save centroids of all components */
+    ptact = classer->ptact;  /* holds centroids of templates */
+
+    /* Store the unbordered pix in a pixaa, in a hierarchical
+     * set of arrays.  There is one pixa for each class,
+     * and the pix in each pixa are all the instances found
+     * of that class.  This is actually more than one would need
+     * for a jbig2 encoder, but there are two reasons to keep
+     * them around: (1) the set of instances for each class
+     * can be used to make an improved binary (or, better,
+     * a grayscale) template, rather than simply using the first
+     * one in the set; (2) we can investigate the failures
+     * of the classifier.  This pixaa grows as we process
+     * successive pages. */
+    pixaa = classer->pixaa;
+
+        /* Array to store class exemplars */
+    pixat = classer->pixat;
+
+        /* Fill up the pixaa tree with the template exemplars as
+         * the first pix in each pixa.  As we add each pix,
+         * we also add the associated box to the pixa.
+         * We also keep track of the centroid of each pix,
+         * and use the difference between centroids (of the
+         * pix with the exemplar we are checking it with)
+         * to align the two when checking that the correlation
+         * score exceeds a threshold.  The correlation score
+         * is given by the square of the area of the AND
+         * between aligned instance and template, divided by
+         * the product of areas of each image.  For identical
+         * template and instance, the score is 1.0.
+         * If the threshold is too small, non-equivalent instances
+         * will be placed in the same class; if too large, there will
+         * be an unnecessary division of classes representing the
+         * same character.  The weightfactor adds in some of the
+         * difference (1.0 - thresh), depending on the heaviness
+         * of the template (measured as the fraction of fg pixels). */
+    thresh = classer->thresh;
+    weight = classer->weightfactor;
+    naarea = classer->naarea;
+    dahash = classer->dahash;
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixa1, i, L_CLONE);
+        area1 = pixcts[i];
+        ptaGetPt(pta, i, &x1, &y1);  /* centroid for this instance */
+        nt = pixaGetCount(pixat);
+        found = FALSE;
+        findcontext = findSimilarSizedTemplatesInit(classer, pix1);
+        while ( (iclass = findSimilarSizedTemplatesNext(findcontext)) > -1) {
+                /* Get the template */
+            pix2 = pixaGetPix(pixat, iclass, L_CLONE);
+            numaGetIValue(nafgt, iclass, &area2);
+            ptaGetPt(ptact, iclass, &x2, &y2);  /* template centroid */
+
+                /* Find threshold for this template */
+            if (weight > 0.0) {
+                numaGetIValue(naarea, iclass, &area);
+                threshold = thresh + (1. - thresh) * weight * area2 / area;
+            } else {
+                threshold = thresh;
+            }
+
+                /* Find score for this template */
+            overthreshold = pixCorrelationScoreThresholded(pix1, pix2,
+                                         area1, area2, x1 - x2, y1 - y2,
+                                         MAX_DIFF_WIDTH, MAX_DIFF_HEIGHT,
+                                         sumtab, pixrowcts[i], threshold);
+#if DEBUG_CORRELATION_SCORE
+            {
+                l_float32 score, testscore;
+                l_int32 count, testcount;
+                pixCorrelationScore(pix1, pix2, area1, area2, x1 - x2, y1 - y2,
+                                    MAX_DIFF_WIDTH, MAX_DIFF_HEIGHT,
+                                    sumtab, &score);
+
+                pixCorrelationScoreSimple(pix1, pix2, area1, area2,
+                                          x1 - x2, y1 - y2, MAX_DIFF_WIDTH,
+                                          MAX_DIFF_HEIGHT, sumtab, &testscore);
+                count = (l_int32)rint(sqrt(score * area1 * area2));
+                testcount = (l_int32)rint(sqrt(testscore * area1 * area2));
+                if ((score >= threshold) != (testscore >= threshold)) {
+                    fprintf(stderr, "Correlation score mismatch: "
+                            "%d(%g,%d) vs %d(%g,%d) (%g)\n",
+                            count, score, score >= threshold,
+                            testcount, testscore, testscore >= threshold,
+                            score - testscore);
+                }
+
+                if ((score >= threshold) != overthreshold) {
+                    fprintf(stderr, "Mismatch between correlation/threshold "
+                            "comparison: %g(%g,%d) >= %g(%g) vs %s\n",
+                            score, score*area1*area2, count, threshold,
+                            threshold*area1*area2,
+                            (overthreshold ? "true" : "false"));
+                }
+            }
+#endif  /* DEBUG_CORRELATION_SCORE */
+            pixDestroy(&pix2);
+
+            if (overthreshold) {  /* greedy match */
+                found = TRUE;
+                numaAddNumber(naclass, iclass);
+                numaAddNumber(napage, npages);
+                if (classer->keep_pixaa) {
+                        /* We are keeping a record of all components */
+                    pixa = pixaaGetPixa(pixaa, iclass, L_CLONE);
+                    pix = pixaGetPix(pixas, i, L_CLONE);
+                    pixaAddPix(pixa, pix, L_INSERT);
+                    box = boxaGetBox(boxa, i, L_CLONE);
+                    pixaAddBox(pixa, box, L_INSERT);
+                    pixaDestroy(&pixa);
+                }
+                break;
+            }
+        }
+        findSimilarSizedTemplatesDestroy(&findcontext);
+        if (found == FALSE) {  /* new class */
+            numaAddNumber(naclass, nt);
+            numaAddNumber(napage, npages);
+            pixa = pixaCreate(0);
+            pix = pixaGetPix(pixas, i, L_CLONE);  /* unbordered instance */
+            pixaAddPix(pixa, pix, L_INSERT);
+            wt = pixGetWidth(pix);
+            ht = pixGetHeight(pix);
+            l_dnaHashAdd(dahash, ht * wt, nt);
+            box = boxaGetBox(boxa, i, L_CLONE);
+            pixaAddBox(pixa, box, L_INSERT);
+            pixaaAddPixa(pixaa, pixa, L_INSERT);  /* unbordered instance */
+            ptaAddPt(ptact, x1, y1);
+            numaAddNumber(nafgt, area1);
+            pixaAddPix(pixat, pix1, L_INSERT);   /* bordered template */
+            area = (pixGetWidth(pix1) - 2 * JB_ADDED_PIXELS) *
+                   (pixGetHeight(pix1) - 2 * JB_ADDED_PIXELS);
+            numaAddNumber(naarea, area);
+        } else {  /* don't save it */
+            pixDestroy(&pix1);
+        }
+    }
+    classer->nclass = pixaGetCount(pixat);
+
+    LEPT_FREE(pixcts);
+    LEPT_FREE(centtab);
+    for (i = 0; i < n; i++) {
+        LEPT_FREE(pixrowcts[i]);
+    }
+    LEPT_FREE(pixrowcts);
+
+    LEPT_FREE(sumtab);
+    ptaDestroy(&pta);
+    pixaDestroy(&pixa1);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *             Determine the image components we start with             *
+ *----------------------------------------------------------------------*/
+/*!
+ *  jbGetComponents()
+ *
+ *      Input:  pixs (1 bpp)
+ *              components (JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS)
+ *              maxwidth, maxheight (of saved components; larger are discarded)
+ *              &pboxa (<return> b.b. of component items)
+ *              &ppixa (<return> component items)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+jbGetComponents(PIX     *pixs,
+                l_int32  components,
+                l_int32  maxwidth,
+                l_int32  maxheight,
+                BOXA   **pboxad,
+                PIXA   **ppixad)
+{
+l_int32    empty, res, redfactor;
+BOXA      *boxa;
+PIX       *pixt1, *pixt2, *pixt3;
+PIXA      *pixa, *pixat;
+
+    PROCNAME("jbGetComponents");
+
+    if (!pboxad)
+        return ERROR_INT("&boxad not defined", procName, 1);
+    *pboxad = NULL;
+    if (!ppixad)
+        return ERROR_INT("&pixad not defined", procName, 1);
+    *ppixad = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
+        components != JB_WORDS)
+        return ERROR_INT("invalid components", procName, 1);
+
+    pixZero(pixs, &empty);
+    if (empty) {
+        *pboxad = boxaCreate(0);
+        *ppixad = pixaCreate(0);
+        return 0;
+    }
+
+        /* If required, preprocess input pixs.  The method for both
+         * characters and words is to generate a connected component
+         * mask over the units that we want to aggregrate, which are,
+         * in general, sets of related connected components in pixs.
+         * For characters, we want to include the dots with
+         * 'i', 'j' and '!', so we do a small vertical closing to
+         * generate the mask.  For words, we make a mask over all
+         * characters in each word.  This is a bit more tricky, because
+         * the spacing between words is difficult to predict a priori,
+         * and words can be typeset with variable spacing that can
+         * in some cases be barely larger than the space between
+         * characters.  The first step is to generate the mask and
+         * identify each of its connected components.  */
+    if (components == JB_CONN_COMPS) {  /* no preprocessing */
+        boxa = pixConnComp(pixs, &pixa, 8);
+    } else if (components == JB_CHARACTERS) {
+        pixt1 = pixMorphSequence(pixs, "c1.6", 0);
+        boxa = pixConnComp(pixt1, &pixat, 8);
+        pixa = pixaClipToPix(pixat, pixs);
+        pixDestroy(&pixt1);
+        pixaDestroy(&pixat);
+    } else {  /* components == JB_WORDS */
+
+            /* Do the operations at about 150 ppi resolution.
+             * It is much faster at 75 ppi, but the results are
+             * more accurate at 150 ppi.  This will segment the
+             * words in body text.  It can be expected that relatively
+             * infrequent words in a larger font will be split. */
+        res = pixGetXRes(pixs);
+        if (res <= 200) {
+            redfactor = 1;
+            pixt1 = pixClone(pixs);
+        } else if (res <= 400) {
+            redfactor = 2;
+            pixt1 = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
+        } else {
+            redfactor = 4;
+            pixt1 = pixReduceRankBinaryCascade(pixs, 1, 1, 0, 0);
+        }
+
+            /* Estimate the word mask, at approximately 150 ppi.
+             * This has both very large and very small components left in. */
+        pixWordMaskByDilation(pixt1, 8, &pixt2, NULL);
+
+            /* Expand the optimally dilated word mask to full res. */
+        pixt3 = pixExpandReplicate(pixt2, redfactor);
+
+            /* Pull out the pixels in pixs corresponding to the mask
+             * components in pixt3.  Note that above we used threshold
+             * levels in the reduction of 1 to insure that the resulting
+             * mask fully covers the input pixs.  The downside of using
+             * a threshold of 1 is that very close characters from adjacent
+             * lines can be joined.  But with a level of 2 or greater,
+             * it is necessary to use a seedfill, followed by a pixOr():
+             *       pixt4 = pixSeedfillBinary(NULL, pixt3, pixs, 8);
+             *       pixOr(pixt3, pixt3, pixt4);
+             * to insure that the mask coverage is complete over pixs.  */
+        boxa = pixConnComp(pixt3, &pixat, 4);
+        pixa = pixaClipToPix(pixat, pixs);
+        pixaDestroy(&pixat);
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt2);
+        pixDestroy(&pixt3);
+    }
+
+        /* Remove large components, and save the results.  */
+    *ppixad = pixaSelectBySize(pixa, maxwidth, maxheight, L_SELECT_IF_BOTH,
+                               L_SELECT_IF_LTE, NULL);
+    *pboxad = boxaSelectBySize(boxa, maxwidth, maxheight, L_SELECT_IF_BOTH,
+                               L_SELECT_IF_LTE, NULL);
+    pixaDestroy(&pixa);
+    boxaDestroy(&boxa);
+
+    return 0;
+}
+
+
+/*!
+ *  pixWordMaskByDilation()
+ *
+ *      Input:  pixs (1 bpp; typ. at 75 to 150 ppi)
+ *              maxdil (maximum dilation; 0 for default; warning if > 20)
+ *              &mask (<optional return> dilated word mask)
+ *              &size (<optional return> size of optimal horiz Sel)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This gives a crude estimate of the word masks.  See
+ *          pixWordBoxesByDilation() for further filtering of the word boxes.
+ *      (2) For 75 to 150 ppi, the optimal dilation will be between 5 and 11.
+ *          For 200 to 300 ppi, it is advisable to use a larger value
+ *          for @maxdil, say between 10 and 20.  Setting maxdil <= 0
+ *          results in a default dilation of 16.
+ *      (3) The best size for dilating to get word masks is optionally returned.
+ */
+l_int32
+pixWordMaskByDilation(PIX      *pixs,
+                      l_int32   maxdil,
+                      PIX     **ppixm,
+                      l_int32  *psize)
+{
+l_int32  i, diffmin, ndiff, imin;
+l_int32  ncc[MAX_ALLOWED_DILATION + 1];
+BOXA    *boxa;
+NUMA    *nacc, *nadiff;
+PIX     *pix1, *pix2;
+
+    PROCNAME("pixWordMaskByDilation");
+
+    if (ppixm) *ppixm = NULL;
+    if (psize) *psize = 0;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs undefined or not 1 bpp", procName, 1);
+    if (!ppixm && !psize)
+        return ERROR_INT("no output requested", procName, 1);
+
+        /* Find the optimal dilation to create the word mask.
+         * Look for successively increasing dilations where the
+         * number of connected components doesn't decrease.
+         * This is the situation where the components in the
+         * word mask should properly cover each word.  If the
+         * input image had been 2x scaled, and you use 8 cc for
+         * counting, every other differential count in the series
+         * will be 0.  We avoid this possibility by using 4 cc. */
+    diffmin = 1000000;
+    pix1 = pixCopy(NULL, pixs);
+    if (maxdil <= 0)
+        maxdil = 16;  /* default for 200 to 300 ppi */
+    maxdil = L_MIN(maxdil, MAX_ALLOWED_DILATION);
+    if (maxdil > 20)
+        L_WARNING("large dilation: exceeds 20\n", procName);
+    nacc = numaCreate(maxdil + 1);
+    nadiff = numaCreate(maxdil + 1);
+    for (i = 0; i <= maxdil; i++) {
+        if (i == 0)  /* first one not dilated */
+            pix2 = pixCopy(NULL, pix1);
+        else  /* successive dilation by sel_2h */
+            pix2 = pixMorphSequence(pix1, "d2.1", 0);
+        boxa = pixConnCompBB(pix2, 4);
+        ncc[i] = boxaGetCount(boxa);
+        numaAddNumber(nacc, ncc[i]);
+        if (i > 0) {
+            ndiff = ncc[i - 1] - ncc[i];
+            numaAddNumber(nadiff, ndiff);
+#if  DEBUG_PLOT_CC
+            fprintf(stderr, "ndiff[%d] = %d\n", i - 1, ndiff);
+#endif  /* DEBUG_PLOT_CC */
+                /* Don't allow imin <= 2 with a 0 value of ndiff,
+                 * which is unlikely to happen.  */
+            if (ndiff < diffmin && (ndiff > 0 || i > 2)) {
+                imin = i;
+                diffmin = ndiff;
+            }
+        }
+        pixDestroy(&pix1);
+        pix1 = pix2;
+        boxaDestroy(&boxa);
+    }
+    pixDestroy(&pix1);
+    if (psize) *psize = imin + 1;
+
+#if  DEBUG_PLOT_CC
+    lept_mkdir("lept/jb");
+    {GPLOT *gplot;
+     NUMA  *naseq;
+        L_INFO("Best dilation: %d\n", procName, imin);
+        naseq = numaMakeSequence(1, 1, numaGetCount(nacc));
+        gplot = gplotCreate("/tmp/lept/jb/numcc", GPLOT_PNG,
+                            "Number of cc vs. horizontal dilation",
+                            "Sel horiz", "Number of cc");
+        gplotAddPlot(gplot, naseq, nacc, GPLOT_LINES, "");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+        numaDestroy(&naseq);
+        naseq = numaMakeSequence(1, 1, numaGetCount(nadiff));
+        gplot = gplotCreate("/tmp/lept/jb/diffcc", GPLOT_PNG,
+                            "Diff count of cc vs. horizontal dilation",
+                            "Sel horiz", "Diff in cc");
+        gplotAddPlot(gplot, naseq, nadiff, GPLOT_LINES, "");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+        numaDestroy(&naseq);
+    }
+#endif  /* DEBUG_PLOT_CC */
+
+        /* Optionally, save the result of the optimal closing */
+    if (ppixm) {
+        if (imin < 3)
+            L_ERROR("imin = %d is too small\n", procName, imin);
+        else
+            *ppixm = pixCloseBrick(NULL, pixs, imin + 1, 1);
+    }
+
+    numaDestroy(&nacc);
+    numaDestroy(&nadiff);
+    return 0;
+}
+
+
+/*!
+ *  pixWordBoxesByDilation()
+ *
+ *      Input:  pixs (1 bpp; typ. at 75 to 150 ppi)
+ *              maxdil (maximum dilation; 0 for default; warning if > 20)
+ *              minwidth, minheight (of saved components; smaller are discarded)
+ *              maxwidth, maxheight (of saved components; larger are discarded)
+ *              &boxa (<return> dilated word mask)
+ *              &size (<optional return> size of optimal horiz Sel)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Returns a pruned set of word boxes.
+ *      (2) See pixWordMaskByDilation().
+ */
+l_int32
+pixWordBoxesByDilation(PIX      *pixs,
+                       l_int32   maxdil,
+                       l_int32   minwidth,
+                       l_int32   minheight,
+                       l_int32   maxwidth,
+                       l_int32   maxheight,
+                       BOXA    **pboxa,
+                       l_int32  *psize)
+{
+BOXA  *boxa1, *boxa2;
+PIX   *pixm;
+
+    PROCNAME("pixWordBoxesByDilation");
+
+    if (psize) *psize = 0;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs undefined or not 1 bpp", procName, 1);
+    if (!pboxa)
+        return ERROR_INT("&boxa not defined", procName, 1);
+    *pboxa = NULL;
+
+        /* Make a first estimate of the word masks */
+    if (pixWordMaskByDilation(pixs, maxdil, &pixm, psize))
+        return ERROR_INT("pixWordMaskByDilation() failed", procName, 1);
+
+        /* Prune it.  Get the bounding boxes of the words.
+         * Remove the small ones, which can be due to punctuation
+         * that was not joined to a word.  Also remove the large ones,
+         * which are not likely to be words. */
+    boxa1 = pixConnComp(pixm, NULL, 8);
+    boxa2 = boxaSelectBySize(boxa1, minwidth, minheight, L_SELECT_IF_BOTH,
+                             L_SELECT_IF_GTE, NULL);
+    *pboxa = boxaSelectBySize(boxa2, maxwidth, maxheight, L_SELECT_IF_BOTH,
+                             L_SELECT_IF_LTE, NULL);
+    boxaDestroy(&boxa1);
+    boxaDestroy(&boxa2);
+    pixDestroy(&pixm);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                 Build grayscale composites (templates)               *
+ *----------------------------------------------------------------------*/
+/*!
+ *  jbAccumulateComposites()
+ *
+ *      Input:  pixaa (one pixa for each class)
+ *              &pna (<return> number of samples used to build each composite)
+ *              &ptat (<return> centroids of bordered composites)
+ *      Return: pixad (accumulated sum of samples in each class),
+ *                     or null on error
+ *
+ */
+PIXA *
+jbAccumulateComposites(PIXAA  *pixaa,
+                       NUMA  **pna,
+                       PTA   **pptat)
+{
+l_int32    n, nt, i, j, d, minw, maxw, minh, maxh, xdiff, ydiff;
+l_float32  x, y, xave, yave;
+NUMA      *na;
+PIX       *pix, *pixt1, *pixt2, *pixsum;
+PIXA      *pixa, *pixad;
+PTA       *ptat, *pta;
+
+    PROCNAME("jbAccumulateComposites");
+
+    if (!pptat)
+        return (PIXA *)ERROR_PTR("&ptat not defined", procName, NULL);
+    *pptat = NULL;
+    if (!pna)
+        return (PIXA *)ERROR_PTR("&na not defined", procName, NULL);
+    *pna = NULL;
+    if (!pixaa)
+        return (PIXA *)ERROR_PTR("pixaa not defined", procName, NULL);
+
+    n = pixaaGetCount(pixaa, NULL);
+    if ((ptat = ptaCreate(n)) == NULL)
+        return (PIXA *)ERROR_PTR("ptat not made", procName, NULL);
+    *pptat = ptat;
+    pixad = pixaCreate(n);
+    na = numaCreate(n);
+    *pna = na;
+
+    for (i = 0; i < n; i++) {
+        pixa = pixaaGetPixa(pixaa, i, L_CLONE);
+        nt = pixaGetCount(pixa);
+        numaAddNumber(na, nt);
+        if (nt == 0) {
+            L_WARNING("empty pixa found!\n", procName);
+            pixaDestroy(&pixa);
+            continue;
+        }
+        pixaSizeRange(pixa, &minw, &minh, &maxw, &maxh);
+        pix = pixaGetPix(pixa, 0, L_CLONE);
+        d = pixGetDepth(pix);
+        pixDestroy(&pix);
+        pixt1 = pixCreate(maxw, maxh, d);
+        pixsum = pixInitAccumulate(maxw, maxh, 0);
+        pta = pixaCentroids(pixa);
+
+            /* Find the average value of the centroids ... */
+        xave = yave = 0;
+        for (j = 0; j < nt; j++) {
+            ptaGetPt(pta, j, &x, &y);
+            xave += x;
+            yave += y;
+        }
+        xave = xave / (l_float32)nt;
+        yave = yave / (l_float32)nt;
+
+            /* and place all centroids at their average value */
+        for (j = 0; j < nt; j++) {
+            pixt2 = pixaGetPix(pixa, j, L_CLONE);
+            ptaGetPt(pta, j, &x, &y);
+            xdiff = (l_int32)(x - xave);
+            ydiff = (l_int32)(y - yave);
+            pixClearAll(pixt1);
+            pixRasterop(pixt1, xdiff, ydiff, maxw, maxh, PIX_SRC,
+                        pixt2, 0, 0);
+            pixAccumulate(pixsum, pixt1, L_ARITH_ADD);
+            pixDestroy(&pixt2);
+        }
+        pixaAddPix(pixad, pixsum, L_INSERT);
+        ptaAddPt(ptat, xave, yave);
+
+        pixaDestroy(&pixa);
+        pixDestroy(&pixt1);
+        ptaDestroy(&pta);
+    }
+
+    return pixad;
+}
+
+
+/*!
+ *  jbTemplatesFromComposites()
+ *
+ *      Input:  pixac (one pix of composites for each class)
+ *              na (number of samples used for each class composite)
+ *      Return: pixad (8 bpp templates for each class), or null on error
+ *
+ */
+PIXA *
+jbTemplatesFromComposites(PIXA  *pixac,
+                          NUMA  *na)
+{
+l_int32    n, i;
+l_float32  nt;  /* number of samples in the composite; always an integer */
+l_float32  factor;
+PIX       *pixsum;   /* accumulated composite */
+PIX       *pixd;
+PIXA      *pixad;
+
+    PROCNAME("jbTemplatesFromComposites");
+
+    if (!pixac)
+        return (PIXA *)ERROR_PTR("pixac not defined", procName, NULL);
+    if (!na)
+        return (PIXA *)ERROR_PTR("na not defined", procName, NULL);
+
+    n = pixaGetCount(pixac);
+    pixad = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pixsum = pixaGetPix(pixac, i, L_COPY);  /* changed internally */
+        numaGetFValue(na, i, &nt);
+        factor = 255. / nt;
+        pixMultConstAccumulate(pixsum, factor, 0);  /* changes pixsum */
+        pixd = pixFinalAccumulate(pixsum, 0, 8);
+        pixaAddPix(pixad, pixd, L_INSERT);
+        pixDestroy(&pixsum);
+    }
+
+    return pixad;
+}
+
+
+
+/*----------------------------------------------------------------------*
+ *                       jbig2 utility routines                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  jbClasserCreate()
+ *
+ *      Input:  method (JB_RANKHAUS, JB_CORRELATION)
+ *              components (JB_CONN_COMPS, JB_CHARACTERS, JB_WORDS)
+ *      Return: jbclasser, or null on error
+ */
+JBCLASSER *
+jbClasserCreate(l_int32  method,
+                l_int32  components)
+{
+JBCLASSER  *classer;
+
+    PROCNAME("jbClasserCreate");
+
+    if ((classer = (JBCLASSER *)LEPT_CALLOC(1, sizeof(JBCLASSER))) == NULL)
+        return (JBCLASSER *)ERROR_PTR("classer not made", procName, NULL);
+    if (method != JB_RANKHAUS && method != JB_CORRELATION)
+        return (JBCLASSER *)ERROR_PTR("invalid type", procName, NULL);
+    if (components != JB_CONN_COMPS && components != JB_CHARACTERS &&
+        components != JB_WORDS)
+        return (JBCLASSER *)ERROR_PTR("invalid type", procName, NULL);
+
+    classer->method = method;
+    classer->components = components;
+    classer->nacomps = numaCreate(0);
+    classer->pixaa = pixaaCreate(0);
+    classer->pixat = pixaCreate(0);
+    classer->pixatd = pixaCreate(0);
+    classer->nafgt = numaCreate(0);
+    classer->naarea = numaCreate(0);
+    classer->ptac = ptaCreate(0);
+    classer->ptact = ptaCreate(0);
+    classer->naclass = numaCreate(0);
+    classer->napage = numaCreate(0);
+    classer->ptaul = ptaCreate(0);
+
+    return classer;
+}
+
+
+/*
+ *  jbClasserDestroy()
+ *
+ *      Input: &classer (<to be nulled>)
+ *      Return: void
+ */
+void
+jbClasserDestroy(JBCLASSER  **pclasser)
+{
+JBCLASSER  *classer;
+
+    if (!pclasser)
+        return;
+    if ((classer = *pclasser) == NULL)
+        return;
+
+    sarrayDestroy(&classer->safiles);
+    numaDestroy(&classer->nacomps);
+    pixaaDestroy(&classer->pixaa);
+    pixaDestroy(&classer->pixat);
+    pixaDestroy(&classer->pixatd);
+    l_dnaHashDestroy(&classer->dahash);
+    numaDestroy(&classer->nafgt);
+    numaDestroy(&classer->naarea);
+    ptaDestroy(&classer->ptac);
+    ptaDestroy(&classer->ptact);
+    numaDestroy(&classer->naclass);
+    numaDestroy(&classer->napage);
+    ptaDestroy(&classer->ptaul);
+    ptaDestroy(&classer->ptall);
+    LEPT_FREE(classer);
+    *pclasser = NULL;
+    return;
+}
+
+
+/*!
+ *  jbDataSave()
+ *
+ *      Input:  jbclasser
+ *              latticew, latticeh (cell size used to store each
+ *                  connected component in the composite)
+ *      Return: jbdata, or null on error
+ *
+ *  Notes:
+ *      (1) This routine stores the jbig2-type data required for
+ *          generating a lossy jbig2 version of the image.
+ *          It can be losslessly written to (and read from) two files.
+ *      (2) It generates and stores the mosaic of templates.
+ *      (3) It clones the Numa and Pta arrays, so these must all
+ *          be destroyed by the caller.
+ *      (4) Input 0 to use the default values for latticew and/or latticeh,
+ */
+JBDATA *
+jbDataSave(JBCLASSER  *classer)
+{
+l_int32  maxw, maxh;
+JBDATA  *data;
+PIX     *pix;
+
+    PROCNAME("jbDataSave");
+
+    if (!classer)
+        return (JBDATA *)ERROR_PTR("classer not defined", procName, NULL);
+
+        /* Write the templates into an array. */
+    pixaSizeRange(classer->pixat, NULL, NULL, &maxw, &maxh);
+    pix = pixaDisplayOnLattice(classer->pixat, maxw + 1, maxh + 1,
+                               NULL, NULL);
+    if (!pix)
+        return (JBDATA *)ERROR_PTR("data not made", procName, NULL);
+
+    if ((data = (JBDATA *)LEPT_CALLOC(1, sizeof(JBDATA))) == NULL)
+        return (JBDATA *)ERROR_PTR("data not made", procName, NULL);
+    data->pix = pix;
+    data->npages = classer->npages;
+    data->w = classer->w;
+    data->h = classer->h;
+    data->nclass = classer->nclass;
+    data->latticew = maxw + 1;
+    data->latticeh = maxh + 1;
+    data->naclass = numaClone(classer->naclass);
+    data->napage = numaClone(classer->napage);
+    data->ptaul = ptaClone(classer->ptaul);
+
+    return data;
+}
+
+
+/*
+ *  jbDataDestroy()
+ *
+ *      Input: &data (<to be nulled>)
+ *      Return: void
+ */
+void
+jbDataDestroy(JBDATA  **pdata)
+{
+JBDATA  *data;
+
+    if (!pdata)
+        return;
+    if ((data = *pdata) == NULL)
+        return;
+
+    pixDestroy(&data->pix);
+    numaDestroy(&data->naclass);
+    numaDestroy(&data->napage);
+    ptaDestroy(&data->ptaul);
+    LEPT_FREE(data);
+    *pdata = NULL;
+    return;
+}
+
+
+/*!
+ *  jbDataWrite()
+ *
+ *      Input:  rootname (for output files; everything but the extension)
+ *              jbdata
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Serialization function that writes data in jbdata to file.
+ */
+l_int32
+jbDataWrite(const char  *rootout,
+            JBDATA      *jbdata)
+{
+char     buf[L_BUF_SIZE];
+l_int32  w, h, nclass, npages, cellw, cellh, ncomp, i, x, y, iclass, ipage;
+NUMA    *naclass, *napage;
+PTA     *ptaul;
+PIX     *pixt;
+FILE    *fp;
+
+    PROCNAME("jbDataWrite");
+
+    if (!rootout)
+        return ERROR_INT("no rootout", procName, 1);
+    if (!jbdata)
+        return ERROR_INT("no jbdata", procName, 1);
+
+    npages = jbdata->npages;
+    w = jbdata->w;
+    h = jbdata->h;
+    pixt = jbdata->pix;
+    nclass = jbdata->nclass;
+    cellw = jbdata->latticew;
+    cellh = jbdata->latticeh;
+    naclass = jbdata->naclass;
+    napage = jbdata->napage;
+    ptaul = jbdata->ptaul;
+
+    snprintf(buf, L_BUF_SIZE, "%s%s", rootout, JB_TEMPLATE_EXT);
+    pixWrite(buf, pixt, IFF_PNG);
+
+    snprintf(buf, L_BUF_SIZE, "%s%s", rootout, JB_DATA_EXT);
+    if ((fp = fopenWriteStream(buf, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    ncomp = ptaGetCount(ptaul);
+    fprintf(fp, "jb data file\n");
+    fprintf(fp, "num pages = %d\n", npages);
+    fprintf(fp, "page size: w = %d, h = %d\n", w, h);
+    fprintf(fp, "num components = %d\n", ncomp);
+    fprintf(fp, "num classes = %d\n", nclass);
+    fprintf(fp, "template lattice size: w = %d, h = %d\n", cellw, cellh);
+    for (i = 0; i < ncomp; i++) {
+        numaGetIValue(napage, i, &ipage);
+        numaGetIValue(naclass, i, &iclass);
+        ptaGetIPt(ptaul, i, &x, &y);
+        fprintf(fp, "%d %d %d %d\n", ipage, iclass, x, y);
+    }
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  jbDataRead()
+ *
+ *      Input:  rootname (for template and data files)
+ *      Return: jbdata, or NULL on error
+ */
+JBDATA *
+jbDataRead(const char  *rootname)
+{
+char      fname[L_BUF_SIZE];
+char     *linestr;
+l_uint8  *data;
+l_int32   nsa, i, w, h, cellw, cellh, x, y, iclass, ipage;
+l_int32   npages, nclass, ncomp;
+size_t    size;
+JBDATA   *jbdata;
+NUMA     *naclass, *napage;
+PIX      *pixs;
+PTA      *ptaul;
+SARRAY   *sa;
+
+    PROCNAME("jbDataRead");
+
+    if (!rootname)
+        return (JBDATA *)ERROR_PTR("rootname not defined", procName, NULL);
+
+    snprintf(fname, L_BUF_SIZE, "%s%s", rootname, JB_TEMPLATE_EXT);
+    if ((pixs = pixRead(fname)) == NULL)
+        return (JBDATA *)ERROR_PTR("pix not read", procName, NULL);
+
+    snprintf(fname, L_BUF_SIZE, "%s%s", rootname, JB_DATA_EXT);
+    if ((data = l_binaryRead(fname, &size)) == NULL)
+        return (JBDATA *)ERROR_PTR("data not read", procName, NULL);
+
+    if ((sa = sarrayCreateLinesFromString((char *)data, 0)) == NULL)
+        return (JBDATA *)ERROR_PTR("sa not made", procName, NULL);
+    nsa = sarrayGetCount(sa);   /* number of cc + 6 */
+    linestr = sarrayGetString(sa, 0, L_NOCOPY);
+    if (strcmp(linestr, "jb data file"))
+        return (JBDATA *)ERROR_PTR("invalid jb data file", procName, NULL);
+    linestr = sarrayGetString(sa, 1, L_NOCOPY);
+    sscanf(linestr, "num pages = %d", &npages);
+    linestr = sarrayGetString(sa, 2, L_NOCOPY);
+    sscanf(linestr, "page size: w = %d, h = %d", &w, &h);
+    linestr = sarrayGetString(sa, 3, L_NOCOPY);
+    sscanf(linestr, "num components = %d", &ncomp);
+    linestr = sarrayGetString(sa, 4, L_NOCOPY);
+    sscanf(linestr, "num classes = %d\n", &nclass);
+    linestr = sarrayGetString(sa, 5, L_NOCOPY);
+    sscanf(linestr, "template lattice size: w = %d, h = %d\n", &cellw, &cellh);
+
+#if 1
+    fprintf(stderr, "num pages = %d\n", npages);
+    fprintf(stderr, "page size: w = %d, h = %d\n", w, h);
+    fprintf(stderr, "num components = %d\n", ncomp);
+    fprintf(stderr, "num classes = %d\n", nclass);
+    fprintf(stderr, "template lattice size: w = %d, h = %d\n", cellw, cellh);
+#endif
+
+    if ((naclass = numaCreate(ncomp)) == NULL)
+        return (JBDATA *)ERROR_PTR("naclass not made", procName, NULL);
+    if ((napage = numaCreate(ncomp)) == NULL)
+        return (JBDATA *)ERROR_PTR("napage not made", procName, NULL);
+    if ((ptaul = ptaCreate(ncomp)) == NULL)
+        return (JBDATA *)ERROR_PTR("pta not made", procName, NULL);
+    for (i = 6; i < nsa; i++) {
+        linestr = sarrayGetString(sa, i, L_NOCOPY);
+        sscanf(linestr, "%d %d %d %d\n", &ipage, &iclass, &x, &y);
+        numaAddNumber(napage, ipage);
+        numaAddNumber(naclass, iclass);
+        ptaAddPt(ptaul, x, y);
+    }
+
+    if ((jbdata = (JBDATA *)LEPT_CALLOC(1, sizeof(JBDATA))) == NULL)
+        return (JBDATA *)ERROR_PTR("data not made", procName, NULL);
+    jbdata->pix = pixs;
+    jbdata->npages = npages;
+    jbdata->w = w;
+    jbdata->h = h;
+    jbdata->nclass = nclass;
+    jbdata->latticew = cellw;
+    jbdata->latticeh = cellh;
+    jbdata->naclass = naclass;
+    jbdata->napage = napage;
+    jbdata->ptaul = ptaul;
+
+    LEPT_FREE(data);
+    sarrayDestroy(&sa);
+    return jbdata;
+}
+
+
+/*!
+ *  jbDataRender()
+ *
+ *      Input:  jbdata
+ *              debugflag (if TRUE, writes into 2 bpp pix and adds
+ *                         component outlines in color)
+ *      Return: pixa (reconstruction of original images, using templates) or
+ *              null on error
+ */
+PIXA *
+jbDataRender(JBDATA  *data,
+             l_int32  debugflag)
+{
+l_int32   i, w, h, cellw, cellh, x, y, iclass, ipage;
+l_int32   npages, nclass, ncomp, wp, hp;
+BOX      *box;
+NUMA     *naclass, *napage;
+PIX      *pixt, *pixt2, *pix, *pixd;
+PIXA     *pixat;   /* pixa of templates */
+PIXA     *pixad;   /* pixa of output images */
+PIXCMAP  *cmap;
+PTA      *ptaul;
+
+    PROCNAME("jbDataRender");
+
+    if (!data)
+        return (PIXA *)ERROR_PTR("data not defined", procName, NULL);
+
+    npages = data->npages;
+    w = data->w;
+    h = data->h;
+    pixt = data->pix;
+    nclass = data->nclass;
+    cellw = data->latticew;
+    cellh = data->latticeh;
+    naclass = data->naclass;
+    napage = data->napage;
+    ptaul = data->ptaul;
+    ncomp = numaGetCount(naclass);
+
+        /* Reconstruct the original set of images from the templates
+         * and the data associated with each component.  First,
+         * generate the output pixa as a set of empty pix. */
+    if ((pixad = pixaCreate(npages)) == NULL)
+        return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);
+    for (i = 0; i < npages; i++) {
+        if (debugflag == FALSE) {
+            pix = pixCreate(w, h, 1);
+        } else {
+            pix = pixCreate(w, h, 2);
+            cmap = pixcmapCreate(2);
+            pixcmapAddColor(cmap, 255, 255, 255);
+            pixcmapAddColor(cmap, 0, 0, 0);
+            pixcmapAddColor(cmap, 255, 0, 0);  /* for box outlines */
+            pixSetColormap(pix, cmap);
+        }
+        pixaAddPix(pixad, pix, L_INSERT);
+    }
+
+        /* Put the class templates into a pixa. */
+    if ((pixat = pixaCreateFromPix(pixt, nclass, cellw, cellh)) == NULL)
+        return (PIXA *)ERROR_PTR("pixat not made", procName, NULL);
+
+        /* Place each component in the right location on its page. */
+    for (i = 0; i < ncomp; i++) {
+        numaGetIValue(napage, i, &ipage);
+        numaGetIValue(naclass, i, &iclass);
+        pix = pixaGetPix(pixat, iclass, L_CLONE);  /* the template */
+        wp = pixGetWidth(pix);
+        hp = pixGetHeight(pix);
+        ptaGetIPt(ptaul, i, &x, &y);
+        pixd = pixaGetPix(pixad, ipage, L_CLONE);   /* the output page */
+        if (debugflag == FALSE) {
+            pixRasterop(pixd, x, y, wp, hp, PIX_SRC | PIX_DST, pix, 0, 0);
+        } else {
+            pixt2 = pixConvert1To2Cmap(pix);
+            pixRasterop(pixd, x, y, wp, hp, PIX_SRC | PIX_DST, pixt2, 0, 0);
+            box = boxCreate(x, y, wp, hp);
+            pixRenderBoxArb(pixd, box, 1, 255, 0, 0);
+            pixDestroy(&pixt2);
+            boxDestroy(&box);
+        }
+        pixDestroy(&pix);   /* the clone only */
+        pixDestroy(&pixd);  /* the clone only */
+    }
+
+    pixaDestroy(&pixat);
+    return pixad;
+}
+
+
+/*!
+ *  jbGetULCorners()
+ *
+ *      Input:  jbclasser
+ *              pixs (full res image)
+ *              boxa (of c.c. bounding rectangles for this page)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This computes the ptaul field, which has the global UL corners,
+ *          adjusted for each specific component, so that each component
+ *          can be replaced by the template for its class and have the
+ *          centroid in the template in the same position as the
+ *          centroid of the original connected component.  It is important
+ *          that this be done properly to avoid a wavy baseline in the
+ *          result.
+ *      (2) The array fields ptac and ptact give the centroids of
+ *          those components relative to the UL corner of each component.
+ *          Here, we compute the difference in each component, round to
+ *          nearest integer, and correct the box->x and box->y by
+ *          the appropriate integral difference.
+ *      (3) The templates and stored instances are all bordered.
+ */
+l_int32
+jbGetULCorners(JBCLASSER  *classer,
+               PIX        *pixs,
+               BOXA       *boxa)
+{
+l_int32    i, baseindex, index, n, iclass, idelx, idely, x, y, dx, dy;
+l_int32   *sumtab;
+l_float32  x1, x2, y1, y2, delx, dely;
+BOX       *box;
+NUMA      *naclass;
+PIX       *pixt;
+PTA       *ptac, *ptact, *ptaul;
+
+    PROCNAME("jbGetULCorners");
+
+    if (!classer)
+        return ERROR_INT("classer not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+
+    n = boxaGetCount(boxa);
+    ptaul = classer->ptaul;
+    naclass = classer->naclass;
+    ptac = classer->ptac;
+    ptact = classer->ptact;
+    baseindex = classer->baseindex;  /* num components before this page */
+    sumtab = makePixelSumTab8();
+    for (i = 0; i < n; i++) {
+        index = baseindex + i;
+        ptaGetPt(ptac, index, &x1, &y1);
+        numaGetIValue(naclass, index, &iclass);
+        ptaGetPt(ptact, iclass, &x2, &y2);
+        delx = x2 - x1;
+        dely = y2 - y1;
+        if (delx >= 0)
+            idelx = (l_int32)(delx + 0.5);
+        else
+            idelx = (l_int32)(delx - 0.5);
+        if (dely >= 0)
+            idely = (l_int32)(dely + 0.5);
+        else
+            idely = (l_int32)(dely - 0.5);
+        if ((box = boxaGetBox(boxa, i, L_CLONE)) == NULL)
+            return ERROR_INT("box not found", procName, 1);
+        boxGetGeometry(box, &x, &y, NULL, NULL);
+
+            /* Get final increments dx and dy for best alignment */
+        pixt = pixaGetPix(classer->pixat, iclass, L_CLONE);
+        finalPositioningForAlignment(pixs, x, y, idelx, idely,
+                                     pixt, sumtab, &dx, &dy);
+/*        if (i % 20 == 0)
+            fprintf(stderr, "dx = %d, dy = %d\n", dx, dy); */
+        ptaAddPt(ptaul, x - idelx + dx, y - idely + dy);
+        boxDestroy(&box);
+        pixDestroy(&pixt);
+    }
+
+    LEPT_FREE(sumtab);
+    return 0;
+}
+
+
+/*!
+ *  jbGetLLCorners()
+ *
+ *      Input:  jbclasser
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This computes the ptall field, which has the global LL corners,
+ *          adjusted for each specific component, so that each component
+ *          can be replaced by the template for its class and have the
+ *          centroid in the template in the same position as the
+ *          centroid of the original connected component. It is important
+ *          that this be done properly to avoid a wavy baseline in the result.
+ *      (2) It is computed here from the corresponding UL corners, where
+ *          the input templates and stored instances are all bordered.
+ *          This should be done after all pages have been processed.
+ *      (3) For proper substitution, the templates whose LL corners are
+ *          placed in these locations must be UN-bordered.
+ *          This is available for a realistic jbig2 encoder, which would
+ *          (1) encode each template without a border, and (2) encode
+ *          the position using the LL corner (rather than the UL
+ *          corner) because the difference between y-values
+ *          of successive instances is typically close to zero.
+ */
+l_int32
+jbGetLLCorners(JBCLASSER  *classer)
+{
+l_int32    i, iclass, n, x1, y1, h;
+NUMA      *naclass;
+PIX       *pix;
+PIXA      *pixat;
+PTA       *ptaul, *ptall;
+
+    PROCNAME("jbGetLLCorners");
+
+    if (!classer)
+        return ERROR_INT("classer not defined", procName, 1);
+
+    ptaul = classer->ptaul;
+    naclass = classer->naclass;
+    pixat = classer->pixat;
+
+    ptaDestroy(&classer->ptall);
+    n = ptaGetCount(ptaul);
+    ptall = ptaCreate(n);
+    classer->ptall = ptall;
+
+        /* If the templates were bordered, we would add h - 1 to the UL
+         * corner y-value.  However, because the templates to be used
+         * here have their borders removed, and the borders are
+         * JB_ADDED_PIXELS on each side, we add h - 1 - 2 * JB_ADDED_PIXELS
+         * to the UL corner y-value.  */
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(ptaul, i, &x1, &y1);
+        numaGetIValue(naclass, i, &iclass);
+        pix = pixaGetPix(pixat, iclass, L_CLONE);
+        h = pixGetHeight(pix);
+        ptaAddPt(ptall, x1, y1 + h - 1 - 2 * JB_ADDED_PIXELS);
+        pixDestroy(&pix);
+    }
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                              Static helpers                          *
+ *----------------------------------------------------------------------*/
+/* When looking for similar matches we check templates whose size is +/- 2 in
+ * each direction. This involves 25 possible sizes. This array contains the
+ * offsets for each of those positions in a spiral pattern. There are 25 pairs
+ * of numbers in this array: even positions are x values. */
+static int two_by_two_walk[50] = {
+  0, 0,
+  0, 1,
+  -1, 0,
+  0, -1,
+  1, 0,
+  -1, 1,
+  1, 1,
+  -1, -1,
+  1, -1,
+  0, -2,
+  2, 0,
+  0, 2,
+  -2, 0,
+  -1, -2,
+  1, -2,
+  2, -1,
+  2, 1,
+  1, 2,
+  -1, 2,
+  -2, 1,
+  -2, -1,
+  -2, -2,
+  2, -2,
+  2, 2,
+  -2, 2};
+
+
+/*!
+ *  findSimilarSizedTemplatesInit()
+ *
+ *      Input:  classer
+ *              pixs (instance to be matched)
+ *      Return: Allocated context to be used with findSimilar*
+ */
+static JBFINDCTX *
+findSimilarSizedTemplatesInit(JBCLASSER  *classer,
+                              PIX        *pixs)
+{
+JBFINDCTX  *state;
+
+    state = (JBFINDCTX *)LEPT_CALLOC(1, sizeof(JBFINDCTX));
+    state->w = pixGetWidth(pixs) - 2 * JB_ADDED_PIXELS;
+    state->h = pixGetHeight(pixs) - 2 * JB_ADDED_PIXELS;
+    state->classer = classer;
+
+    return state;
+}
+
+
+static void
+findSimilarSizedTemplatesDestroy(JBFINDCTX  **pstate)
+{
+JBFINDCTX  *state;
+
+    PROCNAME("findSimilarSizedTemplatesDestroy");
+
+    if (pstate == NULL) {
+        L_WARNING("ptr address is null\n", procName);
+        return;
+    }
+    if ((state = *pstate) == NULL)
+        return;
+
+    l_dnaDestroy(&state->dna);
+    LEPT_FREE(state);
+    *pstate = NULL;
+    return;
+}
+
+
+/*!
+ *  findSimilarSizedTemplatesNext()
+ *
+ *      Input:  state (from findSimilarSizedTemplatesInit)
+ *      Return: next template number, or -1 when finished
+ *
+ *  We have a dna hash table that maps template area to a list of template
+ *  numbers with that area.  We wish to find similar sized templates,
+ *  so we first look for templates with the same width and height, and
+ *  then with width + 1, etc.  This walk is guided by the
+ *  two_by_two_walk array, above.
+ *
+ *  We don't want to have to collect the whole list of templates first,
+ *  because we hope to find a well-matching template quickly.  So we
+ *  keep the context for this walk in an explictit state structure,
+ *  and this function acts like a generator.
+ */
+static l_int32
+findSimilarSizedTemplatesNext(JBFINDCTX  *state)
+{
+l_int32  desiredh, desiredw, size, templ;
+PIX     *pixt;
+
+    while(1) {  /* Continue the walk over step 'i' */
+        if (state->i >= 25) {  /* all done; didn't find a good match */
+            return -1;
+        }
+
+        desiredw = state->w + two_by_two_walk[2 * state->i];
+        desiredh = state->h + two_by_two_walk[2 * state->i + 1];
+        if (desiredh < 1 || desiredw < 1) {  /* invalid size */
+            state->i++;
+            continue;
+        }
+
+        if (!state->dna) {
+                /* We have yet to start walking the array for the step 'i' */
+            state->dna = l_dnaHashGetDna(state->classer->dahash,
+                                         desiredh * desiredw, L_CLONE);
+            if (!state->dna) {  /* nothing there */
+                state->i++;
+                continue;
+            }
+
+            state->n = 0;  /* OK, we got a dna. */
+        }
+
+            /* Continue working on this dna */
+        size = l_dnaGetCount(state->dna);
+        for ( ; state->n < size; ) {
+            templ = (l_int32)(state->dna->array[state->n++] + 0.5);
+            pixt = pixaGetPix(state->classer->pixat, templ, L_CLONE);
+            if (pixGetWidth(pixt) - 2 * JB_ADDED_PIXELS == desiredw &&
+                pixGetHeight(pixt) - 2 * JB_ADDED_PIXELS == desiredh) {
+                pixDestroy(&pixt);
+                return templ;
+            }
+            pixDestroy(&pixt);
+        }
+
+            /* Exhausted the dna (no match found); take another step and
+             * try again. */
+        state->i++;
+        l_dnaDestroy(&state->dna);
+        continue;
+    }
+}
+
+
+/*!
+ *  finalPositioningForAlignment()
+ *
+ *      Input:  pixs (input page image)
+ *              x, y (location of UL corner of bb of component in pixs)
+ *              idelx, idely (compensation to match centroids of component
+ *                            and template)
+ *              pixt (template, with JB_ADDED_PIXELS of padding on all sides)
+ *              sumtab (for summing fg pixels in an image)
+ *              &dx, &dy (return delta on position for best match; each
+ *                        one is in the set {-1, 0, 1})
+ *      Return: 0 if OK, 1 on error
+ *
+ */
+static l_int32
+finalPositioningForAlignment(PIX      *pixs,
+                             l_int32   x,
+                             l_int32   y,
+                             l_int32   idelx,
+                             l_int32   idely,
+                             PIX      *pixt,
+                             l_int32  *sumtab,
+                             l_int32  *pdx,
+                             l_int32  *pdy)
+{
+l_int32  w, h, i, j, minx, miny, count, mincount;
+PIX     *pixi;  /* clipped from source pixs */
+PIX     *pixr;  /* temporary storage */
+BOX     *box;
+
+    PROCNAME("finalPositioningForAlignment");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixt)
+        return ERROR_INT("pixt not defined", procName, 1);
+    if (!pdx || !pdy)
+        return ERROR_INT("&dx and &dy not both defined", procName, 1);
+    if (!sumtab)
+        return ERROR_INT("sumtab not defined", procName, 1);
+    *pdx = *pdy = 0;
+
+        /* Use JB_ADDED_PIXELS pixels padding on each side */
+    w = pixGetWidth(pixt);
+    h = pixGetHeight(pixt);
+    box = boxCreate(x - idelx - JB_ADDED_PIXELS,
+                    y - idely - JB_ADDED_PIXELS, w, h);
+    pixi = pixClipRectangle(pixs, box, NULL);
+    boxDestroy(&box);
+    if (!pixi)
+        return ERROR_INT("pixi not made", procName, 1);
+
+    pixr = pixCreate(pixGetWidth(pixi), pixGetHeight(pixi), 1);
+    mincount = 0x7fffffff;
+    for (i = -1; i <= 1; i++) {
+        for (j = -1; j <= 1; j++) {
+            pixCopy(pixr, pixi);
+            pixRasterop(pixr, j, i, w, h, PIX_SRC ^ PIX_DST, pixt, 0, 0);
+            pixCountPixels(pixr, &count, sumtab);
+            if (count < mincount) {
+                minx = j;
+                miny = i;
+                mincount = count;
+            }
+        }
+    }
+    pixDestroy(&pixi);
+    pixDestroy(&pixr);
+
+    *pdx = minx;
+    *pdy = miny;
+    return 0;
+}
diff --git a/src/jbclass.h b/src/jbclass.h
new file mode 100644 (file)
index 0000000..62d0a6a
--- /dev/null
@@ -0,0 +1,133 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_JBCLASS_H
+#define  LEPTONICA_JBCLASS_H
+
+/*
+ * jbclass.h
+ *
+ *       JbClasser
+ *       JbData
+ */
+
+
+    /* The JbClasser struct holds all the data accumulated during the
+     * classification process that can be used for a compressed
+     * jbig2-type representation of a set of images.  This is created
+     * in an initialization process and added to as the selected components
+     * on each successive page are analyzed.   */
+struct JbClasser
+{
+    struct Sarray   *safiles;      /* input page image file names            */
+    l_int32          method;       /* JB_RANKHAUS, JB_CORRELATION            */
+    l_int32          components;   /* JB_CONN_COMPS, JB_CHARACTERS or        */
+                                   /* JB_WORDS                               */
+    l_int32          maxwidth;     /* max component width allowed            */
+    l_int32          maxheight;    /* max component height allowed           */
+    l_int32          npages;       /* number of pages already processed      */
+    l_int32          baseindex;    /* number of components already processed */
+                                   /* on fully processed pages               */
+    struct Numa     *nacomps;      /* number of components on each page      */
+    l_int32          sizehaus;     /* size of square struct element for haus */
+    l_float32        rankhaus;     /* rank val of haus match, each way       */
+    l_float32        thresh;       /* thresh value for correlation score     */
+    l_float32        weightfactor; /* corrects thresh value for heaver       */
+                                   /* components; use 0 for no correction    */
+    struct Numa     *naarea;       /* w * h of each template, without extra  */
+                                   /* border pixels                          */
+    l_int32          w;            /* max width of original src images       */
+    l_int32          h;            /* max height of original src images      */
+    l_int32          nclass;       /* current number of classes              */
+    l_int32          keep_pixaa;   /* If zero, pixaa isn't filled            */
+    struct Pixaa    *pixaa;        /* instances for each class; unbordered   */
+    struct Pixa     *pixat;        /* templates for each class; bordered     */
+                                   /* and not dilated                        */
+    struct Pixa     *pixatd;       /* templates for each class; bordered     */
+                                   /* and dilated                            */
+    struct L_DnaHash *dahash;      /* Hash table to find templates by size   */
+    struct Numa     *nafgt;        /* fg areas of undilated templates;       */
+                                   /* only used for rank < 1.0               */
+    struct Pta      *ptac;         /* centroids of all bordered cc           */
+    struct Pta      *ptact;        /* centroids of all bordered template cc  */
+    struct Numa     *naclass;      /* array of class ids for each component  */
+    struct Numa     *napage;       /* array of page nums for each component  */
+    struct Pta      *ptaul;        /* array of UL corners at which the       */
+                                   /* template is to be placed for each      */
+                                   /* component                              */
+    struct Pta      *ptall;        /* similar to ptaul, but for LL corners   */
+};
+typedef struct JbClasser  JBCLASSER;
+
+
+    /* The JbData struct holds all the data required for
+     * the compressed jbig-type representation of a set of images.
+     * The data can be written to file, read back, and used
+     * to regenerate an approximate version of the original,
+     * which differs in two ways from the original:
+     *   (1) It uses a template image for each c.c. instead of the
+     *       original instance, for each occurrence on each page.
+     *   (2) It discards components with either a height or width larger
+     *       than the maximuma, given here by the lattice dimensions
+     *       used for storing the templates.   */
+struct JbData
+{
+    struct Pix         *pix;        /* template composite for all classes    */
+    l_int32             npages;     /* number of pages                       */
+    l_int32             w;          /* max width of original page images     */
+    l_int32             h;          /* max height of original page images    */
+    l_int32             nclass;     /* number of classes                     */
+    l_int32             latticew;   /* lattice width for template composite  */
+    l_int32             latticeh;   /* lattice height for template composite */
+    struct Numa        *naclass;    /* array of class ids for each component */
+    struct Numa        *napage;     /* array of page nums for each component */
+    struct Pta         *ptaul;      /* array of UL corners at which the      */
+                                    /* template is to be placed for each     */
+                                    /* component                             */
+};
+typedef struct JbData  JBDATA;
+
+
+    /* Classifier methods */
+enum {
+   JB_RANKHAUS = 0,
+   JB_CORRELATION = 1
+};
+
+    /* For jbGetComponents(): type of component to extract from images */
+enum {
+   JB_CONN_COMPS = 0,
+   JB_CHARACTERS = 1,
+   JB_WORDS = 2
+};
+
+    /* These parameters are used for naming the two files
+     * in which the jbig2-like compressed data is stored.  */
+#define   JB_TEMPLATE_EXT      ".templates.png"
+#define   JB_DATA_EXT          ".data"
+
+
+#endif  /* LEPTONICA_JBCLASS_H */
diff --git a/src/jp2kheader.c b/src/jp2kheader.c
new file mode 100644 (file)
index 0000000..f91c42d
--- /dev/null
@@ -0,0 +1,296 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  jp2kheader.c
+ *
+ *      Read header
+ *          l_int32          readHeaderJp2k()
+ *          l_int32          freadHeaderJp2k()
+ *          l_int32          readHeaderMemJp2k()
+ *          l_int32          fgetJp2kResolution()
+ *
+ *  Note: these function read image metadata from a jp2k file, without
+ *  using any jp2k libraries.
+ *
+ *  To read and write jp2k data, using the OpenJPEG library
+ *  (http://www.openjpeg.org), see jpegio.c.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_IHDR        0
+#endif  /* ~NO_CONSOLE_IO */
+
+/* --------------------------------------------*/
+#if  USE_JP2KHEADER   /* defined in environ.h */
+/* --------------------------------------------*/
+
+    /* a sanity check on the size read from file */
+static const l_int32  MAX_JP2K_WIDTH = 100000;
+static const l_int32  MAX_JP2K_HEIGHT = 100000;
+
+/*--------------------------------------------------------------------*
+ *                          Stream interface                          *
+ *--------------------------------------------------------------------*/
+/*!
+ *  readHeaderJp2k()
+ *
+ *      Input:  filename
+ *              &w (<optional return>)
+ *              &h (<optional return>)
+ *              &bps (<optional return>, bits/sample)
+ *              &spp (<optional return>, samples/pixel)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+readHeaderJp2k(const char *filename,
+               l_int32    *pw,
+               l_int32    *ph,
+               l_int32    *pbps,
+               l_int32    *pspp)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("readHeaderJp2k");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pbps) *pbps = 0;
+    if (pspp) *pspp = 0;
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT("image file not found", procName, 1);
+    ret = freadHeaderJp2k(fp, pw, ph, pbps, pspp);
+    fclose(fp);
+    return ret;
+}
+
+
+/*!
+ *  freadHeaderJp2k()
+ *
+ *      Input:  stream opened for read
+ *              &w (<optional return>)
+ *              &h (<optional return>)
+ *              &bps (<optional return>, bits/sample)
+ *              &spp (<optional return>, samples/pixel)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+freadHeaderJp2k(FILE     *fp,
+                l_int32  *pw,
+                l_int32  *ph,
+                l_int32  *pbps,
+                l_int32  *pspp)
+{
+l_uint8  buf[80];  /* just need the first 80 bytes */
+l_int32  nread;
+
+    PROCNAME("freadHeaderJp2k");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pbps) *pbps = 0;
+    if (pspp) *pspp = 0;
+    if (!fp)
+        return ERROR_INT("fp not defined", procName, 1);
+
+    rewind(fp);
+    nread = fread(buf, 1, sizeof(buf), fp);
+    if (nread != sizeof(buf))
+        return ERROR_INT("read failure", procName, 1);
+
+    readHeaderMemJp2k(buf, sizeof(buf), pw, ph, pbps, pspp);
+    rewind(fp);
+    return 0;
+}
+
+
+/*!
+ *  readHeaderMemJp2k()
+ *
+ *      Input:  data
+ *              size (at least 80)
+ *              &w (<optional return>)
+ *              &h (<optional return>)
+ *              &bps (<optional return>, bits/sample)
+ *              &spp (<optional return>, samples/pixel)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The ISO/IEC reference for jpeg2000 is
+ *               http://www.jpeg.org/public/15444-1annexi.pdf
+ *          and the file format syntax begins at page 127.
+ *      (2) The Image Header Box begins with 'ihdr' = 0x69686472 in
+ *          big-endian order.  This typically, but not always, starts
+ *          byte 44, with the big-endian data fields beginning at byte 48:
+ *               h:    4 bytes
+ *               w:    4 bytes
+ *               spp:  2 bytes
+ *               bps:  1 byte   (contains bps - 1)
+ */
+l_int32
+readHeaderMemJp2k(const l_uint8  *data,
+                  size_t          size,
+                  l_int32        *pw,
+                  l_int32        *ph,
+                  l_int32        *pbps,
+                  l_int32        *pspp)
+{
+l_int32  format, val, w, h, bps, spp, loc, found, windex;
+l_uint8  ihdr[4] = {0x69, 0x68, 0x64, 0x72};  /* 'ihdr' */
+
+    PROCNAME("readHeaderMemJp2k");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pbps) *pbps = 0;
+    if (pspp) *pspp = 0;
+    if (!data)
+        return ERROR_INT("data not defined", procName, 1);
+    if (size < 80)
+        return ERROR_INT("size < 80", procName, 1);
+    findFileFormatBuffer(data, &format);
+    if (format != IFF_JP2)
+        return ERROR_INT("not jp2 file", procName, 1);
+
+        /* Search for beginning of the Image Header Box: 'ihdr' */
+    arrayFindSequence(data, size, ihdr, 4, &loc, &found);
+    if (!found)
+        return ERROR_INT("image parameters not found", procName, 1);
+#if  DEBUG_IHDR
+    if (loc != 44)
+        L_INFO("Beginning of ihdr is at byte %d\n", procName, loc);
+#endif  /* DEBUG_IHDR */
+
+    windex = loc / 4 + 1;
+    val = *((l_uint32 *)data + windex);
+    h = convertOnLittleEnd32(val);
+    val = *((l_uint32 *)data + windex + 1);
+    w = convertOnLittleEnd32(val);
+    val = *((l_uint16 *)data + 2 * (windex + 2));
+    spp = convertOnLittleEnd16(val);
+    bps = *(data + 4 * (windex + 2) + 2) + 1;
+    if (w > MAX_JP2K_WIDTH || h > MAX_JP2K_HEIGHT)
+        return ERROR_INT("unrealistically large sizes", procName, 1);
+    if (pw) *pw = w;
+    if (ph) *ph = h;
+    if (pbps) *pbps = bps;
+    if (pspp) *pspp = spp;
+    return 0;
+}
+
+
+/*
+ *  fgetJp2kResolution()
+ *
+ *      Input:  stream (opened for read)
+ *              &xres, &yres (<return> resolution in ppi)
+ *      Return: 0 if found; 1 if not found or on error
+ *
+ *  Notes:
+ *      (1) If the capture resolution field is not set, this is not an error;
+ *          the returned resolution values are 0 (designating 'unknown').
+ *      (2) Side-effect: this rewinds the stream.
+ *      (3) The capture resolution box is optional in the jp2 spec, and
+ *          it is usually not written.
+ *      (4) The big-endian data fields that follow the 4 bytes of 'resc' are:
+ *             ynum:    2 bytes
+ *             ydenom:  2 bytes
+ *             xnum:    2 bytes
+ *             xdenom:  2 bytes
+ *             yexp:    1 byte
+ *             xexp:    1 byte
+ */
+l_int32
+fgetJp2kResolution(FILE     *fp,
+                   l_int32  *pxres,
+                   l_int32  *pyres)
+{
+l_uint8    xexp, yexp;
+l_uint8   *data;
+l_uint16   xnum, ynum, xdenom, ydenom;  /* these jp2k fields are 2-byte */
+l_int32    loc, found;
+l_uint8    resc[4] = {0x72, 0x65, 0x73, 0x63};  /* 'resc' */
+size_t     nbytes;
+l_float64  xres, yres;
+
+    PROCNAME("fgetJp2kResolution");
+
+    if (pxres) *pxres = 0;
+    if (pyres) *pyres = 0;
+    if (!pxres || !pyres)
+        return ERROR_INT("&xres and &yres not both defined", procName, 1);
+    if (!fp)
+        return ERROR_INT("stream not opened", procName, 1);
+
+    rewind(fp);
+    data = l_binaryReadStream(fp, &nbytes);
+    rewind(fp);
+
+        /* Search for the start of the first capture resolution box: 'resc' */
+    arrayFindSequence(data, nbytes, resc, 4, &loc, &found);
+    if (!found) {
+        L_WARNING("image resolution not found\n", procName);
+        LEPT_FREE(data);
+        return 1;
+    }
+
+        /* Extract the fields and calculate the resolution in pixels/meter.
+         * See section 1.5.3.7.1 of JPEG 2000 ISO/IEC 15444-1 spec.  */
+    ynum = data[loc + 5] << 8 | data[loc + 4];
+    ynum = convertOnLittleEnd16(ynum);
+    ydenom = data[loc + 7] << 8 | data[loc + 6];
+    ydenom = convertOnLittleEnd16(ydenom);
+    xnum = data[loc + 9] << 8 | data[loc + 8];
+    xnum = convertOnLittleEnd16(xnum);
+    xdenom = data[loc + 11] << 8 | data[loc + 10];
+    xdenom = convertOnLittleEnd16(xdenom);
+    yexp = data[loc + 12];
+    xexp = data[loc + 13];
+    yres = ((l_float64)ynum / (l_float64)ydenom) * pow(10.0, (l_float64)yexp);
+    xres = ((l_float64)xnum / (l_float64)xdenom) * pow(10.0, (l_float64)xexp);
+
+        /* Convert from pixels/meter to ppi */
+    yres *= (300.0 / 11811.0);
+    xres *= (300.0 / 11811.0);
+    *pyres = (l_int32)(yres + 0.5);
+    *pxres = (l_int32)(xres + 0.5);
+
+    LEPT_FREE(data);
+    return 0;
+}
+
+/* --------------------------------------------*/
+#endif  /* USE_JP2KHEADER */
+/* --------------------------------------------*/
diff --git a/src/jp2kheaderstub.c b/src/jp2kheaderstub.c
new file mode 100644 (file)
index 0000000..3043032
--- /dev/null
@@ -0,0 +1,70 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  jp2kheaderstub.c
+ *
+ *     Stubs for jp2kheader.c functions
+ */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !USE_JP2KHEADER   /* defined in environ.h */
+/* --------------------------------------------*/
+
+l_int32 readHeaderJp2k(const char *filename, l_int32 *pw, l_int32 *ph,
+                       l_int32 *pbps, l_int32 *pspp)
+{
+    return ERROR_INT("function not present", "readHeaderJp2k", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 freadHeaderJp2k(FILE *fp, l_int32 *pw, l_int32 *ph,
+                        l_int32 *pbps, l_int32 *pspp)
+{
+    return ERROR_INT("function not present", "freadHeaderJp2k", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 readHeaderMemJp2k(const l_uint8 *cdata, size_t size, l_int32 *pw,
+                          l_int32 *ph, l_int32 *pbps, l_int32 *pspp)
+{
+    return ERROR_INT("function not present", "readHeaderMemJp2k", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 fgetJp2kResolution(FILE *fp, l_int32 *pxres, l_int32 *pyres)
+{
+    return ERROR_INT("function not present", "fgetJp2kResolution", 1);
+}
+
+/* --------------------------------------------*/
+#endif  /* !USE_JP2KHEADER */
+/* --------------------------------------------*/
diff --git a/src/jp2kio.c b/src/jp2kio.c
new file mode 100644 (file)
index 0000000..0bb119e
--- /dev/null
@@ -0,0 +1,935 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  jp2kio.c
+ *
+ *    Read jp2k from file
+ *          PIX                *pixReadJp2k()  [special top level]
+ *          PIX                *pixReadStreamJp2k()
+ *
+ *    Write jp2k to file
+ *          l_int32             pixWriteJp2k()  [special top level]
+ *          l_int32             pixWriteStreamJp2k()
+ *          static opj_image_t *pixConvertToOpjImage()
+ *
+ *    Read/write to memory
+ *          PIX                *pixReadMemJp2k()
+ *          l_int32             pixWriteMemJp2k()
+ *
+ *    Static functions from opj 2.0 to retain file stream interface
+ *          static opj_stream_t  *opjCreateStream()
+ *          [other static helpers]
+ *
+ *    Based on the OpenJPEG distribution:
+ *        http://www.openjpeg.org/
+ *    The ISO/IEC reference for jpeg2000 is:
+ *        http://www.jpeg.org/public/15444-1annexi.pdf
+ *
+ *    Compressing to memory and decompressing from memory
+ *    ---------------------------------------------------
+ *    On systems like windows without fmemopen() and open_memstream(),
+ *    we write data to a temp file and read it back for operations
+ *    between pix and compressed-data, such as pixReadMemJp2k() and
+ *    pixWriteMemJp2k().
+ *
+ *    Pdf can accept jp2k compressed strings directly
+ *    -----------------------------------------------
+ *    Transcoding (with the uncompress/compress cycle) is not required
+ *    to wrap images that have already been compressed with jp2k in pdf,
+ *    because the pdf format for jp2k includes the full string of the
+ *    jp2k compressed images.  This is also true for jpeg compressed
+ *    strings.
+ *
+ *    N.B.
+ *    * This is based on the most recent openjpeg release: 2.1.
+ *    * The openjpeg interface was massively changed from 1.X.  The debian
+ *      distribution is way back at 1.3.  We have inquired but are unable
+ *      to determine if or when a debian distribution will be built for 2.1.
+ *    * For version 2.1, the openjpeg.h file is installed in an
+ *      openjpeg-2.1 subdirectory, which is hard to support.
+ *    * In openjpeg-2.1, reading is slow compared to jpeg or webp,
+ *      and writing is very slow compared to jpeg or webp.  This is expected
+ *      to improve significantly in future versions.
+ *    * Reading and writing jp2k are supported here for 2.1.
+ *      The high-level interface to openjpeg continues to change.
+ *      From 2.0 to 2.1, the ability to interface to a C file stream
+ *      was removed permanently.  Leptonica supports both file stream
+ *      and memory buffer interfaces for every image I/O library, and
+ *      it requires the libraries to support at least one of these.
+ *      However, openjpeg-2.1 provides neither, so we have brought
+ *      several static functions over from openjpeg-2.0 in order to
+ *      retain the file stream interface.  See our static function
+ *      opjCreateStream().
+ *    * Specifying a quality factor for jpeg2000 requires caution.  Unlike
+ *      jpeg and webp, which have a sensible scale that goes from 0 (very poor)
+ *      to 100 (nearly lossless), kakadu and openjpeg use idiosyncratic and
+ *      non-intuitive numbers.  kakadu uses "rate/distortion" numbers in
+ *      a narrow range around 50,000; openjpeg (and our write interface)
+ *      use SNR.  The visually apparent artifacts introduced by compression
+ *      are strongly content-dependent and vary in a highly non-linear
+ *      way with SNR.  We take SNR = 34 as default, roughly similar in
+ *      quality to jpeg's default standard of 75.  For document images,
+ *      SNR = 25 is very poor, whereas SNR = 45 is nearly lossless.  If you
+ *      use the latter, you will pay dearly in the size of the compressed file.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  HAVE_LIBJP2K   /* defined in environ.h */
+/* --------------------------------------------*/
+
+    /* Leptonica supports both 2.0 and 2.1. */
+#include LIBJP2K_HEADER
+
+    /* 2.0 didn't define OPJ_VERSION_MINOR. */
+#ifndef OPJ_VERSION_MINOR
+#define OPJ_VERSION_MINOR 0
+#endif
+
+    /* Static generator of opj_stream from file stream.
+     * In 2.0.1, this functionality is provided by
+     *    opj_stream_create_default_file_stream(),
+     * but it was removed in 2.1.0. Because we must have either
+     * a file stream or a memory interface to the compressed data,
+     * it is necessary to recreate the stream interface here.  */
+static opj_stream_t *opjCreateStream(FILE *fp, l_int32 is_read);
+
+    /* Static converter pix --> opj_image.  Used for compressing pix,
+     * because the codec works on data stored in their raster format. */
+static opj_image_t *pixConvertToOpjImage(PIX *pix);
+
+/*---------------------------------------------------------------------*
+ *                        Callback event handlers                      *
+ *---------------------------------------------------------------------*/
+static void error_callback(const char *msg, void *client_data) {
+  (void)client_data;
+  fprintf(stdout, "[ERROR] %s", msg);
+}
+
+static void warning_callback(const char *msg, void *client_data) {
+  (void)client_data;
+  fprintf(stdout, "[WARNING] %s", msg);
+}
+
+static void info_callback(const char *msg, void *client_data) {
+  (void)client_data;
+  fprintf(stdout, "[INFO] %s", msg);
+}
+
+
+/*---------------------------------------------------------------------*
+ *                 Read jp2k from file (special function)              *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixReadJp2k()
+ *
+ *      Input:  filename
+ *              reduction (scaling factor: 1, 2, 4, 8, 16)
+ *              box  (<optional> for extracting a subregion), can be null
+ *              hint (a bitwise OR of L_JP2K_* values; 0 for default)
+ *              debug (output callback messages, etc)
+ *      Return: pix (8 or 32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This is a special function for reading jp2k files.
+ *          The high-level pixReadStream() uses default values:
+ *             @reduction = 1
+ *             @box = NULL
+ *      (2) This decodes at either full resolution or at a reduction by
+ *          a power of 2.  The default value @reduction == 1 gives a full
+ *          resolution image.  Use @reduction > 1 to get a reduced image.
+ *          The actual values of @reduction that can be used on an image
+ *          depend on the number of resolution levels chosen when the
+ *          image was compressed.  Typical values might be 1, 2, 4, 8 and 16.
+ *          Using a value representing a reduction level that was not
+ *          stored when the file was written will fail with the message:
+ *          "failed to read the header".
+ *      (3) Use @box to decode only a part of the image.  The box is defined
+ *          at full resolution.  It is reduced internally by @reduction,
+ *          and clipping to the right and bottom of the image is automatic.
+ *      (4) We presently only handle images with 8 bits/sample (bps).
+ *          If the image has 16 bps, the read will fail.
+ *      (5) There are 4 possible values of samples/pixel (spp).
+ *          The values in brackets give the pixel values in the Pix:
+ *           spp = 1  ==>  grayscale           [8 bpp grayscale]
+ *           spp = 2  ==>  grayscale + alpha   [32 bpp rgba]
+ *           spp = 3  ==>  rgb                 [32 bpp rgb]
+ *           spp = 4  ==>  rgba                [32 bpp rgba]
+ *      (6) The @hint parameter is reserved for future use.
+ */
+PIX *
+pixReadJp2k(const char  *filename,
+            l_uint32     reduction,
+            BOX         *box,
+            l_int32      hint,
+            l_int32      debug)
+{
+FILE     *fp;
+PIX      *pix;
+
+    PROCNAME("pixReadJp2k");
+
+    if (!filename)
+        return (PIX *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PIX *)ERROR_PTR("image file not found", procName, NULL);
+    pix = pixReadStreamJp2k(fp, reduction, box, hint, debug);
+    fclose(fp);
+
+    if (!pix)
+        return (PIX *)ERROR_PTR("image not returned", procName, NULL);
+    return pix;
+}
+
+
+/*!
+ *  pixReadStreamJp2k()
+ *
+ *      Input:  stream
+ *              reduction (scaling factor: 1, 2, 4, 8)
+ *              box  (<optional> for extracting a subregion), can be null
+ *              hint (a bitwise OR of L_JP2K_* values; 0 for default)
+ *              debug (output callback messages, etc)
+ *      Return: pix (8 or 32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) See pixReadJp2k() for usage.
+ */
+PIX *
+pixReadStreamJp2k(FILE     *fp,
+                  l_uint32  reduction,
+                  BOX      *box,
+                  l_int32   hint,
+                  l_int32   debug)
+{
+const char        *opjVersion;
+l_int32            i, j, index, bx, by, bw, bh, val, rval, gval, bval, aval;
+l_int32            w, h, wpl, bps, spp, xres, yres, reduce, prec, colorspace;
+l_uint32           pixel;
+l_uint32          *data, *line;
+opj_dparameters_t  parameters;   /* decompression parameters */
+opj_image_t       *image = NULL;
+opj_codec_t       *l_codec = NULL;  /* handle to decompressor */
+opj_stream_t      *l_stream = NULL;  /* opj stream */
+PIX               *pix = NULL;
+
+    PROCNAME("pixReadStreamJp2k");
+
+    if (!fp)
+        return (PIX *)ERROR_PTR("fp not defined", procName, NULL);
+
+    opjVersion = opj_version();
+    if (opjVersion[0] != '2') {
+        L_ERROR("version is %s; must be 2.0 or higher\n", procName, opjVersion);
+        return NULL;
+    }
+    if ((opjVersion[2] - 0x30) != OPJ_VERSION_MINOR) {
+        L_ERROR("version %s: differs from minor = %d\n",
+                procName, opjVersion, OPJ_VERSION_MINOR);
+         return NULL;
+     }
+
+        /* Get the resolution and the bits/sample */
+    rewind(fp);
+    fgetJp2kResolution(fp, &xres, &yres);
+    freadHeaderJp2k(fp, NULL, NULL, &bps, NULL);
+    rewind(fp);
+
+    if (bps > 8) {
+        L_ERROR("found %d bps; can only handle 8 bps\n", procName, bps);
+        return NULL;
+    }
+
+        /* Set decoding parameters to default values */
+    opj_set_default_decoder_parameters(&parameters);
+
+        /* Find and set the reduce parameter, which is log2(reduction).
+         * Valid reductions are powers of 2, and are determined when the
+         * compressed string is made.  A request for an invalid reduction
+         * will cause an error in opj_read_header(), and no image will
+         * be returned. */
+    for (reduce = 0; (1L << reduce) < reduction; reduce++) { }
+    if ((1L << reduce) != reduction) {
+        L_ERROR("invalid reduction %d; not power of 2\n", procName, reduction);
+        return NULL;
+    }
+    parameters.cp_reduce = reduce;
+
+        /* Open decompression 'stream'.  In 2.0, we could call this:
+         *    opj_stream_create_default_file_stream(fp, 1)
+         * but the file stream interface was removed in 2.1. */
+    if ((l_stream = opjCreateStream(fp, 1)) == NULL) {
+        L_ERROR("failed to open the stream\n", procName);
+        return NULL;
+    }
+
+    if ((l_codec = opj_create_decompress(OPJ_CODEC_JP2)) == NULL) {
+        L_ERROR("failed to make the codec\n", procName);
+        opj_stream_destroy(l_stream);
+        return NULL;
+    }
+
+        /* Catch and report events using callbacks */
+    if (debug) {
+        opj_set_info_handler(l_codec, info_callback, NULL);
+        opj_set_warning_handler(l_codec, warning_callback, NULL);
+        opj_set_error_handler(l_codec, error_callback, NULL);
+    }
+
+        /* Setup the decoding parameters using user parameters */
+    if (!opj_setup_decoder(l_codec, &parameters)){
+        L_ERROR("failed to set up decoder\n", procName);
+        opj_stream_destroy(l_stream);
+        opj_destroy_codec(l_codec);
+        return NULL;
+    }
+
+        /* Read the main header of the codestream and, if necessary,
+         * the JP2 boxes */
+    if(!opj_read_header(l_stream, l_codec, &image)){
+        L_ERROR("failed to read the header\n", procName);
+        opj_stream_destroy(l_stream);
+        opj_destroy_codec(l_codec);
+        opj_image_destroy(image);
+        return NULL;
+    }
+
+        /* Set up to decode a rectangular region */
+    if (box) {
+        boxGetGeometry(box, &bx, &by, &bw, &bh);
+        if (!opj_set_decode_area(l_codec, image, bx, by,
+                                 bx + bw, by + bh)) {
+            L_ERROR("failed to set the region for decoding\n", procName);
+            opj_stream_destroy(l_stream);
+            opj_destroy_codec(l_codec);
+            opj_image_destroy(image);
+            return NULL;
+        }
+    }
+
+        /* Get the decoded image */
+    if (!(opj_decode(l_codec, l_stream, image) &&
+          opj_end_decompress(l_codec, l_stream))) {
+        L_ERROR("failed to decode the image\n", procName);
+        opj_destroy_codec(l_codec);
+        opj_stream_destroy(l_stream);
+        opj_image_destroy(image);
+        return NULL;
+    }
+
+        /* Close the byte stream */
+    opj_stream_destroy(l_stream);
+
+        /* Get the image parameters */
+    spp = image->numcomps;
+    w = image->comps[0].w;
+    h = image->comps[0].h;
+    prec = image->comps[0].prec;
+    if (prec != bps)
+        L_WARNING("precision %d != bps %d!\n", procName, prec, bps);
+    if (debug) {
+        L_INFO("w = %d, h = %d, bps = %d, spp = %d\n",
+               procName, w, h, bps, spp);
+        colorspace = image->color_space;
+        if (colorspace == OPJ_CLRSPC_SRGB)
+            L_INFO("colorspace is sRGB\n", procName);
+        else if (colorspace == OPJ_CLRSPC_GRAY)
+            L_INFO("colorspace is grayscale\n", procName);
+        else if (colorspace == OPJ_CLRSPC_SYCC)
+            L_INFO("colorspace is YUV\n", procName);
+    }
+
+        /* Free the codec structure */
+    if (l_codec)
+        opj_destroy_codec(l_codec);
+
+        /* Convert the image to a pix */
+    if (spp == 1)
+        pix = pixCreate(w, h, 8);
+    else
+        pix = pixCreate(w, h, 32);
+    pixSetInputFormat(pix, IFF_JP2);
+    pixSetResolution(pix, xres, yres);
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    index = 0;
+    if (spp == 1) {
+        for (i = 0; i < h; i++) {
+            line = data + i * wpl;
+            for (j = 0; j < w; j++) {
+                val = image->comps[0].data[index];
+                SET_DATA_BYTE(line, j, val);
+                index++;
+            }
+        }
+    } else if (spp == 2) {  /* convert to RGBA */
+        for (i = 0; i < h; i++) {
+            line = data + i * wpl;
+            for (j = 0; j < w; j++) {
+                val = image->comps[0].data[index];
+                aval = image->comps[1].data[index];
+                composeRGBAPixel(val, val, val, aval, &pixel);
+                line[j] = pixel;
+                index++;
+            }
+        }
+    } else if (spp >= 3) {
+        for (i = 0; i < h; i++) {
+            line = data + i * wpl;
+            for (j = 0; j < w; j++) {
+                rval = image->comps[0].data[index];
+                gval = image->comps[1].data[index];
+                bval = image->comps[2].data[index];
+                if (spp == 3) {
+                    composeRGBPixel(rval, gval, bval, &pixel);
+                } else {  /* spp == 4 */
+                    aval = image->comps[3].data[index];
+                    composeRGBAPixel(rval, gval, bval, aval, &pixel);
+                }
+                line[j] = pixel;
+                index++;
+            }
+        }
+    }
+
+        /* Free the opj image data structure */
+    opj_image_destroy(image);
+
+    return pix;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Write jp2k to file                           *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixWriteJp2k()
+ *
+ *      Input:  filename
+ *              pix  (any depth, cmap is OK)
+ *              quality (SNR > 0; default ~34; 0 for lossless encoding)
+ *              nlevels (resolution levels; <= 10; default = 5)
+ *              hint (a bitwise OR of L_JP2K_* values; 0 for default)
+ *              debug (output callback messages, etc)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The @quality parameter is the SNR.  The useful range is narrow:
+ *             SNR < 27  (terrible quality)
+ *             SNR = 34  (default; approximately equivalent to jpeg quality 75)
+ *             SNR = 40  (very high quality)
+ *             SNR = 45  (nearly lossless)
+ *          Use 0 for default.
+ *      (2) The @nlevels parameter is the number of resolution levels
+ *          to be written.  For example, with nlevels == 5, images with
+ *          reduction factors of 1, 2, 4, 8 and 16 are encoded, and retrieval
+ *          is done at the level requested when reading.  For default,
+ *          use either 5 or 0.
+ *      (3) The @hint parameter is not yet in use.
+ *      (4) For now, we only support 1 "layer" for quality.
+ */
+l_int32
+pixWriteJp2k(const char  *filename,
+             PIX         *pix,
+             l_int32      quality,
+             l_int32      nlevels,
+             l_int32      hint,
+             l_int32      debug)
+{
+FILE  *fp;
+
+    PROCNAME("pixWriteJp2k");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb+")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+
+    if (pixWriteStreamJp2k(fp, pix, quality, nlevels, hint, debug)) {
+        fclose(fp);
+        return ERROR_INT("pix not written to stream", procName, 1);
+    }
+
+    fclose(fp);
+    return 0;
+}
+
+
+/*!
+ *  pixWriteStreamJp2k()
+ *
+ *      Input:  stream
+ *              pix  (any depth, cmap is OK)
+ *              quality (SNR > 0; default ~34; 0 for lossless encoding)
+ *              nlevels (<= 10)
+ *              hint (a bitwise OR of L_JP2K_* values; 0 for default)
+ *              debug (output callback messages, etc)
+ *      Return: 0 if OK, 1 on error
+ *  Notes:
+ *      (1) See pixWriteJp2k() for usage.
+ *      (2) For an encoder with more encoding options, see, e.g.,
+ *    https://github.com/OpenJPEG/openjpeg/blob/master/tests/test_tile_encoder.c
+ */
+l_int32
+pixWriteStreamJp2k(FILE    *fp,
+                   PIX     *pix,
+                   l_int32  quality,
+                   l_int32  nlevels,
+                   l_int32  hint,
+                   l_int32  debug)
+{
+l_int32            w, h, d, success, snr;
+const char        *opjVersion;
+PIX               *pixs;
+opj_cparameters_t  parameters;   /* compression parameters */
+opj_stream_t      *l_stream = NULL;
+opj_codec_t*       l_codec = NULL;;
+opj_image_t       *image = NULL;
+
+    PROCNAME("pixWriteStreamJp2k");
+
+    if (!fp)
+        return ERROR_INT("stream not open", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (quality < 0)
+        return ERROR_INT("quality must be >= 0", procName, 1);
+    if (quality > 0 && quality < 27)
+        L_WARNING("SNR = %d < 27; very low\n", procName, quality);
+    if (quality > 45)
+        L_WARNING("SNR = %d > 45; nearly lossless\n", procName, quality);
+    snr = (l_float32)quality;
+
+    if (nlevels <= 0) nlevels = 5;  /* default */
+    if (nlevels > 10) {
+        L_WARNING("nlevels = %d > 10; setting to 10\n", procName, nlevels);
+        nlevels = 10;
+    }
+
+    opjVersion = opj_version();
+    if (opjVersion[0] != '2') {
+        L_ERROR("version is %s; must be 2.0 or higher\n", procName, opjVersion);
+        return 1;
+    }
+    if ((opjVersion[2] - 0x30) != OPJ_VERSION_MINOR) {
+        L_ERROR("version %s: differs from minor = %d\n",
+                procName, opjVersion, OPJ_VERSION_MINOR);
+         return 1;
+     }
+
+        /* Remove colormap if it exists; result is 8 or 32 bpp */
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d == 24) {
+        pixs = pixConvert24To32(pix);
+    } else if (d == 32) {
+        pixs = pixClone(pix);
+    } else if (pixGetColormap(pix) == NULL) {
+        pixs = pixConvertTo8(pix, 0);
+    } else {  /* colormap */
+        L_INFO("removing colormap; may be better to compress losslessly\n",
+               procName);
+        pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+    }
+
+        /* Convert to opj image format. */
+    image = pixConvertToOpjImage(pixs);
+    pixDestroy(&pixs);
+
+        /* Set encoding parameters to default values.
+         * We use one layer with the input SNR. */
+    opj_set_default_encoder_parameters(&parameters);
+    parameters.cp_fixed_quality = 1;
+    parameters.cp_disto_alloc = 0;
+    parameters.cp_fixed_alloc =  0;
+    parameters.tcp_distoratio[0] = snr;
+    parameters.tcp_numlayers = 1;
+    parameters.numresolution = nlevels + 1;
+
+        /* Create comment for codestream */
+    if (parameters.cp_comment == NULL) {
+        const char comment1[] = "Created by Leptonica, version ";
+        const char comment2[] = "; using OpenJPEG, version ";
+        size_t len1 = strlen(comment1);
+        size_t len2 = strlen(comment2);
+        char *version1 = getLeptonicaVersion();
+        const char *version2 = opj_version();
+        len1 += len2 + strlen(version1) + strlen(version2) + 1;
+        parameters.cp_comment = (char *)LEPT_MALLOC(len1);
+        snprintf(parameters.cp_comment, len1, "%s%s%s%s", comment1, version1,
+                 comment2, version2);
+        LEPT_FREE(version1);
+    }
+
+        /* Get the encoder handle */
+    if ((l_codec = opj_create_compress(OPJ_CODEC_JP2)) == NULL) {
+        opj_image_destroy(image);
+        LEPT_FREE(parameters.cp_comment);
+        return ERROR_INT("failed to get the encoder handle\n", procName, 1);
+    }
+
+        /* Catch and report events using callbacks */
+    if (debug) {
+        opj_set_info_handler(l_codec, info_callback, NULL);
+        opj_set_warning_handler(l_codec, warning_callback, NULL);
+        opj_set_error_handler(l_codec, error_callback, NULL);
+    }
+
+        /* Set up the encoder */
+    if (!opj_setup_encoder(l_codec, &parameters, image)) {
+        opj_destroy_codec(l_codec);
+        opj_image_destroy(image);
+        LEPT_FREE(parameters.cp_comment);
+        return ERROR_INT("failed to set up the encoder\n", procName, 1);
+    }
+
+        /* Open a compression stream for writing.  In 2.0 we could use this:
+         *     opj_stream_create_default_file_stream(fp, 0)
+         * but the file stream interface was removed in 2.1.  */
+    rewind(fp);
+    if ((l_stream = opjCreateStream(fp, 0)) == NULL) {
+        opj_destroy_codec(l_codec);
+        opj_image_destroy(image);
+        LEPT_FREE(parameters.cp_comment);
+        return ERROR_INT("failed to open l_stream\n", procName, 1);
+    }
+
+        /* Encode the image */
+    if (!opj_start_compress(l_codec, image, l_stream)) {
+        opj_stream_destroy(l_stream);
+        opj_destroy_codec(l_codec);
+        opj_image_destroy(image);
+        LEPT_FREE(parameters.cp_comment);
+        return ERROR_INT("opj_start_compress failed\n", procName, 1);
+    }
+    if (!opj_encode(l_codec, l_stream)) {
+        opj_stream_destroy(l_stream);
+        opj_destroy_codec(l_codec);
+        opj_image_destroy(image);
+        LEPT_FREE(parameters.cp_comment);
+        return ERROR_INT("opj_encode failed\n", procName, 1);
+    }
+    success = opj_end_compress(l_codec, l_stream);
+
+        /* Clean up */
+    opj_stream_destroy(l_stream);
+    opj_destroy_codec(l_codec);
+    opj_image_destroy(image);
+    LEPT_FREE(parameters.cp_comment);
+    if (success)
+        return 0;
+    else
+        return ERROR_INT("opj_end_compress failed\n", procName, 1);
+}
+
+
+/*!
+ *  pixConvertToOpjImage()
+ *
+ *      Input:  pix  (8 or 32 bpp)
+ *      Return: opj_image, or NULL on error
+ *
+ *  Notes:
+ *      (1) Input pix is 8 bpp grayscale, 32 bpp rgb, or 32 bpp rgba.
+ *      (2) Gray + alpha pix are all represented as rgba.
+ */
+static opj_image_t *
+pixConvertToOpjImage(PIX  *pix)
+{
+l_int32               i, j, k, w, h, d, spp, wpl;
+OPJ_COLOR_SPACE       colorspace;
+l_int32              *ir = NULL;
+l_int32              *ig = NULL;
+l_int32              *ib = NULL;
+l_int32              *ia = NULL;
+l_uint32             *line, *data;
+opj_image_t          *image;
+opj_image_cmptparm_t  cmptparm[4];
+
+    PROCNAME("pixConvertToOpjImage");
+
+    if (!pix)
+        return (opj_image_t *)ERROR_PTR("pix not defined", procName, NULL);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 8 && d != 32) {
+        L_ERROR("invalid depth: %d\n", procName, d);
+        return NULL;
+    }
+
+        /* Allocate the opj_image. */
+    spp = pixGetSpp(pix);
+    memset(&cmptparm[0], 0, 4 * sizeof(opj_image_cmptparm_t));
+    for (i = 0; i < spp; i++) {
+        cmptparm[i].prec = 8;
+        cmptparm[i].bpp = 8;
+        cmptparm[i].sgnd = 0;
+        cmptparm[i].dx = 1;
+        cmptparm[i].dy = 1;
+        cmptparm[i].w = w;
+        cmptparm[i].h = h;
+    }
+    colorspace = (spp == 1) ? OPJ_CLRSPC_GRAY : OPJ_CLRSPC_SRGB;
+    if ((image = opj_image_create(spp, &cmptparm[0], colorspace)) == NULL)
+        return (opj_image_t *)ERROR_PTR("image not made", procName, NULL);
+    image->x0 = 0;
+    image->y0 = 0;
+    image->x1 = w;
+    image->y1 = h;
+
+        /* Set the component pointers */
+    ir = image->comps[0].data;
+    if (spp > 1) {
+        ig = image->comps[1].data;
+        ib = image->comps[2].data;
+    }
+    if(spp == 4)
+        ia = image->comps[3].data;
+
+        /* Transfer the data from the pix */
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (i = 0, k = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++, k++) {
+            if (spp == 1) {
+                ir[k] = GET_DATA_BYTE(line, j);
+            } else if (spp > 1) {
+                ir[k] = GET_DATA_BYTE(line + j, COLOR_RED);
+                ig[k] = GET_DATA_BYTE(line + j, COLOR_GREEN);
+                ib[k] = GET_DATA_BYTE(line + j, COLOR_BLUE);
+            }
+            if (spp == 4)
+                ia[k] = GET_DATA_BYTE(line + j, L_ALPHA_CHANNEL);
+        }
+    }
+
+    return image;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Read/write to memory                        *
+ *---------------------------------------------------------------------*/
+#if HAVE_FMEMOPEN
+extern FILE *open_memstream(char **data, size_t *size);
+extern FILE *fmemopen(void *data, size_t size, const char *mode);
+#endif  /* HAVE_FMEMOPEN */
+
+/*!
+ *  pixReadMemJp2k()
+ *
+ *      Input:  data (const; jpeg-encoded)
+ *              size (of data)
+ *              reduction (scaling factor: 1, 2, 4, 8)
+ *              box  (<optional> for extracting a subregion), can be null
+ *              hint (a bitwise OR of L_JP2K_* values; 0 for default)
+ *              debug (output callback messages, etc)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) This crashes when reading through the fmemopen cookie.
+ *          Until we can fix this, we use the file-based work-around.
+ *          And fixing this may take some time, because the basic
+ *          stream interface is no longer supported in openjpeg.
+ *      (2) See pixReadJp2k() for usage.
+ */
+PIX *
+pixReadMemJp2k(const l_uint8  *data,
+               size_t          size,
+               l_uint32        reduction,
+               BOX            *box,
+               l_int32         hint,
+               l_int32         debug)
+{
+FILE     *fp;
+PIX      *pix;
+
+    PROCNAME("pixReadMemJp2k");
+
+    if (!data)
+        return (PIX *)ERROR_PTR("data not defined", procName, NULL);
+
+#if HAVE_FMEMOPEN
+    if ((fp = fmemopen((void *)data, size, "rb")) == NULL)
+        return (PIX *)ERROR_PTR("stream not opened", procName, NULL);
+#else
+    L_WARNING("work-around: writing to a temp file\n", procName);
+    if ((fp = tmpfile()) == NULL)
+        return (PIX *)ERROR_PTR("tmpfile stream not opened", procName, NULL);
+    fwrite(data, 1, size, fp);
+    rewind(fp);
+#endif  /* HAVE_FMEMOPEN */
+    pix = pixReadStreamJp2k(fp, reduction, box, hint, debug);
+    fclose(fp);
+    if (!pix) L_ERROR("pix not read\n", procName);
+    return pix;
+}
+
+
+/*!
+ *  pixWriteMemJp2k()
+ *
+ *      Input:  &data (<return> data of jpeg compressed image)
+ *              &size (<return> size of returned data)
+ *              pix (8 or 32 bpp)
+ *              quality (SNR > 0; default ~34; 0 for lossless encoding)
+ *              nlevels (0 for default)
+ *              hint (a bitwise OR of L_JP2K_* values; 0 for default)
+ *              debug (output callback messages, etc)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See pixWriteJp2k() for usage.  This version writes to
+ *          memory instead of to a file stream.
+ */
+l_int32
+pixWriteMemJp2k(l_uint8  **pdata,
+                size_t    *psize,
+                PIX       *pix,
+                l_int32    quality,
+                l_int32    nlevels,
+                l_int32    hint,
+                l_int32    debug)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("pixWriteMemJp2k");
+
+    if (pdata) *pdata = NULL;
+    if (psize) *psize = 0;
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1 );
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1 );
+    if (!pix)
+        return ERROR_INT("&pix not defined", procName, 1 );
+
+#if HAVE_FMEMOPEN
+    if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    ret = pixWriteStreamJp2k(fp, pix, quality, nlevels, hint, debug);
+#else
+    L_WARNING("work-around: writing to a temp file\n", procName);
+    if ((fp = tmpfile()) == NULL)
+        return ERROR_INT("tmpfile stream not opened", procName, 1);
+    ret = pixWriteStreamJp2k(fp, pix, quality, nlevels, hint, debug);
+    rewind(fp);
+    *pdata = l_binaryReadStream(fp, psize);
+#endif  /* HAVE_FMEMOPEN */
+    fclose(fp);
+    return ret;
+}
+
+
+/*---------------------------------------------------------------------*
+ *    Static functions from opj 2.0 to retain file stream interface    *
+ *---------------------------------------------------------------------*/
+static l_uint64
+opj_get_user_data_length(FILE *fp) {
+    OPJ_OFF_T length = 0;
+    fseek(fp, 0, SEEK_END);
+    length = (OPJ_OFF_T)ftell(fp);
+    fseek(fp, 0, SEEK_SET);
+    return (l_uint64)length;
+}
+
+static OPJ_SIZE_T
+opj_read_from_file(void *p_buffer, OPJ_SIZE_T p_nb_bytes, FILE *fp) {
+    OPJ_SIZE_T l_nb_read = fread(p_buffer, 1, p_nb_bytes, fp);
+    return l_nb_read ? l_nb_read : (OPJ_SIZE_T) - 1;
+}
+
+static OPJ_SIZE_T
+opj_write_from_file(void *p_buffer, OPJ_SIZE_T p_nb_bytes, FILE *fp)
+{
+    return fwrite(p_buffer, 1, p_nb_bytes, fp);
+}
+
+static OPJ_OFF_T
+opj_skip_from_file(OPJ_OFF_T offset, FILE *fp) {
+    if (fseek(fp, offset, SEEK_CUR)) {
+        return -1;
+    }
+    return offset;
+}
+
+static l_int32
+opj_seek_from_file(OPJ_OFF_T offset, FILE *fp) {
+    if (fseek(fp, offset, SEEK_SET)) {
+        return 0;
+    }
+    return 1;
+}
+
+    /* Static generator of opj_stream from file stream */
+static opj_stream_t *
+opjCreateStream(FILE    *fp,
+                l_int32  is_read_stream)
+{
+opj_stream_t  *l_stream;
+
+    PROCNAME("opjStreamCreate");
+
+    if (!fp)
+        return (opj_stream_t *)ERROR_PTR("fp not defined", procName, NULL);
+
+    l_stream = opj_stream_create(OPJ_J2K_STREAM_CHUNK_SIZE, is_read_stream);
+    if (!l_stream)
+        return (opj_stream_t *)ERROR_PTR("stream not made", procName, NULL);
+
+#if OPJ_VERSION_MINOR == 0
+    opj_stream_set_user_data(l_stream, fp);
+#else
+    opj_stream_set_user_data(l_stream, fp,
+                             (opj_stream_free_user_data_fn)NULL);
+#endif
+    opj_stream_set_user_data_length(l_stream, opj_get_user_data_length(fp));
+    opj_stream_set_read_function(l_stream,
+                                 (opj_stream_read_fn)opj_read_from_file);
+    opj_stream_set_write_function(l_stream,
+                                  (opj_stream_write_fn)opj_write_from_file);
+    opj_stream_set_skip_function(l_stream,
+                                 (opj_stream_skip_fn)opj_skip_from_file);
+    opj_stream_set_seek_function(l_stream,
+                                 (opj_stream_seek_fn)opj_seek_from_file);
+
+    return l_stream;
+}
+
+/* --------------------------------------------*/
+#endif  /* HAVE_LIBJPEG */
+/* --------------------------------------------*/
diff --git a/src/jp2kiostub.c b/src/jp2kiostub.c
new file mode 100644 (file)
index 0000000..8a0ce43
--- /dev/null
@@ -0,0 +1,96 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  jp2kiostub.c
+ *
+ *     Stubs for jp2kio.c functions
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !HAVE_LIBJP2K   /* defined in environ.h */
+/* --------------------------------------------*/
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadJp2k(const char *filename, l_uint32 reduction, BOX *box,
+                  l_int32 hint, l_int32 debug)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadJp2k", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadStreamJp2k(FILE *fp, l_uint32 reduction, BOX *box,
+                        l_int32 hint, l_int32 debug)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadStreamJp2k", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteJp2k(const char *filename, PIX *pix, l_int32 quality,
+                     l_int32 nlevels, l_int32 hint, l_int32 debug)
+{
+    return ERROR_INT("function not present", "pixWriteJp2k", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteStreamJp2k(FILE *fp, PIX *pix, l_int32 quality,
+                           l_int32 nlevels, l_int32 hint, l_int32 debug)
+{
+    return ERROR_INT("function not present", "pixWriteStreamJp2k", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadMemJp2k(const l_uint8 *data, size_t size, l_uint32 reduction,
+                     BOX *box, l_int32 hint, l_int32 debug)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadMemJp2k", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteMemJp2k(l_uint8 **pdata, size_t *psize, PIX *pix,
+                        l_int32 quality, l_int32 nlevels, l_int32 hint,
+                        l_int32 debug)
+{
+    return ERROR_INT("function not present", "pixWriteMemJp2k", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+/* --------------------------------------------*/
+#endif  /* !HAVE_LIBJP2K */
+/* --------------------------------------------*/
diff --git a/src/jpegio.c b/src/jpegio.c
new file mode 100644 (file)
index 0000000..eb69093
--- /dev/null
@@ -0,0 +1,1241 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  jpegio.c
+ *
+ *    Read jpeg from file
+ *          PIX             *pixReadJpeg()  [special top level]
+ *          PIX             *pixReadStreamJpeg()
+ *
+ *    Read jpeg metadata from file
+ *          l_int32          readHeaderJpeg()
+ *          l_int32          freadHeaderJpeg()
+ *          l_int32          fgetJpegResolution()
+ *          l_int32          fgetJpegComment()
+ *
+ *    Write jpeg to file
+ *          l_int32          pixWriteJpeg()  [special top level]
+ *          l_int32          pixWriteStreamJpeg()
+ *
+ *    Read/write to memory
+ *          PIX             *pixReadMemJpeg()
+ *          l_int32          readHeaderMemJpeg()
+ *          l_int32          pixWriteMemJpeg()
+ *
+ *    Setting special flag for chroma sampling on write
+ *          l_int32          pixSetChromaSampling()
+ *
+ *    Static system helpers
+ *          static void      jpeg_error_catch_all_1()
+ *          static void      jpeg_error_catch_all_2()
+ *          static l_uint8   jpeg_getc()
+ *          static l_int32   jpeg_comment_callback()
+ *
+ *    Documentation: libjpeg.doc can be found, along with all
+ *    source code, at ftp://ftp.uu.net/graphics/jpeg
+ *    Download and untar the file:  jpegsrc.v6b.tar.gz
+ *    A good paper on jpeg can also be found there: wallace.ps.gz
+ *
+ *    The functions in libjpeg make it very simple to compress
+ *    and decompress images.  On input (decompression from file),
+ *    3 component color images can be read into either an 8 bpp Pix
+ *    with a colormap or a 32 bpp Pix with RGB components.  For output
+ *    (compression to file), all color Pix, whether 8 bpp with a
+ *    colormap or 32 bpp, are written compressed as a set of three
+ *    8 bpp (rgb) images.
+ *
+ *    Low-level error handling
+ *    ------------------------
+ *    The default behavior of the jpeg library is to call exit.
+ *    This is often undesirable, and the caller should make the
+ *    decision when to abort a process.  To prevent the jpeg library
+ *    from calling exit(), setjmp() has been inserted into all
+ *    readers and writers, and the cinfo struct has been set up so that
+ *    the low-level jpeg library will call a special error handler
+ *    that doesn't exit, instead of the default function error_exit().
+ *
+ *    To avoid race conditions and make these functions thread-safe in
+ *    the rare situation where calls to two threads are simultaneously
+ *    failing on bad jpegs, we insert a local copy of the jmp_buf struct
+ *    into the cinfo.client_data field, and use this on longjmp.
+ *    For extracting the jpeg comment, we have the added complication
+ *    that the client_data field must also return the jpeg comment,
+ *    and we use a different error handler.
+ *
+ *    How to avoid subsampling the chroma channels
+ *    --------------------------------------------
+ *    When writing, you can avoid subsampling the U,V (chroma)
+ *    channels.  This gives higher quality for the color, which is
+ *    important for some situations.  The default subsampling is 2x2 on
+ *    both channels.  Before writing, call pixSetChromaSampling(pix, 0)
+ *    to prevent chroma subsampling.
+ *
+ *    How to extract just the luminance channel in reading RGB
+ *    --------------------------------------------------------
+ *    For higher resolution and faster decoding of an RGB image, you
+ *    can extract just the 8 bpp luminance channel, using pixReadJpeg(),
+ *    where you use L_JPEG_READ_LUMINANCE for the @hint arg.
+ *
+ *    How to fail to read if the data is corrupted
+ *    ---------------------------------------------
+ *    By default, if the low-level jpeg library functions do not abort,
+ *    a pix will be returned, even if the data is corrupted and warnings
+ *    are issued.  In order to be most likely to fail to read when there
+ *    is data corruption, use L_JPEG_FAIL_ON_BAD_DATA in the @hint arg.
+ *
+ *    Compressing to memory and decompressing from memory
+ *    ---------------------------------------------------
+ *    On systems like windows without fmemopen() and open_memstream(),
+ *    we write data to a temp file and read it back for operations
+ *    between pix and compressed-data, such as pixReadMemJpeg() and
+ *    pixWriteMemJpeg().
+ *
+ *    Vestigial code: parsing the jpeg file for header metadata
+ *    ---------------------------------------------------------
+ *    For extracting header metadata, we previously parsed the file, looking
+ *    for specific markers.  This is error-prone because of non-standard
+ *    jpeg files, and we now use readHeaderJpeg() and readHeaderMemJpeg().
+ *    The vestigial code is retained in jpegio_notused.c to help you
+ *    understand a bit about how to parse jpeg markers.  It is not compiled
+ *    into the library.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  HAVE_LIBJPEG   /* defined in environ.h */
+/* --------------------------------------------*/
+
+#include <setjmp.h>
+
+    /* jconfig.h makes the error of setting
+     *   #define HAVE_STDLIB_H
+     * which conflicts with config_auto.h (where it is set to 1) and results
+     * for some gcc compiler versions in a warning.  The conflict is harmless
+     * but we suppress it by undefining the variable. */
+#undef HAVE_STDLIB_H
+#include "jpeglib.h"
+
+static void jpeg_error_catch_all_1(j_common_ptr cinfo);
+static void jpeg_error_catch_all_2(j_common_ptr cinfo);
+static l_uint8 jpeg_getc(j_decompress_ptr cinfo);
+
+    /* Note: 'boolean' is defined in jmorecfg.h.  We use it explicitly
+     * here because for windows where __MINGW32__ is defined,
+     * the prototype for jpeg_comment_callback() is given as
+     * returning a boolean.  */
+static boolean jpeg_comment_callback(j_decompress_ptr cinfo);
+
+    /* This is saved in the client_data field of cinfo, and used both
+     * to retrieve the comment from its callback and to handle
+     * exceptions with a longjmp. */
+struct callback_data {
+    jmp_buf   jmpbuf;
+    l_uint8  *comment;
+};
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_INFO      0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*---------------------------------------------------------------------*
+ *                 Read jpeg from file (special function)              *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixReadJpeg()
+ *
+ *      Input:  filename
+ *              cmapflag (0 for no colormap in returned pix;
+ *                        1 to return an 8 bpp cmapped pix if spp = 3 or 4)
+ *              reduction (scaling factor: 1, 2, 4 or 8)
+ *              &nwarn (<optional return> number of warnings about
+ *                       corrupted data)
+ *              hint (a bitwise OR of L_JPEG_* values; 0 for default)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) This is a special function for reading jpeg files.
+ *      (2) Use this if you want the jpeg library to create
+ *          an 8 bpp colormapped image.
+ *      (3) Images reduced by factors of 2, 4 or 8 can be returned
+ *          significantly faster than full resolution images.
+ *      (4) If the jpeg data is bad, the jpeg library will continue
+ *          silently, or return warnings, or attempt to exit.  Depending
+ *          on the severity of the data corruption, there are two possible
+ *          outcomes:
+ *          (a) a possibly damaged pix can be generated, along with zero
+ *              or more warnings, or
+ *          (b) the library will attempt to exit (caught by our error
+ *              handler) and no pix will be returned.
+ *          If a pix is generated with at least one warning of data
+ *          corruption, and if L_JPEG_FAIL_ON_BAD_DATA is included in @hint,
+ *          no pix will be returned.
+ *      (5) The possible hint values are given in the enum in imageio.h:
+ *            * L_JPEG_READ_LUMINANCE
+ *            * L_JPEG_FAIL_ON_BAD_DATA
+ *          Default (0) is to do neither.
+ */
+PIX *
+pixReadJpeg(const char  *filename,
+            l_int32      cmapflag,
+            l_int32      reduction,
+            l_int32     *pnwarn,
+            l_int32      hint)
+{
+l_int32   ret;
+l_uint8  *comment;
+FILE     *fp;
+PIX      *pix;
+
+    PROCNAME("pixReadJpeg");
+
+    if (pnwarn) *pnwarn = 0;
+    if (!filename)
+        return (PIX *)ERROR_PTR("filename not defined", procName, NULL);
+    if (cmapflag != 0 && cmapflag != 1)
+        cmapflag = 0;  /* default */
+    if (reduction != 1 && reduction != 2 && reduction != 4 && reduction != 8)
+        return (PIX *)ERROR_PTR("reduction not in {1,2,4,8}", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PIX *)ERROR_PTR("image file not found", procName, NULL);
+    pix = pixReadStreamJpeg(fp, cmapflag, reduction, pnwarn, hint);
+    if (pix) {
+        ret = fgetJpegComment(fp, &comment);
+        if (!ret && comment)
+            pixSetText(pix, (char *)comment);
+        LEPT_FREE(comment);
+    }
+    fclose(fp);
+
+    if (!pix)
+        return (PIX *)ERROR_PTR("image not returned", procName, NULL);
+    return pix;
+}
+
+
+/*!
+ *  pixReadStreamJpeg()
+ *
+ *      Input:  stream
+ *              cmapflag (0 for no colormap in returned pix;
+ *                        1 to return an 8 bpp cmapped pix if spp = 3 or 4)
+ *              reduction (scaling factor: 1, 2, 4 or 8)
+ *              &nwarn (<optional return> number of warnings)
+ *              hint (a bitwise OR of L_JPEG_* values; 0 for default)
+ *      Return: pix, or null on error
+ *
+ *  Usage: see pixReadJpeg()
+ *  Notes:
+ *      (1) The jpeg comment, if it exists, is not stored in the pix.
+ */
+PIX *
+pixReadStreamJpeg(FILE     *fp,
+                  l_int32   cmapflag,
+                  l_int32   reduction,
+                  l_int32  *pnwarn,
+                  l_int32   hint)
+{
+l_int32                        cyan, yellow, magenta, black, nwarn;
+l_int32                        i, j, k, rval, gval, bval;
+l_int32                        w, h, wpl, spp, ncolors, cindex, ycck, cmyk;
+l_uint32                      *data;
+l_uint32                      *line, *ppixel;
+JSAMPROW                       rowbuffer;
+PIX                           *pix;
+PIXCMAP                       *cmap;
+struct jpeg_decompress_struct  cinfo;
+struct jpeg_error_mgr          jerr;
+jmp_buf                        jmpbuf;  /* must be local to the function */
+
+    PROCNAME("pixReadStreamJpeg");
+
+    if (pnwarn) *pnwarn = 0;
+    if (!fp)
+        return (PIX *)ERROR_PTR("fp not defined", procName, NULL);
+    if (cmapflag != 0 && cmapflag != 1)
+        cmapflag = 0;  /* default */
+    if (reduction != 1 && reduction != 2 && reduction != 4 && reduction != 8)
+        return (PIX *)ERROR_PTR("reduction not in {1,2,4,8}", procName, NULL);
+
+    if (BITS_IN_JSAMPLE != 8)  /* set in jmorecfg.h */
+        return (PIX *)ERROR_PTR("BITS_IN_JSAMPLE != 8", procName, NULL);
+
+    rewind(fp);
+    pix = NULL;
+    rowbuffer = NULL;
+
+        /* Modify the jpeg error handling to catch fatal errors  */
+    cinfo.err = jpeg_std_error(&jerr);
+    jerr.error_exit = jpeg_error_catch_all_1;
+    cinfo.client_data = (void *)&jmpbuf;
+    if (setjmp(jmpbuf)) {
+        pixDestroy(&pix);
+        LEPT_FREE(rowbuffer);
+        return (PIX *)ERROR_PTR("internal jpeg error", procName, NULL);
+    }
+
+        /* Initialize jpeg structs for decompression */
+    jpeg_create_decompress(&cinfo);
+    jpeg_stdio_src(&cinfo, fp);
+    jpeg_read_header(&cinfo, TRUE);
+    cinfo.scale_denom = reduction;
+    cinfo.scale_num = 1;
+    jpeg_calc_output_dimensions(&cinfo);
+    if (hint & L_JPEG_READ_LUMINANCE) {
+        cinfo.out_color_space = JCS_GRAYSCALE;
+        spp = 1;
+        L_INFO("reading luminance channel only\n", procName);
+    } else {
+        spp = cinfo.out_color_components;
+    }
+
+        /* Allocate the image and a row buffer */
+    w = cinfo.output_width;
+    h = cinfo.output_height;
+    ycck = (cinfo.jpeg_color_space == JCS_YCCK && spp == 4 && cmapflag == 0);
+    cmyk = (cinfo.jpeg_color_space == JCS_CMYK && spp == 4 && cmapflag == 0);
+    if (spp != 1 && spp != 3 && !ycck && !cmyk) {
+        return (PIX *)ERROR_PTR("spp must be 1 or 3, or YCCK or CMYK",
+                                procName, NULL);
+    }
+    if ((spp == 3 && cmapflag == 0) || ycck || cmyk) {  /* rgb or 4 bpp color */
+        rowbuffer = (JSAMPROW)LEPT_CALLOC(sizeof(JSAMPLE), spp * w);
+        pix = pixCreate(w, h, 32);
+    } else {  /* 8 bpp gray or colormapped */
+        rowbuffer = (JSAMPROW)LEPT_CALLOC(sizeof(JSAMPLE), w);
+        pix = pixCreate(w, h, 8);
+    }
+    pixSetInputFormat(pix, IFF_JFIF_JPEG);
+    if (!rowbuffer || !pix) {
+        LEPT_FREE(rowbuffer);
+        pixDestroy(&pix);
+        return (PIX *)ERROR_PTR("rowbuffer or pix not made", procName, NULL);
+    }
+
+        /* Initialize decompression.  Set up a colormap for color
+         * quantization if requested. */
+    if (spp == 1) {  /* Grayscale or colormapped */
+        jpeg_start_decompress(&cinfo);
+    } else {        /* Color; spp == 3 or YCCK or CMYK */
+        if (cmapflag == 0) {   /* 24 bit color in 32 bit pix or YCCK/CMYK */
+            cinfo.quantize_colors = FALSE;
+            jpeg_start_decompress(&cinfo);
+        } else {      /* Color quantize to 8 bits */
+            cinfo.quantize_colors = TRUE;
+            cinfo.desired_number_of_colors = 256;
+            jpeg_start_decompress(&cinfo);
+
+                /* Construct a pix cmap */
+            cmap = pixcmapCreate(8);
+            ncolors = cinfo.actual_number_of_colors;
+            for (cindex = 0; cindex < ncolors; cindex++) {
+                rval = cinfo.colormap[0][cindex];
+                gval = cinfo.colormap[1][cindex];
+                bval = cinfo.colormap[2][cindex];
+                pixcmapAddColor(cmap, rval, gval, bval);
+            }
+            pixSetColormap(pix, cmap);
+        }
+    }
+    wpl  = pixGetWpl(pix);
+    data = pixGetData(pix);
+
+        /* Decompress.  Unfortunately, we cannot use the return value
+         * from jpeg_read_scanlines() to determine if there was a problem
+         * with the data; it always appears to return 1.  We can only
+         * tell from the warnings during decoding, such as "premature
+         * end of data segment".  The default behavior is to return an
+         * image even if there are warnings.  However, by setting the
+         * hint to have the same bit flag as L_JPEG_FAIL_ON_BAD_DATA,
+         * no image will be returned if there are any warnings. */
+    for (i = 0; i < h; i++) {
+        if (jpeg_read_scanlines(&cinfo, &rowbuffer, (JDIMENSION)1) == 0) {
+            L_ERROR("read error at scanline %d\n", procName, i);
+            pixDestroy(&pix);
+            jpeg_destroy_decompress(&cinfo);
+            LEPT_FREE(rowbuffer);
+            return (PIX *)ERROR_PTR("bad data", procName, NULL);
+        }
+
+            /* -- 24 bit color -- */
+        if ((spp == 3 && cmapflag == 0) || ycck || cmyk) {
+            ppixel = data + i * wpl;
+            if (spp == 3) {
+                for (j = k = 0; j < w; j++) {
+                    SET_DATA_BYTE(ppixel, COLOR_RED, rowbuffer[k++]);
+                    SET_DATA_BYTE(ppixel, COLOR_GREEN, rowbuffer[k++]);
+                    SET_DATA_BYTE(ppixel, COLOR_BLUE, rowbuffer[k++]);
+                    ppixel++;
+                }
+            } else {
+                    /* This is a conversion from CMYK -> RGB that ignores
+                       color profiles, and is invoked when the image header
+                       claims to be in CMYK or YCCK colorspace.  If in YCCK,
+                       libjpeg may be doing YCCK -> CMYK under the hood.
+                       To understand why the colors need to be inverted on
+                       read-in for the Adobe marker, see the "Special
+                       color spaces" section of "Using the IJG JPEG
+                       Library" by Thomas G. Lane:
+                         http://www.jpegcameras.com/libjpeg/libjpeg-3.html#ss3.1
+                       The non-Adobe conversion is equivalent to:
+                           rval = black - black * cyan / 255
+                           ...
+                       The Adobe conversion is equivalent to:
+                           rval = black - black * (255 - cyan) / 255
+                           ...
+                       Note that cyan is the complement to red, and we
+                       are subtracting the complement color (weighted
+                       by black) from black.  For Adobe conversions,
+                       where they've already inverted the CMY but not
+                       the K, we have to invert again.  The results
+                       must be clipped to [0 ... 255]. */
+                for (j = k = 0; j < w; j++) {
+                    cyan = rowbuffer[k++];
+                    magenta = rowbuffer[k++];
+                    yellow = rowbuffer[k++];
+                    black = rowbuffer[k++];
+                    if (cinfo.saw_Adobe_marker) {
+                        rval = (black * cyan) / 255;
+                        gval = (black * magenta) / 255;
+                        bval = (black * yellow) / 255;
+                    } else {
+                        rval = black * (255 - cyan) / 255;
+                        gval = black * (255 - magenta) / 255;
+                        bval = black * (255 - yellow) / 255;
+                    }
+                    rval = L_MIN(L_MAX(rval, 0), 255);
+                    gval = L_MIN(L_MAX(gval, 0), 255);
+                    bval = L_MIN(L_MAX(bval, 0), 255);
+                    composeRGBPixel(rval, gval, bval, ppixel);
+                    ppixel++;
+                }
+            }
+        } else {    /* 8 bpp grayscale or colormapped pix */
+            line = data + i * wpl;
+            for (j = 0; j < w; j++)
+                SET_DATA_BYTE(line, j, rowbuffer[j]);
+        }
+    }
+
+    nwarn = cinfo.err->num_warnings;
+    if (pnwarn) *pnwarn = nwarn;
+
+        /* If the pixel density is neither 1 nor 2, it may not be defined.
+         * In that case, don't set the resolution.  */
+    if (cinfo.density_unit == 1) {  /* pixels per inch */
+        pixSetXRes(pix, cinfo.X_density);
+        pixSetYRes(pix, cinfo.Y_density);
+    } else if (cinfo.density_unit == 2) {  /* pixels per centimeter */
+        pixSetXRes(pix, (l_int32)((l_float32)cinfo.X_density * 2.54 + 0.5));
+        pixSetYRes(pix, (l_int32)((l_float32)cinfo.Y_density * 2.54 + 0.5));
+    }
+
+    if (cinfo.output_components != spp)
+        fprintf(stderr, "output spp = %d, spp = %d\n",
+                cinfo.output_components, spp);
+
+    jpeg_finish_decompress(&cinfo);
+    jpeg_destroy_decompress(&cinfo);
+    LEPT_FREE(rowbuffer);
+
+    if (nwarn > 0) {
+        if (hint & L_JPEG_FAIL_ON_BAD_DATA) {
+            L_ERROR("fail with %d warning(s) of bad data\n", procName, nwarn);
+            pixDestroy(&pix);
+        } else {
+            L_WARNING("%d warning(s) of bad data\n", procName, nwarn);
+        }
+    }
+
+    return pix;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                     Read jpeg metadata from file                    *
+ *---------------------------------------------------------------------*/
+/*!
+ *  readHeaderJpeg()
+ *
+ *      Input:  filename
+ *              &w (<optional return>)
+ *              &h (<optional return>)
+ *              &spp (<optional return>, samples/pixel)
+ *              &ycck (<optional return>, 1 if ycck color space; 0 otherwise)
+ *              &cmyk (<optional return>, 1 if cmyk color space; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+readHeaderJpeg(const char  *filename,
+               l_int32     *pw,
+               l_int32     *ph,
+               l_int32     *pspp,
+               l_int32     *pycck,
+               l_int32     *pcmyk)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("readHeaderJpeg");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pspp) *pspp = 0;
+    if (pycck) *pycck = 0;
+    if (pcmyk) *pcmyk = 0;
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!pw && !ph && !pspp && !pycck && !pcmyk)
+        return ERROR_INT("no results requested", procName, 1);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT("image file not found", procName, 1);
+    ret = freadHeaderJpeg(fp, pw, ph, pspp, pycck, pcmyk);
+    fclose(fp);
+    return ret;
+}
+
+
+/*!
+ *  freadHeaderJpeg()
+ *
+ *      Input:  stream
+ *              &w (<optional return>)
+ *              &h (<optional return>)
+ *              &spp (<optional return>, samples/pixel)
+ *              &ycck (<optional return>, 1 if ycck color space; 0 otherwise)
+ *              &cmyk (<optional return>, 1 if cmyk color space; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+freadHeaderJpeg(FILE     *fp,
+                l_int32  *pw,
+                l_int32  *ph,
+                l_int32  *pspp,
+                l_int32  *pycck,
+                l_int32  *pcmyk)
+{
+l_int32                        spp;
+struct jpeg_decompress_struct  cinfo;
+struct jpeg_error_mgr          jerr;
+jmp_buf                        jmpbuf;  /* must be local to the function */
+
+    PROCNAME("freadHeaderJpeg");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pspp) *pspp = 0;
+    if (pycck) *pycck = 0;
+    if (pcmyk) *pcmyk = 0;
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!pw && !ph && !pspp && !pycck && !pcmyk)
+        return ERROR_INT("no results requested", procName, 1);
+
+    rewind(fp);
+
+        /* Modify the jpeg error handling to catch fatal errors  */
+    cinfo.err = jpeg_std_error(&jerr);
+    cinfo.client_data = (void *)&jmpbuf;
+    jerr.error_exit = jpeg_error_catch_all_1;
+    if (setjmp(jmpbuf))
+        return ERROR_INT("internal jpeg error", procName, 1);
+
+        /* Initialize the jpeg structs for reading the header */
+    jpeg_create_decompress(&cinfo);
+    jpeg_stdio_src(&cinfo, fp);
+    jpeg_read_header(&cinfo, TRUE);
+    jpeg_calc_output_dimensions(&cinfo);
+
+    spp = cinfo.out_color_components;
+    if (pspp) *pspp = spp;
+    if (pw) *pw = cinfo.output_width;
+    if (ph) *ph = cinfo.output_height;
+    if (pycck) *pycck =
+        (cinfo.jpeg_color_space == JCS_YCCK && spp == 4);
+    if (pcmyk) *pcmyk =
+        (cinfo.jpeg_color_space == JCS_CMYK && spp == 4);
+
+    jpeg_destroy_decompress(&cinfo);
+    rewind(fp);
+    return 0;
+}
+
+
+/*
+ *  fgetJpegResolution()
+ *
+ *      Input:  stream (opened for read)
+ *              &xres, &yres (<return> resolution in ppi)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) If neither resolution field is set, this is not an error;
+ *          the returned resolution values are 0 (designating 'unknown').
+ *      (2) Side-effect: this rewinds the stream.
+ */
+l_int32
+fgetJpegResolution(FILE     *fp,
+                   l_int32  *pxres,
+                   l_int32  *pyres)
+{
+struct jpeg_decompress_struct  cinfo;
+struct jpeg_error_mgr          jerr;
+jmp_buf                        jmpbuf;  /* must be local to the function */
+
+    PROCNAME("fgetJpegResolution");
+
+    if (pxres) *pxres = 0;
+    if (pyres) *pyres = 0;
+    if (!pxres || !pyres)
+        return ERROR_INT("&xres and &yres not both defined", procName, 1);
+    if (!fp)
+        return ERROR_INT("stream not opened", procName, 1);
+
+    rewind(fp);
+
+        /* Modify the jpeg error handling to catch fatal errors  */
+    cinfo.err = jpeg_std_error(&jerr);
+    cinfo.client_data = (void *)&jmpbuf;
+    jerr.error_exit = jpeg_error_catch_all_1;
+    if (setjmp(jmpbuf))
+        return ERROR_INT("internal jpeg error", procName, 1);
+
+        /* Initialize the jpeg structs for reading the header */
+    jpeg_create_decompress(&cinfo);
+    jpeg_stdio_src(&cinfo, fp);
+    jpeg_read_header(&cinfo, TRUE);
+
+        /* It is common for the input resolution to be omitted from the
+         * jpeg file.  If density_unit is not 1 or 2, simply return 0. */
+    if (cinfo.density_unit == 1) {  /* pixels/inch */
+        *pxres = cinfo.X_density;
+        *pyres = cinfo.Y_density;
+    } else if (cinfo.density_unit == 2) {  /* pixels/cm */
+        *pxres = (l_int32)((l_float32)cinfo.X_density * 2.54 + 0.5);
+        *pyres = (l_int32)((l_float32)cinfo.Y_density * 2.54 + 0.5);
+    }
+
+    jpeg_destroy_decompress(&cinfo);
+    rewind(fp);
+    return 0;
+}
+
+
+/*
+ *  fgetJpegComment()
+ *
+ *      Input:  stream (opened for read)
+ *              &comment (<return> comment)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Side-effect: this rewinds the stream.
+ */
+l_int32
+fgetJpegComment(FILE      *fp,
+                l_uint8  **pcomment)
+{
+struct jpeg_decompress_struct  cinfo;
+struct jpeg_error_mgr          jerr;
+struct callback_data           cb_data;  /* contains local jmp_buf */
+
+    PROCNAME("fgetJpegComment");
+
+    if (!pcomment)
+        return ERROR_INT("&comment not defined", procName, 1);
+    *pcomment = NULL;
+    if (!fp)
+        return ERROR_INT("stream not opened", procName, 1);
+
+    rewind(fp);
+
+        /* Modify the jpeg error handling to catch fatal errors  */
+    cinfo.err = jpeg_std_error(&jerr);
+    jerr.error_exit = jpeg_error_catch_all_2;
+    cb_data.comment = NULL;
+    cinfo.client_data = (void *)&cb_data;
+    if (setjmp(cb_data.jmpbuf)) {
+        LEPT_FREE(cb_data.comment);
+        return ERROR_INT("internal jpeg error", procName, 1);
+    }
+
+        /* Initialize the jpeg structs for reading the header */
+    jpeg_create_decompress(&cinfo);
+    jpeg_set_marker_processor(&cinfo, JPEG_COM, jpeg_comment_callback);
+    jpeg_stdio_src(&cinfo, fp);
+    jpeg_read_header(&cinfo, TRUE);
+
+        /* Save the result */
+    *pcomment = cb_data.comment;
+    jpeg_destroy_decompress(&cinfo);
+    rewind(fp);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                             Writing Jpeg                            *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixWriteJpeg()
+ *
+ *      Input:  filename
+ *              pix  (any depth; cmap is OK)
+ *              quality (1 - 100; 75 is default)
+ *              progressive (0 for baseline sequential; 1 for progressive)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixWriteJpeg(const char  *filename,
+             PIX         *pix,
+             l_int32      quality,
+             l_int32      progressive)
+{
+FILE  *fp;
+
+    PROCNAME("pixWriteJpeg");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb+")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+
+    if (pixWriteStreamJpeg(fp, pix, quality, progressive)) {
+        fclose(fp);
+        return ERROR_INT("pix not written to stream", procName, 1);
+    }
+
+    fclose(fp);
+    return 0;
+}
+
+
+/*!
+ *  pixWriteStreamJpeg()
+ *
+ *      Input:  stream
+ *              pixs  (any depth; cmap is OK)
+ *              quality  (1 - 100; 75 is default value; 0 is also default)
+ *              progressive (0 for baseline sequential; 1 for progressive)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Progressive encoding gives better compression, at the
+ *          expense of slower encoding and decoding.
+ *      (2) Standard chroma subsampling is 2x2 on both the U and V
+ *          channels.  For highest quality, use no subsampling; this
+ *          option is set by pixSetChromaSampling(pix, 0).
+ *      (3) The only valid pixel depths in leptonica are 1, 2, 4, 8, 16
+ *          and 32 bpp.  However, it is possible, and in some cases desirable,
+ *          to write out a jpeg file using an rgb pix that has 24 bpp.
+ *          This can be created by appending the raster data for a 24 bpp
+ *          image (with proper scanline padding) directly to a 24 bpp
+ *          pix that was created without a data array.
+ *      (4) There are two compression paths in this function:
+ *          * Grayscale image, no colormap: compress as 8 bpp image.
+ *          * rgb full color image: copy each line into the color
+ *            line buffer, and compress as three 8 bpp images.
+ *      (5) Under the covers, the jpeg library transforms rgb to a
+ *          luminance-chromaticity triple, each component of which is
+ *          also 8 bits, and compresses that.  It uses 2 Huffman tables,
+ *          a higher resolution one (with more quantization levels)
+ *          for luminosity and a lower resolution one for the chromas.
+ */
+l_int32
+pixWriteStreamJpeg(FILE    *fp,
+                   PIX     *pixs,
+                   l_int32  quality,
+                   l_int32  progressive)
+{
+l_int32                      xres, yres;
+l_int32                      i, j, k;
+l_int32                      w, h, d, wpl, spp, colorflag, rowsamples;
+l_uint32                    *ppixel, *line, *data;
+JSAMPROW                     rowbuffer;
+PIX                         *pix;
+struct jpeg_compress_struct  cinfo;
+struct jpeg_error_mgr        jerr;
+const char                  *text;
+jmp_buf                      jmpbuf;  /* must be local to the function */
+
+    PROCNAME("pixWriteStreamJpeg");
+
+    if (!fp)
+        return ERROR_INT("stream not open", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (quality <= 0)
+        quality = 75;  /* default */
+
+        /* If necessary, convert the pix so that it can be jpeg compressed.
+         * The colormap is removed based on the source, so if the colormap
+         * has only gray colors, the image will be compressed with spp = 1. */
+    pixGetDimensions(pixs, &w, &h, &d);
+    pix = NULL;
+    if (pixGetColormap(pixs) != NULL) {
+        L_INFO("removing colormap; may be better to compress losslessly\n",
+               procName);
+        pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    } else if (d >= 8 && d != 16) {  /* normal case; no rewrite */
+        pix = pixClone(pixs);
+    } else if (d < 8 || d == 16) {
+        L_INFO("converting from %d to 8 bpp\n", procName, d);
+        pix = pixConvertTo8(pixs, 0);  /* 8 bpp, no cmap */
+    } else {
+        L_ERROR("unknown pix type with d = %d and no cmap\n", procName, d);
+        return 1;
+    }
+    if (!pix)
+        return ERROR_INT("pix not made", procName, 1);
+
+    rewind(fp);
+    rowbuffer = NULL;
+
+        /* Modify the jpeg error handling to catch fatal errors  */
+    cinfo.err = jpeg_std_error(&jerr);
+    cinfo.client_data = (void *)&jmpbuf;
+    jerr.error_exit = jpeg_error_catch_all_1;
+    if (setjmp(jmpbuf)) {
+        LEPT_FREE(rowbuffer);
+        pixDestroy(&pix);
+        return ERROR_INT("internal jpeg error", procName, 1);
+    }
+
+        /* Initialize the jpeg structs for compression */
+    jpeg_create_compress(&cinfo);
+    jpeg_stdio_dest(&cinfo, fp);
+    cinfo.image_width  = w;
+    cinfo.image_height = h;
+
+        /* Set the color space and number of components */
+    d = pixGetDepth(pix);
+    if (d == 8) {
+        colorflag = 0;    /* 8 bpp grayscale; no cmap */
+        cinfo.input_components = 1;
+        cinfo.in_color_space = JCS_GRAYSCALE;
+    } else {  /* d == 32 || d == 24 */
+        colorflag = 1;    /* rgb */
+        cinfo.input_components = 3;
+        cinfo.in_color_space = JCS_RGB;
+    }
+
+    jpeg_set_defaults(&cinfo);
+
+        /* Setting optimize_coding to TRUE seems to improve compression
+         * by approx 2-4 percent, and increases comp time by approx 20%. */
+    cinfo.optimize_coding = FALSE;
+
+        /* Set resolution in pixels/in (density_unit: 1 = in, 2 = cm) */
+    xres = pixGetXRes(pix);
+    yres = pixGetYRes(pix);
+    if ((xres != 0) && (yres != 0)) {
+        cinfo.density_unit = 1;  /* designates pixels per inch */
+        cinfo.X_density = xres;
+        cinfo.Y_density = yres;
+    }
+
+        /* Set the quality and progressive parameters */
+    jpeg_set_quality(&cinfo, quality, TRUE);
+    if (progressive)
+        jpeg_simple_progression(&cinfo);
+
+        /* Set the chroma subsampling parameters.  This is done in
+         * YUV color space.  The Y (intensity) channel is never subsampled.
+         * The standard subsampling is 2x2 on both the U and V channels.
+         * Notation on this is confusing.  For a nice illustrations, see
+         *   http://en.wikipedia.org/wiki/Chroma_subsampling
+         * The standard subsampling is written as 4:2:0.
+         * We allow high quality where there is no subsampling on the
+         * chroma channels: denoted as 4:4:4.  */
+    if (pixs->special == L_NO_CHROMA_SAMPLING_JPEG) {
+        cinfo.comp_info[0].h_samp_factor = 1;
+        cinfo.comp_info[0].v_samp_factor = 1;
+        cinfo.comp_info[1].h_samp_factor = 1;
+        cinfo.comp_info[1].v_samp_factor = 1;
+        cinfo.comp_info[2].h_samp_factor = 1;
+        cinfo.comp_info[2].v_samp_factor = 1;
+    }
+
+    jpeg_start_compress(&cinfo, TRUE);
+
+    if ((text = pixGetText(pix)))
+        jpeg_write_marker(&cinfo, JPEG_COM, (const JOCTET *)text, strlen(text));
+
+        /* Allocate row buffer */
+    spp = cinfo.input_components;
+    rowsamples = spp * w;
+    if ((rowbuffer = (JSAMPROW)LEPT_CALLOC(sizeof(JSAMPLE), rowsamples))
+        == NULL) {
+        pixDestroy(&pix);
+        return ERROR_INT("calloc fail for rowbuffer", procName, 1);
+    }
+
+    data = pixGetData(pix);
+    wpl  = pixGetWpl(pix);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        if (colorflag == 0) {        /* 8 bpp gray */
+            for (j = 0; j < w; j++)
+                rowbuffer[j] = GET_DATA_BYTE(line, j);
+        } else {  /* colorflag == 1 */
+            if (d == 24) {  /* See note 3 above; special case of 24 bpp rgb */
+                jpeg_write_scanlines(&cinfo, (JSAMPROW *)&line, 1);
+            } else {  /* standard 32 bpp rgb */
+                ppixel = line;
+                for (j = k = 0; j < w; j++) {
+                    rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_RED);
+                    rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_GREEN);
+                    rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_BLUE);
+                    ppixel++;
+                }
+            }
+        }
+        if (d != 24)
+            jpeg_write_scanlines(&cinfo, &rowbuffer, 1);
+    }
+    jpeg_finish_compress(&cinfo);
+
+    pixDestroy(&pix);
+    LEPT_FREE(rowbuffer);
+    jpeg_destroy_compress(&cinfo);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Read/write to memory                        *
+ *---------------------------------------------------------------------*/
+#if HAVE_FMEMOPEN
+extern FILE *open_memstream(char **data, size_t *size);
+extern FILE *fmemopen(void *data, size_t size, const char *mode);
+#endif  /* HAVE_FMEMOPEN */
+
+/*!
+ *  pixReadMemJpeg()
+ *
+ *      Input:  data (const; jpeg-encoded)
+ *              size (of data)
+ *              colormap flag (0 means return RGB image if color;
+ *                             1 means create a colormap and return
+ *                             an 8 bpp colormapped image if color)
+ *              reduction (scaling factor: 1, 2, 4 or 8)
+ *              &nwarn (<optional return> number of warnings)
+ *              hint (a bitwise OR of L_JPEG_* values; 0 for default)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) The @size byte of @data must be a null character.
+ *      (2) The only hint flag so far is L_JPEG_READ_LUMINANCE,
+ *          given in the enum in imageio.h.
+ *      (3) See pixReadJpeg() for usage.
+ */
+PIX *
+pixReadMemJpeg(const l_uint8  *data,
+               size_t          size,
+               l_int32         cmflag,
+               l_int32         reduction,
+               l_int32        *pnwarn,
+               l_int32         hint)
+{
+l_int32   ret;
+l_uint8  *comment;
+FILE     *fp;
+PIX      *pix;
+
+    PROCNAME("pixReadMemJpeg");
+
+    if (pnwarn) *pnwarn = 0;
+    if (!data)
+        return (PIX *)ERROR_PTR("data not defined", procName, NULL);
+
+#if HAVE_FMEMOPEN
+    if ((fp = fmemopen((l_uint8 *)data, size, "rb")) == NULL)
+        return (PIX *)ERROR_PTR("stream not opened", procName, NULL);
+#else
+    L_WARNING("work-around: writing to a temp file\n", procName);
+    if ((fp = tmpfile()) == NULL)
+        return (PIX *)ERROR_PTR("tmpfile stream not opened", procName, NULL);
+    fwrite(data, 1, size, fp);
+    rewind(fp);
+#endif  /* HAVE_FMEMOPEN */
+    pix = pixReadStreamJpeg(fp, cmflag, reduction, pnwarn, hint);
+    if (pix) {
+        ret = fgetJpegComment(fp, &comment);
+        if (!ret && comment) {
+            pixSetText(pix, (char *)comment);
+            LEPT_FREE(comment);
+        }
+    }
+    fclose(fp);
+    if (!pix) L_ERROR("pix not read\n", procName);
+    return pix;
+}
+
+
+/*!
+ *  readHeaderMemJpeg()
+ *
+ *      Input:  data (const; jpeg-encoded)
+ *              size (of data)
+ *              &w (<optional return>)
+ *              &h (<optional return>)
+ *              &spp (<optional return>, samples/pixel)
+ *              &ycck (<optional return>, 1 if ycck color space; 0 otherwise)
+ *              &cmyk (<optional return>, 1 if cmyk color space; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+readHeaderMemJpeg(const l_uint8  *data,
+                  size_t          size,
+                  l_int32        *pw,
+                  l_int32        *ph,
+                  l_int32        *pspp,
+                  l_int32        *pycck,
+                  l_int32        *pcmyk)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("readHeaderMemJpeg");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pspp) *pspp = 0;
+    if (pycck) *pycck = 0;
+    if (pcmyk) *pcmyk = 0;
+    if (!data)
+        return ERROR_INT("data not defined", procName, 1);
+    if (!pw && !ph && !pspp && !pycck && !pcmyk)
+        return ERROR_INT("no results requested", procName, 1);
+
+#if HAVE_FMEMOPEN
+    if ((fp = fmemopen((l_uint8 *)data, size, "rb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+#else
+    L_WARNING("work-around: writing to a temp file\n", procName);
+    if ((fp = tmpfile()) == NULL)
+        return ERROR_INT("tmpfile stream not opened", procName, 1);
+    fwrite(data, 1, size, fp);
+    rewind(fp);
+#endif  /* HAVE_FMEMOPEN */
+    ret = freadHeaderJpeg(fp, pw, ph, pspp, pycck, pcmyk);
+    fclose(fp);
+    return ret;
+}
+
+
+/*!
+ *  pixWriteMemJpeg()
+ *
+ *      Input:  &data (<return> data of jpeg compressed image)
+ *              &size (<return> size of returned data)
+ *              pix  (any depth; cmap is OK)
+ *              quality  (1 - 100; 75 is default value; 0 is also default)
+ *              progressive (0 for baseline sequential; 1 for progressive)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See pixWriteStreamJpeg() for usage.  This version writes to
+ *          memory instead of to a file stream.
+ */
+l_int32
+pixWriteMemJpeg(l_uint8  **pdata,
+                size_t    *psize,
+                PIX       *pix,
+                l_int32    quality,
+                l_int32    progressive)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("pixWriteMemJpeg");
+
+    if (pdata) *pdata = NULL;
+    if (psize) *psize = 0;
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1 );
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1 );
+    if (!pix)
+        return ERROR_INT("&pix not defined", procName, 1 );
+
+#if HAVE_FMEMOPEN
+    if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    ret = pixWriteStreamJpeg(fp, pix, quality, progressive);
+#else
+    L_WARNING("work-around: writing to a temp file\n", procName);
+    if ((fp = tmpfile()) == NULL)
+        return ERROR_INT("tmpfile stream not opened", procName, 1);
+    ret = pixWriteStreamJpeg(fp, pix, quality, progressive);
+    rewind(fp);
+    *pdata = l_binaryReadStream(fp, psize);
+#endif  /* HAVE_FMEMOPEN */
+    fclose(fp);
+    return ret;
+}
+
+
+/*---------------------------------------------------------------------*
+ *           Setting special flag for chroma sampling on write         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixSetChromaSampling()
+ *
+ *      Input:  pix
+ *              sampling (1 for subsampling; 0 for no subsampling)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The default is for 2x2 chroma subsampling because the files are
+ *          considerably smaller and the appearance is typically satisfactory.
+ *          To get full resolution output in the chroma channels for
+ *          jpeg writing, call this with @sampling == 0.
+ */
+l_int32
+pixSetChromaSampling(PIX     *pix,
+                     l_int32  sampling)
+{
+    PROCNAME("pixSetChromaSampling");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1 );
+    if (sampling)
+        pixSetSpecial(pix, 0);  /* default */
+    else
+        pixSetSpecial(pix, L_NO_CHROMA_SAMPLING_JPEG);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Static system helpers                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  jpeg_error_catch_all_1()
+ *
+ *  Notes:
+ *      (1) The default jpeg error_exit() kills the process, but we
+ *          never want a call to leptonica to kill a process.  If you
+ *          do want this behavior, remove the calls to these error handlers.
+ *      (2) This is used where cinfo->client_data holds only jmpbuf.
+ */
+static void
+jpeg_error_catch_all_1(j_common_ptr cinfo)
+{
+    jmp_buf *pjmpbuf = (jmp_buf *)cinfo->client_data;
+    (*cinfo->err->output_message) (cinfo);
+    jpeg_destroy(cinfo);
+    longjmp(*pjmpbuf, 1);
+    return;
+}
+
+/*!
+ *  jpeg_error_catch_all_2()
+ *
+ *  Notes:
+ *      (1) This is used where cinfo->client_data needs to hold both
+ *          the jmpbuf and the jpeg comment data.
+ *      (2) On error, the comment data will be freed by the caller.
+ */
+static void
+jpeg_error_catch_all_2(j_common_ptr cinfo)
+{
+struct callback_data  *pcb_data;
+
+    pcb_data = (struct callback_data *)cinfo->client_data;
+    (*cinfo->err->output_message) (cinfo);
+    jpeg_destroy(cinfo);
+    longjmp(pcb_data->jmpbuf, 1);
+    return;
+}
+
+/* This function was borrowed from libjpeg */
+static l_uint8
+jpeg_getc(j_decompress_ptr cinfo)
+{
+struct jpeg_source_mgr *datasrc;
+
+    datasrc = cinfo->src;
+    if (datasrc->bytes_in_buffer == 0) {
+        if (! (*datasrc->fill_input_buffer) (cinfo)) {
+            return 0;
+        }
+    }
+    datasrc->bytes_in_buffer--;
+    return GETJOCTET(*datasrc->next_input_byte++);
+}
+
+/*!
+ *  jpeg_comment_callback()
+ *
+ *  Notes:
+ *      (1) This is used to read the jpeg comment (JPEG_COM).
+ *          See the note above the declaration for why it returns
+ *          a "boolean".
+ */
+static boolean
+jpeg_comment_callback(j_decompress_ptr cinfo)
+{
+l_int32                length, i;
+l_uint8               *comment;
+struct callback_data  *pcb_data;
+
+        /* Get the size of the comment */
+    length = jpeg_getc(cinfo) << 8;
+    length += jpeg_getc(cinfo);
+    length -= 2;
+    if (length <= 0)
+        return 1;
+
+        /* Extract the comment from the file */
+    if ((comment = (l_uint8 *)LEPT_CALLOC(length + 1, sizeof(l_uint8))) == NULL)
+        return 0;
+    for (i = 0; i < length; i++)
+        comment[i] = jpeg_getc(cinfo);
+
+        /* Save the comment and return */
+    pcb_data = (struct callback_data *)cinfo->client_data;
+    pcb_data->comment = comment;
+    return 1;
+}
+
+/* --------------------------------------------*/
+#endif  /* HAVE_LIBJPEG */
+/* --------------------------------------------*/
+
diff --git a/src/jpegiostub.c b/src/jpegiostub.c
new file mode 100644 (file)
index 0000000..43fa063
--- /dev/null
@@ -0,0 +1,141 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  jpegiostub.c
+ *
+ *     Stubs for jpegio.c functions
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !HAVE_LIBJPEG   /* defined in environ.h */
+/* --------------------------------------------*/
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadJpeg(const char *filename, l_int32 cmflag, l_int32 reduction,
+                  l_int32 *pnwarn, l_int32 hint)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadJpeg", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadStreamJpeg(FILE *fp, l_int32 cmflag, l_int32 reduction,
+                        l_int32 *pnwarn, l_int32 hint)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadStreamJpeg", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 readHeaderJpeg(const char *filename, l_int32 *pw, l_int32 *ph,
+                       l_int32 *pspp, l_int32 *pycck, l_int32 *pcmyk)
+{
+    return ERROR_INT("function not present", "readHeaderJpeg", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 freadHeaderJpeg(FILE *fp, l_int32 *pw, l_int32 *ph,
+                       l_int32 *pspp, l_int32 *pycck, l_int32 *pcmyk)
+{
+    return ERROR_INT("function not present", "freadHeaderJpeg", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 fgetJpegResolution(FILE *fp, l_int32 *pxres, l_int32 *pyres)
+{
+    return ERROR_INT("function not present", "fgetJpegResolution", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 fgetJpegComment(FILE *fp, l_uint8 **pcomment)
+{
+    return ERROR_INT("function not present", "fgetJpegComment", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteJpeg(const char *filename, PIX *pix, l_int32 quality,
+                     l_int32 progressive)
+{
+    return ERROR_INT("function not present", "pixWriteJpeg", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteStreamJpeg(FILE *fp, PIX *pix, l_int32 quality,
+                           l_int32 progressive)
+{
+    return ERROR_INT("function not present", "pixWriteStreamJpeg", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadMemJpeg(const l_uint8 *cdata, size_t size, l_int32 cmflag,
+                     l_int32 reduction, l_int32 *pnwarn, l_int32 hint)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadMemJpeg", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 readHeaderMemJpeg(const l_uint8 *cdata, size_t size,
+                          l_int32 *pw, l_int32 *ph, l_int32 *pspp,
+                          l_int32 *pycck, l_int32 *pcmyk)
+{
+    return ERROR_INT("function not present", "readHeaderMemJpeg", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteMemJpeg(l_uint8 **pdata, size_t *psize, PIX *pix,
+                        l_int32 quality, l_int32 progressive)
+{
+    return ERROR_INT("function not present", "pixWriteMemJpeg", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixSetChromaSampling(PIX *pix, l_int32 sampling)
+{
+    return ERROR_INT("function not present", "pixSetChromaSampling", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+/* --------------------------------------------*/
+#endif  /* !HAVE_LIBJPEG */
+/* --------------------------------------------*/
diff --git a/src/kernel.c b/src/kernel.c
new file mode 100644 (file)
index 0000000..d989ddb
--- /dev/null
@@ -0,0 +1,1212 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  kernel.c
+ *
+ *      Basic operations on kernels for image convolution
+ *
+ *         Create/destroy/copy
+ *            L_KERNEL   *kernelCreate()
+ *            void        kernelDestroy()
+ *            L_KERNEL   *kernelCopy()
+ *
+ *         Accessors:
+ *            l_int32     kernelGetElement()
+ *            l_int32     kernelSetElement()
+ *            l_int32     kernelGetParameters()
+ *            l_int32     kernelSetOrigin()
+ *            l_int32     kernelGetSum()
+ *            l_int32     kernelGetMinMax()
+ *
+ *         Normalize/invert
+ *            L_KERNEL   *kernelNormalize()
+ *            L_KERNEL   *kernelInvert()
+ *
+ *         Helper function
+ *            l_float32 **create2dFloatArray()
+ *
+ *         Serialized I/O
+ *            L_KERNEL   *kernelRead()
+ *            L_KERNEL   *kernelReadStream()
+ *            l_int32     kernelWrite()
+ *            l_int32     kernelWriteStream()
+ *
+ *         Making a kernel from a compiled string
+ *            L_KERNEL   *kernelCreateFromString()
+ *
+ *         Making a kernel from a simple file format
+ *            L_KERNEL   *kernelCreateFromFile()
+ *
+ *         Making a kernel from a Pix
+ *            L_KERNEL   *kernelCreateFromPix()
+ *
+ *         Display a kernel in a pix
+ *            PIX        *kernelDisplayInPix()
+ *
+ *         Parse string to extract numbers
+ *            NUMA       *parseStringForNumbers()
+ *
+ *      Simple parametric kernels
+ *            L_KERNEL   *makeFlatKernel()
+ *            L_KERNEL   *makeGaussianKernel()
+ *            L_KERNEL   *makeGaussianKernelSep()
+ *            L_KERNEL   *makeDoGKernel()
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+
+/*------------------------------------------------------------------------*
+ *                           Create / Destroy                             *
+ *------------------------------------------------------------------------*/
+/*!
+ *  kernelCreate()
+ *
+ *      Input:  height, width
+ *      Return: kernel, or null on error
+ *
+ *  Notes:
+ *      (1) kernelCreate() initializes all values to 0.
+ *      (2) After this call, (cy,cx) and nonzero data values must be
+ *          assigned.
+ */
+L_KERNEL *
+kernelCreate(l_int32  height,
+             l_int32  width)
+{
+L_KERNEL  *kel;
+
+    PROCNAME("kernelCreate");
+
+    if ((kel = (L_KERNEL *)LEPT_CALLOC(1, sizeof(L_KERNEL))) == NULL)
+        return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
+    kel->sy = height;
+    kel->sx = width;
+    if ((kel->data = create2dFloatArray(height, width)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("data not allocated", procName, NULL);
+
+    return kel;
+}
+
+
+/*!
+ *  kernelDestroy()
+ *
+ *      Input:  &kel (<to be nulled>)
+ *      Return: void
+ */
+void
+kernelDestroy(L_KERNEL  **pkel)
+{
+l_int32    i;
+L_KERNEL  *kel;
+
+    PROCNAME("kernelDestroy");
+
+    if (pkel == NULL)  {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+    if ((kel = *pkel) == NULL)
+        return;
+
+    for (i = 0; i < kel->sy; i++)
+        LEPT_FREE(kel->data[i]);
+    LEPT_FREE(kel->data);
+    LEPT_FREE(kel);
+
+    *pkel = NULL;
+    return;
+}
+
+
+/*!
+ *  kernelCopy()
+ *
+ *      Input:  kels (source kernel)
+ *      Return: keld (copy of kels), or null on error
+ */
+L_KERNEL *
+kernelCopy(L_KERNEL  *kels)
+{
+l_int32    i, j, sx, sy, cx, cy;
+L_KERNEL  *keld;
+
+    PROCNAME("kernelCopy");
+
+    if (!kels)
+        return (L_KERNEL *)ERROR_PTR("kels not defined", procName, NULL);
+
+    kernelGetParameters(kels, &sy, &sx, &cy, &cx);
+    if ((keld = kernelCreate(sy, sx)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("keld not made", procName, NULL);
+    keld->cy = cy;
+    keld->cx = cx;
+    for (i = 0; i < sy; i++)
+        for (j = 0; j < sx; j++)
+            keld->data[i][j] = kels->data[i][j];
+
+    return keld;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                               Accessors                              *
+ *----------------------------------------------------------------------*/
+/*!
+ *  kernelGetElement()
+ *
+ *      Input:  kel
+ *              row
+ *              col
+ *              &val
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+kernelGetElement(L_KERNEL   *kel,
+                 l_int32     row,
+                 l_int32     col,
+                 l_float32  *pval)
+{
+    PROCNAME("kernelGetElement");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0;
+    if (!kel)
+        return ERROR_INT("kernel not defined", procName, 1);
+    if (row < 0 || row >= kel->sy)
+        return ERROR_INT("kernel row out of bounds", procName, 1);
+    if (col < 0 || col >= kel->sx)
+        return ERROR_INT("kernel col out of bounds", procName, 1);
+
+    *pval = kel->data[row][col];
+    return 0;
+}
+
+
+/*!
+ *  kernelSetElement()
+ *
+ *      Input:  kernel
+ *              row
+ *              col
+ *              val
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+kernelSetElement(L_KERNEL  *kel,
+                 l_int32    row,
+                 l_int32    col,
+                 l_float32  val)
+{
+    PROCNAME("kernelSetElement");
+
+    if (!kel)
+        return ERROR_INT("kel not defined", procName, 1);
+    if (row < 0 || row >= kel->sy)
+        return ERROR_INT("kernel row out of bounds", procName, 1);
+    if (col < 0 || col >= kel->sx)
+        return ERROR_INT("kernel col out of bounds", procName, 1);
+
+    kel->data[row][col] = val;
+    return 0;
+}
+
+
+/*!
+ *  kernelGetParameters()
+ *
+ *      Input:  kernel
+ *              &sy, &sx, &cy, &cx (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+kernelGetParameters(L_KERNEL  *kel,
+                    l_int32   *psy,
+                    l_int32   *psx,
+                    l_int32   *pcy,
+                    l_int32   *pcx)
+{
+    PROCNAME("kernelGetParameters");
+
+    if (psy) *psy = 0;
+    if (psx) *psx = 0;
+    if (pcy) *pcy = 0;
+    if (pcx) *pcx = 0;
+    if (!kel)
+        return ERROR_INT("kernel not defined", procName, 1);
+    if (psy) *psy = kel->sy;
+    if (psx) *psx = kel->sx;
+    if (pcy) *pcy = kel->cy;
+    if (pcx) *pcx = kel->cx;
+    return 0;
+}
+
+
+/*!
+ *  kernelSetOrigin()
+ *
+ *      Input:  kernel
+ *              cy, cx
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+kernelSetOrigin(L_KERNEL  *kel,
+                l_int32    cy,
+                l_int32    cx)
+{
+    PROCNAME("kernelSetOrigin");
+
+    if (!kel)
+        return ERROR_INT("kel not defined", procName, 1);
+    kel->cy = cy;
+    kel->cx = cx;
+    return 0;
+}
+
+
+/*!
+ *  kernelGetSum()
+ *
+ *      Input:  kernel
+ *              &sum (<return> sum of all kernel values)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+kernelGetSum(L_KERNEL   *kel,
+             l_float32  *psum)
+{
+l_int32    sx, sy, i, j;
+
+    PROCNAME("kernelGetSum");
+
+    if (!psum)
+        return ERROR_INT("&sum not defined", procName, 1);
+    *psum = 0.0;
+    if (!kel)
+        return ERROR_INT("kernel not defined", procName, 1);
+
+    kernelGetParameters(kel, &sy, &sx, NULL, NULL);
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++) {
+            *psum += kel->data[i][j];
+        }
+    }
+    return 0;
+}
+
+
+/*!
+ *  kernelGetMinMax()
+ *
+ *      Input:  kernel
+ *              &min (<optional return> minimum value)
+ *              &max (<optional return> maximum value)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+kernelGetMinMax(L_KERNEL   *kel,
+                l_float32  *pmin,
+                l_float32  *pmax)
+{
+l_int32    sx, sy, i, j;
+l_float32  val, minval, maxval;
+
+    PROCNAME("kernelGetMinmax");
+
+    if (!pmin && !pmax)
+        return ERROR_INT("neither &min nor &max defined", procName, 1);
+    if (pmin) *pmin = 0.0;
+    if (pmax) *pmax = 0.0;
+    if (!kel)
+        return ERROR_INT("kernel not defined", procName, 1);
+
+    kernelGetParameters(kel, &sy, &sx, NULL, NULL);
+    minval = 10000000.0;
+    maxval = -10000000.0;
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++) {
+            val = kel->data[i][j];
+            if (val < minval)
+                minval = val;
+            if (val > maxval)
+                maxval = val;
+        }
+    }
+    if (pmin)
+        *pmin = minval;
+    if (pmax)
+        *pmax = maxval;
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                          Normalize/Invert                            *
+ *----------------------------------------------------------------------*/
+/*!
+ *  kernelNormalize()
+ *
+ *      Input:  kels (source kel, to be normalized)
+ *              normsum (desired sum of elements in keld)
+ *      Return: keld (normalized version of kels), or null on error
+ *                   or if sum of elements is very close to 0)
+ *
+ *  Notes:
+ *      (1) If the sum of kernel elements is close to 0, do not
+ *          try to calculate the normalized kernel.  Instead,
+ *          return a copy of the input kernel, with a warning.
+ */
+L_KERNEL *
+kernelNormalize(L_KERNEL  *kels,
+                l_float32  normsum)
+{
+l_int32    i, j, sx, sy, cx, cy;
+l_float32  sum, factor;
+L_KERNEL  *keld;
+
+    PROCNAME("kernelNormalize");
+
+    if (!kels)
+        return (L_KERNEL *)ERROR_PTR("kels not defined", procName, NULL);
+
+    kernelGetSum(kels, &sum);
+    if (L_ABS(sum) < 0.00001) {
+        L_WARNING("null sum; not normalizing; returning a copy\n", procName);
+        return kernelCopy(kels);
+    }
+
+    kernelGetParameters(kels, &sy, &sx, &cy, &cx);
+    if ((keld = kernelCreate(sy, sx)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("keld not made", procName, NULL);
+    keld->cy = cy;
+    keld->cx = cx;
+
+    factor = normsum / sum;
+    for (i = 0; i < sy; i++)
+        for (j = 0; j < sx; j++)
+            keld->data[i][j] = factor * kels->data[i][j];
+
+    return keld;
+}
+
+
+/*!
+ *  kernelInvert()
+ *
+ *      Input:  kels (source kel, to be inverted)
+ *      Return: keld (spatially inverted, about the origin), or null on error
+ *
+ *  Notes:
+ *      (1) For convolution, the kernel is spatially inverted before
+ *          a "correlation" operation is done between the kernel and the image.
+ */
+L_KERNEL *
+kernelInvert(L_KERNEL  *kels)
+{
+l_int32    i, j, sx, sy, cx, cy;
+L_KERNEL  *keld;
+
+    PROCNAME("kernelInvert");
+
+    if (!kels)
+        return (L_KERNEL *)ERROR_PTR("kels not defined", procName, NULL);
+
+    kernelGetParameters(kels, &sy, &sx, &cy, &cx);
+    if ((keld = kernelCreate(sy, sx)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("keld not made", procName, NULL);
+    keld->cy = sy - 1 - cy;
+    keld->cx = sx - 1 - cx;
+
+    for (i = 0; i < sy; i++)
+        for (j = 0; j < sx; j++)
+            keld->data[i][j] = kels->data[sy - 1 - i][sx - 1 - j];
+
+    return keld;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                            Helper function                           *
+ *----------------------------------------------------------------------*/
+/*!
+ *  create2dFloatArray()
+ *
+ *      Input:  sy (rows == height)
+ *              sx (columns == width)
+ *      Return: doubly indexed array (i.e., an array of sy row pointers,
+ *              each of which points to an array of sx floats)
+ *
+ *  Notes:
+ *      (1) The array[sy][sx] is indexed in standard "matrix notation",
+ *          with the row index first.
+ */
+l_float32 **
+create2dFloatArray(l_int32  sy,
+                   l_int32  sx)
+{
+l_int32      i;
+l_float32  **array;
+
+    PROCNAME("create2dFloatArray");
+
+    if ((array = (l_float32 **)LEPT_CALLOC(sy, sizeof(l_float32 *))) == NULL)
+        return (l_float32 **)ERROR_PTR("ptr array not made", procName, NULL);
+
+    for (i = 0; i < sy; i++) {
+        if ((array[i] = (l_float32 *)LEPT_CALLOC(sx, sizeof(l_float32)))
+            == NULL)
+            return (l_float32 **)ERROR_PTR("array not made", procName, NULL);
+    }
+
+    return array;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                            Kernel serialized I/O                     *
+ *----------------------------------------------------------------------*/
+/*!
+ *  kernelRead()
+ *
+ *      Input:  filename
+ *      Return: kernel, or null on error
+ */
+L_KERNEL *
+kernelRead(const char  *fname)
+{
+FILE      *fp;
+L_KERNEL  *kel;
+
+    PROCNAME("kernelRead");
+
+    if (!fname)
+        return (L_KERNEL *)ERROR_PTR("fname not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(fname)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("stream not opened", procName, NULL);
+    if ((kel = kernelReadStream(fp)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("kel not returned", procName, NULL);
+    fclose(fp);
+
+    return kel;
+}
+
+
+/*!
+ *  kernelReadStream()
+ *
+ *      Input:  stream
+ *      Return: kernel, or null on error
+ */
+L_KERNEL *
+kernelReadStream(FILE  *fp)
+{
+l_int32    sy, sx, cy, cx, i, j, ret, version, ignore;
+L_KERNEL  *kel;
+
+    PROCNAME("kernelReadStream");
+
+    if (!fp)
+        return (L_KERNEL *)ERROR_PTR("stream not defined", procName, NULL);
+
+    ret = fscanf(fp, "  Kernel Version %d\n", &version);
+    if (ret != 1)
+        return (L_KERNEL *)ERROR_PTR("not a kernel file", procName, NULL);
+    if (version != KERNEL_VERSION_NUMBER)
+        return (L_KERNEL *)ERROR_PTR("invalid kernel version", procName, NULL);
+
+    if (fscanf(fp, "  sy = %d, sx = %d, cy = %d, cx = %d\n",
+            &sy, &sx, &cy, &cx) != 4)
+        return (L_KERNEL *)ERROR_PTR("dimensions not read", procName, NULL);
+
+    if ((kel = kernelCreate(sy, sx)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
+    kernelSetOrigin(kel, cy, cx);
+
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++)
+            ignore = fscanf(fp, "%15f", &kel->data[i][j]);
+        ignore = fscanf(fp, "\n");
+    }
+    ignore = fscanf(fp, "\n");
+
+    return kel;
+}
+
+
+/*!
+ *  kernelWrite()
+ *
+ *      Input:  fname (output file)
+ *              kernel
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+kernelWrite(const char  *fname,
+            L_KERNEL    *kel)
+{
+FILE  *fp;
+
+    PROCNAME("kernelWrite");
+
+    if (!fname)
+        return ERROR_INT("fname not defined", procName, 1);
+    if (!kel)
+        return ERROR_INT("kel not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(fname, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    kernelWriteStream(fp, kel);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  kernelWriteStream()
+ *
+ *      Input:  stream
+ *              kel
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+kernelWriteStream(FILE      *fp,
+                  L_KERNEL  *kel)
+{
+l_int32  sx, sy, cx, cy, i, j;
+
+    PROCNAME("kernelWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!kel)
+        return ERROR_INT("kel not defined", procName, 1);
+    kernelGetParameters(kel, &sy, &sx, &cy, &cx);
+
+    fprintf(fp, "  Kernel Version %d\n", KERNEL_VERSION_NUMBER);
+    fprintf(fp, "  sy = %d, sx = %d, cy = %d, cx = %d\n", sy, sx, cy, cx);
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++)
+            fprintf(fp, "%15.4f", kel->data[i][j]);
+        fprintf(fp, "\n");
+    }
+    fprintf(fp, "\n");
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                 Making a kernel from a compiled string               *
+ *----------------------------------------------------------------------*/
+/*!
+ *  kernelCreateFromString()
+ *
+ *      Input:  height, width
+ *              cy, cx   (origin)
+ *              kdata
+ *      Return: kernel of the given size, or null on error
+ *
+ *  Notes:
+ *      (1) The data is an array of chars, in row-major order, giving
+ *          space separated integers in the range [-255 ... 255].
+ *      (2) The only other formatting limitation is that you must
+ *          leave space between the last number in each row and
+ *          the double-quote.  If possible, it's also nice to have each
+ *          line in the string represent a line in the kernel; e.g.,
+ *              static const char *kdata =
+ *                  " 20   50   20 "
+ *                  " 70  140   70 "
+ *                  " 20   50   20 ";
+ */
+L_KERNEL *
+kernelCreateFromString(l_int32      h,
+                       l_int32      w,
+                       l_int32      cy,
+                       l_int32      cx,
+                       const char  *kdata)
+{
+l_int32    n, i, j, index;
+l_float32  val;
+L_KERNEL  *kel;
+NUMA      *na;
+
+    PROCNAME("kernelCreateFromString");
+
+    if (h < 1)
+        return (L_KERNEL *)ERROR_PTR("height must be > 0", procName, NULL);
+    if (w < 1)
+        return (L_KERNEL *)ERROR_PTR("width must be > 0", procName, NULL);
+    if (cy < 0 || cy >= h)
+        return (L_KERNEL *)ERROR_PTR("cy invalid", procName, NULL);
+    if (cx < 0 || cx >= w)
+        return (L_KERNEL *)ERROR_PTR("cx invalid", procName, NULL);
+
+    kel = kernelCreate(h, w);
+    kernelSetOrigin(kel, cy, cx);
+    na = parseStringForNumbers(kdata, " \t\n");
+    n = numaGetCount(na);
+    if (n != w * h) {
+        numaDestroy(&na);
+        fprintf(stderr, "w = %d, h = %d, num ints = %d\n", w, h, n);
+        return (L_KERNEL *)ERROR_PTR("invalid integer data", procName, NULL);
+    }
+
+    index = 0;
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < w; j++) {
+            numaGetFValue(na, index, &val);
+            kernelSetElement(kel, i, j, val);
+            index++;
+        }
+    }
+
+    numaDestroy(&na);
+    return kel;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                Making a kernel from a simple file format             *
+ *----------------------------------------------------------------------*/
+/*!
+ *  kernelCreateFromFile()
+ *
+ *      Input:  filename
+ *      Return: kernel, or null on error
+ *
+ *  Notes:
+ *      (1) The file contains, in the following order:
+ *           - Any number of comment lines starting with '#' are ignored
+ *           - The height and width of the kernel
+ *           - The y and x values of the kernel origin
+ *           - The kernel data, formatted as lines of numbers (integers
+ *             or floats) for the kernel values in row-major order,
+ *             and with no other punctuation.
+ *             (Note: this differs from kernelCreateFromString(),
+ *             where each line must begin and end with a double-quote
+ *             to tell the compiler it's part of a string.)
+ *           - The kernel specification ends when a blank line,
+ *             a comment line, or the end of file is reached.
+ *      (2) All lines must be left-justified.
+ *      (3) See kernelCreateFromString() for a description of the string
+ *          format for the kernel data.  As an example, here are the lines
+ *          of a valid kernel description file  In the file, all lines
+ *          are left-justified:
+ *                    # small 3x3 kernel
+ *                    3 3
+ *                    1 1
+ *                    25.5   51    24.3
+ *                    70.2  146.3  73.4
+ *                    20     50.9  18.4
+ */
+L_KERNEL *
+kernelCreateFromFile(const char  *filename)
+{
+char      *filestr, *line;
+l_int32    nlines, i, j, first, index, w, h, cx, cy, n;
+l_float32  val;
+size_t     size;
+NUMA      *na, *nat;
+SARRAY    *sa;
+L_KERNEL  *kel;
+
+    PROCNAME("kernelCreateFromFile");
+
+    if (!filename)
+        return (L_KERNEL *)ERROR_PTR("filename not defined", procName, NULL);
+
+    filestr = (char *)l_binaryRead(filename, &size);
+    sa = sarrayCreateLinesFromString(filestr, 1);
+    LEPT_FREE(filestr);
+    nlines = sarrayGetCount(sa);
+
+        /* Find the first data line. */
+    for (i = 0; i < nlines; i++) {
+        line = sarrayGetString(sa, i, L_NOCOPY);
+        if (line[0] != '#') {
+            first = i;
+            break;
+        }
+    }
+
+        /* Find the kernel dimensions and origin location. */
+    line = sarrayGetString(sa, first, L_NOCOPY);
+    if (sscanf(line, "%d %d", &h, &w) != 2)
+        return (L_KERNEL *)ERROR_PTR("error reading h,w", procName, NULL);
+    line = sarrayGetString(sa, first + 1, L_NOCOPY);
+    if (sscanf(line, "%d %d", &cy, &cx) != 2)
+        return (L_KERNEL *)ERROR_PTR("error reading cy,cx", procName, NULL);
+
+        /* Extract the data.  This ends when we reach eof, or when we
+         * encounter a line of data that is either a null string or
+         * contains just a newline. */
+    na = numaCreate(0);
+    for (i = first + 2; i < nlines; i++) {
+        line = sarrayGetString(sa, i, L_NOCOPY);
+        if (line[0] == '\0' || line[0] == '\n' || line[0] == '#')
+            break;
+        nat = parseStringForNumbers(line, " \t\n");
+        numaJoin(na, nat, 0, -1);
+        numaDestroy(&nat);
+    }
+    sarrayDestroy(&sa);
+
+    n = numaGetCount(na);
+    if (n != w * h) {
+        numaDestroy(&na);
+        fprintf(stderr, "w = %d, h = %d, num ints = %d\n", w, h, n);
+        return (L_KERNEL *)ERROR_PTR("invalid integer data", procName, NULL);
+    }
+
+    kel = kernelCreate(h, w);
+    kernelSetOrigin(kel, cy, cx);
+    index = 0;
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < w; j++) {
+            numaGetFValue(na, index, &val);
+            kernelSetElement(kel, i, j, val);
+            index++;
+        }
+    }
+
+    numaDestroy(&na);
+    return kel;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                       Making a kernel from a Pix                     *
+ *----------------------------------------------------------------------*/
+/*!
+ *  kernelCreateFromPix()
+ *
+ *      Input:  pix
+ *              cy, cx (origin of kernel)
+ *      Return: kernel, or null on error
+ *
+ *  Notes:
+ *      (1) The origin must be positive and within the dimensions of the pix.
+ */
+L_KERNEL *
+kernelCreateFromPix(PIX         *pix,
+                    l_int32      cy,
+                    l_int32      cx)
+{
+l_int32    i, j, w, h, d;
+l_uint32   val;
+L_KERNEL  *kel;
+
+    PROCNAME("kernelCreateFromPix");
+
+    if (!pix)
+        return (L_KERNEL *)ERROR_PTR("pix not defined", procName, NULL);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 8)
+        return (L_KERNEL *)ERROR_PTR("pix not 8 bpp", procName, NULL);
+    if (cy < 0 || cx < 0 || cy >= h || cx >= w)
+        return (L_KERNEL *)ERROR_PTR("(cy, cx) invalid", procName, NULL);
+
+    kel = kernelCreate(h, w);
+    kernelSetOrigin(kel, cy, cx);
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < w; j++) {
+            pixGetPixel(pix, j, i, &val);
+            kernelSetElement(kel, i, j, (l_float32)val);
+        }
+    }
+
+    return kel;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                     Display a kernel in a pix                        *
+ *----------------------------------------------------------------------*/
+/*!
+ *  kernelDisplayInPix()
+ *
+ *      Input:  kernel
+ *              size (of grid interiors; odd; either 1 or a minimum size
+ *                    of 17 is enforced)
+ *              gthick (grid thickness; either 0 or a minimum size of 2
+ *                      is enforced)
+ *      Return: pix (display of kernel), or null on error
+ *
+ *  Notes:
+ *      (1) This gives a visual representation of a kernel.
+ *      (2) There are two modes of display:
+ *          (a) Grid lines of minimum width 2, surrounding regions
+ *              representing kernel elements of minimum size 17,
+ *              with a "plus" mark at the kernel origin, or
+ *          (b) A pix without grid lines and using 1 pixel per kernel element.
+ *      (3) For both cases, the kernel absolute value is displayed,
+ *          normalized such that the maximum absolute value is 255.
+ *      (4) Large 2D separable kernels should be used for convolution
+ *          with two 1D kernels.  However, for the bilateral filter,
+ *          the computation time is independent of the size of the
+ *          2D content kernel.
+ */
+PIX *
+kernelDisplayInPix(L_KERNEL     *kel,
+                   l_int32       size,
+                   l_int32       gthick)
+{
+l_int32    i, j, w, h, sx, sy, cx, cy, width, x0, y0;
+l_int32    normval;
+l_float32  minval, maxval, max, val, norm;
+PIX       *pixd, *pixt0, *pixt1;
+
+    PROCNAME("kernelDisplayInPix");
+
+    if (!kel)
+        return (PIX *)ERROR_PTR("kernel not defined", procName, NULL);
+
+        /* Normalize the max value to be 255 for display */
+    kernelGetParameters(kel, &sy, &sx, &cy, &cx);
+    kernelGetMinMax(kel, &minval, &maxval);
+    max = L_MAX(maxval, -minval);
+    if (max == 0.0)
+        return (PIX *)ERROR_PTR("kernel elements all 0.0", procName, NULL);
+    norm = 255. / (l_float32)max;
+
+        /* Handle the 1 element/pixel case; typically with large kernels */
+    if (size == 1 && gthick == 0) {
+        pixd = pixCreate(sx, sy, 8);
+        for (i = 0; i < sy; i++) {
+            for (j = 0; j < sx; j++) {
+                kernelGetElement(kel, i, j, &val);
+                normval = (l_int32)(norm * L_ABS(val));
+                pixSetPixel(pixd, j, i, normval);
+            }
+        }
+        return pixd;
+    }
+
+        /* Enforce the constraints for the grid line version */
+    if (size < 17) {
+        L_WARNING("size < 17; setting to 17\n", procName);
+        size = 17;
+    }
+    if (size % 2 == 0)
+        size++;
+    if (gthick < 2) {
+        L_WARNING("grid thickness < 2; setting to 2\n", procName);
+        gthick = 2;
+    }
+
+    w = size * sx + gthick * (sx + 1);
+    h = size * sy + gthick * (sy + 1);
+    pixd = pixCreate(w, h, 8);
+
+        /* Generate grid lines */
+    for (i = 0; i <= sy; i++)
+        pixRenderLine(pixd, 0, gthick / 2 + i * (size + gthick),
+                      w - 1, gthick / 2 + i * (size + gthick),
+                      gthick, L_SET_PIXELS);
+    for (j = 0; j <= sx; j++)
+        pixRenderLine(pixd, gthick / 2 + j * (size + gthick), 0,
+                      gthick / 2 + j * (size + gthick), h - 1,
+                      gthick, L_SET_PIXELS);
+
+        /* Generate mask for each element */
+    pixt0 = pixCreate(size, size, 1);
+    pixSetAll(pixt0);
+
+        /* Generate crossed lines for origin pattern */
+    pixt1 = pixCreate(size, size, 1);
+    width = size / 8;
+    pixRenderLine(pixt1, size / 2, (l_int32)(0.12 * size),
+                           size / 2, (l_int32)(0.88 * size),
+                           width, L_SET_PIXELS);
+    pixRenderLine(pixt1, (l_int32)(0.15 * size), size / 2,
+                           (l_int32)(0.85 * size), size / 2,
+                           width, L_FLIP_PIXELS);
+    pixRasterop(pixt1, size / 2 - width, size / 2 - width,
+                2 * width, 2 * width, PIX_NOT(PIX_DST), NULL, 0, 0);
+
+        /* Paste the patterns in */
+    y0 = gthick;
+    for (i = 0; i < sy; i++) {
+        x0 = gthick;
+        for (j = 0; j < sx; j++) {
+            kernelGetElement(kel, i, j, &val);
+            normval = (l_int32)(norm * L_ABS(val));
+            pixSetMaskedGeneral(pixd, pixt0, normval, x0, y0);
+            if (i == cy && j == cx)
+                pixPaintThroughMask(pixd, pixt1, x0, y0, 255 - normval);
+            x0 += size + gthick;
+        }
+        y0 += size + gthick;
+    }
+
+    pixDestroy(&pixt0);
+    pixDestroy(&pixt1);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                     Parse string to extract numbers                    *
+ *------------------------------------------------------------------------*/
+/*!
+ *  parseStringForNumbers()
+ *
+ *      Input:  string (containing numbers; not changed)
+ *              seps (string of characters that can be used between ints)
+ *      Return: numa (of numbers found), or null on error
+ *
+ *  Note:
+ *     (1) The numbers can be ints or floats.
+ */
+NUMA *
+parseStringForNumbers(const char  *str,
+                      const char  *seps)
+{
+char      *newstr, *head, *tail;
+l_float32  val;
+NUMA      *na;
+
+    PROCNAME("parseStringForNumbers");
+
+    if (!str)
+        return (NUMA *)ERROR_PTR("str not defined", procName, NULL);
+
+    newstr = stringNew(str);  /* to enforce const-ness of str */
+    na = numaCreate(0);
+    head = strtokSafe(newstr, seps, &tail);
+    val = atof(head);
+    numaAddNumber(na, val);
+    LEPT_FREE(head);
+    while ((head = strtokSafe(NULL, seps, &tail)) != NULL) {
+        val = atof(head);
+        numaAddNumber(na, val);
+        LEPT_FREE(head);
+    }
+
+    LEPT_FREE(newstr);
+    return na;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                        Simple parametric kernels                       *
+ *------------------------------------------------------------------------*/
+/*!
+ *  makeFlatKernel()
+ *
+ *      Input:  height, width
+ *              cy, cx (origin of kernel)
+ *      Return: kernel, or null on error
+ *
+ *  Notes:
+ *      (1) This is the same low-pass filtering kernel that is used
+ *          in the block convolution functions.
+ *      (2) The kernel origin (@cy, @cx) is typically placed as near
+ *          the center of the kernel as possible.  If height and
+ *          width are odd, then using cy = height / 2 and
+ *          cx = width / 2 places the origin at the exact center.
+ *      (3) This returns a normalized kernel.
+ */
+L_KERNEL *
+makeFlatKernel(l_int32  height,
+               l_int32  width,
+               l_int32  cy,
+               l_int32  cx)
+{
+l_int32    i, j;
+l_float32  normval;
+L_KERNEL  *kel;
+
+    PROCNAME("makeFlatKernel");
+
+    if ((kel = kernelCreate(height, width)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
+    kernelSetOrigin(kel, cy, cx);
+    normval = 1.0 / (l_float32)(height * width);
+    for (i = 0; i < height; i++) {
+        for (j = 0; j < width; j++) {
+            kernelSetElement(kel, i, j, normval);
+        }
+    }
+
+    return kel;
+}
+
+
+/*!
+ *  makeGaussianKernel()
+ *
+ *      Input:  halfheight, halfwidth (sx = 2 * halfwidth + 1, etc)
+ *              stdev (standard deviation)
+ *              max (value at (cx,cy))
+ *      Return: kernel, or null on error
+ *
+ *  Notes:
+ *      (1) The kernel size (sx, sy) = (2 * halfwidth + 1, 2 * halfheight + 1).
+ *      (2) The kernel center (cx, cy) = (halfwidth, halfheight).
+ *      (3) The halfwidth and halfheight are typically equal, and
+ *          are typically several times larger than the standard deviation.
+ *      (4) If pixConvolve() is invoked with normalization (the sum of
+ *          kernel elements = 1.0), use 1.0 for max (or any number that's
+ *          not too small or too large).
+ */
+L_KERNEL *
+makeGaussianKernel(l_int32    halfheight,
+                   l_int32    halfwidth,
+                   l_float32  stdev,
+                   l_float32  max)
+{
+l_int32    sx, sy, i, j;
+l_float32  val;
+L_KERNEL  *kel;
+
+    PROCNAME("makeGaussianKernel");
+
+    sx = 2 * halfwidth + 1;
+    sy = 2 * halfheight + 1;
+    if ((kel = kernelCreate(sy, sx)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
+    kernelSetOrigin(kel, halfheight, halfwidth);
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++) {
+            val = expf(-(l_float32)((i - halfheight) * (i - halfheight) +
+                                    (j - halfwidth) * (j - halfwidth)) /
+                        (2. * stdev * stdev));
+            kernelSetElement(kel, i, j, max * val);
+        }
+    }
+
+    return kel;
+}
+
+
+/*!
+ *  makeGaussianKernelSep()
+ *
+ *      Input:  halfheight, halfwidth (sx = 2 * halfwidth + 1, etc)
+ *              stdev (standard deviation)
+ *              max (value at (cx,cy))
+ *              &kelx (<return> x part of kernel)
+ *              &kely (<return> y part of kernel)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See makeGaussianKernel() for description of input parameters.
+ *      (2) These kernels are constructed so that the result of both
+ *          normalized and un-normalized convolution will be the same
+ *          as when convolving with pixConvolve() using the full kernel.
+ *      (3) The trick for the un-normalized convolution is to have the
+ *          product of the two kernel elemets at (cx,cy) be equal to max,
+ *          not max**2.  That's why the max for kely is 1.0.  If instead
+ *          we use sqrt(max) for both, the results are slightly less
+ *          accurate, when compared to using the full kernel in
+ *          makeGaussianKernel().
+ */
+l_int32
+makeGaussianKernelSep(l_int32    halfheight,
+                      l_int32    halfwidth,
+                      l_float32  stdev,
+                      l_float32  max,
+                      L_KERNEL **pkelx,
+                      L_KERNEL **pkely)
+{
+    PROCNAME("makeGaussianKernelSep");
+
+    if (!pkelx || !pkely)
+        return ERROR_INT("&kelx and &kely not defined", procName, 1);
+
+    *pkelx = makeGaussianKernel(0, halfwidth, stdev, max);
+    *pkely = makeGaussianKernel(halfheight, 0, stdev, 1.0);
+    return 0;
+}
+
+
+/*!
+ *  makeDoGKernel()
+ *
+ *      Input:  halfheight, halfwidth (sx = 2 * halfwidth + 1, etc)
+ *              stdev (standard deviation of narrower gaussian)
+ *              ratio (of stdev for wide filter to stdev for narrow one)
+ *      Return: kernel, or null on error
+ *
+ *  Notes:
+ *      (1) The DoG (difference of gaussians) is a wavelet mother
+ *          function with null total sum.  By subtracting two blurred
+ *          versions of the image, it acts as a bandpass filter for
+ *          frequencies passed by the narrow gaussian but stopped
+ *          by the wide one.See:
+ *               http://en.wikipedia.org/wiki/Difference_of_Gaussians
+ *      (2) The kernel size (sx, sy) = (2 * halfwidth + 1, 2 * halfheight + 1).
+ *      (3) The kernel center (cx, cy) = (halfwidth, halfheight).
+ *      (4) The halfwidth and halfheight are typically equal, and
+ *          are typically several times larger than the standard deviation.
+ *      (5) The ratio is the ratio of standard deviations of the wide
+ *          to narrow gaussian.  It must be >= 1.0; 1.0 is a no-op.
+ *      (6) Because the kernel is a null sum, it must be invoked without
+ *          normalization in pixConvolve().
+ */
+L_KERNEL *
+makeDoGKernel(l_int32    halfheight,
+              l_int32    halfwidth,
+              l_float32  stdev,
+              l_float32  ratio)
+{
+l_int32    sx, sy, i, j;
+l_float32  pi, squaredist, highnorm, lownorm, val;
+L_KERNEL  *kel;
+
+    PROCNAME("makeDoGKernel");
+
+    sx = 2 * halfwidth + 1;
+    sy = 2 * halfheight + 1;
+    if ((kel = kernelCreate(sy, sx)) == NULL)
+        return (L_KERNEL *)ERROR_PTR("kel not made", procName, NULL);
+    kernelSetOrigin(kel, halfheight, halfwidth);
+
+    pi = 3.1415926535;
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++) {
+            squaredist = (l_float32)((i - halfheight) * (i - halfheight) +
+                                     (j - halfwidth) * (j - halfwidth));
+            highnorm = 1. / (2 * stdev * stdev);
+            lownorm = highnorm / (ratio * ratio);
+            val = (highnorm / pi) * expf(-(highnorm * squaredist))
+                  - (lownorm / pi) * expf(-(lownorm * squaredist));
+            kernelSetElement(kel, i, j, val);
+        }
+    }
+
+    return kel;
+}
diff --git a/src/leptonica-license.txt b/src/leptonica-license.txt
new file mode 100644 (file)
index 0000000..da7520b
--- /dev/null
@@ -0,0 +1,26 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
diff --git a/src/leptwin.c b/src/leptwin.c
new file mode 100644 (file)
index 0000000..9f4e52a
--- /dev/null
@@ -0,0 +1,364 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  leptwin.c
+ *
+ *    This file contains Leptonica routines needed only on Microsoft Windows
+ *
+ *    Currently it only contains one public function
+ *    (based on dibsectn.c by jmh, 03-30-98):
+ *
+ *      HBITMAP    pixGetWindowsHBITMAP(PIX *pix)
+ */
+
+#ifdef _WIN32
+#include <stdlib.h>
+#include <string.h>
+#include "allheaders.h"
+#include "leptwin.h"
+
+/* Macro to determine the number of bytes per line in the DIB bits.
+ * This accounts for DWORD alignment by adding 31 bits,
+ * then dividing by 32, then rounding up to the next highest
+ * count of 4-bytes.  Then, we multiply by 4 to get the total byte count.  */
+#define BYTESPERLINE(Width, BPP) ((l_int32)((((DWORD)(Width) * (DWORD)(BPP) + 31) >> 5)) << 2)
+
+
+/* **********************************************************************
+  DWORD DSImageBitsSize(LPBITMAPINFO pbmi)
+
+  PARAMETERS:
+    LPBITMAPINFO - pointer to a BITMAPINFO describing a DIB
+
+  RETURNS:
+    DWORD    - the size, in bytes, of the DIB's image bits
+
+  REMARKS:
+    Calculates and returns the size, in bytes, of the image bits for
+    the DIB described by the BITMAPINFO.
+********************************************************************** */
+static DWORD
+DSImageBitsSize(LPBITMAPINFO pbmi)
+{
+    switch(pbmi->bmiHeader.biCompression)
+    {
+    case BI_RLE8:  /* wrong if haven't called DSCreateDIBSection or
+                    * CreateDIBSection with this pbmi */
+    case BI_RLE4:
+        return pbmi->bmiHeader.biSizeImage;
+        break;
+    default:  /* should not have to use "default" */
+    case BI_RGB:
+    case BI_BITFIELDS:
+        return BYTESPERLINE(pbmi->bmiHeader.biWidth, \
+                   pbmi->bmiHeader.biBitCount * pbmi->bmiHeader.biPlanes) *
+               pbmi->bmiHeader.biHeight;
+        break;
+    }
+    return 0;
+}
+
+/* **********************************************************************
+  DWORD ImageBitsSize(HBITMAP hbitmap)
+
+  PARAMETERS:
+    HBITMAP - hbitmap
+
+  RETURNS:
+    DWORD    - the size, in bytes, of the HBITMAP's image bits
+
+  REMARKS:
+    Calculates and returns the size, in bytes, of the image bits for
+    the DIB described by the HBITMAP.
+********************************************************************** */
+static DWORD
+ImageBitsSize(HBITMAP hBitmap)
+{
+    DIBSECTION  ds;
+
+    GetObject(hBitmap, sizeof(DIBSECTION), &ds);
+    switch( ds.dsBmih.biCompression )
+    {
+    case BI_RLE8:  /* wrong if haven't called DSCreateDIBSection or
+                    * CreateDIBSection with this pbmi */
+    case BI_RLE4:
+        return ds.dsBmih.biSizeImage;
+        break;
+    default:  /* should not have to use "default" */
+    case BI_RGB:
+    case BI_BITFIELDS:
+        return BYTESPERLINE(ds.dsBmih.biWidth, \
+                            ds.dsBmih.biBitCount * ds.dsBmih.biPlanes) *
+                            ds.dsBmih.biHeight;
+        break;
+    }
+    return 0;
+}
+
+/*!
+ *  setColormap(LPBITMAPINFO pbmi, PIXCMAP *cmap)
+ *
+ *      Input:  pbmi (pointer to a BITMAPINFO describing a DIB)
+ *              cmap (leptonica colormap)
+ *      Return: number of colors in cmap
+ */
+static int
+setColormap(LPBITMAPINFO  pbmi,
+            PIXCMAP      *cmap)
+{
+l_int32  i, nColors, rval, gval, bval;
+
+    nColors = pixcmapGetCount(cmap);
+    for (i = 0; i < nColors; i++) {
+        pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+        pbmi->bmiColors[i].rgbRed = rval;
+        pbmi->bmiColors[i].rgbGreen = gval;
+        pbmi->bmiColors[i].rgbBlue = bval;
+        pbmi->bmiColors[i].rgbReserved = 0;
+    }
+    pbmi->bmiHeader.biClrUsed = nColors;
+    return nColors;
+}
+
+/* **********************************************************************
+  HBITMAP DSCreateBitmapInfo(l_int32 width, l_int32 height, l_int32 depth,
+                             PIXCMAP *cmap)
+
+  PARAMETERS:
+    l_int32 width - Desired width of the DIBSection
+    l_int32 height - Desired height of the DIBSection
+    l_int32 depth - Desired bit-depth of the DIBSection
+    PIXCMAP cmap - leptonica colormap for depths < 16
+
+  RETURNS:
+    LPBITMAPINFO - a ptr to BITMAPINFO of the desired size and bit-depth
+                   NULL on failure
+
+  REMARKS:
+    Creates a BITMAPINFO based on the criteria passed in as parameters.
+
+********************************************************************** */
+static LPBITMAPINFO
+DSCreateBitmapInfo(l_int32 width,
+                   l_int32 height,
+                   l_int32 depth,
+                   PIXCMAP *cmap)
+{
+l_int32       nInfoSize;
+LPBITMAPINFO  pbmi;
+LPDWORD       pMasks;
+
+    nInfoSize = sizeof(BITMAPINFOHEADER);
+    if( depth <= 8 )
+        nInfoSize += sizeof(RGBQUAD) * (1 << depth);
+    if((depth == 16) || (depth == 32))
+        nInfoSize += (3 * sizeof(DWORD));
+
+        /* Create the header big enough to contain color table and
+         * bitmasks if needed. */
+    pbmi = (LPBITMAPINFO)malloc(nInfoSize);
+    if (!pbmi)
+        return NULL;
+
+    ZeroMemory(pbmi, nInfoSize);
+    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+    pbmi->bmiHeader.biWidth = width;
+    pbmi->bmiHeader.biHeight = height;
+    pbmi->bmiHeader.biPlanes = 1;
+    pbmi->bmiHeader.biBitCount = depth;
+
+        /* override below for 16 and 32 bpp */
+    pbmi->bmiHeader.biCompression = BI_RGB;
+
+        /*  ?? not sure if this is right?  */
+    pbmi->bmiHeader.biSizeImage = DSImageBitsSize(pbmi);
+
+    pbmi->bmiHeader.biXPelsPerMeter = 0;
+    pbmi->bmiHeader.biYPelsPerMeter = 0;
+    pbmi->bmiHeader.biClrUsed = 0;  /* override below */
+    pbmi->bmiHeader.biClrImportant = 0;
+
+    switch(depth)
+    {
+        case 24:
+                /* 24bpp requires no special handling */
+            break;
+        case 16:
+                /* if it's 16bpp, fill in the masks and override the
+                 * compression.  These are the default masks -- you
+                 * could change them if needed. */
+            pMasks = (LPDWORD)(pbmi->bmiColors);
+            pMasks[0] = 0x00007c00;
+            pMasks[1] = 0x000003e0;
+            pMasks[2] = 0x0000001f;
+            pbmi->bmiHeader.biCompression = BI_BITFIELDS;
+            break;
+        case 32:
+                /* if it's 32 bpp, fill in the masks and override
+                 * the compression */
+            pMasks = (LPDWORD)(pbmi->bmiColors);
+            /*pMasks[0] = 0x00ff0000; */
+            /*pMasks[1] = 0x0000ff00; */
+            /*pMasks[2] = 0x000000ff; */
+            pMasks[0] = 0xff000000;
+            pMasks[1] = 0x00ff0000;
+            pMasks[2] = 0x0000ff00;
+
+            pbmi->bmiHeader.biCompression = BI_BITFIELDS;
+            break;
+        case 8:
+        case 4:
+        case 1:
+            setColormap(pbmi, cmap);
+            break;
+    }
+    return pbmi;
+}
+
+/* **********************************************************************
+  HBITMAP DSCreateDIBSection(l_int32 width, l_int32 height, l_int32 depth,
+                             PIXCMAP *cmap)
+
+  PARAMETERS:
+    l_int32 width - Desired width of the DIBSection
+    l_int32 height - Desired height of the DIBSection
+    l_int32 depth - Desired bit-depth of the DIBSection
+    PIXCMAP cmap - leptonica colormap for depths < 16
+
+  RETURNS:
+    HBITMAP      - a DIBSection HBITMAP of the desired size and bit-depth
+                   NULL on failure
+
+  REMARKS:
+    Creates a DIBSection based on the criteria passed in as parameters.
+
+********************************************************************** */
+static HBITMAP
+DSCreateDIBSection(l_int32  width,
+                   l_int32  height,
+                   l_int32  depth,
+                   PIXCMAP  *cmap)
+{
+HBITMAP       hBitmap;
+l_int32       nInfoSize;
+LPBITMAPINFO  pbmi;
+HDC           hRefDC;
+LPBYTE        pBits;
+
+    pbmi = DSCreateBitmapInfo (width, height, depth, cmap);
+    if (!pbmi)
+        return NULL;
+
+    hRefDC = GetDC(NULL);
+    hBitmap = CreateDIBSection(hRefDC, pbmi, DIB_RGB_COLORS,
+                               (void **) &pBits, NULL, 0);
+    nInfoSize = GetLastError();
+    ReleaseDC(NULL, hRefDC);
+    free(pbmi);
+
+    return hBitmap;
+}
+
+
+/*!
+ *  pixGetWindowsHBITMAP()
+ *
+ *      Input:  pix
+ *      Return: Windows hBitmap, or null on error
+ *
+ *  Notes:
+ *      (1) It's the responsibility of the caller to destroy the
+ *          returned hBitmap with a call to DeleteObject (or with
+ *          something that eventually calls DeleteObject).
+ */
+HBITMAP
+pixGetWindowsHBITMAP(PIX  *pix)
+{
+l_int32    width, height, depth;
+l_uint32  *data;
+HBITMAP    hBitmap = NULL;
+BITMAP     bm;
+DWORD      imageBitsSize;
+PIX       *pixt = NULL;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixGetWindowsHBITMAP");
+    if (!pix)
+        return (HBITMAP)ERROR_PTR("pix not defined", procName, NULL);
+
+    pixGetDimensions(pix, &width, &height, &depth);
+    cmap = pixGetColormap(pix);
+
+    if (depth == 24) depth = 32;
+    if (depth == 2) {
+        pixt = pixConvert2To8(pix, 0, 85, 170, 255, TRUE);
+        if (!pixt)
+            return (HBITMAP)ERROR_PTR("unable to convert pix from 2bpp to 8bpp",
+                    procName, NULL);
+        depth = pixGetDepth(pixt);
+        cmap = pixGetColormap(pixt);
+    }
+
+    if (depth < 16) {
+        if (!cmap)
+            cmap = pixcmapCreateLinear(depth, 1<<depth);
+    }
+
+    hBitmap = DSCreateDIBSection(width, height, depth, cmap);
+    if (!hBitmap)
+        return (HBITMAP)ERROR_PTR("Unable to create HBITMAP", procName, NULL);
+
+        /* By default, Windows assumes bottom up images */
+    if (pixt)
+        pixt = pixFlipTB(pixt, pixt);
+    else
+        pixt = pixFlipTB(NULL, pix);
+
+        /* "standard" color table assumes bit off=black */
+    if (depth == 1) {
+        pixInvert(pixt, pixt);
+    }
+
+        /* Don't byte swap until done manipulating pix! */
+    if (depth <= 16)
+        pixEndianByteSwap(pixt);
+
+    GetObject (hBitmap, sizeof(BITMAP), &bm);
+    imageBitsSize = ImageBitsSize(hBitmap);
+    data = pixGetData(pixt);
+    if (data) {
+        memcpy (bm.bmBits, data, imageBitsSize);
+    } else {
+        DeleteObject (hBitmap);
+        hBitmap = NULL;
+    }
+    pixDestroy(&pixt);
+
+    return hBitmap;
+}
+
+#endif   /* _WIN32 */
diff --git a/src/leptwin.h b/src/leptwin.h
new file mode 100644 (file)
index 0000000..451da6b
--- /dev/null
@@ -0,0 +1,45 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifdef _WIN32
+#ifndef  LEPTONICA_LEPTWIN_H
+#define  LEPTONICA_LEPTWIN_H
+
+#include "allheaders.h"
+#include <windows.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif  /* __cplusplus */
+
+LEPT_DLL extern HBITMAP pixGetWindowsHBITMAP( PIX *pixs );
+
+#ifdef __cplusplus
+}
+#endif  /* __cplusplus */
+
+#endif /* LEPTONICA_LEPTWIN_H */
+#endif /* _WIN32 */
diff --git a/src/libversions.c b/src/libversions.c
new file mode 100644 (file)
index 0000000..cf95e1f
--- /dev/null
@@ -0,0 +1,198 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  libversions.c
+ *
+ *       Image library version number
+ *           char      *getImagelibVersions()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+#if HAVE_LIBGIF
+#include "gif_lib.h"
+#endif
+
+#if HAVE_LIBJPEG
+/* jpeglib.h includes jconfig.h, which makes the error of setting
+ *   #define HAVE_STDLIB_H
+ * which conflicts with config_auto.h (where it is set to 1) and results
+ * for some gcc compiler versions in a warning.  The conflict is harmless
+ * but we suppress it by undefining the variable. */
+#undef HAVE_STDLIB_H
+#include "jpeglib.h"
+#include "jerror.h"
+#endif
+
+#if HAVE_LIBPNG
+#include "png.h"
+#endif
+
+#if HAVE_LIBTIFF
+#include "tiffio.h"
+#endif
+
+#if HAVE_LIBZ
+#include "zlib.h"
+#endif
+
+#if HAVE_LIBWEBP
+#include "webp/encode.h"
+#endif
+
+#if HAVE_LIBJP2K
+#include LIBJP2K_HEADER
+#endif
+
+
+/*---------------------------------------------------------------------*
+ *                    Image Library Version number                     *
+ *---------------------------------------------------------------------*/
+/*!
+ *  getImagelibVersions()
+ *
+ *      Return: string of version numbers; e.g.,
+ *               libgif 5.0.3
+ *               libjpeg 8b (libjpeg-turbo 1.3.0)
+ *               libpng 1.4.3
+ *               libtiff 3.9.5
+ *               zlib 1.2.5
+ *               libwebp 0.3.0
+ *               libopenjp2 2.1.0
+ *
+ *  Notes:
+ *      (1) The caller must free the memory.
+ */
+char *
+getImagelibVersions()
+{
+char     buf[128];
+l_int32  first = TRUE;
+char    *versionNumP;
+char    *nextTokenP;
+char    *versionStrP = NULL;
+
+#if HAVE_LIBGIF
+    first = FALSE;
+    stringJoinIP(&versionStrP, "libgif ");
+  #ifdef GIFLIB_MAJOR
+    snprintf(buf, sizeof(buf), "%d.%d.%d", GIFLIB_MAJOR, GIFLIB_MINOR,
+             GIFLIB_RELEASE);
+  #else
+    stringCopy(buf, "4.1.6(?)", sizeof(buf));
+  #endif
+    stringJoinIP(&versionStrP, buf);
+#endif  /* HAVE_LIBGIF */
+
+#if HAVE_LIBJPEG
+    {
+    struct jpeg_compress_struct  cinfo;
+    struct jpeg_error_mgr        err;
+    char                         buffer[JMSG_LENGTH_MAX];
+    cinfo.err = jpeg_std_error(&err);
+    err.msg_code = JMSG_VERSION;
+    (*err.format_message) ((j_common_ptr ) &cinfo, buffer);
+
+    if (!first) stringJoinIP(&versionStrP, " : ");
+    first = FALSE;
+    stringJoinIP(&versionStrP, "libjpeg ");
+    versionNumP = strtokSafe(buffer, " ", &nextTokenP);
+    stringJoinIP(&versionStrP, versionNumP);
+    LEPT_FREE(versionNumP);
+
+  #if defined(LIBJPEG_TURBO_VERSION)
+        /* To stringify the result of expansion of a macro argument,
+         * you must use two levels of macros.  See:
+         *   https://gcc.gnu.org/onlinedocs/cpp/Stringification.html  */
+  #define l_xstr(s) l_str(s)
+  #define l_str(s) #s
+    snprintf(buf, sizeof(buf), " (libjpeg-turbo %s)",
+             l_xstr(LIBJPEG_TURBO_VERSION));
+    stringJoinIP(&versionStrP, buf);
+  #endif  /* LIBJPEG_TURBO_VERSION */
+    }
+#endif  /* HAVE_LIBJPEG */
+
+#if HAVE_LIBPNG
+    if (!first) stringJoinIP(&versionStrP, " : ");
+    first = FALSE;
+    stringJoinIP(&versionStrP, "libpng ");
+    stringJoinIP(&versionStrP, png_get_libpng_ver(NULL));
+#endif  /* HAVE_LIBPNG */
+
+#if HAVE_LIBTIFF
+    if (!first) stringJoinIP(&versionStrP, " : ");
+    first = FALSE;
+    stringJoinIP(&versionStrP, "libtiff ");
+    versionNumP = strtokSafe((char *)TIFFGetVersion(), " \n", &nextTokenP);
+    LEPT_FREE(versionNumP);
+    versionNumP = strtokSafe(NULL, " \n", &nextTokenP);
+    LEPT_FREE(versionNumP);
+    versionNumP = strtokSafe(NULL, " \n", &nextTokenP);
+    stringJoinIP(&versionStrP, versionNumP);
+    LEPT_FREE(versionNumP);
+#endif  /* HAVE_LIBTIFF */
+
+#if HAVE_LIBZ
+    if (!first) stringJoinIP(&versionStrP, " : ");
+    first = FALSE;
+    stringJoinIP(&versionStrP, "zlib ");
+    stringJoinIP(&versionStrP, zlibVersion());
+#endif  /* HAVE_LIBZ */
+
+#if HAVE_LIBWEBP
+    {
+    l_int32 val;
+    char buf[32];
+    if (!first) stringJoinIP(&versionStrP, " : ");
+    first = FALSE;
+    stringJoinIP(&versionStrP, "libwebp ");
+    val = WebPGetEncoderVersion();
+    snprintf(buf, sizeof(buf), "%d.%d.%d", val >> 16, (val >> 8) & 0xff,
+             val & 0xff);
+    stringJoinIP(&versionStrP, buf);
+    }
+#endif  /* HAVE_LIBWEBP */
+
+#if HAVE_LIBJP2K
+    {
+    const char *version;
+    if (!first) stringJoinIP(&versionStrP, " : ");
+    first = FALSE;
+    stringJoinIP(&versionStrP, "libopenjp2 ");
+    version = opj_version();
+    stringJoinIP(&versionStrP, version);
+    }
+#endif  /* HAVE_LIBJP2K */
+
+    stringJoinIP(&versionStrP, "\n");
+    return versionStrP;
+}
diff --git a/src/list.c b/src/list.c
new file mode 100644 (file)
index 0000000..4487e01
--- /dev/null
@@ -0,0 +1,793 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   list.c
+ *
+ *      Inserting and removing elements
+ *
+ *           void      listDestroy()
+ *           DLLIST   *listAddToHead()
+ *           l_int32   listAddToTail()
+ *           l_int32   listInsertBefore()
+ *           l_int32   listInsertAfter()
+ *           void     *listRemoveElement()
+ *           void     *listRemoveFromHead()
+ *           void     *listRemoveFromTail()
+ *
+ *      Other list operations
+ *
+ *           DLLIST   *listFindElement()
+ *           DLLIST   *listFindTail()
+ *           l_int32   listGetCount()
+ *           l_int32   listReverse()
+ *           DLLIST   *listJoin()
+ *
+ *      Lists are much harder to handle than arrays.  There is
+ *      more overhead for the programmer, both cognitive and
+ *      codewise, and more likelihood that an error can be made.
+ *      For that reason, lists should only be used when it is
+ *      inefficient to use arrays, such as when elements are
+ *      routinely inserted or deleted from inside arrays whose
+ *      average size is greater than about 10.
+ *
+ *      A list of data structures can be implemented in a number
+ *      of ways.  The two most popular are:
+ *
+ *         (1) The list can be composed of a linked list of
+ *             pointer cells ("cons cells"), where the data structures
+ *             are hung off the cells.  This is more difficult
+ *             to use because you have to keep track of both
+ *             your hanging data and the cell structures.
+ *             It requires 3 pointers for every data structure
+ *             that is put in a list.  There is no problem
+ *             cloning (using reference counts) for structures that
+ *             are put in such a list.  We implement lists by this
+ *             method here.
+ *
+ *         (2) The list pointers can be inserted directly into
+ *             the data structures.  This is easy to implement
+ *             and easier to use, but it adds 2 ptrs of overhead
+ *             to every data structure in which the ptrs are embedded.
+ *             It also requires special care not to put the ptrs
+ *             in any data that is cloned with a reference count;
+ *             else your lists will break.
+ *
+ *      Writing C code that uses list pointers explicitly to make
+ *      and alter lists is difficult and prone to error.
+ *      Consequently, a generic list utility that handles lists
+ *      of arbitrary objects and doesn't force the programmer to
+ *      touch the "next" and "prev" pointers, is quite useful.
+ *      Such functions are provided here.   However, the usual
+ *      situation requires traversing a list and applying some
+ *      function to one or more of the list elements.  Macros
+ *      for traversing the list are, in general, necessary, to
+ *      achieve the goal of invisibly handling all "next" and "prev"
+ *      pointers in generic lists.  We provide macros for
+ *      traversing a list in both forward and reverse directions.
+ *
+ *      Because of the typing in C, implementation of a general
+ *      list utility requires casting.  If macros are used, the
+ *      casting can be done implicitly; otherwise, using functions,
+ *      some of the casts must be explicit.  Fortunately, this
+ *      can be implemented with void* so the programmer using
+ *      the library will not have to make any casts!  (Unless you
+ *      compile with g++, in which case the rules on implicit
+ *      conversion are more strict.)
+ *
+ *      For example, to add an arbitrary data structure foo to the
+ *      tail of a list, use
+ *             listAddToTail(&head, &tail, pfoo);
+ *      where head and tail are list cell ptrs and pfoo is
+ *      a pointer to the foo object.
+ *      And to remove an arbitrary data structure foo from a
+ *      list, when you know the list cell element it is hanging from,
+ *      use
+ *             pfoo = listRemoveElement(&head, elem)
+ *      where head and elem are list cell ptrs and pfoo is a pointer
+ *      to the foo object.  No casts are required for foo in
+ *      either direction in ANSI C.  (However, casts are
+ *      required for ANSI C++).
+ *
+ *      We use lists that are composed of doubly-linked
+ *      cells with data structures hanging off the cells.
+ *      We use doubly-linked cells to simplify insertion
+ *      and deletion, and to allow operations to proceed in either
+ *      direction along the list.  With doubly-linked lists,
+ *      it is tempting to make them circular, by setting head->prev
+ *      to the tail of the list and tail->next to the head.
+ *      The circular list costs nothing extra in storage, and
+ *      allows operations to proceed from either end of the list
+ *      with equal speed.  However, the circular link adds
+ *      cognitive overhead for the application programmer in
+ *      general, and it greatly complicates list traversal when
+ *      arbitrary list elements can be added or removed as you
+ *      move through.  It can be done, but in the spirit of
+ *      simplicity, we avoid the temptation.  The price to be paid
+ *      is the extra cost to find the tail of a list -- a full
+ *      traversal -- before the tail can be used.  This is a
+ *      cheap price to pay to avoid major headaches and buggy code.
+ *
+ *      When you are only applying some function to each element
+ *      in a list, you can go either forwards or backwards.
+ *      To run through a list forwards, use:
+ *
+ *          for (elem = head; elem; elem = nextelem) {
+ *              nextelem = elem->next;   (in case we destroy elem)
+ *              <do something with elem->data>
+ *          }
+ *
+ *      To run through a list backwards, find the tail and use:
+ *
+ *          for (elem = tail; elem; elem = prevelem) {
+ #              prevelem = elem->prev;  (in case we destroy elem)
+ *              <do something with elem->data>
+ *          }
+ *
+ *      Even though these patterns are very simple, they are so common
+ *      that we've provided macros for them in list.h.  Using the
+ *      macros, this becomes:
+ *
+ *          L_BEGIN_LIST_FORWARD(head, elem)
+ *              <do something with elem->data>
+ *          L_END_LIST
+ *
+ *          L_BEGIN_LIST_REVERSE(tail, elem)
+ *              <do something with elem->data>
+ *          L_END_LIST
+ *
+ *      Note again that with macros, the application programmer does
+ *      not need to refer explicitly to next and prev fields.  Also,
+ *      in the reverse case, note that we do not explicitly
+ *      show the head of the list.  However, the head of the list
+ *      is always in scope, and functions can be called within the
+ *      iterator that change the head.
+ *
+ *      Some special cases are simpler.  For example, when
+ *      removing all items from the head of the list, you can use
+ *
+ *          while (head) {
+ *              obj = listRemoveFromHead(&head);
+ *              <do something with obj>
+ *          }
+ *
+ *      Removing successive elements from the tail is equally simple:
+ *
+ *          while (tail) {
+ *              obj = listRemoveFromTail(&head, &tail);
+ *              <do something with obj>
+ *          }
+ *
+ *      When removing an arbitrary element from a list, use
+ *
+ *              obj = listRemoveElement(&head, elem);
+ *
+ *      All the listRemove*() functions hand you the object,
+ *      destroy the list cell to which it was attached, and
+ *      reset the list pointers if necessary.
+ *
+ *      Several other list operations, that do not involve
+ *      inserting or removing objects, are also provided.
+ *      The function listFindElement() locates a list pointer
+ *      by matching the object hanging on it to a given
+ *      object.  The function listFindTail() gets a handle
+ *      to the tail list ptr, allowing backwards traversals of
+ *      the list.  listGetCount() gives the number of elements
+ *      in a list.  Functions that reverse a list and concatenate
+ *      two lists are also provided.
+ *
+ *      These functions can be modified for efficiency in the
+ *      situation where there is a large amount of creation and
+ *      destruction of list cells.  If millions of cells are
+ *      made and destroyed, but a relatively small number are
+ *      around at any time, the list cells can be stored for
+ *      later re-use in a stack (see the generic stack functions
+ *      in stack.c).
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+
+/*---------------------------------------------------------------------*
+ *                    Inserting and removing elements                  *
+ *---------------------------------------------------------------------*/
+/*!
+ *  listDestroy()
+ *
+ *      Input:  &head   (<to be nulled> head of list)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This only destroys the cons cells.  Before destroying
+ *          the list, it is necessary to remove all data and set the
+ *          data pointers in each cons cell to NULL.
+ *      (2) listDestroy() will give a warning message for each data
+ *          ptr that is not NULL.
+ */
+void
+listDestroy(DLLIST  **phead)
+{
+DLLIST  *elem, *next, *head;
+
+    PROCNAME("listDestroy");
+
+    if (phead == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((head = *phead) == NULL)
+        return;
+
+    for (elem = head; elem; elem = next) {
+        if (elem->data)
+            L_WARNING("list data ptr is not null\n", procName);
+        next = elem->next;
+        LEPT_FREE(elem);
+    }
+    *phead = NULL;
+    return;
+}
+
+
+/*!
+ *  listAddToHead()
+ *
+ *      Input:  &head  (<optional> input head)
+ *              data  (void* ptr, to be added)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This makes a new cell, attaches the data, and adds the
+ *          cell to the head of the list.
+ *      (2) When consing from NULL, be sure to initialize head to NULL
+ *          before calling this function.
+ */
+l_int32
+listAddToHead(DLLIST  **phead,
+              void     *data)
+{
+DLLIST  *cell, *head;
+
+    PROCNAME("listAddToHead");
+
+    if (!phead)
+        return ERROR_INT("&head not defined", procName, 1);
+    head = *phead;
+    if (!data)
+        return ERROR_INT("data not defined", procName, 1);
+
+    if ((cell = (DLLIST *)LEPT_CALLOC(1, sizeof(DLLIST))) == NULL)
+        return ERROR_INT("cell not made", procName, 1);
+    cell->data = data;
+
+    if (!head) {  /* start the list; initialize the ptrs */
+        cell->prev = NULL;
+        cell->next = NULL;
+    } else {
+        cell->prev = NULL;
+        cell->next = head;
+        head->prev = cell;
+    }
+    *phead = cell;
+    return 0;
+}
+
+
+/*!
+ *  listAddToTail()
+ *
+ *      Input:  &head  (<may be updated>, head can be null)
+ *              &tail  (<updated>, tail can be null)
+ *              data  (void* ptr, to be hung on tail cons cell)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This makes a new cell, attaches the data, and adds the
+ *          cell to the tail of the list.
+ *      (2) &head is input to allow the list to be "cons'd" up from NULL.
+ *      (3) &tail is input to allow the tail to be updated
+ *          for efficient sequential operation with this function.
+ *      (4) We assume that if *phead and/or *ptail are not NULL,
+ *          then they are valid addresses.  Therefore:
+ *           (a) when consing from NULL, be sure to initialize both
+ *               head and tail to NULL.
+ *           (b) when tail == NULL for an existing list, the tail
+ *               will be found and updated.
+ */
+l_int32
+listAddToTail(DLLIST  **phead,
+              DLLIST  **ptail,
+              void     *data)
+{
+DLLIST  *cell, *head, *tail;
+
+    PROCNAME("listAddToTail");
+
+    if (!phead)
+        return ERROR_INT("&head not defined", procName, 1);
+    head = *phead;
+    if (!ptail)
+        return ERROR_INT("&tail not defined", procName, 1);
+    if (!data)
+        return ERROR_INT("data not defined", procName, 1);
+
+    if ((cell = (DLLIST *)LEPT_CALLOC(1, sizeof(DLLIST))) == NULL)
+        return ERROR_INT("cell not made", procName, 1);
+    cell->data = data;
+
+    if (!head) {  /*   Start the list and initialize the ptrs.  *ptail
+                   *   should also have been initialized to NULL */
+        cell->prev = NULL;
+        cell->next = NULL;
+        *phead = cell;
+        *ptail = cell;
+    } else {
+        if ((tail = *ptail) == NULL)
+            tail = listFindTail(head);
+        cell->prev = tail;
+        cell->next = NULL;
+        tail->next = cell;
+        *ptail = cell;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  listInsertBefore()
+ *
+ *      Input:  &head  (<optional> input head)
+ *               elem  (list element to be inserted in front of;
+ *                      must be null if head is null)
+ *               data  (void*  address, to be added)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This can be called on a null list, in which case both
+ *          head and elem must be null.
+ *      (2) If you are searching through a list, looking for a condition
+ *          to add an element, you can do something like this:
+ *            L_BEGIN_LIST_FORWARD(head, elem)
+ *                <identify an elem to insert before>
+ *                listInsertBefore(&head, elem, data);
+ *            L_END_LIST
+ *
+ */
+l_int32
+listInsertBefore(DLLIST  **phead,
+                 DLLIST   *elem,
+                 void     *data)
+{
+DLLIST  *cell, *head;
+
+    PROCNAME("listInsertBefore");
+
+    if (!phead)
+        return ERROR_INT("&head not defined", procName, 1);
+    head = *phead;
+    if (!data)
+        return ERROR_INT("data not defined", procName, 1);
+    if ((!head && elem) || (head && !elem))
+        return ERROR_INT("head and elem not consistent", procName, 1);
+
+        /* New cell to insert */
+    if ((cell = (DLLIST *)LEPT_CALLOC(1, sizeof(DLLIST))) == NULL)
+        return ERROR_INT("cell not made", procName, 1);
+    cell->data = data;
+
+    if (!head) {  /* start the list; initialize the ptrs */
+        cell->prev = NULL;
+        cell->next = NULL;
+        *phead = cell;
+    } else if (head == elem) {  /* insert before head of list */
+        cell->prev = NULL;
+        cell->next = head;
+        head->prev = cell;
+        *phead = cell;
+    } else  {   /* insert before elem and after head of list */
+        cell->prev = elem->prev;
+        cell->next = elem;
+        elem->prev->next = cell;
+        elem->prev = cell;
+    }
+    return 0;
+}
+
+
+/*!
+ *  listInsertAfter()
+ *
+ *      Input:  &head  (<optional> input head)
+ *               elem  (list element to be inserted after;
+ *                      must be null if head is null)
+ *               data  (void*  ptr, to be added)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This can be called on a null list, in which case both
+ *          head and elem must be null.  The head is included
+ *          in the call to allow "consing" up from NULL.
+ *      (2) If you are searching through a list, looking for a condition
+ *          to add an element, you can do something like this:
+ *            L_BEGIN_LIST_FORWARD(head, elem)
+ *                <identify an elem to insert after>
+ *                listInsertAfter(&head, elem, data);
+ *            L_END_LIST
+ */
+l_int32
+listInsertAfter(DLLIST  **phead,
+                DLLIST   *elem,
+                void     *data)
+{
+DLLIST  *cell, *head;
+
+    PROCNAME("listInsertAfter");
+
+    if (!phead)
+        return ERROR_INT("&head not defined", procName, 1);
+    head = *phead;
+    if (!data)
+        return ERROR_INT("data not defined", procName, 1);
+    if ((!head && elem) || (head && !elem))
+        return ERROR_INT("head and elem not consistent", procName, 1);
+
+        /* New cell to insert */
+    if ((cell = (DLLIST *)LEPT_CALLOC(1, sizeof(DLLIST))) == NULL)
+        return ERROR_INT("cell not made", procName, 1);
+    cell->data = data;
+
+    if (!head) {  /* start the list; initialize the ptrs */
+        cell->prev = NULL;
+        cell->next = NULL;
+        *phead = cell;
+    } else if (elem->next == NULL) {  /* insert after last */
+        cell->prev = elem;
+        cell->next = NULL;
+        elem->next = cell;
+    } else  {  /* insert after elem and before the end */
+        cell->prev = elem;
+        cell->next = elem->next;
+        elem->next->prev = cell;
+        elem->next = cell;
+    }
+    return 0;
+}
+
+
+/*!
+ *  listRemoveElement()
+ *
+ *      Input:  &head (<can be changed> input head)
+ *              elem (list element to be removed)
+ *      Return: data  (void* struct on cell)
+ *
+ *  Notes:
+ *      (1) in ANSI C, it is not necessary to cast return to actual type; e.g.,
+ *             pix = listRemoveElement(&head, elem);
+ *          but in ANSI C++, it is necessary to do the cast:
+ *             pix = (Pix *)listRemoveElement(&head, elem);
+ */
+void *
+listRemoveElement(DLLIST  **phead,
+                  DLLIST   *elem)
+{
+void    *data;
+DLLIST  *head;
+
+    PROCNAME("listRemoveElement");
+
+    if (!phead)
+        return (void *)ERROR_PTR("&head not defined", procName, NULL);
+    head = *phead;
+    if (!head)
+        return (void *)ERROR_PTR("head not defined", procName, NULL);
+    if (!elem)
+        return (void *)ERROR_PTR("elem not defined", procName, NULL);
+
+    data = elem->data;
+
+    if (head->next == NULL) {  /* only one */
+        if (elem != head)
+            return (void *)ERROR_PTR("elem must be head", procName, NULL);
+        *phead = NULL;
+    } else if (head == elem) {   /* first one */
+        elem->next->prev = NULL;
+        *phead = elem->next;
+    } else if (elem->next == NULL) {   /* last one */
+        elem->prev->next = NULL;
+    } else {  /* neither the first nor the last one */
+        elem->next->prev = elem->prev;
+        elem->prev->next = elem->next;
+    }
+
+    LEPT_FREE(elem);
+    return data;
+}
+
+
+/*!
+ *  listRemoveFromHead()
+ *
+ *      Input:  &head (<to be updated> head of list)
+ *      Return: data  (void* struct on cell), or null on error
+ *
+ *  Notes:
+ *      (1) in ANSI C, it is not necessary to cast return to actual type; e.g.,
+ *            pix = listRemoveFromHead(&head);
+ *          but in ANSI C++, it is necessary to do the cast; e.g.,
+ *            pix = (Pix *)listRemoveFromHead(&head);
+ */
+void *
+listRemoveFromHead(DLLIST  **phead)
+{
+DLLIST  *head;
+void    *data;
+
+    PROCNAME("listRemoveFromHead");
+
+    if (!phead)
+        return (void *)ERROR_PTR("&head not defined", procName, NULL);
+    if ((head = *phead) == NULL)
+        return (void *)ERROR_PTR("head not defined", procName, NULL);
+
+    if (head->next == NULL) {  /* only one */
+        *phead = NULL;
+    } else {
+        head->next->prev = NULL;
+        *phead = head->next;
+    }
+
+    data = head->data;
+    LEPT_FREE(head);
+    return data;
+}
+
+
+/*!
+ *  listRemoveFromTail()
+ *
+ *      Input:  &head (<may be changed>, head must NOT be null)
+ *              &tail (<always updated>, tail may be null)
+ *      Return: data  (void* struct on cell) or null on error
+ *
+ *  Notes:
+ *      (1) We include &head so that it can be set to NULL if
+ *          if the only element in the list is removed.
+ *      (2) The function is relying on the fact that if tail is
+ *          not NULL, then is is a valid address.  You can use
+ *          this function with tail == NULL for an existing list, in
+ *          which case  the tail is found and updated, and the
+ *          removed element is returned.
+ *      (3) In ANSI C, it is not necessary to cast return to actual type; e.g.,
+ *            pix = listRemoveFromTail(&head, &tail);
+ *          but in ANSI C++, it is necessary to do the cast; e.g.,
+ *            pix = (Pix *)listRemoveFromTail(&head, &tail);
+ */
+void *
+listRemoveFromTail(DLLIST  **phead,
+                   DLLIST  **ptail)
+{
+DLLIST  *head, *tail;
+void    *data;
+
+    PROCNAME("listRemoveFromTail");
+
+    if (!phead)
+        return (void *)ERROR_PTR("&head not defined", procName, NULL);
+    if ((head = *phead) == NULL)
+        return (void *)ERROR_PTR("head not defined", procName, NULL);
+    if (!ptail)
+        return (void *)ERROR_PTR("&tail not defined", procName, NULL);
+    if ((tail = *ptail) == NULL)
+        tail = listFindTail(head);
+
+    if (head->next == NULL) { /* only one */
+        *phead = NULL;
+        *ptail = NULL;
+    } else {
+        tail->prev->next = NULL;
+        *ptail = tail->prev;
+    }
+
+    data = tail->data;
+    LEPT_FREE(tail);
+    return data;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                         Other list operations                       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  listFindElement()
+ *
+ *      Input:  head  (list head)
+ *              data  (void*  address, to be searched for)
+ *      Return: cell  (the containing cell, or null if not found or on error)
+ *
+ *  Notes:
+ *      (1) This returns a ptr to the cell, which is still embedded in
+ *          the list.
+ *      (2) This handle and the attached data have not been copied or
+ *          reference counted, so they must not be destroyed.  This
+ *          violates our basic rule that every handle returned from a
+ *          function is owned by that function and must be destroyed,
+ *          but if rules aren't there to be broken, why have them?
+ */
+DLLIST *
+listFindElement(DLLIST  *head,
+                void    *data)
+{
+DLLIST  *cell;
+
+    PROCNAME("listFindElement");
+
+    if (!head)
+        return (DLLIST *)ERROR_PTR("head not defined", procName, NULL);
+    if (!data)
+        return (DLLIST *)ERROR_PTR("data not defined", procName, NULL);
+
+    for (cell = head; cell; cell = cell->next) {
+        if (cell->data == data)
+            return cell;
+    }
+
+    return NULL;
+}
+
+
+/*!
+ *  listFindTail()
+ *
+ *      Input:  head
+ *      Return: tail, or null on error
+ */
+DLLIST *
+listFindTail(DLLIST  *head)
+{
+DLLIST  *cell;
+
+    PROCNAME("listFindTail");
+
+    if (!head)
+        return (DLLIST *)ERROR_PTR("head not defined", procName, NULL);
+
+    for (cell = head; cell; cell = cell->next) {
+        if (cell->next == NULL)
+            return cell;
+    }
+
+    return (DLLIST *)ERROR_PTR("tail not found !!", procName, NULL);
+}
+
+
+/*!
+ *  listGetCount()
+ *
+ *      Input:  head  (of list)
+ *      Return: number of elements; 0 if no list or on error
+ */
+l_int32
+listGetCount(DLLIST  *head)
+{
+l_int32  count;
+DLLIST  *elem;
+
+    PROCNAME("listGetCount");
+
+    if (!head)
+        return ERROR_INT("head not defined", procName, 0);
+
+    count = 0;
+    for (elem = head; elem; elem = elem->next)
+        count++;
+
+    return count;
+}
+
+
+/*!
+ *  listReverse()
+ *
+ *      Input:  &head  (<may be changed> list head)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This reverses the list in-place.
+ */
+l_int32
+listReverse(DLLIST  **phead)
+{
+void    *obj;  /* whatever */
+DLLIST  *head, *rhead;
+
+    PROCNAME("listReverse");
+
+    if (!phead)
+        return ERROR_INT("&head not defined", procName, 1);
+    if ((head = *phead) == NULL)
+        return ERROR_INT("head not defined", procName, 1);
+
+    rhead = NULL;
+    while (head) {
+        obj = listRemoveFromHead(&head);
+        listAddToHead(&rhead, obj);
+    }
+
+    *phead = rhead;
+    return 0;
+}
+
+
+/*!
+ *  listJoin()
+ *
+ *      Input:  &head1  (<may be changed> head of first list)
+ *              &head2  (<to be nulled> head of second list)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The concatenated list is returned with head1 as the new head.
+ *      (2) Both input ptrs must exist, though either can have the value NULL.
+ */
+l_int32
+listJoin(DLLIST  **phead1,
+         DLLIST  **phead2)
+{
+void    *obj;
+DLLIST  *head1, *head2, *tail1;
+
+    PROCNAME("listJoin");
+
+    if (!phead1)
+        return ERROR_INT("&head1 not defined", procName, 1);
+    if (!phead2)
+        return ERROR_INT("&head2 not defined", procName, 1);
+
+        /* If no list2, just return list1 unchanged */
+    if ((head2 = *phead2) == NULL)
+        return 0;
+
+        /* If no list1, just return list2 */
+    if ((head1 = *phead1) == NULL) {
+        *phead1 = head2;
+        *phead2 = NULL;
+        return 0;
+    }
+
+        /* General case for concatenation into list 1 */
+    tail1 = listFindTail(head1);
+    while (head2) {
+        obj = listRemoveFromHead(&head2);
+        listAddToTail(&head1, &tail1, obj);
+    }
+    *phead2 = NULL;
+    return 0;
+}
diff --git a/src/list.h b/src/list.h
new file mode 100644 (file)
index 0000000..375e44b
--- /dev/null
@@ -0,0 +1,87 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+#ifndef  LEPTONICA_LIST_H
+#define  LEPTONICA_LIST_H
+
+/*
+ *   list.h
+ *
+ *       Cell for double-linked lists
+ *
+ *       This allows composition of a list of cells with 
+ *           prev, next and data pointers.  Generic data
+ *           structures hang on the list cell data pointers.
+ *
+ *       The list is not circular because that would add much
+ *           complexity in traversing the list under general
+ *           conditions where list cells can be added and removed.
+ *           The only disadvantage of not having the head point to
+ *           the last cell is that the list must be traversed to
+ *           find its tail.  However, this traversal is fast, and
+ *           the listRemoveFromTail() function updates the tail
+ *           so there is no searching overhead with repeated use.
+ *
+ *       The list macros are used to run through a list, and their
+ *       use is encouraged.  They are invoked, e.g., as
+ *
+ *             DLLIST  *head, *elem;
+ *             ...
+ *             L_BEGIN_LIST_FORWARD(head, elem)
+ *                 <do something with elem and/or elem->data >
+ *             L_END_LIST
+ *
+ */
+
+struct DoubleLinkedList
+{
+    struct DoubleLinkedList    *prev;
+    struct DoubleLinkedList    *next;
+    void                       *data;
+};
+typedef struct DoubleLinkedList    DLLIST;
+
+
+    /*  Simple list traverse macros */
+#define L_BEGIN_LIST_FORWARD(head, element) \
+        { \
+        DLLIST   *_leptvar_nextelem_; \
+        for ((element) = (head); (element); (element) = _leptvar_nextelem_) { \
+            _leptvar_nextelem_ = (element)->next;
+
+
+#define L_BEGIN_LIST_REVERSE(tail, element) \
+        { \
+        DLLIST   *_leptvar_prevelem_; \
+        for ((element) = (tail); (element); (element) = _leptvar_prevelem_) { \
+            _leptvar_prevelem_ = (element)->prev;
+
+
+#define L_END_LIST    }}
+
+
+#endif  /* LEPTONICA_LIST_H */
diff --git a/src/makefile.static b/src/makefile.static
new file mode 100644 (file)
index 0000000..5d1f419
--- /dev/null
@@ -0,0 +1,395 @@
+#/*====================================================================*
+# -  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.
+# *====================================================================*/
+
+#   makefile  (for linux)
+#
+#   Hand-built -- editable -- simple -- makefile
+#
+#   For a nodebug version:             make
+#   For a debug version:               make DEBUG=yes debug
+#   For a shared library version:      make SHARED=yes shared
+#   For all versions:                  make all
+#   With nonstandard header directories
+#                    make EXTRAINCLUDES="-I<nonstandard-incl-dir>"
+#
+#   To remove all writes to stderr:   add -DNO_CONSOLE_IO to compiler line
+#
+#   To remove object files in src: make clean
+#   To remove object files and executables in prog: make clean
+#
+#   Customization for endianness of machine hardware:
+#   When using the gnu compiler, endianness is automatically
+#   determined and set properly.  Otherwise, set the $CPPFLAGS variable:
+#       On little-endian machines (e.g., i386, x86-64):
+#          CPPFLAGS = $(INCLUDES) -DL_LITTLE_ENDIAN
+#       On big-endian machines (e.g., Mac Power PC, Sun Sparc):
+#          CPPFLAGS = $(INCLUDES) -DL_BIG_ENDIAN
+#
+#   Customization for I/O with external libraries (jpeg, png, tiff, webp, gif):
+#   Set flags in environ.h.  The default is to have libjpeg, libpng,
+#   libtiff and libz, but not libgif.
+#
+#   Customization for non-POSIX-compliant GNU functions
+#   fmemopen() and open_memstream().
+#   The default is not to use, because they only work on linux.
+#   To use these, #define HAVE_FMEMOPEN to 1 in environ.h.
+#
+#   Customization for Cygwin:
+#   (1) Use the appropriate $CC
+#
+#   Compiling under Microsoft Visual Studio
+#   (1) Download the vs2000 package.
+#   (2) You can also substitute arrayaccess.h.vc for arrayaccess.h, to
+#       use the inline macros rather than function calls which are slower.
+#
+#   To generate function prototypes, you need a program called
+#       xtractprotos.  Build it with this command:
+#          make xtractprotos
+#       Then use it with 'make allheaders'
+
+# Tools used by the Makefile
+RM             = rm -f
+TEST           = test
+MKDIR          = mkdir -p
+LIBRARIAN      = ar cq
+RANLIB         = ranlib
+
+# Libraries are built into a binary tree determined by the environmental
+# variable  BINARY_BASE_DIR
+ifndef BINARY_BASE_DIR
+BINARY_BASE_DIR = ..
+endif
+
+BASE_OBJ =     $(BINARY_BASE_DIR)/obj
+OBJ_NODEBUG =  $(BINARY_BASE_DIR)/obj/nodebug
+OBJ_DEBUG =    $(BINARY_BASE_DIR)/obj/debug
+OBJ_SHARED =   $(BINARY_BASE_DIR)/obj/shared
+
+BASE_LIB =     $(BINARY_BASE_DIR)/lib
+LIB_NODEBUG =  $(BINARY_BASE_DIR)/lib/nodebug
+LIB_DEBUG =    $(BINARY_BASE_DIR)/lib/debug
+LIB_SHARED =   $(BINARY_BASE_DIR)/lib/shared
+
+
+#   Include files
+INCLUDES =     -I./ $(EXTRAINCLUDES)
+PROTOTYPE_DIR =        .
+
+#   Which flags to use?
+#     - gcc 4.3.3 enforces checking return values of built-in C functions
+#       such as fgets().  This should compile with the -Werror flag,
+#       but it is turned off in the distribution (just in case...).
+#     - use g++ to apply stricter rules.  Libraries made with g++ may not
+#       link to programs compiled with gcc (depends on the glibc version).
+#     - use -Wunused to identify unused varables
+#     - use -DNO_CONSOLE_IO to remove all L_INFO, L_WARNING, L_ERROR and
+#        ERROR_* logging, and to remove all DEBUG information dependent
+#        on whether or not NO_CONSOLE_IO has been defined.
+#     - remove -fPIC for Cygwin
+CC =           gcc -ansi -D_BSD_SOURCE -DANSI -fPIC
+#CC =          gcc -ansi -D_BSD_SOURCE -DANSI -Werror -fPIC
+#CC =          g++ -D_BSD_SOURCE -fPIC
+#CC =          g++ -Werror -D_BSD_SOURCE -fPIC
+#CC =          g++ -Wunused -D_BSD_SOURCE -fPIC
+#CC =          gcc -ansi -DNO_CONSOLE_IO -D_BSD_SOURCE -DANSI -fPIC
+#CC =          gcc -ansi -D_BSD_SOURCE -DANSI
+
+#   Test for processor endianness (valid with gnu make only)
+ENDIANNESS := $(shell $(CC) -o endiantest endiantest.c; ./endiantest; rm -f endiantest)
+
+# Conditional compilation (depending on processor endianness)
+CPPFLAGS =      $(INCLUDES) -D$(ENDIANNESS)
+#CPPFLAGS =    $(INCLUDES) -DL_LITTLE_ENDIAN
+#CPPFLAGS =    $(INCLUDES) -DL_BIG_ENDIAN
+
+#   Shared library linker options
+SONAME_OPTION = -Wl,-h,
+
+ifdef  SHARED
+    OPTIMIZE =         -O2 -fPIC
+else
+    ifdef  DEBUG
+       OPTIMIZE =      -g
+    else
+       OPTIMIZE =      -O2
+    endif
+endif
+
+
+OPTIONS =
+CFLAGS =               $(OPTIMIZE) $(OPTIONS)
+LIBRARIAN_SHARED =     gcc -shared
+
+#  Libraries differing only in their minor revision numbers
+#  are required to have the same interface.  By using
+#  "-h" in the ld, the "soname" is <libname>.X, where X is
+#  the major revision number.
+#  Links are created among the files <libname>.X.Y,
+#  <libname>.X, and <libname>, where Y is the minor revision number.
+MAJOR_REV = 1
+MINOR_REV = 73
+
+#########################################################
+
+# Libraries
+
+LEPTLIB =              liblept.a
+LEPTLIB_SHARED =       liblept.so
+
+#########################################################
+
+LEPTLIB_C =    adaptmap.c affine.c \
+               affinecompose.c arrayaccess.c \
+               bardecode.c baseline.c bbuffer.c \
+               bilateral.c bilinear.c binarize.c \
+               binexpand.c binreduce.c \
+               blend.c bmf.c bmpio.c bmpiostub.c \
+               bootnumgen1.c bootnumgen2.c \
+               boxbasic.c boxfunc1.c boxfunc2.c \
+               boxfunc3.c boxfunc4.c \
+               bytearray.c ccbord.c ccthin.c classapp.c \
+               colorcontent.c coloring.c \
+               colormap.c colormorph.c \
+               colorquant1.c colorquant2.c \
+               colorseg.c colorspace.c \
+               compare.c conncomp.c convertfiles.c \
+               convolve.c correlscore.c \
+               dewarp1.c dewarp2.c dewarp3.c dewarp4.c dnabasic.c \
+               dwacomb.2.c dwacomblow.2.c \
+               edge.c encoding.c enhance.c \
+               fhmtauto.c fhmtgen.1.c fhmtgenlow.1.c \
+               finditalic.c flipdetect.c fliphmtgen.c \
+               fmorphauto.c fmorphgen.1.c fmorphgenlow.1.c \
+               fpix1.c fpix2.c \
+               gifio.c gifiostub.c gplot.c graphics.c \
+               graymorph.c grayquant.c grayquantlow.c \
+               heap.c jbclass.c \
+               jp2kheader.c jp2kheaderstub.c jp2kio.c jp2kiostub.c \
+               jpegio.c jpegiostub.c kernel.c \
+               libversions.c list.c map.c maze.c \
+               morph.c morphapp.c morphdwa.c morphseq.c \
+               numabasic.c numafunc1.c numafunc2.c \
+               pageseg.c paintcmap.c \
+               parseprotos.c partition.c \
+               pdfio1.c pdfio1stub.c pdfio2.c pdfio2stub.c \
+               pix1.c pix2.c pix3.c pix4.c pix5.c \
+               pixabasic.c pixacc.c \
+               pixafunc1.c pixafunc2.c \
+               pixalloc.c pixarith.c \
+               pixcomp.c pixconv.c pixlabel.c pixtiling.c \
+               pngio.c pngiostub.c \
+               pnmio.c pnmiostub.c \
+               projective.c \
+               psio1.c psio1stub.c psio2.c psio2stub.c \
+               ptabasic.c ptafunc1.c \
+               ptra.c quadtree.c queue.c rank.c rbtree.c \
+               readbarcode.c readfile.c \
+               recogbasic.c recogdid.c recogident.c recogtrain.c \
+               regutils.c \
+               rop.c ropiplow.c roplow.c \
+               rotate.c rotateam.c rotateamlow.c \
+               rotateorth.c rotateshear.c \
+               runlength.c sarray.c \
+               scale.c scalelow.c \
+               seedfill.c seedfilllow.c \
+               sel1.c sel2.c selgen.c \
+               shear.c skew.c spixio.c \
+               stack.c stringcode.c sudoku.c \
+               textops.c tiffio.c tiffiostub.c \
+               utils.c viewfiles.c \
+               warper.c watershed.c \
+               webpio.c webpiostub.c writefile.c \
+               zlibmem.c zlibmemstub.c
+
+LEPTLIB_H =    allheaders.h alltypes.h \
+               array.h arrayaccess.h bbuffer.h \
+               bmf.h bmfdata.h bmp.h ccbord.h \
+               dewarp.h environ.h gplot.h \
+               heap.h imageio.h \
+               jbclass.h list.h morph.h \
+               pix.h ptra.h queue.h rbtree.h \
+               readbarcode.h recog.h regutils.h \
+               stack.h stringcode.h sudoku.h watershed.h
+
+##################################################################
+
+#  Main targets
+
+nodebug: dirs $(LEPTLIB:%=$(LIB_NODEBUG)/%)
+
+all:
+       make -f makefile TARGET=$(TARGET) nodebug
+       make -f makefile TARGET=$(TARGET) DEBUG=true debug
+       make -f makefile TARGET=$(TARGET) SHARED=true shared
+
+DEBUG_LIBS = $(LEPTLIB:%=$(LIB_DEBUG)/%)
+SHARED_LIBS = $(LEPTLIB_SHARED:%=$(LIB_SHARED)/%)
+debug: dirs $(DEBUG_LIBS)
+shared:        dirs $(SHARED_LIBS)
+
+##################################################################
+
+#  Proto targets
+
+#  Note that both of the targets below can be generated by xtractprotos
+#  (a) without requiring the existence of leptprotos.h and (b) with
+#  an empty allheaders.h.  Both generate a new allheaders.h, and
+#  'make allprotos' additionally generates leptprotos.h
+#
+#  In the past we generated leptprotos.h that held the function prototypes,
+#  and included it in allheaders.h.  We now insert the function prototypes
+#  directly in allheaders.h, and do not generate a separate prototype
+#  file leptprotos.h.
+allheaders:     $(LEPTLIB_C)
+       @$(TEST) -f xtractprotos || echo "First run 'make xtractprotos'"
+       ./xtractprotos -protos=inline -prestring=LEPT_DLL $(LEPTLIB_C)
+
+
+#  You can still generate the file leptprotos.h and have it #included
+#  in allheaders.h.  If you do this, be sure to add leptprotos.h to LEPTLIB_H.
+allprotos:      leptprotos
+leptprotos:     $(LEPTLIB_C)
+       @$(TEST) -f xtractprotos || echo "First run 'make xtractprotos'"
+       ./xtractprotos -protos=leptprotos.h -prestring=LEPT_DLL $(LEPTLIB_C)
+
+##################################################################
+
+#  xtractprotos
+
+xtractprotos:  dirs leptlib
+       cd ../prog; make xtractprotos; cp xtractprotos ../src
+
+xtractprotos.o:        xtractprotos.c
+
+##################################################################
+
+#   Rule to make optimized library
+
+$(LIB_NODEBUG)/%.a:
+               $(RM) $@
+               $(LIBRARIAN) $@ $<
+               $(RANLIB) $@
+
+#   Rule to make debuggable library
+
+$(LIB_DEBUG)/%.a:
+               $(RM) $@
+               $(LIBRARIAN) $@ $<
+               $(RANLIB) $@
+
+#   Rule to make shared library
+
+$(LIB_SHARED)/%.so:
+               $(RM) $@
+               $(LIBRARIAN_SHARED) $(SONAME_OPTION)$(notdir $@).$(MAJOR_REV) -o $@ $<
+       mv $@ $@.$(MAJOR_REV).$(MINOR_REV)
+       cd $(LIB_SHARED); rm $(notdir $@).$(MAJOR_REV); \
+         ln -s $(notdir $@).$(MAJOR_REV).$(MINOR_REV) $(notdir $@).$(MAJOR_REV)
+       cd $(LIB_SHARED); rm $(notdir $@); \
+         ln -s $(notdir $@).$(MAJOR_REV) $(notdir $@)
+
+##################################################################
+
+#   No-debug library dependencies and rules
+
+leptlib:       $(LIB_NODEBUG)/$(LEPTLIB)
+$(LIB_NODEBUG)/$(LEPTLIB):     $(LEPTLIB_C:%.c=$(OBJ_NODEBUG)/%.o)
+               $(RM) $@
+               $(LIBRARIAN) $@ $(LEPTLIB_C:%.c=$(OBJ_NODEBUG)/%.o)
+               $(RANLIB) $@
+
+#   Debug library dependencies and rules
+
+leptlibd:      $(LIB_DEBUG)/$(LEPTLIB)
+$(LIB_DEBUG)/$(LEPTLIB):       $(LEPTLIB_C:%.c=$(OBJ_DEBUG)/%.o)
+               $(RM) $@
+               $(LIBRARIAN) $@ $(LEPTLIB_C:%.c=$(OBJ_DEBUG)/%.o)
+               $(RANLIB) $@
+
+#   Shared library dependencies, rules and links
+
+leptlibs:      $(LIB_SHARED)/$(LEPTLIB_SHARED)
+$(LIB_SHARED)/$(LEPTLIB_SHARED):       $(LEPTLIB_C:%.c=$(OBJ_SHARED)/%.o)
+               $(RM) $@
+               $(LIBRARIAN_SHARED) $(SONAME_OPTION)$(notdir $@).$(MAJOR_REV) -o $@ $(LEPTLIB_C:%.c=$(OBJ_SHARED)/%.o)
+       mv $@ $@.$(MAJOR_REV).$(MINOR_REV)
+       cd $(LIB_SHARED); rm $(notdir $@).$(MAJOR_REV); \
+         ln -s $(notdir $@).$(MAJOR_REV).$(MINOR_REV) $(notdir $@).$(MAJOR_REV)
+       cd $(LIB_SHARED); rm $(notdir $@); \
+         ln -s $(notdir $@).$(MAJOR_REV) $(notdir $@)
+
+#########################################################
+
+#   Rules for compiling source
+
+$(OBJ_NODEBUG)/%.o:    %.c  $(LEPTLIB_H)
+               @$(TEST) -d $(OBJ_NODEBUG) || $(MKDIR) $(OBJ_NODEBUG)
+               $(COMPILE.c) -o $@ $<
+
+$(OBJ_DEBUG)/%.o:      %.c  $(LEPTLIB_H)
+               @$(TEST) -d $(OBJ_DEBUG) || $(MKDIR) $(OBJ_DEBUG)
+               $(COMPILE.c) -o $@ $<
+
+$(OBJ_SHARED)/%.o:     %.c  $(LEPTLIB_H)
+               @$(TEST) -d $(OBJ_SHARED) || $(MKDIR) $(OBJ_SHARED)
+               $(COMPILE.c) -o $@ $<
+
+###########################################################
+
+#   Prepare a local environment
+
+dirs:
+               @$(TEST) -d $(BASE_OBJ) || $(MKDIR) $(BASE_OBJ)
+               @$(TEST) -d $(OBJ_NODEBUG) || $(MKDIR) $(OBJ_NODEBUG)
+               @$(TEST) -d $(OBJ_DEBUG) || $(MKDIR) $(OBJ_DEBUG)
+               @$(TEST) -d $(OBJ_SHARED) || $(MKDIR) $(OBJ_SHARED)
+               @$(TEST) -d $(BASE_LIB) || $(MKDIR) $(BASE_LIB)
+               @$(TEST) -d $(LIB_NODEBUG) || $(MKDIR) $(LIB_NODEBUG)
+               @$(TEST) -d $(LIB_DEBUG) || $(MKDIR) $(LIB_DEBUG)
+               @$(TEST) -d $(LIB_SHARED) || $(MKDIR) $(LIB_SHARED)
+
+
+###########################################################
+
+clean:         
+               $(RM) $(OBJ_NODEBUG)/*.o $(OBJ_DEBUG)/*.o \
+                       $(OBJ_SHARED)/*.o \
+                       $(LIB_NODEBUG)/*.a $(LIB_DEBUG)/*.a \
+                       $(LIB_SHARED)/*.so $(LIB_SHARED)/*.so.? \
+                       $(LIB_SHARED)/*.so.?.* \
+                       xtractprotos.o xtractprotos
+
+###########################################################
+
+depend:
+       /usr/bin/makedepend -DNO_PROTOS $(CPPFLAGS) $(LEPTLIB_C)
+
+###########################################################
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+
+
+
+
diff --git a/src/map.c b/src/map.c
new file mode 100644 (file)
index 0000000..5283256
--- /dev/null
+++ b/src/map.c
@@ -0,0 +1,259 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  map.c
+ *
+ *  This is an interface for map and set functions, based on using
+ *  red-black binary search trees.  Because these trees are sorted,
+ *  the are O(nlogn) to build.  They allow logn insertion, find
+ *  and deletion of elements.
+ *
+ *  Both the map and set are ordered by key value, with unique keys.
+ *  For the map, the elements are key/value pairs.
+ *  For the set we only store unique, ordered keys, and the value
+ *  (set to 0 in the implementation) is ignored.
+ *
+ *  The keys for the map and set can be any of the three types in the
+ *  l_rbtree_keytype enum.  The values stored can be any of the four
+ *  types in the rb_type union.
+ *
+ *  In-order forward and reverse iterators are provided for maps and sets.
+ *  To forward iterate over the map for any type of key (in this example,
+ *  uint32), extracting integer values:
+ *
+ *      L_AMAP  *m = l_amapCreate(L_UINT_TYPE);
+ *      [add elements to the map ...]
+ *      L_AMAP_NODE  *n = l_amapGetFirst(m);
+ *      while (n) {
+ *          l_int32 val = n->value.itype;
+ *          // do something ...
+ *          n = l_amapGetNext(n);
+ *      }
+ *
+ *  If the nodes are deleted during the iteration:
+ *
+ *      L_AMAP  *m = l_amapCreate(L_UINT_TYPE);
+ *      [add elements to the map ...]
+ *      L_AMAP_NODE  *n = l_amapGetFirst(m);
+ *      L_AMAP_NODE  *nn;
+ *      while (n) {
+ *          nn = l_amapGetNext(n);
+ *          l_int32 val = n->value.itype;
+ *          l_uint32 key = n->key.utype;
+ *          // do something ...
+ *          l_amapDelete(m, n->key);
+ *          n = nn;
+ *      }
+ *
+ *  See prog/maptest.c and prog/settest.c for more examples of usage.
+ *
+ *  Interface to (a) map using a general key and storing general values
+ *           L_AMAP        *l_amapCreate()
+ *           RB_TYPE       *l_amapFind()
+ *           void           l_amapInsert()
+ *           void           l_amapDelete()
+ *           void           l_amapDestroy()
+ *           L_AMAP_NODE   *l_amapGetFirst()
+ *           L_AMAP_NODE   *l_amapGetNext()
+ *           L_AMAP_NODE   *l_amapGetLast()
+ *           L_AMAP_NODE   *l_amapGetPrev()
+ *           l_int32        l_amapSize()
+ *
+ *  Interface to (a) set using a general key
+ *           L_ASET        *l_asetCreate()
+ *           RB_TYPE       *l_asetFind()
+ *           void           l_asetInsert()
+ *           void           l_asetDelete()
+ *           void           l_asetDestroy()
+ *           L_ASET_NODE   *l_asetGetFirst()
+ *           L_ASET_NODE   *l_asetGetNext()
+ *           L_ASET_NODE   *l_asetGetLast()
+ *           L_ASET_NODE   *l_asetGetPrev()
+ *           l_int32        l_asetSize()
+ */
+
+#include "allheaders.h"
+
+/* ------------------------------------------------------------- *
+ *                         Interface to Map                      *
+ * ------------------------------------------------------------- */
+L_AMAP *
+l_amapCreate(l_int32  keytype)
+{
+    PROCNAME("l_amapCreate");
+
+    if (keytype != L_INT_TYPE && keytype != L_UINT_TYPE &&
+        keytype != L_FLOAT_TYPE)
+        return (L_AMAP *)ERROR_PTR("invalid keytype", procName, NULL);
+
+    L_AMAP *m = (L_AMAP *)LEPT_CALLOC(1, sizeof(L_AMAP));
+    m->keytype = keytype;
+    return m;
+}
+
+RB_TYPE *
+l_amapFind(L_AMAP  *m,
+           RB_TYPE  key)
+{
+    return l_rbtreeLookup(m, key);
+}
+
+void
+l_amapInsert(L_AMAP  *m,
+             RB_TYPE  key,
+             RB_TYPE  value)
+{
+    return l_rbtreeInsert(m, key, value);
+}
+
+void
+l_amapDelete(L_AMAP  *m,
+             RB_TYPE  key)
+{
+    l_rbtreeDelete(m, key);
+}
+
+void
+l_amapDestroy(L_AMAP  **pm)
+{
+    l_rbtreeDestroy(pm);
+}
+
+L_AMAP_NODE *
+l_amapGetFirst(L_AMAP  *m)
+{
+    return l_rbtreeGetFirst(m);
+}
+
+L_AMAP_NODE *
+l_amapGetNext(L_AMAP_NODE  *n)
+{
+    return l_rbtreeGetNext(n);
+}
+
+L_AMAP_NODE *
+l_amapGetLast(L_AMAP  *m)
+{
+    return l_rbtreeGetLast(m);
+}
+
+L_AMAP_NODE *
+l_amapGetPrev(L_AMAP_NODE  *n)
+{
+    return l_rbtreeGetPrev(n);
+}
+
+l_int32
+l_amapSize(L_AMAP  *m)
+{
+    return l_rbtreeGetCount(m);
+}
+
+
+/* ------------------------------------------------------------- *
+ *                         Interface to Set                      *
+ * ------------------------------------------------------------- */
+L_ASET *
+l_asetCreate(l_int32  keytype)
+{
+    PROCNAME("l_asetCreate");
+
+    if (keytype != L_INT_TYPE && keytype != L_UINT_TYPE &&
+        keytype != L_FLOAT_TYPE)
+        return (L_ASET *)ERROR_PTR("invalid keytype", procName, NULL);
+
+    L_ASET *s = (L_ASET *)LEPT_CALLOC(1, sizeof(L_ASET));
+    s->keytype = keytype;
+    return s;
+}
+
+/*
+ *  l_asetFind()
+ *
+ *  This returns NULL if not found, non-null if it is.  In the latter
+ *  case, the value stored in the returned pointer has no significance.
+ */
+RB_TYPE *
+l_asetFind(L_ASET  *s,
+           RB_TYPE  key)
+{
+    return l_rbtreeLookup(s, key);
+}
+
+void
+l_asetInsert(L_ASET  *s,
+             RB_TYPE  key)
+{
+RB_TYPE  value;
+
+    value.itype = 0;  /* meaningless */
+    return l_rbtreeInsert(s, key, value);
+}
+
+void
+l_asetDelete(L_ASET  *s,
+             RB_TYPE  key)
+{
+    l_rbtreeDelete(s, key);
+}
+
+void
+l_asetDestroy(L_ASET  **ps)
+{
+    l_rbtreeDestroy(ps);
+}
+
+L_ASET_NODE *
+l_asetGetFirst(L_ASET  *s)
+{
+    return l_rbtreeGetFirst(s);
+}
+
+L_ASET_NODE *
+l_asetGetNext(L_ASET_NODE  *n)
+{
+    return l_rbtreeGetNext(n);
+}
+
+L_ASET_NODE *
+l_asetGetLast(L_ASET  *s)
+{
+    return l_rbtreeGetLast(s);
+}
+
+L_ASET_NODE *
+l_asetGetPrev(L_ASET_NODE  *n)
+{
+    return l_rbtreeGetPrev(n);
+}
+
+l_int32
+l_asetSize(L_ASET  *s)
+{
+    return l_rbtreeGetCount(s);
+}
+
diff --git a/src/maze.c b/src/maze.c
new file mode 100644 (file)
index 0000000..8720f6f
--- /dev/null
@@ -0,0 +1,1074 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  maze.c
+ *
+ *      This is a game with a pedagogical slant.  A maze is represented
+ *      by a binary image.  The ON pixels (fg) are walls.  The goal is
+ *      to navigate on OFF pixels (bg), using Manhattan steps
+ *      (N, S, E, W), between arbitrary start and end positions.
+ *      The problem is thus to find the shortest route between two points
+ *      in a binary image that are 4-connected in the bg.  This is done
+ *      with a breadth-first search, implemented with a queue.
+ *      We also use a queue of pointers to generate the maze (image).
+ *
+ *          PIX             *generateBinaryMaze()
+ *          static MAZEEL   *mazeelCreate()
+ *
+ *          PIX             *pixSearchBinaryMaze()
+ *          static l_int32   localSearchForBackground()
+ *
+ *      Generalizing a maze to a grayscale image, the search is
+ *      now for the "shortest" or least cost path, for some given
+ *      cost function.
+ *
+ *          PIX             *pixSearchGrayMaze()
+ *
+ *
+ *      Elegant method for finding largest white (or black) rectangle
+ *      in an image.
+ *
+ *          l_int32          pixFindLargestRectangle()
+ */
+
+#include <string.h>
+#ifdef _WIN32
+#include <stdlib.h>
+#include <time.h>
+#endif  /* _WIN32 */
+#include "allheaders.h"
+
+static const l_int32  MIN_MAZE_WIDTH = 50;
+static const l_int32  MIN_MAZE_HEIGHT = 50;
+
+static const l_float32  DEFAULT_WALL_PROBABILITY = 0.65;
+static const l_float32  DEFAULT_ANISOTROPY_RATIO = 0.25;
+
+enum {  /* direction from parent to newly created element */
+    START_LOC = 0,
+    DIR_NORTH = 1,
+    DIR_SOUTH = 2,
+    DIR_WEST = 3,
+    DIR_EAST = 4
+};
+
+struct MazeElement {
+    l_float32  distance;
+    l_int32    x;
+    l_int32    y;
+    l_uint32   val;  /* value of maze pixel at this location */
+    l_int32    dir;  /* direction from parent to child */
+};
+typedef struct MazeElement  MAZEEL;
+
+
+static MAZEEL *mazeelCreate(l_int32  x, l_int32  y, l_int32  dir);
+static l_int32 localSearchForBackground(PIX  *pix, l_int32  *px,
+                                        l_int32  *py, l_int32  maxrad);
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_PATH    0
+#define  DEBUG_MAZE    0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*---------------------------------------------------------------------*
+ *             Binary maze generation as cellular automaton            *
+ *---------------------------------------------------------------------*/
+/*!
+ *  generateBinaryMaze()
+ *
+ *      Input:  w, h  (size of maze)
+ *              xi, yi  (initial location)
+ *              wallps (probability that a pixel to the side is ON)
+ *              ranis (ratio of prob that pixel in forward direction
+ *                     is a wall to the probability that pixel in
+ *                     side directions is a wall)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) We have two input probability factors that determine the
+ *          density of walls and average length of straight passages.
+ *          When ranis < 1.0, you are more likely to generate a wall
+ *          to the side than going forward.  Enter 0.0 for either if
+ *          you want to use the default values.
+ *      (2) This is a type of percolation problem, and exhibits
+ *          different phases for different parameters wallps and ranis.
+ *          For larger values of these parameters, regions in the maze
+ *          are not explored because the maze generator walls them
+ *          off and cannot get through.  The boundary between the
+ *          two phases in this two-dimensional parameter space goes
+ *          near these values:
+ *                wallps       ranis
+ *                0.35         1.00
+ *                0.40         0.85
+ *                0.45         0.70
+ *                0.50         0.50
+ *                0.55         0.40
+ *                0.60         0.30
+ *                0.65         0.25
+ *                0.70         0.19
+ *                0.75         0.15
+ *                0.80         0.11
+ *      (3) Because there is a considerable amount of overhead in calling
+ *          pixGetPixel() and pixSetPixel(), this function can be sped
+ *          up with little effort using raster line pointers and the
+ *          GET_DATA* and SET_DATA* macros.
+ */
+PIX *
+generateBinaryMaze(l_int32  w,
+                   l_int32  h,
+                   l_int32  xi,
+                   l_int32  yi,
+                   l_float32  wallps,
+                   l_float32  ranis)
+{
+l_int32    x, y, dir;
+l_uint32   val;
+l_float32  frand, wallpf, testp;
+MAZEEL    *el, *elp;
+PIX       *pixd;  /* the destination maze */
+PIX       *pixm;  /* for bookkeeping, to indicate pixels already visited */
+L_QUEUE   *lq;
+
+    /* On Windows, seeding is apparently necessary to get decent mazes.
+     * Windows rand() returns a value up to 2^15 - 1, whereas unix
+     * rand() returns a value up to 2^31 - 1.  Therefore the generated
+     * mazes will differ on the two platforms. */
+#ifdef _WIN32
+    srand(28*333);
+#endif /* _WIN32 */
+
+    if (w < MIN_MAZE_WIDTH)
+        w = MIN_MAZE_WIDTH;
+    if (h < MIN_MAZE_HEIGHT)
+        h = MIN_MAZE_HEIGHT;
+    if (xi <= 0 || xi >= w)
+        xi = w / 6;
+    if (yi <= 0 || yi >= h)
+        yi = h / 5;
+    if (wallps < 0.05 || wallps > 0.95)
+        wallps = DEFAULT_WALL_PROBABILITY;
+    if (ranis < 0.05 || ranis > 1.0)
+        ranis = DEFAULT_ANISOTROPY_RATIO;
+    wallpf = wallps * ranis;
+
+#if  DEBUG_MAZE
+    fprintf(stderr, "(w, h) = (%d, %d), (xi, yi) = (%d, %d)\n", w, h, xi, yi);
+    fprintf(stderr, "Using: prob(wall) = %7.4f, anisotropy factor = %7.4f\n",
+            wallps, ranis);
+#endif  /* DEBUG_MAZE */
+
+        /* These are initialized to OFF */
+    pixd = pixCreate(w, h, 1);
+    pixm = pixCreate(w, h, 1);
+
+    lq = lqueueCreate(0);
+
+        /* Prime the queue with the first pixel; it is OFF */
+    el = mazeelCreate(xi, yi, START_LOC);
+    pixSetPixel(pixm, xi, yi, 1);  /* mark visited */
+    lqueueAdd(lq, el);
+
+        /* While we're at it ... */
+    while (lqueueGetCount(lq) > 0) {
+        elp = (MAZEEL *)lqueueRemove(lq);
+        x = elp->x;
+        y = elp->y;
+        dir = elp->dir;
+        if (x > 0) {  /* check west */
+            pixGetPixel(pixm, x - 1, y, &val);
+            if (val == 0) {  /* not yet visited */
+                pixSetPixel(pixm, x - 1, y, 1);  /* mark visited */
+                frand = (l_float32)rand() / (l_float32)RAND_MAX;
+                testp = wallps;
+                if (dir == DIR_WEST)
+                    testp = wallpf;
+                if (frand <= testp) {  /* make it a wall */
+                    pixSetPixel(pixd, x - 1, y, 1);
+                } else {  /* not a wall */
+                    el = mazeelCreate(x - 1, y, DIR_WEST);
+                    lqueueAdd(lq, el);
+                }
+            }
+        }
+        if (y > 0) {  /* check north */
+            pixGetPixel(pixm, x, y - 1, &val);
+            if (val == 0) {  /* not yet visited */
+                pixSetPixel(pixm, x, y - 1, 1);  /* mark visited */
+                frand = (l_float32)rand() / (l_float32)RAND_MAX;
+                testp = wallps;
+                if (dir == DIR_NORTH)
+                    testp = wallpf;
+                if (frand <= testp) {  /* make it a wall */
+                    pixSetPixel(pixd, x, y - 1, 1);
+                } else {  /* not a wall */
+                    el = mazeelCreate(x, y - 1, DIR_NORTH);
+                    lqueueAdd(lq, el);
+                }
+            }
+        }
+        if (x < w - 1) {  /* check east */
+            pixGetPixel(pixm, x + 1, y, &val);
+            if (val == 0) {  /* not yet visited */
+                pixSetPixel(pixm, x + 1, y, 1);  /* mark visited */
+                frand = (l_float32)rand() / (l_float32)RAND_MAX;
+                testp = wallps;
+                if (dir == DIR_EAST)
+                    testp = wallpf;
+                if (frand <= testp) {  /* make it a wall */
+                    pixSetPixel(pixd, x + 1, y, 1);
+                } else {  /* not a wall */
+                    el = mazeelCreate(x + 1, y, DIR_EAST);
+                    lqueueAdd(lq, el);
+                }
+            }
+        }
+        if (y < h - 1) {  /* check south */
+            pixGetPixel(pixm, x, y + 1, &val);
+            if (val == 0) {  /* not yet visited */
+                pixSetPixel(pixm, x, y + 1, 1);  /* mark visited */
+                frand = (l_float32)rand() / (l_float32)RAND_MAX;
+                testp = wallps;
+                if (dir == DIR_SOUTH)
+                    testp = wallpf;
+                if (frand <= testp) {  /* make it a wall */
+                    pixSetPixel(pixd, x, y + 1, 1);
+                } else {  /* not a wall */
+                    el = mazeelCreate(x, y + 1, DIR_SOUTH);
+                    lqueueAdd(lq, el);
+                }
+            }
+        }
+        LEPT_FREE(elp);
+    }
+
+    lqueueDestroy(&lq, TRUE);
+    pixDestroy(&pixm);
+    return pixd;
+}
+
+
+static MAZEEL *
+mazeelCreate(l_int32  x,
+             l_int32  y,
+             l_int32  dir)
+{
+MAZEEL *el;
+
+    el = (MAZEEL *)LEPT_CALLOC(1, sizeof(MAZEEL));
+    el->x = x;
+    el->y = y;
+    el->dir = dir;
+    return el;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                           Binary maze search                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixSearchBinaryMaze()
+ *
+ *      Input:  pixs (1 bpp, maze)
+ *              xi, yi  (beginning point; use same initial point
+ *                       that was used to generate the maze)
+ *              xf, yf  (end point, or close to it)
+ *              &ppixd (<optional return> maze with path illustrated, or
+ *                     if no path possible, the part of the maze
+ *                     that was searched)
+ *      Return: pta (shortest path), or null if either no path
+ *              exists or on error
+ *
+ *  Notes:
+ *      (1) Because of the overhead in calling pixGetPixel() and
+ *          pixSetPixel(), we have used raster line pointers and the
+ *          GET_DATA* and SET_DATA* macros for many of the pix accesses.
+ *      (2) Commentary:
+ *            The goal is to find the shortest path between beginning and
+ *          end points, without going through walls, and there are many
+ *          ways to solve this problem.
+ *            We use a queue to implement a breadth-first search.  Two auxiliary
+ *          "image" data structures can be used: one to mark the visited
+ *          pixels and one to give the direction to the parent for each
+ *          visited pixel.  The first structure is used to avoid putting
+ *          pixels on the queue more than once, and the second is used
+ *          for retracing back to the origin, like the breadcrumbs in
+ *          Hansel and Gretel.  Each pixel taken off the queue is destroyed
+ *          after it is used to locate the allowed neighbors.  In fact,
+ *          only one distance image is required, if you initialize it
+ *          to some value that signifies "not yet visited."  (We use
+ *          a binary image for marking visited pixels because it is clearer.)
+ *          This method for a simple search of a binary maze is implemented in
+ *          pixSearchBinaryMaze().
+ *            An alternative method would store the (manhattan) distance
+ *          from the start point with each pixel on the queue.  The children
+ *          of each pixel get a distance one larger than the parent.  These
+ *          values can be stored in an auxiliary distance map image
+ *          that is constructed simultaneously with the search.  Once the
+ *          end point is reached, the distance map is used to backtrack
+ *          along a minimum path.  There may be several equal length
+ *          minimum paths, any one of which can be chosen this way.
+ */
+PTA *
+pixSearchBinaryMaze(PIX     *pixs,
+                    l_int32  xi,
+                    l_int32  yi,
+                    l_int32  xf,
+                    l_int32  yf,
+                    PIX    **ppixd)
+{
+l_int32    i, j, x, y, w, h, d, found;
+l_uint32   val, rpixel, gpixel, bpixel;
+void     **lines1, **linem1, **linep8, **lined32;
+MAZEEL    *el, *elp;
+PIX       *pixd;  /* the shortest path written on the maze image */
+PIX       *pixm;  /* for bookkeeping, to indicate pixels already visited */
+PIX       *pixp;  /* for bookkeeping, to indicate direction to parent */
+L_QUEUE   *lq;
+PTA       *pta;
+
+    PROCNAME("pixSearchBinaryMaze");
+
+    if (ppixd) *ppixd = NULL;
+    if (!pixs)
+        return (PTA *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1)
+        return (PTA *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (xi <= 0 || xi >= w)
+        return (PTA *)ERROR_PTR("xi not valid", procName, NULL);
+    if (yi <= 0 || yi >= h)
+        return (PTA *)ERROR_PTR("yi not valid", procName, NULL);
+    pixGetPixel(pixs, xi, yi, &val);
+    if (val != 0)
+        return (PTA *)ERROR_PTR("(xi,yi) not bg pixel", procName, NULL);
+    pixd = NULL;
+    pta = NULL;
+
+        /* Find a bg pixel near input point (xf, yf) */
+    localSearchForBackground(pixs, &xf, &yf, 5);
+
+#if  DEBUG_MAZE
+    fprintf(stderr, "(xi, yi) = (%d, %d), (xf, yf) = (%d, %d)\n",
+            xi, yi, xf, yf);
+#endif  /* DEBUG_MAZE */
+
+    pixm = pixCreate(w, h, 1);  /* initialized to OFF */
+    pixp = pixCreate(w, h, 8);  /* direction to parent stored as enum val */
+    lines1 = pixGetLinePtrs(pixs, NULL);
+    linem1 = pixGetLinePtrs(pixm, NULL);
+    linep8 = pixGetLinePtrs(pixp, NULL);
+
+    lq = lqueueCreate(0);
+
+        /* Prime the queue with the first pixel; it is OFF */
+    el = mazeelCreate(xi, yi, 0);  /* don't need direction here */
+    pixSetPixel(pixm, xi, yi, 1);  /* mark visited */
+    lqueueAdd(lq, el);
+
+        /* Fill up the pix storing directions to parents,
+         * stopping when we hit the point (xf, yf)  */
+    found = FALSE;
+    while (lqueueGetCount(lq) > 0) {
+        elp = (MAZEEL *)lqueueRemove(lq);
+        x = elp->x;
+        y = elp->y;
+        if (x == xf && y == yf) {
+            found = TRUE;
+            LEPT_FREE(elp);
+            break;
+        }
+
+        if (x > 0) {  /* check to west */
+            val = GET_DATA_BIT(linem1[y], x - 1);
+            if (val == 0) {  /* not yet visited */
+                SET_DATA_BIT(linem1[y], x - 1);  /* mark visited */
+                val = GET_DATA_BIT(lines1[y], x - 1);
+                if (val == 0) {  /* bg, not a wall */
+                    SET_DATA_BYTE(linep8[y], x - 1, DIR_EAST);  /* parent E */
+                    el = mazeelCreate(x - 1, y, 0);
+                    lqueueAdd(lq, el);
+                }
+            }
+        }
+        if (y > 0) {  /* check north */
+            val = GET_DATA_BIT(linem1[y - 1], x);
+            if (val == 0) {  /* not yet visited */
+                SET_DATA_BIT(linem1[y - 1], x);  /* mark visited */
+                val = GET_DATA_BIT(lines1[y - 1], x);
+                if (val == 0) {  /* bg, not a wall */
+                    SET_DATA_BYTE(linep8[y - 1], x, DIR_SOUTH);  /* parent S */
+                    el = mazeelCreate(x, y - 1, 0);
+                    lqueueAdd(lq, el);
+                }
+            }
+        }
+        if (x < w - 1) {  /* check east */
+            val = GET_DATA_BIT(linem1[y], x + 1);
+            if (val == 0) {  /* not yet visited */
+                SET_DATA_BIT(linem1[y], x + 1);  /* mark visited */
+                val = GET_DATA_BIT(lines1[y], x + 1);
+                if (val == 0) {  /* bg, not a wall */
+                    SET_DATA_BYTE(linep8[y], x + 1, DIR_WEST);  /* parent W */
+                    el = mazeelCreate(x + 1, y, 0);
+                    lqueueAdd(lq, el);
+                }
+            }
+        }
+        if (y < h - 1) {  /* check south */
+            val = GET_DATA_BIT(linem1[y + 1], x);
+            if (val == 0) {  /* not yet visited */
+                SET_DATA_BIT(linem1[y + 1], x);  /* mark visited */
+                val = GET_DATA_BIT(lines1[y + 1], x);
+                if (val == 0) {  /* bg, not a wall */
+                    SET_DATA_BYTE(linep8[y + 1], x, DIR_NORTH);  /* parent N */
+                    el = mazeelCreate(x, y + 1, 0);
+                    lqueueAdd(lq, el);
+                }
+            }
+        }
+        LEPT_FREE(elp);
+    }
+
+    lqueueDestroy(&lq, TRUE);
+    pixDestroy(&pixm);
+    LEPT_FREE(linem1);
+
+    if (ppixd) {
+        pixd = pixUnpackBinary(pixs, 32, 1);
+        *ppixd = pixd;
+    }
+    composeRGBPixel(255, 0, 0, &rpixel);  /* start point */
+    composeRGBPixel(0, 255, 0, &gpixel);
+    composeRGBPixel(0, 0, 255, &bpixel);  /* end point */
+
+    if (found) {
+        L_INFO(" Path found\n", procName);
+        pta = ptaCreate(0);
+        x = xf;
+        y = yf;
+        while (1) {
+            ptaAddPt(pta, x, y);
+            if (x == xi && y == yi)
+                break;
+            if (pixd)  /* write 'gpixel' onto the path */
+                pixSetPixel(pixd, x, y, gpixel);
+            pixGetPixel(pixp, x, y, &val);
+            if (val == DIR_NORTH)
+                y--;
+            else if (val == DIR_SOUTH)
+                y++;
+            else if (val == DIR_EAST)
+                x++;
+            else if (val == DIR_WEST)
+                x--;
+        }
+    } else {
+        L_INFO(" No path found\n", procName);
+        if (pixd) {  /* paint all visited locations */
+            lined32 = pixGetLinePtrs(pixd, NULL);
+            for (i = 0; i < h; i++) {
+                for (j = 0; j < w; j++) {
+                    val = GET_DATA_BYTE(linep8[i], j);
+                    if (val != 0 && pixd)
+                        SET_DATA_FOUR_BYTES(lined32[i], j, gpixel);
+                }
+            }
+            LEPT_FREE(lined32);
+        }
+    }
+    if (pixd) {
+        pixSetPixel(pixd, xi, yi, rpixel);
+        pixSetPixel(pixd, xf, yf, bpixel);
+    }
+
+    pixDestroy(&pixp);
+    LEPT_FREE(lines1);
+    LEPT_FREE(linep8);
+    return pta;
+}
+
+
+/*!
+ *  localSearchForBackground()
+ *
+ *      Input:  &x, &y (starting position for search; return found position)
+ *              maxrad (max distance to search from starting location)
+ *      Return: 0 if bg pixel found; 1 if not found
+ */
+static l_int32
+localSearchForBackground(PIX  *pix,
+                         l_int32  *px,
+                         l_int32  *py,
+                         l_int32  maxrad)
+{
+l_int32   x, y, w, h, r, i, j;
+l_uint32  val;
+
+    x = *px;
+    y = *py;
+    pixGetPixel(pix, x, y, &val);
+    if (val == 0) return 0;
+
+        /* For each value of r, restrict the search to the boundary
+         * pixels in a square centered on (x,y), clipping to the
+         * image boundaries if necessary.  */
+    pixGetDimensions(pix, &w, &h, NULL);
+    for (r = 1; r < maxrad; r++) {
+        for (i = -r; i <= r; i++) {
+            if (y + i < 0 || y + i >= h)
+                continue;
+            for (j = -r; j <= r; j++) {
+                if (x + j < 0 || x + j >= w)
+                    continue;
+                if (L_ABS(i) != r && L_ABS(j) != r)  /* not on "r ring" */
+                    continue;
+                pixGetPixel(pix, x + j, y + i, &val);
+                if (val == 0) {
+                    *px = x + j;
+                    *py = y + i;
+                    return 0;
+                }
+            }
+        }
+    }
+    return 1;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                            Gray maze search                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixSearchGrayMaze()
+ *
+ *      Input:  pixs (1 bpp, maze)
+ *              xi, yi  (beginning point; use same initial point
+ *                       that was used to generate the maze)
+ *              xf, yf  (end point, or close to it)
+ *              &ppixd (<optional return> maze with path illustrated, or
+ *                     if no path possible, the part of the maze
+ *                     that was searched)
+ *      Return: pta (shortest path), or null if either no path
+ *              exists or on error
+ *
+ *  Commentary:
+ *      Consider first a slight generalization of the binary maze
+ *      search problem.  Suppose that you can go through walls,
+ *      but the cost is higher (say, an increment of 3 to go into
+ *      a wall pixel rather than 1)?  You're still trying to find
+ *      the shortest path.  One way to do this is with an ordered
+ *      queue, and a simple way to visualize an ordered queue is as
+ *      a set of stacks, each stack being marked with the distance
+ *      of each pixel in the stack from the start.  We place the
+ *      start pixel in stack 0, pop it, and process its 4 children.
+ *      Each pixel is given a distance that is incremented from that
+ *      of its parent (0 in this case), depending on if it is a wall
+ *      pixel or not.  That value may be recorded on a distance map,
+ *      according to the algorithm below.  For children of the first
+ *      pixel, those not on a wall go in stack 1, and wall
+ *      children go in stack 3.  Stack 0 being emptied, the process
+ *      then continues with pixels being popped from stack 1.
+ *      Here is the algorithm for each child pixel.  The pixel's
+ *      distance value, were it to be placed on a stack, is compared
+ *      with the value for it that is on the distance map.  There
+ *      are three possible cases:
+ *         (1) If the pixel has not yet been registered, it is pushed
+ *             on its stack and the distance is written to the map.
+ *         (2) If it has previously been registered with a higher distance,
+ *             the distance on the map is relaxed to that of the
+ *             current pixel, which is then placed on its stack.
+ *         (3) If it has previously been registered with an equal
+ *             or lower value, the pixel is discarded.
+ *      The pixels are popped and processed successively from
+ *      stack 1, and when stack 1 is empty, popping starts on stack 2.
+ *      This continues until the destination pixel is popped off
+ *      a stack.   The minimum path is then derived from the distance map,
+ *      going back from the end point as before.  This is just Dijkstra's
+ *      algorithm for a directed graph; here, the underlying graph
+ *      (consisting of the pixels and four edges connecting each pixel
+ *      to its 4-neighbor) is a special case of a directed graph, where
+ *      each edge is bi-directional.  The implementation of this generalized
+ *      maze search is left as an exercise to the reader.
+ *
+ *      Let's generalize a bit further.  Suppose the "maze" is just
+ *      a grayscale image -- think of it as an elevation map.  The cost
+ *      of moving on this surface depends on the height, or the gradient,
+ *      or whatever you want.  All that is required is that the cost
+ *      is specified and non-negative on each link between adjacent
+ *      pixels.  Now the problem becomes: find the least cost path
+ *      moving on this surface between two specified end points.
+ *      For example, if the cost across an edge between two pixels
+ *      depends on the "gradient", you can use:
+ *           cost = 1 + L_ABS(deltaV)
+ *      where deltaV is the difference in value between two adjacent
+ *      pixels.  If the costs are all integers, we can still use an array
+ *      of stacks to avoid ordering the queue (e.g., by using a heap sort.)
+ *      This is a neat problem, because you don't even have to build a
+ *      maze -- you can can use it on any grayscale image!
+ *
+ *      Rather than using an array of stacks, a more practical
+ *      approach is to implement with a priority queue, which is
+ *      a queue that is sorted so that the elements with the largest
+ *      (or smallest) key values always come off first.  The
+ *      priority queue is efficiently implemented as a heap, and
+ *      this is how we do it.  Suppose you run the algorithm
+ *      using a priority queue, doing the bookkeeping with an
+ *      auxiliary image data structure that saves the distance of
+ *      each pixel put on the queue as before, according to the method
+ *      described above.  We implement it as a 2-way choice by
+ *      initializing the distance array to a large value and putting
+ *      a pixel on the queue if its distance is less than the value
+ *      found on the array.  When you finally pop the end pixel from
+ *      the queue, you're done, and you can trace the path backward,
+ *      either always going downhill or using an auxiliary image to
+ *      give you the direction to go at each step.  This is implemented
+ *      here in searchGrayMaze().
+ *
+ *      Do we really have to use a sorted queue?  Can we solve this
+ *      generalized maze with an unsorted queue of pixels?  (Or even
+ *      an unsorted stack, doing a depth-first search (DFS)?)
+ *      Consider a different algorithm for this generalized maze, where
+ *      we travel again breadth first, but this time use a single,
+ *      unsorted queue.  An auxiliary image is used as before to
+ *      store the distances and to determine if pixels get pushed
+ *      on the stack or dropped.  As before, we must allow pixels
+ *      to be revisited, with relaxation of the distance if a shorter
+ *      path arrives later.  As a result, we will in general have
+ *      multiple instances of the same pixel on the stack with different
+ *      distances.  However, because the queue is not ordered, some of
+ *      these pixels will be popped when another instance with a lower
+ *      distance is still on the stack.  Here, we're just popping them
+ *      in the order they go on, rather than setting up a priority
+ *      based on minimum distance.  Thus, unlike the priority queue,
+ *      when a pixel is popped we have to check the distance map to
+ *      see if a pixel with a lower distance has been put on the queue,
+ *      and, if so, we discard the pixel we just popped.  So the
+ *      "while" loop looks like this:
+ *        - pop a pixel from the queue
+ *        - check its distance against the distance stored in the
+ *          distance map; if larger, discard
+ *        - otherwise, for each of its neighbors:
+ *            - compute its distance from the start pixel
+ *            - compare this distance with that on the distance map:
+ *                - if the distance map value higher, relax the distance
+ *                  and push the pixel on the queue
+ *                - if the distance map value is lower, discard the pixel
+ *
+ *      How does this loop terminate?  Before, with an ordered queue,
+ *      it terminates when you pop the end pixel.  But with an unordered
+ *      queue (or stack), the first time you hit the end pixel, the
+ *      distance is not guaranteed to be correct, because the pixels
+ *      along the shortest path may not have yet been visited and relaxed.
+ *      Because the shortest path can theoretically go anywhere,
+ *      we must keep going.  How do we know when to stop?   Dijkstra
+ *      uses an ordered queue to systematically remove nodes from
+ *      further consideration.  (Each time a pixel is popped, we're
+ *      done with it; it's "finalized" in the Dijkstra sense because
+ *      we know the shortest path to it.)  However, with an unordered
+ *      queue, the brute force answer is: stop when the queue
+ *      (or stack) is empty, because then every pixel in the image
+ *      has been assigned its minimum "distance" from the start pixel.
+ *
+ *      This is similar to the situation when you use a stack for the
+ *      simpler uniform-step problem: with breadth-first search (BFS)
+ *      the pixels on the queue are automatically ordered, so you are
+ *      done when you locate the end pixel as a neighbor of a popped pixel;
+ *      whereas depth-first search (DFS), using a stack, requires,
+ *      in general, a search of every accessible pixel.  Further, if
+ *      a pixel is revisited with a smaller distance, that distance is
+ *      recorded and the pixel is put on the stack again.
+ *
+ *      But surely, you ask, can't we stop sooner?  What if the
+ *      start and end pixels are very close to each other?
+ *      OK, suppose they are, and you have very high walls and a
+ *      long snaking level path that is actually the minimum cost.
+ *      That long path can wind back and forth across the entire
+ *      maze many times before ending up at the end point, which
+ *      could be just over a wall from the start.  With the unordered
+ *      queue, you very quickly get a high distance for the end
+ *      pixel, which will be relaxed to the minimum distance only
+ *      after all the pixels of the path have been visited and placed
+ *      on the queue, multiple times for many of them.  So that's the
+ *      price for not ordering the queue!
+ */
+PTA *
+pixSearchGrayMaze(PIX     *pixs,
+                  l_int32  xi,
+                  l_int32  yi,
+                  l_int32  xf,
+                  l_int32  yf,
+                  PIX    **ppixd)
+{
+l_int32   x, y, w, h, d;
+l_uint32  val, valr, vals, rpixel, gpixel, bpixel;
+void    **lines8, **liner32, **linep8;
+l_int32   cost, dist, distparent, sival, sivals;
+MAZEEL   *el, *elp;
+PIX      *pixd;  /* optionally plot the path on this RGB version of pixs */
+PIX      *pixr;  /* for bookkeeping, to indicate the minimum distance */
+                 /* to pixels already visited */
+PIX      *pixp;  /* for bookkeeping, to indicate direction to parent */
+L_HEAP   *lh;
+PTA      *pta;
+
+    PROCNAME("pixSearchGrayMaze");
+
+    if (ppixd) *ppixd = NULL;
+    if (!pixs)
+        return (PTA *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PTA *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (xi <= 0 || xi >= w)
+        return (PTA *)ERROR_PTR("xi not valid", procName, NULL);
+    if (yi <= 0 || yi >= h)
+        return (PTA *)ERROR_PTR("yi not valid", procName, NULL);
+    pixd = NULL;
+    pta = NULL;
+
+    pixr = pixCreate(w, h, 32);
+    pixSetAll(pixr);  /* initialize to max value */
+    pixp = pixCreate(w, h, 8);  /* direction to parent stored as enum val */
+    lines8 = pixGetLinePtrs(pixs, NULL);
+    linep8 = pixGetLinePtrs(pixp, NULL);
+    liner32 = pixGetLinePtrs(pixr, NULL);
+
+    lh = lheapCreate(0, L_SORT_INCREASING);  /* always remove closest pixels */
+
+        /* Prime the heap with the first pixel */
+    pixGetPixel(pixs, xi, yi, &val);
+    el = mazeelCreate(xi, yi, 0);  /* don't need direction here */
+    el->distance = 0;
+    pixGetPixel(pixs, xi, yi, &val);
+    el->val = val;
+    pixSetPixel(pixr, xi, yi, 0);  /* distance is 0 */
+    lheapAdd(lh, el);
+
+        /* Breadth-first search with priority queue (implemented by
+           a heap), labeling direction to parents in pixp and minimum
+           distance to visited pixels in pixr.  Stop when we pull
+           the destination point (xf, yf) off the queue. */
+    while (lheapGetCount(lh) > 0) {
+        elp = (MAZEEL *)lheapRemove(lh);
+        if (!elp)
+            return (PTA *)ERROR_PTR("heap broken!!", procName, NULL);
+        x = elp->x;
+        y = elp->y;
+        if (x == xf && y == yf) {  /* exit condition */
+            LEPT_FREE(elp);
+            break;
+        }
+        distparent = (l_int32)elp->distance;
+        val = elp->val;
+        sival = val;
+
+        if (x > 0) {  /* check to west */
+            vals = GET_DATA_BYTE(lines8[y], x - 1);
+            valr = GET_DATA_FOUR_BYTES(liner32[y], x - 1);
+            sivals = (l_int32)vals;
+            cost = 1 + L_ABS(sivals - sival);  /* cost to move to this pixel */
+            dist = distparent + cost;
+            if (dist < valr) {  /* shortest path so far to this pixel */
+                SET_DATA_FOUR_BYTES(liner32[y], x - 1, dist);  /* new dist */
+                SET_DATA_BYTE(linep8[y], x - 1, DIR_EAST);  /* parent to E */
+                el = mazeelCreate(x - 1, y, 0);
+                el->val = vals;
+                el->distance = dist;
+                lheapAdd(lh, el);
+            }
+        }
+        if (y > 0) {  /* check north */
+            vals = GET_DATA_BYTE(lines8[y - 1], x);
+            valr = GET_DATA_FOUR_BYTES(liner32[y - 1], x);
+            sivals = (l_int32)vals;
+            cost = 1 + L_ABS(sivals - sival);  /* cost to move to this pixel */
+            dist = distparent + cost;
+            if (dist < valr) {  /* shortest path so far to this pixel */
+                SET_DATA_FOUR_BYTES(liner32[y - 1], x, dist);  /* new dist */
+                SET_DATA_BYTE(linep8[y - 1], x, DIR_SOUTH);  /* parent to S */
+                el = mazeelCreate(x, y - 1, 0);
+                el->val = vals;
+                el->distance = dist;
+                lheapAdd(lh, el);
+            }
+        }
+        if (x < w - 1) {  /* check east */
+            vals = GET_DATA_BYTE(lines8[y], x + 1);
+            valr = GET_DATA_FOUR_BYTES(liner32[y], x + 1);
+            sivals = (l_int32)vals;
+            cost = 1 + L_ABS(sivals - sival);  /* cost to move to this pixel */
+            dist = distparent + cost;
+            if (dist < valr) {  /* shortest path so far to this pixel */
+                SET_DATA_FOUR_BYTES(liner32[y], x + 1, dist);  /* new dist */
+                SET_DATA_BYTE(linep8[y], x + 1, DIR_WEST);  /* parent to W */
+                el = mazeelCreate(x + 1, y, 0);
+                el->val = vals;
+                el->distance = dist;
+                lheapAdd(lh, el);
+            }
+        }
+        if (y < h - 1) {  /* check south */
+            vals = GET_DATA_BYTE(lines8[y + 1], x);
+            valr = GET_DATA_FOUR_BYTES(liner32[y + 1], x);
+            sivals = (l_int32)vals;
+            cost = 1 + L_ABS(sivals - sival);  /* cost to move to this pixel */
+            dist = distparent + cost;
+            if (dist < valr) {  /* shortest path so far to this pixel */
+                SET_DATA_FOUR_BYTES(liner32[y + 1], x, dist);  /* new dist */
+                SET_DATA_BYTE(linep8[y + 1], x, DIR_NORTH);  /* parent to N */
+                el = mazeelCreate(x, y + 1, 0);
+                el->val = vals;
+                el->distance = dist;
+                lheapAdd(lh, el);
+            }
+        }
+        LEPT_FREE(elp);
+    }
+
+    lheapDestroy(&lh, TRUE);
+
+    if (ppixd) {
+        pixd = pixConvert8To32(pixs);
+        *ppixd = pixd;
+    }
+    composeRGBPixel(255, 0, 0, &rpixel);  /* start point */
+    composeRGBPixel(0, 255, 0, &gpixel);
+    composeRGBPixel(0, 0, 255, &bpixel);  /* end point */
+
+    x = xf;
+    y = yf;
+    pta = ptaCreate(0);
+    while (1) {  /* write path onto pixd */
+        ptaAddPt(pta, x, y);
+        if (x == xi && y == yi)
+            break;
+        if (pixd)
+            pixSetPixel(pixd, x, y, gpixel);
+        pixGetPixel(pixp, x, y, &val);
+        if (val == DIR_NORTH)
+            y--;
+        else if (val == DIR_SOUTH)
+            y++;
+        else if (val == DIR_EAST)
+            x++;
+        else if (val == DIR_WEST)
+            x--;
+        pixGetPixel(pixr, x, y, &val);
+
+#if  DEBUG_PATH
+        fprintf(stderr, "(x,y) = (%d, %d); dist = %d\n", x, y, val);
+#endif  /* DEBUG_PATH */
+
+    }
+    if (pixd) {
+        pixSetPixel(pixd, xi, yi, rpixel);
+        pixSetPixel(pixd, xf, yf, bpixel);
+    }
+
+    pixDestroy(&pixp);
+    pixDestroy(&pixr);
+    LEPT_FREE(lines8);
+    LEPT_FREE(linep8);
+    LEPT_FREE(liner32);
+    return pta;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                      Largest rectangle in an image                  *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixFindLargestRectangle()
+ *
+ *      Input:  pixs  (1 bpp)
+ *              polarity (0 within background, 1 within foreground)
+ *              &box (<return> largest rectangle, either by area or
+ *                    by perimeter)
+ *              debugflag (1 to output image with rectangle drawn on it)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Why is this here?  This is a simple and elegant solution to
+ *          a problem in computational geometry that at first appears
+ *          quite difficult: what is the largest rectangle that can
+ *          be placed in the image, covering only pixels of one polarity
+ *          (bg or fg)?  The solution is O(n), where n is the number
+ *          of pixels in the image, and it requires nothing more than
+ *          using a simple recursion relation in a single sweep of the image.
+ *      (2) In a sweep from UL to LR with left-to-right being the fast
+ *          direction, calculate the largest white rectangle at (x, y),
+ *          using previously calculated values at pixels #1 and #2:
+ *             #1:    (x, y - 1)
+ *             #2:    (x - 1, y)
+ *          We also need the most recent "black" pixels that were seen
+ *          in the current row and column.
+ *          Consider the largest area.  There are only two possibilities:
+ *             (a)  Min(w(1), horizdist) * (h(1) + 1)
+ *             (b)  Min(h(2), vertdist) * (w(2) + 1)
+ *          where
+ *             horizdist: the distance from the rightmost "black" pixel seen
+ *                        in the current row across to the current pixel
+ *             vertdist: the distance from the lowest "black" pixel seen
+ *                       in the current column down to the current pixel
+ *          and we choose the Max of (a) and (b).
+ *      (3) To convince yourself that these recursion relations are correct,
+ *          it helps to draw the maximum rectangles at #1 and #2.
+ *          Then for #1, you try to extend the rectangle down one line,
+ *          so that the height is h(1) + 1.  Do you get the full
+ *          width of #1, w(1)?  It depends on where the black pixels are
+ *          in the current row.  You know the final width is bounded by w(1)
+ *          and w(2) + 1, but the actual value depends on the distribution
+ *          of black pixels in the current row that are at a distance
+ *          from the current pixel that is between these limits.
+ *          We call that value "horizdist", and the area is then given
+ *          by the expression (a) above.  Using similar reasoning for #2,
+ *          where you attempt to extend the rectangle to the right
+ *          by 1 pixel, you arrive at (b).  The largest rectangle is
+ *          then found by taking the Max.
+ */
+l_int32
+pixFindLargestRectangle(PIX         *pixs,
+                        l_int32      polarity,
+                        BOX        **pbox,
+                        const char  *debugfile)
+{
+l_int32    i, j, w, h, d, wpls, val;
+l_int32    wp, hp, w1, w2, h1, h2, wmin, hmin, area1, area2;
+l_int32    xmax, ymax;  /* LR corner of the largest rectangle */
+l_int32    maxarea, wmax, hmax, vertdist, horizdist, prevfg;
+l_int32   *lowestfg;
+l_uint32  *datas, *lines;
+l_uint32 **linew, **lineh;
+BOX       *box;
+PIX       *pixw, *pixh;  /* keeps the width and height for the largest */
+                         /* rectangles whose LR corner is located there. */
+
+    PROCNAME("pixFindLargestRectangle");
+
+    if (!pbox)
+        return ERROR_INT("&box not defined", procName, 1);
+    *pbox = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1)
+        return ERROR_INT("pixs not 1 bpp", procName, 1);
+    if (polarity != 0 && polarity != 1)
+        return ERROR_INT("invalid polarity", procName, 1);
+
+        /* Initialize lowest "fg" seen so far for each column */
+    lowestfg = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32));
+    for (i = 0; i < w; i++)
+        lowestfg[i] = -1;
+
+        /* The combination (val ^ polarity) is the color for which we
+         * are searching for the maximum rectangle.  For polarity == 0,
+         * we search in the bg (white). */
+    pixw = pixCreate(w, h, 32);  /* stores width */
+    pixh = pixCreate(w, h, 32);  /* stores height */
+    linew = (l_uint32 **)pixGetLinePtrs(pixw, NULL);
+    lineh = (l_uint32 **)pixGetLinePtrs(pixh, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    maxarea = xmax = ymax = wmax = hmax = 0;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        prevfg = -1;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BIT(lines, j);
+            if ((val ^ polarity) == 0) {  /* bg (0) if polarity == 0, etc. */
+                if (i == 0 && j == 0) {
+                    wp = hp = 1;
+                } else if (i == 0) {
+                    wp = linew[i][j - 1] + 1;
+                    hp = 1;
+                } else if (j == 0) {
+                    wp = 1;
+                    hp = lineh[i - 1][j] + 1;
+                } else {
+                        /* Expand #1 prev rectangle down */
+                    w1 = linew[i - 1][j];
+                    h1 = lineh[i - 1][j];
+                    horizdist = j - prevfg;
+                    wmin = L_MIN(w1, horizdist);  /* width of new rectangle */
+                    area1 = wmin * (h1 + 1);
+
+                        /* Expand #2 prev rectangle to right */
+                    w2 = linew[i][j - 1];
+                    h2 = lineh[i][j - 1];
+                    vertdist = i - lowestfg[j];
+                    hmin = L_MIN(h2, vertdist);  /* height of new rectangle */
+                    area2 = hmin * (w2 + 1);
+
+                    if (area1 > area2) {
+                         wp = wmin;
+                         hp = h1 + 1;
+                    } else {
+                         wp = w2 + 1;
+                         hp = hmin;
+                    }
+                }
+            } else {  /* fg (1) if polarity == 0; bg (0) if polarity == 1 */
+                prevfg = j;
+                lowestfg[j] = i;
+                wp = hp = 0;
+            }
+            linew[i][j] = wp;
+            lineh[i][j] = hp;
+            if (wp * hp > maxarea) {
+                maxarea = wp * hp;
+                xmax = j;
+                ymax = i;
+                wmax = wp;
+                hmax = hp;
+            }
+        }
+    }
+
+        /* Translate from LR corner to Box coords (UL corner, w, h) */
+    box = boxCreate(xmax - wmax + 1, ymax - hmax + 1, wmax, hmax);
+    *pbox = box;
+
+    if (debugfile) {
+        PIX  *pixdb;
+        pixdb = pixConvertTo8(pixs, TRUE);
+        pixRenderHashBoxArb(pixdb, box, 6, 2, L_NEG_SLOPE_LINE, 1, 255, 0, 0);
+        pixWrite(debugfile, pixdb, IFF_PNG);
+        pixDestroy(&pixdb);
+    }
+
+    LEPT_FREE(linew);
+    LEPT_FREE(lineh);
+    LEPT_FREE(lowestfg);
+    pixDestroy(&pixw);
+    pixDestroy(&pixh);
+    return 0;
+}
diff --git a/src/morph.c b/src/morph.c
new file mode 100644 (file)
index 0000000..13c495b
--- /dev/null
@@ -0,0 +1,1777 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  morph.c
+ *
+ *     Generic binary morphological ops implemented with rasterop
+ *         PIX     *pixDilate()
+ *         PIX     *pixErode()
+ *         PIX     *pixHMT()
+ *         PIX     *pixOpen()
+ *         PIX     *pixClose()
+ *         PIX     *pixCloseSafe()
+ *         PIX     *pixOpenGeneralized()
+ *         PIX     *pixCloseGeneralized()
+ *
+ *     Binary morphological (raster) ops with brick Sels
+ *         PIX     *pixDilateBrick()
+ *         PIX     *pixErodeBrick()
+ *         PIX     *pixOpenBrick()
+ *         PIX     *pixCloseBrick()
+ *         PIX     *pixCloseSafeBrick()
+ *
+ *     Binary composed morphological (raster) ops with brick Sels
+ *         l_int32  selectComposableSels()
+ *         l_int32  selectComposableSizes()
+ *         PIX     *pixDilateCompBrick()
+ *         PIX     *pixErodeCompBrick()
+ *         PIX     *pixOpenCompBrick()
+ *         PIX     *pixCloseCompBrick()
+ *         PIX     *pixCloseSafeCompBrick()
+ *
+ *     Functions associated with boundary conditions
+ *         void     resetMorphBoundaryCondition()
+ *         l_int32  getMorphBorderPixelColor()
+ *
+ *     Static helpers for arg processing
+ *         static PIX     *processMorphArgs1()
+ *         static PIX     *processMorphArgs2()
+ *
+ *  You are provided with many simple ways to do binary morphology.
+ *  In particular, if you are using brick Sels, there are six
+ *  convenient methods, all specially tailored for separable operations
+ *  on brick Sels.  A "brick" Sel is a Sel that is a rectangle
+ *  of solid SEL_HITs with the origin at or near the center.
+ *  Note that a brick Sel can have one dimension of size 1.
+ *  This is very common.  All the brick Sel operations are
+ *  separable, meaning the operation is done first in the horizontal
+ *  direction and then in the vertical direction.  If one of the
+ *  dimensions is 1, this is a special case where the operation is
+ *  only performed in the other direction.
+ *
+ *  These six brick Sel methods are enumerated as follows:
+ *
+ *  (1) Brick Sels: pix*Brick(), where * = {Dilate, Erode, Open, Close}.
+ *      These are separable rasterop implementations.  The Sels are
+ *      automatically generated, used, and destroyed at the end.
+ *      You can get the result as a new Pix, in-place back into the src Pix,
+ *      or written to another existing Pix.
+ *
+ *  (2) Brick Sels: pix*CompBrick(), where * = {Dilate, Erode, Open, Close}.
+ *      These are separable, 2-way composite, rasterop implementations.
+ *      The Sels are automatically generated, used, and destroyed at the end.
+ *      You can get the result as a new Pix, in-place back into the src Pix,
+ *      or written to another existing Pix.  For large Sels, these are
+ *      considerably faster than the corresponding pix*Brick() functions.
+ *      N.B.:  The size of the Sels that are actually used are typically
+ *      close to, but not exactly equal to, the size input to the function.
+ *
+ *  (3) Brick Sels: pix*BrickDwa(), where * = {Dilate, Erode, Open, Close}.
+ *      These are separable dwa (destination word accumulation)
+ *      implementations.  They use auto-gen'd dwa code.  You can get
+ *      the result as a new Pix, in-place back into the src Pix,
+ *      or written to another existing Pix.  This is typically
+ *      about 3x faster than the analogous rasterop pix*Brick()
+ *      function, but it has the limitation that the Sel size must
+ *      be less than 63.  This is pre-set to work on a number
+ *      of pre-generated Sels.  If you want to use other Sels, the
+ *      code can be auto-gen'd for them; see the instructions in morphdwa.c.
+ *
+ *  (4) Same as (1), but you run it through pixMorphSequence(), with
+ *      the sequence string either compiled in or generated using sprintf.
+ *      All intermediate images and Sels are created, used and destroyed.
+ *      You always get the result as a new Pix.  For example, you can
+ *      specify a separable 11 x 17 brick opening as "o11.17",
+ *      or you can specify the horizontal and vertical operations
+ *      explicitly as "o11.1 + o1.11".  See morphseq.c for details.
+ *
+ *  (5) Same as (2), but you run it through pixMorphCompSequence(), with
+ *      the sequence string either compiled in or generated using sprintf.
+ *      All intermediate images and Sels are created, used and destroyed.
+ *      You always get the result as a new Pix.  See morphseq.c for details.
+ *
+ *  (6) Same as (3), but you run it through pixMorphSequenceDwa(), with
+ *      the sequence string either compiled in or generated using sprintf.
+ *      All intermediate images and Sels are created, used and destroyed.
+ *      You always get the result as a new Pix.  See morphseq.c for details.
+ *
+ *  If you are using Sels that are not bricks, you have two choices:
+ *      (a) simplest: use the basic rasterop implementations (pixDilate(), ...)
+ *      (b) fastest: generate the destination word accumumlation (dwa)
+ *          code for your Sels and compile it with the library.
+ *
+ *      For an example, see flipdetect.c, which gives implementations
+ *      using hit-miss Sels with both the rasterop and dwa versions.
+ *      For the latter, the dwa code resides in fliphmtgen.c, and it
+ *      was generated by prog/flipselgen.c.  Both the rasterop and dwa
+ *      implementations are tested by prog/fliptest.c.
+ *
+ *  A global constant MORPH_BC is used to set the boundary conditions
+ *  for rasterop-based binary morphology.  MORPH_BC, in morph.c,
+ *  is set by default to ASYMMETRIC_MORPH_BC for a non-symmetric
+ *  convention for boundary pixels in dilation and erosion:
+ *      All pixels outside the image are assumed to be OFF
+ *      for both dilation and erosion.
+ *  To use a symmetric definition, see comments in pixErode()
+ *  and reset MORPH_BC to SYMMETRIC_MORPH_BC, using
+ *  resetMorphBoundaryCondition().
+ *
+ *  Boundary artifacts are possible in closing when the non-symmetric
+ *  boundary conditions are used, because foreground pixels very close
+ *  to the edge can be removed.  This can be avoided by using either
+ *  the symmetric boundary conditions or the function pixCloseSafe(),
+ *  which adds a border before the operation and removes it afterwards.
+ *
+ *  The hit-miss transform (HMT) is the bit-and of 2 erosions:
+ *     (erosion of the src by the hits)  &  (erosion of the bit-inverted
+ *                                           src by the misses)
+ *
+ *  The 'generalized opening' is an HMT followed by a dilation that uses
+ *  only the hits of the hit-miss Sel.
+ *  The 'generalized closing' is a dilation (again, with the hits
+ *  of a hit-miss Sel), followed by the HMT.
+ *  Both of these 'generalized' functions are idempotent.
+ *
+ *  These functions are extensively tested in prog/binmorph1_reg.c,
+ *  prog/binmorph2_reg.c, and prog/binmorph3_reg.c.
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+    /* Global constant; initialized here; must be declared extern
+     * in other files to access it directly.  However, in most
+     * cases that is not necessary, because it can be reset
+     * using resetMorphBoundaryCondition().  */
+LEPT_DLL l_int32  MORPH_BC = ASYMMETRIC_MORPH_BC;
+
+    /* We accept this cost in extra rasterops for decomposing exactly. */
+static const l_int32  ACCEPTABLE_COST = 5;
+
+    /* Static helpers for arg processing */
+static PIX * processMorphArgs1(PIX *pixd, PIX *pixs, SEL *sel, PIX **ppixt);
+static PIX * processMorphArgs2(PIX *pixd, PIX *pixs, SEL *sel);
+
+
+/*-----------------------------------------------------------------*
+ *    Generic binary morphological ops implemented with rasterop   *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixDilate()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              sel
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This dilates src using hits in Sel.
+ *      (2) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (3) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixDilate(NULL, pixs, ...);
+ *          (b) pixDilate(pixs, pixs, ...);
+ *          (c) pixDilate(pixd, pixs, ...);
+ *      (4) The size of the result is determined by pixs.
+ */
+PIX *
+pixDilate(PIX  *pixd,
+          PIX  *pixs,
+          SEL  *sel)
+{
+l_int32  i, j, w, h, sx, sy, cx, cy, seldata;
+PIX     *pixt;
+
+    PROCNAME("pixDilate");
+
+    if ((pixd = processMorphArgs1(pixd, pixs, sel, &pixt)) == NULL)
+        return (PIX *)ERROR_PTR("processMorphArgs1 failed", procName, pixd);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    selGetParameters(sel, &sy, &sx, &cy, &cx);
+    pixClearAll(pixd);
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++) {
+            seldata = sel->data[i][j];
+            if (seldata == 1) {   /* src | dst */
+                pixRasterop(pixd, j - cx, i - cy, w, h, PIX_SRC | PIX_DST,
+                            pixt, 0, 0);
+            }
+        }
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixErode()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              sel
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This erodes src using hits in Sel.
+ *      (2) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (3) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixErode(NULL, pixs, ...);
+ *          (b) pixErode(pixs, pixs, ...);
+ *          (c) pixErode(pixd, pixs, ...);
+ *      (4) The size of the result is determined by pixs.
+ */
+PIX *
+pixErode(PIX  *pixd,
+         PIX  *pixs,
+         SEL  *sel)
+{
+l_int32  i, j, w, h, sx, sy, cx, cy, seldata;
+l_int32  xp, yp, xn, yn;
+PIX     *pixt;
+
+    PROCNAME("pixErode");
+
+    if ((pixd = processMorphArgs1(pixd, pixs, sel, &pixt)) == NULL)
+        return (PIX *)ERROR_PTR("processMorphArgs1 failed", procName, pixd);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    selGetParameters(sel, &sy, &sx, &cy, &cx);
+    pixSetAll(pixd);
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++) {
+            seldata = sel->data[i][j];
+            if (seldata == 1) {   /* src & dst */
+                    pixRasterop(pixd, cx - j, cy - i, w, h, PIX_SRC & PIX_DST,
+                                pixt, 0, 0);
+            }
+        }
+    }
+
+        /* Clear near edges.  We do this for the asymmetric boundary
+         * condition convention that implements erosion assuming all
+         * pixels surrounding the image are OFF.  If you use a
+         * use a symmetric b.c. convention, where the erosion is
+         * implemented assuming pixels surrounding the image
+         * are ON, these operations are omitted.  */
+    if (MORPH_BC == ASYMMETRIC_MORPH_BC) {
+        selFindMaxTranslations(sel, &xp, &yp, &xn, &yn);
+        if (xp > 0)
+            pixRasterop(pixd, 0, 0, xp, h, PIX_CLR, NULL, 0, 0);
+        if (xn > 0)
+            pixRasterop(pixd, w - xn, 0, xn, h, PIX_CLR, NULL, 0, 0);
+        if (yp > 0)
+            pixRasterop(pixd, 0, 0, w, yp, PIX_CLR, NULL, 0, 0);
+        if (yn > 0)
+            pixRasterop(pixd, 0, h - yn, w, yn, PIX_CLR, NULL, 0, 0);
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixHMT()
+ *
+ *      Input:  pixd (<optional>; this can be null, equal to pixs,
+ *                    or different from pixs)
+ *              pixs (1 bpp)
+ *              sel
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) The hit-miss transform erodes the src, using both hits
+ *          and misses in the Sel.  It ANDs the shifted src for hits
+ *          and ANDs the inverted shifted src for misses.
+ *      (2) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (3) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixHMT(NULL, pixs, ...);
+ *          (b) pixHMT(pixs, pixs, ...);
+ *          (c) pixHMT(pixd, pixs, ...);
+ *      (4) The size of the result is determined by pixs.
+ */
+PIX *
+pixHMT(PIX  *pixd,
+       PIX  *pixs,
+       SEL  *sel)
+{
+l_int32  i, j, w, h, sx, sy, cx, cy, firstrasterop, seldata;
+l_int32  xp, yp, xn, yn;
+PIX     *pixt;
+
+    PROCNAME("pixHMT");
+
+    if ((pixd = processMorphArgs1(pixd, pixs, sel, &pixt)) == NULL)
+        return (PIX *)ERROR_PTR("processMorphArgs1 failed", procName, pixd);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    selGetParameters(sel, &sy, &sx, &cy, &cx);
+    firstrasterop = TRUE;
+    for (i = 0; i < sy; i++) {
+        for (j = 0; j < sx; j++) {
+            seldata = sel->data[i][j];
+            if (seldata == 1) {  /* hit */
+                if (firstrasterop == TRUE) {  /* src only */
+                    pixClearAll(pixd);
+                    pixRasterop(pixd, cx - j, cy - i, w, h, PIX_SRC,
+                                pixt, 0, 0);
+                    firstrasterop = FALSE;
+                } else {   /* src & dst */
+                    pixRasterop(pixd, cx - j, cy - i, w, h, PIX_SRC & PIX_DST,
+                                pixt, 0, 0);
+                }
+            } else if (seldata == 2) {  /* miss */
+                if (firstrasterop == TRUE) {  /* ~src only */
+                    pixSetAll(pixd);
+                    pixRasterop(pixd, cx - j, cy - i, w, h, PIX_NOT(PIX_SRC),
+                             pixt, 0, 0);
+                    firstrasterop = FALSE;
+                } else {  /* ~src & dst */
+                    pixRasterop(pixd, cx - j, cy - i, w, h,
+                                PIX_NOT(PIX_SRC) & PIX_DST,
+                                pixt, 0, 0);
+                }
+            }
+        }
+    }
+
+        /* Clear near edges */
+    selFindMaxTranslations(sel, &xp, &yp, &xn, &yn);
+    if (xp > 0)
+        pixRasterop(pixd, 0, 0, xp, h, PIX_CLR, NULL, 0, 0);
+    if (xn > 0)
+        pixRasterop(pixd, w - xn, 0, xn, h, PIX_CLR, NULL, 0, 0);
+    if (yp > 0)
+        pixRasterop(pixd, 0, 0, w, yp, PIX_CLR, NULL, 0, 0);
+    if (yn > 0)
+        pixRasterop(pixd, 0, h - yn, w, yn, PIX_CLR, NULL, 0, 0);
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixOpen()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              sel
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) Generic morphological opening, using hits in the Sel.
+ *      (2) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (3) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixOpen(NULL, pixs, ...);
+ *          (b) pixOpen(pixs, pixs, ...);
+ *          (c) pixOpen(pixd, pixs, ...);
+ *      (4) The size of the result is determined by pixs.
+ */
+PIX *
+pixOpen(PIX  *pixd,
+        PIX  *pixs,
+        SEL  *sel)
+{
+PIX  *pixt;
+
+    PROCNAME("pixOpen");
+
+    if ((pixd = processMorphArgs2(pixd, pixs, sel)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not returned", procName, pixd);
+
+    if ((pixt = pixErode(NULL, pixs, sel)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+    pixDilate(pixd, pixt, sel);
+    pixDestroy(&pixt);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixClose()
+ *
+ *      Input:  pixd (<optional>; this can be null, equal to pixs,
+ *                    or different from pixs)
+ *              pixs (1 bpp)
+ *              sel
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) Generic morphological closing, using hits in the Sel.
+ *      (2) This implementation is a strict dual of the opening if
+ *          symmetric boundary conditions are used (see notes at top
+ *          of this file).
+ *      (3) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (4) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixClose(NULL, pixs, ...);
+ *          (b) pixClose(pixs, pixs, ...);
+ *          (c) pixClose(pixd, pixs, ...);
+ *      (5) The size of the result is determined by pixs.
+ */
+PIX *
+pixClose(PIX  *pixd,
+         PIX  *pixs,
+         SEL  *sel)
+{
+PIX  *pixt;
+
+    PROCNAME("pixClose");
+
+    if ((pixd = processMorphArgs2(pixd, pixs, sel)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not returned", procName, pixd);
+
+    if ((pixt = pixDilate(NULL, pixs, sel)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+    pixErode(pixd, pixt, sel);
+    pixDestroy(&pixt);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixCloseSafe()
+ *
+ *      Input:  pixd (<optional>; this can be null, equal to pixs,
+ *                    or different from pixs)
+ *              pixs (1 bpp)
+ *              sel
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) Generic morphological closing, using hits in the Sel.
+ *      (2) If non-symmetric boundary conditions are used, this
+ *          function adds a border of OFF pixels that is of
+ *          sufficient size to avoid losing pixels from the dilation,
+ *          and it removes the border after the operation is finished.
+ *          It thus enforces a correct extensive result for closing.
+ *      (3) If symmetric b.c. are used, it is not necessary to add
+ *          and remove this border.
+ *      (4) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (5) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixCloseSafe(NULL, pixs, ...);
+ *          (b) pixCloseSafe(pixs, pixs, ...);
+ *          (c) pixCloseSafe(pixd, pixs, ...);
+ *      (6) The size of the result is determined by pixs.
+ */
+PIX *
+pixCloseSafe(PIX  *pixd,
+             PIX  *pixs,
+             SEL  *sel)
+{
+l_int32  xp, yp, xn, yn, xmax, xbord;
+PIX     *pixt1, *pixt2;
+
+    PROCNAME("pixCloseSafe");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (!sel)
+        return (PIX *)ERROR_PTR("sel not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+
+        /* Symmetric b.c. handles correctly without added pixels */
+    if (MORPH_BC == SYMMETRIC_MORPH_BC)
+        return pixClose(pixd, pixs, sel);
+
+    selFindMaxTranslations(sel, &xp, &yp, &xn, &yn);
+    xmax = L_MAX(xp, xn);
+    xbord = 32 * ((xmax + 31) / 32);  /* full 32 bit words */
+
+    if ((pixt1 = pixAddBorderGeneral(pixs, xbord, xbord, yp, yn, 0)) == NULL)
+        return (PIX *)ERROR_PTR("pixt1 not made", procName, pixd);
+    pixClose(pixt1, pixt1, sel);
+    if ((pixt2 = pixRemoveBorderGeneral(pixt1, xbord, xbord, yp, yn)) == NULL)
+        return (PIX *)ERROR_PTR("pixt2 not made", procName, pixd);
+    pixDestroy(&pixt1);
+
+    if (!pixd)
+        return pixt2;
+
+    pixCopy(pixd, pixt2);
+    pixDestroy(&pixt2);
+    return pixd;
+}
+
+
+/*!
+ *  pixOpenGeneralized()
+ *
+ *      Input:  pixd (<optional>; this can be null, equal to pixs,
+ *                    or different from pixs)
+ *              pixs (1 bpp)
+ *              sel
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) Generalized morphological opening, using both hits and
+ *          misses in the Sel.
+ *      (2) This does a hit-miss transform, followed by a dilation
+ *          using the hits.
+ *      (3) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (4) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixOpenGeneralized(NULL, pixs, ...);
+ *          (b) pixOpenGeneralized(pixs, pixs, ...);
+ *          (c) pixOpenGeneralized(pixd, pixs, ...);
+ *      (5) The size of the result is determined by pixs.
+ */
+PIX *
+pixOpenGeneralized(PIX  *pixd,
+                   PIX  *pixs,
+                   SEL  *sel)
+{
+PIX  *pixt;
+
+    PROCNAME("pixOpenGeneralized");
+
+    if ((pixd = processMorphArgs2(pixd, pixs, sel)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not returned", procName, pixd);
+
+    if ((pixt = pixHMT(NULL, pixs, sel)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+    pixDilate(pixd, pixt, sel);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixCloseGeneralized()
+ *
+ *      Input:  pixd (<optional>; this can be null, equal to pixs,
+ *                    or different from pixs)
+ *              pixs (1 bpp)
+ *              sel
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) Generalized morphological closing, using both hits and
+ *          misses in the Sel.
+ *      (2) This does a dilation using the hits, followed by a
+ *          hit-miss transform.
+ *      (3) This operation is a dual of the generalized opening.
+ *      (4) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (5) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixCloseGeneralized(NULL, pixs, ...);
+ *          (b) pixCloseGeneralized(pixs, pixs, ...);
+ *          (c) pixCloseGeneralized(pixd, pixs, ...);
+ *      (6) The size of the result is determined by pixs.
+ */
+PIX *
+pixCloseGeneralized(PIX  *pixd,
+                    PIX  *pixs,
+                    SEL  *sel)
+{
+PIX  *pixt;
+
+    PROCNAME("pixCloseGeneralized");
+
+    if ((pixd = processMorphArgs2(pixd, pixs, sel)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not returned", procName, pixd);
+
+    if ((pixt = pixDilate(NULL, pixs, sel)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+    pixHMT(pixd, pixt, sel);
+    pixDestroy(&pixt);
+
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *          Binary morphological (raster) ops with brick Sels      *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixDilateBrick()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
+ *      (3) Do separably if both hsize and vsize are > 1.
+ *      (4) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (5) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixDilateBrick(NULL, pixs, ...);
+ *          (b) pixDilateBrick(pixs, pixs, ...);
+ *          (c) pixDilateBrick(pixd, pixs, ...);
+ *      (6) The size of the result is determined by pixs.
+ */
+PIX *
+pixDilateBrick(PIX     *pixd,
+               PIX     *pixs,
+               l_int32  hsize,
+               l_int32  vsize)
+{
+PIX  *pixt;
+SEL  *sel, *selh, *selv;
+
+    PROCNAME("pixDilateBrick");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+    if (hsize == 1 || vsize == 1) {  /* no intermediate result */
+        sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
+        pixd = pixDilate(pixd, pixs, sel);
+        selDestroy(&sel);
+    } else {
+        selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT);
+        selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT);
+        pixt = pixDilate(NULL, pixs, selh);
+        pixd = pixDilate(pixd, pixt, selv);
+        pixDestroy(&pixt);
+        selDestroy(&selh);
+        selDestroy(&selv);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixErodeBrick()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
+ *      (3) Do separably if both hsize and vsize are > 1.
+ *      (4) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (5) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixErodeBrick(NULL, pixs, ...);
+ *          (b) pixErodeBrick(pixs, pixs, ...);
+ *          (c) pixErodeBrick(pixd, pixs, ...);
+ *      (6) The size of the result is determined by pixs.
+ */
+PIX *
+pixErodeBrick(PIX     *pixd,
+              PIX     *pixs,
+              l_int32  hsize,
+              l_int32  vsize)
+{
+PIX  *pixt;
+SEL  *sel, *selh, *selv;
+
+    PROCNAME("pixErodeBrick");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+    if (hsize == 1 || vsize == 1) {  /* no intermediate result */
+        sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
+        pixd = pixErode(pixd, pixs, sel);
+        selDestroy(&sel);
+    } else {
+        selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT);
+        selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT);
+        pixt = pixErode(NULL, pixs, selh);
+        pixd = pixErode(pixd, pixt, selv);
+        pixDestroy(&pixt);
+        selDestroy(&selh);
+        selDestroy(&selv);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixOpenBrick()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
+ *      (3) Do separably if both hsize and vsize are > 1.
+ *      (4) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (5) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixOpenBrick(NULL, pixs, ...);
+ *          (b) pixOpenBrick(pixs, pixs, ...);
+ *          (c) pixOpenBrick(pixd, pixs, ...);
+ *      (6) The size of the result is determined by pixs.
+ */
+PIX *
+pixOpenBrick(PIX     *pixd,
+             PIX     *pixs,
+             l_int32  hsize,
+             l_int32  vsize)
+{
+PIX  *pixt;
+SEL  *sel, *selh, *selv;
+
+    PROCNAME("pixOpenBrick");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+    if (hsize == 1 || vsize == 1) {  /* no intermediate result */
+        sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
+        pixd = pixOpen(pixd, pixs, sel);
+        selDestroy(&sel);
+    } else {  /* do separably */
+        selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT);
+        selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT);
+        pixt = pixErode(NULL, pixs, selh);
+        pixd = pixErode(pixd, pixt, selv);
+        pixDilate(pixt, pixd, selh);
+        pixDilate(pixd, pixt, selv);
+        pixDestroy(&pixt);
+        selDestroy(&selh);
+        selDestroy(&selv);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixCloseBrick()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
+ *      (3) Do separably if both hsize and vsize are > 1.
+ *      (4) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (5) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixCloseBrick(NULL, pixs, ...);
+ *          (b) pixCloseBrick(pixs, pixs, ...);
+ *          (c) pixCloseBrick(pixd, pixs, ...);
+ *      (6) The size of the result is determined by pixs.
+ */
+PIX *
+pixCloseBrick(PIX     *pixd,
+              PIX     *pixs,
+              l_int32  hsize,
+              l_int32  vsize)
+{
+PIX  *pixt;
+SEL  *sel, *selh, *selv;
+
+    PROCNAME("pixCloseBrick");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+    if (hsize == 1 || vsize == 1) {  /* no intermediate result */
+        sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
+        pixd = pixClose(pixd, pixs, sel);
+        selDestroy(&sel);
+    } else {  /* do separably */
+        selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT);
+        selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT);
+        pixt = pixDilate(NULL, pixs, selh);
+        pixd = pixDilate(pixd, pixt, selv);
+        pixErode(pixt, pixd, selh);
+        pixErode(pixd, pixt, selv);
+        pixDestroy(&pixt);
+        selDestroy(&selh);
+        selDestroy(&selv);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixCloseSafeBrick()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
+ *      (3) Do separably if both hsize and vsize are > 1.
+ *      (4) Safe closing adds a border of 0 pixels, of sufficient size so
+ *          that all pixels in input image are processed within
+ *          32-bit words in the expanded image.  As a result, there is
+ *          no special processing for pixels near the boundary, and there
+ *          are no boundary effects.  The border is removed at the end.
+ *      (5) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (6) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixCloseBrick(NULL, pixs, ...);
+ *          (b) pixCloseBrick(pixs, pixs, ...);
+ *          (c) pixCloseBrick(pixd, pixs, ...);
+ *      (7) The size of the result is determined by pixs.
+ */
+PIX *
+pixCloseSafeBrick(PIX     *pixd,
+                  PIX     *pixs,
+                  l_int32  hsize,
+                  l_int32  vsize)
+{
+l_int32  maxtrans, bordsize;
+PIX     *pixsb, *pixt, *pixdb;
+SEL     *sel, *selh, *selv;
+
+    PROCNAME("pixCloseSafeBrick");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+        /* Symmetric b.c. handles correctly without added pixels */
+    if (MORPH_BC == SYMMETRIC_MORPH_BC)
+        return pixCloseBrick(pixd, pixs, hsize, vsize);
+
+    maxtrans = L_MAX(hsize / 2, vsize / 2);
+    bordsize = 32 * ((maxtrans + 31) / 32);  /* full 32 bit words */
+    pixsb = pixAddBorder(pixs, bordsize, 0);
+
+    if (hsize == 1 || vsize == 1) {  /* no intermediate result */
+        sel = selCreateBrick(vsize, hsize, vsize / 2, hsize / 2, SEL_HIT);
+        pixdb = pixClose(NULL, pixsb, sel);
+        selDestroy(&sel);
+    } else {  /* do separably */
+        selh = selCreateBrick(1, hsize, 0, hsize / 2, SEL_HIT);
+        selv = selCreateBrick(vsize, 1, vsize / 2, 0, SEL_HIT);
+        pixt = pixDilate(NULL, pixsb, selh);
+        pixdb = pixDilate(NULL, pixt, selv);
+        pixErode(pixt, pixdb, selh);
+        pixErode(pixdb, pixt, selv);
+        pixDestroy(&pixt);
+        selDestroy(&selh);
+        selDestroy(&selv);
+    }
+
+    pixt = pixRemoveBorder(pixdb, bordsize);
+    pixDestroy(&pixsb);
+    pixDestroy(&pixdb);
+
+    if (!pixd) {
+        pixd = pixt;
+    } else {
+        pixCopy(pixd, pixt);
+        pixDestroy(&pixt);
+    }
+
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *     Binary composed morphological (raster) ops with brick Sels  *
+ *-----------------------------------------------------------------*/
+/*  selectComposableSels()
+ *
+ *      Input:  size (of composed sel)
+ *              direction (L_HORIZ, L_VERT)
+ *              &sel1 (<optional return> contiguous sel; can be null)
+ *              &sel2 (<optional return> comb sel; can be null)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) When using composable Sels, where the original Sel is
+ *          decomposed into two, the best you can do in terms
+ *          of reducing the computation is by a factor:
+ *
+ *               2 * sqrt(size) / size
+ *
+ *          In practice, you get quite close to this.  E.g.,
+ *
+ *             Sel size     |   Optimum reduction factor
+ *             --------         ------------------------
+ *                36        |          1/3
+ *                64        |          1/4
+ *               144        |          1/6
+ *               256        |          1/8
+ */
+l_int32
+selectComposableSels(l_int32  size,
+                     l_int32  direction,
+                     SEL    **psel1,
+                     SEL    **psel2)
+{
+l_int32  factor1, factor2;
+
+    PROCNAME("selectComposableSels");
+
+    if (!psel1 && !psel2)
+        return ERROR_INT("neither &sel1 nor &sel2 are defined", procName, 1);
+    if (psel1) *psel1 = NULL;
+    if (psel2) *psel2 = NULL;
+    if (size < 1 || size > 250 * 250)
+        return ERROR_INT("size < 1", procName, 1);
+    if (direction != L_HORIZ && direction != L_VERT)
+        return ERROR_INT("invalid direction", procName, 1);
+
+    if (selectComposableSizes(size, &factor1, &factor2))
+        return ERROR_INT("factors not found", procName, 1);
+
+    if (psel1) {
+        if (direction == L_HORIZ)
+            *psel1 = selCreateBrick(1, factor1, 0, factor1 / 2, SEL_HIT);
+        else
+            *psel1 = selCreateBrick(factor1, 1, factor1 / 2 , 0, SEL_HIT);
+    }
+    if (psel2)
+        *psel2 = selCreateComb(factor1, factor2, direction);
+    return 0;
+}
+
+
+/*!
+ *  selectComposableSizes()
+ *
+ *      Input:  size (of sel to be decomposed)
+ *              &factor1 (<return> larger factor)
+ *              &factor2 (<return> smaller factor)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This works for Sel sizes up to 62500, which seems sufficient.
+ *      (2) The composable sel size is typically within +- 1 of
+ *          the requested size.  Up to size = 300, the maximum difference
+ *          is +- 2.
+ *      (3) We choose an overall cost function where the penalty for
+ *          the size difference between input and actual is 4 times
+ *          the penalty for additional rasterops.
+ *      (4) Returned values: factor1 >= factor2
+ *          If size > 1, then factor1 > 1.
+ */
+l_int32
+selectComposableSizes(l_int32   size,
+                      l_int32  *pfactor1,
+                      l_int32  *pfactor2)
+{
+l_int32  i, midval, val1, val2m, val2p;
+l_int32  index, prodm, prodp;
+l_int32  mincost, totcost, rastcostm, rastcostp, diffm, diffp;
+l_int32  lowval[256];
+l_int32  hival[256];
+l_int32  rastcost[256];  /* excess in sum of sizes (extra rasterops) */
+l_int32  diff[256];  /* diff between product (sel size) and input size */
+
+    PROCNAME("selectComposableSizes");
+
+    if (size < 1 || size > 250 * 250)
+        return ERROR_INT("size < 1", procName, 1);
+    if (!pfactor1 || !pfactor2)
+        return ERROR_INT("&factor1 or &factor2 not defined", procName, 1);
+
+    midval = (l_int32)(sqrt((l_float64)size) + 0.001);
+    if (midval * midval == size) {
+        *pfactor1 = *pfactor2 = midval;
+        return 0;
+    }
+
+        /* Set up arrays.  For each val1, optimize for lowest diff,
+         * and save the rastcost, the diff, and the two factors. */
+    for (val1 = midval + 1, i = 0; val1 > 0; val1--, i++) {
+        val2m = size / val1;
+        val2p = val2m + 1;
+        prodm = val1 * val2m;
+        prodp = val1 * val2p;
+        rastcostm = val1 + val2m - 2 * midval;
+        rastcostp = val1 + val2p - 2 * midval;
+        diffm = L_ABS(size - prodm);
+        diffp = L_ABS(size - prodp);
+        if (diffm <= diffp) {
+            lowval[i] = L_MIN(val1, val2m);
+            hival[i] = L_MAX(val1, val2m);
+            rastcost[i] = rastcostm;
+            diff[i] = diffm;
+        } else {
+            lowval[i] = L_MIN(val1, val2p);
+            hival[i] = L_MAX(val1, val2p);
+            rastcost[i] = rastcostp;
+            diff[i] = diffp;
+        }
+    }
+
+        /* Choose the optimum factors; use cost ratio 4 on diff */
+    mincost = 10000;
+    for (i = 0; i < midval + 1; i++) {
+        if (diff[i] == 0 && rastcost[i] < ACCEPTABLE_COST) {
+            *pfactor1 = hival[i];
+            *pfactor2 = lowval[i];
+            return 0;
+        }
+        totcost = 4 * diff[i] + rastcost[i];
+        if (totcost < mincost) {
+            mincost = totcost;
+            index = i;
+        }
+    }
+    *pfactor1 = hival[index];
+    *pfactor2 = lowval[index];
+
+    return 0;
+}
+
+
+/*!
+ *  pixDilateCompBrick()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
+ *      (3) Do compositely for each dimension > 1.
+ *      (4) Do separably if both hsize and vsize are > 1.
+ *      (5) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (6) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixDilateCompBrick(NULL, pixs, ...);
+ *          (b) pixDilateCompBrick(pixs, pixs, ...);
+ *          (c) pixDilateCompBrick(pixd, pixs, ...);
+ *      (7) The dimensions of the resulting image are determined by pixs.
+ *      (8) CAUTION: both hsize and vsize are being decomposed.
+ *          The decomposer chooses a product of sizes (call them
+ *          'terms') for each that is close to the input size,
+ *          but not necessarily equal to it.  It attempts to optimize:
+ *             (a) for consistency with the input values: the product
+ *                 of terms is close to the input size
+ *             (b) for efficiency of the operation: the sum of the
+ *                 terms is small; ideally about twice the square
+ *                 root of the input size.
+ *          So, for example, if the input hsize = 37, which is
+ *          a prime number, the decomposer will break this into two
+ *          terms, 6 and 6, so that the net result is a dilation
+ *          with hsize = 36.
+ */
+PIX *
+pixDilateCompBrick(PIX     *pixd,
+                   PIX     *pixs,
+                   l_int32  hsize,
+                   l_int32  vsize)
+{
+PIX  *pixt1, *pixt2, *pixt3;
+SEL  *selh1, *selh2, *selv1, *selv2;
+
+    PROCNAME("pixDilateCompBrick");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    pixt1 = pixAddBorder(pixs, 32, 0);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+    if (hsize > 1)
+        selectComposableSels(hsize, L_HORIZ, &selh1, &selh2);
+    if (vsize > 1)
+        selectComposableSels(vsize, L_VERT, &selv1, &selv2);
+    if (vsize == 1) {
+        pixt2 = pixDilate(NULL, pixt1, selh1);
+        pixt3 = pixDilate(NULL, pixt2, selh2);
+    } else if (hsize == 1) {
+        pixt2 = pixDilate(NULL, pixt1, selv1);
+        pixt3 = pixDilate(NULL, pixt2, selv2);
+    } else {
+        pixt2 = pixDilate(NULL, pixt1, selh1);
+        pixt3 = pixDilate(NULL, pixt2, selh2);
+        pixDilate(pixt2, pixt3, selv1);
+        pixDilate(pixt3, pixt2, selv2);
+    }
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (hsize > 1) {
+        selDestroy(&selh1);
+        selDestroy(&selh2);
+    }
+    if (vsize > 1) {
+        selDestroy(&selv1);
+        selDestroy(&selv2);
+    }
+
+    pixt1 = pixRemoveBorder(pixt3, 32);
+    pixDestroy(&pixt3);
+    if (!pixd)
+        return pixt1;
+    pixCopy(pixd, pixt1);
+    pixDestroy(&pixt1);
+    return pixd;
+}
+
+
+/*!
+ *  pixErodeCompBrick()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
+ *      (3) Do compositely for each dimension > 1.
+ *      (4) Do separably if both hsize and vsize are > 1.
+ *      (5) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (6) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixErodeCompBrick(NULL, pixs, ...);
+ *          (b) pixErodeCompBrick(pixs, pixs, ...);
+ *          (c) pixErodeCompBrick(pixd, pixs, ...);
+ *      (7) The dimensions of the resulting image are determined by pixs.
+ *      (8) CAUTION: both hsize and vsize are being decomposed.
+ *          The decomposer chooses a product of sizes (call them
+ *          'terms') for each that is close to the input size,
+ *          but not necessarily equal to it.  It attempts to optimize:
+ *             (a) for consistency with the input values: the product
+ *                 of terms is close to the input size
+ *             (b) for efficiency of the operation: the sum of the
+ *                 terms is small; ideally about twice the square
+ *                 root of the input size.
+ *          So, for example, if the input hsize = 37, which is
+ *          a prime number, the decomposer will break this into two
+ *          terms, 6 and 6, so that the net result is a dilation
+ *          with hsize = 36.
+ */
+PIX *
+pixErodeCompBrick(PIX     *pixd,
+                  PIX     *pixs,
+                  l_int32  hsize,
+                  l_int32  vsize)
+{
+PIX  *pixt;
+SEL  *selh1, *selh2, *selv1, *selv2;
+
+    PROCNAME("pixErodeCompBrick");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+    if (hsize > 1)
+        selectComposableSels(hsize, L_HORIZ, &selh1, &selh2);
+    if (vsize > 1)
+        selectComposableSels(vsize, L_VERT, &selv1, &selv2);
+    if (vsize == 1) {
+        pixt = pixErode(NULL, pixs, selh1);
+        pixd = pixErode(pixd, pixt, selh2);
+    } else if (hsize == 1) {
+        pixt = pixErode(NULL, pixs, selv1);
+        pixd = pixErode(pixd, pixt, selv2);
+    } else {
+        pixt = pixErode(NULL, pixs, selh1);
+        pixd = pixErode(pixd, pixt, selh2);
+        pixErode(pixt, pixd, selv1);
+        pixErode(pixd, pixt, selv2);
+    }
+    pixDestroy(&pixt);
+
+    if (hsize > 1) {
+        selDestroy(&selh1);
+        selDestroy(&selh2);
+    }
+    if (vsize > 1) {
+        selDestroy(&selv1);
+        selDestroy(&selv2);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixOpenCompBrick()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
+ *      (3) Do compositely for each dimension > 1.
+ *      (4) Do separably if both hsize and vsize are > 1.
+ *      (5) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (6) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixOpenCompBrick(NULL, pixs, ...);
+ *          (b) pixOpenCompBrick(pixs, pixs, ...);
+ *          (c) pixOpenCompBrick(pixd, pixs, ...);
+ *      (7) The dimensions of the resulting image are determined by pixs.
+ *      (8) CAUTION: both hsize and vsize are being decomposed.
+ *          The decomposer chooses a product of sizes (call them
+ *          'terms') for each that is close to the input size,
+ *          but not necessarily equal to it.  It attempts to optimize:
+ *             (a) for consistency with the input values: the product
+ *                 of terms is close to the input size
+ *             (b) for efficiency of the operation: the sum of the
+ *                 terms is small; ideally about twice the square
+ *                 root of the input size.
+ *          So, for example, if the input hsize = 37, which is
+ *          a prime number, the decomposer will break this into two
+ *          terms, 6 and 6, so that the net result is a dilation
+ *          with hsize = 36.
+ */
+PIX *
+pixOpenCompBrick(PIX     *pixd,
+                 PIX     *pixs,
+                 l_int32  hsize,
+                 l_int32  vsize)
+{
+PIX  *pixt;
+SEL  *selh1, *selh2, *selv1, *selv2;
+
+    PROCNAME("pixOpenCompBrick");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+    if (hsize > 1)
+        selectComposableSels(hsize, L_HORIZ, &selh1, &selh2);
+    if (vsize > 1)
+        selectComposableSels(vsize, L_VERT, &selv1, &selv2);
+    if (vsize == 1) {
+        pixt = pixErode(NULL, pixs, selh1);
+        pixd = pixErode(pixd, pixt, selh2);
+        pixDilate(pixt, pixd, selh1);
+        pixDilate(pixd, pixt, selh2);
+    } else if (hsize == 1) {
+        pixt = pixErode(NULL, pixs, selv1);
+        pixd = pixErode(pixd, pixt, selv2);
+        pixDilate(pixt, pixd, selv1);
+        pixDilate(pixd, pixt, selv2);
+    } else {  /* do separably */
+        pixt = pixErode(NULL, pixs, selh1);
+        pixd = pixErode(pixd, pixt, selh2);
+        pixErode(pixt, pixd, selv1);
+        pixErode(pixd, pixt, selv2);
+        pixDilate(pixt, pixd, selh1);
+        pixDilate(pixd, pixt, selh2);
+        pixDilate(pixt, pixd, selv1);
+        pixDilate(pixd, pixt, selv2);
+    }
+    pixDestroy(&pixt);
+
+    if (hsize > 1) {
+        selDestroy(&selh1);
+        selDestroy(&selh2);
+    }
+    if (vsize > 1) {
+        selDestroy(&selv1);
+        selDestroy(&selv2);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixCloseCompBrick()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
+ *      (3) Do compositely for each dimension > 1.
+ *      (4) Do separably if both hsize and vsize are > 1.
+ *      (5) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (6) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixCloseCompBrick(NULL, pixs, ...);
+ *          (b) pixCloseCompBrick(pixs, pixs, ...);
+ *          (c) pixCloseCompBrick(pixd, pixs, ...);
+ *      (7) The dimensions of the resulting image are determined by pixs.
+ *      (8) CAUTION: both hsize and vsize are being decomposed.
+ *          The decomposer chooses a product of sizes (call them
+ *          'terms') for each that is close to the input size,
+ *          but not necessarily equal to it.  It attempts to optimize:
+ *             (a) for consistency with the input values: the product
+ *                 of terms is close to the input size
+ *             (b) for efficiency of the operation: the sum of the
+ *                 terms is small; ideally about twice the square
+ *                 root of the input size.
+ *          So, for example, if the input hsize = 37, which is
+ *          a prime number, the decomposer will break this into two
+ *          terms, 6 and 6, so that the net result is a dilation
+ *          with hsize = 36.
+ */
+PIX *
+pixCloseCompBrick(PIX     *pixd,
+                  PIX     *pixs,
+                  l_int32  hsize,
+                  l_int32  vsize)
+{
+PIX  *pixt;
+SEL  *selh1, *selh2, *selv1, *selv2;
+
+    PROCNAME("pixCloseCompBrick");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+    if (hsize > 1)
+        selectComposableSels(hsize, L_HORIZ, &selh1, &selh2);
+    if (vsize > 1)
+        selectComposableSels(vsize, L_VERT, &selv1, &selv2);
+    if (vsize == 1) {
+        pixt = pixDilate(NULL, pixs, selh1);
+        pixd = pixDilate(pixd, pixt, selh2);
+        pixErode(pixt, pixd, selh1);
+        pixErode(pixd, pixt, selh2);
+    } else if (hsize == 1) {
+        pixt = pixDilate(NULL, pixs, selv1);
+        pixd = pixDilate(pixd, pixt, selv2);
+        pixErode(pixt, pixd, selv1);
+        pixErode(pixd, pixt, selv2);
+    } else {  /* do separably */
+        pixt = pixDilate(NULL, pixs, selh1);
+        pixd = pixDilate(pixd, pixt, selh2);
+        pixDilate(pixt, pixd, selv1);
+        pixDilate(pixd, pixt, selv2);
+        pixErode(pixt, pixd, selh1);
+        pixErode(pixd, pixt, selh2);
+        pixErode(pixt, pixd, selv1);
+        pixErode(pixd, pixt, selv2);
+    }
+    pixDestroy(&pixt);
+
+    if (hsize > 1) {
+        selDestroy(&selh1);
+        selDestroy(&selh2);
+    }
+    if (vsize > 1) {
+        selDestroy(&selv1);
+        selDestroy(&selv2);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixCloseSafeCompBrick()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) The origin is at (x, y) = (hsize/2, vsize/2)
+ *      (3) Do compositely for each dimension > 1.
+ *      (4) Do separably if both hsize and vsize are > 1.
+ *      (5) Safe closing adds a border of 0 pixels, of sufficient size so
+ *          that all pixels in input image are processed within
+ *          32-bit words in the expanded image.  As a result, there is
+ *          no special processing for pixels near the boundary, and there
+ *          are no boundary effects.  The border is removed at the end.
+ *      (6) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (7) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixCloseSafeCompBrick(NULL, pixs, ...);
+ *          (b) pixCloseSafeCompBrick(pixs, pixs, ...);
+ *          (c) pixCloseSafeCompBrick(pixd, pixs, ...);
+ *      (8) The dimensions of the resulting image are determined by pixs.
+ *      (9) CAUTION: both hsize and vsize are being decomposed.
+ *          The decomposer chooses a product of sizes (call them
+ *          'terms') for each that is close to the input size,
+ *          but not necessarily equal to it.  It attempts to optimize:
+ *             (a) for consistency with the input values: the product
+ *                 of terms is close to the input size
+ *             (b) for efficiency of the operation: the sum of the
+ *                 terms is small; ideally about twice the square
+ *                 root of the input size.
+ *          So, for example, if the input hsize = 37, which is
+ *          a prime number, the decomposer will break this into two
+ *          terms, 6 and 6, so that the net result is a dilation
+ *          with hsize = 36.
+ */
+PIX *
+pixCloseSafeCompBrick(PIX     *pixd,
+                      PIX     *pixs,
+                      l_int32  hsize,
+                      l_int32  vsize)
+{
+l_int32  maxtrans, bordsize;
+PIX     *pixsb, *pixt, *pixdb;
+SEL     *selh1, *selh2, *selv1, *selv2;
+
+    PROCNAME("pixCloseSafeCompBrick");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+        /* Symmetric b.c. handles correctly without added pixels */
+    if (MORPH_BC == SYMMETRIC_MORPH_BC)
+        return pixCloseCompBrick(pixd, pixs, hsize, vsize);
+
+    maxtrans = L_MAX(hsize / 2, vsize / 2);
+    bordsize = 32 * ((maxtrans + 31) / 32);  /* full 32 bit words */
+    pixsb = pixAddBorder(pixs, bordsize, 0);
+
+    if (hsize > 1)
+        selectComposableSels(hsize, L_HORIZ, &selh1, &selh2);
+    if (vsize > 1)
+        selectComposableSels(vsize, L_VERT, &selv1, &selv2);
+    if (vsize == 1) {
+        pixt = pixDilate(NULL, pixsb, selh1);
+        pixdb = pixDilate(NULL, pixt, selh2);
+        pixErode(pixt, pixdb, selh1);
+        pixErode(pixdb, pixt, selh2);
+    } else if (hsize == 1) {
+        pixt = pixDilate(NULL, pixsb, selv1);
+        pixdb = pixDilate(NULL, pixt, selv2);
+        pixErode(pixt, pixdb, selv1);
+        pixErode(pixdb, pixt, selv2);
+    } else {  /* do separably */
+        pixt = pixDilate(NULL, pixsb, selh1);
+        pixdb = pixDilate(NULL, pixt, selh2);
+        pixDilate(pixt, pixdb, selv1);
+        pixDilate(pixdb, pixt, selv2);
+        pixErode(pixt, pixdb, selh1);
+        pixErode(pixdb, pixt, selh2);
+        pixErode(pixt, pixdb, selv1);
+        pixErode(pixdb, pixt, selv2);
+    }
+    pixDestroy(&pixt);
+
+    pixt = pixRemoveBorder(pixdb, bordsize);
+    pixDestroy(&pixsb);
+    pixDestroy(&pixdb);
+
+    if (!pixd) {
+        pixd = pixt;
+    } else {
+        pixCopy(pixd, pixt);
+        pixDestroy(&pixt);
+    }
+
+    if (hsize > 1) {
+        selDestroy(&selh1);
+        selDestroy(&selh2);
+    }
+    if (vsize > 1) {
+        selDestroy(&selv1);
+        selDestroy(&selv2);
+    }
+
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *           Functions associated with boundary conditions         *
+ *-----------------------------------------------------------------*/
+/*!
+ *  resetMorphBoundaryCondition()
+ *
+ *      Input:  bc (SYMMETRIC_MORPH_BC, ASYMMETRIC_MORPH_BC)
+ *      Return: void
+ */
+void
+resetMorphBoundaryCondition(l_int32  bc)
+{
+    PROCNAME("resetMorphBoundaryCondition");
+
+    if (bc != SYMMETRIC_MORPH_BC && bc != ASYMMETRIC_MORPH_BC) {
+        L_WARNING("invalid bc; using asymmetric\n", procName);
+        bc = ASYMMETRIC_MORPH_BC;
+    }
+    MORPH_BC = bc;
+    return;
+}
+
+
+/*!
+ *  getMorphBorderPixelColor()
+ *
+ *      Input:  type (L_MORPH_DILATE, L_MORPH_ERODE)
+ *              depth (of pix)
+ *      Return: color of border pixels for this operation
+ */
+l_uint32
+getMorphBorderPixelColor(l_int32  type,
+                         l_int32  depth)
+{
+    PROCNAME("getMorphBorderPixelColor");
+
+    if (type != L_MORPH_DILATE && type != L_MORPH_ERODE)
+        return ERROR_INT("invalid type", procName, 0);
+    if (depth != 1 && depth != 2 && depth != 4 && depth != 8 &&
+        depth != 16 && depth != 32)
+        return ERROR_INT("invalid depth", procName, 0);
+
+    if (MORPH_BC == ASYMMETRIC_MORPH_BC || type == L_MORPH_DILATE)
+        return 0;
+
+        /* Symmetric & erosion */
+    if (depth < 32)
+        return ((1 << depth) - 1);
+    else  /* depth == 32 */
+        return 0xffffff00;
+}
+
+
+/*-----------------------------------------------------------------*
+ *               Static helpers for arg processing                 *
+ *-----------------------------------------------------------------*/
+/*!
+ *  processMorphArgs1()
+ *
+ *      Input:  pixd (<optional>; this can be null, equal to pixs,
+ *                    or different from pixs)
+ *              pixs (1 bpp)
+ *              sel
+ *              &pixt (<returned>)
+ *      Return: pixd, or null on error.
+ *
+ *  Notes:
+ *      (1) This is used for generic erosion, dilation and HMT.
+ */
+static PIX *
+processMorphArgs1(PIX   *pixd,
+                  PIX   *pixs,
+                  SEL   *sel,
+                  PIX  **ppixt)
+{
+l_int32  sx, sy;
+
+    PROCNAME("processMorphArgs1");
+
+    if (!ppixt)
+        return (PIX *)ERROR_PTR("&pixt not defined", procName, pixd);
+    *ppixt = NULL;
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (!sel)
+        return (PIX *)ERROR_PTR("sel not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+
+    selGetParameters(sel, &sx, &sy, NULL, NULL);
+    if (sx == 0 || sy == 0)
+        return (PIX *)ERROR_PTR("sel of size 0", procName, pixd);
+
+        /* We require pixd to exist and to be the same size as pixs.
+         * Further, pixt must be a copy (or clone) of pixs.  */
+    if (!pixd) {
+        if ((pixd = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+        *ppixt = pixClone(pixs);
+    } else {
+        pixResizeImageData(pixd, pixs);
+        if (pixd == pixs) {  /* in-place; must make a copy of pixs */
+            if ((*ppixt = pixCopy(NULL, pixs)) == NULL)
+                return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+        } else {
+            *ppixt = pixClone(pixs);
+        }
+    }
+    return pixd;
+}
+
+
+/*!
+ *  processMorphArgs2()
+ *
+ *  This is used for generic openings and closings.
+ */
+static PIX *
+processMorphArgs2(PIX   *pixd,
+                  PIX   *pixs,
+                  SEL   *sel)
+{
+l_int32  sx, sy;
+
+    PROCNAME("processMorphArgs2");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (!sel)
+        return (PIX *)ERROR_PTR("sel not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+
+    selGetParameters(sel, &sx, &sy, NULL, NULL);
+    if (sx == 0 || sy == 0)
+        return (PIX *)ERROR_PTR("sel of size 0", procName, pixd);
+
+    if (!pixd)
+        return pixCreateTemplate(pixs);
+    pixResizeImageData(pixd, pixs);
+    return pixd;
+}
diff --git a/src/morph.h b/src/morph.h
new file mode 100644 (file)
index 0000000..bf02740
--- /dev/null
@@ -0,0 +1,229 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_MORPH_H
+#define  LEPTONICA_MORPH_H
+
+/* 
+ *  morph.h
+ *
+ *  Contains the following structs:
+ *      struct Sel
+ *      struct Sela
+ *      struct Kernel
+ *
+ *  Contains definitions for:
+ *      morphological b.c. flags
+ *      structuring element types
+ *      runlength flags for granulometry
+ *      direction flags for grayscale morphology
+ *      morphological operation flags
+ *      standard border size
+ *      grayscale intensity scaling flags
+ *      morphological tophat flags
+ *      arithmetic and logical operator flags
+ *      grayscale morphology selection flags
+ *      distance function b.c. flags
+ *      image comparison flags
+ *      color content flags
+ */
+
+/*-------------------------------------------------------------------------*
+ *                             Sel and Sel array                           *
+ *-------------------------------------------------------------------------*/
+#define  SEL_VERSION_NUMBER    1
+
+struct Sel
+{
+    l_int32       sy;          /* sel height                               */
+    l_int32       sx;          /* sel width                                */
+    l_int32       cy;          /* y location of sel origin                 */
+    l_int32       cx;          /* x location of sel origin                 */
+    l_int32     **data;        /* {0,1,2}; data[i][j] in [row][col] order  */
+    char         *name;        /* used to find sel by name                 */
+};
+typedef struct Sel SEL;
+
+struct Sela
+{
+    l_int32          n;         /* number of sel actually stored           */
+    l_int32          nalloc;    /* size of allocated ptr array             */
+    struct Sel     **sel;       /* sel ptr array                           */
+};
+typedef struct Sela SELA;
+
+
+/*-------------------------------------------------------------------------*
+ *                                 Kernel                                  *
+ *-------------------------------------------------------------------------*/
+#define  KERNEL_VERSION_NUMBER    2
+
+struct L_Kernel
+{
+    l_int32       sy;          /* kernel height                            */
+    l_int32       sx;          /* kernel width                             */
+    l_int32       cy;          /* y location of kernel origin              */
+    l_int32       cx;          /* x location of kernel origin              */
+    l_float32   **data;        /* data[i][j] in [row][col] order           */
+};
+typedef struct L_Kernel  L_KERNEL;
+
+
+/*-------------------------------------------------------------------------*
+ *                 Morphological boundary condition flags                  *
+ *
+ *  Two types of boundary condition for erosion.
+ *  The global variable MORPH_BC takes on one of these two values.
+ *  See notes in morph.c for usage.
+ *-------------------------------------------------------------------------*/
+enum {
+    SYMMETRIC_MORPH_BC = 0,
+    ASYMMETRIC_MORPH_BC = 1
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                        Structuring element types                        *
+ *-------------------------------------------------------------------------*/
+enum {
+    SEL_DONT_CARE  = 0,
+    SEL_HIT        = 1,
+    SEL_MISS       = 2
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                  Runlength flags for granulometry                       *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_RUN_OFF = 0,
+    L_RUN_ON  = 1
+};
+
+
+/*-------------------------------------------------------------------------*
+ *         Direction flags for grayscale morphology, granulometry,         *
+ *                 composable Sels, convolution, etc.                      *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_HORIZ            = 1,
+    L_VERT             = 2,
+    L_BOTH_DIRECTIONS  = 3
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                   Morphological operation flags                         *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_MORPH_DILATE    = 1,
+    L_MORPH_ERODE     = 2,
+    L_MORPH_OPEN      = 3,
+    L_MORPH_CLOSE     = 4,
+    L_MORPH_HMT       = 5
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                    Grayscale intensity scaling flags                    *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_LINEAR_SCALE  = 1,
+    L_LOG_SCALE     = 2
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                      Morphological tophat flags                         *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_TOPHAT_WHITE = 0,
+    L_TOPHAT_BLACK = 1
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                Arithmetic and logical operator flags                    *
+ *                 (use on grayscale images and Numas)                     *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_ARITH_ADD       = 1,
+    L_ARITH_SUBTRACT  = 2,
+    L_ARITH_MULTIPLY  = 3,   /* on numas only */
+    L_ARITH_DIVIDE    = 4,   /* on numas only */
+    L_UNION           = 5,   /* on numas only */
+    L_INTERSECTION    = 6,   /* on numas only */
+    L_SUBTRACTION     = 7,   /* on numas only */
+    L_EXCLUSIVE_OR    = 8    /* on numas only */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                        Min/max selection flags                          *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_CHOOSE_MIN = 1,           /* useful in a downscaling "erosion"  */
+    L_CHOOSE_MAX = 2,           /* useful in a downscaling "dilation" */
+    L_CHOOSE_MAX_MIN_DIFF = 3   /* useful in a downscaling contrast   */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                    Distance function b.c. flags                         *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_BOUNDARY_BG = 1,  /* assume bg outside image */
+    L_BOUNDARY_FG = 2   /* assume fg outside image */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                         Image comparison flags                          *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_COMPARE_XOR = 1,
+    L_COMPARE_SUBTRACT = 2,
+    L_COMPARE_ABS_DIFF = 3
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                          Color content flags                            *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_MAX_DIFF_FROM_AVERAGE_2 = 1,
+    L_MAX_MIN_DIFF_FROM_2 = 2,
+    L_MAX_DIFF = 3
+};
+
+
+/*-------------------------------------------------------------------------*
+ *    Standard size of border added around images for special processing   *
+ *-------------------------------------------------------------------------*/
+static const l_int32  ADDED_BORDER = 32;   /* pixels, not bits */
+
+
+#endif  /* LEPTONICA_MORPH_H */
diff --git a/src/morphapp.c b/src/morphapp.c
new file mode 100644 (file)
index 0000000..b01157f
--- /dev/null
@@ -0,0 +1,1524 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  morphapp.c
+ *
+ *      These are some useful and/or interesting composite
+ *      image processing operations, of the type that are often
+ *      useful in applications.  Most are morphological in
+ *      nature.
+ *
+ *      Extraction of boundary pixels
+ *            PIX       *pixExtractBoundary()
+ *
+ *      Selective morph sequence operation under mask
+ *            PIX       *pixMorphSequenceMasked()
+ *
+ *      Selective morph sequence operation on each component
+ *            PIX       *pixMorphSequenceByComponent()
+ *            PIXA      *pixaMorphSequenceByComponent()
+ *
+ *      Selective morph sequence operation on each region
+ *            PIX       *pixMorphSequenceByRegion()
+ *            PIXA      *pixaMorphSequenceByRegion()
+ *
+ *      Union and intersection of parallel composite operations
+ *            PIX       *pixUnionOfMorphOps()
+ *            PIX       *pixIntersectionOfMorphOps()
+ *
+ *      Selective connected component filling
+ *            PIX       *pixSelectiveConnCompFill()
+ *
+ *      Removal of matched patterns
+ *            PIX       *pixRemoveMatchedPattern()
+ *
+ *      Display of matched patterns
+ *            PIX       *pixDisplayMatchedPattern()
+ *
+ *      Extension of pixa by iterative erosion or dilation
+ *            PIXA      *pixaExtendIterative()
+ *
+ *      Iterative morphological seed filling (don't use for real work)
+ *            PIX       *pixSeedfillMorph()
+ *
+ *      Granulometry on binary images
+ *            NUMA      *pixRunHistogramMorph()
+ *
+ *      Composite operations on grayscale images
+ *            PIX       *pixTophat()
+ *            PIX       *pixHDome()
+ *            PIX       *pixFastTophat()
+ *            PIX       *pixMorphGradient()
+ *
+ *      Centroid of component
+ *            PTA       *pixaCentroids()
+ *            l_int32    pixCentroid()
+ */
+
+#include "allheaders.h"
+
+#define   SWAP(x, y)   {temp = (x); (x) = (y); (y) = temp;}
+
+
+/*-----------------------------------------------------------------*
+ *                   Extraction of boundary pixels                 *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixExtractBoundary()
+ *
+ *      Input:  pixs (1 bpp)
+ *              type (0 for background pixels; 1 for foreground pixels)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Extracts the fg or bg boundary pixels for each component.
+ *          Components are assumed to end at the boundary of pixs.
+ */
+PIX *
+pixExtractBoundary(PIX     *pixs,
+                   l_int32  type)
+{
+PIX  *pixd;
+
+    PROCNAME("pixExtractBoundary");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    if (type == 0)
+        pixd = pixDilateBrick(NULL, pixs, 3, 3);
+    else
+        pixd = pixErodeBrick(NULL, pixs, 3, 3);
+    pixXor(pixd, pixd, pixs);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *           Selective morph sequence operation under mask         *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixMorphSequenceMasked()
+ *
+ *      Input:  pixs (1 bpp)
+ *              pixm (<optional> 1 bpp mask)
+ *              sequence (string specifying sequence of operations)
+ *              dispsep (horizontal separation in pixels between
+ *                       successive displays; use zero to suppress display)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This applies the morph sequence to the image, but only allows
+ *          changes in pixs for pixels under the background of pixm.
+ *      (5) If pixm is NULL, this is just pixMorphSequence().
+ */
+PIX *
+pixMorphSequenceMasked(PIX         *pixs,
+                       PIX         *pixm,
+                       const char  *sequence,
+                       l_int32      dispsep)
+{
+PIX  *pixd;
+
+    PROCNAME("pixMorphSequenceMasked");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!sequence)
+        return (PIX *)ERROR_PTR("sequence not defined", procName, NULL);
+
+    pixd = pixMorphSequence(pixs, sequence, dispsep);
+    pixCombineMasked(pixd, pixs, pixm);  /* restore src pixels under mask fg */
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *             Morph sequence operation on each component          *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixMorphSequenceByComponent()
+ *
+ *      Input:  pixs (1 bpp)
+ *              sequence (string specifying sequence)
+ *              connectivity (4 or 8)
+ *              minw  (minimum width to consider; use 0 or 1 for any width)
+ *              minh  (minimum height to consider; use 0 or 1 for any height)
+ *              &boxa (<optional> return boxa of c.c. in pixs)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) See pixMorphSequence() for composing operation sequences.
+ *      (2) This operates separately on each c.c. in the input pix.
+ *      (3) The dilation does NOT increase the c.c. size; it is clipped
+ *          to the size of the original c.c.   This is necessary to
+ *          keep the c.c. independent after the operation.
+ *      (4) You can specify that the width and/or height must equal
+ *          or exceed a minimum size for the operation to take place.
+ *      (5) Use NULL for boxa to avoid returning the boxa.
+ */
+PIX *
+pixMorphSequenceByComponent(PIX         *pixs,
+                            const char  *sequence,
+                            l_int32      connectivity,
+                            l_int32      minw,
+                            l_int32      minh,
+                            BOXA       **pboxa)
+{
+l_int32  n, i, x, y, w, h;
+BOXA    *boxa;
+PIX     *pix, *pixd;
+PIXA    *pixas, *pixad;
+
+    PROCNAME("pixMorphSequenceByComponent");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!sequence)
+        return (PIX *)ERROR_PTR("sequence not defined", procName, NULL);
+
+    if (minw <= 0) minw = 1;
+    if (minh <= 0) minh = 1;
+
+        /* Get the c.c. */
+    if ((boxa = pixConnComp(pixs, &pixas, connectivity)) == NULL)
+        return (PIX *)ERROR_PTR("boxa not made", procName, NULL);
+
+        /* Operate on each c.c. independently */
+    pixad = pixaMorphSequenceByComponent(pixas, sequence, minw, minh);
+    pixaDestroy(&pixas);
+    boxaDestroy(&boxa);
+    if (!pixad)
+        return (PIX *)ERROR_PTR("pixad not made", procName, NULL);
+
+        /* Display the result out into pixd */
+    pixd = pixCreateTemplate(pixs);
+    n = pixaGetCount(pixad);
+    for (i = 0; i < n; i++) {
+        pixaGetBoxGeometry(pixad, i, &x, &y, &w, &h);
+        pix = pixaGetPix(pixad, i, L_CLONE);
+        pixRasterop(pixd, x, y, w, h, PIX_PAINT, pix, 0, 0);
+        pixDestroy(&pix);
+    }
+
+    if (pboxa)
+        *pboxa = pixaGetBoxa(pixad, L_CLONE);
+    pixaDestroy(&pixad);
+    return pixd;
+}
+
+
+/*!
+ *  pixaMorphSequenceByComponent()
+ *
+ *      Input:  pixas (of 1 bpp pix)
+ *              sequence (string specifying sequence)
+ *              minw  (minimum width to consider; use 0 or 1 for any width)
+ *              minh  (minimum height to consider; use 0 or 1 for any height)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) See pixMorphSequence() for composing operation sequences.
+ *      (2) This operates separately on each c.c. in the input pixa.
+ *      (3) You can specify that the width and/or height must equal
+ *          or exceed a minimum size for the operation to take place.
+ *      (4) The input pixa should have a boxa giving the locations
+ *          of the pix components.
+ */
+PIXA *
+pixaMorphSequenceByComponent(PIXA        *pixas,
+                             const char  *sequence,
+                             l_int32      minw,
+                             l_int32      minh)
+{
+l_int32  n, i, w, h, d;
+BOX     *box;
+PIX     *pix1, *pix2;
+PIXA    *pixad;
+
+    PROCNAME("pixaMorphSequenceByComponent");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if ((n = pixaGetCount(pixas)) == 0)
+        return (PIXA *)ERROR_PTR("no pix in pixas", procName, NULL);
+    if (n != pixaGetBoxaCount(pixas))
+        L_WARNING("boxa size != n\n", procName);
+    pixaGetPixDimensions(pixas, 0, NULL, NULL, &d);
+    if (d != 1)
+        return (PIXA *)ERROR_PTR("depth not 1 bpp", procName, NULL);
+
+    if (!sequence)
+        return (PIXA *)ERROR_PTR("sequence not defined", procName, NULL);
+    if (minw <= 0) minw = 1;
+    if (minh <= 0) minh = 1;
+
+    if ((pixad = pixaCreate(n)) == NULL)
+        return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        pixaGetPixDimensions(pixas, i, &w, &h, NULL);
+        if (w >= minw && h >= minh) {
+            if ((pix1 = pixaGetPix(pixas, i, L_CLONE)) == NULL)
+                return (PIXA *)ERROR_PTR("pix1 not found", procName, NULL);
+            if ((pix2 = pixMorphCompSequence(pix1, sequence, 0)) == NULL)
+                return (PIXA *)ERROR_PTR("pix2 not made", procName, NULL);
+            pixaAddPix(pixad, pix2, L_INSERT);
+            box = pixaGetBox(pixas, i, L_COPY);
+            pixaAddBox(pixad, box, L_INSERT);
+            pixDestroy(&pix1);
+        }
+    }
+
+    return pixad;
+}
+
+
+/*-----------------------------------------------------------------*
+ *              Morph sequence operation on each region            *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixMorphSequenceByRegion()
+ *
+ *      Input:  pixs (1 bpp)
+ *              pixm (mask specifying regions)
+ *              sequence (string specifying sequence)
+ *              connectivity (4 or 8, used on mask)
+ *              minw  (minimum width to consider; use 0 or 1 for any width)
+ *              minh  (minimum height to consider; use 0 or 1 for any height)
+ *              &boxa (<optional> return boxa of c.c. in pixm)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) See pixMorphCompSequence() for composing operation sequences.
+ *      (2) This operates separately on the region in pixs corresponding
+ *          to each c.c. in the mask pixm.  It differs from
+ *          pixMorphSequenceByComponent() in that the latter does not have
+ *          a pixm (mask), but instead operates independently on each
+ *          component in pixs.
+ *      (3) Dilation will NOT increase the region size; the result
+ *          is clipped to the size of the mask region.  This is necessary
+ *          to make regions independent after the operation.
+ *      (4) You can specify that the width and/or height of a region must
+ *          equal or exceed a minimum size for the operation to take place.
+ *      (5) Use NULL for @pboxa to avoid returning the boxa.
+ */
+PIX *
+pixMorphSequenceByRegion(PIX         *pixs,
+                         PIX         *pixm,
+                         const char  *sequence,
+                         l_int32      connectivity,
+                         l_int32      minw,
+                         l_int32      minh,
+                         BOXA       **pboxa)
+{
+l_int32  n, i, x, y, w, h;
+BOXA    *boxa;
+PIX     *pix, *pixd;
+PIXA    *pixam, *pixad;
+
+    PROCNAME("pixMorphSequenceByRegion");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!pixm)
+        return (PIX *)ERROR_PTR("pixm not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1 || pixGetDepth(pixm) != 1)
+        return (PIX *)ERROR_PTR("pixs and pixm not both 1 bpp", procName, NULL);
+    if (!sequence)
+        return (PIX *)ERROR_PTR("sequence not defined", procName, NULL);
+
+    if (minw <= 0) minw = 1;
+    if (minh <= 0) minh = 1;
+
+        /* Get the c.c. of the mask */
+    if ((boxa = pixConnComp(pixm, &pixam, connectivity)) == NULL)
+        return (PIX *)ERROR_PTR("boxa not made", procName, NULL);
+
+        /* Operate on each region in pixs independently */
+    pixad = pixaMorphSequenceByRegion(pixs, pixam, sequence, minw, minh);
+    pixaDestroy(&pixam);
+    boxaDestroy(&boxa);
+    if (!pixad)
+        return (PIX *)ERROR_PTR("pixad not made", procName, NULL);
+
+        /* Display the result out into pixd */
+    pixd = pixCreateTemplate(pixs);
+    n = pixaGetCount(pixad);
+    for (i = 0; i < n; i++) {
+        pixaGetBoxGeometry(pixad, i, &x, &y, &w, &h);
+        pix = pixaGetPix(pixad, i, L_CLONE);
+        pixRasterop(pixd, x, y, w, h, PIX_PAINT, pix, 0, 0);
+        pixDestroy(&pix);
+    }
+
+    if (pboxa)
+        *pboxa = pixaGetBoxa(pixad, L_CLONE);
+    pixaDestroy(&pixad);
+    return pixd;
+}
+
+
+/*!
+ *  pixaMorphSequenceByRegion()
+ *
+ *      Input:  pixs (1 bpp)
+ *              pixam (of 1 bpp mask elements)
+ *              sequence (string specifying sequence)
+ *              minw  (minimum width to consider; use 0 or 1 for any width)
+ *              minh  (minimum height to consider; use 0 or 1 for any height)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) See pixMorphSequence() for composing operation sequences.
+ *      (2) This operates separately on each region in the input pixs
+ *          defined by the components in pixam.
+ *      (3) You can specify that the width and/or height of a mask
+ *          component must equal or exceed a minimum size for the
+ *          operation to take place.
+ *      (4) The input pixam should have a boxa giving the locations
+ *          of the regions in pixs.
+ */
+PIXA *
+pixaMorphSequenceByRegion(PIX         *pixs,
+                          PIXA        *pixam,
+                          const char  *sequence,
+                          l_int32      minw,
+                          l_int32      minh)
+{
+l_int32  n, i, w, h, samedepth, maxdepth, fullpa, fullba;
+BOX     *box;
+PIX     *pix1, *pix2, *pix3;
+PIXA    *pixad;
+
+    PROCNAME("pixaMorphSequenceByRegion");
+
+    if (!pixs)
+        return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIXA *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (!sequence)
+        return (PIXA *)ERROR_PTR("sequence not defined", procName, NULL);
+    if (!pixam)
+        return (PIXA *)ERROR_PTR("pixam not defined", procName, NULL);
+    samedepth = pixaVerifyDepth(pixam, &maxdepth);
+    if (samedepth != 1 && maxdepth != 1)
+        return (PIXA *)ERROR_PTR("mask depth not 1 bpp", procName, NULL);
+    pixaIsFull(pixam, &fullpa, &fullba);
+    if (!fullpa || !fullba)
+        return (PIXA *)ERROR_PTR("missing comps in pixam", procName, NULL);
+    n = pixaGetCount(pixam);
+    if (minw <= 0) minw = 1;
+    if (minh <= 0) minh = 1;
+
+    if ((pixad = pixaCreate(n)) == NULL)
+        return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);
+
+        /* Use the rectangle to remove the appropriate part of pixs;
+         * then AND with the mask component to get the actual fg
+         * of pixs that is under the mask component. */
+    for (i = 0; i < n; i++) {
+        pixaGetPixDimensions(pixam, i, &w, &h, NULL);
+        if (w >= minw && h >= minh) {
+            pix1 = pixaGetPix(pixam, i, L_CLONE);
+            box = pixaGetBox(pixam, i, L_COPY);
+            pix2 = pixClipRectangle(pixs, box, NULL);
+            pixAnd(pix2, pix2, pix1);
+            pix3 = pixMorphCompSequence(pix2, sequence, 0);
+            pixDestroy(&pix1);
+            pixDestroy(&pix2);
+            if (!pix3) {
+                boxDestroy(&box);
+                pixaDestroy(&pixad);
+                L_ERROR("pix3 not made in iter %d; aborting\n", procName, i);
+                break;
+            }
+            pixaAddPix(pixad, pix3, L_INSERT);
+            pixaAddBox(pixad, box, L_INSERT);
+        }
+    }
+
+    return pixad;
+}
+
+
+/*-----------------------------------------------------------------*
+ *      Union and intersection of parallel composite operations    *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixUnionOfMorphOps()
+ *
+ *      Input:  pixs (binary)
+ *              sela
+ *              type (L_MORPH_DILATE, etc.)
+ *      Return: pixd (union of the specified morphological operation
+ *                    on pixs for each Sel in the Sela), or null on error
+ */
+PIX *
+pixUnionOfMorphOps(PIX     *pixs,
+                   SELA    *sela,
+                   l_int32  type)
+{
+l_int32  n, i;
+PIX     *pixt, *pixd;
+SEL     *sel;
+
+    PROCNAME("pixUnionOfMorphOps");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (!sela)
+        return (PIX *)ERROR_PTR("sela not defined", procName, NULL);
+    n = selaGetCount(sela);
+    if (n == 0)
+        return (PIX *)ERROR_PTR("no sels in sela", procName, NULL);
+    if (type != L_MORPH_DILATE && type != L_MORPH_ERODE &&
+        type != L_MORPH_OPEN && type != L_MORPH_CLOSE &&
+        type != L_MORPH_HMT)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+    pixd = pixCreateTemplate(pixs);
+    for (i = 0; i < n; i++) {
+        sel = selaGetSel(sela, i);
+        if (type == L_MORPH_DILATE)
+            pixt = pixDilate(NULL, pixs, sel);
+        else if (type == L_MORPH_ERODE)
+            pixt = pixErode(NULL, pixs, sel);
+        else if (type == L_MORPH_OPEN)
+            pixt = pixOpen(NULL, pixs, sel);
+        else if (type == L_MORPH_CLOSE)
+            pixt = pixClose(NULL, pixs, sel);
+        else  /* type == L_MORPH_HMT */
+            pixt = pixHMT(NULL, pixs, sel);
+        pixOr(pixd, pixd, pixt);
+        pixDestroy(&pixt);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixIntersectionOfMorphOps()
+ *
+ *      Input:  pixs (binary)
+ *              sela
+ *              type (L_MORPH_DILATE, etc.)
+ *      Return: pixd (intersection of the specified morphological operation
+ *                    on pixs for each Sel in the Sela), or null on error
+ */
+PIX *
+pixIntersectionOfMorphOps(PIX     *pixs,
+                          SELA    *sela,
+                          l_int32  type)
+{
+l_int32  n, i;
+PIX     *pixt, *pixd;
+SEL     *sel;
+
+    PROCNAME("pixIntersectionOfMorphOps");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (!sela)
+        return (PIX *)ERROR_PTR("sela not defined", procName, NULL);
+    n = selaGetCount(sela);
+    if (n == 0)
+        return (PIX *)ERROR_PTR("no sels in sela", procName, NULL);
+    if (type != L_MORPH_DILATE && type != L_MORPH_ERODE &&
+        type != L_MORPH_OPEN && type != L_MORPH_CLOSE &&
+        type != L_MORPH_HMT)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+    pixd = pixCreateTemplate(pixs);
+    pixSetAll(pixd);
+    for (i = 0; i < n; i++) {
+        sel = selaGetSel(sela, i);
+        if (type == L_MORPH_DILATE)
+            pixt = pixDilate(NULL, pixs, sel);
+        else if (type == L_MORPH_ERODE)
+            pixt = pixErode(NULL, pixs, sel);
+        else if (type == L_MORPH_OPEN)
+            pixt = pixOpen(NULL, pixs, sel);
+        else if (type == L_MORPH_CLOSE)
+            pixt = pixClose(NULL, pixs, sel);
+        else  /* type == L_MORPH_HMT */
+            pixt = pixHMT(NULL, pixs, sel);
+        pixAnd(pixd, pixd, pixt);
+        pixDestroy(&pixt);
+    }
+
+    return pixd;
+}
+
+
+
+/*-----------------------------------------------------------------*
+ *             Selective connected component filling               *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixSelectiveConnCompFill()
+ *
+ *      Input:  pixs (binary)
+ *              connectivity (4 or 8)
+ *              minw  (minimum width to consider; use 0 or 1 for any width)
+ *              minh  (minimum height to consider; use 0 or 1 for any height)
+ *      Return: pix (with holes filled in selected c.c.), or null on error
+ */
+PIX *
+pixSelectiveConnCompFill(PIX     *pixs,
+                         l_int32  connectivity,
+                         l_int32  minw,
+                         l_int32  minh)
+{
+l_int32  n, i, x, y, w, h;
+BOXA    *boxa;
+PIX     *pix1, *pix2, *pixd;
+PIXA    *pixa;
+
+    PROCNAME("pixSelectiveConnCompFill");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (minw <= 0) minw = 1;
+    if (minh <= 0) minh = 1;
+
+    if ((boxa = pixConnComp(pixs, &pixa, connectivity)) == NULL)
+        return (PIX *)ERROR_PTR("boxa not made", procName, NULL);
+    n = boxaGetCount(boxa);
+    pixd = pixCopy(NULL, pixs);
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h);
+        if (w >= minw && h >= minh) {
+            pix1 = pixaGetPix(pixa, i, L_CLONE);
+            if ((pix2 = pixHolesByFilling(pix1, 12 - connectivity)) == NULL) {
+                L_ERROR("pix2 not made in iter %d\n", procName, i);
+                pixDestroy(&pix1);
+                continue;
+            }
+            pixRasterop(pixd, x, y, w, h, PIX_PAINT, pix2, 0, 0);
+            pixDestroy(&pix1);
+            pixDestroy(&pix2);
+        }
+    }
+    pixaDestroy(&pixa);
+    boxaDestroy(&boxa);
+
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *                    Removal of matched patterns                  *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixRemoveMatchedPattern()
+ *
+ *      Input:  pixs (input image, 1 bpp)
+ *              pixp (pattern to be removed from image, 1 bpp)
+ *              pixe (image after erosion by Sel that approximates pixp, 1 bpp)
+ *              x0, y0 (center of Sel)
+ *              dsize (number of pixels on each side by which pixp is
+ *                     dilated before being subtracted from pixs;
+ *                     valid values are {0, 1, 2, 3, 4})
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *    (1) This is in-place.
+ *    (2) You can use various functions in selgen to create a Sel
+ *        that is used to generate pixe from pixs.
+ *    (3) This function is applied after pixe has been computed.
+ *        It finds the centroid of each c.c., and subtracts
+ *        (the appropriately dilated version of) pixp, with the center
+ *        of the Sel used to align pixp with pixs.
+ */
+l_int32
+pixRemoveMatchedPattern(PIX     *pixs,
+                        PIX     *pixp,
+                        PIX     *pixe,
+                        l_int32  x0,
+                        l_int32  y0,
+                        l_int32  dsize)
+{
+l_int32  i, nc, x, y, w, h, xb, yb;
+BOXA    *boxa;
+PIX     *pix1, *pix2;
+PIXA    *pixa;
+PTA     *pta;
+SEL     *sel;
+
+    PROCNAME("pixRemoveMatchedPattern");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixp)
+        return ERROR_INT("pixp not defined", procName, 1);
+    if (!pixe)
+        return ERROR_INT("pixe not defined", procName, 1);
+    if (pixGetDepth(pixs) != 1 || pixGetDepth(pixp) != 1 ||
+        pixGetDepth(pixe) != 1)
+        return ERROR_INT("all input pix not 1 bpp", procName, 1);
+    if (dsize < 0 || dsize > 4)
+        return ERROR_INT("dsize not in {0,1,2,3,4}", procName, 1);
+
+        /* Find the connected components and their centroids */
+    boxa = pixConnComp(pixe, &pixa, 8);
+    if ((nc = boxaGetCount(boxa)) == 0) {
+        L_WARNING("no matched patterns\n", procName);
+        boxaDestroy(&boxa);
+        pixaDestroy(&pixa);
+        return 0;
+    }
+    pta = pixaCentroids(pixa);
+    pixaDestroy(&pixa);
+
+        /* Optionally dilate the pattern, first adding a border that
+         * is large enough to accommodate the dilated pixels */
+    sel = NULL;
+    if (dsize > 0) {
+        sel = selCreateBrick(2 * dsize + 1, 2 * dsize + 1, dsize, dsize,
+                             SEL_HIT);
+        pix1 = pixAddBorder(pixp, dsize, 0);
+        pix2 = pixDilate(NULL, pix1, sel);
+        selDestroy(&sel);
+        pixDestroy(&pix1);
+    } else {
+        pix2 = pixClone(pixp);
+    }
+
+        /* Subtract out each dilated pattern.  The centroid of each
+         * component is located at:
+         *       (box->x + x, box->y + y)
+         * and the 'center' of the pattern used in making pixe is located at
+         *       (x0 + dsize, (y0 + dsize)
+         * relative to the UL corner of the pattern.  The center of the
+         * pattern is placed at the center of the component. */
+    pixGetDimensions(pix2, &w, &h, NULL);
+    for (i = 0; i < nc; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        boxaGetBoxGeometry(boxa, i, &xb, &yb, NULL, NULL);
+        pixRasterop(pixs, xb + x - x0 - dsize, yb + y - y0 - dsize,
+                    w, h, PIX_DST & PIX_NOT(PIX_SRC), pix2, 0, 0);
+    }
+
+    boxaDestroy(&boxa);
+    ptaDestroy(&pta);
+    pixDestroy(&pix2);
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------*
+ *                    Display of matched patterns                  *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixDisplayMatchedPattern()
+ *
+ *      Input:  pixs (input image, 1 bpp)
+ *              pixp (pattern to be removed from image, 1 bpp)
+ *              pixe (image after erosion by Sel that approximates pixp, 1 bpp)
+ *              x0, y0 (center of Sel)
+ *              color (to paint the matched patterns; 0xrrggbb00)
+ *              scale (reduction factor for output pixd)
+ *              nlevels (if scale < 1.0, threshold to this number of levels)
+ *      Return: pixd (8 bpp, colormapped), or null on error
+ *
+ *  Notes:
+ *    (1) A 4 bpp colormapped image is generated.
+ *    (2) If scale <= 1.0, do scale to gray for the output, and threshold
+ *        to nlevels of gray.
+ *    (3) You can use various functions in selgen to create a Sel
+ *        that will generate pixe from pixs.
+ *    (4) This function is applied after pixe has been computed.
+ *        It finds the centroid of each c.c., and colors the output
+ *        pixels using pixp (appropriately aligned) as a stencil.
+ *        Alignment is done using the origin of the Sel and the
+ *        centroid of the eroded image to place the stencil pixp.
+ */
+PIX *
+pixDisplayMatchedPattern(PIX       *pixs,
+                         PIX       *pixp,
+                         PIX       *pixe,
+                         l_int32    x0,
+                         l_int32    y0,
+                         l_uint32   color,
+                         l_float32  scale,
+                         l_int32    nlevels)
+{
+l_int32   i, nc, xb, yb, x, y, xi, yi, rval, gval, bval;
+BOXA     *boxa;
+PIX      *pixd, *pixt, *pixps;
+PIXA     *pixa;
+PTA      *pta;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixDisplayMatchedPattern");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!pixp)
+        return (PIX *)ERROR_PTR("pixp not defined", procName, NULL);
+    if (!pixe)
+        return (PIX *)ERROR_PTR("pixe not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1 || pixGetDepth(pixp) != 1 ||
+        pixGetDepth(pixe) != 1)
+        return (PIX *)ERROR_PTR("all input pix not 1 bpp", procName, NULL);
+    if (scale > 1.0 || scale <= 0.0) {
+        L_WARNING("scale > 1.0 or < 0.0; setting to 1.0\n", procName);
+        scale = 1.0;
+    }
+
+        /* Find the connected components and their centroids */
+    boxa = pixConnComp(pixe, &pixa, 8);
+    if ((nc = boxaGetCount(boxa)) == 0) {
+        L_WARNING("no matched patterns\n", procName);
+        boxaDestroy(&boxa);
+        pixaDestroy(&pixa);
+        return 0;
+    }
+    pta = pixaCentroids(pixa);
+
+    extractRGBValues(color, &rval, &gval, &bval);
+    if (scale == 1.0) {  /* output 4 bpp at full resolution */
+        pixd = pixConvert1To4(NULL, pixs, 0, 1);
+        cmap = pixcmapCreate(4);
+        pixcmapAddColor(cmap, 255, 255, 255);
+        pixcmapAddColor(cmap, 0, 0, 0);
+        pixSetColormap(pixd, cmap);
+
+        /* Paint through pixp for each match location.  The centroid of each
+         * component in pixe is located at:
+         *       (box->x + x, box->y + y)
+         * and the 'center' of the pattern used in making pixe is located at
+         *       (x0, y0)
+         * relative to the UL corner of the pattern.  The center of the
+         * pattern is placed at the center of the component. */
+        for (i = 0; i < nc; i++) {
+            ptaGetIPt(pta, i, &x, &y);
+            boxaGetBoxGeometry(boxa, i, &xb, &yb, NULL, NULL);
+            pixSetMaskedCmap(pixd, pixp, xb + x - x0, yb + y - y0,
+                             rval, gval, bval);
+        }
+    } else {  /* output 4 bpp downscaled */
+        pixt = pixScaleToGray(pixs, scale);
+        pixd = pixThresholdTo4bpp(pixt, nlevels, 1);
+        pixps = pixScaleBySampling(pixp, scale, scale);
+
+        for (i = 0; i < nc; i++) {
+            ptaGetIPt(pta, i, &x, &y);
+            boxaGetBoxGeometry(boxa, i, &xb, &yb, NULL, NULL);
+            xi = (l_int32)(scale * (xb + x - x0));
+            yi = (l_int32)(scale * (yb + y - y0));
+            pixSetMaskedCmap(pixd, pixps, xi, yi, rval, gval, bval);
+        }
+        pixDestroy(&pixt);
+        pixDestroy(&pixps);
+    }
+
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixa);
+    ptaDestroy(&pta);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *       Extension of pixa by iterative erosion or dilation        *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixaExtendIterative()
+ *
+ *      Input:  pixas
+ *              type (L_MORPH_DILATE, L_MORPH_ERODE)
+ *              niters
+ *              sel (used for dilation, erosion); uses 2x2 if null
+ *              include (1 to include a copy of the input pixas in pixad;
+ *                       0 to omit)
+ *      Return: pixad (of derived pix, using all iterations), or null on error
+ *
+ *  Notes:
+ *    (1) This dilates or erodes every pix in @pixas, iteratively,
+ *        using the input Sel (or, if null, a 2x2 Sel by default),
+ *        and puts the results in @pixad.
+ *    (2) If @niters <= 0, this is a no-op; it returns a clone of pixas.
+ *    (3) If @include == 1, the output @pixad contains all the pix
+ *        in @pixas.  Otherwise, it doesn't, but pixaJoin() can be
+ *        used later to join pixas with pixad.
+ */
+PIXA *
+pixaExtendIterative(PIXA    *pixas,
+                    l_int32  type,
+                    l_int32  niters,
+                    SEL     *sel,
+                    l_int32  include)
+{
+l_int32  maxdepth, i, j, n;
+PIX     *pix0, *pix1, *pix2;
+SEL     *selt;
+PIXA    *pixad;
+
+    PROCNAME("pixaExtendIterative");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas undefined", procName, NULL);
+    if (niters <= 0) {
+        L_INFO("niters = %d; nothing to do\n", procName, niters);
+        return pixaCopy(pixas, L_CLONE);
+    }
+    if (type != L_MORPH_DILATE && type != L_MORPH_ERODE)
+        return (PIXA *)ERROR_PTR("invalid type", procName, NULL);
+    pixaGetDepthInfo(pixas, &maxdepth, NULL);
+    if (maxdepth > 1)
+        return (PIXA *)ERROR_PTR("some pix have bpp > 1", procName, NULL);
+
+    if (!sel)
+        selt = selCreateBrick(2, 2, 0, 0, SEL_HIT);  /* default */
+    else
+        selt = selCopy(sel);
+    n = pixaGetCount(pixas);
+    pixad = pixaCreate(n * niters);
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixas, i, L_CLONE);
+        if (include) pixaAddPix(pixad, pix1, L_COPY);
+        pix0 = pix1;  /* need to keep the handle to destroy the clone */
+        for (j = 0; j < niters; j++) {
+            if (type == L_MORPH_DILATE) {
+                pix2 = pixDilate(NULL, pix1, selt);
+            } else {  /* L_MORPH_ERODE */
+                pix2 = pixErode(NULL, pix1, selt);
+            }
+            pixaAddPix(pixad, pix2, L_INSERT);
+            pix1 = pix2;  /* owned by pixad; do not destroy */
+        }
+        pixDestroy(&pix0);
+    }
+
+    selDestroy(&selt);
+    return pixad;
+}
+
+
+/*-----------------------------------------------------------------*
+ *             Iterative morphological seed filling                *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixSeedfillMorph()
+ *
+ *      Input:  pixs (seed)
+ *              pixm (mask)
+ *              maxiters (use 0 to go to completion)
+ *              connectivity (4 or 8)
+ *      Return: pixd (after filling into the mask) or null on error
+ *
+ *  Notes:
+ *    (1) This is in general a very inefficient method for filling
+ *        from a seed into a mask.  Use it for a small number of iterations,
+ *        but if you expect more than a few iterations, use
+ *        pixSeedfillBinary().
+ *    (2) We use a 3x3 brick SEL for 8-cc filling and a 3x3 plus SEL for 4-cc.
+ */
+PIX *
+pixSeedfillMorph(PIX     *pixs,
+                 PIX     *pixm,
+                 l_int32  maxiters,
+                 l_int32  connectivity)
+{
+l_int32  same, i;
+PIX     *pixt, *pixd, *temp;
+SEL     *sel_3;
+
+    PROCNAME("pixSeedfillMorph");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (!pixm)
+        return (PIX *)ERROR_PTR("mask pix not defined", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not in {4,8}", procName, NULL);
+    if (maxiters <= 0) maxiters = 1000;
+    if (pixSizesEqual(pixs, pixm) == 0)
+        return (PIX *)ERROR_PTR("pix sizes unequal", procName, NULL);
+
+    if ((sel_3 = selCreateBrick(3, 3, 1, 1, SEL_HIT)) == NULL)
+        return (PIX *)ERROR_PTR("sel_3 not made", procName, NULL);
+    if (connectivity == 4) {  /* remove corner hits to make a '+' */
+        selSetElement(sel_3, 0, 0, SEL_DONT_CARE);
+        selSetElement(sel_3, 2, 2, SEL_DONT_CARE);
+        selSetElement(sel_3, 2, 0, SEL_DONT_CARE);
+        selSetElement(sel_3, 0, 2, SEL_DONT_CARE);
+    }
+
+    pixt = pixCopy(NULL, pixs);
+    pixd = pixCreateTemplate(pixs);
+    for (i = 1; i <= maxiters; i++) {
+        pixDilate(pixd, pixt, sel_3);
+        pixAnd(pixd, pixd, pixm);
+        pixEqual(pixd, pixt, &same);
+        if (same || i == maxiters)
+            break;
+        else
+            SWAP(pixt, pixd);
+    }
+    fprintf(stderr, " Num iters in binary reconstruction = %d\n", i);
+
+    pixDestroy(&pixt);
+    selDestroy(&sel_3);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *                   Granulometry on binary images                 *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixRunHistogramMorph()
+ *
+ *      Input:  pixs
+ *              runtype (L_RUN_OFF, L_RUN_ON)
+ *              direction (L_HORIZ, L_VERT)
+ *              maxsize  (size of largest runlength counted)
+ *      Return: numa of run-lengths
+ */
+NUMA *
+pixRunHistogramMorph(PIX     *pixs,
+                     l_int32  runtype,
+                     l_int32  direction,
+                     l_int32  maxsize)
+{
+l_int32    count, i, size;
+l_float32  val;
+NUMA      *na, *nah;
+PIX       *pix1, *pix2, *pix3;
+SEL       *sel_2a;
+
+    PROCNAME("pixRunHistogramMorph");
+
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("seed pix not defined", procName, NULL);
+    if (runtype != L_RUN_OFF && runtype != L_RUN_ON)
+        return (NUMA *)ERROR_PTR("invalid run type", procName, NULL);
+    if (direction != L_HORIZ && direction != L_VERT)
+        return (NUMA *)ERROR_PTR("direction not in {L_HORIZ, L_VERT}",
+                                 procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (NUMA *)ERROR_PTR("pixs must be binary", procName, NULL);
+
+    if (direction == L_HORIZ)
+        sel_2a = selCreateBrick(1, 2, 0, 0, SEL_HIT);
+    else   /* direction == L_VERT */
+        sel_2a = selCreateBrick(2, 1, 0, 0, SEL_HIT);
+    if (!sel_2a)
+        return (NUMA *)ERROR_PTR("sel_2a not made", procName, NULL);
+
+    if (runtype == L_RUN_OFF) {
+        if ((pix1 = pixCopy(NULL, pixs)) == NULL)
+            return (NUMA *)ERROR_PTR("pix1 not made", procName, NULL);
+        pixInvert(pix1, pix1);
+    } else {  /* runtype == L_RUN_ON */
+        pix1 = pixClone(pixs);
+    }
+
+        /* Get pixel counts at different stages of erosion */
+    na = numaCreate(0);
+    pix2 = pixCreateTemplate(pixs);
+    pix3 = pixCreateTemplate(pixs);
+    pixCountPixels(pix1, &count, NULL);
+    numaAddNumber(na, count);
+    pixErode(pix2, pix1, sel_2a);
+    pixCountPixels(pix2, &count, NULL);
+    numaAddNumber(na, count);
+    for (i = 0; i < maxsize / 2; i++) {
+        pixErode(pix3, pix2, sel_2a);
+        pixCountPixels(pix3, &count, NULL);
+        numaAddNumber(na, count);
+        pixErode(pix2, pix3, sel_2a);
+        pixCountPixels(pix2, &count, NULL);
+        numaAddNumber(na, count);
+    }
+
+        /* Compute length histogram */
+    size = numaGetCount(na);
+    nah = numaCreate(size);
+    numaAddNumber(nah, 0); /* number at length 0 */
+    for (i = 1; i < size - 1; i++) {
+        val = na->array[i+1] - 2 * na->array[i] + na->array[i-1];
+        numaAddNumber(nah, val);
+    }
+
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    pixDestroy(&pix3);
+    selDestroy(&sel_2a);
+    numaDestroy(&na);
+
+    return nah;
+}
+
+
+/*-----------------------------------------------------------------*
+ *            Composite operations on grayscale images             *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixTophat()
+ *
+ *      Input:  pixs
+ *              hsize (of Sel; must be odd; origin implicitly in center)
+ *              vsize (ditto)
+ *              type   (L_TOPHAT_WHITE: image - opening
+ *                      L_TOPHAT_BLACK: closing - image)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Sel is a brick with all elements being hits
+ *      (2) If hsize = vsize = 1, returns an image with all 0 data.
+ *      (3) The L_TOPHAT_WHITE flag emphasizes small bright regions,
+ *          whereas the L_TOPHAT_BLACK flag emphasizes small dark regions.
+ *          The L_TOPHAT_WHITE tophat can be accomplished by doing a
+ *          L_TOPHAT_BLACK tophat on the inverse, or v.v.
+ */
+PIX *
+pixTophat(PIX     *pixs,
+          l_int32  hsize,
+          l_int32  vsize,
+          l_int32  type)
+{
+PIX  *pixt, *pixd;
+
+    PROCNAME("pixTophat");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("seed pix not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize or vsize < 1", procName, NULL);
+    if ((hsize & 1) == 0 ) {
+        L_WARNING("horiz sel size must be odd; increasing by 1\n", procName);
+        hsize++;
+    }
+    if ((vsize & 1) == 0 ) {
+        L_WARNING("vert sel size must be odd; increasing by 1\n", procName);
+        vsize++;
+    }
+    if (type != L_TOPHAT_WHITE && type != L_TOPHAT_BLACK)
+        return (PIX *)ERROR_PTR("type must be L_TOPHAT_BLACK or L_TOPHAT_WHITE",
+                                procName, NULL);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCreateTemplate(pixs);
+
+    switch (type)
+    {
+    case L_TOPHAT_WHITE:
+        if ((pixt = pixOpenGray(pixs, hsize, vsize)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        pixd = pixSubtractGray(NULL, pixs, pixt);
+        pixDestroy(&pixt);
+        break;
+    case L_TOPHAT_BLACK:
+        if ((pixd = pixCloseGray(pixs, hsize, vsize)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+        pixSubtractGray(pixd, pixd, pixs);
+        break;
+    default:
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixHDome()
+ *
+ *      Input:  pixs (8 bpp, filling mask)
+ *              height (of seed below the filling maskhdome; must be >= 0)
+ *              connectivity (4 or 8)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) It is more efficient to use a connectivity of 4 for the fill.
+ *      (2) This fills bumps to some level, and extracts the unfilled
+ *          part of the bump.  To extract the troughs of basins, first
+ *          invert pixs and then apply pixHDome().
+ *      (3) It is useful to compare the HDome operation with the TopHat.
+ *          The latter extracts peaks or valleys that have a width
+ *          not exceeding the size of the structuring element used
+ *          in the opening or closing, rsp.  The height of the peak is
+ *          irrelevant.  By contrast, for the HDome, the gray seedfill
+ *          is used to extract all peaks that have a height not exceeding
+ *          a given value, regardless of their width!
+ *      (4) Slightly more precisely, suppose you set 'height' = 40.
+ *          Then all bumps in pixs with a height greater than or equal
+ *          to 40 become, in pixd, bumps with a max value of exactly 40.
+ *          All shorter bumps have a max value in pixd equal to the height
+ *          of the bump.
+ *      (5) The method: the filling mask, pixs, is the image whose peaks
+ *          are to be extracted.  The height of a peak is the distance
+ *          between the top of the peak and the highest "leak" to the
+ *          outside -- think of a sombrero, where the leak occurs
+ *          at the highest point on the rim.
+ *            (a) Generate a seed, pixd, by subtracting some value, p, from
+ *                each pixel in the filling mask, pixs.  The value p is
+ *                the 'height' input to this function.
+ *            (b) Fill in pixd starting with this seed, clipping by pixs,
+ *                in the way described in seedfillGrayLow().  The filling
+ *                stops before the peaks in pixs are filled.
+ *                For peaks that have a height > p, pixd is filled to
+ *                the level equal to the (top-of-the-peak - p).
+ *                For peaks of height < p, the peak is left unfilled
+ *                from its highest saddle point (the leak to the outside).
+ *            (c) Subtract the filled seed (pixd) from the filling mask (pixs).
+ *          Note that in this procedure, everything is done starting
+ *          with the filling mask, pixs.
+ *      (6) For segmentation, the resulting image, pixd, can be thresholded
+ *          and used as a seed for another filling operation.
+ */
+PIX *
+pixHDome(PIX     *pixs,
+         l_int32  height,
+         l_int32  connectivity)
+{
+PIX  *pixsd, *pixd;
+
+    PROCNAME("pixHDome");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("src pix not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (height < 0)
+        return (PIX *)ERROR_PTR("height not >= 0", procName, NULL);
+    if (height == 0)
+        return pixCreateTemplate(pixs);
+
+    if ((pixsd = pixCopy(NULL, pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixsd not made", procName, NULL);
+    pixAddConstantGray(pixsd, -height);
+    pixSeedfillGray(pixsd, pixs, connectivity);
+    pixd = pixSubtractGray(NULL, pixs, pixsd);
+    pixDestroy(&pixsd);
+    return pixd;
+}
+
+
+/*!
+ *  pixFastTophat()
+ *
+ *      Input:  pixs
+ *              xsize (width of max/min op, smoothing; any integer >= 1)
+ *              ysize (height of max/min op, smoothing; any integer >= 1)
+ *              type   (L_TOPHAT_WHITE: image - min
+ *                      L_TOPHAT_BLACK: max - image)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Don't be fooled. This is NOT a tophat.  It is a tophat-like
+ *          operation, where the result is similar to what you'd get
+ *          if you used an erosion instead of an opening, or a dilation
+ *          instead of a closing.
+ *      (2) Instead of opening or closing at full resolution, it does
+ *          a fast downscale/minmax operation, then a quick small smoothing
+ *          at low res, a replicative expansion of the "background"
+ *          to full res, and finally a removal of the background level
+ *          from the input image.  The smoothing step may not be important.
+ *      (3) It does not remove noise as well as a tophat, but it is
+ *          5 to 10 times faster.
+ *          If you need the preciseness of the tophat, don't use this.
+ *      (4) The L_TOPHAT_WHITE flag emphasizes small bright regions,
+ *          whereas the L_TOPHAT_BLACK flag emphasizes small dark regions.
+ */
+PIX *
+pixFastTophat(PIX     *pixs,
+              l_int32  xsize,
+              l_int32  ysize,
+              l_int32  type)
+{
+PIX  *pix1, *pix2, *pix3, *pixd;
+
+    PROCNAME("pixFastTophat");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("seed pix not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (xsize < 1 || ysize < 1)
+        return (PIX *)ERROR_PTR("size < 1", procName, NULL);
+    if (type != L_TOPHAT_WHITE && type != L_TOPHAT_BLACK)
+        return (PIX *)ERROR_PTR("type must be L_TOPHAT_BLACK or L_TOPHAT_WHITE",
+                                procName, NULL);
+
+    if (xsize == 1 && ysize == 1)
+        return pixCreateTemplate(pixs);
+
+    switch (type)
+    {
+    case L_TOPHAT_WHITE:
+        if ((pix1 = pixScaleGrayMinMax(pixs, xsize, ysize, L_CHOOSE_MIN))
+               == NULL)
+            return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+        pix2 = pixBlockconv(pix1, 1, 1);  /* small smoothing */
+        pix3 = pixScaleBySampling(pix2, xsize, ysize);
+        pixd = pixSubtractGray(NULL, pixs, pix3);
+        pixDestroy(&pix3);
+        break;
+    case L_TOPHAT_BLACK:
+        if ((pix1 = pixScaleGrayMinMax(pixs, xsize, ysize, L_CHOOSE_MAX))
+               == NULL)
+            return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+        pix2 = pixBlockconv(pix1, 1, 1);  /* small smoothing */
+        pixd = pixScaleBySampling(pix2, xsize, ysize);
+        pixSubtractGray(pixd, pixd, pixs);
+        break;
+    default:
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    }
+
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    return pixd;
+}
+
+
+/*!
+ *  pixMorphGradient()
+ *
+ *      Input:  pixs
+ *              hsize (of Sel; must be odd; origin implicitly in center)
+ *              vsize (ditto)
+ *              smoothing  (half-width of convolution smoothing filter.
+ *                          The width is (2 * smoothing + 1), so 0 is no-op.
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixMorphGradient(PIX     *pixs,
+                 l_int32  hsize,
+                 l_int32  vsize,
+                 l_int32  smoothing)
+{
+PIX  *pixg, *pixd;
+
+    PROCNAME("pixMorphGradient");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("seed pix not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize or vsize < 1", procName, NULL);
+    if ((hsize & 1) == 0 ) {
+        L_WARNING("horiz sel size must be odd; increasing by 1\n", procName);
+        hsize++;
+    }
+    if ((vsize & 1) == 0 ) {
+        L_WARNING("vert sel size must be odd; increasing by 1\n", procName);
+        vsize++;
+    }
+
+        /* Optionally smooth first to remove noise.
+         * If smoothing is 0, just get a copy */
+    pixg = pixBlockconvGray(pixs, NULL, smoothing, smoothing);
+
+        /* This gives approximately the gradient of a transition */
+    pixd = pixDilateGray(pixg, hsize, vsize);
+    pixSubtractGray(pixd, pixd, pixg);
+    pixDestroy(&pixg);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *                       Centroid of component                     *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixaCentroids()
+ *
+ *      Input:  pixa of components (1 or 8 bpp)
+ *      Return: pta of centroids relative to the UL corner of
+ *              each pix, or null on error
+ *
+ *  Notes:
+ *      (1) An error message is returned if any pix has something other
+ *          than 1 bpp or 8 bpp depth, and the centroid from that pix
+ *          is saved as (0, 0).
+ */
+PTA *
+pixaCentroids(PIXA  *pixa)
+{
+l_int32    i, n;
+l_int32   *centtab = NULL;
+l_int32   *sumtab = NULL;
+l_float32  x, y;
+PIX       *pix;
+PTA       *pta;
+
+    PROCNAME("pixaCentroids");
+
+    if (!pixa)
+        return (PTA *)ERROR_PTR("pixa not defined", procName, NULL);
+    if ((n = pixaGetCount(pixa)) == 0)
+        return (PTA *)ERROR_PTR("no pix in pixa", procName, NULL);
+
+    if ((pta = ptaCreate(n)) == NULL)
+        return (PTA *)ERROR_PTR("pta not defined", procName, NULL);
+    centtab = makePixelCentroidTab8();
+    sumtab = makePixelSumTab8();
+
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        if (pixCentroid(pix, centtab, sumtab, &x, &y) == 1)
+            L_ERROR("centroid failure for pix %d\n", procName, i);
+        pixDestroy(&pix);
+        ptaAddPt(pta, x, y);
+    }
+
+    LEPT_FREE(centtab);
+    LEPT_FREE(sumtab);
+    return pta;
+}
+
+
+/*!
+ *  pixCentroid()
+ *
+ *      Input:  pix (1 or 8 bpp)
+ *              centtab (<optional> table for finding centroids; can be null)
+ *              sumtab (<optional> table for finding pixel sums; can be null)
+ *              &xave, &yave (<return> coordinates of centroid, relative to
+ *                            the UL corner of the pix)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Any table not passed in will be made internally and destroyed
+ *          after use.
+ */
+l_int32
+pixCentroid(PIX        *pix,
+            l_int32    *centtab,
+            l_int32    *sumtab,
+            l_float32  *pxave,
+            l_float32  *pyave)
+{
+l_int32    w, h, d, i, j, wpl, pixsum, rowsum, val;
+l_float32  xsum, ysum;
+l_uint32  *data, *line;
+l_uint32   word;
+l_uint8    byte;
+l_int32   *ctab, *stab;
+
+    PROCNAME("pixCentroid");
+
+    if (!pxave || !pyave)
+        return ERROR_INT("&pxave and &pyave not defined", procName, 1);
+    *pxave = *pyave = 0.0;
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 1 && d != 8)
+        return ERROR_INT("pix not 1 or 8 bpp", procName, 1);
+
+    if (!centtab)
+        ctab = makePixelCentroidTab8();
+    else
+        ctab = centtab;
+    if (!sumtab)
+        stab = makePixelSumTab8();
+    else
+        stab = sumtab;
+
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    xsum = ysum = 0.0;
+    pixsum = 0;
+    if (d == 1) {
+        for (i = 0; i < h; i++) {
+                /* The body of this loop computes the sum of the set
+                 * (1) bits on this row, weighted by their distance
+                 * from the left edge of pix, and accumulates that into
+                 * xsum; it accumulates their distance from the top
+                 * edge of pix into ysum, and their total count into
+                 * pixsum.  It's equivalent to
+                 * for (j = 0; j < w; j++) {
+                 *     if (GET_DATA_BIT(line, j)) {
+                 *         xsum += j;
+                 *         ysum += i;
+                 *         pixsum++;
+                 *     }
+                 * }
+                 */
+            line = data + wpl * i;
+            rowsum = 0;
+            for (j = 0; j < wpl; j++) {
+                word = line[j];
+                if (word) {
+                    byte = word & 0xff;
+                    rowsum += stab[byte];
+                    xsum += ctab[byte] + (j * 32 + 24) * stab[byte];
+                    byte = (word >> 8) & 0xff;
+                    rowsum += stab[byte];
+                    xsum += ctab[byte] + (j * 32 + 16) * stab[byte];
+                    byte = (word >> 16) & 0xff;
+                    rowsum += stab[byte];
+                    xsum += ctab[byte] + (j * 32 + 8) * stab[byte];
+                    byte = (word >> 24) & 0xff;
+                    rowsum += stab[byte];
+                    xsum += ctab[byte] + j * 32 * stab[byte];
+                }
+            }
+            pixsum += rowsum;
+            ysum += rowsum * i;
+        }
+        if (pixsum == 0) {
+            L_WARNING("no ON pixels in pix\n", procName);
+        } else {
+            *pxave = xsum / (l_float32)pixsum;
+            *pyave = ysum / (l_float32)pixsum;
+        }
+    } else {  /* d == 8 */
+        for (i = 0; i < h; i++) {
+            line = data + wpl * i;
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_BYTE(line, j);
+                xsum += val * j;
+                ysum += val * i;
+                pixsum += val;
+            }
+        }
+        if (pixsum == 0) {
+            L_WARNING("all pixels are 0\n", procName);
+        } else {
+            *pxave = xsum / (l_float32)pixsum;
+            *pyave = ysum / (l_float32)pixsum;
+        }
+    }
+
+    if (!centtab) LEPT_FREE(ctab);
+    if (!sumtab) LEPT_FREE(stab);
+    return 0;
+}
diff --git a/src/morphdwa.c b/src/morphdwa.c
new file mode 100644 (file)
index 0000000..1b9d971
--- /dev/null
@@ -0,0 +1,1566 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  morphdwa.c
+ *
+ *    Binary morphological (dwa) ops with brick Sels
+ *         PIX     *pixDilateBrickDwa()
+ *         PIX     *pixErodeBrickDwa()
+ *         PIX     *pixOpenBrickDwa()
+ *         PIX     *pixCloseBrickDwa()
+ *
+ *    Binary composite morphological (dwa) ops with brick Sels
+ *         PIX     *pixDilateCompBrickDwa()
+ *         PIX     *pixErodeCompBrickDwa()
+ *         PIX     *pixOpenCompBrickDwa()
+ *         PIX     *pixCloseCompBrickDwa()
+ *
+ *    Binary extended composite morphological (dwa) ops with brick Sels
+ *         PIX     *pixDilateCompBrickExtendDwa()
+ *         PIX     *pixErodeCompBrickExtendDwa()
+ *         PIX     *pixOpenCompBrickExtendDwa()
+ *         PIX     *pixCloseCompBrickExtendDwa()
+ *         l_int32  getExtendedCompositeParameters()
+ *
+ *    These are higher-level interfaces for dwa morphology with brick Sels.
+ *    Because many morphological operations are performed using
+ *    separable brick Sels, it is useful to have a simple interface
+ *    for this.
+ *
+ *    We have included all 58 of the brick Sels that are generated
+ *    by selaAddBasic().  These are sufficient for all the decomposable
+ *    bricks up to size 63, which is the limit for dwa Sels with
+ *    origins at the center of the Sel.
+ *
+ *    All three sets can be used as the basic interface for general
+ *    brick operations.  Here are the internal calling sequences:
+ *
+ *      (1) If you try to apply a non-decomposable operation, such as
+ *          pixErodeBrickDwa(), with a Sel size that doesn't exist,
+ *          this calls a decomposable operation, pixErodeCompBrickDwa(),
+ *          instead.  This can differ in linear Sel size by up to
+ *          2 pixels from the request.
+ *
+ *      (2) If either Sel brick dimension is greater than 63, the extended
+ *          composite function is called.
+ *
+ *      (3) The extended composite function calls the composite function
+ *          a number of times with size 63, and once with size < 63.
+ *          Because each operation with a size of 63 is done compositely
+ *          with 7 x 9 (exactly 63), the net result is correct in
+ *          length to within 2 pixels.
+ *
+ *    For composite operations, both using a comb and extended (beyond 63),
+ *    horizontal and vertical operations are composed separately
+ *    and sequentially.
+ *
+ *    We have also included use of all the 76 comb Sels that are generated
+ *    by selaAddDwaCombs().  The generated code is in dwacomb.2.c
+ *    and dwacomblow.2.c.  These are used for the composite dwa
+ *    brick operations.
+ *
+ *    The non-composite brick operations, such as pixDilateBrickDwa(),
+ *    will call the associated composite operation in situations where
+ *    the requisite brick Sel has not been compiled into fmorphgen*.1.c.
+ *
+ *    If you want to use brick Sels that are not represented in the
+ *    basic set of 58, you must generate the dwa code to implement them.
+ *    You have three choices for how to use these:
+ *
+ *    (1) Add both the new Sels and the dwa code to the library:
+ *        - For simplicity, add your new brick Sels to those defined
+ *          in selaAddBasic().
+ *        - Recompile the library.
+ *        - Make prog/fmorphautogen.
+ *        - Run prog/fmorphautogen, to generate new versions of the
+ *          dwa code in fmorphgen.1.c and fmorphgenlow.1.c.
+ *        - Copy these two files to src.
+ *        - Recompile the library again.
+ *        - Use the new brick Sels in your program and compile it.
+ *
+ *    (2) Make both the new Sels and dwa code outside the library,
+ *        and link it directly to an executable:
+ *        - Write a function to generate the new Sels in a Sela, and call
+ *          fmorphautogen(sela, <N>, filename) to generate the code.
+ *        - Compile your program that uses the newly generated function
+ *          pixMorphDwa_<N>(), and link to the two new C files.
+ *
+ *    (3) Make the new Sels in the library and use the dwa code outside it:
+ *        - Add code in the library to generate your new brick Sels.
+ *          (It is suggested that you NOT add these Sels to the
+ *          selaAddBasic() function; write a new function that generates
+ *          a new Sela.)
+ *        - Recompile the library.
+ *        - Write a small program that generates the Sela and calls
+ *          fmorphautogen(sela, <N>, filename) to generate the code.
+ *        - Compile your program that uses the newly generated function
+ *          pixMorphDwa_<N>(), and link to the two new C files.
+ *       As an example of this approach, see prog/dwamorph*_reg.c:
+ *        - added selaAddDwaLinear() to sel2.c
+ *        - wrote dwamorph1_reg.c, to generate the dwa code.
+ *        - compiled and linked the generated code with the application,
+ *          dwamorph2_reg.c.  (Note: because this was a regression test,
+ *          dwamorph1_reg also builds and runs the application program.)
+ */
+
+#include "allheaders.h"
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_SEL_LOOKUP   0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*-----------------------------------------------------------------*
+ *           Binary morphological (dwa) ops with brick Sels        *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixDilateBrickDwa()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) These implement 2D brick Sels, using linear Sels generated
+ *          with selaAddBasic().
+ *      (2) A brick Sel has hits for all elements.
+ *      (3) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (4) Do separably if both hsize and vsize are > 1.
+ *      (5) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (6) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (7) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixDilateBrickDwa(NULL, pixs, ...);
+ *          (b) pixDilateBrickDwa(pixs, pixs, ...);
+ *          (c) pixDilateBrickDwa(pixd, pixs, ...);
+ *      (8) The size of pixd is determined by pixs.
+ *      (9) If either linear Sel is not found, this calls
+ *          the appropriate decomposible function.
+ */
+PIX *
+pixDilateBrickDwa(PIX     *pixd,
+                  PIX     *pixs,
+                  l_int32  hsize,
+                  l_int32  vsize)
+{
+l_int32  found;
+char    *selnameh, *selnamev;
+SELA    *sela;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixDilateBrickDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    sela = selaAddBasic(NULL);
+    found = TRUE;
+    selnameh = selnamev = NULL;
+    if (hsize > 1) {
+        selnameh = selaGetBrickName(sela, hsize, 1);
+        if (!selnameh) found = FALSE;
+    }
+    if (vsize > 1) {
+        selnamev = selaGetBrickName(sela, 1, vsize);
+        if (!selnamev) found = FALSE;
+    }
+    selaDestroy(&sela);
+    if (!found) {
+        L_INFO("Calling the decomposable dwa function\n", procName);
+        if (selnameh) LEPT_FREE(selnameh);
+        if (selnamev) LEPT_FREE(selnamev);
+        return pixDilateCompBrickDwa(pixd, pixs, hsize, vsize);
+    }
+
+    if (vsize == 1) {
+        pixt2 = pixMorphDwa_1(NULL, pixs, L_MORPH_DILATE, selnameh);
+        LEPT_FREE(selnameh);
+    } else if (hsize == 1) {
+        pixt2 = pixMorphDwa_1(NULL, pixs, L_MORPH_DILATE, selnamev);
+        LEPT_FREE(selnamev);
+    } else {
+        pixt1 = pixAddBorder(pixs, 32, 0);
+        pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh);
+        pixFMorphopGen_1(pixt1, pixt3, L_MORPH_DILATE, selnamev);
+        pixt2 = pixRemoveBorder(pixt1, 32);
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt3);
+        LEPT_FREE(selnameh);
+        LEPT_FREE(selnamev);
+    }
+
+    if (!pixd)
+        return pixt2;
+
+    pixTransferAllData(pixd, &pixt2, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ *  pixErodeBrickDwa()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) These implement 2D brick Sels, using linear Sels generated
+ *          with selaAddBasic().
+ *      (2) A brick Sel has hits for all elements.
+ *      (3) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (4) Do separably if both hsize and vsize are > 1.
+ *      (5) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (6) Note that we must always set or clear the border pixels
+ *          before each operation, depending on the the b.c.
+ *          (symmetric or asymmetric).
+ *      (7) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (8) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixErodeBrickDwa(NULL, pixs, ...);
+ *          (b) pixErodeBrickDwa(pixs, pixs, ...);
+ *          (c) pixErodeBrickDwa(pixd, pixs, ...);
+ *      (9) The size of the result is determined by pixs.
+ *      (10) If either linear Sel is not found, this calls
+ *           the appropriate decomposible function.
+ */
+PIX *
+pixErodeBrickDwa(PIX     *pixd,
+                 PIX     *pixs,
+                 l_int32  hsize,
+                 l_int32  vsize)
+{
+l_int32  found;
+char    *selnameh, *selnamev;
+SELA    *sela;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixErodeBrickDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    sela = selaAddBasic(NULL);
+    found = TRUE;
+    selnameh = selnamev = NULL;
+    if (hsize > 1) {
+        selnameh = selaGetBrickName(sela, hsize, 1);
+        if (!selnameh) found = FALSE;
+    }
+    if (vsize > 1) {
+        selnamev = selaGetBrickName(sela, 1, vsize);
+        if (!selnamev) found = FALSE;
+    }
+    selaDestroy(&sela);
+    if (!found) {
+        L_INFO("Calling the decomposable dwa function\n", procName);
+        if (selnameh) LEPT_FREE(selnameh);
+        if (selnamev) LEPT_FREE(selnamev);
+        return pixErodeCompBrickDwa(pixd, pixs, hsize, vsize);
+    }
+
+    if (vsize == 1) {
+        pixt2 = pixMorphDwa_1(NULL, pixs, L_MORPH_ERODE, selnameh);
+        LEPT_FREE(selnameh);
+    } else if (hsize == 1) {
+        pixt2 = pixMorphDwa_1(NULL, pixs, L_MORPH_ERODE, selnamev);
+        LEPT_FREE(selnamev);
+    } else {
+        pixt1 = pixAddBorder(pixs, 32, 0);
+        pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh);
+        pixFMorphopGen_1(pixt1, pixt3, L_MORPH_ERODE, selnamev);
+        pixt2 = pixRemoveBorder(pixt1, 32);
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt3);
+        LEPT_FREE(selnameh);
+        LEPT_FREE(selnamev);
+    }
+
+    if (!pixd)
+        return pixt2;
+
+    pixTransferAllData(pixd, &pixt2, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ *  pixOpenBrickDwa()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) These implement 2D brick Sels, using linear Sels generated
+ *          with selaAddBasic().
+ *      (2) A brick Sel has hits for all elements.
+ *      (3) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (4) Do separably if both hsize and vsize are > 1.
+ *      (5) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (6) Note that we must always set or clear the border pixels
+ *          before each operation, depending on the the b.c.
+ *          (symmetric or asymmetric).
+ *      (7) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (8) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixOpenBrickDwa(NULL, pixs, ...);
+ *          (b) pixOpenBrickDwa(pixs, pixs, ...);
+ *          (c) pixOpenBrickDwa(pixd, pixs, ...);
+ *      (9) The size of the result is determined by pixs.
+ *      (10) If either linear Sel is not found, this calls
+ *           the appropriate decomposible function.
+ */
+PIX *
+pixOpenBrickDwa(PIX     *pixd,
+                PIX     *pixs,
+                l_int32  hsize,
+                l_int32  vsize)
+{
+l_int32  found;
+char    *selnameh, *selnamev;
+SELA    *sela;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixOpenBrickDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    sela = selaAddBasic(NULL);
+    found = TRUE;
+    selnameh = selnamev = NULL;
+    if (hsize > 1) {
+        selnameh = selaGetBrickName(sela, hsize, 1);
+        if (!selnameh) found = FALSE;
+    }
+    if (vsize > 1) {
+        selnamev = selaGetBrickName(sela, 1, vsize);
+        if (!selnamev) found = FALSE;
+    }
+    selaDestroy(&sela);
+    if (!found) {
+        L_INFO("Calling the decomposable dwa function\n", procName);
+        if (selnameh) LEPT_FREE(selnameh);
+        if (selnamev) LEPT_FREE(selnamev);
+        return pixOpenCompBrickDwa(pixd, pixs, hsize, vsize);
+    }
+
+    pixt1 = pixAddBorder(pixs, 32, 0);
+    if (vsize == 1) {   /* horizontal only */
+        pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_OPEN, selnameh);
+        LEPT_FREE(selnameh);
+    } else if (hsize == 1) {   /* vertical only */
+        pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_OPEN, selnamev);
+        LEPT_FREE(selnamev);
+    } else {  /* do separable */
+        pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh);
+        pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_ERODE, selnamev);
+        pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnameh);
+        pixFMorphopGen_1(pixt2, pixt3, L_MORPH_DILATE, selnamev);
+        LEPT_FREE(selnameh);
+        LEPT_FREE(selnamev);
+        pixDestroy(&pixt3);
+    }
+    pixt3 = pixRemoveBorder(pixt2, 32);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixTransferAllData(pixd, &pixt3, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ *  pixCloseBrickDwa()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This is a 'safe' closing; we add an extra border of 32 OFF
+ *          pixels for the standard asymmetric b.c.
+ *      (2) These implement 2D brick Sels, using linear Sels generated
+ *          with selaAddBasic().
+ *      (3) A brick Sel has hits for all elements.
+ *      (4) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (5) Do separably if both hsize and vsize are > 1.
+ *      (6) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (7) Note that we must always set or clear the border pixels
+ *          before each operation, depending on the the b.c.
+ *          (symmetric or asymmetric).
+ *      (8) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (9) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixCloseBrickDwa(NULL, pixs, ...);
+ *          (b) pixCloseBrickDwa(pixs, pixs, ...);
+ *          (c) pixCloseBrickDwa(pixd, pixs, ...);
+ *      (10) The size of the result is determined by pixs.
+ *      (11) If either linear Sel is not found, this calls
+ *           the appropriate decomposible function.
+ */
+PIX *
+pixCloseBrickDwa(PIX     *pixd,
+                 PIX     *pixs,
+                 l_int32  hsize,
+                 l_int32  vsize)
+{
+l_int32  bordercolor, bordersize, found;
+char    *selnameh, *selnamev;
+SELA    *sela;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixCloseBrickDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    sela = selaAddBasic(NULL);
+    found = TRUE;
+    selnameh = selnamev = NULL;
+    if (hsize > 1) {
+        selnameh = selaGetBrickName(sela, hsize, 1);
+        if (!selnameh) found = FALSE;
+    }
+    if (vsize > 1) {
+        selnamev = selaGetBrickName(sela, 1, vsize);
+        if (!selnamev) found = FALSE;
+    }
+    selaDestroy(&sela);
+    if (!found) {
+        L_INFO("Calling the decomposable dwa function\n", procName);
+        if (selnameh) LEPT_FREE(selnameh);
+        if (selnamev) LEPT_FREE(selnamev);
+        return pixCloseCompBrickDwa(pixd, pixs, hsize, vsize);
+    }
+
+        /* For "safe closing" with ASYMMETRIC_MORPH_BC, we always need
+         * an extra 32 OFF pixels around the image (in addition to
+         * the 32 added pixels for all dwa operations), whereas with
+         * SYMMETRIC_MORPH_BC this is not necessary. */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    if (bordercolor == 0)   /* asymmetric b.c. */
+        bordersize = 64;
+    else   /* symmetric b.c. */
+        bordersize = 32;
+    pixt1 = pixAddBorder(pixs, bordersize, 0);
+
+    if (vsize == 1) {   /* horizontal only */
+        pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_CLOSE, selnameh);
+        LEPT_FREE(selnameh);
+    } else if (hsize == 1) {   /* vertical only */
+        pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_CLOSE, selnamev);
+        LEPT_FREE(selnamev);
+    } else {  /* do separable */
+        pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh);
+        pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnamev);
+        pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnameh);
+        pixFMorphopGen_1(pixt2, pixt3, L_MORPH_ERODE, selnamev);
+        LEPT_FREE(selnameh);
+        LEPT_FREE(selnamev);
+        pixDestroy(&pixt3);
+    }
+    pixt3 = pixRemoveBorder(pixt2, bordersize);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixTransferAllData(pixd, &pixt3, 0, 0);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------*
+ *    Binary composite morphological (dwa) ops with brick Sels     *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixDilateCompBrickDwa()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) These implement a separable composite dilation with 2D brick Sels.
+ *      (2) For efficiency, it may decompose each linear morphological
+ *          operation into two (brick + comb).
+ *      (3) A brick Sel has hits for all elements.
+ *      (4) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (5) Do separably if both hsize and vsize are > 1.
+ *      (6) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (7) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (8) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixDilateCompBrickDwa(NULL, pixs, ...);
+ *          (b) pixDilateCompBrickDwa(pixs, pixs, ...);
+ *          (c) pixDilateCompBrickDwa(pixd, pixs, ...);
+ *      (9) The size of pixd is determined by pixs.
+ *      (10) CAUTION: both hsize and vsize are being decomposed.
+ *          The decomposer chooses a product of sizes (call them
+ *          'terms') for each that is close to the input size,
+ *           but not necessarily equal to it.  It attempts to optimize:
+ *              (a) for consistency with the input values: the product
+ *                  of terms is close to the input size
+ *              (b) for efficiency of the operation: the sum of the
+ *                  terms is small; ideally about twice the square
+ *                   root of the input size.
+ *           So, for example, if the input hsize = 37, which is
+ *           a prime number, the decomposer will break this into two
+ *           terms, 6 and 6, so that the net result is a dilation
+ *           with hsize = 36.
+ */
+PIX *
+pixDilateCompBrickDwa(PIX     *pixd,
+                      PIX     *pixs,
+                      l_int32  hsize,
+                      l_int32  vsize)
+{
+char    *selnameh1, *selnameh2, *selnamev1, *selnamev2;
+l_int32  hsize1, hsize2, vsize1, vsize2;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixDilateCompBrickDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+    if (hsize > 63 || vsize > 63)
+        return pixDilateCompBrickExtendDwa(pixd, pixs, hsize, vsize);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    hsize1 = hsize2 = vsize1 = vsize2 = 1;
+    selnameh1 = selnameh2 = selnamev1 = selnamev2 = NULL;
+    if (hsize > 1)
+        getCompositeParameters(hsize, &hsize1, &hsize2, &selnameh1,
+                               &selnameh2, NULL, NULL);
+    if (vsize > 1)
+        getCompositeParameters(vsize, &vsize1, &vsize2, NULL, NULL,
+                               &selnamev1, &selnamev2);
+
+#if DEBUG_SEL_LOOKUP
+    fprintf(stderr, "nameh1=%s, nameh2=%s, namev1=%s, namev2=%s\n",
+            selnameh1, selnameh2, selnamev1, selnamev2);
+    fprintf(stderr, "hsize1=%d, hsize2=%d, vsize1=%d, vsize2=%d\n",
+            hsize1, hsize2, vsize1, vsize2);
+#endif  /* DEBUG_SEL_LOOKUP */
+
+    pixt1 = pixAddBorder(pixs, 64, 0);
+    if (vsize == 1) {
+        if (hsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_DILATE, selnameh2);
+            pixDestroy(&pixt3);
+        }
+    } else if (hsize == 1) {
+        if (vsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnamev1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnamev1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_DILATE, selnamev2);
+            pixDestroy(&pixt3);
+        }
+    } else {  /* vsize and hsize both > 1 */
+        if (hsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+        } else {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt3 = pixFMorphopGen_2(NULL, pixt2, L_MORPH_DILATE, selnameh2);
+            pixDestroy(&pixt2);
+        }
+        if (vsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnamev1);
+        } else {
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt2, L_MORPH_DILATE, selnamev2);
+        }
+        pixDestroy(&pixt3);
+    }
+    pixDestroy(&pixt1);
+    pixt1 = pixRemoveBorder(pixt2, 64);
+    pixDestroy(&pixt2);
+    if (selnameh1) LEPT_FREE(selnameh1);
+    if (selnameh2) LEPT_FREE(selnameh2);
+    if (selnamev1) LEPT_FREE(selnamev1);
+    if (selnamev2) LEPT_FREE(selnamev2);
+
+    if (!pixd)
+        return pixt1;
+
+    pixTransferAllData(pixd, &pixt1, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ *  pixErodeCompBrickDwa()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) These implement a separable composite erosion with 2D brick Sels.
+ *      (2) For efficiency, it may decompose each linear morphological
+ *          operation into two (brick + comb).
+ *      (3) A brick Sel has hits for all elements.
+ *      (4) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (5) Do separably if both hsize and vsize are > 1.
+ *      (6) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (7) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (8) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixErodeCompBrickDwa(NULL, pixs, ...);
+ *          (b) pixErodeCompBrickDwa(pixs, pixs, ...);
+ *          (c) pixErodeCompBrickDwa(pixd, pixs, ...);
+ *      (9) The size of pixd is determined by pixs.
+ *      (10) CAUTION: both hsize and vsize are being decomposed.
+ *          The decomposer chooses a product of sizes (call them
+ *          'terms') for each that is close to the input size,
+ *           but not necessarily equal to it.  It attempts to optimize:
+ *              (a) for consistency with the input values: the product
+ *                  of terms is close to the input size
+ *              (b) for efficiency of the operation: the sum of the
+ *                  terms is small; ideally about twice the square
+ *                   root of the input size.
+ *           So, for example, if the input hsize = 37, which is
+ *           a prime number, the decomposer will break this into two
+ *           terms, 6 and 6, so that the net result is a dilation
+ *           with hsize = 36.
+ */
+PIX *
+pixErodeCompBrickDwa(PIX     *pixd,
+                     PIX     *pixs,
+                     l_int32  hsize,
+                     l_int32  vsize)
+{
+char    *selnameh1, *selnameh2, *selnamev1, *selnamev2;
+l_int32  hsize1, hsize2, vsize1, vsize2, bordercolor;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixErodeCompBrickDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+    if (hsize > 63 || vsize > 63)
+        return pixErodeCompBrickExtendDwa(pixd, pixs, hsize, vsize);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    hsize1 = hsize2 = vsize1 = vsize2 = 1;
+    selnameh1 = selnameh2 = selnamev1 = selnamev2 = NULL;
+    if (hsize > 1)
+        getCompositeParameters(hsize, &hsize1, &hsize2, &selnameh1,
+                               &selnameh2, NULL, NULL);
+    if (vsize > 1)
+        getCompositeParameters(vsize, &vsize1, &vsize2, NULL, NULL,
+                               &selnamev1, &selnamev2);
+
+        /* For symmetric b.c., bordercolor == 1 for erosion */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    pixt1 = pixAddBorder(pixs, 64, bordercolor);
+
+    if (vsize == 1) {
+        if (hsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_ERODE, selnameh2);
+            pixDestroy(&pixt3);
+        }
+    } else if (hsize == 1) {
+        if (vsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnamev1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnamev1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_ERODE, selnamev2);
+            pixDestroy(&pixt3);
+        }
+    } else {  /* vsize and hsize both > 1 */
+        if (hsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+        } else {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt3 = pixFMorphopGen_2(NULL, pixt2, L_MORPH_ERODE, selnameh2);
+            pixDestroy(&pixt2);
+        }
+        if (vsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_ERODE, selnamev1);
+        } else {
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_ERODE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt2, L_MORPH_ERODE, selnamev2);
+        }
+        pixDestroy(&pixt3);
+    }
+    pixDestroy(&pixt1);
+    pixt1 = pixRemoveBorder(pixt2, 64);
+    pixDestroy(&pixt2);
+    if (selnameh1) LEPT_FREE(selnameh1);
+    if (selnameh2) LEPT_FREE(selnameh2);
+    if (selnamev1) LEPT_FREE(selnamev1);
+    if (selnamev2) LEPT_FREE(selnamev2);
+
+    if (!pixd)
+        return pixt1;
+
+    pixTransferAllData(pixd, &pixt1, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ *  pixOpenCompBrickDwa()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) These implement a separable composite opening with 2D brick Sels.
+ *      (2) For efficiency, it may decompose each linear morphological
+ *          operation into two (brick + comb).
+ *      (3) A brick Sel has hits for all elements.
+ *      (4) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (5) Do separably if both hsize and vsize are > 1.
+ *      (6) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (7) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (8) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixOpenCompBrickDwa(NULL, pixs, ...);
+ *          (b) pixOpenCompBrickDwa(pixs, pixs, ...);
+ *          (c) pixOpenCompBrickDwa(pixd, pixs, ...);
+ *      (9) The size of pixd is determined by pixs.
+ *      (10) CAUTION: both hsize and vsize are being decomposed.
+ *          The decomposer chooses a product of sizes (call them
+ *          'terms') for each that is close to the input size,
+ *           but not necessarily equal to it.  It attempts to optimize:
+ *              (a) for consistency with the input values: the product
+ *                  of terms is close to the input size
+ *              (b) for efficiency of the operation: the sum of the
+ *                  terms is small; ideally about twice the square
+ *                   root of the input size.
+ *           So, for example, if the input hsize = 37, which is
+ *           a prime number, the decomposer will break this into two
+ *           terms, 6 and 6, so that the net result is a dilation
+ *           with hsize = 36.
+ */
+PIX *
+pixOpenCompBrickDwa(PIX     *pixd,
+                    PIX     *pixs,
+                    l_int32  hsize,
+                    l_int32  vsize)
+{
+char    *selnameh1, *selnameh2, *selnamev1, *selnamev2;
+l_int32  hsize1, hsize2, vsize1, vsize2, bordercolor;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixOpenCompBrickDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+    if (hsize > 63 || vsize > 63)
+        return pixOpenCompBrickExtendDwa(pixd, pixs, hsize, vsize);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    hsize1 = hsize2 = vsize1 = vsize2 = 1;
+    selnameh1 = selnameh2 = selnamev1 = selnamev2 = NULL;
+    if (hsize > 1)
+        getCompositeParameters(hsize, &hsize1, &hsize2, &selnameh1,
+                               &selnameh2, NULL, NULL);
+    if (vsize > 1)
+        getCompositeParameters(vsize, &vsize1, &vsize2, NULL, NULL,
+                               &selnamev1, &selnamev2);
+
+        /* For symmetric b.c., initialize erosion with bordercolor == 1 */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    pixt1 = pixAddBorder(pixs, 64, bordercolor);
+
+    if (vsize == 1) {
+        if (hsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt3, 64, 64, 64, 64, PIX_CLR);
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnameh1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_ERODE, selnameh2);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_CLR);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnameh1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_DILATE, selnameh2);
+        }
+    } else if (hsize == 1) {
+        if (vsize2 == 1)  {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnamev1);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt3, 64, 64, 64, 64, PIX_CLR);
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnamev1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnamev1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_ERODE, selnamev2);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_CLR);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_DILATE, selnamev2);
+        }
+    } else {  /* vsize and hsize both > 1 */
+        if (hsize2 == 1 && vsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_ERODE, selnamev1);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_CLR);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnameh1);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_DILATE, selnamev1);
+        } else if (vsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_ERODE, selnameh2);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnamev1);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt3, 64, 64, 64, 64, PIX_CLR);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_DILATE, selnameh1);
+            pixFMorphopGen_2(pixt3, pixt2, L_MORPH_DILATE, selnameh2);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_DILATE, selnamev1);
+        } else if (hsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_ERODE, selnamev1);
+            pixFMorphopGen_2(pixt3, pixt2, L_MORPH_ERODE, selnamev2);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt3, 64, 64, 64, 64, PIX_CLR);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_DILATE, selnameh1);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_DILATE, selnamev2);
+        } else {   /* both directions are combed */
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_ERODE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_ERODE, selnameh2);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_ERODE, selnamev2);
+            if (bordercolor == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_CLR);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnameh1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_DILATE, selnameh2);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_DILATE, selnamev2);
+        }
+    }
+    pixDestroy(&pixt3);
+
+    pixDestroy(&pixt1);
+    pixt1 = pixRemoveBorder(pixt2, 64);
+    pixDestroy(&pixt2);
+    if (selnameh1) LEPT_FREE(selnameh1);
+    if (selnameh2) LEPT_FREE(selnameh2);
+    if (selnamev1) LEPT_FREE(selnamev1);
+    if (selnamev2) LEPT_FREE(selnamev2);
+
+    if (!pixd)
+        return pixt1;
+
+    pixTransferAllData(pixd, &pixt1, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ *  pixCloseCompBrickDwa()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This implements a separable composite safe closing with 2D
+ *          brick Sels.
+ *      (2) For efficiency, it may decompose each linear morphological
+ *          operation into two (brick + comb).
+ *      (3) A brick Sel has hits for all elements.
+ *      (4) The origin of the Sel is at (x, y) = (hsize/2, vsize/2)
+ *      (5) Do separably if both hsize and vsize are > 1.
+ *      (6) It is necessary that both horizontal and vertical Sels
+ *          of the input size are defined in the basic sela.
+ *      (7) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (8) For clarity, if the case is known, use these patterns:
+ *          (a) pixd = pixCloseCompBrickDwa(NULL, pixs, ...);
+ *          (b) pixCloseCompBrickDwa(pixs, pixs, ...);
+ *          (c) pixCloseCompBrickDwa(pixd, pixs, ...);
+ *      (9) The size of pixd is determined by pixs.
+ *      (10) CAUTION: both hsize and vsize are being decomposed.
+ *          The decomposer chooses a product of sizes (call them
+ *          'terms') for each that is close to the input size,
+ *           but not necessarily equal to it.  It attempts to optimize:
+ *              (a) for consistency with the input values: the product
+ *                  of terms is close to the input size
+ *              (b) for efficiency of the operation: the sum of the
+ *                  terms is small; ideally about twice the square
+ *                   root of the input size.
+ *           So, for example, if the input hsize = 37, which is
+ *           a prime number, the decomposer will break this into two
+ *           terms, 6 and 6, so that the net result is a dilation
+ *           with hsize = 36.
+ */
+PIX *
+pixCloseCompBrickDwa(PIX     *pixd,
+                     PIX     *pixs,
+                     l_int32  hsize,
+                     l_int32  vsize)
+{
+char    *selnameh1, *selnameh2, *selnamev1, *selnamev2;
+l_int32  hsize1, hsize2, vsize1, vsize2, setborder;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixCloseCompBrickDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+    if (hsize > 63 || vsize > 63)
+        return pixCloseCompBrickExtendDwa(pixd, pixs, hsize, vsize);
+
+    if (hsize == 1 && vsize == 1)
+        return pixCopy(pixd, pixs);
+
+    hsize1 = hsize2 = vsize1 = vsize2 = 1;
+    selnameh1 = selnameh2 = selnamev1 = selnamev2 = NULL;
+    if (hsize > 1)
+        getCompositeParameters(hsize, &hsize1, &hsize2, &selnameh1,
+                               &selnameh2, NULL, NULL);
+    if (vsize > 1)
+        getCompositeParameters(vsize, &vsize1, &vsize2, NULL, NULL,
+                               &selnamev1, &selnamev2);
+
+    pixt3 = NULL;
+        /* For symmetric b.c., PIX_SET border for erosions */
+    setborder = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    pixt1 = pixAddBorder(pixs, 64, 0);
+
+    if (vsize == 1) {
+        if (hsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_CLOSE, selnameh1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_DILATE, selnameh2);
+            if (setborder == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_SET);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnameh1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_ERODE, selnameh2);
+        }
+    } else if (hsize == 1) {
+        if (vsize2 == 1) {
+            pixt2 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_CLOSE, selnamev1);
+        } else {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnamev1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_DILATE, selnamev2);
+            if (setborder == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_SET);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_ERODE, selnamev2);
+        }
+    } else {  /* vsize and hsize both > 1 */
+        if (hsize2 == 1 && vsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnamev1);
+            if (setborder == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_SET);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnameh1);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_ERODE, selnamev1);
+        } else if (vsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_DILATE, selnameh2);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnamev1);
+            if (setborder == 1)
+                pixSetOrClearBorder(pixt3, 64, 64, 64, 64, PIX_SET);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_ERODE, selnameh1);
+            pixFMorphopGen_2(pixt3, pixt2, L_MORPH_ERODE, selnameh2);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_ERODE, selnamev1);
+        } else if (hsize2 == 1) {
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt2 = pixFMorphopGen_1(NULL, pixt3, L_MORPH_DILATE, selnamev1);
+            pixFMorphopGen_2(pixt3, pixt2, L_MORPH_DILATE, selnamev2);
+            if (setborder == 1)
+                pixSetOrClearBorder(pixt3, 64, 64, 64, 64, PIX_SET);
+            pixFMorphopGen_1(pixt2, pixt3, L_MORPH_ERODE, selnameh1);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_ERODE, selnamev2);
+        } else {   /* both directions are combed */
+            pixt3 = pixFMorphopGen_1(NULL, pixt1, L_MORPH_DILATE, selnameh1);
+            pixt2 = pixFMorphopGen_2(NULL, pixt3, L_MORPH_DILATE, selnameh2);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_DILATE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_DILATE, selnamev2);
+            if (setborder == 1)
+                pixSetOrClearBorder(pixt2, 64, 64, 64, 64, PIX_SET);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnameh1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_ERODE, selnameh2);
+            pixFMorphopGen_1(pixt3, pixt2, L_MORPH_ERODE, selnamev1);
+            pixFMorphopGen_2(pixt2, pixt3, L_MORPH_ERODE, selnamev2);
+        }
+    }
+    pixDestroy(&pixt3);
+
+    pixDestroy(&pixt1);
+    pixt1 = pixRemoveBorder(pixt2, 64);
+    pixDestroy(&pixt2);
+    if (selnameh1) LEPT_FREE(selnameh1);
+    if (selnameh2) LEPT_FREE(selnameh2);
+    if (selnamev1) LEPT_FREE(selnamev1);
+    if (selnamev2) LEPT_FREE(selnamev2);
+
+    if (!pixd)
+        return pixt1;
+
+    pixTransferAllData(pixd, &pixt1, 0, 0);
+    return pixd;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *    Binary expanded composite morphological (dwa) ops with brick Sels     *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  pixDilateCompBrickExtendDwa()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) Ankur Jain suggested and implemented extending the composite
+ *          DWA operations beyond the 63 pixel limit.  This is a
+ *          simplified and approximate implementation of the extension.
+ *          This allows arbitrary Dwa morph operations using brick Sels,
+ *          by decomposing the horizontal and vertical dilations into
+ *          a sequence of 63-element dilations plus a dilation of size
+ *          between 3 and 62.
+ *      (2) The 63-element dilations are exact, whereas the extra dilation
+ *          is approximate, because the underlying decomposition is
+ *          in pixDilateCompBrickDwa().  See there for further details.
+ *      (3) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (4) There is no need to call this directly:  pixDilateCompBrickDwa()
+ *          calls this function if either brick dimension exceeds 63.
+ */
+PIX *
+pixDilateCompBrickExtendDwa(PIX     *pixd,
+                            PIX     *pixs,
+                            l_int32  hsize,
+                            l_int32  vsize)
+{
+l_int32  i, nops, nh, extrah, nv, extrav;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixDilateCompBrickExtendDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize < 64 && vsize < 64)
+        return pixDilateCompBrickDwa(pixd, pixs, hsize, vsize);
+
+    if (hsize > 63)
+        getExtendedCompositeParameters(hsize, &nh, &extrah, NULL);
+    if (vsize > 63)
+        getExtendedCompositeParameters(vsize, &nv, &extrav, NULL);
+
+        /* Horizontal dilation first: pixs --> pixt2.  Do not alter pixs. */
+    pixt1 = pixCreateTemplateNoInit(pixs);  /* temp image */
+    if (hsize == 1) {
+        pixt2 = pixClone(pixs);
+    } else if (hsize < 64) {
+        pixt2 = pixDilateCompBrickDwa(NULL, pixs, hsize, 1);
+    } else if (hsize == 64) {  /* approximate */
+        pixt2 = pixDilateCompBrickDwa(NULL, pixs, 63, 1);
+    } else {
+        nops = (extrah < 3) ? nh : nh + 1;
+        if (nops & 1) {  /* odd */
+            if (extrah > 2)
+                pixt2 = pixDilateCompBrickDwa(NULL, pixs, extrah, 1);
+            else
+                pixt2 = pixDilateCompBrickDwa(NULL, pixs, 63, 1);
+            for (i = 0; i < nops / 2; i++) {
+                pixDilateCompBrickDwa(pixt1, pixt2, 63, 1);
+                pixDilateCompBrickDwa(pixt2, pixt1, 63, 1);
+            }
+        } else {  /* nops even */
+            if (extrah > 2) {
+                pixDilateCompBrickDwa(pixt1, pixs, extrah, 1);
+                pixt2 = pixDilateCompBrickDwa(NULL, pixt1, 63, 1);
+            } else {  /* they're all 63s */
+                pixDilateCompBrickDwa(pixt1, pixs, 63, 1);
+                pixt2 = pixDilateCompBrickDwa(NULL, pixt1, 63, 1);
+            }
+            for (i = 0; i < nops / 2 - 1; i++) {
+                pixDilateCompBrickDwa(pixt1, pixt2, 63, 1);
+                pixDilateCompBrickDwa(pixt2, pixt1, 63, 1);
+            }
+        }
+    }
+
+        /* Vertical dilation: pixt2 --> pixt3.  */
+    if (vsize == 1) {
+        pixt3 = pixClone(pixt2);
+    } else if (vsize < 64) {
+        pixt3 = pixDilateCompBrickDwa(NULL, pixt2, 1, vsize);
+    } else if (vsize == 64) {  /* approximate */
+        pixt3 = pixDilateCompBrickDwa(NULL, pixt2, 1, 63);
+    } else {
+        nops = (extrav < 3) ? nv : nv + 1;
+        if (nops & 1) {  /* odd */
+            if (extrav > 2)
+                pixt3 = pixDilateCompBrickDwa(NULL, pixt2, 1, extrav);
+            else
+                pixt3 = pixDilateCompBrickDwa(NULL, pixt2, 1, 63);
+            for (i = 0; i < nops / 2; i++) {
+                pixDilateCompBrickDwa(pixt1, pixt3, 1, 63);
+                pixDilateCompBrickDwa(pixt3, pixt1, 1, 63);
+            }
+        } else {  /* nops even */
+            if (extrav > 2) {
+                pixDilateCompBrickDwa(pixt1, pixt2, 1, extrav);
+                pixt3 = pixDilateCompBrickDwa(NULL, pixt1, 1, 63);
+            } else {  /* they're all 63s */
+                pixDilateCompBrickDwa(pixt1, pixt2, 1, 63);
+                pixt3 = pixDilateCompBrickDwa(NULL, pixt1, 1, 63);
+            }
+            for (i = 0; i < nops / 2 - 1; i++) {
+                pixDilateCompBrickDwa(pixt1, pixt3, 1, 63);
+                pixDilateCompBrickDwa(pixt3, pixt1, 1, 63);
+            }
+        }
+    }
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixTransferAllData(pixd, &pixt3, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ *  pixErodeCompBrickExtendDwa()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) See pixDilateCompBrickExtendDwa() for usage.
+ *      (2) There is no need to call this directly:  pixErodeCompBrickDwa()
+ *          calls this function if either brick dimension exceeds 63.
+ */
+PIX *
+pixErodeCompBrickExtendDwa(PIX     *pixd,
+                           PIX     *pixs,
+                           l_int32  hsize,
+                           l_int32  vsize)
+{
+l_int32  i, nops, nh, extrah, nv, extrav;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixErodeCompBrickExtendDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    if (hsize < 64 && vsize < 64)
+        return pixErodeCompBrickDwa(pixd, pixs, hsize, vsize);
+
+    if (hsize > 63)
+        getExtendedCompositeParameters(hsize, &nh, &extrah, NULL);
+    if (vsize > 63)
+        getExtendedCompositeParameters(vsize, &nv, &extrav, NULL);
+
+        /* Horizontal erosion first: pixs --> pixt2.  Do not alter pixs. */
+    pixt1 = pixCreateTemplateNoInit(pixs);  /* temp image */
+    if (hsize == 1) {
+        pixt2 = pixClone(pixs);
+    } else if (hsize < 64) {
+        pixt2 = pixErodeCompBrickDwa(NULL, pixs, hsize, 1);
+    } else if (hsize == 64) {  /* approximate */
+        pixt2 = pixErodeCompBrickDwa(NULL, pixs, 63, 1);
+    } else {
+        nops = (extrah < 3) ? nh : nh + 1;
+        if (nops & 1) {  /* odd */
+            if (extrah > 2)
+                pixt2 = pixErodeCompBrickDwa(NULL, pixs, extrah, 1);
+            else
+                pixt2 = pixErodeCompBrickDwa(NULL, pixs, 63, 1);
+            for (i = 0; i < nops / 2; i++) {
+                pixErodeCompBrickDwa(pixt1, pixt2, 63, 1);
+                pixErodeCompBrickDwa(pixt2, pixt1, 63, 1);
+            }
+        } else {  /* nops even */
+            if (extrah > 2) {
+                pixErodeCompBrickDwa(pixt1, pixs, extrah, 1);
+                pixt2 = pixErodeCompBrickDwa(NULL, pixt1, 63, 1);
+            } else {  /* they're all 63s */
+                pixErodeCompBrickDwa(pixt1, pixs, 63, 1);
+                pixt2 = pixErodeCompBrickDwa(NULL, pixt1, 63, 1);
+            }
+            for (i = 0; i < nops / 2 - 1; i++) {
+                pixErodeCompBrickDwa(pixt1, pixt2, 63, 1);
+                pixErodeCompBrickDwa(pixt2, pixt1, 63, 1);
+            }
+        }
+    }
+
+        /* Vertical erosion: pixt2 --> pixt3.  */
+    if (vsize == 1) {
+        pixt3 = pixClone(pixt2);
+    } else if (vsize < 64) {
+        pixt3 = pixErodeCompBrickDwa(NULL, pixt2, 1, vsize);
+    } else if (vsize == 64) {  /* approximate */
+        pixt3 = pixErodeCompBrickDwa(NULL, pixt2, 1, 63);
+    } else {
+        nops = (extrav < 3) ? nv : nv + 1;
+        if (nops & 1) {  /* odd */
+            if (extrav > 2)
+                pixt3 = pixErodeCompBrickDwa(NULL, pixt2, 1, extrav);
+            else
+                pixt3 = pixErodeCompBrickDwa(NULL, pixt2, 1, 63);
+            for (i = 0; i < nops / 2; i++) {
+                pixErodeCompBrickDwa(pixt1, pixt3, 1, 63);
+                pixErodeCompBrickDwa(pixt3, pixt1, 1, 63);
+            }
+        } else {  /* nops even */
+            if (extrav > 2) {
+                pixErodeCompBrickDwa(pixt1, pixt2, 1, extrav);
+                pixt3 = pixErodeCompBrickDwa(NULL, pixt1, 1, 63);
+            } else {  /* they're all 63s */
+                pixErodeCompBrickDwa(pixt1, pixt2, 1, 63);
+                pixt3 = pixErodeCompBrickDwa(NULL, pixt1, 1, 63);
+            }
+            for (i = 0; i < nops / 2 - 1; i++) {
+                pixErodeCompBrickDwa(pixt1, pixt3, 1, 63);
+                pixErodeCompBrickDwa(pixt3, pixt1, 1, 63);
+            }
+        }
+    }
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixTransferAllData(pixd, &pixt3, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ *  pixOpenCompBrickExtendDwa()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *      (1) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (2) There is no need to call this directly:  pixOpenCompBrickDwa()
+ *          calls this function if either brick dimension exceeds 63.
+ */
+PIX *
+pixOpenCompBrickExtendDwa(PIX     *pixd,
+                          PIX     *pixs,
+                          l_int32  hsize,
+                          l_int32  vsize)
+{
+PIX     *pixt;
+
+    PROCNAME("pixOpenCompBrickExtendDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+    pixt = pixErodeCompBrickExtendDwa(NULL, pixs, hsize, vsize);
+    pixd = pixDilateCompBrickExtendDwa(pixd, pixt, hsize, vsize);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixCloseCompBrickExtendDwa()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (1 bpp)
+ *              hsize (width of brick Sel)
+ *              vsize (height of brick Sel)
+ *      Return: pixd
+ *
+ *      (1) There are three cases:
+ *          (a) pixd == null   (result into new pixd)
+ *          (b) pixd == pixs   (in-place; writes result back to pixs)
+ *          (c) pixd != pixs   (puts result into existing pixd)
+ *      (2) There is no need to call this directly:  pixCloseCompBrickDwa()
+ *          calls this function if either brick dimension exceeds 63.
+ */
+PIX *
+pixCloseCompBrickExtendDwa(PIX     *pixd,
+                           PIX     *pixs,
+                           l_int32  hsize,
+                           l_int32  vsize)
+{
+l_int32  bordercolor, borderx, bordery;
+PIX     *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixCloseCompBrickExtendDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+    if (hsize < 1 || vsize < 1)
+        return (PIX *)ERROR_PTR("hsize and vsize not >= 1", procName, pixd);
+
+        /* For "safe closing" with ASYMMETRIC_MORPH_BC, we always need
+         * an extra 32 OFF pixels around the image (in addition to
+         * the 32 added pixels for all dwa operations), whereas with
+         * SYMMETRIC_MORPH_BC this is not necessary. */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    if (bordercolor == 0) {  /* asymmetric b.c. */
+        borderx = 32 + (hsize / 64) * 32;
+        bordery = 32 + (vsize / 64) * 32;
+    } else {  /* symmetric b.c. */
+        borderx = bordery = 32;
+    }
+    pixt1 = pixAddBorderGeneral(pixs, borderx, borderx, bordery, bordery, 0);
+
+    pixt2 = pixDilateCompBrickExtendDwa(NULL, pixt1, hsize, vsize);
+    pixErodeCompBrickExtendDwa(pixt1, pixt2, hsize, vsize);
+
+    pixt3 = pixRemoveBorderGeneral(pixt1, borderx, borderx, bordery, bordery);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixTransferAllData(pixd, &pixt3, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ *  getExtendedCompositeParameters()
+ *
+ *      Input:  size (of linear Sel)
+ *              &pn (<return> number of 63 wide convolutions)
+ *              &pextra (<return> size of extra Sel)
+ *              &actualsize (<optional return> actual size used in operation)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The DWA implementation allows Sels to be used with hits
+ *          up to 31 pixels from the origin, either horizontally or
+ *          vertically.  Larger Sels can be used if decomposed into
+ *          a set of operations with Sels not exceeding 63 pixels
+ *          in either width or height (and with the origin as close
+ *          to the center of the Sel as possible).
+ *      (2) This returns the decomposition of a linear Sel of length
+ *          @size into a set of @n Sels of length 63 plus an extra
+ *          Sel of length @extra.
+ *      (3) For notation, let w == @size, n == @n, and e == @extra.
+ *          We have 1 < e < 63.
+ *
+ *          Then if w < 64, we have n = 0 and e = w.
+ *          The general formula for w > 63 is:
+ *             w = 63 + (n - 1) * 62 + (e - 1)
+ *
+ *          Where did this come from?  Each successive convolution with
+ *          a Sel of length L adds a total length (L - 1) to w.
+ *          This accounts for using 62 for each additional Sel of size 63,
+ *          and using (e - 1) for the additional Sel of size e.
+ *
+ *          Solving for n and e for w > 63:
+ *             n = 1 + Int((w - 63) / 62)
+ *             e = w - 63 - (n - 1) * 62 + 1
+ *
+ *          The extra part is decomposed into two factors f1 and f2,
+ *          and the actual size of the extra part is
+ *             e' = f1 * f2
+ *          Then the actual width is:
+ *             w' = 63 + (n - 1) * 62 + f1 * f2 - 1
+ */
+l_int32
+getExtendedCompositeParameters(l_int32   size,
+                               l_int32  *pn,
+                               l_int32  *pextra,
+                               l_int32  *pactualsize)
+{
+l_int32  n, extra, fact1, fact2;
+
+    PROCNAME("getExtendedCompositeParameters");
+
+    if (!pn || !pextra)
+        return ERROR_INT("&n and &extra not both defined", procName, 1);
+
+    if (size <= 63) {
+        n = 0;
+        extra = L_MIN(1, size);
+    } else {  /* size > 63 */
+        n = 1 + (l_int32)((size - 63) / 62);
+        extra = size - 63 - (n - 1) * 62 + 1;
+    }
+
+    if (pactualsize) {
+        selectComposableSizes(extra, &fact1, &fact2);
+        *pactualsize = 63 + (n - 1) * 62 + fact1 * fact2 - 1;
+    }
+
+    *pn = n;
+    *pextra = extra;
+    return 0;
+}
diff --git a/src/morphseq.c b/src/morphseq.c
new file mode 100644 (file)
index 0000000..5b972ef
--- /dev/null
@@ -0,0 +1,1235 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  morphseq.c
+ *
+ *      Run a sequence of binary rasterop morphological operations
+ *            PIX     *pixMorphSequence()
+ *
+ *      Run a sequence of binary composite rasterop morphological operations
+ *            PIX     *pixMorphCompSequence()
+ *
+ *      Run a sequence of binary dwa morphological operations
+ *            PIX     *pixMorphSequenceDwa()
+ *
+ *      Run a sequence of binary composite dwa morphological operations
+ *            PIX     *pixMorphCompSequenceDwa()
+ *
+ *      Parser verifier for binary morphological operations
+ *            l_int32  morphSequenceVerify()
+ *
+ *      Run a sequence of grayscale morphological operations
+ *            PIX     *pixGrayMorphSequence()
+ *
+ *      Run a sequence of color morphological operations
+ *            PIX     *pixColorMorphSequence()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*-------------------------------------------------------------------------*
+ *         Run a sequence of binary rasterop morphological operations      *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  pixMorphSequence()
+ *
+ *      Input:  pixs
+ *              sequence (string specifying sequence)
+ *              dispsep (controls debug display of each result in the sequence:
+ *                       0: no output
+ *                       > 0: gives horizontal separation in pixels between
+ *                            successive displays
+ *                       < 0: pdf output; abs(dispsep) is used for naming)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This does rasterop morphology on binary images.
+ *      (2) This runs a pipeline of operations; no branching is allowed.
+ *      (3) This only uses brick Sels, which are created on the fly.
+ *          In the future this will be generalized to extract Sels from
+ *          a Sela by name.
+ *      (4) A new image is always produced; the input image is not changed.
+ *      (5) This contains an interpreter, allowing sequences to be
+ *          generated and run.
+ *      (6) The format of the sequence string is defined below.
+ *      (7) In addition to morphological operations, rank order reduction
+ *          and replicated expansion allow operations to take place
+ *          downscaled by a power of 2.
+ *      (8) Intermediate results can optionally be displayed.
+ *      (9) Thanks to Dar-Shyang Lee, who had the idea for this and
+ *          built the first implementation.
+ *      (10) The sequence string is formatted as follows:
+ *            - An arbitrary number of operations,  each separated
+ *              by a '+' character.  White space is ignored.
+ *            - Each operation begins with a case-independent character
+ *              specifying the operation:
+ *                 d or D  (dilation)
+ *                 e or E  (erosion)
+ *                 o or O  (opening)
+ *                 c or C  (closing)
+ *                 r or R  (rank binary reduction)
+ *                 x or X  (replicative binary expansion)
+ *                 b or B  (add a border of 0 pixels of this size)
+ *            - The args to the morphological operations are bricks of hits,
+ *              and are formatted as a.b, where a and b are horizontal and
+ *              vertical dimensions, rsp.
+ *            - The args to the reduction are a sequence of up to 4 integers,
+ *              each from 1 to 4.
+ *            - The arg to the expansion is a power of two, in the set
+ *              {2, 4, 8, 16}.
+ *      (11) An example valid sequence is:
+ *               "b32 + o1.3 + C3.1 + r23 + e2.2 + D3.2 + X4"
+ *           In this example, the following operation sequence is carried out:
+ *             * b32: Add a 32 pixel border around the input image
+ *             * o1.3: Opening with vert sel of length 3 (e.g., 1 x 3)
+ *             * C3.1: Closing with horiz sel of length 3  (e.g., 3 x 1)
+ *             * r23: Two successive 2x2 reductions with rank 2 in the first
+ *                    and rank 3 in the second.  The result is a 4x reduced pix.
+ *             * e2.2: Erosion with a 2x2 sel (origin will be at x,y: 0,0)
+ *             * d3.2: Dilation with a 3x2 sel (origin will be at x,y: 1,0)
+ *             * X4: 4x replicative expansion, back to original resolution
+ *      (12) The safe closing is used.  However, if you implement a
+ *           closing as separable dilations followed by separable erosions,
+ *           it will not be safe.  For that situation, you need to add
+ *           a sufficiently large border as the first operation in
+ *           the sequence.  This will be removed automatically at the
+ *           end.  There are two cautions:
+ *              - When computing what is sufficient, remember that if
+ *                reductions are carried out, the border is also reduced.
+ *              - The border is removed at the end, so if a border is
+ *                added at the beginning, the result must be at the
+ *                same resolution as the input!
+ */
+PIX *
+pixMorphSequence(PIX         *pixs,
+                 const char  *sequence,
+                 l_int32      dispsep)
+{
+char    *rawop, *op, *fname;
+char     buf[256];
+l_int32  nops, i, j, nred, fact, w, h, x, y, border, pdfout;
+l_int32  level[4];
+PIX     *pixt1, *pixt2;
+PIXA    *pixa;
+SARRAY  *sa;
+
+    PROCNAME("pixMorphSequence");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!sequence)
+        return (PIX *)ERROR_PTR("sequence not defined", procName, NULL);
+
+        /* Split sequence into individual operations */
+    sa = sarrayCreate(0);
+    sarraySplitString(sa, sequence, "+");
+    nops = sarrayGetCount(sa);
+    pdfout = (dispsep < 0) ? 1 : 0;
+
+    if (!morphSequenceVerify(sa)) {
+        sarrayDestroy(&sa);
+        return (PIX *)ERROR_PTR("sequence not valid", procName, NULL);
+    }
+
+        /* Parse and operate */
+    pixa = NULL;
+    if (pdfout) {
+        pixa = pixaCreate(0);
+        pixaAddPix(pixa, pixs, L_CLONE);
+        snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep));
+        fname = genPathname(buf, NULL);
+    }
+    border = 0;
+    pixt1 = pixCopy(NULL, pixs);
+    pixt2 = NULL;
+    x = y = 0;
+    for (i = 0; i < nops; i++) {
+        rawop = sarrayGetString(sa, i, L_NOCOPY);
+        op = stringRemoveChars(rawop, " \n\t");
+        switch (op[0])
+        {
+        case 'd':
+        case 'D':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixDilateBrick(NULL, pixt1, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'e':
+        case 'E':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixErodeBrick(NULL, pixt1, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'o':
+        case 'O':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixOpenBrick(pixt1, pixt1, w, h);
+            break;
+        case 'c':
+        case 'C':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixCloseSafeBrick(pixt1, pixt1, w, h);
+            break;
+        case 'r':
+        case 'R':
+            nred = strlen(op) - 1;
+            for (j = 0; j < nred; j++)
+                level[j] = op[j + 1] - '0';
+            for (j = nred; j < 4; j++)
+                level[j] = 0;
+            pixt2 = pixReduceRankBinaryCascade(pixt1, level[0], level[1],
+                                               level[2], level[3]);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'x':
+        case 'X':
+            sscanf(&op[1], "%d", &fact);
+            pixt2 = pixExpandReplicate(pixt1, fact);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'b':
+        case 'B':
+            sscanf(&op[1], "%d", &border);
+            pixt2 = pixAddBorder(pixt1, border, 0);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        default:
+            /* All invalid ops are caught in the first pass */
+            break;
+        }
+        LEPT_FREE(op);
+
+            /* Debug output */
+        if (dispsep > 0) {
+            pixDisplay(pixt1, x, y);
+            x += dispsep;
+        }
+        if (pdfout)
+            pixaAddPix(pixa, pixt1, L_COPY);
+    }
+    if (border > 0) {
+        pixt2 = pixRemoveBorder(pixt1, border);
+        pixSwapAndDestroy(&pixt1, &pixt2);
+    }
+
+    if (pdfout) {
+        pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+        LEPT_FREE(fname);
+        pixaDestroy(&pixa);
+    }
+
+    sarrayDestroy(&sa);
+    return pixt1;
+}
+
+
+/*-------------------------------------------------------------------------*
+ *   Run a sequence of binary composite rasterop morphological operations  *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  pixMorphCompSequence()
+ *
+ *      Input:  pixs
+ *              sequence (string specifying sequence)
+ *              dispsep (controls debug display of each result in the sequence:
+ *                       0: no output
+ *                       > 0: gives horizontal separation in pixels between
+ *                            successive displays
+ *                       < 0: pdf output; abs(dispsep) is used for naming)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This does rasterop morphology on binary images, using composite
+ *          operations for extra speed on large Sels.
+ *      (2) Safe closing is used atomically.  However, if you implement a
+ *          closing as a sequence with a dilation followed by an
+ *          erosion, it will not be safe, and to ensure that you have
+ *          no boundary effects you must add a border in advance and
+ *          remove it at the end.
+ *      (3) For other usage details, see the notes for pixMorphSequence().
+ *      (4) The sequence string is formatted as follows:
+ *            - An arbitrary number of operations,  each separated
+ *              by a '+' character.  White space is ignored.
+ *            - Each operation begins with a case-independent character
+ *              specifying the operation:
+ *                 d or D  (dilation)
+ *                 e or E  (erosion)
+ *                 o or O  (opening)
+ *                 c or C  (closing)
+ *                 r or R  (rank binary reduction)
+ *                 x or X  (replicative binary expansion)
+ *                 b or B  (add a border of 0 pixels of this size)
+ *            - The args to the morphological operations are bricks of hits,
+ *              and are formatted as a.b, where a and b are horizontal and
+ *              vertical dimensions, rsp.
+ *            - The args to the reduction are a sequence of up to 4 integers,
+ *              each from 1 to 4.
+ *            - The arg to the expansion is a power of two, in the set
+ *              {2, 4, 8, 16}.
+ */
+PIX *
+pixMorphCompSequence(PIX         *pixs,
+                     const char  *sequence,
+                     l_int32      dispsep)
+{
+char    *rawop, *op, *fname;
+char     buf[256];
+l_int32  nops, i, j, nred, fact, w, h, x, y, border, pdfout;
+l_int32  level[4];
+PIX     *pixt1, *pixt2;
+PIXA    *pixa;
+SARRAY  *sa;
+
+    PROCNAME("pixMorphCompSequence");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!sequence)
+        return (PIX *)ERROR_PTR("sequence not defined", procName, NULL);
+
+        /* Split sequence into individual operations */
+    sa = sarrayCreate(0);
+    sarraySplitString(sa, sequence, "+");
+    nops = sarrayGetCount(sa);
+    pdfout = (dispsep < 0) ? 1 : 0;
+
+    if (!morphSequenceVerify(sa)) {
+        sarrayDestroy(&sa);
+        return (PIX *)ERROR_PTR("sequence not valid", procName, NULL);
+    }
+
+        /* Parse and operate */
+    pixa = NULL;
+    if (pdfout) {
+        pixa = pixaCreate(0);
+        pixaAddPix(pixa, pixs, L_CLONE);
+        snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep));
+        fname = genPathname(buf, NULL);
+    }
+    border = 0;
+    pixt1 = pixCopy(NULL, pixs);
+    pixt2 = NULL;
+    x = y = 0;
+    for (i = 0; i < nops; i++) {
+        rawop = sarrayGetString(sa, i, L_NOCOPY);
+        op = stringRemoveChars(rawop, " \n\t");
+        switch (op[0])
+        {
+        case 'd':
+        case 'D':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixDilateCompBrick(NULL, pixt1, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'e':
+        case 'E':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixErodeCompBrick(NULL, pixt1, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'o':
+        case 'O':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixOpenCompBrick(pixt1, pixt1, w, h);
+            break;
+        case 'c':
+        case 'C':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixCloseSafeCompBrick(pixt1, pixt1, w, h);
+            break;
+        case 'r':
+        case 'R':
+            nred = strlen(op) - 1;
+            for (j = 0; j < nred; j++)
+                level[j] = op[j + 1] - '0';
+            for (j = nred; j < 4; j++)
+                level[j] = 0;
+            pixt2 = pixReduceRankBinaryCascade(pixt1, level[0], level[1],
+                                               level[2], level[3]);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'x':
+        case 'X':
+            sscanf(&op[1], "%d", &fact);
+            pixt2 = pixExpandReplicate(pixt1, fact);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'b':
+        case 'B':
+            sscanf(&op[1], "%d", &border);
+            pixt2 = pixAddBorder(pixt1, border, 0);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        default:
+            /* All invalid ops are caught in the first pass */
+            break;
+        }
+        LEPT_FREE(op);
+
+            /* Debug output */
+        if (dispsep > 0) {
+            pixDisplay(pixt1, x, y);
+            x += dispsep;
+        }
+        if (pdfout)
+            pixaAddPix(pixa, pixt1, L_COPY);
+    }
+    if (border > 0) {
+        pixt2 = pixRemoveBorder(pixt1, border);
+        pixSwapAndDestroy(&pixt1, &pixt2);
+    }
+
+    if (pdfout) {
+        pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+        LEPT_FREE(fname);
+        pixaDestroy(&pixa);
+    }
+
+    sarrayDestroy(&sa);
+    return pixt1;
+}
+
+
+/*-------------------------------------------------------------------------*
+ *           Run a sequence of binary dwa morphological operations         *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  pixMorphSequenceDwa()
+ *
+ *      Input:  pixs
+ *              sequence (string specifying sequence)
+ *              dispsep (controls debug display of each result in the sequence:
+ *                       0: no output
+ *                       > 0: gives horizontal separation in pixels between
+ *                            successive displays
+ *                       < 0: pdf output; abs(dispsep) is used for naming)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This does dwa morphology on binary images.
+ *      (2) This runs a pipeline of operations; no branching is allowed.
+ *      (3) This only uses brick Sels that have been pre-compiled with
+ *          dwa code.
+ *      (4) A new image is always produced; the input image is not changed.
+ *      (5) This contains an interpreter, allowing sequences to be
+ *          generated and run.
+ *      (6) See pixMorphSequence() for further information about usage.
+ */
+PIX *
+pixMorphSequenceDwa(PIX         *pixs,
+                    const char  *sequence,
+                    l_int32      dispsep)
+{
+char    *rawop, *op, *fname;
+char     buf[256];
+l_int32  nops, i, j, nred, fact, w, h, x, y, border, pdfout;
+l_int32  level[4];
+PIX     *pixt1, *pixt2;
+PIXA    *pixa;
+SARRAY  *sa;
+
+    PROCNAME("pixMorphSequenceDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!sequence)
+        return (PIX *)ERROR_PTR("sequence not defined", procName, NULL);
+
+        /* Split sequence into individual operations */
+    sa = sarrayCreate(0);
+    sarraySplitString(sa, sequence, "+");
+    nops = sarrayGetCount(sa);
+    pdfout = (dispsep < 0) ? 1 : 0;
+
+    if (!morphSequenceVerify(sa)) {
+        sarrayDestroy(&sa);
+        return (PIX *)ERROR_PTR("sequence not valid", procName, NULL);
+    }
+
+        /* Parse and operate */
+    pixa = NULL;
+    if (pdfout) {
+        pixa = pixaCreate(0);
+        pixaAddPix(pixa, pixs, L_CLONE);
+        snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep));
+        fname = genPathname(buf, NULL);
+    }
+    border = 0;
+    pixt1 = pixCopy(NULL, pixs);
+    pixt2 = NULL;
+    x = y = 0;
+    for (i = 0; i < nops; i++) {
+        rawop = sarrayGetString(sa, i, L_NOCOPY);
+        op = stringRemoveChars(rawop, " \n\t");
+        switch (op[0])
+        {
+        case 'd':
+        case 'D':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixDilateBrickDwa(NULL, pixt1, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'e':
+        case 'E':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixErodeBrickDwa(NULL, pixt1, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'o':
+        case 'O':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixOpenBrickDwa(pixt1, pixt1, w, h);
+            break;
+        case 'c':
+        case 'C':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixCloseBrickDwa(pixt1, pixt1, w, h);
+            break;
+        case 'r':
+        case 'R':
+            nred = strlen(op) - 1;
+            for (j = 0; j < nred; j++)
+                level[j] = op[j + 1] - '0';
+            for (j = nred; j < 4; j++)
+                level[j] = 0;
+            pixt2 = pixReduceRankBinaryCascade(pixt1, level[0], level[1],
+                                               level[2], level[3]);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'x':
+        case 'X':
+            sscanf(&op[1], "%d", &fact);
+            pixt2 = pixExpandReplicate(pixt1, fact);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'b':
+        case 'B':
+            sscanf(&op[1], "%d", &border);
+            pixt2 = pixAddBorder(pixt1, border, 0);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        default:
+            /* All invalid ops are caught in the first pass */
+            break;
+        }
+        LEPT_FREE(op);
+
+            /* Debug output */
+        if (dispsep > 0) {
+            pixDisplay(pixt1, x, y);
+            x += dispsep;
+        }
+        if (pdfout)
+            pixaAddPix(pixa, pixt1, L_COPY);
+    }
+    if (border > 0) {
+        pixt2 = pixRemoveBorder(pixt1, border);
+        pixSwapAndDestroy(&pixt1, &pixt2);
+    }
+
+    if (pdfout) {
+        pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+        LEPT_FREE(fname);
+        pixaDestroy(&pixa);
+    }
+
+    sarrayDestroy(&sa);
+    return pixt1;
+}
+
+
+/*-------------------------------------------------------------------------*
+ *      Run a sequence of binary composite dwa morphological operations    *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  pixMorphCompSequenceDwa()
+ *
+ *      Input:  pixs
+ *              sequence (string specifying sequence)
+ *              dispsep (controls debug display of each result in the sequence:
+ *                       0: no output
+ *                       > 0: gives horizontal separation in pixels between
+ *                            successive displays
+ *                       < 0: pdf output; abs(dispsep) is used for naming)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This does dwa morphology on binary images, using brick Sels.
+ *      (2) This runs a pipeline of operations; no branching is allowed.
+ *      (3) It implements all brick Sels that have dimensions up to 63
+ *          on each side, using a composite (linear + comb) when useful.
+ *      (4) A new image is always produced; the input image is not changed.
+ *      (5) This contains an interpreter, allowing sequences to be
+ *          generated and run.
+ *      (6) See pixMorphSequence() for further information about usage.
+ */
+PIX *
+pixMorphCompSequenceDwa(PIX         *pixs,
+                        const char  *sequence,
+                        l_int32      dispsep)
+{
+char    *rawop, *op, *fname;
+char     buf[256];
+l_int32  nops, i, j, nred, fact, w, h, x, y, border, pdfout;
+l_int32  level[4];
+PIX     *pixt1, *pixt2;
+PIXA    *pixa;
+SARRAY  *sa;
+
+    PROCNAME("pixMorphCompSequenceDwa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!sequence)
+        return (PIX *)ERROR_PTR("sequence not defined", procName, NULL);
+
+        /* Split sequence into individual operations */
+    sa = sarrayCreate(0);
+    sarraySplitString(sa, sequence, "+");
+    nops = sarrayGetCount(sa);
+    pdfout = (dispsep < 0) ? 1 : 0;
+
+    if (!morphSequenceVerify(sa)) {
+        sarrayDestroy(&sa);
+        return (PIX *)ERROR_PTR("sequence not valid", procName, NULL);
+    }
+
+        /* Parse and operate */
+    pixa = NULL;
+    if (pdfout) {
+        pixa = pixaCreate(0);
+        pixaAddPix(pixa, pixs, L_CLONE);
+        snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep));
+        fname = genPathname(buf, NULL);
+    }
+    border = 0;
+    pixt1 = pixCopy(NULL, pixs);
+    pixt2 = NULL;
+    x = y = 0;
+    for (i = 0; i < nops; i++) {
+        rawop = sarrayGetString(sa, i, L_NOCOPY);
+        op = stringRemoveChars(rawop, " \n\t");
+        switch (op[0])
+        {
+        case 'd':
+        case 'D':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixDilateCompBrickDwa(NULL, pixt1, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'e':
+        case 'E':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixErodeCompBrickDwa(NULL, pixt1, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'o':
+        case 'O':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixOpenCompBrickDwa(pixt1, pixt1, w, h);
+            break;
+        case 'c':
+        case 'C':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixCloseCompBrickDwa(pixt1, pixt1, w, h);
+            break;
+        case 'r':
+        case 'R':
+            nred = strlen(op) - 1;
+            for (j = 0; j < nred; j++)
+                level[j] = op[j + 1] - '0';
+            for (j = nred; j < 4; j++)
+                level[j] = 0;
+            pixt2 = pixReduceRankBinaryCascade(pixt1, level[0], level[1],
+                                               level[2], level[3]);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'x':
+        case 'X':
+            sscanf(&op[1], "%d", &fact);
+            pixt2 = pixExpandReplicate(pixt1, fact);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'b':
+        case 'B':
+            sscanf(&op[1], "%d", &border);
+            pixt2 = pixAddBorder(pixt1, border, 0);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        default:
+            /* All invalid ops are caught in the first pass */
+            break;
+        }
+        LEPT_FREE(op);
+
+            /* Debug output */
+        if (dispsep > 0) {
+            pixDisplay(pixt1, x, y);
+            x += dispsep;
+        }
+        if (pdfout)
+            pixaAddPix(pixa, pixt1, L_COPY);
+    }
+    if (border > 0) {
+        pixt2 = pixRemoveBorder(pixt1, border);
+        pixSwapAndDestroy(&pixt1, &pixt2);
+    }
+
+    if (pdfout) {
+        pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+        LEPT_FREE(fname);
+        pixaDestroy(&pixa);
+    }
+
+    sarrayDestroy(&sa);
+    return pixt1;
+}
+
+
+/*-------------------------------------------------------------------------*
+ *            Parser verifier for binary morphological operations          *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  morphSequenceVerify()
+ *
+ *      Input:  sarray (of operation sequence)
+ *      Return: TRUE if valid; FALSE otherwise or on error
+ *
+ *  Notes:
+ *      (1) This does verification of valid binary morphological
+ *          operation sequences.
+ *      (2) See pixMorphSequence() for notes on valid operations
+ *          in the sequence.
+ */
+l_int32
+morphSequenceVerify(SARRAY  *sa)
+{
+char    *rawop, *op;
+l_int32  nops, i, j, nred, fact, valid, w, h, netred, border;
+l_int32  level[4];
+l_int32  intlogbase2[5] = {1, 2, 3, 0, 4};  /* of arg/4 */
+
+    PROCNAME("morphSequenceVerify");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, FALSE);
+
+    nops = sarrayGetCount(sa);
+    valid = TRUE;
+    netred = 0;
+    border = 0;
+    for (i = 0; i < nops; i++) {
+        rawop = sarrayGetString(sa, i, L_NOCOPY);
+        op = stringRemoveChars(rawop, " \n\t");
+        switch (op[0])
+        {
+        case 'd':
+        case 'D':
+        case 'e':
+        case 'E':
+        case 'o':
+        case 'O':
+        case 'c':
+        case 'C':
+            if (sscanf(&op[1], "%d.%d", &w, &h) != 2) {
+                fprintf(stderr, "*** op: %s invalid\n", op);
+                valid = FALSE;
+                break;
+            }
+            if (w <= 0 || h <= 0) {
+                fprintf(stderr,
+                        "*** op: %s; w = %d, h = %d; must both be > 0\n",
+                        op, w, h);
+                valid = FALSE;
+                break;
+            }
+/*            fprintf(stderr, "op = %s; w = %d, h = %d\n", op, w, h); */
+            break;
+        case 'r':
+        case 'R':
+            nred = strlen(op) - 1;
+            netred += nred;
+            if (nred < 1 || nred > 4) {
+                fprintf(stderr,
+                        "*** op = %s; num reduct = %d; must be in {1,2,3,4}\n",
+                        op, nred);
+                valid = FALSE;
+                break;
+            }
+            for (j = 0; j < nred; j++) {
+                level[j] = op[j + 1] - '0';
+                if (level[j] < 1 || level[j] > 4) {
+                    fprintf(stderr, "*** op = %s; level[%d] = %d is invalid\n",
+                            op, j, level[j]);
+                    valid = FALSE;
+                    break;
+                }
+            }
+            if (!valid)
+                break;
+/*            fprintf(stderr, "op = %s", op); */
+            for (j = 0; j < nred; j++) {
+                level[j] = op[j + 1] - '0';
+/*                fprintf(stderr, ", level[%d] = %d", j, level[j]); */
+            }
+/*            fprintf(stderr, "\n"); */
+            break;
+        case 'x':
+        case 'X':
+            if (sscanf(&op[1], "%d", &fact) != 1) {
+                fprintf(stderr, "*** op: %s; fact invalid\n", op);
+                valid = FALSE;
+                break;
+            }
+            if (fact != 2 && fact != 4 && fact != 8 && fact != 16) {
+                fprintf(stderr, "*** op = %s; invalid fact = %d\n", op, fact);
+                valid = FALSE;
+                break;
+            }
+            netred -= intlogbase2[fact / 4];
+/*            fprintf(stderr, "op = %s; fact = %d\n", op, fact); */
+            break;
+        case 'b':
+        case 'B':
+            if (sscanf(&op[1], "%d", &fact) != 1) {
+                fprintf(stderr, "*** op: %s; fact invalid\n", op);
+                valid = FALSE;
+                break;
+            }
+            if (i > 0) {
+                fprintf(stderr, "*** op = %s; must be first op\n", op);
+                valid = FALSE;
+                break;
+            }
+            if (fact < 1) {
+                fprintf(stderr, "*** op = %s; invalid fact = %d\n", op, fact);
+                valid = FALSE;
+                break;
+            }
+            border = fact;
+/*            fprintf(stderr, "op = %s; fact = %d\n", op, fact); */
+            break;
+        default:
+            fprintf(stderr, "*** nonexistent op = %s\n", op);
+            valid = FALSE;
+        }
+        LEPT_FREE(op);
+    }
+
+    if (border != 0 && netred != 0) {
+        fprintf(stderr,
+                "*** op = %s; border added but net reduction not 0\n", op);
+        valid = FALSE;
+    }
+    return valid;
+}
+
+
+/*-----------------------------------------------------------------*
+ *       Run a sequence of grayscale morphological operations      *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixGrayMorphSequence()
+ *
+ *      Input:  pixs
+ *              sequence (string specifying sequence)
+ *              dispsep (controls debug display of each result in the sequence:
+ *                       0: no output
+ *                       > 0: gives horizontal separation in pixels between
+ *                            successive displays
+ *                       < 0: pdf output; abs(dispsep) is used for naming)
+ *              dispy (if dispsep > 0, this gives the y-value of the
+ *                     UL corner for display; otherwise it is ignored)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This works on 8 bpp grayscale images.
+ *      (2) This runs a pipeline of operations; no branching is allowed.
+ *      (3) This only uses brick SELs.
+ *      (4) A new image is always produced; the input image is not changed.
+ *      (5) This contains an interpreter, allowing sequences to be
+ *          generated and run.
+ *      (6) The format of the sequence string is defined below.
+ *      (7) In addition to morphological operations, the composite
+ *          morph/subtract tophat can be performed.
+ *      (8) Sel sizes (width, height) must each be odd numbers.
+ *      (9) Intermediate results can optionally be displayed
+ *      (10) The sequence string is formatted as follows:
+ *            - An arbitrary number of operations,  each separated
+ *              by a '+' character.  White space is ignored.
+ *            - Each operation begins with a case-independent character
+ *              specifying the operation:
+ *                 d or D  (dilation)
+ *                 e or E  (erosion)
+ *                 o or O  (opening)
+ *                 c or C  (closing)
+ *                 t or T  (tophat)
+ *            - The args to the morphological operations are bricks of hits,
+ *              and are formatted as a.b, where a and b are horizontal and
+ *              vertical dimensions, rsp. (each must be an odd number)
+ *            - The args to the tophat are w or W (for white tophat)
+ *              or b or B (for black tophat), followed by a.b as for
+ *              the dilation, erosion, opening and closing.
+ *           Example valid sequences are:
+ *             "c5.3 + o7.5"
+ *             "c9.9 + tw9.9"
+ */
+PIX *
+pixGrayMorphSequence(PIX         *pixs,
+                     const char  *sequence,
+                     l_int32      dispsep,
+                     l_int32      dispy)
+{
+char    *rawop, *op, *fname;
+char     buf[256];
+l_int32  nops, i, valid, w, h, x, pdfout;
+PIX     *pixt1, *pixt2;
+PIXA    *pixa;
+SARRAY  *sa;
+
+    PROCNAME("pixGrayMorphSequence");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!sequence)
+        return (PIX *)ERROR_PTR("sequence not defined", procName, NULL);
+
+        /* Split sequence into individual operations */
+    sa = sarrayCreate(0);
+    sarraySplitString(sa, sequence, "+");
+    nops = sarrayGetCount(sa);
+    pdfout = (dispsep < 0) ? 1 : 0;
+
+        /* Verify that the operation sequence is valid */
+    valid = TRUE;
+    for (i = 0; i < nops; i++) {
+        rawop = sarrayGetString(sa, i, L_NOCOPY);
+        op = stringRemoveChars(rawop, " \n\t");
+        switch (op[0])
+        {
+        case 'd':
+        case 'D':
+        case 'e':
+        case 'E':
+        case 'o':
+        case 'O':
+        case 'c':
+        case 'C':
+            if (sscanf(&op[1], "%d.%d", &w, &h) != 2) {
+                fprintf(stderr, "*** op: %s invalid\n", op);
+                valid = FALSE;
+                break;
+            }
+            if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
+                fprintf(stderr,
+                        "*** op: %s; w = %d, h = %d; must both be odd\n",
+                        op, w, h);
+                valid = FALSE;
+                break;
+            }
+/*            fprintf(stderr, "op = %s; w = %d, h = %d\n", op, w, h); */
+            break;
+        case 't':
+        case 'T':
+            if (op[1] != 'w' && op[1] != 'W' &&
+                op[1] != 'b' && op[1] != 'B') {
+                fprintf(stderr,
+                        "*** op = %s; arg %c must be 'w' or 'b'\n", op, op[1]);
+                valid = FALSE;
+                break;
+            }
+            sscanf(&op[2], "%d.%d", &w, &h);
+            if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
+                fprintf(stderr,
+                        "*** op: %s; w = %d, h = %d; must both be odd\n",
+                        op, w, h);
+                valid = FALSE;
+                break;
+            }
+/*            fprintf(stderr, "op = %s", op); */
+            break;
+        default:
+            fprintf(stderr, "*** nonexistent op = %s\n", op);
+            valid = FALSE;
+        }
+        LEPT_FREE(op);
+    }
+    if (!valid) {
+        sarrayDestroy(&sa);
+        return (PIX *)ERROR_PTR("sequence invalid", procName, NULL);
+    }
+
+        /* Parse and operate */
+    pixa = NULL;
+    if (pdfout) {
+        pixa = pixaCreate(0);
+        pixaAddPix(pixa, pixs, L_CLONE);
+        snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep));
+        fname = genPathname(buf, NULL);
+    }
+    pixt1 = pixCopy(NULL, pixs);
+    pixt2 = NULL;
+    x = 0;
+    for (i = 0; i < nops; i++) {
+        rawop = sarrayGetString(sa, i, L_NOCOPY);
+        op = stringRemoveChars(rawop, " \n\t");
+        switch (op[0])
+        {
+        case 'd':
+        case 'D':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixDilateGray(pixt1, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'e':
+        case 'E':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixErodeGray(pixt1, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'o':
+        case 'O':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixOpenGray(pixt1, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'c':
+        case 'C':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixCloseGray(pixt1, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 't':
+        case 'T':
+            sscanf(&op[2], "%d.%d", &w, &h);
+            if (op[1] == 'w' || op[1] == 'W')
+                pixt2 = pixTophat(pixt1, w, h, L_TOPHAT_WHITE);
+            else   /* 'b' or 'B' */
+                pixt2 = pixTophat(pixt1, w, h, L_TOPHAT_BLACK);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        default:
+            /* All invalid ops are caught in the first pass */
+            break;
+        }
+        LEPT_FREE(op);
+
+            /* Debug output */
+        if (dispsep > 0) {
+            pixDisplay(pixt1, x, dispy);
+            x += dispsep;
+        }
+        if (pdfout)
+            pixaAddPix(pixa, pixt1, L_COPY);
+    }
+
+    if (pdfout) {
+        pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+        LEPT_FREE(fname);
+        pixaDestroy(&pixa);
+    }
+
+    sarrayDestroy(&sa);
+    return pixt1;
+}
+
+
+/*-----------------------------------------------------------------*
+ *         Run a sequence of color morphological operations        *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixColorMorphSequence()
+ *
+ *      Input:  pixs
+ *              sequence (string specifying sequence)
+ *              dispsep (controls debug display of each result in the sequence:
+ *                       0: no output
+ *                       > 0: gives horizontal separation in pixels between
+ *                            successive displays
+ *                       < 0: pdf output; abs(dispsep) is used for naming)
+ *              dispy (if dispsep > 0, this gives the y-value of the
+ *                     UL corner for display; otherwise it is ignored)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This works on 32 bpp rgb images.
+ *      (2) Each component is processed separately.
+ *      (3) This runs a pipeline of operations; no branching is allowed.
+ *      (4) This only uses brick SELs.
+ *      (5) A new image is always produced; the input image is not changed.
+ *      (6) This contains an interpreter, allowing sequences to be
+ *          generated and run.
+ *      (7) Sel sizes (width, height) must each be odd numbers.
+ *      (8) The format of the sequence string is defined below.
+ *      (9) Intermediate results can optionally be displayed.
+ *      (10) The sequence string is formatted as follows:
+ *            - An arbitrary number of operations,  each separated
+ *              by a '+' character.  White space is ignored.
+ *            - Each operation begins with a case-independent character
+ *              specifying the operation:
+ *                 d or D  (dilation)
+ *                 e or E  (erosion)
+ *                 o or O  (opening)
+ *                 c or C  (closing)
+ *            - The args to the morphological operations are bricks of hits,
+ *              and are formatted as a.b, where a and b are horizontal and
+ *              vertical dimensions, rsp. (each must be an odd number)
+ *           Example valid sequences are:
+ *             "c5.3 + o7.5"
+ *             "D9.1"
+ */
+PIX *
+pixColorMorphSequence(PIX         *pixs,
+                      const char  *sequence,
+                      l_int32      dispsep,
+                      l_int32      dispy)
+{
+char    *rawop, *op, *fname;
+char     buf[256];
+l_int32  nops, i, valid, w, h, x, pdfout;
+PIX     *pixt1, *pixt2;
+PIXA    *pixa;
+SARRAY  *sa;
+
+    PROCNAME("pixColorMorphSequence");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!sequence)
+        return (PIX *)ERROR_PTR("sequence not defined", procName, NULL);
+
+        /* Split sequence into individual operations */
+    sa = sarrayCreate(0);
+    sarraySplitString(sa, sequence, "+");
+    nops = sarrayGetCount(sa);
+    pdfout = (dispsep < 0) ? 1 : 0;
+
+        /* Verify that the operation sequence is valid */
+    valid = TRUE;
+    for (i = 0; i < nops; i++) {
+        rawop = sarrayGetString(sa, i, L_NOCOPY);
+        op = stringRemoveChars(rawop, " \n\t");
+        switch (op[0])
+        {
+        case 'd':
+        case 'D':
+        case 'e':
+        case 'E':
+        case 'o':
+        case 'O':
+        case 'c':
+        case 'C':
+            if (sscanf(&op[1], "%d.%d", &w, &h) != 2) {
+                fprintf(stderr, "*** op: %s invalid\n", op);
+                valid = FALSE;
+                break;
+            }
+            if (w < 1 || (w & 1) == 0 || h < 1 || (h & 1) == 0 ) {
+                fprintf(stderr,
+                        "*** op: %s; w = %d, h = %d; must both be odd\n",
+                        op, w, h);
+                valid = FALSE;
+                break;
+            }
+/*            fprintf(stderr, "op = %s; w = %d, h = %d\n", op, w, h); */
+            break;
+        default:
+            fprintf(stderr, "*** nonexistent op = %s\n", op);
+            valid = FALSE;
+        }
+        LEPT_FREE(op);
+    }
+    if (!valid) {
+        sarrayDestroy(&sa);
+        return (PIX *)ERROR_PTR("sequence invalid", procName, NULL);
+    }
+
+        /* Parse and operate */
+    pixa = NULL;
+    if (pdfout) {
+        pixa = pixaCreate(0);
+        pixaAddPix(pixa, pixs, L_CLONE);
+        snprintf(buf, sizeof(buf), "/tmp/seq_output_%d.pdf", L_ABS(dispsep));
+        fname = genPathname(buf, NULL);
+    }
+    pixt1 = pixCopy(NULL, pixs);
+    pixt2 = NULL;
+    x = 0;
+    for (i = 0; i < nops; i++) {
+        rawop = sarrayGetString(sa, i, L_NOCOPY);
+        op = stringRemoveChars(rawop, " \n\t");
+        switch (op[0])
+        {
+        case 'd':
+        case 'D':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixColorMorph(pixt1, L_MORPH_DILATE, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'e':
+        case 'E':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixColorMorph(pixt1, L_MORPH_ERODE, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'o':
+        case 'O':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixColorMorph(pixt1, L_MORPH_OPEN, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        case 'c':
+        case 'C':
+            sscanf(&op[1], "%d.%d", &w, &h);
+            pixt2 = pixColorMorph(pixt1, L_MORPH_CLOSE, w, h);
+            pixSwapAndDestroy(&pixt1, &pixt2);
+            break;
+        default:
+            /* All invalid ops are caught in the first pass */
+            break;
+        }
+        LEPT_FREE(op);
+
+            /* Debug output */
+        if (dispsep > 0) {
+            pixDisplay(pixt1, x, dispy);
+            x += dispsep;
+        }
+        if (pdfout)
+            pixaAddPix(pixa, pixt1, L_COPY);
+    }
+
+    if (pdfout) {
+        pixaConvertToPdf(pixa, 0, 1.0, L_FLATE_ENCODE, 0, fname, fname);
+        LEPT_FREE(fname);
+        pixaDestroy(&pixa);
+    }
+
+    sarrayDestroy(&sa);
+    return pixt1;
+}
diff --git a/src/morphtemplate1.txt b/src/morphtemplate1.txt
new file mode 100644 (file)
index 0000000..eecc22c
--- /dev/null
@@ -0,0 +1,220 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*!
+ *      Top-level fast binary morphology with auto-generated sels
+ *
+--- *            PIX      *pixMorphDwa_*()
+--- *            PIX      *pixFMorphopGen_*()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+---              This file is:  morphtemplate1.txt
+---
+---           We need to include these prototypes:
+---    PIX *pixMorphDwa_*(PIX *pixd, PIX *pixs, l_int32 operation,
+---                        char *selname);
+---    PIX *pixFMorphopGen_*(PIX *pixd, PIX *pixs, l_int32 operation,
+---                          char *selname);
+---    l_int32 fmorphopgen_low_*(l_uint32 *datad, l_int32 w, l_int32 h,
+---                              l_int32 wpld, l_uint32 *datas,
+---                              l_int32  wpls, l_int32 index);
+---
+---           We need to input two static globals here:
+---    static l_int32     NUM_SELS_GENERATED =  <some number>;
+---    static char  SEL_NAMES[][80] =    {"<string1>", "<string2>", ...};
+
+/*!
+--- *  pixMorphDwa_*()
+ *
+ *      Input:  pixd (usual 3 choices: null, == pixs, != pixs)
+ *              pixs (1 bpp)
+ *              operation  (L_MORPH_DILATE, L_MORPH_ERODE,
+ *                          L_MORPH_OPEN, L_MORPH_CLOSE)
+ *              sel name
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This simply adds a border, calls the appropriate
+ *          pixFMorphopGen_*(), and removes the border.
+ *          See the notes for that function.
+ *      (2) The size of the border depends on the operation
+ *          and the boundary conditions.
+ */
+PIX *
+---    pixMorphDwa_*(PIX     *pixd,
+              PIX     *pixs,
+              l_int32  operation,
+              char    *selname)
+{
+l_int32  bordercolor, bordersize;
+PIX     *pixt1, *pixt2, *pixt3;
+
+--- PROCNAME("pixMorpDwa_*");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, pixd);
+
+        /* Set the border size */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    bordersize = 32;
+    if (bordercolor == 0 && operation == L_MORPH_CLOSE)
+        bordersize += 32;
+
+    pixt1 = pixAddBorder(pixs, bordersize, 0);
+--- pixt2 = pixFMorphopGen_*(NULL, pixt1, operation, selname);
+    pixt3 = pixRemoveBorder(pixt2, bordersize);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+
+    if (!pixd)
+        return pixt3;
+
+    pixCopy(pixd, pixt3);
+    pixDestroy(&pixt3);
+    return pixd;
+}
+
+
+/*!
+--- *  pixFMorphopGen_*()
+ *
+ *      Input:  pixd (usual 3 choices: null, == pixs, != pixs)
+ *              pixs (1 bpp)
+ *              operation  (L_MORPH_DILATE, L_MORPH_ERODE,
+ *                          L_MORPH_OPEN, L_MORPH_CLOSE)
+ *              sel name
+ *      Return: pixd
+ *
+ *  Notes:
+ *      (1) This is a dwa operation, and the Sels must be limited in
+ *          size to not more than 31 pixels about the origin.
+ *      (2) A border of appropriate size (32 pixels, or 64 pixels
+ *          for safe closing with asymmetric b.c.) must be added before
+ *          this function is called.
+ *      (3) This handles all required setting of the border pixels
+ *          before erosion and dilation.
+ *      (4) The closing operation is safe; no pixels can be removed
+ *          near the boundary.
+ */
+PIX *
+---      pixFMorphopGen_*(PIX     *pixd,
+                 PIX     *pixs,
+                 l_int32  operation,
+                 char    *selname)
+{
+l_int32    i, index, found, w, h, wpls, wpld, bordercolor, erodeop, borderop;
+l_uint32  *datad, *datas, *datat;
+PIX       *pixt;
+
+--- PROCNAME("pixFMorphopGen_*");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, pixd);
+
+        /* Get boundary colors to use */
+    bordercolor = getMorphBorderPixelColor(L_MORPH_ERODE, 1);
+    if (bordercolor == 1)
+        erodeop = PIX_SET;
+    else
+        erodeop = PIX_CLR;
+
+    found = FALSE;
+    for (i = 0; i < NUM_SELS_GENERATED; i++) {
+        if (strcmp(selname, SEL_NAMES[i]) == 0) {
+            found = TRUE;
+            index = 2 * i;
+            break;
+        }
+    }
+    if (found == FALSE)
+        return (PIX *)ERROR_PTR("sel index not found", procName, pixd);
+
+    if (!pixd) {
+        if ((pixd = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    else  /* for in-place or pre-allocated */
+        pixResizeImageData(pixd, pixs);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+        /* The images must be surrounded, in advance, with a border of
+         * size 32 pixels (or 64, for closing), that we'll read from.
+         * Fabricate a "proper" image as the subimage within the 32
+         * pixel border, having the following parameters:  */
+    w = pixGetWidth(pixs) - 64;
+    h = pixGetHeight(pixs) - 64;
+    datas = pixGetData(pixs) + 32 * wpls + 1;
+    datad = pixGetData(pixd) + 32 * wpld + 1;
+
+    if (operation == L_MORPH_DILATE || operation == L_MORPH_ERODE) {
+        borderop = PIX_CLR;
+        if (operation == L_MORPH_ERODE) {
+            borderop = erodeop;
+            index++;
+        }
+        if (pixd == pixs) {  /* in-place; generate a temp image */
+            if ((pixt = pixCopy(NULL, pixs)) == NULL)
+                return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+            datat = pixGetData(pixt) + 32 * wpls + 1;
+            pixSetOrClearBorder(pixt, 32, 32, 32, 32, borderop);
+---         fmorphopgen_low_*(datad, w, h, wpld, datat, wpls, index);
+            pixDestroy(&pixt);
+        }
+        else { /* not in-place */
+            pixSetOrClearBorder(pixs, 32, 32, 32, 32, borderop);
+---         fmorphopgen_low_*(datad, w, h, wpld, datas, wpls, index);
+        }
+    }
+    else {  /* opening or closing; generate a temp image */
+        if ((pixt = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+        datat = pixGetData(pixt) + 32 * wpls + 1;
+        if (operation == L_MORPH_OPEN) {
+            pixSetOrClearBorder(pixs, 32, 32, 32, 32, erodeop);
+---         fmorphopgen_low_*(datat, w, h, wpls, datas, wpls, index + 1);
+            pixSetOrClearBorder(pixt, 32, 32, 32, 32, PIX_CLR);
+---         fmorphopgen_low_*(datad, w, h, wpld, datat, wpls, index);
+        }
+        else {  /* closing */
+            pixSetOrClearBorder(pixs, 32, 32, 32, 32, PIX_CLR);
+---         fmorphopgen_low_*(datat, w, h, wpls, datas, wpls, index);
+            pixSetOrClearBorder(pixt, 32, 32, 32, 32, erodeop);
+---         fmorphopgen_low_*(datad, w, h, wpld, datat, wpls, index + 1);
+        }
+        pixDestroy(&pixt);
+    }
+
+    return pixd;
+}
+
diff --git a/src/morphtemplate2.txt b/src/morphtemplate2.txt
new file mode 100644 (file)
index 0000000..a75c189
--- /dev/null
@@ -0,0 +1,104 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*!
+ *     Low-level fast binary morphology with auto-generated sels
+ *
+ *      Dispatcher:
+--- *            l_int32   fmorphopgen_low_*()
+ *
+ *      Static Low-level:
+--- *            void      fdilate_*_*()
+--- *            void      ferode_*_*()
+ */
+
+#include "allheaders.h"
+
+---              This file is:  morphtemplate2.txt
+---
+---    insert static protos here ...
+
+
+/*---------------------------------------------------------------------*
+ *                          Fast morph dispatcher                      *
+ *---------------------------------------------------------------------*/
+/*!
+--- *  fmorphopgen_low_*()
+ *
+ *       a dispatcher to appropriate low-level code
+ */
+l_int32
+---   fmorphopgen_low_*(l_uint32  *datad,
+                  l_int32    w,
+                  l_int32    h,
+                  l_int32    wpld,
+                  l_uint32  *datas,
+                  l_int32    wpls,
+                  l_int32    index)
+{
+
+    switch (index)
+    {
+---    insert dispatcher code for fdilate* and ferode* routines ...
+    }
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                 Low-level auto-generated static routines                 *
+ *--------------------------------------------------------------------------*/
+/*
+ *  N.B.  In all the low-level routines, the part of the image
+ *        that is accessed has been clipped by 32 pixels on
+ *        all four sides.  This is done in the higher level
+ *        code by redefining w and h smaller and by moving the
+ *        start-of-image pointers up to the beginning of this
+ *        interior rectangle.
+ */
+---   static void fdilate_*_*(l_uint32  *datad,
+            l_int32    w,
+            l_int32    h,
+            l_int32    wpld,
+            l_uint32  *datas,
+            l_int32    wpls)
+{
+l_int32             i;
+register l_int32    j, pwpls;
+register l_uint32  *sptr, *dptr;
+---     declare wplsN args as necessary ...
+    pwpls = (l_uint32)(w + 31) / 32;  /* proper wpl of src */
+
+    for (i = 0; i < h; i++) {
+        sptr = datas + i * wpls;
+        dptr = datad + i * wpld;
+        for (j = 0; j < pwpls; j++, sptr++, dptr++) {
+---     insert barrel-op code for *dptr here ...
+        }
+    }
+}
+
diff --git a/src/numabasic.c b/src/numabasic.c
new file mode 100644 (file)
index 0000000..df76d7d
--- /dev/null
@@ -0,0 +1,1768 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   numabasic.c
+ *
+ *      Numa creation, destruction, copy, clone, etc.
+ *          NUMA        *numaCreate()
+ *          NUMA        *numaCreateFromIArray()
+ *          NUMA        *numaCreateFromFArray()
+ *          NUMA        *numaCreateFromString()
+ *          void        *numaDestroy()
+ *          NUMA        *numaCopy()
+ *          NUMA        *numaClone()
+ *          l_int32      numaEmpty()
+ *
+ *      Add/remove number (float or integer)
+ *          l_int32      numaAddNumber()
+ *          static l_int32  numaExtendArray()
+ *          l_int32      numaInsertNumber()
+ *          l_int32      numaRemoveNumber()
+ *          l_int32      numaReplaceNumber()
+ *
+ *      Numa accessors
+ *          l_int32      numaGetCount()
+ *          l_int32      numaSetCount()
+ *          l_int32      numaGetIValue()
+ *          l_int32      numaGetFValue()
+ *          l_int32      numaSetValue()
+ *          l_int32      numaShiftValue()
+ *          l_int32     *numaGetIArray()
+ *          l_float32   *numaGetFArray()
+ *          l_int32      numaGetRefcount()
+ *          l_int32      numaChangeRefcount()
+ *          l_int32      numaGetParameters()
+ *          l_int32      numaSetParameters()
+ *          l_int32      numaCopyParameters()
+ *
+ *      Convert to string array
+ *          SARRAY      *numaConvertToSarray()
+ *
+ *      Serialize numa for I/O
+ *          NUMA        *numaRead()
+ *          NUMA        *numaReadStream()
+ *          l_int32      numaWrite()
+ *          l_int32      numaWriteStream()
+ *
+ *      Numaa creation, destruction, truncation
+ *          NUMAA       *numaaCreate()
+ *          NUMAA       *numaaCreateFull()
+ *          NUMAA       *numaaTruncate()
+ *          void        *numaaDestroy()
+ *
+ *      Add Numa to Numaa
+ *          l_int32      numaaAddNuma()
+ *          static l_int32   numaaExtendArray()
+ *
+ *      Numaa accessors
+ *          l_int32      numaaGetCount()
+ *          l_int32      numaaGetNumaCount()
+ *          l_int32      numaaGetNumberCount()
+ *          NUMA       **numaaGetPtrArray()
+ *          NUMA        *numaaGetNuma()
+ *          NUMA        *numaaReplaceNuma()
+ *          l_int32      numaaGetValue()
+ *          l_int32      numaaAddNumber()
+ *
+ *      Serialize numaa for I/O
+ *          NUMAA       *numaaRead()
+ *          NUMAA       *numaaReadStream()
+ *          l_int32      numaaWrite()
+ *          l_int32      numaaWriteStream()
+ *
+ *    (1) The Numa is a struct holding an array of floats.  It can also
+ *        be used to store l_int32 values, with some loss of precision
+ *        for floats larger than about 10 million.  Use the L_Dna instead
+ *        if integers larger than a few million need to be stored.
+ *
+ *    (2) Always use the accessors in this file, never the fields directly.
+ *
+ *    (3) Storing and retrieving numbers:
+ *
+ *       * to append a new number to the array, use numaAddNumber().  If
+ *         the number is an int, it will will automatically be converted
+ *         to l_float32 and stored.
+ *
+ *       * to reset a value stored in the array, use numaSetValue().
+ *
+ *       * to increment or decrement a value stored in the array,
+ *         use numaShiftValue().
+ *
+ *       * to obtain a value from the array, use either numaGetIValue()
+ *         or numaGetFValue(), depending on whether you are retrieving
+ *         an integer or a float.  This avoids doing an explicit cast,
+ *         such as
+ *           (a) return a l_float32 and cast it to an l_int32
+ *           (b) cast the return directly to (l_float32 *) to
+ *               satisfy the function prototype, as in
+ *                 numaGetFValue(na, index, (l_float32 *)&ival);   [ugly!]
+ *
+ *    (4) int <--> float conversions:
+ *
+ *        Tradition dictates that type conversions go automatically from
+ *        l_int32 --> l_float32, even though it is possible to lose
+ *        precision for large integers, whereas you must cast (l_int32)
+ *        to go from l_float32 --> l_int32 because you're truncating
+ *        to the integer value.
+ *
+ *    (5) As with other arrays in leptonica, the numa has both an allocated
+ *        size and a count of the stored numbers.  When you add a number, it
+ *        goes on the end of the array, and causes a realloc if the array
+ *        is already filled.  However, in situations where you want to
+ *        add numbers randomly into an array, such as when you build a
+ *        histogram, you must set the count of stored numbers in advance.
+ *        This is done with numaSetCount().  If you set a count larger
+ *        than the allocated array, it does a realloc to the size requested.
+ *
+ *    (6) In situations where the data in a numa correspond to a function
+ *        y(x), the values can be either at equal spacings in x or at
+ *        arbitrary spacings.  For the former, we can represent all x values
+ *        by two parameters: startx (corresponding to y[0]) and delx
+ *        for the change in x for adjacent values y[i] and y[i+1].
+ *        startx and delx are initialized to 0.0 and 1.0, rsp.
+ *        For arbitrary spacings, we use a second numa, and the two
+ *        numas are typically denoted nay and nax.
+ *
+ *    (7) The numa is also the basic struct used for histograms.  Every numa
+ *        has startx and delx fields, initialized to 0.0 and 1.0, that can
+ *        be used to represent the "x" value for the location of the
+ *        first bin and the bin width, respectively.  Accessors are the
+ *        numa*Parameters() functions.  All functions that make numa
+ *        histograms must set these fields properly, and many functions
+ *        that use numa histograms rely on the correctness of these values.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+static const l_int32 INITIAL_PTR_ARRAYSIZE = 50;      /* n'importe quoi */
+
+    /* Static functions */
+static l_int32 numaExtendArray(NUMA  *na);
+static l_int32 numaaExtendArray(NUMAA  *naa);
+
+
+/*--------------------------------------------------------------------------*
+ *               Numa creation, destruction, copy, clone, etc.              *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  numaCreate()
+ *
+ *      Input:  size of number array to be alloc'd (0 for default)
+ *      Return: na, or null on error
+ */
+NUMA *
+numaCreate(l_int32  n)
+{
+NUMA  *na;
+
+    PROCNAME("numaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((na = (NUMA *)LEPT_CALLOC(1, sizeof(NUMA))) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    if ((na->array = (l_float32 *)LEPT_CALLOC(n, sizeof(l_float32))) == NULL)
+        return (NUMA *)ERROR_PTR("number array not made", procName, NULL);
+
+    na->nalloc = n;
+    na->n = 0;
+    na->refcount = 1;
+    na->startx = 0.0;
+    na->delx = 1.0;
+
+    return na;
+}
+
+
+/*!
+ *  numaCreateFromIArray()
+ *
+ *      Input:  iarray (integer)
+ *              size (of the array)
+ *      Return: na, or null on error
+ *
+ *  Notes:
+ *      (1) We can't insert this int array into the numa, because a numa
+ *          takes a float array.  So this just copies the data from the
+ *          input array into the numa.  The input array continues to be
+ *          owned by the caller.
+ */
+NUMA *
+numaCreateFromIArray(l_int32  *iarray,
+                     l_int32   size)
+{
+l_int32  i;
+NUMA    *na;
+
+    PROCNAME("numaCreateFromIArray");
+
+    if (!iarray)
+        return (NUMA *)ERROR_PTR("iarray not defined", procName, NULL);
+    if (size <= 0)
+        return (NUMA *)ERROR_PTR("size must be > 0", procName, NULL);
+
+    na = numaCreate(size);
+    for (i = 0; i < size; i++)
+        numaAddNumber(na, iarray[i]);
+
+    return na;
+}
+
+
+/*!
+ *  numaCreateFromFArray()
+ *
+ *      Input:  farray (float)
+ *              size (of the array)
+ *              copyflag (L_INSERT or L_COPY)
+ *      Return: na, or null on error
+ *
+ *  Notes:
+ *      (1) With L_INSERT, ownership of the input array is transferred
+ *          to the returned numa, and all @size elements are considered
+ *          to be valid.
+ */
+NUMA *
+numaCreateFromFArray(l_float32  *farray,
+                     l_int32     size,
+                     l_int32     copyflag)
+{
+l_int32  i;
+NUMA    *na;
+
+    PROCNAME("numaCreateFromFArray");
+
+    if (!farray)
+        return (NUMA *)ERROR_PTR("farray not defined", procName, NULL);
+    if (size <= 0)
+        return (NUMA *)ERROR_PTR("size must be > 0", procName, NULL);
+    if (copyflag != L_INSERT && copyflag != L_COPY)
+        return (NUMA *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    na = numaCreate(size);
+    if (copyflag == L_INSERT) {
+        if (na->array) LEPT_FREE(na->array);
+        na->array = farray;
+        na->n = size;
+    } else {  /* just copy the contents */
+        for (i = 0; i < size; i++)
+            numaAddNumber(na, farray[i]);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  numaCreateFromString()
+ *
+ *      Input:  string (of comma-separated numbers)
+ *      Return: na, or null on error
+ *
+ *  Notes:
+ *      (1) The numbers can be ints or floats; they will be interpreted
+ *          and stored as floats.  To use them as integers (e.g., for
+ *          indexing into arrays), use numaGetIValue(...).
+ */
+NUMA *
+numaCreateFromString(const char  *str)
+{
+char      *substr;
+l_int32    i, n, nerrors;
+l_float32  val;
+NUMA      *na;
+SARRAY    *sa;
+
+    PROCNAME("numaCreateFromString");
+
+    if (!str || (strlen(str) == 0))
+        return (NUMA *)ERROR_PTR("str not defined or empty", procName, NULL);
+
+    sa = sarrayCreate(0);
+    sarraySplitString(sa, str, ",");
+    n = sarrayGetCount(sa);
+    na = numaCreate(n);
+    nerrors = 0;
+    for (i = 0; i < n; i++) {
+        substr = sarrayGetString(sa, i, L_NOCOPY);
+        if (sscanf(substr, "%f", &val) != 1) {
+            L_ERROR("substr %d not float\n", procName, i);
+            nerrors++;
+        } else {
+            numaAddNumber(na, val);
+        }
+    }
+
+    sarrayDestroy(&sa);
+    if (nerrors > 0) {
+        numaDestroy(&na);
+        return (NUMA *)ERROR_PTR("non-floats in string", procName, NULL);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  numaDestroy()
+ *
+ *      Input:  &na (<to be nulled if it exists>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Decrements the ref count and, if 0, destroys the numa.
+ *      (2) Always nulls the input ptr.
+ */
+void
+numaDestroy(NUMA  **pna)
+{
+NUMA  *na;
+
+    PROCNAME("numaDestroy");
+
+    if (pna == NULL) {
+        L_WARNING("ptr address is NULL\n", procName);
+        return;
+    }
+
+    if ((na = *pna) == NULL)
+        return;
+
+        /* Decrement the ref count.  If it is 0, destroy the numa. */
+    numaChangeRefcount(na, -1);
+    if (numaGetRefcount(na) <= 0) {
+        if (na->array)
+            LEPT_FREE(na->array);
+        LEPT_FREE(na);
+    }
+
+    *pna = NULL;
+    return;
+}
+
+
+/*!
+ *  numaCopy()
+ *
+ *      Input:  na
+ *      Return: copy of numa, or null on error
+ */
+NUMA *
+numaCopy(NUMA  *na)
+{
+l_int32  i;
+NUMA    *cna;
+
+    PROCNAME("numaCopy");
+
+    if (!na)
+        return (NUMA *)ERROR_PTR("na not defined", procName, NULL);
+
+    if ((cna = numaCreate(na->nalloc)) == NULL)
+        return (NUMA *)ERROR_PTR("cna not made", procName, NULL);
+    cna->startx = na->startx;
+    cna->delx = na->delx;
+
+    for (i = 0; i < na->n; i++)
+        numaAddNumber(cna, na->array[i]);
+
+    return cna;
+}
+
+
+/*!
+ *  numaClone()
+ *
+ *      Input:  na
+ *      Return: ptr to same numa, or null on error
+ */
+NUMA *
+numaClone(NUMA  *na)
+{
+    PROCNAME("numaClone");
+
+    if (!na)
+        return (NUMA *)ERROR_PTR("na not defined", procName, NULL);
+
+    numaChangeRefcount(na, 1);
+    return na;
+}
+
+
+/*!
+ *  numaEmpty()
+ *
+ *      Input:  na
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This does not change the allocation of the array.
+ *          It just clears the number of stored numbers, so that
+ *          the array appears to be empty.
+ */
+l_int32
+numaEmpty(NUMA  *na)
+{
+    PROCNAME("numaEmpty");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    na->n = 0;
+    return 0;
+}
+
+
+
+/*--------------------------------------------------------------------------*
+ *                 Number array: add number and extend array                *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  numaAddNumber()
+ *
+ *      Input:  na
+ *              val  (float or int to be added; stored as a float)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaAddNumber(NUMA      *na,
+              l_float32  val)
+{
+l_int32  n;
+
+    PROCNAME("numaAddNumber");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    n = numaGetCount(na);
+    if (n >= na->nalloc)
+        numaExtendArray(na);
+    na->array[n] = val;
+    na->n++;
+    return 0;
+}
+
+
+/*!
+ *  numaExtendArray()
+ *
+ *      Input:  na
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+numaExtendArray(NUMA  *na)
+{
+    PROCNAME("numaExtendArray");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    if ((na->array = (l_float32 *)reallocNew((void **)&na->array,
+                                sizeof(l_float32) * na->nalloc,
+                                2 * sizeof(l_float32) * na->nalloc)) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+
+    na->nalloc *= 2;
+    return 0;
+}
+
+
+/*!
+ *  numaInsertNumber()
+ *
+ *      Input:  na
+ *              index (location in na to insert new value)
+ *              val  (float32 or integer to be added)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This shifts na[i] --> na[i + 1] for all i >= index,
+ *          and then inserts val as na[index].
+ *      (2) It should not be used repeatedly on large arrays,
+ *          because the function is O(n).
+ *
+ */
+l_int32
+numaInsertNumber(NUMA      *na,
+                 l_int32    index,
+                 l_float32  val)
+{
+l_int32  i, n;
+
+    PROCNAME("numaInsertNumber");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    n = numaGetCount(na);
+    if (index < 0 || index > n)
+        return ERROR_INT("index not in {0...n}", procName, 1);
+
+    if (n >= na->nalloc)
+        numaExtendArray(na);
+    for (i = n; i > index; i--)
+        na->array[i] = na->array[i - 1];
+    na->array[index] = val;
+    na->n++;
+    return 0;
+}
+
+
+/*!
+ *  numaRemoveNumber()
+ *
+ *      Input:  na
+ *              index (element to be removed)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This shifts na[i] --> na[i - 1] for all i > index.
+ *      (2) It should not be used repeatedly on large arrays,
+ *          because the function is O(n).
+ */
+l_int32
+numaRemoveNumber(NUMA    *na,
+                 l_int32  index)
+{
+l_int32  i, n;
+
+    PROCNAME("numaRemoveNumber");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    n = numaGetCount(na);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not in {0...n - 1}", procName, 1);
+
+    for (i = index + 1; i < n; i++)
+        na->array[i - 1] = na->array[i];
+    na->n--;
+    return 0;
+}
+
+
+/*!
+ *  numaReplaceNumber()
+ *
+ *      Input:  na
+ *              index (element to be replaced)
+ *              val (new value to replace old one)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaReplaceNumber(NUMA      *na,
+                  l_int32    index,
+                  l_float32  val)
+{
+l_int32  n;
+
+    PROCNAME("numaReplaceNumber");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    n = numaGetCount(na);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not in {0...n - 1}", procName, 1);
+
+    na->array[index] = val;
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                            Numa accessors                            *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaGetCount()
+ *
+ *      Input:  na
+ *      Return: count, or 0 if no numbers or on error
+ */
+l_int32
+numaGetCount(NUMA  *na)
+{
+    PROCNAME("numaGetCount");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 0);
+    return na->n;
+}
+
+
+/*!
+ *  numaSetCount()
+ *
+ *      Input:  na
+ *              newcount
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If newcount <= na->nalloc, this resets na->n.
+ *          Using newcount = 0 is equivalent to numaEmpty().
+ *      (2) If newcount > na->nalloc, this causes a realloc
+ *          to a size na->nalloc = newcount.
+ *      (3) All the previously unused values in na are set to 0.0.
+ */
+l_int32
+numaSetCount(NUMA    *na,
+             l_int32  newcount)
+{
+    PROCNAME("numaSetCount");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (newcount > na->nalloc) {
+        if ((na->array = (l_float32 *)reallocNew((void **)&na->array,
+                         sizeof(l_float32) * na->nalloc,
+                         sizeof(l_float32) * newcount)) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+        na->nalloc = newcount;
+    }
+    na->n = newcount;
+    return 0;
+}
+
+
+/*!
+ *  numaGetFValue()
+ *
+ *      Input:  na
+ *              index (into numa)
+ *              &val  (<return> float value; 0.0 on error)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Caller may need to check the function return value to
+ *          decide if a 0.0 in the returned ival is valid.
+ */
+l_int32
+numaGetFValue(NUMA       *na,
+              l_int32     index,
+              l_float32  *pval)
+{
+    PROCNAME("numaGetFValue");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0.0;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    if (index < 0 || index >= na->n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    *pval = na->array[index];
+    return 0;
+}
+
+
+/*!
+ *  numaGetIValue()
+ *
+ *      Input:  na
+ *              index (into numa)
+ *              &ival  (<return> integer value; 0 on error)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Caller may need to check the function return value to
+ *          decide if a 0 in the returned ival is valid.
+ */
+l_int32
+numaGetIValue(NUMA     *na,
+              l_int32   index,
+              l_int32  *pival)
+{
+l_float32  val;
+
+    PROCNAME("numaGetIValue");
+
+    if (!pival)
+        return ERROR_INT("&ival not defined", procName, 1);
+    *pival = 0;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    if (index < 0 || index >= na->n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    val = na->array[index];
+    *pival = (l_int32)(val + L_SIGN(val) * 0.5);
+    return 0;
+}
+
+
+/*!
+ *  numaSetValue()
+ *
+ *      Input:  na
+ *              index   (to element to be set)
+ *              val  (to set element)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+numaSetValue(NUMA      *na,
+             l_int32    index,
+             l_float32  val)
+{
+    PROCNAME("numaSetValue");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (index < 0 || index >= na->n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    na->array[index] = val;
+    return 0;
+}
+
+
+/*!
+ *  numaShiftValue()
+ *
+ *      Input:  na
+ *              index (to element to change relative to the current value)
+ *              diff  (increment if diff > 0 or decrement if diff < 0)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+numaShiftValue(NUMA      *na,
+               l_int32    index,
+               l_float32  diff)
+{
+    PROCNAME("numaShiftValue");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (index < 0 || index >= na->n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    na->array[index] += diff;
+    return 0;
+}
+
+
+/*!
+ *  numaGetIArray()
+ *
+ *      Input:  na
+ *      Return: a copy of the bare internal array, integerized
+ *              by rounding, or null on error
+ *  Notes:
+ *      (1) A copy of the array is always made, because we need to
+ *          generate an integer array from the bare float array.
+ *          The caller is responsible for freeing the array.
+ *      (2) The array size is determined by the number of stored numbers,
+ *          not by the size of the allocated array in the Numa.
+ *      (3) This function is provided to simplify calculations
+ *          using the bare internal array, rather than continually
+ *          calling accessors on the numa.  It is typically used
+ *          on an array of size 256.
+ */
+l_int32 *
+numaGetIArray(NUMA  *na)
+{
+l_int32   i, n, ival;
+l_int32  *array;
+
+    PROCNAME("numaGetIArray");
+
+    if (!na)
+        return (l_int32 *)ERROR_PTR("na not defined", procName, NULL);
+
+    n = numaGetCount(na);
+    if ((array = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32))) == NULL)
+        return (l_int32 *)ERROR_PTR("array not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(na, i, &ival);
+        array[i] = ival;
+    }
+
+    return array;
+}
+
+
+/*!
+ *  numaGetFArray()
+ *
+ *      Input:  na
+ *              copyflag (L_NOCOPY or L_COPY)
+ *      Return: either the bare internal array or a copy of it,
+ *              or null on error
+ *
+ *  Notes:
+ *      (1) If copyflag == L_COPY, it makes a copy which the caller
+ *          is responsible for freeing.  Otherwise, it operates
+ *          directly on the bare array of the numa.
+ *      (2) Very important: for L_NOCOPY, any writes to the array
+ *          will be in the numa.  Do not write beyond the size of
+ *          the count field, because it will not be accessible
+ *          from the numa!  If necessary, be sure to set the count
+ *          field to a larger number (such as the alloc size)
+ *          BEFORE calling this function.  Creating with numaMakeConstant()
+ *          is another way to insure full initialization.
+ */
+l_float32 *
+numaGetFArray(NUMA    *na,
+              l_int32  copyflag)
+{
+l_int32     i, n;
+l_float32  *array;
+
+    PROCNAME("numaGetFArray");
+
+    if (!na)
+        return (l_float32 *)ERROR_PTR("na not defined", procName, NULL);
+
+    if (copyflag == L_NOCOPY) {
+        array = na->array;
+    } else {  /* copyflag == L_COPY */
+        n = numaGetCount(na);
+        if ((array = (l_float32 *)LEPT_CALLOC(n, sizeof(l_float32))) == NULL)
+            return (l_float32 *)ERROR_PTR("array not made", procName, NULL);
+        for (i = 0; i < n; i++)
+            array[i] = na->array[i];
+    }
+
+    return array;
+}
+
+
+/*!
+ *  numaGetRefCount()
+ *
+ *      Input:  na
+ *      Return: refcount, or UNDEF on error
+ */
+l_int32
+numaGetRefcount(NUMA  *na)
+{
+    PROCNAME("numaGetRefcount");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, UNDEF);
+    return na->refcount;
+}
+
+
+/*!
+ *  numaChangeRefCount()
+ *
+ *      Input:  na
+ *              delta (change to be applied)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaChangeRefcount(NUMA    *na,
+                   l_int32  delta)
+{
+    PROCNAME("numaChangeRefcount");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    na->refcount += delta;
+    return 0;
+}
+
+
+/*!
+ *  numaGetParameters()
+ *
+ *      Input:  na
+ *              &startx (<optional return> startx)
+ *              &delx (<optional return> delx)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaGetParameters(NUMA       *na,
+                  l_float32  *pstartx,
+                  l_float32  *pdelx)
+{
+    PROCNAME("numaGetParameters");
+
+    if (!pdelx && !pstartx)
+        return ERROR_INT("no return val requested", procName, 1);
+    if (pstartx) *pstartx = 0.0;
+    if (pdelx) *pdelx = 1.0;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    if (pstartx) *pstartx = na->startx;
+    if (pdelx) *pdelx = na->delx;
+    return 0;
+}
+
+
+/*!
+ *  numaSetParameters()
+ *
+ *      Input:  na
+ *              startx (x value corresponding to na[0])
+ *              delx (difference in x values for the situation where the
+ *                    elements of na correspond to the evaulation of a
+ *                    function at equal intervals of size @delx)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaSetParameters(NUMA      *na,
+                  l_float32  startx,
+                  l_float32  delx)
+{
+    PROCNAME("numaSetParameters");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    na->startx = startx;
+    na->delx = delx;
+    return 0;
+}
+
+
+/*!
+ *  numaCopyParameters()
+ *
+ *      Input:  nad (destination Numa)
+ *              nas (source Numa)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaCopyParameters(NUMA  *nad,
+                   NUMA  *nas)
+{
+l_float32  start, binsize;
+
+    PROCNAME("numaCopyParameters");
+
+    if (!nas || !nad)
+        return ERROR_INT("nas and nad not both defined", procName, 1);
+
+    numaGetParameters(nas, &start, &binsize);
+    numaSetParameters(nad, start, binsize);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                      Convert to string array                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaConvertToSarray()
+ *
+ *      Input:  na
+ *              size1 (size of conversion field)
+ *              size2 (for float conversion: size of field to the right
+ *                     of the decimal point)
+ *              addzeros (for integer conversion: to add lead zeros)
+ *              type (L_INTEGER_VALUE, L_FLOAT_VALUE)
+ *      Return: a sarray of the float values converted to strings
+ *              representing either integer or float values; or null on error.
+ *
+ *  Notes:
+ *      (1) For integer conversion, size2 is ignored.
+ *          For float conversion, addzeroes is ignored.
+ */
+SARRAY *
+numaConvertToSarray(NUMA    *na,
+                    l_int32  size1,
+                    l_int32  size2,
+                    l_int32  addzeros,
+                    l_int32  type)
+{
+char       fmt[32], strbuf[64];
+l_int32    i, n, ival;
+l_float32  fval;
+SARRAY    *sa;
+
+    PROCNAME("numaConvertToSarray");
+
+    if (!na)
+        return (SARRAY *)ERROR_PTR("na not defined", procName, NULL);
+    if (type != L_INTEGER_VALUE && type != L_FLOAT_VALUE)
+        return (SARRAY *)ERROR_PTR("invalid type", procName, NULL);
+
+    if (type == L_INTEGER_VALUE) {
+        if (addzeros)
+            snprintf(fmt, sizeof(fmt), "%%0%dd", size1);
+        else
+            snprintf(fmt, sizeof(fmt), "%%%dd", size1);
+    } else {  /* L_FLOAT_VALUE */
+        snprintf(fmt, sizeof(fmt), "%%%d.%df", size1, size2);
+    }
+
+    n = numaGetCount(na);
+    if ((sa = sarrayCreate(n)) == NULL)
+        return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+
+    for (i = 0; i < n; i++) {
+        if (type == L_INTEGER_VALUE) {
+            numaGetIValue(na, i, &ival);
+            snprintf(strbuf, sizeof(strbuf), fmt, ival);
+        } else {  /* L_FLOAT_VALUE */
+            numaGetFValue(na, i, &fval);
+            snprintf(strbuf, sizeof(strbuf), fmt, fval);
+        }
+        sarrayAddString(sa, strbuf, L_COPY);
+    }
+
+    return sa;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                       Serialize numa for I/O                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaRead()
+ *
+ *      Input:  filename
+ *      Return: na, or null on error
+ */
+NUMA *
+numaRead(const char  *filename)
+{
+FILE  *fp;
+NUMA  *na;
+
+    PROCNAME("numaRead");
+
+    if (!filename)
+        return (NUMA *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (NUMA *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((na = numaReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (NUMA *)ERROR_PTR("na not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return na;
+}
+
+
+/*!
+ *  numaReadStream()
+ *
+ *      Input:  stream
+ *      Return: numa, or null on error
+ */
+NUMA *
+numaReadStream(FILE  *fp)
+{
+l_int32    i, n, index, ret, version;
+l_float32  val, startx, delx;
+NUMA      *na;
+
+    PROCNAME("numaReadStream");
+
+    if (!fp)
+        return (NUMA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    ret = fscanf(fp, "\nNuma Version %d\n", &version);
+    if (ret != 1)
+        return (NUMA *)ERROR_PTR("not a numa file", procName, NULL);
+    if (version != NUMA_VERSION_NUMBER)
+        return (NUMA *)ERROR_PTR("invalid numa version", procName, NULL);
+    if (fscanf(fp, "Number of numbers = %d\n", &n) != 1)
+        return (NUMA *)ERROR_PTR("invalid number of numbers", procName, NULL);
+
+    if ((na = numaCreate(n)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+
+    for (i = 0; i < n; i++) {
+        if (fscanf(fp, "  [%d] = %f\n", &index, &val) != 2)
+            return (NUMA *)ERROR_PTR("bad input data", procName, NULL);
+        numaAddNumber(na, val);
+    }
+
+        /* Optional data */
+    if (fscanf(fp, "startx = %f, delx = %f\n", &startx, &delx) == 2)
+        numaSetParameters(na, startx, delx);
+
+    return na;
+}
+
+
+/*!
+ *  numaWrite()
+ *
+ *      Input:  filename, na
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaWrite(const char  *filename,
+          NUMA        *na)
+{
+FILE  *fp;
+
+    PROCNAME("numaWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "w")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (numaWriteStream(fp, na))
+        return ERROR_INT("na not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  numaWriteStream()
+ *
+ *      Input:  stream, na
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaWriteStream(FILE  *fp,
+                NUMA  *na)
+{
+l_int32    i, n;
+l_float32  startx, delx;
+
+    PROCNAME("numaWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    n = numaGetCount(na);
+    fprintf(fp, "\nNuma Version %d\n", NUMA_VERSION_NUMBER);
+    fprintf(fp, "Number of numbers = %d\n", n);
+    for (i = 0; i < n; i++)
+        fprintf(fp, "  [%d] = %f\n", i, na->array[i]);
+    fprintf(fp, "\n");
+
+        /* Optional data */
+    numaGetParameters(na, &startx, &delx);
+    if (startx != 0.0 || delx != 1.0)
+        fprintf(fp, "startx = %f, delx = %f\n", startx, delx);
+
+    return 0;
+}
+
+
+
+/*--------------------------------------------------------------------------*
+ *                     Numaa creation, destruction                          *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  numaaCreate()
+ *
+ *      Input:  size of numa ptr array to be alloc'd (0 for default)
+ *      Return: naa, or null on error
+ *
+ */
+NUMAA *
+numaaCreate(l_int32  n)
+{
+NUMAA  *naa;
+
+    PROCNAME("numaaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((naa = (NUMAA *)LEPT_CALLOC(1, sizeof(NUMAA))) == NULL)
+        return (NUMAA *)ERROR_PTR("naa not made", procName, NULL);
+    if ((naa->numa = (NUMA **)LEPT_CALLOC(n, sizeof(NUMA *))) == NULL)
+        return (NUMAA *)ERROR_PTR("numa ptr array not made", procName, NULL);
+
+    naa->nalloc = n;
+    naa->n = 0;
+
+    return naa;
+}
+
+
+/*!
+ *  numaaCreateFull()
+ *
+ *      Input:  nptr: size of numa ptr array to be alloc'd
+ *              n: size of individual numa arrays to be alloc'd (0 for default)
+ *      Return: naa, or null on error
+ *
+ *  Notes:
+ *      (1) This allocates numaa and fills the array with allocated numas.
+ *          In use, after calling this function, use
+ *              numaaAddNumber(naa, index, val);
+ *          to add val to the index-th numa in naa.
+ */
+NUMAA *
+numaaCreateFull(l_int32  nptr,
+                l_int32  n)
+{
+l_int32  i;
+NUMAA   *naa;
+NUMA    *na;
+
+    naa = numaaCreate(nptr);
+    for (i = 0; i < nptr; i++) {
+        na = numaCreate(n);
+        numaaAddNuma(naa, na, L_INSERT);
+    }
+
+    return naa;
+}
+
+
+/*!
+ *  numaaTruncate()
+ *
+ *      Input:  naa
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This identifies the largest index containing a numa that
+ *          has any numbers within it, destroys all numa beyond that
+ *          index, and resets the count.
+ */
+l_int32
+numaaTruncate(NUMAA  *naa)
+{
+l_int32  i, n, nn;
+NUMA    *na;
+
+    PROCNAME("numaaTruncate");
+
+    if (!naa)
+        return ERROR_INT("naa not defined", procName, 1);
+
+    n = numaaGetCount(naa);
+    for (i = n - 1; i >= 0; i--) {
+        na = numaaGetNuma(naa, i, L_CLONE);
+        if (!na)
+            continue;
+        nn = numaGetCount(na);
+        numaDestroy(&na);
+        if (nn == 0)
+            numaDestroy(&naa->numa[i]);
+        else
+            break;
+    }
+    naa->n = i + 1;
+    return 0;
+}
+
+
+/*!
+ *  numaaDestroy()
+ *
+ *      Input: &numaa <to be nulled if it exists>
+ *      Return: void
+ */
+void
+numaaDestroy(NUMAA  **pnaa)
+{
+l_int32  i;
+NUMAA   *naa;
+
+    PROCNAME("numaaDestroy");
+
+    if (pnaa == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+
+    if ((naa = *pnaa) == NULL)
+        return;
+
+    for (i = 0; i < naa->n; i++)
+        numaDestroy(&naa->numa[i]);
+    LEPT_FREE(naa->numa);
+    LEPT_FREE(naa);
+    *pnaa = NULL;
+
+    return;
+}
+
+
+
+/*--------------------------------------------------------------------------*
+ *                              Add Numa to Numaa                           *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  numaaAddNuma()
+ *
+ *      Input:  naa
+ *              na   (to be added)
+ *              copyflag  (L_INSERT, L_COPY, L_CLONE)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaaAddNuma(NUMAA   *naa,
+             NUMA    *na,
+             l_int32  copyflag)
+{
+l_int32  n;
+NUMA    *nac;
+
+    PROCNAME("numaaAddNuma");
+
+    if (!naa)
+        return ERROR_INT("naa not defined", procName, 1);
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    if (copyflag == L_INSERT) {
+        nac = na;
+    } else if (copyflag == L_COPY) {
+        if ((nac = numaCopy(na)) == NULL)
+            return ERROR_INT("nac not made", procName, 1);
+    } else if (copyflag == L_CLONE) {
+        nac = numaClone(na);
+    } else {
+        return ERROR_INT("invalid copyflag", procName, 1);
+    }
+
+    n = numaaGetCount(naa);
+    if (n >= naa->nalloc)
+        numaaExtendArray(naa);
+    naa->numa[n] = nac;
+    naa->n++;
+    return 0;
+}
+
+
+/*!
+ *  numaaExtendArray()
+ *
+ *      Input:  naa
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+numaaExtendArray(NUMAA  *naa)
+{
+    PROCNAME("numaaExtendArray");
+
+    if (!naa)
+        return ERROR_INT("naa not defined", procName, 1);
+
+    if ((naa->numa = (NUMA **)reallocNew((void **)&naa->numa,
+                              sizeof(NUMA *) * naa->nalloc,
+                              2 * sizeof(NUMA *) * naa->nalloc)) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+
+    naa->nalloc *= 2;
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                           Numaa accessors                            *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaaGetCount()
+ *
+ *      Input:  naa
+ *      Return: count (number of numa), or 0 if no numa or on error
+ */
+l_int32
+numaaGetCount(NUMAA  *naa)
+{
+    PROCNAME("numaaGetCount");
+
+    if (!naa)
+        return ERROR_INT("naa not defined", procName, 0);
+    return naa->n;
+}
+
+
+/*!
+ *  numaaGetNumaCount()
+ *
+ *      Input:  naa
+ *              index (of numa in naa)
+ *      Return: count of numbers in the referenced numa, or 0 on error.
+ */
+l_int32
+numaaGetNumaCount(NUMAA   *naa,
+                  l_int32  index)
+{
+    PROCNAME("numaaGetNumaCount");
+
+    if (!naa)
+        return ERROR_INT("naa not defined", procName, 0);
+    if (index < 0 || index >= naa->n)
+        return ERROR_INT("invalid index into naa", procName, 0);
+    return numaGetCount(naa->numa[index]);
+}
+
+
+/*!
+ *  numaaGetNumberCount()
+ *
+ *      Input:  naa
+ *      Return: count (total number of numbers in the numaa),
+ *                     or 0 if no numbers or on error
+ */
+l_int32
+numaaGetNumberCount(NUMAA  *naa)
+{
+NUMA    *na;
+l_int32  n, sum, i;
+
+    PROCNAME("numaaGetNumberCount");
+
+    if (!naa)
+        return ERROR_INT("naa not defined", procName, 0);
+
+    n = numaaGetCount(naa);
+    for (sum = 0, i = 0; i < n; i++) {
+        na = numaaGetNuma(naa, i, L_CLONE);
+        sum += numaGetCount(na);
+        numaDestroy(&na);
+    }
+
+    return sum;
+}
+
+
+/*!
+ *  numaaGetPtrArray()
+ *
+ *      Input:  naa
+ *      Return: the internal array of ptrs to Numa, or null on error
+ *
+ *  Notes:
+ *      (1) This function is convenient for doing direct manipulation on
+ *          a fixed size array of Numas.  To do this, it sets the count
+ *          to the full size of the allocated array of Numa ptrs.
+ *          The originating Numaa owns this array: DO NOT free it!
+ *      (2) Intended usage:
+ *            Numaa *naa = numaaCreate(n);
+ *            Numa **array = numaaGetPtrArray(naa);
+ *             ...  [manipulate Numas directly on the array]
+ *            numaaDestroy(&naa);
+ *      (3) Cautions:
+ *           - Do not free this array; it is owned by tne Numaa.
+ *           - Do not call any functions on the Numaa, other than
+ *             numaaDestroy() when you're finished with the array.
+ *             Adding a Numa will force a resize, destroying the ptr array.
+ *           - Do not address the array outside its allocated size.
+ *             With the bare array, there are no protections.  If the
+ *             allocated size is n, array[n] is an error.
+ */
+NUMA **
+numaaGetPtrArray(NUMAA  *naa)
+{
+    PROCNAME("numaaGetPtrArray");
+
+    if (!naa)
+        return (NUMA **)ERROR_PTR("naa not defined", procName, NULL);
+
+    naa->n = naa->nalloc;
+    return naa->numa;
+}
+
+
+/*!
+ *  numaaGetNuma()
+ *
+ *      Input:  naa
+ *              index  (to the index-th numa)
+ *              accessflag   (L_COPY or L_CLONE)
+ *      Return: numa, or null on error
+ */
+NUMA *
+numaaGetNuma(NUMAA   *naa,
+             l_int32  index,
+             l_int32  accessflag)
+{
+    PROCNAME("numaaGetNuma");
+
+    if (!naa)
+        return (NUMA *)ERROR_PTR("naa not defined", procName, NULL);
+    if (index < 0 || index >= naa->n)
+        return (NUMA *)ERROR_PTR("index not valid", procName, NULL);
+
+    if (accessflag == L_COPY)
+        return numaCopy(naa->numa[index]);
+    else if (accessflag == L_CLONE)
+        return numaClone(naa->numa[index]);
+    else
+        return (NUMA *)ERROR_PTR("invalid accessflag", procName, NULL);
+}
+
+
+/*!
+ *  numaaReplaceNuma()
+ *
+ *      Input:  naa
+ *              index  (to the index-th numa)
+ *              numa (insert and replace any existing one)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Any existing numa is destroyed, and the input one
+ *          is inserted in its place.
+ *      (2) If the index is invalid, return 1 (error)
+ */
+l_int32
+numaaReplaceNuma(NUMAA   *naa,
+                 l_int32  index,
+                 NUMA    *na)
+{
+l_int32  n;
+
+    PROCNAME("numaaReplaceNuma");
+
+    if (!naa)
+        return ERROR_INT("naa not defined", procName, 1);
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    n = numaaGetCount(naa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    numaDestroy(&naa->numa[index]);
+    naa->numa[index] = na;
+    return 0;
+}
+
+
+/*!
+ *  numaaGetValue()
+ *
+ *      Input:  naa
+ *              i (index of numa within numaa)
+ *              j (index into numa)
+ *              fval (<optional return> float value)
+ *              ival (<optional return> int value)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaaGetValue(NUMAA      *naa,
+              l_int32     i,
+              l_int32     j,
+              l_float32  *pfval,
+              l_int32    *pival)
+{
+l_int32  n;
+NUMA    *na;
+
+    PROCNAME("numaaGetValue");
+
+    if (!pfval && !pival)
+        return ERROR_INT("no return val requested", procName, 1);
+    if (pfval) *pfval = 0.0;
+    if (pival) *pival = 0;
+    if (!naa)
+        return ERROR_INT("naa not defined", procName, 1);
+    n = numaaGetCount(naa);
+    if (i < 0 || i >= n)
+        return ERROR_INT("invalid index into naa", procName, 1);
+    na = naa->numa[i];
+    if (j < 0 || j >= na->n)
+        return ERROR_INT("invalid index into na", procName, 1);
+    if (pfval) *pfval = na->array[j];
+    if (pival) *pival = (l_int32)(na->array[j]);
+    return 0;
+}
+
+
+/*!
+ *  numaaAddNumber()
+ *
+ *      Input:  naa
+ *              index (of numa within numaa)
+ *              val  (float or int to be added; stored as a float)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Adds to an existing numa only.
+ */
+l_int32
+numaaAddNumber(NUMAA     *naa,
+               l_int32    index,
+               l_float32  val)
+{
+l_int32  n;
+NUMA    *na;
+
+    PROCNAME("numaaAddNumber");
+
+    if (!naa)
+        return ERROR_INT("naa not defined", procName, 1);
+    n = numaaGetCount(naa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("invalid index in naa", procName, 1);
+
+    na = numaaGetNuma(naa, index, L_CLONE);
+    numaAddNumber(na, val);
+    numaDestroy(&na);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                      Serialize numaa for I/O                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaaRead()
+ *
+ *      Input:  filename
+ *      Return: naa, or null on error
+ */
+NUMAA *
+numaaRead(const char  *filename)
+{
+FILE   *fp;
+NUMAA  *naa;
+
+    PROCNAME("numaaRead");
+
+    if (!filename)
+        return (NUMAA *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (NUMAA *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((naa = numaaReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (NUMAA *)ERROR_PTR("naa not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return naa;
+}
+
+
+/*!
+ *  numaaReadStream()
+ *
+ *      Input:  stream
+ *      Return: naa, or null on error
+ */
+NUMAA *
+numaaReadStream(FILE  *fp)
+{
+l_int32    i, n, index, ret, version;
+NUMA      *na;
+NUMAA     *naa;
+
+    PROCNAME("numaaReadStream");
+
+    if (!fp)
+        return (NUMAA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    ret = fscanf(fp, "\nNumaa Version %d\n", &version);
+    if (ret != 1)
+        return (NUMAA *)ERROR_PTR("not a numa file", procName, NULL);
+    if (version != NUMA_VERSION_NUMBER)
+        return (NUMAA *)ERROR_PTR("invalid numaa version", procName, NULL);
+    if (fscanf(fp, "Number of numa = %d\n\n", &n) != 1)
+        return (NUMAA *)ERROR_PTR("invalid number of numa", procName, NULL);
+    if ((naa = numaaCreate(n)) == NULL)
+        return (NUMAA *)ERROR_PTR("naa not made", procName, NULL);
+
+    for (i = 0; i < n; i++) {
+        if (fscanf(fp, "Numa[%d]:", &index) != 1)
+            return (NUMAA *)ERROR_PTR("invalid numa header", procName, NULL);
+        if ((na = numaReadStream(fp)) == NULL)
+            return (NUMAA *)ERROR_PTR("na not made", procName, NULL);
+        numaaAddNuma(naa, na, L_INSERT);
+    }
+
+    return naa;
+}
+
+
+/*!
+ *  numaaWrite()
+ *
+ *      Input:  filename, naa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaaWrite(const char  *filename,
+           NUMAA       *naa)
+{
+FILE  *fp;
+
+    PROCNAME("numaaWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!naa)
+        return ERROR_INT("naa not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "w")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (numaaWriteStream(fp, naa))
+        return ERROR_INT("naa not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  numaaWriteStream()
+ *
+ *      Input:  stream, naa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaaWriteStream(FILE   *fp,
+                 NUMAA  *naa)
+{
+l_int32  i, n;
+NUMA    *na;
+
+    PROCNAME("numaaWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!naa)
+        return ERROR_INT("naa not defined", procName, 1);
+
+    n = numaaGetCount(naa);
+    fprintf(fp, "\nNumaa Version %d\n", NUMA_VERSION_NUMBER);
+    fprintf(fp, "Number of numa = %d\n\n", n);
+    for (i = 0; i < n; i++) {
+        if ((na = numaaGetNuma(naa, i, L_CLONE)) == NULL)
+            return ERROR_INT("na not found", procName, 1);
+        fprintf(fp, "Numa[%d]:", i);
+        numaWriteStream(fp, na);
+        numaDestroy(&na);
+    }
+
+    return 0;
+}
+
diff --git a/src/numafunc1.c b/src/numafunc1.c
new file mode 100644 (file)
index 0000000..3e322cb
--- /dev/null
@@ -0,0 +1,3476 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   numafunc1.c
+ *
+ *      Arithmetic and logic
+ *          NUMA        *numaArithOp()
+ *          NUMA        *numaLogicalOp()
+ *          NUMA        *numaInvert()
+ *          l_int32      numaSimilar()
+ *          l_int32      numaAddToNumber()
+ *
+ *      Simple extractions
+ *          l_int32      numaGetMin()
+ *          l_int32      numaGetMax()
+ *          l_int32      numaGetSum()
+ *          NUMA        *numaGetPartialSums()
+ *          l_int32      numaGetSumOnInterval()
+ *          l_int32      numaHasOnlyIntegers()
+ *          NUMA        *numaSubsample()
+ *          NUMA        *numaMakeDelta()
+ *          NUMA        *numaMakeSequence()
+ *          NUMA        *numaMakeConstant()
+ *          NUMA        *numaMakeAbsValue()
+ *          NUMA        *numaAddBorder()
+ *          NUMA        *numaAddSpecifiedBorder()
+ *          NUMA        *numaRemoveBorder()
+ *          l_int32      numaGetNonzeroRange()
+ *          l_int32      numaGetCountRelativeToZero()
+ *          NUMA        *numaClipToInterval()
+ *          NUMA        *numaMakeThresholdIndicator()
+ *          NUMA        *numaUniformSampling()
+ *          NUMA        *numaReverse()
+ *
+ *      Signal feature extraction
+ *          NUMA        *numaLowPassIntervals()
+ *          NUMA        *numaThresholdEdges()
+ *          NUMA        *numaGetSpanValues()
+ *          NUMA        *numaGetEdgeValues()
+ *
+ *      Interpolation
+ *          l_int32      numaInterpolateEqxVal()
+ *          l_int32      numaInterpolateEqxInterval()
+ *          l_int32      numaInterpolateArbxVal()
+ *          l_int32      numaInterpolateArbxInterval()
+ *
+ *      Functions requiring interpolation
+ *          l_int32      numaFitMax()
+ *          l_int32      numaDifferentiateInterval()
+ *          l_int32      numaIntegrateInterval()
+ *
+ *      Sorting
+ *          NUMA        *numaSortGeneral()
+ *          NUMA        *numaSortAutoSelect()
+ *          NUMA        *numaSortIndexAutoSelect()
+ *          l_int32      numaChooseSortType()
+ *          NUMA        *numaSort()
+ *          NUMA        *numaBinSort()
+ *          NUMA        *numaGetSortIndex()
+ *          NUMA        *numaGetBinSortIndex()
+ *          NUMA        *numaSortByIndex()
+ *          l_int32      numaIsSorted()
+ *          l_int32      numaSortPair()
+ *          NUMA        *numaInvertMap()
+ *
+ *      Random permutation
+ *          NUMA        *numaPseudorandomSequence()
+ *          NUMA        *numaRandomPermutation()
+ *
+ *      Functions requiring sorting
+ *          l_int32      numaGetRankValue()
+ *          l_int32      numaGetMedian()
+ *          l_int32      numaGetBinnedMedian()
+ *          l_int32      numaGetMode()
+ *          l_int32      numaGetMedianVariation()
+ *
+ *      Rearrangements
+ *          l_int32      numaJoin()
+ *          l_int32      numaaJoin()
+ *          NUMA        *numaaFlattenToNuma()
+ *
+ *      Union and intersection
+ *          NUMA        *numaUnionByAset()
+ *          NUMA        *numaRemoveDupsByAset()
+ *          NUMA        *numaIntersectionByAset()
+ *          L_ASET      *l_asetCreateFromNuma()
+ *
+ *    Things to remember when using the Numa:
+ *
+ *    (1) The numa is a struct, not an array.  Always use accessors
+ *        (see numabasic.c), never the fields directly.
+ *
+ *    (2) The number array holds l_float32 values.  It can also
+ *        be used to store l_int32 values.  See numabasic.c for
+ *        details on using the accessors.
+ *
+ *    (3) If you use numaCreate(), no numbers are stored and the size is 0.
+ *        You have to add numbers to increase the size.
+ *        If you want to start with a numa of a fixed size, with each
+ *        entry initialized to the same value, use numaMakeConstant().
+ *
+ *    (4) Occasionally, in the comments we denote the i-th element of a
+ *        numa by na[i].  This is conceptual only -- the numa is not an array!
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+
+/*----------------------------------------------------------------------*
+ *                Arithmetic and logical ops on Numas                   *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaArithOp()
+ *
+ *      Input:  nad (<optional> can be null or equal to na1 (in-place)
+ *              na1
+ *              na2
+ *              op (L_ARITH_ADD, L_ARITH_SUBTRACT,
+ *                  L_ARITH_MULTIPLY, L_ARITH_DIVIDE)
+ *      Return: nad (always: operation applied to na1 and na2)
+ *
+ *  Notes:
+ *      (1) The sizes of na1 and na2 must be equal.
+ *      (2) nad can only null or equal to na1.
+ *      (3) To add a constant to a numa, or to multipy a numa by
+ *          a constant, use numaTransform().
+ */
+NUMA *
+numaArithOp(NUMA    *nad,
+            NUMA    *na1,
+            NUMA    *na2,
+            l_int32  op)
+{
+l_int32    i, n;
+l_float32  val1, val2;
+
+    PROCNAME("numaArithOp");
+
+    if (!na1 || !na2)
+        return (NUMA *)ERROR_PTR("na1, na2 not both defined", procName, nad);
+    n = numaGetCount(na1);
+    if (n != numaGetCount(na2))
+        return (NUMA *)ERROR_PTR("na1, na2 sizes differ", procName, nad);
+    if (nad && nad != na1)
+        return (NUMA *)ERROR_PTR("nad defined but not in-place", procName, nad);
+    if (op != L_ARITH_ADD && op != L_ARITH_SUBTRACT &&
+        op != L_ARITH_MULTIPLY && op != L_ARITH_DIVIDE)
+        return (NUMA *)ERROR_PTR("invalid op", procName, nad);
+    if (op == L_ARITH_DIVIDE) {
+        for (i = 0; i < n; i++) {
+            numaGetFValue(na2, i, &val2);
+            if (val2 == 0.0)
+                return (NUMA *)ERROR_PTR("na2 has 0 element", procName, nad);
+        }
+    }
+
+        /* If nad is not identical to na1, make it an identical copy */
+    if (!nad)
+        nad = numaCopy(na1);
+
+    for (i = 0; i < n; i++) {
+        numaGetFValue(nad, i, &val1);
+        numaGetFValue(na2, i, &val2);
+        switch (op) {
+        case L_ARITH_ADD:
+            numaSetValue(nad, i, val1 + val2);
+            break;
+        case L_ARITH_SUBTRACT:
+            numaSetValue(nad, i, val1 - val2);
+            break;
+        case L_ARITH_MULTIPLY:
+            numaSetValue(nad, i, val1 * val2);
+            break;
+        case L_ARITH_DIVIDE:
+            numaSetValue(nad, i, val1 / val2);
+            break;
+        default:
+            fprintf(stderr, " Unknown arith op: %d\n", op);
+            return nad;
+        }
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaLogicalOp()
+ *
+ *      Input:  nad (<optional> can be null or equal to na1 (in-place)
+ *              na1
+ *              na2
+ *              op (L_UNION, L_INTERSECTION, L_SUBTRACTION, L_EXCLUSIVE_OR)
+ *      Return: nad (always: operation applied to na1 and na2)
+ *
+ *  Notes:
+ *      (1) The sizes of na1 and na2 must be equal.
+ *      (2) nad can only be null or equal to na1.
+ *      (3) This is intended for use with indicator arrays (0s and 1s).
+ *          Input data is extracted as integers (0 == false, anything
+ *          else == true); output results are 0 and 1.
+ *      (4) L_SUBTRACTION is subtraction of val2 from val1.  For bit logical
+ *          arithmetic this is (val1 & ~val2), but because these values
+ *          are integers, we use (val1 && !val2).
+ */
+NUMA *
+numaLogicalOp(NUMA    *nad,
+              NUMA    *na1,
+              NUMA    *na2,
+              l_int32  op)
+{
+l_int32  i, n, val1, val2, val;
+
+    PROCNAME("numaLogicalOp");
+
+    if (!na1 || !na2)
+        return (NUMA *)ERROR_PTR("na1, na2 not both defined", procName, nad);
+    n = numaGetCount(na1);
+    if (n != numaGetCount(na2))
+        return (NUMA *)ERROR_PTR("na1, na2 sizes differ", procName, nad);
+    if (nad && nad != na1)
+        return (NUMA *)ERROR_PTR("nad defined; not in-place", procName, nad);
+    if (op != L_UNION && op != L_INTERSECTION &&
+        op != L_SUBTRACTION && op != L_EXCLUSIVE_OR)
+        return (NUMA *)ERROR_PTR("invalid op", procName, nad);
+
+        /* If nad is not identical to na1, make it an identical copy */
+    if (!nad)
+        nad = numaCopy(na1);
+
+    for (i = 0; i < n; i++) {
+        numaGetIValue(nad, i, &val1);
+        numaGetIValue(na2, i, &val2);
+        switch (op) {
+        case L_UNION:
+            val = (val1 || val2) ? 1 : 0;
+            numaSetValue(nad, i, val);
+            break;
+        case L_INTERSECTION:
+            val = (val1 && val2) ? 1 : 0;
+            numaSetValue(nad, i, val);
+            break;
+        case L_SUBTRACTION:
+            val = (val1 && !val2) ? 1 : 0;
+            numaSetValue(nad, i, val);
+            break;
+        case L_EXCLUSIVE_OR:
+            val = ((val1 && !val2) || (!val1 && val2)) ? 1 : 0;
+            numaSetValue(nad, i, val);
+            break;
+        default:
+            fprintf(stderr, " Unknown logical op: %d\n", op);
+            return nad;
+        }
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaInvert()
+ *
+ *      Input:  nad (<optional> can be null or equal to nas (in-place)
+ *              nas
+ *      Return: nad (always: 'inverts' nas)
+ *
+ *  Notes:
+ *      (1) This is intended for use with indicator arrays (0s and 1s).
+ *          It gives a boolean-type output, taking the input as
+ *          an integer and inverting it:
+ *              0              -->  1
+ *              anything else  -->   0
+ */
+NUMA *
+numaInvert(NUMA  *nad,
+           NUMA  *nas)
+{
+l_int32  i, n, val;
+
+    PROCNAME("numaInvert");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, nad);
+    if (nad && nad != nas)
+        return (NUMA *)ERROR_PTR("nad defined; not in-place", procName, nad);
+
+    if (!nad)
+        nad = numaCopy(nas);
+    n = numaGetCount(nad);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(nad, i, &val);
+        if (!val)
+            val = 1;
+        else
+            val = 0;
+        numaSetValue(nad, i, val);
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaSimilar()
+ *
+ *      Input:  na1
+ *              na2
+ *              maxdiff (use 0.0 for exact equality)
+ *              &similar (<return> 1 if similar; 0 if different)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Float values can differ slightly due to roundoff and
+ *          accumulated errors.  Using @maxdiff > 0.0 allows similar
+ *          arrays to be identified.
+*/
+l_int32
+numaSimilar(NUMA      *na1,
+            NUMA      *na2,
+            l_float32  maxdiff,
+            l_int32   *psimilar)
+{
+l_int32    i, n;
+l_float32  val1, val2;
+
+    PROCNAME("numaSimilar");
+
+    if (!psimilar)
+        return ERROR_INT("&similar not defined", procName, 1);
+    *psimilar = 0;
+    if (!na1 || !na2)
+        return ERROR_INT("na1 and na2 not both defined", procName, 1);
+    maxdiff = L_ABS(maxdiff);
+
+    n = numaGetCount(na1);
+    if (n != numaGetCount(na2)) return 0;
+
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na1, i, &val1);
+        numaGetFValue(na2, i, &val2);
+        if (L_ABS(val1 - val2) > maxdiff) return 0;
+    }
+
+    *psimilar = 1;
+    return 0;
+}
+
+
+/*!
+ *  numaAddToNumber()
+ *
+ *      Input:  na
+ *              index (element to be changed)
+ *              val (new value to be added)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is useful for accumulating sums, regardless of the index
+ *          order in which the values are made available.
+ *      (2) Before use, the numa has to be filled up to @index.  This would
+ *          typically be used by creating the numa with the full sized
+ *          array, initialized to 0.0, using numaMakeConstant().
+ */
+l_int32
+numaAddToNumber(NUMA      *na,
+                l_int32    index,
+                l_float32  val)
+{
+l_int32  n;
+
+    PROCNAME("numaAddToNumber");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    n = numaGetCount(na);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not in {0...n - 1}", procName, 1);
+
+    na->array[index] += val;
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                         Simple extractions                           *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaGetMin()
+ *
+ *      Input:  na
+ *              &minval (<optional return> min value)
+ *              &iminloc (<optional return> index of min location)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+numaGetMin(NUMA       *na,
+           l_float32  *pminval,
+           l_int32    *piminloc)
+{
+l_int32    i, n, iminloc;
+l_float32  val, minval;
+
+    PROCNAME("numaGetMin");
+
+    if (!pminval && !piminloc)
+        return ERROR_INT("nothing to do", procName, 1);
+    if (pminval) *pminval = 0.0;
+    if (piminloc) *piminloc = 0;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    minval = +1000000000.;
+    iminloc = 0;
+    n = numaGetCount(na);
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        if (val < minval) {
+            minval = val;
+            iminloc = i;
+        }
+    }
+
+    if (pminval) *pminval = minval;
+    if (piminloc) *piminloc = iminloc;
+    return 0;
+}
+
+
+/*!
+ *  numaGetMax()
+ *
+ *      Input:  na
+ *              &maxval (<optional return> max value)
+ *              &imaxloc (<optional return> index of max location)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+numaGetMax(NUMA       *na,
+           l_float32  *pmaxval,
+           l_int32    *pimaxloc)
+{
+l_int32    i, n, imaxloc;
+l_float32  val, maxval;
+
+    PROCNAME("numaGetMax");
+
+    if (!pmaxval && !pimaxloc)
+        return ERROR_INT("nothing to do", procName, 1);
+    if (pmaxval) *pmaxval = 0.0;
+    if (pimaxloc) *pimaxloc = 0;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    maxval = -1000000000.;
+    imaxloc = 0;
+    n = numaGetCount(na);
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        if (val > maxval) {
+            maxval = val;
+            imaxloc = i;
+        }
+    }
+
+    if (pmaxval) *pmaxval = maxval;
+    if (pimaxloc) *pimaxloc = imaxloc;
+    return 0;
+}
+
+
+/*!
+ *  numaGetSum()
+ *
+ *      Input:  na
+ *              &sum (<return> sum of values)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaGetSum(NUMA       *na,
+           l_float32  *psum)
+{
+l_int32    i, n;
+l_float32  val, sum;
+
+    PROCNAME("numaGetSum");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (!psum)
+        return ERROR_INT("&sum not defined", procName, 1);
+
+    sum = 0.0;
+    n = numaGetCount(na);
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        sum += val;
+    }
+    *psum = sum;
+    return 0;
+}
+
+
+/*!
+ *  numaGetPartialSums()
+ *
+ *      Input:  na
+ *      Return: nasum, or null on error
+ *
+ *  Notes:
+ *      (1) nasum[i] is the sum for all j <= i of na[j].
+ *          So nasum[0] = na[0].
+ *      (2) If you want to generate a rank function, where rank[0] - 0.0,
+ *          insert a 0.0 at the beginning of the nasum array.
+ */
+NUMA *
+numaGetPartialSums(NUMA  *na)
+{
+l_int32    i, n;
+l_float32  val, sum;
+NUMA      *nasum;
+
+    PROCNAME("numaGetPartialSums");
+
+    if (!na)
+        return (NUMA *)ERROR_PTR("na not defined", procName, NULL);
+
+    n = numaGetCount(na);
+    nasum = numaCreate(n);
+    sum = 0.0;
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        sum += val;
+        numaAddNumber(nasum, sum);
+    }
+    return nasum;
+}
+
+
+/*!
+ *  numaGetSumOnInterval()
+ *
+ *      Input:  na
+ *              first (beginning index)
+ *              last (final index)
+ *              &sum (<return> sum of values in the index interval range)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaGetSumOnInterval(NUMA       *na,
+                     l_int32     first,
+                     l_int32     last,
+                     l_float32  *psum)
+{
+l_int32    i, n, truelast;
+l_float32  val, sum;
+
+    PROCNAME("numaGetSumOnInterval");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (!psum)
+        return ERROR_INT("&sum not defined", procName, 1);
+    *psum = 0.0;
+
+    sum = 0.0;
+    n = numaGetCount(na);
+    if (first >= n)  /* not an error */
+      return 0;
+    truelast = L_MIN(last, n - 1);
+
+    for (i = first; i <= truelast; i++) {
+        numaGetFValue(na, i, &val);
+        sum += val;
+    }
+    *psum = sum;
+    return 0;
+}
+
+
+/*!
+ *  numaHasOnlyIntegers()
+ *
+ *      Input:  na
+ *              maxsamples (maximum number of samples to check)
+ *              &allints (<return> 1 if all sampled values are ints; else 0)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Set @maxsamples == 0 to check every integer in na.  Otherwise,
+ *          this samples no more than @maxsamples.
+ */
+l_int32
+numaHasOnlyIntegers(NUMA     *na,
+                    l_int32   maxsamples,
+                    l_int32  *pallints)
+{
+l_int32    i, n, incr;
+l_float32  val;
+
+    PROCNAME("numaHasOnlyIntegers");
+
+    if (!pallints)
+        return ERROR_INT("&allints not defined", procName, 1);
+    *pallints = TRUE;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    if ((n = numaGetCount(na)) == 0)
+        return ERROR_INT("na empty", procName, 1);
+    if (maxsamples <= 0)
+        incr = 1;
+    else
+        incr = (l_int32)((n + maxsamples - 1) / maxsamples);
+    for (i = 0; i < n; i += incr) {
+        numaGetFValue(na, i, &val);
+        if (val != (l_int32)val) {
+            *pallints = FALSE;
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  numaSubsample()
+ *
+ *      Input:  nas
+ *              subfactor (subsample factor, >= 1)
+ *      Return: nad (evenly sampled values from nas), or null on error
+ */
+NUMA *
+numaSubsample(NUMA    *nas,
+              l_int32  subfactor)
+{
+l_int32    i, n;
+l_float32  val;
+NUMA      *nad;
+
+    PROCNAME("numaSubsample");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (subfactor < 1)
+        return (NUMA *)ERROR_PTR("subfactor < 1", procName, NULL);
+
+    nad = numaCreate(0);
+    n = numaGetCount(nas);
+    for (i = 0; i < n; i++) {
+        if (i % subfactor != 0) continue;
+        numaGetFValue(nas, i, &val);
+        numaAddNumber(nad, val);
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaMakeDelta()
+ *
+ *      Input:  nas (input numa)
+ *      Return: numa (of difference values val[i+1] - val[i]),
+ *                    or null on error
+ */
+NUMA *
+numaMakeDelta(NUMA  *nas)
+{
+l_int32  i, n, prev, cur;
+NUMA    *nad;
+
+    PROCNAME("numaMakeDelta");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    n = numaGetCount(nas);
+    nad = numaCreate(n - 1);
+    prev = 0;
+    for (i = 1; i < n; i++) {
+        numaGetIValue(nas, i, &cur);
+        numaAddNumber(nad, cur - prev);
+        prev = cur;
+    }
+    return nad;
+}
+
+
+/*!
+ *  numaMakeSequence()
+ *
+ *      Input:  startval
+ *              increment
+ *              size (of sequence)
+ *      Return: numa of sequence of evenly spaced values, or null on error
+ */
+NUMA *
+numaMakeSequence(l_float32  startval,
+                 l_float32  increment,
+                 l_int32    size)
+{
+l_int32    i;
+l_float32  val;
+NUMA      *na;
+
+    PROCNAME("numaMakeSequence");
+
+    if ((na = numaCreate(size)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+
+    for (i = 0; i < size; i++) {
+        val = startval + i * increment;
+        numaAddNumber(na, val);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  numaMakeConstant()
+ *
+ *      Input:  val
+ *              size (of numa)
+ *      Return: numa (of given size with all entries equal to 'val'),
+ *              or null on error
+ */
+NUMA *
+numaMakeConstant(l_float32  val,
+                 l_int32    size)
+{
+    return numaMakeSequence(val, 0.0, size);
+}
+
+
+/*!
+ *  numaMakeAbsValue()
+ *
+ *      Input:  nad (can be null for new array, or the same as nas for inplace)
+ *              nas (input numa)
+ *      Return: nad (with all numbers being the absval of the input),
+ *              or null on error
+ */
+NUMA *
+numaMakeAbsValue(NUMA  *nad,
+                 NUMA  *nas)
+{
+l_int32    i, n;
+l_float32  val;
+
+    PROCNAME("numaMakeAbsValue");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (nad && nad != nas)
+        return (NUMA *)ERROR_PTR("nad and not in-place", procName, NULL);
+
+    if (!nad)
+        nad = numaCopy(nas);
+    n = numaGetCount(nad);
+    for (i = 0; i < n; i++) {
+        val = nad->array[i];
+        nad->array[i] = L_ABS(val);
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaAddBorder()
+ *
+ *      Input:  nas
+ *              left, right (number of elements to add on each side)
+ *              val (initialize border elements)
+ *      Return: nad (with added elements at left and right), or null on error
+ */
+NUMA *
+numaAddBorder(NUMA      *nas,
+              l_int32    left,
+              l_int32    right,
+              l_float32  val)
+{
+l_int32     i, n, len;
+l_float32   startx, delx;
+l_float32  *fas, *fad;
+NUMA       *nad;
+
+    PROCNAME("numaAddBorder");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (left < 0) left = 0;
+    if (right < 0) right = 0;
+    if (left == 0 && right == 0)
+        return numaCopy(nas);
+
+    n = numaGetCount(nas);
+    len = n + left + right;
+    nad = numaMakeConstant(val, len);
+    numaGetParameters(nas, &startx, &delx);
+    numaSetParameters(nad, startx - delx * left, delx);
+    fas = numaGetFArray(nas, L_NOCOPY);
+    fad = numaGetFArray(nad, L_NOCOPY);
+    for (i = 0; i < n; i++)
+        fad[left + i] = fas[i];
+
+    return nad;
+}
+
+
+/*!
+ *  numaAddSpecifiedBorder()
+ *
+ *      Input:  nas
+ *              left, right (number of elements to add on each side)
+ *              type (L_CONTINUED_BORDER, L_MIRRORED_BORDER)
+ *      Return: nad (with added elements at left and right), or null on error
+ */
+NUMA *
+numaAddSpecifiedBorder(NUMA    *nas,
+                       l_int32  left,
+                       l_int32  right,
+                       l_int32  type)
+{
+l_int32     i, n;
+l_float32  *fa;
+NUMA       *nad;
+
+    PROCNAME("numaAddSpecifiedBorder");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (left < 0) left = 0;
+    if (right < 0) right = 0;
+    if (left == 0 && right == 0)
+        return numaCopy(nas);
+    if (type != L_CONTINUED_BORDER && type != L_MIRRORED_BORDER)
+        return (NUMA *)ERROR_PTR("invalid type", procName, NULL);
+    n = numaGetCount(nas);
+    if (type == L_MIRRORED_BORDER && (left > n || right > n))
+        return (NUMA *)ERROR_PTR("border too large", procName, NULL);
+
+    nad = numaAddBorder(nas, left, right, 0);
+    n = numaGetCount(nad);
+    fa = numaGetFArray(nad, L_NOCOPY);
+    if (type == L_CONTINUED_BORDER) {
+        for (i = 0; i < left; i++)
+            fa[i] = fa[left];
+        for (i = n - right; i < n; i++)
+            fa[i] = fa[n - right - 1];
+    } else {  /* type == L_MIRRORED_BORDER */
+        for (i = 0; i < left; i++)
+            fa[i] = fa[2 * left - 1 - i];
+        for (i = 0; i < right; i++)
+            fa[n - right + i] = fa[n - right - i - 1];
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaRemoveBorder()
+ *
+ *      Input:  nas
+ *              left, right (number of elements to remove from each side)
+ *      Return: nad (with removed elements at left and right), or null on error
+ */
+NUMA *
+numaRemoveBorder(NUMA      *nas,
+                 l_int32    left,
+                 l_int32    right)
+{
+l_int32     i, n, len;
+l_float32   startx, delx;
+l_float32  *fas, *fad;
+NUMA       *nad;
+
+    PROCNAME("numaRemoveBorder");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (left < 0) left = 0;
+    if (right < 0) right = 0;
+    if (left == 0 && right == 0)
+        return numaCopy(nas);
+
+    n = numaGetCount(nas);
+    if ((len = n - left - right) < 0)
+        return (NUMA *)ERROR_PTR("len < 0 after removal", procName, NULL);
+    nad = numaMakeConstant(0, len);
+    numaGetParameters(nas, &startx, &delx);
+    numaSetParameters(nad, startx + delx * left, delx);
+    fas = numaGetFArray(nas, L_NOCOPY);
+    fad = numaGetFArray(nad, L_NOCOPY);
+    for (i = 0; i < len; i++)
+        fad[i] = fas[left + i];
+
+    return nad;
+}
+
+
+/*!
+ *  numaGetNonzeroRange()
+ *
+ *      Input:  numa
+ *              eps (largest value considered to be zero)
+ *              &first, &last (<return> interval of array indices
+ *                             where values are nonzero)
+ *      Return: 0 if OK, 1 on error or if no nonzero range is found.
+ */
+l_int32
+numaGetNonzeroRange(NUMA      *na,
+                    l_float32  eps,
+                    l_int32   *pfirst,
+                    l_int32   *plast)
+{
+l_int32    n, i, found;
+l_float32  val;
+
+    PROCNAME("numaGetNonzeroRange");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (!pfirst || !plast)
+        return ERROR_INT("pfirst and plast not both defined", procName, 1);
+    n = numaGetCount(na);
+    found = FALSE;
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        if (val > eps) {
+            found = TRUE;
+            break;
+        }
+    }
+    if (!found) {
+        *pfirst = n - 1;
+        *plast = 0;
+        return 1;
+    }
+
+    *pfirst = i;
+    for (i = n - 1; i >= 0; i--) {
+        numaGetFValue(na, i, &val);
+        if (val > eps)
+            break;
+    }
+    *plast = i;
+    return 0;
+}
+
+
+/*!
+ *  numaGetCountRelativeToZero()
+ *
+ *      Input:  numa
+ *              type (L_LESS_THAN_ZERO, L_EQUAL_TO_ZERO, L_GREATER_THAN_ZERO)
+ *              &count (<return> count of values of given type)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaGetCountRelativeToZero(NUMA     *na,
+                           l_int32   type,
+                           l_int32  *pcount)
+{
+l_int32    n, i, count;
+l_float32  val;
+
+    PROCNAME("numaGetCountRelativeToZero");
+
+    if (!pcount)
+        return ERROR_INT("&count not defined", procName, 1);
+    *pcount = 0;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    n = numaGetCount(na);
+    for (i = 0, count = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        if (type == L_LESS_THAN_ZERO && val < 0.0)
+            count++;
+        else if (type == L_EQUAL_TO_ZERO && val == 0.0)
+            count++;
+        else if (type == L_GREATER_THAN_ZERO && val > 0.0)
+            count++;
+    }
+
+    *pcount = count;
+    return 0;
+}
+
+
+/*!
+ *  numaClipToInterval()
+ *
+ *      Input:  numa
+ *              first, last (clipping interval)
+ *      Return: numa with the same values as the input, but clipped
+ *              to the specified interval
+ *
+ *  Note: If you want the indices of the array values to be unchanged,
+ *        use first = 0.
+ *  Usage: This is useful to clip a histogram that has a few nonzero
+ *         values to its nonzero range.
+ */
+NUMA *
+numaClipToInterval(NUMA    *nas,
+                   l_int32  first,
+                   l_int32  last)
+{
+l_int32    n, i, truelast;
+l_float32  val;
+NUMA      *nad;
+
+    PROCNAME("numaClipToInterval");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (first > last)
+        return (NUMA *)ERROR_PTR("range not valid", procName, NULL);
+
+    n = numaGetCount(nas);
+    if (first >= n)
+        return (NUMA *)ERROR_PTR("no elements in range", procName, NULL);
+    truelast = L_MIN(last, n - 1);
+    if ((nad = numaCreate(truelast - first + 1)) == NULL)
+        return (NUMA *)ERROR_PTR("nad not made", procName, NULL);
+    for (i = first; i <= truelast; i++) {
+        numaGetFValue(nas, i, &val);
+        numaAddNumber(nad, val);
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaMakeThresholdIndicator()
+ *
+ *      Input:  nas (input numa)
+ *              thresh (threshold value)
+ *              type (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                    L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *      Output: nad (indicator array: values are 0 and 1)
+ *
+ *  Notes:
+ *      (1) For each element in nas, if the constraint given by 'type'
+ *          correctly specifies its relation to thresh, a value of 1
+ *          is recorded in nad.
+ */
+NUMA *
+numaMakeThresholdIndicator(NUMA      *nas,
+                           l_float32  thresh,
+                           l_int32    type)
+{
+l_int32    n, i, ival;
+l_float32  fval;
+NUMA      *nai;
+
+    PROCNAME("numaMakeThresholdIndicator");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    n = numaGetCount(nas);
+    nai = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetFValue(nas, i, &fval);
+        ival = 0;
+        switch (type)
+        {
+        case L_SELECT_IF_LT:
+            if (fval < thresh) ival = 1;
+            break;
+        case L_SELECT_IF_GT:
+            if (fval > thresh) ival = 1;
+            break;
+        case L_SELECT_IF_LTE:
+            if (fval <= thresh) ival = 1;
+            break;
+        case L_SELECT_IF_GTE:
+            if (fval >= thresh) ival = 1;
+            break;
+        default:
+            numaDestroy(&nai);
+            return (NUMA *)ERROR_PTR("invalid type", procName, NULL);
+        }
+        numaAddNumber(nai, ival);
+    }
+
+    return nai;
+}
+
+
+/*!
+ *  numaUniformSampling()
+ *
+ *      Input:  nas (input numa)
+ *              nsamp (number of samples)
+ *      Output: nad (resampled array), or null on error
+ *
+ *  Notes:
+ *      (1) This resamples the values in the array, using @nsamp
+ *          equal divisions.
+ */
+NUMA *
+numaUniformSampling(NUMA    *nas,
+                    l_int32  nsamp)
+{
+l_int32     n, i, j, ileft, iright;
+l_float32   left, right, binsize, lfract, rfract, sum, startx, delx;
+l_float32  *array;
+NUMA       *nad;
+
+    PROCNAME("numaUniformSampling");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (nsamp <= 0)
+        return (NUMA *)ERROR_PTR("nsamp must be > 0", procName, NULL);
+
+    n = numaGetCount(nas);
+    nad = numaCreate(nsamp);
+    array = numaGetFArray(nas, L_NOCOPY);
+    binsize = (l_float32)n / (l_float32)nsamp;
+    numaGetParameters(nas, &startx, &delx);
+    numaSetParameters(nad, startx, binsize * delx);
+    left = 0.0;
+    for (i = 0; i < nsamp; i++) {
+        sum = 0.0;
+        right = left + binsize;
+        ileft = (l_int32)left;
+        lfract = 1.0 - left + ileft;
+        if (lfract >= 1.0)  /* on left bin boundary */
+            lfract = 0.0;
+        iright = (l_int32)right;
+        rfract = right - iright;
+        iright = L_MIN(iright, n - 1);
+        if (ileft == iright) {  /* both are within the same original sample */
+            sum += (lfract + rfract - 1.0) * array[ileft];
+        } else {
+            if (lfract > 0.0001)  /* left fraction */
+                sum += lfract * array[ileft];
+            if (rfract > 0.0001)  /* right fraction */
+                sum += rfract * array[iright];
+            for (j = ileft + 1; j < iright; j++)  /* entire pixels */
+                sum += array[j];
+        }
+
+        numaAddNumber(nad, sum);
+        left = right;
+    }
+    return nad;
+}
+
+
+/*!
+ *  numaReverse()
+ *
+ *      Input:  nad (<optional> can be null or equal to nas)
+ *              nas (input numa)
+ *      Output: nad (reversed), or null on error
+ *
+ *  Notes:
+ *      (1) Usage:
+ *            numaReverse(nas, nas);   // in-place
+ *            nad = numaReverse(NULL, nas);  // makes a new one
+ */
+NUMA *
+numaReverse(NUMA  *nad,
+            NUMA  *nas)
+{
+l_int32    n, i;
+l_float32  val1, val2;
+
+    PROCNAME("numaReverse");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (nad && nas != nad)
+        return (NUMA *)ERROR_PTR("nad defined but != nas", procName, NULL);
+
+    n = numaGetCount(nas);
+    if (nad) {  /* in-place */
+        for (i = 0; i < n / 2; i++) {
+            numaGetFValue(nad, i, &val1);
+            numaGetFValue(nad, n - i - 1, &val2);
+            numaSetValue(nad, i, val2);
+            numaSetValue(nad, n - i - 1, val1);
+        }
+    } else {
+        nad = numaCreate(n);
+        for (i = n - 1; i >= 0; i--) {
+            numaGetFValue(nas, i, &val1);
+            numaAddNumber(nad, val1);
+        }
+    }
+
+        /* Reverse the startx and delx fields */
+    nad->startx = nas->startx + (n - 1) * nas->delx;
+    nad->delx = -nas->delx;
+    return nad;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                       Signal feature extraction                      *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaLowPassIntervals()
+ *
+ *      Input:  nas (input numa)
+ *              thresh (threshold fraction of max; in [0.0 ... 1.0])
+ *              maxn (for normalizing; set maxn = 0.0 to use the max in nas)
+ *      Output: nad (interval abscissa pairs), or null on error
+ *
+ *  Notes:
+ *      (1) For each interval where the value is less than a specified
+ *          fraction of the maximum, this records the left and right "x"
+ *          value.
+ */
+NUMA *
+numaLowPassIntervals(NUMA      *nas,
+                     l_float32  thresh,
+                     l_float32  maxn)
+{
+l_int32    n, i, inrun;
+l_float32  maxval, threshval, fval, startx, delx, x0, x1;
+NUMA      *nad;
+
+    PROCNAME("numaLowPassIntervals");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (thresh < 0.0 || thresh > 1.0)
+        return (NUMA *)ERROR_PTR("invalid thresh", procName, NULL);
+
+        /* The input threshold is a fraction of the max.
+         * The first entry in nad is the value of the max. */
+    n = numaGetCount(nas);
+    if (maxn == 0.0)
+        numaGetMax(nas, &maxval, NULL);
+    else
+        maxval = maxn;
+    numaGetParameters(nas, &startx, &delx);
+    threshval = thresh * maxval;
+    nad = numaCreate(0);
+    numaAddNumber(nad, maxval);
+
+        /* Write pairs of pts (x0, x1) for the intervals */
+    inrun = FALSE;
+    for (i = 0; i < n; i++) {
+        numaGetFValue(nas, i, &fval);
+        if (fval < threshval && inrun == FALSE) {  /* start a new run */
+            inrun = TRUE;
+            x0 = startx + i * delx;
+        } else if (fval > threshval && inrun == TRUE) {  /* end the run */
+            inrun = FALSE;
+            x1 = startx + i * delx;
+            numaAddNumber(nad, x0);
+            numaAddNumber(nad, x1);
+        }
+    }
+    if (inrun == TRUE) {  /* must end the last run */
+        x1 = startx + (n - 1) * delx;
+        numaAddNumber(nad, x0);
+        numaAddNumber(nad, x1);
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaThresholdEdges()
+ *
+ *      Input:  nas (input numa)
+ *              thresh1 (low threshold as fraction of max; in [0.0 ... 1.0])
+ *              thresh2 (high threshold as fraction of max; in [0.0 ... 1.0])
+ *              maxn (for normalizing; set maxn = 0.0 to use the max in nas)
+ *      Output: nad (edge interval triplets), or null on error
+ *
+ *  Notes:
+ *      (1) For each edge interval, where where the value is less
+ *          than @thresh1 on one side, greater than @thresh2 on
+ *          the other, and between these thresholds throughout the
+ *          interval, this records a triplet of values: the
+ *          'left' and 'right' edges, and either +1 or -1, depending
+ *          on whether the edge is rising or falling.
+ *      (2) No assumption is made about the value outside the array,
+ *          so if the value at the array edge is between the threshold
+ *          values, it is not considered part of an edge.  We start
+ *          looking for edge intervals only after leaving the thresholded
+ *          band.
+ */
+NUMA *
+numaThresholdEdges(NUMA      *nas,
+                   l_float32  thresh1,
+                   l_float32  thresh2,
+                   l_float32  maxn)
+{
+l_int32    n, i, istart, inband, output, sign;
+l_int32    startbelow, below, above, belowlast, abovelast;
+l_float32  maxval, threshval1, threshval2, fval, startx, delx, x0, x1;
+NUMA      *nad;
+
+    PROCNAME("numaThresholdEdges");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (thresh1 < 0.0 || thresh1 > 1.0 || thresh2 < 0.0 || thresh2 > 1.0)
+        return (NUMA *)ERROR_PTR("invalid thresholds", procName, NULL);
+    if (thresh2 < thresh1)
+        return (NUMA *)ERROR_PTR("thresh2 < thresh1", procName, NULL);
+
+        /* The input thresholds are fractions of the max.
+         * The first entry in nad is the value of the max used
+         * here for normalization. */
+    n = numaGetCount(nas);
+    if (maxn == 0.0)
+        numaGetMax(nas, &maxval, NULL);
+    else
+        maxval = maxn;
+    numaGetMax(nas, &maxval, NULL);
+    numaGetParameters(nas, &startx, &delx);
+    threshval1 = thresh1 * maxval;
+    threshval2 = thresh2 * maxval;
+    nad = numaCreate(0);
+    numaAddNumber(nad, maxval);
+
+        /* Write triplets of pts (x0, x1, sign) for the edges.
+         * First make sure we start search from outside the band.
+         * Only one of {belowlast, abovelast} is true. */
+    for (i = 0; i < n; i++) {
+        istart = i;
+        numaGetFValue(nas, i, &fval);
+        belowlast = (fval < threshval1) ? TRUE : FALSE;
+        abovelast = (fval > threshval2) ? TRUE : FALSE;
+        if (belowlast == TRUE || abovelast == TRUE)
+            break;
+    }
+    if (istart == n)  /* no intervals found */
+        return nad;
+
+        /* x0 and x1 can only be set from outside the edge.
+         * They are the values just before entering the band,
+         * and just after entering the band.  We can jump through
+         * the band, in which case they differ by one index in nas. */
+    inband = FALSE;
+    startbelow = belowlast; /* one of these is true */
+    output = FALSE;
+    x0 = startx + istart * delx;
+    for (i = istart + 1; i < n; i++) {
+        numaGetFValue(nas, i, &fval);
+        below = (fval < threshval1) ? TRUE : FALSE;
+        above = (fval > threshval2) ? TRUE : FALSE;
+        if (!inband && belowlast && above) {  /* full jump up */
+            x1 = startx + i * delx;
+            sign = 1;
+            startbelow = FALSE;  /* for the next transition */
+            output = TRUE;
+        } else if (!inband && abovelast && below) {  /* full jump down */
+            x1 = startx + i * delx;
+            sign = -1;
+            startbelow = TRUE;  /* for the next transition */
+            output = TRUE;
+        } else if (inband && startbelow && above) {  /* exit rising; success */
+            x1 = startx + i * delx;
+            sign = 1;
+            inband = FALSE;
+            startbelow = FALSE;  /* for the next transition */
+            output = TRUE;
+        } else if (inband && !startbelow && below) {
+                /* exit falling; success */
+            x1 = startx + i * delx;
+            sign = -1;
+            inband = FALSE;
+            startbelow = TRUE;  /* for the next transition */
+            output = TRUE;
+        } else if (inband && !startbelow && above) {  /* exit rising; failure */
+            x0 = startx + i * delx;
+            inband = FALSE;
+        } else if (inband && startbelow && below) {  /* exit falling; failure */
+            x0 = startx + i * delx;
+            inband = FALSE;
+        } else if (!inband && !above && !below) {  /* enter */
+            inband = TRUE;
+            startbelow = belowlast;
+        } else if (!inband && (above || below)) {  /* outside and remaining */
+            x0 = startx + i * delx;  /* update position */
+        }
+        belowlast = below;
+        abovelast = above;
+        if (output) {  /* we have exited; save new x0 */
+            numaAddNumber(nad, x0);
+            numaAddNumber(nad, x1);
+            numaAddNumber(nad, sign);
+            output = FALSE;
+            x0 = startx + i * delx;
+        }
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaGetSpanValues()
+ *
+ *      Input:  na (numa that is output of numaLowPassIntervals())
+ *              span (span number, zero-based)
+ *              &start (<optional return> location of start of transition)
+ *              &end (<optional return> location of end of transition)
+ *      Output: 0 if OK, 1 on error
+ */
+l_int32
+numaGetSpanValues(NUMA    *na,
+                  l_int32  span,
+                  l_int32 *pstart,
+                  l_int32 *pend)
+{
+l_int32  n, nspans;
+
+    PROCNAME("numaGetSpanValues");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    n = numaGetCount(na);
+    if (n % 2 != 1)
+        return ERROR_INT("n is not odd", procName, 1);
+    nspans = n / 2;
+    if (nspans < 0 || span >= nspans)
+        return ERROR_INT("invalid span", procName, 1);
+
+    if (pstart) numaGetIValue(na, 2 * span + 1, pstart);
+    if (pend) numaGetIValue(na, 2 * span + 2, pend);
+    return 0;
+}
+
+
+/*!
+ *  numaGetEdgeValues()
+ *
+ *      Input:  na (numa that is output of numaThresholdEdges())
+ *              edge (edge number, zero-based)
+ *              &start (<optional return> location of start of transition)
+ *              &end (<optional return> location of end of transition)
+ *              &sign (<optional return> transition sign: +1 is rising,
+ *                     -1 is falling)
+ *      Output: 0 if OK, 1 on error
+ */
+l_int32
+numaGetEdgeValues(NUMA    *na,
+                  l_int32  edge,
+                  l_int32 *pstart,
+                  l_int32 *pend,
+                  l_int32 *psign)
+{
+l_int32  n, nedges;
+
+    PROCNAME("numaGetEdgeValues");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    n = numaGetCount(na);
+    if (n % 3 != 1)
+        return ERROR_INT("n % 3 is not 1", procName, 1);
+    nedges = (n - 1) / 3;
+    if (edge < 0 || edge >= nedges)
+        return ERROR_INT("invalid edge", procName, 1);
+
+    if (pstart) numaGetIValue(na, 3 * edge + 1, pstart);
+    if (pend) numaGetIValue(na, 3 * edge + 2, pend);
+    if (psign) numaGetIValue(na, 3 * edge + 3, psign);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                             Interpolation                            *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaInterpolateEqxVal()
+ *
+ *      Input:  startx (xval corresponding to first element in array)
+ *              deltax (x increment between array elements)
+ *              nay  (numa of ordinate values, assumed equally spaced)
+ *              type (L_LINEAR_INTERP, L_QUADRATIC_INTERP)
+ *              xval
+ *              &yval (<return> interpolated value)
+ *      Return: 0 if OK, 1 on error (e.g., if xval is outside range)
+ *
+ *  Notes:
+ *      (1) Considering nay as a function of x, the x values
+ *          are equally spaced
+ *      (2) Caller should check for valid return.
+ *
+ *  For linear Lagrangian interpolation (through 2 data pts):
+ *         y(x) = y1(x-x2)/(x1-x2) + y2(x-x1)/(x2-x1)
+ *
+ *  For quadratic Lagrangian interpolation (through 3 data pts):
+ *         y(x) = y1(x-x2)(x-x3)/((x1-x2)(x1-x3)) +
+ *                y2(x-x1)(x-x3)/((x2-x1)(x2-x3)) +
+ *                y3(x-x1)(x-x2)/((x3-x1)(x3-x2))
+ *
+ */
+l_int32
+numaInterpolateEqxVal(l_float32   startx,
+                      l_float32   deltax,
+                      NUMA       *nay,
+                      l_int32     type,
+                      l_float32   xval,
+                      l_float32  *pyval)
+{
+l_int32     i, n, i1, i2, i3;
+l_float32   x1, x2, x3, fy1, fy2, fy3, d1, d2, d3, del, fi, maxx;
+l_float32  *fa;
+
+    PROCNAME("numaInterpolateEqxVal");
+
+    if (!pyval)
+        return ERROR_INT("&yval not defined", procName, 1);
+    *pyval = 0.0;
+    if (!nay)
+        return ERROR_INT("nay not defined", procName, 1);
+    if (deltax <= 0.0)
+        return ERROR_INT("deltax not > 0", procName, 1);
+    if (type != L_LINEAR_INTERP && type != L_QUADRATIC_INTERP)
+        return ERROR_INT("invalid interp type", procName, 1);
+    n = numaGetCount(nay);
+    if (n < 2)
+        return ERROR_INT("not enough points", procName, 1);
+    if (type == L_QUADRATIC_INTERP && n == 2) {
+        type = L_LINEAR_INTERP;
+        L_WARNING("only 2 points; using linear interp\n", procName);
+    }
+    maxx = startx + deltax * (n - 1);
+    if (xval < startx || xval > maxx)
+        return ERROR_INT("xval is out of bounds", procName, 1);
+
+    fa = numaGetFArray(nay, L_NOCOPY);
+    fi = (xval - startx) / deltax;
+    i = (l_int32)fi;
+    del = fi - i;
+    if (del == 0.0) {  /* no interpolation required */
+        *pyval = fa[i];
+        return 0;
+    }
+
+    if (type == L_LINEAR_INTERP) {
+        *pyval = fa[i] + del * (fa[i + 1] - fa[i]);
+        return 0;
+    }
+
+        /* Quadratic interpolation */
+    d1 = d3 = 0.5 / (deltax * deltax);
+    d2 = -2. * d1;
+    if (i == 0) {
+        i1 = i;
+        i2 = i + 1;
+        i3 = i + 2;
+    } else {
+        i1 = i - 1;
+        i2 = i;
+        i3 = i + 1;
+    }
+    x1 = startx + i1 * deltax;
+    x2 = startx + i2 * deltax;
+    x3 = startx + i3 * deltax;
+    fy1 = d1 * fa[i1];
+    fy2 = d2 * fa[i2];
+    fy3 = d3 * fa[i3];
+    *pyval = fy1 * (xval - x2) * (xval - x3) +
+             fy2 * (xval - x1) * (xval - x3) +
+             fy3 * (xval - x1) * (xval - x2);
+    return 0;
+}
+
+
+/*!
+ *  numaInterpolateArbxVal()
+ *
+ *      Input:  nax (numa of abscissa values)
+ *              nay (numa of ordinate values, corresponding to nax)
+ *              type (L_LINEAR_INTERP, L_QUADRATIC_INTERP)
+ *              xval
+ *              &yval (<return> interpolated value)
+ *      Return: 0 if OK, 1 on error (e.g., if xval is outside range)
+ *
+ *  Notes:
+ *      (1) The values in nax must be sorted in increasing order.
+ *          If, additionally, they are equally spaced, you can use
+ *          numaInterpolateEqxVal().
+ *      (2) Caller should check for valid return.
+ *      (3) Uses lagrangian interpolation.  See numaInterpolateEqxVal()
+ *          for formulas.
+ */
+l_int32
+numaInterpolateArbxVal(NUMA       *nax,
+                       NUMA       *nay,
+                       l_int32     type,
+                       l_float32   xval,
+                       l_float32  *pyval)
+{
+l_int32     i, im, nx, ny, i1, i2, i3;
+l_float32   delu, dell, fract, d1, d2, d3;
+l_float32   minx, maxx;
+l_float32  *fax, *fay;
+
+    PROCNAME("numaInterpolateArbxVal");
+
+    if (!pyval)
+        return ERROR_INT("&yval not defined", procName, 1);
+    *pyval = 0.0;
+    if (!nax)
+        return ERROR_INT("nax not defined", procName, 1);
+    if (!nay)
+        return ERROR_INT("nay not defined", procName, 1);
+    if (type != L_LINEAR_INTERP && type != L_QUADRATIC_INTERP)
+        return ERROR_INT("invalid interp type", procName, 1);
+    ny = numaGetCount(nay);
+    nx = numaGetCount(nax);
+    if (nx != ny)
+        return ERROR_INT("nax and nay not same size arrays", procName, 1);
+    if (ny < 2)
+        return ERROR_INT("not enough points", procName, 1);
+    if (type == L_QUADRATIC_INTERP && ny == 2) {
+        type = L_LINEAR_INTERP;
+        L_WARNING("only 2 points; using linear interp\n", procName);
+    }
+    numaGetFValue(nax, 0, &minx);
+    numaGetFValue(nax, nx - 1, &maxx);
+    if (xval < minx || xval > maxx)
+        return ERROR_INT("xval is out of bounds", procName, 1);
+
+    fax = numaGetFArray(nax, L_NOCOPY);
+    fay = numaGetFArray(nay, L_NOCOPY);
+
+        /* Linear search for interval.  We are guaranteed
+         * to either return or break out of the loop.
+         * In addition, we are assured that fax[i] - fax[im] > 0.0 */
+    if (xval == fax[0]) {
+        *pyval = fay[0];
+        return 0;
+    }
+    for (i = 1; i < nx; i++) {
+        delu = fax[i] - xval;
+        if (delu >= 0.0) {  /* we've passed it */
+            if (delu == 0.0) {
+                *pyval = fay[i];
+                return 0;
+            }
+            im = i - 1;
+            dell = xval - fax[im];  /* >= 0 */
+            break;
+        }
+    }
+    fract = dell / (fax[i] - fax[im]);
+
+    if (type == L_LINEAR_INTERP) {
+        *pyval = fay[i] + fract * (fay[i + 1] - fay[i]);
+        return 0;
+    }
+
+        /* Quadratic interpolation */
+    if (im == 0) {
+        i1 = im;
+        i2 = im + 1;
+        i3 = im + 2;
+    } else {
+        i1 = im - 1;
+        i2 = im;
+        i3 = im + 1;
+    }
+    d1 = (fax[i1] - fax[i2]) * (fax[i1] - fax[i3]);
+    d2 = (fax[i2] - fax[i1]) * (fax[i2] - fax[i3]);
+    d3 = (fax[i3] - fax[i1]) * (fax[i3] - fax[i2]);
+    *pyval = fay[i1] * (xval - fax[i2]) * (xval - fax[i3]) / d1 +
+             fay[i2] * (xval - fax[i1]) * (xval - fax[i3]) / d2 +
+             fay[i3] * (xval - fax[i1]) * (xval - fax[i2]) / d3;
+    return 0;
+}
+
+
+/*!
+ *  numaInterpolateEqxInterval()
+ *
+ *      Input:  startx (xval corresponding to first element in nas)
+ *              deltax (x increment between array elements in nas)
+ *              nasy  (numa of ordinate values, assumed equally spaced)
+ *              type (L_LINEAR_INTERP, L_QUADRATIC_INTERP)
+ *              x0 (start value of interval)
+ *              x1 (end value of interval)
+ *              npts (number of points to evaluate function in interval)
+ *              &nax (<optional return> array of x values in interval)
+ *              &nay (<return> array of y values in interval)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Considering nasy as a function of x, the x values
+ *          are equally spaced.
+ *      (2) This creates nay (and optionally nax) of interpolated
+ *          values over the specified interval (x0, x1).
+ *      (3) If the interval (x0, x1) lies partially outside the array
+ *          nasy (as interpreted by startx and deltax), it is an
+ *          error and returns 1.
+ *      (4) Note that deltax is the intrinsic x-increment for the input
+ *          array nasy, whereas delx is the intrinsic x-increment for the
+ *          output interpolated array nay.
+ */
+l_int32
+numaInterpolateEqxInterval(l_float32  startx,
+                           l_float32  deltax,
+                           NUMA      *nasy,
+                           l_int32    type,
+                           l_float32  x0,
+                           l_float32  x1,
+                           l_int32    npts,
+                           NUMA     **pnax,
+                           NUMA     **pnay)
+{
+l_int32     i, n;
+l_float32   x, yval, maxx, delx;
+NUMA       *nax, *nay;
+
+    PROCNAME("numaInterpolateEqxInterval");
+
+    if (pnax) *pnax = NULL;
+    if (!pnay)
+        return ERROR_INT("&nay not defined", procName, 1);
+    *pnay = NULL;
+    if (!nasy)
+        return ERROR_INT("nasy not defined", procName, 1);
+    if (deltax <= 0.0)
+        return ERROR_INT("deltax not > 0", procName, 1);
+    if (type != L_LINEAR_INTERP && type != L_QUADRATIC_INTERP)
+        return ERROR_INT("invalid interp type", procName, 1);
+    n = numaGetCount(nasy);
+    if (type == L_QUADRATIC_INTERP && n == 2) {
+        type = L_LINEAR_INTERP;
+        L_WARNING("only 2 points; using linear interp\n", procName);
+    }
+    maxx = startx + deltax * (n - 1);
+    if (x0 < startx || x1 > maxx || x1 <= x0)
+        return ERROR_INT("[x0 ... x1] is not valid", procName, 1);
+    if (npts < 3)
+        return ERROR_INT("npts < 3", procName, 1);
+    delx = (x1 - x0) / (l_float32)(npts - 1);  /* delx is for output nay */
+
+    if ((nay = numaCreate(npts)) == NULL)
+        return ERROR_INT("nay not made", procName, 1);
+    numaSetParameters(nay, x0, delx);
+    *pnay = nay;
+    if (pnax) {
+        nax = numaCreate(npts);
+        *pnax = nax;
+    }
+
+    for (i = 0; i < npts; i++) {
+        x = x0 + i * delx;
+        if (pnax)
+            numaAddNumber(nax, x);
+        numaInterpolateEqxVal(startx, deltax, nasy, type, x, &yval);
+        numaAddNumber(nay, yval);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  numaInterpolateArbxInterval()
+ *
+ *      Input:  nax (numa of abscissa values)
+ *              nay (numa of ordinate values, corresponding to nax)
+ *              type (L_LINEAR_INTERP, L_QUADRATIC_INTERP)
+ *              x0 (start value of interval)
+ *              x1 (end value of interval)
+ *              npts (number of points to evaluate function in interval)
+ *              &nadx (<optional return> array of x values in interval)
+ *              &nady (<return> array of y values in interval)
+ *      Return: 0 if OK, 1 on error (e.g., if x0 or x1 is outside range)
+ *
+ *  Notes:
+ *      (1) The values in nax must be sorted in increasing order.
+ *          If they are not sorted, we do it here, and complain.
+ *      (2) If the values in nax are equally spaced, you can use
+ *          numaInterpolateEqxInterval().
+ *      (3) Caller should check for valid return.
+ *      (4) We don't call numaInterpolateArbxVal() for each output
+ *          point, because that requires an O(n) search for
+ *          each point.  Instead, we do a single O(n) pass through
+ *          nax, saving the indices to be used for each output yval.
+ *      (5) Uses lagrangian interpolation.  See numaInterpolateEqxVal()
+ *          for formulas.
+ */
+l_int32
+numaInterpolateArbxInterval(NUMA       *nax,
+                            NUMA       *nay,
+                            l_int32     type,
+                            l_float32   x0,
+                            l_float32   x1,
+                            l_int32     npts,
+                            NUMA      **pnadx,
+                            NUMA      **pnady)
+{
+l_int32     i, im, j, nx, ny, i1, i2, i3, sorted;
+l_int32    *index;
+l_float32   del, xval, yval, excess, fract, minx, maxx, d1, d2, d3;
+l_float32  *fax, *fay;
+NUMA       *nasx, *nasy, *nadx, *nady;
+
+    PROCNAME("numaInterpolateArbxInterval");
+
+    if (pnadx) *pnadx = NULL;
+    if (!pnady)
+        return ERROR_INT("&nady not defined", procName, 1);
+    *pnady = NULL;
+    if (!nay)
+        return ERROR_INT("nay not defined", procName, 1);
+    if (!nax)
+        return ERROR_INT("nax not defined", procName, 1);
+    if (type != L_LINEAR_INTERP && type != L_QUADRATIC_INTERP)
+        return ERROR_INT("invalid interp type", procName, 1);
+    if (x0 > x1)
+        return ERROR_INT("x0 > x1", procName, 1);
+    ny = numaGetCount(nay);
+    nx = numaGetCount(nax);
+    if (nx != ny)
+        return ERROR_INT("nax and nay not same size arrays", procName, 1);
+    if (ny < 2)
+        return ERROR_INT("not enough points", procName, 1);
+    if (type == L_QUADRATIC_INTERP && ny == 2) {
+        type = L_LINEAR_INTERP;
+        L_WARNING("only 2 points; using linear interp\n", procName);
+    }
+    numaGetMin(nax, &minx, NULL);
+    numaGetMax(nax, &maxx, NULL);
+    if (x0 < minx || x1 > maxx)
+        return ERROR_INT("xval is out of bounds", procName, 1);
+
+        /* Make sure that nax is sorted in increasing order */
+    numaIsSorted(nax, L_SORT_INCREASING, &sorted);
+    if (!sorted) {
+        L_WARNING("we are sorting nax in increasing order\n", procName);
+        numaSortPair(nax, nay, L_SORT_INCREASING, &nasx, &nasy);
+    } else {
+        nasx = numaClone(nax);
+        nasy = numaClone(nay);
+    }
+
+    fax = numaGetFArray(nasx, L_NOCOPY);
+    fay = numaGetFArray(nasy, L_NOCOPY);
+
+        /* Get array of indices into fax for interpolated locations */
+    if ((index = (l_int32 *)LEPT_CALLOC(npts, sizeof(l_int32))) == NULL)
+        return ERROR_INT("ind not made", procName, 1);
+    del = (x1 - x0) / (npts - 1.0);
+    for (i = 0, j = 0; j < nx && i < npts; i++) {
+        xval = x0 + i * del;
+        while (j < nx - 1 && xval > fax[j])
+            j++;
+        if (xval == fax[j])
+            index[i] = L_MIN(j, nx - 1);
+        else    /* the index of fax[] is just below xval */
+            index[i] = L_MAX(j - 1, 0);
+    }
+
+        /* For each point to be interpolated, get the y value */
+    nady = numaCreate(npts);
+    *pnady = nady;
+    if (pnadx) {
+        nadx = numaCreate(npts);
+        *pnadx = nadx;
+    }
+    for (i = 0; i < npts; i++) {
+        xval = x0 + i * del;
+        if (pnadx)
+            numaAddNumber(nadx, xval);
+        im = index[i];
+        excess = xval - fax[im];
+        if (excess == 0.0) {
+            numaAddNumber(nady, fay[im]);
+            continue;
+        }
+        fract = excess / (fax[im + 1] - fax[im]);
+
+        if (type == L_LINEAR_INTERP) {
+            yval = fay[im] + fract * (fay[im + 1] - fay[im]);
+            numaAddNumber(nady, yval);
+            continue;
+        }
+
+            /* Quadratic interpolation */
+        if (im == 0) {
+            i1 = im;
+            i2 = im + 1;
+            i3 = im + 2;
+        } else {
+            i1 = im - 1;
+            i2 = im;
+            i3 = im + 1;
+        }
+        d1 = (fax[i1] - fax[i2]) * (fax[i1] - fax[i3]);
+        d2 = (fax[i2] - fax[i1]) * (fax[i2] - fax[i3]);
+        d3 = (fax[i3] - fax[i1]) * (fax[i3] - fax[i2]);
+        yval = fay[i1] * (xval - fax[i2]) * (xval - fax[i3]) / d1 +
+               fay[i2] * (xval - fax[i1]) * (xval - fax[i3]) / d2 +
+               fay[i3] * (xval - fax[i1]) * (xval - fax[i2]) / d3;
+        numaAddNumber(nady, yval);
+    }
+
+    LEPT_FREE(index);
+    numaDestroy(&nasx);
+    numaDestroy(&nasy);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                     Functions requiring interpolation                *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaFitMax()
+ *
+ *      Input:  na  (numa of ordinate values, to fit a max to)
+ *              &maxval (<return> max value)
+ *              naloc (<optional> associated numa of abscissa values)
+ *              &maxloc (<return> abscissa value that gives max value in na;
+ *                   if naloc == null, this is given as an interpolated
+ *                   index value)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Note: if naloc is given, there is no requirement that the
+ *        data points are evenly spaced.  Lagrangian interpolation
+ *        handles that.  The only requirement is that the
+ *        data points are ordered so that the values in naloc
+ *        are either increasing or decreasing.  We test to make
+ *        sure that the sizes of na and naloc are equal, and it
+ *        is assumed that the correspondences na[i] as a function
+ *        of naloc[i] are properly arranged for all i.
+ *
+ *  The formula for Lagrangian interpolation through 3 data pts is:
+ *       y(x) = y1(x-x2)(x-x3)/((x1-x2)(x1-x3)) +
+ *              y2(x-x1)(x-x3)/((x2-x1)(x2-x3)) +
+ *              y3(x-x1)(x-x2)/((x3-x1)(x3-x2))
+ *
+ *  Then the derivative, using the constants (c1,c2,c3) defined below,
+ *  is set to 0:
+ *       y'(x) = 2x(c1+c2+c3) - c1(x2+x3) - c2(x1+x3) - c3(x1+x2) = 0
+ */
+l_int32
+numaFitMax(NUMA       *na,
+           l_float32  *pmaxval,
+           NUMA       *naloc,
+           l_float32  *pmaxloc)
+{
+l_float32  val;
+l_float32  smaxval;  /* start value of maximum sample, before interpolating */
+l_int32    n, imaxloc;
+l_float32  x1, x2, x3, y1, y2, y3, c1, c2, c3, a, b, xmax, ymax;
+
+    PROCNAME("numaFitMax");
+
+    *pmaxval = *pmaxloc = 0.0;  /* init */
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (!pmaxval)
+        return ERROR_INT("&maxval not defined", procName, 1);
+    if (!pmaxloc)
+        return ERROR_INT("&maxloc not defined", procName, 1);
+
+    n = numaGetCount(na);
+    if (naloc) {
+        if (n != numaGetCount(naloc))
+            return ERROR_INT("na and naloc of unequal size", procName, 1);
+    }
+
+    numaGetMax(na, &smaxval, &imaxloc);
+
+        /* Simple case: max is at end point */
+    if (imaxloc == 0 || imaxloc == n - 1) {
+        *pmaxval = smaxval;
+        if (naloc) {
+            numaGetFValue(naloc, imaxloc, &val);
+            *pmaxloc = val;
+        } else {
+            *pmaxloc = imaxloc;
+        }
+        return 0;
+    }
+
+        /* Interior point; use quadratic interpolation */
+    y2 = smaxval;
+    numaGetFValue(na, imaxloc - 1, &val);
+    y1 = val;
+    numaGetFValue(na, imaxloc + 1, &val);
+    y3 = val;
+    if (naloc) {
+        numaGetFValue(naloc, imaxloc - 1, &val);
+        x1 = val;
+        numaGetFValue(naloc, imaxloc, &val);
+        x2 = val;
+        numaGetFValue(naloc, imaxloc + 1, &val);
+        x3 = val;
+    } else {
+        x1 = imaxloc - 1;
+        x2 = imaxloc;
+        x3 = imaxloc + 1;
+    }
+
+        /* Can't interpolate; just use the max val in na
+         * and the corresponding one in naloc */
+    if (x1 == x2 || x1 == x3 || x2 == x3) {
+        *pmaxval = y2;
+        *pmaxloc = x2;
+        return 0;
+    }
+
+        /* Use lagrangian interpolation; set dy/dx = 0 */
+    c1 = y1 / ((x1 - x2) * (x1 - x3));
+    c2 = y2 / ((x2 - x1) * (x2 - x3));
+    c3 = y3 / ((x3 - x1) * (x3 - x2));
+    a = c1 + c2 + c3;
+    b = c1 * (x2 + x3) + c2 * (x1 + x3) + c3 * (x1 + x2);
+    xmax = b / (2 * a);
+    ymax = c1 * (xmax - x2) * (xmax - x3) +
+           c2 * (xmax - x1) * (xmax - x3) +
+           c3 * (xmax - x1) * (xmax - x2);
+    *pmaxval = ymax;
+    *pmaxloc = xmax;
+
+    return 0;
+}
+
+
+/*!
+ *  numaDifferentiateInterval()
+ *
+ *      Input:  nax (numa of abscissa values)
+ *              nay (numa of ordinate values, corresponding to nax)
+ *              x0 (start value of interval)
+ *              x1 (end value of interval)
+ *              npts (number of points to evaluate function in interval)
+ *              &nadx (<optional return> array of x values in interval)
+ *              &nady (<return> array of derivatives in interval)
+ *      Return: 0 if OK, 1 on error (e.g., if x0 or x1 is outside range)
+ *
+ *  Notes:
+ *      (1) The values in nax must be sorted in increasing order.
+ *          If they are not sorted, it is done in the interpolation
+ *          step, and a warning is issued.
+ *      (2) Caller should check for valid return.
+ */
+l_int32
+numaDifferentiateInterval(NUMA       *nax,
+                          NUMA       *nay,
+                          l_float32   x0,
+                          l_float32   x1,
+                          l_int32     npts,
+                          NUMA      **pnadx,
+                          NUMA      **pnady)
+{
+l_int32     i, nx, ny;
+l_float32   minx, maxx, der, invdel;
+l_float32  *fay;
+NUMA       *nady, *naiy;
+
+    PROCNAME("numaDifferentiateInterval");
+
+    if (pnadx) *pnadx = NULL;
+    if (!pnady)
+        return ERROR_INT("&nady not defined", procName, 1);
+    *pnady = NULL;
+    if (!nay)
+        return ERROR_INT("nay not defined", procName, 1);
+    if (!nax)
+        return ERROR_INT("nax not defined", procName, 1);
+    if (x0 > x1)
+        return ERROR_INT("x0 > x1", procName, 1);
+    ny = numaGetCount(nay);
+    nx = numaGetCount(nax);
+    if (nx != ny)
+        return ERROR_INT("nax and nay not same size arrays", procName, 1);
+    if (ny < 2)
+        return ERROR_INT("not enough points", procName, 1);
+    numaGetMin(nax, &minx, NULL);
+    numaGetMax(nax, &maxx, NULL);
+    if (x0 < minx || x1 > maxx)
+        return ERROR_INT("xval is out of bounds", procName, 1);
+    if (npts < 2)
+        return ERROR_INT("npts < 2", procName, 1);
+
+        /* Generate interpolated array over specified interval */
+    if (numaInterpolateArbxInterval(nax, nay, L_LINEAR_INTERP, x0, x1,
+                                    npts, pnadx, &naiy))
+        return ERROR_INT("interpolation failed", procName, 1);
+
+    nady = numaCreate(npts);
+    *pnady = nady;
+    invdel = 0.5 * ((l_float32)npts - 1.0) / (x1 - x0);
+    fay = numaGetFArray(naiy, L_NOCOPY);
+
+        /* Compute and save derivatives */
+    der = 0.5 * invdel * (fay[1] - fay[0]);
+    numaAddNumber(nady, der);
+    for (i = 1; i < npts - 1; i++)  {
+        der = invdel * (fay[i + 1] - fay[i - 1]);
+        numaAddNumber(nady, der);
+    }
+    der = 0.5 * invdel * (fay[npts - 1] - fay[npts - 2]);
+    numaAddNumber(nady, der);
+
+    numaDestroy(&naiy);
+    return 0;
+}
+
+
+/*!
+ *  numaIntegrateInterval()
+ *
+ *      Input:  nax (numa of abscissa values)
+ *              nay (numa of ordinate values, corresponding to nax)
+ *              x0 (start value of interval)
+ *              x1 (end value of interval)
+ *              npts (number of points to evaluate function in interval)
+ *              &sum (<return> integral of function over interval)
+ *      Return: 0 if OK, 1 on error (e.g., if x0 or x1 is outside range)
+ *
+ *  Notes:
+ *      (1) The values in nax must be sorted in increasing order.
+ *          If they are not sorted, it is done in the interpolation
+ *          step, and a warning is issued.
+ *      (2) Caller should check for valid return.
+ */
+l_int32
+numaIntegrateInterval(NUMA       *nax,
+                      NUMA       *nay,
+                      l_float32   x0,
+                      l_float32   x1,
+                      l_int32     npts,
+                      l_float32  *psum)
+{
+l_int32     i, nx, ny;
+l_float32   minx, maxx, sum, del;
+l_float32  *fay;
+NUMA       *naiy;
+
+    PROCNAME("numaIntegrateInterval");
+
+    if (!psum)
+        return ERROR_INT("&sum not defined", procName, 1);
+    *psum = 0.0;
+    if (!nay)
+        return ERROR_INT("nay not defined", procName, 1);
+    if (!nax)
+        return ERROR_INT("nax not defined", procName, 1);
+    if (x0 > x1)
+        return ERROR_INT("x0 > x1", procName, 1);
+    if (npts < 2)
+        return ERROR_INT("npts < 2", procName, 1);
+    ny = numaGetCount(nay);
+    nx = numaGetCount(nax);
+    if (nx != ny)
+        return ERROR_INT("nax and nay not same size arrays", procName, 1);
+    if (ny < 2)
+        return ERROR_INT("not enough points", procName, 1);
+    numaGetMin(nax, &minx, NULL);
+    numaGetMax(nax, &maxx, NULL);
+    if (x0 < minx || x1 > maxx)
+        return ERROR_INT("xval is out of bounds", procName, 1);
+
+        /* Generate interpolated array over specified interval */
+    if (numaInterpolateArbxInterval(nax, nay, L_LINEAR_INTERP, x0, x1,
+                                    npts, NULL, &naiy))
+        return ERROR_INT("interpolation failed", procName, 1);
+
+    del = (x1 - x0) / ((l_float32)npts - 1.0);
+    fay = numaGetFArray(naiy, L_NOCOPY);
+
+        /* Compute integral (simple trapezoid) */
+    sum = 0.5 * (fay[0] + fay[npts - 1]);
+    for (i = 1; i < npts - 1; i++)
+        sum += fay[i];
+    *psum = del * sum;
+
+    numaDestroy(&naiy);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                                Sorting                               *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaSortGeneral()
+ *
+ *      Input:  na (source numa)
+ *              nasort (<optional> sorted numa)
+ *              naindex (<optional> index of elements in na associated
+ *                       with each element of nasort)
+ *              nainvert (<optional> index of elements in nasort associated
+ *                        with each element of na)
+ *              sortorder (L_SORT_INCREASING or L_SORT_DECREASING)
+ *              sorttype (L_SHELL_SORT or L_BIN_SORT)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Sorting can be confusing.  Here's an array of five values with
+ *          the results shown for the 3 output arrays.
+ *
+ *          na      nasort   naindex   nainvert
+ *          -----------------------------------
+ *          3         9         2         3
+ *          4         6         3         2
+ *          9         4         1         0
+ *          6         3         0         1
+ *          1         1         4         4
+ *
+ *          Note that naindex is a LUT into na for the sorted array values,
+ *          and nainvert directly gives the sorted index values for the
+ *          input array.  It is useful to view naindex is as a map:
+ *                 0  -->  2
+ *                 1  -->  3
+ *                 2  -->  1
+ *                 3  -->  0
+ *                 4  -->  4
+ *          and nainvert, the inverse of this map:
+ *                 0  -->  3
+ *                 1  -->  2
+ *                 2  -->  0
+ *                 3  -->  1
+ *                 4  -->  4
+ *
+ *          We can write these relations symbolically as:
+ *              nasort[i] = na[naindex[i]]
+ *              na[i] = nasort[nainvert[i]]
+ */
+l_int32
+numaSortGeneral(NUMA    *na,
+                NUMA   **pnasort,
+                NUMA   **pnaindex,
+                NUMA   **pnainvert,
+                l_int32  sortorder,
+                l_int32  sorttype)
+{
+NUMA  *naindex;
+
+    PROCNAME("numaSortGeneral");
+
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return ERROR_INT("invalid sort order", procName, 1);
+    if (sorttype != L_SHELL_SORT && sorttype != L_BIN_SORT)
+        return ERROR_INT("invalid sort type", procName, 1);
+    if (!pnasort && !pnaindex && !pnainvert)
+        return ERROR_INT("nothing to do", procName, 1);
+    if (pnasort) *pnasort = NULL;
+    if (pnaindex) *pnaindex = NULL;
+    if (pnainvert) *pnainvert = NULL;
+
+    if (sorttype == L_SHELL_SORT)
+        naindex = numaGetSortIndex(na, sortorder);
+    else  /* sorttype == L_BIN_SORT */
+        naindex = numaGetBinSortIndex(na, sortorder);
+
+    if (pnasort)
+        *pnasort = numaSortByIndex(na, naindex);
+    if (pnainvert)
+        *pnainvert = numaInvertMap(naindex);
+    if (pnaindex)
+        *pnaindex = naindex;
+    else
+        numaDestroy(&naindex);
+    return 0;
+}
+
+
+/*!
+ *  numaSortAutoSelect()
+ *
+ *      Input:  nas (input numa)
+ *              sortorder (L_SORT_INCREASING or L_SORT_DECREASING)
+ *      Return: naout (output sorted numa), or null on error
+ *
+ *  Notes:
+ *      (1) This does either a shell sort or a bin sort, depending on
+ *          the number of elements in nas and the dynamic range.
+ */
+NUMA *
+numaSortAutoSelect(NUMA    *nas,
+                   l_int32  sortorder)
+{
+l_int32  type;
+
+    PROCNAME("numaSortAutoSelect");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return (NUMA *)ERROR_PTR("invalid sort order", procName, NULL);
+
+    type = numaChooseSortType(nas);
+    if (type == L_SHELL_SORT)
+        return numaSort(NULL, nas, sortorder);
+    else if (type == L_BIN_SORT)
+        return numaBinSort(nas, sortorder);
+    else
+        return (NUMA *)ERROR_PTR("invalid sort type", procName, NULL);
+}
+
+
+/*!
+ *  numaSortIndexAutoSelect()
+ *
+ *      Input:  nas
+ *              sortorder (L_SORT_INCREASING or L_SORT_DECREASING)
+ *      Return: nad (indices of nas, sorted by value in nas), or null on error
+ *
+ *  Notes:
+ *      (1) This does either a shell sort or a bin sort, depending on
+ *          the number of elements in nas and the dynamic range.
+ */
+NUMA *
+numaSortIndexAutoSelect(NUMA    *nas,
+                        l_int32  sortorder)
+{
+l_int32  type;
+
+    PROCNAME("numaSortIndexAutoSelect");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return (NUMA *)ERROR_PTR("invalid sort order", procName, NULL);
+
+    type = numaChooseSortType(nas);
+    if (type == L_SHELL_SORT)
+        return numaGetSortIndex(nas, sortorder);
+    else if (type == L_BIN_SORT)
+        return numaGetBinSortIndex(nas, sortorder);
+    else
+        return (NUMA *)ERROR_PTR("invalid sort type", procName, NULL);
+}
+
+
+/*!
+ *  numaChooseSortType()
+ *
+ *      Input:  na (to be sorted)
+ *      Return: sorttype (L_SHELL_SORT or L_BIN_SORT), or UNDEF on error.
+ *
+ *  Notes:
+ *      (1) This selects either a shell sort or a bin sort, depending on
+ *          the number of elements in nas and the dynamic range.
+ *      (2) If there are negative values in nas, it selects shell sort.
+ */
+l_int32
+numaChooseSortType(NUMA  *nas)
+{
+l_int32    n, type;
+l_float32  minval, maxval;
+
+    PROCNAME("numaChooseSortType");
+
+    if (!nas)
+        return ERROR_INT("nas not defined", procName, UNDEF);
+
+    numaGetMin(nas, &minval, NULL);
+    n = numaGetCount(nas);
+
+        /* Very small histogram; use shell sort */
+    if (minval < 0.0 || n < 200) {
+        L_INFO("Shell sort chosen\n", procName);
+        return L_SHELL_SORT;
+    }
+
+        /* Need to compare nlog(n) with maxval.  The factor of 0.003
+         * was determined by comparing times for different histogram
+         * sizes and maxval.  It is very small because binsort is fast
+         * and shell sort gets slow for large n. */
+    numaGetMax(nas, &maxval, NULL);
+    if (n * log((l_float32)n) < 0.003 * maxval) {
+        type = L_SHELL_SORT;
+        L_INFO("Shell sort chosen\n", procName);
+    } else {
+        type = L_BIN_SORT;
+        L_INFO("Bin sort chosen\n", procName);
+    }
+    return type;
+}
+
+
+/*!
+ *  numaSort()
+ *
+ *      Input:  naout (output numa; can be NULL or equal to nain)
+ *              nain (input numa)
+ *              sortorder (L_SORT_INCREASING or L_SORT_DECREASING)
+ *      Return: naout (output sorted numa), or null on error
+ *
+ *  Notes:
+ *      (1) Set naout = nain for in-place; otherwise, set naout = NULL.
+ *      (2) Source: Shell sort, modified from K&R, 2nd edition, p.62.
+ *          Slow but simple O(n logn) sort.
+ */
+NUMA *
+numaSort(NUMA    *naout,
+         NUMA    *nain,
+         l_int32  sortorder)
+{
+l_int32     i, n, gap, j;
+l_float32   tmp;
+l_float32  *array;
+
+    PROCNAME("numaSort");
+
+    if (!nain)
+        return (NUMA *)ERROR_PTR("nain not defined", procName, NULL);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return (NUMA *)ERROR_PTR("invalid sort order", procName, NULL);
+
+        /* Make naout if necessary; otherwise do in-place */
+    if (!naout)
+        naout = numaCopy(nain);
+    else if (nain != naout)
+        return (NUMA *)ERROR_PTR("invalid: not in-place", procName, NULL);
+    array = naout->array;  /* operate directly on the array */
+    n = numaGetCount(naout);
+
+        /* Shell sort */
+    for (gap = n/2; gap > 0; gap = gap / 2) {
+        for (i = gap; i < n; i++) {
+            for (j = i - gap; j >= 0; j -= gap) {
+                if ((sortorder == L_SORT_INCREASING &&
+                     array[j] > array[j + gap]) ||
+                    (sortorder == L_SORT_DECREASING &&
+                     array[j] < array[j + gap]))
+                {
+                    tmp = array[j];
+                    array[j] = array[j + gap];
+                    array[j + gap] = tmp;
+                }
+            }
+        }
+    }
+
+    return naout;
+}
+
+
+/*!
+ *  numaBinSort()
+ *
+ *      Input:  nas (of non-negative integers with a max that is
+ *                   typically less than 50,000)
+ *              sortorder (L_SORT_INCREASING or L_SORT_DECREASING)
+ *      Return: na (sorted), or null on error
+ *
+ *  Notes:
+ *      (1) Because this uses a bin sort with buckets of size 1, it
+ *          is not appropriate for sorting either small arrays or
+ *          arrays containing very large integer values.  For such
+ *          arrays, use a standard general sort function like
+ *          numaSort().
+ */
+NUMA *
+numaBinSort(NUMA    *nas,
+            l_int32  sortorder)
+{
+NUMA  *nat, *nad;
+
+    PROCNAME("numaBinSort");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return (NUMA *)ERROR_PTR("invalid sort order", procName, NULL);
+
+    nat = numaGetBinSortIndex(nas, sortorder);
+    nad = numaSortByIndex(nas, nat);
+    numaDestroy(&nat);
+    return nad;
+}
+
+
+/*!
+ *  numaGetSortIndex()
+ *
+ *      Input:  na
+ *              sortorder (L_SORT_INCREASING or L_SORT_DECREASING)
+ *      Return: na giving an array of indices that would sort
+ *              the input array, or null on error
+ */
+NUMA *
+numaGetSortIndex(NUMA    *na,
+                 l_int32  sortorder)
+{
+l_int32     i, n, gap, j;
+l_float32   tmp;
+l_float32  *array;   /* copy of input array */
+l_float32  *iarray;  /* array of indices */
+NUMA       *naisort;
+
+    PROCNAME("numaGetSortIndex");
+
+    if (!na)
+        return (NUMA *)ERROR_PTR("na not defined", procName, NULL);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return (NUMA *)ERROR_PTR("invalid sortorder", procName, NULL);
+
+    n = numaGetCount(na);
+    if ((array = numaGetFArray(na, L_COPY)) == NULL)
+        return (NUMA *)ERROR_PTR("array not made", procName, NULL);
+    if ((iarray = (l_float32 *)LEPT_CALLOC(n, sizeof(l_float32))) == NULL)
+        return (NUMA *)ERROR_PTR("iarray not made", procName, NULL);
+    for (i = 0; i < n; i++)
+        iarray[i] = i;
+
+        /* Shell sort */
+    for (gap = n/2; gap > 0; gap = gap / 2) {
+        for (i = gap; i < n; i++) {
+            for (j = i - gap; j >= 0; j -= gap) {
+                if ((sortorder == L_SORT_INCREASING &&
+                     array[j] > array[j + gap]) ||
+                    (sortorder == L_SORT_DECREASING &&
+                     array[j] < array[j + gap]))
+                {
+                    tmp = array[j];
+                    array[j] = array[j + gap];
+                    array[j + gap] = tmp;
+                    tmp = iarray[j];
+                    iarray[j] = iarray[j + gap];
+                    iarray[j + gap] = tmp;
+                }
+            }
+        }
+    }
+
+    naisort = numaCreate(n);
+    for (i = 0; i < n; i++)
+        numaAddNumber(naisort, iarray[i]);
+
+    LEPT_FREE(array);
+    LEPT_FREE(iarray);
+    return naisort;
+}
+
+
+/*!
+ *  numaGetBinSortIndex()
+ *
+ *      Input:  na (of non-negative integers with a max that is typically
+ *                  less than 1,000,000)
+ *              sortorder (L_SORT_INCREASING or L_SORT_DECREASING)
+ *      Return: na (sorted), or null on error
+ *
+ *  Notes:
+ *      (1) This creates an array (or lookup table) that contains
+ *          the sorted position of the elements in the input Numa.
+ *      (2) Because it uses a bin sort with buckets of size 1, it
+ *          is not appropriate for sorting either small arrays or
+ *          arrays containing very large integer values.  For such
+ *          arrays, use a standard general sort function like
+ *          numaGetSortIndex().
+ */
+NUMA *
+numaGetBinSortIndex(NUMA    *nas,
+                    l_int32  sortorder)
+{
+l_int32    i, n, isize, ival, imax;
+l_float32  size;
+NUMA      *na, *nai, *nad;
+L_PTRA    *paindex;
+
+    PROCNAME("numaGetBinSortIndex");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return (NUMA *)ERROR_PTR("invalid sort order", procName, NULL);
+
+        /* Set up a ptra holding numa at indices for which there
+         * are values in nas.  Suppose nas has the value 230 at index
+         * 7355.  A numa holding the index 7355 is created and stored
+         * at the ptra index 230.  If there is another value of 230
+         * in nas, its index is added to the same numa (at index 230
+         * in the ptra).  When finished, the ptra can be scanned for numa,
+         * and the original indices in the nas can be read out.  In this
+         * way, the ptra effectively sorts the input numbers in the nas. */
+    numaGetMax(nas, &size, NULL);
+    isize = (l_int32)size;
+    if (isize > 1000000)
+        L_WARNING("large array: %d elements\n", procName, isize);
+    paindex = ptraCreate(isize + 1);
+    n = numaGetCount(nas);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(nas, i, &ival);
+        nai = (NUMA *)ptraGetPtrToItem(paindex, ival);
+        if (!nai) {  /* make it; no shifting will occur */
+            nai = numaCreate(1);
+            ptraInsert(paindex, ival, nai, L_MIN_DOWNSHIFT);
+        }
+        numaAddNumber(nai, i);
+    }
+
+        /* Sort by scanning the ptra, extracting numas and pulling
+         * the (index into nas) numbers out of each numa, taken
+         * successively in requested order. */
+    ptraGetMaxIndex(paindex, &imax);
+    nad = numaCreate(0);
+    if (sortorder == L_SORT_INCREASING) {
+        for (i = 0; i <= imax; i++) {
+            na = (NUMA *)ptraRemove(paindex, i, L_NO_COMPACTION);
+            if (!na) continue;
+            numaJoin(nad, na, 0, -1);
+            numaDestroy(&na);
+        }
+    } else {  /* L_SORT_DECREASING */
+        for (i = imax; i >= 0; i--) {
+            na = (NUMA *)ptraRemoveLast(paindex);
+            if (!na) break;  /* they've all been removed */
+            numaJoin(nad, na, 0, -1);
+            numaDestroy(&na);
+        }
+    }
+
+    ptraDestroy(&paindex, FALSE, FALSE);
+    return nad;
+}
+
+
+/*!
+ *  numaSortByIndex()
+ *
+ *      Input:  nas
+ *              naindex (na that maps from the new numa to the input numa)
+ *      Return: nad (sorted), or null on error
+ */
+NUMA *
+numaSortByIndex(NUMA  *nas,
+                NUMA  *naindex)
+{
+l_int32    i, n, index;
+l_float32  val;
+NUMA      *nad;
+
+    PROCNAME("numaSortByIndex");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (!naindex)
+        return (NUMA *)ERROR_PTR("naindex not defined", procName, NULL);
+
+    n = numaGetCount(nas);
+    nad = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(naindex, i, &index);
+        numaGetFValue(nas, index, &val);
+        numaAddNumber(nad, val);
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaIsSorted()
+ *
+ *      Input:  nas
+ *              sortorder (L_SORT_INCREASING or L_SORT_DECREASING)
+ *              &sorted (<return> 1 if sorted; 0 if not)
+ *      Return: 1 if OK; 0 on error
+ *
+ *  Notes:
+ *      (1) This is a quick O(n) test if nas is sorted.  It is useful
+ *          in situations where the array is likely to be already
+ *          sorted, and a sort operation can be avoided.
+ */
+l_int32
+numaIsSorted(NUMA     *nas,
+             l_int32   sortorder,
+             l_int32  *psorted)
+{
+l_int32    i, n;
+l_float32  prevval, val;
+
+    PROCNAME("numaIsSorted");
+
+    if (!psorted)
+        return ERROR_INT("&sorted not defined", procName, 1);
+    *psorted = FALSE;
+    if (!nas)
+        return ERROR_INT("nas not defined", procName, 1);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return ERROR_INT("invalid sortorder", procName, 1);
+
+    n = numaGetCount(nas);
+    numaGetFValue(nas, 0, &prevval);
+    for (i = 1; i < n; i++) {
+        numaGetFValue(nas, i, &val);
+        if ((sortorder == L_SORT_INCREASING && val < prevval) ||
+            (sortorder == L_SORT_DECREASING && val > prevval))
+            return 0;
+    }
+
+    *psorted = TRUE;
+    return 0;
+}
+
+
+/*!
+ *  numaSortPair()
+ *
+ *      Input:  nax, nay (input arrays)
+ *              sortorder (L_SORT_INCREASING or L_SORT_DECREASING)
+ *              &nasx (<return> sorted)
+ *              &naxy (<return> sorted exactly in order of nasx)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function sorts the two input arrays, nax and nay,
+ *          together, using nax as the key for sorting.
+ */
+l_int32
+numaSortPair(NUMA    *nax,
+             NUMA    *nay,
+             l_int32  sortorder,
+             NUMA   **pnasx,
+             NUMA   **pnasy)
+{
+l_int32  sorted;
+NUMA    *naindex;
+
+    PROCNAME("numaSortPair");
+
+    if (pnasx) *pnasx = NULL;
+    if (pnasy) *pnasy = NULL;
+    if (!pnasx || !pnasy)
+        return ERROR_INT("&nasx and/or &nasy not defined", procName, 1);
+    if (!nax)
+        return ERROR_INT("nax not defined", procName, 1);
+    if (!nay)
+        return ERROR_INT("nay not defined", procName, 1);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return ERROR_INT("invalid sortorder", procName, 1);
+
+    numaIsSorted(nax, sortorder, &sorted);
+    if (sorted == TRUE) {
+        *pnasx = numaCopy(nax);
+        *pnasy = numaCopy(nay);
+    } else {
+        naindex = numaGetSortIndex(nax, sortorder);
+        *pnasx = numaSortByIndex(nax, naindex);
+        *pnasy = numaSortByIndex(nay, naindex);
+        numaDestroy(&naindex);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  numaInvertMap()
+ *
+ *      Input:  nas
+ *      Return: nad (the inverted map), or null on error or if not invertible
+ *
+ *  Notes:
+ *      (1) This requires that nas contain each integer from 0 to n-1.
+ *          The array is typically an index array into a sort or permutation
+ *          of another array.
+ */
+NUMA *
+numaInvertMap(NUMA  *nas)
+{
+l_int32   i, n, val, error;
+l_int32  *test;
+NUMA     *nad;
+
+    PROCNAME("numaInvertMap");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+
+    n = numaGetCount(nas);
+    nad = numaMakeConstant(0.0, n);
+    test = (l_int32 *)LEPT_CALLOC(n, sizeof(l_int32));
+    error = 0;
+    for (i = 0; i < n; i++) {
+        numaGetIValue(nas, i, &val);
+        if (val >= n) {
+            error = 1;
+            break;
+        }
+        numaReplaceNumber(nad, val, i);
+        if (test[val] == 0) {
+            test[val] = 1;
+        } else {
+            error = 1;
+            break;
+        }
+    }
+
+    LEPT_FREE(test);
+    if (error) {
+        numaDestroy(&nad);
+        return (NUMA *)ERROR_PTR("nas not invertible", procName, NULL);
+    }
+
+    return nad;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                          Random permutation                          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaPseudorandomSequence()
+ *
+ *      Input:  size (of sequence)
+ *              seed (for random number generation)
+ *      Return: na (pseudorandom on {0,...,size - 1}), or null on error
+ *
+ *  Notes:
+ *      (1) This uses the Durstenfeld shuffle.
+ *          See: http://en.wikipedia.org/wiki/Fisher–Yates_shuffle.
+ *          Result is a pseudorandom permutation of the sequence of integers
+ *          from 0 to size - 1.
+ */
+NUMA *
+numaPseudorandomSequence(l_int32  size,
+                         l_int32  seed)
+{
+l_int32   i, index, temp;
+l_int32  *array;
+NUMA     *na;
+
+    PROCNAME("numaPseudorandomSequence");
+
+    if (size <= 0)
+        return (NUMA *)ERROR_PTR("size <= 0", procName, NULL);
+
+    if ((array = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL)
+        return (NUMA *)ERROR_PTR("array not made", procName, NULL);
+    for (i = 0; i < size; i++)
+        array[i] = i;
+    srand(seed);
+    for (i = size - 1; i > 0; i--) {
+        index = (l_int32)((i + 1) * ((l_float64)rand() / (l_float64)RAND_MAX));
+        index = L_MIN(index, i);
+        temp = array[i];
+        array[i] = array[index];
+        array[index] = temp;
+    }
+
+    na = numaCreateFromIArray(array, size);
+    LEPT_FREE(array);
+    return na;
+}
+
+
+/*!
+ *  numaRandomPermutation()
+ *
+ *      Input:  nas (input array)
+ *              seed (for random number generation)
+ *      Return: nas (randomly shuffled array), or null on error
+ */
+NUMA *
+numaRandomPermutation(NUMA    *nas,
+                      l_int32  seed)
+{
+l_int32    i, index, size;
+l_float32  val;
+NUMA      *naindex, *nad;
+
+    PROCNAME("numaRandomPermutation");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+
+    size = numaGetCount(nas);
+    naindex = numaPseudorandomSequence(size, seed);
+    nad = numaCreate(size);
+    for (i = 0; i < size; i++) {
+        numaGetIValue(naindex, i, &index);
+        numaGetFValue(nas, index, &val);
+        numaAddNumber(nad, val);
+    }
+
+    numaDestroy(&naindex);
+    return nad;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                     Functions requiring sorting                      *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaGetRankValue()
+ *
+ *      Input:  na
+ *              fract (use 0.0 for smallest, 1.0 for largest)
+ *              nasort (<optional> increasing sorted version of na)
+ *              usebins (0 for general sort; 1 for bin sort)
+ *              &val  (<return> rank val)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Computes the rank value of a number in the @na, which is
+ *          the number that is a fraction @fract from the small
+ *          end of the sorted version of @na.
+ *      (2) If you do this multiple times for different rank values,
+ *          sort the array in advance and use that for @nasort;
+ *          if you're only calling this once, input @nasort == NULL.
+ *      (3) If @usebins == 1, this uses a bin sorting method.
+ *          Use this only where:
+ *           * the numbers are non-negative integers
+ *           * there are over 100 numbers
+ *           * the maximum value is less than about 50,000
+ *      (4) The advantage of using a bin sort is that it is O(n),
+ *          instead of O(nlogn) for general sort routines.
+ */
+l_int32
+numaGetRankValue(NUMA       *na,
+                 l_float32   fract,
+                 NUMA       *nasort,
+                 l_int32     usebins,
+                 l_float32  *pval)
+{
+l_int32  n, index;
+NUMA    *nas;
+
+    PROCNAME("numaGetRankValue");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0.0;  /* init */
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (fract < 0.0 || fract > 1.0)
+        return ERROR_INT("fract not in [0.0 ... 1.0]", procName, 1);
+    n = numaGetCount(na);
+    if (n == 0)
+        return ERROR_INT("na empty", procName, 1);
+
+    if (nasort) {
+        nas = nasort;
+    } else {
+        if (usebins == 0)
+            nas = numaSort(NULL, na, L_SORT_INCREASING);
+        else
+            nas = numaBinSort(na, L_SORT_INCREASING);
+        if (!nas)
+            return ERROR_INT("nas not made", procName, 1);
+    }
+    index = (l_int32)(fract * (l_float32)(n - 1) + 0.5);
+    numaGetFValue(nas, index, pval);
+
+    if (!nasort) numaDestroy(&nas);
+    return 0;
+}
+
+
+/*!
+ *  numaGetMedian()
+ *
+ *      Input:  na
+ *              &val  (<return> median value)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Computes the median value of the numbers in the numa, by
+ *          sorting and finding the middle value in the sorted array.
+ */
+l_int32
+numaGetMedian(NUMA       *na,
+              l_float32  *pval)
+{
+    PROCNAME("numaGetMedian");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0.0;  /* init */
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    return numaGetRankValue(na, 0.5, NULL, 0, pval);
+}
+
+
+/*!
+ *  numaGetBinnedMedian()
+ *
+ *      Input:  na
+ *              &val  (<return> integer median value)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Computes the median value of the numbers in the numa,
+ *          using bin sort and finding the middle value in the sorted array.
+ *      (2) See numaGetRankValue() for conditions on na for which
+ *          this should be used.  Otherwise, use numaGetMedian().
+ */
+l_int32
+numaGetBinnedMedian(NUMA     *na,
+                    l_int32  *pval)
+{
+l_int32    ret;
+l_float32  fval;
+
+    PROCNAME("numaGetBinnedMedian");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0;  /* init */
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    ret = numaGetRankValue(na, 0.5, NULL, 1, &fval);
+    *pval = lept_roundftoi(fval);
+    return ret;
+}
+
+
+/*!
+ *  numaGetMode()
+ *
+ *      Input:  na
+ *              &val  (<return> mode val)
+ *              &count  (<optional return> mode count)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Computes the mode value of the numbers in the numa, by
+ *          sorting and finding the value of the number with the
+ *          largest count.
+ *      (2) Optionally, also returns that count.
+ */
+l_int32
+numaGetMode(NUMA       *na,
+            l_float32  *pval,
+            l_int32    *pcount)
+{
+l_int32     i, n, maxcount, prevcount;
+l_float32   val, maxval, prevval;
+l_float32  *array;
+NUMA       *nasort;
+
+    PROCNAME("numaGetMode");
+
+    if (pcount) *pcount = 0;
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0.0;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if ((n = numaGetCount(na)) == 0)
+        return 1;
+
+    if ((nasort = numaSort(NULL, na, L_SORT_DECREASING)) == NULL)
+        return ERROR_INT("nas not made", procName, 1);
+    array = numaGetFArray(nasort, L_NOCOPY);
+
+        /* Initialize with array[0] */
+    prevval = array[0];
+    prevcount = 1;
+    maxval = prevval;
+    maxcount = prevcount;
+
+        /* Scan the sorted array, aggregating duplicates */
+    for (i = 1; i < n; i++) {
+        val = array[i];
+        if (val == prevval) {
+            prevcount++;
+        } else {  /* new value */
+            if (prevcount > maxcount) {  /* new max */
+                maxcount = prevcount;
+                maxval = prevval;
+            }
+            prevval = val;
+            prevcount = 1;
+        }
+    }
+
+        /* Was the mode the last run of elements? */
+    if (prevcount > maxcount) {
+        maxcount = prevcount;
+        maxval = prevval;
+    }
+
+    *pval = maxval;
+    if (pcount)
+        *pcount = maxcount;
+
+    numaDestroy(&nasort);
+    return 0;
+}
+
+
+/*!
+ *  numaGetMedianVariation()
+ *
+ *      Input:  na
+ *              &medval  (<optional return> median value)
+ *              &medvar  (<return> median variation from median val)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Finds the median of the absolute value of the variation from
+ *          the median value in the array.  Why take the absolute value?
+ *          Consider the case where you have values equally distributed
+ *          about both sides of a median value.  Without taking the absolute
+ *          value of the differences, you will get 0 for the variation,
+ *          and this is not useful.
+ */
+l_int32
+numaGetMedianVariation(NUMA       *na,
+                       l_float32  *pmedval,
+                       l_float32  *pmedvar)
+{
+l_int32    n, i;
+l_float32  val, medval;
+NUMA      *navar;
+
+    PROCNAME("numaGetMedianVar");
+
+    if (pmedval) *pmedval = 0.0;
+    if (!pmedvar)
+        return ERROR_INT("&medvar not defined", procName, 1);
+    *pmedvar = 0.0;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    numaGetMedian(na, &medval);
+    if (pmedval) *pmedval = medval;
+    n = numaGetCount(na);
+    navar = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        numaAddNumber(navar, L_ABS(val - medval));
+    }
+    numaGetMedian(navar, pmedvar);
+
+    numaDestroy(&navar);
+    return 0;
+}
+
+
+
+/*----------------------------------------------------------------------*
+ *                            Rearrangements                            *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaJoin()
+ *
+ *      Input:  nad  (dest numa; add to this one)
+ *              nas  (<optional> source numa; add from this one)
+ *              istart  (starting index in nas)
+ *              iend  (ending index in nas; use -1 to cat all)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) istart < 0 is taken to mean 'read from the start' (istart = 0)
+ *      (2) iend < 0 means 'read to the end'
+ *      (3) if nas == NULL, this is a no-op
+ */
+l_int32
+numaJoin(NUMA    *nad,
+         NUMA    *nas,
+         l_int32  istart,
+         l_int32  iend)
+{
+l_int32    n, i;
+l_float32  val;
+
+    PROCNAME("numaJoin");
+
+    if (!nad)
+        return ERROR_INT("nad not defined", procName, 1);
+    if (!nas)
+        return 0;
+
+    if (istart < 0)
+        istart = 0;
+    n = numaGetCount(nas);
+    if (iend < 0 || iend >= n)
+        iend = n - 1;
+    if (istart > iend)
+        return ERROR_INT("istart > iend; nothing to add", procName, 1);
+
+    for (i = istart; i <= iend; i++) {
+        numaGetFValue(nas, i, &val);
+        numaAddNumber(nad, val);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  numaaJoin()
+ *
+ *      Input:  naad  (dest naa; add to this one)
+ *              naas  (<optional> source naa; add from this one)
+ *              istart  (starting index in nas)
+ *              iend  (ending index in naas; use -1 to cat all)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) istart < 0 is taken to mean 'read from the start' (istart = 0)
+ *      (2) iend < 0 means 'read to the end'
+ *      (3) if naas == NULL, this is a no-op
+ */
+l_int32
+numaaJoin(NUMAA   *naad,
+          NUMAA   *naas,
+          l_int32  istart,
+          l_int32  iend)
+{
+l_int32  n, i;
+NUMA    *na;
+
+    PROCNAME("numaaJoin");
+
+    if (!naad)
+        return ERROR_INT("naad not defined", procName, 1);
+    if (!naas)
+        return 0;
+
+    if (istart < 0)
+        istart = 0;
+    n = numaaGetCount(naas);
+    if (iend < 0 || iend >= n)
+        iend = n - 1;
+    if (istart > iend)
+        return ERROR_INT("istart > iend; nothing to add", procName, 1);
+
+    for (i = istart; i <= iend; i++) {
+        na = numaaGetNuma(naas, i, L_CLONE);
+        numaaAddNuma(naad, na, L_INSERT);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  numaaFlattenToNuma()
+ *
+ *      Input:  numaa
+ *      Return: numa, or null on error
+ *
+ *  Notes:
+ *      (1) This 'flattens' the Numaa to a Numa, by joining successively
+ *          each Numa in the Numaa.
+ *      (2) It doesn't make any assumptions about the location of the
+ *          Numas in the Numaa array, unlike most Numaa functions.
+ *      (3) It leaves the input Numaa unchanged.
+ */
+NUMA *
+numaaFlattenToNuma(NUMAA  *naa)
+{
+l_int32  i, nalloc;
+NUMA    *na, *nad;
+NUMA   **array;
+
+    PROCNAME("numaaFlattenToNuma");
+
+    if (!naa)
+        return (NUMA *)ERROR_PTR("naa not defined", procName, NULL);
+
+    nalloc = naa->nalloc;
+    array = numaaGetPtrArray(naa);
+    nad = numaCreate(0);
+    for (i = 0; i < nalloc; i++) {
+        na = array[i];
+        if (!na) continue;
+        numaJoin(nad, na, 0, -1);
+    }
+
+    return nad;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                        Union and intersection                        *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaUnionByAset()
+ *
+ *      Input:  na1, na2
+ *      Return: nad (with the union of the set of numbers), or null on error
+ *
+ *  Notes:
+ *      (1) See sarrayUnion() for the approach.
+ *      (2) Here, the key in building the sorted tree is the number itself.
+ *      (3) A bucket sort approach can be used if the numbers are
+ *          integers and if they are small enough, because that is O(n)
+ *          instead of O(nlogn).
+ */
+NUMA *
+numaUnionByAset(NUMA  *na1,
+                NUMA  *na2)
+{
+NUMA  *na3, *nad;
+
+    PROCNAME("numaUnionByAset");
+
+    if (!na1)
+        return (NUMA *)ERROR_PTR("na1 not defined", procName, NULL);
+    if (!na2)
+        return (NUMA *)ERROR_PTR("na2 not defined", procName, NULL);
+
+        /* Join */
+    na3 = numaCopy(na1);
+    numaJoin(na3, na2, 0, -1);
+
+        /* Eliminate duplicates */
+    nad = numaRemoveDupsByAset(na3);
+    numaDestroy(&na3);
+    return nad;
+}
+
+
+/*!
+ *  numaRemoveDupsByAset()
+ *
+ *      Input:  nas
+ *      Return: nad (with duplicates removed), or null on error
+ */
+NUMA *
+numaRemoveDupsByAset(NUMA  *nas)
+{
+l_int32    i, n;
+l_float32  val;
+NUMA      *nad;
+L_ASET    *set;
+RB_TYPE    key;
+
+    PROCNAME("numaRemoveDupsByAset");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+
+    set = l_asetCreate(L_FLOAT_TYPE);
+    nad = numaCreate(0);
+    n = numaGetCount(nas);
+    for (i = 0; i < n; i++) {
+        numaGetFValue(nas, i, &val);
+        key.ftype = val;
+        if (!l_asetFind(set, key)) {
+            numaAddNumber(nad, val);
+            l_asetInsert(set, key);
+        }
+    }
+
+    l_asetDestroy(&set);
+    return nad;
+}
+
+
+/*!
+ *  numaIntersectionByAset()
+ *
+ *      Input:  na1, na2
+ *      Return: nad (with the intersection of the numa set), or null on error
+ *
+ *  Notes:
+ *      (1) See sarrayIntersection() for the approach.
+ *      (2) Here, the key in building the sorted tree is the number itself.
+ *      (3) A bucket sort approach can be used if the numbers are
+ *          integers and if they are small enough, because that is O(n)
+ *          instead of O(nlogn).
+ */
+NUMA *
+numaIntersectionByAset(NUMA  *na1,
+                       NUMA  *na2)
+{
+l_int32    n1, n2, i, n;
+l_float32  val;
+L_ASET    *set1, *set2;
+RB_TYPE    key;
+NUMA      *na_small, *na_big, *nad;
+
+    PROCNAME("numaIntersectionByAset");
+
+    if (!na1)
+        return (NUMA *)ERROR_PTR("na1 not defined", procName, NULL);
+    if (!na2)
+        return (NUMA *)ERROR_PTR("na2 not defined", procName, NULL);
+
+        /* Put the elements of the largest array into a set */
+    n1 = numaGetCount(na1);
+    n2 = numaGetCount(na2);
+    na_small = (n1 < n2) ? na1 : na2;   /* do not destroy na_small */
+    na_big = (n1 < n2) ? na2 : na1;   /* do not destroy na_big */
+    set1 = l_asetCreateFromNuma(na_big);
+
+        /* Build up the intersection of floats */
+    nad = numaCreate(0);
+    n = numaGetCount(na_small);
+    set2 = l_asetCreate(L_FLOAT_TYPE);
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na_small, i, &val);
+        key.ftype = val;
+        if (l_asetFind(set1, key) && !l_asetFind(set2, key)) {
+            numaAddNumber(nad, val);
+            l_asetInsert(set2, key);
+        }
+    }
+
+    l_asetDestroy(&set1);
+    l_asetDestroy(&set2);
+    return nad;
+}
+
+
+/*!
+ *  l_asetCreateFromNuma()
+ *
+ *      Input:  na
+ *      Return: set (using the floats in the numa as keys)
+ */
+L_ASET *
+l_asetCreateFromNuma(NUMA  *na)
+{
+l_int32  i, n, val;
+L_ASET  *set;
+RB_TYPE  key;
+
+    PROCNAME("l_asetCreateFromNuma");
+
+    if (!na)
+        return (L_ASET *)ERROR_PTR("na not defined", procName, NULL);
+
+    set = l_asetCreate(L_FLOAT_TYPE);
+    n = numaGetCount(na);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(na, i, &val);
+        key.ftype = val;
+        l_asetInsert(set, key);
+    }
+
+    return set;
+}
+
+
diff --git a/src/numafunc2.c b/src/numafunc2.c
new file mode 100644 (file)
index 0000000..18f4aa7
--- /dev/null
@@ -0,0 +1,3004 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   numafunc2.c
+ *
+ *      Morphological (min/max) operations
+ *          NUMA        *numaErode()
+ *          NUMA        *numaDilate()
+ *          NUMA        *numaOpen()
+ *          NUMA        *numaClose()
+ *
+ *      Other transforms
+ *          NUMA        *numaTransform()
+ *          l_int32      numaSimpleStats()
+ *          l_int32      numaWindowedStats()
+ *          NUMA        *numaWindowedMean()
+ *          NUMA        *numaWindowedMeanSquare()
+ *          l_int32      numaWindowedVariance()
+ *          NUMA        *numaWindowedMedian()
+ *          NUMA        *numaConvertToInt()
+ *
+ *      Histogram generation and statistics
+ *          NUMA        *numaMakeHistogram()
+ *          NUMA        *numaMakeHistogramAuto()
+ *          NUMA        *numaMakeHistogramClipped()
+ *          NUMA        *numaRebinHistogram()
+ *          NUMA        *numaNormalizeHistogram()
+ *          l_int32      numaGetStatsUsingHistogram()
+ *          l_int32      numaGetHistogramStats()
+ *          l_int32      numaGetHistogramStatsOnInterval()
+ *          l_int32      numaMakeRankFromHistogram()
+ *          l_int32      numaHistogramGetRankFromVal()
+ *          l_int32      numaHistogramGetValFromRank()
+ *          l_int32      numaDiscretizeRankAndIntensity()
+ *          l_int32      numaGetRankBinValues()
+ *
+ *      Splitting a distribution
+ *          l_int32      numaSplitDistribution()
+ *
+ *      Comparing histograms
+ *          l_int32      grayHistogramsToEMD()
+ *          l_int32      numaEarthMoverDistance()
+ *          l_int32      grayInterHistogramStats()
+ *
+ *      Extrema finding
+ *          NUMA        *numaFindPeaks()
+ *          NUMA        *numaFindExtrema()
+ *          l_int32     *numaCountReversals()
+ *
+ *      Threshold crossings and frequency analysis
+ *          l_int32      numaSelectCrossingThreshold()
+ *          NUMA        *numaCrossingsByThreshold()
+ *          NUMA        *numaCrossingsByPeaks()
+ *          NUMA        *numaEvalBestHaarParameters()
+ *          l_int32      numaEvalHaarSum()
+ *
+ *      Generating numbers in a range under constraints
+ *          NUMA        *genConstrainedNumaInRange()
+ *
+ *    Things to remember when using the Numa:
+ *
+ *    (1) The numa is a struct, not an array.  Always use accessors
+ *        (see numabasic.c), never the fields directly.
+ *
+ *    (2) The number array holds l_float32 values.  It can also
+ *        be used to store l_int32 values.  See numabasic.c for
+ *        details on using the accessors.  Integers larger than
+ *        about 10M will lose accuracy due on retrieval due to round-off.
+ *        For large integers, use the dna (array of l_float64) instead.
+ *
+ *    (3) Occasionally, in the comments we denote the i-th element of a
+ *        numa by na[i].  This is conceptual only -- the numa is not an array!
+ *
+ *    Some general comments on histograms:
+ *
+ *    (1) Histograms are the generic statistical representation of
+ *        the data about some attribute.  Typically they're not
+ *        normalized -- they simply give the number of occurrences
+ *        within each range of values of the attribute.  This range
+ *        of values is referred to as a 'bucket'.  For example,
+ *        the histogram could specify how many connected components
+ *        are found for each value of their width; in that case,
+ *        the bucket size is 1.
+ *
+ *    (2) In leptonica, all buckets have the same size.  Histograms
+ *        are therefore specified by a numa of occurrences, along
+ *        with two other numbers: the 'value' associated with the
+ *        occupants of the first bucket and the size (i.e., 'width')
+ *        of each bucket.  These two numbers then allow us to calculate
+ *        the value associated with the occupants of each bucket.
+ *        These numbers are fields in the numa, initialized to
+ *        a startx value of 0.0 and a binsize of 1.0.  Accessors for
+ *        these fields are functions numa*Parameters().  All histograms
+ *        must have these two numbers properly set.
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+    /* bin sizes in numaMakeHistogram() */
+static const l_int32 BinSizeArray[] = {2, 5, 10, 20, 50, 100, 200, 500, 1000,\
+                      2000, 5000, 10000, 20000, 50000, 100000, 200000,\
+                      500000, 1000000, 2000000, 5000000, 10000000,\
+                      200000000, 50000000, 100000000};
+static const l_int32 NBinSizes = 24;
+
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_HISTO        0
+#define  DEBUG_CROSSINGS    0
+#define  DEBUG_FREQUENCY    0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*----------------------------------------------------------------------*
+ *                     Morphological operations                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaErode()
+ *
+ *      Input:  nas
+ *              size (of sel; greater than 0, odd; origin implicitly in center)
+ *      Return: nad (eroded), or null on error
+ *
+ *  Notes:
+ *      (1) The structuring element (sel) is linear, all "hits"
+ *      (2) If size == 1, this returns a copy
+ *      (3) General comment.  The morphological operations are equivalent
+ *          to those that would be performed on a 1-dimensional fpix.
+ *          However, because we have not implemented morphological
+ *          operations on fpix, we do this here.  Because it is only
+ *          1 dimensional, there is no reason to use the more
+ *          complicated van Herk/Gil-Werman algorithm, and we do it
+ *          by brute force.
+ */
+NUMA *
+numaErode(NUMA    *nas,
+          l_int32  size)
+{
+l_int32     i, j, n, hsize, len;
+l_float32   minval;
+l_float32  *fa, *fas, *fad;
+NUMA       *nad;
+
+    PROCNAME("numaErode");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (size <= 0)
+        return (NUMA *)ERROR_PTR("size must be > 0", procName, NULL);
+    if ((size & 1) == 0 ) {
+        L_WARNING("sel size must be odd; increasing by 1\n", procName);
+        size++;
+    }
+
+    if (size == 1)
+        return numaCopy(nas);
+
+        /* Make a source fa (fas) that has an added (size / 2) boundary
+         * on left and right, contains a copy of nas in the interior region
+         * (between 'size' and 'size + n', and has large values
+         * inserted in the boundary (because it is an erosion). */
+    n = numaGetCount(nas);
+    hsize = size / 2;
+    len = n + 2 * hsize;
+    if ((fas = (l_float32 *)LEPT_CALLOC(len, sizeof(l_float32))) == NULL)
+        return (NUMA *)ERROR_PTR("fas not made", procName, NULL);
+    for (i = 0; i < hsize; i++)
+         fas[i] = 1.0e37;
+    for (i = hsize + n; i < len; i++)
+         fas[i] = 1.0e37;
+    fa = numaGetFArray(nas, L_NOCOPY);
+    for (i = 0; i < n; i++)
+         fas[hsize + i] = fa[i];
+
+    nad = numaMakeConstant(0, n);
+    numaCopyParameters(nad, nas);
+    fad = numaGetFArray(nad, L_NOCOPY);
+    for (i = 0; i < n; i++) {
+        minval = 1.0e37;  /* start big */
+        for (j = 0; j < size; j++)
+            minval = L_MIN(minval, fas[i + j]);
+        fad[i] = minval;
+    }
+
+    LEPT_FREE(fas);
+    return nad;
+}
+
+
+/*!
+ *  numaDilate()
+ *
+ *      Input:  nas
+ *              size (of sel; greater than 0, odd; origin implicitly in center)
+ *      Return: nad (dilated), or null on error
+ *
+ *  Notes:
+ *      (1) The structuring element (sel) is linear, all "hits"
+ *      (2) If size == 1, this returns a copy
+ */
+NUMA *
+numaDilate(NUMA    *nas,
+           l_int32  size)
+{
+l_int32     i, j, n, hsize, len;
+l_float32   maxval;
+l_float32  *fa, *fas, *fad;
+NUMA       *nad;
+
+    PROCNAME("numaDilate");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (size <= 0)
+        return (NUMA *)ERROR_PTR("size must be > 0", procName, NULL);
+    if ((size & 1) == 0 ) {
+        L_WARNING("sel size must be odd; increasing by 1\n", procName);
+        size++;
+    }
+
+    if (size == 1)
+        return numaCopy(nas);
+
+        /* Make a source fa (fas) that has an added (size / 2) boundary
+         * on left and right, contains a copy of nas in the interior region
+         * (between 'size' and 'size + n', and has small values
+         * inserted in the boundary (because it is a dilation). */
+    n = numaGetCount(nas);
+    hsize = size / 2;
+    len = n + 2 * hsize;
+    if ((fas = (l_float32 *)LEPT_CALLOC(len, sizeof(l_float32))) == NULL)
+        return (NUMA *)ERROR_PTR("fas not made", procName, NULL);
+    for (i = 0; i < hsize; i++)
+         fas[i] = -1.0e37;
+    for (i = hsize + n; i < len; i++)
+         fas[i] = -1.0e37;
+    fa = numaGetFArray(nas, L_NOCOPY);
+    for (i = 0; i < n; i++)
+         fas[hsize + i] = fa[i];
+
+    nad = numaMakeConstant(0, n);
+    numaCopyParameters(nad, nas);
+    fad = numaGetFArray(nad, L_NOCOPY);
+    for (i = 0; i < n; i++) {
+        maxval = -1.0e37;  /* start small */
+        for (j = 0; j < size; j++)
+            maxval = L_MAX(maxval, fas[i + j]);
+        fad[i] = maxval;
+    }
+
+    LEPT_FREE(fas);
+    return nad;
+}
+
+
+/*!
+ *  numaOpen()
+ *
+ *      Input:  nas
+ *              size (of sel; greater than 0, odd; origin implicitly in center)
+ *      Return: nad (opened), or null on error
+ *
+ *  Notes:
+ *      (1) The structuring element (sel) is linear, all "hits"
+ *      (2) If size == 1, this returns a copy
+ */
+NUMA *
+numaOpen(NUMA    *nas,
+         l_int32  size)
+{
+NUMA  *nat, *nad;
+
+    PROCNAME("numaOpen");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (size <= 0)
+        return (NUMA *)ERROR_PTR("size must be > 0", procName, NULL);
+    if ((size & 1) == 0 ) {
+        L_WARNING("sel size must be odd; increasing by 1\n", procName);
+        size++;
+    }
+
+    if (size == 1)
+        return numaCopy(nas);
+
+    nat = numaErode(nas, size);
+    nad = numaDilate(nat, size);
+    numaDestroy(&nat);
+    return nad;
+}
+
+
+/*!
+ *  numaClose()
+ *
+ *      Input:  nas
+ *              size (of sel; greater than 0, odd; origin implicitly in center)
+ *      Return: nad (opened), or null on error
+ *
+ *  Notes:
+ *      (1) The structuring element (sel) is linear, all "hits"
+ *      (2) If size == 1, this returns a copy
+ *      (3) We add a border before doing this operation, for the same
+ *          reason that we add a border to a pix before doing a safe closing.
+ *          Without the border, a small component near the border gets
+ *          clipped at the border on dilation, and can be entirely removed
+ *          by the following erosion, violating the basic extensivity
+ *          property of closing.
+ */
+NUMA *
+numaClose(NUMA    *nas,
+          l_int32  size)
+{
+NUMA  *nab, *nat1, *nat2, *nad;
+
+    PROCNAME("numaClose");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (size <= 0)
+        return (NUMA *)ERROR_PTR("size must be > 0", procName, NULL);
+    if ((size & 1) == 0 ) {
+        L_WARNING("sel size must be odd; increasing by 1\n", procName);
+        size++;
+    }
+
+    if (size == 1)
+        return numaCopy(nas);
+
+    nab = numaAddBorder(nas, size, size, 0);  /* to preserve extensivity */
+    nat1 = numaDilate(nab, size);
+    nat2 = numaErode(nat1, size);
+    nad = numaRemoveBorder(nat2, size, size);
+    numaDestroy(&nab);
+    numaDestroy(&nat1);
+    numaDestroy(&nat2);
+    return nad;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                            Other transforms                          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaTransform()
+ *
+ *      Input:  nas
+ *              shift (add this to each number)
+ *              scale (multiply each number by this)
+ *      Return: nad (with all values shifted and scaled, or null on error)
+ *
+ *  Notes:
+ *      (1) Each number is shifted before scaling.
+ *      (2) The operation sequence is opposite to that for Box and Pta:
+ *          scale first, then shift.
+ */
+NUMA *
+numaTransform(NUMA      *nas,
+              l_float32  shift,
+              l_float32  scale)
+{
+l_int32    i, n;
+l_float32  val;
+NUMA      *nad;
+
+    PROCNAME("numaTransform");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    n = numaGetCount(nas);
+    if ((nad = numaCreate(n)) == NULL)
+        return (NUMA *)ERROR_PTR("nad not made", procName, NULL);
+    numaCopyParameters(nad, nas);
+    for (i = 0; i < n; i++) {
+        numaGetFValue(nas, i, &val);
+        val = scale * val + shift;
+        numaAddNumber(nad, val);
+    }
+    return nad;
+}
+
+
+/*!
+ *  numaSimpleStats()
+ *
+ *      Input:  na (input numa)
+ *              first (first element to use)
+ *              last (last element to use; 0 to go to the end)
+ *              &mean (<optional return> mean value)
+ *              &var (<optional return> variance)
+ *              &rvar (<optional return> rms deviation from the mean)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaSimpleStats(NUMA       *na,
+                l_int32     first,
+                l_int32     last,
+                l_float32  *pmean,
+                l_float32  *pvar,
+                l_float32  *prvar)
+{
+l_int32    i, n, ni;
+l_float32  sum, sumsq, val, mean, var;
+
+    PROCNAME("numaSimpleStats");
+
+    if (pmean) *pmean = 0.0;
+    if (pvar) *pvar = 0.0;
+    if (prvar) *prvar = 0.0;
+    if (!pmean && !pvar && !prvar)
+        return ERROR_INT("nothing requested", procName, 1);
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if ((n = numaGetCount(na)) == 0)
+        return ERROR_INT("na is empty", procName, 1);
+    if (last == 0) last = n - 1;
+    last = L_MIN(last, n - 1);
+    if (first > last) {
+        L_ERROR("invalid: first(%d) > last(%d)\n", procName, first, last);
+        return 1;
+    }
+    ni = last - first + 1;
+    sum = sumsq = 0.0;
+    for (i = first; i <= last; i++) {
+        numaGetFValue(na, i, &val);
+        sum += val;
+        sumsq += val * val;
+    }
+
+    mean = sum / ni;
+    if (pmean)
+        *pmean = mean;
+    if (pvar || prvar) {
+        var = sumsq / ni - mean * mean;
+        if (pvar) *pvar = var;
+        if (prvar) *prvar = sqrtf(var);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  numaWindowedStats()
+ *
+ *      Input:  nas (input numa)
+ *              wc (half width of the window)
+ *              &nam (<optional return> mean value in window)
+ *              &nams (<optional return> mean square value in window)
+ *              &pnav (<optional return> variance in window)
+ *              &pnarv (<optional return> rms deviation from the mean)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a high-level convenience function for calculating
+ *          any or all of these derived arrays.
+ *      (2) These statistical measures over the values in the
+ *          rectangular window are:
+ *            - average value: <x>  (nam)
+ *            - average squared value: <x*x> (nams)
+ *            - variance: <(x - <x>)*(x - <x>)> = <x*x> - <x>*<x>  (nav)
+ *            - square-root of variance: (narv)
+ *          where the brackets < .. > indicate that the average value is
+ *          to be taken over the window.
+ *      (3) Note that the variance is just the mean square difference from
+ *          the mean value; and the square root of the variance is the
+ *          root mean square difference from the mean, sometimes also
+ *          called the 'standard deviation'.
+ *      (4) Internally, use mirrored borders to handle values near the
+ *          end of each array.
+ */
+l_int32
+numaWindowedStats(NUMA    *nas,
+                  l_int32  wc,
+                  NUMA   **pnam,
+                  NUMA   **pnams,
+                  NUMA   **pnav,
+                  NUMA   **pnarv)
+{
+NUMA  *nam, *nams;
+
+    PROCNAME("numaWindowedStats");
+
+    if (!nas)
+        return ERROR_INT("nas not defined", procName, 1);
+    if (2 * wc + 1 > numaGetCount(nas))
+        L_WARNING("filter wider than input array!\n", procName);
+
+    if (!pnav && !pnarv) {
+        if (pnam) *pnam = numaWindowedMean(nas, wc);
+        if (pnams) *pnams = numaWindowedMeanSquare(nas, wc);
+        return 0;
+    }
+
+    nam = numaWindowedMean(nas, wc);
+    nams = numaWindowedMeanSquare(nas, wc);
+    numaWindowedVariance(nam, nams, pnav, pnarv);
+    if (pnam)
+        *pnam = nam;
+    else
+        numaDestroy(&nam);
+    if (pnams)
+        *pnams = nams;
+    else
+        numaDestroy(&nams);
+    return 0;
+}
+
+
+/*!
+ *  numaWindowedMean()
+ *
+ *      Input:  nas
+ *              wc (half width of the convolution window)
+ *      Return: nad (after low-pass filtering), or null on error
+ *
+ *  Notes:
+ *      (1) This is a convolution.  The window has width = 2 * @wc + 1.
+ *      (2) We add a mirrored border of size @wc to each end of the array.
+ */
+NUMA *
+numaWindowedMean(NUMA    *nas,
+                 l_int32  wc)
+{
+l_int32     i, n, n1, width;
+l_float32   sum, norm;
+l_float32  *fa1, *fad, *suma;
+NUMA       *na1, *nad;
+
+    PROCNAME("numaWindowedMean");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    n = numaGetCount(nas);
+    width = 2 * wc + 1;  /* filter width */
+    if (width > n)
+        L_WARNING("filter wider than input array!\n", procName);
+
+    na1 = numaAddSpecifiedBorder(nas, wc, wc, L_MIRRORED_BORDER);
+    n1 = n + 2 * wc;
+    fa1 = numaGetFArray(na1, L_NOCOPY);
+    nad = numaMakeConstant(0, n);
+    fad = numaGetFArray(nad, L_NOCOPY);
+
+        /* Make sum array; note the indexing */
+    if ((suma = (l_float32 *)LEPT_CALLOC(n1 + 1, sizeof(l_float32))) == NULL)
+        return (NUMA *)ERROR_PTR("suma not made", procName, NULL);
+    sum = 0.0;
+    suma[0] = 0.0;
+    for (i = 0; i < n1; i++) {
+        sum += fa1[i];
+        suma[i + 1] = sum;
+    }
+
+    norm = 1. / (2 * wc + 1);
+    for (i = 0; i < n; i++)
+        fad[i] = norm * (suma[width + i] - suma[i]);
+
+    LEPT_FREE(suma);
+    numaDestroy(&na1);
+    return nad;
+}
+
+
+/*!
+ *  numaWindowedMeanSquare()
+ *
+ *      Input:  nas
+ *              wc (half width of the window)
+ *      Return: nad (containing windowed mean square values), or null on error
+ *
+ *  Notes:
+ *      (1) The window has width = 2 * @wc + 1.
+ *      (2) We add a mirrored border of size @wc to each end of the array.
+ */
+NUMA *
+numaWindowedMeanSquare(NUMA    *nas,
+                       l_int32  wc)
+{
+l_int32     i, n, n1, width;
+l_float32   sum, norm;
+l_float32  *fa1, *fad, *suma;
+NUMA       *na1, *nad;
+
+    PROCNAME("numaWindowedMeanSquare");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    n = numaGetCount(nas);
+    width = 2 * wc + 1;  /* filter width */
+    if (width > n)
+        L_WARNING("filter wider than input array!\n", procName);
+
+    na1 = numaAddSpecifiedBorder(nas, wc, wc, L_MIRRORED_BORDER);
+    n1 = n + 2 * wc;
+    fa1 = numaGetFArray(na1, L_NOCOPY);
+    nad = numaMakeConstant(0, n);
+    fad = numaGetFArray(nad, L_NOCOPY);
+
+        /* Make sum array; note the indexing */
+    if ((suma = (l_float32 *)LEPT_CALLOC(n1 + 1, sizeof(l_float32))) == NULL)
+        return (NUMA *)ERROR_PTR("suma not made", procName, NULL);
+    sum = 0.0;
+    suma[0] = 0.0;
+    for (i = 0; i < n1; i++) {
+        sum += fa1[i] * fa1[i];
+        suma[i + 1] = sum;
+    }
+
+    norm = 1. / (2 * wc + 1);
+    for (i = 0; i < n; i++)
+        fad[i] = norm * (suma[width + i] - suma[i]);
+
+    LEPT_FREE(suma);
+    numaDestroy(&na1);
+    return nad;
+}
+
+
+/*!
+ *  numaWindowedVariance()
+ *
+ *      Input:  nam (windowed mean values)
+ *              nams (windowed mean square values)
+ *              &pnav (<optional return> numa of variance -- the ms deviation
+ *                     from the mean)
+ *              &pnarv (<optional return> numa of rms deviation from the mean)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The numas of windowed mean and mean square are precomputed,
+ *          using numaWindowedMean() and numaWindowedMeanSquare().
+ *      (2) Either or both of the variance and square-root of variance
+ *          are returned, where the variance is the average over the
+ *          window of the mean square difference of the pixel value
+ *          from the mean:
+ *                <(x - <x>)*(x - <x>)> = <x*x> - <x>*<x>
+ */
+l_int32
+numaWindowedVariance(NUMA   *nam,
+                     NUMA   *nams,
+                     NUMA  **pnav,
+                     NUMA  **pnarv)
+{
+l_int32     i, nm, nms;
+l_float32   var;
+l_float32  *fam, *fams, *fav, *farv;
+NUMA       *nav, *narv;  /* variance and square root of variance */
+
+    PROCNAME("numaWindowedVariance");
+
+    if (pnav) *pnav = NULL;
+    if (pnarv) *pnarv = NULL;
+    if (!pnav && !pnarv)
+        return ERROR_INT("neither &nav nor &narv are defined", procName, 1);
+    if (!nam)
+        return ERROR_INT("nam not defined", procName, 1);
+    if (!nams)
+        return ERROR_INT("nams not defined", procName, 1);
+    nm = numaGetCount(nam);
+    nms = numaGetCount(nams);
+    if (nm != nms)
+        return ERROR_INT("sizes of nam and nams differ", procName, 1);
+
+    if (pnav) {
+        nav = numaMakeConstant(0, nm);
+        *pnav = nav;
+        fav = numaGetFArray(nav, L_NOCOPY);
+    }
+    if (pnarv) {
+        narv = numaMakeConstant(0, nm);
+        *pnarv = narv;
+        farv = numaGetFArray(narv, L_NOCOPY);
+    }
+    fam = numaGetFArray(nam, L_NOCOPY);
+    fams = numaGetFArray(nams, L_NOCOPY);
+
+    for (i = 0; i < nm; i++) {
+        var = fams[i] - fam[i] * fam[i];
+        if (pnav)
+            fav[i] = var;
+        if (pnarv)
+            farv[i] = sqrtf(var);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  numaWindowedMedian()
+ *
+ *      Input:  nas
+ *              halfwin (half width of window over which the median is found)
+ *      Return: nad (after windowed median filtering), or null on error
+ *
+ *  Notes:
+ *      (1) The requested window has width = 2 * @halfwin + 1.
+ *      (2) If the input nas has less then 3 elements, return a copy.
+ *      (3) If the filter is too small (@halfwin <= 0), return a copy.
+ *      (4) If the filter is too large, it is reduced in size.
+ *      (5) We add a mirrored border of size @halfwin to each end of
+ *          the array to simplify the calculation by avoiding end-effects.
+ */
+NUMA *
+numaWindowedMedian(NUMA    *nas,
+                   l_int32  halfwin)
+{
+l_int32    i, n;
+l_float32  medval;
+NUMA      *na1, *na2, *nad;
+
+    PROCNAME("numaWindowedMedian");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if ((n = numaGetCount(nas)) < 3)
+        return numaCopy(nas);
+    if (halfwin <= 0) {
+        L_ERROR("filter too small; returning a copy\n", procName);
+        return numaCopy(nas);
+    }
+
+    if (halfwin > (n - 1) / 2) {
+        halfwin = (n - 1) / 2;
+        L_INFO("reducing filter to halfwin = %d\n", procName, halfwin);
+    }
+
+        /* Add a border to both ends */
+    na1 = numaAddSpecifiedBorder(nas, halfwin, halfwin, L_MIRRORED_BORDER);
+
+        /* Get the median value at the center of each window, corresponding
+         * to locations in the input nas. */
+    nad = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        na2 = numaClipToInterval(na1, i, i + 2 * halfwin);
+        numaGetMedian(na2, &medval);
+        numaAddNumber(nad, medval);
+        numaDestroy(&na2);
+    }
+
+    numaDestroy(&na1);
+    return nad;
+}
+
+
+/*!
+ *  numaConvertToInt()
+ *
+ *      Input:  na
+ *      Return: na with all values rounded to nearest integer, or
+ *              null on error
+ */
+NUMA *
+numaConvertToInt(NUMA  *nas)
+{
+l_int32  i, n, ival;
+NUMA    *nad;
+
+    PROCNAME("numaConvertToInt");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+
+    n = numaGetCount(nas);
+    if ((nad = numaCreate(n)) == NULL)
+        return (NUMA *)ERROR_PTR("nad not made", procName, NULL);
+    numaCopyParameters(nad, nas);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(nas, i, &ival);
+        numaAddNumber(nad, ival);
+    }
+    return nad;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                 Histogram generation and statistics                  *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaMakeHistogram()
+ *
+ *      Input:  na
+ *              maxbins (max number of histogram bins)
+ *              &binsize  (<return> size of histogram bins)
+ *              &binstart (<optional return> start val of minimum bin;
+ *                         input NULL to force start at 0)
+ *      Return: na consisiting of histogram of integerized values,
+ *              or null on error.
+ *
+ *  Note:
+ *      (1) This simple interface is designed for integer data.
+ *          The bins are of integer width and start on integer boundaries,
+ *          so the results on float data will not have high precision.
+ *      (2) Specify the max number of input bins.   Then @binsize,
+ *          the size of bins necessary to accommodate the input data,
+ *          is returned.  It is one of the sequence:
+ *                {1, 2, 5, 10, 20, 50, ...}.
+ *      (3) If &binstart is given, all values are accommodated,
+ *          and the min value of the starting bin is returned.
+ *          Otherwise, all negative values are discarded and
+ *          the histogram bins start at 0.
+ */
+NUMA *
+numaMakeHistogram(NUMA     *na,
+                  l_int32   maxbins,
+                  l_int32  *pbinsize,
+                  l_int32  *pbinstart)
+{
+l_int32    i, n, ival, hval;
+l_int32    iminval, imaxval, range, binsize, nbins, ibin;
+l_float32  val, ratio;
+NUMA      *nai, *nahist;
+
+    PROCNAME("numaMakeHistogram");
+
+    if (!na)
+        return (NUMA *)ERROR_PTR("na not defined", procName, NULL);
+    if (!pbinsize)
+        return (NUMA *)ERROR_PTR("&binsize not defined", procName, NULL);
+
+        /* Determine input range */
+    numaGetMin(na, &val, NULL);
+    iminval = (l_int32)(val + 0.5);
+    numaGetMax(na, &val, NULL);
+    imaxval = (l_int32)(val + 0.5);
+    if (pbinstart == NULL) {  /* clip negative vals; start from 0 */
+        iminval = 0;
+        if (imaxval < 0)
+            return (NUMA *)ERROR_PTR("all values < 0", procName, NULL);
+    }
+
+        /* Determine binsize */
+    range = imaxval - iminval + 1;
+    if (range > maxbins - 1) {
+        ratio = (l_float64)range / (l_float64)maxbins;
+        binsize = 0;
+        for (i = 0; i < NBinSizes; i++) {
+            if (ratio < BinSizeArray[i]) {
+                binsize = BinSizeArray[i];
+                break;
+            }
+        }
+        if (binsize == 0)
+            return (NUMA *)ERROR_PTR("numbers too large", procName, NULL);
+    } else {
+        binsize = 1;
+    }
+    *pbinsize = binsize;
+    nbins = 1 + range / binsize;  /* +1 seems to be sufficient */
+
+        /* Redetermine iminval */
+    if (pbinstart && binsize > 1) {
+        if (iminval >= 0)
+            iminval = binsize * (iminval / binsize);
+        else
+            iminval = binsize * ((iminval - binsize + 1) / binsize);
+    }
+    if (pbinstart)
+        *pbinstart = iminval;
+
+#if  DEBUG_HISTO
+    fprintf(stderr, " imaxval = %d, range = %d, nbins = %d\n",
+            imaxval, range, nbins);
+#endif  /* DEBUG_HISTO */
+
+        /* Use integerized data for input */
+    if ((nai = numaConvertToInt(na)) == NULL)
+        return (NUMA *)ERROR_PTR("nai not made", procName, NULL);
+    n = numaGetCount(nai);
+
+        /* Make histogram, converting value in input array
+         * into a bin number for this histogram array. */
+    if ((nahist = numaCreate(nbins)) == NULL)
+        return (NUMA *)ERROR_PTR("nahist not made", procName, NULL);
+    numaSetCount(nahist, nbins);
+    numaSetParameters(nahist, iminval, binsize);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(nai, i, &ival);
+        ibin = (ival - iminval) / binsize;
+        if (ibin >= 0 && ibin < nbins) {
+            numaGetIValue(nahist, ibin, &hval);
+            numaSetValue(nahist, ibin, hval + 1.0);
+        }
+    }
+
+    numaDestroy(&nai);
+    return nahist;
+}
+
+
+/*!
+ *  numaMakeHistogramAuto()
+ *
+ *      Input:  na (numa of floats; these may be integers)
+ *              maxbins (max number of histogram bins; >= 1)
+ *      Return: na consisiting of histogram of quantized float values,
+ *              or null on error.
+ *
+ *  Notes:
+ *      (1) This simple interface is designed for accurate binning
+ *          of both integer and float data.
+ *      (2) If the array data is integers, and the range of integers
+ *          is smaller than @maxbins, they are binned as they fall,
+ *          with binsize = 1.
+ *      (3) If the range of data, (maxval - minval), is larger than
+ *          @maxbins, or if the data is floats, they are binned into
+ *          exactly @maxbins bins.
+ *      (4) Unlike numaMakeHistogram(), these bins in general have
+ *          non-integer location and width, even for integer data.
+ */
+NUMA *
+numaMakeHistogramAuto(NUMA    *na,
+                      l_int32  maxbins)
+{
+l_int32    i, n, imin, imax, irange, ibin, ival, allints;
+l_float32  minval, maxval, range, binsize, fval;
+NUMA      *nah;
+
+    PROCNAME("numaMakeHistogramAuto");
+
+    if (!na)
+        return (NUMA *)ERROR_PTR("na not defined", procName, NULL);
+    maxbins = L_MAX(1, maxbins);
+
+        /* Determine input range */
+    numaGetMin(na, &minval, NULL);
+    numaGetMax(na, &maxval, NULL);
+
+        /* Determine if values are all integers */
+    n = numaGetCount(na);
+    numaHasOnlyIntegers(na, maxbins, &allints);
+
+        /* Do simple integer binning if possible */
+    if (allints && (maxval - minval < maxbins)) {
+        imin = (l_int32)minval;
+        imax = (l_int32)maxval;
+        irange = imax - imin + 1;
+        nah = numaCreate(irange);
+        numaSetCount(nah, irange);  /* init */
+        numaSetParameters(nah, minval, 1.0);
+        for (i = 0; i < n; i++) {
+            numaGetIValue(na, i, &ival);
+            ibin = ival - imin;
+            numaGetIValue(nah, ibin, &ival);
+            numaSetValue(nah, ibin, ival + 1.0);
+        }
+
+        return nah;
+    }
+
+        /* Do float binning, even if the data is integers. */
+    range = maxval - minval;
+    binsize = range / (l_float32)maxbins;
+    if (range == 0.0) {
+        nah = numaCreate(1);
+        numaSetParameters(nah, minval, binsize);
+        numaAddNumber(nah, n);
+        return nah;
+    }
+    nah = numaCreate(maxbins);
+    numaSetCount(nah, maxbins);
+    numaSetParameters(nah, minval, binsize);
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &fval);
+        ibin = (l_int32)((fval - minval) / binsize);
+        ibin = L_MIN(ibin, maxbins - 1);  /* "edge" case; stay in bounds */
+        numaGetIValue(nah, ibin, &ival);
+        numaSetValue(nah, ibin, ival + 1.0);
+    }
+
+    return nah;
+}
+
+
+/*!
+ *  numaMakeHistogramClipped()
+ *
+ *      Input:  na
+ *              binsize (typically 1.0)
+ *              maxsize (of histogram ordinate)
+ *      Return: na (histogram of bins of size @binsize, starting with
+ *                  the na[0] (x = 0.0) and going up to a maximum of
+ *                  x = @maxsize, by increments of @binsize), or null on error
+ *
+ *  Notes:
+ *      (1) This simple function generates a histogram of values
+ *          from na, discarding all values < 0.0 or greater than
+ *          min(@maxsize, maxval), where maxval is the maximum value in na.
+ *          The histogram data is put in bins of size delx = @binsize,
+ *          starting at x = 0.0.  We use as many bins as are
+ *          needed to hold the data.
+ */
+NUMA *
+numaMakeHistogramClipped(NUMA      *na,
+                         l_float32  binsize,
+                         l_float32  maxsize)
+{
+l_int32    i, n, nbins, ival, ibin;
+l_float32  val, maxval;
+NUMA      *nad;
+
+    PROCNAME("numaMakeHistogramClipped");
+
+    if (!na)
+        return (NUMA *)ERROR_PTR("na not defined", procName, NULL);
+    if (binsize <= 0.0)
+        return (NUMA *)ERROR_PTR("binsize must be > 0.0", procName, NULL);
+    if (binsize > maxsize)
+        binsize = maxsize;  /* just one bin */
+
+    numaGetMax(na, &maxval, NULL);
+    n = numaGetCount(na);
+    maxsize = L_MIN(maxsize, maxval);
+    nbins = (l_int32)(maxsize / binsize) + 1;
+
+/*    fprintf(stderr, "maxsize = %7.3f, nbins = %d\n", maxsize, nbins); */
+
+    if ((nad = numaCreate(nbins)) == NULL)
+        return (NUMA *)ERROR_PTR("nad not made", procName, NULL);
+    numaSetParameters(nad, 0.0, binsize);
+    numaSetCount(nad, nbins);  /* interpret zeroes in bins as data */
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        ibin = (l_int32)(val / binsize);
+        if (ibin >= 0 && ibin < nbins) {
+            numaGetIValue(nad, ibin, &ival);
+            numaSetValue(nad, ibin, ival + 1.0);
+        }
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaRebinHistogram()
+ *
+ *      Input:  nas (input histogram)
+ *              newsize (number of old bins contained in each new bin)
+ *      Return: nad (more coarsely re-binned histogram), or null on error
+ */
+NUMA *
+numaRebinHistogram(NUMA    *nas,
+                   l_int32  newsize)
+{
+l_int32    i, j, ns, nd, index, count, val;
+l_float32  start, oldsize;
+NUMA      *nad;
+
+    PROCNAME("numaRebinHistogram");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (newsize <= 1)
+        return (NUMA *)ERROR_PTR("newsize must be > 1", procName, NULL);
+    if ((ns = numaGetCount(nas)) == 0)
+        return (NUMA *)ERROR_PTR("no bins in nas", procName, NULL);
+
+    nd = (ns + newsize - 1) / newsize;
+    if ((nad = numaCreate(nd)) == NULL)
+        return (NUMA *)ERROR_PTR("nad not made", procName, NULL);
+    numaGetParameters(nad, &start, &oldsize);
+    numaSetParameters(nad, start, oldsize * newsize);
+
+    for (i = 0; i < nd; i++) {  /* new bins */
+        count = 0;
+        index = i * newsize;
+        for (j = 0; j < newsize; j++) {
+            if (index < ns) {
+                numaGetIValue(nas, index, &val);
+                count += val;
+                index++;
+            }
+        }
+        numaAddNumber(nad, count);
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaNormalizeHistogram()
+ *
+ *      Input:  nas (input histogram)
+ *              tsum (target sum of all numbers in dest histogram;
+ *                    e.g., use @tsum= 1.0 if this represents a
+ *                    probability distribution)
+ *      Return: nad (normalized histogram), or null on error
+ */
+NUMA *
+numaNormalizeHistogram(NUMA      *nas,
+                       l_float32  tsum)
+{
+l_int32    i, ns;
+l_float32  sum, factor, fval;
+NUMA      *nad;
+
+    PROCNAME("numaNormalizeHistogram");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (tsum <= 0.0)
+        return (NUMA *)ERROR_PTR("tsum must be > 0.0", procName, NULL);
+    if ((ns = numaGetCount(nas)) == 0)
+        return (NUMA *)ERROR_PTR("no bins in nas", procName, NULL);
+
+    numaGetSum(nas, &sum);
+    factor = tsum / sum;
+
+    if ((nad = numaCreate(ns)) == NULL)
+        return (NUMA *)ERROR_PTR("nad not made", procName, NULL);
+    numaCopyParameters(nad, nas);
+
+    for (i = 0; i < ns; i++) {
+        numaGetFValue(nas, i, &fval);
+        fval *= factor;
+        numaAddNumber(nad, fval);
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaGetStatsUsingHistogram()
+ *
+ *      Input:  na (an arbitrary set of numbers; not ordered and not
+ *                  a histogram)
+ *              maxbins (the maximum number of bins to be allowed in
+ *                       the histogram; use an integer larger than the
+ *                       largest number in @na for consecutive integer bins)
+ *              &min (<optional return> min value of set)
+ *              &max (<optional return> max value of set)
+ *              &mean (<optional return> mean value of set)
+ *              &variance (<optional return> variance)
+ *              &median (<optional return> median value of set)
+ *              rank (in [0.0 ... 1.0]; median has a rank 0.5; ignored
+ *                    if &rval == NULL)
+ *              &rval (<optional return> value in na corresponding to @rank)
+ *              &histo (<optional return> Numa histogram; use NULL to prevent)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a simple interface for gathering statistics
+ *          from a numa, where a histogram is used 'under the covers'
+ *          to avoid sorting if a rank value is requested.  In that case,
+ *          by using a histogram we are trading speed for accuracy, because
+ *          the values in @na are quantized to the center of a set of bins.
+ *      (2) If the median, other rank value, or histogram are not requested,
+ *          the calculation is all performed on the input Numa.
+ *      (3) The variance is the average of the square of the
+ *          difference from the mean.  The median is the value in na
+ *          with rank 0.5.
+ *      (4) There are two situations where this gives rank results with
+ *          accuracy comparable to computing stastics directly on the input
+ *          data, without binning into a histogram:
+ *           (a) the data is integers and the range of data is less than
+ *               @maxbins, and
+ *           (b) the data is floats and the range is small compared to
+ *               @maxbins, so that the binsize is much less than 1.
+ *      (5) If a histogram is used and the numbers in the Numa extend
+ *          over a large range, you can limit the required storage by
+ *          specifying the maximum number of bins in the histogram.
+ *          Use @maxbins == 0 to force the bin size to be 1.
+ *      (6) This optionally returns the median and one arbitrary rank value.
+ *          If you need several rank values, return the histogram and use
+ *               numaHistogramGetValFromRank(nah, rank, &rval)
+ *          multiple times.
+ */
+l_int32
+numaGetStatsUsingHistogram(NUMA       *na,
+                           l_int32     maxbins,
+                           l_float32  *pmin,
+                           l_float32  *pmax,
+                           l_float32  *pmean,
+                           l_float32  *pvariance,
+                           l_float32  *pmedian,
+                           l_float32   rank,
+                           l_float32  *prval,
+                           NUMA      **phisto)
+{
+l_int32    i, n;
+l_float32  minval, maxval, fval, mean, sum;
+NUMA      *nah;
+
+    PROCNAME("numaGetStatsUsingHistogram");
+
+    if (pmin) *pmin = 0.0;
+    if (pmax) *pmax = 0.0;
+    if (pmean) *pmean = 0.0;
+    if (pvariance) *pvariance = 0.0;
+    if (pmedian) *pmedian = 0.0;
+    if (prval) *prval = 0.0;
+    if (phisto) *phisto = NULL;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if ((n = numaGetCount(na)) == 0)
+        return ERROR_INT("numa is empty", procName, 1);
+
+    numaGetMin(na, &minval, NULL);
+    numaGetMax(na, &maxval, NULL);
+    if (pmin) *pmin = minval;
+    if (pmax) *pmax = maxval;
+    if (pmean || pvariance) {
+        sum = 0.0;
+        for (i = 0; i < n; i++) {
+            numaGetFValue(na, i, &fval);
+            sum += fval;
+        }
+        mean = sum / (l_float32)n;
+        if (pmean) *pmean = mean;
+    }
+    if (pvariance) {
+        sum = 0.0;
+        for (i = 0; i < n; i++) {
+            numaGetFValue(na, i, &fval);
+            sum += fval * fval;
+        }
+        *pvariance = sum / (l_float32)n - mean * mean;
+    }
+
+    if (!pmedian && !prval && !phisto)
+        return 0;
+
+    nah = numaMakeHistogramAuto(na, maxbins);
+    if (pmedian)
+        numaHistogramGetValFromRank(nah, 0.5, pmedian);
+    if (prval)
+        numaHistogramGetValFromRank(nah, rank, prval);
+    if (phisto)
+        *phisto = nah;
+    else
+        numaDestroy(&nah);
+    return 0;
+}
+
+
+/*!
+ *  numaGetHistogramStats()
+ *
+ *      Input:  nahisto (histogram: y(x(i)), i = 0 ... nbins - 1)
+ *              startx (x value of first bin: x(0))
+ *              deltax (x increment between bins; the bin size; x(1) - x(0))
+ *              &xmean (<optional return> mean value of histogram)
+ *              &xmedian (<optional return> median value of histogram)
+ *              &xmode (<optional return> mode value of histogram:
+ *                     xmode = x(imode), where y(xmode) >= y(x(i)) for
+ *                     all i != imode)
+ *              &xvariance (<optional return> variance of x)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If the histogram represents the relation y(x), the
+ *          computed values that are returned are the x values.
+ *          These are NOT the bucket indices i; they are related to the
+ *          bucket indices by
+ *                x(i) = startx + i * deltax
+ */
+l_int32
+numaGetHistogramStats(NUMA       *nahisto,
+                      l_float32   startx,
+                      l_float32   deltax,
+                      l_float32  *pxmean,
+                      l_float32  *pxmedian,
+                      l_float32  *pxmode,
+                      l_float32  *pxvariance)
+{
+    PROCNAME("numaGetHistogramStats");
+
+    if (pxmean) *pxmean = 0.0;
+    if (pxmedian) *pxmedian = 0.0;
+    if (pxmode) *pxmode = 0.0;
+    if (pxvariance) *pxvariance = 0.0;
+    if (!nahisto)
+        return ERROR_INT("nahisto not defined", procName, 1);
+
+    return numaGetHistogramStatsOnInterval(nahisto, startx, deltax, 0, 0,
+                                           pxmean, pxmedian, pxmode,
+                                           pxvariance);
+}
+
+
+/*!
+ *  numaGetHistogramStatsOnInterval()
+ *
+ *      Input:  nahisto (histogram: y(x(i)), i = 0 ... nbins - 1)
+ *              startx (x value of first bin: x(0))
+ *              deltax (x increment between bins; the bin size; x(1) - x(0))
+ *              ifirst (first bin to use for collecting stats)
+ *              ilast (last bin for collecting stats; use 0 to go to the end)
+ *              &xmean (<optional return> mean value of histogram)
+ *              &xmedian (<optional return> median value of histogram)
+ *              &xmode (<optional return> mode value of histogram:
+ *                     xmode = x(imode), where y(xmode) >= y(x(i)) for
+ *                     all i != imode)
+ *              &xvariance (<optional return> variance of x)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If the histogram represents the relation y(x), the
+ *          computed values that are returned are the x values.
+ *          These are NOT the bucket indices i; they are related to the
+ *          bucket indices by
+ *                x(i) = startx + i * deltax
+ */
+l_int32
+numaGetHistogramStatsOnInterval(NUMA       *nahisto,
+                                l_float32   startx,
+                                l_float32   deltax,
+                                l_int32     ifirst,
+                                l_int32     ilast,
+                                l_float32  *pxmean,
+                                l_float32  *pxmedian,
+                                l_float32  *pxmode,
+                                l_float32  *pxvariance)
+{
+l_int32    i, n, imax;
+l_float32  sum, sumval, halfsum, moment, var, x, y, ymax;
+
+    PROCNAME("numaGetHistogramStatsOnInterval");
+
+    if (pxmean) *pxmean = 0.0;
+    if (pxmedian) *pxmedian = 0.0;
+    if (pxmode) *pxmode = 0.0;
+    if (pxvariance) *pxvariance = 0.0;
+    if (!nahisto)
+        return ERROR_INT("nahisto not defined", procName, 1);
+    if (!pxmean && !pxmedian && !pxmode && !pxvariance)
+        return ERROR_INT("nothing to compute", procName, 1);
+
+    n = numaGetCount(nahisto);
+    if (ilast <= 0) ilast = n - 1;
+    if (ifirst < 0) ifirst = 0;
+    if (ifirst > ilast || ifirst > n - 1)
+        return ERROR_INT("ifirst is too large", procName, 1);
+    for (sum = 0.0, moment = 0.0, var = 0.0, i = ifirst; i <= ilast ; i++) {
+        x = startx + i * deltax;
+        numaGetFValue(nahisto, i, &y);
+        sum += y;
+        moment += x * y;
+        var += x * x * y;
+    }
+    if (sum == 0.0) {
+        L_INFO("sum is 0\n", procName);
+        return 0;
+    }
+
+    if (pxmean)
+        *pxmean = moment / sum;
+    if (pxvariance)
+        *pxvariance = var / sum - moment * moment / (sum * sum);
+
+    if (pxmedian) {
+        halfsum = sum / 2.0;
+        for (sumval = 0.0, i = ifirst; i <= ilast; i++) {
+            numaGetFValue(nahisto, i, &y);
+            sumval += y;
+            if (sumval >= halfsum) {
+                *pxmedian = startx + i * deltax;
+                break;
+            }
+        }
+    }
+
+    if (pxmode) {
+        ymax = -1.0e10;
+        for (i = ifirst; i <= ilast; i++) {
+            numaGetFValue(nahisto, i, &y);
+            if (y > ymax) {
+                ymax = y;
+                imax = i;
+            }
+        }
+        *pxmode = startx + imax * deltax;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  numaMakeRankFromHistogram()
+ *
+ *      Input:  startx (xval corresponding to first element in nay)
+ *              deltax (x increment between array elements in nay)
+ *              nasy (input histogram, assumed equally spaced)
+ *              npts (number of points to evaluate rank function)
+ *              &nax (<optional return> array of x values in range)
+ *              &nay (<return> rank array of specified npts)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+numaMakeRankFromHistogram(l_float32  startx,
+                          l_float32  deltax,
+                          NUMA      *nasy,
+                          l_int32    npts,
+                          NUMA     **pnax,
+                          NUMA     **pnay)
+{
+l_int32    i, n;
+l_float32  sum, fval;
+NUMA      *nan, *nar;
+
+    PROCNAME("numaMakeRankFromHistogram");
+
+    if (pnax) *pnax = NULL;
+    if (!pnay)
+        return ERROR_INT("&nay not defined", procName, 1);
+    *pnay = NULL;
+    if (!nasy)
+        return ERROR_INT("nasy not defined", procName, 1);
+    if ((n = numaGetCount(nasy)) == 0)
+        return ERROR_INT("no bins in nas", procName, 1);
+
+        /* Normalize and generate the rank array corresponding to
+         * the binned histogram. */
+    nan = numaNormalizeHistogram(nasy, 1.0);
+    nar = numaCreate(n + 1);  /* rank numa corresponding to nan */
+    sum = 0.0;
+    numaAddNumber(nar, sum);  /* first element is 0.0 */
+    for (i = 0; i < n; i++) {
+        numaGetFValue(nan, i, &fval);
+        sum += fval;
+        numaAddNumber(nar, sum);
+    }
+
+        /* Compute rank array on full range with specified
+         * number of points and correspondence to x-values. */
+    numaInterpolateEqxInterval(startx, deltax, nar, L_LINEAR_INTERP,
+                               startx, startx + n * deltax, npts,
+                               pnax, pnay);
+    numaDestroy(&nan);
+    numaDestroy(&nar);
+    return 0;
+}
+
+
+/*!
+ *  numaHistogramGetRankFromVal()
+ *
+ *      Input:  na (histogram)
+ *              rval (value of input sample for which we want the rank)
+ *              &rank (<return> fraction of total samples below rval)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If we think of the histogram as a function y(x), normalized
+ *          to 1, for a given input value of x, this computes the
+ *          rank of x, which is the integral of y(x) from the start
+ *          value of x to the input value.
+ *      (2) This function only makes sense when applied to a Numa that
+ *          is a histogram.  The values in the histogram can be ints and
+ *          floats, and are computed as floats.  The rank is returned
+ *          as a float between 0.0 and 1.0.
+ *      (3) The numa parameters startx and binsize are used to
+ *          compute x from the Numa index i.
+ */
+l_int32
+numaHistogramGetRankFromVal(NUMA       *na,
+                            l_float32   rval,
+                            l_float32  *prank)
+{
+l_int32    i, ibinval, n;
+l_float32  startval, binsize, binval, maxval, fractval, total, sum, val;
+
+    PROCNAME("numaHistogramGetRankFromVal");
+
+    if (!prank)
+        return ERROR_INT("prank not defined", procName, 1);
+    *prank = 0.0;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    numaGetParameters(na, &startval, &binsize);
+    n = numaGetCount(na);
+    if (rval < startval)
+        return 0;
+    maxval = startval + n * binsize;
+    if (rval > maxval) {
+        *prank = 1.0;
+        return 0;
+    }
+
+    binval = (rval - startval) / binsize;
+    ibinval = (l_int32)binval;
+    if (ibinval >= n) {
+        *prank = 1.0;
+        return 0;
+    }
+    fractval = binval - (l_float32)ibinval;
+
+    sum = 0.0;
+    for (i = 0; i < ibinval; i++) {
+        numaGetFValue(na, i, &val);
+        sum += val;
+    }
+    numaGetFValue(na, ibinval, &val);
+    sum += fractval * val;
+    numaGetSum(na, &total);
+    *prank = sum / total;
+
+/*    fprintf(stderr, "binval = %7.3f, rank = %7.3f\n", binval, *prank); */
+
+    return 0;
+}
+
+
+/*!
+ *  numaHistogramGetValFromRank()
+ *
+ *      Input:  na (histogram)
+ *              rank (fraction of total samples)
+ *              &rval (<return> approx. to the bin value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If we think of the histogram as a function y(x), this returns
+ *          the value x such that the integral of y(x) from the start
+ *          value to x gives the fraction 'rank' of the integral
+ *          of y(x) over all bins.
+ *      (2) This function only makes sense when applied to a Numa that
+ *          is a histogram.  The values in the histogram can be ints and
+ *          floats, and are computed as floats.  The val is returned
+ *          as a float, even though the buckets are of integer width.
+ *      (3) The numa parameters startx and binsize are used to
+ *          compute x from the Numa index i.
+ */
+l_int32
+numaHistogramGetValFromRank(NUMA       *na,
+                            l_float32   rank,
+                            l_float32  *prval)
+{
+l_int32    i, n;
+l_float32  startval, binsize, rankcount, total, sum, fract, val;
+
+    PROCNAME("numaHistogramGetValFromRank");
+
+    if (!prval)
+        return ERROR_INT("prval not defined", procName, 1);
+    *prval = 0.0;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (rank < 0.0) {
+        L_WARNING("rank < 0; setting to 0.0\n", procName);
+        rank = 0.0;
+    }
+    if (rank > 1.0) {
+        L_WARNING("rank > 1.0; setting to 1.0\n", procName);
+        rank = 1.0;
+    }
+
+    n = numaGetCount(na);
+    numaGetParameters(na, &startval, &binsize);
+    numaGetSum(na, &total);
+    rankcount = rank * total;  /* count that corresponds to rank */
+    sum = 0.0;
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        if (sum + val >= rankcount)
+            break;
+        sum += val;
+    }
+    if (val <= 0.0)  /* can == 0 if rank == 0.0 */
+        fract = 0.0;
+    else  /* sum + fract * val = rankcount */
+        fract = (rankcount - sum) / val;
+
+    /* The use of the fraction of a bin allows a simple calculation
+     * for the histogram value at the given rank. */
+    *prval = startval + binsize * ((l_float32)i + fract);
+
+/*    fprintf(stderr, "rank = %7.3f, val = %7.3f\n", rank, *prval); */
+
+    return 0;
+}
+
+
+/*!
+ *  numaDiscretizeRankAndIntensity()
+ *
+ *      Input:  na (normalized histogram of probability density vs intensity)
+ *              nbins (number of bins at which the rank is divided)
+ *              &pnarbin (<optional return> rank bin value vs intensity)
+ *              &pnam (<optional return> median intensity in a bin vs
+ *                     rank bin value, with @nbins of discretized rank values)
+ *              &pnar (<optional return> rank vs intensity; this is
+ *                     a cumulative norm histogram)
+ *              &pnabb (<optional return> intensity at the right bin boundary
+ *                      vs rank bin)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) We are inverting the rank(intensity) function to get
+ *          the intensity(rank) function at @nbins equally spaced
+ *          values of rank between 0.0 and 1.0.  We save integer values
+ *          for the intensity.
+ *      (2) We are using the word "intensity" to describe the type of
+ *          array values, but any array of non-negative numbers will work.
+ *      (3) The output arrays give the following mappings, where the
+ *          input is a normalized histogram of array values:
+ *             array values     -->  rank bin number  (narbin)
+ *             rank bin number  -->  median array value in bin (nam)
+ *             array values     -->  cumulative norm = rank  (nar)
+ *             rank bin number  -->  array value at right bin edge (nabb)
+ */
+l_int32
+numaDiscretizeRankAndIntensity(NUMA    *na,
+                               l_int32  nbins,
+                               NUMA   **pnarbin,
+                               NUMA   **pnam,
+                               NUMA   **pnar,
+                               NUMA   **pnabb)
+{
+NUMA      *nar;  /* rank value as function of intensity */
+NUMA      *nam;  /* median intensity in the rank bins */
+NUMA      *nabb;  /* rank bin right boundaries (in intensity) */
+NUMA      *narbin;  /* binned rank value as a function of intensity */
+l_int32    i, j, npts, start, midfound, mcount, rightedge;
+l_float32  sum, midrank, endrank, val;
+
+    PROCNAME("numaDiscretizeRankAndIntensity");
+
+    if (pnarbin) *pnarbin = NULL;
+    if (pnam) *pnam = NULL;
+    if (pnar) *pnar = NULL;
+    if (pnabb) *pnabb = NULL;
+    if (!pnarbin && !pnam && !pnar && !pnabb)
+        return ERROR_INT("no output requested", procName, 1);
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (nbins < 2)
+        return ERROR_INT("nbins must be > 1", procName, 1);
+
+        /* Get cumulative normalized histogram (rank vs intensity value).
+         * For a normalized histogram from an 8 bpp grayscale image
+         * as input, we have 256 bins and 257 points in the
+         * cumulative (rank) histogram. */
+    npts = numaGetCount(na);
+    nar = numaCreate(npts + 1);
+    sum = 0.0;
+    numaAddNumber(nar, sum);  /* left side of first bin */
+    for (i = 0; i < npts; i++) {
+        numaGetFValue(na, i, &val);
+        sum += val;
+        numaAddNumber(nar, sum);
+    }
+
+    if ((nam = numaCreate(nbins)) == NULL)
+        return ERROR_INT("nam not made", procName, 1);
+    if ((narbin = numaCreate(npts)) == NULL)
+        return ERROR_INT("narbin not made", procName, 1);
+    if ((nabb = numaCreate(nbins)) == NULL)
+        return ERROR_INT("nabb not made", procName, 1);
+
+        /* We find the intensity value at the right edge of each of
+         * the rank bins.  We also find the median intensity in the bin,
+         * where approximately half the samples are lower and half are
+         * higher.  This can be considered as a simple approximation
+         * for the average intensity in the bin. */
+    start = 0;  /* index in nar */
+    mcount = 0;  /* count of median values in rank bins; not to exceed nbins */
+    for (i = 0; i < nbins; i++) {
+        midrank = (l_float32)(i + 0.5) / (l_float32)(nbins);
+        endrank = (l_float32)(i + 1.0) / (l_float32)(nbins);
+        endrank = L_MAX(0.0, L_MIN(endrank - 0.001, 1.0));
+        midfound = FALSE;
+        for (j = start; j < npts; j++) {  /* scan up for each bin value */
+            numaGetFValue(nar, j, &val);
+                /* Use (j == npts - 1) tests in case all weight is at top end */
+            if ((!midfound && val >= midrank) ||
+                (mcount < nbins && j == npts - 1)) {
+                midfound = TRUE;
+                numaAddNumber(nam, j);
+                mcount++;
+            }
+            if ((val >= endrank) || (j == npts - 1)) {
+                numaAddNumber(nabb, j);
+                if (val == endrank)
+                    start = j;
+                else
+                    start = j - 1;
+                break;
+            }
+        }
+    }
+    numaSetValue(nabb, nbins - 1, npts - 1);  /* extend to max */
+
+        /* Error checking: did we get data in all bins? */
+    if (mcount != nbins)
+        L_WARNING("found data for %d bins; should be %d\n",
+                  procName, mcount, nbins);
+
+        /* Generate LUT that maps from intensity to bin number */
+    start = 0;
+    for (i = 0; i < nbins; i++) {
+        numaGetIValue(nabb, i, &rightedge);
+        for (j = start; j < npts; j++) {
+            if (j <= rightedge)
+                numaAddNumber(narbin, i);
+            if (j > rightedge) {
+                start = j;
+                break;
+            }
+            if (j == npts - 1) {  /* we're done */
+                start = j + 1;
+                break;
+            }
+        }
+    }
+
+    if (pnarbin)
+        *pnarbin = narbin;
+    else
+        numaDestroy(&narbin);
+    if (pnam)
+        *pnam = nam;
+    else
+        numaDestroy(&nam);
+    if (pnar)
+        *pnar = nar;
+    else
+        numaDestroy(&nar);
+    if (pnabb)
+        *pnabb = nabb;
+    else
+        numaDestroy(&nabb);
+    return 0;
+}
+
+
+/*!
+ *  numaGetRankBinValues()
+ *
+ *      Input:  na (just an array of values)
+ *              nbins (number of bins at which the rank is divided)
+ *              &pnarbin (<optional return> rank bin value vs array value)
+ *              &pnam (<optional return> median intensity in a bin vs
+ *                     rank bin value, with @nbins of discretized rank values)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Simple interface for getting a binned rank representation
+ *          of an input array of values.  This returns two mappings:
+ *             array value     -->  rank bin number  (narbin)
+ *             rank bin number -->  median array value in each rank bin (nam)
+ */
+l_int32
+numaGetRankBinValues(NUMA    *na,
+                     l_int32  nbins,
+                     NUMA   **pnarbin,
+                     NUMA   **pnam)
+{
+NUMA      *nah, *nan;  /* histo and normalized histo */
+l_int32    maxbins, discardval;
+l_float32  maxval, delx;
+
+    PROCNAME("numaGetRankBinValues");
+
+    if (pnarbin) *pnarbin = NULL;
+    if (pnam) *pnam = NULL;
+    if (!pnarbin && !pnam)
+        return ERROR_INT("no output requested", procName, 1);
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    if (numaGetCount(na) == 0)
+        return ERROR_INT("na is empty", procName, 1);
+    if (nbins < 2)
+        return ERROR_INT("nbins must be > 1", procName, 1);
+
+        /* Get normalized histogram  */
+    numaGetMax(na, &maxval, NULL);
+    maxbins = L_MIN(100002, (l_int32)maxval + 2);
+    nah = numaMakeHistogram(na, maxbins, &discardval, NULL);
+    nan = numaNormalizeHistogram(nah, 1.0);
+
+        /* Warn if there is a scale change.  This shouldn't happen
+         * unless the max value is above 100000.  */
+    numaGetParameters(nan, NULL, &delx);
+    if (delx > 1.0)
+        L_WARNING("scale change: delx = %6.2f\n", procName, delx);
+
+        /* Rank bin the results */
+    numaDiscretizeRankAndIntensity(nan, nbins, pnarbin, pnam, NULL, NULL);
+    numaDestroy(&nah);
+    numaDestroy(&nan);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                      Splitting a distribution                        *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaSplitDistribution()
+ *
+ *      Input:  na (histogram)
+ *              scorefract (fraction of the max score, used to determine
+ *                          the range over which the histogram min is searched)
+ *              &splitindex (<optional return> index for splitting)
+ *              &ave1 (<optional return> average of lower distribution)
+ *              &ave2 (<optional return> average of upper distribution)
+ *              &num1 (<optional return> population of lower distribution)
+ *              &num2 (<optional return> population of upper distribution)
+ *              &nascore (<optional return> for debugging; otherwise use null)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function is intended to be used on a distribution of
+ *          values that represent two sets, such as a histogram of
+ *          pixel values for an image with a fg and bg, and the goal
+ *          is to determine the averages of the two sets and the
+ *          best splitting point.
+ *      (2) The Otsu method finds a split point that divides the distribution
+ *          into two parts by maximizing a score function that is the
+ *          product of two terms:
+ *            (a) the square of the difference of centroids, (ave1 - ave2)^2
+ *            (b) fract1 * (1 - fract1)
+ *          where fract1 is the fraction in the lower distribution.
+ *      (3) This works well for images where the fg and bg are
+ *          each relatively homogeneous and well-separated in color.
+ *          However, if the actual fg and bg sets are very different
+ *          in size, and the bg is highly varied, as can occur in some
+ *          scanned document images, this will bias the split point
+ *          into the larger "bump" (i.e., toward the point where the
+ *          (b) term reaches its maximum of 0.25 at fract1 = 0.5.
+ *          To avoid this, we define a range of values near the
+ *          maximum of the score function, and choose the value within
+ *          this range such that the histogram itself has a minimum value.
+ *          The range is determined by scorefract: we include all abscissa
+ *          values to the left and right of the value that maximizes the
+ *          score, such that the score stays above (1 - scorefract) * maxscore.
+ *          The intuition behind this modification is to try to find
+ *          a split point that both has a high variance score and is
+ *          at or near a minimum in the histogram, so that the histogram
+ *          slope is small at the split point.
+ *      (4) We normalize the score so that if the two distributions
+ *          were of equal size and at opposite ends of the numa, the
+ *          score would be 1.0.
+ */
+l_int32
+numaSplitDistribution(NUMA       *na,
+                      l_float32   scorefract,
+                      l_int32    *psplitindex,
+                      l_float32  *pave1,
+                      l_float32  *pave2,
+                      l_float32  *pnum1,
+                      l_float32  *pnum2,
+                      NUMA      **pnascore)
+{
+l_int32    i, n, bestsplit, minrange, maxrange, maxindex;
+l_float32  ave1, ave2, ave1prev, ave2prev;
+l_float32  num1, num2, num1prev, num2prev;
+l_float32  val, minval, sum, fract1;
+l_float32  norm, score, minscore, maxscore;
+NUMA      *nascore, *naave1, *naave2, *nanum1, *nanum2;
+
+    PROCNAME("numaSplitDistribution");
+
+    if (psplitindex) *psplitindex = 0;
+    if (pave1) *pave1 = 0.0;
+    if (pave2) *pave2 = 0.0;
+    if (pnum1) *pnum1 = 0.0;
+    if (pnum2) *pnum2 = 0.0;
+    if (pnascore) *pnascore = NULL;
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+
+    n = numaGetCount(na);
+    if (n <= 1)
+        return ERROR_INT("n = 1 in histogram", procName, 1);
+    numaGetSum(na, &sum);
+    if (sum <= 0.0)
+        return ERROR_INT("sum <= 0.0", procName, 1);
+    norm = 4.0 / ((n - 1) * (n - 1));
+    ave1prev = 0.0;
+    numaGetHistogramStats(na, 0.0, 1.0, &ave2prev, NULL, NULL, NULL);
+    num1prev = 0.0;
+    num2prev = sum;
+    maxindex = n / 2;  /* initialize with something */
+
+        /* Split the histogram with [0 ... i] in the lower part
+         * and [i+1 ... n-1] in upper part.  First, compute an otsu
+         * score for each possible splitting.  */
+    nascore = numaCreate(n);
+    if (pave2) naave1 = numaCreate(n);
+    if (pave2) naave2 = numaCreate(n);
+    if (pnum1) nanum1 = numaCreate(n);
+    if (pnum2) nanum2 = numaCreate(n);
+    maxscore = 0.0;
+    for (i = 0; i < n; i++) {
+        numaGetFValue(na, i, &val);
+        num1 = num1prev + val;
+        if (num1 == 0)
+            ave1 = ave1prev;
+        else
+            ave1 = (num1prev * ave1prev + i * val) / num1;
+        num2 = num2prev - val;
+        if (num2 == 0)
+            ave2 = ave2prev;
+        else
+            ave2 = (num2prev * ave2prev - i * val) / num2;
+        fract1 = num1 / sum;
+        score = norm * (fract1 * (1 - fract1)) * (ave2 - ave1) * (ave2 - ave1);
+        numaAddNumber(nascore, score);
+        if (pave1) numaAddNumber(naave1, ave1);
+        if (pave2) numaAddNumber(naave2, ave2);
+        if (pnum1) numaAddNumber(nanum1, num1);
+        if (pnum1) numaAddNumber(nanum2, num2);
+        if (score > maxscore) {
+            maxscore = score;
+            maxindex = i;
+        }
+        num1prev = num1;
+        num2prev = num2;
+        ave1prev = ave1;
+        ave2prev = ave2;
+    }
+
+        /* Next, for all contiguous scores within a specified fraction
+         * of the max, choose the split point as the value with the
+         * minimum in the histogram. */
+    minscore = (1. - scorefract) * maxscore;
+    for (i = maxindex - 1; i >= 0; i--) {
+        numaGetFValue(nascore, i, &val);
+        if (val < minscore)
+            break;
+    }
+    minrange = i + 1;
+    for (i = maxindex + 1; i < n; i++) {
+        numaGetFValue(nascore, i, &val);
+        if (val < minscore)
+            break;
+    }
+    maxrange = i - 1;
+    numaGetFValue(na, minrange, &minval);
+    bestsplit = minrange;
+    for (i = minrange + 1; i <= maxrange; i++) {
+        numaGetFValue(na, i, &val);
+        if (val < minval) {
+            minval = val;
+            bestsplit = i;
+        }
+    }
+
+        /* Add one to the bestsplit value to get the threshold value,
+         * because when we take a threshold, as in pixThresholdToBinary(),
+         * we always choose the set with values below the threshold. */
+    bestsplit = L_MIN(255, bestsplit + 1);
+
+    if (psplitindex) *psplitindex = bestsplit;
+    if (pave1) numaGetFValue(naave1, bestsplit, pave1);
+    if (pave2) numaGetFValue(naave2, bestsplit, pave2);
+    if (pnum1) numaGetFValue(nanum1, bestsplit, pnum1);
+    if (pnum2) numaGetFValue(nanum2, bestsplit, pnum2);
+
+    if (pnascore) {  /* debug mode */
+        fprintf(stderr, "minrange = %d, maxrange = %d\n", minrange, maxrange);
+        fprintf(stderr, "minval = %10.0f\n", minval);
+        gplotSimple1(nascore, GPLOT_PNG, "/tmp/lept/nascore",
+                     "Score for split distribution");
+        *pnascore = nascore;
+    } else {
+        numaDestroy(&nascore);
+    }
+
+    if (pave1) numaDestroy(&naave1);
+    if (pave2) numaDestroy(&naave2);
+    if (pnum1) numaDestroy(&nanum1);
+    if (pnum2) numaDestroy(&nanum2);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                         Comparing histograms                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  grayHistogramsToEMD()
+ *
+ *      Input:  naa1, naa2 (two numaa, each with one or more 256-element
+ *                          histograms)
+ *              &nad (<return> nad of EM distances for each histogram)
+ *      Return: 0 if OK, 1 on error
+ *
+ * Notes:
+ *     (1) The two numaas must be the same size and have corresponding
+ *         256-element histograms.  Pairs do not need to be normalized
+ *         to the same sum.
+ *     (2) This is typically used on two sets of histograms from
+ *         corresponding tiles of two images.  The similarity of two
+ *         images can be found with the scoring function used in
+ *         pixCompareGrayByHisto():
+ *             score S = 1.0 - k * D, where
+ *                 k is a constant, say in the range 5-10
+ *                 D = EMD
+ *             for each tile; for multiple tiles, take the Min(S) over
+ *             the set of tiles to be the final score.
+ */
+l_int32
+grayHistogramsToEMD(NUMAA  *naa1,
+                    NUMAA  *naa2,
+                    NUMA  **pnad)
+{
+l_int32     i, n, nt;
+l_float32   dist;
+NUMA       *na1, *na2, *nad;
+
+    PROCNAME("grayHistogramsToEMD");
+
+    if (!pnad)
+        return ERROR_INT("&nad not defined", procName, 1);
+    *pnad = NULL;
+    if (!naa1 || !naa2)
+        return ERROR_INT("na1 and na2 not both defined", procName, 1);
+    n = numaaGetCount(naa1);
+    if (n != numaaGetCount(naa2))
+        return ERROR_INT("naa1 and naa2 numa counts differ", procName, 1);
+    nt = numaaGetNumberCount(naa1);
+    if (nt != numaaGetNumberCount(naa2))
+        return ERROR_INT("naa1 and naa2 number counts differ", procName, 1);
+    if (256 * n != nt)  /* good enough check */
+        return ERROR_INT("na sizes must be 256", procName, 1);
+
+    nad = numaCreate(n);
+    *pnad = nad;
+    for (i = 0; i < n; i++) {
+        na1 = numaaGetNuma(naa1, i, L_CLONE);
+        na2 = numaaGetNuma(naa2, i, L_CLONE);
+        numaEarthMoverDistance(na1, na2, &dist);
+        numaAddNumber(nad, dist / 255.);  /* normalize to [0.0 - 1.0] */
+        numaDestroy(&na1);
+        numaDestroy(&na2);
+    }
+    return 0;
+}
+
+
+/*!
+ *  numaEarthMoverDistance()
+ *
+ *      Input:  na1, na2 (two numas of the same size, typically histograms)
+ *              &dist (<return> EM distance)
+ *      Return: 0 if OK, 1 on error
+ *
+ * Notes:
+ *     (1) The two numas must have the same size.  They do not need to be
+ *         normalized to the same sum before applying the function.
+ *     (2) For a 1D discrete function, the implementation of the EMD
+ *         is trivial.  Just keep filling or emptying buckets in one numa
+ *         to match the amount in the other, moving sequentially along
+ *         both arrays.
+ *     (3) We divide the sum of the absolute value of everything moved
+ *         (by 1 unit at a time) by the sum of the numa (amount of "earth")
+ *         to get the average distance that the "earth" was moved.
+ *         This is the value returned here.
+ *     (4) The caller can do a further normalization, by the number of
+ *         buckets (minus 1), to get the EM distance as a fraction of
+ *         the maximum possible distance, which is n-1.  This fraction
+ *         is 1.0 for the situation where all the 'earth' in the first
+ *         array is at one end, and all in the second array is at the
+ *         other end.
+ */
+l_int32
+numaEarthMoverDistance(NUMA       *na1,
+                       NUMA       *na2,
+                       l_float32  *pdist)
+{
+l_int32     n, norm, i;
+l_float32   sum1, sum2, diff, total;
+l_float32  *array1, *array3;
+NUMA       *na3;
+
+    PROCNAME("numaEarthMoverDistance");
+
+    if (!pdist)
+        return ERROR_INT("&dist not defined", procName, 1);
+    *pdist = 0.0;
+    if (!na1 || !na2)
+        return ERROR_INT("na1 and na2 not both defined", procName, 1);
+    n = numaGetCount(na1);
+    if (n != numaGetCount(na2))
+        return ERROR_INT("na1 and na2 have different size", procName, 1);
+
+        /* Generate na3; normalize to na1 if necessary */
+    numaGetSum(na1, &sum1);
+    numaGetSum(na2, &sum2);
+    norm = (L_ABS(sum1 - sum2) < 0.00001 * L_ABS(sum1)) ? 1 : 0;
+    if (!norm)
+        na3 = numaTransform(na2, 0, sum1 / sum2);
+    else
+        na3 = numaCopy(na2);
+    array1 = numaGetFArray(na1, L_NOCOPY);
+    array3 = numaGetFArray(na3, L_NOCOPY);
+
+        /* Move earth in n3 from array elements, to match n1 */
+    total = 0;
+    for (i = 1; i < n; i++) {
+        diff = array1[i - 1] - array3[i - 1];
+        array3[i] -= diff;
+        total += L_ABS(diff);
+    }
+    *pdist = total / sum1;
+
+    numaDestroy(&na3);
+    return 0;
+}
+
+
+/*!
+ *  grayInterHistogramStats()
+ *
+ *      Input:  naa (numaa with two or more 256-element histograms)
+ *              wc (half-width of the smoothing window)
+ *              &nam (<optional return> mean values)
+ *              &nams (<optional return> mean square values)
+ *              &pnav (<optional return> variances)
+ *              &pnarv (<optional return> rms deviations from the mean)
+ *      Return: 0 if OK, 1 on error
+ *
+ * Notes:
+ *     (1) The @naa has two or more 256-element numa histograms, which
+ *         are to be compared value-wise at each of the 256 gray levels.
+ *         The result are stats (mean, mean square, variance, root variance)
+ *         aggregated across the set of histograms, and each is output
+ *         as a 256 entry numa.  Think of these histograms as a matrix,
+ *         where each histogram is one row of the array.  The stats are
+ *         then aggregated column-wise, between the histograms.
+ *     (2) These stats are:
+*             - average value: <v>  (nam)
+ *            - average squared value: <v*v> (nams)
+ *            - variance: <(v - <v>)*(v - <v>)> = <v*v> - <v>*<v>  (nav)
+ *            - square-root of variance: (narv)
+ *         where the brackets < .. > indicate that the average value is
+ *         to be taken over each column of the array.
+ *     (3) The input histograms are optionally smoothed before these
+ *         statistical operations.
+ *     (4) The input histograms are normalized to a sum of 10000.  By
+ *         doing this, the resulting numbers are independent of the
+ *         number of samples used in building the individual histograms.
+ *     (5) A typical application is on a set of histograms from tiles
+ *         of an image, to distinguish between text/tables and photo
+ *         regions.  If the tiles are much larger than the text line
+ *         spacing, text/table regions typically have smaller variance
+ *         across tiles than photo regions.  For this application, it
+ *         may be useful to ignore values near white, which are large for
+ *         text and would magnify the variance due to variations in
+ *         illumination.  However, because the variance of a drawing or
+ *         a light photo can be similar to that of grayscale text, this
+ *         function is only a discriminator between darker photos/drawings
+ *         and light photos/text/line-graphics.
+ */
+l_int32
+grayInterHistogramStats(NUMAA   *naa,
+                        l_int32  wc,
+                        NUMA   **pnam,
+                        NUMA   **pnams,
+                        NUMA   **pnav,
+                        NUMA   **pnarv)
+{
+l_int32      i, j, n, nn;
+l_float32  **arrays;
+l_float32    mean, var, rvar;
+NUMA        *na1, *na2, *na3, *na4;
+
+    PROCNAME("grayInterHistogramStats");
+
+    if (pnam) *pnam = NULL;
+    if (pnams) *pnams = NULL;
+    if (pnav) *pnav = NULL;
+    if (pnarv) *pnarv = NULL;
+    if (!pnam && !pnams && !pnav && !pnarv)
+        return ERROR_INT("nothing requested", procName, 1);
+    if (!naa)
+        return ERROR_INT("naa not defined", procName, 1);
+    n = numaaGetCount(naa);
+    for (i = 0; i < n; i++) {
+        nn = numaaGetNumaCount(naa, i);
+        if (nn != 256) {
+            L_ERROR("%d numbers in numa[%d]\n", procName, nn, i);
+            return 1;
+        }
+    }
+
+    if (pnam) *pnam = numaCreate(256);
+    if (pnams) *pnams = numaCreate(256);
+    if (pnav) *pnav = numaCreate(256);
+    if (pnarv) *pnarv = numaCreate(256);
+
+        /* First, use mean smoothing, normalize each histogram,
+         * and save all results in a 2D matrix. */
+    arrays = (l_float32 **)LEPT_CALLOC(n, sizeof(l_float32 *));
+    for (i = 0; i < n; i++) {
+        na1 = numaaGetNuma(naa, i, L_CLONE);
+        na2 = numaWindowedMean(na1, wc);
+        na3 = numaNormalizeHistogram(na2, 10000.);
+        arrays[i] = numaGetFArray(na3, L_COPY);
+        numaDestroy(&na1);
+        numaDestroy(&na2);
+        numaDestroy(&na3);
+    }
+
+        /* Get stats between histograms */
+    for (j = 0; j < 256; j++) {
+        na4 = numaCreate(n);
+        for (i = 0; i < n; i++) {
+            numaAddNumber(na4, arrays[i][j]);
+        }
+        numaSimpleStats(na4, 0, 0, &mean, &var, &rvar);
+        if (pnam) numaAddNumber(*pnam, mean);
+        if (pnams) numaAddNumber(*pnams, mean * mean);
+        if (pnav) numaAddNumber(*pnav, var);
+        if (pnarv) numaAddNumber(*pnarv, rvar);
+        numaDestroy(&na4);
+    }
+
+    for (i = 0; i < n; i++)
+        LEPT_FREE(arrays[i]);
+    LEPT_FREE(arrays);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                             Extrema finding                          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaFindPeaks()
+ *
+ *      Input:  source na
+ *              max number of peaks to be found
+ *              fract1  (min fraction of peak value)
+ *              fract2  (min slope)
+ *      Return: peak na, or null on error.
+ *
+ * Notes:
+ *     (1) The returned na consists of sets of four numbers representing
+ *         the peak, in the following order:
+ *            left edge; peak center; right edge; normalized peak area
+ */
+NUMA *
+numaFindPeaks(NUMA      *nas,
+              l_int32    nmax,
+              l_float32  fract1,
+              l_float32  fract2)
+{
+l_int32    i, k, n, maxloc, lloc, rloc;
+l_float32  fmaxval, sum, total, newtotal, val, lastval;
+l_float32  peakfract;
+NUMA      *na, *napeak;
+
+    PROCNAME("numaFindPeaks");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    n = numaGetCount(nas);
+    numaGetSum(nas, &total);
+
+        /* We munge this copy */
+    if ((na = numaCopy(nas)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    if ((napeak = numaCreate(4 * nmax)) == NULL)
+        return (NUMA *)ERROR_PTR("napeak not made", procName, NULL);
+
+    for (k = 0; k < nmax; k++) {
+        numaGetSum(na, &newtotal);
+        if (newtotal == 0.0)   /* sanity check */
+            break;
+        numaGetMax(na, &fmaxval, &maxloc);
+        sum = fmaxval;
+        lastval = fmaxval;
+        lloc = 0;
+        for (i = maxloc - 1; i >= 0; --i) {
+            numaGetFValue(na, i, &val);
+            if (val == 0.0) {
+                lloc = i + 1;
+                break;
+            }
+            if (val > fract1 * fmaxval) {
+                sum += val;
+                lastval = val;
+                continue;
+            }
+            if (lastval - val > fract2 * lastval) {
+                sum += val;
+                lastval = val;
+                continue;
+            }
+            lloc = i;
+            break;
+        }
+        lastval = fmaxval;
+        rloc = n - 1;
+        for (i = maxloc + 1; i < n; ++i) {
+            numaGetFValue(na, i, &val);
+            if (val == 0.0) {
+                rloc = i - 1;
+                break;
+            }
+            if (val > fract1 * fmaxval) {
+                sum += val;
+                lastval = val;
+                continue;
+            }
+            if (lastval - val > fract2 * lastval) {
+                sum += val;
+                lastval = val;
+                continue;
+            }
+            rloc = i;
+            break;
+        }
+        peakfract = sum / total;
+        numaAddNumber(napeak, lloc);
+        numaAddNumber(napeak, maxloc);
+        numaAddNumber(napeak, rloc);
+        numaAddNumber(napeak, peakfract);
+
+        for (i = lloc; i <= rloc; i++)
+            numaSetValue(na, i, 0.0);
+    }
+
+    numaDestroy(&na);
+    return napeak;
+}
+
+
+/*!
+ *  numaFindExtrema()
+ *
+ *      Input:  nas (input values)
+ *              delta (relative amount to resolve peaks and valleys)
+ *      Return: nad (locations of extrema), or null on error
+ *
+ *  Notes:
+ *      (1) This returns a sequence of extrema (peaks and valleys).
+ *      (2) The algorithm is analogous to that for determining
+ *          mountain peaks.  Suppose we have a local peak, with
+ *          bumps on the side.  Under what conditions can we consider
+ *          those 'bumps' to be actual peaks?  The answer: if the
+ *          bump is separated from the peak by a saddle that is at
+ *          least 500 feet below the bump.
+ *      (3) Operationally, suppose we are looking for a peak.
+ *          We are keeping the largest value we've seen since the
+ *          last valley, and are looking for a value that is delta
+ *          BELOW our current peak.  When we find such a value,
+ *          we label the peak, use the current value to label the
+ *          valley, and then do the same operation in reverse (looking
+ *          for a valley).
+ */
+NUMA *
+numaFindExtrema(NUMA      *nas,
+                l_float32  delta)
+{
+l_int32    i, n, found, loc, direction;
+l_float32  startval, val, maxval, minval;
+NUMA      *nad;
+
+    PROCNAME("numaFindExtrema");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+
+    n = numaGetCount(nas);
+    nad = numaCreate(0);
+
+        /* We don't know if we'll find a peak or valley first,
+         * but use the first element of nas as the reference point.
+         * Break when we deviate by 'delta' from the first point. */
+    numaGetFValue(nas, 0, &startval);
+    found = FALSE;
+    for (i = 1; i < n; i++) {
+        numaGetFValue(nas, i, &val);
+        if (L_ABS(val - startval) >= delta) {
+            found = TRUE;
+            break;
+        }
+    }
+
+    if (!found)
+        return nad;  /* it's empty */
+
+        /* Are we looking for a peak or a valley? */
+    if (val > startval) {  /* peak */
+        direction = 1;
+        maxval = val;
+    } else {
+        direction = -1;
+        minval = val;
+    }
+    loc = i;
+
+        /* Sweep through the rest of the array, recording alternating
+         * peak/valley extrema. */
+    for (i = i + 1; i < n; i++) {
+        numaGetFValue(nas, i, &val);
+        if (direction == 1 && val > maxval ) {  /* new local max */
+            maxval = val;
+            loc = i;
+        } else if (direction == -1 && val < minval ) {  /* new local min */
+            minval = val;
+            loc = i;
+        } else if (direction == 1 && (maxval - val >= delta)) {
+            numaAddNumber(nad, loc);  /* save the current max location */
+            direction = -1;  /* reverse: start looking for a min */
+            minval = val;
+            loc = i;  /* current min location */
+        } else if (direction == -1 && (val - minval >= delta)) {
+            numaAddNumber(nad, loc);  /* save the current min location */
+            direction = 1;  /* reverse: start looking for a max */
+            maxval = val;
+            loc = i;  /* current max location */
+        }
+    }
+
+        /* Save the final extremum */
+/*    numaAddNumber(nad, loc); */
+    return nad;
+}
+
+
+/*!
+ *  numaCountReversals()
+ *
+ *      Input:  nas (input values)
+ *              minreversal (relative amount to resolve peaks and valleys)
+ *              &nr (<optional return> number of reversals
+ *              &nrpl (<optional return> reversal density: reversals/length)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The input numa is can be generated from pixExtractAlongLine().
+ *          If so, the x parameters can be used to find the reversal
+ *          frequency along a line.
+ */
+l_int32
+numaCountReversals(NUMA       *nas,
+                   l_float32   minreversal,
+                   l_int32    *pnr,
+                   l_float32  *pnrpl)
+{
+l_int32    n, nr;
+l_float32  delx, len;
+NUMA      *nat;
+
+    PROCNAME("numaCountReversals");
+
+    if (pnr) *pnr = 0;
+    if (pnrpl) *pnrpl = 0.0;
+    if (!pnr && !pnrpl)
+        return ERROR_INT("neither &nr nor &nrpl are defined", procName, 1);
+    if (!nas)
+        return ERROR_INT("nas not defined", procName, 1);
+
+    n = numaGetCount(nas);
+    nat = numaFindExtrema(nas, minreversal);
+    nr = numaGetCount(nat);
+    if (pnr) *pnr = nr;
+    if (pnrpl) {
+        numaGetParameters(nas, NULL, &delx);
+        len = delx * n;
+        *pnrpl = (l_float32)nr / len;
+    }
+
+    numaDestroy(&nat);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                Threshold crossings and frequency analysis            *
+ *----------------------------------------------------------------------*/
+/*!
+ *  numaSelectCrossingThreshold()
+ *
+ *      Input:  nax (<optional> numa of abscissa values; can be NULL)
+ *              nay (signal)
+ *              estthresh (estimated pixel threshold for crossing: e.g., for
+ *                         images, white <--> black; typ. ~120)
+ *              &bestthresh (<return> robust estimate of threshold to use)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Note:
+ *     (1) When a valid threshold is used, the number of crossings is
+ *         a maximum, because none are missed.  If no threshold intersects
+ *         all the crossings, the crossings must be determined with
+ *         numaCrossingsByPeaks().
+ *     (2) @estthresh is an input estimate of the threshold that should
+ *         be used.  We compute the crossings with 41 thresholds
+ *         (20 below and 20 above).  There is a range in which the
+ *         number of crossings is a maximum.  Return a threshold
+ *         in the center of this stable plateau of crossings.
+ *         This can then be used with numaCrossingsByThreshold()
+ *         to get a good estimate of crossing locations.
+ */
+l_int32
+numaSelectCrossingThreshold(NUMA       *nax,
+                            NUMA       *nay,
+                            l_float32   estthresh,
+                            l_float32  *pbestthresh)
+{
+l_int32    i, inrun, istart, iend, maxstart, maxend, runlen, maxrunlen;
+l_int32    val, maxval, nmax, count;
+l_float32  thresh, fmaxval, fmodeval;
+NUMA      *nat, *nac;
+
+    PROCNAME("numaSelectCrossingThreshold");
+
+    if (!pbestthresh)
+        return ERROR_INT("&bestthresh not defined", procName, 1);
+    *pbestthresh = 0.0;
+    if (!nay)
+        return ERROR_INT("nay not defined", procName, 1);
+
+        /* Compute the number of crossings for different thresholds */
+    nat = numaCreate(41);
+    for (i = 0; i < 41; i++) {
+        thresh = estthresh - 80.0 + 4.0 * i;
+        nac = numaCrossingsByThreshold(nax, nay, thresh);
+        numaAddNumber(nat, numaGetCount(nac));
+        numaDestroy(&nac);
+    }
+
+        /* Find the center of the plateau of max crossings, which
+         * extends from thresh[istart] to thresh[iend]. */
+    numaGetMax(nat, &fmaxval, NULL);
+    maxval = (l_int32)fmaxval;
+    nmax = 0;
+    for (i = 0; i < 41; i++) {
+        numaGetIValue(nat, i, &val);
+        if (val == maxval)
+            nmax++;
+    }
+    if (nmax < 3) {  /* likely accidental max; try the mode */
+        numaGetMode(nat, &fmodeval, &count);
+        if (count > nmax && fmodeval > 0.5 * fmaxval)
+            maxval = (l_int32)fmodeval;  /* use the mode */
+    }
+
+    inrun = FALSE;
+    iend = 40;
+    maxrunlen = 0;
+    for (i = 0; i < 41; i++) {
+        numaGetIValue(nat, i, &val);
+        if (val == maxval) {
+            if (!inrun) {
+                istart = i;
+                inrun = TRUE;
+            }
+            continue;
+        }
+        if (inrun && (val != maxval)) {
+            iend = i - 1;
+            runlen = iend - istart + 1;
+            inrun = FALSE;
+            if (runlen > maxrunlen) {
+                maxstart = istart;
+                maxend = iend;
+                maxrunlen = runlen;
+            }
+        }
+    }
+    if (inrun) {
+        runlen = i - istart;
+        if (runlen > maxrunlen) {
+            maxstart = istart;
+            maxend = i - 1;
+            maxrunlen = runlen;
+        }
+    }
+
+#if 0
+    foundfirst = FALSE;
+    iend = 40;
+    for (i = 0; i < 41; i++) {
+        numaGetIValue(nat, i, &val);
+        if (val == maxval) {
+            if (!foundfirst) {
+                istart = i;
+                foundfirst = TRUE;
+            }
+        }
+        if ((val != maxval) && foundfirst) {
+            iend = i - 1;
+            break;
+        }
+    }
+    nmax = iend - istart + 1;
+#endif
+
+    *pbestthresh = estthresh - 80.0 + 2.0 * (l_float32)(maxstart + maxend);
+
+#if  DEBUG_CROSSINGS
+    fprintf(stderr, "\nCrossings attain a maximum at %d thresholds, between:\n"
+                    "  thresh[%d] = %5.1f and thresh[%d] = %5.1f\n",
+                    nmax, maxstart, estthresh - 80.0 + 4.0 * maxstart,
+                    maxend, estthresh - 80.0 + 4.0 * maxend);
+    fprintf(stderr, "The best choice: %5.1f\n", *pbestthresh);
+    fprintf(stderr, "Number of crossings at the 41 thresholds:");
+    numaWriteStream(stderr, nat);
+#endif  /* DEBUG_CROSSINGS */
+
+    numaDestroy(&nat);
+    return 0;
+}
+
+
+/*!
+ *  numaCrossingsByThreshold()
+ *
+ *      Input:  nax (<optional> numa of abscissa values; can be NULL)
+ *              nay (numa of ordinate values, corresponding to nax)
+ *              thresh (threshold value for nay)
+ *      Return: nad (abscissa pts at threshold), or null on error
+ *
+ *  Notes:
+ *      (1) If nax == NULL, we use startx and delx from nay to compute
+ *          the crossing values in nad.
+ */
+NUMA *
+numaCrossingsByThreshold(NUMA      *nax,
+                         NUMA      *nay,
+                         l_float32  thresh)
+{
+l_int32    i, n;
+l_float32  startx, delx;
+l_float32  xval1, xval2, yval1, yval2, delta1, delta2, crossval, fract;
+NUMA      *nad;
+
+    PROCNAME("numaCrossingsByThreshold");
+
+    if (!nay)
+        return (NUMA *)ERROR_PTR("nay not defined", procName, NULL);
+    n = numaGetCount(nay);
+
+    if (nax && (numaGetCount(nax) != n))
+        return (NUMA *)ERROR_PTR("nax and nay sizes differ", procName, NULL);
+
+    nad = numaCreate(0);
+    numaGetFValue(nay, 0, &yval1);
+    numaGetParameters(nay, &startx, &delx);
+    if (nax)
+        numaGetFValue(nax, 0, &xval1);
+    else
+        xval1 = startx;
+    for (i = 1; i < n; i++) {
+        numaGetFValue(nay, i, &yval2);
+        if (nax)
+            numaGetFValue(nax, i, &xval2);
+        else
+            xval2 = startx + i * delx;
+        delta1 = yval1 - thresh;
+        delta2 = yval2 - thresh;
+        if (delta1 == 0.0) {
+            numaAddNumber(nad, xval1);
+        } else if (delta2 == 0.0) {
+            numaAddNumber(nad, xval2);
+        } else if (delta1 * delta2 < 0.0) {  /* crossing */
+            fract = L_ABS(delta1) / L_ABS(yval1 - yval2);
+            crossval = xval1 + fract * (xval2 - xval1);
+            numaAddNumber(nad, crossval);
+        }
+        xval1 = xval2;
+        yval1 = yval2;
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaCrossingsByPeaks()
+ *
+ *      Input:  nax (<optional> numa of abscissa values)
+ *              nay (numa of ordinate values, corresponding to nax)
+ *              delta (parameter used to identify when a new peak can be found)
+ *      Return: nad (abscissa pts at threshold), or null on error
+ *
+ *  Notes:
+ *      (1) If nax == NULL, we use startx and delx from nay to compute
+ *          the crossing values in nad.
+ */
+NUMA *
+numaCrossingsByPeaks(NUMA      *nax,
+                     NUMA      *nay,
+                     l_float32  delta)
+{
+l_int32    i, j, n, np, previndex, curindex;
+l_float32  startx, delx;
+l_float32  xval1, xval2, yval1, yval2, delta1, delta2;
+l_float32  prevval, curval, thresh, crossval, fract;
+NUMA      *nap, *nad;
+
+    PROCNAME("numaCrossingsByPeaks");
+
+    if (!nax)
+        return (NUMA *)ERROR_PTR("nax not defined", procName, NULL);
+    if (!nay)
+        return (NUMA *)ERROR_PTR("nay not defined", procName, NULL);
+
+    n = numaGetCount(nax);
+    if (numaGetCount(nay) != n)
+        return (NUMA *)ERROR_PTR("nax and nay sizes differ", procName, NULL);
+
+        /* Find the extrema.  Also add last point in nay to get
+         * the last transition (from the last peak to the end).
+         * The number of crossings is 1 more than the number of extrema. */
+    nap = numaFindExtrema(nay, delta);
+    numaAddNumber(nap, n - 1);
+    np = numaGetCount(nap);
+    L_INFO("Number of crossings: %d\n", procName, np);
+
+        /* Do all computation in index units of nax */
+    nad = numaCreate(np);  /* output crossings, in nax units */
+    previndex = 0;  /* prime the search with 1st point */
+    numaGetFValue(nay, 0, &prevval);  /* prime the search with 1st point */
+    numaGetParameters(nay, &startx, &delx);
+    for (i = 0; i < np; i++) {
+        numaGetIValue(nap, i, &curindex);
+        numaGetFValue(nay, curindex, &curval);
+        thresh = (prevval + curval) / 2.0;
+        if (nax)
+            numaGetFValue(nax, previndex, &xval1);
+        else
+            xval1 = startx + previndex * delx;
+        numaGetFValue(nay, previndex, &yval1);
+        for (j = previndex + 1; j <= curindex; j++) {
+            if (nax)
+                numaGetFValue(nax, j, &xval2);
+            else
+                xval2 = startx + j * delx;
+            numaGetFValue(nay, j, &yval2);
+            delta1 = yval1 - thresh;
+            delta2 = yval2 - thresh;
+            if (delta1 == 0.0) {
+                numaAddNumber(nad, xval1);
+                break;
+            } else if (delta2 == 0.0) {
+                numaAddNumber(nad, xval2);
+                break;
+            } else if (delta1 * delta2 < 0.0) {  /* crossing */
+                fract = L_ABS(delta1) / L_ABS(yval1 - yval2);
+                crossval = xval1 + fract * (xval2 - xval1);
+                numaAddNumber(nad, crossval);
+                break;
+            }
+            xval1 = xval2;
+            yval1 = yval2;
+        }
+        previndex = curindex;
+        prevval = curval;
+    }
+
+    numaDestroy(&nap);
+    return nad;
+}
+
+
+/*!
+ *  numaEvalBestHaarParameters()
+ *
+ *      Input:  nas (numa of non-negative signal values)
+ *              relweight (relative weight of (-1 comb) / (+1 comb)
+ *                         contributions to the 'convolution'.  In effect,
+ *                         the convolution kernel is a comb consisting of
+ *                         alternating +1 and -weight.)
+ *              nwidth (number of widths to consider)
+ *              nshift (number of shifts to consider for each width)
+ *              minwidth (smallest width to consider)
+ *              maxwidth (largest width to consider)
+ *              &bestwidth (<return> width giving largest score)
+ *              &bestshift (<return> shift giving largest score)
+ *              &bestscore (<optional return> convolution with
+ *                          "Haar"-like comb)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does a linear sweep of widths, evaluating at @nshift
+ *          shifts for each width, computing the score from a convolution
+ *          with a long comb, and finding the (width, shift) pair that
+ *          gives the maximum score.  The best width is the "half-wavelength"
+ *          of the signal.
+ *      (2) The convolving function is a comb of alternating values
+ *          +1 and -1 * relweight, separated by the width and phased by
+ *          the shift.  This is similar to a Haar transform, except
+ *          there the convolution is performed with a square wave.
+ *      (3) The function is useful for finding the line spacing
+ *          and strength of line signal from pixel sum projections.
+ *      (4) The score is normalized to the size of nas divided by
+ *          the number of half-widths.  For image applications, the input is
+ *          typically an array of pixel projections, so one should
+ *          normalize by dividing the score by the image width in the
+ *          pixel projection direction.
+ */
+l_int32
+numaEvalBestHaarParameters(NUMA       *nas,
+                           l_float32   relweight,
+                           l_int32     nwidth,
+                           l_int32     nshift,
+                           l_float32   minwidth,
+                           l_float32   maxwidth,
+                           l_float32  *pbestwidth,
+                           l_float32  *pbestshift,
+                           l_float32  *pbestscore)
+{
+l_int32    i, j;
+l_float32  delwidth, delshift, width, shift, score;
+l_float32  bestwidth, bestshift, bestscore;
+
+    PROCNAME("numaEvalBestHaarParameters");
+
+    if (pbestscore) *pbestscore = 0.0;
+    if (pbestwidth) *pbestwidth = 0.0;
+    if (pbestshift) *pbestshift = 0.0;
+    if (!pbestwidth || !pbestshift)
+        return ERROR_INT("&bestwidth and &bestshift not defined", procName, 1);
+    if (!nas)
+        return ERROR_INT("nas not defined", procName, 1);
+
+    bestscore = 0.0;
+    delwidth = (maxwidth - minwidth) / (nwidth - 1.0);
+    for (i = 0; i < nwidth; i++) {
+        width = minwidth + delwidth * i;
+        delshift = width / (l_float32)(nshift);
+        for (j = 0; j < nshift; j++) {
+            shift = j * delshift;
+            numaEvalHaarSum(nas, width, shift, relweight, &score);
+            if (score > bestscore) {
+                bestscore = score;
+                bestwidth = width;
+                bestshift = shift;
+#if  DEBUG_FREQUENCY
+                fprintf(stderr, "width = %7.3f, shift = %7.3f, score = %7.3f\n",
+                        width, shift, score);
+#endif  /* DEBUG_FREQUENCY */
+            }
+        }
+    }
+
+    *pbestwidth = bestwidth;
+    *pbestshift = bestshift;
+    if (pbestscore)
+        *pbestscore = bestscore;
+    return 0;
+}
+
+
+/*!
+ *  numaEvalHaarSum()
+ *
+ *      Input:  nas (numa of non-negative signal values)
+ *              width (distance between +1 and -1 in convolution comb)
+ *              shift (phase of the comb: location of first +1)
+ *              relweight (relative weight of (-1 comb) / (+1 comb)
+ *                         contributions to the 'convolution'.  In effect,
+ *                         the convolution kernel is a comb consisting of
+ *                         alternating +1 and -weight.)
+ *              &score (<return> convolution with "Haar"-like comb)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does a convolution with a comb of alternating values
+ *          +1 and -relweight, separated by the width and phased by the shift.
+ *          This is similar to a Haar transform, except that for Haar,
+ *            (1) the convolution kernel is symmetric about 0, so the
+ *                relweight is 1.0, and
+ *            (2) the convolution is performed with a square wave.
+ *      (2) The score is normalized to the size of nas divided by
+ *          twice the "width".  For image applications, the input is
+ *          typically an array of pixel projections, so one should
+ *          normalize by dividing the score by the image width in the
+ *          pixel projection direction.
+ *      (3) To get a Haar-like result, use relweight = 1.0.  For detecting
+ *          signals where you expect every other sample to be close to
+ *          zero, as with barcodes or filtered text lines, you can
+ *          use relweight > 1.0.
+ */
+l_int32
+numaEvalHaarSum(NUMA       *nas,
+                l_float32   width,
+                l_float32   shift,
+                l_float32   relweight,
+                l_float32  *pscore)
+{
+l_int32    i, n, nsamp, index;
+l_float32  score, weight, val;
+
+    PROCNAME("numaEvalHaarSum");
+
+    if (!pscore)
+        return ERROR_INT("&score not defined", procName, 1);
+    *pscore = 0.0;
+    if (!nas)
+        return ERROR_INT("nas not defined", procName, 1);
+    if ((n = numaGetCount(nas)) < 2 * width)
+        return ERROR_INT("nas size too small", procName, 1);
+
+    score = 0.0;
+    nsamp = (l_int32)((n - shift) / width);
+    for (i = 0; i < nsamp; i++) {
+        index = (l_int32)(shift + i * width);
+        weight = (i % 2) ? 1.0 : -1.0 * relweight;
+        numaGetFValue(nas, index, &val);
+        score += weight * val;
+    }
+
+    *pscore = 2.0 * width * score / (l_float32)n;
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *            Generating numbers in a range under constraints           *
+ *----------------------------------------------------------------------*/
+/*!
+ *  genConstrainedNumaInRange()
+ *
+ *      Input:  first (first number to choose; >= 0)
+ *              last (biggest possible number to reach; >= first)
+ *              nmax (maximum number of numbers to select; > 0)
+ *              use_pairs (1 = select pairs of adjacent numbers;
+ *                         0 = select individual numbers)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Note:
+ *     (1) Selection is made uniformly in the range.  This can be used
+ *         to select pages distributed as uniformly as possible
+ *         through a book, where you are constrained to:
+ *          - choose between [first, ... biggest],
+ *          - choose no more than nmax numbers, and
+ *         and you have the option of requiring pairs of adjacent numbers.
+ */
+NUMA *
+genConstrainedNumaInRange(l_int32  first,
+                          l_int32  last,
+                          l_int32  nmax,
+                          l_int32  use_pairs)
+{
+l_int32    i, nsets, val;
+l_float32  delta;
+NUMA      *na;
+
+    PROCNAME("genConstrainedNumaInRange");
+
+    first = L_MAX(0, first);
+    if (last < first)
+        return (NUMA *)ERROR_PTR("last < first!", procName, NULL);
+    if (nmax < 1)
+        return (NUMA *)ERROR_PTR("nmax < 1!", procName, NULL);
+
+    nsets = L_MIN(nmax, last - first + 1);
+    if (use_pairs == 1)
+        nsets = nsets / 2;
+    if (nsets == 0)
+        return (NUMA *)ERROR_PTR("nsets == 0", procName, NULL);
+
+        /* Select delta so that selection covers the full range if possible */
+    if (nsets == 1) {
+        delta = 0.0;
+    } else {
+        if (use_pairs == 0)
+            delta = (l_float32)(last - first) / (nsets - 1);
+        else
+            delta = (l_float32)(last - first - 1) / (nsets - 1);
+    }
+
+    na = numaCreate(nsets);
+    for (i = 0; i < nsets; i++) {
+        val = (l_int32)(first + i * delta + 0.5);
+        numaAddNumber(na, val);
+        if (use_pairs == 1)
+            numaAddNumber(na, val + 1);
+    }
+
+    return na;
+}
+
+
diff --git a/src/pageseg.c b/src/pageseg.c
new file mode 100644 (file)
index 0000000..b608989
--- /dev/null
@@ -0,0 +1,1255 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   pageseg.c
+ *
+ *      Top level page segmentation
+ *          l_int32   pixGetRegionsBinary()
+ *
+ *      Halftone region extraction
+ *          PIX      *pixGenHalftoneMask()
+ *
+ *      Textline extraction
+ *          PIX      *pixGenTextlineMask()
+ *
+ *      Textblock extraction
+ *          PIX      *pixGenTextblockMask()
+ *
+ *      Location of page foreground
+ *          PIX      *pixFindPageForeground()
+ *
+ *      Extraction of characters from image with only text
+ *          l_int32   pixSplitIntoCharacters()
+ *          BOXA     *pixSplitComponentWithProfile()
+ *
+ *      Extraction of lines of text
+ *          PIXA     *pixExtractTextlines()
+ *
+ *      Decision text vs photo
+ *          l_int32   pixDecideIfText()
+ *          l_int32   pixFindThreshFgExtent()
+ */
+
+#include "allheaders.h"
+
+    /* These functions are not intended to work on very low-res images */
+static const l_int32  MinWidth = 100;
+static const l_int32  MinHeight = 100;
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_LINES     0
+#endif  /* ~NO_CONSOLE_IO */
+
+/*------------------------------------------------------------------*
+ *                     Top level page segmentation                  *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixGetRegionsBinary()
+ *
+ *      Input:  pixs (1 bpp, assumed to be 300 to 400 ppi)
+ *              &pixhm (<optional return> halftone mask)
+ *              &pixtm (<optional return> textline mask)
+ *              &pixtb (<optional return> textblock mask)
+ *              debug (flag: set to 1 for debug output)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) It is best to deskew the image before segmenting.
+ *      (2) The debug flag enables a number of outputs.  These
+ *          are included to show how to generate and save/display
+ *          these results.
+ */
+l_int32
+pixGetRegionsBinary(PIX     *pixs,
+                    PIX    **ppixhm,
+                    PIX    **ppixtm,
+                    PIX    **ppixtb,
+                    l_int32  debug)
+{
+l_int32  w, h, htfound, tlfound;
+PIX     *pixr, *pix1, *pix2;
+PIX     *pixtext;  /* text pixels only */
+PIX     *pixhm2;   /* halftone mask; 2x reduction */
+PIX     *pixhm;    /* halftone mask;  */
+PIX     *pixtm2;   /* textline mask; 2x reduction */
+PIX     *pixtm;    /* textline mask */
+PIX     *pixvws;   /* vertical white space mask */
+PIX     *pixtb2;   /* textblock mask; 2x reduction */
+PIX     *pixtbf2;  /* textblock mask; 2x reduction; small comps filtered */
+PIX     *pixtb;    /* textblock mask */
+
+    PROCNAME("pixGetRegionsBinary");
+
+    if (ppixhm) *ppixhm = NULL;
+    if (ppixtm) *ppixtm = NULL;
+    if (ppixtb) *ppixtb = NULL;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs undefined or not 1 bpp", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (w < MinWidth || h < MinHeight) {
+        L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
+        return 1;
+    }
+
+        /* 2x reduce, to 150 -200 ppi */
+    pixr = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
+    pixDisplayWrite(pixr, debug);
+
+        /* Get the halftone mask */
+    pixhm2 = pixGenHalftoneMask(pixr, &pixtext, &htfound, debug);
+
+        /* Get the textline mask from the text pixels */
+    pixtm2 = pixGenTextlineMask(pixtext, &pixvws, &tlfound, debug);
+
+        /* Get the textblock mask from the textline mask */
+    pixtb2 = pixGenTextblockMask(pixtm2, pixvws, debug);
+    pixDestroy(&pixr);
+    pixDestroy(&pixtext);
+    pixDestroy(&pixvws);
+
+        /* Remove small components from the mask, where a small
+         * component is defined as one with both width and height < 60 */
+    pixtbf2 = pixSelectBySize(pixtb2, 60, 60, 4, L_SELECT_IF_EITHER,
+                              L_SELECT_IF_GTE, NULL);
+    pixDestroy(&pixtb2);
+    pixDisplayWriteFormat(pixtbf2, debug, IFF_PNG);
+
+        /* Expand all masks to full resolution, and do filling or
+         * small dilations for better coverage. */
+    pixhm = pixExpandReplicate(pixhm2, 2);
+    pix1 = pixSeedfillBinary(NULL, pixhm, pixs, 8);
+    pixOr(pixhm, pixhm, pix1);
+    pixDestroy(&pix1);
+    pixDisplayWriteFormat(pixhm, debug, IFF_PNG);
+
+    pix1 = pixExpandReplicate(pixtm2, 2);
+    pixtm = pixDilateBrick(NULL, pix1, 3, 3);
+    pixDestroy(&pix1);
+    pixDisplayWriteFormat(pixtm, debug, IFF_PNG);
+
+    pix1 = pixExpandReplicate(pixtbf2, 2);
+    pixtb = pixDilateBrick(NULL, pix1, 3, 3);
+    pixDestroy(&pix1);
+    pixDisplayWriteFormat(pixtb, debug, IFF_PNG);
+
+    pixDestroy(&pixhm2);
+    pixDestroy(&pixtm2);
+    pixDestroy(&pixtbf2);
+
+        /* Debug: identify objects that are neither text nor halftone image */
+    if (debug) {
+        pix1 = pixSubtract(NULL, pixs, pixtm);  /* remove text pixels */
+        pix2 = pixSubtract(NULL, pix1, pixhm);  /* remove halftone pixels */
+        pixDisplayWriteFormat(pix2, 1, IFF_PNG);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+        /* Debug: display textline components with random colors */
+    if (debug) {
+        l_int32  w, h;
+        BOXA    *boxa;
+        PIXA    *pixa;
+        boxa = pixConnComp(pixtm, &pixa, 8);
+        pixGetDimensions(pixtm, &w, &h, NULL);
+        pix1 = pixaDisplayRandomCmap(pixa, w, h);
+        pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
+        pixDisplay(pix1, 100, 100);
+        pixDisplayWriteFormat(pix1, 1, IFF_PNG);
+        pixaDestroy(&pixa);
+        boxaDestroy(&boxa);
+        pixDestroy(&pix1);
+    }
+
+        /* Debug: identify the outlines of each textblock */
+    if (debug) {
+        PIXCMAP  *cmap;
+        PTAA     *ptaa;
+        ptaa = pixGetOuterBordersPtaa(pixtb);
+        lept_mkdir("pageseg");
+        ptaaWrite("/tmp/pageseg/tb_outlines.ptaa", ptaa, 1);
+        pix1 = pixRenderRandomCmapPtaa(pixtb, ptaa, 1, 16, 1);
+        cmap = pixGetColormap(pix1);
+        pixcmapResetColor(cmap, 0, 130, 130, 130);
+        pixDisplay(pix1, 500, 100);
+        pixDisplayWriteFormat(pix1, 1, IFF_PNG);
+        pixDestroy(&pix1);
+        ptaaDestroy(&ptaa);
+    }
+
+        /* Debug: get b.b. for all mask components */
+    if (debug) {
+        BOXA  *bahm, *batm, *batb;
+        bahm = pixConnComp(pixhm, NULL, 4);
+        batm = pixConnComp(pixtm, NULL, 4);
+        batb = pixConnComp(pixtb, NULL, 4);
+        boxaWrite("/tmp/pageseg/htmask.boxa", bahm);
+        boxaWrite("/tmp/pageseg/textmask.boxa", batm);
+        boxaWrite("/tmp/pageseg/textblock.boxa", batb);
+        boxaDestroy(&bahm);
+        boxaDestroy(&batm);
+        boxaDestroy(&batb);
+    }
+
+    if (ppixhm)
+        *ppixhm = pixhm;
+    else
+        pixDestroy(&pixhm);
+    if (ppixtm)
+        *ppixtm = pixtm;
+    else
+        pixDestroy(&pixtm);
+    if (ppixtb)
+        *ppixtb = pixtb;
+    else
+        pixDestroy(&pixtb);
+
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *                    Halftone region extraction                    *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixGenHalftoneMask()
+ *
+ *      Input:  pixs (1 bpp, assumed to be 150 to 200 ppi)
+ *              &pixtext (<optional return> text part of pixs)
+ *              &htfound (<optional return> 1 if the mask is not empty)
+ *              debug (flag: 1 for debug output)
+ *      Return: pixd (halftone mask), or null on error
+ *
+ *  Notes:
+ *      (1) This is not intended to work on small thumbnails.  The
+ *          dimensions of pixs must be at least MinWidth x MinHeight.
+ */
+PIX *
+pixGenHalftoneMask(PIX      *pixs,
+                   PIX     **ppixtext,
+                   l_int32  *phtfound,
+                   l_int32   debug)
+{
+l_int32  w, h, empty;
+PIX     *pix1, *pix2, *pixhs, *pixhm, *pixd;
+
+    PROCNAME("pixGenHalftoneMask");
+
+    if (ppixtext) *ppixtext = NULL;
+    if (phtfound) *phtfound = 0;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (w < MinWidth || h < MinHeight) {
+        L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
+        return NULL;
+    }
+
+        /* Compute seed for halftone parts at 8x reduction */
+    pix1 = pixReduceRankBinaryCascade(pixs, 4, 4, 3, 0);
+    pix2 = pixOpenBrick(NULL, pix1, 5, 5);
+    pixhs = pixExpandReplicate(pix2, 8);  /* back to 2x reduction */
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    pixDisplayWriteFormat(pixhs, debug, IFF_PNG);
+
+        /* Compute mask for connected regions */
+    pixhm = pixCloseSafeBrick(NULL, pixs, 4, 4);
+    pixDisplayWriteFormat(pixhm, debug, IFF_PNG);
+
+        /* Fill seed into mask to get halftone mask */
+    pixd = pixSeedfillBinary(NULL, pixhs, pixhm, 4);
+
+#if 0
+        /* Moderate opening to remove thin lines, etc. */
+    pixOpenBrick(pixd, pixd, 10, 10);
+    pixDisplayWrite(pixd, debug);
+#endif
+
+        /* Check if mask is empty */
+    pixZero(pixd, &empty);
+    if (phtfound && !empty)
+        *phtfound = 1;
+
+        /* Optionally, get all pixels that are not under the halftone mask */
+    if (ppixtext) {
+        if (empty)
+            *ppixtext = pixCopy(NULL, pixs);
+        else
+            *ppixtext = pixSubtract(NULL, pixs, pixd);
+        pixDisplayWriteFormat(*ppixtext, debug, IFF_PNG);
+    }
+
+    pixDestroy(&pixhs);
+    pixDestroy(&pixhm);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                         Textline extraction                      *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixGenTextlineMask()
+ *
+ *      Input:  pixs (1 bpp, assumed to be 150 to 200 ppi)
+ *              &pixvws (<return> vertical whitespace mask)
+ *              &tlfound (<optional return> 1 if the mask is not empty)
+ *              debug (flag: 1 for debug output)
+ *      Return: pixd (textline mask), or null on error
+ *
+ *  Notes:
+ *      (1) The input pixs should be deskewed.
+ *      (2) pixs should have no halftone pixels.
+ *      (3) This is not intended to work on small thumbnails.  The
+ *          dimensions of pixs must be at least MinWidth x MinHeight.
+ *      (4) Both the input image and the returned textline mask
+ *          are at the same resolution.
+ */
+PIX *
+pixGenTextlineMask(PIX      *pixs,
+                   PIX     **ppixvws,
+                   l_int32  *ptlfound,
+                   l_int32   debug)
+{
+l_int32  w, h, empty;
+PIX     *pix1, *pix2, *pixvws, *pixd;
+
+    PROCNAME("pixGenTextlineMask");
+
+    if (ptlfound) *ptlfound = 0;
+    if (!ppixvws)
+        return (PIX *)ERROR_PTR("&pixvws not defined", procName, NULL);
+    *ppixvws = NULL;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (w < MinWidth || h < MinHeight) {
+        L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
+        return NULL;
+    }
+
+        /* First we need a vertical whitespace mask.  Invert the image. */
+    pix1 = pixInvert(NULL, pixs);
+
+        /* The whitespace mask will break textlines where there
+         * is a large amount of white space below or above.
+         * This can be prevented by identifying regions of the
+         * inverted image that have large horizontal extent (bigger than
+         * the separation between columns) and significant
+         * vertical extent (bigger than the separation between
+         * textlines), and subtracting this from the bg. */
+    pix2 = pixMorphCompSequence(pix1, "o80.60", 0);
+    pixSubtract(pix1, pix1, pix2);
+    pixDisplayWriteFormat(pix1, debug, IFF_PNG);
+    pixDestroy(&pix2);
+
+        /* Identify vertical whitespace by opening the remaining bg.
+         * o5.1 removes thin vertical bg lines and o1.200 extracts
+         * long vertical bg lines. */
+    pixvws = pixMorphCompSequence(pix1, "o5.1 + o1.200", 0);
+    *ppixvws = pixvws;
+    pixDisplayWriteFormat(pixvws, debug, IFF_PNG);
+    pixDestroy(&pix1);
+
+        /* Three steps to getting text line mask:
+         *   (1) close the characters and words in the textlines
+         *   (2) open the vertical whitespace corridors back up
+         *   (3) small opening to remove noise    */
+    pix1 = pixCloseSafeBrick(NULL, pixs, 30, 1);
+    pixDisplayWrite(pix1, debug);
+    pixd = pixSubtract(NULL, pix1, pixvws);
+    pixOpenBrick(pixd, pixd, 3, 3);
+    pixDisplayWriteFormat(pixd, debug, IFF_PNG);
+    pixDestroy(&pix1);
+
+        /* Check if text line mask is empty */
+    if (ptlfound) {
+        pixZero(pixd, &empty);
+        if (!empty)
+            *ptlfound = 1;
+    }
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                       Textblock extraction                       *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixGenTextblockMask()
+ *
+ *      Input:  pixs (1 bpp, textline mask, assumed to be 150 to 200 ppi)
+ *              pixvws (vertical white space mask)
+ *              debug (flag: 1 for debug output)
+ *      Return: pixd (textblock mask), or null on error
+ *
+ *  Notes:
+ *      (1) Both the input masks (textline and vertical white space) and
+ *          the returned textblock mask are at the same resolution.
+ *      (2) This is not intended to work on small thumbnails.  The
+ *          dimensions of pixs must be at least MinWidth x MinHeight.
+ *      (3) The result is somewhat noisy, in that small "blocks" of
+ *          text may be included.  These can be removed by post-processing,
+ *          using, e.g.,
+ *             pixSelectBySize(pix, 60, 60, 4, L_SELECT_IF_EITHER,
+ *                             L_SELECT_IF_GTE, NULL);
+ */
+PIX *
+pixGenTextblockMask(PIX     *pixs,
+                    PIX     *pixvws,
+                    l_int32  debug)
+{
+l_int32  w, h;
+PIX     *pix1, *pix2, *pix3, *pixd;
+
+    PROCNAME("pixGenTextblockMask");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (w < MinWidth || h < MinHeight) {
+        L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
+        return NULL;
+    }
+    if (!pixvws)
+        return (PIX *)ERROR_PTR("pixvws not defined", procName, NULL);
+
+        /* Join pixels vertically to make a textblock mask */
+    pix1 = pixMorphSequence(pixs, "c1.10 + o4.1", 0);
+    pixDisplayWriteFormat(pix1, debug, IFF_PNG);
+
+        /* Solidify the textblock mask and remove noise:
+         *   (1) For each cc, close the blocks and dilate slightly
+         *       to form a solid mask.
+         *   (2) Small horizontal closing between components.
+         *   (3) Open the white space between columns, again.
+         *   (4) Remove small components. */
+    pix2 = pixMorphSequenceByComponent(pix1, "c30.30 + d3.3", 8, 0, 0, NULL);
+    pixCloseSafeBrick(pix2, pix2, 10, 1);
+    pixDisplayWriteFormat(pix2, debug, IFF_PNG);
+    pix3 = pixSubtract(NULL, pix2, pixvws);
+    pixDisplayWriteFormat(pix3, debug, IFF_PNG);
+    pixd = pixSelectBySize(pix3, 25, 5, 8, L_SELECT_IF_BOTH,
+                            L_SELECT_IF_GTE, NULL);
+    pixDisplayWriteFormat(pixd, debug, IFF_PNG);
+
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    pixDestroy(&pix3);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                    Location of page foreground                   *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixFindPageForeground()
+ *
+ *      Input:  pixs (full resolution (any type or depth)
+ *              threshold (for binarization; typically about 128)
+ *              mindist (min distance of text from border to allow
+ *                       cleaning near border; at 2x reduction, this
+ *                       should be larger than 50; typically about 70)
+ *              erasedist (when conditions are satisfied, erase anything
+ *                         within this distance of the edge;
+ *                         typically 30 at 2x reduction)
+ *              pagenum (use for debugging when called repeatedly; labels
+ *                       debug images that are assembled into pdfdir)
+ *              showmorph (set to a negative integer to show steps in
+ *                         generating masks; this is typically used
+ *                         for debugging region extraction)
+ *              display (set to 1  to display mask and selected region
+ *                       for debugging a single page)
+ *              pdfdir (subdirectory of /tmp where images showing the
+ *                      result are placed when called repeatedly; use
+ *                      null if no output requested)
+ *      Return: box (region including foreground, with some pixel noise
+ *                   removed), or null if not found
+ *
+ *  Notes:
+ *      (1) This doesn't simply crop to the fg.  It attempts to remove
+ *          pixel noise and junk at the edge of the image before cropping.
+ *          The input @threshold is used if pixs is not 1 bpp.
+ *      (2) There are several debugging options, determined by the
+ *          last 4 arguments.
+ *      (3) This is not intended to work on small thumbnails.  The
+ *          dimensions of pixs must be at least MinWidth x MinHeight.
+ *      (4) If you want pdf output of results when called repeatedly,
+ *          the pagenum arg labels the images written, which go into
+ *          /tmp/lept/<pdfdir>/<pagenum>.png.  In that case,
+ *          you would clean out the /tmp directory before calling this
+ *          function on each page:
+ *              lept_rmdir("/lept/<pdfdir>");
+ *              lept_mkdir("/lept/<pdfdir>");
+ */
+BOX *
+pixFindPageForeground(PIX         *pixs,
+                      l_int32      threshold,
+                      l_int32      mindist,
+                      l_int32      erasedist,
+                      l_int32      pagenum,
+                      l_int32      showmorph,
+                      l_int32      display,
+                      const char  *pdfdir)
+{
+char     buf[64];
+l_int32  flag, nbox, intersects;
+l_int32  w, h, bx, by, bw, bh, left, right, top, bottom;
+PIX     *pixb, *pixb2, *pixseed, *pixsf, *pixm, *pix1, *pixg2;
+BOX     *box, *boxfg, *boxin, *boxd;
+BOXA    *ba1, *ba2;
+
+    PROCNAME("pixFindPageForeground");
+
+    if (!pixs)
+        return (BOX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (w < MinWidth || h < MinHeight) {
+        L_ERROR("pix too small: w = %d, h = %d\n", procName, w, h);
+        return NULL;
+    }
+
+        /* Binarize, downscale by 0.5, remove the noise to generate a seed,
+         * and do a seedfill back from the seed into those 8-connected
+         * components of the binarized image for which there was at least
+         * one seed pixel.  Also clear out any components that are within
+         * 10 pixels of the edge at 2x reduction. */
+    flag = (showmorph) ? -1 : 0;  /* if showmorph == -1, write intermediate
+                                   * images to /tmp/seq_output_1.pdf */
+    pixb = pixConvertTo1(pixs, threshold);
+    pixb2 = pixScale(pixb, 0.5, 0.5);
+    pixseed = pixMorphSequence(pixb2, "o1.2 + c9.9 + o3.5", flag);
+    pixsf = pixSeedfillBinary(NULL, pixseed, pixb2, 8);
+    pixSetOrClearBorder(pixsf, 10, 10, 10, 10, PIX_SET);
+    pixm = pixRemoveBorderConnComps(pixsf, 8);
+    if (display) pixDisplay(pixm, 100, 100);
+
+        /* Now, where is the main block of text?  We want to remove noise near
+         * the edge of the image, but to do that, we have to be convinced that
+         * (1) there is noise and (2) it is far enough from the text block
+         * and close enough to the edge.  For each edge, if the block
+         * is more than mindist from that edge, then clean 'erasedist'
+         * pixels from the edge. */
+    pix1 = pixMorphSequence(pixm, "c50.50", flag - 1);
+    ba1 = pixConnComp(pix1, NULL, 8);
+    ba2 = boxaSort(ba1, L_SORT_BY_AREA, L_SORT_DECREASING, NULL);
+    pixGetDimensions(pix1, &w, &h, NULL);
+    nbox = boxaGetCount(ba2);
+    if (nbox > 1) {
+        box = boxaGetBox(ba2, 0, L_CLONE);
+        boxGetGeometry(box, &bx, &by, &bw, &bh);
+        left = (bx > mindist) ? erasedist : 0;
+        right = (w - bx - bw > mindist) ? erasedist : 0;
+        top = (by > mindist) ? erasedist : 0;
+        bottom = (h - by - bh > mindist) ? erasedist : 0;
+        pixSetOrClearBorder(pixm, left, right, top, bottom, PIX_CLR);
+        boxDestroy(&box);
+    }
+    pixDestroy(&pix1);
+    boxaDestroy(&ba1);
+    boxaDestroy(&ba2);
+
+        /* Locate the foreground region; don't bother cropping */
+    pixClipToForeground(pixm, NULL, &boxfg);
+
+        /* Sanity check the fg region.  Make sure it's not confined
+         * to a thin boundary on the left and right sides of the image,
+         * in which case it is likely to be noise. */
+    if (boxfg) {
+        boxin = boxCreate(0.1 * w, 0, 0.8 * w, h);
+        boxIntersects(boxfg, boxin, &intersects);
+        if (!intersects) {
+            L_INFO("found only noise on page %d\n", procName, pagenum);
+            boxDestroy(&boxfg);
+        }
+        boxDestroy(&boxin);
+    }
+
+    boxd = NULL;
+    if (!boxfg) {
+        L_INFO("no fg region found for page %d\n", procName, pagenum);
+    } else {
+        boxAdjustSides(boxfg, boxfg, -2, 2, -2, 2);  /* tiny expansion */
+        boxd = boxTransform(boxfg, 0, 0, 2.0, 2.0);
+
+            /* Write image showing box for this page.  This is to be
+             * bundled up into a pdf of all the pages, which can be
+             * generated by convertFilesToPdf()  */
+        if (pdfdir) {
+            snprintf(buf, sizeof(buf), "lept/%s", pdfdir);
+            lept_mkdir(buf);
+
+            pixg2 = pixConvert1To4Cmap(pixb);
+            pixRenderBoxArb(pixg2, boxd, 3, 255, 0, 0);
+            snprintf(buf, sizeof(buf), "/tmp/lept/%s/%04d.png",
+                     pdfdir, pagenum);
+            if (display) pixDisplay(pixg2, 700, 100);
+            pixWrite(buf, pixg2, IFF_PNG);
+            pixDestroy(&pixg2);
+        }
+    }
+
+    pixDestroy(&pixb);
+    pixDestroy(&pixb2);
+    pixDestroy(&pixseed);
+    pixDestroy(&pixsf);
+    pixDestroy(&pixm);
+    boxDestroy(&boxfg);
+    return boxd;
+}
+
+
+/*------------------------------------------------------------------*
+ *         Extraction of characters from image with only text       *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixSplitIntoCharacters()
+ *
+ *      Input:  pixs (1 bpp, contains only deskewed text)
+ *              minw (minimum component width for initial filtering; typ. 4)
+ *              minh (minimum component height for initial filtering; typ. 4)
+ *              &boxa (<optional return> character bounding boxes)
+ *              &pixa (<optional return> character images)
+ *              &pixdebug (<optional return> showing splittings)
+ *
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a simple function that attempts to find split points
+ *          based on vertical pixel profiles.
+ *      (2) It should be given an image that has an arbitrary number
+ *          of text characters.
+ *      (3) The returned pixa includes the boxes from which the
+ *          (possibly split) components are extracted.
+ */
+l_int32
+pixSplitIntoCharacters(PIX     *pixs,
+                       l_int32  minw,
+                       l_int32  minh,
+                       BOXA   **pboxa,
+                       PIXA   **ppixa,
+                       PIX    **ppixdebug)
+{
+l_int32  ncomp, i, xoff, yoff;
+BOXA   *boxa1, *boxa2, *boxat1, *boxat2, *boxad;
+BOXAA  *baa;
+PIX    *pix, *pix1, *pix2, *pixdb;
+PIXA   *pixa1, *pixadb;
+
+    PROCNAME("pixSplitIntoCharacters");
+
+    if (pboxa) *pboxa = NULL;
+    if (ppixa) *ppixa = NULL;
+    if (ppixdebug) *ppixdebug = NULL;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+        /* Remove the small stuff */
+    pix1 = pixSelectBySize(pixs, minw, minh, 8, L_SELECT_IF_BOTH,
+                           L_SELECT_IF_GT, NULL);
+
+        /* Small vertical close for consolidation */
+    pix2 = pixMorphSequence(pix1, "c1.10", 0);
+    pixDestroy(&pix1);
+
+        /* Get the 8-connected components */
+    boxa1 = pixConnComp(pix2, &pixa1, 8);
+    pixDestroy(&pix2);
+    boxaDestroy(&boxa1);
+
+        /* Split the components if obvious */
+    ncomp = pixaGetCount(pixa1);
+    boxa2 = boxaCreate(ncomp);
+    pixadb = (ppixdebug) ? pixaCreate(ncomp) : NULL;
+    for (i = 0; i < ncomp; i++) {
+        pix = pixaGetPix(pixa1, i, L_CLONE);
+        if (ppixdebug) {
+            boxat1 = pixSplitComponentWithProfile(pix, 10, 7, &pixdb);
+            if (pixdb)
+                pixaAddPix(pixadb, pixdb, L_INSERT);
+        } else {
+            boxat1 = pixSplitComponentWithProfile(pix, 10, 7, NULL);
+        }
+        pixaGetBoxGeometry(pixa1, i, &xoff, &yoff, NULL, NULL);
+        boxat2 = boxaTransform(boxat1, xoff, yoff, 1.0, 1.0);
+        boxaJoin(boxa2, boxat2, 0, -1);
+        pixDestroy(&pix);
+        boxaDestroy(&boxat1);
+        boxaDestroy(&boxat2);
+    }
+    pixaDestroy(&pixa1);
+
+        /* Generate the debug image */
+    if (ppixdebug) {
+        if (pixaGetCount(pixadb) > 0) {
+            *ppixdebug = pixaDisplayTiledInRows(pixadb, 32, 1500,
+                                                1.0, 0, 20, 1);
+        }
+        pixaDestroy(&pixadb);
+    }
+
+        /* Do a 2D sort on the bounding boxes, and flatten the result to 1D */
+    baa = boxaSort2d(boxa2, NULL, 0, 0, 5);
+    boxad = boxaaFlattenToBoxa(baa, NULL, L_CLONE);
+    boxaaDestroy(&baa);
+    boxaDestroy(&boxa2);
+
+        /* Optionally extract the pieces from the input image */
+    if (ppixa)
+        *ppixa = pixClipRectangles(pixs, boxad);
+    if (pboxa)
+        *pboxa = boxad;
+    else
+        boxaDestroy(&boxad);
+    return 0;
+}
+
+
+/*!
+ *  pixSplitComponentWithProfile()
+ *
+ *      Input:  pixs (1 bpp, exactly one connected component)
+ *              delta (distance used in extrema finding in a numa; typ. 10)
+ *              mindel (minimum required difference between profile minimum
+ *                      and profile values +2 and -2 away; typ. 7)
+ *              &pixdebug (<optional return> debug image of splitting)
+ *      Return: boxa (of c.c. after splitting), or null on error
+ *
+ *  Notes:
+ *      (1) This will split the most obvious cases of touching characters.
+ *          The split points it is searching for are narrow and deep
+ *          minimima in the vertical pixel projection profile, after a
+ *          large vertical closing has been applied to the component.
+ */
+BOXA *
+pixSplitComponentWithProfile(PIX     *pixs,
+                             l_int32  delta,
+                             l_int32  mindel,
+                             PIX    **ppixdebug)
+{
+l_int32   w, h, n2, i, firstmin, xmin, xshift;
+l_int32   nmin, nleft, nright, nsplit, isplit, ncomp;
+l_int32  *array1, *array2;
+BOX      *box;
+BOXA     *boxad;
+NUMA     *na1, *na2, *nasplit;
+PIX      *pix1, *pixdb;
+
+    PROCNAME("pixSplitComponentsWithProfile");
+
+    if (ppixdebug) *ppixdebug = NULL;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (BOXA *)ERROR_PTR("pixa undefined or not 1 bpp", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+
+        /* Closing to consolidate characters vertically */
+    pix1 = pixCloseSafeBrick(NULL, pixs, 1, 100);
+
+        /* Get extrema of column projections */
+    boxad = boxaCreate(2);
+    na1 = pixCountPixelsByColumn(pix1);  /* w elements */
+    pixDestroy(&pix1);
+    na2 = numaFindExtrema(na1, delta);
+    n2 = numaGetCount(na2);
+    if (n2 < 3) {  /* no split possible */
+        box = boxCreate(0, 0, w, h);
+        boxaAddBox(boxad, box, L_INSERT);
+        numaDestroy(&na1);
+        numaDestroy(&na2);
+        return boxad;
+    }
+
+        /* Look for sufficiently deep and narrow minima.
+         * All minima of of interest must be surrounded by max on each
+         * side.  firstmin is the index of first possible minimum. */
+    array1 = numaGetIArray(na1);
+    array2 = numaGetIArray(na2);
+    if (ppixdebug) numaWriteStream(stderr, na2);
+    firstmin = (array1[array2[0]] > array1[array2[1]]) ? 1 : 2;
+    nasplit = numaCreate(n2);  /* will hold split locations */
+    for (i = firstmin; i < n2 - 1; i+= 2) {
+        xmin = array2[i];
+        nmin = array1[xmin];
+        if (xmin + 2 >= w) break;  /* no more splits possible */
+        nleft = array1[xmin - 2];
+        nright = array1[xmin + 2];
+        if (ppixdebug) {
+            fprintf(stderr,
+                "Splitting: xmin = %d, w = %d; nl = %d, nmin = %d, nr = %d\n",
+                xmin, w, nleft, nmin, nright);
+        }
+        if (nleft - nmin >= mindel && nright - nmin >= mindel)  /* split */
+            numaAddNumber(nasplit, xmin);
+    }
+    nsplit = numaGetCount(nasplit);
+
+#if 0
+    if (ppixdebug && nsplit > 0)
+        gplotSimple1(na1, GPLOT_X11, "/tmp/splitroot", NULL);
+#endif
+
+    numaDestroy(&na1);
+    numaDestroy(&na2);
+    LEPT_FREE(array1);
+    LEPT_FREE(array2);
+
+    if (nsplit == 0) {  /* no splitting */
+        box = boxCreate(0, 0, w, h);
+        boxaAddBox(boxad, box, L_INSERT);
+        return boxad;
+    }
+
+        /* Use split points to generate b.b. after splitting */
+    for (i = 0, xshift = 0; i < nsplit; i++) {
+        numaGetIValue(nasplit, i, &isplit);
+        box = boxCreate(xshift, 0, isplit - xshift, h);
+        boxaAddBox(boxad, box, L_INSERT);
+        xshift = isplit + 1;
+    }
+    box = boxCreate(xshift, 0, w - xshift, h);
+    boxaAddBox(boxad, box, L_INSERT);
+
+    numaDestroy(&nasplit);
+
+    if (ppixdebug) {
+        pixdb = pixConvertTo32(pixs);
+        ncomp = boxaGetCount(boxad);
+        for (i = 0; i < ncomp; i++) {
+            box = boxaGetBox(boxad, i, L_CLONE);
+            pixRenderBoxBlend(pixdb, box, 1, 255, 0, 0, 0.5);
+            boxDestroy(&box);
+        }
+        *ppixdebug = pixdb;
+    }
+
+    return boxad;
+}
+
+
+/*------------------------------------------------------------------*
+ *                    Extraction of lines of text                   *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixExtractTextlines()
+ *
+ *      Input:  pixs (any depth, assumed to have nearly horizontal text)
+ *              maxw, maxh (initial filtering: remove any components in pixs
+ *                          with components larger than maxw or maxh)
+ *              minw, minh (final filtering: remove extracted 'lines'
+ *                          with sizes smaller than minw or minh)
+ *      Return: pixa (of textline images, including bounding boxes), or
+ *                    null on error
+ *
+ *  Notes:
+ *      (1) This first removes components from pixs that are either
+ *          wide (> @maxw) or tall (> @maxh).
+ *      (2) This function assumes that textlines have sufficient
+ *          vertical separation and small enough skew so that a
+ *          horizontal dilation sufficient to join words will not join
+ *          textlines.  Images with multiple columns of text may have
+ *          the textlines join across the space between columns.
+ *      (3) A final filtering operation removes small components, such
+ *          that width < @minw or height < @minh.
+ *      (4) For reasonable accuracy, the resolution of pixs should be
+ *          at least 100 ppi.  For reasonable efficiency, the resolution
+ *          should not exceed 600 ppi.
+ *      (5) This can be used to determine if some region of a scanned
+ *          image is horizontal text.
+ *      (6) As an example, for a pix with resolution 300 ppi, a reasonable
+ *          set of parameters is:
+ *             pixExtractTextlines(pix, 150, 150, 10, 5);
+ */
+PIXA *
+pixExtractTextlines(PIX     *pixs,
+                    l_int32  maxw,
+                    l_int32  maxh,
+                    l_int32  minw,
+                    l_int32  minh)
+{
+char     buf[64];
+l_int32  i, n, res, csize, empty;
+BOX     *box;
+BOXA    *boxa1, *boxa2;
+PIX     *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXA    *pixa1, *pixa2, *pixa3, *pixad;
+
+    PROCNAME("pixExtractTextlines");
+
+    if (!pixs)
+        return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
+
+        /* Binarize carefully, if necessary */
+    if (pixGetDepth(pixs) > 1) {
+        pix2 = pixConvertTo8(pixs, FALSE);
+        pix3 = pixCleanBackgroundToWhite(pix2, NULL, NULL, 1.0, 70, 190);
+        pix1 = pixThresholdToBinary(pix3, 150);
+        pixDestroy(&pix3);
+        pixDestroy(&pix3);
+    } else {
+        pix1 = pixClone(pixs);
+    }
+    pixZero(pix1, &empty);
+    if (empty) {
+        pixDestroy(&pix1);
+        L_INFO("no fg pixels in input image\n", procName);
+        return NULL;
+    }
+
+        /* Remove any very tall or very wide connected components */
+    pix2 = pixSelectBySize(pix1, maxw, maxh, 8, L_SELECT_IF_BOTH,
+                           L_SELECT_IF_LT, NULL);
+    pixDestroy(&pix1);
+
+        /* Filter to solidify the text lines within the x-height region.
+         * The closing (csize) bridges gaps between words.  The opening
+         * removes isolated bridges between textlines. */
+    if ((res = pixGetXRes(pixs)) == 0) {
+        L_INFO("Resolution is not set: setting to 300 ppi\n", procName);
+        res = 300;
+    }
+    csize = L_MIN(120., 60.0 * (res / 300));
+    snprintf(buf, sizeof(buf), "c%d.1 + o20.1", csize);
+    pix3 = pixMorphCompSequence(pix2, buf, 0);
+
+        /* Extract the connected components.  These should be dilated lines */
+    boxa1 = pixConnComp(pix3, &pixa1, 4);
+    pixDestroy(&pix3);
+
+        /* Remove line components that are too small */
+    pixa2 = pixaSelectBySize(pixa1, minw, minh, L_SELECT_IF_BOTH,
+                           L_SELECT_IF_GTE, NULL);
+
+#if DEBUG_LINES
+    pix1 = pixaDisplayRandomCmap(pixa2, 0, 0);
+    pixcmapResetColor(pixGetColormap(pix1), 0, 255, 255, 255);
+    pixWrite("/tmp/lept/junklines.png", pix1, IFF_PNG);
+    pixDestroy(&pix1);
+#endif
+
+        /* Selectively AND with the version before dilation, and save */
+    boxa2 = pixaGetBoxa(pixa2, L_CLONE);
+    n = boxaGetCount(boxa2);
+    pixa3 = pixClipRectangles(pix2, boxa2);
+    pixad = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix4 = pixaGetPix(pixa2, i, L_CLONE);
+        pix5 = pixaGetPix(pixa3, i, L_COPY);
+        pixAnd(pix5, pix5, pix4);
+        pixaAddPix(pixad, pix5, L_INSERT);
+        box = boxaGetBox(boxa2, i, L_COPY);
+        pixaAddBox(pixad, box, L_INSERT);
+        pixDestroy(&pix4);
+    }
+
+    pixDestroy(&pix2);
+    pixaDestroy(&pixa1);
+    pixaDestroy(&pixa2);
+    pixaDestroy(&pixa3);
+    boxaDestroy(&boxa1);
+    boxaDestroy(&boxa2);
+    return pixad;
+}
+
+
+/*------------------------------------------------------------------*
+ *                      Decision text vs photo                      *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixDecideIfText()
+ *
+ *      Input:  pixs (any depth)
+ *              box (<optional> if null, use entire pixs)
+ *              &istext (<return> 1 if text; 0 if photo; -1 if not determined)
+ *              pixadb (<optional> pre-allocated, for showing intermediate
+ *                      computation; use null to skip)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) It is assumed that pixs has the correct resolution set.
+ *          If the resolution is 0, we set to 300 and issue a warning.
+ *      (2) If necessary, the image is scaled to 300 ppi; most of the
+ *          processing is done at this resolution.
+ *      (3) Text is assumed to be in horizontal lines.
+ *      (4) Because thin vertical lines are removed before filtering for
+ *          text lines, this should identify tables as text.
+ *      (5) If @box is null and pixs contains both text lines and line art,
+ *          this function might return @istext == true.
+ *      (6) If the input pixs is empty, or for some other reason the
+ *          result can not be determined, return -1.
+ *      (7) For debug output, input a pre-allocated pixa.
+ */
+l_int32
+pixDecideIfText(PIX      *pixs,
+                BOX      *box,
+                l_int32  *pistext,
+                PIXA     *pixadb)
+{
+l_int32    i, empty, maxw, maxh, w, h, n1, n2, n3, minlines;
+l_int32    res, big_comp;
+l_float32  ratio1, ratio2, factor;
+L_BMF     *bmf;
+BOX       *box1;
+BOXA      *boxa1, *boxa2, *boxa3, *boxa4, *boxa5;
+PIX       *pix1, *pix2, *pix3, *pix4, *pix5, *pix5a;
+PIX       *pix6, *pix7, *pix8, *pix9, *pix10;
+PIXA      *pixa1, *pixa2;
+SEL       *sel1;
+
+    PROCNAME("pixDecideIfText");
+
+    if (pistext) *pistext = -1;  /* init */
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+        /* Crop and convert to 1 bpp with adaptive background cleaning.
+         * If no box is given, use most of the image.  Removing the
+         * edges helps avoid false negatives from noise near the edges. */
+    if (box) {
+        pix1 = pixClipRectangle(pixs, box, NULL);
+    } else {
+        pixGetDimensions(pixs, &w, &h, NULL);
+        box1 = boxCreate(w / 10, h / 10, 4 * w / 5, 4 * h / 5);
+        pix1 = pixClipRectangle(pixs, box1, NULL);
+        boxDestroy(&box1);
+    }
+    pix2 = pixConvertTo8(pix1, 0);
+    pix3 = pixCleanBackgroundToWhite(pix2, NULL, NULL, 1.0, 70, 160);
+    pixDestroy(&pix1);
+    if (!pix3) {
+        pixDestroy(&pix2);
+        L_INFO("pix cleaning failed\n", procName);
+        return 1;
+    }
+    pix4 = pixThresholdToBinary(pix3, 200);
+    pixZero(pix4, &empty);
+    if (empty) {
+        pixDestroy(&pix2);
+        pixDestroy(&pix3);
+        pixDestroy(&pix4);
+        L_INFO("pix is empty\n", procName);
+        return 0;
+    }
+
+        /* Get the resolution, or guess, and scale the image to 300 ppi */
+    if ((res = pixGetXRes(pixs)) == 0) {
+        L_WARNING("Resolution is not set: using 300 ppi\n", procName);
+        res = 300;
+    }
+    if (res != 300) {
+        factor = 300. / res;
+        pix5 = pixScale(pix4, factor, factor);
+    } else {
+        pix5 = pixClone(pix4);
+    }
+    w = pixGetWidth(pix5);
+
+        /* Identify and remove tall, thin vertical lines (as found in tables)
+         * that are up to 9 pixels wide.  Make a hit-miss sel with an
+         * 81 pixel vertical set of hits and with 3 pairs of misses that
+         * are 10 pixels apart horizontally.  It is necessary to use a
+         * hit-miss transform; if we only opened with a vertical line of
+         * hits, we would remove solid regions of pixels that are not
+         * text or vertical lines. */
+    pix5a = pixCreate(11, 81, 1);
+    for (i = 0; i < 81; i++)
+        pixSetPixel(pix5a, 5, i, 1);
+    sel1 = selCreateFromPix(pix5a, 40, 5, NULL);
+    selSetElement(sel1, 20, 0, SEL_MISS);
+    selSetElement(sel1, 20, 10, SEL_MISS);
+    selSetElement(sel1, 40, 0, SEL_MISS);
+    selSetElement(sel1, 40, 10, SEL_MISS);
+    selSetElement(sel1, 60, 0, SEL_MISS);
+    selSetElement(sel1, 60, 10, SEL_MISS);
+    pix6 = pixHMT(NULL, pix5, sel1);
+    pix7 = pixSeedfillBinaryRestricted(NULL, pix6, pix5, 8, 5, 1000);
+    pix8 = pixXor(NULL, pix5, pix7);
+    pixDestroy(&pix5a);
+    selDestroy(&sel1);
+
+        /* Convert the text lines to separate long horizontal components */
+    pix9 = pixMorphCompSequence(pix8, "c30.1 + o15.1 + c60.1 + o2.2", 0);
+
+        /* Estimate the distance to the bottom of the significant region */
+    if (box) {  /* use full height */
+        pixGetDimensions(pix9, NULL, &h, NULL);
+    } else {  /* use height of region that has text lines */
+        pixFindThreshFgExtent(pix9, 400, NULL, &h);
+    }
+
+    if (pixadb) {
+        bmf = bmfCreate(NULL, 8);
+        pixaAddPixWithText(pixadb, pix2, 1, bmf, "initial 8 bpp",
+                           0x0000ff00, L_ADD_BELOW);
+        pixaAddPixWithText(pixadb, pix3, 1, bmf, "with background cleaning",
+                           0x0000ff00, L_ADD_BELOW);
+        pixaAddPixWithText(pixadb, pix4, 1, bmf, "threshold to binary",
+                           0x0000ff00, L_ADD_BELOW);
+        pixaAddPixWithText(pixadb, pix6, 2, bmf, "hit-miss for vertical line",
+                           0x0000ff00, L_ADD_BELOW);
+        pixaAddPixWithText(pixadb, pix7, 2, bmf, "restricted seed-fill",
+                           0x0000ff00, L_ADD_BELOW);
+        pixaAddPixWithText(pixadb, pix8, 2, bmf, "remove using xor",
+                           0x0000ff00, L_ADD_BELOW);
+        pixaAddPixWithText(pixadb, pix9, 2, bmf, "make long horiz components",
+                           0x0000ff00, L_ADD_BELOW);
+    }
+
+        /* Extract the connected components */
+    if (pixadb) {
+        boxa1 = pixConnComp(pix9, &pixa1, 8);
+        pix10 = pixaDisplayRandomCmap(pixa1, 0, 0);
+        pixcmapResetColor(pixGetColormap(pix10), 0, 255, 255, 255);
+        pixaAddPixWithText(pixadb, pix10, 2, bmf, "show connected components",
+                           0x0000ff00, L_ADD_BELOW);
+        pixDestroy(&pix10);
+        pixaDestroy(&pixa1);
+        bmfDestroy(&bmf);
+    } else {
+        boxa1 = pixConnComp(pix9, NULL, 8);
+    }
+
+        /* Analyze the connected components.  The following conditions
+         * at 300 ppi must be satisfied if the image is text:
+         * (1) There are no components that are wider than 400 pixels and
+         *     taller than 175 pixels.
+         * (2) The second longest component is at least 60% of the
+         *     (possibly cropped) image width.  This catches images
+         *     that don't have any significant content.
+         * (3) Of the components that are at least 40% of the length
+         *     of the longest (n2), at least 80% of them must not exceed
+         *     60 pixels in height.
+         * (4) The number of those long, thin components (n3) must
+         *     equal or exceed a minimum that scales linearly with the
+         *     image height.
+         * Most images that are not text fail more than one of these
+         * conditions. */
+    boxa2 = boxaSort(boxa1, L_SORT_BY_WIDTH, L_SORT_DECREASING, NULL);
+    boxaGetBoxGeometry(boxa2, 1, NULL, NULL, &maxw, NULL);  /* 2nd longest */
+    boxa3 = boxaSelectBySize(boxa1, 0.4 * maxw, 0, L_SELECT_WIDTH,
+                             L_SELECT_IF_GTE, NULL);
+    boxa4 = boxaSelectBySize(boxa3, 0, 60, L_SELECT_HEIGHT,
+                             L_SELECT_IF_LTE, NULL);
+    boxa5 = boxaSelectBySize(boxa1, 400, 175, L_SELECT_IF_BOTH,
+                             L_SELECT_IF_GT, NULL);
+    big_comp = (boxaGetCount(boxa5) == 0) ? 0 : 1;
+    n1 = boxaGetCount(boxa1);
+    n2 = boxaGetCount(boxa3);
+    n3 = boxaGetCount(boxa4);
+    ratio1 = (l_float32)maxw / (l_float32)w;
+    ratio2 = (l_float32)n3 / (l_float32)n2;
+    minlines = L_MAX(2, h / 125);
+    if (big_comp || ratio1 < 0.6 || ratio2 < 0.8 || n3 < minlines)
+        *pistext = 0;
+    else
+        *pistext = 1;
+    if (pixadb) {
+        if (*pistext == 1) {
+            L_INFO("This is text: \n  n1 = %d, n2 = %d, n3 = %d, "
+                   "minlines = %d\n  maxw = %d, ratio1 = %4.2f, h = %d, "
+                   "big_comp = %d\n", procName, n1, n2, n3, minlines,
+                   maxw, ratio1, h, big_comp);
+        } else {
+            L_INFO("This is not text: \n  n1 = %d, n2 = %d, n3 = %d, "
+                   "minlines = %d\n  maxw = %d, ratio1 = %4.2f, h = %d, "
+                   "big_comp = %d\n", procName, n1, n2, n3, minlines,
+                   maxw, ratio1, h, big_comp);
+        }
+    }
+
+    boxaDestroy(&boxa1);
+    boxaDestroy(&boxa2);
+    boxaDestroy(&boxa3);
+    boxaDestroy(&boxa4);
+    boxaDestroy(&boxa5);
+    pixDestroy(&pix2);
+    pixDestroy(&pix3);
+    pixDestroy(&pix4);
+    pixDestroy(&pix5);
+    pixDestroy(&pix6);
+    pixDestroy(&pix7);
+    pixDestroy(&pix8);
+    pixDestroy(&pix9);
+    return 0;
+}
+
+
+/*!
+ *  pixFindThreshFgExtent()
+ *
+ *      Input:  pixs (1 bpp)
+ *              thresh (threshold number of pixels in row)
+ *              &top (<optional return> location of top of region)
+ *              &bot (<optional return> location of bottom of region)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixFindThreshFgExtent(PIX      *pixs,
+                      l_int32   thresh,
+                      l_int32  *ptop,
+                      l_int32  *pbot)
+{
+l_int32    i, n, res;
+l_int32   *array;
+l_float32  factor;
+NUMA      *na;
+
+    PROCNAME("pixFindThreshFgExtent");
+
+    if (ptop) *ptop = 0;
+    if (pbot) *pbot = 0;
+    if (!ptop && !pbot)
+        return ERROR_INT("nothing to determine", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+    na = pixCountPixelsByRow(pixs, NULL);
+    n = numaGetCount(na);
+    array = numaGetIArray(na);
+    if (ptop) {
+        for (i = 0; i < n; i++) {
+            if (array[i] >= thresh) {
+                *ptop = i;
+                break;
+            }
+        }
+    }
+    if (pbot) {
+        for (i = n - 1; i >= 0; i--) {
+            if (array[i] >= thresh) {
+                *pbot = i;
+                break;
+            }
+        }
+    }
+    LEPT_FREE(array);
+    numaDestroy(&na);
+    return 0;
+}
+
diff --git a/src/paintcmap.c b/src/paintcmap.c
new file mode 100644 (file)
index 0000000..f491909
--- /dev/null
@@ -0,0 +1,755 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  paintcmap.c
+ *
+ *      These in-place functions paint onto colormap images.
+ *
+ *      Repaint selected pixels in region
+ *           l_int32     pixSetSelectCmap()
+ *
+ *      Repaint non-white pixels in region
+ *           l_int32     pixColorGrayRegionsCmap()
+ *           l_int32     pixColorGrayCmap()
+ *           l_int32     pixColorGrayMaskedCmap()
+ *           l_int32     addColorizedGrayToCmap()
+ *
+ *      Repaint selected pixels through mask
+ *           l_int32     pixSetSelectMaskedCmap()
+ *
+ *      Repaint all pixels through mask
+ *           l_int32     pixSetMaskedCmap()
+ *
+ *
+ *  The 'set select' functions condition the setting on a specific
+ *  pixel value (i.e., index into the colormap) of the underyling
+ *  Pix that is being modified.  The same conditioning is used in
+ *  pixBlendCmap().
+ *
+ *  The pixColorGrayCmap() function sets all truly gray (r = g = b) pixels,
+ *  with the exception of either black or white pixels, to a new color.
+ *
+ *  The pixSetSelectMaskedCmap() function conditions pixel painting
+ *  on both a specific pixel value and location within the fg mask.
+ *  By contrast, pixSetMaskedCmap() sets all pixels under the
+ *  mask foreground, without considering the initial pixel values.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+/*-------------------------------------------------------------*
+ *               Repaint selected pixels in region             *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixSetSelectCmap()
+ *
+ *      Input:  pixs (1, 2, 4 or 8 bpp, with colormap)
+ *              box (<optional> region to set color; can be NULL)
+ *              sindex (colormap index of pixels to be changed)
+ *              rval, gval, bval (new color to paint)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Note:
+ *      (1) This is an in-place operation.
+ *      (2) It sets all pixels in region that have the color specified
+ *          by the colormap index 'sindex' to the new color.
+ *      (3) sindex must be in the existing colormap; otherwise an
+ *          error is returned.
+ *      (4) If the new color exists in the colormap, it is used;
+ *          otherwise, it is added to the colormap.  If it cannot be
+ *          added because the colormap is full, an error is returned.
+ *      (5) If box is NULL, applies function to the entire image; otherwise,
+ *          clips the operation to the intersection of the box and pix.
+ *      (6) An example of use would be to set to a specific color all
+ *          the light (background) pixels within a certain region of
+ *          a 3-level 2 bpp image, while leaving light pixels outside
+ *          this region unchanged.
+ */
+l_int32
+pixSetSelectCmap(PIX     *pixs,
+                 BOX     *box,
+                 l_int32  sindex,
+                 l_int32  rval,
+                 l_int32  gval,
+                 l_int32  bval)
+{
+l_int32    i, j, w, h, d, n, x1, y1, x2, y2, bw, bh, val, wpls;
+l_int32    index;  /* of new color to be set */
+l_uint32  *lines, *datas;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixSetSelectCmap");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if ((cmap = pixGetColormap(pixs)) == NULL)
+        return ERROR_INT("no colormap", procName, 1);
+    d = pixGetDepth(pixs);
+    if (d != 1 && d != 2 && d != 4 && d != 8)
+        return ERROR_INT("depth not in {1,2,4,8}", procName, 1);
+
+        /* Add new color if necessary; get index of this color in cmap */
+    n = pixcmapGetCount(cmap);
+    if (sindex >= n)
+        return ERROR_INT("sindex too large; no cmap entry", procName, 1);
+    if (pixcmapGetIndex(cmap, rval, gval, bval, &index)) { /* not found */
+        if (pixcmapAddColor(cmap, rval, gval, bval))
+            return ERROR_INT("error adding cmap entry", procName, 1);
+        else
+            index = n;  /* we've added one color */
+    }
+
+        /* Determine the region of substitution */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (!box) {
+        x1 = y1 = 0;
+        x2 = w;
+        y2 = h;
+    } else {
+        boxGetGeometry(box, &x1, &y1, &bw, &bh);
+        x2 = x1 + bw - 1;
+        y2 = y1 + bh - 1;
+    }
+
+        /* Replace pixel value sindex by index in the region */
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    for (i = y1; i <= y2; i++) {
+        if (i < 0 || i >= h)  /* clip */
+            continue;
+        lines = datas + i * wpls;
+        for (j = x1; j <= x2; j++) {
+            if (j < 0 || j >= w)  /* clip */
+                continue;
+            switch (d) {
+            case 1:
+                val = GET_DATA_BIT(lines, j);
+                if (val == sindex) {
+                    if (index == 0)
+                        CLEAR_DATA_BIT(lines, j);
+                    else
+                        SET_DATA_BIT(lines, j);
+                }
+                break;
+            case 2:
+                val = GET_DATA_DIBIT(lines, j);
+                if (val == sindex)
+                    SET_DATA_DIBIT(lines, j, index);
+                break;
+            case 4:
+                val = GET_DATA_QBIT(lines, j);
+                if (val == sindex)
+                    SET_DATA_QBIT(lines, j, index);
+                break;
+            case 8:
+                val = GET_DATA_BYTE(lines, j);
+                if (val == sindex)
+                    SET_DATA_BYTE(lines, j, index);
+                break;
+            default:
+                return ERROR_INT("depth not in {1,2,4,8}", procName, 1);
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *                  Repaint gray pixels in region              *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixColorGrayRegionsCmap()
+ *
+ *      Input:  pixs (8 bpp, with colormap)
+ *              boxa (of regions in which to apply color)
+ *              type (L_PAINT_LIGHT, L_PAINT_DARK)
+ *              rval, gval, bval (target color)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place operation.
+ *      (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels,
+ *          preserving antialiasing.
+ *          If type == L_PAINT_DARK, it colorizes non-white pixels,
+ *          preserving antialiasing.  See pixColorGrayCmap() for details.
+ *      (3) This can also be called through pixColorGrayRegions().
+ *      (4) This increases the colormap size by the number of
+ *          different gray (non-black or non-white) colors in the
+ *          selected regions of pixs.  If there is not enough room in
+ *          the colormap for this expansion, it returns 1 (error),
+ *          and the caller should check the return value.
+ *      (5) Because two boxes in the boxa can overlap, pixels that
+ *          are colorized in the first box must be excluded in the
+ *          second because their value exceeds the size of the map.
+ */
+l_int32
+pixColorGrayRegionsCmap(PIX     *pixs,
+                        BOXA    *boxa,
+                        l_int32  type,
+                        l_int32  rval,
+                        l_int32  gval,
+                        l_int32  bval)
+{
+l_int32    i, j, k, w, h, n, nc, x1, y1, x2, y2, bw, bh, wpl;
+l_int32    val, nval;
+l_int32   *map;
+l_uint32  *line, *data;
+BOX       *box;
+NUMA      *na;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixColorGrayRegionsCmap");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if ((cmap = pixGetColormap(pixs)) == NULL)
+        return ERROR_INT("no colormap", procName, 1);
+    if (pixGetDepth(pixs) != 8)
+        return ERROR_INT("depth not 8 bpp", procName, 1);
+    if (type != L_PAINT_DARK && type != L_PAINT_LIGHT)
+        return ERROR_INT("invalid type", procName, 1);
+
+    nc = pixcmapGetCount(cmap);
+    if (addColorizedGrayToCmap(cmap, type, rval, gval, bval, &na))
+        return ERROR_INT("no room; cmap full", procName, 1);
+    map = numaGetIArray(na);
+    numaDestroy(&na);
+    if (!map)
+        return ERROR_INT("map not made", procName, 1);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    n = boxaGetCount(boxa);
+    for (k = 0; k < n; k++) {
+        box = boxaGetBox(boxa, k, L_CLONE);
+        boxGetGeometry(box, &x1, &y1, &bw, &bh);
+        x2 = x1 + bw - 1;
+        y2 = y1 + bh - 1;
+
+            /* Remap gray pixels in the region */
+        for (i = y1; i <= y2; i++) {
+            if (i < 0 || i >= h)  /* clip */
+                continue;
+            line = data + i * wpl;
+            for (j = x1; j <= x2; j++) {
+                if (j < 0 || j >= w)  /* clip */
+                    continue;
+                val = GET_DATA_BYTE(line, j);
+                if (val >= nc) continue;  /* from overlapping b.b. */
+                nval = map[val];
+                if (nval != 256)
+                    SET_DATA_BYTE(line, j, nval);
+            }
+        }
+        boxDestroy(&box);
+    }
+
+    LEPT_FREE(map);
+    return 0;
+}
+
+
+/*!
+ *  pixColorGrayCmap()
+ *
+ *      Input:  pixs (2, 4 or 8 bpp, with colormap)
+ *              box (<optional> region to set color; can be NULL)
+ *              type (L_PAINT_LIGHT, L_PAINT_DARK)
+ *              rval, gval, bval (target color)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place operation.
+ *      (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels,
+ *          preserving antialiasing.
+ *          If type == L_PAINT_DARK, it colorizes non-white pixels,
+ *          preserving antialiasing.
+ *      (3) box gives the region to apply color; if NULL, this
+ *          colorizes the entire image.
+ *      (4) If the cmap is only 2 or 4 bpp, pixs is converted in-place
+ *          to an 8 bpp cmap.  A 1 bpp cmap is not a valid input pix.
+ *      (5) This can also be called through pixColorGray().
+ *      (6) This operation increases the colormap size by the number of
+ *          different gray (non-black or non-white) colors in the
+ *          input colormap.  If there is not enough room in the colormap
+ *          for this expansion, it returns 1 (error), and the caller
+ *          should check the return value.
+ *      (7) Using the darkness of each original pixel in the rect,
+ *          it generates a new color (based on the input rgb values).
+ *          If type == L_PAINT_LIGHT, the new color is a (generally)
+ *          darken-to-black version of the  input rgb color, where the
+ *          amount of darkening increases with the darkness of the
+ *          original pixel color.
+ *          If type == L_PAINT_DARK, the new color is a (generally)
+ *          faded-to-white version of the  input rgb color, where the
+ *          amount of fading increases with the brightness of the
+ *          original pixel color.
+ */
+l_int32
+pixColorGrayCmap(PIX     *pixs,
+                 BOX     *box,
+                 l_int32  type,
+                 l_int32  rval,
+                 l_int32  gval,
+                 l_int32  bval)
+{
+l_int32   w, h, d, ret;
+PIX      *pixt;
+BOXA     *boxa;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixColorGrayCmap");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if ((cmap = pixGetColormap(pixs)) == NULL)
+        return ERROR_INT("no colormap", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 2 && d != 4 && d != 8)
+        return ERROR_INT("depth not in {2, 4, 8}", procName, 1);
+    if (type != L_PAINT_DARK && type != L_PAINT_LIGHT)
+        return ERROR_INT("invalid type", procName, 1);
+
+        /* If 2 bpp or 4 bpp, convert in-place to 8 bpp. */
+    if (d == 2 || d == 4) {
+        pixt = pixConvertTo8(pixs, 1);
+        pixTransferAllData(pixs, &pixt, 0, 0);
+    }
+
+        /* If box == NULL, color the entire image */
+    boxa = boxaCreate(1);
+    if (box) {
+        boxaAddBox(boxa, box, L_COPY);
+    } else {
+        box = boxCreate(0, 0, w, h);
+        boxaAddBox(boxa, box, L_INSERT);
+    }
+    ret = pixColorGrayRegionsCmap(pixs, boxa, type, rval, gval, bval);
+
+    boxaDestroy(&boxa);
+    return ret;
+}
+
+
+/*!
+ *  pixColorGrayMaskedCmap()
+ *
+ *      Input:  pixs (8 bpp, with colormap)
+ *              pixm (1 bpp mask, through which to apply color)
+ *              type (L_PAINT_LIGHT, L_PAINT_DARK)
+ *              rval, gval, bval (target color)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place operation.
+ *      (2) If type == L_PAINT_LIGHT, it colorizes non-black pixels,
+ *          preserving antialiasing.
+ *          If type == L_PAINT_DARK, it colorizes non-white pixels,
+ *          preserving antialiasing.  See pixColorGrayCmap() for details.
+ *      (3) This increases the colormap size by the number of
+ *          different gray (non-black or non-white) colors in the
+ *          input colormap.  If there is not enough room in the colormap
+ *          for this expansion, it returns 1 (error).
+ */
+l_int32
+pixColorGrayMaskedCmap(PIX     *pixs,
+                       PIX     *pixm,
+                       l_int32  type,
+                       l_int32  rval,
+                       l_int32  gval,
+                       l_int32  bval)
+{
+l_int32    i, j, w, h, wm, hm, wmin, hmin, wpl, wplm;
+l_int32    val, nval;
+l_int32   *map;
+l_uint32  *line, *data, *linem, *datam;
+NUMA      *na;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixColorGrayMaskedCmap");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixm || pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm undefined or not 1 bpp", procName, 1);
+    if ((cmap = pixGetColormap(pixs)) == NULL)
+        return ERROR_INT("no colormap", procName, 1);
+    if (pixGetDepth(pixs) != 8)
+        return ERROR_INT("depth not 8 bpp", procName, 1);
+    if (type != L_PAINT_DARK && type != L_PAINT_LIGHT)
+        return ERROR_INT("invalid type", procName, 1);
+
+    if (addColorizedGrayToCmap(cmap, type, rval, gval, bval, &na))
+        return ERROR_INT("no room; cmap full", procName, 1);
+    map = numaGetIArray(na);
+    numaDestroy(&na);
+    if (!map)
+        return ERROR_INT("map not made", procName, 1);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixGetDimensions(pixm, &wm, &hm, NULL);
+    if (wm != w)
+        L_WARNING("wm = %d differs from w = %d\n", procName, wm, w);
+    if (hm != h)
+        L_WARNING("hm = %d differs from h = %d\n", procName, hm, h);
+    wmin = L_MIN(w, wm);
+    hmin = L_MIN(h, hm);
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    datam = pixGetData(pixm);
+    wplm = pixGetWpl(pixm);
+
+        /* Remap gray pixels in the region */
+    for (i = 0; i < hmin; i++) {
+        line = data + i * wpl;
+        linem = datam + i * wplm;
+        for (j = 0; j < wmin; j++) {
+            if (GET_DATA_BIT(linem, j) == 0)
+                continue;
+            val = GET_DATA_BYTE(line, j);
+            nval = map[val];
+            if (nval != 256)
+                SET_DATA_BYTE(line, j, nval);
+        }
+    }
+
+    LEPT_FREE(map);
+    return 0;
+}
+
+
+/*!
+ *  addColorizedGrayToCmap()
+ *
+ *      Input:  cmap (from 2 or 4 bpp pix)
+ *              type (L_PAINT_LIGHT, L_PAINT_DARK)
+ *              rval, gval, bval (target color)
+ *              &na (<optional return> table for mapping new cmap entries)
+ *      Return: 0 if OK; 1 on error; 2 if new colors will not fit in cmap.
+ *
+ *  Notes:
+ *      (1) If type == L_PAINT_LIGHT, it colorizes non-black pixels,
+ *          preserving antialiasing.
+ *          If type == L_PAINT_DARK, it colorizes non-white pixels,
+ *          preserving antialiasing.
+ *      (2) This increases the colormap size by the number of
+ *          different gray (non-black or non-white) colors in the
+ *          input colormap.  If there is not enough room in the colormap
+ *          for this expansion, it returns 1 (treated as a warning);
+ *          the caller should check the return value.
+ *      (3) This can be used to determine if the new colors will fit in
+ *          the cmap, using null for &na.  Returns 0 if they fit; 2 if
+ *          they don't fit.
+ *      (4) The mapping table contains, for each gray color found, the
+ *          index of the corresponding colorized pixel.  Non-gray
+ *          pixels are assigned the invalid index 256.
+ *      (5) See pixColorGrayCmap() for usage.
+ */
+l_int32
+addColorizedGrayToCmap(PIXCMAP  *cmap,
+                       l_int32   type,
+                       l_int32   rval,
+                       l_int32   gval,
+                       l_int32   bval,
+                       NUMA    **pna)
+{
+l_int32  i, n, erval, egval, ebval, nrval, ngval, nbval, newindex;
+NUMA    *na;
+
+    PROCNAME("addColorizedGrayToCmap");
+
+    if (pna) *pna = NULL;
+    if (!cmap)
+        return ERROR_INT("cmap not defined", procName, 1);
+    if (type != L_PAINT_DARK && type != L_PAINT_LIGHT)
+        return ERROR_INT("invalid type", procName, 1);
+
+    n = pixcmapGetCount(cmap);
+    na = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        pixcmapGetColor(cmap, i, &erval, &egval, &ebval);
+        if (type == L_PAINT_LIGHT) {
+            if (erval == egval && erval == ebval && erval != 0) {
+                nrval = (l_int32)(rval * (l_float32)erval / 255.);
+                ngval = (l_int32)(gval * (l_float32)egval / 255.);
+                nbval = (l_int32)(bval * (l_float32)ebval / 255.);
+                if (pixcmapAddNewColor(cmap, nrval, ngval, nbval, &newindex)) {
+                    numaDestroy(&na);
+                    L_WARNING("no room; colormap full\n", procName);
+                    return 2;
+                }
+                numaAddNumber(na, newindex);
+            } else {
+                numaAddNumber(na, 256);  /* invalid number; not gray */
+            }
+        } else {  /* L_PAINT_DARK */
+            if (erval == egval && erval == ebval && erval != 255) {
+                nrval = rval +
+                        (l_int32)((255. - rval) * (l_float32)erval / 255.);
+                ngval = gval +
+                        (l_int32)((255. - gval) * (l_float32)egval / 255.);
+                nbval = bval +
+                        (l_int32)((255. - bval) * (l_float32)ebval / 255.);
+                if (pixcmapAddNewColor(cmap, nrval, ngval, nbval, &newindex)) {
+                    numaDestroy(&na);
+                    L_WARNING("no room; colormap full\n", procName);
+                    return 2;
+                }
+                numaAddNumber(na, newindex);
+            } else {
+                numaAddNumber(na, 256);  /* invalid number; not gray */
+            }
+        }
+    }
+
+    if (pna)
+        *pna = na;
+    else
+        numaDestroy(&na);
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *             Repaint selected pixels through mask            *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixSetSelectMaskedCmap()
+ *
+ *      Input:  pixs (2, 4 or 8 bpp, with colormap)
+ *              pixm (<optional> 1 bpp mask; no-op if NULL)
+ *              x, y (UL corner of mask relative to pixs)
+ *              sindex (colormap index of pixels in pixs to be changed)
+ *              rval, gval, bval (new color to substitute)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Note:
+ *      (1) This is an in-place operation.
+ *      (2) This paints through the fg of pixm and replaces all pixels
+ *          in pixs that have a particular value (sindex) with the new color.
+ *      (3) If pixm == NULL, a warning is given.
+ *      (4) sindex must be in the existing colormap; otherwise an
+ *          error is returned.
+ *      (5) If the new color exists in the colormap, it is used;
+ *          otherwise, it is added to the colormap.  If the colormap
+ *          is full, an error is returned.
+ */
+l_int32
+pixSetSelectMaskedCmap(PIX     *pixs,
+                       PIX     *pixm,
+                       l_int32  x,
+                       l_int32  y,
+                       l_int32  sindex,
+                       l_int32  rval,
+                       l_int32  gval,
+                       l_int32  bval)
+{
+l_int32    i, j, w, h, d, n, wm, hm, wpls, wplm, val;
+l_int32    index;  /* of new color to be set */
+l_uint32  *lines, *linem, *datas, *datam;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixSetSelectMaskedCmap");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if ((cmap = pixGetColormap(pixs)) == NULL)
+        return ERROR_INT("no colormap", procName, 1);
+    if (!pixm) {
+        L_WARNING("no mask; nothing to do\n", procName);
+        return 0;
+    }
+
+    d = pixGetDepth(pixs);
+    if (d != 2 && d != 4 && d != 8)
+        return ERROR_INT("depth not in {2, 4, 8}", procName, 1);
+
+        /* add new color if necessary; get index of this color in cmap */
+    n = pixcmapGetCount(cmap);
+    if (sindex >= n)
+        return ERROR_INT("sindex too large; no cmap entry", procName, 1);
+    if (pixcmapGetIndex(cmap, rval, gval, bval, &index)) { /* not found */
+        if (pixcmapAddColor(cmap, rval, gval, bval))
+            return ERROR_INT("error adding cmap entry", procName, 1);
+        else
+            index = n;  /* we've added one color */
+    }
+
+        /* replace pixel value sindex by index when fg pixel in pixmc
+         * overlays it */
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    wm = pixGetWidth(pixm);
+    hm = pixGetHeight(pixm);
+    datam = pixGetData(pixm);
+    wplm = pixGetWpl(pixm);
+    for (i = 0; i < hm; i++) {
+        if (i + y < 0 || i + y >= h) continue;
+        lines = datas + (y + i) * wpls;
+        linem = datam + i * wplm;
+        for (j = 0; j < wm; j++) {
+            if (j + x < 0  || j + x >= w) continue;
+            if (GET_DATA_BIT(linem, j)) {
+                switch (d) {
+                case 1:
+                    val = GET_DATA_BIT(lines, x + j);
+                    if (val == sindex) {
+                        if (index == 0)
+                            CLEAR_DATA_BIT(lines, x + j);
+                        else
+                            SET_DATA_BIT(lines, x + j);
+                    }
+                    break;
+                case 2:
+                    val = GET_DATA_DIBIT(lines, x + j);
+                    if (val == sindex)
+                        SET_DATA_DIBIT(lines, x + j, index);
+                    break;
+                case 4:
+                    val = GET_DATA_QBIT(lines, x + j);
+                    if (val == sindex)
+                        SET_DATA_QBIT(lines, x + j, index);
+                    break;
+                case 8:
+                    val = GET_DATA_BYTE(lines, x + j);
+                    if (val == sindex)
+                        SET_DATA_BYTE(lines, x + j, index);
+                    break;
+                default:
+                    return ERROR_INT("depth not in {1,2,4,8}", procName, 1);
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *               Repaint all pixels through mask               *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixSetMaskedCmap()
+ *
+ *      Input:  pixs (2, 4 or 8 bpp, colormapped)
+ *              pixm (<optional> 1 bpp mask; no-op if NULL)
+ *              x, y (origin of pixm relative to pixs; can be negative)
+ *              rval, gval, bval (new color to set at each masked pixel)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place operation.
+ *      (2) It paints a single color through the mask (as a stencil).
+ *      (3) The mask origin is placed at (x,y) on pixs, and the
+ *          operation is clipped to the intersection of the mask and pixs.
+ *      (4) If pixm == NULL, a warning is given.
+ *      (5) Typically, pixm is a small binary mask located somewhere
+ *          on the larger pixs.
+ *      (6) If the color is in the colormap, it is used.  Otherwise,
+ *          it is added if possible; an error is returned if the
+ *          colormap is already full.
+ */
+l_int32
+pixSetMaskedCmap(PIX      *pixs,
+                 PIX      *pixm,
+                 l_int32   x,
+                 l_int32   y,
+                 l_int32   rval,
+                 l_int32   gval,
+                 l_int32   bval)
+{
+l_int32    w, h, d, wpl, wm, hm, wplm;
+l_int32    i, j, index;
+l_uint32  *data, *datam, *line, *linem;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixSetMaskedCmap");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if ((cmap = pixGetColormap(pixs)) == NULL)
+        return ERROR_INT("no colormap in pixs", procName, 1);
+    if (!pixm) {
+        L_WARNING("no mask; nothing to do\n", procName);
+        return 0;
+    }
+    d = pixGetDepth(pixs);
+    if (d != 2 && d != 4 && d != 8)
+        return ERROR_INT("depth not in {2,4,8}", procName, 1);
+    if (pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+
+        /* Add new color if necessary; store in 'index' */
+    if (pixcmapGetIndex(cmap, rval, gval, bval, &index)) {  /* not found */
+        if (pixcmapAddColor(cmap, rval, gval, bval))
+            return ERROR_INT("no room in cmap", procName, 1);
+        index = pixcmapGetCount(cmap) - 1;
+    }
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    wpl = pixGetWpl(pixs);
+    data = pixGetData(pixs);
+    pixGetDimensions(pixm, &wm, &hm, NULL);
+    wplm = pixGetWpl(pixm);
+    datam = pixGetData(pixm);
+    for (i = 0; i < hm; i++) {
+        if (i + y < 0 || i + y >= h) continue;
+        line = data + (i + y) * wpl;
+        linem = datam + i * wplm;
+        for (j = 0; j < wm; j++) {
+            if (j + x < 0  || j + x >= w) continue;
+            if (GET_DATA_BIT(linem, j)) {  /* paint color */
+                switch (d)
+                {
+                case 2:
+                    SET_DATA_DIBIT(line, j + x, index);
+                    break;
+                case 4:
+                    SET_DATA_QBIT(line, j + x, index);
+                    break;
+                case 8:
+                    SET_DATA_BYTE(line, j + x, index);
+                    break;
+                default:
+                    return ERROR_INT("depth not in {2,4,8}", procName, 1);
+                }
+            }
+        }
+    }
+
+    return 0;
+}
diff --git a/src/parseprotos.c b/src/parseprotos.c
new file mode 100644 (file)
index 0000000..7ed917b
--- /dev/null
@@ -0,0 +1,942 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ * parseprotos.c
+ *
+ *       char             *parseForProtos()
+ *
+ *    Static helpers
+ *       static l_int32    getNextNonCommentLine()
+ *       static l_int32    getNextNonBlankLine()
+ *       static l_int32    getNextNonDoubleSlashLine()
+ *       static l_int32    searchForProtoSignature()
+ *       static char      *captureProtoSignature()
+ *       static char      *cleanProtoSignature()
+ *       static l_int32    skipToEndOfFunction()
+ *       static l_int32    skipToMatchingBrace()
+ *       static l_int32    skipToSemicolon()
+ *       static l_int32    getOffsetForCharacter()
+ *       static l_int32    getOffsetForMatchingRP()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  L_BUF_SIZE = 512;    /* max token size */
+
+static l_int32 getNextNonCommentLine(SARRAY *sa, l_int32 start, l_int32 *pnext);
+static l_int32 getNextNonBlankLine(SARRAY *sa, l_int32 start, l_int32 *pnext);
+static l_int32 getNextNonDoubleSlashLine(SARRAY *sa, l_int32 start,
+            l_int32 *pnext);
+static l_int32 searchForProtoSignature(SARRAY *sa, l_int32 begin,
+            l_int32 *pstart, l_int32 *pstop, l_int32 *pcharindex,
+            l_int32 *pfound);
+static char * captureProtoSignature(SARRAY *sa, l_int32 start, l_int32 stop,
+            l_int32 charindex);
+static char * cleanProtoSignature(char *str);
+static l_int32 skipToEndOfFunction(SARRAY *sa, l_int32 start,
+            l_int32 charindex, l_int32 *pnext);
+static l_int32 skipToMatchingBrace(SARRAY *sa, l_int32 start,
+            l_int32 lbindex, l_int32 *prbline, l_int32 *prbindex);
+static l_int32 skipToSemicolon(SARRAY *sa, l_int32 start,
+            l_int32 charindex, l_int32 *pnext);
+static l_int32 getOffsetForCharacter(SARRAY *sa, l_int32 start, char tchar,
+            l_int32 *psoffset, l_int32 *pboffset, l_int32 *ptoffset);
+static l_int32 getOffsetForMatchingRP(SARRAY *sa, l_int32 start,
+            l_int32 soffsetlp, l_int32 boffsetlp, l_int32 toffsetlp,
+            l_int32 *psoffset, l_int32 *pboffset, l_int32 *ptoffset);
+
+
+/*
+ *  parseForProtos()
+ *
+ *      Input:  filein (output of cpp)
+ *              prestring (<optional> string that prefaces each decl;
+ *                        use NULL to omit)
+ *      Return: parsestr (string of function prototypes), or NULL on error
+ *
+ *  Notes:
+ *      (1) We parse the output of cpp:
+ *              cpp -ansi <filein>
+ *          Three plans were attempted, with success on the third.
+ *      (2) Plan 1.  A cursory examination of the cpp output indicated that
+ *          every function was preceded by a cpp comment statement.
+ *          So we just need to look at statements beginning after comments.
+ *          Unfortunately, this is NOT the case.  Some functions start
+ *          without cpp comment lines, typically when there are no
+ *          comments in the source that immediately precede the function.
+ *      (3) Plan 2.  Consider the keywords in the language that start
+ *          parts of the cpp file.  Some, like 'typedef', 'enum',
+ *          'union' and 'struct', are followed after a while by '{',
+ *          and eventually end with '}, plus an optional token and a
+ *          final ';'  Others, like 'extern' and 'static', are never
+ *          the beginnings of global function definitions.   Function
+ *          prototypes have one or more sets of '(' followed eventually
+ *          by a ')', and end with ';'.  But function definitions have
+ *          tokens, followed by '(', more tokens, ')' and then
+ *          immediately a '{'.  We would generate a prototype from this
+ *          by adding a ';' to all tokens up to the ')'.  So we use
+ *          these special tokens to decide what we are parsing.  And
+ *          whenever a function definition is found and the prototype
+ *          extracted, we skip through the rest of the function
+ *          past the corresponding '}'.  This token ends a line, and
+ *          is often on a line of its own.  But as it turns out,
+ *          the only keyword we need to consider is 'static'.
+ *      (4) Plan 3.  Consider the parentheses and braces for various
+ *          declarations.  A struct, enum, or union has a pair of
+ *          braces followed by a semicolon.  They cannot have parentheses
+ *          before the left brace, but a struct can have lots of parentheses
+ *          within the brace set.  A function prototype has no braces.
+ *          A function declaration can have sets of left and right
+ *          parentheses, but these are followed by a left brace.
+ *          So plan 3 looks at the way parentheses and braces are
+ *          organized.  Once the beginning of a function definition
+ *          is found, the prototype is extracted and we search for
+ *          the ending right brace.
+ *      (5) To find the ending right brace, it is necessary to do some
+ *          careful parsing.  For example, in this file, we have
+ *          left and right braces as characters, and these must not
+ *          be counted.  Somewhat more tricky, the file fhmtauto.c
+ *          generates code, and includes a right brace in a string.
+ *          So we must not include braces that are in strings.  But how
+ *          do we know if something is inside a string?  Keep state,
+ *          starting with not-inside, and every time you hit a double quote
+ *          that is not escaped, toggle the condition.  Any brace
+ *          found in the state of being within a string is ignored.
+ *      (6) When a prototype is extracted, it is put in a canonical
+ *          form (i.e., cleaned up).  Finally, we check that it is
+ *          not static and save it.  (If static, it is ignored).
+ *      (7) The @prestring for unix is NULL; it is included here so that
+ *          you can use Microsoft's declaration for importing or
+ *          exporting to a dll.  See environ.h for examples of use.
+ *          Here, we set: @prestring = "LEPT_DLL ".  Note in particular
+ *          the space character that will separate 'LEPT_DLL' from
+ *          the standard unix prototype that follows.
+ */
+char *
+parseForProtos(const char *filein,
+               const char *prestring)
+{
+char    *strdata, *str, *newstr, *parsestr, *secondword;
+l_int32  start, next, stop, charindex, found;
+size_t   nbytes;
+SARRAY  *sa, *saout, *satest;
+
+    PROCNAME("parseForProtos");
+
+    if (!filein)
+        return (char *)ERROR_PTR("filein not defined", procName, NULL);
+
+        /* Read in the cpp output into memory, one string for each
+         * line in the file, omitting blank lines.  */
+    strdata = (char *)l_binaryRead(filein, &nbytes);
+    sa = sarrayCreateLinesFromString(strdata, 0);
+
+    saout = sarrayCreate(0);
+    next = 0;
+    while (1) {  /* repeat after each non-static prototype is extracted */
+        searchForProtoSignature(sa, next, &start, &stop, &charindex, &found);
+        if (!found)
+            break;
+/*        fprintf(stderr, "  start = %d, stop = %d, charindex = %d\n",
+                start, stop, charindex); */
+        str = captureProtoSignature(sa, start, stop, charindex);
+
+            /* Make sure that the signature found by cpp is neither
+             * static nor extern.  We get 'extern' declarations from
+             * header files, and with some versions of cpp running on
+             * #include <sys/stat.h> we get something of the form:
+             *    extern ... (( ... )) ... ( ... ) { ...
+             * For this, the 1st '(' is the lp, the 2nd ')' is the rp,
+             * and there is a lot of garbage between the rp and the lb.
+             * It is easiest to simply reject any signature that starts
+             * with 'extern'.  Note also that an 'extern' token has been
+             * prepended to each prototype, so the 'static' or
+             * 'extern' keywords we are looking for, if they exist,
+             * would be the second word. */
+        satest = sarrayCreateWordsFromString(str);
+        secondword = sarrayGetString(satest, 1, L_NOCOPY);
+        if (strcmp(secondword, "static") &&  /* not static */
+            strcmp(secondword, "extern")) {  /* not extern */
+            if (prestring) {  /* prepend it to the prototype */
+                newstr = stringJoin(prestring, str);
+                sarrayAddString(saout, newstr, L_INSERT);
+                LEPT_FREE(str);
+            } else {
+                sarrayAddString(saout, str, L_INSERT);
+            }
+        } else {
+            LEPT_FREE(str);
+        }
+        sarrayDestroy(&satest);
+
+        skipToEndOfFunction(sa, stop, charindex, &next);
+        if (next == -1) break;
+    }
+
+        /* Flatten into a string with newlines between prototypes */
+    parsestr = sarrayToString(saout, 1);
+    LEPT_FREE(strdata);
+    sarrayDestroy(&sa);
+    sarrayDestroy(&saout);
+
+    return parsestr;
+}
+
+
+/*
+ *  getNextNonCommentLine()
+ *
+ *      Input:  sa (output from cpp, by line)
+ *              start (starting index to search)
+ *              &next (<return> index of first uncommented line after
+ *                     the start line)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Skips over all consecutive comment lines, beginning at 'start'
+ *      (2) If all lines to the end are '#' comments, return next = -1
+ */
+static l_int32
+getNextNonCommentLine(SARRAY  *sa,
+                      l_int32  start,
+                      l_int32 *pnext)
+{
+char    *str;
+l_int32  i, n;
+
+    PROCNAME("getNextNonCommentLine");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!pnext)
+        return ERROR_INT("&pnext not defined", procName, 1);
+
+        /* Init for situation where this line and all following are comments */
+    *pnext = -1;
+
+    n = sarrayGetCount(sa);
+    for (i = start; i < n; i++) {
+        if ((str = sarrayGetString(sa, i, L_NOCOPY)) == NULL)
+            return ERROR_INT("str not returned; shouldn't happen", procName, 1);
+        if (str[0] != '#') {
+            *pnext = i;
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+
+/*
+ *  getNextNonBlankLine()
+ *
+ *      Input:  sa (output from cpp, by line)
+ *              start (starting index to search)
+ *              &next (<return> index of first nonblank line after
+ *                     the start line)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Skips over all consecutive blank lines, beginning at 'start'
+ *      (2) A blank line has only whitespace characters (' ', '\t', '\n', '\r')
+ *      (3) If all lines to the end are blank, return next = -1
+ */
+static l_int32
+getNextNonBlankLine(SARRAY  *sa,
+                    l_int32  start,
+                    l_int32 *pnext)
+{
+char    *str;
+l_int32  i, j, n, len;
+
+    PROCNAME("getNextNonBlankLine");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!pnext)
+        return ERROR_INT("&pnext not defined", procName, 1);
+
+        /* Init for situation where this line and all following are blank */
+    *pnext = -1;
+
+    n = sarrayGetCount(sa);
+    for (i = start; i < n; i++) {
+        if ((str = sarrayGetString(sa, i, L_NOCOPY)) == NULL)
+            return ERROR_INT("str not returned; shouldn't happen", procName, 1);
+        len = strlen(str);
+        for (j = 0; j < len; j++) {
+            if (str[j] != ' ' && str[j] != '\t'
+                && str[j] != '\n' && str[j] != '\r') {  /* non-blank */
+                *pnext = i;
+                return 0;
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*
+ *  getNextNonDoubleSlashLine()
+ *
+ *      Input:  sa (output from cpp, by line)
+ *              start (starting index to search)
+ *              &next (<return> index of first uncommented line after
+ *                     the start line)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Skips over all consecutive '//' lines, beginning at 'start'
+ *      (2) If all lines to the end start with '//', return next = -1
+ */
+static l_int32
+getNextNonDoubleSlashLine(SARRAY  *sa,
+                          l_int32  start,
+                          l_int32 *pnext)
+{
+char    *str;
+l_int32  i, n, len;
+
+    PROCNAME("getNextNonDoubleSlashLine");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!pnext)
+        return ERROR_INT("&pnext not defined", procName, 1);
+
+        /* Init for situation where this line and all following
+         * start with '//' */
+    *pnext = -1;
+
+    n = sarrayGetCount(sa);
+    for (i = start; i < n; i++) {
+        if ((str = sarrayGetString(sa, i, L_NOCOPY)) == NULL)
+            return ERROR_INT("str not returned; shouldn't happen", procName, 1);
+        len = strlen(str);
+        if (len < 2 || str[0] != '/' || str[1] != '/') {
+            *pnext = i;
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+
+/*
+ *  searchForProtoSignature()
+ *
+ *      Input:  sa (output from cpp, by line)
+ *              begin (beginning index to search)
+ *              &start (<return> starting index for function definition)
+ *              &stop (<return> index of line on which proto is completed)
+ *              &charindex (<return> char index of completing ')' character)
+ *              &found (<return> 1 if valid signature is found; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If this returns found == 0, it means that there are no
+ *          more function definitions in the file.  Caller must check
+ *          this value and exit the loop over the entire cpp file.
+ *      (2) This follows plan 3 (see above).  We skip comment and blank
+ *          lines at the beginning.  Then we don't check for keywords.
+ *          Instead, find the relative locations of the first occurrences
+ *          of these four tokens: left parenthesis (lp), right
+ *          parenthesis (rp), left brace (lb) and semicolon (sc).
+ *      (3) The signature of a function definition looks like this:
+ *               .... '(' .... ')' '{'
+ *          where the lp and rp must both precede the lb, with only
+ *          whitespace between the rp and the lb.  The '....'
+ *          are sets of tokens that have no braces.
+ *      (4) If a function definition is found, this returns found = 1,
+ *          with 'start' being the first line of the definition and
+ *          'charindex' being the position of the ')' in line 'stop'
+ *          at the end of the arg list.
+ */
+static l_int32
+searchForProtoSignature(SARRAY   *sa,
+                        l_int32   begin,
+                        l_int32  *pstart,
+                        l_int32  *pstop,
+                        l_int32  *pcharindex,
+                        l_int32  *pfound)
+{
+l_int32  next, rbline, rbindex, scline;
+l_int32  soffsetlp, soffsetrp, soffsetlb, soffsetsc;
+l_int32  boffsetlp, boffsetrp, boffsetlb, boffsetsc;
+l_int32  toffsetlp, toffsetrp, toffsetlb, toffsetsc;
+
+    PROCNAME("searchForProtoSignature");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!pstart)
+        return ERROR_INT("&start not defined", procName, 1);
+    if (!pstop)
+        return ERROR_INT("&stop not defined", procName, 1);
+    if (!pcharindex)
+        return ERROR_INT("&charindex not defined", procName, 1);
+    if (!pfound)
+        return ERROR_INT("&found not defined", procName, 1);
+
+    *pfound = FALSE;
+
+    while (1) {
+
+            /* Skip over sequential '#' comment lines */
+        getNextNonCommentLine(sa, begin, &next);
+        if (next == -1) return 0;
+        if (next != begin) {
+            begin = next;
+            continue;
+        }
+
+            /* Skip over sequential blank lines */
+        getNextNonBlankLine(sa, begin, &next);
+        if (next == -1) return 0;
+        if (next != begin) {
+            begin = next;
+            continue;
+        }
+
+            /* Skip over sequential lines starting with '//' */
+        getNextNonDoubleSlashLine(sa, begin, &next);
+        if (next == -1) return 0;
+        if (next != begin) {
+            begin = next;
+            continue;
+        }
+
+            /* Search for specific character sequence patterns; namely
+             * a lp, a matching rp, a lb and a semicolon.
+             * Abort the search if no lp is found. */
+        getOffsetForCharacter(sa, next, '(', &soffsetlp, &boffsetlp,
+                              &toffsetlp);
+        if (soffsetlp == -1)
+            break;
+        getOffsetForMatchingRP(sa, next, soffsetlp, boffsetlp, toffsetlp,
+                               &soffsetrp, &boffsetrp, &toffsetrp);
+        getOffsetForCharacter(sa, next, '{', &soffsetlb, &boffsetlb,
+                              &toffsetlb);
+        getOffsetForCharacter(sa, next, ';', &soffsetsc, &boffsetsc,
+                              &toffsetsc);
+
+            /* We've found a lp.  Now weed out the case where a matching
+             * rp and a lb are not both found. */
+        if (soffsetrp == -1 || soffsetlb == -1)
+            break;
+
+            /* Check if a left brace occurs before a left parenthesis;
+             * if so, skip it */
+        if (toffsetlb < toffsetlp) {
+            skipToMatchingBrace(sa, next + soffsetlb, boffsetlb,
+                &rbline, &rbindex);
+            skipToSemicolon(sa, rbline, rbindex, &scline);
+            begin = scline + 1;
+            continue;
+        }
+
+            /* Check if a semicolon occurs before a left brace or
+             * a left parenthesis; if so, skip it */
+        if ((soffsetsc != -1) &&
+            (toffsetsc < toffsetlb || toffsetsc < toffsetlp)) {
+            skipToSemicolon(sa, next, 0, &scline);
+            begin = scline + 1;
+            continue;
+        }
+
+            /* OK, it should be a function definition.  We haven't
+             * checked that there is only white space between the
+             * rp and lb, but we've only seen problems with two
+             * extern inlines in sys/stat.h, and this is handled
+             * later by eliminating any prototype beginning with 'extern'. */
+        *pstart = next;
+        *pstop = next + soffsetrp;
+        *pcharindex = boffsetrp;
+        *pfound = TRUE;
+        break;
+    }
+
+    return 0;
+}
+
+
+/*
+ *  captureProtoSignature()
+ *
+ *      Input:  sa (output from cpp, by line)
+ *              start (starting index to search; never a comment line)
+ *              stop (index of line on which pattern is completed)
+ *              charindex (char index of completing ')' character)
+ *      Return: cleanstr (prototype string), or NULL on error
+ *
+ *  Notes:
+ *      (1) Return all characters, ending with a ';' after the ')'
+ */
+static char *
+captureProtoSignature(SARRAY  *sa,
+                      l_int32  start,
+                      l_int32  stop,
+                      l_int32  charindex)
+{
+char    *str, *newstr, *protostr, *cleanstr;
+SARRAY  *sap;
+l_int32  i;
+
+    PROCNAME("captureProtoSignature");
+
+    if (!sa)
+        return (char *)ERROR_PTR("sa not defined", procName, NULL);
+
+    sap = sarrayCreate(0);
+    for (i = start; i < stop; i++) {
+        str = sarrayGetString(sa, i, L_COPY);
+        sarrayAddString(sap, str, L_INSERT);
+    }
+    str = sarrayGetString(sa, stop, L_COPY);
+    str[charindex + 1] = '\0';
+    newstr = stringJoin(str, ";");
+    sarrayAddString(sap, newstr, L_INSERT);
+    LEPT_FREE(str);
+    protostr = sarrayToString(sap, 2);
+    sarrayDestroy(&sap);
+    cleanstr = cleanProtoSignature(protostr);
+    LEPT_FREE(protostr);
+
+    return cleanstr;
+}
+
+
+/*
+ *  cleanProtoSignature()
+ *
+ *      Input:  instr (input prototype string)
+ *      Return: cleanstr (clean prototype string), or NULL on error
+ *
+ *  Notes:
+ *      (1) Adds 'extern' at beginning and regularizes spaces
+ *          between tokens.
+ */
+static char *
+cleanProtoSignature(char *instr)
+{
+char    *str, *cleanstr;
+char     buf[L_BUF_SIZE];
+char     externstring[] = "extern";
+l_int32  i, j, nwords, nchars, index, len;
+SARRAY  *sa, *saout;
+
+    PROCNAME("cleanProtoSignature");
+
+    if (!instr)
+        return (char *)ERROR_PTR("instr not defined", procName, NULL);
+
+    sa = sarrayCreateWordsFromString(instr);
+    nwords = sarrayGetCount(sa);
+    saout = sarrayCreate(0);
+    sarrayAddString(saout, externstring, L_COPY);
+    for (i = 0; i < nwords; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        nchars = strlen(str);
+        index = 0;
+        for (j = 0; j < nchars; j++) {
+            if (index > L_BUF_SIZE - 6)
+                return (char *)ERROR_PTR("token too large", procName, NULL);
+            if (str[j] == '(') {
+                buf[index++] = ' ';
+                buf[index++] = '(';
+                buf[index++] = ' ';
+            } else if (str[j] == ')') {
+                buf[index++] = ' ';
+                buf[index++] = ')';
+            } else {
+                buf[index++] = str[j];
+            }
+        }
+        buf[index] = '\0';
+        sarrayAddString(saout, buf, L_COPY);
+    }
+
+        /* Flatten to a prototype string with spaces added after
+         * each word, and remove the last space */
+    cleanstr = sarrayToString(saout, 2);
+    len = strlen(cleanstr);
+    cleanstr[len - 1] = '\0';
+
+    sarrayDestroy(&sa);
+    sarrayDestroy(&saout);
+    return cleanstr;
+}
+
+
+/*
+ *  skipToEndOfFunction()
+ *
+ *      Input:  sa (output from cpp, by line)
+ *              start (index of starting line with left bracket to search)
+ *              lbindex (starting char index for left bracket)
+ *              &next (index of line following the ending '}' for function
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+skipToEndOfFunction(SARRAY   *sa,
+                    l_int32   start,
+                    l_int32   lbindex,
+                    l_int32  *pnext)
+{
+l_int32  end, rbindex;
+l_int32 soffsetlb, boffsetlb, toffsetlb;
+
+    PROCNAME("skipToEndOfFunction");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!pnext)
+        return ERROR_INT("&next not defined", procName, 1);
+
+    getOffsetForCharacter(sa, start, '{', &soffsetlb, &boffsetlb,
+                &toffsetlb);
+    skipToMatchingBrace(sa, start + soffsetlb, boffsetlb, &end, &rbindex);
+    if (end == -1) {  /* shouldn't happen! */
+        *pnext = -1;
+        return 1;
+    }
+
+    *pnext = end + 1;
+    return 0;
+}
+
+
+/*
+ *  skipToMatchingBrace()
+ *
+ *      Input:  sa (output from cpp, by line)
+ *              start (index of starting line with left bracket to search)
+ *              lbindex (starting char index for left bracket)
+ *              &stop (index of line with the matching right bracket)
+ *              &rbindex (char index of matching right bracket)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If the matching right brace is not found, returns
+ *          stop = -1.  This shouldn't happen.
+ */
+static l_int32
+skipToMatchingBrace(SARRAY   *sa,
+                    l_int32   start,
+                    l_int32   lbindex,
+                    l_int32  *pstop,
+                    l_int32  *prbindex)
+{
+char    *str;
+l_int32  i, j, jstart, n, sumbrace, found, instring, nchars;
+
+    PROCNAME("skipToMatchingBrace");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!pstop)
+        return ERROR_INT("&stop not defined", procName, 1);
+    if (!prbindex)
+        return ERROR_INT("&rbindex not defined", procName, 1);
+
+    instring = 0;  /* init to FALSE; toggle on double quotes */
+    *pstop = -1;
+    n = sarrayGetCount(sa);
+    sumbrace = 1;
+    found = FALSE;
+    for (i = start; i < n; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        jstart = 0;
+        if (i == start)
+            jstart = lbindex + 1;
+        nchars = strlen(str);
+        for (j = jstart; j < nchars; j++) {
+                /* Toggle the instring state every time you encounter
+                 * a double quote that is NOT escaped. */
+            if (j == jstart && str[j] == '\"')
+                instring = 1 - instring;
+            if (j > jstart && str[j] == '\"' && str[j-1] != '\\')
+                instring = 1 - instring;
+                /* Record the braces if they are neither a literal character
+                 * nor within a string. */
+            if (str[j] == '{' && str[j+1] != '\'' && !instring) {
+                sumbrace++;
+            } else if (str[j] == '}' && str[j+1] != '\'' && !instring) {
+                sumbrace--;
+                if (sumbrace == 0) {
+                    found = TRUE;
+                    *prbindex = j;
+                    break;
+                }
+            }
+        }
+        if (found) {
+            *pstop = i;
+            return 0;
+        }
+    }
+
+    return ERROR_INT("matching right brace not found", procName, 1);
+}
+
+
+/*
+ *  skipToSemicolon()
+ *
+ *      Input:  sa (output from cpp, by line)
+ *              start (index of starting line to search)
+ *              charindex (starting char index for search)
+ *              &next (index of line containing the next ';')
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If the semicolon isn't found, returns next = -1.
+ *          This shouldn't happen.
+ *      (2) This is only used in contexts where the semicolon is
+ *          not within a string.
+ */
+static l_int32
+skipToSemicolon(SARRAY   *sa,
+                l_int32   start,
+                l_int32   charindex,
+                l_int32  *pnext)
+{
+char    *str;
+l_int32  i, j, n, jstart, nchars, found;
+
+    PROCNAME("skipToSemicolon");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!pnext)
+        return ERROR_INT("&next not defined", procName, 1);
+
+    *pnext = -1;
+    n = sarrayGetCount(sa);
+    found = FALSE;
+    for (i = start; i < n; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        jstart = 0;
+        if (i == start)
+            jstart = charindex + 1;
+        nchars = strlen(str);
+        for (j = jstart; j < nchars; j++) {
+            if (str[j] == ';') {
+                found = TRUE;;
+                break;
+            }
+        }
+        if (found) {
+            *pnext = i;
+            return 0;
+        }
+    }
+
+    return ERROR_INT("semicolon not found", procName, 1);
+}
+
+
+/*
+ *  getOffsetForCharacter()
+ *
+ *      Input:  sa (output from cpp, by line)
+ *              start (starting index in sa to search; never a comment line)
+ *              tchar (we are searching for the first instance of this)
+ *              &soffset (<return> offset in strings from start index)
+ *              &boffset (<return> offset in bytes within string in which
+ *                        the character is first found)
+ *              &toffset (<return> offset in total bytes from beginning of
+ *                        string indexed by 'start' to the location where
+ *                        the character is first found)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) We are searching for the first instance of 'tchar', starting
+ *          at the beginning of the string indexed by start.
+ *      (2) If the character is not found, soffset is returned as -1,
+ *          and the other offsets are set to very large numbers.  The
+ *          caller must check the value of soffset.
+ *      (3) This is only used in contexts where it is not necessary to
+ *          consider if the character is inside a string.
+ */
+static l_int32
+getOffsetForCharacter(SARRAY   *sa,
+                      l_int32   start,
+                      char      tchar,
+                      l_int32  *psoffset,
+                      l_int32  *pboffset,
+                      l_int32  *ptoffset)
+{
+char    *str;
+l_int32  i, j, n, nchars, totchars, found;
+
+    PROCNAME("getOffsetForCharacter");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!psoffset)
+        return ERROR_INT("&soffset not defined", procName, 1);
+    if (!pboffset)
+        return ERROR_INT("&boffset not defined", procName, 1);
+    if (!ptoffset)
+        return ERROR_INT("&toffset not defined", procName, 1);
+
+    *psoffset = -1;  /* init to not found */
+    *pboffset = 100000000;
+    *ptoffset = 100000000;
+
+    n = sarrayGetCount(sa);
+    found = FALSE;
+    totchars = 0;
+    for (i = start; i < n; i++) {
+        if ((str = sarrayGetString(sa, i, L_NOCOPY)) == NULL)
+            return ERROR_INT("str not returned; shouldn't happen", procName, 1);
+        nchars = strlen(str);
+        for (j = 0; j < nchars; j++) {
+            if (str[j] == tchar) {
+                found = TRUE;
+                break;
+            }
+        }
+        if (found)
+            break;
+        totchars += nchars;
+    }
+
+    if (found) {
+        *psoffset = i - start;
+        *pboffset = j;
+        *ptoffset = totchars + j;
+    }
+
+    return 0;
+}
+
+
+/*
+ *  getOffsetForMatchingRP()
+ *
+ *      Input:  sa (output from cpp, by line)
+ *              start (starting index in sa to search; never a comment line)
+ *              soffsetlp (string offset to first LP)
+ *              boffsetlp (byte offset within string to first LP)
+ *              toffsetlp (total byte offset to first LP)
+ *              &soffset (<return> offset in strings from start index)
+ *              &boffset (<return> offset in bytes within string in which
+ *                        the matching RP is found)
+ *              &toffset (<return> offset in total bytes from beginning of
+ *                        string indexed by 'start' to the location where
+ *                        the matching RP is found);
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) We are searching for the matching right parenthesis (RP) that
+ *          corresponds to the first LP found beginning at the string
+ *          indexed by start.
+ *      (2) If the matching RP is not found, soffset is returned as -1,
+ *          and the other offsets are set to very large numbers.  The
+ *          caller must check the value of soffset.
+ *      (3) This is only used in contexts where it is not necessary to
+ *          consider if the character is inside a string.
+ *      (4) We must do this because although most arg lists have a single
+ *          left and right parenthesis, it is possible to construct
+ *          more complicated prototype declarations, such as those
+ *          where functions are passed in.  The C++ rules for prototypes
+ *          are strict, and require that for functions passed in as args,
+ *          the function name arg be placed in parenthesis, as well
+ *          as its arg list, thus incurring two extra levels of parentheses.
+ */
+static l_int32
+getOffsetForMatchingRP(SARRAY   *sa,
+                       l_int32   start,
+                       l_int32   soffsetlp,
+                       l_int32   boffsetlp,
+                       l_int32   toffsetlp,
+                       l_int32  *psoffset,
+                       l_int32  *pboffset,
+                       l_int32  *ptoffset)
+{
+char    *str;
+l_int32  i, j, n, nchars, totchars, leftmatch, firstline, jstart, found;
+
+    PROCNAME("getOffsetForMatchingRP");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!psoffset)
+        return ERROR_INT("&soffset not defined", procName, 1);
+    if (!pboffset)
+        return ERROR_INT("&boffset not defined", procName, 1);
+    if (!ptoffset)
+        return ERROR_INT("&toffset not defined", procName, 1);
+
+    *psoffset = -1;  /* init to not found */
+    *pboffset = 100000000;
+    *ptoffset = 100000000;
+
+    n = sarrayGetCount(sa);
+    found = FALSE;
+    totchars = toffsetlp;
+    leftmatch = 1;  /* count of (LP - RP); we're finished when it goes to 0. */
+    firstline = start + soffsetlp;
+    for (i = firstline; i < n; i++) {
+        if ((str = sarrayGetString(sa, i, L_NOCOPY)) == NULL)
+            return ERROR_INT("str not returned; shouldn't happen", procName, 1);
+        nchars = strlen(str);
+        jstart = 0;
+        if (i == firstline)
+            jstart = boffsetlp + 1;
+        for (j = jstart; j < nchars; j++) {
+            if (str[j] == '(')
+                leftmatch++;
+            else if (str[j] == ')')
+                leftmatch--;
+            if (leftmatch == 0) {
+                found = TRUE;
+                break;
+            }
+        }
+        if (found)
+            break;
+        if (i == firstline)
+            totchars += nchars - boffsetlp;
+        else
+            totchars += nchars;
+    }
+
+    if (found) {
+        *psoffset = i - start;
+        *pboffset = j;
+        *ptoffset = totchars + j;
+    }
+
+    return 0;
+}
diff --git a/src/partition.c b/src/partition.c
new file mode 100644 (file)
index 0000000..a0040c3
--- /dev/null
@@ -0,0 +1,643 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   partition.c
+ *
+ *      Whitespace block extraction
+ *          BOXA            *boxaGetWhiteblocks()
+ *
+ *      Helpers
+ *          static PARTEL   *partelCreate()
+ *          static void      partelDestroy()
+ *          static l_int32   partelSetSize()
+ *          static BOXA     *boxaGenerateSubboxes()
+ *          static BOX      *boxaSelectPivotBox()
+ *          static l_int32   boxaCheckIfOverlapIsSmall()
+ *          BOXA            *boxaPruneSortedOnOverlap()
+ */
+
+#include "allheaders.h"
+
+struct PartitionElement {
+    l_float32  size;  /* sorting key */
+    BOX       *box;   /* region of the element */
+    BOXA      *boxa;  /* set of intersecting boxes */
+};
+typedef struct PartitionElement PARTEL;
+
+static PARTEL * partelCreate(BOX *box);
+static void partelDestroy(PARTEL **ppartel);
+static l_int32 partelSetSize(PARTEL *partel, l_int32 sortflag);
+static BOXA * boxaGenerateSubboxes(BOX *box, BOXA *boxa, l_int32 maxperim,
+                                   l_float32  fract);
+static BOX * boxaSelectPivotBox(BOX *box, BOXA *boxa, l_int32 maxperim,
+                                l_float32 fract);
+static l_int32 boxCheckIfOverlapIsBig(BOX *box, BOXA *boxa,
+                                      l_float32 maxoverlap);
+
+static const l_int32  DEFAULT_MAX_POPS = 20000;  /* a big number! */
+
+
+#ifndef  NO_CONSOLE_IO
+#define  OUTPUT_HEAP_STATS   0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*------------------------------------------------------------------*
+ *                    Whitespace block extraction                   *
+ *------------------------------------------------------------------*/
+/*!
+ *  boxaGetWhiteblocks()
+ *
+ *      Input:  boxas (typically, a set of bounding boxes of fg components)
+ *              box (initial region; typically including all boxes in boxas;
+ *                   if null, it computes the region to include all boxes
+ *                   in boxas)
+ *              sortflag (L_SORT_BY_WIDTH, L_SORT_BY_HEIGHT,
+ *                        L_SORT_BY_MIN_DIMENSION, L_SORT_BY_MAX_DIMENSION,
+ *                        L_SORT_BY_PERIMETER, L_SORT_BY_AREA)
+ *              maxboxes (maximum number of output whitespace boxes; e.g., 100)
+ *              maxoverlap (maximum fractional overlap of a box by any
+ *                          of the larger boxes; e.g., 0.2)
+ *              maxperim (maximum half-perimeter, in pixels, for which
+ *                        pivot is selected by proximity to box centroid;
+ *                        e.g., 200)
+ *              fract (fraction of box diagonal that is an acceptable
+ *                     distance from the box centroid to select the pivot;
+ *                     e.g., 0.2)
+ *              maxpops (maximum number of pops from the heap; use 0 as default)
+ *      Return: boxa (of sorted whitespace boxes), or null on error
+ *
+ *  Notes:
+ *      (1) This uses the elegant Breuel algorithm, found in "Two
+ *          Geometric Algorithms for Layout Analysis", 2002,
+ *          url: "citeseer.ist.psu.edu/breuel02two.html".
+ *          It starts with the bounding boxes (b.b.) of the connected
+ *          components (c.c.) in a region, along with the rectangle
+ *          representing that region.  It repeatedly divides the
+ *          rectangle into four maximal rectangles that exclude a
+ *          pivot rectangle, sorting them in a priority queue
+ *          according to one of the six sort flags.  It returns a boxa
+ *          of the "largest" set that have no intersection with boxes
+ *          from the input boxas.
+ *      (2) If box == NULL, the initial region is the minimal region
+ *          that includes the origin and every box in boxas.
+ *      (3) maxboxes is the maximum number of whitespace boxes that will
+ *          be returned.  The actual number will depend on the image
+ *          and the values chosen for maxoverlap and maxpops.  In many
+ *          cases, the actual number will be 'maxboxes'.
+ *      (4) maxoverlap allows pruning of whitespace boxes depending on
+ *          the overlap.  To avoid all pruning, use maxoverlap = 1.0.
+ *          To select only boxes that have no overlap with each other
+ *          (maximal pruning), choose maxoverlap = 0.0.
+ *          Otherwise, no box can have more than the 'maxoverlap' fraction
+ *          of its area overlapped by any larger (in the sense of the
+ *          sortflag) box.
+ *      (5) Choose maxperim (actually, maximum half-perimeter) to
+ *          represent a c.c. that is small enough so that you don't care
+ *          about the white space that could be inside of it.  For all such
+ *          c.c., the pivot for 'quadfurcation' of a rectangle is selected
+ *          as having a reasonable proximity to the rectangle centroid.
+ *      (6) Use fract in the range [0.0 ... 1.0].  Set fract = 0.0
+ *          to choose the small box nearest the centroid as the pivot.
+ *          If you choose fract > 0.0, it is suggested that you call
+ *          boxaPermuteRandom() first, to permute the boxes (see usage below).
+ *          This should reduce the search time for each of the pivot boxes.
+ *      (7) Choose maxpops to be the maximum number of rectangles that
+ *          are popped from the heap.  This is an indirect way to limit the
+ *          execution time.  Use 0 for default (a fairly large number).
+ *          At any time, you can expect the heap to contain about
+ *          2.5 times as many boxes as have been popped off.
+ *      (8) The output result is a sorted set of overlapping
+ *          boxes, constrained by 'maxboxes', 'maxoverlap' and 'maxpops'.
+ *      (9) The main defect of the method is that it abstracts out the
+ *          actual components, retaining only the b.b. for analysis.
+ *          Consider a component with a large b.b.  If this is chosen
+ *          as a pivot, all white space inside is immediately taken
+ *          out of consideration.  Furthermore, even if it is never chosen
+ *          as a pivot, as the partitioning continues, at no time will
+ *          any of the whitespace inside this component be part of a
+ *          rectangle with zero overlapping boxes.  Thus, the interiors
+*           of all boxes are necessarily excluded from the union of
+*           the returned whitespace boxes.
+ *     (10) USAGE: One way to accommodate to this weakness is to remove such
+ *          large b.b. before starting the computation.  For example,
+ *          if 'box' is an input image region containing 'boxa' b.b. of c.c.:
+ *
+ *                   // Faster pivot choosing
+ *               boxaPermuteRandom(boxa, boxa);
+ *
+ *                   // Remove anything either large width or height
+ *               boxat = boxaSelectBySize(boxa, maxwidth, maxheight,
+ *                                        L_SELECT_IF_BOTH, L_SELECT_IF_LT,
+ *                                        NULL);
+ *
+ *               boxad = boxaGetWhiteblocks(boxat, box, type, maxboxes,
+ *                                          maxoverlap, maxperim, fract,
+ *                                          maxpops);
+ *
+ *          The result will be rectangular regions of "white space" that
+ *          extend into (and often through) the excluded components.
+ *     (11) As a simple example, suppose you wish to find the columns on a page.
+ *          First exclude large c.c. that may block the columns, and then call:
+ *
+ *               boxad = boxaGetWhiteblocks(boxa, box, L_SORT_BY_HEIGHT,
+ *                                          20, 0.15, 200, 0.2, 2000);
+ *
+ *          to get the 20 tallest boxes with no more than 0.15 overlap
+ *          between a box and any of the taller ones, and avoiding the
+ *          use of any c.c. with a b.b. half perimeter greater than 200
+ *          as a pivot.
+ */
+BOXA *
+boxaGetWhiteblocks(BOXA      *boxas,
+                   BOX       *box,
+                   l_int32    sortflag,
+                   l_int32    maxboxes,
+                   l_float32  maxoverlap,
+                   l_int32    maxperim,
+                   l_float32  fract,
+                   l_int32    maxpops)
+{
+l_int32  i, w, h, n, nsub, npush, npop;
+BOX     *boxsub;
+BOXA    *boxa, *boxa4, *boxasub, *boxad;
+PARTEL  *partel;
+L_HEAP  *lh;
+
+    PROCNAME("boxaGetWhiteblocks");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (sortflag != L_SORT_BY_WIDTH && sortflag != L_SORT_BY_HEIGHT &&
+        sortflag != L_SORT_BY_MIN_DIMENSION &&
+        sortflag != L_SORT_BY_MAX_DIMENSION &&
+        sortflag != L_SORT_BY_PERIMETER && sortflag != L_SORT_BY_AREA)
+        return (BOXA *)ERROR_PTR("invalid sort flag", procName, NULL);
+    if (maxboxes < 1) {
+        maxboxes = 1;
+        L_WARNING("setting maxboxes = 1\n", procName);
+    }
+    if (maxoverlap < 0.0 || maxoverlap > 1.0)
+        return (BOXA *)ERROR_PTR("invalid maxoverlap", procName, NULL);
+    if (maxpops == 0)
+        maxpops = DEFAULT_MAX_POPS;
+
+    if (!box) {
+        boxaGetExtent(boxas, &w, &h, NULL);
+        box = boxCreate(0, 0, w, h);
+    }
+
+        /* Prime the heap */
+    lh = lheapCreate(20, L_SORT_DECREASING);
+    partel = partelCreate(box);
+    partel->boxa = boxaCopy(boxas, L_CLONE);
+    partelSetSize(partel, sortflag);
+    lheapAdd(lh, partel);
+
+    boxad = boxaCreate(0);
+
+    npush = npop = 0;
+    while (1) {
+        if ((partel = (PARTEL *)lheapRemove(lh)) == NULL)  /* we're done */
+            break;
+
+        npop++;  /* How many boxes have we retrieved from the queue? */
+        if (npop > maxpops)
+            break;
+
+            /* Extract the contents */
+        boxa = boxaCopy(partel->boxa, L_CLONE);
+        box = boxClone(partel->box);
+        partelDestroy(&partel);
+
+            /* Can we output this one? */
+        n = boxaGetCount(boxa);
+        if (n == 0) {
+            if (boxCheckIfOverlapIsBig(box, boxad, maxoverlap) == 0)
+                boxaAddBox(boxad, box, L_INSERT);
+            else
+                boxDestroy(&box);
+            boxaDestroy(&boxa);
+            if (boxaGetCount(boxad) >= maxboxes)  /* we're done */
+                break;
+            continue;
+        }
+
+
+            /* Generate up to 4 subboxes and put them on the heap */
+        boxa4 = boxaGenerateSubboxes(box, boxa, maxperim, fract);
+        boxDestroy(&box);
+        nsub = boxaGetCount(boxa4);
+        for (i = 0; i < nsub; i++) {
+            boxsub = boxaGetBox(boxa4, i, L_CLONE);
+            boxasub = boxaIntersectsBox(boxa, boxsub);
+            partel = partelCreate(boxsub);
+            partel->boxa = boxasub;
+            partelSetSize(partel, sortflag);
+            lheapAdd(lh, partel);
+            boxDestroy(&boxsub);
+        }
+        npush += nsub;  /* How many boxes have we put on the queue? */
+
+/*        boxaWriteStream(stderr, boxa4); */
+
+        boxaDestroy(&boxa4);
+        boxaDestroy(&boxa);
+    }
+
+#if  OUTPUT_HEAP_STATS
+    fprintf(stderr, "Heap statistics:\n");
+    fprintf(stderr, "  Number of boxes pushed: %d\n", npush);
+    fprintf(stderr, "  Number of boxes popped: %d\n", npop);
+#endif  /* OUTPUT_HEAP_STATS */
+
+        /* Clean up the heap */
+    while ((partel = (PARTEL *)lheapRemove(lh)) != NULL)
+        partelDestroy(&partel);
+    lheapDestroy(&lh, FALSE);
+
+    return boxad;
+}
+
+
+/*------------------------------------------------------------------*
+ *                               Helpers                            *
+ *------------------------------------------------------------------*/
+/*!
+ *  partelCreate()
+ *
+ *      Input:  box (region; inserts a copy)
+ *      Return: partel, or null on error
+ */
+static PARTEL *
+partelCreate(BOX  *box)
+{
+PARTEL  *partel;
+
+    PROCNAME("partelCreate");
+
+    if ((partel = (PARTEL *)LEPT_CALLOC(1, sizeof(PARTEL))) == NULL)
+        return (PARTEL *)ERROR_PTR("partel not made", procName, NULL);
+
+    partel->box = boxCopy(box);
+    return partel;
+}
+
+
+/*!
+ *  partelDestroy()
+ *
+ *      Input:  &partel (<will be set to null before returning>)
+ *      Return: void
+ */
+static void
+partelDestroy(PARTEL  **ppartel)
+{
+PARTEL  *partel;
+
+    PROCNAME("partelDestroy");
+
+    if (ppartel == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((partel = *ppartel) == NULL)
+        return;
+
+    boxDestroy(&partel->box);
+    boxaDestroy(&partel->boxa);
+    LEPT_FREE(partel);
+    *ppartel = NULL;
+    return;
+}
+
+
+/*!
+ *  partelSetSize()
+ *
+ *      Input:  partel
+ *              sortflag (L_SORT_BY_WIDTH, L_SORT_BY_HEIGHT,
+ *                        L_SORT_BY_MIN_DIMENSION, L_SORT_BY_MAX_DIMENSION,
+ *                        L_SORT_BY_PERIMETER, L_SORT_BY_AREA)
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+partelSetSize(PARTEL  *partel,
+              l_int32  sortflag)
+{
+l_int32  w, h;
+
+    PROCNAME("partelSetSize");
+
+    if (!partel)
+        return ERROR_INT("partel not defined", procName, 1);
+
+    boxGetGeometry(partel->box, NULL, NULL, &w, &h);
+    if (sortflag == L_SORT_BY_WIDTH)
+        partel->size = (l_float32)w;
+    else if (sortflag == L_SORT_BY_HEIGHT)
+        partel->size = (l_float32)h;
+    else if (sortflag == L_SORT_BY_MIN_DIMENSION)
+        partel->size = (l_float32)L_MIN(w, h);
+    else if (sortflag == L_SORT_BY_MAX_DIMENSION)
+        partel->size = (l_float32)L_MAX(w, h);
+    else if (sortflag == L_SORT_BY_PERIMETER)
+        partel->size = (l_float32)(w + h);
+    else if (sortflag == L_SORT_BY_AREA)
+        partel->size = (l_float32)(w * h);
+    else
+        return ERROR_INT("invalid sortflag", procName, 1);
+    return 0;
+}
+
+
+/*!
+ *  boxaGenerateSubboxes()
+ *
+ *      Input:  box (region to be split into up to four overlapping subregions)
+ *              boxa (boxes of rectangles intersecting the box)
+ *              maxperim (maximum half-perimeter for which pivot
+ *                        is selected by proximity to box centroid)
+ *              fract (fraction of box diagonal that is an acceptable
+ *                     distance from the box centroid to select the pivot)
+ *      Return: boxa (of four or less overlapping subrectangles of the box),
+ *              or null on error
+ */
+static BOXA *
+boxaGenerateSubboxes(BOX       *box,
+                     BOXA      *boxa,
+                     l_int32    maxperim,
+                     l_float32  fract)
+{
+l_int32  x, y, w, h, xp, yp, wp, hp;
+BOX     *boxp;  /* pivot box */
+BOX     *boxsub;
+BOXA    *boxa4;
+
+    PROCNAME("boxaGenerateSubboxes");
+
+    if (!box)
+        return (BOXA *)ERROR_PTR("box not defined", procName, NULL);
+    if (!boxa)
+        return (BOXA *)ERROR_PTR("boxa not defined", procName, NULL);
+
+    boxa4 = boxaCreate(4);
+    boxp = boxaSelectPivotBox(box, boxa, maxperim, fract);
+    boxGetGeometry(box, &x, &y, &w, &h);
+    boxGetGeometry(boxp, &xp, &yp, &wp, &hp);
+    boxDestroy(&boxp);
+    if (xp > x) {   /* left sub-box */
+        boxsub = boxCreate(x, y, xp - x, h);
+        boxaAddBox(boxa4, boxsub, L_INSERT);
+    }
+    if (yp > y) {   /* top sub-box */
+        boxsub = boxCreate(x, y, w, yp - y);
+        boxaAddBox(boxa4, boxsub, L_INSERT);
+    }
+    if (xp + wp < x + w) {   /* right sub-box */
+        boxsub = boxCreate(xp + wp, y, x + w - xp - wp, h);
+        boxaAddBox(boxa4, boxsub, L_INSERT);
+    }
+    if (yp + hp < y + h) {   /* bottom sub-box */
+        boxsub = boxCreate(x, yp + hp, w, y + h - yp - hp);
+        boxaAddBox(boxa4, boxsub, L_INSERT);
+    }
+
+    return boxa4;
+}
+
+
+/*!
+ *  boxaSelectPivotBox()
+ *
+ *      Input:  box (containing box; to be split by the pivot box)
+ *              boxa (boxes of rectangles, from which 1 is to be chosen)
+ *              maxperim (maximum half-perimeter for which pivot
+ *                        is selected by proximity to box centroid)
+ *              fract (fraction of box diagonal that is an acceptable
+ *                     distance from the box centroid to select the pivot)
+ *      Return: box (pivot box for subdivision into 4 rectangles), or
+ *                   null on error
+ *
+ *  Notes:
+ *      (1) This is a tricky piece that wasn't discussed in the
+ *          Breuel's 2002 paper.
+ *      (2) Selects a box from boxa whose centroid is reasonably close to
+ *          the centroid of the containing box (xc, yc) and whose
+ *          half-perimeter does not exceed the maxperim value.
+ *      (3) If there are no boxes in the boxa that are small enough,
+ *          then it selects the smallest of the larger boxes,
+ *          without reference to its location in the containing box.
+ *      (4) If a small box has a centroid at a distance from the
+ *          centroid of the containing box that is not more than
+ *          the fraction 'fract' of the diagonal of the containing
+ *          box, that box is chosen as the pivot, terminating the
+ *          search for the nearest small box.
+ *      (5) Use fract in the range [0.0 ... 1.0].  Set fract = 0.0
+ *          to choose the small box nearest the centroid.
+ *      (6) Choose maxperim to represent a connected component that is
+ *          small enough so that you don't care about the white space
+ *          that could be inside of it.
+ */
+static BOX *
+boxaSelectPivotBox(BOX       *box,
+                   BOXA      *boxa,
+                   l_int32    maxperim,
+                   l_float32  fract)
+{
+l_int32    i, n, bw, bh, w, h;
+l_int32    smallfound, minindex, perim, minsize;
+l_float32  delx, dely, mindist, threshdist, dist, x, y, cx, cy;
+BOX       *boxt;
+
+    PROCNAME("boxaSelectPivotBox");
+
+    if (!box)
+        return (BOX *)ERROR_PTR("box not defined", procName, NULL);
+    if (!boxa)
+        return (BOX *)ERROR_PTR("boxa not defined", procName, NULL);
+    n = boxaGetCount(boxa);
+    if (n == 0)
+        return (BOX *)ERROR_PTR("no boxes in boxa", procName, NULL);
+    if (fract < 0.0 || fract > 1.0) {
+        L_WARNING("fract out of bounds; using 0.0\n", procName);
+        fract = 0.0;
+    }
+
+    boxGetGeometry(box, NULL, NULL, &w, &h);
+    boxGetCenter(box, &x, &y);
+    threshdist = fract * (w * w + h * h);
+    mindist = 1000000000.;
+    minindex = 0;
+    smallfound = FALSE;
+    for (i = 0; i < n; i++) {
+        boxt = boxaGetBox(boxa, i, L_CLONE);
+        boxGetGeometry(boxt, NULL, NULL, &bw, &bh);
+        boxGetCenter(boxt, &cx, &cy);
+        boxDestroy(&boxt);
+        if (bw + bh > maxperim)
+            continue;
+        smallfound = TRUE;
+        delx = cx - x;
+        dely = cy - y;
+        dist = delx * delx + dely * dely;
+        if (dist <= threshdist)
+            return boxaGetBox(boxa, i, L_COPY);
+        if (dist < mindist) {
+            minindex = i;
+            mindist = dist;
+        }
+    }
+
+        /* If there are small boxes but none are within 'fract' of the
+         * centroid, return the nearest one. */
+    if (smallfound == TRUE)
+        return boxaGetBox(boxa, minindex, L_COPY);
+
+        /* No small boxes; return the smallest of the large boxes */
+    minsize = 1000000000;
+    minindex = 0;
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, NULL, NULL, &bw, &bh);
+        perim = bw + bh;
+        if (perim < minsize) {
+            minsize = perim;
+            minindex = i;
+        }
+    }
+    return boxaGetBox(boxa, minindex, L_COPY);
+}
+
+
+/*!
+ *  boxCheckIfOverlapIsBig()
+ *
+ *      Input:  box (to be tested)
+ *              boxa (of boxes already stored)
+ *              maxoverlap (maximum fractional overlap of the input box
+ *                          by any of the boxes in boxa)
+ *      Return: 0 if box has small overlap with every box in boxa;
+ *              1 otherwise or on error
+ */
+static l_int32
+boxCheckIfOverlapIsBig(BOX       *box,
+                       BOXA      *boxa,
+                       l_float32  maxoverlap)
+{
+l_int32    i, n, bigoverlap;
+l_float32  fract;
+BOX       *boxt;
+
+    PROCNAME("boxCheckIfOverlapIsBig");
+
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (maxoverlap < 0.0 || maxoverlap > 1.0)
+        return ERROR_INT("invalid maxoverlap", procName, 1);
+
+    n = boxaGetCount(boxa);
+    if (n == 0 || maxoverlap == 1.0)
+        return 0;
+
+    bigoverlap = 0;
+    for (i = 0; i < n; i++) {
+        boxt = boxaGetBox(boxa, i, L_CLONE);
+        boxOverlapFraction(boxt, box, &fract);
+        boxDestroy(&boxt);
+        if (fract > maxoverlap) {
+            bigoverlap = 1;
+            break;
+        }
+    }
+
+    return bigoverlap;
+}
+
+
+/*!
+ *  boxaPruneSortedOnOverlap()
+ *
+ *      Input:  boxas (sorted by size in decreasing order)
+ *              maxoverlap (maximum fractional overlap of a box by any
+ *                          of the larger boxes)
+ *      Return: boxad (pruned), or null on error
+ *
+ *  Notes:
+ *      (1) This selectively removes smaller boxes when they are overlapped
+ *          by any larger box by more than the input 'maxoverlap' fraction.
+ *      (2) To avoid all pruning, use maxoverlap = 1.0.  To select only
+ *          boxes that have no overlap with each other (maximal pruning),
+ *          set maxoverlap = 0.0.
+ *      (3) If there are no boxes in boxas, returns an empty boxa.
+ */
+BOXA *
+boxaPruneSortedOnOverlap(BOXA      *boxas,
+                         l_float32  maxoverlap)
+{
+l_int32    i, j, n, remove;
+l_float32  fract;
+BOX       *box1, *box2;
+BOXA      *boxad;
+
+    PROCNAME("boxaPruneSortedOnOverlap");
+
+    if (!boxas)
+        return (BOXA *)ERROR_PTR("boxas not defined", procName, NULL);
+    if (maxoverlap < 0.0 || maxoverlap > 1.0)
+        return (BOXA *)ERROR_PTR("invalid maxoverlap", procName, NULL);
+
+    n = boxaGetCount(boxas);
+    if (n == 0 || maxoverlap == 1.0)
+        return boxaCopy(boxas, L_COPY);
+
+    boxad = boxaCreate(0);
+    box2 = boxaGetBox(boxas, 0, L_COPY);
+    boxaAddBox(boxad, box2, L_INSERT);
+    for (j = 1; j < n; j++) {   /* prune on j */
+        box2 = boxaGetBox(boxas, j, L_COPY);
+        remove = FALSE;
+        for (i = 0; i < j; i++) {   /* test on i */
+            box1 = boxaGetBox(boxas, i, L_CLONE);
+            boxOverlapFraction(box1, box2, &fract);
+            boxDestroy(&box1);
+            if (fract > maxoverlap) {
+                remove = TRUE;
+                break;
+            }
+        }
+        if (remove == TRUE)
+            boxDestroy(&box2);
+        else
+            boxaAddBox(boxad, box2, L_INSERT);
+    }
+
+    return boxad;
+}
diff --git a/src/pdfio1.c b/src/pdfio1.c
new file mode 100644 (file)
index 0000000..ac3f553
--- /dev/null
@@ -0,0 +1,2135 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pdfio1.c
+ *
+ *    Higher-level operations for generating pdf.
+ *
+ *    |=============================================================|
+ *    |                         Important note                      |
+ *    |=============================================================|
+ *    | Some of these functions require libtiff, libjpeg, and libz  |
+ *    | If you do not have these libraries, you must set            |
+ *    |      #define  USE_PDFIO     0                               |
+ *    | in environ.h.  This will link pdfiostub.c                   |
+ *    |=============================================================|
+ *
+ *     Set 1. These functions convert a set of image files
+ *     to a multi-page pdf file, with one image on each page.
+ *     All images are rendered at the same (input) resolution.
+ *     The images can be specified as being in a directory, or they
+ *     can be in an sarray.  The output pdf can be either a file
+ *     or an array of bytes in memory.
+ *
+ *     Set 2. These functions are a special case of set 1, where
+ *     no scaling or change in quality is requires.  For jpeg and
+ *     jp2k images, the bytes in each jpeg file can be directly
+ *     incorporated into the output pdf, and the wrapping up of
+ *     multiple image files is very fast.  For non-interlaced png,
+ *     the data bytes including the predictors can also be written
+ *     directly into the flate pdf data.  For other image formats,
+ *     transcoding is required, where the image data is first
+ *     decompressed and then the G4 or Flate (gzip) encodings are generated.
+ *
+ *     Set 3. These functions convert a set of images in memory
+ *     to a multi-page pdf, with one image on each page.  The pdf
+ *     output can be either a file or an array of bytes in memory.
+ *
+ *     Set 4. These functions implement a pdf output "device driver"
+ *     for wrapping (encoding) any number of images on a single page
+ *     in pdf.  The input can be either an image file or a Pix;
+ *     the pdf output can be either a file or an array of bytes in memory.
+ *
+ *     Set 5. These "segmented" functions take a set of image
+ *     files, along with optional segmentation information, and
+ *     generate a multi-page pdf file, where each page consists
+ *     in general of a mixed raster pdf of image and non-image regions.
+ *     The segmentation information for each page can be input as
+ *     either a mask over the image parts, or as a Boxa of those
+ *     regions.
+ *
+ *     Set 6. These "segmented" functions convert an image and
+ *     an optional Boxa of image regions into a mixed raster pdf file
+ *     for the page.  The input image can be either a file or a Pix.
+ *
+ *     Set 7. These functions take a set of single-page pdf files
+ *     and concatenates them into a multi-page pdf.
+ *     The input can be a set of single page pdf files, or of
+ *     pdf 'strings' in memory.  The output can be either a file or
+ *     an array of bytes in memory.
+ *
+ *     The images in the pdf file can be rendered using a pdf viewer,
+ *     such as gv, evince, xpdf or acroread.
+ *
+ *     Reference on the pdf file format:
+ *         http://www.adobe.com/devnet/pdf/pdf_reference_archive.html
+ *
+ *     1. Convert specified image files to pdf (one image file per page)
+ *          l_int32             convertFilesToPdf()
+ *          l_int32             saConvertFilesToPdf()
+ *          l_int32             saConvertFilesToPdfData()
+ *          l_int32             selectDefaultPdfEncoding()
+ *
+ *     2. Convert specified image files to pdf without scaling
+ *          l_int32             convertUnscaledFilesToPdf()
+ *          l_int32             saConvertUnscaledFilesToPdf()
+ *          l_int32             saConvertUnscaledFilesToPdfData()
+ *          l_int32             convertUnscaledToPdfData()
+ *
+ *     3. Convert multiple images to pdf (one image per page)
+ *          l_int32             pixaConvertToPdf()
+ *          l_int32             pixaConvertToPdfData()
+ *
+ *     4. Single page, multi-image converters
+ *          l_int32             convertToPdf()
+ *          l_int32             convertImageDataToPdf()
+ *          l_int32             convertToPdfData()
+ *          l_int32             convertImageDataToPdfData()
+ *          l_int32             pixConvertToPdf()
+ *          l_int32             pixWriteStreamPdf()
+ *          l_int32             pixWriteMemPdf()
+ *
+ *     5. Segmented multi-page, multi-image converter
+ *          l_int32             convertSegmentedFilesToPdf()
+ *          BOXAA              *convertNumberedMasksToBoxaa()
+ *
+ *     6. Segmented single page, multi-image converters
+ *          l_int32             convertToPdfSegmented()
+ *          l_int32             pixConvertToPdfSegmented()
+ *          l_int32             convertToPdfDataSegmented()
+ *          l_int32             pixConvertToPdfDataSegmented()
+ *
+ *     7. Multipage concatenation
+ *          l_int32             concatenatePdf()
+ *          l_int32             saConcatenatePdf()
+ *          l_int32             ptraConcatenatePdf()
+ *          l_int32             concatenatePdfToData()
+ *          l_int32             saConcatenatePdfToData()
+ *
+ *     The top-level multi-image functions can be visualized as follows:
+ *          Output pdf data to file:
+ *             convertToPdf()  and  convertImageDataToPdf()
+ *                     --> pixConvertToPdf()
+ *                           --> pixConvertToPdfData()
+ *
+ *          Output pdf data to array in memory:
+ *             convertToPdfData()  and  convertImageDataToPdfData()
+ *                     --> pixConvertToPdfData()
+ *
+ *     The top-level segmented image functions can be visualized as follows:
+ *          Output pdf data to file:
+ *             convertToPdfSegmented()
+ *                     --> pixConvertToPdfSegmented()
+ *                           --> pixConvertToPdfDataSegmented()
+ *
+ *          Output pdf data to array in memory:
+ *             convertToPdfDataSegmented()
+ *                     --> pixConvertToPdfDataSegmented()
+ *
+ *     For multi-page concatenation, there are three different types of input
+ *        (1) directory and optional filename filter
+ *        (2) sarray of filenames
+ *        (3) ptra of byte arrays of pdf data
+ *     and two types of output for the concatenated pdf data
+ *        (1) filename
+ *        (2) data array and size
+ *     High-level interfaces are given for each of the six combinations.
+ *
+ *     Note: When wrapping small images into pdf, it is useful to give
+ *     them a relatively low resolution value, to avoid rounding errors
+ *     when rendering the images.  For example, if you want an image
+ *     of width w pixels to be 5 inches wide on a screen, choose a
+ *     resolution w/5.
+ *
+ *     The very fast functions in section (2) require neither transcoding
+ *     nor parsing of the compressed jpeg file.  With three types of image
+ *     compression, the compressed strings can be incorporated into
+ *     the pdf data without decompression and re-encoding: jpeg, jp2k
+ *     and png.  The DCTDecode and JPXDecode filters can handle the
+ *     entire jpeg and jp2k encoded string as a byte array in the pdf file.
+ *     The FlateDecode filter can handle the png compressed image data,
+ *     including predictors that occur as the first byte in each
+ *     raster line, but it is necessary to store only the png IDAT chunk
+ *     data in the pdf array.  The alternative for wrapping png images
+ *     is to uncompress into a raster (a pix) and then gzip the raster data.
+ *     This typically results in a larger pdf file, because it doesn't
+ *     use the two-dimensional png predictor.  Colormaps, which are found
+ *     in png PLTE chunks, must always be pulled out and included separately
+ *     in the pdf.  For CCITT-G4 compression, you can not simply
+ *     include a tiff G4 file -- you must either parse it and extract the
+ *     G4 compressed data within it, or uncompress to a raster and
+ *     G4 compress again.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  USE_PDFIO   /* defined in environ.h */
+ /* --------------------------------------------*/
+
+    /* Typical scan resolution in ppi (pixels/inch) */
+static const l_int32  DEFAULT_INPUT_RES = 300;
+
+
+/*---------------------------------------------------------------------*
+ *    Convert specified image files to pdf (one image file per page)   *
+ *---------------------------------------------------------------------*/
+/*!
+ *  convertFilesToPdf()
+ *
+ *      Input:  directory name (containing images)
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              res (input resolution of all images)
+ *              scalefactor (scaling factor applied to each image; > 0.0)
+ *              type (encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ *                    L_FLATE_ENCODE, or 0 for default)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              title (<optional> pdf title; if null, taken from the first
+ *                     image filename)
+ *              fileout (pdf file of all images)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If @substr is not NULL, only image filenames that contain
+ *          the substring can be used.  If @substr == NULL, all files
+ *          in the directory are used.
+ *      (2) The files in the directory, after optional filtering by
+ *          the substring, are lexically sorted in increasing order
+ *          before concatenation.
+ *      (3) The scalefactor is applied to each image before encoding.
+ *          If you enter a value <= 0.0, it will be set to 1.0.
+ *      (4) Specifying one of the three encoding types for @type forces
+ *          all images to be compressed with that type.  Use 0 to have
+ *          the type determined for each image based on depth and whether
+ *          or not it has a colormap.
+ */
+l_int32
+convertFilesToPdf(const char  *dirname,
+                  const char  *substr,
+                  l_int32      res,
+                  l_float32    scalefactor,
+                  l_int32      type,
+                  l_int32      quality,
+                  const char  *title,
+                  const char  *fileout)
+{
+l_int32  ret;
+SARRAY  *sa;
+
+    PROCNAME("convertFilesToPdf");
+
+    if (!dirname)
+        return ERROR_INT("dirname not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+    if ((sa = getSortedPathnamesInDirectory(dirname, substr, 0, 0)) == NULL)
+        return ERROR_INT("sa not made", procName, 1);
+    ret = saConvertFilesToPdf(sa, res, scalefactor, type, quality,
+                              title, fileout);
+    sarrayDestroy(&sa);
+    return ret;
+}
+
+
+/*!
+ *  saConvertFilesToPdf()
+ *
+ *      Input:  sarray (of pathnames for images)
+ *              res (input resolution of all images)
+ *              scalefactor (scaling factor applied to each image; > 0.0)
+ *              type (encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ *                    L_FLATE_ENCODE, or 0 for default)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              title (<optional> pdf title; if null, taken from the first
+ *                     image filename)
+ *              fileout (pdf file of all images)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See convertFilesToPdf().
+ */
+l_int32
+saConvertFilesToPdf(SARRAY      *sa,
+                    l_int32      res,
+                    l_float32    scalefactor,
+                    l_int32      type,
+                    l_int32      quality,
+                    const char  *title,
+                    const char  *fileout)
+{
+l_uint8  *data;
+l_int32   ret;
+size_t    nbytes;
+
+    PROCNAME("saConvertFilesToPdf");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+
+    ret = saConvertFilesToPdfData(sa, res, scalefactor, type, quality,
+                                  title, &data, &nbytes);
+    if (ret) {
+        if (data) LEPT_FREE(data);
+        return ERROR_INT("pdf data not made", procName, 1);
+    }
+
+    ret = l_binaryWrite(fileout, "w", data, nbytes);
+    LEPT_FREE(data);
+    if (ret)
+        L_ERROR("pdf data not written to file\n", procName);
+    return ret;
+}
+
+
+/*!
+ *  saConvertFilesToPdfData()
+ *
+ *      Input:  sarray (of pathnames for images)
+ *              res (input resolution of all images)
+ *              scalefactor (scaling factor applied to each image; > 0.0)
+ *              type (encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ *                    L_FLATE_ENCODE, or 0 for default)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              title (<optional> pdf title; if null, taken from the first
+ *                     image filename)
+ *              &data (<return> output pdf data (of all images)
+ *              &nbytes (<return> size of output pdf data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See convertFilesToPdf().
+ */
+l_int32
+saConvertFilesToPdfData(SARRAY      *sa,
+                        l_int32      res,
+                        l_float32    scalefactor,
+                        l_int32      type,
+                        l_int32      quality,
+                        const char  *title,
+                        l_uint8    **pdata,
+                        size_t      *pnbytes)
+{
+char        *fname;
+const char  *pdftitle;
+l_uint8     *imdata;
+l_int32      i, n, ret, pagetype, npages, scaledres;
+size_t       imbytes;
+L_BYTEA     *ba;
+PIX         *pixs, *pix;
+L_PTRA      *pa_data;
+
+    PROCNAME("saConvertFilesToPdfData");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (scalefactor <= 0.0) scalefactor = 1.0;
+    if (type < 0 || type > L_FLATE_ENCODE) {
+        L_WARNING("invalid compression type; using per-page default\n",
+                  procName);
+        type = 0;
+    }
+
+        /* Generate all the encoded pdf strings */
+    n = sarrayGetCount(sa);
+    pa_data = ptraCreate(n);
+    pdftitle = NULL;
+    for (i = 0; i < n; i++) {
+        if (i && (i % 10 == 0)) fprintf(stderr, ".. %d ", i);
+        fname = sarrayGetString(sa, i, L_NOCOPY);
+        if ((pixs = pixRead(fname)) == NULL) {
+            L_ERROR("image not readable from file %s\n", procName, fname);
+            continue;
+        }
+        if (!pdftitle)
+            pdftitle = (title) ? title : fname;
+        if (scalefactor != 1.0)
+            pix = pixScale(pixs, scalefactor, scalefactor);
+        else
+            pix = pixClone(pixs);
+        scaledres = (l_int32)(res * scalefactor);
+        if (type != 0) {
+            pagetype = type;
+        } else if (selectDefaultPdfEncoding(pix, &pagetype) != 0) {
+            L_ERROR("encoding type selection failed for file %s\n",
+                    procName, fname);
+            continue;
+        }
+        ret = pixConvertToPdfData(pix, pagetype, quality, &imdata, &imbytes,
+                                  0, 0, scaledres, pdftitle, NULL, 0);
+        pixDestroy(&pix);
+        pixDestroy(&pixs);
+        if (ret) {
+            L_ERROR("pdf encoding failed for %s\n", procName, fname);
+            continue;
+        }
+        ba = l_byteaInitFromMem(imdata, imbytes);
+        if (imdata) LEPT_FREE(imdata);
+        ptraAdd(pa_data, ba);
+    }
+    ptraGetActualCount(pa_data, &npages);
+    if (npages == 0) {
+        L_ERROR("no pdf files made\n", procName);
+        ptraDestroy(&pa_data, FALSE, FALSE);
+        return 1;
+    }
+
+        /* Concatenate them */
+    fprintf(stderr, "\nconcatenating ... ");
+    ret = ptraConcatenatePdfToData(pa_data, NULL, pdata, pnbytes);
+    fprintf(stderr, "done\n");
+
+    ptraGetActualCount(pa_data, &npages);  /* recalculate in case it changes */
+    for (i = 0; i < npages; i++) {
+        ba = (L_BYTEA *)ptraRemove(pa_data, i, L_NO_COMPACTION);
+        l_byteaDestroy(&ba);
+    }
+    ptraDestroy(&pa_data, FALSE, FALSE);
+    return ret;
+}
+
+
+/*!
+ *  selectDefaultPdfEncoding()
+ *
+ *      Input:  pix
+ *              &type (<return> L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE)
+ *
+ *  Notes:
+ *      (1) This attempts to choose an encoding for the pix that results
+ *          in the smallest file, assuming that if jpeg encoded, it will
+ *          use quality = 75.  The decision is approximate, in that
+ *          (a) all colormapped images will be losslessly encoded with
+ *          gzip (flate), and (b) an image with less than about 20 colors
+ *          is likely to be smaller if flate encoded than if encoded
+ *          as a jpeg (dct).  For example, an image made by pixScaleToGray3()
+ *          will have 10 colors, and flate encoding will give about
+ *          twice the compression as jpeg with quality = 75.
+ */
+l_int32
+selectDefaultPdfEncoding(PIX      *pix,
+                         l_int32  *ptype)
+{
+l_int32   w, h, d, factor, ncolors;
+PIXCMAP  *cmap;
+
+    PROCNAME("selectDefaultPdfEncoding");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!ptype)
+        return ERROR_INT("&type not defined", procName, 1);
+    *ptype = L_FLATE_ENCODE;  /* default universal encoding */
+    pixGetDimensions(pix, &w, &h, &d);
+    cmap = pixGetColormap(pix);
+    if (d == 8 && !cmap) {
+        factor = L_MAX(1, (l_int32)sqrt((l_float64)(w * h) / 20000.));
+        pixNumColors(pix, factor, &ncolors);
+        if (ncolors < 20)
+            *ptype = L_FLATE_ENCODE;
+        else
+            *ptype = L_JPEG_ENCODE;
+    } else if (d == 1) {
+        *ptype = L_G4_ENCODE;
+    } else if (cmap || d == 2 || d == 4) {
+        *ptype = L_FLATE_ENCODE;
+    } else if (d == 8 || d == 32) {
+        *ptype = L_JPEG_ENCODE;
+    } else {
+        return ERROR_INT("type selection failure", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *          Convert specified image files to pdf without scaling       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  convertUnscaledFilesToPdf()
+ *
+ *      Input:  directory name (containing images)
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              title (<optional> pdf title; if null, taken from the first
+ *                     image filename)
+ *              fileout (pdf file of all images)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If @substr is not NULL, only image filenames that contain
+ *          the substring can be used.  If @substr == NULL, all files
+ *          in the directory are used.
+ *      (2) The files in the directory, after optional filtering by
+ *          the substring, are lexically sorted in increasing order
+ *          before concatenation.
+ *      (3) For jpeg and jp2k, this is very fast because the compressed
+ *          data is wrapped up and concatenated.  For png and tiffg4,
+ *          the images must be read and recompressed.
+ */
+l_int32
+convertUnscaledFilesToPdf(const char  *dirname,
+                          const char  *substr,
+                          const char  *title,
+                          const char  *fileout)
+{
+l_int32  ret;
+SARRAY  *sa;
+
+    PROCNAME("convertUnscaledFilesToPdf");
+
+    if (!dirname)
+        return ERROR_INT("dirname not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+    if ((sa = getSortedPathnamesInDirectory(dirname, substr, 0, 0)) == NULL)
+        return ERROR_INT("sa not made", procName, 1);
+    ret = saConvertUnscaledFilesToPdf(sa, title, fileout);
+    sarrayDestroy(&sa);
+    return ret;
+}
+
+
+/*!
+ *  saConvertUnscaledFilesToPdf()
+ *
+ *      Input:  sarray (of pathnames for images)
+ *              title (<optional> pdf title; if null, taken from the first
+ *                     image filename)
+ *              fileout (pdf file of all images)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See convertUnscaledFilesToPdf().
+ */
+l_int32
+saConvertUnscaledFilesToPdf(SARRAY      *sa,
+                            const char  *title,
+                            const char  *fileout)
+{
+l_uint8  *data;
+l_int32   ret;
+size_t    nbytes;
+
+    PROCNAME("saConvertUnscaledFilesToPdf");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+
+    ret = saConvertUnscaledFilesToPdfData(sa, title, &data, &nbytes);
+    if (ret) {
+        if (data) LEPT_FREE(data);
+        return ERROR_INT("pdf data not made", procName, 1);
+    }
+
+    ret = l_binaryWrite(fileout, "w", data, nbytes);
+    LEPT_FREE(data);
+    if (ret)
+        L_ERROR("pdf data not written to file\n", procName);
+    return ret;
+}
+
+
+/*!
+ *  saConvertUnscaledFilesToPdfData()
+ *
+ *      Input:  sarray (of pathnames for images)
+ *              title (<optional> pdf title; if null, taken from the first
+ *                     image filename)
+ *              &data (<return> output pdf data (of all images)
+ *              &nbytes (<return> size of output pdf data)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+saConvertUnscaledFilesToPdfData(SARRAY      *sa,
+                                const char  *title,
+                                l_uint8    **pdata,
+                                size_t      *pnbytes)
+{
+char         *fname;
+l_uint8      *imdata;
+l_int32       i, n, ret, npages;
+size_t        imbytes;
+L_BYTEA      *ba;
+L_PTRA       *pa_data;
+
+    PROCNAME("saConvertUnscaledFilesToPdfData");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+
+        /* Generate all the encoded pdf strings */
+    n = sarrayGetCount(sa);
+    pa_data = ptraCreate(n);
+    for (i = 0; i < n; i++) {
+        if (i && (i % 10 == 0)) fprintf(stderr, ".. %d ", i);
+        fname = sarrayGetString(sa, i, L_NOCOPY);
+
+            /* Generate the pdf data */
+        if (convertUnscaledToPdfData(fname, title, &imdata, &imbytes))
+            continue;
+
+            /* ... and add it to the array of single page data */
+        ba = l_byteaInitFromMem(imdata, imbytes);
+        if (imdata) LEPT_FREE(imdata);
+        ptraAdd(pa_data, ba);
+    }
+    ptraGetActualCount(pa_data, &npages);
+    if (npages == 0) {
+        L_ERROR("no pdf files made\n", procName);
+        ptraDestroy(&pa_data, FALSE, FALSE);
+        return 1;
+    }
+
+        /* Concatenate to generate a multipage pdf */
+    fprintf(stderr, "\nconcatenating ... ");
+    ret = ptraConcatenatePdfToData(pa_data, NULL, pdata, pnbytes);
+    fprintf(stderr, "done\n");
+
+        /* Clean up */
+    ptraGetActualCount(pa_data, &npages);  /* maybe failed to read some files */
+    for (i = 0; i < npages; i++) {
+        ba = (L_BYTEA *)ptraRemove(pa_data, i, L_NO_COMPACTION);
+        l_byteaDestroy(&ba);
+    }
+    ptraDestroy(&pa_data, FALSE, FALSE);
+    return ret;
+}
+
+
+/*!
+ *  convertUnscaledToPdfData()
+ *
+ *      Input:  fname (of image file)
+ *              title (<optional> pdf title; can be NULL)
+ *              &data (<return> output pdf data for image)
+ *              &nbytes (<return> size of output pdf data)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+convertUnscaledToPdfData(const char  *fname,
+                         const char  *title,
+                         l_uint8    **pdata,
+                         size_t      *pnbytes)
+{
+const char   *pdftitle = NULL;
+char         *tail = NULL;
+l_int32       format;
+L_COMP_DATA  *cid;
+
+    PROCNAME("convertUnscaledToPdfData");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!fname)
+        return ERROR_INT("fname not defined", procName, 1);
+
+    findFileFormat(fname, &format);
+    if (format == IFF_UNKNOWN) {
+        L_WARNING("file %s format is unknown; skip\n", procName, fname);
+        return 1;
+    }
+    if (format == IFF_PS || format == IFF_LPDF) {
+        L_WARNING("file %s format is %d; skip\n", procName, fname, format);
+        return 1;
+    }
+
+        /* Generate the image data required for pdf generation, always
+         * in binary (not ascii85) coding; jpeg files are never transcoded.  */
+    l_generateCIDataForPdf(fname, NULL, 0, &cid);
+    if (!cid) {
+        L_ERROR("file %s format is %d; unreadable\n", procName, fname, format);
+        return 1;
+    }
+
+        /* If @title == NULL, use the tail of @fname. */
+    if (title) {
+        pdftitle = title;
+    } else {
+        splitPathAtDirectory(fname, NULL, &tail);
+        pdftitle = tail;
+    }
+
+        /* Generate the pdf string for this page (image).  This destroys
+         * the cid by attaching it to an lpd and destroying the lpd. */
+    cidConvertToPdfData(cid, pdftitle, pdata, pnbytes);
+    LEPT_FREE(tail);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *          Convert multiple images to pdf (one image per page)        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaConvertToPdf()
+ *
+ *      Input:  pixa (containing images all at the same resolution)
+ *              res (override the resolution of each input image, in ppi;
+ *                   use 0 to respect the resolution embedded in the input)
+ *              scalefactor (scaling factor applied to each image; > 0.0)
+ *              type (encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ *                    L_FLATE_ENCODE, or 0 for default)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              title (<optional> pdf title; if null, taken from the first
+ *                     image filename)
+ *              fileout (pdf file of all images)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The images are encoded with G4 if 1 bpp; JPEG if 8 bpp without
+ *          colormap and many colors, or 32 bpp; FLATE for anything else.
+ *      (2) The scalefactor must be > 0.0; otherwise it is set to 1.0.
+ *      (3) Specifying one of the three encoding types for @type forces
+ *          all images to be compressed with that type.  Use 0 to have
+ *          the type determined for each image based on depth and whether
+ *          or not it has a colormap.
+ */
+l_int32
+pixaConvertToPdf(PIXA        *pixa,
+                 l_int32      res,
+                 l_float32    scalefactor,
+                 l_int32      type,
+                 l_int32      quality,
+                 const char  *title,
+                 const char  *fileout)
+{
+l_uint8  *data;
+l_int32   ret;
+size_t    nbytes;
+
+    PROCNAME("pixaConvertToPdf");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    ret = pixaConvertToPdfData(pixa, res, scalefactor, type, quality,
+                               title, &data, &nbytes);
+    if (ret) {
+        LEPT_FREE(data);
+        return ERROR_INT("conversion to pdf failed", procName, 1);
+    }
+
+    ret = l_binaryWrite(fileout, "w", data, nbytes);
+    LEPT_FREE(data);
+    if (ret)
+        L_ERROR("pdf data not written to file\n", procName);
+    return ret;
+}
+
+
+/*!
+ *  pixaConvertToPdfData()
+ *
+ *      Input:  pixa (containing images all at the same resolution)
+ *              res (input resolution of all images)
+ *              scalefactor (scaling factor applied to each image; > 0.0)
+ *              type (encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ *                    L_FLATE_ENCODE, or 0 for default)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              title (<optional> pdf title)
+ *              &data (<return> output pdf data (of all images)
+ *              &nbytes (<return> size of output pdf data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See pixaConvertToPdf().
+ */
+l_int32
+pixaConvertToPdfData(PIXA        *pixa,
+                     l_int32      res,
+                     l_float32    scalefactor,
+                     l_int32      type,
+                     l_int32      quality,
+                     const char  *title,
+                     l_uint8    **pdata,
+                     size_t      *pnbytes)
+{
+l_uint8  *imdata;
+l_int32   i, n, ret, scaledres, pagetype;
+size_t    imbytes;
+L_BYTEA  *ba;
+PIX      *pixs, *pix;
+L_PTRA   *pa_data;
+
+    PROCNAME("pixaConvertToPdfData");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (scalefactor <= 0.0) scalefactor = 1.0;
+    if (type < 0 || type > L_FLATE_ENCODE) {
+        L_WARNING("invalid compression type; using per-page default\n",
+                  procName);
+        type = 0;
+    }
+
+        /* Generate all the encoded pdf strings */
+    n = pixaGetCount(pixa);
+    pa_data = ptraCreate(n);
+    for (i = 0; i < n; i++) {
+        if ((pixs = pixaGetPix(pixa, i, L_CLONE)) == NULL) {
+            L_ERROR("pix[%d] not retrieved\n", procName, i);
+            continue;
+        }
+        if (scalefactor != 1.0)
+            pix = pixScale(pixs, scalefactor, scalefactor);
+        else
+            pix = pixClone(pixs);
+        pixDestroy(&pixs);
+        scaledres = (l_int32)(res * scalefactor);
+        if (type != 0) {
+            pagetype = type;
+        } else if (selectDefaultPdfEncoding(pix, &pagetype) != 0) {
+            L_ERROR("encoding type selection failed for pix[%d]\n",
+                        procName, i);
+            pixDestroy(&pix);
+            continue;
+        }
+        ret = pixConvertToPdfData(pix, pagetype, quality, &imdata, &imbytes,
+                                  0, 0, scaledres, title, NULL, 0);
+        pixDestroy(&pix);
+        if (ret) {
+            L_ERROR("pdf encoding failed for pix[%d]\n", procName, i);
+            continue;
+        }
+        ba = l_byteaInitFromMem(imdata, imbytes);
+        if (imdata) LEPT_FREE(imdata);
+        ptraAdd(pa_data, ba);
+    }
+    ptraGetActualCount(pa_data, &n);
+    if (n == 0) {
+        L_ERROR("no pdf files made\n", procName);
+        ptraDestroy(&pa_data, FALSE, FALSE);
+        return 1;
+    }
+
+        /* Concatenate them */
+    ret = ptraConcatenatePdfToData(pa_data, NULL, pdata, pnbytes);
+
+    ptraGetActualCount(pa_data, &n);  /* recalculate in case it changes */
+    for (i = 0; i < n; i++) {
+        ba = (L_BYTEA *)ptraRemove(pa_data, i, L_NO_COMPACTION);
+        l_byteaDestroy(&ba);
+    }
+    ptraDestroy(&pa_data, FALSE, FALSE);
+    return ret;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                Single page, multi-image converters                  *
+ *---------------------------------------------------------------------*/
+/*!
+ *  convertToPdf()
+ *
+ *      Input:  filein (input image file -- any format)
+ *              type (L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              fileout (output pdf file; only required on last image on page)
+ *              x, y (location of lower-left corner of image, in pixels,
+ *                    relative to the PostScript origin (0,0) at
+ *                    the lower-left corner of the page)
+ *              res (override the resolution of the input image, in ppi;
+ *                   use 0 to respect the resolution embedded in the input)
+ *              title (<optional> pdf title; if null, taken from filein)
+ *              &lpd (ptr to lpd, which is created on the first invocation
+ *                    and returned until last image is processed, at which
+ *                    time it is destroyed)
+ *              position (in image sequence: L_FIRST_IMAGE, L_NEXT_IMAGE,
+ *                       L_LAST_IMAGE)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) To wrap only one image in pdf, input @plpd = NULL, and
+ *          the value of @position will be ignored:
+ *            convertToPdf(...  type, quality, x, y, res, NULL, 0);
+ *      (2) To wrap multiple images on a single pdf page, this is called
+ *          once for each successive image.  Do it this way:
+ *            L_PDF_DATA   *lpd;
+ *            convertToPdf(...  type, quality, x, y, res, &lpd, L_FIRST_IMAGE);
+ *            convertToPdf(...  type, quality, x, y, res, &lpd, L_NEXT_IMAGE);
+ *            ...
+ *            convertToPdf(...  type, quality, x, y, res, &lpd, L_LAST_IMAGE);
+ *          This will write the result to the value of @fileout specified
+ *          in the first call; succeeding values of @fileout are ignored.
+ *          On the last call: the pdf data bytes are computed and written
+ *          to @fileout, lpd is destroyed internally, and the returned
+ *          value of lpd is null.  So the client has nothing to clean up.
+ *      (3) (a) Set @res == 0 to respect the resolution embedded in the
+ *              image file.  If no resolution is embedded, it will be set
+ *              to the default value.
+ *          (b) Set @res to some other value to override the file resolution.
+ *      (4) (a) If the input @res and the resolution of the output device
+ *              are equal, the image will be "displayed" at the same size
+ *              as the original.
+ *          (b) If the input @res is 72, the output device will render
+ *              the image at 1 pt/pixel.
+ *          (c) Some possible choices for the default input pix resolution are:
+ *                 72 ppi     Render pix on any output device at one pt/pixel
+ *                 96 ppi     Windows default for generated display images
+ *                300 ppi     Typical default for scanned images.
+ *              We choose 300, which is sensible for rendering page images.
+ *              However,  images come from a variety of sources, and
+ *              some are explicitly created for viewing on a display.
+ */
+l_int32
+convertToPdf(const char   *filein,
+             l_int32       type,
+             l_int32       quality,
+             const char   *fileout,
+             l_int32       x,
+             l_int32       y,
+             l_int32       res,
+             const char   *title,
+             L_PDF_DATA  **plpd,
+             l_int32       position)
+{
+l_uint8  *data;
+l_int32   ret;
+size_t    nbytes;
+
+    PROCNAME("convertToPdf");
+
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (!plpd || (position == L_LAST_IMAGE)) {
+        if (!fileout)
+            return ERROR_INT("fileout not defined", procName, 1);
+    }
+    if (type != L_G4_ENCODE && type != L_JPEG_ENCODE &&
+        type != L_FLATE_ENCODE)
+        return ERROR_INT("invalid conversion type", procName, 1);
+
+    if (convertToPdfData(filein, type, quality, &data, &nbytes, x, y,
+                         res, title, plpd, position))
+        return ERROR_INT("pdf data not made", procName, 1);
+
+    if (!plpd || (position == L_LAST_IMAGE)) {
+        ret = l_binaryWrite(fileout, "w", data, nbytes);
+        LEPT_FREE(data);
+        if (ret)
+            return ERROR_INT("pdf data not written to file", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  convertImageDataToPdf()
+ *
+ *      Input:  imdata (array of formatted image data; e.g., png, jpeg)
+ *              size (size of image data)
+ *              type (L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              fileout (output pdf file; only required on last image on page)
+ *              x, y (location of lower-left corner of image, in pixels,
+ *                    relative to the PostScript origin (0,0) at
+ *                    the lower-left corner of the page)
+ *              res (override the resolution of the input image, in ppi;
+ *                   use 0 to respect the resolution embedded in the input)
+ *              title (<optional> pdf title)
+ *              &lpd (ptr to lpd, which is created on the first invocation
+ *                    and returned until last image is processed, at which
+ *                    time it is destroyed)
+ *              position (in image sequence: L_FIRST_IMAGE, L_NEXT_IMAGE,
+ *                       L_LAST_IMAGE)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If @res == 0 and the input resolution field is 0,
+ *          this will use DEFAULT_INPUT_RES.
+ *      (2) See comments in convertToPdf().
+ */
+l_int32
+convertImageDataToPdf(l_uint8      *imdata,
+                      size_t        size,
+                      l_int32       type,
+                      l_int32       quality,
+                      const char   *fileout,
+                      l_int32       x,
+                      l_int32       y,
+                      l_int32       res,
+                      const char   *title,
+                      L_PDF_DATA  **plpd,
+                      l_int32       position)
+{
+l_int32  ret;
+PIX     *pix;
+
+    PROCNAME("convertImageDataToPdf");
+
+    if (!imdata)
+        return ERROR_INT("image data not defined", procName, 1);
+    if (type != L_G4_ENCODE && type != L_JPEG_ENCODE &&
+        type != L_FLATE_ENCODE)
+        return ERROR_INT("invalid conversion type", procName, 1);
+    if (!plpd || (position == L_LAST_IMAGE)) {
+        if (!fileout)
+            return ERROR_INT("fileout not defined", procName, 1);
+    }
+
+    if ((pix = pixReadMem(imdata, size)) == NULL)
+        return ERROR_INT("pix not read", procName, 1);
+    ret = pixConvertToPdf(pix, type, quality, fileout, x, y, res,
+                          title, plpd, position);
+    pixDestroy(&pix);
+    return ret;
+}
+
+
+/*!
+ *  convertToPdfData()
+ *
+ *      Input:  filein (input image file -- any format)
+ *              type (L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              &data (<return> pdf data in memory)
+ *              &nbytes (<return> number of bytes in pdf data)
+ *              x, y (location of lower-left corner of image, in pixels,
+ *                    relative to the PostScript origin (0,0) at
+ *                    the lower-left corner of the page)
+ *              res (override the resolution of the input image, in ppi;
+ *                   use 0 to respect the resolution embedded in the input)
+ *              title (<optional> pdf title; if null, use filein)
+ *              &lpd (ptr to lpd, which is created on the first invocation
+ *                    and returned until last image is processed, at which
+ *                    time it is destroyed)
+ *              position (in image sequence: L_FIRST_IMAGE, L_NEXT_IMAGE,
+ *                       L_LAST_IMAGE)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If @res == 0 and the input resolution field is 0,
+ *          this will use DEFAULT_INPUT_RES.
+ *      (2) See comments in convertToPdf().
+ */
+l_int32
+convertToPdfData(const char   *filein,
+                 l_int32       type,
+                 l_int32       quality,
+                 l_uint8     **pdata,
+                 size_t       *pnbytes,
+                 l_int32       x,
+                 l_int32       y,
+                 l_int32       res,
+                 const char   *title,
+                 L_PDF_DATA  **plpd,
+                 l_int32       position)
+{
+PIX  *pix;
+
+    PROCNAME("convertToPdfData");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (type != L_G4_ENCODE && type != L_JPEG_ENCODE &&
+        type != L_FLATE_ENCODE)
+        return ERROR_INT("invalid conversion type", procName, 1);
+
+    if ((pix = pixRead(filein)) == NULL)
+        return ERROR_INT("pix not made", procName, 1);
+
+    pixConvertToPdfData(pix, type, quality, pdata, pnbytes,
+                        x, y, res, (title) ? title : filein, plpd, position);
+    pixDestroy(&pix);
+    return 0;
+}
+
+
+/*!
+ *  convertImageDataToPdfData()
+ *
+ *      Input:  imdata (array of formatted image data; e.g., png, jpeg)
+ *              size (size of image data)
+ *              type (L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              &data (<return> pdf data in memory)
+ *              &nbytes (<return> number of bytes in pdf data)
+ *              x, y (location of lower-left corner of image, in pixels,
+ *                    relative to the PostScript origin (0,0) at
+ *                    the lower-left corner of the page)
+ *              res (override the resolution of the input image, in ppi;
+ *                   use 0 to respect the resolution embedded in the input)
+ *              title (<optional> pdf title)
+ *              &lpd (ptr to lpd, which is created on the first invocation
+ *                    and returned until last image is processed, at which
+ *                    time it is destroyed)
+ *              position (in image sequence: L_FIRST_IMAGE, L_NEXT_IMAGE,
+ *                       L_LAST_IMAGE)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If @res == 0 and the input resolution field is 0,
+ *          this will use DEFAULT_INPUT_RES.
+ *      (2) See comments in convertToPdf().
+ */
+l_int32
+convertImageDataToPdfData(l_uint8      *imdata,
+                          size_t        size,
+                          l_int32       type,
+                          l_int32       quality,
+                          l_uint8     **pdata,
+                          size_t       *pnbytes,
+                          l_int32       x,
+                          l_int32       y,
+                          l_int32       res,
+                          const char   *title,
+                          L_PDF_DATA  **plpd,
+                          l_int32       position)
+{
+l_int32  ret;
+PIX     *pix;
+
+    PROCNAME("convertImageDataToPdfData");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!imdata)
+        return ERROR_INT("image data not defined", procName, 1);
+    if (plpd) {  /* part of multi-page invocation */
+        if (position == L_FIRST_IMAGE)
+            *plpd = NULL;
+    }
+
+    if ((pix = pixReadMem(imdata, size)) == NULL)
+        return ERROR_INT("pix not read", procName, 1);
+    ret = pixConvertToPdfData(pix, type, quality, pdata, pnbytes,
+                              x, y, res, title, plpd, position);
+    pixDestroy(&pix);
+    return ret;
+}
+
+
+/*!
+ *  pixConvertToPdf()
+ *
+ *      Input:  pix
+ *              type (L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              fileout (output pdf file; only required on last image on page)
+ *              x, y (location of lower-left corner of image, in pixels,
+ *                    relative to the PostScript origin (0,0) at
+ *                    the lower-left corner of the page)
+ *              res (override the resolution of the input image, in ppi;
+ *                   use 0 to respect the resolution embedded in the input)
+ *              title (<optional> pdf title)
+ *              &lpd (ptr to lpd, which is created on the first invocation
+ *                    and returned until last image is processed)
+ *              position (in image sequence: L_FIRST_IMAGE, L_NEXT_IMAGE,
+ *                       L_LAST_IMAGE)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If @res == 0 and the input resolution field is 0,
+ *          this will use DEFAULT_INPUT_RES.
+ *      (2) This only writes data to fileout if it is the last
+ *          image to be written on the page.
+ *      (3) See comments in convertToPdf().
+ */
+l_int32
+pixConvertToPdf(PIX          *pix,
+                l_int32       type,
+                l_int32       quality,
+                const char   *fileout,
+                l_int32       x,
+                l_int32       y,
+                l_int32       res,
+                const char   *title,
+                L_PDF_DATA  **plpd,
+                l_int32       position)
+{
+l_uint8  *data;
+l_int32   ret;
+size_t    nbytes;
+
+    PROCNAME("pixConvertToPdf");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (type != L_G4_ENCODE && type != L_JPEG_ENCODE &&
+        type != L_FLATE_ENCODE)
+        return ERROR_INT("invalid conversion type", procName, 1);
+    if (!plpd || (position == L_LAST_IMAGE)) {
+        if (!fileout)
+            return ERROR_INT("fileout not defined", procName, 1);
+    }
+
+    if (pixConvertToPdfData(pix, type, quality, &data, &nbytes,
+                            x, y, res, title, plpd, position))
+        return ERROR_INT("pdf data not made", procName, 1);
+
+    if (!plpd || (position == L_LAST_IMAGE)) {
+        ret = l_binaryWrite(fileout, "w", data, nbytes);
+        LEPT_FREE(data);
+        if (ret)
+            return ERROR_INT("pdf data not written to file", procName, 1);
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixWriteStreamPdf()
+ *
+ *      Input:  fp (stream opened for writing)
+ *              pix (all depths, cmap OK)
+ *              res (override the resolution of the input image, in ppi;
+ *                   use 0 to respect the resolution embedded in the input)
+ *              title (<optional> pdf title; taken from the first image
+ *                     placed on a page; e.g., an input image filename)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is the simplest interface for writing a single image
+ *          with pdf encoding to a stream.  It uses G4 encoding for 1 bpp,
+ *          JPEG encoding for 8 bpp (no cmap) and 32 bpp, and FLATE
+ *          encoding for everything else.
+ */
+l_int32
+pixWriteStreamPdf(FILE        *fp,
+                  PIX         *pix,
+                  l_int32      res,
+                  const char  *title)
+{
+l_uint8  *data;
+size_t    nbytes, nbytes_written;
+
+    PROCNAME("pixWriteStreamPdf");
+
+    if (!fp)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    if (pixWriteMemPdf(&data, &nbytes, pix, res, title) != 0)
+        return ERROR_INT("pdf data not made", procName, 1);
+
+    nbytes_written = fwrite(data, 1, nbytes, fp);
+    LEPT_FREE(data);
+    if (nbytes != nbytes_written)
+        return ERROR_INT("failure writing pdf data to stream", procName, 1);
+    return 0;
+}
+
+
+/*!
+ *  pixWriteMemPdf()
+ *
+ *      Input:  &data (<return> pdf as byte array)
+ *              &nbytes (<return> number of bytes in pdf array)
+ *              pix (all depths, cmap OK)
+ *              res (override the resolution of the input image, in ppi;
+ *                   use 0 to respect the resolution embedded in the input)
+ *              title (<optional> pdf title; taken from the first image
+ *                     placed on a page; e.g., an input image filename)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is the simplest interface for writing a single image
+ *          with pdf encoding to memory.  It uses G4 encoding for 1 bpp,
+ *          JPEG encoding for 8 bpp (no cmap) and 32 bpp, and FLATE
+ *          encoding for everything else.
+ */
+l_int32
+pixWriteMemPdf(l_uint8    **pdata,
+               size_t      *pnbytes,
+               PIX         *pix,
+               l_int32      res,
+               const char  *title)
+{
+l_int32   ret, d, type;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixWriteMemPdf");
+
+    if (pdata) *pdata = NULL;
+    if (pnbytes) *pnbytes = 0;
+    if (!pdata || !pnbytes)
+        return ERROR_INT("&data or &nbytes not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    d = pixGetDepth(pix);
+    cmap = pixGetColormap(pix);
+    if (d == 1)
+        type = L_G4_ENCODE;
+    else if (cmap || d == 2 || d == 4 || d == 16)
+        type = L_FLATE_ENCODE;
+    else  /* d == 8 (no cmap) or d == 32 */
+        type = L_JPEG_ENCODE;
+
+    ret = pixConvertToPdfData(pix, type, 75, pdata, pnbytes,
+                              0, 0, res, title, NULL, 0);
+    if (ret)
+        return ERROR_INT("pdf data not made", procName, 1);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *            Segmented multi-page, multi-image converter              *
+ *---------------------------------------------------------------------*/
+/*!
+ *  convertSegmentedFilesToPdf()
+ *
+ *      Input:  directory name (containing images)
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              res (input resolution of all images)
+ *              type (compression type for non-image regions; the
+ *                    image regions are always compressed with L_JPEG_ENCODE)
+ *              thresh (used for converting gray --> 1 bpp with L_G4_ENCODE)
+ *              boxaa (<optional> of image regions)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              scalefactor (scaling factor applied to each image region)
+ *              title (<optional> pdf title; if null, taken from the first
+ *                     image filename)
+ *              fileout (pdf file of all images)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If @substr is not NULL, only image filenames that contain
+ *          the substring can be used.  If @substr == NULL, all files
+ *          in the directory are used.
+ *      (2) The files in the directory, after optional filtering by
+ *          the substring, are lexically sorted in increasing order
+ *          before concatenation.
+ *      (3) The images are encoded with G4 if 1 bpp; JPEG if 8 bpp without
+ *          colormap and many colors, or 32 bpp; FLATE for anything else.
+ *      (4) The boxaa, if it exists, contains one boxa of "image regions"
+ *          for each image file.  The boxa must be aligned with the
+ *          sorted set of images.
+ *      (5) The scalefactor is applied to each image region.  It is
+ *          typically < 1.0, to save bytes in the final pdf, because
+ *          the resolution is often not critical in non-text regions.
+ *      (6) If the non-image regions have pixel depth > 1 and the encoding
+ *          type is G4, they are automatically scaled up by 2x and
+ *          thresholded.  Otherwise, no scaling is performed on them.
+ *      (7) Note that this function can be used to generate multipage
+ *          G4 compressed pdf from any input, by using @boxaa == NULL
+ *          and @type == L_G4_ENCODE.
+ */
+l_int32
+convertSegmentedFilesToPdf(const char  *dirname,
+                           const char  *substr,
+                           l_int32      res,
+                           l_int32      type,
+                           l_int32      thresh,
+                           BOXAA       *baa,
+                           l_int32      quality,
+                           l_float32    scalefactor,
+                           const char  *title,
+                           const char  *fileout)
+{
+char     *fname;
+l_uint8  *imdata, *data;
+l_int32   i, npages, nboxa, nboxes, ret;
+size_t    imbytes, databytes;
+BOXA     *boxa;
+L_BYTEA  *ba;
+L_PTRA   *pa_data;
+SARRAY   *sa;
+
+    PROCNAME("convertSegmentedFilesToPdf");
+
+    if (!dirname)
+        return ERROR_INT("dirname not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+    if ((sa = getNumberedPathnamesInDirectory(dirname, substr, 0, 0, 10000))
+            == NULL)
+        return ERROR_INT("sa not made", procName, 1);
+
+    npages = sarrayGetCount(sa);
+        /* If necessary, extend the boxaa, which is page-aligned with
+         * the image files, to be as large as the set of images. */
+    if (baa) {
+        nboxa = boxaaGetCount(baa);
+        if (nboxa < npages) {
+            boxa = boxaCreate(1);
+            boxaaExtendWithInit(baa, npages, boxa);
+            boxaDestroy(&boxa);
+        }
+    }
+
+        /* Generate and save all the encoded pdf strings */
+    pa_data = ptraCreate(npages);
+    for (i = 0; i < npages; i++) {
+        fname = sarrayGetString(sa, i, L_NOCOPY);
+        if (!strcmp(fname, "")) continue;
+        boxa = NULL;
+        if (baa) {
+            boxa = boxaaGetBoxa(baa, i, L_CLONE);
+            nboxes = boxaGetCount(boxa);
+            if (nboxes == 0)
+                boxaDestroy(&boxa);
+        }
+        ret = convertToPdfDataSegmented(fname, res, type, thresh, boxa,
+                                        quality, scalefactor, title,
+                                        &imdata, &imbytes);
+        boxaDestroy(&boxa);  /* safe; in case nboxes > 0 */
+        if (ret) {
+            L_ERROR("pdf encoding failed for %s\n", procName, fname);
+            continue;
+        }
+        ba = l_byteaInitFromMem(imdata, imbytes);
+        if (imdata) LEPT_FREE(imdata);
+        ptraAdd(pa_data, ba);
+    }
+    sarrayDestroy(&sa);
+
+    ptraGetActualCount(pa_data, &npages);
+    if (npages == 0) {
+        L_ERROR("no pdf files made\n", procName);
+        ptraDestroy(&pa_data, FALSE, FALSE);
+        return 1;
+    }
+
+        /* Concatenate */
+    ret = ptraConcatenatePdfToData(pa_data, NULL, &data, &databytes);
+
+        /* Clean up */
+    ptraGetActualCount(pa_data, &npages);  /* recalculate in case it changes */
+    for (i = 0; i < npages; i++) {
+        ba = (L_BYTEA *)ptraRemove(pa_data, i, L_NO_COMPACTION);
+        l_byteaDestroy(&ba);
+    }
+    ptraDestroy(&pa_data, FALSE, FALSE);
+
+    if (ret) {
+        if (data) LEPT_FREE(data);
+        return ERROR_INT("pdf data not made", procName, 1);
+    }
+
+    ret = l_binaryWrite(fileout, "w", data, databytes);
+    LEPT_FREE(data);
+    if (ret)
+        L_ERROR("pdf data not written to file\n", procName);
+    return ret;
+}
+
+
+/*!
+ *  convertNumberedMasksToBoxaa()
+ *
+ *      Input:  directory name (containing mask images)
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              numpre (number of characters in name before number)
+ *              numpost (number of characters in name after number, up
+ *                       to a dot before an extension)
+ *                       including an extension and the dot separator)
+ *      Return: boxaa of mask regions, or null on error
+ *
+ *  Notes:
+ *      (1) This is conveniently used to generate the input boxaa
+ *          for convertSegmentedFilesToPdf().  It guarantees that the
+ *          boxa will be aligned with the page images, even if some
+ *          of the boxa are empty.
+ */
+BOXAA *
+convertNumberedMasksToBoxaa(const char  *dirname,
+                            const char  *substr,
+                            l_int32      numpre,
+                            l_int32      numpost)
+{
+char    *fname;
+l_int32  i, n;
+BOXA    *boxa;
+BOXAA   *baa;
+PIX     *pix;
+SARRAY  *sa;
+
+    PROCNAME("convertNumberedMasksToBoxaa");
+
+    if (!dirname)
+        return (BOXAA *)ERROR_PTR("dirname not defined", procName, NULL);
+
+    if ((sa = getNumberedPathnamesInDirectory(dirname, substr, numpre,
+                                              numpost, 10000)) == NULL)
+        return (BOXAA *)ERROR_PTR("sa not made", procName, NULL);
+
+        /* Generate and save all the encoded pdf strings */
+    n = sarrayGetCount(sa);
+    baa = boxaaCreate(n);
+    boxa = boxaCreate(1);
+    boxaaInitFull(baa, boxa);
+    boxaDestroy(&boxa);
+    for (i = 0; i < n; i++) {
+        fname = sarrayGetString(sa, i, L_NOCOPY);
+        if (!strcmp(fname, "")) continue;
+        if ((pix = pixRead(fname)) == NULL) {
+            L_WARNING("invalid image on page %d\n", procName, i);
+            continue;
+        }
+        boxa = pixConnComp(pix, NULL, 8);
+        boxaaReplaceBoxa(baa, i, boxa);
+        pixDestroy(&pix);
+    }
+
+    sarrayDestroy(&sa);
+    return baa;
+}
+
+
+/*---------------------------------------------------------------------*
+ *            Segmented single page, multi-image converters            *
+ *---------------------------------------------------------------------*/
+/*!
+ *  convertToPdfSegmented()
+ *
+ *      Input:  filein (input image file -- any format)
+ *              res (input image resolution; typ. 300 ppi; use 0 for default)
+ *              type (compression type for non-image regions; the
+ *                    image regions are always compressed with L_JPEG_ENCODE)
+ *              thresh (used for converting gray --> 1 bpp with L_G4_ENCODE)
+ *              boxa (<optional> of image regions; can be null)
+ *              quality (used for jpeg image regions; 0 for default)
+ *              scalefactor (used for jpeg regions; must be <= 1.0)
+ *              title (<optional> pdf title; typically taken from the
+ *                     input file for the pix)
+ *              fileout (output pdf file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If there are no image regions, set @boxa == NULL;
+ *          @quality and @scalefactor are ignored.
+ *      (2) Typically, @scalefactor is < 1.0, because the image regions
+ *          can be rendered at a lower resolution (for better compression)
+ *          than the text regions.  If @scalefactor == 0, we use 1.0.
+ *          If the input image is 1 bpp and scalefactor < 1.0, we
+ *          use scaleToGray() to downsample the image regions to gray
+ *          before compressing them.
+ *      (3) If the compression type for non-image regions is L_G4_ENCODE
+ *          and bpp > 1, the image is upscaled 2x and thresholded
+ *          to 1 bpp.  That is the only situation where @thresh is used.
+ *      (4) The parameter @quality is only used for image regions.
+ *          If @type == L_JPEG_ENCODE, default jpeg quality (75) is
+ *          used for the non-image regions.
+ *      (5) Processing matrix for non-image regions.
+ *
+ *          Input           G4              JPEG                FLATE
+ *          ----------|---------------------------------------------------
+ *          1 bpp     |  1x, 1 bpp       1x flate, 1 bpp     1x, 1 bpp
+ *                    |
+ *          cmap      |  2x, 1 bpp       1x flate, cmap      1x, cmap
+ *                    |
+ *          2,4 bpp   |  2x, 1 bpp       1x flate            1x, 2,4 bpp
+ *          no cmap   |                  2,4 bpp
+ *                    |
+ *          8,32 bpp  |  2x, 1 bpp       1x (jpeg)           1x, 8,32 bpp
+ *          no cmap   |                  8,32 bpp
+ *
+ *          Summary:
+ *          (a) if G4 is requested, G4 is used, with 2x upscaling
+ *              for all cases except 1 bpp.
+ *          (b) if JPEG is requested, use flate encoding for all cases
+ *              except 8 bpp without cmap and 32 bpp (rgb).
+ *          (c) if FLATE is requested, use flate with no transformation
+ *              of the raster data.
+ *      (6) Calling options/sequence for these functions:
+ *              file  -->  file      (convertToPdfSegmented)
+ *                  pix  -->  file      (pixConvertToPdfSegmented)
+ *                      pix  -->  data      (pixConvertToPdfDataSegmented)
+ *              file  -->  data      (convertToPdfDataSegmented)
+ *                      pix  -->  data      (pixConvertToPdfDataSegmented)
+ */
+l_int32
+convertToPdfSegmented(const char  *filein,
+                      l_int32      res,
+                      l_int32      type,
+                      l_int32      thresh,
+                      BOXA        *boxa,
+                      l_int32      quality,
+                      l_float32    scalefactor,
+                      const char  *title,
+                      const char  *fileout)
+{
+l_int32  ret;
+PIX     *pixs;
+
+    PROCNAME("convertToPdfSegmented");
+
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (type != L_G4_ENCODE && type != L_JPEG_ENCODE &&
+        type != L_FLATE_ENCODE)
+        return ERROR_INT("invalid conversion type", procName, 1);
+    if (boxa && scalefactor > 1.0) {
+        L_WARNING("setting scalefactor to 1.0\n", procName);
+        scalefactor = 1.0;
+    }
+
+    if ((pixs = pixRead(filein)) == NULL)
+        return ERROR_INT("pixs not made", procName, 1);
+
+    ret = pixConvertToPdfSegmented(pixs, res, type, thresh, boxa, quality,
+                                   scalefactor, (title) ? title : filein,
+                                   fileout);
+    pixDestroy(&pixs);
+    return ret;
+}
+
+
+/*!
+ *  pixConvertToPdfSegmented()
+ *
+ *      Input:  pixs (any depth, cmap OK)
+ *              res (input image resolution; typ. 300 ppi; use 0 for default)
+ *              type (compression type for non-image regions; the
+ *                    image regions are always compressed with L_JPEG_ENCODE)
+ *              thresh (used for converting gray --> 1 bpp with L_G4_ENCODE)
+ *              boxa (<optional> of image regions; can be null)
+ *              quality (used for jpeg image regions; 0 for default)
+ *              scalefactor (used for jpeg regions; must be <= 1.0)
+ *              title (<optional> pdf title; typically taken from the
+ *                     input file for the pix)
+ *              fileout (output pdf file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See convertToPdfSegmented() for details.
+ */
+l_int32
+pixConvertToPdfSegmented(PIX         *pixs,
+                         l_int32      res,
+                         l_int32      type,
+                         l_int32      thresh,
+                         BOXA        *boxa,
+                         l_int32      quality,
+                         l_float32    scalefactor,
+                         const char  *title,
+                         const char  *fileout)
+{
+l_uint8  *data;
+l_int32   ret;
+size_t    nbytes;
+
+    PROCNAME("pixConvertToPdfSegmented");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (type != L_G4_ENCODE && type != L_JPEG_ENCODE &&
+        type != L_FLATE_ENCODE)
+        return ERROR_INT("invalid conversion type", procName, 1);
+    if (boxa && scalefactor > 1.0) {
+        L_WARNING("setting scalefactor to 1.0\n", procName);
+        scalefactor = 1.0;
+    }
+
+    ret = pixConvertToPdfDataSegmented(pixs, res, type, thresh, boxa, quality,
+                                       scalefactor, title, &data, &nbytes);
+    if (ret)
+        return ERROR_INT("pdf generation failure", procName, 1);
+
+    ret = l_binaryWrite(fileout, "w", data, nbytes);
+    if (data) LEPT_FREE(data);
+    return ret;
+}
+
+
+/*!
+ *  convertToPdfDataSegmented()
+ *
+ *      Input:  filein (input image file -- any format)
+ *              res (input image resolution; typ. 300 ppi; use 0 for default)
+ *              type (compression type for non-image regions; the
+ *                    image regions are always compressed with L_JPEG_ENCODE)
+ *              thresh (used for converting gray --> 1 bpp with L_G4_ENCODE)
+ *              boxa (<optional> image regions; can be null)
+ *              quality (used for jpeg image regions; 0 for default)
+ *              scalefactor (used for jpeg regions; must be <= 1.0)
+ *              title (<optional> pdf title; if null, uses filein)
+ *              &data (<return> pdf data in memory)
+ *              &nbytes (<return> number of bytes in pdf data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If there are no image regions, set @boxa == NULL;
+ *          @quality and @scalefactor are ignored.
+ *      (2) Typically, @scalefactor is < 1.0.  The image regions are
+ */
+l_int32
+convertToPdfDataSegmented(const char  *filein,
+                          l_int32      res,
+                          l_int32      type,
+                          l_int32      thresh,
+                          BOXA        *boxa,
+                          l_int32      quality,
+                          l_float32    scalefactor,
+                          const char  *title,
+                          l_uint8    **pdata,
+                          size_t      *pnbytes)
+{
+l_int32  ret;
+PIX     *pixs;
+
+    PROCNAME("convertToPdfDataSegmented");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (type != L_G4_ENCODE && type != L_JPEG_ENCODE &&
+        type != L_FLATE_ENCODE)
+        return ERROR_INT("invalid conversion type", procName, 1);
+    if (boxa && scalefactor > 1.0) {
+        L_WARNING("setting scalefactor to 1.0\n", procName);
+        scalefactor = 1.0;
+    }
+
+    if ((pixs = pixRead(filein)) == NULL)
+        return ERROR_INT("pixs not made", procName, 1);
+
+    ret = pixConvertToPdfDataSegmented(pixs, res, type, thresh, boxa,
+                                       quality, scalefactor,
+                                       (title) ? title : filein,
+                                       pdata, pnbytes);
+    pixDestroy(&pixs);
+    return ret;
+}
+
+
+/*!
+ *  pixConvertToPdfDataSegmented()
+ *
+ *      Input:  pixs (any depth, cmap OK)
+ *              res (input image resolution; typ. 300 ppi; use 0 for default)
+ *              type (compression type for non-image regions; the
+ *                    image regions are always compressed with L_JPEG_ENCODE)
+ *              thresh (used for converting gray --> 1 bpp with L_G4_ENCODE)
+ *              boxa (<optional> of image regions; can be null)
+ *              quality (used for jpeg image regions; 0 for default)
+ *              scalefactor (used for jpeg regions; must be <= 1.0)
+ *              title (<optional> pdf title; typically taken from the
+ *                     input file for the pix)
+ *              &data (<return> pdf data in memory)
+ *              &nbytes (<return> number of bytes in pdf data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See convertToPdfSegmented() for details.
+ */
+l_int32
+pixConvertToPdfDataSegmented(PIX         *pixs,
+                             l_int32      res,
+                             l_int32      type,
+                             l_int32      thresh,
+                             BOXA        *boxa,
+                             l_int32      quality,
+                             l_float32    scalefactor,
+                             const char  *title,
+                             l_uint8    **pdata,
+                             size_t      *pnbytes)
+{
+l_int32      i, nbox, seq, bx, by, bw, bh, upscale;
+l_float32    scale;
+BOX         *box, *boxc, *box2;
+PIX         *pix, *pixt1, *pixt2, *pixt3, *pixt4, *pixt5, *pixt6;
+PIXCMAP     *cmap;
+L_PDF_DATA  *lpd;
+
+    PROCNAME("pixConvertToPdfDataSegmented");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (type != L_G4_ENCODE && type != L_JPEG_ENCODE &&
+        type != L_FLATE_ENCODE)
+        return ERROR_INT("invalid conversion type", procName, 1);
+    if (boxa && (scalefactor <= 0.0 || scalefactor > 1.0)) {
+        L_WARNING("setting scalefactor to 1.0\n", procName);
+        scalefactor = 1.0;
+    }
+
+        /* Adjust scalefactor so that the product with res gives an integer */
+    if (res <= 0)
+        res = DEFAULT_INPUT_RES;
+    scale = (l_float32)((l_int32)(scalefactor * res + 0.5)) / (l_float32)res;
+    cmap = pixGetColormap(pixs);
+
+        /* Simple case: single image to be encoded */
+    if (!boxa || boxaGetCount(boxa) == 0) {
+        if (pixGetDepth(pixs) > 1 && type == L_G4_ENCODE) {
+            if (cmap)
+                pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+            else
+                pixt1 = pixConvertTo8(pixs, FALSE);
+            pixt2 = pixScaleGray2xLIThresh(pixt1, thresh);
+            pixConvertToPdfData(pixt2, type, quality, pdata, pnbytes,
+                                0, 0, 2 * res, title, NULL, 0);
+            pixDestroy(&pixt1);
+            pixDestroy(&pixt2);
+        } else {
+            pixConvertToPdfData(pixs, type, quality, pdata, pnbytes,
+                                0, 0, res, title, NULL, 0);
+        }
+        return 0;
+    }
+
+        /* Multiple images to be encoded.  If @type == L_G4_ENCODE,
+         * jpeg encode a version of pixs that is blanked in the non-image
+         * regions, and paint the scaled non-image part onto it through a mask.
+         * Otherwise, we must put the non-image part down first and
+         * then render all the image regions separately on top of it,
+         * at their own resolution. */
+    pixt1 = pixSetBlackOrWhiteBoxa(pixs, boxa, L_SET_WHITE);  /* non-image */
+    nbox = boxaGetCount(boxa);
+    if (type == L_G4_ENCODE) {
+        pixt2 = pixCreateTemplate(pixs);  /* only image regions */
+        pixSetBlackOrWhite(pixt2, L_SET_WHITE);
+        for (i = 0; i < nbox; i++) {
+             box = boxaGetBox(boxa, i, L_CLONE);
+             pix = pixClipRectangle(pixs, box, &boxc);
+             boxGetGeometry(boxc, &bx, &by, &bw, &bh);
+             pixRasterop(pixt2, bx, by, bw, bh, PIX_SRC, pix, 0, 0);
+             pixDestroy(&pix);
+             boxDestroy(&box);
+             boxDestroy(&boxc);
+        }
+        pixt3 = pixRemoveColormap(pixt2, REMOVE_CMAP_BASED_ON_SRC);
+        if (pixGetDepth(pixt3) == 1)
+            pixt4 = pixScaleToGray(pixt3, scale);
+        else
+            pixt4 = pixScale(pixt3, scale, scale);
+        pixConvertToPdfData(pixt4, L_JPEG_ENCODE, quality, pdata, pnbytes,
+                            0, 0, (l_int32)(scale * res), title,
+                            &lpd, L_FIRST_IMAGE);
+
+        if (pixGetDepth(pixt1) == 1) {
+            pixt5 = pixClone(pixt1);
+            upscale = 1;
+        } else {
+            pixt6 = pixConvertTo8(pixt1, 0);
+            pixt5 = pixScaleGray2xLIThresh(pixt6, thresh);
+            pixDestroy(&pixt6);
+            upscale = 2;
+        }
+        pixConvertToPdfData(pixt5, L_G4_ENCODE, quality, pdata, pnbytes,
+                            0, 0, upscale * res, title, &lpd, L_LAST_IMAGE);
+        pixDestroy(&pixt2);
+        pixDestroy(&pixt3);
+        pixDestroy(&pixt4);
+        pixDestroy(&pixt5);
+    } else {
+            /* Put the non-image part down first.  This is the full
+               size of the page, so we can use it to find the page
+               height in pixels, which is required for determining
+               the LL corner of the image relative to the LL corner
+               of the page. */
+        pixConvertToPdfData(pixt1, type, quality, pdata, pnbytes, 0, 0,
+                            res, title, &lpd, L_FIRST_IMAGE);
+        for (i = 0; i < nbox; i++) {
+            box = boxaGetBox(boxa, i, L_CLONE);
+            pixt2 = pixClipRectangle(pixs, box, &boxc);
+            pixt3 = pixRemoveColormap(pixt2, REMOVE_CMAP_BASED_ON_SRC);
+            if (pixGetDepth(pixt3) == 1)
+                pixt4 = pixScaleToGray(pixt3, scale);
+            else
+                pixt4 = pixScale(pixt3, scale, scale);
+            box2 = boxTransform(boxc, 0, 0, scale, scale);
+            boxGetGeometry(box2, &bx, &by, NULL, &bh);
+            seq = (i == nbox - 1) ? L_LAST_IMAGE : L_NEXT_IMAGE;
+            pixConvertToPdfData(pixt4, L_JPEG_ENCODE, quality, pdata, pnbytes,
+                                bx, by, (l_int32)(scale * res), title,
+                                &lpd, seq);
+            pixDestroy(&pixt2);
+            pixDestroy(&pixt3);
+            pixDestroy(&pixt4);
+            boxDestroy(&box);
+            boxDestroy(&boxc);
+            boxDestroy(&box2);
+        }
+    }
+
+    pixDestroy(&pixt1);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Multi-page concatenation                    *
+ *---------------------------------------------------------------------*/
+/*!
+ *  concatenatePdf()
+ *
+ *      Input:  directory name (containing single-page pdf files)
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              fileout (concatenated pdf file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This only works with leptonica-formatted single-page pdf files.
+ *      (2) If @substr is not NULL, only filenames that contain
+ *          the substring can be returned.  If @substr == NULL,
+ *          none of the filenames are filtered out.
+ *      (3) The files in the directory, after optional filtering by
+ *          the substring, are lexically sorted in increasing order
+ *          before concatenation.
+ */
+l_int32
+concatenatePdf(const char  *dirname,
+               const char  *substr,
+               const char  *fileout)
+{
+l_int32  ret;
+SARRAY  *sa;
+
+    PROCNAME("concatenatePdf");
+
+    if (!dirname)
+        return ERROR_INT("dirname not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+    if ((sa = getSortedPathnamesInDirectory(dirname, substr, 0, 0)) == NULL)
+        return ERROR_INT("sa not made", procName, 1);
+    ret = saConcatenatePdf(sa, fileout);
+    sarrayDestroy(&sa);
+    return ret;
+}
+
+
+/*!
+ *  saConcatenatePdf()
+ *
+ *      Input:  sarray (of pathnames for single-page pdf files)
+ *              fileout (concatenated pdf file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This only works with leptonica-formatted single-page pdf files.
+ */
+l_int32
+saConcatenatePdf(SARRAY      *sa,
+                 const char  *fileout)
+{
+l_uint8  *data;
+l_int32   ret;
+size_t    nbytes;
+
+    PROCNAME("saConcatenatePdf");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+    ret = saConcatenatePdfToData(sa, &data, &nbytes);
+    if (ret)
+        return ERROR_INT("pdf data not made", procName, 1);
+    ret = l_binaryWrite(fileout, "w", data, nbytes);
+    LEPT_FREE(data);
+    return ret;
+}
+
+
+/*!
+ *  ptraConcatenatePdf()
+ *
+ *      Input:  ptra (array of pdf strings, each for a single-page pdf file)
+ *              fileout (concatenated pdf file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This only works with leptonica-formatted single-page pdf files.
+ */
+l_int32
+ptraConcatenatePdf(L_PTRA      *pa,
+                   const char  *fileout)
+{
+l_uint8  *data;
+l_int32   ret;
+size_t    nbytes;
+
+    PROCNAME("ptraConcatenatePdf");
+
+    if (!pa)
+        return ERROR_INT("pa not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+    ret = ptraConcatenatePdfToData(pa, NULL, &data, &nbytes);
+    if (ret)
+        return ERROR_INT("pdf data not made", procName, 1);
+    ret = l_binaryWrite(fileout, "w", data, nbytes);
+    LEPT_FREE(data);
+    return ret;
+}
+
+
+/*!
+ *  concatenatePdfToData()
+ *
+ *      Input:  directory name (containing single-page pdf files)
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              &data (<return> concatenated pdf data in memory)
+ *              &nbytes (<return> number of bytes in pdf data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This only works with leptonica-formatted single-page pdf files.
+ *      (2) If @substr is not NULL, only filenames that contain
+ *          the substring can be returned.  If @substr == NULL,
+ *          none of the filenames are filtered out.
+ *      (3) The files in the directory, after optional filtering by
+ *          the substring, are lexically sorted in increasing order
+ *          before concatenation.
+ */
+l_int32
+concatenatePdfToData(const char  *dirname,
+                     const char  *substr,
+                     l_uint8    **pdata,
+                     size_t      *pnbytes)
+{
+l_int32  ret;
+SARRAY  *sa;
+
+    PROCNAME("concatenatePdfToData");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!dirname)
+        return ERROR_INT("dirname not defined", procName, 1);
+
+    if ((sa = getSortedPathnamesInDirectory(dirname, substr, 0, 0)) == NULL)
+        return ERROR_INT("sa not made", procName, 1);
+    ret = saConcatenatePdfToData(sa, pdata, pnbytes);
+    sarrayDestroy(&sa);
+    return ret;
+}
+
+
+/*!
+ *  saConcatenatePdfToData()
+ *
+ *      Input:  sarray (of pathnames for single-page pdf files)
+ *              &data (<return> concatenated pdf data in memory)
+ *              &nbytes (<return> number of bytes in pdf data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This only works with leptonica-formatted single-page pdf files.
+ */
+l_int32
+saConcatenatePdfToData(SARRAY    *sa,
+                       l_uint8  **pdata,
+                       size_t    *pnbytes)
+{
+char     *fname;
+l_int32   i, npages, ret;
+L_BYTEA  *bas;
+L_PTRA   *pa_data;  /* input pdf data for each page */
+
+    PROCNAME("saConcatenatePdfToData");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+
+        /* Read the pdf files into memory */
+    if ((npages = sarrayGetCount(sa)) == 0)
+        return ERROR_INT("no filenames found", procName, 1);
+    pa_data = ptraCreate(npages);
+    for (i = 0; i < npages; i++) {
+        fname = sarrayGetString(sa, i, L_NOCOPY);
+        bas = l_byteaInitFromFile(fname);
+        ptraAdd(pa_data, bas);
+    }
+
+    ret = ptraConcatenatePdfToData(pa_data, sa, pdata, pnbytes);
+
+        /* Cleanup: some pages could have been removed */
+    ptraGetActualCount(pa_data, &npages);
+    for (i = 0; i < npages; i++) {
+        bas = (L_BYTEA *)ptraRemove(pa_data, i, L_NO_COMPACTION);
+        l_byteaDestroy(&bas);
+    }
+    ptraDestroy(&pa_data, FALSE, FALSE);
+    return ret;
+}
+
+/* --------------------------------------------*/
+#endif  /* USE_PDFIO */
+/* --------------------------------------------*/
diff --git a/src/pdfio1stub.c b/src/pdfio1stub.c
new file mode 100644 (file)
index 0000000..071bc8f
--- /dev/null
@@ -0,0 +1,303 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pdfio1stub.c
+ *
+ *     Stubs for pdfio1.c functions
+ */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !USE_PDFIO   /* defined in environ.h */
+/* --------------------------------------------*/
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertFilesToPdf(const char *dirname, const char *substr,
+                          l_int32 res, l_float32 scalefactor,
+                          l_int32 type, l_int32 quality,
+                          const char *title, const char *fileout)
+{
+    return ERROR_INT("function not present", "convertFilesToPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 saConvertFilesToPdf(SARRAY *sa, l_int32 res, l_float32 scalefactor,
+                            l_int32 type, l_int32 quality,
+                            const char *title, const char *fileout)
+{
+    return ERROR_INT("function not present", "saConvertFilesToPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 saConvertFilesToPdfData(SARRAY *sa, l_int32 res,
+                                l_float32 scalefactor, l_int32 type,
+                                l_int32 quality, const char *title,
+                                l_uint8 **pdata, size_t *pnbytes)
+{
+    return ERROR_INT("function not present", "saConvertFilesToPdfData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 selectDefaultPdfEncoding(PIX *pix, l_int32 *ptype)
+{
+    return ERROR_INT("function not present", "selectDefaultPdfEncoding", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertUnscaledFilesToPdf(const char *dirname, const char *substr,
+                                  const char *title, const char *fileout)
+{
+    return ERROR_INT("function not present", "convertUnscaledFilesToPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 saConvertUnscaledFilesToPdf(SARRAY *sa, const char *title,
+                                    const char *fileout)
+{
+    return ERROR_INT("function not present", "saConvertUnscaledFilesToPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 saConvertUnscaledFilesToPdfData(SARRAY *sa, const char *title,
+                                        l_uint8 **pdata, size_t *pnbytes)
+{
+    return ERROR_INT("function not present",
+                     "saConvertUnscaledFilesToPdfData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertUnscaledToPdfData(const char *fname, const char *title,
+                                 l_uint8 **pdata, size_t *pnbytes)
+{
+    return ERROR_INT("function not present", "convertUnscaledToPdfData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixaConvertToPdf(PIXA *pixa, l_int32 res, l_float32 scalefactor,
+                         l_int32 type, l_int32 quality,
+                         const char *title, const char *fileout)
+{
+    return ERROR_INT("function not present", "pixaConvertToPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixaConvertToPdfData(PIXA *pixa, l_int32 res, l_float32 scalefactor,
+                             l_int32 type, l_int32 quality, const char *title,
+                             l_uint8 **pdata, size_t *pnbytes)
+{
+    return ERROR_INT("function not present", "pixaConvertToPdfData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertToPdf(const char *filein,
+                     l_int32 type, l_int32 quality,
+                     const char *fileout,
+                     l_int32 x, l_int32 y, l_int32 res,
+                     const char *title,
+                     L_PDF_DATA **plpd, l_int32 position)
+{
+    return ERROR_INT("function not present", "convertToPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertImageDataToPdf(l_uint8 *imdata, size_t size,
+                              l_int32 type, l_int32 quality,
+                              const char *fileout,
+                              l_int32 x, l_int32 y, l_int32 res,
+                              const char *title,
+                              L_PDF_DATA **plpd, l_int32 position)
+{
+    return ERROR_INT("function not present", "convertImageDataToPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertToPdfData(const char *filein,
+                         l_int32 type, l_int32 quality,
+                         l_uint8 **pdata, size_t *pnbytes,
+                         l_int32 x, l_int32 y, l_int32 res,
+                         const char *title,
+                         L_PDF_DATA **plpd, l_int32 position)
+{
+    return ERROR_INT("function not present", "convertToPdfData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertImageDataToPdfData(l_uint8 *imdata, size_t size,
+                                  l_int32 type, l_int32 quality,
+                                  l_uint8 **pdata, size_t *pnbytes,
+                                  l_int32 x, l_int32 y, l_int32 res,
+                                  const char *title,
+                                  L_PDF_DATA **plpd, l_int32 position)
+{
+    return ERROR_INT("function not present", "convertImageDataToPdfData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixConvertToPdf(PIX *pix, l_int32 type, l_int32 quality,
+                        const char *fileout,
+                        l_int32 x, l_int32 y, l_int32 res,
+                        const char *title,
+                        L_PDF_DATA **plpd, l_int32 position)
+{
+    return ERROR_INT("function not present", "pixConvertToPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteStreamPdf(FILE *fp, PIX *pix, l_int32 res, const char *title)
+{
+    return ERROR_INT("function not present", "pixWriteStreamPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteMemPdf(l_uint8 **pdata, size_t *pnbytes, PIX *pix,
+                       l_int32 res, const char *title)
+{
+    return ERROR_INT("function not present", "pixWriteMemPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertSegmentedFilesToPdf(const char *dirname, const char *substr,
+                                   l_int32 res, l_int32 type, l_int32 thresh,
+                                   BOXAA *baa, l_int32 quality,
+                                   l_float32 scalefactor, const char *title,
+                                   const char *fileout)
+{
+    return ERROR_INT("function not present", "convertSegmentedFilesToPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+BOXAA * convertNumberedMasksToBoxaa(const char *dirname, const char *substr,
+                                    l_int32 numpre, l_int32 numpost)
+{
+    return (BOXAA *)ERROR_PTR("function not present",
+                              "convertNumberedMasksToBoxaa", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertToPdfSegmented(const char *filein, l_int32 res, l_int32 type,
+                              l_int32 thresh, BOXA *boxa, l_int32 quality,
+                              l_float32 scalefactor, const char *title,
+                              const char *fileout)
+{
+    return ERROR_INT("function not present", "convertToPdfSegmented", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixConvertToPdfSegmented(PIX *pixs, l_int32 res, l_int32 type,
+                                 l_int32 thresh, BOXA *boxa, l_int32 quality,
+                                 l_float32 scalefactor, const char *title,
+                                 const char *fileout)
+{
+    return ERROR_INT("function not present", "pixConvertToPdfSegmented", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertToPdfDataSegmented(const char *filein, l_int32 res,
+                                  l_int32 type, l_int32 thresh, BOXA *boxa,
+                                  l_int32 quality, l_float32 scalefactor,
+                                  const char *title,
+                                  l_uint8 **pdata, size_t *pnbytes)
+{
+    return ERROR_INT("function not present", "convertToPdfDataSegmented", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixConvertToPdfDataSegmented(PIX *pixs, l_int32 res, l_int32 type,
+                                     l_int32 thresh, BOXA *boxa,
+                                     l_int32 quality, l_float32 scalefactor,
+                                     const char *title,
+                                     l_uint8 **pdata, size_t *pnbytes)
+{
+    return ERROR_INT("function not present", "pixConvertToPdfDataSegmented", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 concatenatePdf(const char *dirname, const char *substr,
+                       const char *fileout)
+{
+    return ERROR_INT("function not present", "concatenatePdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 saConcatenatePdf(SARRAY *sa, const char *fileout)
+{
+    return ERROR_INT("function not present", "saConcatenatePdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 ptraConcatenatePdf(L_PTRA *pa, const char *fileout)
+{
+    return ERROR_INT("function not present", "ptraConcatenatePdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 concatenatePdfToData(const char *dirname, const char *substr,
+                             l_uint8 **pdata, size_t *pnbytes)
+{
+    return ERROR_INT("function not present", "concatenatePdfToData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 saConcatenatePdfToData(SARRAY *sa, l_uint8 **pdata, size_t *pnbytes)
+{
+    return ERROR_INT("function not present", "saConcatenatePdfToData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+/* --------------------------------------------*/
+#endif  /* !USE_PDFIO */
+/* --------------------------------------------*/
diff --git a/src/pdfio2.c b/src/pdfio2.c
new file mode 100644 (file)
index 0000000..b0e4d3f
--- /dev/null
@@ -0,0 +1,2280 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pdfio2.c
+ *
+ *    Lower-level operations for generating pdf.
+ *
+ *     Intermediate function for single page, multi-image conversion
+ *          l_int32              pixConvertToPdfData()
+ *
+ *     Intermediate function for generating multipage pdf output
+ *          l_int32              ptraConcatenatePdfToData()
+ *
+ *     Low-level CID-based operations
+ *
+ *       Without transcoding
+ *          l_int32              l_generateCIDataForPdf()
+ *          L_COMP_DATA         *l_generateFlateDataPdf()
+ *          L_COMP_DATA         *l_generateJpegData()
+ *          static L_COMP_DATA  *l_generateJp2kData()
+ *
+ *       With transcoding
+ *          l_int32              l_generateCIData()
+ *          static l_int32       pixGenerateCIData()
+ *          L_COMP_DATA         *l_generateFlateData()
+ *          static L_COMP_DATA  *pixGenerateFlateData()
+ *          static L_COMP_DATA  *pixGenerateJpegData()
+ *          static L_COMP_DATA  *pixGenerateG4Data()
+ *          L_COMP_DATA         *l_generateG4Data()
+ *
+ *       Other
+ *          l_int32              cidConvertToPdfData()
+ *          void                 l_CIDataDestroy()
+ *
+ *     Helper functions for generating the output pdf string
+ *          static l_int32       l_generatePdf()
+ *          static void          generateFixedStringsPdf()
+ *          static void          generateMediaboxPdf()
+ *          static l_int32       generatePageStringPdf()
+ *          static l_int32       generateContentStringPdf()
+ *          static l_int32       generatePreXStringsPdf()
+ *          static l_int32       generateColormapStringsPdf()
+ *          static void          generateTrailerPdf()
+ *          static l_int32       makeTrailerStringPdf()
+ *          static l_int32       generateOutputDataPdf()
+ *
+ *     Helper functions for generating multipage pdf output
+ *          static l_int32       parseTrailerPdf()
+ *          static char         *generatePagesObjStringPdf()
+ *          static L_BYTEA      *substituteObjectNumbers()
+ *
+ *     Create/destroy/access pdf data
+ *          static L_PDF_DATA   *pdfdataCreate()
+ *          static void          pdfdataDestroy()
+ *          static L_COMP_DATA  *pdfdataGetCid()
+ *
+ *     Set flags for special modes
+ *          void                 l_pdfSetG4ImageMask()
+ *          void                 l_pdfSetDateAndVersion()
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  USE_PDFIO   /* defined in environ.h */
+ /* --------------------------------------------*/
+
+    /* Typical scan resolution in ppi (pixels/inch) */
+static const l_int32  DEFAULT_INPUT_RES = 300;
+
+    /* Static helpers */
+static L_COMP_DATA  *l_generateJp2kData(const char *fname);
+static L_COMP_DATA  *pixGenerateFlateData(PIX *pixs, l_int32 ascii85flag);
+static L_COMP_DATA  *pixGenerateJpegData(PIX *pixs, l_int32 ascii85flag,
+                                         l_int32 quality);
+static L_COMP_DATA  *pixGenerateG4Data(PIX *pixs, l_int32 ascii85flag);
+
+static l_int32       l_generatePdf(l_uint8 **pdata, size_t *pnbytes,
+                                   L_PDF_DATA  *lpd);
+static void          generateFixedStringsPdf(L_PDF_DATA *lpd);
+static void          generateMediaboxPdf(L_PDF_DATA *lpd);
+static l_int32       generatePageStringPdf(L_PDF_DATA *lpd);
+static l_int32       generateContentStringPdf(L_PDF_DATA *lpd);
+static l_int32       generatePreXStringsPdf(L_PDF_DATA *lpd);
+static l_int32       generateColormapStringsPdf(L_PDF_DATA *lpd);
+static void          generateTrailerPdf(L_PDF_DATA *lpd);
+static char         *makeTrailerStringPdf(L_DNA *daloc);
+static l_int32       generateOutputDataPdf(l_uint8 **pdata, size_t *pnbytes,
+                                       L_PDF_DATA *lpd);
+
+static l_int32       parseTrailerPdf(L_BYTEA *bas, L_DNA **pda);
+static char         *generatePagesObjStringPdf(NUMA *napage);
+static L_BYTEA      *substituteObjectNumbers(L_BYTEA *bas, NUMA *na_objs);
+
+static L_PDF_DATA   *pdfdataCreate(const char *title);
+static void          pdfdataDestroy(L_PDF_DATA **plpd);
+static L_COMP_DATA  *pdfdataGetCid(L_PDF_DATA *lpd, l_int32 index);
+
+
+/* ---------------- Defaults for rendering options ----------------- */
+    /* Output G4 as writing through image mask; this is the default */
+static l_int32   var_WRITE_G4_IMAGE_MASK = 1;
+    /* Write date/time and lib version into pdf; this is the default */
+static l_int32   var_WRITE_DATE_AND_VERSION = 1;
+
+#define L_SMALLBUF   256
+#define L_BIGBUF    2048   /* must be able to hold hex colormap */
+
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_MULTIPAGE      0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*---------------------------------------------------------------------*
+ *       Intermediate function for generating multipage pdf output     *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixConvertToPdfData()
+ *
+ *      Input:  pix (all depths; cmap OK)
+ *              type (L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              &data (<return> pdf array)
+ *              &nbytes (<return> number of bytes in pdf array)
+ *              x, y (location of lower-left corner of image, in pixels,
+ *                    relative to the PostScript origin (0,0) at
+ *                    the lower-left corner of the page)
+ *              res (override the resolution of the input image, in ppi;
+ *                   use 0 to respect the resolution embedded in the input)
+ *              title (<optional> pdf title)
+ *              &lpd (ptr to lpd, which is created on the first invocation
+ *                    and returned until last image is processed)
+ *              position (in image sequence: L_FIRST_IMAGE, L_NEXT_IMAGE,
+ *                       L_LAST_IMAGE)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If @res == 0 and the input resolution field is 0,
+ *          this will use DEFAULT_INPUT_RES.
+ *      (2) This only writes @data if it is the last image to be
+ *          written on the page.
+ *      (3) See comments in convertToPdf().
+ */
+l_int32
+pixConvertToPdfData(PIX          *pix,
+                    l_int32       type,
+                    l_int32       quality,
+                    l_uint8     **pdata,
+                    size_t       *pnbytes,
+                    l_int32       x,
+                    l_int32       y,
+                    l_int32       res,
+                    const char   *title,
+                    L_PDF_DATA  **plpd,
+                    l_int32       position)
+{
+l_int32       pixres, w, h, ret;
+l_float32     xpt, ypt, wpt, hpt;
+L_COMP_DATA  *cid = NULL;
+L_PDF_DATA   *lpd = NULL;
+
+    PROCNAME("pixConvertToPdfData");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (plpd) {  /* part of multi-page invocation */
+        if (position == L_FIRST_IMAGE)
+            *plpd = NULL;
+    }
+
+        /* Generate the compressed image data.  It must NOT
+         * be ascii85 encoded. */
+    pixGenerateCIData(pix, type, quality, 0, &cid);
+    if (!cid)
+        return ERROR_INT("cid not made", procName, 1);
+
+        /* Get media box in pts.  Guess the input image resolution
+         * based on the input parameter @res, the resolution data in
+         * the pix, and the size of the image. */
+    pixres = cid->res;
+    w = cid->w;
+    h = cid->h;
+    if (res <= 0.0) {
+        if (pixres > 0)
+            res = pixres;
+        else
+            res = DEFAULT_INPUT_RES;
+    }
+    xpt = x * 72. / res;
+    ypt = y * 72. / res;
+    wpt = w * 72. / res;
+    hpt = h * 72. / res;
+
+        /* Set up lpd */
+    if (!plpd) {  /* single image */
+        if ((lpd = pdfdataCreate(title)) == NULL)
+            return ERROR_INT("lpd not made", procName, 1);
+    } else if (position == L_FIRST_IMAGE) {  /* first of multiple images */
+        if ((lpd = pdfdataCreate(title)) == NULL)
+            return ERROR_INT("lpd not made", procName, 1);
+        *plpd = lpd;
+    } else {  /* not the first of multiple images */
+        lpd = *plpd;
+    }
+
+        /* Add the data to the lpd */
+    ptraAdd(lpd->cida, cid);
+    lpd->n++;
+    ptaAddPt(lpd->xy, xpt, ypt);
+    ptaAddPt(lpd->wh, wpt, hpt);
+
+        /* If a single image or the last of multiple images,
+         * generate the pdf and destroy the lpd */
+    if (!plpd || (position == L_LAST_IMAGE)) {
+        ret = l_generatePdf(pdata, pnbytes, lpd);
+        pdfdataDestroy(&lpd);
+        if (plpd) *plpd = NULL;
+        if (ret)
+            return ERROR_INT("pdf output not made", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *      Intermediate function for generating multipage pdf output      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptraConcatenatePdfToData()
+ *
+ *      Input:  ptra (array of pdf strings, each for a single-page pdf file)
+ *              sarray (<optional> of pathnames for input pdf files)
+ *              &data (<return> concatenated pdf data in memory)
+ *              &nbytes (<return> number of bytes in pdf data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This only works with leptonica-formatted single-page pdf files.
+ *          pdf files generated by other programs will have unpredictable
+ *          (and usually bad) results.  The requirements for each pdf file:
+ *            (a) The Catalog and Info objects are the first two.
+ *            (b) Object 3 is Pages
+ *            (c) Object 4 is Page
+ *            (d) The remaining objects are Contents, XObjects, and ColorSpace
+ *      (2) We remove trailers from each page, and append the full trailer
+ *          for all pages at the end.
+ *      (3) For all but the first file, remove the ID and the first 3
+ *          objects (catalog, info, pages), so that each subsequent
+ *          file has only objects of these classes:
+ *              Page, Contents, XObject, ColorSpace (Indexed RGB).
+ *          For those objects, we substitute these refs to objects
+ *          in the local file:
+ *              Page:  Parent(object 3), Contents, XObject(typically multiple)
+ *              XObject:  [ColorSpace if indexed]
+ *          The Pages object on the first page (object 3) has a Kids array
+ *          of references to all the Page objects, with a Count equal
+ *          to the number of pages.  Each Page object refers back to
+ *          this parent.
+ */
+l_int32
+ptraConcatenatePdfToData(L_PTRA    *pa_data,
+                         SARRAY    *sa,
+                         l_uint8  **pdata,
+                         size_t    *pnbytes)
+{
+char     *fname, *str_pages, *str_trailer;
+l_uint8  *pdfdata, *data;
+l_int32   i, j, index, nobj, npages;
+l_int32  *sizes, *locs;
+size_t    size;
+L_BYTEA  *bas, *bad, *bat1, *bat2;
+L_DNA    *da_locs, *da_sizes, *da_outlocs, *da;
+L_DNAA   *daa_locs;  /* object locations on each page */
+NUMA     *na_objs, *napage;
+NUMAA    *naa_objs;  /* object mapping numbers to new values */
+
+    PROCNAME("ptraConcatenatePdfToData");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!pa_data)
+        return ERROR_INT("pa_data not defined", procName, 1);
+
+        /* Parse the files and find the object locations.
+         * Remove file data that cannot be parsed. */
+    ptraGetActualCount(pa_data, &npages);
+    daa_locs = l_dnaaCreate(npages);
+    for (i = 0; i < npages; i++) {
+        bas = (L_BYTEA *)ptraGetPtrToItem(pa_data, i);
+        if (parseTrailerPdf(bas, &da_locs) != 0) {
+            bas = (L_BYTEA *)ptraRemove(pa_data, i, L_NO_COMPACTION);
+            l_byteaDestroy(&bas);
+            if (sa) {
+                fname = sarrayGetString(sa, i, L_NOCOPY);
+                L_ERROR("can't parse file %s; skipping\n", procName, fname);
+            } else {
+                L_ERROR("can't parse file %d; skipping\n", procName, i);
+            }
+        } else {
+            l_dnaaAddDna(daa_locs, da_locs, L_INSERT);
+        }
+    }
+
+        /* Recompute npages in case some of the files were not pdf */
+    ptraCompactArray(pa_data);
+    ptraGetActualCount(pa_data, &npages);
+    if (npages == 0) {
+        l_dnaaDestroy(&daa_locs);
+        return ERROR_INT("no parsable pdf files found", procName, 1);
+    }
+
+        /* Find the mapping from initial to final object numbers */
+    naa_objs = numaaCreate(npages);  /* stores final object numbers */
+    napage = numaCreate(npages);  /* stores "Page" object numbers */
+    index = 0;
+    for (i = 0; i < npages; i++) {
+        da = l_dnaaGetDna(daa_locs, i, L_CLONE);
+        nobj = l_dnaGetCount(da);
+        if (i == 0) {
+            numaAddNumber(napage, 4);  /* object 4 on first page */
+            na_objs = numaMakeSequence(0.0, 1.0, nobj - 1);
+            index = nobj - 1;
+        } else {  /* skip the first 3 objects in each file */
+            numaAddNumber(napage, index);  /* Page object is first we add */
+            na_objs = numaMakeConstant(0.0, nobj - 1);
+            numaReplaceNumber(na_objs, 3, 3);  /* refers to parent of all */
+            for (j = 4; j < nobj - 1; j++)
+                numaSetValue(na_objs, j, index++);
+        }
+        numaaAddNuma(naa_objs, na_objs, L_INSERT);
+        l_dnaDestroy(&da);
+    }
+
+        /* Make the Pages object (#3) */
+    str_pages = generatePagesObjStringPdf(napage);
+
+        /* Build the output */
+    bad = l_byteaCreate(5000);
+    da_outlocs = l_dnaCreate(0);  /* locations of all output objects */
+    for (i = 0; i < npages; i++) {
+        bas = (L_BYTEA *)ptraGetPtrToItem(pa_data, i);
+        pdfdata = l_byteaGetData(bas, &size);
+        da_locs = l_dnaaGetDna(daa_locs, i, L_CLONE);  /* locs on this page */
+        na_objs = numaaGetNuma(naa_objs, i, L_CLONE);  /* obj # on this page */
+        nobj = l_dnaGetCount(da_locs) - 1;
+        da_sizes = l_dnaMakeDelta(da_locs);  /* object sizes on this page */
+        sizes = l_dnaGetIArray(da_sizes);
+        locs = l_dnaGetIArray(da_locs);
+        if (i == 0) {
+            l_byteaAppendData(bad, pdfdata, sizes[0]);
+            l_byteaAppendData(bad, pdfdata + locs[1], sizes[1]);
+            l_byteaAppendData(bad, pdfdata + locs[2], sizes[2]);
+            l_byteaAppendString(bad, str_pages);
+            for (j = 0; j < 4; j++)
+                l_dnaAddNumber(da_outlocs, locs[j]);
+        }
+        for (j = 4; j < nobj; j++) {
+            l_dnaAddNumber(da_outlocs, l_byteaGetSize(bad));
+            bat1 = l_byteaInitFromMem(pdfdata + locs[j], sizes[j]);
+            bat2 = substituteObjectNumbers(bat1, na_objs);
+            data = l_byteaGetData(bat2, &size);
+            l_byteaAppendData(bad, data, size);
+            l_byteaDestroy(&bat1);
+            l_byteaDestroy(&bat2);
+        }
+        if (i == npages - 1)  /* last one */
+            l_dnaAddNumber(da_outlocs, l_byteaGetSize(bad));
+        LEPT_FREE(sizes);
+        LEPT_FREE(locs);
+        l_dnaDestroy(&da_locs);
+        numaDestroy(&na_objs);
+        l_dnaDestroy(&da_sizes);
+    }
+
+        /* Add the trailer */
+    str_trailer = makeTrailerStringPdf(da_outlocs);
+    l_byteaAppendString(bad, str_trailer);
+
+        /* Transfer the output data */
+    *pdata = l_byteaCopyData(bad, pnbytes);
+    l_byteaDestroy(&bad);
+
+#if  DEBUG_MULTIPAGE
+    fprintf(stderr, "******** object mapper **********");
+    numaaWriteStream(stderr, naa_objs);
+
+    fprintf(stderr, "******** Page object numbers ***********");
+    numaWriteStream(stderr, napage);
+
+    fprintf(stderr, "******** Pages object ***********\n");
+    fprintf(stderr, "%s\n", str_pages);
+#endif  /* DEBUG_MULTIPAGE */
+
+    numaDestroy(&napage);
+    numaaDestroy(&naa_objs);
+    l_dnaDestroy(&da_outlocs);
+    l_dnaaDestroy(&daa_locs);
+    LEPT_FREE(str_pages);
+    LEPT_FREE(str_trailer);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                     Low-level CID-based operations                  *
+ *---------------------------------------------------------------------*/
+/*!
+ *  l_generateCIDataForPdf()
+ *
+ *      Input:  fname
+ *              pix (<optional>; can be null)
+ *              quality (for jpeg if transcoded; 75 is standard)
+ *              &cid (<return> compressed data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Given an image file and optionally a pix raster of that data,
+ *          this provides a CID that is compatible with PDF, preferably
+ *          without transcoding.
+ *      (2) The pix is included for efficiency, in case transcoding
+ *          is required and the pix is available to the caller.
+ */
+l_int32
+l_generateCIDataForPdf(const char    *fname,
+                       PIX           *pix,
+                       l_int32        quality,
+                       L_COMP_DATA  **pcid)
+{
+l_int32       format, type;
+L_COMP_DATA  *cid;
+PIX          *pixt;
+
+    PROCNAME("l_generateCIDataForPdf");
+
+    if (!pcid)
+        return ERROR_INT("&cid not defined", procName, 1);
+    *pcid = NULL;
+    if (!fname)
+        return ERROR_INT("fname not defined", procName, 1);
+
+    findFileFormat(fname, &format);
+    if (format == IFF_UNKNOWN)
+        L_WARNING("file %s format is unknown\n", procName, fname);
+    if (format == IFF_PS || format == IFF_LPDF) {
+        L_ERROR("file %s is unsupported format %d\n", procName, fname, format);
+        return 1;
+    }
+
+    if (format == IFF_JFIF_JPEG) {
+        cid = l_generateJpegData(fname, 0);
+    } else if (format == IFF_JP2) {
+        cid = l_generateJp2kData(fname);
+    } else if (format == IFF_PNG) {  /* use Jeff's special function for png */
+        cid = l_generateFlateDataPdf(fname, pix);
+    } else {  /* any other format ... */
+        if (!pix)
+            pixt = pixRead(fname);
+        else
+            pixt = pixClone(pix);
+        if (!pixt)
+            return ERROR_INT("pixt not made", procName, 1);
+        selectDefaultPdfEncoding(pixt, &type);
+        pixGenerateCIData(pixt, type, quality, 0, &cid);
+        pixDestroy(&pixt);
+    }
+    if (!cid) {
+        L_ERROR("file %s format is %d; unreadable\n", procName, fname, format);
+        return 1;
+    }
+    *pcid = cid;
+    return 0;
+}
+
+
+/*!
+ *  l_generateFlateDataPdf()
+ *
+ *      Input:  fname (preferably png)
+ *              pix (<optional>; can be null)
+ *      Return: cid (containing png data), or null on error
+ *
+ *  Notes:
+ *      (1) If you hand this a png file, you are going to get
+ *          png predictors embedded in the flate data. So it has
+ *          come to this. http://xkcd.com/1022/
+ *      (2) Exception: if the png is interlaced or if it is RGBA,
+ *          it will be transcoded.
+ *      (3) If transcoding is required, this will not have to read from
+ *          file if you also input a pix.
+ */
+L_COMP_DATA *
+l_generateFlateDataPdf(const char  *fname,
+                       PIX         *pixs)
+{
+l_uint8      *pngcomp = NULL;  /* entire PNG compressed file */
+l_uint8      *datacomp = NULL;  /* gzipped raster data */
+l_uint8      *cmapdata = NULL;  /* uncompressed colormap */
+char         *cmapdatahex = NULL;  /* hex ascii uncompressed colormap */
+l_uint32      i, j, n;
+l_int32       format, interlaced;
+l_int32       ncolors;  /* in colormap */
+l_int32       bps;  /* bits/sample: usually 8 */
+l_int32       spp;  /* samples/pixel: 1-grayscale/cmap); 3-rgb; 4-rgba */
+l_int32       w, h, cmapflag;
+l_int32       xres, yres;
+size_t        nbytescomp = 0, nbytespng = 0;
+FILE         *fp;
+L_COMP_DATA  *cid;
+PIX          *pix;
+PIXCMAP      *cmap = NULL;
+
+    PROCNAME("l_generateFlateDataPdf");
+
+    if (!fname)
+        return (L_COMP_DATA *)ERROR_PTR("fname not defined", procName, NULL);
+
+    findFileFormat(fname, &format);
+    spp = 0;  /* init to spp != 4 if not png */
+    interlaced = 0;  /* initialize to no interlacing */
+    if (format == IFF_PNG) {
+        isPngInterlaced(fname, &interlaced);
+        readHeaderPng(fname, NULL, NULL, NULL, &spp, NULL);
+    }
+
+        /* PDF is capable of inlining some types of PNG files, but not all
+           of them. We need to transcode anything with interlacing or an
+           alpha channel.
+
+           Be careful with spp. Any PNG image file with an alpha
+           channel is converted on reading to RGBA (spp == 4). This
+           includes the (gray + alpha) format with spp == 2. You
+           will get different results if you look at spp via
+           readHeaderPng() versus pixGetSpp() */
+    if (format != IFF_PNG || interlaced || spp == 4 || spp == 2) {
+        if (!pixs)
+            pix = pixRead(fname);
+        else
+            pix = pixClone(pixs);
+        if (!pix)
+            return (L_COMP_DATA *)ERROR_PTR("pix not made", procName, NULL);
+        cid = pixGenerateFlateData(pix, 0);
+        pixDestroy(&pix);
+        return cid;
+    }
+
+        /* It's png.  Generate the pdf data without transcoding.
+         * Implementation by Jeff Breidenbach.
+         * First, read the metadata */
+    if ((fp = fopenReadStream(fname)) == NULL)
+        return (L_COMP_DATA *)ERROR_PTR("stream not opened", procName, NULL);
+    freadHeaderPng(fp, &w, &h, &bps, &spp, &cmapflag);
+    fgetPngResolution(fp, &xres, &yres);
+    fclose(fp);
+
+        /* We get pdf corruption when inlining the data from 16 bpp png. */
+    if (bps == 16)
+        return l_generateFlateData(fname, 0);
+
+        /* Read the entire png file */
+    if ((pngcomp = l_binaryRead(fname, &nbytespng)) == NULL)
+        return (L_COMP_DATA *)ERROR_PTR("unable to read file",
+                                        procName, NULL);
+
+        /* Extract flate data, copying portions of it to memory, including
+         * the predictor information in a byte at the beginning of each
+         * raster line.  The flate data makes up the vast majority of
+         * the png file, so after extraction we expect datacomp to
+         * be nearly full (i.e., nbytescomp will be only slightly less
+         * than nbytespng).  Also extract the colormap if present. */
+    if ((datacomp = (l_uint8 *)LEPT_CALLOC(1, nbytespng)) == NULL)
+        return (L_COMP_DATA *)ERROR_PTR("unable to allocate memory",
+                                        procName, NULL);
+
+        /* Parse the png file.  Each chunk consists of:
+         *    length: 4 bytes
+         *    name:   4 bytes (e.g., "IDAT")
+         *    data:   n bytes
+         *    CRC:    4 bytes
+         * Start at the beginning of the data section of the first chunk,
+         * byte 16, because the png file begins with 8 bytes of header,
+         * followed by the first 8 bytes of the first chunk
+         * (length and name).  On each loop, increment by 12 bytes to
+         * skip over the CRC, length and name of the next chunk. */
+    for (i = 16; i < nbytespng; i += 12) {  /* do each successive chunk */
+            /* Get the chunk length */
+        n  = pngcomp[i - 8] << 24;
+        n += pngcomp[i - 7] << 16;
+        n += pngcomp[i - 6] << 8;
+        n += pngcomp[i - 5] << 0;
+        if (i + n >= nbytespng) {
+            LEPT_FREE(pngcomp);
+            LEPT_FREE(datacomp);
+            pixcmapDestroy(&cmap);
+            L_ERROR("invalid png: i = %d, n = %d, nbytes = %lu\n", procName,
+                    i, n, (unsigned long)nbytespng);
+            return NULL;
+        }
+
+            /* Is it a data chunk? */
+        if (strncmp((const char *)(pngcomp + i - 4), "IDAT", 4) == 0) {
+            memcpy(datacomp + nbytescomp, pngcomp + i, n);
+            nbytescomp += n;
+        }
+
+            /* Is it a palette chunk? */
+        if (cmapflag && !cmap &&
+            strncmp((const char *)(pngcomp + i - 4), "PLTE", 4) == 0) {
+            if ((n / 3) > (1 << bps)) {
+                LEPT_FREE(pngcomp);
+                LEPT_FREE(datacomp);
+                pixcmapDestroy(&cmap);
+                L_ERROR("invalid png: i = %d, n = %d, cmapsize = %d\n",
+                        procName, i, n, (1 << bps));
+                return NULL;
+            }
+            cmap = pixcmapCreate(bps);
+            for (j = i; j < i + n; j += 3) {
+                pixcmapAddColor(cmap, pngcomp[j], pngcomp[j + 1],
+                                pngcomp[j + 2]);
+            }
+        }
+        i += n;  /* move to the end of the data chunk */
+    }
+    LEPT_FREE(pngcomp);
+
+    if (nbytescomp == 0) {
+        LEPT_FREE(datacomp);
+        pixcmapDestroy(&cmap);
+        return (L_COMP_DATA *)ERROR_PTR("invalid PNG file", procName, NULL);
+    }
+
+        /* Extract and encode the colormap data as hexascii  */
+    ncolors = 0;
+    if (cmap) {
+        pixcmapSerializeToMemory(cmap, 3, &ncolors, &cmapdata);
+        pixcmapDestroy(&cmap);
+        if (!cmapdata) {
+            LEPT_FREE(datacomp);
+            return (L_COMP_DATA *)ERROR_PTR("cmapdata not made",
+                                            procName, NULL);
+        }
+        cmapdatahex = pixcmapConvertToHex(cmapdata, ncolors);
+        LEPT_FREE(cmapdata);
+    }
+
+        /* Note that this is the only situation where the predictor
+         * field of the CID is set to 1.  Adobe's predictor values on
+         * p. 76 of pdf_reference_1-7.pdf give 1 for no predictor and
+         * 10-14 for inline predictors, the specifics of which are
+         * ignored by the pdf interpreter, which just needs to know that
+         * the first byte on each compressed scanline is some predictor
+         * whose type can be inferred from the byte itself.  */
+    cid = (L_COMP_DATA *)LEPT_CALLOC(1, sizeof(L_COMP_DATA));
+    cid->datacomp = datacomp;
+    cid->type = L_FLATE_ENCODE;
+    cid->cmapdatahex = cmapdatahex;
+    cid->nbytescomp = nbytescomp;
+    cid->ncolors = ncolors;
+    cid->predictor = TRUE;
+    cid->w = w;
+    cid->h = h;
+    cid->bps = bps;
+    cid->spp = spp;
+    cid->res = xres;
+    return cid;
+}
+
+
+/*!
+ *  l_generateJpegData()
+ *
+ *      Input:  fname (of jpeg file)
+ *              ascii85flag (0 for jpeg; 1 for ascii85-encoded jpeg)
+ *      Return: cid (containing jpeg data), or null on error
+ *
+ *  Notes:
+ *      (1) Set ascii85flag:
+ *           - 0 for binary data (not permitted in PostScript)
+ *           - 1 for ascii85 (5 for 4) encoded binary data
+ *               (not permitted in pdf)
+ */
+L_COMP_DATA *
+l_generateJpegData(const char  *fname,
+                   l_int32      ascii85flag)
+{
+l_uint8      *datacomp = NULL;  /* entire jpeg compressed file */
+char         *data85 = NULL;  /* ascii85 encoded jpeg compressed file */
+l_int32       w, h, xres, yres, bps, spp;
+l_int32       nbytes85;
+size_t        nbytescomp;
+FILE         *fp;
+L_COMP_DATA  *cid;
+
+    PROCNAME("l_generateJpegData");
+
+    if (!fname)
+        return (L_COMP_DATA *)ERROR_PTR("fname not defined", procName, NULL);
+
+        /* The returned jpeg data in memory is the entire jpeg file,
+         * which starts with ffd8 and ends with ffd9 */
+    if ((datacomp = l_binaryRead(fname, &nbytescomp)) == NULL)
+        return (L_COMP_DATA *)ERROR_PTR("datacomp not extracted",
+                                        procName, NULL);
+
+        /* Read the metadata */
+    if ((fp = fopenReadStream(fname)) == NULL)
+        return (L_COMP_DATA *)ERROR_PTR("stream not opened", procName, NULL);
+    freadHeaderJpeg(fp, &w, &h, &spp, NULL, NULL);
+    bps = 8;
+    fgetJpegResolution(fp, &xres, &yres);
+    fclose(fp);
+
+        /* Optionally, encode the compressed data */
+    if (ascii85flag == 1) {
+        data85 = encodeAscii85(datacomp, nbytescomp, &nbytes85);
+        LEPT_FREE(datacomp);
+        if (!data85)
+            return (L_COMP_DATA *)ERROR_PTR("data85 not made", procName, NULL);
+        else
+            data85[nbytes85 - 1] = '\0';  /* remove the newline */
+    }
+
+    cid = (L_COMP_DATA *)LEPT_CALLOC(1, sizeof(L_COMP_DATA));
+    if (!cid)
+        return (L_COMP_DATA *)ERROR_PTR("cid not made", procName, NULL);
+    if (ascii85flag == 0) {
+        cid->datacomp = datacomp;
+    } else {  /* ascii85 */
+        cid->data85 = data85;
+        cid->nbytes85 = nbytes85;
+    }
+    cid->type = L_JPEG_ENCODE;
+    cid->nbytescomp = nbytescomp;
+    cid->w = w;
+    cid->h = h;
+    cid->bps = bps;
+    cid->spp = spp;
+    cid->res = xres;
+    return cid;
+}
+
+
+/*!
+ *  l_generateJp2kData()
+ *
+ *      Input:  fname (of jp2k file)
+ *      Return: cid (containing jp2k data), or null on error
+ *
+ *  Notes:
+ *      (1) This is only called after the file is verified to be jp2k.
+ */
+static L_COMP_DATA *
+l_generateJp2kData(const char  *fname)
+{
+l_int32       w, h, bps, spp;
+size_t        nbytes;
+L_COMP_DATA  *cid;
+
+    PROCNAME("l_generateJp2kData");
+
+    if (!fname)
+        return (L_COMP_DATA *)ERROR_PTR("fname not defined", procName, NULL);
+
+    if ((cid = (L_COMP_DATA *)LEPT_CALLOC(1, sizeof(L_COMP_DATA))) == NULL)
+        return (L_COMP_DATA *)ERROR_PTR("cid not made", procName, NULL);
+
+        /* The returned jp2k data in memory is the entire jp2k file */
+    if ((cid->datacomp = l_binaryRead(fname, &nbytes)) == NULL)
+        return (L_COMP_DATA *)ERROR_PTR("data not extracted", procName, NULL);
+
+    readHeaderJp2k(fname, &w, &h, &bps, &spp);
+    cid->type = L_JP2K_ENCODE;
+    cid->nbytescomp = nbytes;
+    cid->w = w;
+    cid->h = h;
+    cid->bps = bps;
+    cid->spp = spp;
+    cid->res = 0;  /* don't know how to extract this */
+    return cid;
+}
+
+
+/*!
+ *  l_generateCIData()
+ *
+ *      Input:  fname
+ *              type (L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE, L_JP2K_ENCODE)
+ *              quality (used for jpeg only; 0 for default (75))
+ *              ascii85 (0 for binary; 1 for ascii85-encoded)
+ *              &cid (<return> compressed data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This can be used for both PostScript and pdf.
+ *      (1) Set ascii85:
+ *           - 0 for binary data (not permitted in PostScript)
+ *           - 1 for ascii85 (5 for 4) encoded binary data
+ *      (2) This attempts to compress according to the requested type.
+ *          If this can't be done, it falls back to ordinary flate encoding.
+ *      (3) This differs from l_generateCIDataPdf(), which determines
+ *          the format and attempts to generate the CID without transcoding.
+ */
+l_int32
+l_generateCIData(const char    *fname,
+                 l_int32        type,
+                 l_int32        quality,
+                 l_int32        ascii85,
+                 L_COMP_DATA  **pcid)
+{
+l_int32       format, d, bps, spp, iscmap;
+L_COMP_DATA  *cid;
+PIX          *pix;
+
+    PROCNAME("l_generateCIData");
+
+    if (!pcid)
+        return ERROR_INT("&cid not defined", procName, 1);
+    *pcid = NULL;
+    if (!fname)
+        return ERROR_INT("fname not defined", procName, 1);
+    if (type != L_G4_ENCODE && type != L_JPEG_ENCODE &&
+        type != L_FLATE_ENCODE && type != L_JP2K_ENCODE)
+        return ERROR_INT("invalid conversion type", procName, 1);
+    if (ascii85 != 0 && ascii85 != 1)
+        return ERROR_INT("invalid ascii85", procName, 1);
+
+        /* Sanity check on requested encoding */
+    pixReadHeader(fname, &format, NULL, NULL, &bps, &spp, &iscmap);
+    d = bps * spp;
+    if (d == 24) d = 32;
+    if (iscmap && type != L_FLATE_ENCODE) {
+        L_WARNING("pixs has cmap; using flate encoding\n", procName);
+        type = L_FLATE_ENCODE;
+    } else if (d < 8 && type == L_JPEG_ENCODE) {
+        L_WARNING("pixs has < 8 bpp; using flate encoding\n", procName);
+        type = L_FLATE_ENCODE;
+    } else if (d < 8 && type == L_JP2K_ENCODE) {
+        L_WARNING("pixs has < 8 bpp; using flate encoding\n", procName);
+        type = L_FLATE_ENCODE;
+    } else if (d > 1 && type == L_G4_ENCODE) {
+        L_WARNING("pixs has > 1 bpp; using flate encoding\n", procName);
+        type = L_FLATE_ENCODE;
+    }
+
+    if (type == L_JPEG_ENCODE) {
+        if (format == IFF_JFIF_JPEG) {  /* do not transcode */
+            cid = l_generateJpegData(fname, ascii85);
+        } else {
+            if ((pix = pixRead(fname)) == NULL)
+                return ERROR_INT("pix not returned", procName, 1);
+            cid = pixGenerateJpegData(pix, ascii85, quality);
+            pixDestroy(&pix);
+        }
+        if (!cid)
+            return ERROR_INT("jpeg data not made", procName, 1);
+    } else if (type == L_JP2K_ENCODE) {
+        if (format == IFF_JP2) {  /* do not transcode */
+            cid = l_generateJp2kData(fname);
+        } else {
+            if ((pix = pixRead(fname)) == NULL)
+                return ERROR_INT("pix not returned", procName, 1);
+            cid = pixGenerateJpegData(pix, ascii85, quality);
+            pixDestroy(&pix);
+        }
+        if (!cid)
+            return ERROR_INT("jpeg data not made", procName, 1);
+    } else if (type == L_G4_ENCODE) {
+        if ((cid = l_generateG4Data(fname, ascii85)) == NULL)
+            return ERROR_INT("g4 data not made", procName, 1);
+    } else if (type == L_FLATE_ENCODE) {
+        if ((cid = l_generateFlateData(fname, ascii85)) == NULL)
+            return ERROR_INT("flate data not made", procName, 1);
+    } else {
+        return ERROR_INT("invalid conversion type", procName, 1);
+    }
+    *pcid = cid;
+
+    return 0;
+}
+
+
+/*!
+ *  pixGenerateCIData()
+ *
+ *      Input:  pixs (8 or 32 bpp, no colormap)
+ *              type (L_G4_ENCODE, L_JPEG_ENCODE, L_FLATE_ENCODE)
+ *              quality (used for jpeg only; 0 for default (75))
+ *              ascii85 (0 for binary; 1 for ascii85-encoded)
+ *              &cid (<return> compressed data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Set ascii85:
+ *           - 0 for binary data (not permitted in PostScript)
+ *           - 1 for ascii85 (5 for 4) encoded binary data
+ */
+l_int32
+pixGenerateCIData(PIX           *pixs,
+                  l_int32        type,
+                  l_int32        quality,
+                  l_int32        ascii85,
+                  L_COMP_DATA  **pcid)
+{
+l_int32   d;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixGenerateCIData");
+
+    if (!pcid)
+        return ERROR_INT("&cid not defined", procName, 1);
+    *pcid = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (type != L_G4_ENCODE && type != L_JPEG_ENCODE &&
+        type != L_FLATE_ENCODE)
+        return ERROR_INT("invalid conversion type", procName, 1);
+    if (ascii85 != 0 && ascii85 != 1)
+        return ERROR_INT("invalid ascii85", procName, 1);
+
+        /* Sanity check on requested encoding */
+    d = pixGetDepth(pixs);
+    cmap = pixGetColormap(pixs);
+    if (cmap && type != L_FLATE_ENCODE) {
+        L_WARNING("pixs has cmap; using flate encoding\n", procName);
+        type = L_FLATE_ENCODE;
+    } else if (d < 8 && type == L_JPEG_ENCODE) {
+        L_WARNING("pixs has < 8 bpp; using flate encoding\n", procName);
+        type = L_FLATE_ENCODE;
+    } else if (d > 1 && type == L_G4_ENCODE) {
+        L_WARNING("pixs has > 1 bpp; using flate encoding\n", procName);
+        type = L_FLATE_ENCODE;
+    }
+
+    if (type == L_JPEG_ENCODE) {
+        if ((*pcid = pixGenerateJpegData(pixs, ascii85, quality)) == NULL)
+            return ERROR_INT("jpeg data not made", procName, 1);
+    } else if (type == L_G4_ENCODE) {
+        if ((*pcid = pixGenerateG4Data(pixs, ascii85)) == NULL)
+            return ERROR_INT("g4 data not made", procName, 1);
+    } else if (type == L_FLATE_ENCODE) {
+        if ((*pcid = pixGenerateFlateData(pixs, ascii85)) == NULL)
+            return ERROR_INT("flate data not made", procName, 1);
+    } else {
+        return ERROR_INT("invalid conversion type", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  l_generateFlateData()
+ *
+ *      Input:  fname
+ *              ascii85flag (0 for gzipped; 1 for ascii85-encoded gzipped)
+ *      Return: cid (flate compressed image data), or null on error
+ *
+ *  Notes:
+ *      (1) The input image is converted to one of these 4 types:
+ *           - 1 bpp
+ *           - 8 bpp, no colormap
+ *           - 8 bpp, colormap
+ *           - 32 bpp rgb
+ *      (2) Set ascii85flag:
+ *           - 0 for binary data (not permitted in PostScript)
+ *           - 1 for ascii85 (5 for 4) encoded binary data
+ */
+L_COMP_DATA *
+l_generateFlateData(const char  *fname,
+                    l_int32      ascii85flag)
+{
+L_COMP_DATA  *cid;
+PIX          *pixs;
+
+    PROCNAME("l_generateFlateData");
+
+    if (!fname)
+        return (L_COMP_DATA *)ERROR_PTR("fname not defined", procName, NULL);
+
+    if ((pixs = pixRead(fname)) == NULL)
+        return (L_COMP_DATA *)ERROR_PTR("pixs not made", procName, NULL);
+    cid = pixGenerateFlateData(pixs, ascii85flag);
+    pixDestroy(&pixs);
+    return cid;
+}
+
+
+/*!
+ *  pixGenerateFlateData()
+ *
+ *      Input:  pixs
+ *              ascii85flag (0 for gzipped; 1 for ascii85-encoded gzipped)
+ *      Return: cid (flate compressed image data), or null on error
+ *
+ *      Notes:
+ *          (1) This should not be called with an RGBA pix (spp == 4); it
+ *              will ignore the alpha channel.  Likewise, if called with a
+ *              colormapped pix, the alpha component in the colormap will
+ *              be ignored (as it is for all leptonica operations
+ *              on colormapped pix).
+ */
+static L_COMP_DATA *
+pixGenerateFlateData(PIX     *pixs,
+                     l_int32  ascii85flag)
+{
+l_uint8      *data = NULL;  /* uncompressed raster data in required format */
+l_uint8      *datacomp = NULL;  /* gzipped raster data */
+char         *data85 = NULL;  /* ascii85 encoded gzipped raster data */
+l_uint8      *cmapdata = NULL;  /* uncompressed colormap */
+char         *cmapdata85 = NULL;  /* ascii85 encoded uncompressed colormap */
+char         *cmapdatahex = NULL;  /* hex ascii uncompressed colormap */
+l_int32       ncolors;  /* in colormap; not used if cmapdata85 is null */
+l_int32       bps;  /* bits/sample: usually 8 */
+l_int32       spp;  /* samples/pixel: 1-grayscale/cmap); 3-rgb */
+l_int32       w, h, d, cmapflag;
+l_int32       ncmapbytes85 = 0;
+l_int32       nbytes85 = 0;
+size_t        nbytes, nbytescomp;
+L_COMP_DATA  *cid;
+PIX          *pixt;
+PIXCMAP      *cmap;
+
+    PROCNAME("pixGenerateFlateData");
+
+    if (!pixs)
+        return (L_COMP_DATA *)ERROR_PTR("pixs not defined", procName, NULL);
+
+        /* Convert the image to one of these 4 types:
+         *     1 bpp
+         *     8 bpp, no colormap
+         *     8 bpp, colormap
+         *     32 bpp rgb    */
+    pixGetDimensions(pixs, &w, &h, &d);
+    cmap = pixGetColormap(pixs);
+    cmapflag = (cmap) ? 1 : 0;
+    if (d == 2 || d == 4 || d == 16) {
+        pixt = pixConvertTo8(pixs, cmapflag);
+        cmap = pixGetColormap(pixt);
+        d = pixGetDepth(pixt);
+    } else {
+        pixt = pixClone(pixs);
+    }
+    spp = (d == 32) ? 3 : 1;  /* ignores alpha */
+    bps = (d == 32) ? 8 : d;
+
+        /* Extract and encode the colormap data as both ascii85 and hexascii  */
+    ncolors = 0;
+    if (cmap) {
+        pixcmapSerializeToMemory(cmap, 3, &ncolors, &cmapdata);
+        if (!cmapdata)
+            return (L_COMP_DATA *)ERROR_PTR("cmapdata not made",
+                                            procName, NULL);
+
+        cmapdata85 = encodeAscii85(cmapdata, 3 * ncolors, &ncmapbytes85);
+        cmapdatahex = pixcmapConvertToHex(cmapdata, ncolors);
+        LEPT_FREE(cmapdata);
+    }
+
+        /* Extract and compress the raster data */
+    pixGetRasterData(pixt, &data, &nbytes);
+    pixDestroy(&pixt);
+    datacomp = zlibCompress(data, nbytes, &nbytescomp);
+    if (!datacomp) {
+        if (cmapdata85) LEPT_FREE(cmapdata85);
+        if (cmapdatahex) LEPT_FREE(cmapdatahex);
+        return (L_COMP_DATA *)ERROR_PTR("datacomp not made", procName, NULL);
+    }
+    LEPT_FREE(data);
+
+        /* Optionally, encode the compressed data */
+    if (ascii85flag == 1) {
+        data85 = encodeAscii85(datacomp, nbytescomp, &nbytes85);
+        LEPT_FREE(datacomp);
+        if (!data85) {
+            LEPT_FREE(cmapdata85);
+            return (L_COMP_DATA *)ERROR_PTR("data85 not made", procName, NULL);
+        } else {
+            data85[nbytes85 - 1] = '\0';  /* remove the newline */
+        }
+    }
+
+    cid = (L_COMP_DATA *)LEPT_CALLOC(1, sizeof(L_COMP_DATA));
+    if (!cid)
+        return (L_COMP_DATA *)ERROR_PTR("cid not made", procName, NULL);
+    if (ascii85flag == 0) {
+        cid->datacomp = datacomp;
+    } else {  /* ascii85 */
+        cid->data85 = data85;
+        cid->nbytes85 = nbytes85;
+    }
+    cid->type = L_FLATE_ENCODE;
+    cid->cmapdatahex = cmapdatahex;
+    cid->cmapdata85 = cmapdata85;
+    cid->nbytescomp = nbytescomp;
+    cid->ncolors = ncolors;
+    cid->w = w;
+    cid->h = h;
+    cid->bps = bps;
+    cid->spp = spp;
+    cid->res = pixGetXRes(pixs);
+    cid->nbytes = nbytes;  /* only for debugging */
+    return cid;
+}
+
+
+/*!
+ *  pixGenerateJpegData()
+ *
+ *      Input:  pixs (8 or 32 bpp, no colormap)
+ *              ascii85flag (0 for jpeg; 1 for ascii85-encoded jpeg)
+ *              quality (0 for default, which is 75)
+ *      Return: cid (jpeg compressed data), or null on error
+ *
+ *  Notes:
+ *      (1) Set ascii85flag:
+ *           - 0 for binary data (not permitted in PostScript)
+ *           - 1 for ascii85 (5 for 4) encoded binary data
+ */
+static L_COMP_DATA *
+pixGenerateJpegData(PIX     *pixs,
+                    l_int32  ascii85flag,
+                    l_int32  quality)
+{
+l_int32       d;
+char         *fname;
+L_COMP_DATA  *cid;
+
+    PROCNAME("pixGenerateJpegData");
+
+    if (!pixs)
+        return (L_COMP_DATA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (L_COMP_DATA *)ERROR_PTR("pixs has colormap", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (L_COMP_DATA *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+
+        /* Compress to a temp jpeg file */
+    lept_mkdir("lept");
+    fname = genTempFilename("/tmp/lept", "temp.jpg", 1, 1);
+    pixWriteJpeg(fname, pixs, quality, 0);
+
+    cid = l_generateJpegData(fname, ascii85flag);
+    lept_rmfile(fname);
+    lept_free(fname);
+    return cid;
+}
+
+
+/*!
+ *  pixGenerateG4Data()
+ *
+ *      Input:  pixs (1 bpp)
+ *              ascii85flag (0 for gzipped; 1 for ascii85-encoded gzipped)
+ *      Return: cid (g4 compressed image data), or null on error
+ *
+ *  Notes:
+ *      (1) Set ascii85flag:
+ *           - 0 for binary data (not permitted in PostScript)
+ *           - 1 for ascii85 (5 for 4) encoded binary data
+ */
+static L_COMP_DATA *
+pixGenerateG4Data(PIX     *pixs,
+                  l_int32  ascii85flag)
+{
+char         *tname;
+L_COMP_DATA  *cid;
+
+    PROCNAME("pixGenerateG4Data");
+
+    if (!pixs)
+        return (L_COMP_DATA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (L_COMP_DATA *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+
+        /* Compress to a temp tiff g4 file */
+    lept_mkdir("lept");
+    tname = genTempFilename("/tmp/lept", "temp.tif", 1, 1);
+    pixWrite(tname, pixs, IFF_TIFF_G4);
+
+    cid = l_generateG4Data(tname, ascii85flag);
+    lept_rmfile(tname);
+    lept_free(tname);
+    return cid;
+}
+
+
+/*!
+ *  l_generateG4Data()
+ *
+ *      Input:  fname (of g4 compressed file)
+ *              ascii85flag (0 for g4 compressed; 1 for ascii85-encoded g4)
+ *      Return: cid (g4 compressed image data), or null on error
+ *
+ *  Notes:
+ *      (1) Set ascii85flag:
+ *           - 0 for binary data (not permitted in PostScript)
+ *           - 1 for ascii85 (5 for 4) encoded binary data
+ *             (not permitted in pdf)
+ */
+L_COMP_DATA *
+l_generateG4Data(const char  *fname,
+                 l_int32      ascii85flag)
+{
+l_uint8      *datacomp = NULL;  /* g4 compressed raster data */
+char         *data85 = NULL;  /* ascii85 encoded g4 compressed data */
+l_int32       w, h, xres, yres;
+l_int32       minisblack;  /* TRUE or FALSE */
+l_int32       nbytes85;
+size_t        nbytescomp;
+L_COMP_DATA  *cid;
+FILE         *fp;
+
+    PROCNAME("l_generateG4Data");
+
+    if (!fname)
+        return (L_COMP_DATA *)ERROR_PTR("fname not defined", procName, NULL);
+
+        /* The returned ccitt g4 data in memory is the block of
+         * bytes in the tiff file, starting after 8 bytes and
+         * ending before the directory. */
+    if (extractG4DataFromFile(fname, &datacomp, &nbytescomp,
+                              &w, &h, &minisblack)) {
+        return (L_COMP_DATA *)ERROR_PTR("datacomp not extracted",
+                                        procName, NULL);
+    }
+
+        /* Read the resolution */
+    if ((fp = fopenReadStream(fname)) == NULL)
+        return (L_COMP_DATA *)ERROR_PTR("stream not opened", procName, NULL);
+    getTiffResolution(fp, &xres, &yres);
+    fclose(fp);
+
+        /* Optionally, encode the compressed data */
+    if (ascii85flag == 1) {
+        data85 = encodeAscii85(datacomp, nbytescomp, &nbytes85);
+        LEPT_FREE(datacomp);
+        if (!data85)
+            return (L_COMP_DATA *)ERROR_PTR("data85 not made", procName, NULL);
+        else
+            data85[nbytes85 - 1] = '\0';  /* remove the newline */
+    }
+
+    cid = (L_COMP_DATA *)LEPT_CALLOC(1, sizeof(L_COMP_DATA));
+    if (!cid)
+        return (L_COMP_DATA *)ERROR_PTR("cid not made", procName, NULL);
+    if (ascii85flag == 0) {
+        cid->datacomp = datacomp;
+    } else {  /* ascii85 */
+        cid->data85 = data85;
+        cid->nbytes85 = nbytes85;
+    }
+    cid->type = L_G4_ENCODE;
+    cid->nbytescomp = nbytescomp;
+    cid->w = w;
+    cid->h = h;
+    cid->bps = 1;
+    cid->spp = 1;
+    cid->minisblack = minisblack;
+    cid->res = xres;
+    return cid;
+}
+
+
+/*!
+ *  cidConvertToPdfData()
+ *
+ *      Input:  cid (compressed image data -- of jp2k image)
+ *              title (<optional> pdf title; can be NULL)
+ *              &data (<return> output pdf data for image)
+ *              &nbytes (<return> size of output pdf data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Caller must not destroy the cid.  It is absorbed in the
+ *          lpd and destroyed by this function.
+ */
+l_int32
+cidConvertToPdfData(L_COMP_DATA  *cid,
+                    const char   *title,
+                    l_uint8     **pdata,
+                    size_t       *pnbytes)
+{
+l_int32      res, ret;
+l_float32    wpt, hpt;
+L_PDF_DATA  *lpd = NULL;
+
+    PROCNAME("cidConvertToPdfData");
+
+    if (!pdata || !pnbytes)
+        return ERROR_INT("&data and &nbytes not both defined", procName, 1);
+    *pdata = NULL;
+    *pnbytes = 0;
+    if (!cid)
+        return ERROR_INT("cid not defined", procName, 1);
+
+        /* Get media box parameters, in pts */
+    res = cid->res;
+    if (res <= 0)
+        res = DEFAULT_INPUT_RES;
+    wpt = cid->w * 72. / res;
+    hpt = cid->h * 72. / res;
+
+        /* Set up the pdf data struct (lpd) */
+    if ((lpd = pdfdataCreate(title)) == NULL)
+        return ERROR_INT("lpd not made", procName, 1);
+    ptraAdd(lpd->cida, cid);
+    lpd->n++;
+    ptaAddPt(lpd->xy, 0, 0);   /* xpt = ypt = 0 */
+    ptaAddPt(lpd->wh, wpt, hpt);
+
+        /* Generate the pdf string and destroy the lpd */
+    ret = l_generatePdf(pdata, pnbytes, lpd);
+    pdfdataDestroy(&lpd);
+    if (ret)
+        return ERROR_INT("pdf output not made", procName, 1);
+    return 0;
+}
+
+
+/*!
+ *  l_CIDataDestroy()
+ *
+ *      Input:  &cid (<will be set to null before returning>)
+ *      Return: void
+ */
+void
+l_CIDataDestroy(L_COMP_DATA  **pcid)
+{
+L_COMP_DATA  *cid;
+
+    PROCNAME("l_CIDataDestroy");
+
+    if (pcid == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+    if ((cid = *pcid) == NULL)
+        return;
+
+    if (cid->datacomp) LEPT_FREE(cid->datacomp);
+    if (cid->data85) LEPT_FREE(cid->data85);
+    if (cid->cmapdata85) LEPT_FREE(cid->cmapdata85);
+    if (cid->cmapdatahex) LEPT_FREE(cid->cmapdatahex);
+    LEPT_FREE(cid);
+    *pcid = NULL;
+    return;
+}
+
+
+/*---------------------------------------------------------------------*
+ *         Helper functions for generating the output pdf string       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  l_generatePdf()
+ *
+ *      Input:  &data (<return> pdf array)
+ *              &nbytes (<return> number of bytes in pdf array)
+ *              lpd (all the required input image data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) On error, no data is returned.
+ *      (2) The objects are:
+ *            1: Catalog
+ *            2: Info
+ *            3: Pages
+ *            4: Page
+ *            5: Contents  (rendering command)
+ *            6 to 6+n-1: n XObjects
+ *            6+n to 6+n+m-1: m colormaps
+ */
+static l_int32
+l_generatePdf(l_uint8    **pdata,
+              size_t      *pnbytes,
+              L_PDF_DATA  *lpd)
+{
+    PROCNAME("l_generatePdf");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!lpd)
+        return ERROR_INT("lpd not defined", procName, 1);
+
+    generateFixedStringsPdf(lpd);
+    generateMediaboxPdf(lpd);
+    generatePageStringPdf(lpd);
+    generateContentStringPdf(lpd);
+    generatePreXStringsPdf(lpd);
+    generateColormapStringsPdf(lpd);
+    generateTrailerPdf(lpd);
+    return generateOutputDataPdf(pdata, pnbytes, lpd);
+}
+
+
+static void
+generateFixedStringsPdf(L_PDF_DATA  *lpd)
+{
+char     buf[L_SMALLBUF];
+char    *version, *datestr;
+SARRAY  *sa;
+
+        /* Accumulate data for the header and objects 1-3 */
+    lpd->id = stringNew("%PDF-1.5\n");
+    l_dnaAddNumber(lpd->objsize, strlen(lpd->id));
+
+    lpd->obj1 = stringNew("1 0 obj\n"
+                          "<<\n"
+                          "/Type /Catalog\n"
+                          "/Pages 3 0 R\n"
+                          ">>\n"
+                          "endobj\n");
+    l_dnaAddNumber(lpd->objsize, strlen(lpd->obj1));
+
+    sa = sarrayCreate(0);
+    sarrayAddString(sa, (char *)"2 0 obj\n"
+                                 "<<\n", L_COPY);
+    if (var_WRITE_DATE_AND_VERSION) {
+        datestr = l_getFormattedDate();
+        snprintf(buf, sizeof(buf), "/CreationDate (D:%s)\n", datestr);
+        sarrayAddString(sa, (char *)buf, L_COPY);
+        LEPT_FREE(datestr);
+        version = getLeptonicaVersion();
+        snprintf(buf, sizeof(buf),
+                 "/Producer (leptonica: %s)\n", version);
+        LEPT_FREE(version);
+    } else {
+        snprintf(buf, sizeof(buf), "/Producer (leptonica)\n");
+    }
+    sarrayAddString(sa, (char *)buf, L_COPY);
+    if (lpd->title) {
+        snprintf(buf, sizeof(buf), "/Title (%s)\n", lpd->title);
+        sarrayAddString(sa, (char *)buf, L_COPY);
+    }
+    sarrayAddString(sa, (char *)">>\n"
+                                "endobj\n", L_COPY);
+    lpd->obj2 = sarrayToString(sa, 0);
+    l_dnaAddNumber(lpd->objsize, strlen(lpd->obj2));
+    sarrayDestroy(&sa);
+
+    lpd->obj3 = stringNew("3 0 obj\n"
+                          "<<\n"
+                          "/Type /Pages\n"
+                          "/Kids [ 4 0 R ]\n"
+                          "/Count 1\n"
+                          ">>\n");
+    l_dnaAddNumber(lpd->objsize, strlen(lpd->obj3));
+
+        /* Do the post-datastream string */
+    lpd->poststream = stringNew("\n"
+                                "endstream\n"
+                                "endobj\n");
+    return;
+}
+
+
+static void
+generateMediaboxPdf(L_PDF_DATA  *lpd)
+{
+l_int32    i;
+l_float32  xpt, ypt, wpt, hpt, maxx, maxy;
+
+        /* First get the full extent of all the images.
+         * This is the mediabox, in pts. */
+    maxx = maxy = 0;
+    for (i = 0; i < lpd->n; i++) {
+        ptaGetPt(lpd->xy, i, &xpt, &ypt);
+        ptaGetPt(lpd->wh, i, &wpt, &hpt);
+        maxx = L_MAX(maxx, xpt + wpt);
+        maxy = L_MAX(maxy, ypt + hpt);
+    }
+
+    lpd->mediabox = boxCreate(0, 0, (l_int32)(maxx + 0.5),
+                              (l_int32)(maxy + 0.5));
+
+        /* ypt is in standard image coordinates: the location of
+         * the UL image corner with respect to the UL media box corner.
+         * Rewrite each ypt for PostScript coordinates: the location of
+         * the LL image corner with respect to the LL media box corner. */
+    for (i = 0; i < lpd->n; i++) {
+        ptaGetPt(lpd->xy, i, &xpt, &ypt);
+        ptaGetPt(lpd->wh, i, &wpt, &hpt);
+        ptaSetPt(lpd->xy, i, xpt, maxy - ypt - hpt);
+    }
+
+    return;
+}
+
+
+static l_int32
+generatePageStringPdf(L_PDF_DATA  *lpd)
+{
+char    *buf;
+char    *xstr;
+l_int32  bufsize, i, wpt, hpt;
+SARRAY  *sa;
+
+    PROCNAME("generatePageStringPdf");
+
+        /* Allocate 1000 bytes for the boilerplate text, and
+         * 50 bytes for each reference to an image in the
+         * ProcSet array.  */
+    bufsize = 1000 + 50 * lpd->n;
+    if ((buf = (char *)LEPT_CALLOC(bufsize, sizeof(char))) == NULL)
+        return ERROR_INT("calloc fail for buf", procName, 1);
+
+    boxGetGeometry(lpd->mediabox, NULL, NULL, &wpt, &hpt);
+    sa = sarrayCreate(lpd->n);
+    for (i = 0; i < lpd->n; i++) {
+        snprintf(buf, bufsize, "/Im%d %d 0 R   ", i + 1, 6 + i);
+        sarrayAddString(sa, buf, L_COPY);
+    }
+    if ((xstr = sarrayToString(sa, 0)) == NULL)
+        return ERROR_INT("xstr not found", procName, 1);
+    sarrayDestroy(&sa);
+
+    snprintf(buf, bufsize, "4 0 obj\n"
+                           "<<\n"
+                           "/Type /Page\n"
+                           "/Parent 3 0 R\n"
+                           "/MediaBox [%d %d %d %d]\n"
+                           "/Contents 5 0 R\n"
+                           "/Resources\n"
+                           "<<\n"
+                           "/XObject << %s >>\n"
+                           "/ProcSet [ /ImageB /ImageI /ImageC ]\n"
+                           ">>\n"
+                           ">>\n"
+                           "endobj\n",
+                           0, 0, wpt, hpt, xstr);
+
+    lpd->obj4 = stringNew(buf);
+    l_dnaAddNumber(lpd->objsize, strlen(lpd->obj4));
+    sarrayDestroy(&sa);
+    LEPT_FREE(buf);
+    LEPT_FREE(xstr);
+    return 0;
+}
+
+
+static l_int32
+generateContentStringPdf(L_PDF_DATA  *lpd)
+{
+char      *buf;
+char      *cstr;
+l_int32    i, bufsize;
+l_float32  xpt, ypt, wpt, hpt;
+SARRAY    *sa;
+
+    PROCNAME("generateContentStringPdf");
+
+    bufsize = 1000 + 200 * lpd->n;
+    if ((buf = (char *)LEPT_CALLOC(bufsize, sizeof(char))) == NULL)
+        return ERROR_INT("calloc fail for buf", procName, 1);
+
+    sa = sarrayCreate(lpd->n);
+    for (i = 0; i < lpd->n; i++) {
+        ptaGetPt(lpd->xy, i, &xpt, &ypt);
+        ptaGetPt(lpd->wh, i, &wpt, &hpt);
+        snprintf(buf, bufsize,
+                 "q %.4f %.4f %.4f %.4f %.4f %.4f cm /Im%d Do Q\n",
+                 wpt, 0.0, 0.0, hpt, xpt, ypt, i + 1);
+        sarrayAddString(sa, buf, L_COPY);
+    }
+    if ((cstr = sarrayToString(sa, 0)) == NULL)
+        return ERROR_INT("cstr not found", procName, 1);
+    sarrayDestroy(&sa);
+
+    snprintf(buf, bufsize, "5 0 obj\n"
+                           "<< /Length %d >>\n"
+                           "stream\n"
+                           "%s"
+                           "endstream\n"
+                           "endobj\n",
+                           (l_int32)strlen(cstr), cstr);
+
+    lpd->obj5 = stringNew(buf);
+    l_dnaAddNumber(lpd->objsize, strlen(lpd->obj5));
+    sarrayDestroy(&sa);
+    LEPT_FREE(buf);
+    LEPT_FREE(cstr);
+    return 0;
+}
+
+
+static l_int32
+generatePreXStringsPdf(L_PDF_DATA  *lpd)
+{
+char          buff[256];
+char          buf[L_BIGBUF];
+char         *cstr, *bstr, *fstr, *pstr, *xstr;
+l_int32       i, cmindex;
+L_COMP_DATA  *cid;
+SARRAY       *sa;
+
+    PROCNAME("generatePreXStringsPdf");
+
+    sa = lpd->saprex;
+    cmindex = 6 + lpd->n;  /* starting value */
+    for (i = 0; i < lpd->n; i++) {
+        pstr = cstr = NULL;
+        if ((cid = pdfdataGetCid(lpd, i)) == NULL)
+            return ERROR_INT("cid not found", procName, 1);
+
+        if (cid->type == L_G4_ENCODE) {
+            if (var_WRITE_G4_IMAGE_MASK) {
+                cstr = stringNew("/ImageMask true\n"
+                                 "/ColorSpace /DeviceGray");
+            } else {
+                cstr = stringNew("/ColorSpace /DeviceGray");
+            }
+            bstr = stringNew("/BitsPerComponent 1\n"
+                             "/Interpolate true");
+            snprintf(buff, sizeof(buff),
+                     "/Filter /CCITTFaxDecode\n"
+                     "/DecodeParms\n"
+                     "<<\n"
+                     "/K -1\n"
+                     "/Columns %d\n"
+                     ">>", cid->w);
+            fstr = stringNew(buff);
+        } else if (cid->type == L_JPEG_ENCODE) {
+            if (cid->spp == 1)
+                cstr = stringNew("/ColorSpace /DeviceGray");
+            else if (cid->spp == 3)
+                cstr = stringNew("/ColorSpace /DeviceRGB");
+            else
+                L_ERROR("in jpeg: spp != 1 && spp != 3\n", procName);
+            bstr = stringNew("/BitsPerComponent 8");
+            fstr = stringNew("/Filter /DCTDecode");
+        } else if (cid->type == L_JP2K_ENCODE) {
+            if (cid->spp == 1)
+                cstr = stringNew("/ColorSpace /DeviceGray");
+            else if (cid->spp == 3)
+                cstr = stringNew("/ColorSpace /DeviceRGB");
+            else
+                L_ERROR("in jp2k: spp != 1 && spp != 3\n", procName);
+            bstr = stringNew("/BitsPerComponent 8");
+            fstr = stringNew("/Filter /JPXDecode");
+        } else {  /* type == L_FLATE_ENCODE */
+            if (cid->ncolors > 0) {  /* cmapped */
+                snprintf(buff, sizeof(buff), "/ColorSpace %d 0 R", cmindex++);
+                cstr = stringNew(buff);
+            } else {
+                if (cid->spp == 1 && cid->bps == 1)
+                    cstr = stringNew("/ColorSpace /DeviceGray\n"
+                                     "/Decode [1 0]");
+                else if (cid->spp == 1)  /* 8 bpp */
+                    cstr = stringNew("/ColorSpace /DeviceGray");
+                else if (cid->spp == 3)
+                    cstr = stringNew("/ColorSpace /DeviceRGB");
+                else
+                    L_ERROR("unknown colorspace: spp = %d\n",
+                            procName, cid->spp);
+            }
+            snprintf(buff, sizeof(buff), "/BitsPerComponent %d", cid->bps);
+            bstr = stringNew(buff);
+            fstr = stringNew("/Filter /FlateDecode");
+            if (cid->predictor == TRUE) {
+                snprintf(buff, sizeof(buff),
+                         "/DecodeParms\n"
+                         "<<\n"
+                         "  /Columns %d\n"
+                         "  /Predictor 14\n"
+                         "  /Colors %d\n"
+                         "  /BitsPerComponent %d\n"
+                         ">>\n", cid->w, cid->spp, cid->bps);
+                pstr = stringNew(buff);
+            }
+        }
+        if (!pstr)  /* no decode parameters */
+            pstr = stringNew("");
+
+        snprintf(buf, sizeof(buf),
+                 "%d 0 obj\n"
+                 "<<\n"
+                 "/Length %lu\n"
+                 "/Subtype /Image\n"
+                 "%s\n"  /* colorspace */
+                 "/Width %d\n"
+                 "/Height %d\n"
+                 "%s\n"  /* bits/component */
+                 "%s\n"  /* filter */
+                 "%s"   /* decode parms; can be empty */
+                 ">>\n"
+                 "stream\n",
+                 6 + i, (unsigned long)cid->nbytescomp, cstr,
+                 cid->w, cid->h, bstr, fstr, pstr);
+        xstr = stringNew(buf);
+        sarrayAddString(sa, xstr, L_INSERT);
+        l_dnaAddNumber(lpd->objsize,
+                      strlen(xstr) + cid->nbytescomp + strlen(lpd->poststream));
+        LEPT_FREE(cstr);
+        LEPT_FREE(bstr);
+        LEPT_FREE(fstr);
+        LEPT_FREE(pstr);
+    }
+
+    return 0;
+}
+
+
+static l_int32
+generateColormapStringsPdf(L_PDF_DATA  *lpd)
+{
+char          buf[L_BIGBUF];
+char         *cmstr;
+l_int32       i, cmindex, ncmap;
+L_COMP_DATA  *cid;
+SARRAY       *sa;
+
+    PROCNAME("generateColormapStringsPdf");
+
+        /* In our canonical format, we have 5 objects, followed
+         * by n XObjects, followed by m colormaps, so the index of
+         * the first colormap object is 6 + n. */
+    sa = lpd->sacmap;
+    cmindex = 6 + lpd->n;  /* starting value */
+    ncmap = 0;
+    for (i = 0; i < lpd->n; i++) {
+        if ((cid = pdfdataGetCid(lpd, i)) == NULL)
+            return ERROR_INT("cid not found", procName, 1);
+        if (cid->ncolors == 0) continue;
+
+        ncmap++;
+        snprintf(buf, sizeof(buf), "%d 0 obj\n"
+                                   "[ /Indexed /DeviceRGB\n"
+                                   "%d\n"
+                                   "%s\n"
+                                   "]\n"
+                                   "endobj\n",
+                                   cmindex, cid->ncolors - 1, cid->cmapdatahex);
+        cmindex++;
+        cmstr = stringNew(buf);
+        l_dnaAddNumber(lpd->objsize, strlen(cmstr));
+        sarrayAddString(sa, cmstr, L_INSERT);
+    }
+
+    lpd->ncmap = ncmap;
+    return 0;
+}
+
+
+static void
+generateTrailerPdf(L_PDF_DATA  *lpd)
+{
+l_int32  i, n, size, linestart;
+L_DNA   *daloc, *dasize;
+
+        /* Let nobj be the number of numbered objects.  These numbered
+         * objects are indexed by their pdf number in arrays naloc[]
+         * and nasize[].  The 0th object is the 9 byte header.  Then
+         * the number of objects in nasize, which includes the header,
+         * is n = nobj + 1.  The array naloc[] has n + 1 elements,
+         * because it includes as the last element the starting
+         * location of xref.  The indexing of these objects, their
+         * starting locations and sizes are:
+         *
+         *     Object number         Starting location         Size
+         *     -------------         -----------------     --------------
+         *          0                   daloc[0] = 0       dasize[0] = 9
+         *          1                   daloc[1] = 9       dasize[1] = 49
+         *          n                   daloc[n]           dasize[n]
+         *          xref                daloc[n+1]
+         *
+         * We first generate daloc.
+         */
+    dasize = lpd->objsize;
+    daloc = lpd->objloc;
+    linestart = 0;
+    l_dnaAddNumber(daloc, linestart);  /* header */
+    n = l_dnaGetCount(dasize);
+    for (i = 0; i < n; i++) {
+        l_dnaGetIValue(dasize, i, &size);
+        linestart += size;
+        l_dnaAddNumber(daloc, linestart);
+    }
+    l_dnaGetIValue(daloc, n, &lpd->xrefloc);  /* save it */
+
+        /* Now make the actual trailer string */
+    lpd->trailer = makeTrailerStringPdf(daloc);
+}
+
+
+static char *
+makeTrailerStringPdf(L_DNA  *daloc)
+{
+char    *outstr;
+char     buf[L_BIGBUF];
+l_int32  i, n, linestart, xrefloc;
+SARRAY  *sa;
+
+    PROCNAME("makeTrailerStringPdf");
+
+    if (!daloc)
+        return (char *)ERROR_PTR("daloc not defined", procName, NULL);
+    n = l_dnaGetCount(daloc) - 1;  /* numbered objects + 1 (yes, +1) */
+
+    sa = sarrayCreate(0);
+    snprintf(buf, sizeof(buf), "xref\n"
+                               "0 %d\n"
+                               "0000000000 65535 f \n", n);
+    sarrayAddString(sa, (char *)buf, L_COPY);
+    for (i = 1; i < n; i++) {
+        l_dnaGetIValue(daloc, i, &linestart);
+        snprintf(buf, sizeof(buf), "%010d 00000 n \n", linestart);
+        sarrayAddString(sa, (char *)buf, L_COPY);
+    }
+
+    l_dnaGetIValue(daloc, n, &xrefloc);
+    snprintf(buf, sizeof(buf), "trailer\n"
+                               "<<\n"
+                               "/Size %d\n"
+                               "/Root 1 0 R\n"
+                               "/Info 2 0 R\n"
+                               ">>\n"
+                               "startxref\n"
+                               "%d\n"
+                               "%%%%EOF\n", n, xrefloc);
+    sarrayAddString(sa, (char *)buf, L_COPY);
+    outstr = sarrayToString(sa, 0);
+    sarrayDestroy(&sa);
+    return outstr;
+}
+
+
+/*!
+ *  generateOutputDataPdf()
+ *
+ *      Input:  &data (<return> pdf data array)
+ *              &nbytes (<return> size of pdf data array)
+ *              lpd (input data used to make pdf)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Only called from l_generatePdf().  On error, no data is returned.
+ */
+static l_int32
+generateOutputDataPdf(l_uint8    **pdata,
+                      size_t      *pnbytes,
+                      L_PDF_DATA  *lpd)
+{
+char         *str;
+l_uint8      *data;
+l_int32       nimages, i, len;
+l_int32      *sizes, *locs;
+size_t        nbytes;
+L_COMP_DATA  *cid;
+
+    PROCNAME("generateOutputDataPdf");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    nbytes = lpd->xrefloc + strlen(lpd->trailer);
+    *pnbytes = nbytes;
+    if ((data = (l_uint8 *)LEPT_CALLOC(nbytes, sizeof(l_uint8))) == NULL)
+        return ERROR_INT("calloc fail for data", procName, 1);
+    *pdata = data;
+
+    sizes = l_dnaGetIArray(lpd->objsize);
+    locs = l_dnaGetIArray(lpd->objloc);
+    memcpy((char *)data, lpd->id, sizes[0]);
+    memcpy((char *)(data + locs[1]), lpd->obj1, sizes[1]);
+    memcpy((char *)(data + locs[2]), lpd->obj2, sizes[2]);
+    memcpy((char *)(data + locs[3]), lpd->obj3, sizes[3]);
+    memcpy((char *)(data + locs[4]), lpd->obj4, sizes[4]);
+    memcpy((char *)(data + locs[5]), lpd->obj5, sizes[5]);
+
+        /* Each image has 3 parts: variable preamble, the compressed
+         * data stream, and the fixed poststream. */
+    nimages = lpd->n;
+    for (i = 0; i < nimages; i++) {
+        if ((cid = pdfdataGetCid(lpd, i)) == NULL)  /* this should not happen */
+            return ERROR_INT("cid not found", procName, 1);
+        str = sarrayGetString(lpd->saprex, i, L_NOCOPY);
+        len = strlen(str);
+        memcpy((char *)(data + locs[6 + i]), str, len);
+        memcpy((char *)(data + locs[6 + i] + len),
+               (char *)cid->datacomp, cid->nbytescomp);
+        memcpy((char *)(data + locs[6 + i] + len + cid->nbytescomp),
+               lpd->poststream, strlen(lpd->poststream));
+    }
+
+        /* Each colormap is simply a stored string */
+    for (i = 0; i < lpd->ncmap; i++) {
+        str = sarrayGetString(lpd->sacmap, i, L_NOCOPY);
+        memcpy((char *)(data + locs[6 + nimages + i]), str, strlen(str));
+    }
+
+        /* And finally the trailer */
+    memcpy((char *)(data + lpd->xrefloc), lpd->trailer, strlen(lpd->trailer));
+    LEPT_FREE(sizes);
+    LEPT_FREE(locs);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *          Helper functions for generating multipage pdf output       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  parseTrailerPdf()
+ *
+ *  Input:  bas (lba of a pdf file)
+ *          da (<return> byte locations of the beginning of each object)
+ *  Return: 0 if OK, 1 on error
+ */
+static l_int32
+parseTrailerPdf(L_BYTEA  *bas,
+                L_DNA   **pda)
+{
+char     *str;
+l_uint8   nl = '\n';
+l_uint8  *data;
+l_int32   i, j, start, startloc, xrefloc, found, loc, nobj, objno, trailer_ok;
+size_t    size;
+L_DNA    *da, *daobj, *daxref;
+SARRAY   *sa;
+
+    PROCNAME("parseTrailerPdf");
+
+    if (!pda)
+        return ERROR_INT("&da not defined", procName, 1);
+    *pda = NULL;
+    if (!bas)
+        return ERROR_INT("bas not defined", procName, 1);
+    data = l_byteaGetData(bas, &size);
+    if (strncmp((char *)data, "%PDF-1.", 7) != 0)
+        return ERROR_INT("PDF header signature not found", procName, 1);
+
+        /* Search for "startxref" starting 50 bytes from the EOF */
+    start = 0;
+    if (size > 50)
+        start = size - 50;
+    arrayFindSequence(data + start, size - start,
+                      (l_uint8 *)"startxref\n", 10, &loc, &found);
+    if (!found)
+        return ERROR_INT("startxref not found!", procName, 1);
+    if (sscanf((char *)(data + start + loc + 10), "%d\n", &xrefloc) != 1)
+        return ERROR_INT("xrefloc not found!", procName, 1);
+    if (xrefloc < 0 || xrefloc >= size)
+        return ERROR_INT("invalid xrefloc!", procName, 1);
+    sa = sarrayCreateLinesFromString((char *)(data + xrefloc), 0);
+    str = sarrayGetString(sa, 1, L_NOCOPY);
+    if ((sscanf(str, "0 %d", &nobj)) != 1)
+        return ERROR_INT("nobj not found", procName, 1);
+
+        /* Get starting locations.  The numa index is the
+         * object number.  loc[0] is the ID; loc[nobj + 1] is xrefloc.  */
+    da = l_dnaCreate(nobj + 1);
+    *pda = da;
+    for (i = 0; i < nobj; i++) {
+        str = sarrayGetString(sa, i + 2, L_NOCOPY);
+        sscanf(str, "%d", &startloc);
+        l_dnaAddNumber(da, startloc);
+    }
+    l_dnaAddNumber(da, xrefloc);
+
+#if  DEBUG_MULTIPAGE
+    fprintf(stderr, "************** Trailer string ************\n");
+    fprintf(stderr, "xrefloc = %d", xrefloc);
+    sarrayWriteStream(stderr, sa);
+
+    fprintf(stderr, "************** Object locations ************");
+    l_dnaWriteStream(stderr, da);
+#endif  /* DEBUG_MULTIPAGE */
+    sarrayDestroy(&sa);
+
+        /* Verify correct parsing */
+    trailer_ok = TRUE;
+    for (i = 1; i < nobj; i++) {
+        l_dnaGetIValue(da, i, &startloc);
+        if ((sscanf((char *)(data + startloc), "%d 0 obj", &objno)) != 1) {
+            L_ERROR("bad trailer for object %d\n", procName, i);
+            trailer_ok = FALSE;
+            break;
+        }
+    }
+
+        /* If the trailer is broken, reconstruct the correct obj locations */
+    if (!trailer_ok) {
+        L_INFO("rebuilding pdf trailer\n", procName);
+        l_dnaEmpty(da);
+        l_dnaAddNumber(da, 0);
+        l_byteaFindEachSequence(bas, (l_uint8 *)" 0 obj\n", 7, &daobj);
+        nobj = l_dnaGetCount(daobj);
+        for (i = 0; i < nobj; i++) {
+            l_dnaGetIValue(daobj, i, &loc);
+            for (j = loc - 1; j > 0; j--) {
+                if (data[j] == nl)
+                    break;
+            }
+            l_dnaAddNumber(da, j + 1);
+        }
+        l_byteaFindEachSequence(bas, (l_uint8 *)"xref", 4, &daxref);
+        l_dnaGetIValue(daxref, 0, &loc);
+        l_dnaAddNumber(da, loc);
+        l_dnaDestroy(&daobj);
+        l_dnaDestroy(&daxref);
+    }
+
+    return 0;
+}
+
+
+static char *
+generatePagesObjStringPdf(NUMA  *napage)
+{
+char    *str;
+char    *buf;
+l_int32  i, n, index, bufsize;
+SARRAY  *sa;
+
+    PROCNAME("generatePagesObjStringPdf");
+
+    if (!napage)
+        return (char *)ERROR_PTR("napage not defined", procName, NULL);
+
+    n = numaGetCount(napage);
+    bufsize = 100 + 16 * n;  /* large enough to hold the output string */
+    buf = (char *)LEPT_CALLOC(bufsize, sizeof(char));
+    sa = sarrayCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(napage, i, &index);
+        snprintf(buf, bufsize, " %d 0 R ", index);
+        sarrayAddString(sa, buf, L_COPY);
+    }
+
+    str = sarrayToString(sa, 0);
+    snprintf(buf, bufsize - 1, "3 0 obj\n"
+                               "<<\n"
+                               "/Type /Pages\n"
+                               "/Kids [%s]\n"
+                               "/Count %d\n"
+                               ">>\n", str, n);
+    sarrayDestroy(&sa);
+    LEPT_FREE(str);
+    return buf;
+}
+
+
+/*!
+ *  substituteObjectNumbers()
+ *
+ *  Input:  bas (lba of a pdf object)
+ *          na_objs (object number mapping array)
+ *  Return: bad (lba of rewritten pdf for the object)
+ *
+ *  Notes:
+ *      (1) Interpret the first set of bytes as the object number,
+ *          map to the new number, and write it out.
+ *      (2) Find all occurrences of this 4-byte sequence: " 0 R"
+ *      (3) Find the location and value of the integer preceding this,
+ *          and map it to the new value.
+ *      (4) Rewrite the object with new object numbers.
+ */
+static L_BYTEA *
+substituteObjectNumbers(L_BYTEA  *bas,
+                        NUMA     *na_objs)
+{
+l_uint8   space = ' ';
+l_uint8  *datas;
+l_uint8   buf[32];  /* only needs to hold one integer in ascii format */
+l_int32   start, nrepl, i, j, objin, objout, found;
+l_int32  *objs, *matches;
+size_t    size;
+L_BYTEA  *bad;
+L_DNA    *da_match;
+
+    datas = l_byteaGetData(bas, &size);
+    bad = l_byteaCreate(100);
+    objs = numaGetIArray(na_objs);  /* object number mapper */
+
+        /* Substitute the object number on the first line */
+    sscanf((char *)datas, "%d", &objin);
+    objout = objs[objin];
+    snprintf((char *)buf, 32, "%d", objout);
+    l_byteaAppendString(bad, (char *)buf);
+
+        /* Find the set of matching locations for object references */
+    arrayFindSequence(datas, size, &space, 1, &start, &found);
+    da_match = arrayFindEachSequence(datas, size, (l_uint8 *)" 0 R", 4);
+    if (!da_match) {
+        l_byteaAppendData(bad, datas + start, size - start);
+        LEPT_FREE(objs);
+        return bad;
+    }
+
+        /* Substitute all the object reference numbers */
+    nrepl = l_dnaGetCount(da_match);
+    matches = l_dnaGetIArray(da_match);
+    for (i = 0; i < nrepl; i++) {
+            /* Find the first space before the object number */
+        for (j = matches[i] - 1; j > 0; j--) {
+            if (datas[j] == space)
+                break;
+        }
+            /* Copy bytes from 'start' up to the object number */
+        l_byteaAppendData(bad, datas + start, j - start + 1);
+        sscanf((char *)(datas + j + 1), "%d", &objin);
+        objout = objs[objin];
+        snprintf((char *)buf, 32, "%d", objout);
+        l_byteaAppendString(bad, (char *)buf);
+        start = matches[i];
+    }
+    l_byteaAppendData(bad, datas + start, size - start);
+
+    LEPT_FREE(objs);
+    LEPT_FREE(matches);
+    l_dnaDestroy(&da_match);
+    return bad;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                     Create/destroy/access pdf data                  *
+ *---------------------------------------------------------------------*/
+static L_PDF_DATA *
+pdfdataCreate(const char  *title)
+{
+L_PDF_DATA *lpd;
+
+    lpd = (L_PDF_DATA *)LEPT_CALLOC(1, sizeof(L_PDF_DATA));
+    if (title) lpd->title = stringNew(title);
+    lpd->cida = ptraCreate(10);
+    lpd->xy = ptaCreate(10);
+    lpd->wh = ptaCreate(10);
+    lpd->saprex = sarrayCreate(10);
+    lpd->sacmap = sarrayCreate(10);
+    lpd->objsize = l_dnaCreate(20);
+    lpd->objloc = l_dnaCreate(20);
+    return lpd;
+}
+
+static void
+pdfdataDestroy(L_PDF_DATA  **plpd)
+{
+l_int32       i;
+L_COMP_DATA  *cid;
+L_PDF_DATA   *lpd;
+
+    PROCNAME("pdfdataDestroy");
+
+    if (plpd== NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+    if ((lpd = *plpd) == NULL)
+        return;
+
+    if (lpd->title) LEPT_FREE(lpd->title);
+    for (i = 0; i < lpd->n; i++) {
+        cid = (L_COMP_DATA *)ptraRemove(lpd->cida, i, L_NO_COMPACTION);
+        l_CIDataDestroy(&cid);
+    }
+
+    ptraDestroy(&lpd->cida, 0, 0);
+    if (lpd->id) LEPT_FREE(lpd->id);
+    if (lpd->obj1) LEPT_FREE(lpd->obj1);
+    if (lpd->obj2) LEPT_FREE(lpd->obj2);
+    if (lpd->obj3) LEPT_FREE(lpd->obj3);
+    if (lpd->obj4) LEPT_FREE(lpd->obj4);
+    if (lpd->obj5) LEPT_FREE(lpd->obj5);
+    if (lpd->poststream) LEPT_FREE(lpd->poststream);
+    if (lpd->trailer) LEPT_FREE(lpd->trailer);
+    if (lpd->xy) ptaDestroy(&lpd->xy);
+    if (lpd->wh) ptaDestroy(&lpd->wh);
+    if (lpd->mediabox) boxDestroy(&lpd->mediabox);
+    if (lpd->saprex) sarrayDestroy(&lpd->saprex);
+    if (lpd->sacmap) sarrayDestroy(&lpd->sacmap);
+    if (lpd->objsize) l_dnaDestroy(&lpd->objsize);
+    if (lpd->objloc) l_dnaDestroy(&lpd->objloc);
+    LEPT_FREE(lpd);
+    *plpd = NULL;
+    return;
+}
+
+
+static L_COMP_DATA *
+pdfdataGetCid(L_PDF_DATA  *lpd,
+              l_int32      index)
+{
+    PROCNAME("pdfdataGetCid");
+
+    if (!lpd)
+        return (L_COMP_DATA *)ERROR_PTR("lpd not defined", procName, NULL);
+    if (index < 0 || index >= lpd->n)
+        return (L_COMP_DATA *)ERROR_PTR("invalid image index", procName, NULL);
+
+    return (L_COMP_DATA *)ptraGetPtrToItem(lpd->cida, index);
+}
+
+
+/*---------------------------------------------------------------------*
+ *                       Set flags for special modes                   *
+ *---------------------------------------------------------------------*/
+/*!
+ *  l_pdfSetG4ImageMask()
+ *
+ *      Input:  flag (1 for writing g4 data as fg only through a mask;
+ *                    0 for writing fg and bg)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) The default is for writing only the fg (through the mask).
+ *          That way when you write a 1 bpp image, the bg is transparent,
+ *          so any previously written image remains visible behind it.
+ */
+void
+l_pdfSetG4ImageMask(l_int32  flag)
+{
+    var_WRITE_G4_IMAGE_MASK = flag;
+}
+
+
+/*!
+ *  l_pdfSetDateAndVersion()
+ *
+ *      Input:  flag (1 for writing date/time and leptonica version;
+ *                    0 for omitting this from the metadata)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) The default is for writing this data.  For regression tests
+ *          that compare output against golden files, it is useful to omit.
+ */
+void
+l_pdfSetDateAndVersion(l_int32  flag)
+{
+    var_WRITE_DATE_AND_VERSION = flag;
+}
+
+/* --------------------------------------------*/
+#endif  /* USE_PDFIO */
+/* --------------------------------------------*/
diff --git a/src/pdfio2stub.c b/src/pdfio2stub.c
new file mode 100644 (file)
index 0000000..3722de8
--- /dev/null
@@ -0,0 +1,158 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pdfio2stub.c
+ *
+ *     Stubs for pdfio2.c functions
+ */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !USE_PDFIO   /* defined in environ.h */
+/* --------------------------------------------*/
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixConvertToPdfData(PIX *pix, l_int32 type, l_int32 quality,
+                            l_uint8 **pdata, size_t *pnbytes,
+                            l_int32 x, l_int32 y, l_int32 res,
+                            const char *title,
+                            L_PDF_DATA **plpd, l_int32 position)
+{
+    return ERROR_INT("function not present", "pixConvertToPdfData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 ptraConcatenatePdfToData(L_PTRA *pa_data, SARRAY *sa,
+                                 l_uint8 **pdata, size_t *pnbytes)
+{
+    return ERROR_INT("function not present", "ptraConcatenatePdfToData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 l_generateCIDataForPdf(const char *fname, PIX *pix, l_int32 quality,
+                               L_COMP_DATA **pcid)
+{
+    return ERROR_INT("function not present", "l_generateCIDataForPdf", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+L_COMP_DATA * l_generateFlateDataPdf(const char *fname, PIX *pix)
+{
+    return (L_COMP_DATA *)ERROR_PTR("function not present",
+                                    "l_generateFlateDataPdf", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+L_COMP_DATA * l_generateJpegData(const char *fname, l_int32 ascii85flag)
+{
+    return (L_COMP_DATA *)ERROR_PTR("function not present",
+                                    "l_generateJpegData", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+L_COMP_DATA * l_generateJp2kData(const char *fname)
+{
+    return (L_COMP_DATA *)ERROR_PTR("function not present",
+                                    "l_generateJp2kData", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 l_generateCIData(const char *fname, l_int32 type, l_int32 quality,
+                         l_int32 ascii85, L_COMP_DATA **pcid)
+{
+    return ERROR_INT("function not present", "l_generateCIData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixGenerateCIData(PIX *pixs, l_int32 type, l_int32 quality,
+                          l_int32 ascii85, L_COMP_DATA **pcid)
+{
+    return ERROR_INT("function not present", "pixGenerateCIData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+L_COMP_DATA * l_generateFlateData(const char *fname, l_int32 ascii85flag)
+{
+    return (L_COMP_DATA *)ERROR_PTR("function not present",
+                                    "l_generateFlateData", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+L_COMP_DATA * l_generateG4Data(const char *fname, l_int32 ascii85flag)
+{
+    return (L_COMP_DATA *)ERROR_PTR("function not present",
+                                    "l_generateG4Data", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 cidConvertToPdfData(L_COMP_DATA *cid, const char *title,
+                            l_uint8 **pdata, size_t *pnbytes)
+{
+    return ERROR_INT("function not present", "cidConvertToPdfData", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+void l_CIDataDestroy(L_COMP_DATA  **pcid)
+{
+    L_ERROR("function not present\n", "l_CIDataDestroy");
+    return;
+}
+
+/* ----------------------------------------------------------------------*/
+
+void l_pdfSetG4ImageMask(l_int32 flag)
+{
+    L_ERROR("function not present\n", "l_pdfSetG4ImageMask");
+    return;
+}
+
+/* ----------------------------------------------------------------------*/
+
+void l_pdfSetDateAndVersion(l_int32 flag)
+{
+    L_ERROR("function not present\n", "l_pdfSetDateAndVersion");
+    return;
+}
+
+/* ----------------------------------------------------------------------*/
+
+/* --------------------------------------------*/
+#endif  /* !USE_PDFIO */
+/* --------------------------------------------*/
diff --git a/src/pix.h b/src/pix.h
new file mode 100644 (file)
index 0000000..bcc0dc6
--- /dev/null
+++ b/src/pix.h
@@ -0,0 +1,1131 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_PIX_H
+#define  LEPTONICA_PIX_H
+
+/*
+ *   pix.h
+ *
+ *   Valid image types in leptonica:
+ *       Pix: 1 bpp, with and without colormap
+ *       Pix: 2 bpp, with and without colormap
+ *       Pix: 4 bpp, with and without colormap
+ *       Pix: 8 bpp, with and without colormap
+ *       Pix: 16 bpp (1 spp)
+ *       Pix: 32 bpp (rgb, 3 spp)
+ *       Pix: 32 bpp (rgba, 4 spp)
+ *       FPix: 32 bpp float
+ *       DPix: 64 bpp double
+ *       Notes:
+ *          (1) The only valid Pix image type with alpha is rgba.
+ *              In particular, the alpha component is not used in
+ *              cmapped images.
+ *          (2) PixComp can hold any Pix with IFF_PNG encoding.
+ *
+ *   This file defines most of the image-related structs used in leptonica:
+ *       struct Pix
+ *       struct PixColormap
+ *       struct RGBA_Quad
+ *       struct Pixa
+ *       struct Pixaa
+ *       struct Box
+ *       struct Boxa
+ *       struct Boxaa
+ *       struct Pta
+ *       struct Ptaa
+ *       struct Pixacc
+ *       struct PixTiling
+ *       struct FPix
+ *       struct FPixa
+ *       struct DPix
+ *       struct PixComp
+ *       struct PixaComp
+ *
+ *   This file has definitions for:
+ *       Colors for RGB
+ *       Perceptual color weights
+ *       Colormap conversion flags
+ *       Rasterop bit flags
+ *       Structure access flags (for insert, copy, clone, copy-clone)
+ *       Sorting flags (by type and direction)
+ *       Blending flags
+ *       Graphics pixel setting flags
+ *       Size filtering flags
+ *       Color component selection flags
+ *       16-bit conversion flags
+ *       Rotation and shear flags
+ *       Affine transform order flags
+ *       Grayscale filling flags
+ *       Flags for setting to white or black
+ *       Flags for getting white or black pixel value
+ *       Flags for 8 and 16 bit pixel sums
+ *       Dithering flags
+ *       Distance flags
+ *       Statistical measures
+ *       Set selection flags
+ *       Text orientation flags
+ *       Edge orientation flags
+ *       Line orientation flags
+ *       Scan direction flags
+ *       Box size adjustment flags
+ *       Flags for selecting box boundaries from two choices
+ *       Handling overlapping bounding boxes in boxa
+ *       Flags for replacing invalid boxes
+ *       Horizontal warp
+ *       Pixel selection for resampling
+ *       Thinning flags
+ *       Runlength flags
+ *       Edge filter flags
+ *       Subpixel color component ordering in LCD display
+ *       HSV histogram flags
+ *       Region flags (inclusion, exclusion)
+ *       Flags for adding text to a pix
+ *       Flags for plotting on a pix
+ *       Flags for selecting display program
+ *       Flags in the 'special' pix field for non-default operations
+ *       Handling negative values in conversion to unsigned int
+ *       Relative to zero flags
+ *       Flags for adding or removing traling slash from string                *
+ */
+
+
+/*-------------------------------------------------------------------------*
+ *                              Basic Pix                                  *
+ *-------------------------------------------------------------------------*/
+    /* The 'special' field is by default 0, but it can hold integers
+     * that direct non-default actions, e.g., in png and jpeg I/O. */
+struct Pix
+{
+    l_uint32             w;           /* width in pixels                   */
+    l_uint32             h;           /* height in pixels                  */
+    l_uint32             d;           /* depth in bits (bpp)               */
+    l_uint32             spp;         /* number of samples per pixel       */
+    l_uint32             wpl;         /* 32-bit words/line                 */
+    l_uint32             refcount;    /* reference count (1 if no clones)  */
+    l_int32              xres;        /* image res (ppi) in x direction    */
+                                      /* (use 0 if unknown)                */
+    l_int32              yres;        /* image res (ppi) in y direction    */
+                                      /* (use 0 if unknown)                */
+    l_int32              informat;    /* input file format, IFF_*          */
+    l_int32              special;     /* special instructions for I/O, etc */
+    char                *text;        /* text string associated with pix   */
+    struct PixColormap  *colormap;    /* colormap (may be null)            */
+    l_uint32            *data;        /* the image data                    */
+};
+typedef struct Pix PIX;
+
+
+struct PixColormap
+{
+    void            *array;     /* colormap table (array of RGBA_QUAD)     */
+    l_int32          depth;     /* of pix (1, 2, 4 or 8 bpp)               */
+    l_int32          nalloc;    /* number of color entries allocated       */
+    l_int32          n;         /* number of color entries used            */
+};
+typedef struct PixColormap  PIXCMAP;
+
+
+    /* Colormap table entry (after the BMP version).
+     * Note that the BMP format stores the colormap table exactly
+     * as it appears here, with color samples being stored sequentially,
+     * in the order (b,g,r,a). */
+struct RGBA_Quad
+{
+    l_uint8     blue;
+    l_uint8     green;
+    l_uint8     red;
+    l_uint8     alpha;
+};
+typedef struct RGBA_Quad  RGBA_QUAD;
+
+
+
+/*-------------------------------------------------------------------------*
+ *                             Colors for 32 bpp                           *
+ *-------------------------------------------------------------------------*/
+/*  Notes:
+ *      (1) These are the byte indices for colors in 32 bpp images.
+ *          They are used through the GET/SET_DATA_BYTE accessors.
+ *          The 4th byte, typically known as the "alpha channel" and used
+ *          for blending, is used to a small extent in leptonica.
+ *      (2) Do not change these values!  If you redefine them, functions
+ *          that have the shifts hardcoded for efficiency and conciseness
+ *          (instead of using the constants below) will break.  These
+ *          functions are labelled with "***"  next to their names at
+ *          the top of the files in which they are defined.
+ *      (3) The shifts to extract the red, green, blue and alpha components
+ *          from a 32 bit pixel are defined here.
+ */
+enum {
+    COLOR_RED = 0,
+    COLOR_GREEN = 1,
+    COLOR_BLUE = 2,
+    L_ALPHA_CHANNEL = 3
+};
+
+static const l_int32  L_RED_SHIFT =
+       8 * (sizeof(l_uint32) - 1 - COLOR_RED);           /* 24 */
+static const l_int32  L_GREEN_SHIFT =
+       8 * (sizeof(l_uint32) - 1 - COLOR_GREEN);         /* 16 */
+static const l_int32  L_BLUE_SHIFT =
+       8 * (sizeof(l_uint32) - 1 - COLOR_BLUE);          /*  8 */
+static const l_int32  L_ALPHA_SHIFT =
+       8 * (sizeof(l_uint32) - 1 - L_ALPHA_CHANNEL);     /*  0 */
+
+
+/*-------------------------------------------------------------------------*
+ *                       Perceptual color weights                          *
+ *-------------------------------------------------------------------------*/
+/*  Notes:
+ *      (1) These numbers are ad-hoc, but they do add up to 1.
+ *          Unlike, for example, the weighting factor for conversion
+ *          of RGB to luminance, or more specifically to Y in the
+ *          YUV colorspace.  Those numbers come from the
+ *          International Telecommunications Union, via ITU-R.
+ */
+static const l_float32  L_RED_WEIGHT =   0.3;
+static const l_float32  L_GREEN_WEIGHT = 0.5;
+static const l_float32  L_BLUE_WEIGHT =  0.2;
+
+
+/*-------------------------------------------------------------------------*
+ *                        Flags for colormap conversion                    *
+ *-------------------------------------------------------------------------*/
+enum {
+    REMOVE_CMAP_TO_BINARY = 0,
+    REMOVE_CMAP_TO_GRAYSCALE = 1,
+    REMOVE_CMAP_TO_FULL_COLOR = 2,
+    REMOVE_CMAP_WITH_ALPHA = 3,
+    REMOVE_CMAP_BASED_ON_SRC = 4
+};
+
+
+/*-------------------------------------------------------------------------*
+ *
+ * The following operation bit flags have been modified from
+ * Sun's pixrect.h.
+ *
+ * The 'op' in 'rasterop' is represented by an integer
+ * composed with Boolean functions using the set of five integers
+ * given below.  The integers, and the op codes resulting from
+ * boolean expressions on them, need only be in the range from 0 to 15.
+ * The function is applied on a per-pixel basis.
+ *
+ * Examples: the op code representing ORing the src and dest
+ * is computed using the bit OR, as PIX_SRC | PIX_DST;  the op
+ * code representing XORing src and dest is found from
+ * PIX_SRC ^ PIX_DST;  the op code representing ANDing src and dest
+ * is found from PIX_SRC & PIX_DST.  Note that
+ * PIX_NOT(PIX_CLR) = PIX_SET, and v.v., as they must be.
+ *
+ * We use the following set of definitions:
+ *
+ *      #define   PIX_SRC      0xc
+ *      #define   PIX_DST      0xa
+ *      #define   PIX_NOT(op)  (op) ^ 0xf
+ *      #define   PIX_CLR      0x0
+ *      #define   PIX_SET      0xf
+ *
+ * These definitions differ from Sun's, in that Sun left-shifted
+ * each value by 1 pixel, and used the least significant bit as a
+ * flag for the "pseudo-operation" of clipping.  We don't need
+ * this bit, because it is both efficient and safe ALWAYS to clip
+ * the rectangles to the src and dest images, which is what we do.
+ * See the notes in rop.h on the general choice of these bit flags.
+ *
+ * [If for some reason you need compatibility with Sun's xview package,
+ * you can adopt the original Sun definitions to avoid redefinition conflicts:
+ *
+ *      #define   PIX_SRC      (0xc << 1)
+ *      #define   PIX_DST      (0xa << 1)
+ *      #define   PIX_NOT(op)  ((op) ^ 0x1e)
+ *      #define   PIX_CLR      (0x0 << 1)
+ *      #define   PIX_SET      (0xf << 1)
+ * ]
+ *
+ * We have, for reference, the following 16 unique op flags:
+ *
+ *      PIX_CLR                           0000             0x0
+ *      PIX_SET                           1111             0xf
+ *      PIX_SRC                           1100             0xc
+ *      PIX_DST                           1010             0xa
+ *      PIX_NOT(PIX_SRC)                  0011             0x3
+ *      PIX_NOT(PIX_DST)                  0101             0x5
+ *      PIX_SRC | PIX_DST                 1110             0xe
+ *      PIX_SRC & PIX_DST                 1000             0x8
+ *      PIX_SRC ^ PIX_DST                 0110             0x6
+ *      PIX_NOT(PIX_SRC) | PIX_DST        1011             0xb
+ *      PIX_NOT(PIX_SRC) & PIX_DST        0010             0x2
+ *      PIX_SRC | PIX_NOT(PIX_DST)        1101             0xd
+ *      PIX_SRC & PIX_NOT(PIX_DST)        0100             0x4
+ *      PIX_NOT(PIX_SRC | PIX_DST)        0001             0x1
+ *      PIX_NOT(PIX_SRC & PIX_DST)        0111             0x7
+ *      PIX_NOT(PIX_SRC ^ PIX_DST)        1001             0x9
+ *
+ *-------------------------------------------------------------------------*/
+#define   PIX_SRC      (0xc)
+#define   PIX_DST      (0xa)
+#define   PIX_NOT(op)  ((op) ^ 0x0f)
+#define   PIX_CLR      (0x0)
+#define   PIX_SET      (0xf)
+
+#define   PIX_PAINT    (PIX_SRC | PIX_DST)
+#define   PIX_MASK     (PIX_SRC & PIX_DST)
+#define   PIX_SUBTRACT (PIX_DST & PIX_NOT(PIX_SRC))
+#define   PIX_XOR      (PIX_SRC ^ PIX_DST)
+
+
+/*-------------------------------------------------------------------------*
+ *
+ *   Important Notes:
+ *
+ *       (1) The image data is stored in a single contiguous
+ *           array of l_uint32, into which the pixels are packed.
+ *           By "packed" we mean that there are no unused bits
+ *           between pixels, except for end-of-line padding to
+ *           satisfy item (2) below.
+ *
+ *       (2) Every image raster line begins on a 32-bit word
+ *           boundary within this array.
+ *
+ *       (3) Pix image data is stored in 32-bit units, with the
+ *           pixels ordered from left to right in the image being
+ *           stored in order from the MSB to LSB within the word,
+ *           for both big-endian and little-endian machines.
+ *           This is the natural ordering for big-endian machines,
+ *           as successive bytes are stored and fetched progressively
+ *           to the right.  However, for little-endians, when storing
+ *           we re-order the bytes from this byte stream order, and
+ *           reshuffle again for byte access on 32-bit entities.
+ *           So if the bytes come in sequence from left to right, we
+ *           store them on little-endians in byte order:
+ *                3 2 1 0 7 6 5 4 ...
+ *           This MSB to LSB ordering allows left and right shift
+ *           operations on 32 bit words to move the pixels properly.
+ *
+ *       (4) We use 32 bit pixels for both RGB and RGBA color images.
+ *           The A (alpha) byte is ignored in most leptonica functions
+ *           operating on color images.  Within each 4 byte pixel, the
+ *           colors are ordered from MSB to LSB, as follows:
+ *
+ *                |  MSB  |  2nd MSB  |  3rd MSB  |  LSB  |
+ *                   red      green       blue      alpha
+ *                    0         1           2         3   (big-endian)
+ *                    3         2           1         0   (little-endian)
+ *
+ *           Because we use MSB to LSB ordering within the 32-bit word,
+ *           the individual 8-bit samples can be accessed with
+ *           GET_DATA_BYTE and SET_DATA_BYTE macros, using the
+ *           (implicitly big-ending) ordering
+ *                 red:    byte 0  (MSB)
+ *                 green:  byte 1  (2nd MSB)
+ *                 blue:   byte 2  (3rd MSB)
+ *                 alpha:  byte 3  (LSB)
+ *
+ *           The specific color assignment is made in this file,
+ *           through the definitions of COLOR_RED, etc.  Then the R, G
+ *           B and A sample values can be retrieved using
+ *                 redval = GET_DATA_BYTE(&pixel, COLOR_RED);
+ *                 greenval = GET_DATA_BYTE(&pixel, COLOR_GREEN);
+ *                 blueval = GET_DATA_BYTE(&pixel, COLOR_BLUE);
+ *                 alphaval = GET_DATA_BYTE(&pixel, L_ALPHA_CHANNEL);
+ *           and they can be set with
+ *                 SET_DATA_BYTE(&pixel, COLOR_RED, redval);
+ *                 SET_DATA_BYTE(&pixel, COLOR_GREEN, greenval);
+ *                 SET_DATA_BYTE(&pixel, COLOR_BLUE, blueval);
+ *                 SET_DATA_BYTE(&pixel, L_ALPHA_CHANNEL, alphaval);
+ *
+ *           For extra speed we extract these components directly
+ *           by shifting and masking, explicitly using the values in
+ *           L_RED_SHIFT, etc.:
+ *                 (pixel32 >> L_RED_SHIFT) & 0xff;         (red)
+ *                 (pixel32 >> L_GREEN_SHIFT) & 0xff;       (green)
+ *                 (pixel32 >> L_BLUE_SHIFT) & 0xff;        (blue)
+ *                 (pixel32 >> L_ALPHA_SHIFT) & 0xff;       (alpha)
+ *           All these operations work properly on both big- and little-endians.
+ *
+ *           For a few situations, these color shift values are hard-coded.
+ *           Changing the RGB color component ordering through the assignments
+ *           in this file will cause functions marked with "***" to fail.
+ *
+ *       (5) A reference count is held within each pix, giving the
+ *           number of ptrs to the pix.  When a pixClone() call
+ *           is made, the ref count is increased by 1, and
+ *           when a pixDestroy() call is made, the reference count
+ *           of the pix is decremented.  The pix is only destroyed
+ *           when the reference count goes to zero.
+ *
+ *       (6) The version numbers (below) are used in the serialization
+ *           of these data structures.  They are placed in the files,
+ *           and rarely (if ever) change.  Provision is currently made for
+ *           backward compatibility in reading from boxaa version 2.
+ *
+ *       (7) The serialization dependencies are as follows:
+ *               pixaa  :  pixa  :  boxa
+ *               boxaa  :  boxa
+ *           So, for example, pixaa and boxaa can be changed without
+ *           forcing a change in pixa or boxa.  However, if pixa is
+ *           changed, it forces a change in pixaa, and if boxa is
+ *           changed, if forces a change in the other three.
+ *           We define four version numbers:
+ *               PIXAA_VERSION_NUMBER
+ *               PIXA_VERSION_NUMBER
+ *               BOXAA_VERSION_NUMBER
+ *               BOXA_VERSION_NUMBER
+ *
+ *-------------------------------------------------------------------------*/
+
+
+
+/*-------------------------------------------------------------------------*
+ *                              Array of pix                               *
+ *-------------------------------------------------------------------------*/
+
+    /*  Serialization for primary data structures */
+#define  PIXAA_VERSION_NUMBER      2
+#define  PIXA_VERSION_NUMBER       2
+#define  BOXA_VERSION_NUMBER       2
+#define  BOXAA_VERSION_NUMBER      3
+
+
+struct Pixa
+{
+    l_int32             n;            /* number of Pix in ptr array        */
+    l_int32             nalloc;       /* number of Pix ptrs allocated      */
+    l_uint32            refcount;     /* reference count (1 if no clones)  */
+    struct Pix        **pix;          /* the array of ptrs to pix          */
+    struct Boxa        *boxa;         /* array of boxes                    */
+};
+typedef struct Pixa PIXA;
+
+
+struct Pixaa
+{
+    l_int32             n;            /* number of Pixa in ptr array       */
+    l_int32             nalloc;       /* number of Pixa ptrs allocated     */
+    struct Pixa       **pixa;         /* array of ptrs to pixa             */
+    struct Boxa        *boxa;         /* array of boxes                    */
+};
+typedef struct Pixaa PIXAA;
+
+
+/*-------------------------------------------------------------------------*
+ *                    Basic rectangle and rectangle arrays                 *
+ *-------------------------------------------------------------------------*/
+struct Box
+{
+    l_int32            x;
+    l_int32            y;
+    l_int32            w;
+    l_int32            h;
+    l_uint32           refcount;      /* reference count (1 if no clones)  */
+
+};
+typedef struct Box    BOX;
+
+struct Boxa
+{
+    l_int32            n;             /* number of box in ptr array        */
+    l_int32            nalloc;        /* number of box ptrs allocated      */
+    l_uint32           refcount;      /* reference count (1 if no clones)  */
+    struct Box       **box;           /* box ptr array                     */
+};
+typedef struct Boxa  BOXA;
+
+struct Boxaa
+{
+    l_int32            n;             /* number of boxa in ptr array       */
+    l_int32            nalloc;        /* number of boxa ptrs allocated     */
+    struct Boxa      **boxa;          /* boxa ptr array                    */
+};
+typedef struct Boxaa  BOXAA;
+
+
+/*-------------------------------------------------------------------------*
+ *                               Array of points                           *
+ *-------------------------------------------------------------------------*/
+#define  PTA_VERSION_NUMBER      1
+
+struct Pta
+{
+    l_int32            n;             /* actual number of pts              */
+    l_int32            nalloc;        /* size of allocated arrays          */
+    l_uint32           refcount;      /* reference count (1 if no clones)  */
+    l_float32         *x, *y;         /* arrays of floats                  */
+};
+typedef struct Pta PTA;
+
+
+/*-------------------------------------------------------------------------*
+ *                              Array of Pta                               *
+ *-------------------------------------------------------------------------*/
+struct Ptaa
+{
+    l_int32              n;           /* number of pta in ptr array        */
+    l_int32              nalloc;      /* number of pta ptrs allocated      */
+    struct Pta         **pta;         /* pta ptr array                     */
+};
+typedef struct Ptaa PTAA;
+
+
+/*-------------------------------------------------------------------------*
+ *                       Pix accumulator container                         *
+ *-------------------------------------------------------------------------*/
+struct Pixacc
+{
+    l_int32             w;            /* array width                       */
+    l_int32             h;            /* array height                      */
+    l_int32             offset;       /* used to allow negative            */
+                                      /* intermediate results              */
+    struct Pix         *pix;          /* the 32 bit accumulator pix        */
+};
+typedef struct Pixacc PIXACC;
+
+
+/*-------------------------------------------------------------------------*
+ *                              Pix tiling                                 *
+ *-------------------------------------------------------------------------*/
+struct PixTiling
+{
+    struct Pix          *pix;         /* input pix (a clone)               */
+    l_int32              nx;          /* number of tiles horizontally      */
+    l_int32              ny;          /* number of tiles vertically        */
+    l_int32              w;           /* tile width                        */
+    l_int32              h;           /* tile height                       */
+    l_int32              xoverlap;    /* overlap on left and right         */
+    l_int32              yoverlap;    /* overlap on top and bottom         */
+    l_int32              strip;       /* strip for paint; default is TRUE  */
+};
+typedef struct PixTiling PIXTILING;
+
+
+/*-------------------------------------------------------------------------*
+ *                       FPix: pix with float array                        *
+ *-------------------------------------------------------------------------*/
+#define  FPIX_VERSION_NUMBER      2
+
+struct FPix
+{
+    l_int32              w;           /* width in pixels                   */
+    l_int32              h;           /* height in pixels                  */
+    l_int32              wpl;         /* 32-bit words/line                 */
+    l_uint32             refcount;    /* reference count (1 if no clones)  */
+    l_int32              xres;        /* image res (ppi) in x direction    */
+                                      /* (use 0 if unknown)                */
+    l_int32              yres;        /* image res (ppi) in y direction    */
+                                      /* (use 0 if unknown)                */
+    l_float32           *data;        /* the float image data              */
+};
+typedef struct FPix FPIX;
+
+
+struct FPixa
+{
+    l_int32             n;            /* number of fpix in ptr array       */
+    l_int32             nalloc;       /* number of fpix ptrs allocated     */
+    l_uint32            refcount;     /* reference count (1 if no clones)  */
+    struct FPix       **fpix;         /* the array of ptrs to fpix         */
+};
+typedef struct FPixa FPIXA;
+
+
+/*-------------------------------------------------------------------------*
+ *                       DPix: pix with double array                       *
+ *-------------------------------------------------------------------------*/
+#define  DPIX_VERSION_NUMBER      2
+
+struct DPix
+{
+    l_int32              w;           /* width in pixels                   */
+    l_int32              h;           /* height in pixels                  */
+    l_int32              wpl;         /* 32-bit words/line                 */
+    l_uint32             refcount;    /* reference count (1 if no clones)  */
+    l_int32              xres;        /* image res (ppi) in x direction    */
+                                      /* (use 0 if unknown)                */
+    l_int32              yres;        /* image res (ppi) in y direction    */
+                                      /* (use 0 if unknown)                */
+    l_float64           *data;        /* the double image data             */
+};
+typedef struct DPix DPIX;
+
+
+/*-------------------------------------------------------------------------*
+ *                        PixComp: compressed pix                          *
+ *-------------------------------------------------------------------------*/
+struct PixComp
+{
+    l_int32              w;           /* width in pixels                   */
+    l_int32              h;           /* height in pixels                  */
+    l_int32              d;           /* depth in bits                     */
+    l_int32              xres;        /* image res (ppi) in x direction    */
+                                      /*   (use 0 if unknown)              */
+    l_int32              yres;        /* image res (ppi) in y direction    */
+                                      /*   (use 0 if unknown)              */
+    l_int32              comptype;    /* compressed format (IFF_TIFF_G4,   */
+                                      /*   IFF_PNG, IFF_JFIF_JPEG)         */
+    char                *text;        /* text string associated with pix   */
+    l_int32              cmapflag;    /* flag (1 for cmap, 0 otherwise)    */
+    l_uint8             *data;        /* the compressed image data         */
+    size_t               size;        /* size of the data array            */
+};
+typedef struct PixComp PIXC;
+
+
+/*-------------------------------------------------------------------------*
+ *                     PixaComp: array of compressed pix                   *
+ *-------------------------------------------------------------------------*/
+#define  PIXACOMP_VERSION_NUMBER      2
+
+struct PixaComp
+{
+    l_int32              n;           /* number of PixComp in ptr array    */
+    l_int32              nalloc;      /* number of PixComp ptrs allocated  */
+    l_int32              offset;      /* indexing offset into ptr array    */
+    struct PixComp     **pixc;        /* the array of ptrs to PixComp      */
+    struct Boxa         *boxa;        /* array of boxes                    */
+};
+typedef struct PixaComp PIXAC;
+
+
+/*-------------------------------------------------------------------------*
+ *                         Access and storage flags                        *
+ *-------------------------------------------------------------------------*/
+/*
+ *  For Pix, Box, Pta and Numa, there are 3 standard methods for handling
+ *  the retrieval or insertion of a struct:
+ *     (1) direct insertion (Don't do this if there is another handle
+ *                           somewhere to this same struct!)
+ *     (2) copy (Always safe, sets up a refcount of 1 on the new object.
+ *               Can be undesirable if very large, such as an image or
+ *               an array of images.)
+ *     (3) clone (Makes another handle to the same struct, and bumps the
+ *                refcount up by 1.  Safe to do unless you're changing
+ *                data through one of the handles but don't want those
+ *                changes to be seen by the other handle.)
+ *
+ *  For Pixa and Boxa, which are structs that hold an array of clonable
+ *  structs, there is an additional method:
+ *     (4) copy-clone (Makes a new higher-level struct with a refcount
+ *                     of 1, but clones all the structs in the array.)
+ *
+ *  Unlike the other structs, when retrieving a string from an Sarray,
+ *  you are allowed to get a handle without a copy or clone (i.e., that
+ *  you don't own!).  You must not free or insert such a string!
+ *  Specifically, for an Sarray, the copyflag for retrieval is either:
+ *         TRUE (or 1 or L_COPY)
+ *  or
+ *         FALSE (or 0 or L_NOCOPY)
+ *  For insertion, the copyflag is either:
+ *         TRUE (or 1 or L_COPY)
+ *  or
+ *         FALSE (or 0 or L_INSERT)
+ *  Note that L_COPY is always 1, and L_INSERT and L_NOCOPY are always 0.
+ */
+enum {
+    L_INSERT = 0,     /* stuff it in; no copy, clone or copy-clone    */
+    L_COPY = 1,       /* make/use a copy of the object                */
+    L_CLONE = 2,      /* make/use clone (ref count) of the object     */
+    L_COPY_CLONE = 3  /* make a new object and fill with with clones  */
+                      /* of each object in the array(s)               */
+};
+static const l_int32  L_NOCOPY = 0;  /* copyflag value in sarrayGetString() */
+
+
+/*--------------------------------------------------------------------------*
+ *                              Sort flags                                  *
+ *--------------------------------------------------------------------------*/
+enum {
+    L_SHELL_SORT = 1,             /* use shell sort                         */
+    L_BIN_SORT = 2                /* use bin sort                           */
+};
+
+enum {
+    L_SORT_INCREASING = 1,        /* sort in increasing order               */
+    L_SORT_DECREASING = 2         /* sort in decreasing order               */
+};
+
+enum {
+    L_SORT_BY_X = 1,              /* sort box or c.c. by left edge location  */
+    L_SORT_BY_Y = 2,              /* sort box or c.c. by top edge location   */
+    L_SORT_BY_RIGHT = 3,          /* sort box or c.c. by right edge location */
+    L_SORT_BY_BOT = 4,            /* sort box or c.c. by bot edge location   */
+    L_SORT_BY_WIDTH = 5,          /* sort box or c.c. by width               */
+    L_SORT_BY_HEIGHT = 6,         /* sort box or c.c. by height              */
+    L_SORT_BY_MIN_DIMENSION = 7,  /* sort box or c.c. by min dimension       */
+    L_SORT_BY_MAX_DIMENSION = 8,  /* sort box or c.c. by max dimension       */
+    L_SORT_BY_PERIMETER = 9,      /* sort box or c.c. by perimeter           */
+    L_SORT_BY_AREA = 10,          /* sort box or c.c. by area                */
+    L_SORT_BY_ASPECT_RATIO = 11   /* sort box or c.c. by width/height ratio  */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                             Blend flags                                 *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_BLEND_WITH_INVERSE = 1,     /* add some of src inverse to itself     */
+    L_BLEND_TO_WHITE = 2,         /* shift src colors towards white        */
+    L_BLEND_TO_BLACK = 3,         /* shift src colors towards black        */
+    L_BLEND_GRAY = 4,             /* blend src directly with blender       */
+    L_BLEND_GRAY_WITH_INVERSE = 5 /* add amount of src inverse to itself,  */
+                                  /* based on blender pix value            */
+};
+
+enum {
+    L_PAINT_LIGHT = 1,            /* colorize non-black pixels             */
+    L_PAINT_DARK = 2              /* colorize non-white pixels             */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                        Graphics pixel setting                           *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_SET_PIXELS = 1,             /* set all bits in each pixel to 1       */
+    L_CLEAR_PIXELS = 2,           /* set all bits in each pixel to 0       */
+    L_FLIP_PIXELS = 3             /* flip all bits in each pixel           */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                           Size filter flags                             *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_SELECT_WIDTH = 1,           /* width must satisfy constraint         */
+    L_SELECT_HEIGHT = 2,          /* height must satisfy constraint        */
+    L_SELECT_IF_EITHER = 3,       /* either width or height can satisfy    */
+    L_SELECT_IF_BOTH = 4          /* both width and height must satisfy    */
+};
+
+enum {
+    L_SELECT_IF_LT = 1,           /* save if value is less than threshold  */
+    L_SELECT_IF_GT = 2,           /* save if value is more than threshold  */
+    L_SELECT_IF_LTE = 3,          /* save if value is <= to the threshold  */
+    L_SELECT_IF_GTE = 4           /* save if value is >= to the threshold  */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                     Color component selection flags                     *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_SELECT_RED = 1,             /* use red component                     */
+    L_SELECT_GREEN = 2,           /* use green component                   */
+    L_SELECT_BLUE = 3,            /* use blue component                    */
+    L_SELECT_MIN = 4,             /* use min color component               */
+    L_SELECT_MAX = 5,             /* use max color component               */
+    L_SELECT_AVERAGE = 6          /* use average of color components       */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                         16-bit conversion flags                         *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_LS_BYTE = 1,                /* use LSB                               */
+    L_MS_BYTE = 2,                /* use MSB                               */
+    L_CLIP_TO_FF = 3,             /* use max(val, 255)                     */
+    L_LS_TWO_BYTES = 4,           /* use two LSB                           */
+    L_MS_TWO_BYTES = 5,           /* use two MSB                           */
+    L_CLIP_TO_FFFF = 6            /* use max(val, 65535)                   */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                        Rotate and shear flags                           *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_ROTATE_AREA_MAP = 1,       /* use area map rotation, if possible     */
+    L_ROTATE_SHEAR = 2,          /* use shear rotation                     */
+    L_ROTATE_SAMPLING = 3        /* use sampling                           */
+};
+
+enum {
+    L_BRING_IN_WHITE = 1,        /* bring in white pixels from the outside */
+    L_BRING_IN_BLACK = 2         /* bring in black pixels from the outside */
+};
+
+enum {
+    L_SHEAR_ABOUT_CORNER = 1,    /* shear image about UL corner            */
+    L_SHEAR_ABOUT_CENTER = 2     /* shear image about center               */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                     Affine transform order flags                        *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_TR_SC_RO = 1,              /* translate, scale, rotate               */
+    L_SC_RO_TR = 2,              /* scale, rotate, translate               */
+    L_RO_TR_SC = 3,              /* rotate, translate, scale               */
+    L_TR_RO_SC = 4,              /* translate, rotate, scale               */
+    L_RO_SC_TR = 5,              /* rotate, scale, translate               */
+    L_SC_TR_RO = 6               /* scale, translate, rotate               */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                       Grayscale filling flags                           *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_FILL_WHITE = 1,           /* fill white pixels (e.g, in fg map)      */
+    L_FILL_BLACK = 2            /* fill black pixels (e.g., in bg map)     */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                   Flags for setting to white or black                   *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_SET_WHITE = 1,           /* set pixels to white                      */
+    L_SET_BLACK = 2            /* set pixels to black                      */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                  Flags for getting white or black value                 *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_GET_WHITE_VAL = 1,       /* get white pixel value                    */
+    L_GET_BLACK_VAL = 2        /* get black pixel value                    */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                  Flags for 8 bit and 16 bit pixel sums                  *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_WHITE_IS_MAX = 1,   /* white pixels are 0xff or 0xffff; black are 0  */
+    L_BLACK_IS_MAX = 2    /* black pixels are 0xff or 0xffff; white are 0  */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                           Dither parameters                             *
+ *         If within this grayscale distance from black or white,          *
+ *         do not propagate excess or deficit to neighboring pixels.       *
+ *-------------------------------------------------------------------------*/
+enum {
+    DEFAULT_CLIP_LOWER_1 = 10,   /* dist to black with no prop; 1 bpp      */
+    DEFAULT_CLIP_UPPER_1 = 10,   /* dist to black with no prop; 1 bpp      */
+    DEFAULT_CLIP_LOWER_2 = 5,    /* dist to black with no prop; 2 bpp      */
+    DEFAULT_CLIP_UPPER_2 = 5     /* dist to black with no prop; 2 bpp      */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                             Distance flags                              *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_MANHATTAN_DISTANCE = 1,    /* L1 distance (e.g., in color space)     */
+    L_EUCLIDEAN_DISTANCE = 2     /* L2 distance                            */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                         Statistical measures                            *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_MEAN_ABSVAL = 1,           /* average of abs values                  */
+    L_MEDIAN_VAL = 2,            /* median value of set                    */
+    L_MODE_VAL = 3,              /* mode value of set                      */
+    L_MODE_COUNT = 4,            /* mode count of set                      */
+    L_ROOT_MEAN_SQUARE = 5,      /* rms of values                          */
+    L_STANDARD_DEVIATION = 6,    /* standard deviation from mean           */
+    L_VARIANCE = 7               /* variance of values                     */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                          Set selection flags                            *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_CHOOSE_CONSECUTIVE = 1,    /* select 'n' consecutive                 */
+    L_CHOOSE_SKIP_BY = 2         /* select at intervals of 'n'             */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                         Text orientation flags                          *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_TEXT_ORIENT_UNKNOWN = 0,   /* low confidence on text orientation     */
+    L_TEXT_ORIENT_UP = 1,        /* portrait, text rightside-up            */
+    L_TEXT_ORIENT_LEFT = 2,      /* landscape, text up to left             */
+    L_TEXT_ORIENT_DOWN = 3,      /* portrait, text upside-down             */
+    L_TEXT_ORIENT_RIGHT = 4      /* landscape, text up to right            */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                         Edge orientation flags                          *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_HORIZONTAL_EDGES = 0,     /* filters for horizontal edges            */
+    L_VERTICAL_EDGES = 1,       /* filters for vertical edges              */
+    L_ALL_EDGES = 2             /* filters for all edges                   */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                         Line orientation flags                          *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_HORIZONTAL_LINE = 0,     /* horizontal line                          */
+    L_POS_SLOPE_LINE = 1,      /* 45 degree line with positive slope       */
+    L_VERTICAL_LINE = 2,       /* vertical line                            */
+    L_NEG_SLOPE_LINE = 3,      /* 45 degree line with negative slope       */
+    L_OBLIQUE_LINE = 4         /* neither horizontal nor vertical */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                           Scan direction flags                          *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_FROM_LEFT = 0,           /* scan from left                           */
+    L_FROM_RIGHT = 1,          /* scan from right                          */
+    L_FROM_TOP = 2,            /* scan from top                            */
+    L_FROM_BOT = 3,            /* scan from bottom                         */
+    L_SCAN_NEGATIVE = 4,       /* scan in negative direction               */
+    L_SCAN_POSITIVE = 5,       /* scan in positive direction               */
+    L_SCAN_BOTH = 6,           /* scan in both directions                  */
+    L_SCAN_HORIZONTAL = 7,     /* horizontal scan (direction unimportant)  */
+    L_SCAN_VERTICAL = 8        /* vertical scan (direction unimportant)    */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                Box size adjustment and location flags                   *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_ADJUST_SKIP = 0,             /* do not adjust                        */
+    L_ADJUST_LEFT = 1,             /* adjust left edge                     */
+    L_ADJUST_RIGHT = 2,            /* adjust right edge                    */
+    L_ADJUST_LEFT_AND_RIGHT = 3,   /* adjust both left and right edges     */
+    L_ADJUST_TOP = 4,              /* adjust top edge                      */
+    L_ADJUST_BOT = 5,              /* adjust bottom edge                   */
+    L_ADJUST_TOP_AND_BOT = 6,      /* adjust both top and bottom edges     */
+    L_ADJUST_CHOOSE_MIN = 7,       /* choose the min median value          */
+    L_ADJUST_CHOOSE_MAX = 8,       /* choose the max median value          */
+    L_SET_LEFT = 9,                /* set left side to a given value       */
+    L_SET_RIGHT = 10,              /* set right side to a given value      */
+    L_SET_TOP = 11,                /* set top side to a given value        */
+    L_SET_BOT = 12,                /* set bottom side to a given value     */
+    L_GET_LEFT = 13,               /* get left side location               */
+    L_GET_RIGHT = 14,              /* get right side location              */
+    L_GET_TOP = 15,                /* get top side location                */
+    L_GET_BOT = 16                 /* get bottom side location             */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *          Flags for selecting box boundaries from two choices            *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_USE_MINSIZE = 1,             /* use boundaries giving min size       */
+    L_USE_MAXSIZE = 2,             /* use boundaries giving max size       */
+    L_SUB_ON_BIG_DIFF = 3,         /* substitute boundary if big abs diff  */
+    L_USE_CAPPED_MIN = 4,          /* substitute boundary with capped min  */
+    L_USE_CAPPED_MAX = 5           /* substitute boundary with capped max  */
+};
+
+/*-------------------------------------------------------------------------*
+ *              Handling overlapping bounding boxes in boxa                *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_COMBINE = 1,           /* resize to bounding region; remove smaller  */
+    L_REMOVE_SMALL = 2       /* only remove smaller                        */
+};
+
+/*-------------------------------------------------------------------------*
+ *                    Flags for replacing invalid boxes                    *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_USE_ALL_BOXES = 1,         /* consider all boxes in the sequence     */
+    L_USE_SAME_PARITY_BOXES = 2  /* consider boxes with the same parity    */
+};
+
+/*-------------------------------------------------------------------------*
+ *                            Horizontal warp                              *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_WARP_TO_LEFT = 1,      /* increasing stretch or contraction to left  */
+    L_WARP_TO_RIGHT = 2      /* increasing stretch or contraction to right */
+};
+
+enum {
+    L_LINEAR_WARP = 1,       /* stretch or contraction grows linearly      */
+    L_QUADRATIC_WARP = 2     /* stretch or contraction grows quadratically */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                      Pixel selection for resampling                     *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_INTERPOLATED = 1,      /* linear interpolation from src pixels       */
+    L_SAMPLED = 2            /* nearest src pixel sampling only            */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                             Thinning flags                              *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_THIN_FG = 1,               /* thin foreground of 1 bpp image         */
+    L_THIN_BG = 2                /* thin background of 1 bpp image         */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                            Runlength flags                              *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_HORIZONTAL_RUNS = 0,     /* determine runlengths of horizontal runs  */
+    L_VERTICAL_RUNS = 1        /* determine runlengths of vertical runs    */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                          Edge filter flags                              *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_SOBEL_EDGE = 1,          /* Sobel edge filter                        */
+    L_TWO_SIDED_EDGE = 2       /* Two-sided edge filter                    */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *             Subpixel color component ordering in LCD display            *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_SUBPIXEL_ORDER_RGB = 1,   /* sensor order left-to-right RGB          */
+    L_SUBPIXEL_ORDER_BGR = 2,   /* sensor order left-to-right BGR          */
+    L_SUBPIXEL_ORDER_VRGB = 3,  /* sensor order top-to-bottom RGB          */
+    L_SUBPIXEL_ORDER_VBGR = 4   /* sensor order top-to-bottom BGR          */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                          HSV histogram flags                            *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_HS_HISTO = 1,            /* Use hue-saturation histogram             */
+    L_HV_HISTO = 2,            /* Use hue-value histogram                  */
+    L_SV_HISTO = 3             /* Use saturation-value histogram           */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                    Region flags (inclusion, exclusion)                  *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_INCLUDE_REGION = 1,      /* Use hue-saturation histogram             */
+    L_EXCLUDE_REGION = 2       /* Use hue-value histogram                  */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                    Flags for adding text to a pix                       *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_ADD_ABOVE = 1,           /* Add text above the image                 */
+    L_ADD_BELOW = 2,           /* Add text below the image                 */
+    L_ADD_LEFT = 3,            /* Add text to the left of the image        */
+    L_ADD_RIGHT = 4,           /* Add text to the right of the image       */
+    L_ADD_AT_TOP = 5,          /* Add text over the top of the image       */
+    L_ADD_AT_BOT = 6,          /* Add text over the bottom of the image    */
+    L_ADD_AT_LEFT = 7,         /* Add text over left side of the image     */
+    L_ADD_AT_RIGHT = 8         /* Add text over right side of the image    */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                       Flags for plotting on a pix                       *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_PLOT_AT_TOP = 1,         /* Plot horizontally at top                 */
+    L_PLOT_AT_MID_HORIZ = 2,   /* Plot horizontally at middle              */
+    L_PLOT_AT_BOT = 3,         /* Plot horizontally at bottom              */
+    L_PLOT_AT_LEFT = 4,        /* Plot vertically at left                  */
+    L_PLOT_AT_MID_VERT = 5,    /* Plot vertically at middle                */
+    L_PLOT_AT_RIGHT = 6        /* Plot vertically at right                 */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                   Flags for selecting display program                   *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_DISPLAY_WITH_XZGV = 1,    /* Use xzgv with pixDisplay()              */
+    L_DISPLAY_WITH_XLI = 2,     /* Use xli with pixDisplay()               */
+    L_DISPLAY_WITH_XV = 3,      /* Use xv with pixDisplay()                */
+    L_DISPLAY_WITH_IV = 4,      /* Use irfvanview (win) with pixDisplay()  */
+    L_DISPLAY_WITH_OPEN = 5     /* Use open (apple) with pixDisplay()      */
+};
+
+/*-------------------------------------------------------------------------*
+ *    Flag(s) used in the 'special' pix field for non-default operations   *
+ *      - 0 is default for chroma sampling in jpeg                         *
+ *      - 10-19 are used for zlib compression in png write                 *
+ *      - 4 and 8 are used for specifying connectivity in labelling        *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_NO_CHROMA_SAMPLING_JPEG = 1     /* Write full resolution chroma      */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *          Handling negative values in conversion to unsigned int         *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_CLIP_TO_ZERO = 1,        /* Clip negative values to 0                */
+    L_TAKE_ABSVAL = 2          /* Convert to positive using L_ABS()        */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *                        Relative to zero flags                           *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_LESS_THAN_ZERO = 1,      /* Choose values less than zero             */
+    L_EQUAL_TO_ZERO = 2,       /* Choose values equal to zero              */
+    L_GREATER_THAN_ZERO = 3    /* Choose values greater than zero          */
+};
+
+
+/*-------------------------------------------------------------------------*
+ *         Flags for adding or removing traling slash from string          *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_ADD_TRAIL_SLASH = 1,     /* Add trailing slash to string             */
+    L_REMOVE_TRAIL_SLASH = 2   /* Remove trailing slash from string        */
+};
+
+
+#endif  /* LEPTONICA_PIX_H */
diff --git a/src/pix1.c b/src/pix1.c
new file mode 100644 (file)
index 0000000..f27c834
--- /dev/null
@@ -0,0 +1,1790 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pix1.c
+ *
+ *    The pixN.c {N = 1,2,3,4,5} files are sorted by the type of operation.
+ *    The primary functions in these files are:
+ *
+ *        pix1.c: constructors, destructors and field accessors
+ *        pix2.c: pixel poking of image, pad and border pixels
+ *        pix3.c: masking and logical ops, counting, mirrored tiling
+ *        pix4.c: histograms, statistics, fg/bg estimation
+ *        pix5.c: property measurements, rectangle extraction
+ *
+ *
+ *    This file has the basic constructors, destructors and field accessors
+ *
+ *    Pix memory management (allows custom allocator and deallocator)
+ *          static void  *pix_malloc()
+ *          static void   pix_free()
+ *          void          setPixMemoryManager()
+ *
+ *    Pix creation
+ *          PIX          *pixCreate()
+ *          PIX          *pixCreateNoInit()
+ *          PIX          *pixCreateTemplate()
+ *          PIX          *pixCreateTemplateNoInit()
+ *          PIX          *pixCreateHeader()
+ *          PIX          *pixClone()
+ *
+ *    Pix destruction
+ *          void          pixDestroy()
+ *          static void   pixFree()
+ *
+ *    Pix copy
+ *          PIX          *pixCopy()
+ *          l_int32       pixResizeImageData()
+ *          l_int32       pixCopyColormap()
+ *          l_int32       pixSizesEqual()
+ *          l_int32       pixTransferAllData()
+ *          l_int32       pixSwapAndDestroy()
+ *
+ *    Pix accessors
+ *          l_int32       pixGetWidth()
+ *          l_int32       pixSetWidth()
+ *          l_int32       pixGetHeight()
+ *          l_int32       pixSetHeight()
+ *          l_int32       pixGetDepth()
+ *          l_int32       pixSetDepth()
+ *          l_int32       pixGetDimensions()
+ *          l_int32       pixSetDimensions()
+ *          l_int32       pixCopyDimensions()
+ *          l_int32       pixGetSpp()
+ *          l_int32       pixSetSpp()
+ *          l_int32       pixCopySpp()
+ *          l_int32       pixGetWpl()
+ *          l_int32       pixSetWpl()
+ *          l_int32       pixGetRefcount()
+ *          l_int32       pixChangeRefcount()
+ *          l_uint32      pixGetXRes()
+ *          l_int32       pixSetXRes()
+ *          l_uint32      pixGetYRes()
+ *          l_int32       pixSetYRes()
+ *          l_int32       pixGetResolution()
+ *          l_int32       pixSetResolution()
+ *          l_int32       pixCopyResolution()
+ *          l_int32       pixScaleResolution()
+ *          l_int32       pixGetInputFormat()
+ *          l_int32       pixSetInputFormat()
+ *          l_int32       pixCopyInputFormat()
+ *          l_int32       pixSetSpecial()
+ *          char         *pixGetText()
+ *          l_int32       pixSetText()
+ *          l_int32       pixAddText()
+ *          l_int32       pixCopyText()
+ *          PIXCMAP      *pixGetColormap()
+ *          l_int32       pixSetColormap()
+ *          l_int32       pixDestroyColormap()
+ *          l_uint32     *pixGetData()
+ *          l_int32       pixSetData()
+ *          l_uint32     *pixExtractData()
+ *          l_int32       pixFreeData()
+ *
+ *    Pix line ptrs
+ *          void        **pixGetLinePtrs()
+ *
+ *    Pix debug
+ *          l_int32       pixPrintStreamInfo()
+ *
+ *
+ *  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ *      Important notes on direct management of pix image data
+ *  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ *
+ *  Custom allocator and deallocator
+ *  --------------------------------
+ *
+ *  At the lowest level, you can specify the function that does the
+ *  allocation and deallocation of the data field in the pix.
+ *  By default, this is malloc and free.  However, by calling
+ *  setPixMemoryManager(), custom functions can be substituted.
+ *  When using this, keep two things in mind:
+ *
+ *   (1) Call setPixMemoryManager() before any pix have been allocated
+ *   (2) Destroy all pix as usual, in order to prevent leaks.
+ *
+ *  In pixalloc.c, we provide an example custom allocator and deallocator.
+ *  To use it, you must call pmsCreate() before any pix have been allocated
+ *  and pmsDestroy() at the end after all pix have been destroyed.
+ *
+ *
+ *  Direct manipulation of the pix data field
+ *  -----------------------------------------
+ *
+ *  Memory management of the (image) data field in the pix is
+ *  handled differently from that in the colormap or text fields.
+ *  For colormap and text, the functions pixSetColormap() and
+ *  pixSetText() remove the existing heap data and insert the
+ *  new data.  For the image data, pixSetData() just reassigns the
+ *  data field; any existing data will be lost if there isn't
+ *  another handle for it.
+ *
+ *  Why is pixSetData() limited in this way?  Because the image
+ *  data can be very large, we need flexible ways to handle it,
+ *  particularly when you want to re-use the data in a different
+ *  context without making a copy.  Here are some different
+ *  things you might want to do:
+ *
+ *  (1) Use pixCopy(pixd, pixs) where pixd is not the same size
+ *      as pixs.  This will remove the data in pixd, allocate a
+ *      new data field in pixd, and copy the data from pixs, leaving
+ *      pixs unchanged.
+ *
+ *  (2) Use pixTransferAllData(pixd, &pixs, ...) to transfer the
+ *      data from pixs to pixd without making a copy of it.  If
+ *      pixs is not cloned, this will do the transfer and destroy pixs.
+ *      But if the refcount of pixs is greater than 1, it just copies
+ *      the data and decrements the ref count.
+ *
+ *  (3) Use pixSwapAndDestroy(pixd, &pixs) to replace pixs by an
+ *      existing pixd.  This is similar to pixTransferAllData(), but
+ *      simpler, in that it never makes any copies and if pixs is
+ *      cloned, the other references are not changed by this operation.
+ *
+ *  (4) Use pixExtractData() to extract the image data from the pix
+ *      without copying if possible.  This could be used, for example,
+ *      to convert from a pix to some other data structure with minimal
+ *      heap allocation.  After the data is extracated, the pixels can
+ *      be munged and used in another context.  However, the danger
+ *      here is that the pix might have a refcount > 1, in which case
+ *      a copy of the data must be made and the input pix left unchanged.
+ *      If there are no clones, the image data can be extracted without
+ *      a copy, and the data ptr in the pix must be nulled before
+ *      destroying it because the pix will no longer 'own' the data.
+ *
+ *  We have provided accessors and functions here that should be
+ *  sufficient so that you can do anything you want without
+ *  explicitly referencing any of the pix member fields.
+ *
+ *  However, to avoid memory smashes and leaks when doing special operations
+ *  on the pix data field, look carefully at the behavior of the image
+ *  data accessors and keep in mind that when you invoke pixDestroy(),
+ *  the pix considers itself the owner of all its heap data.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static void pixFree(PIX *pix);
+
+
+/*-------------------------------------------------------------------------*
+ *                        Pix Memory Management                            *
+ *                                                                         *
+ *  These functions give you the freedom to specify at compile or run      *
+ *  time the allocator and deallocator to be used for pix.  It has no      *
+ *  effect on memory management for other data structs, which are          *
+ *  controlled by the #defines in environ.h.  Likewise, the #defines       *
+ *  in environ.h have no effect on the pix memory management.              *
+ *  The default functions are malloc and free.  Use setPixMemoryManager()  *
+ *  to specify other functions to use.                                     *
+ *-------------------------------------------------------------------------*/
+struct PixMemoryManager
+{
+    void     *(*allocator)(size_t);
+    void      (*deallocator)(void *);
+};
+
+static struct PixMemoryManager  pix_mem_manager = {
+    &malloc,
+    &free
+};
+
+static void *
+pix_malloc(size_t  size)
+{
+#ifndef _MSC_VER
+    return (*pix_mem_manager.allocator)(size);
+#else  /* _MSC_VER */
+    /* Under MSVC++, pix_mem_manager is initialized after a call
+     * to pix_malloc.  Just ignore the custom allocator feature. */
+    return malloc(size);
+#endif  /* _MSC_VER */
+}
+
+static void
+pix_free(void  *ptr)
+{
+#ifndef _MSC_VER
+    (*pix_mem_manager.deallocator)(ptr);
+    return;
+#else  /* _MSC_VER */
+    /* Under MSVC++, pix_mem_manager is initialized after a call
+     * to pix_malloc.  Just ignore the custom allocator feature. */
+    free(ptr);
+    return;
+#endif  /* _MSC_VER */
+}
+
+/*!
+ *  setPixMemoryManager()
+ *
+ *      Input: allocator (<optional>; use null to skip)
+ *             deallocator (<optional>; use null to skip)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Use this to change the alloc and/or dealloc functions;
+ *          e.g., setPixMemoryManager(my_malloc, my_free).
+ *      (2) The C99 standard (section 6.7.5.3, par. 8) says:
+ *            A declaration of a parameter as "function returning type"
+ *            shall be adjusted to "pointer to function returning type"
+ *          so that it can be in either of these two forms:
+ *            (a) type (function-ptr(type, ...))
+ *            (b) type ((*function-ptr)(type, ...))
+ *          because form (a) is implictly converted to form (b), as in the
+ *          definition of struct PixMemoryManager above.  So, for example,
+ *          we should be able to declare either of these:
+ *            (a) void *(allocator(size_t))
+ *            (b) void *((*allocator)(size_t))
+ *          However, MSVC++ only accepts the second version.
+ */
+void
+setPixMemoryManager(void  *((*allocator)(size_t)),
+                    void   ((*deallocator)(void *)))
+{
+    if (allocator) pix_mem_manager.allocator = allocator;
+    if (deallocator) pix_mem_manager.deallocator = deallocator;
+    return;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                              Pix Creation                          *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixCreate()
+ *
+ *      Input:  width, height, depth
+ *      Return: pixd (with data allocated and initialized to 0),
+ *                    or null on error
+ */
+PIX *
+pixCreate(l_int32  width,
+          l_int32  height,
+          l_int32  depth)
+{
+PIX  *pixd;
+
+    PROCNAME("pixCreate");
+
+    if ((pixd = pixCreateNoInit(width, height, depth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    memset(pixd->data, 0, 4 * pixd->wpl * pixd->h);
+    return pixd;
+}
+
+
+/*!
+ *  pixCreateNoInit()
+ *
+ *      Input:  width, height, depth
+ *      Return: pixd (with data allocated but not initialized),
+ *                    or null on error
+ *
+ *  Notes:
+ *      (1) Must set pad bits to avoid reading unitialized data, because
+ *          some optimized routines (e.g., pixConnComp()) read from pad bits.
+ */
+PIX *
+pixCreateNoInit(l_int32  width,
+                l_int32  height,
+                l_int32  depth)
+{
+l_int32    wpl;
+PIX       *pixd;
+l_uint32  *data;
+
+    PROCNAME("pixCreateNoInit");
+    if ((pixd = pixCreateHeader(width, height, depth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    wpl = pixGetWpl(pixd);
+    if ((data = (l_uint32 *)pix_malloc(4LL * wpl * height)) == NULL) {
+        pixDestroy(&pixd);
+        return (PIX *)ERROR_PTR("pix_malloc fail for data", procName, NULL);
+    }
+    pixSetData(pixd, data);
+    pixSetPadBits(pixd, 0);
+    return pixd;
+}
+
+
+/*!
+ *  pixCreateTemplate()
+ *
+ *      Input:  pixs
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Makes a Pix of the same size as the input Pix, with the
+ *          data array allocated and initialized to 0.
+ *      (2) Copies the other fields, including colormap if it exists.
+ */
+PIX *
+pixCreateTemplate(PIX  *pixs)
+{
+PIX  *pixd;
+
+    PROCNAME("pixCreateTemplate");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    if ((pixd = pixCreateTemplateNoInit(pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    memset(pixd->data, 0, 4 * pixd->wpl * pixd->h);
+    return pixd;
+}
+
+
+/*!
+ *  pixCreateTemplateNoInit()
+ *
+ *      Input:  pixs
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Makes a Pix of the same size as the input Pix, with
+ *          the data array allocated but not initialized to 0.
+ *      (2) Copies the other fields, including colormap if it exists.
+ */
+PIX *
+pixCreateTemplateNoInit(PIX  *pixs)
+{
+l_int32  w, h, d;
+PIX     *pixd;
+
+    PROCNAME("pixCreateTemplateNoInit");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, &d);
+    if ((pixd = pixCreateNoInit(w, h, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopySpp(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixCopyColormap(pixd, pixs);
+    pixCopyText(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixCreateHeader()
+ *
+ *      Input:  width, height, depth
+ *      Return: pixd (with no data allocated), or null on error
+ *
+ *  Notes:
+ *      (1) It is assumed that all 32 bit pix have 3 spp.  If there is
+ *          a valid alpha channel, this will be set to 4 spp later.
+ *      (2) If the number of bytes to be allocated is larger than the
+ *          maximum value in an int32, we can get overflow, resulting
+ *          in a smaller amount of memory actually being allocated.
+ *          Later, an attempt to access memory that wasn't allocated will
+ *          cause a crash.  So to avoid crashing a program (or worse)
+ *          with bad (or malicious) input, this is where we limit the
+ *          requested allocation of image data in a typesafe way.
+ */
+PIX *
+pixCreateHeader(l_int32  width,
+                l_int32  height,
+                l_int32  depth)
+{
+l_int32   wpl;
+l_uint64  wpl64, bignum;
+PIX      *pixd;
+
+    PROCNAME("pixCreateHeader");
+
+    if ((depth != 1) && (depth != 2) && (depth != 4) && (depth != 8)
+         && (depth != 16) && (depth != 24) && (depth != 32))
+        return (PIX *)ERROR_PTR("depth must be {1, 2, 4, 8, 16, 24, 32}",
+                                procName, NULL);
+    if (width <= 0)
+        return (PIX *)ERROR_PTR("width must be > 0", procName, NULL);
+    if (height <= 0)
+        return (PIX *)ERROR_PTR("height must be > 0", procName, NULL);
+
+        /* Avoid overflow in malloc arg, malicious or otherwise */
+    wpl = 0;
+    wpl64 = ((l_uint64)width * (l_uint64)depth + 31) / 32;
+    if (wpl64 > ((1LL << 29) - 1)) {
+        L_ERROR("requested w = %d, h = %d, d = %d\n",
+                procName, width, height, depth);
+        return (PIX *)ERROR_PTR("wpl >= 2^29", procName, NULL);
+    } else {
+      wpl = (l_int32)wpl64;
+    }
+    bignum = 4L * wpl * height;   /* number of bytes to be requested */
+    if (bignum > ((1LL << 31) - 1)) {
+        L_ERROR("requested w = %d, h = %d, d = %d\n",
+                procName, width, height, depth);
+        return (PIX *)ERROR_PTR("requested bytes >= 2^31", procName, NULL);
+    }
+
+    if ((pixd = (PIX *)LEPT_CALLOC(1, sizeof(PIX))) == NULL)
+        return (PIX *)ERROR_PTR("LEPT_CALLOC fail for pixd", procName, NULL);
+    pixSetWidth(pixd, width);
+    pixSetHeight(pixd, height);
+    pixSetDepth(pixd, depth);
+    pixSetWpl(pixd, wpl);
+    if (depth == 24 || depth == 32)
+        pixSetSpp(pixd, 3);
+    else
+        pixSetSpp(pixd, 1);
+
+    pixd->refcount = 1;
+    pixd->informat = IFF_UNKNOWN;
+    return pixd;
+}
+
+
+/*!
+ *  pixClone()
+ *
+ *      Input:  pix
+ *      Return: same pix (ptr), or null on error
+ *
+ *  Notes:
+ *      (1) A "clone" is simply a handle (ptr) to an existing pix.
+ *          It is implemented because (a) images can be large and
+ *          hence expensive to copy, and (b) extra handles to a data
+ *          structure need to be made with a simple policy to avoid
+ *          both double frees and memory leaks.  Pix are reference
+ *          counted.  The side effect of pixClone() is an increase
+ *          by 1 in the ref count.
+ *      (2) The protocol to be used is:
+ *          (a) Whenever you want a new handle to an existing image,
+ *              call pixClone(), which just bumps a ref count.
+ *          (b) Always call pixDestroy() on all handles.  This
+ *              decrements the ref count, nulls the handle, and
+ *              only destroys the pix when pixDestroy() has been
+ *              called on all handles.
+ */
+PIX *
+pixClone(PIX  *pixs)
+{
+    PROCNAME("pixClone");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixChangeRefcount(pixs, 1);
+
+    return pixs;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                           Pix Destruction                          *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixDestroy()
+ *
+ *      Input:  &pix <will be nulled>
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Decrements the ref count and, if 0, destroys the pix.
+ *      (2) Always nulls the input ptr.
+ */
+void
+pixDestroy(PIX  **ppix)
+{
+PIX  *pix;
+
+    PROCNAME("pixDestroy");
+
+    if (!ppix) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((pix = *ppix) == NULL)
+        return;
+    pixFree(pix);
+    *ppix = NULL;
+    return;
+}
+
+
+/*!
+ *  pixFree()
+ *
+ *      Input:  pix
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Decrements the ref count and, if 0, destroys the pix.
+ */
+static void
+pixFree(PIX  *pix)
+{
+l_uint32  *data;
+char      *text;
+
+    if (!pix) return;
+
+    pixChangeRefcount(pix, -1);
+    if (pixGetRefcount(pix) <= 0) {
+        if ((data = pixGetData(pix)) != NULL)
+            pix_free(data);
+        if ((text = pixGetText(pix)) != NULL)
+            LEPT_FREE(text);
+        pixDestroyColormap(pix);
+        LEPT_FREE(pix);
+    }
+    return;
+}
+
+
+/*-------------------------------------------------------------------------*
+ *                                 Pix Copy                                *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  pixCopy()
+ *
+ *      Input:  pixd (<optional>; can be null, or equal to pixs,
+ *                    or different from pixs)
+ *              pixs
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) There are three cases:
+ *            (a) pixd == null  (makes a new pix; refcount = 1)
+ *            (b) pixd == pixs  (no-op)
+ *            (c) pixd != pixs  (data copy; no change in refcount)
+ *          If the refcount of pixd > 1, case (c) will side-effect
+ *          these handles.
+ *      (2) The general pattern of use is:
+ *             pixd = pixCopy(pixd, pixs);
+ *          This will work for all three cases.
+ *          For clarity when the case is known, you can use:
+ *            (a) pixd = pixCopy(NULL, pixs);
+ *            (c) pixCopy(pixd, pixs);
+ *      (3) For case (c), we check if pixs and pixd are the same
+ *          size (w,h,d).  If so, the data is copied directly.
+ *          Otherwise, the data is reallocated to the correct size
+ *          and the copy proceeds.  The refcount of pixd is unchanged.
+ *      (4) This operation, like all others that may involve a pre-existing
+ *          pixd, will side-effect any existing clones of pixd.
+ */
+PIX *
+pixCopy(PIX  *pixd,   /* can be null */
+        PIX  *pixs)
+{
+l_int32    bytes;
+l_uint32  *datas, *datad;
+
+    PROCNAME("pixCopy");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixs == pixd)
+        return pixd;
+
+        /* Total bytes in image data */
+    bytes = 4 * pixGetWpl(pixs) * pixGetHeight(pixs);
+
+        /* If we're making a new pix ... */
+    if (!pixd) {
+        if ((pixd = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+        datas = pixGetData(pixs);
+        datad = pixGetData(pixd);
+        memcpy((char *)datad, (char *)datas, bytes);
+        return pixd;
+    }
+
+        /* Reallocate image data if sizes are different */
+    if (pixResizeImageData(pixd, pixs) == 1)
+        return (PIX *)ERROR_PTR("reallocation of data failed", procName, NULL);
+
+        /* Copy non-image data fields */
+    pixCopyColormap(pixd, pixs);
+    pixCopySpp(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyText(pixd, pixs);
+
+        /* Copy image data */
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    memcpy((char*)datad, (char*)datas, bytes);
+    return pixd;
+}
+
+
+/*!
+ *  pixResizeImageData()
+ *
+ *      Input:  pixd (gets new uninitialized buffer for image data)
+ *              pixs (determines the size of the buffer; not changed)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This removes any existing image data from pixd and
+ *          allocates an uninitialized buffer that will hold the
+ *          amount of image data that is in pixs.
+ */
+l_int32
+pixResizeImageData(PIX  *pixd,
+                   PIX  *pixs)
+{
+l_int32    w, h, d, wpl, bytes;
+l_uint32  *data;
+
+    PROCNAME("pixResizeImageData");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+
+    if (pixSizesEqual(pixs, pixd))  /* nothing to do */
+        return 0;
+
+    pixGetDimensions(pixs, &w, &h, &d);
+    wpl = pixGetWpl(pixs);
+    pixSetWidth(pixd, w);
+    pixSetHeight(pixd, h);
+    pixSetDepth(pixd, d);
+    pixSetWpl(pixd, wpl);
+    bytes = 4 * wpl * h;
+    pixFreeData(pixd);  /* free any existing image data */
+    if ((data = (l_uint32 *)pix_malloc(bytes)) == NULL)
+        return ERROR_INT("pix_malloc fail for data", procName, 1);
+    pixSetData(pixd, data);
+    return 0;
+}
+
+
+/*!
+ *  pixCopyColormap()
+ *
+ *      Input:  src and dest Pix
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This always destroys any colormap in pixd (except if
+ *          the operation is a no-op.
+ */
+l_int32
+pixCopyColormap(PIX  *pixd,
+                PIX  *pixs)
+{
+PIXCMAP  *cmaps, *cmapd;
+
+    PROCNAME("pixCopyColormap");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (pixs == pixd)
+        return 0;   /* no-op */
+
+    pixDestroyColormap(pixd);
+    if ((cmaps = pixGetColormap(pixs)) == NULL)  /* not an error */
+        return 0;
+
+    if ((cmapd = pixcmapCopy(cmaps)) == NULL)
+        return ERROR_INT("cmapd not made", procName, 1);
+    pixSetColormap(pixd, cmapd);
+
+    return 0;
+}
+
+
+/*!
+ *  pixSizesEqual()
+ *
+ *      Input:  two pix
+ *      Return: 1 if the two pix have same {h, w, d}; 0 otherwise.
+ */
+l_int32
+pixSizesEqual(PIX  *pix1,
+              PIX  *pix2)
+{
+    PROCNAME("pixSizesEqual");
+
+    if (!pix1 || !pix2)
+        return ERROR_INT("pix1 and pix2 not both defined", procName, 0);
+
+    if (pix1 == pix2)
+        return 1;
+
+    if ((pixGetWidth(pix1) != pixGetWidth(pix2)) ||
+        (pixGetHeight(pix1) != pixGetHeight(pix2)) ||
+        (pixGetDepth(pix1) != pixGetDepth(pix2)))
+        return 0;
+    else
+        return 1;
+}
+
+
+/*!
+ *  pixTransferAllData()
+ *
+ *      Input:  pixd (must be different from pixs)
+ *              &pixs (will be nulled if refcount goes to 0)
+ *              copytext (1 to copy the text field; 0 to skip)
+ *              copyformat (1 to copy the informat field; 0 to skip)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does a complete data transfer from pixs to pixd,
+ *          followed by the destruction of pixs (refcount permitting).
+ *      (2) If the refcount of pixs is 1, pixs is destroyed.  Otherwise,
+ *          the data in pixs is copied (rather than transferred) to pixd.
+ *      (3) This operation, like all others with a pre-existing pixd,
+ *          will side-effect any existing clones of pixd.  The pixd
+ *          refcount does not change.
+ *      (4) When might you use this?  Suppose you have an in-place Pix
+ *          function (returning void) with the typical signature:
+ *              void function-inplace(PIX *pix, ...)
+ *          where "..." are non-pointer input parameters, and suppose
+ *          further that you sometimes want to return an arbitrary Pix
+ *          in place of the input Pix.  There are two ways you can do this:
+ *          (a) The straightforward way is to change the function
+ *              signature to take the address of the Pix ptr:
+ *                  void function-inplace(PIX **ppix, ...) {
+ *                      PIX *pixt = function-makenew(*ppix);
+ *                      pixDestroy(ppix);
+ *                      *ppix = pixt;
+ *                      return;
+ *                  }
+ *              Here, the input and returned pix are different, as viewed
+ *              by the calling function, and the inplace function is
+ *              expected to destroy the input pix to avoid a memory leak.
+ *          (b) Keep the signature the same and use pixTransferAllData()
+ *              to return the new Pix in the input Pix struct:
+ *                  void function-inplace(PIX *pix, ...) {
+ *                      PIX *pixt = function-makenew(pix);
+ *                      pixTransferAllData(pix, &pixt, 0, 0);
+ *                               // pixDestroy() is called on pixt
+ *                      return;
+ *                  }
+ *              Here, the input and returned pix are the same, as viewed
+ *              by the calling function, and the inplace function must
+ *              never destroy the input pix, because the calling function
+ *              maintains an unchanged handle to it.
+ */
+l_int32
+pixTransferAllData(PIX     *pixd,
+                   PIX    **ppixs,
+                   l_int32  copytext,
+                   l_int32  copyformat)
+{
+l_int32  nbytes;
+PIX     *pixs;
+
+    PROCNAME("pixTransferAllData");
+
+    if (!ppixs)
+        return ERROR_INT("&pixs not defined", procName, 1);
+    if ((pixs = *ppixs) == NULL)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (pixs == pixd)  /* no-op */
+        return ERROR_INT("pixd == pixs", procName, 1);
+
+    if (pixGetRefcount(pixs) == 1) {  /* transfer the data, cmap, text */
+        pixFreeData(pixd);  /* dealloc any existing data */
+        pixSetData(pixd, pixGetData(pixs));  /* transfer new data from pixs */
+        pixs->data = NULL;  /* pixs no longer owns data */
+        pixSetColormap(pixd, pixGetColormap(pixs));  /* frees old; sets new */
+        pixs->colormap = NULL;  /* pixs no longer owns colormap */
+        if (copytext) {
+            pixSetText(pixd, pixGetText(pixs));
+            pixSetText(pixs, NULL);
+        }
+    } else {  /* preserve pixs by making a copy of the data, cmap, text */
+        pixResizeImageData(pixd, pixs);
+        nbytes = 4 * pixGetWpl(pixs) * pixGetHeight(pixs);
+        memcpy((char *)pixGetData(pixd), (char *)pixGetData(pixs), nbytes);
+        pixCopyColormap(pixd, pixs);
+        if (copytext)
+            pixCopyText(pixd, pixs);
+    }
+
+    pixCopySpp(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixCopyDimensions(pixd, pixs);
+    if (copyformat)
+        pixCopyInputFormat(pixd, pixs);
+
+        /* This will destroy pixs if data was transferred;
+         * otherwise, it just decrements its refcount. */
+    pixDestroy(ppixs);
+    return 0;
+}
+
+
+/*!
+ *  pixSwapAndDestroy()
+ *
+ *      Input:  &pixd (<optional return> input pixd can be null,
+ *                     and it must be different from pixs)
+ *              &pixs (will be nulled after the swap)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Simple operation to change the handle name safely.
+ *          After this operation, the original image in pixd has
+ *          been destroyed, pixd points to what was pixs, and
+ *          the input pixs ptr has been nulled.
+ *      (2) This works safely whether or not pixs and pixd are cloned.
+ *          If pixs is cloned, the other handles still point to
+ *          the original image, with the ref count reduced by 1.
+ *      (3) Usage example:
+ *            Pix *pix1 = pixRead("...");
+ *            Pix *pix2 = function(pix1, ...);
+ *            pixSwapAndDestroy(&pix1, &pix2);
+ *            pixDestroy(&pix1);  // holds what was in pix2
+ *          Example with clones ([] shows ref count of image generated
+ *                               by the function):
+ *            Pix *pixs = pixRead("...");
+ *            Pix *pix1 = pixClone(pixs);
+ *            Pix *pix2 = function(pix1, ...);   [1]
+ *            Pix *pix3 = pixClone(pix2);   [1] --> [2]
+ *            pixSwapAndDestroy(&pix1, &pix2);
+ *            pixDestroy(&pixs);  // still holds read image
+ *            pixDestroy(&pix1);  // holds what was in pix2  [2] --> [1]
+ *            pixDestroy(&pix3);  // holds what was in pix2  [1] --> [0]
+ */
+l_int32
+pixSwapAndDestroy(PIX  **ppixd,
+                  PIX  **ppixs)
+{
+    PROCNAME("pixSwapAndDestroy");
+
+    if (!ppixd)
+        return ERROR_INT("&pixd not defined", procName, 1);
+    if (!ppixs)
+        return ERROR_INT("&pixs not defined", procName, 1);
+    if (*ppixs == NULL)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (ppixs == ppixd)  /* no-op */
+        return ERROR_INT("&pixd == &pixs", procName, 1);
+
+    pixDestroy(ppixd);
+    *ppixd = pixClone(*ppixs);
+    pixDestroy(ppixs);
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                                Accessors                           *
+ *--------------------------------------------------------------------*/
+l_int32
+pixGetWidth(PIX  *pix)
+{
+    PROCNAME("pixGetWidth");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, UNDEF);
+
+    return pix->w;
+}
+
+
+l_int32
+pixSetWidth(PIX     *pix,
+            l_int32  width)
+{
+    PROCNAME("pixSetWidth");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (width < 0) {
+        pix->w = 0;
+        return ERROR_INT("width must be >= 0", procName, 1);
+    }
+
+    pix->w = width;
+    return 0;
+}
+
+
+l_int32
+pixGetHeight(PIX  *pix)
+{
+    PROCNAME("pixGetHeight");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, UNDEF);
+
+    return pix->h;
+}
+
+
+l_int32
+pixSetHeight(PIX     *pix,
+             l_int32  height)
+{
+    PROCNAME("pixSetHeight");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (height < 0) {
+        pix->h = 0;
+        return ERROR_INT("h must be >= 0", procName, 1);
+    }
+
+    pix->h = height;
+    return 0;
+}
+
+
+l_int32
+pixGetDepth(PIX  *pix)
+{
+    PROCNAME("pixGetDepth");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, UNDEF);
+
+    return pix->d;
+}
+
+
+l_int32
+pixSetDepth(PIX     *pix,
+            l_int32  depth)
+{
+    PROCNAME("pixSetDepth");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (depth < 1)
+        return ERROR_INT("d must be >= 1", procName, 1);
+
+    pix->d = depth;
+    return 0;
+}
+
+
+/*!
+ *  pixGetDimensions()
+ *
+ *      Input:  pix
+ *              &w, &h, &d (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixGetDimensions(PIX      *pix,
+                 l_int32  *pw,
+                 l_int32  *ph,
+                 l_int32  *pd)
+{
+    PROCNAME("pixGetDimensions");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pd) *pd = 0;
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (pw) *pw = pix->w;
+    if (ph) *ph = pix->h;
+    if (pd) *pd = pix->d;
+    return 0;
+}
+
+
+/*!
+ *  pixSetDimensions()
+ *
+ *      Input:  pix
+ *              w, h, d (use 0 to skip the setting for any of these)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixSetDimensions(PIX     *pix,
+                 l_int32  w,
+                 l_int32  h,
+                 l_int32  d)
+{
+    PROCNAME("pixSetDimensions");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (w > 0) pixSetWidth(pix, w);
+    if (h > 0) pixSetHeight(pix, h);
+    if (d > 0) pixSetDepth(pix, d);
+    return 0;
+}
+
+
+/*!
+ *  pixCopyDimensions()
+ *
+ *      Input:  pixd
+ *              pixd
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixCopyDimensions(PIX  *pixd,
+                  PIX  *pixs)
+{
+    PROCNAME("pixCopyDimensions");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixs == pixd)
+        return 0;   /* no-op */
+
+    pixSetWidth(pixd, pixGetWidth(pixs));
+    pixSetHeight(pixd, pixGetHeight(pixs));
+    pixSetDepth(pixd, pixGetDepth(pixs));
+    pixSetWpl(pixd, pixGetWpl(pixs));
+    return 0;
+}
+
+
+l_int32
+pixGetSpp(PIX  *pix)
+{
+    PROCNAME("pixGetSpp");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, UNDEF);
+
+    return pix->spp;
+}
+
+
+/*
+ *  pixSetSpp()
+ *      Input:  pix
+ *              spp (1, 3 or 4)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For a 32 bpp pix, this can be used to ignore the
+ *          alpha sample (spp == 3) or to use it (spp == 4).
+ *          For example, to write a spp == 4 image without the alpha
+ *          sample (as an rgb pix), call pixSetSpp(pix, 3) and
+ *          then write it out as a png.
+ */
+l_int32
+pixSetSpp(PIX     *pix,
+          l_int32  spp)
+{
+    PROCNAME("pixSetSpp");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (spp < 1)
+        return ERROR_INT("spp must be >= 1", procName, 1);
+
+    pix->spp = spp;
+    return 0;
+}
+
+
+/*!
+ *  pixCopySpp()
+ *
+ *      Input:  pixd
+ *              pixs
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixCopySpp(PIX  *pixd,
+           PIX  *pixs)
+{
+    PROCNAME("pixCopySpp");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixs == pixd)
+        return 0;   /* no-op */
+
+    pixSetSpp(pixd, pixGetSpp(pixs));
+    return 0;
+}
+
+
+l_int32
+pixGetWpl(PIX  *pix)
+{
+    PROCNAME("pixGetWpl");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, UNDEF);
+    return pix->wpl;
+}
+
+
+l_int32
+pixSetWpl(PIX     *pix,
+          l_int32  wpl)
+{
+    PROCNAME("pixSetWpl");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pix->wpl = wpl;
+    return 0;
+}
+
+
+l_int32
+pixGetRefcount(PIX  *pix)
+{
+    PROCNAME("pixGetRefcount");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, UNDEF);
+    return pix->refcount;
+}
+
+
+l_int32
+pixChangeRefcount(PIX     *pix,
+                  l_int32  delta)
+{
+    PROCNAME("pixChangeRefcount");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pix->refcount += delta;
+    return 0;
+}
+
+
+l_int32
+pixGetXRes(PIX  *pix)
+{
+    PROCNAME("pixGetXRes");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 0);
+    return pix->xres;
+}
+
+
+l_int32
+pixSetXRes(PIX     *pix,
+           l_int32  res)
+{
+    PROCNAME("pixSetXRes");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pix->xres = res;
+    return 0;
+}
+
+
+l_int32
+pixGetYRes(PIX  *pix)
+{
+    PROCNAME("pixGetYRes");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 0);
+    return pix->yres;
+}
+
+
+l_int32
+pixSetYRes(PIX     *pix,
+           l_int32  res)
+{
+    PROCNAME("pixSetYRes");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pix->yres = res;
+    return 0;
+}
+
+
+/*!
+ *  pixGetResolution()
+ *
+ *      Input:  pix
+ *              &xres, &yres (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixGetResolution(PIX      *pix,
+                 l_int32  *pxres,
+                 l_int32  *pyres)
+{
+    PROCNAME("pixGetResolution");
+
+    if (pxres) *pxres = 0;
+    if (pyres) *pyres = 0;
+    if (!pxres && !pyres)
+        return ERROR_INT("no output requested", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (pxres) *pxres = pix->xres;
+    if (pyres) *pyres = pix->yres;
+    return 0;
+}
+
+
+/*!
+ *  pixSetResolution()
+ *
+ *      Input:  pix
+ *              xres, yres (use 0 to skip the setting for either of these)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixSetResolution(PIX     *pix,
+                 l_int32  xres,
+                 l_int32  yres)
+{
+    PROCNAME("pixSetResolution");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (xres > 0) pix->xres = xres;
+    if (yres > 0) pix->yres = yres;
+    return 0;
+}
+
+
+l_int32
+pixCopyResolution(PIX  *pixd,
+                  PIX  *pixs)
+{
+    PROCNAME("pixCopyResolution");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (pixs == pixd)
+        return 0;   /* no-op */
+
+    pixSetXRes(pixd, pixGetXRes(pixs));
+    pixSetYRes(pixd, pixGetYRes(pixs));
+    return 0;
+}
+
+
+l_int32
+pixScaleResolution(PIX       *pix,
+                   l_float32  xscale,
+                   l_float32  yscale)
+{
+    PROCNAME("pixScaleResolution");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    if (pix->xres != 0 && pix->yres != 0) {
+        pix->xres = (l_uint32)(xscale * (l_float32)(pix->xres) + 0.5);
+        pix->yres = (l_uint32)(yscale * (l_float32)(pix->yres) + 0.5);
+    }
+    return 0;
+}
+
+
+l_int32
+pixGetInputFormat(PIX  *pix)
+{
+    PROCNAME("pixGetInputFormat");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, UNDEF);
+    return pix->informat;
+}
+
+
+l_int32
+pixSetInputFormat(PIX     *pix,
+                  l_int32  informat)
+{
+    PROCNAME("pixSetInputFormat");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pix->informat = informat;
+    return 0;
+}
+
+
+l_int32
+pixCopyInputFormat(PIX  *pixd,
+                   PIX  *pixs)
+{
+    PROCNAME("pixCopyInputFormat");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (pixs == pixd)
+        return 0;   /* no-op */
+
+    pixSetInputFormat(pixd, pixGetInputFormat(pixs));
+    return 0;
+}
+
+
+l_int32
+pixSetSpecial(PIX     *pix,
+              l_int32  special)
+{
+    PROCNAME("pixSetSpecial");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pix->special = special;
+    return 0;
+}
+
+
+/*!
+ *  pixGetText()
+ *
+ *      Input:  pix
+ *      Return: ptr to existing text string
+ *
+ *  Notes:
+ *      (1) The text string belongs to the pix.  The caller must
+ *          NOT free it!
+ */
+char *
+pixGetText(PIX  *pix)
+{
+    PROCNAME("pixGetText");
+
+    if (!pix)
+        return (char *)ERROR_PTR("pix not defined", procName, NULL);
+    return pix->text;
+}
+
+
+/*!
+ *  pixSetText()
+ *
+ *      Input:  pix
+ *              textstring (can be null)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This removes any existing textstring and puts a copy of
+ *          the input textstring there.
+ */
+l_int32
+pixSetText(PIX         *pix,
+           const char  *textstring)
+{
+    PROCNAME("pixSetText");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    stringReplace(&pix->text, textstring);
+    return 0;
+}
+
+
+/*!
+ *  pixAddText()
+ *
+ *      Input:  pix
+ *              textstring
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This adds the new textstring to any existing text.
+ *      (2) Either or both the existing text and the new text
+ *          string can be null.
+ */
+l_int32
+pixAddText(PIX         *pix,
+           const char  *textstring)
+{
+char  *newstring;
+
+    PROCNAME("pixAddText");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    newstring = stringJoin(pixGetText(pix), textstring);
+    stringReplace(&pix->text, newstring);
+    LEPT_FREE(newstring);
+    return 0;
+}
+
+
+l_int32
+pixCopyText(PIX  *pixd,
+            PIX  *pixs)
+{
+    PROCNAME("pixCopyText");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (pixs == pixd)
+        return 0;   /* no-op */
+
+    pixSetText(pixd, pixGetText(pixs));
+    return 0;
+}
+
+
+PIXCMAP *
+pixGetColormap(PIX  *pix)
+{
+    PROCNAME("pixGetColormap");
+
+    if (!pix)
+        return (PIXCMAP *)ERROR_PTR("pix not defined", procName, NULL);
+    return pix->colormap;
+}
+
+
+/*!
+ *  pixSetColormap()
+ *
+ *      Input:  pix
+ *              colormap (to be assigned)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) Unlike with the pix data field, pixSetColormap() destroys
+ *          any existing colormap before assigning the new one.
+ *          Because colormaps are not ref counted, it is important that
+ *          the new colormap does not belong to any other pix.
+ */
+l_int32
+pixSetColormap(PIX      *pix,
+               PIXCMAP  *colormap)
+{
+    PROCNAME("pixSetColormap");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pixDestroyColormap(pix);
+    pix->colormap = colormap;
+    return 0;
+}
+
+
+/*!
+ *  pixDestroyColormap()
+ *
+ *      Input:  pix
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixDestroyColormap(PIX  *pix)
+{
+PIXCMAP  *cmap;
+
+    PROCNAME("pixDestroyColormap");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    if ((cmap = pix->colormap) != NULL) {
+        pixcmapDestroy(&cmap);
+        pix->colormap = NULL;
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixGetData()
+ *
+ *  Notes:
+ *      (1) This gives a new handle for the data.  The data is still
+ *          owned by the pix, so do not call LEPT_FREE() on it.
+ */
+l_uint32 *
+pixGetData(PIX  *pix)
+{
+    PROCNAME("pixGetData");
+
+    if (!pix)
+        return (l_uint32 *)ERROR_PTR("pix not defined", procName, NULL);
+    return pix->data;
+}
+
+
+/*!
+ *  pixSetData()
+ *
+ *  Notes:
+ *      (1) This does not free any existing data.  To free existing
+ *          data, use pixFreeData() before pixSetData().
+ */
+l_int32
+pixSetData(PIX       *pix,
+           l_uint32  *data)
+{
+    PROCNAME("pixSetData");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pix->data = data;
+    return 0;
+}
+
+
+/*!
+ *  pixExtractData()
+ *
+ *  Notes:
+ *      (1) This extracts the pix image data for use in another context.
+ *          The caller still needs to use pixDestroy() on the input pix.
+ *      (2) If refcount == 1, the data is extracted and the
+ *          pix->data ptr is set to NULL.
+ *      (3) If refcount > 1, this simply returns a copy of the data,
+ *          using the pix allocator, and leaving the input pix unchanged.
+ */
+l_uint32 *
+pixExtractData(PIX  *pixs)
+{
+l_int32    count, bytes;
+l_uint32  *data, *datas;
+
+    PROCNAME("pixExtractData");
+
+    if (!pixs)
+        return (l_uint32 *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    count = pixGetRefcount(pixs);
+    if (count == 1) {  /* extract */
+        data = pixGetData(pixs);
+        pixSetData(pixs, NULL);
+    } else {  /* refcount > 1; copy */
+        bytes = 4 * pixGetWpl(pixs) * pixGetHeight(pixs);
+        datas = pixGetData(pixs);
+        if ((data = (l_uint32 *)pix_malloc(bytes)) == NULL)
+            return (l_uint32 *)ERROR_PTR("data not made", procName, NULL);
+        memcpy((char *)data, (char *)datas, bytes);
+    }
+
+    return data;
+}
+
+
+/*!
+ *  pixFreeData()
+ *
+ *  Notes:
+ *      (1) This frees the data and sets the pix data ptr to null.
+ *          It should be used before pixSetData() in the situation where
+ *          you want to free any existing data before doing
+ *          a subsequent assignment with pixSetData().
+ */
+l_int32
+pixFreeData(PIX  *pix)
+{
+l_uint32  *data;
+
+    PROCNAME("pixFreeData");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    if ((data = pixGetData(pix)) != NULL) {
+        pix_free(data);
+        pix->data = NULL;
+    }
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                          Pix line ptrs                             *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixGetLinePtrs()
+ *
+ *      Input:  pix
+ *              &size (<optional return> array size, which is the pix height)
+ *      Return: array of line ptrs, or null on error
+ *
+ *  Notes:
+ *      (1) This is intended to be used for fast random pixel access.
+ *          For example, for an 8 bpp image,
+ *              val = GET_DATA_BYTE(lines8[i], j);
+ *          is equivalent to, but much faster than,
+ *              pixGetPixel(pix, j, i, &val);
+ *      (2) How much faster?  For 1 bpp, it's from 6 to 10x faster.
+ *          For 8 bpp, it's an amazing 30x faster.  So if you are
+ *          doing random access over a substantial part of the image,
+ *          use this line ptr array.
+ *      (3) When random access is used in conjunction with a stack,
+ *          queue or heap, the overall computation time depends on
+ *          the operations performed on each struct that is popped
+ *          or pushed, and whether we are using a priority queue (O(logn))
+ *          or a queue or stack (O(1)).  For example, for maze search,
+ *          the overall ratio of time for line ptrs vs. pixGet/Set* is
+ *             Maze type     Type                   Time ratio
+ *               binary      queue                     0.4
+ *               gray        heap (priority queue)     0.6
+ *      (4) Because this returns a void** and the accessors take void*,
+ *          the compiler cannot check the pointer types.  It is
+ *          strongly recommended that you adopt a naming scheme for
+ *          the returned ptr arrays that indicates the pixel depth.
+ *          (This follows the original intent of Simonyi's "Hungarian"
+ *          application notation, where naming is used proactively
+ *          to make errors visibly obvious.)  By doing this, you can
+ *          tell by inspection if the correct accessor is used.
+ *          For example, for an 8 bpp pixg:
+ *              void **lineg8 = pixGetLinePtrs(pixg, NULL);
+ *              val = GET_DATA_BYTE(lineg8[i], j);  // fast access; BYTE, 8
+ *              ...
+ *              LEPT_FREE(lineg8);  // don't forget this
+ *      (5) These are convenient for accessing bytes sequentially in an
+ *          8 bpp grayscale image.  People who write image processing code
+ *          on 8 bpp images are accustomed to grabbing pixels directly out
+ *          of the raster array.  Note that for little endians, you first
+ *          need to reverse the byte order in each 32-bit word.
+ *          Here's a typical usage pattern:
+ *              pixEndianByteSwap(pix);   // always safe; no-op on big-endians
+ *              l_uint8 **lineptrs = (l_uint8 **)pixGetLinePtrs(pix, NULL);
+ *              pixGetDimensions(pix, &w, &h, NULL);
+ *              for (i = 0; i < h; i++) {
+ *                  l_uint8 *line = lineptrs[i];
+ *                  for (j = 0; j < w; j++) {
+ *                      val = line[j];
+ *                      ...
+ *                  }
+ *              }
+ *              pixEndianByteSwap(pix);  // restore big-endian order
+ *              LEPT_FREE(lineptrs);
+ *          This can be done even more simply as follows:
+ *              l_uint8 **lineptrs = pixSetupByteProcessing(pix, &w, &h);
+ *              for (i = 0; i < h; i++) {
+ *                  l_uint8 *line = lineptrs[i];
+ *                  for (j = 0; j < w; j++) {
+ *                      val = line[j];
+ *                      ...
+ *                  }
+ *              }
+ *              pixCleanupByteProcessing(pix, lineptrs);
+ */
+void **
+pixGetLinePtrs(PIX      *pix,
+               l_int32  *psize)
+{
+l_int32    i, h, wpl;
+l_uint32  *data;
+void     **lines;
+
+    PROCNAME("pixGetLinePtrs");
+
+    if (psize) *psize = 0;
+    if (!pix)
+        return (void **)ERROR_PTR("pix not defined", procName, NULL);
+
+    h = pixGetHeight(pix);
+    if (psize) *psize = h;
+    if ((lines = (void **)LEPT_CALLOC(h, sizeof(void *))) == NULL)
+        return (void **)ERROR_PTR("lines not made", procName, NULL);
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    for (i = 0; i < h; i++)
+        lines[i] = (void *)(data + i * wpl);
+
+    return lines;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                    Print output for debugging                      *
+ *--------------------------------------------------------------------*/
+extern const char *ImageFileFormatExtensions[];
+
+/*!
+ *  pixPrintStreamInfo()
+ *
+ *      Input:  fp (file stream)
+ *              pix
+ *              text (<optional> identifying string; can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixPrintStreamInfo(FILE        *fp,
+                   PIX         *pix,
+                   const char  *text)
+{
+char     *textdata;
+l_int32   informat;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixPrintStreamInfo");
+
+    if (!fp)
+        return ERROR_INT("fp not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    if (text)
+        fprintf(fp, "  Pix Info for %s:\n", text);
+    fprintf(fp, "    width = %d, height = %d, depth = %d, spp = %d\n",
+               pixGetWidth(pix), pixGetHeight(pix), pixGetDepth(pix),
+               pixGetSpp(pix));
+    fprintf(fp, "    wpl = %d, data = %p, refcount = %d\n",
+               pixGetWpl(pix), pixGetData(pix), pixGetRefcount(pix));
+    fprintf(fp, "    xres = %d, yres = %d\n", pixGetXRes(pix), pixGetYRes(pix));
+    if ((cmap = pixGetColormap(pix)) != NULL)
+        pixcmapWriteStream(fp, cmap);
+    else
+        fprintf(fp, "    no colormap\n");
+    informat = pixGetInputFormat(pix);
+    fprintf(fp, "    input format: %d (%s)\n", informat,
+            ImageFileFormatExtensions[informat]);
+    if ((textdata = pixGetText(pix)) != NULL)
+        fprintf(fp, "    text: %s\n", textdata);
+
+    return 0;
+}
diff --git a/src/pix2.c b/src/pix2.c
new file mode 100644 (file)
index 0000000..b4fa5b2
--- /dev/null
@@ -0,0 +1,3246 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pix2.c
+ *
+ *    This file has these basic operations:
+ *
+ *      (1) Get and set: individual pixels, full image, rectangular region,
+ *          pad pixels, border pixels, and color components for RGB
+ *      (2) Add and remove border pixels
+ *      (3) Endian byte swaps
+ *      (4) Simple method for byte-processing images (instead of words)
+ *
+ *      Pixel poking
+ *           l_int32     pixGetPixel()
+ *           l_int32     pixSetPixel()
+ *           l_int32     pixGetRGBPixel()
+ *           l_int32     pixSetRGBPixel()
+ *           l_int32     pixGetRandomPixel()
+ *           l_int32     pixClearPixel()
+ *           l_int32     pixFlipPixel()
+ *           void        setPixelLow()
+ *
+ *      Find black or white value
+ *           l_int32     pixGetBlackOrWhiteVal()
+ *
+ *      Full image clear/set/set-to-arbitrary-value
+ *           l_int32     pixClearAll()
+ *           l_int32     pixSetAll()
+ *           l_int32     pixSetAllGray()
+ *           l_int32     pixSetAllArbitrary()
+ *           l_int32     pixSetBlackOrWhite()
+ *           l_int32     pixSetComponentArbitrary()
+ *
+ *      Rectangular region clear/set/set-to-arbitrary-value/blend
+ *           l_int32     pixClearInRect()
+ *           l_int32     pixSetInRect()
+ *           l_int32     pixSetInRectArbitrary()
+ *           l_int32     pixBlendInRect()
+ *
+ *      Set pad bits
+ *           l_int32     pixSetPadBits()
+ *           l_int32     pixSetPadBitsBand()
+ *
+ *      Assign border pixels
+ *           l_int32     pixSetOrClearBorder()
+ *           l_int32     pixSetBorderVal()
+ *           l_int32     pixSetBorderRingVal()
+ *           l_int32     pixSetMirroredBorder()
+ *           PIX        *pixCopyBorder()
+ *
+ *      Add and remove border
+ *           PIX        *pixAddBorder()
+ *           PIX        *pixAddBlackOrWhiteBorder()
+ *           PIX        *pixAddBorderGeneral()
+ *           PIX        *pixRemoveBorder()
+ *           PIX        *pixRemoveBorderGeneral()
+ *           PIX        *pixRemoveBorderToSize()
+ *           PIX        *pixAddMirroredBorder()
+ *           PIX        *pixAddRepeatedBorder()
+ *           PIX        *pixAddMixedBorder()
+ *           PIX        *pixAddContinuedBorder()
+ *
+ *      Helper functions using alpha
+ *           l_int32     pixShiftAndTransferAlpha()
+ *           PIX        *pixDisplayLayersRGBA()
+ *
+ *      Color sample setting and extraction
+ *           PIX        *pixCreateRGBImage()
+ *           PIX        *pixGetRGBComponent()
+ *           l_int32     pixSetRGBComponent()
+ *           PIX        *pixGetRGBComponentCmap()
+ *           l_int32     pixCopyRGBComponent()
+ *           l_int32     composeRGBPixel()
+ *           l_int32     composeRGBAPixel()
+ *           void        extractRGBValues()
+ *           void        extractRGBAValues()
+ *           l_int32     extractMinMaxComponent()
+ *           l_int32     pixGetRGBLine()
+ *
+ *      Conversion between big and little endians
+ *           PIX        *pixEndianByteSwapNew()
+ *           l_int32     pixEndianByteSwap()
+ *           l_int32     lineEndianByteSwap()
+ *           PIX        *pixEndianTwoByteSwapNew()
+ *           l_int32     pixEndianTwoByteSwap()
+ *
+ *      Extract raster data as binary string
+ *           l_int32     pixGetRasterData()
+ *
+ *      Test alpha component opaqueness
+ *           l_int32     pixAlphaIsOpaque
+ *
+ *      Setup helpers for 8 bpp byte processing
+ *           l_uint8   **pixSetupByteProcessing()
+ *           l_int32     pixCleanupByteProcessing()
+ *
+ *      Setting parameters for antialias masking with alpha transforms
+ *           void        l_setAlphaMaskBorder()
+ *
+ *      *** indicates implicit assumption about RGB component ordering
+ */
+
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_uint32 rmask32[] = {0x0,
+    0x00000001, 0x00000003, 0x00000007, 0x0000000f,
+    0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
+    0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff,
+    0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff,
+    0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff,
+    0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff,
+    0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff,
+    0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff};
+
+    /* This is a global that determines the default 8 bpp alpha mask values
+     * for rings at distance 1 and 2 from the border.  Declare extern
+     * to use.  To change the values, use l_setAlphaMaskBorder(). */
+LEPT_DLL l_float32  AlphaMaskBorderVals[2] = {0.0, 0.5};
+
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_SERIALIZE        0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*-------------------------------------------------------------*
+ *                         Pixel poking                        *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixGetPixel()
+ *
+ *      Input:  pix
+ *              (x,y) pixel coords
+ *              &val (<return> pixel value)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This returns the value in the data array.  If the pix is
+ *          colormapped, it returns the colormap index, not the rgb value.
+ *      (2) Because of the function overhead and the parameter checking,
+ *          this is much slower than using the GET_DATA_*() macros directly.
+ *          Speed on a 1 Mpixel RGB image, using a 3 GHz machine:
+ *            * pixGet/pixSet: ~25 Mpix/sec
+ *            * GET_DATA/SET_DATA: ~350 MPix/sec
+ *          If speed is important and you're doing random access into
+ *          the pix, use pixGetLinePtrs() and the array access macros.
+ */
+l_int32
+pixGetPixel(PIX       *pix,
+            l_int32    x,
+            l_int32    y,
+            l_uint32  *pval)
+{
+l_int32    w, h, d, wpl, val;
+l_uint32  *line, *data;
+
+    PROCNAME("pixGetPixel");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0;
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pixGetDimensions(pix, &w, &h, &d);
+    if (x < 0 || x >= w)
+        return ERROR_INT("x out of bounds", procName, 1);
+    if (y < 0 || y >= h)
+        return ERROR_INT("y out of bounds", procName, 1);
+
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    line = data + y * wpl;
+    switch (d)
+    {
+    case 1:
+        val = GET_DATA_BIT(line, x);
+        break;
+    case 2:
+        val = GET_DATA_DIBIT(line, x);
+        break;
+    case 4:
+        val = GET_DATA_QBIT(line, x);
+        break;
+    case 8:
+        val = GET_DATA_BYTE(line, x);
+        break;
+    case 16:
+        val = GET_DATA_TWO_BYTES(line, x);
+        break;
+    case 32:
+        val = line[x];
+        break;
+    default:
+        return ERROR_INT("depth must be in {1,2,4,8,16,32} bpp", procName, 1);
+    }
+
+    *pval = val;
+    return 0;
+}
+
+
+/*!
+ *  pixSetPixel()
+ *
+ *      Input:  pix
+ *              (x,y) pixel coords
+ *              val (value to be inserted)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Warning: the input value is not checked for overflow with respect
+ *          the the depth of @pix, and the sign bit (if any) is ignored.
+ *          * For d == 1, @val > 0 sets the bit on.
+ *          * For d == 2, 4, 8 and 16, @val is masked to the maximum allowable
+ *            pixel value, and any (invalid) higher order bits are discarded.
+ *      (2) See pixGetPixel() for information on performance.
+ */
+l_int32
+pixSetPixel(PIX      *pix,
+            l_int32   x,
+            l_int32   y,
+            l_uint32  val)
+{
+l_int32    w, h, d, wpl;
+l_uint32  *line, *data;
+
+    PROCNAME("pixSetPixel");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (x < 0 || x >= w)
+        return ERROR_INT("x out of bounds", procName, 1);
+    if (y < 0 || y >= h)
+        return ERROR_INT("y out of bounds", procName, 1);
+
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    line = data + y * wpl;
+    switch (d)
+    {
+    case 1:
+        if (val)
+            SET_DATA_BIT(line, x);
+        else
+            CLEAR_DATA_BIT(line, x);
+        break;
+    case 2:
+        SET_DATA_DIBIT(line, x, val);
+        break;
+    case 4:
+        SET_DATA_QBIT(line, x, val);
+        break;
+    case 8:
+        SET_DATA_BYTE(line, x, val);
+        break;
+    case 16:
+        SET_DATA_TWO_BYTES(line, x, val);
+        break;
+    case 32:
+        line[x] = val;
+        break;
+    default:
+        return ERROR_INT("depth must be in {1,2,4,8,16,32} bpp", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixGetRGBPixel()
+ *
+ *      Input:  pix (32 bpp rgb, not colormapped)
+ *              (x,y) pixel coords
+ *              &rval (<optional return> red component)
+ *              &gval (<optional return> green component)
+ *              &bval (<optional return> blue component)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixGetRGBPixel(PIX      *pix,
+               l_int32   x,
+               l_int32   y,
+               l_int32  *prval,
+               l_int32  *pgval,
+               l_int32  *pbval)
+{
+l_int32    w, h, d, wpl;
+l_uint32  *data, *ppixel;
+
+    PROCNAME("pixGetRGBPixel");
+
+    if (prval) *prval = 0;
+    if (pgval) *pgval = 0;
+    if (pbval) *pbval = 0;
+    if (!prval && !pgval && !pbval)
+        return ERROR_INT("no output requested", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 32)
+        return ERROR_INT("pix not 32 bpp", procName, 1);
+    if (x < 0 || x >= w)
+        return ERROR_INT("x out of bounds", procName, 1);
+    if (y < 0 || y >= h)
+        return ERROR_INT("y out of bounds", procName, 1);
+
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    ppixel = data + y * wpl + x;
+    if (prval) *prval = GET_DATA_BYTE(ppixel, COLOR_RED);
+    if (pgval) *pgval = GET_DATA_BYTE(ppixel, COLOR_GREEN);
+    if (pbval) *pbval = GET_DATA_BYTE(ppixel, COLOR_BLUE);
+    return 0;
+}
+
+
+/*!
+ *  pixSetRGBPixel()
+ *
+ *      Input:  pix (32 bpp rgb)
+ *              (x,y) pixel coords
+ *              rval (red component)
+ *              gval (green component)
+ *              bval (blue component)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixSetRGBPixel(PIX     *pix,
+               l_int32  x,
+               l_int32  y,
+               l_int32  rval,
+               l_int32  gval,
+               l_int32  bval)
+{
+l_int32    w, h, d, wpl;
+l_uint32   pixel;
+l_uint32  *data, *line;
+
+    PROCNAME("pixSetRGBPixel");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 32)
+        return ERROR_INT("pix not 32 bpp", procName, 1);
+    if (x < 0 || x >= w)
+        return ERROR_INT("x out of bounds", procName, 1);
+    if (y < 0 || y >= h)
+        return ERROR_INT("y out of bounds", procName, 1);
+
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    line = data + y * wpl;
+    composeRGBPixel(rval, gval, bval, &pixel);
+    *(line + x) = pixel;
+    return 0;
+}
+
+
+/*!
+ *  pixGetRandomPixel()
+ *
+ *      Input:  pix (any depth; can be colormapped)
+ *              &val (<optional return> pixel value)
+ *              &x (<optional return> x coordinate chosen; can be null)
+ *              &y (<optional return> y coordinate chosen; can be null)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) If the pix is colormapped, it returns the rgb value.
+ */
+l_int32
+pixGetRandomPixel(PIX       *pix,
+                  l_uint32  *pval,
+                  l_int32   *px,
+                  l_int32   *py)
+{
+l_int32   w, h, x, y, rval, gval, bval;
+l_uint32  val;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixGetRandomPixel");
+
+    if (pval) *pval = 0;
+    if (px) *px = 0;
+    if (py) *py = 0;
+    if (!pval && !px && !py)
+        return ERROR_INT("no output requested", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    x = rand() % w;
+    y = rand() % h;
+    if (px) *px = x;
+    if (py) *py = y;
+    if (pval) {
+        pixGetPixel(pix, x, y, &val);
+        if ((cmap = pixGetColormap(pix)) != NULL) {
+            pixcmapGetColor(cmap, val, &rval, &gval, &bval);
+            composeRGBPixel(rval, gval, bval, pval);
+        } else {
+            *pval = val;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixClearPixel()
+ *
+ *      Input:  pix
+ *              (x,y) pixel coords
+ *      Return: 0 if OK; 1 on error.
+ */
+l_int32
+pixClearPixel(PIX     *pix,
+              l_int32  x,
+              l_int32  y)
+{
+l_int32    w, h, d, wpl;
+l_uint32  *line, *data;
+
+    PROCNAME("pixClearPixel");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (x < 0 || x >= w)
+        return ERROR_INT("x out of bounds", procName, 1);
+    if (y < 0 || y >= h)
+        return ERROR_INT("y out of bounds", procName, 1);
+
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    line = data + y * wpl;
+    switch (d)
+    {
+    case 1:
+        CLEAR_DATA_BIT(line, x);
+        break;
+    case 2:
+        CLEAR_DATA_DIBIT(line, x);
+        break;
+    case 4:
+        CLEAR_DATA_QBIT(line, x);
+        break;
+    case 8:
+        SET_DATA_BYTE(line, x, 0);
+        break;
+    case 16:
+        SET_DATA_TWO_BYTES(line, x, 0);
+        break;
+    case 32:
+        line[x] = 0;
+        break;
+    default:
+        return ERROR_INT("depth must be in {1,2,4,8,16,32} bpp", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixFlipPixel()
+ *
+ *      Input:  pix
+ *              (x,y) pixel coords
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixFlipPixel(PIX     *pix,
+             l_int32  x,
+             l_int32  y)
+{
+l_int32    w, h, d, wpl;
+l_uint32   val;
+l_uint32  *line, *data;
+
+    PROCNAME("pixFlipPixel");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (x < 0 || x >= w)
+        return ERROR_INT("x out of bounds", procName, 1);
+    if (y < 0 || y >= h)
+        return ERROR_INT("y out of bounds", procName, 1);
+
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    line = data + y * wpl;
+    switch (d)
+    {
+    case 1:
+        val = GET_DATA_BIT(line, x);
+        if (val)
+            CLEAR_DATA_BIT(line, x);
+        else
+            SET_DATA_BIT(line, x);
+        break;
+    case 2:
+        val = GET_DATA_DIBIT(line, x);
+        val ^= 0x3;
+        SET_DATA_DIBIT(line, x, val);
+        break;
+    case 4:
+        val = GET_DATA_QBIT(line, x);
+        val ^= 0xf;
+        SET_DATA_QBIT(line, x, val);
+        break;
+    case 8:
+        val = GET_DATA_BYTE(line, x);
+        val ^= 0xff;
+        SET_DATA_BYTE(line, x, val);
+        break;
+    case 16:
+        val = GET_DATA_TWO_BYTES(line, x);
+        val ^= 0xffff;
+        SET_DATA_TWO_BYTES(line, x, val);
+        break;
+    case 32:
+        val = line[x] ^ 0xffffffff;
+        line[x] = val;
+        break;
+    default:
+        return ERROR_INT("depth must be in {1,2,4,8,16,32} bpp", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  setPixelLow()
+ *
+ *      Input:  line (ptr to beginning of line),
+ *              x (pixel location in line)
+ *              depth (bpp)
+ *              val (to be inserted)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Caution: input variables are not checked!
+ */
+void
+setPixelLow(l_uint32  *line,
+            l_int32    x,
+            l_int32    depth,
+            l_uint32   val)
+{
+    switch (depth)
+    {
+    case 1:
+        if (val)
+            SET_DATA_BIT(line, x);
+        else
+            CLEAR_DATA_BIT(line, x);
+        break;
+    case 2:
+        SET_DATA_DIBIT(line, x, val);
+        break;
+    case 4:
+        SET_DATA_QBIT(line, x, val);
+        break;
+    case 8:
+        SET_DATA_BYTE(line, x, val);
+        break;
+    case 16:
+        SET_DATA_TWO_BYTES(line, x, val);
+        break;
+    case 32:
+        line[x] = val;
+        break;
+    default:
+        fprintf(stderr, "illegal depth in setPixelLow()\n");
+    }
+
+    return;
+}
+
+
+/*-------------------------------------------------------------*
+ *                     Find black or white value               *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixGetBlackOrWhiteVal()
+ *
+ *      Input:  pixs (all depths; cmap ok)
+ *              op (L_GET_BLACK_VAL, L_GET_WHITE_VAL)
+ *              &val (<return> pixel value)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Side effect.  For a colormapped image, if the requested
+ *          color is not present and there is room to add it in the cmap,
+ *          it is added and the new index is returned.  If there is no room,
+ *          the index of the closest color in intensity is returned.
+ */
+l_int32
+pixGetBlackOrWhiteVal(PIX       *pixs,
+                      l_int32    op,
+                      l_uint32  *pval)
+{
+l_int32   d, val;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixGetBlackOrWhiteVal");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (op != L_GET_BLACK_VAL && op != L_GET_WHITE_VAL)
+        return ERROR_INT("invalid op", procName, 1);
+
+    cmap = pixGetColormap(pixs);
+    d = pixGetDepth(pixs);
+    if (!cmap) {
+        if ((d == 1 && op == L_GET_WHITE_VAL) ||
+            (d > 1 && op == L_GET_BLACK_VAL)) {  /* min val */
+            val = 0;
+        } else {  /* max val */
+            val = (d == 32) ? 0xffffff00 : (1 << d) - 1;
+        }
+    } else {  /* handle colormap */
+        if (op == L_GET_BLACK_VAL)
+            pixcmapAddBlackOrWhite(cmap, 0, &val);
+        else  /* L_GET_WHITE_VAL */
+            pixcmapAddBlackOrWhite(cmap, 1, &val);
+    }
+    *pval = val;
+
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *     Full image clear/set/set-to-arbitrary-value/invert      *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixClearAll()
+ *
+ *      Input:  pix (all depths; use cmapped with caution)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Clears all data to 0.  For 1 bpp, this is white; for grayscale
+ *          or color, this is black.
+ *      (2) Caution: for colormapped pix, this sets the color to the first
+ *          one in the colormap.  Be sure that this is the intended color!
+ */
+l_int32
+pixClearAll(PIX  *pix)
+{
+    PROCNAME("pixClearAll");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pixRasterop(pix, 0, 0, pixGetWidth(pix), pixGetHeight(pix),
+                PIX_CLR, NULL, 0, 0);
+    return 0;
+}
+
+
+/*!
+ *  pixSetAll()
+ *
+ *      Input:  pix (all depths; use cmapped with caution)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Sets all data to 1.  For 1 bpp, this is black; for grayscale
+ *          or color, this is white.
+ *      (2) Caution: for colormapped pix, this sets the pixel value to the
+ *          maximum value supported by the colormap: 2^d - 1.  However, this
+ *          color may not be defined, because the colormap may not be full.
+ */
+l_int32
+pixSetAll(PIX  *pix)
+{
+l_int32   n;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixSetAll");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if ((cmap = pixGetColormap(pix)) != NULL) {
+        n = pixcmapGetCount(cmap);
+        if (n < cmap->nalloc)  /* cmap is not full */
+            return ERROR_INT("cmap entry does not exist", procName, 1);
+    }
+
+    pixRasterop(pix, 0, 0, pixGetWidth(pix), pixGetHeight(pix),
+                PIX_SET, NULL, 0, 0);
+    return 0;
+}
+
+
+/*!
+ *  pixSetAllGray()
+ *
+ *      Input:  pix (all depths, cmap ok)
+ *              grayval (in range 0 ... 255)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) N.B.  For all images, @grayval == 0 represents black and
+ *          @grayval == 255 represents white.
+ *      (2) For depth < 8, we do our best to approximate the gray level.
+ *          For 1 bpp images, any @grayval < 128 is black; >= 128 is white.
+ *          For 32 bpp images, each r,g,b component is set to @grayval,
+ *          and the alpha component is preserved.
+ *      (3) If pix is colormapped, it adds the gray value, replicated in
+ *          all components, to the colormap if it's not there and there
+ *          is room.  If the colormap is full, it finds the closest color in
+ *          L2 distance of components.  This index is written to all pixels.
+ */
+l_int32
+pixSetAllGray(PIX     *pix,
+              l_int32  grayval)
+{
+l_int32   d, spp, index;
+l_uint32  val32;
+PIX      *alpha;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixSetAllGray");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (grayval < 0) {
+        L_WARNING("grayval < 0; setting to 0\n", procName);
+        grayval = 0;
+    } else if (grayval > 255) {
+        L_WARNING("grayval > 255; setting to 255\n", procName);
+        grayval = 255;
+    }
+
+        /* Handle the colormap case */
+    cmap = pixGetColormap(pix);
+    if (cmap) {
+        pixcmapAddNearestColor(cmap, grayval, grayval, grayval, &index);
+        pixSetAllArbitrary(pix, index);
+        return 0;
+    }
+
+        /* Non-cmapped */
+    d = pixGetDepth(pix);
+    spp = pixGetSpp(pix);
+    if (d == 1) {
+        if (grayval < 128)  /* black */
+            pixSetAll(pix);
+        else
+            pixClearAll(pix);  /* white */
+    } else if (d < 8) {
+        grayval >>= 8 - d;
+        pixSetAllArbitrary(pix, grayval);
+    } else if (d == 8) {
+        pixSetAllArbitrary(pix, grayval);
+    } else if (d == 16) {
+        grayval |= (grayval << 8);
+        pixSetAllArbitrary(pix, grayval);
+    } else if (d == 32 && spp == 3) {
+        composeRGBPixel(grayval, grayval, grayval, &val32);
+        pixSetAllArbitrary(pix, val32);
+    } else if (d == 32 && spp == 4) {
+        alpha = pixGetRGBComponent(pix, L_ALPHA_CHANNEL);
+        composeRGBPixel(grayval, grayval, grayval, &val32);
+        pixSetAllArbitrary(pix, val32);
+        pixSetRGBComponent(pix, alpha, L_ALPHA_CHANNEL);
+        pixDestroy(&alpha);
+    } else {
+        L_ERROR("invalid depth: %d\n", procName, d);
+        return 1;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixSetAllArbitrary()
+ *
+ *      Input:  pix (all depths; use cmapped with caution)
+ *              val  (value to set all pixels)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Caution!  For colormapped pix, @val is used as an index
+ *          into a colormap.  Be sure that index refers to the intended color.
+ *          If the color is not in the colormap, you should first add it
+ *          and then call this function.
+ */
+l_int32
+pixSetAllArbitrary(PIX      *pix,
+                   l_uint32  val)
+{
+l_int32    n, i, j, w, h, d, wpl, npix;
+l_uint32   maxval, wordval;
+l_uint32  *data, *line;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixSetAllArbitrary");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+        /* If colormapped, make sure that val is less than the size
+         * of the cmap array. */
+    if ((cmap = pixGetColormap(pix)) != NULL) {
+        n = pixcmapGetCount(cmap);
+        if (val >= n) {
+            L_WARNING("index not in colormap; using last color\n", procName);
+            val = n - 1;
+        }
+    }
+
+        /* Make sure val isn't too large for the pixel depth.
+         * If it is too large, set the pixel color to white.  */
+    pixGetDimensions(pix, &w, &h, &d);
+    maxval = (d == 32) ? 0xffffff00 : (1 << d) - 1;
+    if (val > maxval) {
+        L_WARNING("val too large for depth; using maxval\n", procName);
+        val = maxval;
+    }
+
+        /* Set up word to tile with */
+    wordval = 0;
+    npix = 32 / d;    /* number of pixels per 32 bit word */
+    for (j = 0; j < npix; j++)
+        wordval |= (val << (j * d));
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < wpl; j++) {
+            *(line + j) = wordval;
+        }
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixSetBlackOrWhite()
+ *
+ *      Input:  pixs (all depths; cmap ok)
+ *              op (L_SET_BLACK, L_SET_WHITE)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Function for setting all pixels in an image to either black
+ *          or white.
+ *      (2) If pixs is colormapped, it adds black or white to the
+ *          colormap if it's not there and there is room.  If the colormap
+ *          is full, it finds the closest color in intensity.
+ *          This index is written to all pixels.
+ */
+l_int32
+pixSetBlackOrWhite(PIX     *pixs,
+                   l_int32  op)
+{
+l_int32   d, index;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixSetBlackOrWhite");
+
+    if (!pixs)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (op != L_SET_BLACK && op != L_SET_WHITE)
+        return ERROR_INT("invalid op", procName, 1);
+
+    cmap = pixGetColormap(pixs);
+    d = pixGetDepth(pixs);
+    if (!cmap) {
+        if ((d == 1 && op == L_SET_BLACK) || (d > 1 && op == L_SET_WHITE))
+            pixSetAll(pixs);
+        else
+            pixClearAll(pixs);
+    } else {  /* handle colormap */
+        if (op == L_SET_BLACK)
+            pixcmapAddBlackOrWhite(cmap, 0, &index);
+        else  /* L_SET_WHITE */
+            pixcmapAddBlackOrWhite(cmap, 1, &index);
+        pixSetAllArbitrary(pixs, index);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixSetComponentArbitrary()
+ *
+ *      Input:  pix (32 bpp)
+ *              comp (COLOR_RED, COLOR_GREEN, COLOR_BLUE, L_ALPHA_CHANNEL)
+ *              val  (value to set this component)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) For example, this can be used to set the alpha component to opaque:
+ *              pixSetComponentArbitrary(pix, L_ALPHA_CHANNEL, 255)
+ */
+l_int32
+pixSetComponentArbitrary(PIX     *pix,
+                         l_int32  comp,
+                         l_int32  val)
+{
+l_int32    i, nwords;
+l_uint32   mask1, mask2;
+l_uint32  *data;
+
+    PROCNAME("pixSetComponentArbitrary");
+
+    if (!pix || pixGetDepth(pix) != 32)
+        return ERROR_INT("pix not defined or not 32 bpp", procName, 1);
+    if (comp != COLOR_RED && comp != COLOR_GREEN && comp != COLOR_BLUE &&
+        comp != L_ALPHA_CHANNEL)
+        return ERROR_INT("invalid component", procName, 1);
+    if (val < 0 || val > 255)
+        return ERROR_INT("val not in [0 ... 255]", procName, 1);
+
+    mask1 = ~(255 << (8 * (3 - comp)));
+    mask2 = val << (8 * (3 - comp));
+    nwords = pixGetHeight(pix) * pixGetWpl(pix);
+    data = pixGetData(pix);
+    for (i = 0; i < nwords; i++) {
+        data[i] &= mask1;  /* clear out the component */
+        data[i] |= mask2;  /* insert the new component value */
+    }
+
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *     Rectangular region clear/set/set-to-arbitrary-value     *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixClearInRect()
+ *
+ *      Input:  pix (all depths; can be cmapped)
+ *              box (in which all pixels will be cleared)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Clears all data in rect to 0.  For 1 bpp, this is white;
+ *          for grayscale or color, this is black.
+ *      (2) Caution: for colormapped pix, this sets the color to the first
+ *          one in the colormap.  Be sure that this is the intended color!
+ */
+l_int32
+pixClearInRect(PIX  *pix,
+               BOX  *box)
+{
+l_int32  x, y, w, h;
+
+    PROCNAME("pixClearInRect");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+
+    boxGetGeometry(box, &x, &y, &w, &h);
+    pixRasterop(pix, x, y, w, h, PIX_CLR, NULL, 0, 0);
+    return 0;
+}
+
+
+/*!
+ *  pixSetInRect()
+ *
+ *      Input:  pix (all depths, can be cmapped)
+ *              box (in which all pixels will be set)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Sets all data in rect to 1.  For 1 bpp, this is black;
+ *          for grayscale or color, this is white.
+ *      (2) Caution: for colormapped pix, this sets the pixel value to the
+ *          maximum value supported by the colormap: 2^d - 1.  However, this
+ *          color may not be defined, because the colormap may not be full.
+ */
+l_int32
+pixSetInRect(PIX  *pix,
+             BOX  *box)
+{
+l_int32   n, x, y, w, h;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixSetInRect");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if ((cmap = pixGetColormap(pix)) != NULL) {
+        n = pixcmapGetCount(cmap);
+        if (n < cmap->nalloc)  /* cmap is not full */
+            return ERROR_INT("cmap entry does not exist", procName, 1);
+    }
+
+    boxGetGeometry(box, &x, &y, &w, &h);
+    pixRasterop(pix, x, y, w, h, PIX_SET, NULL, 0, 0);
+    return 0;
+}
+
+
+/*!
+ *  pixSetInRectArbitrary()
+ *
+ *      Input:  pix (all depths; can be cmapped)
+ *              box (in which all pixels will be set to val)
+ *              val  (value to set all pixels)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) For colormapped pix, be sure the value is the intended
+ *          one in the colormap.
+ *      (2) Caution: for colormapped pix, this sets each pixel in the
+ *          rect to the color at the index equal to val.  Be sure that
+ *          this index exists in the colormap and that it is the intended one!
+ */
+l_int32
+pixSetInRectArbitrary(PIX      *pix,
+                      BOX      *box,
+                      l_uint32  val)
+{
+l_int32    n, x, y, xstart, xend, ystart, yend, bw, bh, w, h, d, wpl, maxval;
+l_uint32  *data, *line;
+BOX       *boxc;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixSetInRectArbitrary");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d !=8 && d != 16 && d != 32)
+        return ERROR_INT("depth must be in {1,2,4,8,16,32} bpp", procName, 1);
+    if ((cmap = pixGetColormap(pix)) != NULL) {
+        n = pixcmapGetCount(cmap);
+        if (val >= n) {
+            L_WARNING("index not in colormap; using last color\n", procName);
+            val = n - 1;
+        }
+    }
+
+    maxval = (d == 32) ? 0xffffff00 : (1 << d) - 1;
+    if (val > maxval) val = maxval;
+
+        /* Handle the simple cases: the min and max values */
+    if (val == 0) {
+        pixClearInRect(pix, box);
+        return 0;
+    }
+    if (d == 1 ||
+        (d == 2 && val == 3) ||
+        (d == 4 && val == 0xf) ||
+        (d == 8 && val == 0xff) ||
+        (d == 16 && val == 0xffff) ||
+        (d == 32 && ((val ^ 0xffffff00) >> 8 == 0))) {
+        pixSetInRect(pix, box);
+        return 0;
+    }
+
+        /* Find the overlap of box with the input pix */
+    if ((boxc = boxClipToRectangle(box, w, h)) == NULL)
+        return ERROR_INT("no overlap of box with image", procName, 1);
+    boxGetGeometry(boxc, &xstart, &ystart, &bw, &bh);
+    xend = xstart + bw - 1;
+    yend = ystart + bh - 1;
+    boxDestroy(&boxc);
+
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    for (y = ystart; y <= yend; y++) {
+        line = data + y * wpl;
+        for (x = xstart; x <= xend; x++) {
+            switch(d)
+            {
+            case 2:
+                SET_DATA_DIBIT(line, x, val);
+                break;
+            case 4:
+                SET_DATA_QBIT(line, x, val);
+                break;
+            case 8:
+                SET_DATA_BYTE(line, x, val);
+                break;
+            case 16:
+                SET_DATA_TWO_BYTES(line, x, val);
+                break;
+            case 32:
+                line[x] = val;
+                break;
+            default:
+                return ERROR_INT("depth not 2|4|8|16|32 bpp", procName, 1);
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixBlendInRect()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              box (<optional> in which all pixels will be blended)
+ *              val  (blend value; 0xrrggbb00)
+ *              fract (fraction of color to be blended with each pixel in pixs)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place function.  It blends the input color @val
+ *          with the pixels in pixs in the specified rectangle.
+ *          If no rectangle is specified, it blends over the entire image.
+ */
+l_int32
+pixBlendInRect(PIX       *pixs,
+               BOX       *box,
+               l_uint32   val,
+               l_float32  fract)
+{
+l_int32    i, j, bx, by, bw, bh, w, h, wpls;
+l_int32    prval, pgval, pbval, rval, gval, bval;
+l_uint32   val32;
+l_uint32  *datas, *lines;
+
+    PROCNAME("pixBlendInRect");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not defined or not 32 bpp", procName, 1);
+
+    extractRGBValues(val, &rval, &gval, &bval);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if (!box) {
+        for (i = 0; i < h; i++) {   /* scan over box */
+            lines = datas +  i * wpls;
+            for (j = 0; j < w; j++) {
+                val32 = *(lines + j);
+                extractRGBValues(val32, &prval, &pgval, &pbval);
+                prval = (l_int32)((1. - fract) * prval + fract * rval);
+                pgval = (l_int32)((1. - fract) * pgval + fract * gval);
+                pbval = (l_int32)((1. - fract) * pbval + fract * bval);
+                composeRGBPixel(prval, pgval, pbval, &val32);
+                *(lines + j) = val32;
+            }
+        }
+        return 0;
+    }
+
+    boxGetGeometry(box, &bx, &by, &bw, &bh);
+    for (i = 0; i < bh; i++) {   /* scan over box */
+        if (by + i < 0 || by + i >= h) continue;
+        lines = datas + (by + i) * wpls;
+        for (j = 0; j < bw; j++) {
+            if (bx + j < 0 || bx + j >= w) continue;
+            val32 = *(lines + bx + j);
+            extractRGBValues(val32, &prval, &pgval, &pbval);
+            prval = (l_int32)((1. - fract) * prval + fract * rval);
+            pgval = (l_int32)((1. - fract) * pgval + fract * gval);
+            pbval = (l_int32)((1. - fract) * pbval + fract * bval);
+            composeRGBPixel(prval, pgval, pbval, &val32);
+            *(lines + bx + j) = val32;
+        }
+    }
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *                         Set pad bits                        *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixSetPadBits()
+ *
+ *      Input:  pix (1, 2, 4, 8, 16, 32 bpp)
+ *              val  (0 or 1)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The pad bits are the bits that expand each scanline to a
+ *          multiple of 32 bits.  They are usually not used in
+ *          image processing operations.  When boundary conditions
+ *          are important, as in seedfill, they must be set properly.
+ *      (2) This sets the value of the pad bits (if any) in the last
+ *          32-bit word in each scanline.
+ *      (3) For 32 bpp pix, there are no pad bits, so this is a no-op.
+ */
+l_int32
+pixSetPadBits(PIX     *pix,
+              l_int32  val)
+{
+l_int32    i, w, h, d, wpl, endbits, fullwords;
+l_uint32   mask;
+l_uint32  *data, *pword;
+
+    PROCNAME("pixSetPadBits");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d == 32)  /* no padding exists for 32 bpp */
+        return 0;
+
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    endbits = 32 - (((l_int64)w * d) % 32);
+    if (endbits == 32)  /* no partial word */
+        return 0;
+    fullwords = (1LL * w * d) / 32;
+    mask = rmask32[endbits];
+    if (val == 0)
+        mask = ~mask;
+
+    for (i = 0; i < h; i++) {
+        pword = data + i * wpl + fullwords;
+        if (val == 0) /* clear */
+            *pword = *pword & mask;
+        else  /* set */
+            *pword = *pword | mask;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixSetPadBitsBand()
+ *
+ *      Input:  pix (1, 2, 4, 8, 16, 32 bpp)
+ *              by  (starting y value of band)
+ *              bh  (height of band)
+ *              val  (0 or 1)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The pad bits are the bits that expand each scanline to a
+ *          multiple of 32 bits.  They are usually not used in
+ *          image processing operations.  When boundary conditions
+ *          are important, as in seedfill, they must be set properly.
+ *      (2) This sets the value of the pad bits (if any) in the last
+ *          32-bit word in each scanline, within the specified
+ *          band of raster lines.
+ *      (3) For 32 bpp pix, there are no pad bits, so this is a no-op.
+ */
+l_int32
+pixSetPadBitsBand(PIX     *pix,
+                  l_int32  by,
+                  l_int32  bh,
+                  l_int32  val)
+{
+l_int32    i, w, h, d, wpl, endbits, fullwords;
+l_uint32   mask;
+l_uint32  *data, *pword;
+
+    PROCNAME("pixSetPadBitsBand");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d == 32)  /* no padding exists for 32 bpp */
+        return 0;
+
+    if (by < 0)
+        by = 0;
+    if (by >= h)
+        return ERROR_INT("start y not in image", procName, 1);
+    if (by + bh > h)
+        bh = h - by;
+
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    endbits = 32 - (((l_int64)w * d) % 32);
+    if (endbits == 32)  /* no partial word */
+        return 0;
+    fullwords = (l_int64)w * d / 32;
+
+    mask = rmask32[endbits];
+    if (val == 0)
+        mask = ~mask;
+
+    for (i = by; i < by + bh; i++) {
+        pword = data + i * wpl + fullwords;
+        if (val == 0) /* clear */
+            *pword = *pword & mask;
+        else  /* set */
+            *pword = *pword | mask;
+    }
+
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *                       Set border pixels                     *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixSetOrClearBorder()
+ *
+ *      Input:  pixs (all depths)
+ *              left, right, top, bot (amount to set or clear)
+ *              operation (PIX_SET or PIX_CLR)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The border region is defined to be the region in the
+ *          image within a specific distance of each edge.  Here, we
+ *          allow the pixels within a specified distance of each
+ *          edge to be set independently.  This either sets or
+ *          clears all pixels in the border region.
+ *      (2) For binary images, use PIX_SET for black and PIX_CLR for white.
+ *      (3) For grayscale or color images, use PIX_SET for white
+ *          and PIX_CLR for black.
+ */
+l_int32
+pixSetOrClearBorder(PIX     *pixs,
+                    l_int32  left,
+                    l_int32  right,
+                    l_int32  top,
+                    l_int32  bot,
+                    l_int32  op)
+{
+l_int32  w, h;
+
+    PROCNAME("pixSetOrClearBorder");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (op != PIX_SET && op != PIX_CLR)
+        return ERROR_INT("op must be PIX_SET or PIX_CLR", procName, 1);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixRasterop(pixs, 0, 0, left, h, op, NULL, 0, 0);
+    pixRasterop(pixs, w - right, 0, right, h, op, NULL, 0, 0);
+    pixRasterop(pixs, 0, 0, w, top, op, NULL, 0, 0);
+    pixRasterop(pixs, 0, h - bot, w, bot, op, NULL, 0, 0);
+
+    return 0;
+}
+
+
+/*!
+ *  pixSetBorderVal()
+ *
+ *      Input:  pixs (8, 16 or 32 bpp)
+ *              left, right, top, bot (amount to set)
+ *              val (value to set at each border pixel)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The border region is defined to be the region in the
+ *          image within a specific distance of each edge.  Here, we
+ *          allow the pixels within a specified distance of each
+ *          edge to be set independently.  This sets the pixels
+ *          in the border region to the given input value.
+ *      (2) For efficiency, use pixSetOrClearBorder() if
+ *          you're setting the border to either black or white.
+ *      (3) If d != 32, the input value should be masked off
+ *          to the appropriate number of least significant bits.
+ *      (4) The code is easily generalized for 2 or 4 bpp.
+ */
+l_int32
+pixSetBorderVal(PIX      *pixs,
+                l_int32   left,
+                l_int32   right,
+                l_int32   top,
+                l_int32   bot,
+                l_uint32  val)
+{
+l_int32    w, h, d, wpls, i, j, bstart, rstart;
+l_uint32  *datas, *lines;
+
+    PROCNAME("pixSetBorderVal");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 16 && d != 32)
+        return ERROR_INT("depth must be 8, 16 or 32 bpp", procName, 1);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if (d == 8) {
+        val &= 0xff;
+        for (i = 0; i < top; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < w; j++)
+                SET_DATA_BYTE(lines, j, val);
+        }
+        rstart = w - right;
+        bstart = h - bot;
+        for (i = top; i < bstart; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < left; j++)
+                SET_DATA_BYTE(lines, j, val);
+            for (j = rstart; j < w; j++)
+                SET_DATA_BYTE(lines, j, val);
+        }
+        for (i = bstart; i < h; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < w; j++)
+                SET_DATA_BYTE(lines, j, val);
+        }
+    } else if (d == 16) {
+        val &= 0xffff;
+        for (i = 0; i < top; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < w; j++)
+                SET_DATA_TWO_BYTES(lines, j, val);
+        }
+        rstart = w - right;
+        bstart = h - bot;
+        for (i = top; i < bstart; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < left; j++)
+                SET_DATA_TWO_BYTES(lines, j, val);
+            for (j = rstart; j < w; j++)
+                SET_DATA_TWO_BYTES(lines, j, val);
+        }
+        for (i = bstart; i < h; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < w; j++)
+                SET_DATA_TWO_BYTES(lines, j, val);
+        }
+    } else {   /* d == 32 */
+        for (i = 0; i < top; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < w; j++)
+                *(lines + j) = val;
+        }
+        rstart = w - right;
+        bstart = h - bot;
+        for (i = top; i < bstart; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < left; j++)
+                *(lines + j) = val;
+            for (j = rstart; j < w; j++)
+                *(lines + j) = val;
+        }
+        for (i = bstart; i < h; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < w; j++)
+                *(lines + j) = val;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixSetBorderRingVal()
+ *
+ *      Input:  pixs (any depth; cmap OK)
+ *              dist (distance from outside; must be > 0; first ring is 1)
+ *              val (value to set at each border pixel)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The rings are single-pixel-wide rectangular sets of
+ *          pixels at a given distance from the edge of the pix.
+ *          This sets all pixels in a given ring to a value.
+ */
+l_int32
+pixSetBorderRingVal(PIX      *pixs,
+                    l_int32   dist,
+                    l_uint32  val)
+{
+l_int32  w, h, d, i, j, xend, yend;
+
+    PROCNAME("pixSetBorderRingVal");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (dist < 1)
+        return ERROR_INT("dist must be > 0", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (w < 2 * dist + 1 || h < 2 * dist + 1)
+        return ERROR_INT("ring doesn't exist", procName, 1);
+    if (d < 32 && (val >= (1 << d)))
+        return ERROR_INT("invalid pixel value", procName, 1);
+
+    xend = w - dist;
+    yend = h - dist;
+    for (j = dist - 1; j <= xend; j++)
+        pixSetPixel(pixs, j, dist - 1, val);
+    for (j = dist - 1; j <= xend; j++)
+        pixSetPixel(pixs, j, yend, val);
+    for (i = dist - 1; i <= yend; i++)
+        pixSetPixel(pixs, dist - 1, i, val);
+    for (i = dist - 1; i <= yend; i++)
+        pixSetPixel(pixs, xend, i, val);
+
+    return 0;
+}
+
+
+/*!
+ *  pixSetMirroredBorder()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              left, right, top, bot (number of pixels to set)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This applies what is effectively mirror boundary conditions
+ *          to a border region in the image.  It is in-place.
+ *      (2) This is useful for setting pixels near the border to a
+ *          value representative of the near pixels to the interior.
+ *      (3) The general pixRasterop() is used for an in-place operation here
+ *          because there is no overlap between the src and dest rectangles.
+ */
+l_int32
+pixSetMirroredBorder(PIX     *pixs,
+                     l_int32  left,
+                     l_int32  right,
+                     l_int32  top,
+                     l_int32  bot)
+{
+l_int32  i, j, w, h;
+
+    PROCNAME("pixSetMirroredBorder");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    for (j = 0; j < left; j++)
+        pixRasterop(pixs, left - 1 - j, top, 1, h - top - bot, PIX_SRC,
+                    pixs, left + j, top);
+    for (j = 0; j < right; j++)
+        pixRasterop(pixs, w - right + j, top, 1, h - top - bot, PIX_SRC,
+                    pixs, w - right - 1 - j, top);
+    for (i = 0; i < top; i++)
+        pixRasterop(pixs, 0, top - 1 - i, w, 1, PIX_SRC,
+                    pixs, 0, top + i);
+    for (i = 0; i < bot; i++)
+        pixRasterop(pixs, 0, h - bot + i, w, 1, PIX_SRC,
+                    pixs, 0, h - bot - 1 - i);
+
+    return 0;
+}
+
+
+/*!
+ *  pixCopyBorder()
+ *
+ *      Input:  pixd (all depths; colormap ok; can be NULL)
+ *              pixs (same depth and size as pixd)
+ *              left, right, top, bot (number of pixels to copy)
+ *      Return: pixd, or null on error if pixd is not defined
+ *
+ *  Notes:
+ *      (1) pixd can be null, but otherwise it must be the same size
+ *          and depth as pixs.  Always returns pixd.
+ *      (1) This is useful in situations where by setting a few border
+ *          pixels we can avoid having to copy all pixels in pixs into
+ *          pixd as an initialization step for some operation.
+ */
+PIX *
+pixCopyBorder(PIX     *pixd,
+              PIX     *pixs,
+              l_int32  left,
+              l_int32  right,
+              l_int32  top,
+              l_int32  bot)
+{
+l_int32  w, h;
+
+    PROCNAME("pixCopyBorder");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+
+    if (pixd) {
+        if (pixd == pixs) {
+            L_WARNING("same: nothing to do\n", procName);
+            return pixd;
+        } else if (!pixSizesEqual(pixs, pixd)) {
+            return (PIX *)ERROR_PTR("pixs and pixd sizes differ",
+                                    procName, pixd);
+        }
+    } else {
+        if ((pixd = pixCreateTemplateNoInit(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, pixd);
+    }
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixRasterop(pixd, 0, 0, left, h, PIX_SRC, pixs, 0, 0);
+    pixRasterop(pixd, w - right, 0, right, h, PIX_SRC, pixs, w - right, 0);
+    pixRasterop(pixd, 0, 0, w, top, PIX_SRC, pixs, 0, 0);
+    pixRasterop(pixd, 0, h - bot, w, bot, PIX_SRC, pixs, 0, h - bot);
+
+    return pixd;
+}
+
+
+
+/*-------------------------------------------------------------*
+ *                     Add and remove border                   *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixAddBorder()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              npix (number of pixels to be added to each side)
+ *              val  (value of added border pixels)
+ *      Return: pixd (with the added exterior pixels), or null on error
+ *
+ *  Notes:
+ *      (1) See pixGetBlackOrWhiteVal() for values of black and white pixels.
+ */
+PIX *
+pixAddBorder(PIX      *pixs,
+             l_int32   npix,
+             l_uint32  val)
+{
+    PROCNAME("pixAddBorder");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (npix == 0)
+        return pixClone(pixs);
+    return pixAddBorderGeneral(pixs, npix, npix, npix, npix, val);
+}
+
+
+/*!
+ *  pixAddBlackOrWhiteBorder()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              left, right, top, bot  (number of pixels added)
+ *              op (L_GET_BLACK_VAL, L_GET_WHITE_VAL)
+ *      Return: pixd (with the added exterior pixels), or null on error
+ *
+ *  Notes:
+ *      (1) See pixGetBlackOrWhiteVal() for possible side effect (adding
+ *          a color to a colormap).
+ *      (2) The only complication is that pixs may have a colormap.
+ *          There are two ways to add the black or white border:
+ *          (a) As done here (simplest, most efficient)
+ *          (b) l_int32 ws, hs, d;
+ *              pixGetDimensions(pixs, &ws, &hs, &d);
+ *              Pix *pixd = pixCreate(ws + left + right, hs + top + bot, d);
+ *              PixColormap *cmap = pixGetColormap(pixs);
+ *              if (cmap != NULL)
+ *                  pixSetColormap(pixd, pixcmapCopy(cmap));
+ *              pixSetBlackOrWhite(pixd, L_SET_WHITE);  // uses cmap
+ *              pixRasterop(pixd, left, top, ws, hs, PIX_SET, pixs, 0, 0);
+ */
+PIX *
+pixAddBlackOrWhiteBorder(PIX     *pixs,
+                         l_int32  left,
+                         l_int32  right,
+                         l_int32  top,
+                         l_int32  bot,
+                         l_int32  op)
+{
+l_uint32  val;
+
+    PROCNAME("pixAddBlackOrWhiteBorder");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (op != L_GET_BLACK_VAL && op != L_GET_WHITE_VAL)
+        return (PIX *)ERROR_PTR("invalid op", procName, NULL);
+
+    pixGetBlackOrWhiteVal(pixs, op, &val);
+    return pixAddBorderGeneral(pixs, left, right, top, bot, val);
+}
+
+
+/*!
+ *  pixAddBorderGeneral()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              left, right, top, bot  (number of pixels added)
+ *              val   (value of added border pixels)
+ *      Return: pixd (with the added exterior pixels), or null on error
+ *
+ *  Notes:
+ *      (1) For binary images:
+ *             white:  val = 0
+ *             black:  val = 1
+ *          For grayscale images:
+ *             white:  val = 2 ** d - 1
+ *             black:  val = 0
+ *          For rgb color images:
+ *             white:  val = 0xffffff00
+ *             black:  val = 0
+ *          For colormapped images, set val to the appropriate colormap index.
+ *      (2) If the added border is either black or white, you can use
+ *             pixAddBlackOrWhiteBorder()
+ *          The black and white values for all images can be found with
+ *             pixGetBlackOrWhiteVal()
+ *          which, if pixs is cmapped, may add an entry to the colormap.
+ *          Alternatively, if pixs has a colormap, you can find the index
+ *          of the pixel whose intensity is closest to white or black:
+ *             white: pixcmapGetRankIntensity(cmap, 1.0, &index);
+ *             black: pixcmapGetRankIntensity(cmap, 0.0, &index);
+ *          and use that for val.
+ */
+PIX *
+pixAddBorderGeneral(PIX      *pixs,
+                    l_int32   left,
+                    l_int32   right,
+                    l_int32   top,
+                    l_int32   bot,
+                    l_uint32  val)
+{
+l_int32  ws, hs, wd, hd, d, maxval, op;
+PIX     *pixd;
+
+    PROCNAME("pixAddBorderGeneral");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (left < 0 || right < 0 || top < 0 || bot < 0)
+        return (PIX *)ERROR_PTR("negative border added!", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, &d);
+    wd = ws + left + right;
+    hd = hs + top + bot;
+    if ((pixd = pixCreateNoInit(wd, hd, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyColormap(pixd, pixs);
+
+        /* Set the new border pixels */
+    maxval = (d == 32) ? 0xffffff00 : (1 << d) - 1;
+    op = UNDEF;
+    if (val == 0)
+        op = PIX_CLR;
+    else if (val >= maxval)
+        op = PIX_SET;
+    if (op == UNDEF) {
+        pixSetAllArbitrary(pixd, val);
+    } else {  /* just set or clear the border pixels */
+        pixRasterop(pixd, 0, 0, left, hd, op, NULL, 0, 0);
+        pixRasterop(pixd, wd - right, 0, right, hd, op, NULL, 0, 0);
+        pixRasterop(pixd, 0, 0, wd, top, op, NULL, 0, 0);
+        pixRasterop(pixd, 0, hd - bot, wd, bot, op, NULL, 0, 0);
+    }
+
+        /* Copy pixs into the interior */
+    pixRasterop(pixd, left, top, ws, hs, PIX_SRC, pixs, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ *  pixRemoveBorder()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              npix (number to be removed from each of the 4 sides)
+ *      Return: pixd (with pixels removed around border), or null on error
+ */
+PIX *
+pixRemoveBorder(PIX     *pixs,
+                l_int32  npix)
+{
+    PROCNAME("pixRemoveBorder");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (npix == 0)
+        return pixClone(pixs);
+    return pixRemoveBorderGeneral(pixs, npix, npix, npix, npix);
+}
+
+
+/*!
+ *  pixRemoveBorderGeneral()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              left, right, top, bot  (number of pixels removed)
+ *      Return: pixd (with pixels removed around border), or null on error
+ */
+PIX *
+pixRemoveBorderGeneral(PIX     *pixs,
+                       l_int32  left,
+                       l_int32  right,
+                       l_int32  top,
+                       l_int32  bot)
+{
+l_int32  ws, hs, wd, hd, d;
+PIX     *pixd;
+
+    PROCNAME("pixRemoveBorderGeneral");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (left < 0 || right < 0 || top < 0 || bot < 0)
+        return (PIX *)ERROR_PTR("negative border removed!", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, &d);
+    wd = ws - left - right;
+    hd = hs - top - bot;
+    if (wd <= 0)
+        return (PIX *)ERROR_PTR("width must be > 0", procName, NULL);
+    if (hd <= 0)
+        return (PIX *)ERROR_PTR("height must be > 0", procName, NULL);
+    if ((pixd = pixCreateNoInit(wd, hd, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopySpp(pixd, pixs);
+    pixCopyColormap(pixd, pixs);
+
+    pixRasterop(pixd, 0, 0, wd, hd, PIX_SRC, pixs, left, top);
+    if (pixGetDepth(pixs) == 32 && pixGetSpp(pixs) == 4)
+        pixShiftAndTransferAlpha(pixd, pixs, -left, -top);
+    return pixd;
+}
+
+
+/*!
+ *  pixRemoveBorderToSize()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              wd  (target width; use 0 if only removing from height)
+ *              hd  (target height; use 0 if only removing from width)
+ *      Return: pixd (with pixels removed around border), or null on error
+ *
+ *  Notes:
+ *      (1) Removes pixels as evenly as possible from the sides of the
+ *          image, leaving the central part.
+ *      (2) Returns clone if no pixels requested removed, or the target
+ *          sizes are larger than the image.
+ */
+PIX *
+pixRemoveBorderToSize(PIX     *pixs,
+                      l_int32  wd,
+                      l_int32  hd)
+{
+l_int32  w, h, top, bot, left, right, delta;
+
+    PROCNAME("pixRemoveBorderToSize");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((wd <= 0 || wd >= w) && (hd <= 0 || hd >= h))
+        return pixClone(pixs);
+
+    left = right = (w - wd) / 2;
+    delta = w - 2 * left - wd;
+    right += delta;
+    top = bot = (h - hd) / 2;
+    delta = h - hd - 2 * top;
+    bot += delta;
+    if (wd <= 0 || wd > w)
+        left = right = 0;
+    else if (hd <= 0 || hd > h)
+        top = bot = 0;
+
+    return pixRemoveBorderGeneral(pixs, left, right, top, bot);
+}
+
+
+/*!
+ *  pixAddMirroredBorder()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              left, right, top, bot (number of pixels added)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This applies what is effectively mirror boundary conditions.
+ *          For the added border pixels in pixd, the pixels in pixs
+ *          near the border are mirror-copied into the border region.
+ *      (2) This is useful for avoiding special operations near
+ *          boundaries when doing image processing operations
+ *          such as rank filters and convolution.  In use, one first
+ *          adds mirrored pixels to each side of the image.  The number
+ *          of pixels added on each side is half the filter dimension.
+ *          Then the image processing operations proceed over a
+ *          region equal to the size of the original image, and
+ *          write directly into a dest pix of the same size as pixs.
+ *      (3) The general pixRasterop() is used for an in-place operation here
+ *          because there is no overlap between the src and dest rectangles.
+ */
+PIX  *
+pixAddMirroredBorder(PIX      *pixs,
+                      l_int32  left,
+                      l_int32  right,
+                      l_int32  top,
+                      l_int32  bot)
+{
+l_int32  i, j, w, h;
+PIX     *pixd;
+
+    PROCNAME("pixAddMirroredBorder");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (left > w || right > w || top > h || bot > h)
+        return (PIX *)ERROR_PTR("border too large", procName, NULL);
+
+        /* Set pixels on left, right, top and bottom, in that order */
+    pixd = pixAddBorderGeneral(pixs, left, right, top, bot, 0);
+    for (j = 0; j < left; j++)
+        pixRasterop(pixd, left - 1 - j, top, 1, h, PIX_SRC,
+                    pixd, left + j, top);
+    for (j = 0; j < right; j++)
+        pixRasterop(pixd, left + w + j, top, 1, h, PIX_SRC,
+                    pixd, left + w - 1 - j, top);
+    for (i = 0; i < top; i++)
+        pixRasterop(pixd, 0, top - 1 - i, left + w + right, 1, PIX_SRC,
+                    pixd, 0, top + i);
+    for (i = 0; i < bot; i++)
+        pixRasterop(pixd, 0, top + h + i, left + w + right, 1, PIX_SRC,
+                    pixd, 0, top + h - 1 - i);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixAddRepeatedBorder()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              left, right, top, bot (number of pixels added)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This applies a repeated border, as if the central part of
+ *          the image is tiled over the plane.  So, for example, the
+ *          pixels in the left border come from the right side of the image.
+ *      (2) The general pixRasterop() is used for an in-place operation here
+ *          because there is no overlap between the src and dest rectangles.
+ */
+PIX  *
+pixAddRepeatedBorder(PIX      *pixs,
+                     l_int32  left,
+                     l_int32  right,
+                     l_int32  top,
+                     l_int32  bot)
+{
+l_int32  w, h;
+PIX     *pixd;
+
+    PROCNAME("pixAddRepeatedBorder");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (left > w || right > w || top > h || bot > h)
+        return (PIX *)ERROR_PTR("border too large", procName, NULL);
+
+    pixd = pixAddBorderGeneral(pixs, left, right, top, bot, 0);
+
+        /* Set pixels on left, right, top and bottom, in that order */
+    pixRasterop(pixd, 0, top, left, h, PIX_SRC, pixd, w, top);
+    pixRasterop(pixd, left + w, top, right, h, PIX_SRC, pixd, left, top);
+    pixRasterop(pixd, 0, 0, left + w + right, top, PIX_SRC, pixd, 0, h);
+    pixRasterop(pixd, 0, top + h, left + w + right, bot, PIX_SRC, pixd, 0, top);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixAddMixedBorder()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              left, right, top, bot (number of pixels added)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This applies mirrored boundary conditions horizontally
+ *          and repeated b.c. vertically.
+ *      (2) It is specifically used for avoiding special operations
+ *          near boundaries when convolving a hue-saturation histogram
+ *          with a given window size.  The repeated b.c. are used
+ *          vertically for hue, and the mirrored b.c. are used
+ *          horizontally for saturation.  The number of pixels added
+ *          on each side is approximately (but not quite) half the
+ *          filter dimension.  The image processing operations can
+ *          then proceed over a region equal to the size of the original
+ *          image, and write directly into a dest pix of the same
+ *          size as pixs.
+ *      (3) The general pixRasterop() can be used for an in-place
+ *          operation here because there is no overlap between the
+ *          src and dest rectangles.
+ */
+PIX  *
+pixAddMixedBorder(PIX      *pixs,
+                  l_int32  left,
+                  l_int32  right,
+                  l_int32  top,
+                  l_int32  bot)
+{
+l_int32  j, w, h;
+PIX     *pixd;
+
+    PROCNAME("pixAddMixedBorder");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (left > w || right > w || top > h || bot > h)
+        return (PIX *)ERROR_PTR("border too large", procName, NULL);
+
+        /* Set mirrored pixels on left and right;
+         * then set repeated pixels on top and bottom. */
+    pixd = pixAddBorderGeneral(pixs, left, right, top, bot, 0);
+    for (j = 0; j < left; j++)
+        pixRasterop(pixd, left - 1 - j, top, 1, h, PIX_SRC,
+                    pixd, left + j, top);
+    for (j = 0; j < right; j++)
+        pixRasterop(pixd, left + w + j, top, 1, h, PIX_SRC,
+                    pixd, left + w - 1 - j, top);
+    pixRasterop(pixd, 0, 0, left + w + right, top, PIX_SRC, pixd, 0, h);
+    pixRasterop(pixd, 0, top + h, left + w + right, bot, PIX_SRC, pixd, 0, top);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixAddContinuedBorder()
+ *
+ *      Input:  pixs
+ *              left, right, top, bot (pixels on each side to be added)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This adds pixels on each side whose values are equal to
+ *          the value on the closest boundary pixel.
+ */
+PIX *
+pixAddContinuedBorder(PIX     *pixs,
+                      l_int32  left,
+                      l_int32  right,
+                      l_int32  top,
+                      l_int32  bot)
+{
+l_int32  i, j, w, h;
+PIX     *pixd;
+
+    PROCNAME("pixAddContinuedBorder");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    pixd = pixAddBorderGeneral(pixs, left, right, top, bot, 0);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    for (j = 0; j < left; j++)
+        pixRasterop(pixd, j, top, 1, h, PIX_SRC, pixd, left, top);
+    for (j = 0; j < right; j++)
+        pixRasterop(pixd, left + w + j, top, 1, h,
+                    PIX_SRC, pixd, left + w - 1, top);
+    for (i = 0; i < top; i++)
+        pixRasterop(pixd, 0, i, left + w + right, 1, PIX_SRC, pixd, 0, top);
+    for (i = 0; i < bot; i++)
+        pixRasterop(pixd, 0, top + h + i, left + w + right, 1,
+                    PIX_SRC, pixd, 0, top + h - 1);
+
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------------*
+ *                     Helper functions using alpha                  *
+ *-------------------------------------------------------------------*/
+/*!
+ *  pixShiftAndTransferAlpha()
+ *
+ *      Input:  pixd  (32 bpp)
+ *              pixs  (32 bpp)
+ *              shiftx, shifty
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixShiftAndTransferAlpha(PIX       *pixd,
+                         PIX       *pixs,
+                         l_float32  shiftx,
+                         l_float32  shifty)
+{
+l_int32  w, h;
+PIX     *pix1, *pix2;
+
+    PROCNAME("pixShiftAndTransferAlpha");
+
+    if (!pixs || !pixd)
+        return ERROR_INT("pixs and pixd not both defined", procName, 1);
+    if (pixGetDepth(pixs) != 32 || pixGetSpp(pixs) != 4)
+        return ERROR_INT("pixs not 32 bpp and 4 spp", procName, 1);
+    if (pixGetDepth(pixd) != 32)
+        return ERROR_INT("pixd not 32 bpp", procName, 1);
+
+    if (shiftx == 0 && shifty == 0) {
+        pixCopyRGBComponent(pixd, pixs, L_ALPHA_CHANNEL);
+        return 0;
+    }
+
+    pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+    pixGetDimensions(pixd, &w, &h, NULL);
+    pix2 = pixCreate(w, h, 8);
+    pixRasterop(pix2, 0, 0, w, h, PIX_SRC, pix1, -shiftx, -shifty);
+    pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL);
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    return 0;
+}
+
+
+/*!
+ *  pixDisplayLayersRGBA()
+ *
+ *      Input:  pixs (cmap or 32 bpp rgba)
+ *              val (32 bit unsigned color to use as background)
+ *              maxw (max output image width; 0 for no scaling)
+ *      Return: pixd (showing various image views), or null on error
+ *
+ *  Notes:
+ *      (1) Use @val == 0xffffff00 for white background.
+ *      (2) Three views are given:
+ *           - the image with a fully opaque alpha
+ *           - the alpha layer
+ *           - the image as it would appear with a white background.
+ */
+PIX *
+pixDisplayLayersRGBA(PIX      *pixs,
+                     l_uint32  val,
+                     l_int32   maxw)
+{
+l_int32    w, width;
+l_float32  scalefact;
+PIX       *pix1, *pix2, *pixd;
+PIXA      *pixa;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixDisplayLayersRGBA");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    cmap = pixGetColormap(pixs);
+    if (!cmap && !(pixGetDepth(pixs) == 32 && pixGetSpp(pixs) == 4))
+        return (PIX *)ERROR_PTR("pixs not cmap and not 32 bpp rgba",
+                                procName, NULL);
+    if ((w = pixGetWidth(pixs)) == 0)
+        return (PIX *)ERROR_PTR("pixs width 0 !!", procName, NULL);
+
+    if (cmap)
+        pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_WITH_ALPHA);
+    else
+        pix1 = pixCopy(NULL, pixs);
+
+        /* Scale if necessary so the output width is not larger than maxw */
+    scalefact = (maxw == 0) ? 1.0 : L_MIN(1.0, maxw / w);
+    width = (l_int32)(scalefact * w);
+
+    pixa = pixaCreate(3);
+    pixSetSpp(pix1, 3);
+    pixaAddPix(pixa, pix1, L_INSERT);  /* show the rgb values */
+    pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+    pix2 = pixConvertTo32(pix1);
+    pixaAddPix(pixa, pix2, L_INSERT);  /* show the alpha channel */
+    pixDestroy(&pix1);
+    pix1 = pixAlphaBlendUniform(pixs, (val & 0xffffff00));
+    pixaAddPix(pixa, pix1, L_INSERT);  /* with @val color bg showing */
+    pixd = pixaDisplayTiledInRows(pixa, 32, width, scalefact, 0, 25, 2);
+    pixaDestroy(&pixa);
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ *                Color sample setting and extraction          *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixCreateRGBImage()
+ *
+ *      Input:  8 bpp red pix
+ *              8 bpp green pix
+ *              8 bpp blue pix
+ *      Return: 32 bpp pix, interleaved with 4 samples/pixel,
+ *              or null on error
+ *
+ *  Notes:
+ *      (1) the 4th byte, sometimes called the "alpha channel",
+ *          and which is often used for blending between different
+ *          images, is left with 0 value.
+ *      (2) see Note (4) in pix.h for details on storage of
+ *          8-bit samples within each 32-bit word.
+ *      (3) This implementation, setting the r, g and b components
+ *          sequentially, is much faster than setting them in parallel
+ *          by constructing an RGB dest pixel and writing it to dest.
+ *          The reason is there are many more cache misses when reading
+ *          from 3 input images simultaneously.
+ */
+PIX *
+pixCreateRGBImage(PIX  *pixr,
+                  PIX  *pixg,
+                  PIX  *pixb)
+{
+l_int32  wr, wg, wb, hr, hg, hb, dr, dg, db;
+PIX     *pixd;
+
+    PROCNAME("pixCreateRGBImage");
+
+    if (!pixr)
+        return (PIX *)ERROR_PTR("pixr not defined", procName, NULL);
+    if (!pixg)
+        return (PIX *)ERROR_PTR("pixg not defined", procName, NULL);
+    if (!pixb)
+        return (PIX *)ERROR_PTR("pixb not defined", procName, NULL);
+    pixGetDimensions(pixr, &wr, &hr, &dr);
+    pixGetDimensions(pixg, &wg, &hg, &dg);
+    pixGetDimensions(pixb, &wb, &hb, &db);
+    if (dr != 8 || dg != 8 || db != 8)
+        return (PIX *)ERROR_PTR("input pix not all 8 bpp", procName, NULL);
+    if (wr != wg || wr != wb)
+        return (PIX *)ERROR_PTR("widths not the same", procName, NULL);
+    if (hr != hg || hr != hb)
+        return (PIX *)ERROR_PTR("heights not the same", procName, NULL);
+
+    if ((pixd = pixCreate(wr, hr, 32)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixr);
+    pixSetRGBComponent(pixd, pixr, COLOR_RED);
+    pixSetRGBComponent(pixd, pixg, COLOR_GREEN);
+    pixSetRGBComponent(pixd, pixb, COLOR_BLUE);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixGetRGBComponent()
+ *
+ *      Input:  pixs (32 bpp, or colormapped)
+ *              comp (one of {COLOR_RED, COLOR_GREEN, COLOR_BLUE,
+ *                    L_ALPHA_CHANNEL})
+ *      Return: pixd (the selected 8 bpp component image of the
+ *                    input 32 bpp image) or null on error
+ *
+ *  Notes:
+ *      (1) Three calls to this function generate the r, g and b 8 bpp
+ *          component images.  This is much faster than generating the
+ *          three images in parallel, by extracting a src pixel and setting
+ *          the pixels of each component image from it.  The reason is
+ *          there are many more cache misses when writing to three
+ *          output images simultaneously.
+ */
+PIX *
+pixGetRGBComponent(PIX     *pixs,
+                   l_int32  comp)
+{
+l_int32    i, j, w, h, wpls, wpld, val;
+l_uint32  *lines, *lined;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixGetRGBComponent");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs))
+        return pixGetRGBComponentCmap(pixs, comp);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (comp != COLOR_RED && comp != COLOR_GREEN &&
+        comp != COLOR_BLUE && comp != L_ALPHA_CHANNEL)
+        return (PIX *)ERROR_PTR("invalid comp", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(lines + j, comp);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixSetRGBComponent()
+ *
+ *      Input:  pixd  (32 bpp)
+ *              pixs  (8 bpp)
+ *              comp  (one of the set: {COLOR_RED, COLOR_GREEN,
+ *                                      COLOR_BLUE, L_ALPHA_CHANNEL})
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This places the 8 bpp pixel in pixs into the
+ *          specified component (properly interleaved) in pixd,
+ *      (2) The two images are registered to the UL corner; the sizes
+ *          need not be the same, but a warning is issued if they differ.
+ */
+l_int32
+pixSetRGBComponent(PIX     *pixd,
+                   PIX     *pixs,
+                   l_int32  comp)
+{
+l_uint8    srcbyte;
+l_int32    i, j, w, h, ws, hs, wd, hd;
+l_int32    wpls, wpld;
+l_uint32  *lines, *lined;
+l_uint32  *datas, *datad;
+
+    PROCNAME("pixSetRGBComponent");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixd) != 32)
+        return ERROR_INT("pixd not 32 bpp", procName, 1);
+    if (pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not 8 bpp", procName, 1);
+    if (comp != COLOR_RED && comp != COLOR_GREEN &&
+        comp != COLOR_BLUE && comp != L_ALPHA_CHANNEL)
+        return ERROR_INT("invalid comp", procName, 1);
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    pixGetDimensions(pixd, &wd, &hd, NULL);
+    if (ws != wd || hs != hd)
+        L_WARNING("images sizes not equal\n", procName);
+    w = L_MIN(ws, wd);
+    h = L_MIN(hs, hd);
+    if (comp == L_ALPHA_CHANNEL)
+        pixSetSpp(pixd, 4);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            srcbyte = GET_DATA_BYTE(lines, j);
+            SET_DATA_BYTE(lined + j, comp, srcbyte);
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixGetRGBComponentCmap()
+ *
+ *      Input:  pixs  (colormapped)
+ *              comp  (one of the set: {COLOR_RED, COLOR_GREEN, COLOR_BLUE})
+ *      Return: pixd  (the selected 8 bpp component image of the
+ *                     input cmapped image), or null on error
+ *
+ *  Notes:
+ *      (1) In leptonica, we do not support alpha in colormaps.
+ */
+PIX *
+pixGetRGBComponentCmap(PIX     *pixs,
+                       l_int32  comp)
+{
+l_int32     i, j, w, h, val, index;
+l_int32     wplc, wpld;
+l_uint32   *linec, *lined;
+l_uint32   *datac, *datad;
+PIX        *pixc, *pixd;
+PIXCMAP    *cmap;
+RGBA_QUAD  *cta;
+
+    PROCNAME("pixGetRGBComponentCmap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if ((cmap = pixGetColormap(pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixs not cmapped", procName, NULL);
+    if (comp == L_ALPHA_CHANNEL)
+        return (PIX *)ERROR_PTR("alpha in cmaps not supported", procName, NULL);
+    if (comp != COLOR_RED && comp != COLOR_GREEN && comp != COLOR_BLUE)
+        return (PIX *)ERROR_PTR("invalid comp", procName, NULL);
+
+        /* If not 8 bpp, make a cmapped 8 bpp pix */
+    if (pixGetDepth(pixs) == 8)
+        pixc = pixClone(pixs);
+    else
+        pixc = pixConvertTo8(pixs, TRUE);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreateNoInit(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    wplc = pixGetWpl(pixc);
+    wpld = pixGetWpl(pixd);
+    datac = pixGetData(pixc);
+    datad = pixGetData(pixd);
+    cta = (RGBA_QUAD *)cmap->array;
+
+    for (i = 0; i < h; i++) {
+        linec = datac + i * wplc;
+        lined = datad + i * wpld;
+        if (comp == COLOR_RED) {
+            for (j = 0; j < w; j++) {
+                index = GET_DATA_BYTE(linec, j);
+                val = cta[index].red;
+                SET_DATA_BYTE(lined, j, val);
+            }
+        } else if (comp == COLOR_GREEN) {
+            for (j = 0; j < w; j++) {
+                index = GET_DATA_BYTE(linec, j);
+                val = cta[index].green;
+                SET_DATA_BYTE(lined, j, val);
+            }
+        } else if (comp == COLOR_BLUE) {
+            for (j = 0; j < w; j++) {
+                index = GET_DATA_BYTE(linec, j);
+                val = cta[index].blue;
+                SET_DATA_BYTE(lined, j, val);
+            }
+        }
+    }
+
+    pixDestroy(&pixc);
+    return pixd;
+}
+
+
+/*!
+ *  pixCopyRGBComponent()
+ *
+ *      Input:  pixd (32 bpp)
+ *              pixs (32 bpp)
+ *              comp (one of the set: {COLOR_RED, COLOR_GREEN,
+ *                                     COLOR_BLUE, L_ALPHA_CHANNEL})
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The two images are registered to the UL corner.  The sizes
+ *          are usually the same, and a warning is issued if they differ.
+ */
+l_int32
+pixCopyRGBComponent(PIX     *pixd,
+                    PIX     *pixs,
+                    l_int32  comp)
+{
+l_int32    i, j, w, h, ws, hs, wd, hd, val;
+l_int32    wpls, wpld;
+l_uint32  *lines, *lined;
+l_uint32  *datas, *datad;
+
+    PROCNAME("pixCopyRGBComponent");
+
+    if (!pixd && pixGetDepth(pixd) != 32)
+        return ERROR_INT("pixd not defined or not 32 bpp", procName, 1);
+    if (!pixs && pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not defined or not 32 bpp", procName, 1);
+    if (comp != COLOR_RED && comp != COLOR_GREEN &&
+        comp != COLOR_BLUE && comp != L_ALPHA_CHANNEL)
+        return ERROR_INT("invalid component", procName, 1);
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    pixGetDimensions(pixd, &wd, &hd, NULL);
+    if (ws != wd || hs != hd)
+        L_WARNING("images sizes not equal\n", procName);
+    w = L_MIN(ws, wd);
+    h = L_MIN(hs, hd);
+    if (comp == L_ALPHA_CHANNEL)
+        pixSetSpp(pixd, 4);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(lines + j, comp);
+            SET_DATA_BYTE(lined + j, comp, val);
+        }
+    }
+    return 0;
+}
+
+
+/*!
+ *  composeRGBPixel()
+ *
+ *      Input:  rval, gval, bval
+ *              &pixel  (<return> 32-bit pixel)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) All channels are 8 bits: the input values must be between
+ *          0 and 255.  For speed, this is not enforced by masking
+ *          with 0xff before shifting.
+ *      (2) A slower implementation uses macros:
+ *            SET_DATA_BYTE(ppixel, COLOR_RED, rval);
+ *            SET_DATA_BYTE(ppixel, COLOR_GREEN, gval);
+ *            SET_DATA_BYTE(ppixel, COLOR_BLUE, bval);
+ */
+l_int32
+composeRGBPixel(l_int32    rval,
+                l_int32    gval,
+                l_int32    bval,
+                l_uint32  *ppixel)
+{
+    PROCNAME("composeRGBPixel");
+
+    if (!ppixel)
+        return ERROR_INT("&pixel not defined", procName, 1);
+
+    *ppixel = (rval << L_RED_SHIFT) | (gval << L_GREEN_SHIFT) |
+              (bval << L_BLUE_SHIFT);
+    return 0;
+}
+
+
+/*!
+ *  composeRGBAPixel()
+ *
+ *      Input:  rval, gval, bval, aval
+ *              &pixel  (<return> 32-bit pixel)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) All channels are 8 bits: the input values must be between
+ *          0 and 255.  For speed, this is not enforced by masking
+ *          with 0xff before shifting.
+ */
+l_int32
+composeRGBAPixel(l_int32    rval,
+                 l_int32    gval,
+                 l_int32    bval,
+                 l_int32    aval,
+                 l_uint32  *ppixel)
+{
+    PROCNAME("composeRGBAPixel");
+
+    if (!ppixel)
+        return ERROR_INT("&pixel not defined", procName, 1);
+
+    *ppixel = (rval << L_RED_SHIFT) | (gval << L_GREEN_SHIFT) |
+              (bval << L_BLUE_SHIFT) | aval;
+    return 0;
+}
+
+
+/*!
+ *  extractRGBValues()
+ *
+ *      Input:  pixel (32 bit)
+ *              &rval (<optional return> red component)
+ *              &gval (<optional return> green component)
+ *              &bval (<optional return> blue component)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) A slower implementation uses macros:
+ *             *prval = GET_DATA_BYTE(&pixel, COLOR_RED);
+ *             *pgval = GET_DATA_BYTE(&pixel, COLOR_GREEN);
+ *             *pbval = GET_DATA_BYTE(&pixel, COLOR_BLUE);
+ */
+void
+extractRGBValues(l_uint32  pixel,
+                 l_int32  *prval,
+                 l_int32  *pgval,
+                 l_int32  *pbval)
+{
+    if (prval) *prval = (pixel >> L_RED_SHIFT) & 0xff;
+    if (pgval) *pgval = (pixel >> L_GREEN_SHIFT) & 0xff;
+    if (pbval) *pbval = (pixel >> L_BLUE_SHIFT) & 0xff;
+    return;
+}
+
+
+/*!
+ *  extractRGBAValues()
+ *
+ *      Input:  pixel (32 bit)
+ *              &rval (<optional return> red component)
+ *              &gval (<optional return> green component)
+ *              &bval (<optional return> blue component)
+ *              &aval (<optional return> alpha component)
+ *      Return: void
+ */
+void
+extractRGBAValues(l_uint32  pixel,
+                  l_int32  *prval,
+                  l_int32  *pgval,
+                  l_int32  *pbval,
+                  l_int32  *paval)
+{
+    if (prval) *prval = (pixel >> L_RED_SHIFT) & 0xff;
+    if (pgval) *pgval = (pixel >> L_GREEN_SHIFT) & 0xff;
+    if (pbval) *pbval = (pixel >> L_BLUE_SHIFT) & 0xff;
+    if (paval) *paval = (pixel >> L_ALPHA_SHIFT) & 0xff;
+    return;
+}
+
+
+/*!
+ *  extractMinMaxComponent()
+ *
+ *      Input:  pixel (32 bpp RGB)
+ *              type (L_CHOOSE_MIN or L_CHOOSE_MAX)
+ *      Return: component (in range [0 ... 255], or null on error
+ */
+l_int32
+extractMinMaxComponent(l_uint32  pixel,
+                       l_int32   type)
+{
+l_int32  rval, gval, bval, val;
+
+    extractRGBValues(pixel, &rval, &gval, &bval);
+    if (type == L_CHOOSE_MIN) {
+        val = L_MIN(rval, gval);
+        val = L_MIN(val, bval);
+    } else {  /* type == L_CHOOSE_MAX */
+        val = L_MAX(rval, gval);
+        val = L_MAX(val, bval);
+    }
+    return val;
+}
+
+
+/*!
+ *  pixGetRGBLine()
+ *
+ *      Input:  pixs  (32 bpp)
+ *              row
+ *              bufr  (array of red samples; size w bytes)
+ *              bufg  (array of green samples; size w bytes)
+ *              bufb  (array of blue samples; size w bytes)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This puts rgb components from the input line in pixs
+ *          into the given buffers.
+ */
+l_int32
+pixGetRGBLine(PIX      *pixs,
+              l_int32   row,
+              l_uint8  *bufr,
+              l_uint8  *bufg,
+              l_uint8  *bufb)
+{
+l_uint32  *lines;
+l_int32    j, w, h;
+l_int32    wpls;
+
+    PROCNAME("pixGetRGBLine");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not 32 bpp", procName, 1);
+    if (!bufr || !bufg || !bufb)
+        return ERROR_INT("buffer not defined", procName, 1);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (row < 0 || row >= h)
+        return ERROR_INT("row out of bounds", procName, 1);
+    wpls = pixGetWpl(pixs);
+    lines = pixGetData(pixs) + row * wpls;
+
+    for (j = 0; j < w; j++) {
+        bufr[j] = GET_DATA_BYTE(lines + j, COLOR_RED);
+        bufg[j] = GET_DATA_BYTE(lines + j, COLOR_GREEN);
+        bufb[j] = GET_DATA_BYTE(lines + j, COLOR_BLUE);
+    }
+
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *                    Pixel endian conversion                  *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixEndianByteSwapNew()
+ *
+ *      Input:  pixs
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This is used to convert the data in a pix to a
+ *          serialized byte buffer in raster order, and, for RGB,
+ *          in order RGBA.  This requires flipping bytes within
+ *          each 32-bit word for little-endian platforms, because the
+ *          words have a MSB-to-the-left rule, whereas byte raster-order
+ *          requires the left-most byte in each word to be byte 0.
+ *          For big-endians, no swap is necessary, so this returns a clone.
+ *      (2) Unlike pixEndianByteSwap(), which swaps the bytes in-place,
+ *          this returns a new pix (or a clone).  We provide this
+ *          because often when serialization is done, the source
+ *          pix needs to be restored to canonical little-endian order,
+ *          and this requires a second byte swap.  In such a situation,
+ *          it is twice as fast to make a new pix in big-endian order,
+ *          use it, and destroy it.
+ */
+PIX *
+pixEndianByteSwapNew(PIX  *pixs)
+{
+l_uint32  *datas, *datad;
+l_int32    i, j, h, wpl;
+l_uint32   word;
+PIX       *pixd;
+
+    PROCNAME("pixEndianByteSwapNew");
+
+#ifdef L_BIG_ENDIAN
+
+    return pixClone(pixs);
+
+#else   /* L_LITTLE_ENDIAN */
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    datas = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    h = pixGetHeight(pixs);
+    pixd = pixCreateTemplate(pixs);
+    datad = pixGetData(pixd);
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < wpl; j++, datas++, datad++) {
+            word = *datas;
+            *datad = (word >> 24) |
+                    ((word >> 8) & 0x0000ff00) |
+                    ((word << 8) & 0x00ff0000) |
+                    (word << 24);
+        }
+    }
+
+    return pixd;
+
+#endif   /* L_BIG_ENDIAN */
+
+}
+
+
+/*!
+ *  pixEndianByteSwap()
+ *
+ *      Input:  pixs
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is used on little-endian platforms to swap
+ *          the bytes within a word; bytes 0 and 3 are swapped,
+ *          and bytes 1 and 2 are swapped.
+ *      (2) This is required for little-endians in situations
+ *          where we convert from a serialized byte order that is
+ *          in raster order, as one typically has in file formats,
+ *          to one with MSB-to-the-left in each 32-bit word, or v.v.
+ *          See pix.h for a description of the canonical format
+ *          (MSB-to-the left) that is used for both little-endian
+ *          and big-endian platforms.   For big-endians, the
+ *          MSB-to-the-left word order has the bytes in raster
+ *          order when serialized, so no byte flipping is required.
+ */
+l_int32
+pixEndianByteSwap(PIX  *pixs)
+{
+l_uint32  *data;
+l_int32    i, j, h, wpl;
+l_uint32   word;
+
+    PROCNAME("pixEndianByteSwap");
+
+#ifdef L_BIG_ENDIAN
+
+    return 0;
+
+#else   /* L_LITTLE_ENDIAN */
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    h = pixGetHeight(pixs);
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < wpl; j++, data++) {
+            word = *data;
+            *data = (word >> 24) |
+                    ((word >> 8) & 0x0000ff00) |
+                    ((word << 8) & 0x00ff0000) |
+                    (word << 24);
+        }
+    }
+
+    return 0;
+
+#endif   /* L_BIG_ENDIAN */
+
+}
+
+
+/*!
+ *  lineEndianByteSwap()
+ *
+ *      Input   datad (dest byte array data, reordered on little-endians)
+ *              datas (a src line of pix data)
+ *              wpl (number of 32 bit words in the line)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is used on little-endian platforms to swap
+ *          the bytes within each word in the line of image data.
+ *          Bytes 0 <==> 3 and 1 <==> 2 are swapped in the dest
+ *          byte array data8d, relative to the pix data in datas.
+ *      (2) The bytes represent 8 bit pixel values.  They are swapped
+ *          for little endians so that when the dest array (char *)datad
+ *          is addressed by bytes, the pixels are chosen sequentially
+ *          from left to right in the image.
+ */
+l_int32
+lineEndianByteSwap(l_uint32  *datad,
+                   l_uint32  *datas,
+                   l_int32    wpl)
+{
+l_int32   j;
+l_uint32  word;
+
+    PROCNAME("lineEndianByteSwap");
+
+    if (!datad || !datas)
+        return ERROR_INT("datad and datas not both defined", procName, 1);
+
+#ifdef L_BIG_ENDIAN
+
+    memcpy((char *)datad, (char *)datas, 4 * wpl);
+    return 0;
+
+#else   /* L_LITTLE_ENDIAN */
+
+    for (j = 0; j < wpl; j++, datas++, datad++) {
+        word = *datas;
+        *datad = (word >> 24) |
+                 ((word >> 8) & 0x0000ff00) |
+                 ((word << 8) & 0x00ff0000) |
+                 (word << 24);
+    }
+    return 0;
+
+#endif   /* L_BIG_ENDIAN */
+
+}
+
+
+/*!
+ *  pixEndianTwoByteSwapNew()
+ *
+ *      Input:  pixs
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is used on little-endian platforms to swap the
+ *          2-byte entities within a 32-bit word.
+ *      (2) This is equivalent to a full byte swap, as performed
+ *          by pixEndianByteSwap(), followed by byte swaps in
+ *          each of the 16-bit entities separately.
+ *      (3) Unlike pixEndianTwoByteSwap(), which swaps the shorts in-place,
+ *          this returns a new pix (or a clone).  We provide this
+ *          to avoid having to swap twice in situations where the input
+ *          pix must be restored to canonical little-endian order.
+ */
+PIX *
+pixEndianTwoByteSwapNew(PIX  *pixs)
+{
+l_uint32  *datas, *datad;
+l_int32    i, j, h, wpl;
+l_uint32   word;
+PIX       *pixd;
+
+    PROCNAME("pixEndianTwoByteSwapNew");
+
+#ifdef L_BIG_ENDIAN
+
+    return pixClone(pixs);
+
+#else   /* L_LITTLE_ENDIAN */
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    datas = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    h = pixGetHeight(pixs);
+    pixd = pixCreateTemplate(pixs);
+    datad = pixGetData(pixd);
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < wpl; j++, datas++, datad++) {
+            word = *datas;
+            *datad = (word << 16) | (word >> 16);
+        }
+    }
+
+    return pixd;
+
+#endif   /* L_BIG_ENDIAN */
+
+}
+
+
+/*!
+ *  pixEndianTwoByteSwap()
+ *
+ *      Input:  pixs
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is used on little-endian platforms to swap the
+ *          2-byte entities within a 32-bit word.
+ *      (2) This is equivalent to a full byte swap, as performed
+ *          by pixEndianByteSwap(), followed by byte swaps in
+ *          each of the 16-bit entities separately.
+ */
+l_int32
+pixEndianTwoByteSwap(PIX  *pixs)
+{
+l_uint32  *data;
+l_int32    i, j, h, wpl;
+l_uint32   word;
+
+    PROCNAME("pixEndianTwoByteSwap");
+
+#ifdef L_BIG_ENDIAN
+
+    return 0;
+
+#else   /* L_LITTLE_ENDIAN */
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    h = pixGetHeight(pixs);
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < wpl; j++, data++) {
+            word = *data;
+            *data = (word << 16) | (word >> 16);
+        }
+    }
+
+    return 0;
+
+#endif   /* L_BIG_ENDIAN */
+
+}
+
+
+/*-------------------------------------------------------------*
+ *             Extract raster data as binary string            *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixGetRasterData()
+ *
+ *      Input:  pixs (1, 8, 32 bpp)
+ *              &data (<return> raster data in memory)
+ *              &nbytes (<return> number of bytes in data string)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This returns the raster data as a byte string, padded to the
+ *          byte.  For 1 bpp, the first pixel is the MSbit in the first byte.
+ *          For rgb, the bytes are in (rgb) order.  This is the format
+ *          required for flate encoding of pixels in a PostScript file.
+ */
+l_int32
+pixGetRasterData(PIX       *pixs,
+                 l_uint8  **pdata,
+                 size_t    *pnbytes)
+{
+l_int32    w, h, d, wpl, i, j, rval, gval, bval;
+l_int32    databpl;  /* bytes for each raster line in returned data */
+l_uint8   *line, *data;  /* packed data in returned array */
+l_uint32  *rline, *rdata;  /* data in pix raster */
+
+    PROCNAME("pixGetRasterData");
+
+    if (pdata) *pdata = NULL;
+    if (pnbytes) *pnbytes = 0;
+    if (!pdata || !pnbytes)
+        return ERROR_INT("&data and &nbytes not both defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return ERROR_INT("depth not in {1,2,4,8,16,32}", procName, 1);
+    rdata = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    if (d == 1)
+        databpl = (w + 7) / 8;
+    else if (d == 2)
+        databpl = (w + 3) / 4;
+    else if (d == 4)
+        databpl = (w + 1) / 2;
+    else if (d == 8 || d == 16)
+        databpl = w * (d / 8);
+    else  /* d == 32 bpp rgb */
+        databpl = 3 * w;
+    if ((data = (l_uint8 *)LEPT_CALLOC(databpl * h, sizeof(l_uint8))) == NULL)
+        return ERROR_INT("data not allocated", procName, 1);
+    *pdata = data;
+    *pnbytes = databpl * h;
+
+    for (i = 0; i < h; i++) {
+         rline = rdata + i * wpl;
+         line = data + i * databpl;
+         if (d <= 8) {
+             for (j = 0; j < databpl; j++)
+                  line[j] = GET_DATA_BYTE(rline, j);
+         } else if (d == 16) {
+             for (j = 0; j < w; j++)
+                  line[2 * j] = GET_DATA_TWO_BYTES(rline, j);
+         } else {  /* d == 32 bpp rgb */
+             for (j = 0; j < w; j++) {
+                  extractRGBValues(rline[j], &rval, &gval, &bval);
+                  *(line + 3 * j) = rval;
+                  *(line + 3 * j + 1) = gval;
+                  *(line + 3 * j + 2) = bval;
+             }
+         }
+    }
+
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *                 Test alpha component opaqueness             *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixAlphaIsOpaque()
+ *
+ *      Input:  pix (32 bpp, spp == 4)
+ *              &opaque (<return> 1 if spp == 4 and all alpha component
+ *                       values are 255 (opaque); 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *      Notes:
+ *          (1) On error, opaque is returned as 0 (FALSE).
+ */
+l_int32
+pixAlphaIsOpaque(PIX      *pix,
+                 l_int32  *popaque)
+{
+l_int32    w, h, wpl, i, j, alpha;
+l_uint32  *data, *line;
+
+    PROCNAME("pixAlphaIsOpaque");
+
+    if (!popaque)
+        return ERROR_INT("&opaque not defined", procName, 1);
+    *popaque = FALSE;
+    if (!pix)
+        return ERROR_INT("&pix not defined", procName, 1);
+    if (pixGetDepth(pix) != 32)
+        return ERROR_INT("&pix not 32 bpp", procName, 1);
+    if (pixGetSpp(pix) != 4)
+        return ERROR_INT("&pix not 4 spp", procName, 1);
+
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    pixGetDimensions(pix, &w, &h, NULL);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            alpha = GET_DATA_BYTE(line + j, L_ALPHA_CHANNEL);
+            if (alpha ^ 0xff)  /* not opaque */
+                return 0;
+        }
+    }
+
+    *popaque = TRUE;
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *             Setup helpers for 8 bpp byte processing         *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixSetupByteProcessing()
+ *
+ *      Input:  pix (8 bpp, no colormap)
+ *              &w (<optional return> width)
+ *              &h (<optional return> height)
+ *      Return: line ptr array, or null on error
+ *
+ *  Notes:
+ *      (1) This is a simple helper for processing 8 bpp images with
+ *          direct byte access.  It can swap byte order within each word.
+ *      (2) After processing, you must call pixCleanupByteProcessing(),
+ *          which frees the lineptr array and restores byte order.
+ *      (3) Usage:
+ *              l_uint8 **lineptrs = pixSetupByteProcessing(pix, &w, &h);
+ *              for (i = 0; i < h; i++) {
+ *                  l_uint8 *line = lineptrs[i];
+ *                  for (j = 0; j < w; j++) {
+ *                      val = line[j];
+ *                      ...
+ *                  }
+ *              }
+ *              pixCleanupByteProcessing(pix, lineptrs);
+ */
+l_uint8 **
+pixSetupByteProcessing(PIX      *pix,
+                       l_int32  *pw,
+                       l_int32  *ph)
+{
+l_int32  w, h;
+
+    PROCNAME("pixSetupByteProcessing");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (!pix || pixGetDepth(pix) != 8)
+        return (l_uint8 **)ERROR_PTR("pix not defined or not 8 bpp",
+                                     procName, NULL);
+    pixGetDimensions(pix, &w, &h, NULL);
+    if (pw) *pw = w;
+    if (ph) *ph = h;
+    if (pixGetColormap(pix))
+        return (l_uint8 **)ERROR_PTR("pix has colormap", procName, NULL);
+
+    pixEndianByteSwap(pix);
+    return (l_uint8 **)pixGetLinePtrs(pix, NULL);
+}
+
+
+/*!
+ *  pixCleanupByteProcessing()
+ *
+ *      Input:  pix (8 bpp, no colormap)
+ *              lineptrs (ptrs to the beginning of each raster line of data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This must be called after processing that was initiated
+ *          by pixSetupByteProcessing() has finished.
+ */
+l_int32
+pixCleanupByteProcessing(PIX      *pix,
+                         l_uint8 **lineptrs)
+{
+    PROCNAME("pixCleanupByteProcessing");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!lineptrs)
+        return ERROR_INT("lineptrs not defined", procName, 1);
+
+    pixEndianByteSwap(pix);
+    LEPT_FREE(lineptrs);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *      Setting parameters for antialias masking with alpha transforms    *
+ *------------------------------------------------------------------------*/
+/*!
+ *  l_setAlphaMaskBorder()
+ *
+ *      Input:  val1, val2 (in [0.0 ... 1.0])
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This sets the opacity values used to generate the two outer
+ *          boundary rings in the alpha mask associated with geometric
+ *          transforms such as pixRotateWithAlpha().
+ *      (2) The default values are val1 = 0.0 (completely transparent
+ *          in the outermost ring) and val2 = 0.5 (half transparent
+ *          in the second ring).  When the image is blended, this
+ *          completely removes the outer ring (shrinking the image by
+ *          2 in each direction), and alpha-blends with 0.5 the second ring.
+ *          Using val1 = 0.25 and val2 = 0.75 gives a slightly more
+ *          blurred border, with no perceptual difference at screen resolution.
+ *      (3) The actual mask values are found by multiplying these
+ *          normalized opacity values by 255.
+ */
+void
+l_setAlphaMaskBorder(l_float32  val1,
+                     l_float32  val2)
+{
+    val1 = L_MAX(0.0, L_MIN(1.0, val1));
+    val2 = L_MAX(0.0, L_MIN(1.0, val2));
+    AlphaMaskBorderVals[0] = val1;
+    AlphaMaskBorderVals[1] = val2;
+}
diff --git a/src/pix3.c b/src/pix3.c
new file mode 100644 (file)
index 0000000..1e89050
--- /dev/null
@@ -0,0 +1,3267 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pix3.c
+ *
+ *    This file has these operations:
+ *
+ *      (1) Mask-directed operations
+ *      (2) Full-image bit-logical operations
+ *      (3) Foreground pixel counting operations on 1 bpp images
+ *      (4) Average and variance of pixel values
+ *      (5) Mirrored tiling of a smaller image
+ *
+ *
+ *    Masked operations
+ *           l_int32     pixSetMasked()
+ *           l_int32     pixSetMaskedGeneral()
+ *           l_int32     pixCombineMasked()
+ *           l_int32     pixCombineMaskedGeneral()
+ *           l_int32     pixPaintThroughMask()
+ *           PIX        *pixPaintSelfThroughMask()
+ *           PIX        *pixMakeMaskFromLUT()
+ *           PIX        *pixSetUnderTransparency()
+ *           PIX        *pixMakeAlphaFromMask()
+ *           l_int32     pixGetColorNearMaskBoundary()
+ *
+ *    One and two-image boolean operations on arbitrary depth images
+ *           PIX        *pixInvert()
+ *           PIX        *pixOr()
+ *           PIX        *pixAnd()
+ *           PIX        *pixXor()
+ *           PIX        *pixSubtract()
+ *
+ *    Foreground pixel counting in 1 bpp images
+ *           l_int32     pixZero()
+ *           l_int32     pixForegroundFraction()
+ *           NUMA       *pixaCountPixels()
+ *           l_int32     pixCountPixels()
+ *           NUMA       *pixCountByRow()
+ *           NUMA       *pixCountByColumn()
+ *           NUMA       *pixCountPixelsByRow()
+ *           NUMA       *pixCountPixelsByColumn()
+ *           l_int32     pixCountPixelsInRow()
+ *           NUMA       *pixGetMomentByColumn()
+ *           l_int32     pixThresholdPixelSum()
+ *           l_int32    *makePixelSumTab8()
+ *           l_int32    *makePixelCentroidTab8()
+ *
+ *    Average of pixel values in gray images
+ *           NUMA       *pixAverageByRow()
+ *           NUMA       *pixAverageByColumn()
+ *           l_int32     pixAverageInRect()
+ *
+ *    Variance of pixel values in gray images
+ *           NUMA       *pixVarianceByRow()
+ *           NUMA       *pixVarianceByColumn()
+ *           l_int32     pixVarianceInRect()
+ *
+ *    Average of absolute value of pixel differences in gray images
+ *           NUMA       *pixAbsDiffByRow()
+ *           NUMA       *pixAbsDiffByColumn()
+ *           l_int32     pixAbsDiffInRect()
+ *           l_int32     pixAbsDiffOnLine()
+ *
+ *    Count of pixels with specific value            *
+ *           l_int32     pixCountArbInRect()
+ *
+ *    Mirrored tiling
+ *           PIX        *pixMirroredTiling()
+ *
+ *    Representative tile near but outside region
+ *           l_int32     pixFindRepCloseTile()
+ *
+ *    Static helper function
+ *           static BOXA    *findTileRegionsForSearch()
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+static BOXA *findTileRegionsForSearch(BOX *box, l_int32 w, l_int32 h,
+                                      l_int32 searchdir, l_int32 mindist,
+                                      l_int32 tsize, l_int32 ntiles);
+
+#ifndef  NO_CONSOLE_IO
+#define   EQUAL_SIZE_WARNING      0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*-------------------------------------------------------------*
+ *                        Masked operations                    *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixSetMasked()
+ *
+ *      Input:  pixd (1, 2, 4, 8, 16 or 32 bpp; or colormapped)
+ *              pixm (<optional> 1 bpp mask; no operation if NULL)
+ *              val (value to set at each masked pixel)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) In-place operation.
+ *      (2) NOTE: For cmapped images, this calls pixSetMaskedCmap().
+ *          @val must be the 32-bit color representation of the RGB pixel.
+ *          It is not the index into the colormap!
+ *      (2) If pixm == NULL, a warning is given.
+ *      (3) This is an implicitly aligned operation, where the UL
+ *          corners of pixd and pixm coincide.  A warning is
+ *          issued if the two image sizes differ significantly,
+ *          but the operation proceeds.
+ *      (4) Each pixel in pixd that co-locates with an ON pixel
+ *          in pixm is set to the specified input value.
+ *          Other pixels in pixd are not changed.
+ *      (5) You can visualize this as painting the color through
+ *          the mask, as a stencil.
+ *      (6) If you do not want to have the UL corners aligned,
+ *          use the function pixSetMaskedGeneral(), which requires
+ *          you to input the UL corner of pixm relative to pixd.
+ *      (7) Implementation details: see comments in pixPaintThroughMask()
+ *          for when we use rasterop to do the painting.
+ */
+l_int32
+pixSetMasked(PIX      *pixd,
+             PIX      *pixm,
+             l_uint32  val)
+{
+l_int32    wd, hd, wm, hm, w, h, d, wpld, wplm;
+l_int32    i, j, rval, gval, bval;
+l_uint32  *datad, *datam, *lined, *linem;
+
+    PROCNAME("pixSetMasked");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (!pixm) {
+        L_WARNING("no mask; nothing to do\n", procName);
+        return 0;
+    }
+    if (pixGetColormap(pixd)) {
+        extractRGBValues(val, &rval, &gval, &bval);
+        return pixSetMaskedCmap(pixd, pixm, 0, 0, rval, gval, bval);
+    }
+
+    if (pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+    d = pixGetDepth(pixd);
+    if (d == 1)
+        val &= 1;
+    else if (d == 2)
+        val &= 3;
+    else if (d == 4)
+        val &= 0x0f;
+    else if (d == 8)
+        val &= 0xff;
+    else if (d == 16)
+        val &= 0xffff;
+    else if (d != 32)
+        return ERROR_INT("pixd not 1, 2, 4, 8, 16 or 32 bpp", procName, 1);
+    pixGetDimensions(pixm, &wm, &hm, NULL);
+
+        /* If d == 1, use rasterop; it's about 25x faster */
+    if (d == 1) {
+        if (val == 0) {
+            PIX *pixmi = pixInvert(NULL, pixm);
+            pixRasterop(pixd, 0, 0, wm, hm, PIX_MASK, pixmi, 0, 0);
+            pixDestroy(&pixmi);
+        } else {  /* val == 1 */
+            pixRasterop(pixd, 0, 0, wm, hm, PIX_PAINT, pixm, 0, 0);
+        }
+        return 0;
+    }
+
+        /* For d < 32, use rasterop for val == 0 (black); ~3x faster. */
+    if (d < 32 && val == 0) {
+        PIX *pixmd = pixUnpackBinary(pixm, d, 1);
+        pixRasterop(pixd, 0, 0, wm, hm, PIX_MASK, pixmd, 0, 0);
+        pixDestroy(&pixmd);
+        return 0;
+    }
+
+        /* For d < 32, use rasterop for val == maxval (white); ~3x faster. */
+    if (d < 32 && val == ((1 << d) - 1)) {
+        PIX *pixmd = pixUnpackBinary(pixm, d, 0);
+        pixRasterop(pixd, 0, 0, wm, hm, PIX_PAINT, pixmd, 0, 0);
+        pixDestroy(&pixmd);
+        return 0;
+    }
+
+    pixGetDimensions(pixd, &wd, &hd, &d);
+    w = L_MIN(wd, wm);
+    h = L_MIN(hd, hm);
+    if (L_ABS(wd - wm) > 7 || L_ABS(hd - hm) > 7)  /* allow a small tolerance */
+        L_WARNING("pixd and pixm sizes differ\n", procName);
+
+    datad = pixGetData(pixd);
+    datam = pixGetData(pixm);
+    wpld = pixGetWpl(pixd);
+    wplm = pixGetWpl(pixm);
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        linem = datam + i * wplm;
+        for (j = 0; j < w; j++) {
+            if (GET_DATA_BIT(linem, j)) {
+                switch(d)
+                {
+                case 2:
+                    SET_DATA_DIBIT(lined, j, val);
+                    break;
+                case 4:
+                    SET_DATA_QBIT(lined, j, val);
+                    break;
+                case 8:
+                    SET_DATA_BYTE(lined, j, val);
+                    break;
+                case 16:
+                    SET_DATA_TWO_BYTES(lined, j, val);
+                    break;
+                case 32:
+                    *(lined + j) = val;
+                    break;
+                default:
+                    return ERROR_INT("shouldn't get here", procName, 1);
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixSetMaskedGeneral()
+ *
+ *      Input:  pixd (8, 16 or 32 bpp)
+ *              pixm (<optional> 1 bpp mask; no operation if null)
+ *              val (value to set at each masked pixel)
+ *              x, y (location of UL corner of pixm relative to pixd;
+ *                    can be negative)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place operation.
+ *      (2) Alignment is explicit.  If you want the UL corners of
+ *          the two images to be aligned, use pixSetMasked().
+ *      (3) A typical use would be painting through the foreground
+ *          of a small binary mask pixm, located somewhere on a
+ *          larger pixd.  Other pixels in pixd are not changed.
+ *      (4) You can visualize this as painting the color through
+ *          the mask, as a stencil.
+ *      (5) This uses rasterop to handle clipping and different depths of pixd.
+ *      (6) If pixd has a colormap, you should call pixPaintThroughMask().
+ *      (7) Why is this function here, if pixPaintThroughMask() does the
+ *          same thing, and does it more generally?  I've retained it here
+ *          to show how one can paint through a mask using only full
+ *          image rasterops, rather than pixel peeking in pixm and poking
+ *          in pixd.  It's somewhat baroque, but I found it amusing.
+ */
+l_int32
+pixSetMaskedGeneral(PIX      *pixd,
+                    PIX      *pixm,
+                    l_uint32  val,
+                    l_int32   x,
+                    l_int32   y)
+{
+l_int32    wm, hm, d;
+PIX       *pixmu, *pixc;
+
+    PROCNAME("pixSetMaskedGeneral");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (!pixm)  /* nothing to do */
+        return 0;
+
+    d = pixGetDepth(pixd);
+    if (d != 8 && d != 16 && d != 32)
+        return ERROR_INT("pixd not 8, 16 or 32 bpp", procName, 1);
+    if (pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+
+        /* Unpack binary to depth d, with inversion:  1 --> 0, 0 --> 0xff... */
+    if ((pixmu = pixUnpackBinary(pixm, d, 1)) == NULL)
+        return ERROR_INT("pixmu not made", procName, 1);
+
+        /* Clear stenciled pixels in pixd */
+    pixGetDimensions(pixm, &wm, &hm, NULL);
+    pixRasterop(pixd, x, y, wm, hm, PIX_SRC & PIX_DST, pixmu, 0, 0);
+
+        /* Generate image with requisite color */
+    if ((pixc = pixCreateTemplate(pixmu)) == NULL)
+        return ERROR_INT("pixc not made", procName, 1);
+    pixSetAllArbitrary(pixc, val);
+
+        /* Invert stencil mask, and paint color color into stencil */
+    pixInvert(pixmu, pixmu);
+    pixAnd(pixmu, pixmu, pixc);
+
+        /* Finally, repaint stenciled pixels, with val, in pixd */
+    pixRasterop(pixd, x, y, wm, hm, PIX_SRC | PIX_DST, pixmu, 0, 0);
+
+    pixDestroy(&pixmu);
+    pixDestroy(&pixc);
+    return 0;
+}
+
+
+/*!
+ *  pixCombineMasked()
+ *
+ *      Input:  pixd (1 bpp, 8 bpp gray or 32 bpp rgb; no cmap)
+ *              pixs (1 bpp, 8 bpp gray or 32 bpp rgb; no cmap)
+ *              pixm (<optional> 1 bpp mask; no operation if NULL)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) In-place operation; pixd is changed.
+ *      (2) This sets each pixel in pixd that co-locates with an ON
+ *          pixel in pixm to the corresponding value of pixs.
+ *      (3) pixs and pixd must be the same depth and not colormapped.
+ *      (4) All three input pix are aligned at the UL corner, and the
+ *          operation is clipped to the intersection of all three images.
+ *      (5) If pixm == NULL, it's a no-op.
+ *      (6) Implementation: see notes in pixCombineMaskedGeneral().
+ *          For 8 bpp selective masking, you might guess that it
+ *          would be faster to generate an 8 bpp version of pixm,
+ *          using pixConvert1To8(pixm, 0, 255), and then use a
+ *          general combine operation
+ *               d = (d & ~m) | (s & m)
+ *          on a word-by-word basis.  Not always.  The word-by-word
+ *          combine takes a time that is independent of the mask data.
+ *          If the mask is relatively sparse, the byte-check method
+ *          is actually faster!
+ */
+l_int32
+pixCombineMasked(PIX  *pixd,
+                 PIX  *pixs,
+                 PIX  *pixm)
+{
+l_int32    w, h, d, ws, hs, ds, wm, hm, dm, wmin, hmin;
+l_int32    wpl, wpls, wplm, i, j, val;
+l_uint32  *data, *datas, *datam, *line, *lines, *linem;
+PIX       *pixt;
+
+    PROCNAME("pixCombineMasked");
+
+    if (!pixm)  /* nothing to do */
+        return 0;
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixd, &w, &h, &d);
+    pixGetDimensions(pixs, &ws, &hs, &ds);
+    pixGetDimensions(pixm, &wm, &hm, &dm);
+    if (d != ds)
+        return ERROR_INT("pixs and pixd depths differ", procName, 1);
+    if (dm != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+    if (d != 1 && d != 8 && d != 32)
+        return ERROR_INT("pixd not 1, 8 or 32 bpp", procName, 1);
+    if (pixGetColormap(pixd) || pixGetColormap(pixs))
+        return ERROR_INT("pixs and/or pixd is cmapped", procName, 1);
+
+        /* For d = 1, use rasterop.  pixt is the part from pixs, under
+         * the fg of pixm, that is to be combined with pixd.  We also
+         * use pixt to remove all fg of pixd that is under the fg of pixm.
+         * Then pixt and pixd are combined by ORing. */
+    wmin = L_MIN(w, L_MIN(ws, wm));
+    hmin = L_MIN(h, L_MIN(hs, hm));
+    if (d == 1) {
+        pixt = pixAnd(NULL, pixs, pixm);
+        pixRasterop(pixd, 0, 0, wmin, hmin, PIX_DST & PIX_NOT(PIX_SRC),
+                    pixm, 0, 0);
+        pixRasterop(pixd, 0, 0, wmin, hmin, PIX_SRC | PIX_DST, pixt, 0, 0);
+        pixDestroy(&pixt);
+        return 0;
+    }
+
+    data = pixGetData(pixd);
+    datas = pixGetData(pixs);
+    datam = pixGetData(pixm);
+    wpl = pixGetWpl(pixd);
+    wpls = pixGetWpl(pixs);
+    wplm = pixGetWpl(pixm);
+    if (d == 8) {
+        for (i = 0; i < hmin; i++) {
+            line = data + i * wpl;
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < wmin; j++) {
+                if (GET_DATA_BIT(linem, j)) {
+                   val = GET_DATA_BYTE(lines, j);
+                   SET_DATA_BYTE(line, j, val);
+                }
+            }
+        }
+    } else {  /* d == 32 */
+        for (i = 0; i < hmin; i++) {
+            line = data + i * wpl;
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < wmin; j++) {
+                if (GET_DATA_BIT(linem, j))
+                   line[j] = lines[j];
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixCombineMaskedGeneral()
+ *
+ *      Input:  pixd (1 bpp, 8 bpp gray or 32 bpp rgb)
+ *              pixs (1 bpp, 8 bpp gray or 32 bpp rgb)
+ *              pixm (<optional> 1 bpp mask)
+ *              x, y (origin of pixs and pixm relative to pixd; can be negative)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) In-place operation; pixd is changed.
+ *      (2) This is a generalized version of pixCombinedMasked(), where
+ *          the source and mask can be placed at the same (arbitrary)
+ *          location relative to pixd.
+ *      (3) pixs and pixd must be the same depth and not colormapped.
+ *      (4) The UL corners of both pixs and pixm are aligned with
+ *          the point (x, y) of pixd, and the operation is clipped to
+ *          the intersection of all three images.
+ *      (5) If pixm == NULL, it's a no-op.
+ *      (6) Implementation.  There are two ways to do these.  In the first,
+ *          we use rasterop, ORing the part of pixs under the mask
+ *          with pixd (which has been appropriately cleared there first).
+ *          In the second, the mask is used one pixel at a time to
+ *          selectively replace pixels of pixd with those of pixs.
+ *          Here, we use rasterop for 1 bpp and pixel-wise replacement
+ *          for 8 and 32 bpp.  To use rasterop for 8 bpp, for example,
+ *          we must first generate an 8 bpp version of the mask.
+ *          The code is simple:
+ *
+ *             Pix *pixm8 = pixConvert1To8(NULL, pixm, 0, 255);
+ *             Pix *pixt = pixAnd(NULL, pixs, pixm8);
+ *             pixRasterop(pixd, x, y, wmin, hmin, PIX_DST & PIX_NOT(PIX_SRC),
+ *                         pixm8, 0, 0);
+ *             pixRasterop(pixd, x, y, wmin, hmin, PIX_SRC | PIX_DST,
+ *                         pixt, 0, 0);
+ *             pixDestroy(&pixt);
+ *             pixDestroy(&pixm8);
+ */
+l_int32
+pixCombineMaskedGeneral(PIX      *pixd,
+                        PIX      *pixs,
+                        PIX      *pixm,
+                        l_int32   x,
+                        l_int32   y)
+{
+l_int32    d, w, h, ws, hs, ds, wm, hm, dm, wmin, hmin;
+l_int32    wpl, wpls, wplm, i, j, val;
+l_uint32  *data, *datas, *datam, *line, *lines, *linem;
+PIX       *pixt;
+
+    PROCNAME("pixCombineMaskedGeneral");
+
+    if (!pixm)  /* nothing to do */
+        return 0;
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixd, &w, &h, &d);
+    pixGetDimensions(pixs, &ws, &hs, &ds);
+    pixGetDimensions(pixm, &wm, &hm, &dm);
+    if (d != ds)
+        return ERROR_INT("pixs and pixd depths differ", procName, 1);
+    if (dm != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+    if (d != 1 && d != 8 && d != 32)
+        return ERROR_INT("pixd not 1, 8 or 32 bpp", procName, 1);
+    if (pixGetColormap(pixd) || pixGetColormap(pixs))
+        return ERROR_INT("pixs and/or pixd is cmapped", procName, 1);
+
+        /* For d = 1, use rasterop.  pixt is the part from pixs, under
+         * the fg of pixm, that is to be combined with pixd.  We also
+         * use pixt to remove all fg of pixd that is under the fg of pixm.
+         * Then pixt and pixd are combined by ORing. */
+    wmin = L_MIN(ws, wm);
+    hmin = L_MIN(hs, hm);
+    if (d == 1) {
+        pixt = pixAnd(NULL, pixs, pixm);
+        pixRasterop(pixd, x, y, wmin, hmin, PIX_DST & PIX_NOT(PIX_SRC),
+                    pixm, 0, 0);
+        pixRasterop(pixd, x, y, wmin, hmin, PIX_SRC | PIX_DST, pixt, 0, 0);
+        pixDestroy(&pixt);
+        return 0;
+    }
+
+    wpl = pixGetWpl(pixd);
+    data = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    datas = pixGetData(pixs);
+    wplm = pixGetWpl(pixm);
+    datam = pixGetData(pixm);
+
+    for (i = 0; i < hmin; i++) {
+        if (y + i < 0 || y + i >= h) continue;
+        line = data + (y + i) * wpl;
+        lines = datas + i * wpls;
+        linem = datam + i * wplm;
+        for (j = 0; j < wmin; j++) {
+            if (x + j < 0 || x + j >= w) continue;
+            if (GET_DATA_BIT(linem, j)) {
+                switch (d)
+                {
+                case 8:
+                    val = GET_DATA_BYTE(lines, j);
+                    SET_DATA_BYTE(line, x + j, val);
+                    break;
+                case 32:
+                    *(line + x + j) = *(lines + j);
+                    break;
+                default:
+                    return ERROR_INT("shouldn't get here", procName, 1);
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixPaintThroughMask()
+ *
+ *      Input:  pixd (1, 2, 4, 8, 16 or 32 bpp; or colormapped)
+ *              pixm (<optional> 1 bpp mask)
+ *              x, y (origin of pixm relative to pixd; can be negative)
+ *              val (pixel value to set at each masked pixel)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) In-place operation.  Calls pixSetMaskedCmap() for colormapped
+ *          images.
+ *      (2) For 1, 2, 4, 8 and 16 bpp gray, we take the appropriate
+ *          number of least significant bits of val.
+ *      (3) If pixm == NULL, it's a no-op.
+ *      (4) The mask origin is placed at (x,y) on pixd, and the
+ *          operation is clipped to the intersection of rectangles.
+ *      (5) For rgb, the components in val are in the canonical locations,
+ *          with red in location COLOR_RED, etc.
+ *      (6) Implementation detail 1:
+ *          For painting with val == 0 or val == maxval, you can use rasterop.
+ *          If val == 0, invert the mask so that it's 0 over the region
+ *          into which you want to write, and use PIX_SRC & PIX_DST to
+ *          clear those pixels.  To write with val = maxval (all 1's),
+ *          use PIX_SRC | PIX_DST to set all bits under the mask.
+ *      (7) Implementation detail 2:
+ *          The rasterop trick can be used for depth > 1 as well.
+ *          For val == 0, generate the mask for depth d from the binary
+ *          mask using
+ *              pixmd = pixUnpackBinary(pixm, d, 1);
+ *          and use pixRasterop() with PIX_MASK.  For val == maxval,
+ *              pixmd = pixUnpackBinary(pixm, d, 0);
+ *          and use pixRasterop() with PIX_PAINT.
+ *          But note that if d == 32 bpp, it is about 3x faster to use
+ *          the general implementation (not pixRasterop()).
+ *      (8) Implementation detail 3:
+ *          It might be expected that the switch in the inner loop will
+ *          cause large branching delays and should be avoided.
+ *          This is not the case, because the entrance is always the
+ *          same and the compiler can correctly predict the jump.
+ */
+l_int32
+pixPaintThroughMask(PIX      *pixd,
+                    PIX      *pixm,
+                    l_int32   x,
+                    l_int32   y,
+                    l_uint32  val)
+{
+l_int32    d, w, h, wm, hm, wpl, wplm, i, j, rval, gval, bval;
+l_uint32  *data, *datam, *line, *linem;
+
+    PROCNAME("pixPaintThroughMask");
+
+    if (!pixm)  /* nothing to do */
+        return 0;
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (pixGetColormap(pixd)) {
+        extractRGBValues(val, &rval, &gval, &bval);
+        return pixSetMaskedCmap(pixd, pixm, x, y, rval, gval, bval);
+    }
+
+    if (pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+    d = pixGetDepth(pixd);
+    if (d == 1)
+        val &= 1;
+    else if (d == 2)
+        val &= 3;
+    else if (d == 4)
+        val &= 0x0f;
+    else if (d == 8)
+        val &= 0xff;
+    else if (d == 16)
+        val &= 0xffff;
+    else if (d != 32)
+        return ERROR_INT("pixd not 1, 2, 4, 8, 16 or 32 bpp", procName, 1);
+    pixGetDimensions(pixm, &wm, &hm, NULL);
+
+        /* If d == 1, use rasterop; it's about 25x faster. */
+    if (d == 1) {
+        if (val == 0) {
+            PIX *pixmi = pixInvert(NULL, pixm);
+            pixRasterop(pixd, x, y, wm, hm, PIX_MASK, pixmi, 0, 0);
+            pixDestroy(&pixmi);
+        } else {  /* val == 1 */
+            pixRasterop(pixd, x, y, wm, hm, PIX_PAINT, pixm, 0, 0);
+        }
+        return 0;
+    }
+
+        /* For d < 32, use rasterop if val == 0 (black); ~3x faster. */
+    if (d < 32 && val == 0) {
+        PIX *pixmd = pixUnpackBinary(pixm, d, 1);
+        pixRasterop(pixd, x, y, wm, hm, PIX_MASK, pixmd, 0, 0);
+        pixDestroy(&pixmd);
+        return 0;
+    }
+
+        /* For d < 32, use rasterop if val == maxval (white); ~3x faster. */
+    if (d < 32 && val == ((1 << d) - 1)) {
+        PIX *pixmd = pixUnpackBinary(pixm, d, 0);
+        pixRasterop(pixd, x, y, wm, hm, PIX_PAINT, pixmd, 0, 0);
+        pixDestroy(&pixmd);
+        return 0;
+    }
+
+        /* All other cases */
+    pixGetDimensions(pixd, &w, &h, NULL);
+    wpl = pixGetWpl(pixd);
+    data = pixGetData(pixd);
+    wplm = pixGetWpl(pixm);
+    datam = pixGetData(pixm);
+    for (i = 0; i < hm; i++) {
+        if (y + i < 0 || y + i >= h) continue;
+        line = data + (y + i) * wpl;
+        linem = datam + i * wplm;
+        for (j = 0; j < wm; j++) {
+            if (x + j < 0 || x + j >= w) continue;
+            if (GET_DATA_BIT(linem, j)) {
+                switch (d)
+                {
+                case 2:
+                    SET_DATA_DIBIT(line, x + j, val);
+                    break;
+                case 4:
+                    SET_DATA_QBIT(line, x + j, val);
+                    break;
+                case 8:
+                    SET_DATA_BYTE(line, x + j, val);
+                    break;
+                case 16:
+                    SET_DATA_TWO_BYTES(line, x + j, val);
+                    break;
+                case 32:
+                    *(line + x + j) = val;
+                    break;
+                default:
+                    return ERROR_INT("shouldn't get here", procName, 1);
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixPaintSelfThroughMask()
+ *
+ *      Input:  pixd (8 bpp gray or 32 bpp rgb; not colormapped)
+ *              pixm (1 bpp mask)
+ *              x, y (origin of pixm relative to pixd; must not be negative)
+ *              searchdir (L_HORIZ, L_VERT or L_BOTH_DIRECTIONS)
+ *              mindist (min distance of nearest tile edge to box; >= 0)
+ *              tilesize (requested size for tiling; may be reduced)
+ *              ntiles (number of tiles tested in each row/column)
+ *              distblend (distance outside the fg used for blending with pixs)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) In-place operation; pixd is changed.
+ *      (2) If pixm == NULL, it's a no-op.
+ *      (3) The mask origin is placed at (x,y) on pixd, and the
+ *          operation is clipped to the intersection of pixd and the
+ *          fg of the mask.
+ *      (4) @tsize is the the requested size for tiling.  The actual
+ *          actual size for each c.c. will be bounded by the minimum
+ *          dimension of the c.c.
+ *      (5) For @mindist, @searchdir and @ntiles, see pixFindRepCloseTile().
+ *          They determine the set of possible tiles that can be used
+ *          to build a larger mirrored tile to paint onto pixd through
+ *          the c.c. of pixm.
+ *      (6) @distblend is used for alpha blending.  It is only applied
+ *          if there is exactly one c.c. in the mask.  Use distblend == 0
+ *          to skip blending and just paint through the 1 bpp mask.
+ *      (7) To apply blending to more than 1 component, call this function
+ *          repeatedly with @pixm, @x and @y representing one component of
+ *          the mask each time.  This would be done as follows, for an
+ *          underlying image pixs and mask pixm of components to fill:
+ *              Boxa *boxa = pixConnComp(pixm, &pixa, 8);
+ *              n = boxaGetCount(boxa);
+ *              for (i = 0; i < n; i++) {
+ *                  Pix *pix = pixaGetPix(pixa, i, L_CLONE);
+ *                  Box *box = pixaGetBox(pixa, i, L_CLONE);
+ *                  boxGetGeometry(box, &bx, &by, &bw, &bh);
+ *                  pixPaintSelfThroughMask(pixs, pix, bx, by, searchdir,
+ *                                     mindist, tilesize, ntiles, distblend);
+ *                  pixDestroy(&pix);
+ *                  boxDestroy(&box);
+ *              }
+ *              pixaDestroy(&pixa);
+ *              boxaDestroy(&boxa);
+ *      (8) If no tiles can be found, this falls back to estimating the
+ *          color near the boundary of the region to be textured.
+ *      (9) This can be used to replace the pixels in some regions of
+ *          an image by selected neighboring pixels.  The mask represents
+ *          the pixels to be replaced.  For each connected component in
+ *          the mask, this function selects up to two tiles of neighboring
+ *          pixels to be used for replacement of pixels represented by
+ *          the component (i.e., under the FG of that component in the mask).
+ *          After selection, mirror replication is used to generate an
+ *          image that is large enough to cover the component.  Alpha
+ *          blending can also be used outside of the component, but near the
+ *          edge, to blur the transition between painted and original pixels.
+ */
+l_int32
+pixPaintSelfThroughMask(PIX      *pixd,
+                        PIX      *pixm,
+                        l_int32   x,
+                        l_int32   y,
+                        l_int32   searchdir,
+                        l_int32   mindist,
+                        l_int32   tilesize,
+                        l_int32   ntiles,
+                        l_int32   distblend)
+{
+l_int32   w, h, d, wm, hm, dm, i, n, bx, by, bw, bh, edgeblend, retval, minside;
+l_uint32  pixval;
+BOX      *box, *boxv, *boxh;
+BOXA     *boxa;
+PIX      *pixf, *pixv, *pixh, *pix1, *pix2, *pix3, *pix4, *pix5;
+PIXA     *pixa;
+
+    PROCNAME("pixPaintSelfThroughMask");
+
+    if (!pixm)  /* nothing to do */
+        return 0;
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (pixGetColormap(pixd) != NULL)
+        return ERROR_INT("pixd has colormap", procName, 1);
+    pixGetDimensions(pixd, &w, &h, &d);
+    if (d != 8 && d != 32)
+        return ERROR_INT("pixd not 8 or 32 bpp", procName, 1);
+    pixGetDimensions(pixm, &wm, &hm, &dm);
+    if (dm != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+    if (x < 0 || y < 0)
+        return ERROR_INT("x and y must be non-negative", procName, 1);
+    if (searchdir != L_HORIZ && searchdir != L_VERT &&
+        searchdir != L_BOTH_DIRECTIONS)
+        return ERROR_INT("invalid searchdir", procName, 1);
+    if (tilesize < 2)
+        return ERROR_INT("tilesize must be >= 2", procName, 1);
+    if (distblend < 0)
+        return ERROR_INT("distblend must be >= 0", procName, 1);
+
+        /* Embed mask in full sized mask */
+    if (wm < w || hm < h) {
+        pixf = pixCreate(w, h, 1);
+        pixRasterop(pixf, x, y, wm, hm, PIX_SRC, pixm, 0, 0);
+    } else {
+        pixf = pixCopy(NULL, pixm);
+    }
+
+        /* Get connected components of mask */
+    boxa = pixConnComp(pixf, &pixa, 8);
+    if ((n = pixaGetCount(pixa)) == 0) {
+        L_WARNING("no fg in mask\n", procName);
+        pixDestroy(&pixf);
+        pixaDestroy(&pixa);
+        boxaDestroy(&boxa);
+        return 1;
+    }
+    boxaDestroy(&boxa);
+
+        /* For each c.c., generate one or two representative tiles for
+         * texturizing and apply through the mask.  The input 'tilesize'
+         * is the requested value.  Note that if there is exactly one
+         * component, and blending at the edge is requested, an alpha mask
+         * is generated, which is larger than the bounding box of the c.c. */
+    edgeblend = (n == 1 && distblend > 0) ? 1 : 0;
+    if (distblend > 0 && n > 1)
+        L_WARNING("%d components; can not blend at edges\n", procName, n);
+    retval = 0;
+    for (i = 0; i < n; i++) {
+        if (edgeblend) {
+            pix1 = pixMakeAlphaFromMask(pixf, distblend, &box);
+        } else {
+            pix1 = pixaGetPix(pixa, i, L_CLONE);
+            box = pixaGetBox(pixa, i, L_CLONE);
+        }
+        boxGetGeometry(box, &bx, &by, &bw, &bh);
+        minside = L_MIN(bw, bh);
+
+        boxh = boxv = NULL;
+        if (searchdir == L_HORIZ || searchdir == L_BOTH_DIRECTIONS) {
+            pixFindRepCloseTile(pixd, box, L_HORIZ, mindist,
+                                L_MIN(minside, tilesize), ntiles, &boxh, 0);
+        }
+        if (searchdir == L_VERT || searchdir == L_BOTH_DIRECTIONS) {
+            pixFindRepCloseTile(pixd, box, L_VERT, mindist,
+                                L_MIN(minside, tilesize), ntiles, &boxv, 0);
+        }
+        if (!boxh && !boxv) {
+            L_WARNING("tile region not selected; paint color near boundary\n",
+                      procName);
+            pixDestroy(&pix1);
+            pix1 = pixaGetPix(pixa, i, L_CLONE);
+            pixaGetBoxGeometry(pixa, i, &bx, &by, NULL, NULL);
+            retval = pixGetColorNearMaskBoundary(pixd, pixm, box, distblend,
+                                                 &pixval, 0);
+            pixSetMaskedGeneral(pixd, pix1, pixval, bx, by);
+            pixDestroy(&pix1);
+            boxDestroy(&box);
+            continue;
+        }
+
+            /* Extract the selected squares from pixd */
+        pixh = (boxh) ? pixClipRectangle(pixd, boxh, NULL) : NULL;
+        pixv = (boxv) ? pixClipRectangle(pixd, boxv, NULL) : NULL;
+        if (pixh && pixv)
+            pix2 = pixBlend(pixh, pixv, 0, 0, 0.5);
+        else if (pixh)
+            pix2 = pixClone(pixh);
+        else  /* pixv */
+            pix2 = pixClone(pixv);
+        pixDestroy(&pixh);
+        pixDestroy(&pixv);
+        boxDestroy(&boxh);
+        boxDestroy(&boxv);
+
+            /* Generate an image the size of the b.b. of the c.c.,
+             * possibly extended by the blending distance, which
+             * is then either painted through the c.c. mask or
+             * blended using the alpha mask for that c.c.  */
+        pix3 = pixMirroredTiling(pix2, bw, bh);
+        if (edgeblend) {
+            pix4 = pixClipRectangle(pixd, box, NULL);
+            pix5 = pixBlendWithGrayMask(pix4, pix3, pix1, 0, 0);
+            pixRasterop(pixd, bx, by, bw, bh, PIX_SRC, pix5, 0, 0);
+            pixDestroy(&pix4);
+            pixDestroy(&pix5);
+        } else {
+            pixCombineMaskedGeneral(pixd, pix3, pix1, bx, by);
+        }
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+        pixDestroy(&pix3);
+        boxDestroy(&box);
+    }
+
+    pixaDestroy(&pixa);
+    pixDestroy(&pixf);
+    return retval;
+}
+
+
+/*!
+ *  pixMakeMaskFromLUT()
+ *
+ *      Input:  pixs (2, 4 or 8 bpp; can be colormapped)
+ *              tab (256-entry LUT; 1 means to write to mask)
+ *      Return: pixd (1 bpp mask), or null on error
+ *
+ *  Notes:
+ *      (1) This generates a 1 bpp mask image, where a 1 is written in
+ *          the mask for each pixel in pixs that has a value corresponding
+ *          to a 1 in the LUT.
+ *      (2) The LUT should be of size 256.
+ */
+PIX *
+pixMakeMaskFromLUT(PIX      *pixs,
+                   l_int32  *tab)
+{
+l_int32    w, h, d, i, j, val, wpls, wpld;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixMakeMaskFromLUT");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!tab)
+        return (PIX *)ERROR_PTR("tab not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 2 && d != 4 && d != 8)
+        return (PIX *)ERROR_PTR("pix not 2, 4 or 8 bpp", procName, NULL);
+
+    pixd = pixCreate(w, h, 1);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            if (d == 2)
+                val = GET_DATA_DIBIT(lines, j);
+            else if (d == 4)
+                val = GET_DATA_QBIT(lines, j);
+            else  /* d == 8 */
+                val = GET_DATA_BYTE(lines, j);
+            if (tab[val] == 1)
+                SET_DATA_BIT(lined, j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixSetUnderTransparency()
+ *
+ *      Input:  pixs (32 bpp rgba)
+ *              val (32 bit unsigned color to use where alpha == 0)
+ *              debug (displays layers of pixs)
+ *      Return: pixd (32 bpp rgba), or null on error
+ *
+ *  Notes:
+ *      (1) This sets the r, g and b components under every fully
+ *          transparent alpha component to @val.  The alpha components
+ *          are unchanged.
+ *      (2) Full transparency is denoted by alpha == 0.  Setting
+ *          all pixels to a constant @val where alpha is transparent
+ *          can improve compressibility by reducing the entropy.
+ *      (3) The visual result depends on how the image is displayed.
+ *          (a) For display devices that respect the use of the alpha
+ *              layer, this will not affect the appearance.
+ *          (b) For typical leptonica operations, alpha is ignored,
+ *              so there will be a change in appearance because this
+ *              resets the rgb values in the fully transparent region.
+ *      (4) pixRead() and pixWrite() will, by default, read and write
+ *          4-component (rgba) pix in png format.  To ignore the alpha
+ *          component after reading, or omit it on writing, pixSetSpp(..., 3).
+ *      (5) Here are some examples:
+ *          * To convert all fully transparent pixels in a 4 component
+ *            (rgba) png file to white:
+ *              pixs = pixRead(<infile>);
+ *              pixd = pixSetUnderTransparency(pixs, 0xffffff00, 0);
+ *          * To write pixd with the alpha component:
+ *              pixWrite(<outfile>, pixd, IFF_PNG);
+ *          * To write and rgba image without the alpha component, first do:
+ *              pixSetSpp(pixd, 3);
+ *            If you later want to use the alpha, spp must be reset to 4.
+ *          * (fancier) To remove the alpha by blending the image over
+ *            a white background:
+ *              pixRemoveAlpha()
+ *            This changes all pixel values where the alpha component is
+ *            not opaque (255).
+ *      (6) Caution.  rgb images in leptonica typically have value 0 in
+ *          the alpha channel, which is fully transparent.  If spp for
+ *          such an image were changed from 3 to 4, the image becomes
+ *          fully transparent, and this function will set each pixel to @val.
+ *          If you really want to set every pixel to the same value,
+ *          use pixSetAllArbitrary().
+ *      (7) This is useful for compressing an RGBA image where the part
+ *          of the image that is fully transparent is random junk; compression
+ *          is typically improved by setting that region to a constant.
+ *          For rendering as a 3 component RGB image over a uniform
+ *          background of arbitrary color, use pixAlphaBlendUniform().
+ */
+PIX *
+pixSetUnderTransparency(PIX      *pixs,
+                        l_uint32  val,
+                        l_int32   debug)
+{
+PIX  *pixg, *pixm, *pixt, *pixd;
+
+    PROCNAME("pixSetUnderTransparency");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not defined or not 32 bpp",
+                                procName, NULL);
+
+    if (pixGetSpp(pixs) != 4) {
+        L_WARNING("no alpha channel; returning a copy\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+        /* Make a mask from the alpha component with ON pixels
+         * wherever the alpha component is fully transparent (0).
+         * The hard way:
+         *     l_int32 *lut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+         *     lut[0] = 1;
+         *     pixg = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+         *     pixm = pixMakeMaskFromLUT(pixg, lut);
+         *     LEPT_FREE(lut);
+         * But there's an easier way to set pixels in a mask where
+         * the alpha component is 0 ...  */
+    pixg = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+    pixm = pixThresholdToBinary(pixg, 1);
+
+    if (debug) {
+        pixt = pixDisplayLayersRGBA(pixs, 0xffffff00, 600);
+        pixDisplay(pixt, 0, 0);
+        pixDestroy(&pixt);
+    }
+
+    pixd = pixCopy(NULL, pixs);
+    pixSetMasked(pixd, pixm, (val & 0xffffff00));
+    pixDestroy(&pixg);
+    pixDestroy(&pixm);
+    return pixd;
+}
+
+
+/*!
+ *  pixMakeAlphaFromMask()
+ *
+ *      Input:  pixs (1 bpp)
+ *              dist (blending distance; typically 10 - 30)
+ *              &box (<optional return>, use null to get the full size
+ *      Return: pixd (8 bpp gray), or null on error
+ *
+ *  Notes:
+ *      (1) This generates a 8 bpp alpha layer that is opaque (256)
+ *          over the FG of pixs, and goes transparent linearly away
+ *          from the FG pixels, decaying to 0 (transparent) is an
+ *          8-connected distance given by @dist.  If @dist == 0,
+ *          this does a simple conversion from 1 to 8 bpp.
+ *      (2) If &box == NULL, this returns an alpha mask that is the
+ *          full size of pixs.  Otherwise, the returned mask pixd covers
+ *          just the FG pixels of pixs, expanded by @dist in each
+ *          direction (if possible), and the returned box gives the
+ *          location of the returned mask relative to pixs.
+ *      (3) This is useful for painting through a mask and allowing
+ *          blending of the painted image with an underlying image
+ *          in the mask background for pixels near foreground mask pixels.
+ *          For example, with an underlying rgb image pix1, an overlaying
+ *          image rgb pix2, binary mask pixm, and dist > 0, this
+ *          blending is achieved with:
+ *              pix3 = pixMakeAlphaFromMask(pixm, dist, &box);
+ *              boxGetGeometry(box, &x, &y, NULL, NULL);
+ *              pix4 = pixBlendWithGrayMask(pix1, pix2, pix3, x, y);
+ */
+PIX *
+pixMakeAlphaFromMask(PIX     *pixs,
+                     l_int32  dist,
+                     BOX    **pbox)
+{
+l_int32  w, h;
+BOX     *box1, *box2;
+PIX     *pix1, *pixd;
+
+    PROCNAME("pixMakeAlphaFromMask");
+
+    if (pbox) *pbox = NULL;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (dist < 0)
+        return (PIX *)ERROR_PTR("dist must be >= 0", procName, NULL);
+
+        /* If requested, extract just the region to be affected by the mask */
+    if (pbox) {
+        pixClipToForeground(pixs, NULL, &box1);
+        if (!box1) {
+            L_WARNING("no ON pixels in mask\n", procName);
+            return pixCreateTemplate(pixs);  /* all background (0) */
+        }
+
+        boxAdjustSides(box1, box1, -dist, dist, -dist, dist);
+        pixGetDimensions(pixs, &w, &h, NULL);
+        box2 = boxClipToRectangle(box1, w, h);
+        *pbox = box2;
+        pix1 = pixClipRectangle(pixs, box2, NULL);
+        boxDestroy(&box1);
+    } else {
+        pix1 = pixCopy(NULL, pixs);
+    }
+
+    if (dist == 0) {
+        pixd = pixConvert1To8(NULL, pix1, 0, 255);
+        pixDestroy(&pix1);
+        return pixd;
+    }
+
+        /* Blur the boundary of the input mask */
+    pixInvert(pix1, pix1);
+    pixd = pixDistanceFunction(pix1, 8, 8, L_BOUNDARY_FG);
+    pixMultConstantGray(pixd, 256.0 / dist);
+    pixInvert(pixd, pixd);
+    pixDestroy(&pix1);
+    return pixd;
+}
+
+
+/*!
+ *  pixGetColorNearMaskBoundary()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              pixm (1 bpp mask, full image)
+ *              box (region of mask; typically b.b. of a component)
+ *              dist (distance into BG from mask boundary to use)
+ *              &pval (<return> average pixel value)
+ *              debug (1 to output mask images)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) This finds the average color in a set of pixels that are
+ *          roughly a distance @dist from the c.c. boundary and in the
+ *          background of the mask image.
+ */
+l_int32
+pixGetColorNearMaskBoundary(PIX       *pixs,
+                            PIX       *pixm,
+                            BOX       *box,
+                            l_int32    dist,
+                            l_uint32  *pval,
+                            l_int32    debug)
+{
+char       op[64];
+l_int32    empty, bx, by;
+l_float32  rval, gval, bval;
+BOX       *box1, *box2;
+PIX       *pix1, *pix2, *pix3;
+
+    PROCNAME("pixGetColorNearMaskBoundary");
+
+    if (!pval)
+        return ERROR_INT("&pval not defined", procName, 1);
+    *pval = 0xffffff00;  /* white */
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs undefined or not 32 bpp", procName, 1);
+    if (!pixm || pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm undefined or not 1 bpp", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (dist < 0)
+        return ERROR_INT("dist must be >= 0", procName, 1);
+
+        /* Clip mask piece, expanded beyond @box by (@dist + 5) on each side.
+         * box1 is the region requested; box2 is the actual region retrieved,
+         * which is clipped to @pixm */
+    box1 = boxAdjustSides(NULL, box, -dist - 5, dist + 5, -dist - 5, dist + 5);
+    pix1 = pixClipRectangle(pixm, box1, &box2);
+
+        /* Expand FG by @dist into the BG */
+    if (dist == 0) {
+        pix2 = pixCopy(NULL, pix1);
+    } else {
+        snprintf(op, sizeof(op), "d%d.%d", 2 * dist, 2 * dist);
+        pix2 = pixMorphSequence(pix1, op, 0);
+    }
+
+        /* Expand again by 5 pixels on all sides (dilate 11x11) and XOR,
+         * getting the annulus of FG pixels between @dist and @dist + 5 */
+    pix3 = pixCopy(NULL, pix2);
+    pixDilateBrick(pix3, pix3, 11, 11);
+    pixXor(pix3, pix3, pix2);
+    pixZero(pix3, &empty);
+    if (!empty) {
+            /* Scan the same region in @pixs, to get average under FG in pix3 */
+        boxGetGeometry(box2, &bx, &by, NULL, NULL);
+        pixGetAverageMaskedRGB(pixs, pix3, bx, by, 1, L_MEAN_ABSVAL,
+                               &rval, &gval, &bval);
+        composeRGBPixel((l_int32)(rval + 0.5), (l_int32)(gval + 0.5),
+                        (l_int32)(bval + 0.5), pval);
+    } else {
+        L_WARNING("no pixels found\n", procName);
+    }
+
+    if (debug) {
+        lept_rmdir("masknear");  /* erase previous images */
+        lept_mkdir("masknear");
+        pixWrite("/tmp/masknear/input.png", pix1, IFF_PNG);
+        pixWrite("/tmp/masknear/adjusted.png", pix2, IFF_PNG);
+        pixWrite("/tmp/masknear/outerfive.png", pix3, IFF_PNG);
+        fprintf(stderr, "Input box; with adjusted sides; clipped\n");
+        boxPrintStreamInfo(stderr, box);
+        boxPrintStreamInfo(stderr, box1);
+        boxPrintStreamInfo(stderr, box2);
+    }
+
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    pixDestroy(&pix3);
+    boxDestroy(&box1);
+    boxDestroy(&box2);
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *    One and two-image boolean ops on arbitrary depth images  *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixInvert()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This inverts pixs, for all pixel depths.
+ *      (2) There are 3 cases:
+ *           (a) pixd == null,   ~src --> new pixd
+ *           (b) pixd == pixs,   ~src --> src  (in-place)
+ *           (c) pixd != pixs,   ~src --> input pixd
+ *      (3) For clarity, if the case is known, use these patterns:
+ *           (a) pixd = pixInvert(NULL, pixs);
+ *           (b) pixInvert(pixs, pixs);
+ *           (c) pixInvert(pixd, pixs);
+ */
+PIX *
+pixInvert(PIX  *pixd,
+          PIX  *pixs)
+{
+    PROCNAME("pixInvert");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+        /* Prepare pixd for in-place operation */
+    if ((pixd = pixCopy(pixd, pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd),
+                PIX_NOT(PIX_DST), NULL, 0, 0);   /* invert pixd */
+
+    return pixd;
+}
+
+
+/*!
+ *  pixOr()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs1,
+ *                     different from pixs1)
+ *              pixs1 (can be == pixd)
+ *              pixs2 (must be != pixd)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) This gives the union of two images with equal depth,
+ *          aligning them to the the UL corner.  pixs1 and pixs2
+ *          need not have the same width and height.
+ *      (2) There are 3 cases:
+ *            (a) pixd == null,   (src1 | src2) --> new pixd
+ *            (b) pixd == pixs1,  (src1 | src2) --> src1  (in-place)
+ *            (c) pixd != pixs1,  (src1 | src2) --> input pixd
+ *      (3) For clarity, if the case is known, use these patterns:
+ *            (a) pixd = pixOr(NULL, pixs1, pixs2);
+ *            (b) pixOr(pixs1, pixs1, pixs2);
+ *            (c) pixOr(pixd, pixs1, pixs2);
+ *      (4) The size of the result is determined by pixs1.
+ *      (5) The depths of pixs1 and pixs2 must be equal.
+ *      (6) Note carefully that the order of pixs1 and pixs2 only matters
+ *          for the in-place case.  For in-place, you must have
+ *          pixd == pixs1.  Setting pixd == pixs2 gives an incorrect
+ *          result: the copy puts pixs1 image data in pixs2, and
+ *          the rasterop is then between pixs2 and pixs2 (a no-op).
+ */
+PIX *
+pixOr(PIX  *pixd,
+      PIX  *pixs1,
+      PIX  *pixs2)
+{
+    PROCNAME("pixOr");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
+    if (pixd == pixs2)
+        return (PIX *)ERROR_PTR("cannot have pixs2 == pixd", procName, pixd);
+    if (pixGetDepth(pixs1) != pixGetDepth(pixs2))
+        return (PIX *)ERROR_PTR("depths of pixs* unequal", procName, pixd);
+
+#if  EQUAL_SIZE_WARNING
+    if (!pixSizesEqual(pixs1, pixs2))
+        L_WARNING("pixs1 and pixs2 not equal sizes\n", procName);
+#endif  /* EQUAL_SIZE_WARNING */
+
+        /* Prepare pixd to be a copy of pixs1 */
+    if ((pixd = pixCopy(pixd, pixs1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, pixd);
+
+        /* src1 | src2 --> dest */
+    pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd),
+                PIX_SRC | PIX_DST, pixs2, 0, 0);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixAnd()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs1,
+ *                     different from pixs1)
+ *              pixs1 (can be == pixd)
+ *              pixs2 (must be != pixd)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) This gives the intersection of two images with equal depth,
+ *          aligning them to the the UL corner.  pixs1 and pixs2
+ *          need not have the same width and height.
+ *      (2) There are 3 cases:
+ *            (a) pixd == null,   (src1 & src2) --> new pixd
+ *            (b) pixd == pixs1,  (src1 & src2) --> src1  (in-place)
+ *            (c) pixd != pixs1,  (src1 & src2) --> input pixd
+ *      (3) For clarity, if the case is known, use these patterns:
+ *            (a) pixd = pixAnd(NULL, pixs1, pixs2);
+ *            (b) pixAnd(pixs1, pixs1, pixs2);
+ *            (c) pixAnd(pixd, pixs1, pixs2);
+ *      (4) The size of the result is determined by pixs1.
+ *      (5) The depths of pixs1 and pixs2 must be equal.
+ *      (6) Note carefully that the order of pixs1 and pixs2 only matters
+ *          for the in-place case.  For in-place, you must have
+ *          pixd == pixs1.  Setting pixd == pixs2 gives an incorrect
+ *          result: the copy puts pixs1 image data in pixs2, and
+ *          the rasterop is then between pixs2 and pixs2 (a no-op).
+ */
+PIX *
+pixAnd(PIX  *pixd,
+       PIX  *pixs1,
+       PIX  *pixs2)
+{
+    PROCNAME("pixAnd");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
+    if (pixd == pixs2)
+        return (PIX *)ERROR_PTR("cannot have pixs2 == pixd", procName, pixd);
+    if (pixGetDepth(pixs1) != pixGetDepth(pixs2))
+        return (PIX *)ERROR_PTR("depths of pixs* unequal", procName, pixd);
+
+#if  EQUAL_SIZE_WARNING
+    if (!pixSizesEqual(pixs1, pixs2))
+        L_WARNING("pixs1 and pixs2 not equal sizes\n", procName);
+#endif  /* EQUAL_SIZE_WARNING */
+
+        /* Prepare pixd to be a copy of pixs1 */
+    if ((pixd = pixCopy(pixd, pixs1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, pixd);
+
+        /* src1 & src2 --> dest */
+    pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd),
+                PIX_SRC & PIX_DST, pixs2, 0, 0);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixXor()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs1,
+ *                     different from pixs1)
+ *              pixs1 (can be == pixd)
+ *              pixs2 (must be != pixd)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) This gives the XOR of two images with equal depth,
+ *          aligning them to the the UL corner.  pixs1 and pixs2
+ *          need not have the same width and height.
+ *      (2) There are 3 cases:
+ *            (a) pixd == null,   (src1 ^ src2) --> new pixd
+ *            (b) pixd == pixs1,  (src1 ^ src2) --> src1  (in-place)
+ *            (c) pixd != pixs1,  (src1 ^ src2) --> input pixd
+ *      (3) For clarity, if the case is known, use these patterns:
+ *            (a) pixd = pixXor(NULL, pixs1, pixs2);
+ *            (b) pixXor(pixs1, pixs1, pixs2);
+ *            (c) pixXor(pixd, pixs1, pixs2);
+ *      (4) The size of the result is determined by pixs1.
+ *      (5) The depths of pixs1 and pixs2 must be equal.
+ *      (6) Note carefully that the order of pixs1 and pixs2 only matters
+ *          for the in-place case.  For in-place, you must have
+ *          pixd == pixs1.  Setting pixd == pixs2 gives an incorrect
+ *          result: the copy puts pixs1 image data in pixs2, and
+ *          the rasterop is then between pixs2 and pixs2 (a no-op).
+ */
+PIX *
+pixXor(PIX  *pixd,
+       PIX  *pixs1,
+       PIX  *pixs2)
+{
+    PROCNAME("pixXor");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
+    if (pixd == pixs2)
+        return (PIX *)ERROR_PTR("cannot have pixs2 == pixd", procName, pixd);
+    if (pixGetDepth(pixs1) != pixGetDepth(pixs2))
+        return (PIX *)ERROR_PTR("depths of pixs* unequal", procName, pixd);
+
+#if  EQUAL_SIZE_WARNING
+    if (!pixSizesEqual(pixs1, pixs2))
+        L_WARNING("pixs1 and pixs2 not equal sizes\n", procName);
+#endif  /* EQUAL_SIZE_WARNING */
+
+        /* Prepare pixd to be a copy of pixs1 */
+    if ((pixd = pixCopy(pixd, pixs1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, pixd);
+
+        /* src1 ^ src2 --> dest */
+    pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd),
+                PIX_SRC ^ PIX_DST, pixs2, 0, 0);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixSubtract()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs1,
+ *                     equal to pixs2, or different from both pixs1 and pixs2)
+ *              pixs1 (can be == pixd)
+ *              pixs2 (can be == pixd)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) This gives the set subtraction of two images with equal depth,
+ *          aligning them to the the UL corner.  pixs1 and pixs2
+ *          need not have the same width and height.
+ *      (2) Source pixs2 is always subtracted from source pixs1.
+ *          The result is
+ *                  pixs1 \ pixs2 = pixs1 & (~pixs2)
+ *      (3) There are 4 cases:
+ *            (a) pixd == null,   (src1 - src2) --> new pixd
+ *            (b) pixd == pixs1,  (src1 - src2) --> src1  (in-place)
+ *            (c) pixd == pixs2,  (src1 - src2) --> src2  (in-place)
+ *            (d) pixd != pixs1 && pixd != pixs2),
+ *                                 (src1 - src2) --> input pixd
+ *      (4) For clarity, if the case is known, use these patterns:
+ *            (a) pixd = pixSubtract(NULL, pixs1, pixs2);
+ *            (b) pixSubtract(pixs1, pixs1, pixs2);
+ *            (c) pixSubtract(pixs2, pixs1, pixs2);
+ *            (d) pixSubtract(pixd, pixs1, pixs2);
+ *      (5) The size of the result is determined by pixs1.
+ *      (6) The depths of pixs1 and pixs2 must be equal.
+ */
+PIX *
+pixSubtract(PIX  *pixd,
+            PIX  *pixs1,
+            PIX  *pixs2)
+{
+l_int32  w, h;
+
+    PROCNAME("pixSubtract");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
+    if (pixGetDepth(pixs1) != pixGetDepth(pixs2))
+        return (PIX *)ERROR_PTR("depths of pixs* unequal", procName, pixd);
+
+#if  EQUAL_SIZE_WARNING
+    if (!pixSizesEqual(pixs1, pixs2))
+        L_WARNING("pixs1 and pixs2 not equal sizes\n", procName);
+#endif  /* EQUAL_SIZE_WARNING */
+
+    pixGetDimensions(pixs1, &w, &h, NULL);
+    if (!pixd) {
+        pixd = pixCopy(NULL, pixs1);
+        pixRasterop(pixd, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC),
+            pixs2, 0, 0);   /* src1 & (~src2)  */
+    } else if (pixd == pixs1) {
+        pixRasterop(pixd, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC),
+            pixs2, 0, 0);   /* src1 & (~src2)  */
+    } else if (pixd == pixs2) {
+        pixRasterop(pixd, 0, 0, w, h, PIX_NOT(PIX_DST) & PIX_SRC,
+            pixs1, 0, 0);   /* src1 & (~src2)  */
+    } else  { /* pixd != pixs1 && pixd != pixs2 */
+        pixCopy(pixd, pixs1);  /* sizes pixd to pixs1 if unequal */
+        pixRasterop(pixd, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC),
+            pixs2, 0, 0);   /* src1 & (~src2)  */
+    }
+
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ *                         Pixel counting                      *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixZero()
+ *
+ *      Input:  pix (all depths; colormap OK)
+ *              &empty  (<return> 1 if all bits in image data field are 0;
+ *                       0 otherwise)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) For a binary image, if there are no fg (black) pixels, empty = 1.
+ *      (2) For a grayscale image, if all pixels are black (0), empty = 1.
+ *      (3) For an RGB image, if all 4 components in every pixel is 0,
+ *          empty = 1.
+ *      (4) For a colormapped image, pixel values are 0.  The colormap
+ *          is ignored.
+ */
+l_int32
+pixZero(PIX      *pix,
+        l_int32  *pempty)
+{
+l_int32    w, h, wpl, i, j, fullwords, endbits;
+l_uint32   endmask;
+l_uint32  *data, *line;
+
+    PROCNAME("pixZero");
+
+    if (!pempty)
+        return ERROR_INT("&empty not defined", procName, 1);
+    *pempty = 1;
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    w = pixGetWidth(pix) * pixGetDepth(pix);  /* in bits */
+    h = pixGetHeight(pix);
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    fullwords = w / 32;
+    endbits = w & 31;
+    endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits));
+
+    for (i = 0; i < h; i++) {
+        line = data + wpl * i;
+        for (j = 0; j < fullwords; j++)
+            if (*line++) {
+                *pempty = 0;
+                return 0;
+            }
+        if (endbits) {
+            if (*line & endmask) {
+                *pempty = 0;
+                return 0;
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixForegroundFraction()
+ *
+ *      Input:  pix (1 bpp)
+ *              &fract (<return> fraction of ON pixels)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixForegroundFraction(PIX        *pix,
+                      l_float32  *pfract)
+{
+l_int32  w, h, count;
+
+    PROCNAME("pixForegroundFraction");
+
+    if (!pfract)
+        return ERROR_INT("&fract not defined", procName, 1);
+    *pfract = 0.0;
+    if (!pix || pixGetDepth(pix) != 1)
+        return ERROR_INT("pix not defined or not 1 bpp", procName, 1);
+
+    pixCountPixels(pix, &count, NULL);
+    pixGetDimensions(pix, &w, &h, NULL);
+    *pfract = (l_float32)count / (l_float32)(w * h);
+    return 0;
+}
+
+
+/*!
+ *  pixaCountPixels()
+ *
+ *      Input:  pixa (array of 1 bpp pix)
+ *      Return: na of ON pixels in each pix, or null on error
+ */
+NUMA *
+pixaCountPixels(PIXA  *pixa)
+{
+l_int32   d, i, n, count;
+l_int32  *tab;
+NUMA     *na;
+PIX      *pix;
+
+    PROCNAME("pixaCountPixels");
+
+    if (!pixa)
+        return (NUMA *)ERROR_PTR("pix not defined", procName, NULL);
+
+    if ((n = pixaGetCount(pixa)) == 0)
+        return numaCreate(1);
+
+    pix = pixaGetPix(pixa, 0, L_CLONE);
+    d = pixGetDepth(pix);
+    pixDestroy(&pix);
+    if (d != 1)
+        return (NUMA *)ERROR_PTR("pixa not 1 bpp", procName, NULL);
+
+    tab = makePixelSumTab8();
+    if ((na = numaCreate(n)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        pixCountPixels(pix, &count, tab);
+        numaAddNumber(na, count);
+        pixDestroy(&pix);
+    }
+
+    LEPT_FREE(tab);
+    return na;
+}
+
+
+/*!
+ *  pixCountPixels()
+ *
+ *      Input:  pix (1 bpp)
+ *              &count (<return> count of ON pixels)
+ *              tab8  (<optional> 8-bit pixel lookup table)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixCountPixels(PIX      *pix,
+               l_int32  *pcount,
+               l_int32  *tab8)
+{
+l_uint32   endmask;
+l_int32    w, h, wpl, i, j;
+l_int32    fullwords, endbits, sum;
+l_int32   *tab;
+l_uint32  *data;
+
+    PROCNAME("pixCountPixels");
+
+    if (!pcount)
+        return ERROR_INT("&count not defined", procName, 1);
+    *pcount = 0;
+    if (!pix || pixGetDepth(pix) != 1)
+        return ERROR_INT("pix not defined or not 1 bpp", procName, 1);
+
+    if (!tab8)
+        tab = makePixelSumTab8();
+    else
+        tab = tab8;
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    fullwords = w >> 5;
+    endbits = w & 31;
+    endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits));
+
+    sum = 0;
+    for (i = 0; i < h; i++, data += wpl) {
+        for (j = 0; j < fullwords; j++) {
+            l_uint32 word = data[j];
+            if (word) {
+                sum += tab[word & 0xff] +
+                       tab[(word >> 8) & 0xff] +
+                       tab[(word >> 16) & 0xff] +
+                       tab[(word >> 24) & 0xff];
+            }
+        }
+        if (endbits) {
+            l_uint32 word = data[j] & endmask;
+            if (word) {
+                sum += tab[word & 0xff] +
+                       tab[(word >> 8) & 0xff] +
+                       tab[(word >> 16) & 0xff] +
+                       tab[(word >> 24) & 0xff];
+            }
+        }
+    }
+    *pcount = sum;
+
+    if (!tab8)
+        LEPT_FREE(tab);
+    return 0;
+}
+
+
+/*!
+ *  pixCountByRow()
+ *
+ *      Input:  pix (1 bpp)
+ *              box (<optional> clipping box for count; can be null)
+ *      Return: na of number of ON pixels by row, or null on error
+ *
+ *  Notes:
+ *      (1) To resample for a bin size different from 1, use
+ *          numaUniformSampling() on the result of this function.
+ */
+NUMA *
+pixCountByRow(PIX      *pix,
+              BOX      *box)
+{
+l_int32    i, j, w, h, wpl, count, xstart, xend, ystart, yend, bw, bh;
+l_uint32  *line, *data;
+NUMA      *na;
+
+    PROCNAME("pixCountByRow");
+
+    if (!pix || pixGetDepth(pix) != 1)
+        return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL);
+    if (!box)
+        return pixCountPixelsByRow(pix, NULL);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL);
+
+    if ((na = numaCreate(bh)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetParameters(na, ystart, 1);
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (i = ystart; i < yend; i++) {
+        count = 0;
+        line = data + i * wpl;
+        for (j = xstart; j < xend; j++) {
+            if (GET_DATA_BIT(line, j))
+                count++;
+        }
+        numaAddNumber(na, count);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixCountByColumn()
+ *
+ *      Input:  pix (1 bpp)
+ *              box (<optional> clipping box for count; can be null)
+ *      Return: na of number of ON pixels by column, or null on error
+ *
+ *  Notes:
+ *      (1) To resample for a bin size different from 1, use
+ *          numaUniformSampling() on the result of this function.
+ */
+NUMA *
+pixCountByColumn(PIX      *pix,
+                 BOX      *box)
+{
+l_int32    i, j, w, h, wpl, count, xstart, xend, ystart, yend, bw, bh;
+l_uint32  *line, *data;
+NUMA      *na;
+
+    PROCNAME("pixCountByColumn");
+
+    if (!pix || pixGetDepth(pix) != 1)
+        return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL);
+    if (!box)
+        return pixCountPixelsByColumn(pix);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL);
+
+    if ((na = numaCreate(bw)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetParameters(na, xstart, 1);
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (j = xstart; j < xend; j++) {
+        count = 0;
+        for (i = ystart; i < yend; i++) {
+            line = data + i * wpl;
+            if (GET_DATA_BIT(line, j))
+                count++;
+        }
+        numaAddNumber(na, count);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixCountPixelsByRow()
+ *
+ *      Input:  pix (1 bpp)
+ *              tab8  (<optional> 8-bit pixel lookup table)
+ *      Return: na of counts, or null on error
+ */
+NUMA *
+pixCountPixelsByRow(PIX      *pix,
+                    l_int32  *tab8)
+{
+l_int32   h, i, count;
+l_int32  *tab;
+NUMA     *na;
+
+    PROCNAME("pixCountPixelsByRow");
+
+    if (!pix || pixGetDepth(pix) != 1)
+        return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL);
+
+    if (!tab8)
+        tab = makePixelSumTab8();
+    else
+        tab = tab8;
+
+    h = pixGetHeight(pix);
+    if ((na = numaCreate(h)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    for (i = 0; i < h; i++) {
+        pixCountPixelsInRow(pix, i, &count, tab);
+        numaAddNumber(na, count);
+    }
+
+    if (!tab8) LEPT_FREE(tab);
+    return na;
+}
+
+
+/*!
+ *  pixCountPixelsByColumn()
+ *
+ *      Input:  pix (1 bpp)
+ *      Return: na of counts in each column, or null on error
+ */
+NUMA *
+pixCountPixelsByColumn(PIX  *pix)
+{
+l_int32     i, j, w, h, wpl;
+l_uint32   *line, *data;
+l_float32  *array;
+NUMA       *na;
+
+    PROCNAME("pixCountPixelsByColumn");
+
+    if (!pix || pixGetDepth(pix) != 1)
+        return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    if ((na = numaCreate(w)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetCount(na, w);
+    array = numaGetFArray(na, L_NOCOPY);
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (i = 0; i < h; i++) {
+        line = data + wpl * i;
+        for (j = 0; j < w; j++) {
+            if (GET_DATA_BIT(line, j))
+                array[j] += 1.0;
+        }
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixCountPixelsInRow()
+ *
+ *      Input:  pix (1 bpp)
+ *              row number
+ *              &count (<return> sum of ON pixels in raster line)
+ *              tab8  (<optional> 8-bit pixel lookup table)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixCountPixelsInRow(PIX      *pix,
+                    l_int32   row,
+                    l_int32  *pcount,
+                    l_int32  *tab8)
+{
+l_uint32   word, endmask;
+l_int32    j, w, h, wpl;
+l_int32    fullwords, endbits, sum;
+l_int32   *tab;
+l_uint32  *line;
+
+    PROCNAME("pixCountPixelsInRow");
+
+    if (!pcount)
+        return ERROR_INT("&count not defined", procName, 1);
+    *pcount = 0;
+    if (!pix || pixGetDepth(pix) != 1)
+        return ERROR_INT("pix not defined or not 1 bpp", procName, 1);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    if (row < 0 || row >= h)
+        return ERROR_INT("row out of bounds", procName, 1);
+    wpl = pixGetWpl(pix);
+    line = pixGetData(pix) + row * wpl;
+    fullwords = w >> 5;
+    endbits = w & 31;
+    endmask = (endbits == 0) ? 0 : (0xffffffffU << (32 - endbits));
+
+    if (!tab8)
+        tab = makePixelSumTab8();
+    else
+        tab = tab8;
+
+    sum = 0;
+    for (j = 0; j < fullwords; j++) {
+        word = line[j];
+        if (word) {
+            sum += tab[word & 0xff] +
+                   tab[(word >> 8) & 0xff] +
+                   tab[(word >> 16) & 0xff] +
+                   tab[(word >> 24) & 0xff];
+        }
+    }
+    if (endbits) {
+        word = line[j] & endmask;
+        if (word) {
+            sum += tab[word & 0xff] +
+                   tab[(word >> 8) & 0xff] +
+                   tab[(word >> 16) & 0xff] +
+                   tab[(word >> 24) & 0xff];
+        }
+    }
+    *pcount = sum;
+
+    if (!tab8)
+        LEPT_FREE(tab);
+    return 0;
+}
+
+
+/*!
+ *  pixGetMomentByColumn()
+ *
+ *      Input:  pix (1 bpp)
+ *              order (of moment, either 1 or 2)
+ *      Return: na of first moment of fg pixels, by column, or null on error
+ */
+NUMA *
+pixGetMomentByColumn(PIX     *pix,
+                     l_int32  order)
+{
+l_int32     i, j, w, h, wpl;
+l_uint32   *line, *data;
+l_float32  *array;
+NUMA       *na;
+
+    PROCNAME("pixGetMomentByColumn");
+
+    if (!pix || pixGetDepth(pix) != 1)
+        return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL);
+    if (order != 1 && order != 2)
+        return (NUMA *)ERROR_PTR("order of moment not 1 or 2", procName, NULL);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    if ((na = numaCreate(w)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetCount(na, w);
+    array = numaGetFArray(na, L_NOCOPY);
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (i = 0; i < h; i++) {
+        line = data + wpl * i;
+        for (j = 0; j < w; j++) {
+            if (GET_DATA_BIT(line, j)) {
+                if (order == 1)
+                    array[j] += i;
+                else  /* order == 2 */
+                    array[j] += i * i;
+            }
+        }
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixThresholdPixelSum()
+ *
+ *      Input:  pix (1 bpp)
+ *              threshold
+ *              &above (<return> 1 if above threshold;
+ *                               0 if equal to or less than threshold)
+ *              tab8  (<optional> 8-bit pixel lookup table)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This sums the ON pixels and returns immediately if the count
+ *          goes above threshold.  It is therefore more efficient
+ *          for matching images (by running this function on the xor of
+ *          the 2 images) than using pixCountPixels(), which counts all
+ *          pixels before returning.
+ */
+l_int32
+pixThresholdPixelSum(PIX      *pix,
+                     l_int32   thresh,
+                     l_int32  *pabove,
+                     l_int32  *tab8)
+{
+l_uint32   word, endmask;
+l_int32   *tab;
+l_int32    w, h, wpl, i, j;
+l_int32    fullwords, endbits, sum;
+l_uint32  *line, *data;
+
+    PROCNAME("pixThresholdPixelSum");
+
+    if (!pabove)
+        return ERROR_INT("&above not defined", procName, 1);
+    *pabove = 0;
+    if (!pix || pixGetDepth(pix) != 1)
+        return ERROR_INT("pix not defined or not 1 bpp", procName, 1);
+
+    if (!tab8)
+        tab = makePixelSumTab8();
+    else
+        tab = tab8;
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    fullwords = w >> 5;
+    endbits = w & 31;
+    endmask = 0xffffffff << (32 - endbits);
+
+    sum = 0;
+    for (i = 0; i < h; i++) {
+        line = data + wpl * i;
+        for (j = 0; j < fullwords; j++) {
+            word = line[j];
+            if (word) {
+                sum += tab[word & 0xff] +
+                       tab[(word >> 8) & 0xff] +
+                       tab[(word >> 16) & 0xff] +
+                       tab[(word >> 24) & 0xff];
+            }
+        }
+        if (endbits) {
+            word = line[j] & endmask;
+            if (word) {
+                sum += tab[word & 0xff] +
+                       tab[(word >> 8) & 0xff] +
+                       tab[(word >> 16) & 0xff] +
+                       tab[(word >> 24) & 0xff];
+            }
+        }
+        if (sum > thresh) {
+            *pabove = 1;
+            if (!tab8)
+                LEPT_FREE(tab);
+            return 0;
+        }
+    }
+
+    if (!tab8)
+        LEPT_FREE(tab);
+    return 0;
+}
+
+
+/*!
+ *  makePixelSumTab8()
+ *
+ *      Input:  void
+ *      Return: table of 256 l_int32, or null on error
+ *
+ *  Notes:
+ *      (1) This table of integers gives the number of 1 bits
+ *          in the 8 bit index.
+ */
+l_int32 *
+makePixelSumTab8(void)
+{
+l_uint8   byte;
+l_int32   i;
+l_int32  *tab;
+
+    PROCNAME("makePixelSumTab8");
+
+    if ((tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return (l_int32 *)ERROR_PTR("tab not made", procName, NULL);
+
+    for (i = 0; i < 256; i++) {
+        byte = (l_uint8)i;
+        tab[i] = (byte & 0x1) +
+                 ((byte >> 1) & 0x1) +
+                 ((byte >> 2) & 0x1) +
+                 ((byte >> 3) & 0x1) +
+                 ((byte >> 4) & 0x1) +
+                 ((byte >> 5) & 0x1) +
+                 ((byte >> 6) & 0x1) +
+                 ((byte >> 7) & 0x1);
+    }
+
+    return tab;
+}
+
+
+/*!
+ *  makePixelCentroidTab8()
+ *
+ *      Input:  void
+ *      Return: table of 256 l_int32, or null on error
+ *
+ *  Notes:
+ *      (1) This table of integers gives the centroid weight of the 1 bits
+ *          in the 8 bit index.  In other words, if sumtab is obtained by
+ *          makePixelSumTab8, and centroidtab is obtained by
+ *          makePixelCentroidTab8, then, for 1 <= i <= 255,
+ *          centroidtab[i] / (float)sumtab[i]
+ *          is the centroid of the 1 bits in the 8-bit index i, where the
+ *          MSB is considered to have position 0 and the LSB is considered
+ *          to have position 7.
+ */
+l_int32 *
+makePixelCentroidTab8(void)
+{
+l_int32   i;
+l_int32  *tab;
+
+    PROCNAME("makePixelCentroidTab8");
+
+    if ((tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return (l_int32 *)ERROR_PTR("tab not made", procName, NULL);
+
+    tab[0] = 0;
+    tab[1] = 7;
+    for (i = 2; i < 4; i++) {
+        tab[i] = tab[i - 2] + 6;
+    }
+    for (i = 4; i < 8; i++) {
+        tab[i] = tab[i - 4] + 5;
+    }
+    for (i = 8; i < 16; i++) {
+        tab[i] = tab[i - 8] + 4;
+    }
+    for (i = 16; i < 32; i++) {
+        tab[i] = tab[i - 16] + 3;
+    }
+    for (i = 32; i < 64; i++) {
+        tab[i] = tab[i - 32] + 2;
+    }
+    for (i = 64; i < 128; i++) {
+        tab[i] = tab[i - 64] + 1;
+    }
+    for (i = 128; i < 256; i++) {
+        tab[i] = tab[i - 128];
+    }
+
+    return tab;
+}
+
+
+/*-------------------------------------------------------------*
+ *             Average of pixel values in gray images          *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixAverageByRow()
+ *
+ *      Input:  pix (8 or 16 bpp; no colormap)
+ *              box (<optional> clipping box for sum; can be null)
+ *              type (L_WHITE_IS_MAX, L_BLACK_IS_MAX)
+ *      Return: na of pixel averages by row, or null on error
+ *
+ *  Notes:
+ *      (1) To resample for a bin size different from 1, use
+ *          numaUniformSampling() on the result of this function.
+ *      (2) If type == L_BLACK_IS_MAX, black pixels get the maximum
+ *          value (0xff for 8 bpp, 0xffff for 16 bpp) and white get 0.
+ */
+NUMA *
+pixAverageByRow(PIX     *pix,
+                BOX     *box,
+                l_int32  type)
+{
+l_int32    i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh;
+l_uint32  *line, *data;
+l_float64  norm, sum;
+NUMA      *na;
+
+    PROCNAME("pixAverageByRow");
+
+    if (!pix)
+        return (NUMA *)ERROR_PTR("pix not defined", procName, NULL);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 8 && d != 16)
+        return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", procName, NULL);
+    if (type != L_WHITE_IS_MAX && type != L_BLACK_IS_MAX)
+        return (NUMA *)ERROR_PTR("invalid type", procName, NULL);
+    if (pixGetColormap(pix) != NULL)
+        return (NUMA *)ERROR_PTR("pix colormapped", procName, NULL);
+
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL);
+
+    norm = 1. / (l_float32)bw;
+    if ((na = numaCreate(bh)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetParameters(na, ystart, 1);
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (i = ystart; i < yend; i++) {
+        sum = 0.0;
+        line = data + i * wpl;
+        if (d == 8) {
+            for (j = xstart; j < xend; j++)
+                sum += GET_DATA_BYTE(line, j);
+            if (type == L_BLACK_IS_MAX)
+                sum = bw * 255 - sum;
+        } else {  /* d == 16 */
+            for (j = xstart; j < xend; j++)
+                sum += GET_DATA_TWO_BYTES(line, j);
+            if (type == L_BLACK_IS_MAX)
+                sum = bw * 0xffff - sum;
+        }
+        numaAddNumber(na, (l_float32)(norm * sum));
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixAverageByColumn()
+ *
+ *      Input:  pix (8 or 16 bpp; no colormap)
+ *              box (<optional> clipping box for sum; can be null)
+ *              type (L_WHITE_IS_MAX, L_BLACK_IS_MAX)
+ *      Return: na of pixel averages by column, or null on error
+ *
+ *  Notes:
+ *      (1) To resample for a bin size different from 1, use
+ *          numaUniformSampling() on the result of this function.
+ *      (2) If type == L_BLACK_IS_MAX, black pixels get the maximum
+ *          value (0xff for 8 bpp, 0xffff for 16 bpp) and white get 0.
+ */
+NUMA *
+pixAverageByColumn(PIX     *pix,
+                   BOX     *box,
+                   l_int32  type)
+{
+l_int32     i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh;
+l_uint32   *line, *data;
+l_float32   norm, sum;
+NUMA       *na;
+
+    PROCNAME("pixAverageByColumn");
+
+    if (!pix)
+        return (NUMA *)ERROR_PTR("pix not defined", procName, NULL);
+    pixGetDimensions(pix, &w, &h, &d);
+
+    if (d != 8 && d != 16)
+        return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", procName, NULL);
+    if (type != L_WHITE_IS_MAX && type != L_BLACK_IS_MAX)
+        return (NUMA *)ERROR_PTR("invalid type", procName, NULL);
+    if (pixGetColormap(pix) != NULL)
+        return (NUMA *)ERROR_PTR("pix colormapped", procName, NULL);
+
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL);
+
+    if ((na = numaCreate(bw)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetParameters(na, xstart, 1);
+    norm = 1. / (l_float32)bh;
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (j = xstart; j < xend; j++) {
+        sum = 0.0;
+        if (d == 8) {
+            for (i = ystart; i < yend; i++) {
+                line = data + i * wpl;
+                sum += GET_DATA_BYTE(line, j);
+            }
+            if (type == L_BLACK_IS_MAX)
+                sum = bh * 255 - sum;
+        } else {  /* d == 16 */
+            for (i = ystart; i < yend; i++) {
+                line = data + i * wpl;
+                sum += GET_DATA_TWO_BYTES(line, j);
+            }
+            if (type == L_BLACK_IS_MAX)
+                sum = bh * 0xffff - sum;
+        }
+        numaAddNumber(na, (l_float32)(norm * sum));
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixAverageInRect()
+ *
+ *      Input:  pix (1, 2, 4, 8 bpp; not cmapped)
+ *              box (<optional> if null, use entire image)
+ *              &ave (<return> average of pixel values in region)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixAverageInRect(PIX        *pix,
+                 BOX        *box,
+                 l_float32  *pave)
+{
+l_int32    w, h, d, wpl, i, j, xstart, xend, ystart, yend, bw, bh;
+l_uint32  *data, *line;
+l_float64  ave;
+
+    PROCNAME("pixAverageInRect");
+
+    if (!pave)
+        return ERROR_INT("&ave not defined", procName, 1);
+    *pave = 0;
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8)
+        return ERROR_INT("pix not 1, 2, 4 or 8 bpp", procName, 1);
+    if (pixGetColormap(pix) != NULL)
+        return ERROR_INT("pix is colormapped", procName, 1);
+
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return ERROR_INT("invalid clipping box", procName, 1);
+
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    ave = 0;
+    for (i = ystart; i < yend; i++) {
+        line = data + i * wpl;
+        for (j = xstart; j < xend; j++) {
+            if (d == 1)
+                ave += GET_DATA_BIT(line, j);
+            else if (d == 2)
+                ave += GET_DATA_DIBIT(line, j);
+            else if (d == 4)
+                ave += GET_DATA_QBIT(line, j);
+            else  /* d == 8 */
+                ave += GET_DATA_BYTE(line, j);
+        }
+    }
+
+    *pave = ave / (bw * bh);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *               Variance of pixel values in gray images            *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixVarianceByRow()
+ *
+ *      Input:  pix (8 or 16 bpp; no colormap)
+ *              box (<optional> clipping box for variance; can be null)
+ *      Return: na of rmsdev by row, or null on error
+ *
+ *  Notes:
+ *      (1) To resample for a bin size different from 1, use
+ *          numaUniformSampling() on the result of this function.
+ *      (2) We are actually computing the RMS deviation in each row.
+ *          This is the square root of the variance.
+ */
+NUMA *
+pixVarianceByRow(PIX     *pix,
+                 BOX     *box)
+{
+l_int32     i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh, val;
+l_uint32   *line, *data;
+l_float64   sum1, sum2, norm, ave, var, rootvar;
+NUMA       *na;
+
+    PROCNAME("pixVarianceByRow");
+
+    if (!pix)
+        return (NUMA *)ERROR_PTR("pix not defined", procName, NULL);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 8 && d != 16)
+        return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", procName, NULL);
+    if (pixGetColormap(pix) != NULL)
+        return (NUMA *)ERROR_PTR("pix colormapped", procName, NULL);
+
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL);
+
+    if ((na = numaCreate(bh)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetParameters(na, ystart, 1);
+    norm = 1. / (l_float32)bw;
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (i = ystart; i < yend; i++) {
+        sum1 = sum2 = 0.0;
+        line = data + i * wpl;
+        for (j = xstart; j < xend; j++) {
+            if (d == 8)
+                val = GET_DATA_BYTE(line, j);
+            else  /* d == 16 */
+                val = GET_DATA_TWO_BYTES(line, j);
+            sum1 += val;
+            sum2 += val * val;
+        }
+        ave = norm * sum1;
+        var = norm * sum2 - ave * ave;
+        rootvar = sqrt(var);
+        numaAddNumber(na, (l_float32)rootvar);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixVarianceByColumn()
+ *
+ *      Input:  pix (8 or 16 bpp; no colormap)
+ *              box (<optional> clipping box for variance; can be null)
+ *      Return: na of rmsdev by column, or null on error
+ *
+ *  Notes:
+ *      (1) To resample for a bin size different from 1, use
+ *          numaUniformSampling() on the result of this function.
+ *      (2) We are actually computing the RMS deviation in each row.
+ *          This is the square root of the variance.
+ */
+NUMA *
+pixVarianceByColumn(PIX     *pix,
+                    BOX     *box)
+{
+l_int32     i, j, w, h, d, wpl, xstart, xend, ystart, yend, bw, bh, val;
+l_uint32   *line, *data;
+l_float64   sum1, sum2, norm, ave, var, rootvar;
+NUMA       *na;
+
+    PROCNAME("pixVarianceByColumn");
+
+    if (!pix)
+        return (NUMA *)ERROR_PTR("pix not defined", procName, NULL);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 8 && d != 16)
+        return (NUMA *)ERROR_PTR("pix not 8 or 16 bpp", procName, NULL);
+    if (pixGetColormap(pix) != NULL)
+        return (NUMA *)ERROR_PTR("pix colormapped", procName, NULL);
+
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL);
+
+    if ((na = numaCreate(bw)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetParameters(na, xstart, 1);
+    norm = 1. / (l_float32)bh;
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (j = xstart; j < xend; j++) {
+        sum1 = sum2 = 0.0;
+        for (i = ystart; i < yend; i++) {
+            line = data + wpl * i;
+            if (d == 8)
+                val = GET_DATA_BYTE(line, j);
+            else  /* d == 16 */
+                val = GET_DATA_TWO_BYTES(line, j);
+            sum1 += val;
+            sum2 += val * val;
+        }
+        ave = norm * sum1;
+        var = norm * sum2 - ave * ave;
+        rootvar = sqrt(var);
+        numaAddNumber(na, (l_float32)rootvar);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixVarianceInRect()
+ *
+ *      Input:  pix (1, 2, 4, 8 bpp; not cmapped)
+ *              box (<optional> if null, use entire image)
+ *              &rootvar (<return> sqrt variance of pixel values in region)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixVarianceInRect(PIX        *pix,
+                  BOX        *box,
+                  l_float32  *prootvar)
+{
+l_int32    w, h, d, wpl, i, j, xstart, xend, ystart, yend, bw, bh, val;
+l_uint32  *data, *line;
+l_float64  sum1, sum2, norm, ave, var;
+
+    PROCNAME("pixVarianceInRect");
+
+    if (!prootvar)
+        return ERROR_INT("&rootvar not defined", procName, 1);
+    *prootvar = 0.0;
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8)
+        return ERROR_INT("pix not 1, 2, 4 or 8 bpp", procName, 1);
+    if (pixGetColormap(pix) != NULL)
+        return ERROR_INT("pix is colormapped", procName, 1);
+
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return ERROR_INT("invalid clipping box", procName, 1);
+
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    sum1 = sum2 = 0.0;
+    for (i = ystart; i < yend; i++) {
+        line = data + i * wpl;
+        for (j = xstart; j < xend; j++) {
+            if (d == 1) {
+                val = GET_DATA_BIT(line, j);
+                sum1 += val;
+                sum2 += val * val;
+            } else if (d == 2) {
+                val = GET_DATA_DIBIT(line, j);
+                sum1 += val;
+                sum2 += val * val;
+            } else if (d == 4) {
+                val = GET_DATA_QBIT(line, j);
+                sum1 += val;
+                sum2 += val * val;
+            } else {  /* d == 8 */
+                val = GET_DATA_BYTE(line, j);
+                sum1 += val;
+                sum2 += val * val;
+            }
+        }
+    }
+    norm = 1.0 / (bw * bh);
+    ave = norm * sum1;
+    var = norm * sum2 - ave * ave;
+    *prootvar = (l_float32)sqrt(var);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *    Average of absolute value of pixel differences in gray images    *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixAbsDiffByRow()
+ *
+ *      Input:  pix (8 bpp; no colormap)
+ *              box (<optional> clipping box for region; can be null)
+ *      Return: na of abs val pixel difference averages by row, or null on error
+ *
+ *  Notes:
+ *      (1) This is an average over differences of adjacent pixels along
+ *          each row.
+ *      (2) To resample for a bin size different from 1, use
+ *          numaUniformSampling() on the result of this function.
+ */
+NUMA *
+pixAbsDiffByRow(PIX  *pix,
+                BOX  *box)
+{
+l_int32    i, j, w, h, wpl, xstart, xend, ystart, yend, bw, bh, val0, val1;
+l_uint32  *line, *data;
+l_float64  norm, sum;
+NUMA      *na;
+
+    PROCNAME("pixAbsDiffByRow");
+
+    if (!pix || pixGetDepth(pix) != 8)
+        return (NUMA *)ERROR_PTR("pix undefined or not 8 bpp", procName, NULL);
+    if (pixGetColormap(pix) != NULL)
+        return (NUMA *)ERROR_PTR("pix colormapped", procName, NULL);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL);
+    if (bw < 2)
+        return (NUMA *)ERROR_PTR("row width must be >= 2", procName, NULL);
+
+    norm = 1. / (l_float32)(bw - 1);
+    if ((na = numaCreate(bh)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetParameters(na, ystart, 1);
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (i = ystart; i < yend; i++) {
+        sum = 0.0;
+        line = data + i * wpl;
+        val0 = GET_DATA_BYTE(line, xstart);
+        for (j = xstart + 1; j < xend; j++) {
+            val1 = GET_DATA_BYTE(line, j);
+            sum += L_ABS(val1 - val0);
+            val0 = val1;
+        }
+        numaAddNumber(na, (l_float32)(norm * sum));
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixAbsDiffByColumn()
+ *
+ *      Input:  pix (8 bpp; no colormap)
+ *              box (<optional> clipping box for region; can be null)
+ *      Return: na of abs val pixel difference averages by column,
+ *              or null on error
+ *
+ *  Notes:
+ *      (1) This is an average over differences of adjacent pixels along
+ *          each column.
+ *      (2) To resample for a bin size different from 1, use
+ *          numaUniformSampling() on the result of this function.
+ */
+NUMA *
+pixAbsDiffByColumn(PIX  *pix,
+                   BOX  *box)
+{
+l_int32    i, j, w, h, wpl, xstart, xend, ystart, yend, bw, bh, val0, val1;
+l_uint32  *line, *data;
+l_float64  norm, sum;
+NUMA      *na;
+
+    PROCNAME("pixAbsDiffByColumn");
+
+    if (!pix || pixGetDepth(pix) != 8)
+        return (NUMA *)ERROR_PTR("pix undefined or not 8 bpp", procName, NULL);
+    if (pixGetColormap(pix) != NULL)
+        return (NUMA *)ERROR_PTR("pix colormapped", procName, NULL);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return (NUMA *)ERROR_PTR("invalid clipping box", procName, NULL);
+    if (bh < 2)
+        return (NUMA *)ERROR_PTR("column height must be >= 2", procName, NULL);
+
+    norm = 1. / (l_float32)(bh - 1);
+    if ((na = numaCreate(bw)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetParameters(na, xstart, 1);
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (j = xstart; j < xend; j++) {
+        sum = 0.0;
+        line = data + ystart * wpl;
+        val0 = GET_DATA_BYTE(line, j);
+        for (i = ystart + 1; i < yend; i++) {
+            line = data + i * wpl;
+            val1 = GET_DATA_BYTE(line, j);
+            sum += L_ABS(val1 - val0);
+            val0 = val1;
+        }
+        numaAddNumber(na, (l_float32)(norm * sum));
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixAbsDiffInRect()
+ *
+ *      Input:  pix (8 bpp; not cmapped)
+ *              box (<optional> if null, use entire image)
+ *              dir (differences along L_HORIZONTAL_LINE or L_VERTICAL_LINE)
+ *              &absdiff (<return> average of abs diff pixel values in region)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This gives the average over the abs val of differences of
+ *          adjacent pixels values, along either each
+ *             row:     dir == L_HORIZONTAL_LINE
+ *             column:  dir == L_VERTICAL_LINE
+ */
+l_int32
+pixAbsDiffInRect(PIX        *pix,
+                 BOX        *box,
+                 l_int32     dir,
+                 l_float32  *pabsdiff)
+{
+l_int32    w, h, wpl, i, j, xstart, xend, ystart, yend, bw, bh, val0, val1;
+l_uint32  *data, *line;
+l_float64  norm, sum;
+
+    PROCNAME("pixAbsDiffInRect");
+
+    if (!pabsdiff)
+        return ERROR_INT("&absdiff not defined", procName, 1);
+    *pabsdiff = 0.0;
+    if (!pix || pixGetDepth(pix) != 8)
+        return ERROR_INT("pix undefined or not 8 bpp", procName, 1);
+    if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE)
+        return ERROR_INT("invalid direction", procName, 1);
+    if (pixGetColormap(pix) != NULL)
+        return ERROR_INT("pix is colormapped", procName, 1);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return ERROR_INT("invalid clipping box", procName, 1);
+
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    if (dir == L_HORIZONTAL_LINE) {
+        norm = 1. / (l_float32)(bh * (bw - 1));
+        sum = 0.0;
+        for (i = ystart; i < yend; i++) {
+            line = data + i * wpl;
+            val0 = GET_DATA_BYTE(line, xstart);
+            for (j = xstart + 1; j < xend; j++) {
+                val1 = GET_DATA_BYTE(line, j);
+                sum += L_ABS(val1 - val0);
+                val0 = val1;
+            }
+        }
+    } else {  /* vertical line */
+        norm = 1. / (l_float32)(bw * (bh - 1));
+        sum = 0.0;
+        for (j = xstart; j < xend; j++) {
+            line = data + ystart * wpl;
+            val0 = GET_DATA_BYTE(line, j);
+            for (i = ystart + 1; i < yend; i++) {
+                line = data + i * wpl;
+                val1 = GET_DATA_BYTE(line, j);
+                sum += L_ABS(val1 - val0);
+                val0 = val1;
+            }
+        }
+    }
+    *pabsdiff = (l_float32)(norm * sum);
+    return 0;
+}
+
+
+/*!
+ *  pixAbsDiffOnLine()
+ *
+ *      Input:  pix (8 bpp; not cmapped)
+ *              x1, y1 (first point; x1 <= x2, y1 <= y2)
+ *              x2, y2 (first point)
+ *              &absdiff (<return> average of abs diff pixel values on line)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This gives the average over the abs val of differences of
+ *          adjacent pixels values, along a line that is either horizontal
+ *          or vertical.
+ *      (2) If horizontal, require x1 < x2; if vertical, require y1 < y2.
+ */
+l_int32
+pixAbsDiffOnLine(PIX        *pix,
+                 l_int32     x1,
+                 l_int32     y1,
+                 l_int32     x2,
+                 l_int32     y2,
+                 l_float32  *pabsdiff)
+{
+l_int32    w, h, i, j, dir, size, sum;
+l_uint32   val0, val1;
+
+    PROCNAME("pixAbsDiffOnLine");
+
+    if (!pabsdiff)
+        return ERROR_INT("&absdiff not defined", procName, 1);
+    *pabsdiff = 0.0;
+    if (!pix || pixGetDepth(pix) != 8)
+        return ERROR_INT("pix undefined or not 8 bpp", procName, 1);
+    if (y1 == y2) {
+        dir = L_HORIZONTAL_LINE;
+    } else if (x1 == x2) {
+        dir = L_VERTICAL_LINE;
+    } else {
+        return ERROR_INT("line is neither horiz nor vert", procName, 1);
+    }
+    if (pixGetColormap(pix) != NULL)
+        return ERROR_INT("pix is colormapped", procName, 1);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    sum = 0;
+    if (dir == L_HORIZONTAL_LINE) {
+        x1 = L_MAX(x1, 0);
+        x2 = L_MIN(x2, w - 1);
+        if (x1 >= x2)
+            return ERROR_INT("x1 >= x2", procName, 1);
+        size = x2 - x1;
+        pixGetPixel(pix, x1, y1, &val0);
+        for (j = x1 + 1; j <= x2; j++) {
+            pixGetPixel(pix, j, y1, &val1);
+            sum += L_ABS((l_int32)val1 - (l_int32)val0);
+            val0 = val1;
+        }
+    } else {  /* vertical */
+        y1 = L_MAX(y1, 0);
+        y2 = L_MIN(y2, h - 1);
+        if (y1 >= y2)
+            return ERROR_INT("y1 >= y2", procName, 1);
+        size = y2 - y1;
+        pixGetPixel(pix, x1, y1, &val0);
+        for (i = y1 + 1; i <= y2; i++) {
+            pixGetPixel(pix, x1, i, &val1);
+            sum += L_ABS((l_int32)val1 - (l_int32)val0);
+            val0 = val1;
+        }
+    }
+    *pabsdiff = (l_float32)sum / (l_float32)size;
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *              Count of pixels with specific value            *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixCountArbInRect()
+ *
+ *      Input:  pixs (8 bpp, or colormapped)
+ *              box (<optional>) over which count is made;
+ *                   use entire image null)
+ *              val (pixel value to count)
+ *              factor (subsampling factor; integer >= 1)
+ *              &count (<return> count; estimate it if factor > 1)
+ *      Return: na (histogram), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs is cmapped, @val is compared to the colormap index;
+ *          otherwise, @val is compared to the grayscale value.
+ *      (2) Set the subsampling @factor > 1 to reduce the amount of computation.
+ *          If @factor > 1, multiply the count by @factor * @factor.
+ */
+l_int32
+pixCountArbInRect(PIX      *pixs,
+                  BOX      *box,
+                  l_int32   val,
+                  l_int32   factor,
+                  l_int32  *pcount)
+{
+l_int32    i, j, bx, by, bw, bh, w, h, wpl, pixval;
+l_uint32  *data, *line;
+
+    PROCNAME("pixCountArbInRect");
+
+    if (!pcount)
+        return ERROR_INT("&count not defined", procName, 1);
+    *pcount = 0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 8 && !pixGetColormap(pixs))
+        return ERROR_INT("pixs neither 8 bpp nor colormapped",
+                                 procName, 1);
+    if (factor < 1)
+        return ERROR_INT("sampling factor < 1", procName, 1);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+
+    if (!box) {
+        for (i = 0; i < h; i += factor) {
+            line = data + i * wpl;
+            for (j = 0; j < w; j += factor) {
+                pixval = GET_DATA_BYTE(line, j);
+                if (pixval == val) (*pcount)++;
+            }
+        }
+    } else {
+        boxGetGeometry(box, &bx, &by, &bw, &bh);
+        for (i = 0; i < bh; i += factor) {
+            if (by + i < 0 || by + i >= h) continue;
+            line = data + (by + i) * wpl;
+            for (j = 0; j < bw; j += factor) {
+                if (bx + j < 0 || bx + j >= w) continue;
+                pixval = GET_DATA_BYTE(line, bx + j);
+                if (pixval == val) (*pcount)++;
+            }
+        }
+    }
+
+    if (factor > 1)  /* assume pixel color is randomly distributed */
+        *pcount = *pcount * factor * factor;
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *              Mirrored tiling of a smaller image             *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixMirroredTiling()
+ *
+ *      Input:  pixs (8 or 32 bpp, small tile; to be replicated)
+ *              w, h (dimensions of output pix)
+ *      Return: pixd (usually larger pix, mirror-tiled with pixs),
+ *              or null on error
+ *
+ *  Notes:
+ *      (1) This uses mirrored tiling, where each row alternates
+ *          with LR flips and every column alternates with TB
+ *          flips, such that the result is a tiling with identical
+ *          2 x 2 tiles, each of which is composed of these transforms:
+ *                  -----------------
+ *                  | 1    |  LR    |
+ *                  -----------------
+ *                  | TB   |  LR/TB |
+ *                  -----------------
+ */
+PIX *
+pixMirroredTiling(PIX     *pixs,
+                  l_int32  w,
+                  l_int32  h)
+{
+l_int32   wt, ht, d, i, j, nx, ny;
+PIX      *pixd, *pixsfx, *pixsfy, *pixsfxy, *pix;
+
+    PROCNAME("pixMirroredTiling");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &wt, &ht, &d);
+    if (wt <= 0 || ht <= 0)
+        return (PIX *)ERROR_PTR("pixs size illegal", procName, NULL);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("depth not 32 bpp", procName, NULL);
+
+    if ((pixd = pixCreate(w, h, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopySpp(pixd, pixs);
+
+    nx = (w + wt - 1) / wt;
+    ny = (h + ht - 1) / ht;
+    pixsfx = pixFlipLR(NULL, pixs);
+    pixsfy = pixFlipTB(NULL, pixs);
+    pixsfxy = pixFlipTB(NULL, pixsfx);
+    for (i = 0; i < ny; i++) {
+        for (j = 0; j < nx; j++) {
+            pix = pixs;
+            if ((i & 1) && !(j & 1))
+                pix = pixsfy;
+            else if (!(i & 1) && (j & 1))
+                pix = pixsfx;
+            else if ((i & 1) && (j & 1))
+                pix = pixsfxy;
+            pixRasterop(pixd, j * wt, i * ht, wt, ht, PIX_SRC, pix, 0, 0);
+        }
+    }
+
+    pixDestroy(&pixsfx);
+    pixDestroy(&pixsfy);
+    pixDestroy(&pixsfxy);
+    return pixd;
+}
+
+
+/*!
+ *  pixFindRepCloseTile()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              box (region of pixs to search around)
+ *              searchdir (L_HORIZ or L_VERT; direction to search)
+ *              mindist (min distance of selected tile edge from box; >= 0)
+ *              tsize (tile size; > 1; even; typically ~50)
+ *              ntiles (number of tiles tested in each row/column)
+ *              &boxtile (<return> region of best tile)
+ *              debug (1 for debug output)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This looks for one or two square tiles with conforming median
+ *          intensity and low variance, that is outside but near the input box.
+ *      (2) @mindist specifies the gap between the box and the
+ *          potential tiles.  The tiles are given an overlap of 50%.
+ *          @ntiles specifies the number of tiles that are tested
+ *          beyond @mindist for each row or column.
+ *      (3) For example, if @mindist = 20, @tilesize = 50 and @ntiles = 3,
+ *          a horizontal search to the right will have 3 tiles in each row,
+ *          with left edges at 20, 45 and 70 from the right edge of the
+ *          input @box.  The number of rows of tiles is determined by
+ *          the height of @box and @tsize, with the 50% overlap..
+ */
+l_int32
+pixFindRepCloseTile(PIX     *pixs,
+                    BOX     *box,
+                    l_int32  searchdir,
+                    l_int32  mindist,
+                    l_int32  tsize,
+                    l_int32  ntiles,
+                    BOX    **pboxtile,
+                    l_int32  debug)
+{
+l_int32    w, h, i, n, bestindex;
+l_float32  var_of_mean, median_of_mean, median_of_stdev, mean_val, stdev_val;
+l_float32  mindels, bestdelm, delm, dels, mean, stdev;
+BOXA      *boxa;
+NUMA      *namean, *nastdev;
+PIX       *pix, *pixg;
+PIXA      *pixa;
+
+    PROCNAME("pixFindRepCloseTile");
+
+    if (!pboxtile)
+        return ERROR_INT("&boxtile not defined", procName, 1);
+    *pboxtile = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (searchdir != L_HORIZ && searchdir != L_VERT)
+        return ERROR_INT("invalid searchdir", procName, 1);
+    if (mindist < 0)
+        return ERROR_INT("mindist must be >= 0", procName, 1);
+    if (tsize < 2)
+        return ERROR_INT("tsize must be > 1", procName, 1);
+    if (ntiles > 7) {
+        L_WARNING("ntiles = %d; larger than suggested max of 7\n",
+                  procName, ntiles);
+    }
+
+        /* Locate tile regions */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    boxa = findTileRegionsForSearch(box, w, h, searchdir, mindist,
+                                    tsize, ntiles);
+    if (!boxa)
+        return ERROR_INT("no tiles found", procName, 1);
+
+        /* Generate the tiles and the mean and stdev of intensity */
+    pixa = pixClipRectangles(pixs, boxa);
+    n = pixaGetCount(pixa);
+    namean = numaCreate(n);
+    nastdev = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        pixg = pixConvertRGBToGray(pix, 0.33, 0.34, 0.33);
+        pixGetAverageMasked(pixg, NULL, 0, 0, 1, L_MEAN_ABSVAL, &mean);
+        pixGetAverageMasked(pixg, NULL, 0, 0, 1, L_STANDARD_DEVIATION, &stdev);
+        numaAddNumber(namean, mean);
+        numaAddNumber(nastdev, stdev);
+        pixDestroy(&pix);
+        pixDestroy(&pixg);
+    }
+
+        /* Find the median and variance of the averages.  We require
+         * the best tile to have a mean pixel intensity within a standard
+         * deviation of the median of mean intensities, and choose the
+         * tile in that set with the smallest stdev of pixel intensities
+         * (as a proxy for the tile with least visible structure).
+         * The median of the stdev is used, for debugging, as a normalizing
+         * factor for the stdev of intensities within a tile. */
+    numaGetStatsUsingHistogram(namean, 256, NULL, NULL, NULL, &var_of_mean,
+                               &median_of_mean, 0.0, NULL, NULL);
+    numaGetStatsUsingHistogram(nastdev, 256, NULL, NULL, NULL, NULL,
+                               &median_of_stdev, 0.0, NULL, NULL);
+    mindels = 1000.0;
+    bestdelm = 1000.0;
+    bestindex = 0;
+    for (i = 0; i < n; i++) {
+        numaGetFValue(namean, i, &mean_val);
+        numaGetFValue(nastdev, i, &stdev_val);
+        if (var_of_mean == 0.0) {  /* uniform color; any box will do */
+            delm = 0.0;  /* any value < 1.01 */
+            dels = 1.0;  /* n'importe quoi */
+        } else {
+            delm = L_ABS(mean_val - median_of_mean) / sqrt(var_of_mean);
+            dels = stdev_val / median_of_stdev;
+        }
+        if (delm < 1.01) {
+            if (dels < mindels) {
+                if (debug) {
+                    fprintf(stderr, "i = %d, mean = %7.3f, delm = %7.3f,"
+                            " stdev = %7.3f, dels = %7.3f\n",
+                            i, mean_val, delm, stdev_val, dels);
+                }
+                mindels = dels;
+                bestdelm = delm;
+                bestindex = i;
+            }
+        }
+    }
+    *pboxtile = boxaGetBox(boxa, bestindex, L_COPY);
+
+    if (debug) {
+        L_INFO("median of mean = %7.3f\n", procName, median_of_mean);
+        L_INFO("standard dev of mean = %7.3f\n", procName, sqrt(var_of_mean));
+        L_INFO("median of stdev = %7.3f\n", procName, median_of_stdev);
+        L_INFO("best tile: index = %d\n", procName, bestindex);
+        L_INFO("delta from median in units of stdev = %5.3f\n",
+               procName, bestdelm);
+        L_INFO("stdev as fraction of median stdev = %5.3f\n",
+               procName, mindels);
+    }
+
+    numaDestroy(&namean);
+    numaDestroy(&nastdev);
+    pixaDestroy(&pixa);
+    boxaDestroy(&boxa);
+    return 0;
+}
+
+
+/*!
+ *  findTileRegionsForSearch()
+ *
+ *      Input:  box (region of Pix to search around)
+ *              w, h (dimensions of Pix)
+ *              searchdir (L_HORIZ or L_VERT; direction to search)
+ *              mindist (min distance of selected tile edge from box; >= 0)
+ *              tsize (tile size; > 1; even; typically ~50)
+ *              ntiles (number of tiles tested in each row/column)
+ *      Return: boxa if OK, or null on error
+ *
+ *  Notes:
+ *      (1) See calling function pixfindRepCloseTile().
+ */
+static BOXA *
+findTileRegionsForSearch(BOX     *box,
+                         l_int32  w,
+                         l_int32  h,
+                         l_int32  searchdir,
+                         l_int32  mindist,
+                         l_int32  tsize,
+                         l_int32  ntiles)
+{
+l_int32  bx, by, bw, bh, left, right, top, bot, i, j, nrows, ncols;
+l_int32  x0, y0, x, y, w_avail, w_needed, h_avail, h_needed, t_avail;
+BOX     *box1;
+BOXA    *boxa;
+
+    PROCNAME("findTileRegionsForSearch");
+
+    if (!box)
+        return (BOXA *)ERROR_PTR("box not defined", procName, NULL);
+    if (ntiles == 0)
+        return (BOXA *)ERROR_PTR("no tiles requested", procName, NULL);
+
+    boxGetGeometry(box, &bx, &by, &bw, &bh);
+    if (searchdir == L_HORIZ) {
+            /* Find the tile parameters for the search.  Note that the
+             * tiles are overlapping by 50% in each direction. */
+        left = bx;   /* distance to left of box */
+        right = w - bx - bw + 1;   /* distance to right of box */
+        w_avail = L_MAX(left, right) - mindist;
+        if (tsize & 1) tsize++;  /* be sure it's even */
+        if (w_avail < tsize) {
+            L_ERROR("tsize = %d, w_avail = %d\n", procName, tsize, w_avail);
+            return NULL;
+        }
+        w_needed = tsize + (ntiles - 1) * (tsize / 2);
+        if (w_needed > w_avail) {
+            t_avail = 1 + 2 * (w_avail - tsize) / tsize;
+            L_WARNING("ntiles = %d; room for only %d\n", procName,
+                      ntiles, t_avail);
+            ntiles = t_avail;
+            w_needed = tsize + (ntiles - 1) * (tsize / 2);
+        }
+        nrows = L_MAX(1, 1 + 2 * (bh - tsize) / tsize);
+
+            /* Generate the tile regions to search */
+        boxa = boxaCreate(0);
+        if (left > right)  /* search to left */
+            x0 = bx - w_needed;
+        else  /* search to right */
+            x0 = bx + bw + mindist;
+        for (i = 0; i < nrows; i++) {
+            y = by + i * tsize / 2;
+            for (j = 0; j < ntiles; j++) {
+                x = x0 + j * tsize / 2;
+                box1 = boxCreate(x, y, tsize, tsize);
+                boxaAddBox(boxa, box1, L_INSERT);
+            }
+        }
+    } else {  /* L_VERT */
+            /* Find the tile parameters for the search */
+        top = by;   /* distance above box */
+        bot = h - by - bh + 1;   /* distance below box */
+        h_avail = L_MAX(top, bot) - mindist;
+        if (h_avail < tsize) {
+            L_ERROR("tsize = %d, h_avail = %d\n", procName, tsize, h_avail);
+            return NULL;
+        }
+        h_needed = tsize + (ntiles - 1) * (tsize / 2);
+        if (h_needed > h_avail) {
+            t_avail = 1 + 2 * (h_avail - tsize) / tsize;
+            L_WARNING("ntiles = %d; room for only %d\n", procName,
+                      ntiles, t_avail);
+            ntiles = t_avail;
+            h_needed = tsize + (ntiles - 1) * (tsize / 2);
+        }
+        ncols = L_MAX(1, 1 + 2 * (bw - tsize) / tsize);
+
+            /* Generate the tile regions to search */
+        boxa = boxaCreate(0);
+        if (top > bot)  /* search above */
+            y0 = by - h_needed;
+        else  /* search below */
+            y0 = by + bh + mindist;
+        for (j = 0; j < ncols; j++) {
+            x = bx + j * tsize / 2;
+            for (i = 0; i < ntiles; i++) {
+                y = y0 + i * tsize / 2;
+                box1 = boxCreate(x, y, tsize, tsize);
+                boxaAddBox(boxa, box1, L_INSERT);
+            }
+        }
+    }
+    return boxa;
+}
+
diff --git a/src/pix4.c b/src/pix4.c
new file mode 100644 (file)
index 0000000..3fa7c39
--- /dev/null
@@ -0,0 +1,3121 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pix4.c
+ *
+ *    This file has these operations:
+ *
+ *      (1) Pixel histograms
+ *      (2) Pixel row/column statistics
+ *      (3) Foreground/background estimation
+ *
+ *    Pixel histogram, rank val, averaging and min/max
+ *           NUMA       *pixGetGrayHistogram()
+ *           NUMA       *pixGetGrayHistogramMasked()
+ *           NUMA       *pixGetGrayHistogramInRect()
+ *           NUMAA      *pixGetGrayHistogramTiled()
+ *           l_int32     pixGetColorHistogram()
+ *           l_int32     pixGetColorHistogramMasked()
+ *           NUMA       *pixGetCmapHistogram()
+ *           NUMA       *pixGetCmapHistogramMasked()
+ *           NUMA       *pixGetCmapHistogramInRect()
+ *           l_int32     pixGetRankValue()
+ *           l_int32     pixGetRankValueMaskedRGB()
+ *           l_int32     pixGetRankValueMasked()
+ *           l_int32     pixGetAverageValue()
+ *           l_int32     pixGetAverageMaskedRGB()
+ *           l_int32     pixGetAverageMasked()
+ *           l_int32     pixGetAverageTiledRGB()
+ *           PIX        *pixGetAverageTiled()
+ *           NUMA       *pixRowStats()
+ *           NUMA       *pixColumnStats()
+ *           l_int32     pixGetComponentRange()
+ *           l_int32     pixGetExtremeValue()
+ *           l_int32     pixGetMaxValueInRect()
+ *           l_int32     pixGetBinnedComponentRange()
+ *           l_int32     pixGetRankColorArray()
+ *           l_int32     pixGetBinnedColor()
+ *           PIX        *pixDisplayColorArray()
+ *           PIX        *pixRankBinByStrip()
+ *
+ *    Pixelwise aligned statistics
+ *           PIX        *pixaGetAlignedStats()
+ *           l_int32     pixaExtractColumnFromEachPix()
+ *           l_int32     pixGetRowStats()
+ *           l_int32     pixGetColumnStats()
+ *           l_int32     pixSetPixelColumn()
+ *
+ *    Foreground/background estimation
+ *           l_int32     pixThresholdForFgBg()
+ *           l_int32     pixSplitDistributionFgBg()
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+
+/*------------------------------------------------------------------*
+ *                  Pixel histogram and averaging                   *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixGetGrayHistogram()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 bpp; can be colormapped)
+ *              factor (subsampling factor; integer >= 1)
+ *      Return: na (histogram), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs has a colormap, it is converted to 8 bpp gray.
+ *          If you want a histogram of the colormap indices, use
+ *          pixGetCmapHistogram().
+ *      (2) If pixs does not have a colormap, the output histogram is
+ *          of size 2^d, where d is the depth of pixs.
+ *      (3) This always returns a 256-value histogram of pixel values.
+ *      (4) Set the subsampling factor > 1 to reduce the amount of computation.
+ */
+NUMA *
+pixGetGrayHistogram(PIX     *pixs,
+                    l_int32  factor)
+{
+l_int32     i, j, w, h, d, wpl, val, size, count;
+l_uint32   *data, *line;
+l_float32  *array;
+NUMA       *na;
+PIX        *pixg;
+
+    PROCNAME("pixGetGrayHistogram");
+
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d > 16)
+        return (NUMA *)ERROR_PTR("depth not in {1,2,4,8,16}", procName, NULL);
+    if (factor < 1)
+        return (NUMA *)ERROR_PTR("sampling must be >= 1", procName, NULL);
+
+    if (pixGetColormap(pixs))
+        pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else
+        pixg = pixClone(pixs);
+
+    pixGetDimensions(pixg, &w, &h, &d);
+    size = 1 << d;
+    if ((na = numaCreate(size)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetCount(na, size);  /* all initialized to 0.0 */
+    array = numaGetFArray(na, L_NOCOPY);
+
+    if (d == 1) {  /* special case */
+        pixCountPixels(pixg, &count, NULL);
+        array[0] = w * h - count;
+        array[1] = count;
+        pixDestroy(&pixg);
+        return na;
+    }
+
+    wpl = pixGetWpl(pixg);
+    data = pixGetData(pixg);
+    for (i = 0; i < h; i += factor) {
+        line = data + i * wpl;
+        switch (d)
+        {
+        case 2:
+            for (j = 0; j < w; j += factor) {
+                val = GET_DATA_DIBIT(line, j);
+                array[val] += 1.0;
+            }
+            break;
+        case 4:
+            for (j = 0; j < w; j += factor) {
+                val = GET_DATA_QBIT(line, j);
+                array[val] += 1.0;
+            }
+            break;
+        case 8:
+            for (j = 0; j < w; j += factor) {
+                val = GET_DATA_BYTE(line, j);
+                array[val] += 1.0;
+            }
+            break;
+        case 16:
+            for (j = 0; j < w; j += factor) {
+                val = GET_DATA_TWO_BYTES(line, j);
+                array[val] += 1.0;
+            }
+            break;
+        default:
+            numaDestroy(&na);
+            return (NUMA *)ERROR_PTR("illegal depth", procName, NULL);
+        }
+    }
+
+    pixDestroy(&pixg);
+    return na;
+}
+
+
+/*!
+ *  pixGetGrayHistogramMasked()
+ *
+ *      Input:  pixs (8 bpp, or colormapped)
+ *              pixm (<optional> 1 bpp mask over which histogram is
+ *                    to be computed; use all pixels if null)
+ *              x, y (UL corner of pixm relative to the UL corner of pixs;
+ *                    can be < 0; these values are ignored if pixm is null)
+ *              factor (subsampling factor; integer >= 1)
+ *      Return: na (histogram), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs is cmapped, it is converted to 8 bpp gray.
+ *          If you want a histogram of the colormap indices, use
+ *          pixGetCmapHistogramMasked().
+ *      (2) This always returns a 256-value histogram of pixel values.
+ *      (3) Set the subsampling factor > 1 to reduce the amount of computation.
+ *      (4) Clipping of pixm (if it exists) to pixs is done in the inner loop.
+ *      (5) Input x,y are ignored unless pixm exists.
+ */
+NUMA *
+pixGetGrayHistogramMasked(PIX        *pixs,
+                          PIX        *pixm,
+                          l_int32     x,
+                          l_int32     y,
+                          l_int32     factor)
+{
+l_int32     i, j, w, h, wm, hm, dm, wplg, wplm, val;
+l_uint32   *datag, *datam, *lineg, *linem;
+l_float32  *array;
+NUMA       *na;
+PIX        *pixg;
+
+    PROCNAME("pixGetGrayHistogramMasked");
+
+    if (!pixm)
+        return pixGetGrayHistogram(pixs, factor);
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8 && !pixGetColormap(pixs))
+        return (NUMA *)ERROR_PTR("pixs neither 8 bpp nor colormapped",
+                                 procName, NULL);
+    pixGetDimensions(pixm, &wm, &hm, &dm);
+    if (dm != 1)
+        return (NUMA *)ERROR_PTR("pixm not 1 bpp", procName, NULL);
+    if (factor < 1)
+        return (NUMA *)ERROR_PTR("sampling must be >= 1", procName, NULL);
+
+    if ((na = numaCreate(256)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetCount(na, 256);  /* all initialized to 0.0 */
+    array = numaGetFArray(na, L_NOCOPY);
+
+    if (pixGetColormap(pixs))
+        pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else
+        pixg = pixClone(pixs);
+    pixGetDimensions(pixg, &w, &h, NULL);
+    datag = pixGetData(pixg);
+    wplg = pixGetWpl(pixg);
+    datam = pixGetData(pixm);
+    wplm = pixGetWpl(pixm);
+
+        /* Generate the histogram */
+    for (i = 0; i < hm; i += factor) {
+        if (y + i < 0 || y + i >= h) continue;
+        lineg = datag + (y + i) * wplg;
+        linem = datam + i * wplm;
+        for (j = 0; j < wm; j += factor) {
+            if (x + j < 0 || x + j >= w) continue;
+            if (GET_DATA_BIT(linem, j)) {
+                val = GET_DATA_BYTE(lineg, x + j);
+                array[val] += 1.0;
+            }
+        }
+    }
+
+    pixDestroy(&pixg);
+    return na;
+}
+
+
+/*!
+ *  pixGetGrayHistogramInRect()
+ *
+ *      Input:  pixs (8 bpp, or colormapped)
+ *              box (<optional>) over which histogram is to be computed;
+ *                   use full image if null)
+ *              factor (subsampling factor; integer >= 1)
+ *      Return: na (histogram), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs is cmapped, it is converted to 8 bpp gray.
+ *          If you want a histogram of the colormap indices, use
+ *          pixGetCmapHistogramInRect().
+ *      (2) This always returns a 256-value histogram of pixel values.
+ *      (3) Set the subsampling @factor > 1 to reduce the amount of computation.
+ */
+NUMA *
+pixGetGrayHistogramInRect(PIX     *pixs,
+                          BOX     *box,
+                          l_int32  factor)
+{
+l_int32     i, j, bx, by, bw, bh, w, h, wplg, val;
+l_uint32   *datag, *lineg;
+l_float32  *array;
+NUMA       *na;
+PIX        *pixg;
+
+    PROCNAME("pixGetGrayHistogramInRect");
+
+    if (!box)
+        return pixGetGrayHistogram(pixs, factor);
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8 && !pixGetColormap(pixs))
+        return (NUMA *)ERROR_PTR("pixs neither 8 bpp nor colormapped",
+                                 procName, NULL);
+    if (factor < 1)
+        return (NUMA *)ERROR_PTR("sampling must be >= 1", procName, NULL);
+
+    if ((na = numaCreate(256)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetCount(na, 256);  /* all initialized to 0.0 */
+    array = numaGetFArray(na, L_NOCOPY);
+
+    if (pixGetColormap(pixs))
+        pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else
+        pixg = pixClone(pixs);
+    pixGetDimensions(pixg, &w, &h, NULL);
+    datag = pixGetData(pixg);
+    wplg = pixGetWpl(pixg);
+    boxGetGeometry(box, &bx, &by, &bw, &bh);
+
+        /* Generate the histogram */
+    for (i = 0; i < bh; i += factor) {
+        if (by + i < 0 || by + i >= h) continue;
+        lineg = datag + (by + i) * wplg;
+        for (j = 0; j < bw; j += factor) {
+            if (bx + j < 0 || bx + j >= w) continue;
+            val = GET_DATA_BYTE(lineg, bx + j);
+            array[val] += 1.0;
+        }
+    }
+
+    pixDestroy(&pixg);
+    return na;
+}
+
+
+/*!
+ *  pixGetGrayHistogramTiled()
+ *
+ *      Input:  pixs (any depth, colormap OK)
+ *              factor (subsampling factor; integer >= 1)
+ *              nx, ny (tiling; >= 1; typically small)
+ *      Return: naa (set of histograms), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs is cmapped, it is converted to 8 bpp gray.
+ *      (2) This returns a set of 256-value histograms of pixel values.
+ *      (3) Set the subsampling factor > 1 to reduce the amount of computation.
+ */
+NUMAA *
+pixGetGrayHistogramTiled(PIX     *pixs,
+                         l_int32  factor,
+                         l_int32  nx,
+                         l_int32  ny)
+{
+l_int32  i, n;
+NUMA    *na;
+NUMAA   *naa;
+PIX     *pix1, *pix2;
+PIXA    *pixa;
+
+    PROCNAME("pixGetGrayHistogramTiled");
+
+    if (!pixs)
+        return (NUMAA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (factor < 1)
+        return (NUMAA *)ERROR_PTR("sampling must be >= 1", procName, NULL);
+    if (nx < 1 || ny < 1)
+        return (NUMAA *)ERROR_PTR("nx and ny must both be > 0", procName, NULL);
+
+    n = nx * ny;
+    if ((naa = numaaCreate(n)) == NULL)
+        return (NUMAA *)ERROR_PTR("naa not made", procName, NULL);
+
+    pix1 = pixConvertTo8(pixs, FALSE);
+    pixa = pixaSplitPix(pix1, nx, ny, 0, 0);
+    for (i = 0; i < n; i++) {
+        pix2 = pixaGetPix(pixa, i, L_CLONE);
+        na = pixGetGrayHistogram(pix2, factor);
+        numaaAddNuma(naa, na, L_INSERT);
+        pixDestroy(&pix2);
+    }
+
+    pixDestroy(&pix1);
+    pixaDestroy(&pixa);
+    return naa;
+}
+
+
+/*!
+ *  pixGetColorHistogram()
+ *
+ *      Input:  pixs (rgb or colormapped)
+ *              factor (subsampling factor; integer >= 1)
+ *              &nar (<return> red histogram)
+ *              &nag (<return> green histogram)
+ *              &nab (<return> blue histogram)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This generates a set of three 256 entry histograms,
+ *          one for each color component (r,g,b).
+ *      (2) Set the subsampling @factor > 1 to reduce the amount of computation.
+ */
+l_int32
+pixGetColorHistogram(PIX     *pixs,
+                     l_int32  factor,
+                     NUMA   **pnar,
+                     NUMA   **pnag,
+                     NUMA   **pnab)
+{
+l_int32     i, j, w, h, d, wpl, index, rval, gval, bval;
+l_uint32   *data, *line;
+l_float32  *rarray, *garray, *barray;
+NUMA       *nar, *nag, *nab;
+PIXCMAP    *cmap;
+
+    PROCNAME("pixGetColorHistogram");
+
+    if (pnar) *pnar = NULL;
+    if (pnag) *pnag = NULL;
+    if (pnab) *pnab = NULL;
+    if (!pnar || !pnag || !pnab)
+        return ERROR_INT("&nar, &nag, &nab not all defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    cmap = pixGetColormap(pixs);
+    if (cmap && (d != 2 && d != 4 && d != 8))
+        return ERROR_INT("colormap and not 2, 4, or 8 bpp", procName, 1);
+    if (!cmap && d != 32)
+        return ERROR_INT("no colormap and not rgb", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("sampling factor must be >= 1", procName, 1);
+
+        /* Set up the histogram arrays */
+    nar = numaCreate(256);
+    nag = numaCreate(256);
+    nab = numaCreate(256);
+    numaSetCount(nar, 256);
+    numaSetCount(nag, 256);
+    numaSetCount(nab, 256);
+    rarray = numaGetFArray(nar, L_NOCOPY);
+    garray = numaGetFArray(nag, L_NOCOPY);
+    barray = numaGetFArray(nab, L_NOCOPY);
+    *pnar = nar;
+    *pnag = nag;
+    *pnab = nab;
+
+        /* Generate the color histograms */
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    if (cmap) {
+        for (i = 0; i < h; i += factor) {
+            line = data + i * wpl;
+            for (j = 0; j < w; j += factor) {
+                if (d == 8)
+                    index = GET_DATA_BYTE(line, j);
+                else if (d == 4)
+                    index = GET_DATA_QBIT(line, j);
+                else   /* 2 bpp */
+                    index = GET_DATA_DIBIT(line, j);
+                pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+                rarray[rval] += 1.0;
+                garray[gval] += 1.0;
+                barray[bval] += 1.0;
+            }
+        }
+    } else {  /* 32 bpp rgb */
+        for (i = 0; i < h; i += factor) {
+            line = data + i * wpl;
+            for (j = 0; j < w; j += factor) {
+                extractRGBValues(line[j], &rval, &gval, &bval);
+                rarray[rval] += 1.0;
+                garray[gval] += 1.0;
+                barray[bval] += 1.0;
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixGetColorHistogramMasked()
+ *
+ *      Input:  pixs (32 bpp rgb, or colormapped)
+ *              pixm (<optional> 1 bpp mask over which histogram is
+ *                    to be computed; use all pixels if null)
+ *              x, y (UL corner of pixm relative to the UL corner of pixs;
+ *                    can be < 0; these values are ignored if pixm is null)
+ *              factor (subsampling factor; integer >= 1)
+ *              &nar (<return> red histogram)
+ *              &nag (<return> green histogram)
+ *              &nab (<return> blue histogram)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This generates a set of three 256 entry histograms,
+ *      (2) Set the subsampling @factor > 1 to reduce the amount of computation.
+ *      (3) Clipping of pixm (if it exists) to pixs is done in the inner loop.
+ *      (4) Input x,y are ignored unless pixm exists.
+ */
+l_int32
+pixGetColorHistogramMasked(PIX        *pixs,
+                           PIX        *pixm,
+                           l_int32     x,
+                           l_int32     y,
+                           l_int32     factor,
+                           NUMA      **pnar,
+                           NUMA      **pnag,
+                           NUMA      **pnab)
+{
+l_int32     i, j, w, h, d, wm, hm, dm, wpls, wplm, index, rval, gval, bval;
+l_uint32   *datas, *datam, *lines, *linem;
+l_float32  *rarray, *garray, *barray;
+NUMA       *nar, *nag, *nab;
+PIXCMAP    *cmap;
+
+    PROCNAME("pixGetColorHistogramMasked");
+
+    if (!pixm)
+        return pixGetColorHistogram(pixs, factor, pnar, pnag, pnab);
+
+    if (pnar) *pnar = NULL;
+    if (pnag) *pnag = NULL;
+    if (pnab) *pnab = NULL;
+    if (!pnar || !pnag || !pnab)
+        return ERROR_INT("&nar, &nag, &nab not all defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    cmap = pixGetColormap(pixs);
+    if (cmap && (d != 2 && d != 4 && d != 8))
+        return ERROR_INT("colormap and not 2, 4, or 8 bpp", procName, 1);
+    if (!cmap && d != 32)
+        return ERROR_INT("no colormap and not rgb", procName, 1);
+    pixGetDimensions(pixm, &wm, &hm, &dm);
+    if (dm != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("sampling factor must be >= 1", procName, 1);
+
+        /* Set up the histogram arrays */
+    nar = numaCreate(256);
+    nag = numaCreate(256);
+    nab = numaCreate(256);
+    numaSetCount(nar, 256);
+    numaSetCount(nag, 256);
+    numaSetCount(nab, 256);
+    rarray = numaGetFArray(nar, L_NOCOPY);
+    garray = numaGetFArray(nag, L_NOCOPY);
+    barray = numaGetFArray(nab, L_NOCOPY);
+    *pnar = nar;
+    *pnag = nag;
+    *pnab = nab;
+
+        /* Generate the color histograms */
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datam = pixGetData(pixm);
+    wplm = pixGetWpl(pixm);
+    if (cmap) {
+        for (i = 0; i < hm; i += factor) {
+            if (y + i < 0 || y + i >= h) continue;
+            lines = datas + (y + i) * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < wm; j += factor) {
+                if (x + j < 0 || x + j >= w) continue;
+                if (GET_DATA_BIT(linem, j)) {
+                    if (d == 8)
+                        index = GET_DATA_BYTE(lines, x + j);
+                    else if (d == 4)
+                        index = GET_DATA_QBIT(lines, x + j);
+                    else   /* 2 bpp */
+                        index = GET_DATA_DIBIT(lines, x + j);
+                    pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+                    rarray[rval] += 1.0;
+                    garray[gval] += 1.0;
+                    barray[bval] += 1.0;
+                }
+            }
+        }
+    } else {  /* 32 bpp rgb */
+        for (i = 0; i < hm; i += factor) {
+            if (y + i < 0 || y + i >= h) continue;
+            lines = datas + (y + i) * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < wm; j += factor) {
+                if (x + j < 0 || x + j >= w) continue;
+                if (GET_DATA_BIT(linem, j)) {
+                    extractRGBValues(lines[x + j], &rval, &gval, &bval);
+                    rarray[rval] += 1.0;
+                    garray[gval] += 1.0;
+                    barray[bval] += 1.0;
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixGetCmapHistogram()
+ *
+ *      Input:  pixs (colormapped: d = 2, 4 or 8)
+ *              factor (subsampling factor; integer >= 1)
+ *      Return: na (histogram of cmap indices), or null on error
+ *
+ *  Notes:
+ *      (1) This generates a histogram of colormap pixel indices,
+ *          and is of size 2^d.
+ *      (2) Set the subsampling @factor > 1 to reduce the amount of computation.
+ */
+NUMA *
+pixGetCmapHistogram(PIX     *pixs,
+                    l_int32  factor)
+{
+l_int32     i, j, w, h, d, wpl, val, size;
+l_uint32   *data, *line;
+l_float32  *array;
+NUMA       *na;
+
+    PROCNAME("pixGetCmapHistogram");
+
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs) == NULL)
+        return (NUMA *)ERROR_PTR("pixs not cmapped", procName, NULL);
+    if (factor < 1)
+        return (NUMA *)ERROR_PTR("sampling must be >= 1", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 2 && d != 4 && d != 8)
+        return (NUMA *)ERROR_PTR("d not 2, 4 or 8", procName, NULL);
+
+    size = 1 << d;
+    if ((na = numaCreate(size)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetCount(na, size);  /* all initialized to 0.0 */
+    array = numaGetFArray(na, L_NOCOPY);
+
+    wpl = pixGetWpl(pixs);
+    data = pixGetData(pixs);
+    for (i = 0; i < h; i += factor) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j += factor) {
+            if (d == 8)
+                val = GET_DATA_BYTE(line, j);
+            else if (d == 4)
+                val = GET_DATA_QBIT(line, j);
+            else  /* d == 2 */
+                val = GET_DATA_DIBIT(line, j);
+            array[val] += 1.0;
+        }
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixGetCmapHistogramMasked()
+ *
+ *      Input:  pixs (colormapped: d = 2, 4 or 8)
+ *              pixm (<optional> 1 bpp mask over which histogram is
+ *                    to be computed; use all pixels if null)
+ *              x, y (UL corner of pixm relative to the UL corner of pixs;
+ *                    can be < 0; these values are ignored if pixm is null)
+ *              factor (subsampling factor; integer >= 1)
+ *      Return: na (histogram), or null on error
+ *
+ *  Notes:
+ *      (1) This generates a histogram of colormap pixel indices,
+ *          and is of size 2^d.
+ *      (2) Set the subsampling @factor > 1 to reduce the amount of computation.
+ *      (3) Clipping of pixm to pixs is done in the inner loop.
+ */
+NUMA *
+pixGetCmapHistogramMasked(PIX     *pixs,
+                          PIX     *pixm,
+                          l_int32  x,
+                          l_int32  y,
+                          l_int32  factor)
+{
+l_int32     i, j, w, h, d, wm, hm, dm, wpls, wplm, val, size;
+l_uint32   *datas, *datam, *lines, *linem;
+l_float32  *array;
+NUMA       *na;
+
+    PROCNAME("pixGetCmapHistogramMasked");
+
+    if (!pixm)
+        return pixGetCmapHistogram(pixs, factor);
+
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs) == NULL)
+        return (NUMA *)ERROR_PTR("pixs not cmapped", procName, NULL);
+    pixGetDimensions(pixm, &wm, &hm, &dm);
+    if (dm != 1)
+        return (NUMA *)ERROR_PTR("pixm not 1 bpp", procName, NULL);
+    if (factor < 1)
+        return (NUMA *)ERROR_PTR("sampling must be >= 1", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 2 && d != 4 && d != 8)
+        return (NUMA *)ERROR_PTR("d not 2, 4 or 8", procName, NULL);
+
+    size = 1 << d;
+    if ((na = numaCreate(size)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetCount(na, size);  /* all initialized to 0.0 */
+    array = numaGetFArray(na, L_NOCOPY);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datam = pixGetData(pixm);
+    wplm = pixGetWpl(pixm);
+
+    for (i = 0; i < hm; i += factor) {
+        if (y + i < 0 || y + i >= h) continue;
+        lines = datas + (y + i) * wpls;
+        linem = datam + i * wplm;
+        for (j = 0; j < wm; j += factor) {
+            if (x + j < 0 || x + j >= w) continue;
+            if (GET_DATA_BIT(linem, j)) {
+                if (d == 8)
+                    val = GET_DATA_BYTE(lines, x + j);
+                else if (d == 4)
+                    val = GET_DATA_QBIT(lines, x + j);
+                else  /* d == 2 */
+                    val = GET_DATA_DIBIT(lines, x + j);
+                array[val] += 1.0;
+            }
+        }
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixGetCmapHistogramInRect()
+ *
+ *      Input:  pixs (colormapped: d = 2, 4 or 8)
+ *              box (<optional>) over which histogram is to be computed;
+ *                   use full image if null)
+ *              factor (subsampling factor; integer >= 1)
+ *      Return: na (histogram), or null on error
+ *
+ *  Notes:
+ *      (1) This generates a histogram of colormap pixel indices,
+ *          and is of size 2^d.
+ *      (2) Set the subsampling @factor > 1 to reduce the amount of computation.
+ *      (3) Clipping to the box is done in the inner loop.
+ */
+NUMA *
+pixGetCmapHistogramInRect(PIX     *pixs,
+                          BOX     *box,
+                          l_int32  factor)
+{
+l_int32     i, j, bx, by, bw, bh, w, h, d, wpls, val, size;
+l_uint32   *datas, *lines;
+l_float32  *array;
+NUMA       *na;
+
+    PROCNAME("pixGetCmapHistogramInRect");
+
+    if (!box)
+        return pixGetCmapHistogram(pixs, factor);
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs) == NULL)
+        return (NUMA *)ERROR_PTR("pixs not cmapped", procName, NULL);
+    if (factor < 1)
+        return (NUMA *)ERROR_PTR("sampling must be >= 1", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 2 && d != 4 && d != 8)
+        return (NUMA *)ERROR_PTR("d not 2, 4 or 8", procName, NULL);
+
+    size = 1 << d;
+    if ((na = numaCreate(size)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+    numaSetCount(na, size);  /* all initialized to 0.0 */
+    array = numaGetFArray(na, L_NOCOPY);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    boxGetGeometry(box, &bx, &by, &bw, &bh);
+
+    for (i = 0; i < bh; i += factor) {
+        if (by + i < 0 || by + i >= h) continue;
+        lines = datas + (by + i) * wpls;
+        for (j = 0; j < bw; j += factor) {
+            if (bx + j < 0 || bx + j >= w) continue;
+            if (d == 8)
+                val = GET_DATA_BYTE(lines, bx + j);
+            else if (d == 4)
+                val = GET_DATA_QBIT(lines, bx + j);
+            else  /* d == 2 */
+                val = GET_DATA_DIBIT(lines, bx + j);
+            array[val] += 1.0;
+        }
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixGetRankValue()
+ *
+ *      Input:  pixs (8 bpp, 32 bpp or colormapped)
+ *              factor (subsampling factor; integer >= 1)
+ *              rank (between 0.0 and 1.0; 1.0 is brightest, 0.0 is darkest)
+ *              &value (<return> pixel value corresponding to input rank)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Simple function to get rank values of an image.
+ *          For a color image, the median value (rank = 0.5) can be
+ *          used to linearly remap the colors based on the median
+ *          of a target image, using pixLinearMapToTargetColor().
+ */
+l_int32
+pixGetRankValue(PIX       *pixs,
+                l_int32    factor,
+                l_float32  rank,
+                l_uint32  *pvalue)
+{
+l_int32    d;
+l_float32  val, rval, gval, bval;
+PIX       *pixt;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixGetRankValue");
+
+    if (!pvalue)
+        return ERROR_INT("&value not defined", procName, 1);
+    *pvalue = 0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    d = pixGetDepth(pixs);
+    cmap = pixGetColormap(pixs);
+    if (d != 8 && d != 32 && !cmap)
+        return ERROR_INT("pixs not 8 or 32 bpp, or cmapped", procName, 1);
+    if (cmap)
+        pixt = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    else
+        pixt = pixClone(pixs);
+    d = pixGetDepth(pixt);
+
+    if (d == 8) {
+        pixGetRankValueMasked(pixt, NULL, 0, 0, factor, rank, &val, NULL);
+        *pvalue = lept_roundftoi(val);
+    } else {
+        pixGetRankValueMaskedRGB(pixt, NULL, 0, 0, factor, rank,
+                                 &rval, &gval, &bval);
+        composeRGBPixel(lept_roundftoi(rval), lept_roundftoi(gval),
+                        lept_roundftoi(bval), pvalue);
+    }
+
+    pixDestroy(&pixt);
+    return 0;
+}
+
+
+/*!
+ *  pixGetRankValueMaskedRGB()
+ *
+ *      Input:  pixs (32 bpp)
+ *              pixm (<optional> 1 bpp mask over which rank val is to be taken;
+ *                    use all pixels if null)
+ *              x, y (UL corner of pixm relative to the UL corner of pixs;
+ *                    can be < 0; these values are ignored if pixm is null)
+ *              factor (subsampling factor; integer >= 1)
+ *              rank (between 0.0 and 1.0; 1.0 is brightest, 0.0 is darkest)
+ *              &rval (<optional return> red component val for input rank)
+ *              &gval (<optional return> green component val for input rank)
+ *              &bval (<optional return> blue component val for input rank)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Computes the rank component values of pixels in pixs that
+ *          are under the fg of the optional mask.  If the mask is null, it
+ *          computes the average of the pixels in pixs.
+ *      (2) Set the subsampling @factor > 1 to reduce the amount of
+ *          computation.
+ *      (4) Input x,y are ignored unless pixm exists.
+ *      (5) The rank must be in [0.0 ... 1.0], where the brightest pixel
+ *          has rank 1.0.  For the median pixel value, use 0.5.
+ */
+l_int32
+pixGetRankValueMaskedRGB(PIX        *pixs,
+                         PIX        *pixm,
+                         l_int32     x,
+                         l_int32     y,
+                         l_int32     factor,
+                         l_float32   rank,
+                         l_float32  *prval,
+                         l_float32  *pgval,
+                         l_float32  *pbval)
+{
+l_float32  scale;
+PIX       *pixmt, *pixt;
+
+    PROCNAME("pixGetRankValueMaskedRGB");
+
+    if (prval) *prval = 0.0;
+    if (pgval) *pgval = 0.0;
+    if (pbval) *pbval = 0.0;
+    if (!prval && !pgval && !pbval)
+        return ERROR_INT("no results requested", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not 32 bpp", procName, 1);
+    if (pixm && pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("sampling factor must be >= 1", procName, 1);
+    if (rank < 0.0 || rank > 1.0)
+        return ERROR_INT("rank not in [0.0 ... 1.0]", procName, 1);
+
+    pixmt = NULL;
+    if (pixm) {
+        scale = 1.0 / (l_float32)factor;
+        pixmt = pixScale(pixm, scale, scale);
+    }
+    if (prval) {
+        pixt = pixScaleRGBToGrayFast(pixs, factor, COLOR_RED);
+        pixGetRankValueMasked(pixt, pixmt, x / factor, y / factor,
+                              factor, rank, prval, NULL);
+        pixDestroy(&pixt);
+    }
+    if (pgval) {
+        pixt = pixScaleRGBToGrayFast(pixs, factor, COLOR_GREEN);
+        pixGetRankValueMasked(pixt, pixmt, x / factor, y / factor,
+                              factor, rank, pgval, NULL);
+        pixDestroy(&pixt);
+    }
+    if (pbval) {
+        pixt = pixScaleRGBToGrayFast(pixs, factor, COLOR_BLUE);
+        pixGetRankValueMasked(pixt, pixmt, x / factor, y / factor,
+                              factor, rank, pbval, NULL);
+        pixDestroy(&pixt);
+    }
+    pixDestroy(&pixmt);
+    return 0;
+}
+
+
+/*!
+ *  pixGetRankValueMasked()
+ *
+ *      Input:  pixs (8 bpp, or colormapped)
+ *              pixm (<optional> 1 bpp mask over which rank val is to be taken;
+ *                    use all pixels if null)
+ *              x, y (UL corner of pixm relative to the UL corner of pixs;
+ *                    can be < 0; these values are ignored if pixm is null)
+ *              factor (subsampling factor; integer >= 1)
+ *              rank (between 0.0 and 1.0; 1.0 is brightest, 0.0 is darkest)
+ *              &val (<return> pixel value corresponding to input rank)
+ *              &na (<optional return> of histogram)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Computes the rank value of pixels in pixs that are under
+ *          the fg of the optional mask.  If the mask is null, it
+ *          computes the average of the pixels in pixs.
+ *      (2) Set the subsampling @factor > 1 to reduce the amount of
+ *          computation.
+ *      (3) Clipping of pixm (if it exists) to pixs is done in the inner loop.
+ *      (4) Input x,y are ignored unless pixm exists.
+ *      (5) The rank must be in [0.0 ... 1.0], where the brightest pixel
+ *          has rank 1.0.  For the median pixel value, use 0.5.
+ *      (6) The histogram can optionally be returned, so that other rank
+ *          values can be extracted without recomputing the histogram.
+ *          In that case, just use
+ *              numaHistogramGetValFromRank(na, rank, &val);
+ *          on the returned Numa for additional rank values.
+ */
+l_int32
+pixGetRankValueMasked(PIX        *pixs,
+                      PIX        *pixm,
+                      l_int32     x,
+                      l_int32     y,
+                      l_int32     factor,
+                      l_float32   rank,
+                      l_float32  *pval,
+                      NUMA      **pna)
+{
+NUMA  *na;
+
+    PROCNAME("pixGetRankValueMasked");
+
+    if (pna) *pna = NULL;
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0.0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 8 && !pixGetColormap(pixs))
+        return ERROR_INT("pixs neither 8 bpp nor colormapped", procName, 1);
+    if (pixm && pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("sampling factor must be >= 1", procName, 1);
+    if (rank < 0.0 || rank > 1.0)
+        return ERROR_INT("rank not in [0.0 ... 1.0]", procName, 1);
+
+    if ((na = pixGetGrayHistogramMasked(pixs, pixm, x, y, factor)) == NULL)
+        return ERROR_INT("na not made", procName, 1);
+    numaHistogramGetValFromRank(na, rank, pval);
+    if (pna)
+        *pna = na;
+    else
+        numaDestroy(&na);
+
+    return 0;
+}
+
+
+/*!
+ *  pixGetAverageValue()
+ *
+ *      Input:  pixs (8 bpp, 32 bpp or colormapped)
+ *              factor (subsampling factor; integer >= 1)
+ *              type (L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE,
+ *                    L_STANDARD_DEVIATION, L_VARIANCE)
+ *              &value (<return> pixel value corresponding to input type)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Simple function to get average statistical values of an image.
+ */
+l_int32
+pixGetAverageValue(PIX       *pixs,
+                   l_int32    factor,
+                   l_int32    type,
+                   l_uint32  *pvalue)
+{
+l_int32    d;
+l_float32  val, rval, gval, bval;
+PIX       *pixt;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixGetAverageValue");
+
+    if (!pvalue)
+        return ERROR_INT("&value not defined", procName, 1);
+    *pvalue = 0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    d = pixGetDepth(pixs);
+    cmap = pixGetColormap(pixs);
+    if (d != 8 && d != 32 && !cmap)
+        return ERROR_INT("pixs not 8 or 32 bpp, or cmapped", procName, 1);
+    if (cmap)
+        pixt = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    else
+        pixt = pixClone(pixs);
+    d = pixGetDepth(pixt);
+
+    if (d == 8) {
+        pixGetAverageMasked(pixt, NULL, 0, 0, factor, type, &val);
+        *pvalue = lept_roundftoi(val);
+    } else {
+        pixGetAverageMaskedRGB(pixt, NULL, 0, 0, factor, type,
+                               &rval, &gval, &bval);
+        composeRGBPixel(lept_roundftoi(rval), lept_roundftoi(gval),
+                        lept_roundftoi(bval), pvalue);
+    }
+
+    pixDestroy(&pixt);
+    return 0;
+}
+
+
+/*!
+ *  pixGetAverageMaskedRGB()
+ *
+ *      Input:  pixs (32 bpp, or colormapped)
+ *              pixm (<optional> 1 bpp mask over which average is to be taken;
+ *                    use all pixels if null)
+ *              x, y (UL corner of pixm relative to the UL corner of pixs;
+ *                    can be < 0)
+ *              factor (subsampling factor; >= 1)
+ *              type (L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE,
+ *                    L_STANDARD_DEVIATION, L_VARIANCE)
+ *              &rval (<return optional> measured red value of given 'type')
+ *              &gval (<return optional> measured green value of given 'type')
+ *              &bval (<return optional> measured blue value of given 'type')
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For usage, see pixGetAverageMasked().
+ *      (2) If there is a colormap, it is removed before the 8 bpp
+ *          component images are extracted.
+ */
+l_int32
+pixGetAverageMaskedRGB(PIX        *pixs,
+                       PIX        *pixm,
+                       l_int32     x,
+                       l_int32     y,
+                       l_int32     factor,
+                       l_int32     type,
+                       l_float32  *prval,
+                       l_float32  *pgval,
+                       l_float32  *pbval)
+{
+PIX      *pixt;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixGetAverageMaskedRGB");
+
+    if (prval) *prval = 0.0;
+    if (pgval) *pgval = 0.0;
+    if (pbval) *pbval = 0.0;
+    if (!prval && !pgval && !pbval)
+        return ERROR_INT("no values requested", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    cmap = pixGetColormap(pixs);
+    if (pixGetDepth(pixs) != 32 && !cmap)
+        return ERROR_INT("pixs neither 32 bpp nor colormapped", procName, 1);
+    if (pixm && pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("sampling factor must be >= 1", procName, 1);
+    if (type != L_MEAN_ABSVAL && type != L_ROOT_MEAN_SQUARE &&
+        type != L_STANDARD_DEVIATION && type != L_VARIANCE)
+        return ERROR_INT("invalid measure type", procName, 1);
+
+    if (prval) {
+        if (cmap)
+            pixt = pixGetRGBComponentCmap(pixs, COLOR_RED);
+        else
+            pixt = pixGetRGBComponent(pixs, COLOR_RED);
+        pixGetAverageMasked(pixt, pixm, x, y, factor, type, prval);
+        pixDestroy(&pixt);
+    }
+    if (pgval) {
+        if (cmap)
+            pixt = pixGetRGBComponentCmap(pixs, COLOR_GREEN);
+        else
+            pixt = pixGetRGBComponent(pixs, COLOR_GREEN);
+        pixGetAverageMasked(pixt, pixm, x, y, factor, type, pgval);
+        pixDestroy(&pixt);
+    }
+    if (pbval) {
+        if (cmap)
+            pixt = pixGetRGBComponentCmap(pixs, COLOR_BLUE);
+        else
+            pixt = pixGetRGBComponent(pixs, COLOR_BLUE);
+        pixGetAverageMasked(pixt, pixm, x, y, factor, type, pbval);
+        pixDestroy(&pixt);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixGetAverageMasked()
+ *
+ *      Input:  pixs (8 or 16 bpp, or colormapped)
+ *              pixm (<optional> 1 bpp mask over which average is to be taken;
+ *                    use all pixels if null)
+ *              x, y (UL corner of pixm relative to the UL corner of pixs;
+ *                    can be < 0)
+ *              factor (subsampling factor; >= 1)
+ *              type (L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE,
+ *                    L_STANDARD_DEVIATION, L_VARIANCE)
+ *              &val (<return> measured value of given 'type')
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Use L_MEAN_ABSVAL to get the average value of pixels in pixs
+ *          that are under the fg of the optional mask.  If the mask
+ *          is null, it finds the average of the pixels in pixs.
+ *      (2) Likewise, use L_ROOT_MEAN_SQUARE to get the rms value of
+ *          pixels in pixs, either masked or not; L_STANDARD_DEVIATION
+ *          to get the standard deviation from the mean of the pixels;
+ *          L_VARIANCE to get the average squared difference from the
+ *          expected value.  The variance is the square of the stdev.
+ *          For the standard deviation, we use
+ *              sqrt(<(<x> - x)>^2) = sqrt(<x^2> - <x>^2)
+ *      (3) Set the subsampling @factor > 1 to reduce the amount of
+ *          computation.
+ *      (4) Clipping of pixm (if it exists) to pixs is done in the inner loop.
+ *      (5) Input x,y are ignored unless pixm exists.
+ */
+l_int32
+pixGetAverageMasked(PIX        *pixs,
+                    PIX        *pixm,
+                    l_int32     x,
+                    l_int32     y,
+                    l_int32     factor,
+                    l_int32     type,
+                    l_float32  *pval)
+{
+l_int32    i, j, w, h, d, wm, hm, wplg, wplm, val, count;
+l_uint32  *datag, *datam, *lineg, *linem;
+l_float64  sumave, summs, ave, meansq, var;
+PIX       *pixg;
+
+    PROCNAME("pixGetAverageMasked");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0.0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 16 && !pixGetColormap(pixs))
+        return ERROR_INT("pixs not 8 or 16 bpp or colormapped", procName, 1);
+    if (pixm && pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm not 1 bpp", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("sampling factor must be >= 1", procName, 1);
+    if (type != L_MEAN_ABSVAL && type != L_ROOT_MEAN_SQUARE &&
+        type != L_STANDARD_DEVIATION && type != L_VARIANCE)
+        return ERROR_INT("invalid measure type", procName, 1);
+
+    if (pixGetColormap(pixs))
+        pixg = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else
+        pixg = pixClone(pixs);
+    pixGetDimensions(pixg, &w, &h, &d);
+    datag = pixGetData(pixg);
+    wplg = pixGetWpl(pixg);
+
+    sumave = summs = 0.0;
+    count = 0;
+    if (!pixm) {
+        for (i = 0; i < h; i += factor) {
+            lineg = datag + i * wplg;
+            for (j = 0; j < w; j += factor) {
+                if (d == 8)
+                    val = GET_DATA_BYTE(lineg, j);
+                else  /* d == 16 */
+                    val = GET_DATA_TWO_BYTES(lineg, j);
+                if (type != L_ROOT_MEAN_SQUARE)
+                    sumave += val;
+                if (type != L_MEAN_ABSVAL)
+                    summs += val * val;
+                count++;
+            }
+        }
+    } else {
+        pixGetDimensions(pixm, &wm, &hm, NULL);
+        datam = pixGetData(pixm);
+        wplm = pixGetWpl(pixm);
+        for (i = 0; i < hm; i += factor) {
+            if (y + i < 0 || y + i >= h) continue;
+            lineg = datag + (y + i) * wplg;
+            linem = datam + i * wplm;
+            for (j = 0; j < wm; j += factor) {
+                if (x + j < 0 || x + j >= w) continue;
+                if (GET_DATA_BIT(linem, j)) {
+                    if (d == 8)
+                        val = GET_DATA_BYTE(lineg, x + j);
+                    else  /* d == 16 */
+                        val = GET_DATA_TWO_BYTES(lineg, x + j);
+                    if (type != L_ROOT_MEAN_SQUARE)
+                        sumave += val;
+                    if (type != L_MEAN_ABSVAL)
+                        summs += val * val;
+                    count++;
+                }
+            }
+        }
+    }
+
+    pixDestroy(&pixg);
+    if (count == 0)
+        return ERROR_INT("no pixels sampled", procName, 1);
+    ave = sumave / (l_float64)count;
+    meansq = summs / (l_float64)count;
+    var = meansq - ave * ave;
+    if (type == L_MEAN_ABSVAL)
+        *pval = (l_float32)ave;
+    else if (type == L_ROOT_MEAN_SQUARE)
+        *pval = (l_float32)sqrt(meansq);
+    else if (type == L_STANDARD_DEVIATION)
+        *pval = (l_float32)sqrt(var);
+    else  /* type == L_VARIANCE */
+        *pval = (l_float32)var;
+
+    return 0;
+}
+
+
+/*!
+ *  pixGetAverageTiledRGB()
+ *
+ *      Input:  pixs (32 bpp, or colormapped)
+ *              sx, sy (tile size; must be at least 2 x 2)
+ *              type (L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE, L_STANDARD_DEVIATION)
+ *              &pixr (<optional return> tiled 'average' of red component)
+ *              &pixg (<optional return> tiled 'average' of green component)
+ *              &pixb (<optional return> tiled 'average' of blue component)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For usage, see pixGetAverageTiled().
+ *      (2) If there is a colormap, it is removed before the 8 bpp
+ *          component images are extracted.
+ */
+l_int32
+pixGetAverageTiledRGB(PIX     *pixs,
+                      l_int32  sx,
+                      l_int32  sy,
+                      l_int32  type,
+                      PIX    **ppixr,
+                      PIX    **ppixg,
+                      PIX    **ppixb)
+{
+PIX      *pixt;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixGetAverageTiledRGB");
+
+    if (ppixr) *ppixr = NULL;
+    if (ppixg) *ppixg = NULL;
+    if (ppixb) *ppixb = NULL;
+    if (!ppixr && !ppixg && !ppixb)
+        return ERROR_INT("no data requested", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    cmap = pixGetColormap(pixs);
+    if (pixGetDepth(pixs) != 32 && !cmap)
+        return ERROR_INT("pixs neither 32 bpp nor colormapped", procName, 1);
+    if (sx < 2 || sy < 2)
+        return ERROR_INT("sx and sy not both > 1", procName, 1);
+    if (type != L_MEAN_ABSVAL && type != L_ROOT_MEAN_SQUARE &&
+        type != L_STANDARD_DEVIATION)
+        return ERROR_INT("invalid measure type", procName, 1);
+
+    if (ppixr) {
+        if (cmap)
+            pixt = pixGetRGBComponentCmap(pixs, COLOR_RED);
+        else
+            pixt = pixGetRGBComponent(pixs, COLOR_RED);
+        *ppixr = pixGetAverageTiled(pixt, sx, sy, type);
+        pixDestroy(&pixt);
+    }
+    if (ppixg) {
+        if (cmap)
+            pixt = pixGetRGBComponentCmap(pixs, COLOR_GREEN);
+        else
+            pixt = pixGetRGBComponent(pixs, COLOR_GREEN);
+        *ppixg = pixGetAverageTiled(pixt, sx, sy, type);
+        pixDestroy(&pixt);
+    }
+    if (ppixb) {
+        if (cmap)
+            pixt = pixGetRGBComponentCmap(pixs, COLOR_BLUE);
+        else
+            pixt = pixGetRGBComponent(pixs, COLOR_BLUE);
+        *ppixb = pixGetAverageTiled(pixt, sx, sy, type);
+        pixDestroy(&pixt);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixGetAverageTiled()
+ *
+ *      Input:  pixs (8 bpp, or colormapped)
+ *              sx, sy (tile size; must be at least 2 x 2)
+ *              type (L_MEAN_ABSVAL, L_ROOT_MEAN_SQUARE, L_STANDARD_DEVIATION)
+ *      Return: pixd (average values in each tile), or null on error
+ *
+ *  Notes:
+ *      (1) Only computes for tiles that are entirely contained in pixs.
+ *      (2) Use L_MEAN_ABSVAL to get the average abs value within the tile;
+ *          L_ROOT_MEAN_SQUARE to get the rms value within each tile;
+ *          L_STANDARD_DEVIATION to get the standard dev. from the average
+ *          within each tile.
+ *      (3) If colormapped, converts to 8 bpp gray.
+ */
+PIX *
+pixGetAverageTiled(PIX     *pixs,
+                   l_int32  sx,
+                   l_int32  sy,
+                   l_int32  type)
+{
+l_int32    i, j, k, m, w, h, wd, hd, d, pos, wplt, wpld, valt;
+l_uint32  *datat, *datad, *linet, *lined, *startt;
+l_float64  sumave, summs, ave, meansq, normfact;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixGetAverageTiled");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && !pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs not 8 bpp or cmapped", procName, NULL);
+    if (sx < 2 || sy < 2)
+        return (PIX *)ERROR_PTR("sx and sy not both > 1", procName, NULL);
+    wd = w / sx;
+    hd = h / sy;
+    if (wd < 1 || hd < 1)
+        return (PIX *)ERROR_PTR("wd or hd == 0", procName, NULL);
+    if (type != L_MEAN_ABSVAL && type != L_ROOT_MEAN_SQUARE &&
+        type != L_STANDARD_DEVIATION)
+        return (PIX *)ERROR_PTR("invalid measure type", procName, NULL);
+
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    pixd = pixCreate(wd, hd, 8);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    normfact = 1. / (l_float64)(sx * sy);
+    for (i = 0; i < hd; i++) {
+        lined = datad + i * wpld;
+        linet = datat + i * sy * wplt;
+        for (j = 0; j < wd; j++) {
+            if (type == L_MEAN_ABSVAL || type == L_STANDARD_DEVIATION) {
+                sumave = 0.0;
+                for (k = 0; k < sy; k++) {
+                    startt = linet + k * wplt;
+                    for (m = 0; m < sx; m++) {
+                        pos = j * sx + m;
+                        valt = GET_DATA_BYTE(startt, pos);
+                        sumave += valt;
+                    }
+                }
+                ave = normfact * sumave;
+            }
+            if (type == L_ROOT_MEAN_SQUARE || type == L_STANDARD_DEVIATION) {
+                summs = 0.0;
+                for (k = 0; k < sy; k++) {
+                    startt = linet + k * wplt;
+                    for (m = 0; m < sx; m++) {
+                        pos = j * sx + m;
+                        valt = GET_DATA_BYTE(startt, pos);
+                        summs += valt * valt;
+                    }
+                }
+                meansq = normfact * summs;
+            }
+            if (type == L_MEAN_ABSVAL)
+                valt = (l_int32)(ave + 0.5);
+            else if (type == L_ROOT_MEAN_SQUARE)
+                valt = (l_int32)(sqrt(meansq) + 0.5);
+            else  /* type == L_STANDARD_DEVIATION */
+                valt = (l_int32)(sqrt(meansq - ave * ave) + 0.5);
+            SET_DATA_BYTE(lined, j, valt);
+        }
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixRowStats()
+ *
+ *      Input:  pixs (8 bpp; not cmapped)
+ *              box (<optional> clipping box; can be null)
+ *              &namean (<optional return> numa of mean values)
+ *              &namedian (<optional return> numa of median values)
+ *              &namode (<optional return> numa of mode intensity values)
+ *              &namodecount (<optional return> numa of mode counts)
+ *              &navar (<optional return> numa of variance)
+ *              &narootvar (<optional return> numa of square root of variance)
+ *      Return: na (numa of requested statistic for each row), or null on error
+ *
+ *  Notes:
+ *      (1) This computes numas that represent column vectors of statistics,
+ *          with each of its values derived from the corresponding row of a Pix.
+ *      (2) Use NULL on input to prevent computation of any of the 5 numas.
+ *      (3) Other functions that compute pixel row statistics are:
+ *             pixCountPixelsByRow()
+ *             pixAverageByRow()
+ *             pixVarianceByRow()
+ *             pixGetRowStats()
+ */
+l_int32
+pixRowStats(PIX    *pixs,
+            BOX    *box,
+            NUMA  **pnamean,
+            NUMA  **pnamedian,
+            NUMA  **pnamode,
+            NUMA  **pnamodecount,
+            NUMA  **pnavar,
+            NUMA  **pnarootvar)
+{
+l_int32     i, j, k, w, h, val, wpls, sum, sumsq, target, max, modeval;
+l_int32     xstart, xend, ystart, yend, bw, bh;
+l_int32    *histo;
+l_uint32   *lines, *datas;
+l_float32   norm;
+l_float32  *famean, *fameansq, *favar, *farootvar;
+l_float32  *famedian, *famode, *famodecount;
+
+    PROCNAME("pixRowStats");
+
+    if (pnamean) *pnamean = NULL;
+    if (pnamedian) *pnamedian = NULL;
+    if (pnamode) *pnamode = NULL;
+    if (pnamodecount) *pnamodecount = NULL;
+    if (pnavar) *pnavar = NULL;
+    if (pnarootvar) *pnarootvar = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs undefined or not 8 bpp", procName, 1);
+    famean = fameansq = favar = farootvar = NULL;
+    famedian = famode = famodecount = NULL;
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return ERROR_INT("invalid clipping box", procName, 1);
+
+        /* We need the mean for variance and root variance */
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if (pnamean || pnavar || pnarootvar) {
+        norm = 1. / (l_float32)bw;
+        famean = (l_float32 *)LEPT_CALLOC(bh, sizeof(l_float32));
+        fameansq = (l_float32 *)LEPT_CALLOC(bh, sizeof(l_float32));
+        if (pnavar || pnarootvar) {
+            favar = (l_float32 *)LEPT_CALLOC(bh, sizeof(l_float32));
+            if (pnarootvar)
+                farootvar = (l_float32 *)LEPT_CALLOC(bh, sizeof(l_float32));
+        }
+        for (i = ystart; i < yend; i++) {
+            sum = sumsq = 0;
+            lines = datas + i * wpls;
+            for (j = xstart; j < xend; j++) {
+                val = GET_DATA_BYTE(lines, j);
+                sum += val;
+                sumsq += val * val;
+            }
+            famean[i] = norm * sum;
+            fameansq[i] = norm * sumsq;
+            if (pnavar || pnarootvar) {
+                favar[i] = fameansq[i] - famean[i] * famean[i];
+                if (pnarootvar)
+                    farootvar[i] = sqrtf(favar[i]);
+            }
+        }
+        LEPT_FREE(fameansq);
+        if (pnamean)
+            *pnamean = numaCreateFromFArray(famean, bh, L_INSERT);
+        else
+            LEPT_FREE(famean);
+        if (pnavar)
+            *pnavar = numaCreateFromFArray(favar, bh, L_INSERT);
+        else
+            LEPT_FREE(favar);
+        if (pnarootvar)
+            *pnarootvar = numaCreateFromFArray(farootvar, bh, L_INSERT);
+    }
+
+        /* We need a histogram to find the median and/or mode values */
+    if (pnamedian || pnamode || pnamodecount) {
+        histo = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+        if (pnamedian) {
+            *pnamedian = numaMakeConstant(0, bh);
+            famedian = numaGetFArray(*pnamedian, L_NOCOPY);
+        }
+        if (pnamode) {
+            *pnamode = numaMakeConstant(0, bh);
+            famode = numaGetFArray(*pnamode, L_NOCOPY);
+        }
+        if (pnamodecount) {
+            *pnamodecount = numaMakeConstant(0, bh);
+            famodecount = numaGetFArray(*pnamodecount, L_NOCOPY);
+        }
+        for (i = ystart; i < yend; i++) {
+            lines = datas + i * wpls;
+            memset(histo, 0, 1024);
+            for (j = xstart; j < xend; j++) {
+                val = GET_DATA_BYTE(lines, j);
+                histo[val]++;
+            }
+
+            if (pnamedian) {
+                sum = 0;
+                target = (bw + 1) / 2;
+                for (k = 0; k < 256; k++) {
+                    sum += histo[k];
+                    if (sum >= target) {
+                        famedian[i] = k;
+                        break;
+                    }
+                }
+            }
+
+            if (pnamode || pnamodecount) {
+                max = 0;
+                modeval = 0;
+                for (k = 0; k < 256; k++) {
+                    if (histo[k] > max) {
+                        max = histo[k];
+                        modeval = k;
+                    }
+                }
+                if (pnamode)
+                    famode[i] = modeval;
+                if (pnamodecount)
+                    famodecount[i] = max;
+            }
+        }
+        LEPT_FREE(histo);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixColumnStats()
+ *
+ *      Input:  pixs (8 bpp; not cmapped)
+ *              box (<optional> clipping box; can be null)
+ *              &namean (<optional return> numa of mean values)
+ *              &namedian (<optional return> numa of median values)
+ *              &namode (<optional return> numa of mode intensity values)
+ *              &namodecount (<optional return> numa of mode counts)
+ *              &navar (<optional return> numa of variance)
+ *              &narootvar (<optional return> numa of square root of variance)
+ *      Return: na (numa of requested statistic for each column),
+ *                  or null on error
+ *
+ *  Notes:
+ *      (1) This computes numas that represent row vectors of statistics,
+ *          with each of its values derived from the corresponding col of a Pix.
+ *      (2) Use NULL on input to prevent computation of any of the 5 numas.
+ *      (3) Other functions that compute pixel column statistics are:
+ *             pixCountPixelsByColumn()
+ *             pixAverageByColumn()
+ *             pixVarianceByColumn()
+ *             pixGetColumnStats()
+ */
+l_int32
+pixColumnStats(PIX    *pixs,
+               BOX    *box,
+               NUMA  **pnamean,
+               NUMA  **pnamedian,
+               NUMA  **pnamode,
+               NUMA  **pnamodecount,
+               NUMA  **pnavar,
+               NUMA  **pnarootvar)
+{
+l_int32     i, j, k, w, h, val, wpls, sum, sumsq, target, max, modeval;
+l_int32     xstart, xend, ystart, yend, bw, bh;
+l_int32    *histo;
+l_uint32   *lines, *datas;
+l_float32   norm;
+l_float32  *famean, *fameansq, *favar, *farootvar;
+l_float32  *famedian, *famode, *famodecount;
+
+    PROCNAME("pixColumnStats");
+
+    if (pnamean) *pnamean = NULL;
+    if (pnamedian) *pnamedian = NULL;
+    if (pnamode) *pnamode = NULL;
+    if (pnamodecount) *pnamodecount = NULL;
+    if (pnavar) *pnavar = NULL;
+    if (pnarootvar) *pnarootvar = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs undefined or not 8 bpp", procName, 1);
+    famean = fameansq = favar = farootvar = NULL;
+    famedian = famode = famodecount = NULL;
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (boxClipToRectangleParams(box, w, h, &xstart, &ystart, &xend, &yend,
+                                 &bw, &bh) == 1)
+        return ERROR_INT("invalid clipping box", procName, 1);
+
+        /* We need the mean for variance and root variance */
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if (pnamean || pnavar || pnarootvar) {
+        norm = 1. / (l_float32)bh;
+        famean = (l_float32 *)LEPT_CALLOC(bw, sizeof(l_float32));
+        fameansq = (l_float32 *)LEPT_CALLOC(bw, sizeof(l_float32));
+        if (pnavar || pnarootvar) {
+            favar = (l_float32 *)LEPT_CALLOC(bw, sizeof(l_float32));
+            if (pnarootvar)
+                farootvar = (l_float32 *)LEPT_CALLOC(bw, sizeof(l_float32));
+        }
+        for (j = xstart; j < xend; j++) {
+            sum = sumsq = 0;
+            for (i = ystart, lines = datas; i < yend; lines += wpls, i++) {
+                val = GET_DATA_BYTE(lines, j);
+                sum += val;
+                sumsq += val * val;
+            }
+            famean[j] = norm * sum;
+            fameansq[j] = norm * sumsq;
+            if (pnavar || pnarootvar) {
+                favar[j] = fameansq[j] - famean[j] * famean[j];
+                if (pnarootvar)
+                    farootvar[j] = sqrtf(favar[j]);
+            }
+        }
+        LEPT_FREE(fameansq);
+        if (pnamean)
+            *pnamean = numaCreateFromFArray(famean, bw, L_INSERT);
+        else
+            LEPT_FREE(famean);
+        if (pnavar)
+            *pnavar = numaCreateFromFArray(favar, bw, L_INSERT);
+        else
+            LEPT_FREE(favar);
+        if (pnarootvar)
+            *pnarootvar = numaCreateFromFArray(farootvar, bw, L_INSERT);
+    }
+
+        /* We need a histogram to find the median and/or mode values */
+    if (pnamedian || pnamode || pnamodecount) {
+        histo = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+        if (pnamedian) {
+            *pnamedian = numaMakeConstant(0, bw);
+            famedian = numaGetFArray(*pnamedian, L_NOCOPY);
+        }
+        if (pnamode) {
+            *pnamode = numaMakeConstant(0, bw);
+            famode = numaGetFArray(*pnamode, L_NOCOPY);
+        }
+        if (pnamodecount) {
+            *pnamodecount = numaMakeConstant(0, bw);
+            famodecount = numaGetFArray(*pnamodecount, L_NOCOPY);
+        }
+        for (j = xstart; j < xend; j++) {
+            memset(histo, 0, 1024);
+            for (i = ystart, lines = datas; i < yend; lines += wpls, i++) {
+                val = GET_DATA_BYTE(lines, j);
+                histo[val]++;
+            }
+
+            if (pnamedian) {
+                sum = 0;
+                target = (bh + 1) / 2;
+                for (k = 0; k < 256; k++) {
+                    sum += histo[k];
+                    if (sum >= target) {
+                        famedian[j] = k;
+                        break;
+                    }
+                }
+            }
+
+            if (pnamode || pnamodecount) {
+                max = 0;
+                modeval = 0;
+                for (k = 0; k < 256; k++) {
+                    if (histo[k] > max) {
+                        max = histo[k];
+                        modeval = k;
+                    }
+                }
+                if (pnamode)
+                    famode[j] = modeval;
+                if (pnamodecount)
+                    famodecount[j] = max;
+            }
+        }
+        LEPT_FREE(histo);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixGetComponentRange()
+ *
+ *      Input:  pixs (8 bpp grayscale, 32 bpp rgb, or colormapped)
+ *              factor (subsampling factor; >= 1; ignored if colormapped)
+ *              color (L_SELECT_RED, L_SELECT_GREEN or L_SELECT_BLUE)
+ *              &minval (<optional return> minimum value of component)
+ *              &maxval (<optional return> maximum value of component)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If pixs is 8 bpp grayscale, the color selection type is ignored.
+ */
+l_int32
+pixGetComponentRange(PIX      *pixs,
+                     l_int32   factor,
+                     l_int32   color,
+                     l_int32  *pminval,
+                     l_int32  *pmaxval)
+{
+l_int32   d;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixGetComponentRange");
+
+    if (pminval) *pminval = 0;
+    if (pmaxval) *pmaxval = 0;
+    if (!pminval && !pmaxval)
+        return ERROR_INT("no result requested", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    cmap = pixGetColormap(pixs);
+    if (cmap)
+        return pixcmapGetComponentRange(cmap, color, pminval, pmaxval);
+
+    if (factor < 1)
+        return ERROR_INT("sampling factor must be >= 1", procName, 1);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return ERROR_INT("pixs not 8 or 32 bpp", procName, 1);
+
+    if (d == 8) {
+        pixGetExtremeValue(pixs, factor, L_SELECT_MIN,
+                           NULL, NULL, NULL, pminval);
+        pixGetExtremeValue(pixs, factor, L_SELECT_MAX,
+                           NULL, NULL, NULL, pmaxval);
+    } else if (color == L_SELECT_RED) {
+        pixGetExtremeValue(pixs, factor, L_SELECT_MIN,
+                           pminval, NULL, NULL, NULL);
+        pixGetExtremeValue(pixs, factor, L_SELECT_MAX,
+                           pmaxval, NULL, NULL, NULL);
+    } else if (color == L_SELECT_GREEN) {
+        pixGetExtremeValue(pixs, factor, L_SELECT_MIN,
+                           NULL, pminval, NULL, NULL);
+        pixGetExtremeValue(pixs, factor, L_SELECT_MAX,
+                           NULL, pmaxval, NULL, NULL);
+    } else if (color == L_SELECT_BLUE) {
+        pixGetExtremeValue(pixs, factor, L_SELECT_MIN,
+                           NULL, NULL, pminval, NULL);
+        pixGetExtremeValue(pixs, factor, L_SELECT_MAX,
+                           NULL, NULL, pmaxval, NULL);
+    } else {
+        return ERROR_INT("invalid color", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixGetExtremeValue()
+ *
+ *      Input:  pixs (8 bpp grayscale, 32 bpp rgb, or colormapped)
+ *              factor (subsampling factor; >= 1; ignored if colormapped)
+ *              type (L_SELECT_MIN or L_SELECT_MAX)
+ *              &rval (<optional return> red component)
+ *              &gval (<optional return> green component)
+ *              &bval (<optional return> blue component)
+ *              &grayval (<optional return> min or max gray value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If pixs is grayscale, the result is returned in &grayval.
+ *          Otherwise, if there is a colormap or d == 32,
+ *          each requested color component is returned.  At least
+ *          one color component (address) must be input.
+ */
+l_int32
+pixGetExtremeValue(PIX      *pixs,
+                   l_int32   factor,
+                   l_int32   type,
+                   l_int32  *prval,
+                   l_int32  *pgval,
+                   l_int32  *pbval,
+                   l_int32  *pgrayval)
+{
+l_int32    i, j, w, h, d, wpl;
+l_int32    val, extval, rval, gval, bval, extrval, extgval, extbval;
+l_uint32   pixel;
+l_uint32  *data, *line;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixGetExtremeValue");
+
+    if (prval) *prval = 0;
+    if (pgval) *pgval = 0;
+    if (pbval) *pbval = 0;
+    if (pgrayval) *pgrayval = 0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    cmap = pixGetColormap(pixs);
+    if (cmap)
+        return pixcmapGetExtremeValue(cmap, type, prval, pgval, pbval);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (type != L_SELECT_MIN && type != L_SELECT_MAX)
+        return ERROR_INT("invalid type", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("sampling factor must be >= 1", procName, 1);
+    if (d != 8 && d != 32)
+        return ERROR_INT("pixs not 8 or 32 bpp", procName, 1);
+    if (d == 8 && !pgrayval)
+        return ERROR_INT("can't return result in grayval", procName, 1);
+    if (d == 32 && !prval && !pgval && !pbval)
+        return ERROR_INT("can't return result in r/g/b-val", procName, 1);
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    if (d == 8) {
+        if (type == L_SELECT_MIN)
+            extval = 100000;
+        else  /* get max */
+            extval = 0;
+
+        for (i = 0; i < h; i += factor) {
+            line = data + i * wpl;
+            for (j = 0; j < w; j += factor) {
+                val = GET_DATA_BYTE(line, j);
+                if ((type == L_SELECT_MIN && val < extval) ||
+                    (type == L_SELECT_MAX && val > extval))
+                    extval = val;
+            }
+        }
+        *pgrayval = extval;
+        return 0;
+    }
+
+        /* 32 bpp rgb */
+    if (type == L_SELECT_MIN) {
+        extrval = 100000;
+        extgval = 100000;
+        extbval = 100000;
+    } else {
+        extrval = 0;
+        extgval = 0;
+        extbval = 0;
+    }
+    for (i = 0; i < h; i += factor) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j += factor) {
+            pixel = line[j];
+            if (prval) {
+                rval = (pixel >> L_RED_SHIFT) & 0xff;
+                if ((type == L_SELECT_MIN && rval < extrval) ||
+                    (type == L_SELECT_MAX && rval > extrval))
+                    extrval = rval;
+            }
+            if (pgval) {
+                gval = (pixel >> L_GREEN_SHIFT) & 0xff;
+                if ((type == L_SELECT_MIN && gval < extgval) ||
+                    (type == L_SELECT_MAX && gval > extgval))
+                    extgval = gval;
+            }
+            if (pbval) {
+                bval = (pixel >> L_BLUE_SHIFT) & 0xff;
+                if ((type == L_SELECT_MIN && bval < extbval) ||
+                    (type == L_SELECT_MAX && bval > extbval))
+                    extbval = bval;
+            }
+        }
+    }
+    if (prval) *prval = extrval;
+    if (pgval) *pgval = extgval;
+    if (pbval) *pbval = extbval;
+    return 0;
+}
+
+
+/*!
+ *  pixGetMaxValueInRect()
+ *
+ *      Input:  pixs (8, 16 or 32 bpp grayscale; no color space components)
+ *              box (<optional> region; set box = NULL to use entire pixs)
+ *              &maxval (<optional return> max value in region)
+ *              &xmax (<optional return> x location of max value)
+ *              &ymax (<optional return> y location of max value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This can be used to find the maximum and its location
+ *          in a 2-dimensional histogram, where the x and y directions
+ *          represent two color components (e.g., saturation and hue).
+ *      (2) Note that here a 32 bpp pixs has pixel values that are simply
+ *          numbers.  They are not 8 bpp components in a colorspace.
+ */
+l_int32
+pixGetMaxValueInRect(PIX       *pixs,
+                     BOX       *box,
+                     l_uint32  *pmaxval,
+                     l_int32   *pxmax,
+                     l_int32   *pymax)
+{
+l_int32    i, j, w, h, d, wpl, bw, bh;
+l_int32    xstart, ystart, xend, yend, xmax, ymax;
+l_uint32   val, maxval;
+l_uint32  *data, *line;
+
+    PROCNAME("pixGetMaxValueInRect");
+
+    if (pmaxval) *pmaxval = 0;
+    if (pxmax) *pxmax = 0;
+    if (pymax) *pymax = 0;
+    if (!pmaxval && !pxmax && !pymax)
+        return ERROR_INT("no data requested", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetColormap(pixs) != NULL)
+        return ERROR_INT("pixs has colormap", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 16 && d != 32)
+        return ERROR_INT("pixs not 8, 16 or 32 bpp", procName, 1);
+
+    xstart = ystart = 0;
+    xend = w - 1;
+    yend = h - 1;
+    if (box) {
+        boxGetGeometry(box, &xstart, &ystart, &bw, &bh);
+        xend = xstart + bw - 1;
+        yend = ystart + bh - 1;
+    }
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    maxval = 0;
+    xmax = ymax = 0;
+    for (i = ystart; i <= yend; i++) {
+        line = data + i * wpl;
+        for (j = xstart; j <= xend; j++) {
+            if (d == 8)
+                val = GET_DATA_BYTE(line, j);
+            else if (d == 16)
+                val = GET_DATA_TWO_BYTES(line, j);
+            else  /* d == 32 */
+                val = line[j];
+            if (val > maxval) {
+                maxval = val;
+                xmax = j;
+                ymax = i;
+            }
+        }
+    }
+    if (maxval == 0) {  /* no counts; pick the center of the rectangle */
+        xmax = (xstart + xend) / 2;
+        ymax = (ystart + yend) / 2;
+    }
+
+    if (pmaxval) *pmaxval = maxval;
+    if (pxmax) *pxmax = xmax;
+    if (pymax) *pymax = ymax;
+    return 0;
+}
+
+
+/*!
+ *  pixGetBinnedComponentRange()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              nbins (number of equal population bins; must be > 1)
+ *              factor (subsampling factor; >= 1)
+ *              color (L_SELECT_RED, L_SELECT_GREEN or L_SELECT_BLUE)
+ *              &minval (<optional return> minimum value of component)
+ *              &maxval (<optional return> maximum value of component)
+ *              &carray (<optional return> color array of bins)
+ *              fontsize (<optional> 0 for no debug; for debug, valid set
+ *                        is {4,6,8,10,12,14,16,18,20}.)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This returns the min and max average values of the
+ *          selected color component in the set of rank bins,
+ *          where the ranking is done using the specified component.
+ */
+l_int32
+pixGetBinnedComponentRange(PIX        *pixs,
+                           l_int32     nbins,
+                           l_int32     factor,
+                           l_int32     color,
+                           l_int32    *pminval,
+                           l_int32    *pmaxval,
+                           l_uint32  **pcarray,
+                           l_int32     fontsize)
+{
+l_int32    i, minval, maxval, rval, gval, bval;
+l_uint32  *carray;
+PIX       *pixt;
+
+    PROCNAME("pixGetBinnedComponentRange");
+
+    if (pminval) *pminval = 0;
+    if (pmaxval) *pmaxval = 0;
+    if (pcarray) *pcarray = NULL;
+    if (!pminval && !pmaxval)
+        return ERROR_INT("no result requested", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not defined or not 32 bpp", procName, 1);
+    if (factor < 1)
+        return ERROR_INT("sampling factor must be >= 1", procName, 1);
+    if (color != L_SELECT_RED && color != L_SELECT_GREEN &&
+        color != L_SELECT_BLUE)
+        return ERROR_INT("invalid color", procName, 1);
+    if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2)
+        return ERROR_INT("invalid fontsize", procName, 1);
+
+    pixGetRankColorArray(pixs, nbins, color, factor, &carray, 0, 0);
+    if (fontsize > 0) {
+        for (i = 0; i < nbins; i++)
+            L_INFO("c[%d] = %x\n", procName, i, carray[i]);
+        pixt = pixDisplayColorArray(carray, nbins, 200, 5, fontsize);
+        pixDisplay(pixt, 100, 100);
+        pixDestroy(&pixt);
+    }
+
+    extractRGBValues(carray[0], &rval, &gval, &bval);
+    minval = rval;
+    if (color == L_SELECT_GREEN)
+        minval = gval;
+    else if (color == L_SELECT_BLUE)
+        minval = bval;
+    extractRGBValues(carray[nbins - 1], &rval, &gval, &bval);
+    maxval = rval;
+    if (color == L_SELECT_GREEN)
+        maxval = gval;
+    else if (color == L_SELECT_BLUE)
+        maxval = bval;
+
+    if (pminval) *pminval = minval;
+    if (pmaxval) *pmaxval = maxval;
+    if (pcarray)
+        *pcarray = carray;
+    else
+        LEPT_FREE(carray);
+    return 0;
+}
+
+
+/*!
+ *  pixGetRankColorArray()
+ *
+ *      Input:  pixs (32 bpp or cmapped)
+ *              nbins (number of equal population bins; must be > 1)
+ *              type (color selection flag)
+ *              factor (subsampling factor; integer >= 1)
+ *              &carray (<return> array of colors, ranked by intensity)
+ *              debugflag (1 to display color squares and plots of color
+ *                         components; 2 to write them as png to file)
+ *              fontsize (<optional> 0 for no debug; for debug, valid set
+ *                        is {4,6,8,10,12,14,16,18,20}.  Ignored if
+ *                        debugflag == 0.  fontsize == 6 is typical.)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The color selection flag is one of: L_SELECT_RED, L_SELECT_GREEN,
+ *          L_SELECT_BLUE, L_SELECT_MIN, L_SELECT_MAX, L_SELECT_AVERAGE.
+ *      (2) Then it finds the histogram of the selected component in each
+ *          RGB pixel.  For each of the @nbins sets of pixels,
+ *          ordered by this component value, find the average color,
+ *          and return this as a "rank color" array.  The output array
+ *          has @nbins colors.
+ *      (3) Set the subsampling factor > 1 to reduce the amount of
+ *          computation.  Typically you want at least 10,000 pixels
+ *          for reasonable statistics.
+ *      (4) The rank color as a function of rank can then be found from
+ *             rankint = (l_int32)(rank * (nbins - 1) + 0.5);
+ *             extractRGBValues(array[rankint], &rval, &gval, &bval);
+ *          where the rank is in [0.0 ... 1.0].
+ *          This function is meant to be simple and approximate.
+ *      (5) Compare this with pixGetBinnedColor(), which generates equal
+ *          width intensity bins and finds the average color in each bin.
+ */
+l_int32
+pixGetRankColorArray(PIX        *pixs,
+                     l_int32     nbins,
+                     l_int32     type,
+                     l_int32     factor,
+                     l_uint32  **pcarray,
+                     l_int32     debugflag,
+                     l_int32     fontsize)
+{
+l_int32    ret;
+l_uint32  *array;
+NUMA      *na, *nan, *narbin;
+PIX       *pixt, *pixc, *pixg, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixGetRankColorArray");
+
+    if (!pcarray)
+        return ERROR_INT("&carray not defined", procName, 1);
+    *pcarray = NULL;
+    if (factor < 1)
+        return ERROR_INT("sampling factor must be >= 1", procName, 1);
+    if (nbins < 2)
+        return ERROR_INT("nbins must be at least 2", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    cmap = pixGetColormap(pixs);
+    if (pixGetDepth(pixs) != 32 && !cmap)
+        return ERROR_INT("pixs neither 32 bpp nor cmapped", procName, 1);
+    if (type != L_SELECT_RED && type != L_SELECT_GREEN &&
+        type != L_SELECT_BLUE && type != L_SELECT_MIN &&
+        type != L_SELECT_MAX && type != L_SELECT_AVERAGE)
+        return ERROR_INT("invalid type", procName, 1);
+    if (debugflag > 0) {
+        if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2)
+            return ERROR_INT("invalid fontsize", procName, 1);
+    }
+
+        /* Downscale by factor and remove colormap if it exists */
+    pixt = pixScaleByIntSampling(pixs, factor);
+    if (cmap)
+        pixc = pixRemoveColormap(pixt, REMOVE_CMAP_TO_FULL_COLOR);
+    else
+        pixc = pixClone(pixt);
+    pixDestroy(&pixt);
+
+        /* Get normalized histogram of the selected component */
+    if (type == L_SELECT_RED)
+        pixg = pixGetRGBComponent(pixc, COLOR_RED);
+    else if (type == L_SELECT_GREEN)
+        pixg = pixGetRGBComponent(pixc, COLOR_GREEN);
+    else if (type == L_SELECT_BLUE)
+        pixg = pixGetRGBComponent(pixc, COLOR_BLUE);
+    else if (type == L_SELECT_MIN)
+        pixg = pixConvertRGBToGrayMinMax(pixc, L_CHOOSE_MIN);
+    else if (type == L_SELECT_MAX)
+        pixg = pixConvertRGBToGrayMinMax(pixc, L_CHOOSE_MAX);
+    else  /* L_SELECT_AVERAGE */
+        pixg = pixConvertRGBToGray(pixc, 0.34, 0.33, 0.33);
+    if ((na = pixGetGrayHistogram(pixg, 1)) == NULL) {
+        pixDestroy(&pixc);
+        pixDestroy(&pixg);
+        return ERROR_INT("na not made", procName, 1);
+    }
+    nan = numaNormalizeHistogram(na, 1.0);
+
+        /* Get the following arrays:
+         * (1) nar: cumulative normalized histogram (rank vs intensity value).
+         *     With 256 intensity values, we have 257 rank values.
+         * (2) nai: "average" intensity as function of rank bin, for
+         *     @nbins equally spaced in rank between 0.0 and 1.0.
+         * (3) narbin: bin number of discretized rank as a function of
+         *     intensity.  This is the 'inverse' of nai.
+         * (4) nabb: intensity value of the right bin boundary, for each
+         *     of the @nbins discretized rank bins. */
+    if (!debugflag) {
+        numaDiscretizeRankAndIntensity(nan, nbins, &narbin, NULL, NULL, NULL);
+    } else {
+        l_int32  type;
+        NUMA    *nai, *nar, *nabb;
+        numaDiscretizeRankAndIntensity(nan, nbins, &narbin, &nai, &nar, &nabb);
+        type = (debugflag == 1) ? GPLOT_X11 : GPLOT_PNG;
+        lept_mkdir("lept/regout");
+        gplotSimple1(nan, type, "/tmp/lept/regout/rtnan",
+                     "Normalized Histogram");
+        gplotSimple1(nar, type, "/tmp/lept/regout/rtnar",
+                     "Cumulative Histogram");
+        gplotSimple1(nai, type, "/tmp/lept/regout/rtnai",
+                     "Intensity vs. rank bin");
+        gplotSimple1(narbin, type, "/tmp/lept/regout/rtnarbin",
+                     "LUT: rank bin vs. Intensity");
+        gplotSimple1(nabb, type, "/tmp/lept/regout/rtnabb",
+                     "Intensity of right edge vs. rank bin");
+        numaDestroy(&nai);
+        numaDestroy(&nar);
+        numaDestroy(&nabb);
+    }
+
+        /* Get the average color in each bin for pixels whose grayscale
+         * values fall in the bin range.  @narbin is the LUT that
+         * determines the bin number from the grayscale version of
+         * the image.  Because this mapping may not be unique,
+         * some bins may not be represented in the LUT. In use, to get fair
+         * allocation into all the bins, bin population is monitored
+         * as pixels are accumulated, and when bins fill up,
+         * pixels are required to overflow into succeeding bins. */
+    pixGetBinnedColor(pixc, pixg, 1, nbins, narbin, pcarray, debugflag);
+    ret = 0;
+    if ((array = *pcarray) == NULL) {
+        L_ERROR("color array not returned\n", procName);
+        ret = 1;
+        debugflag = 0;  /* make sure to skip the following */
+    }
+    if (debugflag) {
+        pixd = pixDisplayColorArray(array, nbins, 200, 5, fontsize);
+        if (debugflag == 1)
+            pixDisplayWithTitle(pixd, 0, 500, "binned colors", 1);
+        else  /* debugflag == 2 */
+            pixWrite("/tmp/lept/regout/rankhisto.png", pixd, IFF_PNG);
+        pixDestroy(&pixd);
+    }
+
+    pixDestroy(&pixc);
+    pixDestroy(&pixg);
+    numaDestroy(&na);
+    numaDestroy(&nan);
+    numaDestroy(&narbin);
+    return ret;
+}
+
+
+/*!
+ *  pixGetBinnedColor()
+ *
+ *      Input:  pixs (32 bpp)
+ *              pixg (8 bpp grayscale version of pixs)
+ *              factor (sampling factor along pixel counting direction)
+ *              nbins (number of intensity bins)
+ *              nalut (LUT for mapping from intensity to bin number)
+ *              &carray (<return> array of average color values in each bin)
+ *              debugflag (1 to display output debug plots of color
+ *                         components; 2 to write them as png to file)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This takes a color image, a grayscale (intensity) version,
+ *          a LUT from intensity to bin number, and the number of bins.
+ *          It computes the average color for pixels whose intensity
+ *          is in each bin.  This is returned as an array of l_uint32
+ *          colors in our standard RGBA ordering.
+ *      (2) This function generates equal width intensity bins and
+ *          finds the average color in each bin.  Compare this with
+ *          pixGetRankColorArray(), which rank orders the pixels
+ *          by the value of the selected component in each pixel,
+ *          sets up bins with equal population (not intensity width!),
+ *          and gets the average color in each bin.
+ */
+l_int32
+pixGetBinnedColor(PIX        *pixs,
+                  PIX        *pixg,
+                  l_int32     factor,
+                  l_int32     nbins,
+                  NUMA       *nalut,
+                  l_uint32  **pcarray,
+                  l_int32     debugflag)
+{
+l_int32     i, j, w, h, wpls, wplg, grayval, bin, rval, gval, bval;
+l_int32     npts, avepts, maxpts;
+l_uint32   *datas, *datag, *lines, *lineg, *carray;
+l_float64   norm;
+l_float64  *rarray, *garray, *barray, *narray;
+
+    PROCNAME("pixGetBinnedColor");
+
+    if (!pcarray)
+        return ERROR_INT("&carray not defined", procName, 1);
+    *pcarray = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixg)
+        return ERROR_INT("pixg not defined", procName, 1);
+    if (!nalut)
+        return ERROR_INT("nalut not defined", procName, 1);
+    if (factor < 1) {
+        L_WARNING("sampling factor less than 1; setting to 1\n", procName);
+        factor = 1;
+    }
+
+        /* Find the color for each rank bin.  Note that we can have
+         * multiple bins filled with pixels having the same gray value.
+         * Therefore, because in general the mapping from gray value
+         * to bin number is not unique, if a bin fills up (actually,
+         * we allow it to slightly overfill), we roll the excess
+         * over to the next bin, etc. */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    npts = (w + factor - 1) * (h + factor - 1) / (factor * factor);
+    avepts = (npts + nbins - 1) / nbins;  /* average number of pts in a bin */
+    maxpts = (l_int32)((1.0 + 0.5 / (l_float32)nbins) * avepts);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datag = pixGetData(pixg);
+    wplg = pixGetWpl(pixg);
+    rarray = (l_float64 *)LEPT_CALLOC(nbins, sizeof(l_float64));
+    garray = (l_float64 *)LEPT_CALLOC(nbins, sizeof(l_float64));
+    barray = (l_float64 *)LEPT_CALLOC(nbins, sizeof(l_float64));
+    narray = (l_float64 *)LEPT_CALLOC(nbins, sizeof(l_float64));
+    for (i = 0; i < h; i += factor) {
+        lines = datas + i * wpls;
+        lineg = datag + i * wplg;
+        for (j = 0; j < w; j += factor) {
+            grayval = GET_DATA_BYTE(lineg, j);
+            numaGetIValue(nalut, grayval, &bin);
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            while (narray[bin] >= maxpts && bin < nbins - 1)
+                bin++;
+            rarray[bin] += rval;
+            garray[bin] += gval;
+            barray[bin] += bval;
+            narray[bin] += 1.0;  /* count samples in each bin */
+        }
+    }
+
+    for (i = 0; i < nbins; i++) {
+        norm = 1. / narray[i];
+        rarray[i] *= norm;
+        garray[i] *= norm;
+        barray[i] *= norm;
+/*        fprintf(stderr, "narray[%d] = %f\n", i, narray[i]);  */
+    }
+
+    if (debugflag) {
+        l_int32  type;
+        NUMA *nared, *nagreen, *nablue;
+        nared = numaCreate(nbins);
+        nagreen = numaCreate(nbins);
+        nablue = numaCreate(nbins);
+        for (i = 0; i < nbins; i++) {
+            numaAddNumber(nared, rarray[i]);
+            numaAddNumber(nagreen, garray[i]);
+            numaAddNumber(nablue, barray[i]);
+        }
+        type = (debugflag == 1) ? GPLOT_X11 : GPLOT_PNG;
+        lept_mkdir("lept/regout");
+        gplotSimple1(nared, type, "/tmp/lept/regout/rtnared",
+                     "Average red val vs. rank bin");
+        gplotSimple1(nagreen, type, "/tmp/lept/regout/rtnagreen",
+                     "Average green val vs. rank bin");
+        gplotSimple1(nablue, type, "/tmp/lept/regout/rtnablue",
+                     "Average blue val vs. rank bin");
+        numaDestroy(&nared);
+        numaDestroy(&nagreen);
+        numaDestroy(&nablue);
+    }
+
+        /* Save colors for all bins  in a single array */
+    if ((carray = (l_uint32 *)LEPT_CALLOC(nbins, sizeof(l_uint32))) == NULL)
+        return ERROR_INT("rankcolor not made", procName, 1);
+    *pcarray = carray;
+    for (i = 0; i < nbins; i++) {
+        rval = (l_int32)(rarray[i] + 0.5);
+        gval = (l_int32)(garray[i] + 0.5);
+        bval = (l_int32)(barray[i] + 0.5);
+        composeRGBPixel(rval, gval, bval, carray + i);
+    }
+
+    LEPT_FREE(rarray);
+    LEPT_FREE(garray);
+    LEPT_FREE(barray);
+    LEPT_FREE(narray);
+    return 0;
+}
+
+
+/*!
+ *  pixDisplayColorArray()
+ *
+ *      Input:  carray (array of colors: 0xrrggbb00)
+ *              ncolors (size of array)
+ *              side (size of each color square; suggest 200)
+ *              ncols (number of columns in output color matrix)
+ *              fontsize (to label each square with text.  Valid set is
+ *                        {4,6,8,10,12,14,16,18,20}.  Use 0 to disable.)
+ *      Return: pixd (color array), or null on error
+ */
+PIX *
+pixDisplayColorArray(l_uint32  *carray,
+                     l_int32    ncolors,
+                     l_int32    side,
+                     l_int32    ncols,
+                     l_int32    fontsize)
+{
+char     textstr[256];
+l_int32  i, rval, gval, bval;
+L_BMF   *bmf;
+PIX     *pixt, *pixd;
+PIXA    *pixa;
+
+    PROCNAME("pixDisplayColorArray");
+
+    if (!carray)
+        return (PIX *)ERROR_PTR("carray not defined", procName, NULL);
+    if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2)
+        return (PIX *)ERROR_PTR("invalid fontsize", procName, NULL);
+
+    bmf = (fontsize == 0) ? NULL : bmfCreate(NULL, fontsize);
+    pixa = pixaCreate(ncolors);
+    for (i = 0; i < ncolors; i++) {
+        pixt = pixCreate(side, side, 32);
+        pixSetAllArbitrary(pixt, carray[i]);
+        if (bmf) {
+            extractRGBValues(carray[i], &rval, &gval, &bval);
+            snprintf(textstr, sizeof(textstr),
+                     "%d: (%d %d %d)", i, rval, gval, bval);
+            pixSaveTiledWithText(pixt, pixa, side, (i % ncols == 0) ? 1 : 0,
+                                 20, 2, bmf, textstr, 0xff000000, L_ADD_BELOW);
+        } else {
+            pixSaveTiled(pixt, pixa, 1.0, (i % ncols == 0) ? 1 : 0, 20, 32);
+        }
+        pixDestroy(&pixt);
+    }
+    pixd = pixaDisplay(pixa, 0, 0);
+
+    pixaDestroy(&pixa);
+    bmfDestroy(&bmf);
+    return pixd;
+}
+
+
+/*!
+ *  pixRankBinByStrip()
+ *
+ *      Input:  pixs (32 bpp or cmapped)
+ *              direction (L_SCAN_HORIZONTAL or L_SCAN_VERTICAL)
+ *              size (of strips in scan direction)
+ *              nbins (number of equal population bins; must be > 1)
+ *              type (color selection flag)
+ *      Return: pixd (result), or null on error
+ *
+ *  Notes:
+ *      (1) This generates a pix where each column represents a strip of
+ *          the input image.  If @direction == L_SCAN_HORIZONTAL, the
+ *          input impage is tiled into vertical strips of width @size,
+ *          where @size is a compromise between getting better spatial
+ *          columnwise resolution (small @size) and getting better
+ *          columnwise statistical information (larger @size).  Likewise
+ *          with rows of the image if @direction == L_SCAN_VERTICAL.
+ *      (2) For L_HORIZONTAL_SCAN, the output pix contains rank binned
+ *          median colors in each column that correspond to a vertical
+ *          strip of width @size in the input image.
+ *      (3) The color selection flag is one of: L_SELECT_RED, L_SELECT_GREEN,
+ *          L_SELECT_BLUE, L_SELECT_MIN, L_SELECT_MAX, L_SELECT_AVERAGE.
+ *          It determines how the rank ordering is done.
+ *      (4) Typical input values might be @size = 5, @nbins = 10.
+ */
+PIX *
+pixRankBinByStrip(PIX     *pixs,
+                  l_int32  direction,
+                  l_int32  size,
+                  l_int32  nbins,
+                  l_int32  type)
+{
+l_int32    i, j, w, h, nstrips;
+l_uint32  *array;
+BOXA      *boxa;
+PIX       *pix1, *pix2, *pixd;
+PIXA      *pixa;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixRankBinByStrip");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    cmap = pixGetColormap(pixs);
+    if (pixGetDepth(pixs) != 32 && !cmap)
+        return (PIX *)ERROR_PTR("pixs neither 32 bpp nor cmapped",
+                                procName, NULL);
+    if (direction != L_SCAN_HORIZONTAL && direction != L_SCAN_VERTICAL)
+        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
+    if (size < 1)
+        return (PIX *)ERROR_PTR("size < 1", procName, NULL);
+    if (nbins < 2)
+        return (PIX *)ERROR_PTR("nbins must be at least 2", procName, NULL);
+    if (type != L_SELECT_RED && type != L_SELECT_GREEN &&
+        type != L_SELECT_BLUE && type != L_SELECT_MIN &&
+        type != L_SELECT_MAX && type != L_SELECT_AVERAGE)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+        /* Downscale by factor and remove colormap if it exists */
+    if (cmap)
+        pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+    else
+        pix1 = pixClone(pixs);
+    pixGetDimensions(pixs, &w, &h, NULL);
+
+    pixd = NULL;
+    boxa = makeMosaicStrips(w, h, direction, size);
+    pixa = pixClipRectangles(pix1, boxa);
+    nstrips = pixaGetCount(pixa);
+    if (direction == L_SCAN_HORIZONTAL) {
+        pixd = pixCreate(nstrips, nbins, 32);
+        for (i = 0; i < nstrips; i++) {
+            pix2 = pixaGetPix(pixa, i, L_CLONE);
+            pixGetRankColorArray(pix2, nbins, type, 1, &array, 0, 0);
+            for (j = 0; j < nbins; j++)
+                pixSetPixel(pixd, i, j, array[j]);
+            LEPT_FREE(array);
+            pixDestroy(&pix2);
+        }
+    } else {  /* L_SCAN_VERTICAL */
+        pixd = pixCreate(nbins, nstrips, 32);
+        for (i = 0; i < nstrips; i++) {
+            pix2 = pixaGetPix(pixa, i, L_CLONE);
+            pixGetRankColorArray(pix2, nbins, type, 1, &array, 0, 0);
+            for (j = 0; j < nbins; j++)
+                pixSetPixel(pixd, j, i, array[j]);
+            LEPT_FREE(array);
+            pixDestroy(&pix2);
+        }
+    }
+    pixDestroy(&pix1);
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixa);
+    return pixd;
+}
+
+
+
+/*-------------------------------------------------------------*
+ *                 Pixelwise aligned statistics                *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixaGetAlignedStats()
+ *
+ *      Input:  pixa (of identically sized, 8 bpp pix; not cmapped)
+ *              type (L_MEAN_ABSVAL, L_MEDIAN_VAL, L_MODE_VAL, L_MODE_COUNT)
+ *              nbins (of histogram for median and mode; ignored for mean)
+ *              thresh (on histogram for mode val; ignored for all other types)
+ *      Return: pix (with pixelwise aligned stats), or null on error.
+ *
+ *  Notes:
+ *      (1) Each pixel in the returned pix represents an average
+ *          (or median, or mode) over the corresponding pixels in each
+ *          pix in the pixa.
+ *      (2) The @thresh parameter works with L_MODE_VAL only, and
+ *          sets a minimum occupancy of the mode bin.
+ *          If the occupancy of the mode bin is less than @thresh, the
+ *          mode value is returned as 0.  To always return the actual
+ *          mode value, set @thresh = 0.  See pixGetRowStats().
+ */
+PIX *
+pixaGetAlignedStats(PIXA     *pixa,
+                    l_int32   type,
+                    l_int32   nbins,
+                    l_int32   thresh)
+{
+l_int32     j, n, w, h, d;
+l_float32  *colvect;
+PIX        *pixt, *pixd;
+
+    PROCNAME("pixaGetAlignedStats");
+
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (type != L_MEAN_ABSVAL && type != L_MEDIAN_VAL &&
+        type != L_MODE_VAL && type != L_MODE_COUNT)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    n = pixaGetCount(pixa);
+    if (n == 0)
+        return (PIX *)ERROR_PTR("no pix in pixa", procName, NULL);
+    pixaGetPixDimensions(pixa, 0, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pix not 8 bpp", procName, NULL);
+
+    pixd = pixCreate(w, h, 8);
+    pixt = pixCreate(n, h, 8);
+    colvect = (l_float32 *)LEPT_CALLOC(h, sizeof(l_float32));
+    for (j = 0; j < w; j++) {
+        pixaExtractColumnFromEachPix(pixa, j, pixt);
+        pixGetRowStats(pixt, type, nbins, thresh, colvect);
+        pixSetPixelColumn(pixd, j, colvect);
+    }
+
+    LEPT_FREE(colvect);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixaExtractColumnFromEachPix()
+ *
+ *      Input:  pixa (of identically sized, 8 bpp; not cmapped)
+ *              col (column index)
+ *              pixd (pix into which each column is inserted)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixaExtractColumnFromEachPix(PIXA    *pixa,
+                             l_int32  col,
+                             PIX     *pixd)
+{
+l_int32    i, k, n, w, h, ht, val, wplt, wpld;
+l_uint32  *datad, *datat;
+PIX       *pixt;
+
+    PROCNAME("pixaExtractColumnFromEachPix");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (!pixd || pixGetDepth(pixd) != 8)
+        return ERROR_INT("pixd not defined or not 8 bpp", procName, 1);
+    n = pixaGetCount(pixa);
+    pixGetDimensions(pixd, &w, &h, NULL);
+    if (n != w)
+        return ERROR_INT("pix width != n", procName, 1);
+    pixt = pixaGetPix(pixa, 0, L_CLONE);
+    wplt = pixGetWpl(pixt);
+    pixGetDimensions(pixt, NULL, &ht, NULL);
+    pixDestroy(&pixt);
+    if (h != ht)
+        return ERROR_INT("pixd height != column height", procName, 1);
+
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (k = 0; k < n; k++) {
+        pixt = pixaGetPix(pixa, k, L_CLONE);
+        datat = pixGetData(pixt);
+        for (i = 0; i < h; i++) {
+            val = GET_DATA_BYTE(datat, col);
+            SET_DATA_BYTE(datad + i * wpld, k, val);
+            datat += wplt;
+        }
+        pixDestroy(&pixt);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixGetRowStats()
+ *
+ *      Input:  pixs (8 bpp; not cmapped)
+ *              type (L_MEAN_ABSVAL, L_MEDIAN_VAL, L_MODE_VAL, L_MODE_COUNT)
+ *              nbins (of histogram for median and mode; ignored for mean)
+ *              thresh (on histogram for mode; ignored for mean and median)
+ *              colvect (vector of results gathered across the rows of pixs)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This computes a column vector of statistics using each
+ *          row of a Pix.  The result is put in @colvect.
+ *      (2) The @thresh parameter works with L_MODE_VAL only, and
+ *          sets a minimum occupancy of the mode bin.
+ *          If the occupancy of the mode bin is less than @thresh, the
+ *          mode value is returned as 0.  To always return the actual
+ *          mode value, set @thresh = 0.
+ *      (3) What is the meaning of this @thresh parameter?
+ *          For each row, the total count in the histogram is w, the
+ *          image width.  So @thresh, relative to w, gives a measure
+ *          of the ratio of the bin width to the width of the distribution.
+ *          The larger @thresh, the narrower the distribution must be
+ *          for the mode value to be returned (instead of returning 0).
+ *      (4) If the Pix consists of a set of corresponding columns,
+ *          one for each Pix in a Pixa, the width of the Pix is the
+ *          number of Pix in the Pixa and the column vector can
+ *          be stored as a column in a Pix of the same size as
+ *          each Pix in the Pixa.
+ */
+l_int32
+pixGetRowStats(PIX        *pixs,
+               l_int32     type,
+               l_int32     nbins,
+               l_int32     thresh,
+               l_float32  *colvect)
+{
+l_int32    i, j, k, w, h, val, wpls, sum, target, max, modeval;
+l_int32   *histo, *gray2bin, *bin2gray;
+l_uint32  *lines, *datas;
+
+    PROCNAME("pixGetRowStats");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (!colvect)
+        return ERROR_INT("colvect not defined", procName, 1);
+    if (type != L_MEAN_ABSVAL && type != L_MEDIAN_VAL &&
+        type != L_MODE_VAL && type != L_MODE_COUNT)
+        return ERROR_INT("invalid type", procName, 1);
+    if (type != L_MEAN_ABSVAL && (nbins < 1 || nbins > 256))
+        return ERROR_INT("invalid nbins", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if (type == L_MEAN_ABSVAL) {
+        for (i = 0; i < h; i++) {
+            sum = 0;
+            lines = datas + i * wpls;
+            for (j = 0; j < w; j++)
+                sum += GET_DATA_BYTE(lines, j);
+            colvect[i] = (l_float32)sum / (l_float32)w;
+        }
+        return 0;
+    }
+
+        /* We need a histogram; binwidth ~ 256 / nbins */
+    histo = (l_int32 *)LEPT_CALLOC(nbins, sizeof(l_int32));
+    gray2bin = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    bin2gray = (l_int32 *)LEPT_CALLOC(nbins, sizeof(l_int32));
+    for (i = 0; i < 256; i++)  /* gray value --> histo bin */
+        gray2bin[i] = (i * nbins) / 256;
+    for (i = 0; i < nbins; i++)  /* histo bin --> gray value */
+        bin2gray[i] = (i * 256 + 128) / nbins;
+
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        for (k = 0; k < nbins; k++)
+            histo[k] = 0;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(lines, j);
+            histo[gray2bin[val]]++;
+        }
+
+        if (type == L_MEDIAN_VAL) {
+            sum = 0;
+            target = (w + 1) / 2;
+            for (k = 0; k < nbins; k++) {
+                sum += histo[k];
+                if (sum >= target) {
+                    colvect[i] = bin2gray[k];
+                    break;
+                }
+            }
+        } else if (type == L_MODE_VAL) {
+            max = 0;
+            modeval = 0;
+            for (k = 0; k < nbins; k++) {
+                if (histo[k] > max) {
+                    max = histo[k];
+                    modeval = k;
+                }
+            }
+            if (max < thresh)
+                colvect[i] = 0;
+            else
+                colvect[i] = bin2gray[modeval];
+        } else {  /* type == L_MODE_COUNT */
+            max = 0;
+            modeval = 0;
+            for (k = 0; k < nbins; k++) {
+                if (histo[k] > max) {
+                    max = histo[k];
+                    modeval = k;
+                }
+            }
+            colvect[i] = max;
+        }
+    }
+
+    LEPT_FREE(histo);
+    LEPT_FREE(gray2bin);
+    LEPT_FREE(bin2gray);
+    return 0;
+}
+
+
+/*!
+ *  pixGetColumnStats()
+ *
+ *      Input:  pixs (8 bpp; not cmapped)
+ *              type (L_MEAN_ABSVAL, L_MEDIAN_VAL, L_MODE_VAL, L_MODE_COUNT)
+ *              nbins (of histogram for median and mode; ignored for mean)
+ *              thresh (on histogram for mode val; ignored for all other types)
+ *              rowvect (vector of results gathered down the columns of pixs)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This computes a row vector of statistics using each
+ *          column of a Pix.  The result is put in @rowvect.
+ *      (2) The @thresh parameter works with L_MODE_VAL only, and
+ *          sets a minimum occupancy of the mode bin.
+ *          If the occupancy of the mode bin is less than @thresh, the
+ *          mode value is returned as 0.  To always return the actual
+ *          mode value, set @thresh = 0.
+ *      (3) What is the meaning of this @thresh parameter?
+ *          For each column, the total count in the histogram is h, the
+ *          image height.  So @thresh, relative to h, gives a measure
+ *          of the ratio of the bin width to the width of the distribution.
+ *          The larger @thresh, the narrower the distribution must be
+ *          for the mode value to be returned (instead of returning 0).
+ */
+l_int32
+pixGetColumnStats(PIX        *pixs,
+                  l_int32     type,
+                  l_int32     nbins,
+                  l_int32     thresh,
+                  l_float32  *rowvect)
+{
+l_int32    i, j, k, w, h, val, wpls, sum, target, max, modeval;
+l_int32   *histo, *gray2bin, *bin2gray;
+l_uint32  *datas;
+
+    PROCNAME("pixGetColumnStats");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (!rowvect)
+        return ERROR_INT("rowvect not defined", procName, 1);
+    if (type != L_MEAN_ABSVAL && type != L_MEDIAN_VAL &&
+        type != L_MODE_VAL && type != L_MODE_COUNT)
+        return ERROR_INT("invalid type", procName, 1);
+    if (type != L_MEAN_ABSVAL && (nbins < 1 || nbins > 256))
+        return ERROR_INT("invalid nbins", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if (type == L_MEAN_ABSVAL) {
+        for (j = 0; j < w; j++) {
+            sum = 0;
+            for (i = 0; i < h; i++)
+                sum += GET_DATA_BYTE(datas + i * wpls, j);
+            rowvect[j] = (l_float32)sum / (l_float32)h;
+        }
+        return 0;
+    }
+
+        /* We need a histogram; binwidth ~ 256 / nbins */
+    histo = (l_int32 *)LEPT_CALLOC(nbins, sizeof(l_int32));
+    gray2bin = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    bin2gray = (l_int32 *)LEPT_CALLOC(nbins, sizeof(l_int32));
+    for (i = 0; i < 256; i++)  /* gray value --> histo bin */
+        gray2bin[i] = (i * nbins) / 256;
+    for (i = 0; i < nbins; i++)  /* histo bin --> gray value */
+        bin2gray[i] = (i * 256 + 128) / nbins;
+
+    for (j = 0; j < w; j++) {
+        for (i = 0; i < h; i++) {
+            val = GET_DATA_BYTE(datas + i * wpls, j);
+            histo[gray2bin[val]]++;
+        }
+
+        if (type == L_MEDIAN_VAL) {
+            sum = 0;
+            target = (h + 1) / 2;
+            for (k = 0; k < nbins; k++) {
+                sum += histo[k];
+                if (sum >= target) {
+                    rowvect[j] = bin2gray[k];
+                    break;
+                }
+            }
+        } else if (type == L_MODE_VAL) {
+            max = 0;
+            modeval = 0;
+            for (k = 0; k < nbins; k++) {
+                if (histo[k] > max) {
+                    max = histo[k];
+                    modeval = k;
+                }
+            }
+            if (max < thresh)
+                rowvect[j] = 0;
+            else
+                rowvect[j] = bin2gray[modeval];
+        } else {  /* type == L_MODE_COUNT */
+            max = 0;
+            modeval = 0;
+            for (k = 0; k < nbins; k++) {
+                if (histo[k] > max) {
+                    max = histo[k];
+                    modeval = k;
+                }
+            }
+            rowvect[j] = max;
+        }
+        for (k = 0; k < nbins; k++)
+            histo[k] = 0;
+    }
+
+    LEPT_FREE(histo);
+    LEPT_FREE(gray2bin);
+    LEPT_FREE(bin2gray);
+    return 0;
+}
+
+
+/*!
+ *  pixSetPixelColumn()
+ *
+ *      Input:  pix (8 bpp; not cmapped)
+ *              col (column index)
+ *              colvect (vector of floats)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixSetPixelColumn(PIX        *pix,
+                  l_int32     col,
+                  l_float32  *colvect)
+{
+l_int32    i, w, h, wpl;
+l_uint32  *data;
+
+    PROCNAME("pixSetCPixelColumn");
+
+    if (!pix || pixGetDepth(pix) != 8)
+        return ERROR_INT("pix not defined or not 8 bpp", procName, 1);
+    if (!colvect)
+        return ERROR_INT("colvect not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, NULL);
+    if (col < 0 || col > w)
+        return ERROR_INT("invalid col", procName, 1);
+
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    for (i = 0; i < h; i++)
+        SET_DATA_BYTE(data + i * wpl, col, (l_int32)colvect[i]);
+
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *              Foreground/background estimation               *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixThresholdForFgBg()
+ *
+ *      Input:  pixs (any depth; cmapped ok)
+ *              factor (subsampling factor; integer >= 1)
+ *              thresh (threshold for generating foreground mask)
+ *              &fgval (<optional return> average foreground value)
+ *              &bgval (<optional return> average background value)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixThresholdForFgBg(PIX      *pixs,
+                    l_int32   factor,
+                    l_int32   thresh,
+                    l_int32  *pfgval,
+                    l_int32  *pbgval)
+{
+l_float32  fval;
+PIX       *pixg, *pixm;
+
+    PROCNAME("pixThresholdForFgBg");
+
+    if (pfgval) *pfgval = 0;
+    if (pbgval) *pbgval = 0;
+    if (!pfgval && !pbgval)
+        return ERROR_INT("no data requested", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+        /* Generate a subsampled 8 bpp version and a mask over the fg */
+    pixg = pixConvertTo8BySampling(pixs, factor, 0);
+    pixm = pixThresholdToBinary(pixg, thresh);
+
+    if (pfgval) {
+        pixGetAverageMasked(pixg, pixm, 0, 0, 1, L_MEAN_ABSVAL, &fval);
+        *pfgval = (l_int32)(fval + 0.5);
+    }
+
+    if (pbgval) {
+        pixInvert(pixm, pixm);
+        pixGetAverageMasked(pixg, pixm, 0, 0, 1, L_MEAN_ABSVAL, &fval);
+        *pbgval = (l_int32)(fval + 0.5);
+    }
+
+    pixDestroy(&pixg);
+    pixDestroy(&pixm);
+    return 0;
+}
+
+
+/*!
+ *  pixSplitDistributionFgBg()
+ *
+ *      Input:  pixs (any depth; cmapped ok)
+ *              scorefract (fraction of the max score, used to determine
+ *                          the range over which the histogram min is searched)
+ *              factor (subsampling factor; integer >= 1)
+ *              &thresh (<optional return> best threshold for separating)
+ *              &fgval (<optional return> average foreground value)
+ *              &bgval (<optional return> average background value)
+ *              debugflag (1 for plotting of distribution and split point)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See numaSplitDistribution() for details on the underlying
+ *          method of choosing a threshold.
+ */
+l_int32
+pixSplitDistributionFgBg(PIX       *pixs,
+                         l_float32  scorefract,
+                         l_int32    factor,
+                         l_int32   *pthresh,
+                         l_int32   *pfgval,
+                         l_int32   *pbgval,
+                         l_int32    debugflag)
+{
+char       buf[256];
+l_int32    thresh;
+l_float32  avefg, avebg, maxnum;
+GPLOT     *gplot;
+NUMA      *na, *nascore, *nax, *nay;
+PIX       *pixg;
+
+    PROCNAME("pixSplitDistributionFgBg");
+
+    if (pthresh) *pthresh = 0;
+    if (pfgval) *pfgval = 0;
+    if (pbgval) *pbgval = 0;
+    if (!pthresh && !pfgval && !pbgval)
+        return ERROR_INT("no data requested", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+        /* Generate a subsampled 8 bpp version */
+    pixg = pixConvertTo8BySampling(pixs, factor, 0);
+
+        /* Make the fg/bg estimates */
+    na = pixGetGrayHistogram(pixg, 1);
+    if (debugflag) {
+        numaSplitDistribution(na, scorefract, &thresh, &avefg, &avebg,
+                              NULL, NULL, &nascore);
+        numaDestroy(&nascore);
+    } else {
+        numaSplitDistribution(na, scorefract, &thresh, &avefg, &avebg,
+                              NULL, NULL, NULL);
+    }
+
+    if (pthresh) *pthresh = thresh;
+    if (pfgval) *pfgval = (l_int32)(avefg + 0.5);
+    if (pbgval) *pbgval = (l_int32)(avebg + 0.5);
+
+    if (debugflag) {
+        lept_mkdir("redout");
+        gplot = gplotCreate("/tmp/redout/histplot", GPLOT_PNG, "Histogram",
+                            "Grayscale value", "Number of pixels");
+        gplotAddPlot(gplot, NULL, na, GPLOT_LINES, NULL);
+        nax = numaMakeConstant(thresh, 2);
+        numaGetMax(na, &maxnum, NULL);
+        nay = numaMakeConstant(0, 2);
+        numaReplaceNumber(nay, 1, (l_int32)(0.5 * maxnum));
+        snprintf(buf, sizeof(buf), "score fract = %3.1f", scorefract);
+        gplotAddPlot(gplot, nax, nay, GPLOT_LINES, buf);
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+        numaDestroy(&nax);
+        numaDestroy(&nay);
+    }
+
+    pixDestroy(&pixg);
+    numaDestroy(&na);
+    return 0;
+}
diff --git a/src/pix5.c b/src/pix5.c
new file mode 100644 (file)
index 0000000..f73d085
--- /dev/null
@@ -0,0 +1,2803 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pix5.c
+ *
+ *    This file has these operations:
+ *
+ *      (1) Measurement of 1 bpp image properties
+ *      (2) Extract rectangular regions
+ *      (3) Clip to foreground
+ *      (4) Extract pixel averages, reversals and variance along lines
+ *      (5) Rank row and column transforms
+ *
+ *    Measurement of properties
+ *           l_int32     pixaFindDimensions()
+ *           l_int32     pixFindAreaPerimRatio()
+ *           NUMA       *pixaFindPerimToAreaRatio()
+ *           l_int32     pixFindPerimToAreaRatio()
+ *           NUMA       *pixaFindPerimSizeRatio()
+ *           l_int32     pixFindPerimSizeRatio()
+ *           NUMA       *pixaFindAreaFraction()
+ *           l_int32     pixFindAreaFraction()
+ *           NUMA       *pixaFindAreaFractionMasked()
+ *           l_int32     pixFindAreaFractionMasked()
+ *           NUMA       *pixaFindWidthHeightRatio()
+ *           NUMA       *pixaFindWidthHeightProduct()
+ *           l_int32     pixFindOverlapFraction()
+ *           BOXA       *pixFindRectangleComps()
+ *           l_int32     pixConformsToRectangle()
+ *
+ *    Extract rectangular region
+ *           PIXA       *pixClipRectangles()
+ *           PIX        *pixClipRectangle()
+ *           PIX        *pixClipMasked()
+ *           l_int32     pixCropToMatch()
+ *           PIX        *pixCropToSize()
+ *           PIX        *pixResizeToMatch()
+ *
+ *    Make a frame mask
+ *           PIX        *pixMakeFrameMask()
+ *
+ *    Fraction of Fg pixels under a mask
+ *           l_int32     pixFractionFgInMask()
+ *
+ *    Clip to foreground
+ *           PIX        *pixClipToForeground()
+ *           l_int32     pixTestClipToForeground()
+ *           l_int32     pixClipBoxToForeground()
+ *           l_int32     pixScanForForeground()
+ *           l_int32     pixClipBoxToEdges()
+ *           l_int32     pixScanForEdge()
+ *
+ *    Extract pixel averages and reversals along lines
+ *           NUMA       *pixExtractOnLine()
+ *           l_float32   pixAverageOnLine()
+ *           NUMA       *pixAverageIntensityProfile()
+ *           NUMA       *pixReversalProfile()
+ *
+ *    Extract windowed variance along a line
+ *           NUMA       *pixWindowedVarianceOnLine()
+ *
+ *    Extract min/max of pixel values near lines
+ *           l_int32     pixMinMaxNearLine()
+ *
+ *    Rank row and column transforms
+ *           PIX        *pixRankRowTransform()
+ *           PIX        *pixRankColumnTransform()
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+static const l_uint32 rmask32[] = {0x0,
+    0x00000001, 0x00000003, 0x00000007, 0x0000000f,
+    0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
+    0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff,
+    0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff,
+    0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff,
+    0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff,
+    0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff,
+    0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff};
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_EDGES         0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*-------------------------------------------------------------*
+ *                 Measurement of properties                   *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixaFindDimensions()
+ *
+ *      Input:  pixa
+ *              &naw (<optional return> numa of pix widths)
+ *              &nah (<optional return> numa of pix heights)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixaFindDimensions(PIXA   *pixa,
+                   NUMA  **pnaw,
+                   NUMA  **pnah)
+{
+l_int32  i, n, w, h;
+PIX     *pixt;
+
+    PROCNAME("pixaFindDimensions");
+
+    if (pnaw) *pnaw = NULL;
+    if (pnah) *pnah = NULL;
+    if (!pnaw && !pnah)
+        return ERROR_INT("no output requested", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    n = pixaGetCount(pixa);
+    if (pnaw) *pnaw = numaCreate(n);
+    if (pnah) *pnah = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        pixt = pixaGetPix(pixa, i, L_CLONE);
+        pixGetDimensions(pixt, &w, &h, NULL);
+        if (pnaw)
+            numaAddNumber(*pnaw, w);
+        if (pnah)
+            numaAddNumber(*pnah, h);
+        pixDestroy(&pixt);
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixFindAreaPerimRatio()
+ *
+ *      Input:  pixs (1 bpp)
+ *              tab (<optional> pixel sum table, can be NULL)
+ *              &fract (<return> area/perimeter ratio)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The area is the number of fg pixels that are not on the
+ *          boundary (i.e., are not 8-connected to a bg pixel), and the
+ *          perimeter is the number of fg boundary pixels.  Returns
+ *          0.0 if there are no fg pixels.
+ *      (2) This function is retained because clients are using it.
+ */
+l_int32
+pixFindAreaPerimRatio(PIX        *pixs,
+                      l_int32    *tab,
+                      l_float32  *pfract)
+{
+l_int32  *tab8;
+l_int32   nfg, nbound;
+PIX      *pixt;
+
+    PROCNAME("pixFindAreaPerimRatio");
+
+    if (!pfract)
+        return ERROR_INT("&fract not defined", procName, 1);
+    *pfract = 0.0;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+    if (!tab)
+        tab8 = makePixelSumTab8();
+    else
+        tab8 = tab;
+
+    pixt = pixErodeBrick(NULL, pixs, 3, 3);
+    pixCountPixels(pixt, &nfg, tab8);
+    if (nfg == 0) {
+        pixDestroy(&pixt);
+        if (!tab) LEPT_FREE(tab8);
+        return 0;
+    }
+    pixXor(pixt, pixt, pixs);
+    pixCountPixels(pixt, &nbound, tab8);
+    *pfract = (l_float32)nfg / (l_float32)nbound;
+    pixDestroy(&pixt);
+
+    if (!tab) LEPT_FREE(tab8);
+    return 0;
+}
+
+
+/*!
+ *  pixaFindPerimToAreaRatio()
+ *
+ *      Input:  pixa (of 1 bpp pix)
+ *      Return: na (of perimeter/arear ratio for each pix), or null on error
+ *
+ *  Notes:
+ *      (1) This is typically used for a pixa consisting of
+ *          1 bpp connected components.
+ */
+NUMA *
+pixaFindPerimToAreaRatio(PIXA  *pixa)
+{
+l_int32    i, n;
+l_int32   *tab;
+l_float32  fract;
+NUMA      *na;
+PIX       *pixt;
+
+    PROCNAME("pixaFindPerimToAreaRatio");
+
+    if (!pixa)
+        return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL);
+
+    n = pixaGetCount(pixa);
+    na = numaCreate(n);
+    tab = makePixelSumTab8();
+    for (i = 0; i < n; i++) {
+        pixt = pixaGetPix(pixa, i, L_CLONE);
+        pixFindPerimToAreaRatio(pixt, tab, &fract);
+        numaAddNumber(na, fract);
+        pixDestroy(&pixt);
+    }
+    LEPT_FREE(tab);
+    return na;
+}
+
+
+/*!
+ *  pixFindPerimToAreaRatio()
+ *
+ *      Input:  pixs (1 bpp)
+ *              tab (<optional> pixel sum table, can be NULL)
+ *              &fract (<return> perimeter/area ratio)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The perimeter is the number of fg boundary pixels, and the
+ *          area is the number of fg pixels.  This returns 0.0 if
+ *          there are no fg pixels.
+ *      (2) Unlike pixFindAreaPerimRatio(), this uses the full set of
+ *          fg pixels for the area, and the ratio is taken in the opposite
+ *          order.
+ *      (3) This is typically used for a single connected component.
+ *          This always has a value <= 1.0, and if the average distance
+ *          of a fg pixel from the nearest bg pixel is d, this has
+ *          a value ~1/d.
+ */
+l_int32
+pixFindPerimToAreaRatio(PIX        *pixs,
+                        l_int32    *tab,
+                        l_float32  *pfract)
+{
+l_int32  *tab8;
+l_int32   nfg, nbound;
+PIX      *pixt;
+
+    PROCNAME("pixFindPerimToAreaRatio");
+
+    if (!pfract)
+        return ERROR_INT("&fract not defined", procName, 1);
+    *pfract = 0.0;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+    if (!tab)
+        tab8 = makePixelSumTab8();
+    else
+        tab8 = tab;
+
+    pixCountPixels(pixs, &nfg, tab8);
+    if (nfg == 0) {
+        if (!tab) LEPT_FREE(tab8);
+        return 0;
+    }
+    pixt = pixErodeBrick(NULL, pixs, 3, 3);
+    pixXor(pixt, pixt, pixs);
+    pixCountPixels(pixt, &nbound, tab8);
+    *pfract = (l_float32)nbound / (l_float32)nfg;
+    pixDestroy(&pixt);
+
+    if (!tab) LEPT_FREE(tab8);
+    return 0;
+}
+
+
+/*!
+ *  pixaFindPerimSizeRatio()
+ *
+ *      Input:  pixa (of 1 bpp pix)
+ *      Return: na (of fg perimeter/(2*(w+h)) ratio for each pix),
+ *                  or null on error
+ *
+ *  Notes:
+ *      (1) This is typically used for a pixa consisting of
+ *          1 bpp connected components.
+ *      (2) This has a minimum value for a circle of pi/4; a value for
+ *          a rectangle component of approx. 1.0; and a value much larger
+ *          than 1.0 for a component with a highly irregular boundary.
+ */
+NUMA *
+pixaFindPerimSizeRatio(PIXA  *pixa)
+{
+l_int32    i, n;
+l_int32   *tab;
+l_float32  ratio;
+NUMA      *na;
+PIX       *pixt;
+
+    PROCNAME("pixaFindPerimSizeRatio");
+
+    if (!pixa)
+        return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL);
+
+    n = pixaGetCount(pixa);
+    na = numaCreate(n);
+    tab = makePixelSumTab8();
+    for (i = 0; i < n; i++) {
+        pixt = pixaGetPix(pixa, i, L_CLONE);
+        pixFindPerimSizeRatio(pixt, tab, &ratio);
+        numaAddNumber(na, ratio);
+        pixDestroy(&pixt);
+    }
+    LEPT_FREE(tab);
+    return na;
+}
+
+
+/*!
+ *  pixFindPerimSizeRatio()
+ *
+ *      Input:  pixs (1 bpp)
+ *              tab (<optional> pixel sum table, can be NULL)
+ *              &ratio (<return> perimeter/size ratio)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) We take the 'size' as twice the sum of the width and
+ *          height of pixs, and the perimeter is the number of fg
+ *          boundary pixels.  We use the fg pixels of the boundary
+ *          because the pix may be clipped to the boundary, so an
+ *          erosion is required to count all boundary pixels.
+ *      (2) This has a large value for dendritic, fractal-like components
+ *          with highly irregular boundaries.
+ *      (3) This is typically used for a single connected component.
+ *          It has a value of about 1.0 for rectangular components with
+ *          relatively smooth boundaries.
+ */
+l_int32
+pixFindPerimSizeRatio(PIX        *pixs,
+                      l_int32    *tab,
+                      l_float32  *pratio)
+{
+l_int32  *tab8;
+l_int32   w, h, nbound;
+PIX      *pixt;
+
+    PROCNAME("pixFindPerimSizeRatio");
+
+    if (!pratio)
+        return ERROR_INT("&ratio not defined", procName, 1);
+    *pratio = 0.0;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+    if (!tab)
+        tab8 = makePixelSumTab8();
+    else
+        tab8 = tab;
+
+    pixt = pixErodeBrick(NULL, pixs, 3, 3);
+    pixXor(pixt, pixt, pixs);
+    pixCountPixels(pixt, &nbound, tab8);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    *pratio = (0.5 * nbound) / (l_float32)(w + h);
+    pixDestroy(&pixt);
+
+    if (!tab) LEPT_FREE(tab8);
+    return 0;
+}
+
+
+/*!
+ *  pixaFindAreaFraction()
+ *
+ *      Input:  pixa (of 1 bpp pix)
+ *      Return: na (of area fractions for each pix), or null on error
+ *
+ *  Notes:
+ *      (1) This is typically used for a pixa consisting of
+ *          1 bpp connected components.
+ */
+NUMA *
+pixaFindAreaFraction(PIXA  *pixa)
+{
+l_int32    i, n;
+l_int32   *tab;
+l_float32  fract;
+NUMA      *na;
+PIX       *pixt;
+
+    PROCNAME("pixaFindAreaFraction");
+
+    if (!pixa)
+        return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL);
+
+    n = pixaGetCount(pixa);
+    na = numaCreate(n);
+    tab = makePixelSumTab8();
+    for (i = 0; i < n; i++) {
+        pixt = pixaGetPix(pixa, i, L_CLONE);
+        pixFindAreaFraction(pixt, tab, &fract);
+        numaAddNumber(na, fract);
+        pixDestroy(&pixt);
+    }
+    LEPT_FREE(tab);
+    return na;
+}
+
+
+/*!
+ *  pixFindAreaFraction()
+ *
+ *      Input:  pixs (1 bpp)
+ *              tab (<optional> pixel sum table, can be NULL)
+ *              &fract (<return> fg area/size ratio)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This finds the ratio of the number of fg pixels to the
+ *          size of the pix (w * h).  It is typically used for a
+ *          single connected component.
+ */
+l_int32
+pixFindAreaFraction(PIX        *pixs,
+                    l_int32    *tab,
+                    l_float32  *pfract)
+{
+l_int32   w, h, sum;
+l_int32  *tab8;
+
+    PROCNAME("pixFindAreaFraction");
+
+    if (!pfract)
+        return ERROR_INT("&fract not defined", procName, 1);
+    *pfract = 0.0;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+    if (!tab)
+        tab8 = makePixelSumTab8();
+    else
+        tab8 = tab;
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixCountPixels(pixs, &sum, tab8);
+    *pfract = (l_float32)sum / (l_float32)(w * h);
+
+    if (!tab) LEPT_FREE(tab8);
+    return 0;
+}
+
+
+/*!
+ *  pixaFindAreaFractionMasked()
+ *
+ *      Input:  pixa (of 1 bpp pix)
+ *              pixm (mask image)
+ *              debug (1 for output, 0 to suppress)
+ *      Return: na (of ratio masked/total fractions for each pix),
+ *                  or null on error
+ *
+ *  Notes:
+ *      (1) This is typically used for a pixa consisting of
+ *          1 bpp connected components, which has an associated
+ *          boxa giving the location of the components relative
+ *          to the mask origin.
+ *      (2) The debug flag displays in green and red the masked and
+ *          unmasked parts of the image from which pixa was derived.
+ */
+NUMA *
+pixaFindAreaFractionMasked(PIXA    *pixa,
+                           PIX     *pixm,
+                           l_int32  debug)
+{
+l_int32    i, n, full;
+l_int32   *tab;
+l_float32  fract;
+BOX       *box;
+NUMA      *na;
+PIX       *pix;
+
+    PROCNAME("pixaFindAreaFractionMasked");
+
+    if (!pixa)
+        return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (!pixm || pixGetDepth(pixm) != 1)
+        return (NUMA *)ERROR_PTR("pixm undefined or not 1 bpp", procName, NULL);
+
+    n = pixaGetCount(pixa);
+    na = numaCreate(n);
+    tab = makePixelSumTab8();
+    pixaIsFull(pixa, NULL, &full);  /* check boxa */
+    box = NULL;
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        if (full)
+            box = pixaGetBox(pixa, i, L_CLONE);
+        pixFindAreaFractionMasked(pix, box, pixm, tab, &fract);
+        numaAddNumber(na, fract);
+        boxDestroy(&box);
+        pixDestroy(&pix);
+    }
+    LEPT_FREE(tab);
+
+    if (debug) {
+        l_int32  w, h;
+        PIX     *pix1, *pix2;
+        pixGetDimensions(pixm, &w, &h, NULL);
+        pix1 = pixaDisplay(pixa, w, h);  /* recover original image */
+        pix2 = pixCreate(w, h, 8);  /* make an 8 bpp white image ... */
+        pixSetColormap(pix2, pixcmapCreate(8));  /* that's cmapped ... */
+        pixSetBlackOrWhite(pix2, L_SET_WHITE);  /* and init to white */
+        pixSetMaskedCmap(pix2, pix1, 0, 0, 255, 0, 0);  /* color all fg red */
+        pixRasterop(pix1, 0, 0, w, h, PIX_MASK, pixm, 0, 0);
+        pixSetMaskedCmap(pix2, pix1, 0, 0, 0, 255, 0);  /* turn masked green */
+        pixDisplay(pix2, 100, 100);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixFindAreaFractionMasked()
+ *
+ *      Input:  pixs (1 bpp, typically a single component)
+ *              box (<optional> for pixs relative to pixm)
+ *              pixm (1 bpp mask, typically over the entire image from
+ *                    which the component pixs was extracted)
+ *              tab (<optional> pixel sum table, can be NULL)
+ *              &fract (<return> fg area/size ratio)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This finds the ratio of the number of masked fg pixels
+ *          in pixs to the total number of fg pixels in pixs.
+ *          It is typically used for a single connected component.
+ *          If there are no fg pixels, this returns a ratio of 0.0.
+ *      (2) The box gives the location of the pix relative to that
+ *          of the UL corner of the mask.  Therefore, the rasterop
+ *          is performed with the pix translated to its location
+ *          (x, y) in the mask before ANDing.
+ *          If box == NULL, the UL corners of pixs and pixm are aligned.
+ */
+l_int32
+pixFindAreaFractionMasked(PIX        *pixs,
+                          BOX        *box,
+                          PIX        *pixm,
+                          l_int32    *tab,
+                          l_float32  *pfract)
+{
+l_int32   x, y, w, h, sum, masksum;
+l_int32  *tab8;
+PIX      *pix1;
+
+    PROCNAME("pixFindAreaFractionMasked");
+
+    if (!pfract)
+        return ERROR_INT("&fract not defined", procName, 1);
+    *pfract = 0.0;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (!pixm || pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm not defined or not 1 bpp", procName, 1);
+
+    if (!tab)
+        tab8 = makePixelSumTab8();
+    else
+        tab8 = tab;
+    x = y = 0;
+    if (box)
+        boxGetGeometry(box, &x, &y, NULL, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+
+    pix1 = pixCopy(NULL, pixs);
+    pixRasterop(pix1, 0, 0, w, h, PIX_MASK, pixm, x, y);
+    pixCountPixels(pixs, &sum, tab8);
+    if (sum == 0) {
+        pixDestroy(&pix1);
+        if (!tab) LEPT_FREE(tab8);
+        return 0;
+    }
+    pixCountPixels(pix1, &masksum, tab8);
+    *pfract = (l_float32)masksum / (l_float32)sum;
+
+    if (!tab) LEPT_FREE(tab8);
+    pixDestroy(&pix1);
+    return 0;
+}
+
+
+/*!
+ *  pixaFindWidthHeightRatio()
+ *
+ *      Input:  pixa (of 1 bpp pix)
+ *      Return: na (of width/height ratios for each pix), or null on error
+ *
+ *  Notes:
+ *      (1) This is typically used for a pixa consisting of
+ *          1 bpp connected components.
+ */
+NUMA *
+pixaFindWidthHeightRatio(PIXA  *pixa)
+{
+l_int32  i, n, w, h;
+NUMA    *na;
+PIX     *pixt;
+
+    PROCNAME("pixaFindWidthHeightRatio");
+
+    if (!pixa)
+        return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL);
+
+    n = pixaGetCount(pixa);
+    na = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        pixt = pixaGetPix(pixa, i, L_CLONE);
+        pixGetDimensions(pixt, &w, &h, NULL);
+        numaAddNumber(na, (l_float32)w / (l_float32)h);
+        pixDestroy(&pixt);
+    }
+    return na;
+}
+
+
+/*!
+ *  pixaFindWidthHeightProduct()
+ *
+ *      Input:  pixa (of 1 bpp pix)
+ *      Return: na (of width*height products for each pix), or null on error
+ *
+ *  Notes:
+ *      (1) This is typically used for a pixa consisting of
+ *          1 bpp connected components.
+ */
+NUMA *
+pixaFindWidthHeightProduct(PIXA  *pixa)
+{
+l_int32  i, n, w, h;
+NUMA    *na;
+PIX     *pixt;
+
+    PROCNAME("pixaFindWidthHeightProduct");
+
+    if (!pixa)
+        return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL);
+
+    n = pixaGetCount(pixa);
+    na = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        pixt = pixaGetPix(pixa, i, L_CLONE);
+        pixGetDimensions(pixt, &w, &h, NULL);
+        numaAddNumber(na, w * h);
+        pixDestroy(&pixt);
+    }
+    return na;
+}
+
+
+/*!
+ *  pixFindOverlapFraction()
+ *
+ *      Input:  pixs1, pixs2 (1 bpp)
+ *              x2, y2 (location in pixs1 of UL corner of pixs2)
+ *              tab (<optional> pixel sum table, can be null)
+ *              &ratio (<return> ratio fg intersection to fg union)
+ *              &noverlap (<optional return> number of overlapping pixels)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The UL corner of pixs2 is placed at (x2, y2) in pixs1.
+ *      (2) This measure is similar to the correlation.
+ */
+l_int32
+pixFindOverlapFraction(PIX        *pixs1,
+                       PIX        *pixs2,
+                       l_int32     x2,
+                       l_int32     y2,
+                       l_int32    *tab,
+                       l_float32  *pratio,
+                       l_int32    *pnoverlap)
+{
+l_int32  *tab8;
+l_int32   w, h, nintersect, nunion;
+PIX      *pixt;
+
+    PROCNAME("pixFindOverlapFraction");
+
+    if (pnoverlap) *pnoverlap = 0;
+    if (!pratio)
+        return ERROR_INT("&ratio not defined", procName, 1);
+    *pratio = 0.0;
+    if (!pixs1 || pixGetDepth(pixs1) != 1)
+        return ERROR_INT("pixs1 not defined or not 1 bpp", procName, 1);
+    if (!pixs2 || pixGetDepth(pixs2) != 1)
+        return ERROR_INT("pixs2 not defined or not 1 bpp", procName, 1);
+
+    if (!tab)
+        tab8 = makePixelSumTab8();
+    else
+        tab8 = tab;
+
+    pixGetDimensions(pixs2, &w, &h, NULL);
+    pixt = pixCopy(NULL, pixs1);
+    pixRasterop(pixt, x2, y2, w, h, PIX_MASK, pixs2, 0, 0);  /* AND */
+    pixCountPixels(pixt, &nintersect, tab8);
+    if (pnoverlap)
+        *pnoverlap = nintersect;
+    pixCopy(pixt, pixs1);
+    pixRasterop(pixt, x2, y2, w, h, PIX_PAINT, pixs2, 0, 0);  /* OR */
+    pixCountPixels(pixt, &nunion, tab8);
+    if (!tab) LEPT_FREE(tab8);
+    pixDestroy(&pixt);
+
+    if (nunion > 0)
+        *pratio = (l_float32)nintersect / (l_float32)nunion;
+    return 0;
+}
+
+
+/*!
+ *  pixFindRectangleComps()
+ *
+ *      Input:  pixs (1 bpp)
+ *              dist (max distance allowed between bounding box and nearest
+ *                    foreground pixel within it)
+ *              minw, minh (minimum size in each direction as a requirement
+ *                          for a conforming rectangle)
+ *      Return: boxa (of components that conform), or null on error
+ *
+ *  Notes:
+ *      (1) This applies the function pixConformsToRectangle() to
+ *          each 8-c.c. in pixs, and returns a boxa containing the
+ *          regions of all components that are conforming.
+ *      (2) Conforming components must satisfy both the size constraint
+ *          given by @minsize and the slop in conforming to a rectangle
+ *          determined by @dist.
+ */
+BOXA *
+pixFindRectangleComps(PIX     *pixs,
+                      l_int32  dist,
+                      l_int32  minw,
+                      l_int32  minh)
+{
+l_int32  w, h, i, n, conforms;
+BOX     *box;
+BOXA    *boxa, *boxad;
+PIX     *pix;
+PIXA    *pixa;
+
+    PROCNAME("pixFindRectangleComps");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (BOXA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (dist < 0)
+        return (BOXA *)ERROR_PTR("dist must be >= 0", procName, NULL);
+    if (minw <= 2 * dist && minh <= 2 * dist)
+        return (BOXA *)ERROR_PTR("invalid parameters", procName, NULL);
+
+    boxa = pixConnComp(pixs, &pixa, 8);
+    boxad = boxaCreate(0);
+    n = pixaGetCount(pixa);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        pixGetDimensions(pix, &w, &h, NULL);
+        if (w < minw || h < minh) {
+            pixDestroy(&pix);
+            continue;
+        }
+        pixConformsToRectangle(pix, NULL, dist, &conforms);
+        if (conforms) {
+            box = boxaGetBox(boxa, i, L_COPY);
+            boxaAddBox(boxad, box, L_INSERT);
+        }
+        pixDestroy(&pix);
+    }
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixa);
+    return boxad;
+}
+
+
+/*!
+ *  pixConformsToRectangle()
+ *
+ *      Input:  pixs (1 bpp)
+ *              box (<optional> if null, use the entire pixs)
+ *              dist (max distance allowed between bounding box and nearest
+ *                    foreground pixel within it)
+ *              &conforms (<return> 0 (false) if not conforming;
+ *                        1 (true) if conforming)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) There are several ways to test if a connected component has
+ *          an essentially rectangular boundary, such as:
+ *           a. Fraction of fill into the bounding box
+ *           b. Max-min distance of fg pixel from periphery of bounding box
+ *           c. Max depth of bg intrusions into component within bounding box
+ *          The weakness of (a) is that it is highly sensitive to holes
+ *          within the c.c.  The weakness of (b) is that it can have
+ *          arbitrarily large intrusions into the c.c.  Method (c) tests
+ *          the integrity of the outer boundary of the c.c., with respect
+ *          to the enclosing bounding box, so we use it.
+ *      (2) This tests if the connected component within the box conforms
+ *          to the box at all points on the periphery within @dist.
+ *          Inside, at a distance from the box boundary that is greater
+ *          than @dist, we don't care about the pixels in the c.c.
+ *      (3) We can think of the conforming condition as follows:
+ *          No pixel inside a distance @dist from the boundary
+ *          can connect to the boundary through a path through the bg.
+ *          To implement this, we need to do a flood fill.  We can go
+ *          either from inside toward the boundary, or the other direction.
+ *          It's easiest to fill from the boundary, and then verify that
+ *          there are no filled pixels farther than @dist from the boundary.
+ */
+l_int32
+pixConformsToRectangle(PIX      *pixs,
+                       BOX      *box,
+                       l_int32   dist,
+                       l_int32  *pconforms)
+{
+l_int32  w, h, empty;
+PIX     *pix1, *pix2;
+
+    PROCNAME("pixConformsToRectangle");
+
+    if (!pconforms)
+        return ERROR_INT("&conforms not defined", procName, 1);
+    *pconforms = 0;
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (dist < 0)
+        return ERROR_INT("dist must be >= 0", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (w <= 2 * dist || h <= 2 * dist) {
+        L_WARNING("automatic conformation: distance too large\n", procName);
+        *pconforms = 1;
+        return 0;
+    }
+
+        /* Extract the region, if necessary */
+    if (box)
+        pix1 = pixClipRectangle(pixs, box, NULL);
+    else
+        pix1 = pixCopy(NULL, pixs);
+
+        /* Invert and fill from the boundary into the interior.
+         * Because we're considering the connected component in an
+         * 8-connected sense, we do the background filling as 4 c.c. */
+    pixInvert(pix1, pix1);
+    pix2 = pixExtractBorderConnComps(pix1, 4);
+
+        /* Mask out all pixels within a distance @dist from the box
+         * boundary.  Any remaining pixels are from filling that goes
+         * more than @dist from the boundary.  If no pixels remain,
+         * the component conforms to the bounding rectangle within
+         * a distance @dist. */
+    pixSetOrClearBorder(pix2, dist, dist, dist, dist, PIX_CLR);
+    pixZero(pix2, &empty);
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    *pconforms = (empty) ? 1 : 0;
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                      Extract rectangular region                       *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixClipRectangles()
+ *
+ *      Input:  pixs
+ *              boxa (requested clipping regions)
+ *      Return: pixa (consisting of requested regions), or null on error
+ *
+ *  Notes:
+ *     (1) The returned pixa includes the actual regions clipped out from
+ *         the input pixs.
+ */
+PIXA *
+pixClipRectangles(PIX   *pixs,
+                  BOXA  *boxa)
+{
+l_int32  i, n;
+BOX     *box, *boxc;
+PIX     *pix;
+PIXA    *pixa;
+
+    PROCNAME("pixClipRectangles");
+
+    if (!pixs)
+        return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!boxa)
+        return (PIXA *)ERROR_PTR("boxa not defined", procName, NULL);
+
+    n = boxaGetCount(boxa);
+    pixa = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        pix = pixClipRectangle(pixs, box, &boxc);
+        pixaAddPix(pixa, pix, L_INSERT);
+        pixaAddBox(pixa, boxc, L_INSERT);
+        boxDestroy(&box);
+    }
+
+    return pixa;
+}
+
+
+/*!
+ *  pixClipRectangle()
+ *
+ *      Input:  pixs
+ *              box  (requested clipping region; const)
+ *              &boxc (<optional return> actual box of clipped region)
+ *      Return: clipped pix, or null on error or if rectangle
+ *              doesn't intersect pixs
+ *
+ *  Notes:
+ *
+ *  This should be simple, but there are choices to be made.
+ *  The box is defined relative to the pix coordinates.  However,
+ *  if the box is not contained within the pix, we have two choices:
+ *
+ *      (1) clip the box to the pix
+ *      (2) make a new pix equal to the full box dimensions,
+ *          but let rasterop do the clipping and positioning
+ *          of the src with respect to the dest
+ *
+ *  Choice (2) immediately brings up the problem of what pixel values
+ *  to use that were not taken from the src.  For example, on a grayscale
+ *  image, do you want the pixels not taken from the src to be black
+ *  or white or something else?  To implement choice 2, one needs to
+ *  specify the color of these extra pixels.
+ *
+ *  So we adopt (1), and clip the box first, if necessary,
+ *  before making the dest pix and doing the rasterop.  But there
+ *  is another issue to consider.  If you want to paste the
+ *  clipped pix back into pixs, it must be properly aligned, and
+ *  it is necessary to use the clipped box for alignment.
+ *  Accordingly, this function has a third (optional) argument, which is
+ *  the input box clipped to the src pix.
+ */
+PIX *
+pixClipRectangle(PIX   *pixs,
+                 BOX   *box,
+                 BOX  **pboxc)
+{
+l_int32  w, h, d, bx, by, bw, bh;
+BOX     *boxc;
+PIX     *pixd;
+
+    PROCNAME("pixClipRectangle");
+
+    if (pboxc) *pboxc = NULL;
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!box)
+        return (PIX *)ERROR_PTR("box not defined", procName, NULL);
+
+        /* Clip the input box to the pix */
+    pixGetDimensions(pixs, &w, &h, &d);
+    if ((boxc = boxClipToRectangle(box, w, h)) == NULL) {
+        L_WARNING("box doesn't overlap pix\n", procName);
+        return NULL;
+    }
+    boxGetGeometry(boxc, &bx, &by, &bw, &bh);
+
+        /* Extract the block */
+    if ((pixd = pixCreate(bw, bh, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyColormap(pixd, pixs);
+    pixRasterop(pixd, 0, 0, bw, bh, PIX_SRC, pixs, bx, by);
+
+    if (pboxc)
+        *pboxc = boxc;
+    else
+        boxDestroy(&boxc);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixClipMasked()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16, 32 bpp; colormap ok)
+ *              pixm  (clipping mask, 1 bpp)
+ *              x, y (origin of clipping mask relative to pixs)
+ *              outval (val to use for pixels that are outside the mask)
+ *      Return: pixd, (clipped pix) or null on error or if pixm doesn't
+ *              intersect pixs
+ *
+ *  Notes:
+ *      (1) If pixs has a colormap, it is preserved in pixd.
+ *      (2) The depth of pixd is the same as that of pixs.
+ *      (3) If the depth of pixs is 1, use @outval = 0 for white background
+ *          and 1 for black; otherwise, use the max value for white
+ *          and 0 for black.  If pixs has a colormap, the max value for
+ *          @outval is 0xffffffff; otherwise, it is 2^d - 1.
+ *      (4) When using 1 bpp pixs, this is a simple clip and
+ *          blend operation.  For example, if both pix1 and pix2 are
+ *          black text on white background, and you want to OR the
+ *          fg on the two images, let pixm be the inverse of pix2.
+ *          Then the operation takes all of pix1 that's in the bg of
+ *          pix2, and for the remainder (which are the pixels
+ *          corresponding to the fg of the pix2), paint them black
+ *          (1) in pix1.  The function call looks like
+ *             pixClipMasked(pix2, pixInvert(pix1, pix1), x, y, 1);
+ */
+PIX *
+pixClipMasked(PIX      *pixs,
+              PIX      *pixm,
+              l_int32   x,
+              l_int32   y,
+              l_uint32  outval)
+{
+l_int32   wm, hm, index, rval, gval, bval;
+l_uint32  pixel;
+BOX      *box;
+PIX      *pixmi, *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixClipMasked");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!pixm || pixGetDepth(pixm) != 1)
+        return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", procName, NULL);
+
+        /* Clip out the region specified by pixm and (x,y) */
+    pixGetDimensions(pixm, &wm, &hm, NULL);
+    box = boxCreate(x, y, wm, hm);
+    pixd = pixClipRectangle(pixs, box, NULL);
+
+        /* Paint 'outval' (or something close to it if cmapped) through
+         * the pixels not masked by pixm */
+    cmap = pixGetColormap(pixd);
+    pixmi = pixInvert(NULL, pixm);
+    if (cmap) {
+        extractRGBValues(outval, &rval, &gval, &bval);
+        pixcmapGetNearestIndex(cmap, rval, gval, bval, &index);
+        pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+        composeRGBPixel(rval, gval, bval, &pixel);
+        pixPaintThroughMask(pixd, pixmi, 0, 0, pixel);
+    } else {
+        pixPaintThroughMask(pixd, pixmi, 0, 0, outval);
+    }
+
+    boxDestroy(&box);
+    pixDestroy(&pixmi);
+    return pixd;
+}
+
+
+/*!
+ *  pixCropToMatch()
+ *
+ *      Input:  pixs1 (any depth, colormap OK)
+ *              pixs2 (any depth, colormap OK)
+ *              &pixd1 (<return> may be a clone)
+ *              &pixd2 (<return> may be a clone)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This resizes pixs1 and/or pixs2 by cropping at the right
+ *          and bottom, so that they're the same size.
+ *      (2) If a pix doesn't need to be cropped, a clone is returned.
+ *      (3) Note: the images are implicitly aligned to the UL corner.
+ */
+l_int32
+pixCropToMatch(PIX   *pixs1,
+               PIX   *pixs2,
+               PIX  **ppixd1,
+               PIX  **ppixd2)
+{
+l_int32  w1, h1, w2, h2, w, h;
+
+    PROCNAME("pixCropToMatch");
+
+    if (!ppixd1 || !ppixd2)
+        return ERROR_INT("&pixd1 and &pixd2 not both defined", procName, 1);
+    *ppixd1 = *ppixd2 = NULL;
+    if (!pixs1 || !pixs2)
+        return ERROR_INT("pixs1 and pixs2 not defined", procName, 1);
+
+    pixGetDimensions(pixs1, &w1, &h1, NULL);
+    pixGetDimensions(pixs2, &w2, &h2, NULL);
+    w = L_MIN(w1, w2);
+    h = L_MIN(h1, h2);
+
+    *ppixd1 = pixCropToSize(pixs1, w, h);
+    *ppixd2 = pixCropToSize(pixs2, w, h);
+    if (*ppixd1 == NULL || *ppixd2 == NULL)
+        return ERROR_INT("cropped image failure", procName, 1);
+    return 0;
+}
+
+
+/*!
+ *  pixCropToSize()
+ *
+ *      Input:  pixs (any depth, colormap OK)
+ *              w, h (max dimensions of cropped image)
+ *      Return: pixd (cropped if necessary) or null on error.
+ *
+ *  Notes:
+ *      (1) If either w or h is smaller than the corresponding dimension
+ *          of pixs, this returns a cropped image; otherwise it returns
+ *          a clone of pixs.
+ */
+PIX *
+pixCropToSize(PIX     *pixs,
+              l_int32  w,
+              l_int32  h)
+{
+l_int32  ws, hs, wd, hd, d;
+PIX     *pixd;
+
+    PROCNAME("pixCropToSize");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, &d);
+    if (ws <= w && hs <= h)  /* no cropping necessary */
+        return pixClone(pixs);
+
+    wd = L_MIN(ws, w);
+    hd = L_MIN(hs, h);
+    if ((pixd = pixCreate(wd, hd, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyColormap(pixd, pixs);
+    pixCopyText(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    pixRasterop(pixd, 0, 0, wd, hd, PIX_SRC, pixs, 0, 0);
+    return pixd;
+}
+
+
+/*!
+ *  pixResizeToMatch()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16, 32 bpp; colormap ok)
+ *              pixt  (can be null; we use only the size)
+ *              w, h (ignored if pixt is defined)
+ *      Return: pixd (resized to match) or null on error
+ *
+ *  Notes:
+ *      (1) This resizes pixs to make pixd, without scaling, by either
+ *          cropping or extending separately in both width and height.
+ *          Extension is done by replicating the last row or column.
+ *          This is useful in a situation where, due to scaling
+ *          operations, two images that are expected to be the
+ *          same size can differ slightly in each dimension.
+ *      (2) You can use either an existing pixt or specify
+ *          both @w and @h.  If pixt is defined, the values
+ *          in @w and @h are ignored.
+ *      (3) If pixt is larger than pixs (or if w and/or d is larger
+ *          than the dimension of pixs, replicate the outer row and
+ *          column of pixels in pixs into pixd.
+ */
+PIX *
+pixResizeToMatch(PIX     *pixs,
+                 PIX     *pixt,
+                 l_int32  w,
+                 l_int32  h)
+{
+l_int32  i, j, ws, hs, d;
+PIX     *pixd;
+
+    PROCNAME("pixResizeToMatch");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!pixt && (w <= 0 || h <= 0))
+        return (PIX *)ERROR_PTR("both w and h not > 0", procName, NULL);
+
+    if (pixt)  /* redefine w, h */
+        pixGetDimensions(pixt, &w, &h, NULL);
+    pixGetDimensions(pixs, &ws, &hs, &d);
+    if (ws == w && hs == h)
+        return pixCopy(NULL, pixs);
+
+    if ((pixd = pixCreate(w, h, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyColormap(pixd, pixs);
+    pixCopyText(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    pixRasterop(pixd, 0, 0, ws, hs, PIX_SRC, pixs, 0, 0);
+    if (ws >= w && hs >= h)
+        return pixd;
+
+        /* Replicate the last column and then the last row */
+    if (ws < w) {
+        for (j = ws; j < w; j++)
+            pixRasterop(pixd, j, 0, 1, h, PIX_SRC, pixd, ws - 1, 0);
+    }
+    if (hs < h) {
+        for (i = hs; i < h; i++)
+            pixRasterop(pixd, 0, i, w, 1, PIX_SRC, pixd, 0, hs - 1);
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                          Make a frame mask                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixMakeFrameMask()
+ *
+ *      Input:  w, h (dimensions of output 1 bpp pix)
+ *              hf1 (horizontal fraction of half-width at outer frame bdry)
+ *              hf2 (horizontal fraction of half-width at inner frame bdry)
+ *              vf1 (vertical fraction of half-width at outer frame bdry)
+ *              vf2 (vertical fraction of half-width at inner frame bdry)
+ *      Return: pixd (1 bpp), or null on error.
+ *
+ *  Notes:
+ *      (1) This makes an arbitrary 1-component mask with a centered frame.
+ *          Input fractions are in [0.0 ... 1.0]; hf1 <= hf2 and vf1 <= vf2.
+ *          Horizontal and vertical frame widths are independently specified.
+ *      (2) Special case: to get a full fg mask, set all input values to 0.0.
+ *          An empty fg mask has hf1 = vf1 = 1.0.
+ *          A fg rectangle with no hole has hf2 == 1.0 or hv2 == 1.0.
+ *      (3) The vertical thickness of the horizontal mask parts
+ *          is 0.5 * (vf2 - vf1) * h.  The horizontal thickness of the
+ *          vertical mask parts is 0.5 * (hf2 - hf1) * w.
+ */
+PIX *
+pixMakeFrameMask(l_int32    w,
+                 l_int32    h,
+                 l_float32  hf1,
+                 l_float32  hf2,
+                 l_float32  vf1,
+                 l_float32  vf2)
+{
+l_int32  h1, h2, v1, v2;
+PIX     *pixd;
+
+    PROCNAME("pixMakeFrameMask");
+
+    if (w <= 0 || h <= 0)
+        return (PIX *)ERROR_PTR("mask size 0", procName, NULL);
+    if (hf1 < 0.0 || hf1 > 1.0 || hf2 < 0.0 || hf2 > 1.0)
+        return (PIX *)ERROR_PTR("invalid horiz fractions", procName, NULL);
+    if (vf1 < 0.0 || vf1 > 1.0 || vf2 < 0.0 || vf2 > 1.0)
+        return (PIX *)ERROR_PTR("invalid vert fractions", procName, NULL);
+    if (hf1 > hf2 || vf1 > vf2)
+        return (PIX *)ERROR_PTR("invalid relative sizes", procName, NULL);
+
+    pixd = pixCreate(w, h, 1);
+
+        /* Special cases */
+    if (hf1 == 0.0 && hf2 == 0.0 && vf1 == 0.0 && vf2 == 0.0) {  /* full */
+        pixSetAll(pixd);
+        return pixd;
+    }
+    if (hf1 == 1.0 && vf1 == 1.0) {  /* empty */
+        return pixd;
+    }
+
+        /* General case */
+    h1 = 0.5 * hf1 * w;
+    h2 = 0.5 * hf2 * w;
+    v1 = 0.5 * vf1 * h;
+    v2 = 0.5 * vf2 * h;
+    pixRasterop(pixd, h1, v1, w - 2 * h1, h - 2 * v1, PIX_SET, NULL, 0, 0);
+    if (hf2 < 1.0 && vf2 < 1.0)
+        pixRasterop(pixd, h2, v2, w - 2 * h2, h - 2 * v2, PIX_CLR, NULL, 0, 0);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                 Fraction of Fg pixels under a mask                  *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixFractionFgInMask()
+ *
+ *      Input:  pix1 (1 bpp)
+ *              pix2 (1 bpp)
+ *              &fract (<return> fraction of fg pixels in 1 that are
+ *                      aligned with the fg of 2)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) This gives the fraction of fg pixels in pix1 that are in
+ *          the intersection (i.e., under the fg) of pix2:
+ *          |1 & 2|/|1|, where |...| means the number of fg pixels.
+ *          Note that this is different from the situation where
+ *          pix1 and pix2 are reversed.
+ *      (2) Both pix1 and pix2 are registered to the UL corners.  A warning
+ *          is issued if pix1 and pix2 have different sizes.
+ *      (3) This can also be used to find the fraction of fg pixels in pix1
+ *          that are NOT under the fg of pix2: 1.0 - |1 & 2|/|1|
+ *      (4) If pix1 or pix2 are empty, this returns @fract = 0.0.
+ *      (5) For example, pix2 could be a frame around the outside of the
+ *          image, made from pixMakeFrameMask().
+ */
+l_int32
+pixFractionFgInMask(PIX        *pix1,
+                    PIX        *pix2,
+                    l_float32  *pfract)
+{
+l_int32  w1, h1, w2, h2, empty, count1, count3;
+PIX     *pix3;
+
+    PROCNAME("pixFractionFgInMask");
+
+    if (!pfract)
+        return ERROR_INT("&fract not defined", procName, 1);
+    *pfract = 0.0;
+    if (!pix1 || pixGetDepth(pix1) != 1)
+        return ERROR_INT("pix1 not defined or not 1 bpp", procName, 1);
+    if (!pix2 || pixGetDepth(pix2) != 1)
+        return ERROR_INT("pix2 not defined or not 1 bpp", procName, 1);
+
+    pixGetDimensions(pix1, &w1, &h1, NULL);
+    pixGetDimensions(pix2, &w2, &h2, NULL);
+    if (w1 != w2 || h1 != h2) {
+        L_INFO("sizes unequal: (w1,w2) = (%d,%d), (h1,h2) = (%d,%d)\n",
+               procName, w1, w2, h1, h2);
+    }
+    pixZero(pix1, &empty);
+    if (empty) return 0;
+    pixZero(pix2, &empty);
+    if (empty) return 0;
+
+    pix3 = pixCopy(NULL, pix1);
+    pixAnd(pix3, pix3, pix2);
+    pixCountPixels(pix1, &count1, NULL);  /* |1| */
+    pixCountPixels(pix3, &count3, NULL);  /* |1 & 2| */
+    *pfract = (l_float32)count3 / (l_float32)count1;
+    pixDestroy(&pix3);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                           Clip to Foreground                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixClipToForeground()
+ *
+ *      Input:  pixs (1 bpp)
+ *              &pixd  (<optional return> clipped pix returned)
+ *              &box   (<optional return> bounding box)
+ *      Return: 0 if OK; 1 on error or if there are no fg pixels
+ *
+ *  Notes:
+ *      (1) At least one of {&pixd, &box} must be specified.
+ *      (2) If there are no fg pixels, the returned ptrs are null.
+ */
+l_int32
+pixClipToForeground(PIX   *pixs,
+                    PIX  **ppixd,
+                    BOX  **pbox)
+{
+l_int32    w, h, wpl, nfullwords, extra, i, j;
+l_int32    minx, miny, maxx, maxy;
+l_uint32   result, mask;
+l_uint32  *data, *line;
+BOX       *box;
+
+    PROCNAME("pixClipToForeground");
+
+    if (ppixd) *ppixd = NULL;
+    if (pbox) *pbox = NULL;
+    if (!ppixd && !pbox)
+        return ERROR_INT("no output requested", procName, 1);
+    if (!pixs || (pixGetDepth(pixs) != 1))
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    nfullwords = w / 32;
+    extra = w & 31;
+    mask = ~rmask32[32 - extra];
+    wpl = pixGetWpl(pixs);
+    data = pixGetData(pixs);
+
+    result = 0;
+    for (i = 0, miny = 0; i < h; i++, miny++) {
+        line = data + i * wpl;
+        for (j = 0; j < nfullwords; j++)
+            result |= line[j];
+        if (extra)
+            result |= (line[j] & mask);
+        if (result)
+            break;
+    }
+    if (miny == h)  /* no ON pixels */
+        return 1;
+
+    result = 0;
+    for (i = h - 1, maxy = h - 1; i >= 0; i--, maxy--) {
+        line = data + i * wpl;
+        for (j = 0; j < nfullwords; j++)
+            result |= line[j];
+        if (extra)
+            result |= (line[j] & mask);
+        if (result)
+            break;
+    }
+
+    minx = 0;
+    for (j = 0, minx = 0; j < w; j++, minx++) {
+        for (i = 0; i < h; i++) {
+            line = data + i * wpl;
+            if (GET_DATA_BIT(line, j))
+                goto minx_found;
+        }
+    }
+
+minx_found:
+    for (j = w - 1, maxx = w - 1; j >= 0; j--, maxx--) {
+        for (i = 0; i < h; i++) {
+            line = data + i * wpl;
+            if (GET_DATA_BIT(line, j))
+                goto maxx_found;
+        }
+    }
+
+maxx_found:
+    box = boxCreate(minx, miny, maxx - minx + 1, maxy - miny + 1);
+
+    if (ppixd)
+        *ppixd = pixClipRectangle(pixs, box, NULL);
+    if (pbox)
+        *pbox = box;
+    else
+        boxDestroy(&box);
+
+    return 0;
+}
+
+
+/*!
+ *  pixTestClipToForeground()
+ *
+ *      Input:  pixs (1 bpp)
+ *              &canclip (<return> 1 if fg does not extend to all four edges)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This is a lightweight test to determine if a 1 bpp image
+ *          can be further cropped without loss of fg pixels.
+ *          If it cannot, canclip is set to 0.
+ *      (2) It does not test for the existence of any fg pixels.
+ *          If there are no fg pixels, it will return @canclip = 1.
+ *          Check the output of the subsequent call to pixClipToForeground().
+ */
+l_int32
+pixTestClipToForeground(PIX      *pixs,
+                        l_int32  *pcanclip)
+{
+l_int32    i, j, w, h, wpl, found;
+l_uint32  *data, *line;
+
+    PROCNAME("pixTestClipToForeground");
+
+    if (!pcanclip)
+        return ERROR_INT("&canclip not defined", procName, 1);
+    *pcanclip = 0;
+    if (!pixs || (pixGetDepth(pixs) != 1))
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+        /* Check top and bottom raster lines */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    found = FALSE;
+    for (j = 0; found == FALSE && j < w; j++)
+        found = GET_DATA_BIT(data, j);
+    if (!found) {
+        *pcanclip = 1;
+        return 0;
+    }
+
+    line = data + (h - 1) * wpl;
+    found = FALSE;
+    for (j = 0; found == FALSE && j < w; j++)
+        found = GET_DATA_BIT(data, j);
+    if (!found) {
+        *pcanclip = 1;
+        return 0;
+    }
+
+        /* Check left and right edges */
+    found = FALSE;
+    for (i = 0, line = data; found == FALSE && i < h; line += wpl, i++)
+        found = GET_DATA_BIT(line, 0);
+    if (!found) {
+        *pcanclip = 1;
+        return 0;
+    }
+
+    found = FALSE;
+    for (i = 0, line = data; found == FALSE && i < h; line += wpl, i++)
+        found = GET_DATA_BIT(line, w - 1);
+    if (!found)
+        *pcanclip = 1;
+
+    return 0;  /* fg pixels found on all edges */
+}
+
+
+/*!
+ *  pixClipBoxToForeground()
+ *
+ *      Input:  pixs (1 bpp)
+ *              boxs  (<optional> ; use full image if null)
+ *              &pixd  (<optional return> clipped pix returned)
+ *              &boxd  (<optional return> bounding box)
+ *      Return: 0 if OK; 1 on error or if there are no fg pixels
+ *
+ *  Notes:
+ *      (1) At least one of {&pixd, &boxd} must be specified.
+ *      (2) If there are no fg pixels, the returned ptrs are null.
+ *      (3) Do not use &pixs for the 3rd arg or &boxs for the 4th arg;
+ *          this will leak memory.
+ */
+l_int32
+pixClipBoxToForeground(PIX   *pixs,
+                       BOX   *boxs,
+                       PIX  **ppixd,
+                       BOX  **pboxd)
+{
+l_int32  w, h, bx, by, bw, bh, cbw, cbh, left, right, top, bottom;
+BOX     *boxt, *boxd;
+
+    PROCNAME("pixClipBoxToForeground");
+
+    if (ppixd) *ppixd = NULL;
+    if (pboxd) *pboxd = NULL;
+    if (!ppixd && !pboxd)
+        return ERROR_INT("no output requested", procName, 1);
+    if (!pixs || (pixGetDepth(pixs) != 1))
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+    if (!boxs)
+        return pixClipToForeground(pixs, ppixd, pboxd);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    boxGetGeometry(boxs, &bx, &by, &bw, &bh);
+    cbw = L_MIN(bw, w - bx);
+    cbh = L_MIN(bh, h - by);
+    if (cbw < 0 || cbh < 0)
+        return ERROR_INT("box not within image", procName, 1);
+    boxt = boxCreate(bx, by, cbw, cbh);
+
+    if (pixScanForForeground(pixs, boxt, L_FROM_LEFT, &left)) {
+        boxDestroy(&boxt);
+        return 1;
+    }
+    pixScanForForeground(pixs, boxt, L_FROM_RIGHT, &right);
+    pixScanForForeground(pixs, boxt, L_FROM_TOP, &top);
+    pixScanForForeground(pixs, boxt, L_FROM_BOT, &bottom);
+
+    boxd = boxCreate(left, top, right - left + 1, bottom - top + 1);
+    if (ppixd)
+        *ppixd = pixClipRectangle(pixs, boxd, NULL);
+    if (pboxd)
+        *pboxd = boxd;
+    else
+        boxDestroy(&boxd);
+
+    boxDestroy(&boxt);
+    return 0;
+}
+
+
+/*!
+ *  pixScanForForeground()
+ *
+ *      Input:  pixs (1 bpp)
+ *              box  (<optional> within which the search is conducted)
+ *              scanflag (direction of scan; e.g., L_FROM_LEFT)
+ *              &loc (location in scan direction of first black pixel)
+ *      Return: 0 if OK; 1 on error or if no fg pixels are found
+ *
+ *  Notes:
+ *      (1) If there are no fg pixels, the position is set to 0.
+ *          Caller must check the return value!
+ *      (2) Use @box == NULL to scan from edge of pixs
+ */
+l_int32
+pixScanForForeground(PIX      *pixs,
+                     BOX      *box,
+                     l_int32   scanflag,
+                     l_int32  *ploc)
+{
+l_int32    bx, by, bw, bh, x, xstart, xend, y, ystart, yend, wpl;
+l_uint32  *data, *line;
+BOX       *boxt;
+
+    PROCNAME("pixScanForForeground");
+
+    if (!ploc)
+        return ERROR_INT("&loc not defined", procName, 1);
+    *ploc = 0;
+    if (!pixs || (pixGetDepth(pixs) != 1))
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+        /* Clip box to pixs if it exists */
+    pixGetDimensions(pixs, &bw, &bh, NULL);
+    if (box) {
+        if ((boxt = boxClipToRectangle(box, bw, bh)) == NULL)
+            return ERROR_INT("invalid box", procName, 1);
+        boxGetGeometry(boxt, &bx, &by, &bw, &bh);
+        boxDestroy(&boxt);
+    } else {
+        bx = by = 0;
+    }
+    xstart = bx;
+    ystart = by;
+    xend = bx + bw - 1;
+    yend = by + bh - 1;
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    if (scanflag == L_FROM_LEFT) {
+        for (x = xstart; x <= xend; x++) {
+            for (y = ystart; y <= yend; y++) {
+                line = data + y * wpl;
+                if (GET_DATA_BIT(line, x)) {
+                    *ploc = x;
+                    return 0;
+                }
+            }
+        }
+    } else if (scanflag == L_FROM_RIGHT) {
+        for (x = xend; x >= xstart; x--) {
+            for (y = ystart; y <= yend; y++) {
+                line = data + y * wpl;
+                if (GET_DATA_BIT(line, x)) {
+                    *ploc = x;
+                    return 0;
+                }
+            }
+        }
+    } else if (scanflag == L_FROM_TOP) {
+        for (y = ystart; y <= yend; y++) {
+            line = data + y * wpl;
+            for (x = xstart; x <= xend; x++) {
+                if (GET_DATA_BIT(line, x)) {
+                    *ploc = y;
+                    return 0;
+                }
+            }
+        }
+    } else if (scanflag == L_FROM_BOT) {
+        for (y = yend; y >= ystart; y--) {
+            line = data + y * wpl;
+            for (x = xstart; x <= xend; x++) {
+                if (GET_DATA_BIT(line, x)) {
+                    *ploc = y;
+                    return 0;
+                }
+            }
+        }
+    } else {
+        return ERROR_INT("invalid scanflag", procName, 1);
+    }
+
+    return 1;  /* no fg found */
+}
+
+
+/*!
+ *  pixClipBoxToEdges()
+ *
+ *      Input:  pixs (1 bpp)
+ *              boxs  (<optional> ; use full image if null)
+ *              lowthresh (threshold to choose clipping location)
+ *              highthresh (threshold required to find an edge)
+ *              maxwidth (max allowed width between low and high thresh locs)
+ *              factor (sampling factor along pixel counting direction)
+ *              &pixd  (<optional return> clipped pix returned)
+ *              &boxd  (<optional return> bounding box)
+ *      Return: 0 if OK; 1 on error or if a fg edge is not found from
+ *              all four sides.
+ *
+ *  Notes:
+ *      (1) At least one of {&pixd, &boxd} must be specified.
+ *      (2) If there are no fg pixels, the returned ptrs are null.
+ *      (3) This function attempts to locate rectangular "image" regions
+ *          of high-density fg pixels, that have well-defined edges
+ *          on the four sides.
+ *      (4) Edges are searched for on each side, iterating in order
+ *          from left, right, top and bottom.  As each new edge is
+ *          found, the search box is resized to use that location.
+ *          Once an edge is found, it is held.  If no more edges
+ *          are found in one iteration, the search fails.
+ *      (5) See pixScanForEdge() for usage of the thresholds and @maxwidth.
+ *      (6) The thresholds must be at least 1, and the low threshold
+ *          cannot be larger than the high threshold.
+ *      (7) If the low and high thresholds are both 1, this is equivalent
+ *          to pixClipBoxToForeground().
+ */
+l_int32
+pixClipBoxToEdges(PIX     *pixs,
+                  BOX     *boxs,
+                  l_int32  lowthresh,
+                  l_int32  highthresh,
+                  l_int32  maxwidth,
+                  l_int32  factor,
+                  PIX    **ppixd,
+                  BOX    **pboxd)
+{
+l_int32  w, h, bx, by, bw, bh, cbw, cbh, left, right, top, bottom;
+l_int32  lfound, rfound, tfound, bfound, change;
+BOX     *boxt, *boxd;
+
+    PROCNAME("pixClipBoxToEdges");
+
+    if (ppixd) *ppixd = NULL;
+    if (pboxd) *pboxd = NULL;
+    if (!ppixd && !pboxd)
+        return ERROR_INT("no output requested", procName, 1);
+    if (!pixs || (pixGetDepth(pixs) != 1))
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (lowthresh < 1 || highthresh < 1 ||
+        lowthresh > highthresh || maxwidth < 1)
+        return ERROR_INT("invalid thresholds", procName, 1);
+    factor = L_MIN(1, factor);
+
+    if (lowthresh == 1 && highthresh == 1)
+        return pixClipBoxToForeground(pixs, boxs, ppixd, pboxd);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (boxs) {
+        boxGetGeometry(boxs, &bx, &by, &bw, &bh);
+        cbw = L_MIN(bw, w - bx);
+        cbh = L_MIN(bh, h - by);
+        if (cbw < 0 || cbh < 0)
+            return ERROR_INT("box not within image", procName, 1);
+        boxt = boxCreate(bx, by, cbw, cbh);
+    } else {
+        boxt = boxCreate(0, 0, w, h);
+    }
+
+    lfound = rfound = tfound = bfound = 0;
+    while (!lfound || !rfound || !tfound || !bfound) {
+        change = 0;
+        if (!lfound) {
+            if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth,
+                                factor, L_FROM_LEFT, &left)) {
+                lfound = 1;
+                change = 1;
+                boxRelocateOneSide(boxt, boxt, left, L_FROM_LEFT);
+            }
+        }
+        if (!rfound) {
+            if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth,
+                                factor, L_FROM_RIGHT, &right)) {
+                rfound = 1;
+                change = 1;
+                boxRelocateOneSide(boxt, boxt, right, L_FROM_RIGHT);
+            }
+        }
+        if (!tfound) {
+            if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth,
+                                factor, L_FROM_TOP, &top)) {
+                tfound = 1;
+                change = 1;
+                boxRelocateOneSide(boxt, boxt, top, L_FROM_TOP);
+            }
+        }
+        if (!bfound) {
+            if (!pixScanForEdge(pixs, boxt, lowthresh, highthresh, maxwidth,
+                                factor, L_FROM_BOT, &bottom)) {
+                bfound = 1;
+                change = 1;
+                boxRelocateOneSide(boxt, boxt, bottom, L_FROM_BOT);
+            }
+        }
+
+#if DEBUG_EDGES
+        fprintf(stderr, "iter: %d %d %d %d\n", lfound, rfound, tfound, bfound);
+#endif  /* DEBUG_EDGES */
+
+        if (change == 0) break;
+    }
+    boxDestroy(&boxt);
+
+    if (change == 0)
+        return ERROR_INT("not all edges found", procName, 1);
+
+    boxd = boxCreate(left, top, right - left + 1, bottom - top + 1);
+    if (ppixd)
+        *ppixd = pixClipRectangle(pixs, boxd, NULL);
+    if (pboxd)
+        *pboxd = boxd;
+    else
+        boxDestroy(&boxd);
+
+    return 0;
+}
+
+
+/*!
+ *  pixScanForEdge()
+ *
+ *      Input:  pixs (1 bpp)
+ *              box  (<optional> within which the search is conducted)
+ *              lowthresh (threshold to choose clipping location)
+ *              highthresh (threshold required to find an edge)
+ *              maxwidth (max allowed width between low and high thresh locs)
+ *              factor (sampling factor along pixel counting direction)
+ *              scanflag (direction of scan; e.g., L_FROM_LEFT)
+ *              &loc (<return> location in scan direction of first black pixel)
+ *      Return: 0 if OK; 1 on error or if the edge is not found
+ *
+ *  Notes:
+ *      (1) If there are no fg pixels, the position is set to 0.
+ *          Caller must check the return value!
+ *      (2) Use @box == NULL to scan from edge of pixs
+ *      (3) As the scan progresses, the location where the sum of
+ *          pixels equals or excees @lowthresh is noted (loc).  The
+ *          scan is stopped when the sum of pixels equals or exceeds
+ *          @highthresh.  If the scan distance between loc and that
+ *          point does not exceed @maxwidth, an edge is found and
+ *          its position is taken to be loc.  @maxwidth implicitly
+ *          sets a minimum on the required gradient of the edge.
+ *      (4) The thresholds must be at least 1, and the low threshold
+ *          cannot be larger than the high threshold.
+ */
+l_int32
+pixScanForEdge(PIX      *pixs,
+               BOX      *box,
+               l_int32   lowthresh,
+               l_int32   highthresh,
+               l_int32   maxwidth,
+               l_int32   factor,
+               l_int32   scanflag,
+               l_int32  *ploc)
+{
+l_int32    bx, by, bw, bh, foundmin, loc, sum, wpl;
+l_int32    x, xstart, xend, y, ystart, yend;
+l_uint32  *data, *line;
+BOX       *boxt;
+
+    PROCNAME("pixScanForEdge");
+
+    if (!ploc)
+        return ERROR_INT("&ploc not defined", procName, 1);
+    *ploc = 0;
+    if (!pixs || (pixGetDepth(pixs) != 1))
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (lowthresh < 1 || highthresh < 1 ||
+        lowthresh > highthresh || maxwidth < 1)
+        return ERROR_INT("invalid thresholds", procName, 1);
+    factor = L_MIN(1, factor);
+
+        /* Clip box to pixs if it exists */
+    pixGetDimensions(pixs, &bw, &bh, NULL);
+    if (box) {
+        if ((boxt = boxClipToRectangle(box, bw, bh)) == NULL)
+            return ERROR_INT("invalid box", procName, 1);
+        boxGetGeometry(boxt, &bx, &by, &bw, &bh);
+        boxDestroy(&boxt);
+    } else {
+        bx = by = 0;
+    }
+    xstart = bx;
+    ystart = by;
+    xend = bx + bw - 1;
+    yend = by + bh - 1;
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    foundmin = 0;
+    if (scanflag == L_FROM_LEFT) {
+        for (x = xstart; x <= xend; x++) {
+            sum = 0;
+            for (y = ystart; y <= yend; y += factor) {
+                line = data + y * wpl;
+                if (GET_DATA_BIT(line, x))
+                    sum++;
+            }
+            if (!foundmin && sum < lowthresh)
+                continue;
+            if (!foundmin) {  /* save the loc of the beginning of the edge */
+                foundmin = 1;
+                loc = x;
+            }
+            if (sum >= highthresh) {
+#if DEBUG_EDGES
+                fprintf(stderr, "Left: x = %d, loc = %d\n", x, loc);
+#endif  /* DEBUG_EDGES */
+                if (x - loc < maxwidth) {
+                    *ploc = loc;
+                    return 0;
+                } else {
+                  return 1;
+                }
+            }
+        }
+    } else if (scanflag == L_FROM_RIGHT) {
+        for (x = xend; x >= xstart; x--) {
+            sum = 0;
+            for (y = ystart; y <= yend; y += factor) {
+                line = data + y * wpl;
+                if (GET_DATA_BIT(line, x))
+                    sum++;
+            }
+            if (!foundmin && sum < lowthresh)
+                continue;
+            if (!foundmin) {
+                foundmin = 1;
+                loc = x;
+            }
+            if (sum >= highthresh) {
+#if DEBUG_EDGES
+                fprintf(stderr, "Right: x = %d, loc = %d\n", x, loc);
+#endif  /* DEBUG_EDGES */
+                if (loc - x < maxwidth) {
+                    *ploc = loc;
+                    return 0;
+                } else {
+                  return 1;
+                }
+            }
+        }
+    } else if (scanflag == L_FROM_TOP) {
+        for (y = ystart; y <= yend; y++) {
+            sum = 0;
+            line = data + y * wpl;
+            for (x = xstart; x <= xend; x += factor) {
+                if (GET_DATA_BIT(line, x))
+                    sum++;
+            }
+            if (!foundmin && sum < lowthresh)
+                continue;
+            if (!foundmin) {
+                foundmin = 1;
+                loc = y;
+            }
+            if (sum >= highthresh) {
+#if DEBUG_EDGES
+                fprintf(stderr, "Top: y = %d, loc = %d\n", y, loc);
+#endif  /* DEBUG_EDGES */
+                if (y - loc < maxwidth) {
+                    *ploc = loc;
+                    return 0;
+                } else {
+                  return 1;
+                }
+            }
+        }
+    } else if (scanflag == L_FROM_BOT) {
+        for (y = yend; y >= ystart; y--) {
+            sum = 0;
+            line = data + y * wpl;
+            for (x = xstart; x <= xend; x += factor) {
+                if (GET_DATA_BIT(line, x))
+                    sum++;
+            }
+            if (!foundmin && sum < lowthresh)
+                continue;
+            if (!foundmin) {
+                foundmin = 1;
+                loc = y;
+            }
+            if (sum >= highthresh) {
+#if DEBUG_EDGES
+                fprintf(stderr, "Bottom: y = %d, loc = %d\n", y, loc);
+#endif  /* DEBUG_EDGES */
+                if (loc - y < maxwidth) {
+                    *ploc = loc;
+                    return 0;
+                } else {
+                  return 1;
+                }
+            }
+        }
+    } else {
+        return ERROR_INT("invalid scanflag", procName, 1);
+    }
+
+    return 1;  /* edge not found */
+}
+
+
+/*---------------------------------------------------------------------*
+ *           Extract pixel averages and reversals along lines          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixExtractOnLine()
+ *
+ *      Input:  pixs (1 bpp or 8 bpp; no colormap)
+ *              x1, y1 (one end point for line)
+ *              x2, y2 (another end pt for line)
+ *              factor (sampling; >= 1)
+ *      Return: na (of pixel values along line), or null on error.
+ *
+ *  Notes:
+ *      (1) Input end points are clipped to the pix.
+ *      (2) If the line is either horizontal, or closer to horizontal
+ *          than to vertical, the points will be extracted from left
+ *          to right in the pix.  Likewise, if the line is vertical,
+ *          or closer to vertical than to horizontal, the points will
+ *          be extracted from top to bottom.
+ *      (3) Can be used with numaCountReverals(), for example, to
+ *          characterize the intensity smoothness along a line.
+ */
+NUMA *
+pixExtractOnLine(PIX     *pixs,
+                 l_int32  x1,
+                 l_int32  y1,
+                 l_int32  x2,
+                 l_int32  y2,
+                 l_int32  factor)
+{
+l_int32    i, w, h, d, xmin, ymin, xmax, ymax, npts, direction;
+l_uint32   val;
+l_float32  x, y;
+l_float64  slope;
+NUMA      *na;
+PTA       *pta;
+
+    PROCNAME("pixExtractOnLine");
+
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 8)
+        return (NUMA *)ERROR_PTR("d not 1 or 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (NUMA *)ERROR_PTR("pixs has a colormap", procName, NULL);
+    if (factor < 1) {
+        L_WARNING("factor must be >= 1; setting to 1\n", procName);
+        factor = 1;
+    }
+
+        /* Clip line to the image */
+    x1 = L_MAX(0, L_MIN(x1, w - 1));
+    x2 = L_MAX(0, L_MIN(x2, w - 1));
+    y1 = L_MAX(0, L_MIN(y1, h - 1));
+    y2 = L_MAX(0, L_MIN(y2, h - 1));
+
+    if (x1 == x2 && y1 == y2) {
+        pixGetPixel(pixs, x1, y1, &val);
+        na = numaCreate(1);
+        numaAddNumber(na, val);
+        return na;
+    }
+
+    if (y1 == y2)
+        direction = L_HORIZONTAL_LINE;
+    else if (x1 == x2)
+        direction = L_VERTICAL_LINE;
+    else
+        direction = L_OBLIQUE_LINE;
+
+    na = numaCreate(0);
+    if (direction == L_HORIZONTAL_LINE) {  /* plot against x */
+        xmin = L_MIN(x1, x2);
+        xmax = L_MAX(x1, x2);
+        numaSetParameters(na, xmin, factor);
+        for (i = xmin; i <= xmax; i += factor) {
+            pixGetPixel(pixs, i, y1, &val);
+            numaAddNumber(na, val);
+        }
+    } else if (direction == L_VERTICAL_LINE) {  /* plot against y */
+        ymin = L_MIN(y1, y2);
+        ymax = L_MAX(y1, y2);
+        numaSetParameters(na, ymin, factor);
+        for (i = ymin; i <= ymax; i += factor) {
+            pixGetPixel(pixs, x1, i, &val);
+            numaAddNumber(na, val);
+        }
+    } else {  /* direction == L_OBLIQUE_LINE */
+        slope = (l_float64)((y2 - y1) / (x2 - x1));
+        if (L_ABS(slope) < 1.0) {  /* quasi-horizontal */
+            xmin = L_MIN(x1, x2);
+            xmax = L_MAX(x1, x2);
+            ymin = (xmin == x1) ? y1 : y2;  /* pt that goes with xmin */
+            ymax = (ymin == y1) ? y2 : y1;  /* pt that goes with xmax */
+            pta = generatePtaLine(xmin, ymin, xmax, ymax);
+            numaSetParameters(na, xmin, (l_float32)factor);
+        } else {  /* quasi-vertical */
+            ymin = L_MIN(y1, y2);
+            ymax = L_MAX(y1, y2);
+            xmin = (ymin == y1) ? x1 : x2;  /* pt that goes with ymin */
+            xmax = (xmin == x1) ? x2 : x1;  /* pt that goes with ymax */
+            pta = generatePtaLine(xmin, ymin, xmax, ymax);
+            numaSetParameters(na, ymin, (l_float32)factor);
+        }
+        npts = ptaGetCount(pta);
+        for (i = 0; i < npts; i += factor) {
+            ptaGetPt(pta, i, &x, &y);
+            pixGetPixel(pixs, (l_int32)x, (l_int32)y, &val);
+            numaAddNumber(na, val);
+        }
+
+#if 0  /* debugging */
+        pixPlotAlongPta(pixs, pta, GPLOT_X11, NULL);
+#endif
+
+        ptaDestroy(&pta);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixAverageOnLine()
+ *
+ *      Input:  pixs (1 bpp or 8 bpp; no colormap)
+ *              x1, y1 (starting pt for line)
+ *              x2, y2 (end pt for line)
+ *              factor (sampling; >= 1)
+ *      Return: average of pixel values along line, or null on error.
+ *
+ *  Notes:
+ *      (1) The line must be either horizontal or vertical, so either
+ *          y1 == y2 (horizontal) or x1 == x2 (vertical).
+ *      (2) If horizontal, x1 must be <= x2.
+ *          If vertical, y1 must be <= y2.
+ *          characterize the intensity smoothness along a line.
+ *      (3) Input end points are clipped to the pix.
+ */
+l_float32
+pixAverageOnLine(PIX     *pixs,
+                 l_int32  x1,
+                 l_int32  y1,
+                 l_int32  x2,
+                 l_int32  y2,
+                 l_int32  factor)
+{
+l_int32    i, j, w, h, d, direction, count, wpl;
+l_uint32  *data, *line;
+l_float32  sum;
+
+    PROCNAME("pixAverageOnLine");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 8)
+        return ERROR_INT("d not 1 or 8 bpp", procName, 1);
+    if (pixGetColormap(pixs))
+        return ERROR_INT("pixs has a colormap", procName, 1);
+    if (x1 > x2 || y1 > y2)
+        return ERROR_INT("x1 > x2 or y1 > y2", procName, 1);
+
+    if (y1 == y2) {
+        x1 = L_MAX(0, x1);
+        x2 = L_MIN(w - 1, x2);
+        y1 = L_MAX(0, L_MIN(y1, h - 1));
+        direction = L_HORIZONTAL_LINE;
+    } else if (x1 == x2) {
+        y1 = L_MAX(0, y1);
+        y2 = L_MIN(h - 1, y2);
+        x1 = L_MAX(0, L_MIN(x1, w - 1));
+        direction = L_VERTICAL_LINE;
+    } else {
+        return ERROR_INT("line neither horiz nor vert", procName, 1);
+    }
+
+    if (factor < 1) {
+        L_WARNING("factor must be >= 1; setting to 1\n", procName);
+        factor = 1;
+    }
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    sum = 0;
+    if (direction == L_HORIZONTAL_LINE) {
+        line = data + y1 * wpl;
+        for (j = x1, count = 0; j <= x2; count++, j += factor) {
+            if (d == 1)
+                sum += GET_DATA_BIT(line, j);
+            else  /* d == 8 */
+                sum += GET_DATA_BYTE(line, j);
+        }
+    } else if (direction == L_VERTICAL_LINE) {
+        for (i = y1, count = 0; i <= y2; count++, i += factor) {
+            line = data + i * wpl;
+            if (d == 1)
+                sum += GET_DATA_BIT(line, x1);
+            else  /* d == 8 */
+                sum += GET_DATA_BYTE(line, x1);
+        }
+    }
+
+    return sum / (l_float32)count;
+}
+
+
+/*!
+ *  pixAverageIntensityProfile()
+ *
+ *      Input:  pixs (any depth; colormap OK)
+ *              fract (fraction of image width or height to be used)
+ *              dir (averaging direction: L_HORIZONTAL_LINE or L_VERTICAL_LINE)
+ *              first, last (span of rows or columns to measure)
+ *              factor1 (sampling along fast scan direction; >= 1)
+ *              factor2 (sampling along slow scan direction; >= 1)
+ *      Return: na (of reversal profile), or null on error.
+ *
+ *  Notes:
+ *      (1) If d != 1 bpp, colormaps are removed and the result
+ *          is converted to 8 bpp.
+ *      (2) If @dir == L_HORIZONTAL_LINE, the intensity is averaged
+ *          along each horizontal raster line (sampled by @factor1),
+ *          and the profile is the array of these averages in the
+ *          vertical direction between @first and @last raster lines,
+ *          and sampled by @factor2.
+ *      (3) If @dir == L_VERTICAL_LINE, the intensity is averaged
+ *          along each vertical line (sampled by @factor1),
+ *          and the profile is the array of these averages in the
+ *          horizontal direction between @first and @last columns,
+ *          and sampled by @factor2.
+ *      (4) The averages are measured over the central @fract of the image.
+ *          Use @fract == 1.0 to average across the entire width or height.
+ */
+NUMA *
+pixAverageIntensityProfile(PIX       *pixs,
+                           l_float32  fract,
+                           l_int32    dir,
+                           l_int32    first,
+                           l_int32    last,
+                           l_int32    factor1,
+                           l_int32    factor2)
+{
+l_int32    i, j, w, h, d, start, end;
+l_float32  ave;
+NUMA      *nad;
+PIX       *pixr, *pixg;
+
+    PROCNAME("pixAverageIntensityProfile");
+
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (fract < 0.0 || fract > 1.0)
+        return (NUMA *)ERROR_PTR("fract < 0.0 or > 1.0", procName, NULL);
+    if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE)
+        return (NUMA *)ERROR_PTR("invalid direction", procName, NULL);
+    if (first < 0) first = 0;
+    if (last < first)
+        return (NUMA *)ERROR_PTR("last must be >= first", procName, NULL);
+    if (factor1 < 1) {
+        L_WARNING("factor1 must be >= 1; setting to 1\n", procName);
+        factor1 = 1;
+    }
+    if (factor2 < 1) {
+        L_WARNING("factor2 must be >= 1; setting to 1\n", procName);
+        factor2 = 1;
+    }
+
+        /* Use 1 or 8 bpp, without colormap */
+    if (pixGetColormap(pixs))
+        pixr = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else
+        pixr = pixClone(pixs);
+    pixGetDimensions(pixr, &w, &h, &d);
+    if (d == 1)
+        pixg = pixClone(pixr);
+    else
+        pixg = pixConvertTo8(pixr, 0);
+
+    nad = numaCreate(0);  /* output: samples in slow scan direction */
+    numaSetParameters(nad, 0, factor2);
+    if (dir == L_HORIZONTAL_LINE) {
+        start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)w);
+        end = w - start;
+        if (last > h - 1) {
+            L_WARNING("last > h - 1; clipping\n", procName);
+            last = h - 1;
+        }
+        for (i = first; i <= last; i += factor2) {
+            ave = pixAverageOnLine(pixg, start, i, end, i, factor1);
+            numaAddNumber(nad, ave);
+        }
+    } else if (dir == L_VERTICAL_LINE) {
+        start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)h);
+        end = h - start;
+        if (last > w - 1) {
+            L_WARNING("last > w - 1; clipping\n", procName);
+            last = w - 1;
+        }
+        for (j = first; j <= last; j += factor2) {
+            ave = pixAverageOnLine(pixg, j, start, j, end, factor1);
+            numaAddNumber(nad, ave);
+        }
+    }
+
+    pixDestroy(&pixr);
+    pixDestroy(&pixg);
+    return nad;
+}
+
+
+/*!
+ *  pixReversalProfile()
+ *
+ *      Input:  pixs (any depth; colormap OK)
+ *              fract (fraction of image width or height to be used)
+ *              dir (profile direction: L_HORIZONTAL_LINE or L_VERTICAL_LINE)
+ *              first, last (span of rows or columns to measure)
+ *              minreversal (minimum change in intensity to trigger a reversal)
+ *              factor1 (sampling along raster line (fast scan); >= 1)
+ *              factor2 (sampling of raster lines (slow scan); >= 1)
+ *      Return: na (of reversal profile), or null on error.
+ *
+ *  Notes:
+ *      (1) If d != 1 bpp, colormaps are removed and the result
+ *          is converted to 8 bpp.
+ *      (2) If @dir == L_HORIZONTAL_LINE, the the reversals are counted
+ *          along each horizontal raster line (sampled by @factor1),
+ *          and the profile is the array of these sums in the
+ *          vertical direction between @first and @last raster lines,
+ *          and sampled by @factor2.
+ *      (3) If @dir == L_VERTICAL_LINE, the the reversals are counted
+ *          along each vertical column (sampled by @factor1),
+ *          and the profile is the array of these sums in the
+ *          horizontal direction between @first and @last columns,
+ *          and sampled by @factor2.
+ *      (4) For each row or column, the reversals are summed over the
+ *          central @fract of the image.  Use @fract == 1.0 to sum
+ *          across the entire width (of row) or height (of column).
+ *      (5) @minreversal is the relative change in intensity that is
+ *          required to resolve peaks and valleys.  A typical number for
+ *          locating text in 8 bpp might be 50.  For 1 bpp, minreversal
+ *          must be 1.
+ *      (6) The reversal profile is simply the number of reversals
+ *          in a row or column, vs the row or column index.
+ */
+NUMA *
+pixReversalProfile(PIX       *pixs,
+                   l_float32  fract,
+                   l_int32    dir,
+                   l_int32    first,
+                   l_int32    last,
+                   l_int32    minreversal,
+                   l_int32    factor1,
+                   l_int32    factor2)
+{
+l_int32   i, j, w, h, d, start, end, nr;
+NUMA     *naline, *nad;
+PIX      *pixr, *pixg;
+
+    PROCNAME("pixReversalProfile");
+
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (fract < 0.0 || fract > 1.0)
+        return (NUMA *)ERROR_PTR("fract < 0.0 or > 1.0", procName, NULL);
+    if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE)
+        return (NUMA *)ERROR_PTR("invalid direction", procName, NULL);
+    if (first < 0) first = 0;
+    if (last < first)
+        return (NUMA *)ERROR_PTR("last must be >= first", procName, NULL);
+    if (factor1 < 1) {
+        L_WARNING("factor1 must be >= 1; setting to 1\n", procName);
+        factor1 = 1;
+    }
+    if (factor2 < 1) {
+        L_WARNING("factor2 must be >= 1; setting to 1\n", procName);
+        factor2 = 1;
+    }
+
+        /* Use 1 or 8 bpp, without colormap */
+    if (pixGetColormap(pixs))
+        pixr = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else
+        pixr = pixClone(pixs);
+    pixGetDimensions(pixr, &w, &h, &d);
+    if (d == 1) {
+        pixg = pixClone(pixr);
+        minreversal = 1;  /* enforce this */
+    } else {
+        pixg = pixConvertTo8(pixr, 0);
+    }
+
+    nad = numaCreate(0);  /* output: samples in slow scan direction */
+    numaSetParameters(nad, 0, factor2);
+    if (dir == L_HORIZONTAL_LINE) {
+        start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)w);
+        end = w - start;
+        if (last > h - 1) {
+            L_WARNING("last > h - 1; clipping\n", procName);
+            last = h - 1;
+        }
+        for (i = first; i <= last; i += factor2) {
+            naline = pixExtractOnLine(pixg, start, i, end, i, factor1);
+            numaCountReversals(naline, minreversal, &nr, NULL);
+            numaAddNumber(nad, nr);
+            numaDestroy(&naline);
+        }
+    } else if (dir == L_VERTICAL_LINE) {
+        start = (l_int32)(0.5 * (1.0 - fract) * (l_float32)h);
+        end = h - start;
+        if (last > w - 1) {
+            L_WARNING("last > w - 1; clipping\n", procName);
+            last = w - 1;
+        }
+        for (j = first; j <= last; j += factor2) {
+            naline = pixExtractOnLine(pixg, j, start, j, end, factor1);
+            numaCountReversals(naline, minreversal, &nr, NULL);
+            numaAddNumber(nad, nr);
+            numaDestroy(&naline);
+        }
+    }
+
+    pixDestroy(&pixr);
+    pixDestroy(&pixg);
+    return nad;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                 Extract windowed variance along a line              *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixWindowedVarianceOnLine()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              dir (L_HORIZONTAL_LINE or L_VERTICAL_LINE)
+ *              loc (location of the constant coordinate for the line)
+ *              c1, c2 (end point coordinates for the line)
+ *              size (window size; must be > 1)
+ *              &nad (<return> windowed square root of variance)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The returned variance array traverses the line starting
+ *          from the smallest coordinate, min(c1,c2).
+ *      (2) Line end points are clipped to pixs.
+ *      (3) The reference point for the variance calculation is the center of
+ *          the window.  Therefore, the numa start parameter from
+ *          pixExtractOnLine() is incremented by @size/2,
+ *          to align the variance values with the pixel coordinate.
+ *      (4) The square root of the variance is the RMS deviation from the mean.
+ */
+l_int32
+pixWindowedVarianceOnLine(PIX     *pixs,
+                          l_int32  dir,
+                          l_int32  loc,
+                          l_int32  c1,
+                          l_int32  c2,
+                          l_int32  size,
+                          NUMA   **pnad)
+{
+l_int32     i, j, w, h, cmin, cmax, maxloc, n, x, y;
+l_uint32    val;
+l_float32   norm, rootvar;
+l_float32  *array;
+l_float64   sum1, sum2, ave, var;
+NUMA       *na1, *nad;
+PTA        *pta;
+
+    PROCNAME("pixWindowedVarianceOnLine");
+
+    if (!pnad)
+        return ERROR_INT("&nad not defined", procName, 1);
+    *pnad = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8bpp", procName, 1);
+    if (size < 2)
+        return ERROR_INT("window size must be > 1", procName, 1);
+    if (dir != L_HORIZONTAL_LINE && dir != L_VERTICAL_LINE)
+        return ERROR_INT("invalid direction", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    maxloc = (dir == L_HORIZONTAL_LINE) ? h - 1 : w - 1;
+    if (loc < 0 || loc > maxloc)
+        return ERROR_INT("invalid line position", procName, 1);
+
+        /* Clip line to the image */
+    cmin = L_MIN(c1, c2);
+    cmax = L_MAX(c1, c2);
+    maxloc = (dir == L_HORIZONTAL_LINE) ? w - 1 : h - 1;
+    cmin = L_MAX(0, L_MIN(cmin, maxloc));
+    cmax = L_MAX(0, L_MIN(cmax, maxloc));
+    n = cmax - cmin + 1;
+
+        /* Generate pta along the line */
+    pta = ptaCreate(n);
+    if (dir == L_HORIZONTAL_LINE) {
+        for (i = cmin; i <= cmax; i++)
+            ptaAddPt(pta, i, loc);
+    } else {  /* vertical line */
+        for (i = cmin; i <= cmax; i++)
+            ptaAddPt(pta, loc, i);
+    }
+
+        /* Get numa of pixel values on the line */
+    na1 = numaCreate(n);
+    numaSetParameters(na1, cmin, 1);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        pixGetPixel(pixs, x, y, &val);
+        numaAddNumber(na1, val);
+    }
+    array = numaGetFArray(na1, L_NOCOPY);
+    ptaDestroy(&pta);
+
+        /* Compute root variance on overlapping windows */
+    nad = numaCreate(n);
+    *pnad = nad;
+    numaSetParameters(nad, cmin + size / 2, 1);
+    norm = 1.0 / (l_float32)size;
+    for (i = 0; i < n - size; i++) {  /* along the line */
+        sum1 = sum2 = 0;
+        for (j = 0; j < size; j++) {  /* over the window */
+            val = array[i + j];
+            sum1 += val;
+            sum2 += val * val;
+        }
+        ave = norm * sum1;
+        var = norm * sum2 - ave * ave;
+        rootvar = (l_float32)sqrt(var);
+        numaAddNumber(nad, rootvar);
+    }
+
+    numaDestroy(&na1);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *              Extract min/max of pixel values near lines             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixMinMaxNearLine()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              x1, y1 (starting pt for line)
+ *              x2, y2 (end pt for line)
+ *              dist (distance to search from line in each direction)
+ *              direction (L_SCAN_NEGATIVE, L_SCAN_POSITIVE, L_SCAN_BOTH)
+ *              &namin (<optional return> minimum values)
+ *              &namax (<optional return> maximum values)
+ *              &minave (<optional return> average of minimum values)
+ *              &maxave (<optional return> average of maximum values)
+ *      Return: 0 if OK; 1 on error or if there are no sampled points
+ *              within the image.
+ *
+ *  Notes:
+ *      (1) If the line is more horizontal than vertical, the values
+ *          are computed for [x1, x2], and the pixels are taken
+ *          below and/or above the local y-value.  Otherwise, the
+ *          values are computed for [y1, y2] and the pixels are taken
+ *          to the left and/or right of the local x value.
+ *      (2) @direction specifies which side (or both sides) of the
+ *          line are scanned for min and max values.
+ *      (3) There are two ways to tell if the returned values of min
+ *          and max averages are valid: the returned values cannot be
+ *          negative and the function must return 0.
+ *      (4) All accessed pixels are clipped to the pix.
+ */
+l_int32
+pixMinMaxNearLine(PIX        *pixs,
+                  l_int32     x1,
+                  l_int32     y1,
+                  l_int32     x2,
+                  l_int32     y2,
+                  l_int32     dist,
+                  l_int32     direction,
+                  NUMA      **pnamin,
+                  NUMA      **pnamax,
+                  l_float32  *pminave,
+                  l_float32  *pmaxave)
+{
+l_int32    i, j, w, h, d, x, y, n, dir, found, minval, maxval, negloc, posloc;
+l_uint32   val;
+l_float32  sum;
+NUMA      *namin, *namax;
+PTA       *pta;
+
+    PROCNAME("pixMinMaxNearLine");
+
+    if (pnamin) *pnamin = NULL;
+    if (pnamax) *pnamax = NULL;
+    if (pminave) *pminave = UNDEF;
+    if (pmaxave) *pmaxave = UNDEF;
+    if (!pnamin && !pnamax && !pminave && !pmaxave)
+        return ERROR_INT("no output requested", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 || pixGetColormap(pixs))
+        return ERROR_INT("pixs not 8 bpp or has colormap", procName, 1);
+    dist = L_ABS(dist);
+    if (direction != L_SCAN_NEGATIVE && direction != L_SCAN_POSITIVE &&
+        direction != L_SCAN_BOTH)
+        return ERROR_INT("invalid direction", procName, 1);
+
+    pta = generatePtaLine(x1, y1, x2, y2);
+    n = ptaGetCount(pta);
+    dir = (L_ABS(x1 - x2) == n - 1) ? L_HORIZ : L_VERT;
+    namin = numaCreate(n);
+    namax = numaCreate(n);
+    negloc = -dist;
+    posloc = dist;
+    if (direction == L_SCAN_NEGATIVE)
+        posloc = 0;
+    else if (direction == L_SCAN_POSITIVE)
+        negloc = 0;
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        minval = 255;
+        maxval = 0;
+        found = FALSE;
+        if (dir == L_HORIZ) {
+            if (x < 0 || x >= w) continue;
+            for (j = negloc; j <= posloc; j++) {
+                if (y + j < 0 || y + j >= h) continue;
+                pixGetPixel(pixs, x, y + j, &val);
+                found = TRUE;
+                if (val < minval) minval = val;
+                if (val > maxval) maxval = val;
+            }
+        } else {  /* dir == L_VERT */
+            if (y < 0 || y >= h) continue;
+            for (j = negloc; j <= posloc; j++) {
+                if (x + j < 0 || x + j >= w) continue;
+                pixGetPixel(pixs, x + j, y, &val);
+                found = TRUE;
+                if (val < minval) minval = val;
+                if (val > maxval) maxval = val;
+            }
+        }
+        if (found) {
+            numaAddNumber(namin, minval);
+            numaAddNumber(namax, maxval);
+        }
+    }
+
+    n = numaGetCount(namin);
+    if (n == 0) {
+        numaDestroy(&namin);
+        numaDestroy(&namax);
+        ptaDestroy(&pta);
+        return ERROR_INT("no output from this line", procName, 1);
+    }
+
+    if (pminave) {
+        numaGetSum(namin, &sum);
+        *pminave = sum / n;
+    }
+    if (pmaxave) {
+        numaGetSum(namax, &sum);
+        *pmaxave = sum / n;
+    }
+    if (pnamin)
+        *pnamin = namin;
+    else
+        numaDestroy(&namin);
+    if (pnamax)
+        *pnamax = namax;
+    else
+        numaDestroy(&namax);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                     Rank row and column transforms                  *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixRankRowTransform()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *      Return: pixd (with pixels sorted in each row, from
+ *                    min to max value)
+ *
+ * Notes:
+ *     (1) The time is O(n) in the number of pixels and runs about
+ *         100 Mpixels/sec on a 3 GHz machine.
+ */
+PIX *
+pixRankRowTransform(PIX  *pixs)
+{
+l_int32    i, j, k, m, w, h, wpl, val;
+l_int32    histo[256];
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixRankRowTransform");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs has a colormap", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixCreateTemplateNoInit(pixs);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpl = pixGetWpl(pixs);
+    for (i = 0; i < h; i++) {
+        memset(histo, 0, 1024);
+        lines = datas + i * wpl;
+        lined = datad + i * wpl;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(lines, j);
+            histo[val]++;
+        }
+        for (m = 0, j = 0; m < 256; m++) {
+            for (k = 0; k < histo[m]; k++, j++)
+                SET_DATA_BYTE(lined, j, m);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixRankColumnTransform()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *      Return: pixd (with pixels sorted in each column, from
+ *                    min to max value)
+ *
+ * Notes:
+ *     (1) The time is O(n) in the number of pixels and runs about
+ *         50 Mpixels/sec on a 3 GHz machine.
+ */
+PIX *
+pixRankColumnTransform(PIX  *pixs)
+{
+l_int32    i, j, k, m, w, h, val;
+l_int32    histo[256];
+void     **lines8, **lined8;
+PIX       *pixd;
+
+    PROCNAME("pixRankColumnTransform");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs has a colormap", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixCreateTemplateNoInit(pixs);
+    lines8 = pixGetLinePtrs(pixs, NULL);
+    lined8 = pixGetLinePtrs(pixd, NULL);
+    for (j = 0; j < w; j++) {
+        memset(histo, 0, 1024);
+        for (i = 0; i < h; i++) {
+            val = GET_DATA_BYTE(lines8[i], j);
+            histo[val]++;
+        }
+        for (m = 0, i = 0; m < 256; m++) {
+            for (k = 0; k < histo[m]; k++, i++)
+                SET_DATA_BYTE(lined8[i], j, m);
+        }
+    }
+
+    LEPT_FREE(lines8);
+    LEPT_FREE(lined8);
+    return pixd;
+}
diff --git a/src/pixabasic.c b/src/pixabasic.c
new file mode 100644 (file)
index 0000000..13f60a9
--- /dev/null
@@ -0,0 +1,2593 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   pixabasic.c
+ *
+ *      Pixa creation, destruction, copying
+ *           PIXA     *pixaCreate()
+ *           PIXA     *pixaCreateFromPix()
+ *           PIXA     *pixaCreateFromBoxa()
+ *           PIXA     *pixaSplitPix()
+ *           void      pixaDestroy()
+ *           PIXA     *pixaCopy()
+ *
+ *      Pixa addition
+ *           l_int32   pixaAddPix()
+ *           l_int32   pixaAddBox()
+ *           static l_int32   pixaExtendArray()
+ *           l_int32   pixaExtendArrayToSize()
+ *
+ *      Pixa accessors
+ *           l_int32   pixaGetCount()
+ *           l_int32   pixaChangeRefcount()
+ *           PIX      *pixaGetPix()
+ *           l_int32   pixaGetPixDimensions()
+ *           BOXA     *pixaGetBoxa()
+ *           l_int32   pixaGetBoxaCount()
+ *           BOX      *pixaGetBox()
+ *           l_int32   pixaGetBoxGeometry()
+ *           l_int32   pixaSetBoxa()
+ *           PIX     **pixaGetPixArray()
+ *           l_int32   pixaVerifyDepth()
+ *           l_int32   pixaIsFull()
+ *           l_int32   pixaCountText()
+ *           void   ***pixaGetLinePtrs()
+ *
+ *      Pixa output info
+ *           l_int32   pixaWriteStreamInfo()
+ *
+ *      Pixa array modifiers
+ *           l_int32   pixaReplacePix()
+ *           l_int32   pixaInsertPix()
+ *           l_int32   pixaRemovePix()
+ *           l_int32   pixaRemovePixAndSave()
+ *           l_int32   pixaInitFull()
+ *           l_int32   pixaClear()
+ *
+ *      Pixa and Pixaa combination
+ *           l_int32   pixaJoin()
+ *           l_int32   pixaaJoin()
+ *
+ *      Pixaa creation, destruction
+ *           PIXAA    *pixaaCreate()
+ *           PIXAA    *pixaaCreateFromPixa()
+ *           void      pixaaDestroy()
+ *
+ *      Pixaa addition
+ *           l_int32   pixaaAddPixa()
+ *           l_int32   pixaaExtendArray()
+ *           l_int32   pixaaAddPix()
+ *           l_int32   pixaaAddBox()
+ *
+ *      Pixaa accessors
+ *           l_int32   pixaaGetCount()
+ *           PIXA     *pixaaGetPixa()
+ *           BOXA     *pixaaGetBoxa()
+ *           PIX      *pixaaGetPix()
+ *           l_int32   pixaaVerifyDepth()
+ *           l_int32   pixaaIsFull()
+ *
+ *      Pixaa array modifiers
+ *           l_int32   pixaaInitFull()
+ *           l_int32   pixaaReplacePixa()
+ *           l_int32   pixaaClear()
+ *           l_int32   pixaaTruncate()
+ *
+ *      Pixa serialized I/O  (requires png support)
+ *           PIXA     *pixaRead()
+ *           PIXA     *pixaReadStream()
+ *           l_int32   pixaWrite()
+ *           l_int32   pixaWriteStream()
+ *
+ *      Pixaa serialized I/O  (requires png support)
+ *           PIXAA    *pixaaReadFromFiles()
+ *           PIXAA    *pixaaRead()
+ *           PIXAA    *pixaaReadStream()
+ *           l_int32   pixaaWrite()
+ *           l_int32   pixaaWriteStream()
+ *
+ *
+ *   Important note on reference counting:
+ *     Reference counting for the Pixa is analogous to that for the Boxa.
+ *     See pix.h for details.   pixaCopy() provides three possible modes
+ *     of copy.  The basic rule is that however a Pixa is obtained
+ *     (e.g., from pixaCreate*(), pixaCopy(), or a Pixaa accessor),
+ *     it is necessary to call pixaDestroy() on it.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;   /* n'import quoi */
+
+    /* Static functions */
+static l_int32 pixaExtendArray(PIXA  *pixa);
+
+
+/*---------------------------------------------------------------------*
+ *                    Pixa creation, destruction, copy                 *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaCreate()
+ *
+ *      Input:  n  (initial number of ptrs)
+ *      Return: pixa, or null on error
+ *
+ *  Notes:
+ *      (1) This creates an empty boxa.
+ */
+PIXA *
+pixaCreate(l_int32  n)
+{
+PIXA  *pixa;
+
+    PROCNAME("pixaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((pixa = (PIXA *)LEPT_CALLOC(1, sizeof(PIXA))) == NULL)
+        return (PIXA *)ERROR_PTR("pixa not made", procName, NULL);
+    pixa->n = 0;
+    pixa->nalloc = n;
+    pixa->refcount = 1;
+
+    if ((pixa->pix = (PIX **)LEPT_CALLOC(n, sizeof(PIX *))) == NULL)
+        return (PIXA *)ERROR_PTR("pix ptrs not made", procName, NULL);
+    if ((pixa->boxa = boxaCreate(n)) == NULL)
+        return (PIXA *)ERROR_PTR("boxa not made", procName, NULL);
+
+    return pixa;
+}
+
+
+/*!
+ *  pixaCreateFromPix()
+ *
+ *      Input:  pixs  (with individual components on a lattice)
+ *              n   (number of components)
+ *              cellw   (width of each cell)
+ *              cellh   (height of each cell)
+ *      Return: pixa, or null on error
+ *
+ *  Notes:
+ *      (1) For bpp = 1, we truncate each retrieved pix to the ON
+ *          pixels, which we assume for now start at (0,0)
+ */
+PIXA *
+pixaCreateFromPix(PIX     *pixs,
+                  l_int32  n,
+                  l_int32  cellw,
+                  l_int32  cellh)
+{
+l_int32  w, h, d, nw, nh, i, j, index;
+PIX     *pix, *pixt;
+PIXA    *pixa;
+
+    PROCNAME("pixaCreateFromPix");
+
+    if (!pixs)
+        return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (n <= 0)
+        return (PIXA *)ERROR_PTR("n must be > 0", procName, NULL);
+
+    if ((pixa = pixaCreate(n)) == NULL)
+        return (PIXA *)ERROR_PTR("pixa not made", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if ((pixt = pixCreate(cellw, cellh, d)) == NULL)
+        return (PIXA *)ERROR_PTR("pixt not made", procName, NULL);
+
+    nw = (w + cellw - 1) / cellw;
+    nh = (h + cellh - 1) / cellh;
+    for (i = 0, index = 0; i < nh; i++) {
+        for (j = 0; j < nw && index < n; j++, index++) {
+            pixRasterop(pixt, 0, 0, cellw, cellh, PIX_SRC, pixs,
+                   j * cellw, i * cellh);
+            if (d == 1 && !pixClipToForeground(pixt, &pix, NULL))
+                pixaAddPix(pixa, pix, L_INSERT);
+            else
+                pixaAddPix(pixa, pixt, L_COPY);
+        }
+    }
+
+    pixDestroy(&pixt);
+    return pixa;
+}
+
+
+/*!
+ *  pixaCreateFromBoxa()
+ *
+ *      Input:  pixs
+ *              boxa
+ *              &cropwarn (<optional return> TRUE if the boxa extent
+ *                         is larger than pixs.
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) This simply extracts from pixs the region corresponding to each
+ *          box in the boxa.
+ *      (2) The 3rd arg is optional.  If the extent of the boxa exceeds the
+ *          size of the pixa, so that some boxes are either clipped
+ *          or entirely outside the pix, a warning is returned as TRUE.
+ *      (3) pixad will have only the properly clipped elements, and
+ *          the internal boxa will be correct.
+ */
+PIXA *
+pixaCreateFromBoxa(PIX      *pixs,
+                   BOXA     *boxa,
+                   l_int32  *pcropwarn)
+{
+l_int32  i, n, w, h, wbox, hbox, cropwarn;
+BOX     *box, *boxc;
+PIX     *pixd;
+PIXA    *pixad;
+
+    PROCNAME("pixaCreateFromBoxa");
+
+    if (!pixs)
+        return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!boxa)
+        return (PIXA *)ERROR_PTR("boxa not defined", procName, NULL);
+
+    n = boxaGetCount(boxa);
+    if ((pixad = pixaCreate(n)) == NULL)
+        return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);
+
+    boxaGetExtent(boxa, &wbox, &hbox, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    cropwarn = FALSE;
+    if (wbox > w || hbox > h)
+        cropwarn = TRUE;
+    if (pcropwarn)
+        *pcropwarn = cropwarn;
+
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_COPY);
+        if (cropwarn) {  /* if box is outside pixs, pixd is NULL */
+            pixd = pixClipRectangle(pixs, box, &boxc);  /* may be NULL */
+            if (pixd) {
+                pixaAddPix(pixad, pixd, L_INSERT);
+                pixaAddBox(pixad, boxc, L_INSERT);
+            }
+            boxDestroy(&box);
+        } else {
+            pixd = pixClipRectangle(pixs, box, NULL);
+            pixaAddPix(pixad, pixd, L_INSERT);
+            pixaAddBox(pixad, box, L_INSERT);
+        }
+    }
+
+    return pixad;
+}
+
+
+/*!
+ *  pixaSplitPix()
+ *
+ *      Input:  pixs  (with individual components on a lattice)
+ *              nx   (number of mosaic cells horizontally)
+ *              ny   (number of mosaic cells vertically)
+ *              borderwidth  (of added border on all sides)
+ *              bordercolor  (in our RGBA format: 0xrrggbbaa)
+ *      Return: pixa, or null on error
+ *
+ *  Notes:
+ *      (1) This is a variant on pixaCreateFromPix(), where we
+ *          simply divide the image up into (approximately) equal
+ *          subunits.  If you want the subimages to have essentially
+ *          the same aspect ratio as the input pix, use nx = ny.
+ *      (2) If borderwidth is 0, we ignore the input bordercolor and
+ *          redefine it to white.
+ *      (3) The bordercolor is always used to initialize each tiled pix,
+ *          so that if the src is clipped, the unblitted part will
+ *          be this color.  This avoids 1 pixel wide black stripes at the
+ *          left and lower edges.
+ */
+PIXA *
+pixaSplitPix(PIX      *pixs,
+             l_int32   nx,
+             l_int32   ny,
+             l_int32   borderwidth,
+             l_uint32  bordercolor)
+{
+l_int32  w, h, d, cellw, cellh, i, j;
+PIX     *pixt;
+PIXA    *pixa;
+
+    PROCNAME("pixaSplitPix");
+
+    if (!pixs)
+        return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (nx <= 0 || ny <= 0)
+        return (PIXA *)ERROR_PTR("nx and ny must be > 0", procName, NULL);
+    borderwidth = L_MAX(0, borderwidth);
+
+    if ((pixa = pixaCreate(nx * ny)) == NULL)
+        return (PIXA *)ERROR_PTR("pixa not made", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    cellw = (w + nx - 1) / nx;  /* round up */
+    cellh = (h + ny - 1) / ny;
+
+    for (i = 0; i < ny; i++) {
+        for (j = 0; j < nx; j++) {
+            if ((pixt = pixCreate(cellw + 2 * borderwidth,
+                                  cellh + 2 * borderwidth, d)) == NULL)
+                return (PIXA *)ERROR_PTR("pixt not made", procName, NULL);
+            pixCopyColormap(pixt, pixs);
+            if (borderwidth == 0) {  /* initialize full image to white */
+                if (d == 1)
+                    pixClearAll(pixt);
+                else
+                    pixSetAll(pixt);
+            } else {
+                pixSetAllArbitrary(pixt, bordercolor);
+            }
+            pixRasterop(pixt, borderwidth, borderwidth, cellw, cellh,
+                        PIX_SRC, pixs, j * cellw, i * cellh);
+            pixaAddPix(pixa, pixt, L_INSERT);
+        }
+    }
+
+    return pixa;
+}
+
+
+/*!
+ *  pixaDestroy()
+ *
+ *      Input:  &pixa (<can be nulled>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Decrements the ref count and, if 0, destroys the pixa.
+ *      (2) Always nulls the input ptr.
+ */
+void
+pixaDestroy(PIXA  **ppixa)
+{
+l_int32  i;
+PIXA    *pixa;
+
+    PROCNAME("pixaDestroy");
+
+    if (ppixa == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+
+    if ((pixa = *ppixa) == NULL)
+        return;
+
+        /* Decrement the refcount.  If it is 0, destroy the pixa. */
+    pixaChangeRefcount(pixa, -1);
+    if (pixa->refcount <= 0) {
+        for (i = 0; i < pixa->n; i++)
+            pixDestroy(&pixa->pix[i]);
+        LEPT_FREE(pixa->pix);
+        boxaDestroy(&pixa->boxa);
+        LEPT_FREE(pixa);
+    }
+
+    *ppixa = NULL;
+    return;
+}
+
+
+/*!
+ *  pixaCopy()
+ *
+ *      Input:  pixas
+ *              copyflag (see pix.h for details):
+ *                L_COPY makes a new pixa and copies each pix and each box
+ *                L_CLONE gives a new ref-counted handle to the input pixa
+ *                L_COPY_CLONE makes a new pixa and inserts clones of
+ *                    all pix and boxes
+ *      Return: new pixa, or null on error
+ */
+PIXA *
+pixaCopy(PIXA    *pixa,
+         l_int32  copyflag)
+{
+l_int32  i;
+BOX     *boxc;
+PIX     *pixc;
+PIXA    *pixac;
+
+    PROCNAME("pixaCopy");
+
+    if (!pixa)
+        return (PIXA *)ERROR_PTR("pixa not defined", procName, NULL);
+
+    if (copyflag == L_CLONE) {
+        pixaChangeRefcount(pixa, 1);
+        return pixa;
+    }
+
+    if (copyflag != L_COPY && copyflag != L_COPY_CLONE)
+        return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    if ((pixac = pixaCreate(pixa->n)) == NULL)
+        return (PIXA *)ERROR_PTR("pixac not made", procName, NULL);
+    for (i = 0; i < pixa->n; i++) {
+        if (copyflag == L_COPY) {
+            pixc = pixaGetPix(pixa, i, L_COPY);
+            boxc = pixaGetBox(pixa, i, L_COPY);
+        } else {  /* copy-clone */
+            pixc = pixaGetPix(pixa, i, L_CLONE);
+            boxc = pixaGetBox(pixa, i, L_CLONE);
+        }
+        pixaAddPix(pixac, pixc, L_INSERT);
+        pixaAddBox(pixac, boxc, L_INSERT);
+    }
+
+    return pixac;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                              Pixa addition                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaAddPix()
+ *
+ *      Input:  pixa
+ *              pix  (to be added)
+ *              copyflag (L_INSERT, L_COPY, L_CLONE)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixaAddPix(PIXA    *pixa,
+           PIX     *pix,
+           l_int32  copyflag)
+{
+l_int32  n;
+PIX     *pixc;
+
+    PROCNAME("pixaAddPix");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    if (copyflag == L_INSERT)
+        pixc = pix;
+    else if (copyflag == L_COPY)
+        pixc = pixCopy(NULL, pix);
+    else if (copyflag == L_CLONE)
+        pixc = pixClone(pix);
+    else
+        return ERROR_INT("invalid copyflag", procName, 1);
+    if (!pixc)
+        return ERROR_INT("pixc not made", procName, 1);
+
+    n = pixaGetCount(pixa);
+    if (n >= pixa->nalloc)
+        pixaExtendArray(pixa);
+    pixa->pix[n] = pixc;
+    pixa->n++;
+
+    return 0;
+}
+
+
+/*!
+ *  pixaAddBox()
+ *
+ *      Input:  pixa
+ *              box
+ *              copyflag (L_INSERT, L_COPY, L_CLONE)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixaAddBox(PIXA    *pixa,
+           BOX     *box,
+           l_int32  copyflag)
+{
+    PROCNAME("pixaAddBox");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (copyflag != L_INSERT && copyflag != L_COPY && copyflag != L_CLONE)
+        return ERROR_INT("invalid copyflag", procName, 1);
+
+    boxaAddBox(pixa->boxa, box, copyflag);
+    return 0;
+}
+
+
+/*!
+ *  pixaExtendArray()
+ *
+ *      Input:  pixa
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Doubles the size of the pixa and boxa ptr arrays.
+ */
+static l_int32
+pixaExtendArray(PIXA  *pixa)
+{
+    PROCNAME("pixaExtendArray");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    return pixaExtendArrayToSize(pixa, 2 * pixa->nalloc);
+}
+
+
+/*!
+ *  pixaExtendArrayToSize()
+ *
+ *      Input:  pixa
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) If necessary, reallocs new pixa and boxa ptrs arrays to @size.
+ *          The pixa and boxa ptr arrays must always be equal in size.
+ */
+l_int32
+pixaExtendArrayToSize(PIXA    *pixa,
+                      l_int32  size)
+{
+    PROCNAME("pixaExtendArrayToSize");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    if (size > pixa->nalloc) {
+        if ((pixa->pix = (PIX **)reallocNew((void **)&pixa->pix,
+                                 sizeof(PIX *) * pixa->nalloc,
+                                 size * sizeof(PIX *))) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+        pixa->nalloc = size;
+    }
+    return boxaExtendArrayToSize(pixa->boxa, size);
+}
+
+
+/*---------------------------------------------------------------------*
+ *                             Pixa accessors                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaGetCount()
+ *
+ *      Input:  pixa
+ *      Return: count, or 0 if no pixa
+ */
+l_int32
+pixaGetCount(PIXA  *pixa)
+{
+    PROCNAME("pixaGetCount");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 0);
+
+    return pixa->n;
+}
+
+
+/*!
+ *  pixaChangeRefcount()
+ *
+ *      Input:  pixa
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixaChangeRefcount(PIXA    *pixa,
+                   l_int32  delta)
+{
+    PROCNAME("pixaChangeRefcount");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    pixa->refcount += delta;
+    return 0;
+}
+
+
+/*!
+ *  pixaGetPix()
+ *
+ *      Input:  pixa
+ *              index  (to the index-th pix)
+ *              accesstype  (L_COPY or L_CLONE)
+ *      Return: pix, or null on error
+ */
+PIX *
+pixaGetPix(PIXA    *pixa,
+           l_int32  index,
+           l_int32  accesstype)
+{
+PIX  *pix;
+
+    PROCNAME("pixaGetPix");
+
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (index < 0 || index >= pixa->n)
+        return (PIX *)ERROR_PTR("index not valid", procName, NULL);
+    if ((pix = pixa->pix[index]) == NULL) {
+        L_ERROR("no pix at pixa[%d]\n", procName, index);
+        return (PIX *)ERROR_PTR("pix not found!", procName, NULL);
+    }
+
+    if (accesstype == L_COPY)
+        return pixCopy(NULL, pix);
+    else if (accesstype == L_CLONE)
+        return pixClone(pix);
+    else
+        return (PIX *)ERROR_PTR("invalid accesstype", procName, NULL);
+}
+
+
+/*!
+ *  pixaGetPixDimensions()
+ *
+ *      Input:  pixa
+ *              index  (to the index-th box)
+ *              &w, &h, &d (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixaGetPixDimensions(PIXA     *pixa,
+                     l_int32   index,
+                     l_int32  *pw,
+                     l_int32  *ph,
+                     l_int32  *pd)
+{
+PIX  *pix;
+
+    PROCNAME("pixaGetPixDimensions");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pd) *pd = 0;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (index < 0 || index >= pixa->n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    if ((pix = pixaGetPix(pixa, index, L_CLONE)) == NULL)
+        return ERROR_INT("pix not found!", procName, 1);
+    pixGetDimensions(pix, pw, ph, pd);
+    pixDestroy(&pix);
+    return 0;
+}
+
+
+/*!
+ *  pixaGetBoxa()
+ *
+ *      Input:  pixa
+ *              accesstype  (L_COPY, L_CLONE, L_COPY_CLONE)
+ *      Return: boxa, or null on error
+ */
+BOXA *
+pixaGetBoxa(PIXA    *pixa,
+            l_int32  accesstype)
+{
+    PROCNAME("pixaGetBoxa");
+
+    if (!pixa)
+        return (BOXA *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (!pixa->boxa)
+        return (BOXA *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (accesstype != L_COPY && accesstype != L_CLONE &&
+        accesstype != L_COPY_CLONE)
+        return (BOXA *)ERROR_PTR("invalid accesstype", procName, NULL);
+
+    return boxaCopy(pixa->boxa, accesstype);
+}
+
+
+/*!
+ *  pixaGetBoxaCount()
+ *
+ *      Input:  pixa
+ *      Return: count, or 0 on error
+ */
+l_int32
+pixaGetBoxaCount(PIXA  *pixa)
+{
+    PROCNAME("pixaGetBoxaCount");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 0);
+
+    return boxaGetCount(pixa->boxa);
+}
+
+
+/*!
+ *  pixaGetBox()
+ *
+ *      Input:  pixa
+ *              index  (to the index-th pix)
+ *              accesstype  (L_COPY or L_CLONE)
+ *      Return: box (if null, not automatically an error), or null on error
+ *
+ *  Notes:
+ *      (1) There is always a boxa with a pixa, and it is initialized so
+ *          that each box ptr is NULL.
+ *      (2) In general, we expect that there is either a box associated
+ *          with each pix, or no boxes at all in the boxa.
+ *      (3) Having no boxes is thus not an automatic error.  Whether it
+ *          is an actual error is determined by the calling program.
+ *          If the caller expects to get a box, it is an error; see, e.g.,
+ *          pixaGetBoxGeometry().
+ */
+BOX *
+pixaGetBox(PIXA    *pixa,
+           l_int32  index,
+           l_int32  accesstype)
+{
+BOX  *box;
+
+    PROCNAME("pixaGetBox");
+
+    if (!pixa)
+        return (BOX *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (!pixa->boxa)
+        return (BOX *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (index < 0 || index >= pixa->boxa->n)
+        return (BOX *)ERROR_PTR("index not valid", procName, NULL);
+    if (accesstype != L_COPY && accesstype != L_CLONE)
+        return (BOX *)ERROR_PTR("invalid accesstype", procName, NULL);
+
+    box = pixa->boxa->box[index];
+    if (box) {
+        if (accesstype == L_COPY)
+            return boxCopy(box);
+        else  /* accesstype == L_CLONE */
+            return boxClone(box);
+    } else {
+        return NULL;
+    }
+}
+
+
+/*!
+ *  pixaGetBoxGeometry()
+ *
+ *      Input:  pixa
+ *              index  (to the index-th box)
+ *              &x, &y, &w, &h (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixaGetBoxGeometry(PIXA     *pixa,
+                   l_int32   index,
+                   l_int32  *px,
+                   l_int32  *py,
+                   l_int32  *pw,
+                   l_int32  *ph)
+{
+BOX  *box;
+
+    PROCNAME("pixaGetBoxGeometry");
+
+    if (px) *px = 0;
+    if (py) *py = 0;
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (index < 0 || index >= pixa->n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    if ((box = pixaGetBox(pixa, index, L_CLONE)) == NULL)
+        return ERROR_INT("box not found!", procName, 1);
+    boxGetGeometry(box, px, py, pw, ph);
+    boxDestroy(&box);
+    return 0;
+}
+
+
+/*!
+ *  pixaSetBoxa()
+ *
+ *      Input:  pixa
+ *              boxa
+ *              accesstype  (L_INSERT, L_COPY, L_CLONE)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This destroys the existing boxa in the pixa.
+ */
+l_int32
+pixaSetBoxa(PIXA    *pixa,
+            BOXA    *boxa,
+            l_int32  accesstype)
+{
+    PROCNAME("pixaSetBoxa");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (!boxa)
+        return ERROR_INT("boxa not defined", procName, 1);
+    if (accesstype != L_INSERT && accesstype != L_COPY &&
+        accesstype != L_CLONE)
+        return ERROR_INT("invalid access type", procName, 1);
+
+    boxaDestroy(&pixa->boxa);
+    if (accesstype == L_INSERT)
+        pixa->boxa = boxa;
+    else
+        pixa->boxa = boxaCopy(boxa, accesstype);
+
+    return 0;
+}
+
+
+/*!
+ *  pixaGetPixArray()
+ *
+ *      Input:  pixa
+ *      Return: pix array, or null on error
+ *
+ *  Notes:
+ *      (1) This returns a ptr to the actual array.  The array is
+ *          owned by the pixa, so it must not be destroyed.
+ *      (2) The caller should always check if the return value is NULL
+ *          before accessing any of the pix ptrs in this array!
+ */
+PIX **
+pixaGetPixArray(PIXA  *pixa)
+{
+    PROCNAME("pixaGetPixArray");
+
+    if (!pixa)
+        return (PIX **)ERROR_PTR("pixa not defined", procName, NULL);
+
+    return pixa->pix;
+}
+
+
+/*!
+ *  pixaVerifyDepth()
+ *
+ *      Input:  pixa
+ *              &maxdepth (<optional return> max depth of all pix)
+ *      Return: depth (return 0 if they're not all the same, or on error)
+ *
+ *  Notes:
+ *      (1) It is considered to be an error if there are no pix.
+ */
+l_int32
+pixaVerifyDepth(PIXA     *pixa,
+                l_int32  *pmaxdepth)
+{
+l_int32  i, n, d, depth, maxdepth, same;
+
+    PROCNAME("pixaVerifyDepth");
+
+    if (pmaxdepth) *pmaxdepth = 0;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 0);
+
+    depth = 0;
+    n = pixaGetCount(pixa);
+    maxdepth = 0;
+    same = 1;
+    for (i = 0; i < n; i++) {
+        if (pixaGetPixDimensions(pixa, i, NULL, NULL, &d))
+            return ERROR_INT("pix depth not found", procName, 0);
+        maxdepth = L_MAX(maxdepth, d);
+        if (i == 0)
+            depth = d;
+        else if (d != depth)
+            same = 0;
+    }
+    if (pmaxdepth) *pmaxdepth = maxdepth;
+    return (same == 1) ? depth : 0;
+}
+
+
+/*!
+ *  pixaIsFull()
+ *
+ *      Input:  pixa
+ *              &fullpa (<optional return> 1 if pixa is full)
+ *              &fullba (<optional return> 1 if boxa is full)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) A pixa is "full" if the array of pix is fully
+ *          occupied from index 0 to index (pixa->n - 1).
+ */
+l_int32
+pixaIsFull(PIXA     *pixa,
+           l_int32  *pfullpa,
+           l_int32  *pfullba)
+{
+l_int32  i, n, full;
+BOXA    *boxa;
+PIX     *pix;
+
+    PROCNAME("pixaIsFull");
+
+    if (pfullpa) *pfullpa = 0;
+    if (pfullba) *pfullba = 0;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    n = pixaGetCount(pixa);
+    if (pfullpa) {
+        full = 1;
+        for (i = 0; i < n; i++) {
+            if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) {
+                full = 0;
+                break;
+            }
+            pixDestroy(&pix);
+        }
+        *pfullpa = full;
+    }
+    if (pfullba) {
+        boxa = pixaGetBoxa(pixa, L_CLONE);
+        boxaIsFull(boxa, pfullba);
+        boxaDestroy(&boxa);
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixaCountText()
+ *
+ *      Input:  pixa
+ *              &ntext (<return> number of pix with non-empty text strings)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) All pix have non-empty text strings if the returned value @ntext
+ *          equals the pixa count.
+ */
+l_int32
+pixaCountText(PIXA     *pixa,
+              l_int32  *pntext)
+{
+char    *text;
+l_int32  i, n;
+PIX     *pix;
+
+    PROCNAME("pixaCountText");
+
+    if (!pntext)
+        return ERROR_INT("&ntext not defined", procName, 1);
+    *pntext = 0;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    n = pixaGetCount(pixa);
+    for (i = 0; i < n; i++) {
+        if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL)
+            continue;
+        text = pixGetText(pix);
+        if (text && strlen(text) > 0)
+            (*pntext)++;
+        pixDestroy(&pix);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixaGetLinePtrs()
+ *
+ *      Input:  pixa (of pix that all have the same depth)
+ *              &size (<optional return> number of pix in the pixa)
+ *      Return: array of array of line ptrs, or null on error
+ *
+ *  Notes:
+ *      (1) See pixGetLinePtrs() for details.
+ *      (2) It is best if all pix in the pixa are the same size.
+ *          The size of each line ptr array is equal to the height
+ *          of the pix that it refers to.
+ *      (3) This is an array of arrays.  To destroy it:
+ *            for (i = 0; i < size; i++)
+ *                LEPT_FREE(lineset[i]);
+ *            LEPT_FREE(lineset);
+ */
+void ***
+pixaGetLinePtrs(PIXA     *pixa,
+                l_int32  *psize)
+{
+l_int32  i, n;
+void   **lineptrs;
+void  ***lineset;
+PIX     *pix;
+
+    PROCNAME("pixaGetLinePtrs");
+
+    if (psize) *psize = 0;
+    if (!pixa)
+        return (void ***)ERROR_PTR("pixa not defined", procName, NULL);
+    if (pixaVerifyDepth(pixa, NULL) == 0)
+        return (void ***)ERROR_PTR("pixa not all same depth", procName, NULL);
+    n = pixaGetCount(pixa);
+    if (psize) *psize = n;
+    if ((lineset = (void ***)LEPT_CALLOC(n, sizeof(void **))) == NULL)
+        return (void ***)ERROR_PTR("lineset not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        lineptrs = pixGetLinePtrs(pix, NULL);
+        lineset[i] = lineptrs;
+        pixDestroy(&pix);
+    }
+
+    return lineset;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Pixa output info                            *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaWriteStreamInfo()
+ *
+ *      Input:  stream
+ *              pixa
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) For each pix in the pixa, write out the pix dimensions, spp,
+ *          text string (if it exists), and cmap info.
+ */
+l_int32
+pixaWriteStreamInfo(FILE  *fp,
+                    PIXA  *pixa)
+{
+char     *text;
+l_int32   i, n, w, h, d, spp, count, hastext;
+PIX      *pix;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixaWriteStreamInfo");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    n = pixaGetCount(pixa);
+    for (i = 0; i < n; i++) {
+        if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL) {
+            fprintf(fp, "%d: no pix at this index\n", i);
+            continue;
+        }
+        pixGetDimensions(pix, &w, &h, &d);
+        spp = pixGetSpp(pix);
+        text = pixGetText(pix);
+        hastext = (text && strlen(text) > 0);
+        if ((cmap = pixGetColormap(pix)) != NULL)
+            count = pixcmapGetCount(cmap);
+        fprintf(fp, "Pix %d: w = %d, h = %d, d = %d, spp = %d",
+                i, w, h, d, spp);
+        if (cmap) fprintf(fp, ", cmap(%d colors)", count);
+        if (hastext) fprintf(fp, ", text = %s", text);
+        fprintf(fp, "\n");
+        pixDestroy(&pix);
+    }
+
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                       Pixa array modifiers                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaReplacePix()
+ *
+ *      Input:  pixa
+ *              index  (to the index-th pix)
+ *              pix (insert to replace existing one)
+ *              box (<optional> insert to replace existing)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) In-place replacement of one pix.
+ *      (2) The previous pix at that location is destroyed.
+ */
+l_int32
+pixaReplacePix(PIXA    *pixa,
+               l_int32  index,
+               PIX     *pix,
+               BOX     *box)
+{
+BOXA  *boxa;
+
+    PROCNAME("pixaReplacePix");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (index < 0 || index >= pixa->n)
+        return ERROR_INT("index not valid", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pixDestroy(&(pixa->pix[index]));
+    pixa->pix[index] = pix;
+
+    if (box) {
+        boxa = pixa->boxa;
+        if (index > boxa->n)
+            return ERROR_INT("boxa index not valid", procName, 1);
+        boxaReplaceBox(boxa, index, box);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixaInsertPix()
+ *
+ *      Input:  pixa
+ *              index (at which pix is to be inserted)
+ *              pixs (new pix to be inserted)
+ *              box (<optional> new box to be inserted)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This shifts pixa[i] --> pixa[i + 1] for all i >= index,
+ *          and then inserts at pixa[index].
+ *      (2) To insert at the beginning of the array, set index = 0.
+ *      (3) It should not be used repeatedly on large arrays,
+ *          because the function is O(n).
+ *      (4) To append a pix to a pixa, it's easier to use pixaAddPix().
+ */
+l_int32
+pixaInsertPix(PIXA    *pixa,
+              l_int32  index,
+              PIX     *pixs,
+              BOX     *box)
+{
+l_int32  i, n;
+
+    PROCNAME("pixaInsertPix");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    n = pixaGetCount(pixa);
+    if (index < 0 || index > n)
+        return ERROR_INT("index not in {0...n}", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    if (n >= pixa->nalloc) {  /* extend both ptr arrays */
+        pixaExtendArray(pixa);
+        boxaExtendArray(pixa->boxa);
+    }
+    pixa->n++;
+    for (i = n; i > index; i--)
+      pixa->pix[i] = pixa->pix[i - 1];
+    pixa->pix[index] = pixs;
+
+        /* Optionally, insert the box */
+    if (box)
+        boxaInsertBox(pixa->boxa, index, box);
+
+    return 0;
+}
+
+
+/*!
+ *  pixaRemovePix()
+ *
+ *      Input:  pixa
+ *              index (of pix to be removed)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This shifts pixa[i] --> pixa[i - 1] for all i > index.
+ *      (2) It should not be used repeatedly on large arrays,
+ *          because the function is O(n).
+ *      (3) The corresponding box is removed as well, if it exists.
+ */
+l_int32
+pixaRemovePix(PIXA    *pixa,
+              l_int32  index)
+{
+l_int32  i, n, nbox;
+BOXA    *boxa;
+PIX    **array;
+
+    PROCNAME("pixaRemovePix");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    n = pixaGetCount(pixa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not in {0...n - 1}", procName, 1);
+
+        /* Remove the pix */
+    array = pixa->pix;
+    pixDestroy(&array[index]);
+    for (i = index + 1; i < n; i++)
+        array[i - 1] = array[i];
+    array[n - 1] = NULL;
+    pixa->n--;
+
+        /* Remove the box if it exists */
+    boxa = pixa->boxa;
+    nbox = boxaGetCount(boxa);
+    if (index < nbox)
+        boxaRemoveBox(boxa, index);
+
+    return 0;
+}
+
+
+/*!
+ *  pixaRemovePixAndSave()
+ *
+ *      Input:  pixa
+ *              index (of pix to be removed)
+ *              &pix (<optional return> removed pix)
+ *              &box (<optional return> removed box)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This shifts pixa[i] --> pixa[i - 1] for all i > index.
+ *      (2) It should not be used repeatedly on large arrays,
+ *          because the function is O(n).
+ *      (3) The corresponding box is removed as well, if it exists.
+ *      (4) The removed pix and box can either be retained or destroyed.
+ */
+l_int32
+pixaRemovePixAndSave(PIXA    *pixa,
+                     l_int32  index,
+                     PIX    **ppix,
+                     BOX    **pbox)
+{
+l_int32  i, n, nbox;
+BOXA    *boxa;
+PIX    **array;
+
+    PROCNAME("pixaRemovePixAndSave");
+
+    if (ppix) *ppix = NULL;
+    if (pbox) *pbox = NULL;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    n = pixaGetCount(pixa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not in {0...n - 1}", procName, 1);
+
+        /* Remove the pix */
+    array = pixa->pix;
+    if (ppix)
+        *ppix = pixaGetPix(pixa, index, L_CLONE);
+    pixDestroy(&array[index]);
+    for (i = index + 1; i < n; i++)
+        array[i - 1] = array[i];
+    array[n - 1] = NULL;
+    pixa->n--;
+
+        /* Remove the box if it exists  */
+    boxa = pixa->boxa;
+    nbox = boxaGetCount(boxa);
+    if (index < nbox)
+        boxaRemoveBoxAndSave(boxa, index, pbox);
+
+    return 0;
+}
+
+
+/*!
+ *  pixaInitFull()
+ *
+ *      Input:  pixa (typically empty)
+ *              pix (<optional> to be replicated into the entire pixa ptr array)
+ *              box (<optional> to be replicated into the entire boxa ptr array)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This initializes a pixa by filling up the entire pix ptr array
+ *          with copies of @pix.  If @pix == NULL, we use a tiny placeholder
+ *          pix (w = h = d = 1).  Any existing pix are destroyed.
+ *          It also optionally fills the boxa with copies of @box.
+ *          After this operation, the numbers of pix and (optionally)
+ *          boxes are equal to the number of allocated ptrs.
+ *      (2) Note that we use pixaReplacePix() instead of pixaInsertPix().
+ *          They both have the same effect when inserting into a NULL ptr
+ *          in the pixa ptr array:
+ *      (3) If the boxa is not initialized (i.e., filled with boxes),
+ *          later insertion of boxes will cause an error, because the
+ *          'n' field is 0.
+ *      (4) Example usage.  This function is useful to prepare for a
+ *          random insertion (or replacement) of pix into a pixa.
+ *          To randomly insert pix into a pixa, without boxes, up to
+ *          some index "max":
+ *             Pixa *pixa = pixaCreate(max);
+ *             pixaInitFull(pixa, NULL, NULL);
+ *          An existing pixa with a smaller ptr array can also be reused:
+ *             pixaExtendArrayToSize(pixa, max);
+ *             pixaInitFull(pixa, NULL, NULL);
+ *          The initialization allows the pixa to always be properly
+ *          filled, even if all pix (and boxes) are not later replaced.
+ */
+l_int32
+pixaInitFull(PIXA  *pixa,
+             PIX   *pix,
+             BOX   *box)
+{
+l_int32  i, n;
+PIX     *pixt;
+
+    PROCNAME("pixaInitFull");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    n = pixa->nalloc;
+    pixa->n = n;
+    for (i = 0; i < n; i++) {
+        if (pix)
+            pixt = pixCopy(NULL, pix);
+        else
+            pixt = pixCreate(1, 1, 1);
+        pixaReplacePix(pixa, i, pixt, NULL);
+    }
+    if (box)
+        boxaInitFull(pixa->boxa, box);
+
+    return 0;
+}
+
+
+/*!
+ *  pixaClear()
+ *
+ *      Input:  pixa
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This destroys all pix in the pixa, as well as
+ *          all boxes in the boxa.  The ptrs in the pix ptr array
+ *          are all null'd.  The number of allocated pix, n, is set to 0.
+ */
+l_int32
+pixaClear(PIXA  *pixa)
+{
+l_int32  i, n;
+
+    PROCNAME("pixaClear");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    n = pixaGetCount(pixa);
+    for (i = 0; i < n; i++)
+        pixDestroy(&pixa->pix[i]);
+    pixa->n = 0;
+    return boxaClear(pixa->boxa);
+}
+
+
+/*---------------------------------------------------------------------*
+ *                      Pixa and Pixaa combination                     *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaJoin()
+ *
+ *      Input:  pixad  (dest pixa; add to this one)
+ *              pixas  (<optional> source pixa; add from this one)
+ *              istart  (starting index in pixas)
+ *              iend  (ending index in pixas; use -1 to cat all)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This appends a clone of each indicated pix in pixas to pixad
+ *      (2) istart < 0 is taken to mean 'read from the start' (istart = 0)
+ *      (3) iend < 0 means 'read to the end'
+ *      (4) If pixas is NULL or contains no pix, this is a no-op.
+ */
+l_int32
+pixaJoin(PIXA    *pixad,
+         PIXA    *pixas,
+         l_int32  istart,
+         l_int32  iend)
+{
+l_int32  i, n, nb;
+BOXA    *boxas, *boxad;
+PIX     *pix;
+
+    PROCNAME("pixaJoin");
+
+    if (!pixad)
+        return ERROR_INT("pixad not defined", procName, 1);
+    if (!pixas || ((n = pixaGetCount(pixas)) == 0))
+        return 0;
+
+    if (istart < 0)
+        istart = 0;
+    if (iend < 0 || iend >= n)
+        iend = n - 1;
+    if (istart > iend)
+        return ERROR_INT("istart > iend; nothing to add", procName, 1);
+
+    for (i = istart; i <= iend; i++) {
+        pix = pixaGetPix(pixas, i, L_CLONE);
+        pixaAddPix(pixad, pix, L_INSERT);
+    }
+
+    boxas = pixaGetBoxa(pixas, L_CLONE);
+    boxad = pixaGetBoxa(pixad, L_CLONE);
+    nb = pixaGetBoxaCount(pixas);
+    iend = L_MIN(iend, nb - 1);
+    boxaJoin(boxad, boxas, istart, iend);
+    boxaDestroy(&boxas);  /* just the clones */
+    boxaDestroy(&boxad);
+    return 0;
+}
+
+
+/*!
+ *  pixaaJoin()
+ *
+ *      Input:  paad  (dest pixaa; add to this one)
+ *              paas  (<optional> source pixaa; add from this one)
+ *              istart  (starting index in pixaas)
+ *              iend  (ending index in pixaas; use -1 to cat all)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This appends a clone of each indicated pixa in paas to pixaad
+ *      (2) istart < 0 is taken to mean 'read from the start' (istart = 0)
+ *      (3) iend < 0 means 'read to the end'
+ */
+l_int32
+pixaaJoin(PIXAA   *paad,
+          PIXAA   *paas,
+          l_int32  istart,
+          l_int32  iend)
+{
+l_int32  i, n;
+PIXA    *pixa;
+
+    PROCNAME("pixaaJoin");
+
+    if (!paad)
+        return ERROR_INT("pixaad not defined", procName, 1);
+    if (!paas)
+        return 0;
+
+    if (istart < 0)
+        istart = 0;
+    n = pixaaGetCount(paas, NULL);
+    if (iend < 0 || iend >= n)
+        iend = n - 1;
+    if (istart > iend)
+        return ERROR_INT("istart > iend; nothing to add", procName, 1);
+
+    for (i = istart; i <= iend; i++) {
+        pixa = pixaaGetPixa(paas, i, L_CLONE);
+        pixaaAddPixa(paad, pixa, L_INSERT);
+    }
+
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                    Pixaa creation and destruction                   *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaaCreate()
+ *
+ *      Input:  n  (initial number of pixa ptrs)
+ *      Return: paa, or null on error
+ *
+ *  Notes:
+ *      (1) A pixaa provides a 2-level hierarchy of images.
+ *          A common use is for segmentation masks, which are
+ *          inexpensive to store in png format.
+ *      (2) For example, suppose you want a mask for each textline
+ *          in a two-column page.  The textline masks for each column
+ *          can be represented by a pixa, of which there are 2 in the pixaa.
+ *          The boxes for the textline mask components within a column
+ *          can have their origin referred to the column rather than the page.
+ *          Then the boxa field can be used to represent the two box (regions)
+ *          for the columns, and the (x,y) components of each box can
+ *          be used to get the absolute position of the textlines on
+ *          the page.
+ */
+PIXAA *
+pixaaCreate(l_int32  n)
+{
+PIXAA  *paa;
+
+    PROCNAME("pixaaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((paa = (PIXAA *)LEPT_CALLOC(1, sizeof(PIXAA))) == NULL)
+        return (PIXAA *)ERROR_PTR("paa not made", procName, NULL);
+    paa->n = 0;
+    paa->nalloc = n;
+
+    if ((paa->pixa = (PIXA **)LEPT_CALLOC(n, sizeof(PIXA *))) == NULL) {
+        pixaaDestroy(&paa);
+        return (PIXAA *)ERROR_PTR("pixa ptrs not made", procName, NULL);
+    }
+    paa->boxa = boxaCreate(n);
+
+    return paa;
+}
+
+
+/*!
+ *  pixaaCreateFromPixa()
+ *
+ *      Input:  pixa
+ *              n (number specifying subdivision of pixa)
+ *              type (L_CHOOSE_CONSECUTIVE, L_CHOOSE_SKIP_BY)
+ *              copyflag (L_CLONE, L_COPY)
+ *      Return: paa, or null on error
+ *
+ *  Notes:
+ *      (1) This subdivides a pixa into a set of smaller pixa that
+ *          are accumulated into a pixaa.
+ *      (2) If type == L_CHOOSE_CONSECUTIVE, the first 'n' pix are
+ *          put in a pixa and added to pixaa, then the next 'n', etc.
+ *          If type == L_CHOOSE_SKIP_BY, the first pixa is made by
+ *          aggregating pix[0], pix[n], pix[2*n], etc.
+ *      (3) The copyflag specifies if each new pix is a copy or a clone.
+ */
+PIXAA *
+pixaaCreateFromPixa(PIXA    *pixa,
+                    l_int32  n,
+                    l_int32  type,
+                    l_int32  copyflag)
+{
+l_int32  count, i, j, npixa;
+PIX     *pix;
+PIXA    *pixat;
+PIXAA   *paa;
+
+    PROCNAME("pixaaCreateFromPixa");
+
+    if (!pixa)
+        return (PIXAA *)ERROR_PTR("pixa not defined", procName, NULL);
+    count = pixaGetCount(pixa);
+    if (count == 0)
+        return (PIXAA *)ERROR_PTR("no pix in pixa", procName, NULL);
+    if (n <= 0)
+        return (PIXAA *)ERROR_PTR("n must be > 0", procName, NULL);
+    if (type != L_CHOOSE_CONSECUTIVE && type != L_CHOOSE_SKIP_BY)
+        return (PIXAA *)ERROR_PTR("invalid type", procName, NULL);
+    if (copyflag != L_CLONE && copyflag != L_COPY)
+        return (PIXAA *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    if (type == L_CHOOSE_CONSECUTIVE)
+        npixa = (count + n - 1) / n;
+    else  /* L_CHOOSE_SKIP_BY */
+        npixa = L_MIN(n, count);
+    paa = pixaaCreate(npixa);
+    if (type == L_CHOOSE_CONSECUTIVE) {
+        for (i = 0; i < count; i++) {
+            if (i % n == 0)
+                pixat = pixaCreate(n);
+            pix = pixaGetPix(pixa, i, copyflag);
+            pixaAddPix(pixat, pix, L_INSERT);
+            if (i % n == n - 1)
+                pixaaAddPixa(paa, pixat, L_INSERT);
+        }
+        if (i % n != 0)
+            pixaaAddPixa(paa, pixat, L_INSERT);
+    } else {  /* L_CHOOSE_SKIP_BY */
+        for (i = 0; i < npixa; i++) {
+            pixat = pixaCreate(count / npixa + 1);
+            for (j = i; j < count; j += n) {
+                pix = pixaGetPix(pixa, j, copyflag);
+                pixaAddPix(pixat, pix, L_INSERT);
+            }
+            pixaaAddPixa(paa, pixat, L_INSERT);
+        }
+    }
+
+    return paa;
+}
+
+
+/*!
+ *  pixaaDestroy()
+ *
+ *      Input:  &paa <to be nulled>
+ *      Return: void
+ */
+void
+pixaaDestroy(PIXAA  **ppaa)
+{
+l_int32  i;
+PIXAA   *paa;
+
+    PROCNAME("pixaaDestroy");
+
+    if (ppaa == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+
+    if ((paa = *ppaa) == NULL)
+        return;
+
+    for (i = 0; i < paa->n; i++)
+        pixaDestroy(&paa->pixa[i]);
+    LEPT_FREE(paa->pixa);
+    boxaDestroy(&paa->boxa);
+
+    LEPT_FREE(paa);
+    *ppaa = NULL;
+
+    return;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                             Pixaa addition                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaaAddPixa()
+ *
+ *      Input:  paa
+ *              pixa  (to be added)
+ *              copyflag:
+ *                L_INSERT inserts the pixa directly
+ *                L_COPY makes a new pixa and copies each pix and each box
+ *                L_CLONE gives a new handle to the input pixa
+ *                L_COPY_CLONE makes a new pixa and inserts clones of
+ *                    all pix and boxes
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixaaAddPixa(PIXAA   *paa,
+             PIXA    *pixa,
+             l_int32  copyflag)
+{
+l_int32  n;
+PIXA    *pixac;
+
+    PROCNAME("pixaaAddPixa");
+
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (copyflag != L_INSERT && copyflag != L_COPY &&
+        copyflag != L_CLONE && copyflag != L_COPY_CLONE)
+        return ERROR_INT("invalid copyflag", procName, 1);
+
+    if (copyflag == L_INSERT) {
+        pixac = pixa;
+    } else {
+        if ((pixac = pixaCopy(pixa, copyflag)) == NULL)
+            return ERROR_INT("pixac not made", procName, 1);
+    }
+
+    n = pixaaGetCount(paa, NULL);
+    if (n >= paa->nalloc)
+        pixaaExtendArray(paa);
+    paa->pixa[n] = pixac;
+    paa->n++;
+
+    return 0;
+}
+
+
+/*!
+ *  pixaaExtendArray()
+ *
+ *      Input:  paa
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixaaExtendArray(PIXAA  *paa)
+{
+    PROCNAME("pixaaExtendArray");
+
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+
+    if ((paa->pixa = (PIXA **)reallocNew((void **)&paa->pixa,
+                             sizeof(PIXA *) * paa->nalloc,
+                             2 * sizeof(PIXA *) * paa->nalloc)) == NULL)
+        return ERROR_INT("new ptr array not returned", procName, 1);
+
+    paa->nalloc = 2 * paa->nalloc;
+    return 0;
+}
+
+
+/*!
+ *  pixaaAddPix()
+ *
+ *      Input:  paa  (input paa)
+ *              index (index of pixa in paa)
+ *              pix (to be added)
+ *              box (<optional> to be added)
+ *              copyflag (L_INSERT, L_COPY, L_CLONE)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixaaAddPix(PIXAA   *paa,
+            l_int32  index,
+            PIX     *pix,
+            BOX     *box,
+            l_int32  copyflag)
+{
+PIXA  *pixa;
+
+    PROCNAME("pixaaAddPix");
+
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    if ((pixa = pixaaGetPixa(paa, index, L_CLONE)) == NULL)
+        return ERROR_INT("pixa not found", procName, 1);
+    pixaAddPix(pixa, pix, copyflag);
+    if (box) pixaAddBox(pixa, box, copyflag);
+    pixaDestroy(&pixa);
+    return 0;
+}
+
+
+/*!
+ *  pixaaAddBox()
+ *
+ *      Input:  paa
+ *              box
+ *              copyflag (L_INSERT, L_COPY, L_CLONE)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The box can be used, for example, to hold the support region
+ *          of a pixa that is being added to the pixaa.
+ */
+l_int32
+pixaaAddBox(PIXAA   *paa,
+            BOX     *box,
+            l_int32  copyflag)
+{
+    PROCNAME("pixaaAddBox");
+
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (copyflag != L_INSERT && copyflag != L_COPY && copyflag != L_CLONE)
+        return ERROR_INT("invalid copyflag", procName, 1);
+
+    boxaAddBox(paa->boxa, box, copyflag);
+    return 0;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                            Pixaa accessors                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaaGetCount()
+ *
+ *      Input:  paa
+ *              &na (<optional return> number of pix in each pixa)
+ *      Return: count, or 0 if no pixaa
+ *
+ *  Notes:
+ *      (1) If paa is empty, a returned na will also be empty.
+ */
+l_int32
+pixaaGetCount(PIXAA  *paa,
+              NUMA  **pna)
+{
+l_int32  i, n;
+NUMA    *na;
+PIXA    *pixa;
+
+    PROCNAME("pixaaGetCount");
+
+    if (pna) *pna = NULL;
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 0);
+
+    n = paa->n;
+    if (pna) {
+        if ((na = numaCreate(n)) == NULL)
+            return ERROR_INT("na not made", procName, 0);
+        *pna = na;
+        for (i = 0; i < n; i++) {
+            pixa = pixaaGetPixa(paa, i, L_CLONE);
+            numaAddNumber(na, pixaGetCount(pixa));
+            pixaDestroy(&pixa);
+        }
+    }
+    return n;
+}
+
+
+/*!
+ *  pixaaGetPixa()
+ *
+ *      Input:  paa
+ *              index  (to the index-th pixa)
+ *              accesstype  (L_COPY, L_CLONE, L_COPY_CLONE)
+ *      Return: pixa, or null on error
+ *
+ *  Notes:
+ *      (1) L_COPY makes a new pixa with a copy of every pix
+ *      (2) L_CLONE just makes a new reference to the pixa,
+ *          and bumps the counter.  You would use this, for example,
+ *          when you need to extract some data from a pix within a
+ *          pixa within a pixaa.
+ *      (3) L_COPY_CLONE makes a new pixa with a clone of every pix
+ *          and box
+ *      (4) In all cases, you must invoke pixaDestroy() on the returned pixa
+ */
+PIXA *
+pixaaGetPixa(PIXAA   *paa,
+             l_int32  index,
+             l_int32  accesstype)
+{
+PIXA  *pixa;
+
+    PROCNAME("pixaaGetPixa");
+
+    if (!paa)
+        return (PIXA *)ERROR_PTR("paa not defined", procName, NULL);
+    if (index < 0 || index >= paa->n)
+        return (PIXA *)ERROR_PTR("index not valid", procName, NULL);
+    if (accesstype != L_COPY && accesstype != L_CLONE &&
+        accesstype != L_COPY_CLONE)
+        return (PIXA *)ERROR_PTR("invalid accesstype", procName, NULL);
+
+    if ((pixa = paa->pixa[index]) == NULL) {  /* shouldn't happen! */
+        L_ERROR("missing pixa[%d]\n", procName, index);
+        return (PIXA *)ERROR_PTR("pixa not found at index", procName, NULL);
+    }
+    return pixaCopy(pixa, accesstype);
+}
+
+
+/*!
+ *  pixaaGetBoxa()
+ *
+ *      Input:  paa
+ *              accesstype  (L_COPY, L_CLONE)
+ *      Return: boxa, or null on error
+ *
+ *  Notes:
+ *      (1) L_COPY returns a copy; L_CLONE returns a new reference to the boxa.
+ *      (2) In both cases, invoke boxaDestroy() on the returned boxa.
+ */
+BOXA *
+pixaaGetBoxa(PIXAA   *paa,
+             l_int32  accesstype)
+{
+    PROCNAME("pixaaGetBoxa");
+
+    if (!paa)
+        return (BOXA *)ERROR_PTR("paa not defined", procName, NULL);
+    if (accesstype != L_COPY && accesstype != L_CLONE)
+        return (BOXA *)ERROR_PTR("invalid access type", procName, NULL);
+
+    return boxaCopy(paa->boxa, accesstype);
+}
+
+
+/*!
+ *  pixaaGetPix()
+ *
+ *      Input:  paa
+ *              index  (index into the pixa array in the pixaa)
+ *              ipix  (index into the pix array in the pixa)
+ *              accessflag  (L_COPY or L_CLONE)
+ *      Return: pix, or null on error
+ */
+PIX *
+pixaaGetPix(PIXAA   *paa,
+            l_int32  index,
+            l_int32  ipix,
+            l_int32  accessflag)
+{
+PIX   *pix;
+PIXA  *pixa;
+
+    PROCNAME("pixaaGetPix");
+
+    if ((pixa = pixaaGetPixa(paa, index, L_CLONE)) == NULL)
+        return (PIX *)ERROR_PTR("pixa not retrieved", procName, NULL);
+    if ((pix = pixaGetPix(pixa, ipix, accessflag)) == NULL)
+        L_ERROR("pix not retrieved\n", procName);
+    pixaDestroy(&pixa);
+    return pix;
+}
+
+
+/*!
+ *  pixaaVerifyDepth()
+ *
+ *      Input:  paa
+ *              &maxdepth (<optional return> max depth of all pix in pixaa)
+ *      Return: depth (return 0 if they're not all the same, or on error)
+ */
+l_int32
+pixaaVerifyDepth(PIXAA    *paa,
+                 l_int32  *pmaxdepth)
+{
+l_int32  i, npixa, d, maxd, maxdepth, same;
+PIXA    *pixa;
+
+    PROCNAME("pixaaVerifyDepth");
+
+    if (pmaxdepth) *pmaxdepth = 0;
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 0);
+
+    npixa = pixaaGetCount(paa, NULL);
+    maxdepth = 0;
+    same = 1;
+    for (i = 0; i < npixa; i++) {
+        pixa = pixaaGetPixa(paa, i, L_CLONE);
+        if (pixaGetCount(pixa) > 0) {
+            d = pixaVerifyDepth(pixa, &maxd);
+            maxdepth = L_MAX(maxdepth, maxd);  /* biggest up to this point */
+            if (d != maxdepth) same = 0;
+        }
+        pixaDestroy(&pixa);
+    }
+    if (pmaxdepth) *pmaxdepth = maxdepth;
+    return (same == 1) ? maxdepth : 0;
+}
+
+
+/*!
+ *  pixaaIsFull()
+ *
+ *      Input:  paa
+ *              &full (<return> 1 if all pixa in the paa have full pix arrays)
+ *      Return: return 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Does not require boxa associated with each pixa to be full.
+ */
+l_int32
+pixaaIsFull(PIXAA    *paa,
+            l_int32  *pfull)
+{
+l_int32  i, n, full;
+PIXA    *pixa;
+
+    PROCNAME("pixaaIsFull");
+
+    if (!pfull)
+        return ERROR_INT("&full not defined", procName, 0);
+    *pfull = 0;
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 0);
+
+    n = pixaaGetCount(paa, NULL);
+    full = 1;
+    for (i = 0; i < n; i++) {
+        pixa = pixaaGetPixa(paa, i, L_CLONE);
+        pixaIsFull(pixa, &full, NULL);
+        pixaDestroy(&pixa);
+        if (!full) break;
+    }
+    *pfull = full;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Pixaa array modifiers                       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaaInitFull()
+ *
+ *      Input:  paa (typically empty)
+ *              pixa (to be replicated into the entire pixa ptr array)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This initializes a pixaa by filling up the entire pixa ptr array
+ *          with copies of @pixa.  Any existing pixa are destroyed.
+ *      (2) Example usage.  This function is useful to prepare for a
+ *          random insertion (or replacement) of pixa into a pixaa.
+ *          To randomly insert pixa into a pixaa, up to some index "max":
+ *             Pixaa *paa = pixaaCreate(max);
+ *             Pixa *pixa = pixaCreate(1);  // if you want little memory
+ *             pixaaInitFull(paa, pixa);  // copy it to entire array
+ *             pixaDestroy(&pixa);  // no longer needed
+ *          The initialization allows the pixaa to always be properly filled.
+ */
+l_int32
+pixaaInitFull(PIXAA  *paa,
+              PIXA   *pixa)
+{
+l_int32  i, n;
+PIXA    *pixat;
+
+    PROCNAME("pixaaInitFull");
+
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    n = paa->nalloc;
+    paa->n = n;
+    for (i = 0; i < n; i++) {
+        pixat = pixaCopy(pixa, L_COPY);
+        pixaaReplacePixa(paa, i, pixat);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixaaReplacePixa()
+ *
+ *      Input:  paa
+ *              index  (to the index-th pixa)
+ *              pixa (insert to replace existing one)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This allows random insertion of a pixa into a pixaa, with
+ *          destruction of any existing pixa at that location.
+ *          The input pixa is now owned by the pixaa.
+ *      (2) No other pixa in the array are affected.
+ *      (3) The index must be within the allowed set.
+ */
+l_int32
+pixaaReplacePixa(PIXAA   *paa,
+                 l_int32  index,
+                 PIXA    *pixa)
+{
+
+    PROCNAME("pixaaReplacePixa");
+
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+    if (index < 0 || index >= paa->n)
+        return ERROR_INT("index not valid", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    pixaDestroy(&(paa->pixa[index]));
+    paa->pixa[index] = pixa;
+    return 0;
+}
+
+
+/*!
+ *  pixaaClear()
+ *
+ *      Input:  paa
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This destroys all pixa in the pixaa, and nulls the ptrs
+ *          in the pixa ptr array.
+ */
+l_int32
+pixaaClear(PIXAA  *paa)
+{
+l_int32  i, n;
+
+    PROCNAME("pixaClear");
+
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+
+    n = pixaaGetCount(paa, NULL);
+    for (i = 0; i < n; i++)
+        pixaDestroy(&paa->pixa[i]);
+    paa->n = 0;
+    return 0;
+}
+
+
+/*!
+ *  pixaaTruncate()
+ *
+ *      Input:  paa
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This identifies the largest index containing a pixa that
+ *          has any pix within it, destroys all pixa above that index,
+ *          and resets the count.
+ */
+l_int32
+pixaaTruncate(PIXAA  *paa)
+{
+l_int32  i, n, np;
+PIXA    *pixa;
+
+    PROCNAME("pixaaTruncate");
+
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+
+    n = pixaaGetCount(paa, NULL);
+    for (i = n - 1; i >= 0; i--) {
+        pixa = pixaaGetPixa(paa, i, L_CLONE);
+        if (!pixa) {
+            paa->n--;
+            continue;
+        }
+        np = pixaGetCount(pixa);
+        pixaDestroy(&pixa);
+        if (np == 0) {
+            pixaDestroy(&paa->pixa[i]);
+            paa->n--;
+        } else {
+            break;
+        }
+    }
+    return 0;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                          Pixa serialized I/O                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaRead()
+ *
+ *      Input:  filename
+ *      Return: pixa, or null on error
+ *
+ *  Notes:
+ *      (1) The pix are stored in the file as png.
+ *          If the png library is not linked, this will fail.
+ */
+PIXA *
+pixaRead(const char  *filename)
+{
+FILE  *fp;
+PIXA  *pixa;
+
+    PROCNAME("pixaRead");
+
+#if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
+    return (PIXA *)ERROR_PTR("no libpng: can't read data", procName, NULL);
+#endif  /* !HAVE_LIBPNG */
+
+    if (!filename)
+        return (PIXA *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PIXA *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((pixa = pixaReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (PIXA *)ERROR_PTR("pixa not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return pixa;
+}
+
+
+/*!
+ *  pixaReadStream()
+ *
+ *      Input:  stream
+ *      Return: pixa, or null on error
+ *
+ *  Notes:
+ *      (1) The pix are stored in the file as png.
+ *          If the png library is not linked, this will fail.
+ */
+PIXA *
+pixaReadStream(FILE  *fp)
+{
+l_int32  n, i, xres, yres, version;
+l_int32  ignore;
+BOXA    *boxa;
+PIX     *pix;
+PIXA    *pixa;
+
+    PROCNAME("pixaReadStream");
+
+#if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
+    return (PIXA *)ERROR_PTR("no libpng: can't read data", procName, NULL);
+#endif  /* !HAVE_LIBPNG */
+
+    if (!fp)
+        return (PIXA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nPixa Version %d\n", &version) != 1)
+        return (PIXA *)ERROR_PTR("not a pixa file", procName, NULL);
+    if (version != PIXA_VERSION_NUMBER)
+        return (PIXA *)ERROR_PTR("invalid pixa version", procName, NULL);
+    if (fscanf(fp, "Number of pix = %d\n", &n) != 1)
+        return (PIXA *)ERROR_PTR("not a pixa file", procName, NULL);
+
+    if ((boxa = boxaReadStream(fp)) == NULL)
+        return (PIXA *)ERROR_PTR("boxa not made", procName, NULL);
+    if ((pixa = pixaCreate(n)) == NULL) {
+        boxaDestroy(&boxa);
+        return (PIXA *)ERROR_PTR("pixa not made", procName, NULL);
+    }
+    boxaDestroy(&pixa->boxa);
+    pixa->boxa = boxa;
+
+    for (i = 0; i < n; i++) {
+        if ((fscanf(fp, " pix[%d]: xres = %d, yres = %d\n",
+              &ignore, &xres, &yres)) != 3) {
+            pixaDestroy(&pixa);
+            return (PIXA *)ERROR_PTR("res reading error", procName, NULL);
+        }
+        if ((pix = pixReadStreamPng(fp)) == NULL) {
+            pixaDestroy(&pixa);
+            return (PIXA *)ERROR_PTR("pix not read", procName, NULL);
+        }
+        pixSetXRes(pix, xres);
+        pixSetYRes(pix, yres);
+        pixaAddPix(pixa, pix, L_INSERT);
+    }
+    return pixa;
+}
+
+
+/*!
+ *  pixaWrite()
+ *
+ *      Input:  filename
+ *              pixa
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The pix are stored in the file as png.
+ *          If the png library is not linked, this will fail.
+ */
+l_int32
+pixaWrite(const char  *filename,
+          PIXA        *pixa)
+{
+FILE  *fp;
+
+    PROCNAME("pixaWrite");
+
+#if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
+    return ERROR_INT("no libpng: can't write data", procName, 1);
+#endif  /* !HAVE_LIBPNG */
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (pixaWriteStream(fp, pixa))
+        return ERROR_INT("pixa not written to stream", procName, 1);
+    fclose(fp);
+    return 0;
+}
+
+
+/*!
+ *  pixaWriteStream()
+ *
+ *      Input:  stream (opened for "wb")
+ *              pixa
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The pix are stored in the file as png.
+ *          If the png library is not linked, this will fail.
+ */
+l_int32
+pixaWriteStream(FILE  *fp,
+                PIXA  *pixa)
+{
+l_int32  n, i;
+PIX     *pix;
+
+    PROCNAME("pixaWriteStream");
+
+#if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
+    return ERROR_INT("no libpng: can't write data", procName, 1);
+#endif  /* !HAVE_LIBPNG */
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    n = pixaGetCount(pixa);
+    fprintf(fp, "\nPixa Version %d\n", PIXA_VERSION_NUMBER);
+    fprintf(fp, "Number of pix = %d\n", n);
+    boxaWriteStream(fp, pixa->boxa);
+    for (i = 0; i < n; i++) {
+        if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL)
+            return ERROR_INT("pix not found", procName, 1);
+        fprintf(fp, " pix[%d]: xres = %d, yres = %d\n",
+                i, pix->xres, pix->yres);
+        pixWriteStreamPng(fp, pix, 0.0);
+        pixDestroy(&pix);
+    }
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Pixaa serialized I/O                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaaReadFromFiles()
+ *
+ *      Input:  dirname (directory)
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              first (0-based)
+ *              nfiles (use 0 for everything from @first to the end)
+ *      Return: paa, or null on error or if no pixa files are found.
+ *
+ *  Notes:
+ *      (1) The files must be serialized pixa files (e.g., *.pa)
+ *          If some files cannot be read, warnings are issued.
+ *      (2) Use @substr to filter filenames in the directory.  If
+ *          @substr == NULL, this takes all files.
+ *      (3) After filtering, use @first and @nfiles to select
+ *          a contiguous set of files, that have been lexically
+ *          sorted in increasing order.
+ */
+PIXAA *
+pixaaReadFromFiles(const char  *dirname,
+                   const char  *substr,
+                   l_int32      first,
+                   l_int32      nfiles)
+{
+char    *fname;
+l_int32  i, n;
+PIXA    *pixa;
+PIXAA   *paa;
+SARRAY  *sa;
+
+  PROCNAME("pixaaReadFromFiles");
+
+  if (!dirname)
+      return (PIXAA *)ERROR_PTR("dirname not defined", procName, NULL);
+
+  sa = getSortedPathnamesInDirectory(dirname, substr, first, nfiles);
+  if (!sa || ((n = sarrayGetCount(sa)) == 0)) {
+      sarrayDestroy(&sa);
+      return (PIXAA *)ERROR_PTR("no pixa files found", procName, NULL);
+  }
+
+  paa = pixaaCreate(n);
+  for (i = 0; i < n; i++) {
+      fname = sarrayGetString(sa, i, L_NOCOPY);
+      if ((pixa = pixaRead(fname)) == NULL) {
+          L_ERROR("pixa not read for %d-th file", procName, i);
+          continue;
+      }
+      pixaaAddPixa(paa, pixa, L_INSERT);
+  }
+
+  sarrayDestroy(&sa);
+  return paa;
+}
+
+
+/*!
+ *  pixaaRead()
+ *
+ *      Input:  filename
+ *      Return: paa, or null on error
+ *
+ *  Notes:
+ *      (1) The pix are stored in the file as png.
+ *          If the png library is not linked, this will fail.
+ */
+PIXAA *
+pixaaRead(const char  *filename)
+{
+FILE   *fp;
+PIXAA  *paa;
+
+    PROCNAME("pixaaRead");
+
+#if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
+    return (PIXAA *)ERROR_PTR("no libpng: can't read data", procName, NULL);
+#endif  /* !HAVE_LIBPNG */
+
+    if (!filename)
+        return (PIXAA *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PIXAA *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((paa = pixaaReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (PIXAA *)ERROR_PTR("paa not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return paa;
+}
+
+
+/*!
+ *  pixaaReadStream()
+ *
+ *      Input:  stream
+ *      Return: paa, or null on error
+ *
+ *  Notes:
+ *      (1) The pix are stored in the file as png.
+ *          If the png library is not linked, this will fail.
+ */
+PIXAA *
+pixaaReadStream(FILE  *fp)
+{
+l_int32  n, i, version;
+l_int32  ignore;
+BOXA    *boxa;
+PIXA    *pixa;
+PIXAA   *paa;
+
+    PROCNAME("pixaaReadStream");
+
+#if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
+    return (PIXAA *)ERROR_PTR("no libpng: can't read data", procName, NULL);
+#endif  /* !HAVE_LIBPNG */
+
+    if (!fp)
+        return (PIXAA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nPixaa Version %d\n", &version) != 1)
+        return (PIXAA *)ERROR_PTR("not a pixaa file", procName, NULL);
+    if (version != PIXAA_VERSION_NUMBER)
+        return (PIXAA *)ERROR_PTR("invalid pixaa version", procName, NULL);
+    if (fscanf(fp, "Number of pixa = %d\n", &n) != 1)
+        return (PIXAA *)ERROR_PTR("not a pixaa file", procName, NULL);
+
+    if ((paa = pixaaCreate(n)) == NULL)
+        return (PIXAA *)ERROR_PTR("paa not made", procName, NULL);
+    if ((boxa = boxaReadStream(fp)) == NULL)
+        return (PIXAA *)ERROR_PTR("boxa not made", procName, NULL);
+    boxaDestroy(&paa->boxa);
+    paa->boxa = boxa;
+
+    for (i = 0; i < n; i++) {
+        if ((fscanf(fp, "\n\n --------------- pixa[%d] ---------------\n",
+                    &ignore)) != 1) {
+            return (PIXAA *)ERROR_PTR("text reading", procName, NULL);
+        }
+        if ((pixa = pixaReadStream(fp)) == NULL)
+            return (PIXAA *)ERROR_PTR("pixa not read", procName, NULL);
+        pixaaAddPixa(paa, pixa, L_INSERT);
+    }
+
+    return paa;
+}
+
+
+/*!
+ *  pixaaWrite()
+ *
+ *      Input:  filename
+ *              paa
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The pix are stored in the file as png.
+ *          If the png library is not linked, this will fail.
+ */
+l_int32
+pixaaWrite(const char  *filename,
+           PIXAA       *paa)
+{
+FILE  *fp;
+
+    PROCNAME("pixaaWrite");
+
+#if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
+    return ERROR_INT("no libpng: can't read data", procName, 1);
+#endif  /* !HAVE_LIBPNG */
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (pixaaWriteStream(fp, paa))
+        return ERROR_INT("paa not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  pixaaWriteStream()
+ *
+ *      Input:  stream (opened for "wb")
+ *              paa
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The pix are stored in the file as png.
+ *          If the png library is not linked, this will fail.
+ */
+l_int32
+pixaaWriteStream(FILE   *fp,
+                 PIXAA  *paa)
+{
+l_int32  n, i;
+PIXA    *pixa;
+
+    PROCNAME("pixaaWriteStream");
+
+#if !HAVE_LIBPNG     /* defined in environ.h and config_auto.h */
+    return ERROR_INT("no libpng: can't read data", procName, 1);
+#endif  /* !HAVE_LIBPNG */
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+
+    n = pixaaGetCount(paa, NULL);
+    fprintf(fp, "\nPixaa Version %d\n", PIXAA_VERSION_NUMBER);
+    fprintf(fp, "Number of pixa = %d\n", n);
+    boxaWriteStream(fp, paa->boxa);
+    for (i = 0; i < n; i++) {
+        if ((pixa = pixaaGetPixa(paa, i, L_CLONE)) == NULL)
+            return ERROR_INT("pixa not found", procName, 1);
+        fprintf(fp, "\n\n --------------- pixa[%d] ---------------\n", i);
+        pixaWriteStream(fp, pixa);
+        pixaDestroy(&pixa);
+    }
+    return 0;
+}
diff --git a/src/pixacc.c b/src/pixacc.c
new file mode 100644 (file)
index 0000000..272b429
--- /dev/null
@@ -0,0 +1,342 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *   pixacc.c
+ *
+ *      Pixacc creation, destruction
+ *           PIXACC   *pixaccCreate()
+ *           PIXACC   *pixaccCreateFromPix()
+ *           void      pixaccDestroy()
+ *
+ *      Pixacc finalization
+ *           PIX      *pixaccFinal()
+ *
+ *      Pixacc accessors
+ *           PIX      *pixaccGetPix()
+ *           l_int32   pixaccGetOffset()
+ *
+ *      Pixacc accumulators
+ *           l_int32   pixaccAdd()
+ *           l_int32   pixaccSubtract()
+ *           l_int32   pixaccMultConst()
+ *           l_int32   pixaccMultConstAccumulate()
+ *
+ *  This is a simple interface for some of the pixel arithmetic operations
+ *  in pixarith.c.  These are easy to code up, but not as fast as
+ *  hand-coded functions that do arithmetic on corresponding pixels.
+ *
+ *  Suppose you want to make a linear combination of pix1 and pix2:
+ *     pixd = 0.4 * pix1 + 0.6 * pix2
+ *  where pix1 and pix2 are the same size and have depth 'd'.  Then:
+ *     Pixacc *pacc = pixaccCreateFromPix(pix1, 0);  // first; addition only
+ *     pixaccMultConst(pacc, 0.4);
+ *     pixaccMultConstAccumulate(pacc, pix2, 0.6);  // Add in 0.6 of the second
+ *     pixd = pixaccFinal(pacc, d);  // Get the result
+ *     pixaccDestroy(&pacc);
+ */
+
+#include "allheaders.h"
+
+
+/*---------------------------------------------------------------------*
+ *                     Pixacc creation, destruction                    *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaccCreate()
+ *
+ *      Input:  w, h (of 32 bpp internal Pix)
+ *              negflag (0 if only positive numbers are involved;
+ *                       1 if there will be negative numbers)
+ *      Return: pixacc, or null on error
+ *
+ *  Notes:
+ *      (1) Use @negflag = 1 for safety if any negative numbers are going
+ *          to be used in the chain of operations.  Negative numbers
+ *          arise, e.g., by subtracting a pix, or by adding a pix
+ *          that has been pre-multiplied by a negative number.
+ *      (2) Initializes the internal 32 bpp pix, similarly to the
+ *          initialization in pixInitAccumulate().
+ */
+PIXACC *
+pixaccCreate(l_int32  w,
+             l_int32  h,
+             l_int32  negflag)
+{
+PIXACC  *pixacc;
+
+    PROCNAME("pixaccCreate");
+
+    if ((pixacc = (PIXACC *)LEPT_CALLOC(1, sizeof(PIXACC))) == NULL)
+        return (PIXACC *)ERROR_PTR("pixacc not made", procName, NULL);
+    pixacc->w = w;
+    pixacc->h = h;
+
+    if ((pixacc->pix = pixCreate(w, h, 32)) == NULL)
+        return (PIXACC *)ERROR_PTR("pix not made", procName, NULL);
+
+    if (negflag) {
+        pixacc->offset = 0x40000000;
+        pixSetAllArbitrary(pixacc->pix, pixacc->offset);
+    }
+
+    return pixacc;
+}
+
+
+/*!
+ *  pixaccCreateFromPix()
+ *
+ *      Input:  pix
+ *              negflag (0 if only positive numbers are involved;
+ *                       1 if there will be negative numbers)
+ *      Return: pixacc, or null on error
+ *
+ *  Notes:
+ *      (1) See pixaccCreate()
+ */
+PIXACC *
+pixaccCreateFromPix(PIX     *pix,
+                    l_int32  negflag)
+{
+l_int32  w, h;
+PIXACC  *pixacc;
+
+    PROCNAME("pixaccCreateFromPix");
+
+    if (!pix)
+        return (PIXACC *)ERROR_PTR("pix not defined", procName, NULL);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    pixacc = pixaccCreate(w, h, negflag);
+    pixaccAdd(pixacc, pix);
+    return pixacc;
+}
+
+
+/*!
+ *  pixaccDestroy()
+ *
+ *      Input:  &pixacc (<can be null>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Always nulls the input ptr.
+ */
+void
+pixaccDestroy(PIXACC  **ppixacc)
+{
+PIXACC  *pixacc;
+
+    PROCNAME("pixaccDestroy");
+
+    if (ppixacc == NULL) {
+        L_WARNING("ptr address is NULL!", procName);
+        return;
+    }
+
+    if ((pixacc = *ppixacc) == NULL)
+        return;
+
+    pixDestroy(&pixacc->pix);
+    LEPT_FREE(pixacc);
+    *ppixacc = NULL;
+    return;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                            Pixacc finalization                      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaccFinal()
+ *
+ *      Input:  pixacc
+ *              outdepth (8, 16 or 32 bpp)
+ *      Return: pixd (8 , 16 or 32 bpp), or null on error
+ */
+PIX *
+pixaccFinal(PIXACC  *pixacc,
+            l_int32  outdepth)
+{
+    PROCNAME("pixaccFinal");
+
+    if (!pixacc)
+        return (PIX *)ERROR_PTR("pixacc not defined", procName, NULL);
+
+    return pixFinalAccumulate(pixaccGetPix(pixacc), pixaccGetOffset(pixacc),
+                              outdepth);
+}
+
+
+/*---------------------------------------------------------------------*
+ *                            Pixacc accessors                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaccGetPix()
+ *
+ *      Input:  pixacc
+ *      Return: pix, or null on error
+ */
+PIX *
+pixaccGetPix(PIXACC  *pixacc)
+{
+    PROCNAME("pixaccGetPix");
+
+    if (!pixacc)
+        return (PIX *)ERROR_PTR("pixacc not defined", procName, NULL);
+    return pixacc->pix;
+}
+
+
+/*!
+ *  pixaccGetOffset()
+ *
+ *      Input:  pixacc
+ *      Return: offset, or -1 on error
+ */
+l_int32
+pixaccGetOffset(PIXACC  *pixacc)
+{
+    PROCNAME("pixaccGetOffset");
+
+    if (!pixacc)
+        return ERROR_INT("pixacc not defined", procName, -1);
+    return pixacc->offset;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                          Pixacc accumulators                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaccAdd()
+ *
+ *      Input:  pixacc
+ *              pix (to be added)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixaccAdd(PIXACC  *pixacc,
+          PIX     *pix)
+{
+    PROCNAME("pixaccAdd");
+
+    if (!pixacc)
+        return ERROR_INT("pixacc not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixAccumulate(pixaccGetPix(pixacc), pix, L_ARITH_ADD);
+    return 0;
+}
+
+
+/*!
+ *  pixaccSubtract()
+ *
+ *      Input:  pixacc
+ *              pix (to be subtracted)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixaccSubtract(PIXACC  *pixacc,
+               PIX     *pix)
+{
+    PROCNAME("pixaccSubtract");
+
+    if (!pixacc)
+        return ERROR_INT("pixacc not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixAccumulate(pixaccGetPix(pixacc), pix, L_ARITH_SUBTRACT);
+    return 0;
+}
+
+
+/*!
+ *  pixaccMultConst()
+ *
+ *      Input:  pixacc
+ *              factor
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixaccMultConst(PIXACC    *pixacc,
+                l_float32  factor)
+{
+    PROCNAME("pixaccMultConst");
+
+    if (!pixacc)
+        return ERROR_INT("pixacc not defined", procName, 1);
+    pixMultConstAccumulate(pixaccGetPix(pixacc), factor,
+                           pixaccGetOffset(pixacc));
+    return 0;
+}
+
+
+/*!
+ *  pixaccMultConstAccumulate()
+ *
+ *      Input:  pixacc
+ *              pix
+ *              factor
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This creates a temp pix that is @pix multiplied by the
+ *          constant @factor.  It then adds that into @pixacc.
+ */
+l_int32
+pixaccMultConstAccumulate(PIXACC    *pixacc,
+                          PIX       *pix,
+                          l_float32  factor)
+{
+l_int32  w, h, d, negflag;
+PIX     *pixt;
+PIXACC  *pacct;
+
+    PROCNAME("pixaccMultConstAccumulate");
+
+    if (!pixacc)
+        return ERROR_INT("pixacc not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    if (factor == 0.0) return 0;
+
+    pixGetDimensions(pix, &w, &h, &d);
+    negflag = (factor > 0.0) ? 0 : 1;
+    pacct = pixaccCreate(w, h, negflag);
+    pixaccAdd(pacct, pix);
+    pixaccMultConst(pacct, factor);
+    pixt = pixaccFinal(pacct, d);
+    pixaccAdd(pixacc, pixt);
+
+    pixaccDestroy(&pacct);
+    pixDestroy(&pixt);
+    return 0;
+}
diff --git a/src/pixafunc1.c b/src/pixafunc1.c
new file mode 100644 (file)
index 0000000..ad2e886
--- /dev/null
@@ -0,0 +1,2434 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   pixafunc1.c
+ *
+ *      Filters
+ *           PIX      *pixSelectBySize()
+ *           PIXA     *pixaSelectBySize()
+ *           NUMA     *pixaMakeSizeIndicator()
+ *
+ *           PIX      *pixSelectByPerimToAreaRatio()
+ *           PIXA     *pixaSelectByPerimToAreaRatio()
+ *           PIX      *pixSelectByPerimSizeRatio()
+ *           PIXA     *pixaSelectByPerimSizeRatio()
+ *           PIX      *pixSelectByAreaFraction()
+ *           PIXA     *pixaSelectByAreaFraction()
+ *           PIX      *pixSelectByWidthHeightRatio()
+ *           PIXA     *pixaSelectByWidthHeightRatio()
+ *
+ *           PIXA     *pixaSelectWithIndicator()
+ *           l_int32   pixRemoveWithIndicator()
+ *           l_int32   pixAddWithIndicator()
+ *           PIXA     *pixaSelectWithString()
+ *           PIX      *pixaRenderComponent()
+ *
+ *      Sort functions
+ *           PIXA     *pixaSort()
+ *           PIXA     *pixaBinSort()
+ *           PIXA     *pixaSortByIndex()
+ *           PIXAA    *pixaSort2dByIndex()
+ *
+ *      Pixa and Pixaa range selection
+ *           PIXA     *pixaSelectRange()
+ *           PIXAA    *pixaaSelectRange()
+ *
+ *      Pixa and Pixaa scaling
+ *           PIXAA    *pixaaScaleToSize()
+ *           PIXAA    *pixaaScaleToSizeVar()
+ *           PIXA     *pixaScaleToSize()
+ *
+ *      Miscellaneous
+ *           PIXA     *pixaAddBorderGeneral()
+ *           PIXA     *pixaaFlattenToPixa()
+ *           l_int32   pixaaSizeRange()
+ *           l_int32   pixaSizeRange()
+ *           PIXA     *pixaClipToPix()
+ *           l_int32   pixaGetRenderingDepth()
+ *           l_int32   pixaHasColor()
+ *           l_int32   pixaAnyColormaps()
+ *           l_int32   pixaGetDepthInfo()
+ *           PIXA     *pixaConvertToSameDepth()
+ *           l_int32   pixaEqual()
+ *           PIXA     *pixaRotateOrth()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+    /* For more than this number of c.c. in a binarized image of
+     * semi-perimeter (w + h) about 5000 or less, the O(n) binsort
+     * is faster than the O(nlogn) shellsort.  */
+static const l_int32   MIN_COMPS_FOR_BIN_SORT = 200;
+
+
+/*---------------------------------------------------------------------*
+ *                                Filters                              *
+ *---------------------------------------------------------------------*/
+/*
+ * These filters work on the connected components of 1 bpp images.
+ * They are typically used on pixa that have been generated from a Pix
+ * using pixConnComp(), so that the corresponding Boxa is available.
+ *
+ * The filters remove or retain c.c. based on these properties:
+ *    (a) size  [pixaFindDimensions()]
+ *    (b) area-to-perimeter ratio   [pixaFindAreaPerimRatio()]
+ *    (c) foreground area as a fraction of bounding box area (w * h)
+ *        [pixaFindForegroundArea()]
+ *    (d) number of foreground pixels   [pixaCountPixels()]
+ *    (e) width/height aspect ratio  [pixFindWidthHeightRatio()]
+ *
+ * We provide two different high-level interfaces:
+ *    (1) Functions that use one of the filters on either
+ *        a pix or the pixa of components.
+ *    (2) A general method that generates numas of indicator functions,
+ *        logically combines them, and efficiently removes or adds
+ *        the selected components.
+ *
+ * For interface (1), the filtering is performed with a single function call.
+ * This is the easiest way to do simple filtering.  These functions
+ * are named pixSelectBy*() and pixaSelectBy*(), where the '*' is one of:
+ *        Size
+ *        PerimToAreaRatio
+ *        PerimSizeRatio
+ *        AreaFraction
+ *        WidthHeightRatio
+ *
+ * For more complicated filtering, use the general method (2).
+ * The numa indicator functions for a pixa are generated by these functions:
+ *        pixaFindDimensions()
+ *        pixaFindPerimToAreaRatio()
+ *        pixaFindPerimSizeRatio()
+ *        pixaFindAreaFraction()
+ *        pixaCountPixels()
+ *        pixaFindWidthHeightRatio()
+ *        pixaFindWidthHeightProduct()
+ *
+ * Here is an illustration using the general method.  Suppose you want
+ * all 8-connected components that have a height greater than 40 pixels,
+ * a width not more than 30 pixels, between 150 and 300 fg pixels,
+ * and a perimeter-to-size ratio between 1.2 and 2.0.
+ *
+ *        // Generate the pixa of 8 cc pieces.
+ *    boxa = pixConnComp(pixs, &pixa, 8);
+ *
+ *        // Extract the data we need about each component.
+ *    pixaFindDimensions(pixa, &naw, &nah);
+ *    nas = pixaCountPixels(pixa);
+ *    nar = pixaFindPerimSizeRatio(pixa);
+ *
+ *        // Build the indicator arrays for the set of components,
+ *        // based on thresholds and selection criteria.
+ *    na1 = numaMakeThresholdIndicator(nah, 40, L_SELECT_IF_GT);
+ *    na2 = numaMakeThresholdIndicator(naw, 30, L_SELECT_IF_LTE);
+ *    na3 = numaMakeThresholdIndicator(nas, 150, L_SELECT_IF_GTE);
+ *    na4 = numaMakeThresholdIndicator(nas, 300, L_SELECT_IF_LTE);
+ *    na5 = numaMakeThresholdIndicator(nar, 1.2, L_SELECT_IF_GTE);
+ *    na6 = numaMakeThresholdIndicator(nar, 2.0, L_SELECT_IF_LTE);
+ *
+ *        // Combine the indicator arrays logically to find
+ *        // the components that will be retained.
+ *    nad = numaLogicalOp(NULL, na1, na2, L_INTERSECTION);
+ *    numaLogicalOp(nad, nad, na3, L_INTERSECTION);
+ *    numaLogicalOp(nad, nad, na4, L_INTERSECTION);
+ *    numaLogicalOp(nad, nad, na5, L_INTERSECTION);
+ *    numaLogicalOp(nad, nad, na6, L_INTERSECTION);
+ *
+ *        // Invert to get the components that will be removed.
+ *    numaInvert(nad, nad);
+ *
+ *        // Remove the components, in-place.
+ *    pixRemoveWithIndicator(pixs, pixa, nad);
+ */
+
+
+/*!
+ *  pixSelectBySize()
+ *
+ *      Input:  pixs (1 bpp)
+ *              width, height (threshold dimensions)
+ *              connectivity (4 or 8)
+ *              type (L_SELECT_WIDTH, L_SELECT_HEIGHT,
+ *                    L_SELECT_IF_EITHER, L_SELECT_IF_BOTH)
+ *              relation (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                        L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *              &changed (<optional return> 1 if changed; 0 otherwise)
+ *      Return: filtered pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The args specify constraints on the size of the
+ *          components that are kept.
+ *      (2) If unchanged, returns a copy of pixs.  Otherwise,
+ *          returns a new pix with the filtered components.
+ *      (3) If the selection type is L_SELECT_WIDTH, the input
+ *          height is ignored, and v.v.
+ *      (4) To keep small components, use relation = L_SELECT_IF_LT or
+ *          L_SELECT_IF_LTE.
+ *          To keep large components, use relation = L_SELECT_IF_GT or
+ *          L_SELECT_IF_GTE.
+ */
+PIX *
+pixSelectBySize(PIX      *pixs,
+                l_int32   width,
+                l_int32   height,
+                l_int32   connectivity,
+                l_int32   type,
+                l_int32   relation,
+                l_int32  *pchanged)
+{
+l_int32  w, h, empty, changed, count;
+BOXA    *boxa;
+PIX     *pixd;
+PIXA    *pixas, *pixad;
+
+    PROCNAME("pixSelectBySize");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+    if (type != L_SELECT_WIDTH && type != L_SELECT_HEIGHT &&
+        type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT &&
+        relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE)
+        return (PIX *)ERROR_PTR("invalid relation", procName, NULL);
+    if (pchanged) *pchanged = FALSE;
+
+        /* Check if any components exist */
+    pixZero(pixs, &empty);
+    if (empty)
+        return pixCopy(NULL, pixs);
+
+        /* Identify and select the components */
+    boxa = pixConnComp(pixs, &pixas, connectivity);
+    pixad = pixaSelectBySize(pixas, width, height, type, relation, &changed);
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixas);
+
+    if (!changed) {
+        pixaDestroy(&pixad);
+        return pixCopy(NULL, pixs);
+    }
+
+        /* Render the result */
+    if (pchanged) *pchanged = TRUE;
+    pixGetDimensions(pixs, &w, &h, NULL);
+    count = pixaGetCount(pixad);
+    if (count == 0) {  /* return empty pix */
+        pixd = pixCreateTemplate(pixs);
+    } else {
+        pixd = pixaDisplay(pixad, w, h);
+        pixCopyResolution(pixd, pixs);
+        pixCopyColormap(pixd, pixs);
+        pixCopyText(pixd, pixs);
+        pixCopyInputFormat(pixd, pixs);
+    }
+    pixaDestroy(&pixad);
+    return pixd;
+}
+
+
+/*!
+ *  pixaSelectBySize()
+ *
+ *      Input:  pixas
+ *              width, height (threshold dimensions)
+ *              type (L_SELECT_WIDTH, L_SELECT_HEIGHT,
+ *                    L_SELECT_IF_EITHER, L_SELECT_IF_BOTH)
+ *              relation (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                        L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *              &changed (<optional return> 1 if changed; 0 otherwise)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) The args specify constraints on the size of the
+ *          components that are kept.
+ *      (2) Uses pix and box clones in the new pixa.
+ *      (3) If the selection type is L_SELECT_WIDTH, the input
+ *          height is ignored, and v.v.
+ *      (4) To keep small components, use relation = L_SELECT_IF_LT or
+ *          L_SELECT_IF_LTE.
+ *          To keep large components, use relation = L_SELECT_IF_GT or
+ *          L_SELECT_IF_GTE.
+ */
+PIXA *
+pixaSelectBySize(PIXA     *pixas,
+                 l_int32   width,
+                 l_int32   height,
+                 l_int32   type,
+                 l_int32   relation,
+                 l_int32  *pchanged)
+{
+NUMA  *na;
+PIXA  *pixad;
+
+    PROCNAME("pixaSelectBySize");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (type != L_SELECT_WIDTH && type != L_SELECT_HEIGHT &&
+        type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH)
+        return (PIXA *)ERROR_PTR("invalid type", procName, NULL);
+    if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT &&
+        relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE)
+        return (PIXA *)ERROR_PTR("invalid relation", procName, NULL);
+
+        /* Compute the indicator array for saving components */
+    na = pixaMakeSizeIndicator(pixas, width, height, type, relation);
+
+        /* Filter to get output */
+    pixad = pixaSelectWithIndicator(pixas, na, pchanged);
+
+    numaDestroy(&na);
+    return pixad;
+}
+
+
+/*!
+ *  pixaMakeSizeIndicator()
+ *
+ *      Input:  pixa
+ *              width, height (threshold dimensions)
+ *              type (L_SELECT_WIDTH, L_SELECT_HEIGHT,
+ *                    L_SELECT_IF_EITHER, L_SELECT_IF_BOTH)
+ *              relation (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                        L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *      Return: na (indicator array), or null on error
+ *
+ *  Notes:
+ *      (1) The args specify constraints on the size of the
+ *          components that are kept.
+ *      (2) If the selection type is L_SELECT_WIDTH, the input
+ *          height is ignored, and v.v.
+ *      (3) To keep small components, use relation = L_SELECT_IF_LT or
+ *          L_SELECT_IF_LTE.
+ *          To keep large components, use relation = L_SELECT_IF_GT or
+ *          L_SELECT_IF_GTE.
+ */
+NUMA *
+pixaMakeSizeIndicator(PIXA     *pixa,
+                      l_int32   width,
+                      l_int32   height,
+                      l_int32   type,
+                      l_int32   relation)
+{
+l_int32  i, n, w, h, ival;
+NUMA    *na;
+
+    PROCNAME("pixaMakeSizeIndicator");
+
+    if (!pixa)
+        return (NUMA *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (type != L_SELECT_WIDTH && type != L_SELECT_HEIGHT &&
+        type != L_SELECT_IF_EITHER && type != L_SELECT_IF_BOTH)
+        return (NUMA *)ERROR_PTR("invalid type", procName, NULL);
+    if (relation != L_SELECT_IF_LT && relation != L_SELECT_IF_GT &&
+        relation != L_SELECT_IF_LTE && relation != L_SELECT_IF_GTE)
+        return (NUMA *)ERROR_PTR("invalid relation", procName, NULL);
+
+    n = pixaGetCount(pixa);
+    na = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        ival = 0;
+        pixaGetPixDimensions(pixa, i, &w, &h, NULL);
+        switch (type)
+        {
+        case L_SELECT_WIDTH:
+            if ((relation == L_SELECT_IF_LT && w < width) ||
+                (relation == L_SELECT_IF_GT && w > width) ||
+                (relation == L_SELECT_IF_LTE && w <= width) ||
+                (relation == L_SELECT_IF_GTE && w >= width))
+                ival = 1;
+            break;
+        case L_SELECT_HEIGHT:
+            if ((relation == L_SELECT_IF_LT && h < height) ||
+                (relation == L_SELECT_IF_GT && h > height) ||
+                (relation == L_SELECT_IF_LTE && h <= height) ||
+                (relation == L_SELECT_IF_GTE && h >= height))
+                ival = 1;
+            break;
+        case L_SELECT_IF_EITHER:
+            if (((relation == L_SELECT_IF_LT) && (w < width || h < height)) ||
+                ((relation == L_SELECT_IF_GT) && (w > width || h > height)) ||
+               ((relation == L_SELECT_IF_LTE) && (w <= width || h <= height)) ||
+                ((relation == L_SELECT_IF_GTE) && (w >= width || h >= height)))
+                    ival = 1;
+            break;
+        case L_SELECT_IF_BOTH:
+            if (((relation == L_SELECT_IF_LT) && (w < width && h < height)) ||
+                ((relation == L_SELECT_IF_GT) && (w > width && h > height)) ||
+               ((relation == L_SELECT_IF_LTE) && (w <= width && h <= height)) ||
+                ((relation == L_SELECT_IF_GTE) && (w >= width && h >= height)))
+                    ival = 1;
+            break;
+        default:
+            L_WARNING("can't get here!\n", procName);
+            break;
+        }
+        numaAddNumber(na, ival);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  pixSelectByPerimToAreaRatio()
+ *
+ *      Input:  pixs (1 bpp)
+ *              thresh (threshold ratio of fg boundary to fg pixels)
+ *              connectivity (4 or 8)
+ *              type (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                    L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *              &changed (<optional return> 1 if changed; 0 if clone returned)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The args specify constraints on the size of the
+ *          components that are kept.
+ *      (2) If unchanged, returns a copy of pixs.  Otherwise,
+ *          returns a new pix with the filtered components.
+ *      (3) This filters "thick" components, where a thick component
+ *          is defined to have a ratio of boundary to interior pixels
+ *          that is smaller than a given threshold value.
+ *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save the thicker
+ *          components, and L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
+ */
+PIX *
+pixSelectByPerimToAreaRatio(PIX       *pixs,
+                            l_float32  thresh,
+                            l_int32    connectivity,
+                            l_int32    type,
+                            l_int32   *pchanged)
+{
+l_int32  w, h, empty, changed, count;
+BOXA    *boxa;
+PIX     *pixd;
+PIXA    *pixas, *pixad;
+
+    PROCNAME("pixSelectByPerimToAreaRatio");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+    if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT &&
+        type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    if (pchanged) *pchanged = FALSE;
+
+        /* Check if any components exist */
+    pixZero(pixs, &empty);
+    if (empty)
+        return pixCopy(NULL, pixs);
+
+        /* Filter thin components */
+    boxa = pixConnComp(pixs, &pixas, connectivity);
+    pixad = pixaSelectByPerimToAreaRatio(pixas, thresh, type, &changed);
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixas);
+
+    if (!changed) {
+        pixaDestroy(&pixad);
+        return pixCopy(NULL, pixs);
+    }
+
+        /* Render the result */
+    if (pchanged) *pchanged = TRUE;
+    pixGetDimensions(pixs, &w, &h, NULL);
+    count = pixaGetCount(pixad);
+    if (count == 0) {  /* return empty pix */
+        pixd = pixCreateTemplate(pixs);
+    } else {
+        pixd = pixaDisplay(pixad, w, h);
+        pixCopyResolution(pixd, pixs);
+        pixCopyColormap(pixd, pixs);
+        pixCopyText(pixd, pixs);
+        pixCopyInputFormat(pixd, pixs);
+    }
+    pixaDestroy(&pixad);
+    return pixd;
+}
+
+
+/*!
+ *  pixaSelectByPerimToAreaRatio()
+ *
+ *      Input:  pixas
+ *              thresh (threshold ratio of fg boundary to fg pixels)
+ *              type (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                    L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *              &changed (<optional return> 1 if changed; 0 if clone returned)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) Returns a pixa clone if no components are removed.
+ *      (2) Uses pix and box clones in the new pixa.
+ *      (3) See pixSelectByPerimToAreaRatio().
+ */
+PIXA *
+pixaSelectByPerimToAreaRatio(PIXA      *pixas,
+                             l_float32  thresh,
+                             l_int32    type,
+                             l_int32   *pchanged)
+{
+NUMA  *na, *nai;
+PIXA  *pixad;
+
+    PROCNAME("pixaSelectByPerimToAreaRatio");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT &&
+        type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE)
+        return (PIXA *)ERROR_PTR("invalid type", procName, NULL);
+
+        /* Compute component ratios. */
+    na = pixaFindPerimToAreaRatio(pixas);
+
+        /* Generate indicator array for elements to be saved. */
+    nai = numaMakeThresholdIndicator(na, thresh, type);
+    numaDestroy(&na);
+
+        /* Filter to get output */
+    pixad = pixaSelectWithIndicator(pixas, nai, pchanged);
+
+    numaDestroy(&nai);
+    return pixad;
+}
+
+
+/*!
+ *  pixSelectByPerimSizeRatio()
+ *
+ *      Input:  pixs (1 bpp)
+ *              thresh (threshold ratio of fg boundary to fg pixels)
+ *              connectivity (4 or 8)
+ *              type (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                    L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *              &changed (<optional return> 1 if changed; 0 if clone returned)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The args specify constraints on the size of the
+ *          components that are kept.
+ *      (2) If unchanged, returns a copy of pixs.  Otherwise,
+ *          returns a new pix with the filtered components.
+ *      (3) This filters components with smooth vs. dendritic shape, using
+ *          the ratio of the fg boundary pixels to the circumference of
+ *          the bounding box, and comparing it to a threshold value.
+ *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save the smooth
+ *          boundary components, and L_SELECT_IF_GT or L_SELECT_IF_GTE
+ *          to remove them.
+ */
+PIX *
+pixSelectByPerimSizeRatio(PIX       *pixs,
+                          l_float32  thresh,
+                          l_int32    connectivity,
+                          l_int32    type,
+                          l_int32   *pchanged)
+{
+l_int32  w, h, empty, changed, count;
+BOXA    *boxa;
+PIX     *pixd;
+PIXA    *pixas, *pixad;
+
+    PROCNAME("pixSelectByPerimSizeRatio");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+    if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT &&
+        type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    if (pchanged) *pchanged = FALSE;
+
+        /* Check if any components exist */
+    pixZero(pixs, &empty);
+    if (empty)
+        return pixCopy(NULL, pixs);
+
+        /* Filter thin components */
+    boxa = pixConnComp(pixs, &pixas, connectivity);
+    pixad = pixaSelectByPerimSizeRatio(pixas, thresh, type, &changed);
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixas);
+
+    if (!changed) {
+        pixaDestroy(&pixad);
+        return pixCopy(NULL, pixs);
+    }
+
+        /* Render the result */
+    if (pchanged) *pchanged = TRUE;
+    pixGetDimensions(pixs, &w, &h, NULL);
+    count = pixaGetCount(pixad);
+    if (count == 0) {  /* return empty pix */
+        pixd = pixCreateTemplate(pixs);
+    } else {
+        pixd = pixaDisplay(pixad, w, h);
+        pixCopyResolution(pixd, pixs);
+        pixCopyColormap(pixd, pixs);
+        pixCopyText(pixd, pixs);
+        pixCopyInputFormat(pixd, pixs);
+    }
+    pixaDestroy(&pixad);
+    return pixd;
+}
+
+
+/*!
+ *  pixaSelectByPerimSizeRatio()
+ *
+ *      Input:  pixas
+ *              thresh (threshold ratio of fg boundary to b.b. circumference)
+ *              type (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                    L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *              &changed (<optional return> 1 if changed; 0 if clone returned)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) Returns a pixa clone if no components are removed.
+ *      (2) Uses pix and box clones in the new pixa.
+ *      (3) See pixSelectByPerimSizeRatio().
+ */
+PIXA *
+pixaSelectByPerimSizeRatio(PIXA      *pixas,
+                           l_float32  thresh,
+                           l_int32    type,
+                           l_int32   *pchanged)
+{
+NUMA  *na, *nai;
+PIXA  *pixad;
+
+    PROCNAME("pixaSelectByPerimSizeRatio");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT &&
+        type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE)
+        return (PIXA *)ERROR_PTR("invalid type", procName, NULL);
+
+        /* Compute component ratios. */
+    na = pixaFindPerimSizeRatio(pixas);
+
+        /* Generate indicator array for elements to be saved. */
+    nai = numaMakeThresholdIndicator(na, thresh, type);
+    numaDestroy(&na);
+
+        /* Filter to get output */
+    pixad = pixaSelectWithIndicator(pixas, nai, pchanged);
+
+    numaDestroy(&nai);
+    return pixad;
+}
+
+
+/*!
+ *  pixSelectByAreaFraction()
+ *
+ *      Input:  pixs (1 bpp)
+ *              thresh (threshold ratio of fg pixels to (w * h))
+ *              connectivity (4 or 8)
+ *              type (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                    L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *              &changed (<optional return> 1 if changed; 0 if clone returned)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The args specify constraints on the amount of foreground
+ *          coverage of the components that are kept.
+ *      (2) If unchanged, returns a copy of pixs.  Otherwise,
+ *          returns a new pix with the filtered components.
+ *      (3) This filters components based on the fraction of fg pixels
+ *          of the component in its bounding box.
+ *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components
+ *          with less than the threshold fraction of foreground, and
+ *          L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
+ */
+PIX *
+pixSelectByAreaFraction(PIX       *pixs,
+                        l_float32  thresh,
+                        l_int32    connectivity,
+                        l_int32    type,
+                        l_int32   *pchanged)
+{
+l_int32  w, h, empty, changed, count;
+BOXA    *boxa;
+PIX     *pixd;
+PIXA    *pixas, *pixad;
+
+    PROCNAME("pixSelectByAreaFraction");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+    if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT &&
+        type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    if (pchanged) *pchanged = FALSE;
+
+        /* Check if any components exist */
+    pixZero(pixs, &empty);
+    if (empty)
+        return pixCopy(NULL, pixs);
+
+        /* Filter components */
+    boxa = pixConnComp(pixs, &pixas, connectivity);
+    pixad = pixaSelectByAreaFraction(pixas, thresh, type, &changed);
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixas);
+
+    if (!changed) {
+        pixaDestroy(&pixad);
+        return pixCopy(NULL, pixs);
+    }
+
+        /* Render the result */
+    if (pchanged) *pchanged = TRUE;
+    pixGetDimensions(pixs, &w, &h, NULL);
+    count = pixaGetCount(pixad);
+    if (count == 0) {  /* return empty pix */
+        pixd = pixCreateTemplate(pixs);
+    } else {
+        pixd = pixaDisplay(pixad, w, h);
+        pixCopyResolution(pixd, pixs);
+        pixCopyColormap(pixd, pixs);
+        pixCopyText(pixd, pixs);
+        pixCopyInputFormat(pixd, pixs);
+    }
+    pixaDestroy(&pixad);
+    return pixd;
+}
+
+
+/*!
+ *  pixaSelectByAreaFraction()
+ *
+ *      Input:  pixas
+ *              thresh (threshold ratio of fg pixels to (w * h))
+ *              type (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                    L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *              &changed (<optional return> 1 if changed; 0 if clone returned)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) Returns a pixa clone if no components are removed.
+ *      (2) Uses pix and box clones in the new pixa.
+ *      (3) This filters components based on the fraction of fg pixels
+ *          of the component in its bounding box.
+ *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components
+ *          with less than the threshold fraction of foreground, and
+ *          L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
+ */
+PIXA *
+pixaSelectByAreaFraction(PIXA      *pixas,
+                         l_float32  thresh,
+                         l_int32    type,
+                         l_int32   *pchanged)
+{
+NUMA  *na, *nai;
+PIXA  *pixad;
+
+    PROCNAME("pixaSelectByAreaFraction");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT &&
+        type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE)
+        return (PIXA *)ERROR_PTR("invalid type", procName, NULL);
+
+        /* Compute component ratios. */
+    na = pixaFindAreaFraction(pixas);
+
+        /* Generate indicator array for elements to be saved. */
+    nai = numaMakeThresholdIndicator(na, thresh, type);
+    numaDestroy(&na);
+
+        /* Filter to get output */
+    pixad = pixaSelectWithIndicator(pixas, nai, pchanged);
+
+    numaDestroy(&nai);
+    return pixad;
+}
+
+
+/*!
+ *  pixSelectByWidthHeightRatio()
+ *
+ *      Input:  pixs (1 bpp)
+ *              thresh (threshold ratio of width/height)
+ *              connectivity (4 or 8)
+ *              type (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                    L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *              &changed (<optional return> 1 if changed; 0 if clone returned)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The args specify constraints on the width-to-height ratio
+ *          for components that are kept.
+ *      (2) If unchanged, returns a copy of pixs.  Otherwise,
+ *          returns a new pix with the filtered components.
+ *      (3) This filters components based on the width-to-height ratios.
+ *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components
+ *          with less than the threshold ratio, and
+ *          L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
+ */
+PIX *
+pixSelectByWidthHeightRatio(PIX       *pixs,
+                            l_float32  thresh,
+                            l_int32    connectivity,
+                            l_int32    type,
+                            l_int32   *pchanged)
+{
+l_int32  w, h, empty, changed, count;
+BOXA    *boxa;
+PIX     *pixd;
+PIXA    *pixas, *pixad;
+
+    PROCNAME("pixSelectByWidthHeightRatio");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+    if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT &&
+        type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    if (pchanged) *pchanged = FALSE;
+
+        /* Check if any components exist */
+    pixZero(pixs, &empty);
+    if (empty)
+        return pixCopy(NULL, pixs);
+
+        /* Filter components */
+    boxa = pixConnComp(pixs, &pixas, connectivity);
+    pixad = pixaSelectByWidthHeightRatio(pixas, thresh, type, &changed);
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixas);
+
+    if (!changed) {
+        pixaDestroy(&pixad);
+        return pixCopy(NULL, pixs);
+    }
+
+        /* Render the result */
+    if (pchanged) *pchanged = TRUE;
+    pixGetDimensions(pixs, &w, &h, NULL);
+    count = pixaGetCount(pixad);
+    if (count == 0) {  /* return empty pix */
+        pixd = pixCreateTemplate(pixs);
+    } else {
+        pixd = pixaDisplay(pixad, w, h);
+        pixCopyResolution(pixd, pixs);
+        pixCopyColormap(pixd, pixs);
+        pixCopyText(pixd, pixs);
+        pixCopyInputFormat(pixd, pixs);
+    }
+    pixaDestroy(&pixad);
+    return pixd;
+}
+
+
+/*!
+ *  pixaSelectByWidthHeightRatio()
+ *
+ *      Input:  pixas
+ *              thresh (threshold ratio of width/height)
+ *              type (L_SELECT_IF_LT, L_SELECT_IF_GT,
+ *                    L_SELECT_IF_LTE, L_SELECT_IF_GTE)
+ *              &changed (<optional return> 1 if changed; 0 if clone returned)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) Returns a pixa clone if no components are removed.
+ *      (2) Uses pix and box clones in the new pixa.
+ *      (3) This filters components based on the width-to-height ratio
+ *          of each pix.
+ *      (4) Use L_SELECT_IF_LT or L_SELECT_IF_LTE to save components
+ *          with less than the threshold ratio, and
+ *          L_SELECT_IF_GT or L_SELECT_IF_GTE to remove them.
+ */
+PIXA *
+pixaSelectByWidthHeightRatio(PIXA      *pixas,
+                             l_float32  thresh,
+                             l_int32    type,
+                             l_int32   *pchanged)
+{
+NUMA  *na, *nai;
+PIXA  *pixad;
+
+    PROCNAME("pixaSelectByWidthHeightRatio");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (type != L_SELECT_IF_LT && type != L_SELECT_IF_GT &&
+        type != L_SELECT_IF_LTE && type != L_SELECT_IF_GTE)
+        return (PIXA *)ERROR_PTR("invalid type", procName, NULL);
+
+        /* Compute component ratios. */
+    na = pixaFindWidthHeightRatio(pixas);
+
+        /* Generate indicator array for elements to be saved. */
+    nai = numaMakeThresholdIndicator(na, thresh, type);
+    numaDestroy(&na);
+
+        /* Filter to get output */
+    pixad = pixaSelectWithIndicator(pixas, nai, pchanged);
+
+    numaDestroy(&nai);
+    return pixad;
+}
+
+
+/*!
+ *  pixaSelectWithIndicator()
+ *
+ *      Input:  pixas
+ *              na (indicator numa)
+ *              &changed (<optional return> 1 if changed; 0 if clone returned)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) Returns a pixa clone if no components are removed.
+ *      (2) Uses pix and box clones in the new pixa.
+ *      (3) The indicator numa has values 0 (ignore) and 1 (accept).
+ *      (4) If the source boxa is not fully populated, it is left
+ *          empty in the dest pixa.
+ */
+PIXA *
+pixaSelectWithIndicator(PIXA     *pixas,
+                        NUMA     *na,
+                        l_int32  *pchanged)
+{
+l_int32  i, n, nbox, ival, nsave;
+BOX     *box;
+PIX     *pixt;
+PIXA    *pixad;
+
+    PROCNAME("pixaSelectWithIndicator");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (!na)
+        return (PIXA *)ERROR_PTR("na not defined", procName, NULL);
+
+    nsave = 0;
+    n = numaGetCount(na);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(na, i, &ival);
+        if (ival == 1) nsave++;
+    }
+
+    if (nsave == n) {
+        if (pchanged) *pchanged = FALSE;
+        return pixaCopy(pixas, L_CLONE);
+    }
+    if (pchanged) *pchanged = TRUE;
+    pixad = pixaCreate(nsave);
+    nbox = pixaGetBoxaCount(pixas);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(na, i, &ival);
+        if (ival == 0) continue;
+        pixt = pixaGetPix(pixas, i, L_CLONE);
+        pixaAddPix(pixad, pixt, L_INSERT);
+        if (nbox == n) {   /* fully populated boxa */
+            box = pixaGetBox(pixas, i, L_CLONE);
+            pixaAddBox(pixad, box, L_INSERT);
+        }
+    }
+
+    return pixad;
+}
+
+
+/*!
+ *  pixRemoveWithIndicator()
+ *
+ *      Input:  pixs (1 bpp pix from which components are removed; in-place)
+ *              pixa (of connected components in pixs)
+ *              na (numa indicator: remove components corresponding to 1s)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This complements pixAddWithIndicator().   Here, the selected
+ *          components are set subtracted from pixs.
+ */
+l_int32
+pixRemoveWithIndicator(PIX   *pixs,
+                       PIXA  *pixa,
+                       NUMA  *na)
+{
+l_int32  i, n, ival, x, y, w, h;
+BOX     *box;
+PIX     *pix;
+
+    PROCNAME("pixRemoveWithIndicator");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    n = pixaGetCount(pixa);
+    if (n != numaGetCount(na))
+        return ERROR_INT("pixa and na sizes not equal", procName, 1);
+
+    for (i = 0; i < n; i++) {
+        numaGetIValue(na, i, &ival);
+        if (ival == 1) {
+            pix = pixaGetPix(pixa, i, L_CLONE);
+            box = pixaGetBox(pixa, i, L_CLONE);
+            boxGetGeometry(box, &x, &y, &w, &h);
+            pixRasterop(pixs, x, y, w, h, PIX_DST & PIX_NOT(PIX_SRC),
+                        pix, 0, 0);
+            boxDestroy(&box);
+            pixDestroy(&pix);
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixAddWithIndicator()
+ *
+ *      Input:  pixs (1 bpp pix from which components are added; in-place)
+ *              pixa (of connected components, some of which will be put
+ *                    into pixs)
+ *              na (numa indicator: add components corresponding to 1s)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This complements pixRemoveWithIndicator().   Here, the selected
+ *          components are added to pixs.
+ */
+l_int32
+pixAddWithIndicator(PIX   *pixs,
+                    PIXA  *pixa,
+                    NUMA  *na)
+{
+l_int32  i, n, ival, x, y, w, h;
+BOX     *box;
+PIX     *pix;
+
+    PROCNAME("pixAddWithIndicator");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (!na)
+        return ERROR_INT("na not defined", procName, 1);
+    n = pixaGetCount(pixa);
+    if (n != numaGetCount(na))
+        return ERROR_INT("pixa and na sizes not equal", procName, 1);
+
+    for (i = 0; i < n; i++) {
+        numaGetIValue(na, i, &ival);
+        if (ival == 1) {
+            pix = pixaGetPix(pixa, i, L_CLONE);
+            box = pixaGetBox(pixa, i, L_CLONE);
+            boxGetGeometry(box, &x, &y, &w, &h);
+            pixRasterop(pixs, x, y, w, h, PIX_SRC | PIX_DST, pix, 0, 0);
+            boxDestroy(&box);
+            pixDestroy(&pix);
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixaSelectWithString()
+ *
+ *      Input:  pixas
+ *              str (string of indices into pixa, giving the pix to be selected)
+ *              &error (<optional return> 1 if any indices are invalid;
+ *                      0 if all indices are valid)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) Returns a pixa with copies of selected pix.
+ *      (2) Associated boxes are also copied, if fully populated.
+ */
+PIXA *
+pixaSelectWithString(PIXA        *pixas,
+                     const char  *str,
+                     l_int32     *perror)
+{
+l_int32    i, nval, npix, nbox, val, imaxval;
+l_float32  maxval;
+BOX       *box;
+NUMA      *na;
+PIX       *pix1;
+PIXA      *pixad;
+
+    PROCNAME("pixaSelectWithString");
+
+    if (perror) *perror = 0;
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (!str)
+        return (PIXA *)ERROR_PTR("str not defined", procName, NULL);
+
+    if ((na = numaCreateFromString(str)) == NULL)
+        return (PIXA *)ERROR_PTR("na not made", procName, NULL);
+    if ((nval = numaGetCount(na)) == 0) {
+        numaDestroy(&na);
+        return (PIXA *)ERROR_PTR("no indices found", procName, NULL);
+    }
+    numaGetMax(na, &maxval, NULL);
+    imaxval = (l_int32)(maxval + 0.1);
+    nbox = pixaGetBoxaCount(pixas);
+    npix = pixaGetCount(pixas);
+    if (imaxval >= npix) {
+        if (perror) *perror = 1;
+        L_ERROR("max index = %d, size of pixa = %d\n", procName, imaxval, npix);
+    }
+
+    pixad = pixaCreate(nval);
+    for (i = 0; i < nval; i++) {
+        numaGetIValue(na, i, &val);
+        if (val < 0 || val >= npix) {
+            L_ERROR("index %d out of range of pix\n", procName, val);
+            continue;
+        }
+        pix1 = pixaGetPix(pixas, val, L_COPY);
+        pixaAddPix(pixad, pix1, L_INSERT);
+        if (nbox == npix) {   /* fully populated boxa */
+            box = pixaGetBox(pixas, val, L_COPY);
+            pixaAddBox(pixad, box, L_INSERT);
+        }
+    }
+    numaDestroy(&na);
+    return pixad;
+}
+
+
+/*!
+ *  pixaRenderComponent()
+ *
+ *      Input:  pixs (<optional> 1 bpp pix)
+ *              pixa (of 1 bpp connected components, one of which will
+ *                    be rendered in pixs, with its origin determined
+ *                    by the associated box.)
+ *              index (of component to be rendered)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) If pixs is null, this generates an empty pix of a size determined
+ *          by union of the component bounding boxes, and including the origin.
+ *      (2) The selected component is blitted into pixs.
+ */
+PIX *
+pixaRenderComponent(PIX     *pixs,
+                    PIXA    *pixa,
+                    l_int32  index)
+{
+l_int32  n, x, y, w, h, maxdepth;
+BOX     *box;
+BOXA    *boxa;
+PIX     *pix;
+
+    PROCNAME("pixaRenderComponent");
+
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, pixs);
+    n = pixaGetCount(pixa);
+    if (index < 0 || index >= n)
+        return (PIX *)ERROR_PTR("invalid index", procName, pixs);
+    if (pixs && (pixGetDepth(pixs) != 1))
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixs);
+    pixaVerifyDepth(pixa, &maxdepth);
+    if (maxdepth > 1)
+        return (PIX *)ERROR_PTR("not all pix with d == 1", procName, pixs);
+
+    boxa = pixaGetBoxa(pixa, L_CLONE);
+    if (!pixs) {
+        boxaGetExtent(boxa, &w, &h, NULL);
+        pixs = pixCreate(w, h, 1);
+    }
+
+    pix = pixaGetPix(pixa, index, L_CLONE);
+    box = boxaGetBox(boxa, index, L_CLONE);
+    boxGetGeometry(box, &x, &y, &w, &h);
+    pixRasterop(pixs, x, y, w, h, PIX_SRC | PIX_DST, pix, 0, 0);
+    boxDestroy(&box);
+    pixDestroy(&pix);
+    boxaDestroy(&boxa);
+
+    return pixs;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                              Sort functions                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaSort()
+ *
+ *      Input:  pixas
+ *              sorttype (L_SORT_BY_X, L_SORT_BY_Y, L_SORT_BY_WIDTH,
+ *                        L_SORT_BY_HEIGHT, L_SORT_BY_MIN_DIMENSION,
+ *                        L_SORT_BY_MAX_DIMENSION, L_SORT_BY_PERIMETER,
+ *                        L_SORT_BY_AREA, L_SORT_BY_ASPECT_RATIO)
+ *              sortorder  (L_SORT_INCREASING, L_SORT_DECREASING)
+ *              &naindex (<optional return> index of sorted order into
+ *                        original array)
+ *              copyflag (L_COPY, L_CLONE)
+ *      Return: pixad (sorted version of pixas), or null on error
+ *
+ *  Notes:
+ *      (1) This sorts based on the data in the boxa.  If the boxa
+ *          count is not the same as the pixa count, this returns an error.
+ *      (2) The copyflag refers to the pix and box copies that are
+ *          inserted into the sorted pixa.  These are either L_COPY
+ *          or L_CLONE.
+ */
+PIXA *
+pixaSort(PIXA    *pixas,
+         l_int32  sorttype,
+         l_int32  sortorder,
+         NUMA   **pnaindex,
+         l_int32  copyflag)
+{
+l_int32  i, n, x, y, w, h;
+BOXA    *boxa;
+NUMA    *na, *naindex;
+PIXA    *pixad;
+
+    PROCNAME("pixaSort");
+
+    if (pnaindex) *pnaindex = NULL;
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y &&
+        sorttype != L_SORT_BY_WIDTH && sorttype != L_SORT_BY_HEIGHT &&
+        sorttype != L_SORT_BY_MIN_DIMENSION &&
+        sorttype != L_SORT_BY_MAX_DIMENSION &&
+        sorttype != L_SORT_BY_PERIMETER &&
+        sorttype != L_SORT_BY_AREA &&
+        sorttype != L_SORT_BY_ASPECT_RATIO)
+        return (PIXA *)ERROR_PTR("invalid sort type", procName, NULL);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return (PIXA *)ERROR_PTR("invalid sort order", procName, NULL);
+    if (copyflag != L_COPY && copyflag != L_CLONE)
+        return (PIXA *)ERROR_PTR("invalid copy flag", procName, NULL);
+
+    if ((boxa = pixas->boxa) == NULL)   /* not owned; do not destroy */
+        return (PIXA *)ERROR_PTR("boxa not found", procName, NULL);
+    n = pixaGetCount(pixas);
+    if (boxaGetCount(boxa) != n)
+        return (PIXA *)ERROR_PTR("boxa and pixa counts differ", procName, NULL);
+
+        /* Use O(n) binsort if possible */
+    if (n > MIN_COMPS_FOR_BIN_SORT &&
+        ((sorttype == L_SORT_BY_X) || (sorttype == L_SORT_BY_Y) ||
+         (sorttype == L_SORT_BY_WIDTH) || (sorttype == L_SORT_BY_HEIGHT) ||
+         (sorttype == L_SORT_BY_PERIMETER)))
+        return pixaBinSort(pixas, sorttype, sortorder, pnaindex, copyflag);
+
+        /* Build up numa of specific data */
+    if ((na = numaCreate(n)) == NULL)
+        return (PIXA *)ERROR_PTR("na not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h);
+        switch (sorttype)
+        {
+        case L_SORT_BY_X:
+            numaAddNumber(na, x);
+            break;
+        case L_SORT_BY_Y:
+            numaAddNumber(na, y);
+            break;
+        case L_SORT_BY_WIDTH:
+            numaAddNumber(na, w);
+            break;
+        case L_SORT_BY_HEIGHT:
+            numaAddNumber(na, h);
+            break;
+        case L_SORT_BY_MIN_DIMENSION:
+            numaAddNumber(na, L_MIN(w, h));
+            break;
+        case L_SORT_BY_MAX_DIMENSION:
+            numaAddNumber(na, L_MAX(w, h));
+            break;
+        case L_SORT_BY_PERIMETER:
+            numaAddNumber(na, w + h);
+            break;
+        case L_SORT_BY_AREA:
+            numaAddNumber(na, w * h);
+            break;
+        case L_SORT_BY_ASPECT_RATIO:
+            numaAddNumber(na, (l_float32)w / (l_float32)h);
+            break;
+        default:
+            L_WARNING("invalid sort type\n", procName);
+        }
+    }
+
+        /* Get the sort index for data array */
+    if ((naindex = numaGetSortIndex(na, sortorder)) == NULL)
+        return (PIXA *)ERROR_PTR("naindex not made", procName, NULL);
+
+        /* Build up sorted pixa using sort index */
+    if ((pixad = pixaSortByIndex(pixas, naindex, copyflag)) == NULL)
+        return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);
+
+    if (pnaindex)
+        *pnaindex = naindex;
+    else
+        numaDestroy(&naindex);
+    numaDestroy(&na);
+    return pixad;
+}
+
+
+/*!
+ *  pixaBinSort()
+ *
+ *      Input:  pixas
+ *              sorttype (L_SORT_BY_X, L_SORT_BY_Y, L_SORT_BY_WIDTH,
+ *                        L_SORT_BY_HEIGHT, L_SORT_BY_PERIMETER)
+ *              sortorder  (L_SORT_INCREASING, L_SORT_DECREASING)
+ *              &naindex (<optional return> index of sorted order into
+ *                        original array)
+ *              copyflag (L_COPY, L_CLONE)
+ *      Return: pixad (sorted version of pixas), or null on error
+ *
+ *  Notes:
+ *      (1) This sorts based on the data in the boxa.  If the boxa
+ *          count is not the same as the pixa count, this returns an error.
+ *      (2) The copyflag refers to the pix and box copies that are
+ *          inserted into the sorted pixa.  These are either L_COPY
+ *          or L_CLONE.
+ *      (3) For a large number of boxes (say, greater than 1000), this
+ *          O(n) binsort is much faster than the O(nlogn) shellsort.
+ *          For 5000 components, this is over 20x faster than boxaSort().
+ *      (4) Consequently, pixaSort() calls this function if it will
+ *          likely go much faster.
+ */
+PIXA *
+pixaBinSort(PIXA    *pixas,
+            l_int32  sorttype,
+            l_int32  sortorder,
+            NUMA   **pnaindex,
+            l_int32  copyflag)
+{
+l_int32  i, n, x, y, w, h;
+BOXA    *boxa;
+NUMA    *na, *naindex;
+PIXA    *pixad;
+
+    PROCNAME("pixaBinSort");
+
+    if (pnaindex) *pnaindex = NULL;
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y &&
+        sorttype != L_SORT_BY_WIDTH && sorttype != L_SORT_BY_HEIGHT &&
+        sorttype != L_SORT_BY_PERIMETER)
+        return (PIXA *)ERROR_PTR("invalid sort type", procName, NULL);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return (PIXA *)ERROR_PTR("invalid sort order", procName, NULL);
+    if (copyflag != L_COPY && copyflag != L_CLONE)
+        return (PIXA *)ERROR_PTR("invalid copy flag", procName, NULL);
+
+        /* Verify that the pixa and its boxa have the same count */
+    if ((boxa = pixas->boxa) == NULL)   /* not owned; do not destroy */
+        return (PIXA *)ERROR_PTR("boxa not found", procName, NULL);
+    n = pixaGetCount(pixas);
+    if (boxaGetCount(boxa) != n)
+        return (PIXA *)ERROR_PTR("boxa and pixa counts differ", procName, NULL);
+
+        /* Generate Numa of appropriate box dimensions */
+    if ((na = numaCreate(n)) == NULL)
+        return (PIXA *)ERROR_PTR("na not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h);
+        switch (sorttype)
+        {
+        case L_SORT_BY_X:
+            numaAddNumber(na, x);
+            break;
+        case L_SORT_BY_Y:
+            numaAddNumber(na, y);
+            break;
+        case L_SORT_BY_WIDTH:
+            numaAddNumber(na, w);
+            break;
+        case L_SORT_BY_HEIGHT:
+            numaAddNumber(na, h);
+            break;
+        case L_SORT_BY_PERIMETER:
+            numaAddNumber(na, w + h);
+            break;
+        default:
+            L_WARNING("invalid sort type\n", procName);
+        }
+    }
+
+        /* Get the sort index for data array */
+    if ((naindex = numaGetBinSortIndex(na, sortorder)) == NULL)
+        return (PIXA *)ERROR_PTR("naindex not made", procName, NULL);
+
+        /* Build up sorted pixa using sort index */
+    if ((pixad = pixaSortByIndex(pixas, naindex, copyflag)) == NULL)
+        return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);
+
+    if (pnaindex)
+        *pnaindex = naindex;
+    else
+        numaDestroy(&naindex);
+    numaDestroy(&na);
+    return pixad;
+}
+
+
+/*!
+ *  pixaSortByIndex()
+ *
+ *      Input:  pixas
+ *              naindex (na that maps from the new pixa to the input pixa)
+ *              copyflag (L_COPY, L_CLONE)
+ *      Return: pixad (sorted), or null on error
+ */
+PIXA *
+pixaSortByIndex(PIXA    *pixas,
+                NUMA    *naindex,
+                l_int32  copyflag)
+{
+l_int32  i, n, index;
+BOX     *box;
+PIX     *pix;
+PIXA    *pixad;
+
+    PROCNAME("pixaSortByIndex");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (!naindex)
+        return (PIXA *)ERROR_PTR("naindex not defined", procName, NULL);
+    if (copyflag != L_CLONE && copyflag != L_COPY)
+        return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    n = pixaGetCount(pixas);
+    pixad = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(naindex, i, &index);
+        pix = pixaGetPix(pixas, index, copyflag);
+        box = pixaGetBox(pixas, index, copyflag);
+        pixaAddPix(pixad, pix, L_INSERT);
+        pixaAddBox(pixad, box, L_INSERT);
+    }
+
+    return pixad;
+}
+
+
+/*!
+ *  pixaSort2dByIndex()
+ *
+ *      Input:  pixas
+ *              naa (numaa that maps from the new pixaa to the input pixas)
+ *              copyflag (L_CLONE or L_COPY)
+ *      Return: paa (sorted), or null on error
+ */
+PIXAA *
+pixaSort2dByIndex(PIXA    *pixas,
+                  NUMAA   *naa,
+                  l_int32  copyflag)
+{
+l_int32  pixtot, ntot, i, j, n, nn, index;
+BOX     *box;
+NUMA    *na;
+PIX     *pix;
+PIXA    *pixa;
+PIXAA   *paa;
+
+    PROCNAME("pixaSort2dByIndex");
+
+    if (!pixas)
+        return (PIXAA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (!naa)
+        return (PIXAA *)ERROR_PTR("naindex not defined", procName, NULL);
+
+        /* Check counts */
+    ntot = numaaGetNumberCount(naa);
+    pixtot = pixaGetCount(pixas);
+    if (ntot != pixtot)
+        return (PIXAA *)ERROR_PTR("element count mismatch", procName, NULL);
+
+    n = numaaGetCount(naa);
+    paa = pixaaCreate(n);
+    for (i = 0; i < n; i++) {
+        na = numaaGetNuma(naa, i, L_CLONE);
+        nn = numaGetCount(na);
+        pixa = pixaCreate(nn);
+        for (j = 0; j < nn; j++) {
+            numaGetIValue(na, j, &index);
+            pix = pixaGetPix(pixas, index, copyflag);
+            box = pixaGetBox(pixas, index, copyflag);
+            pixaAddPix(pixa, pix, L_INSERT);
+            pixaAddBox(pixa, box, L_INSERT);
+        }
+        pixaaAddPixa(paa, pixa, L_INSERT);
+        numaDestroy(&na);
+    }
+
+    return paa;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                    Pixa and Pixaa range selection                   *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaSelectRange()
+ *
+ *      Input:  pixas
+ *              first (use 0 to select from the beginning)
+ *              last (use 0 to select to the end)
+ *              copyflag (L_COPY, L_CLONE)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) The copyflag specifies what we do with each pix from pixas.
+ *          Specifically, L_CLONE inserts a clone into pixad of each
+ *          selected pix from pixas.
+ */
+PIXA *
+pixaSelectRange(PIXA    *pixas,
+                l_int32  first,
+                l_int32  last,
+                l_int32  copyflag)
+{
+l_int32  n, npix, i;
+PIX     *pix;
+PIXA    *pixad;
+
+    PROCNAME("pixaSelectRange");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (copyflag != L_COPY && copyflag != L_CLONE)
+        return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL);
+    n = pixaGetCount(pixas);
+    first = L_MAX(0, first);
+    if (last <= 0) last = n - 1;
+    if (first >= n)
+        return (PIXA *)ERROR_PTR("invalid first", procName, NULL);
+    if (first > last)
+        return (PIXA *)ERROR_PTR("first > last", procName, NULL);
+
+    npix = last - first + 1;
+    pixad = pixaCreate(npix);
+    for (i = first; i <= last; i++) {
+        pix = pixaGetPix(pixas, i, copyflag);
+        pixaAddPix(pixad, pix, L_INSERT);
+    }
+    return pixad;
+}
+
+
+/*!
+ *  pixaaSelectRange()
+ *
+ *      Input:  paas
+ *              first (use 0 to select from the beginning)
+ *              last (use 0 to select to the end)
+ *              copyflag (L_COPY, L_CLONE)
+ *      Return: paad, or null on error
+ *
+ *  Notes:
+ *      (1) The copyflag specifies what we do with each pixa from paas.
+ *          Specifically, L_CLONE inserts a clone into paad of each
+ *          selected pixa from paas.
+ */
+PIXAA *
+pixaaSelectRange(PIXAA   *paas,
+                 l_int32  first,
+                 l_int32  last,
+                 l_int32  copyflag)
+{
+l_int32  n, npixa, i;
+PIXA    *pixa;
+PIXAA   *paad;
+
+    PROCNAME("pixaaSelectRange");
+
+    if (!paas)
+        return (PIXAA *)ERROR_PTR("paas not defined", procName, NULL);
+    if (copyflag != L_COPY && copyflag != L_CLONE)
+        return (PIXAA *)ERROR_PTR("invalid copyflag", procName, NULL);
+    n = pixaaGetCount(paas, NULL);
+    first = L_MAX(0, first);
+    if (last <= 0) last = n - 1;
+    if (first >= n)
+        return (PIXAA *)ERROR_PTR("invalid first", procName, NULL);
+    if (first > last)
+        return (PIXAA *)ERROR_PTR("first > last", procName, NULL);
+
+    npixa = last - first + 1;
+    paad = pixaaCreate(npixa);
+    for (i = first; i <= last; i++) {
+        pixa = pixaaGetPixa(paas, i, copyflag);
+        pixaaAddPixa(paad, pixa, L_INSERT);
+    }
+    return paad;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Pixa and Pixaa scaling                       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaaScaleToSize()
+ *
+ *      Input:  paas
+ *              wd  (target width; use 0 if using height as target)
+ *              hd  (target height; use 0 if using width as target)
+ *      Return: paad, or null on error
+ *
+ *  Notes:
+ *      (1) This guarantees that each output scaled image has the
+ *          dimension(s) you specify.
+ *           - To specify the width with isotropic scaling, set @hd = 0.
+ *           - To specify the height with isotropic scaling, set @wd = 0.
+ *           - If both @wd and @hd are specified, the image is scaled
+ *             (in general, anisotropically) to that size.
+ *           - It is an error to set both @wd and @hd to 0.
+ */
+PIXAA *
+pixaaScaleToSize(PIXAA   *paas,
+                 l_int32  wd,
+                 l_int32  hd)
+{
+l_int32  n, i;
+PIXA    *pixa1, *pixa2;
+PIXAA   *paad;
+
+    PROCNAME("pixaaScaleToSize");
+
+    if (!paas)
+        return (PIXAA *)ERROR_PTR("paas not defined", procName, NULL);
+    if (wd <= 0 && hd <= 0)
+        return (PIXAA *)ERROR_PTR("neither wd nor hd > 0", procName, NULL);
+
+    n = pixaaGetCount(paas, NULL);
+    paad = pixaaCreate(n);
+    for (i = 0; i < n; i++) {
+        pixa1 = pixaaGetPixa(paas, i, L_CLONE);
+        pixa2 = pixaScaleToSize(pixa1, wd, hd);
+        pixaaAddPixa(paad, pixa2, L_INSERT);
+        pixaDestroy(&pixa1);
+    }
+    return paad;
+}
+
+
+/*!
+ *  pixaaScaleToSizeVar()
+ *
+ *      Input:  paas
+ *              nawd  (<optional> target widths; use NULL if using height)
+ *              nahd  (<optional> target height; use NULL if using width)
+ *      Return: paad, or null on error
+ *
+ *  Notes:
+ *      (1) This guarantees that the scaled images in each pixa have the
+ *          dimension(s) you specify in the numas.
+ *           - To specify the width with isotropic scaling, set @nahd = NULL.
+ *           - To specify the height with isotropic scaling, set @nawd = NULL.
+ *           - If both @nawd and @nahd are specified, the image is scaled
+ *             (in general, anisotropically) to that size.
+ *           - It is an error to set both @nawd and @nahd to NULL.
+ *      (2) If either nawd and/or nahd is defined, it must have the same
+ *          count as the number of pixa in paas.
+ */
+PIXAA *
+pixaaScaleToSizeVar(PIXAA  *paas,
+                    NUMA   *nawd,
+                    NUMA   *nahd)
+{
+l_int32  n, i, wd, hd;
+PIXA    *pixa1, *pixa2;
+PIXAA   *paad;
+
+    PROCNAME("pixaaScaleToSizeVar");
+
+    if (!paas)
+        return (PIXAA *)ERROR_PTR("paas not defined", procName, NULL);
+    if (!nawd && !nahd)
+        return (PIXAA *)ERROR_PTR("!nawd && !nahd", procName, NULL);
+
+    n = pixaaGetCount(paas, NULL);
+    if (nawd && (n != numaGetCount(nawd)))
+        return (PIXAA *)ERROR_PTR("nawd wrong size", procName, NULL);
+    if (nahd && (n != numaGetCount(nahd)))
+        return (PIXAA *)ERROR_PTR("nahd wrong size", procName, NULL);
+    paad = pixaaCreate(n);
+    for (i = 0; i < n; i++) {
+        wd = hd = 0;
+        if (nawd) numaGetIValue(nawd, i, &wd);
+        if (nahd) numaGetIValue(nahd, i, &hd);
+        pixa1 = pixaaGetPixa(paas, i, L_CLONE);
+        pixa2 = pixaScaleToSize(pixa1, wd, hd);
+        pixaaAddPixa(paad, pixa2, L_INSERT);
+        pixaDestroy(&pixa1);
+    }
+    return paad;
+}
+
+
+/*!
+ *  pixaScaleToSize()
+ *
+ *      Input:  pixas
+ *              wd  (target width; use 0 if using height as target)
+ *              hd  (target height; use 0 if using width as target)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) See pixaaScaleToSize()
+ */
+PIXA *
+pixaScaleToSize(PIXA    *pixas,
+                l_int32  wd,
+                l_int32  hd)
+{
+l_int32  n, i;
+PIX     *pix1, *pix2;
+PIXA    *pixad;
+
+    PROCNAME("pixaScaleToSize");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (wd <= 0 && hd <= 0)
+        return (PIXA *)ERROR_PTR("neither wd nor hd > 0", procName, NULL);
+
+    n = pixaGetCount(pixas);
+    pixad = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixas, i, L_CLONE);
+        pix2 = pixScaleToSize(pix1, wd, hd);
+        pixCopyText(pix2, pix1);
+        pixaAddPix(pixad, pix2, L_INSERT);
+        pixDestroy(&pix1);
+    }
+    return pixad;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Miscellaneous functions                      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaAddBorderGeneral()
+ *
+ *      Input:  pixad (can be null or equal to pixas)
+ *              pixas (containing pix of all depths; colormap ok)
+ *              left, right, top, bot  (number of pixels added)
+ *              val   (value of added border pixels)
+ *      Return: pixad (with border added to each pix), including on error
+ *
+ *  Notes:
+ *      (1) For binary images:
+ *             white:  val = 0
+ *             black:  val = 1
+ *          For grayscale images:
+ *             white:  val = 2 ** d - 1
+ *             black:  val = 0
+ *          For rgb color images:
+ *             white:  val = 0xffffff00
+ *             black:  val = 0
+ *          For colormapped images, use 'index' found this way:
+ *             white: pixcmapGetRankIntensity(cmap, 1.0, &index);
+ *             black: pixcmapGetRankIntensity(cmap, 0.0, &index);
+ *      (2) For in-place replacement of each pix with a bordered version,
+ *          use @pixad = @pixas.  To make a new pixa, use @pixad = NULL.
+ *      (3) In both cases, the boxa has sides adjusted as if it were
+ *          expanded by the border.
+ */
+PIXA *
+pixaAddBorderGeneral(PIXA     *pixad,
+                     PIXA     *pixas,
+                     l_int32   left,
+                     l_int32   right,
+                     l_int32   top,
+                     l_int32   bot,
+                     l_uint32  val)
+{
+l_int32  i, n, nbox;
+BOX     *box;
+BOXA    *boxad;
+PIX     *pixs, *pixd;
+
+    PROCNAME("pixaAddBorderGeneral");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, pixad);
+    if (left < 0 || right < 0 || top < 0 || bot < 0)
+        return (PIXA *)ERROR_PTR("negative border added!", procName, pixad);
+    if (pixad && (pixad != pixas))
+        return (PIXA *)ERROR_PTR("pixad defined but != pixas", procName, pixad);
+
+    n = pixaGetCount(pixas);
+    if (!pixad)
+        pixad = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pixs = pixaGetPix(pixas, i, L_CLONE);
+        pixd = pixAddBorderGeneral(pixs, left, right, top, bot, val);
+        if (pixad == pixas)  /* replace */
+            pixaReplacePix(pixad, i, pixd, NULL);
+        else
+            pixaAddPix(pixad, pixd, L_INSERT);
+        pixDestroy(&pixs);
+    }
+
+    nbox = pixaGetBoxaCount(pixas);
+    boxad = pixaGetBoxa(pixad, L_CLONE);
+    for (i = 0; i < nbox; i++) {
+        if ((box = pixaGetBox(pixas, i, L_COPY)) == NULL) {
+            L_WARNING("box %d not found\n", procName, i);
+            break;
+        }
+        boxAdjustSides(box, box, -left, right, -top, bot);
+        if (pixad == pixas)  /* replace */
+            boxaReplaceBox(boxad, i, box);
+        else
+            boxaAddBox(boxad, box, L_INSERT);
+    }
+    boxaDestroy(&boxad);
+
+    return pixad;
+}
+
+
+/*!
+ *  pixaaFlattenToPixa()
+ *
+ *      Input:  paa
+ *              &naindex  (<optional return> the pixa index in the pixaa)
+ *              copyflag  (L_COPY or L_CLONE)
+ *      Return: pixa, or null on error
+ *
+ *  Notes:
+ *      (1) This 'flattens' the pixaa to a pixa, taking the pix in
+ *          order in the first pixa, then the second, etc.
+ *      (2) If &naindex is defined, we generate a Numa that gives, for
+ *          each pix in the pixaa, the index of the pixa to which it belongs.
+ */
+PIXA *
+pixaaFlattenToPixa(PIXAA   *paa,
+                   NUMA   **pnaindex,
+                   l_int32  copyflag)
+{
+l_int32  i, j, m, mb, n;
+BOX     *box;
+NUMA    *naindex;
+PIX     *pix;
+PIXA    *pixa, *pixat;
+
+    PROCNAME("pixaaFlattenToPixa");
+
+    if (pnaindex) *pnaindex = NULL;
+    if (!paa)
+        return (PIXA *)ERROR_PTR("paa not defined", procName, NULL);
+    if (copyflag != L_COPY && copyflag != L_CLONE)
+        return (PIXA *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    if (pnaindex) {
+        naindex = numaCreate(0);
+        *pnaindex = naindex;
+    }
+
+    n = pixaaGetCount(paa, NULL);
+    pixa = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pixat = pixaaGetPixa(paa, i, L_CLONE);
+        m = pixaGetCount(pixat);
+        mb = pixaGetBoxaCount(pixat);
+        for (j = 0; j < m; j++) {
+            pix = pixaGetPix(pixat, j, copyflag);
+            pixaAddPix(pixa, pix, L_INSERT);
+            if (j < mb) {
+                box = pixaGetBox(pixat, j, copyflag);
+                pixaAddBox(pixa, box, L_INSERT);
+            }
+            if (pnaindex)
+                numaAddNumber(naindex, i);  /* save 'row' number */
+        }
+        pixaDestroy(&pixat);
+    }
+
+    return pixa;
+}
+
+
+/*!
+ *  pixaaSizeRange()
+ *
+ *      Input:  paa
+ *              &minw, &minh, &maxw, &maxh (<optional return> range of
+ *                                          dimensions of all boxes)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixaaSizeRange(PIXAA    *paa,
+               l_int32  *pminw,
+               l_int32  *pminh,
+               l_int32  *pmaxw,
+               l_int32  *pmaxh)
+{
+l_int32  minw, minh, maxw, maxh, minpw, minph, maxpw, maxph, i, n;
+PIXA    *pixa;
+
+    PROCNAME("pixaaSizeRange");
+
+    if (pminw) *pminw = 0;
+    if (pminh) *pminh = 0;
+    if (pmaxw) *pmaxw = 0;
+    if (pmaxh) *pmaxh = 0;
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+    if (!pminw && !pmaxw && !pminh && !pmaxh)
+        return ERROR_INT("no data can be returned", procName, 1);
+
+    minw = minh = 100000000;
+    maxw = maxh = 0;
+    n = pixaaGetCount(paa, NULL);
+    for (i = 0; i < n; i++) {
+        pixa = pixaaGetPixa(paa, i, L_CLONE);
+        pixaSizeRange(pixa, &minpw, &minph, &maxpw, &maxph);
+        if (minpw < minw)
+            minw = minpw;
+        if (minph < minh)
+            minh = minph;
+        if (maxpw > maxw)
+            maxw = maxpw;
+        if (maxph > maxh)
+            maxh = maxph;
+        pixaDestroy(&pixa);
+    }
+
+    if (pminw) *pminw = minw;
+    if (pminh) *pminh = minh;
+    if (pmaxw) *pmaxw = maxw;
+    if (pmaxh) *pmaxh = maxh;
+    return 0;
+}
+
+
+/*!
+ *  pixaSizeRange()
+ *
+ *      Input:  pixa
+ *              &minw, &minh, &maxw, &maxh (<optional return> range of
+ *                                          dimensions of pix in the array)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixaSizeRange(PIXA     *pixa,
+              l_int32  *pminw,
+              l_int32  *pminh,
+              l_int32  *pmaxw,
+              l_int32  *pmaxh)
+{
+l_int32  minw, minh, maxw, maxh, i, n, w, h;
+PIX     *pix;
+
+    PROCNAME("pixaSizeRange");
+
+    if (pminw) *pminw = 0;
+    if (pminh) *pminh = 0;
+    if (pmaxw) *pmaxw = 0;
+    if (pmaxh) *pmaxh = 0;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (!pminw && !pmaxw && !pminh && !pmaxh)
+        return ERROR_INT("no data can be returned", procName, 1);
+
+    minw = minh = 1000000;
+    maxw = maxh = 0;
+    n = pixaGetCount(pixa);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        w = pixGetWidth(pix);
+        h = pixGetHeight(pix);
+        if (w < minw)
+            minw = w;
+        if (h < minh)
+            minh = h;
+        if (w > maxw)
+            maxw = w;
+        if (h > maxh)
+            maxh = h;
+        pixDestroy(&pix);
+    }
+
+    if (pminw) *pminw = minw;
+    if (pminh) *pminh = minh;
+    if (pmaxw) *pmaxw = maxw;
+    if (pmaxh) *pmaxh = maxh;
+
+    return 0;
+}
+
+
+/*!
+ *  pixaClipToPix()
+ *
+ *      Input:  pixas
+ *              pixs
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) This is intended for use in situations where pixas
+ *          was originally generated from the input pixs.
+ *      (2) Returns a pixad where each pix in pixas is ANDed
+ *          with its associated region of the input pixs.  This
+ *          region is specified by the the box that is associated
+ *          with the pix.
+ *      (3) In a typical application of this function, pixas has
+ *          a set of region masks, so this generates a pixa of
+ *          the parts of pixs that correspond to each region
+ *          mask component, along with the bounding box for
+ *          the region.
+ */
+PIXA *
+pixaClipToPix(PIXA  *pixas,
+              PIX   *pixs)
+{
+l_int32  i, n;
+BOX     *box;
+PIX     *pix, *pixc;
+PIXA    *pixad;
+
+    PROCNAME("pixaClipToPix");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (!pixs)
+        return (PIXA *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    n = pixaGetCount(pixas);
+    if ((pixad = pixaCreate(n)) == NULL)
+        return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);
+
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixas, i, L_CLONE);
+        box = pixaGetBox(pixas, i, L_COPY);
+        pixc = pixClipRectangle(pixs, box, NULL);
+        pixAnd(pixc, pixc, pix);
+        pixaAddPix(pixad, pixc, L_INSERT);
+        pixaAddBox(pixad, box, L_INSERT);
+        pixDestroy(&pix);
+    }
+
+    return pixad;
+}
+
+
+/*!
+ *  pixaGetRenderingDepth()
+ *
+ *      Input:  pixa
+ *              &depth (<return> depth required to render if all
+ *                      colormaps are removed)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixaGetRenderingDepth(PIXA     *pixa,
+                      l_int32  *pdepth)
+{
+l_int32  hascolor, maxdepth;
+
+    PROCNAME("pixaGetRenderingDepth");
+
+    if (!pdepth)
+        return ERROR_INT("&depth not defined", procName, 1);
+    *pdepth = 0;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    pixaHasColor(pixa, &hascolor);
+    if (hascolor) {
+        *pdepth = 32;
+        return 0;
+    }
+
+    pixaGetDepthInfo(pixa, &maxdepth, NULL);
+    if (maxdepth == 1)
+        *pdepth = 1;
+    else  /* 2, 4, 8 or 16 */
+        *pdepth = 8;
+    return 0;
+}
+
+
+/*!
+ *  pixaHasColor()
+ *
+ *      Input:  pixa
+ *              &hascolor (<return> 1 if any pix is rgb or has
+ *                         a colormap with color; 0 otherwise)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixaHasColor(PIXA     *pixa,
+             l_int32  *phascolor)
+{
+l_int32   i, n, hascolor, d;
+PIX      *pix;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixaHasColor");
+
+    if (!phascolor)
+        return ERROR_INT("&hascolor not defined", procName, 1);
+    *phascolor = 0;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    n = pixaGetCount(pixa);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        if ((cmap = pixGetColormap(pix)) != NULL)
+            pixcmapHasColor(cmap, &hascolor);
+        d = pixGetDepth(pix);
+        pixDestroy(&pix);
+        if (d == 32 || hascolor == 1) {
+            *phascolor = 1;
+            break;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixaAnyColormaps()
+ *
+ *      Input:  pixa
+ *              &hascmap (<return> 1 if any pix has a colormap; 0 otherwise)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixaAnyColormaps(PIXA     *pixa,
+                 l_int32  *phascmap)
+{
+l_int32   i, n;
+PIX      *pix;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixaAnyColormaps");
+
+    if (!phascmap)
+        return ERROR_INT("&hascmap not defined", procName, 1);
+    *phascmap = 0;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    n = pixaGetCount(pixa);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        cmap = pixGetColormap(pix);
+        pixDestroy(&pix);
+        if (cmap) {
+            *phascmap = 1;
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixaGetDepthInfo()
+ *
+ *      Input:  pixa
+ *              &maxdepth (<optional return> max pixel depth of pix in pixa)
+ *              &same (<optional return> true if all depths are equal)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixaGetDepthInfo(PIXA     *pixa,
+                 l_int32  *pmaxdepth,
+                 l_int32  *psame)
+{
+l_int32  i, n, d, d0;
+l_int32  maxd, same;  /* depth info */
+
+    PROCNAME("pixaGetDepthInfo");
+
+    if (pmaxdepth) *pmaxdepth = 0;
+    if (psame) *psame = TRUE;
+    if (!pmaxdepth && !psame) return 0;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if ((n = pixaGetCount(pixa)) == 0)
+        return ERROR_INT("pixa is empty", procName, 1);
+
+    same = TRUE;
+    maxd = 0;
+    for (i = 0; i < n; i++) {
+        pixaGetPixDimensions(pixa, i, NULL, NULL, &d);
+        if (i == 0)
+            d0 = d;
+        else if (d != d0)
+            same = FALSE;
+        if (d > maxd) maxd = d;
+    }
+
+    if (pmaxdepth) *pmaxdepth = maxd;
+    if (psame) *psame = same;
+    return 0;
+}
+
+
+/*!
+ *  pixaConvertToSameDepth()
+ *
+ *      Input:  pixas
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) If any pix has a colormap, they are all converted to rgb.
+ *          Otherwise, they are all converted to the maximum depth of
+ *          all the pix.
+ *      (2) This can be used to allow lossless rendering onto a single pix.
+ */
+PIXA *
+pixaConvertToSameDepth(PIXA  *pixas)
+{
+l_int32  i, n, same, hascmap, maxdepth;
+PIX     *pix, *pixt;
+PIXA    *pixat, *pixad;
+
+    PROCNAME("pixaConvertToSameDepth");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+
+        /* Remove colormaps to rgb */
+    if ((n = pixaGetCount(pixas)) == 0)
+        return (PIXA *)ERROR_PTR("no components", procName, NULL);
+    pixaAnyColormaps(pixas, &hascmap);
+    if (hascmap) {
+        pixat = pixaCreate(n);
+        for (i = 0; i < n; i++) {
+            pixt = pixaGetPix(pixas, i, L_CLONE);
+            pix = pixConvertTo32(pixt);
+            pixaAddPix(pixat, pix, L_INSERT);
+            pixDestroy(&pixt);
+        }
+    } else {
+        pixat = pixaCopy(pixas, L_CLONE);
+    }
+
+    pixaGetDepthInfo(pixat, &maxdepth, &same);
+    if (!same) {  /* at least one pix has depth > 1 */
+        pixad = pixaCreate(n);
+        for (i = 0; i < n; i++) {
+            pixt = pixaGetPix(pixat, i, L_CLONE);
+            if (maxdepth <= 8)
+                pix = pixConvertTo8(pixt, 0);
+            else
+                pix = pixConvertTo32(pixt);
+            pixaAddPix(pixad, pix, L_INSERT);
+            pixDestroy(&pixt);
+        }
+    } else {
+        pixad = pixaCopy(pixat, L_CLONE);
+    }
+    pixaDestroy(&pixat);
+    return pixad;
+}
+
+
+/*!
+ *  pixaEqual()
+ *
+ *      Input:  pixa1
+ *              pixa2
+ *              maxdist
+ *              &naindex (<optional return> index array of correspondences
+ *              &same (<return> 1 if equal; 0 otherwise)
+ *      Return  0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The two pixa are the "same" if they contain the same
+ *          boxa and the same ordered set of pix.  However, if they
+ *          have boxa, the pix in each pixa can differ in ordering
+ *          by an amount given by the parameter @maxdist.  If they
+ *          don't have a boxa, the @maxdist parameter is ignored,
+ *          and the ordering must be identical.
+ *      (2) This applies only to boxa geometry, pixels and ordering;
+ *          other fields in the pix are ignored.
+ *      (3) naindex[i] gives the position of the box in pixa2 that
+ *          corresponds to box i in pixa1.  It is only returned if the
+ *          pixa have boxa and the boxa are equal.
+ *      (4) In situations where the ordering is very different, so that
+ *          a large @maxdist is required for "equality", this should be
+ *          implemented with a hash function for efficiency.
+ */
+l_int32
+pixaEqual(PIXA     *pixa1,
+          PIXA     *pixa2,
+          l_int32   maxdist,
+          NUMA    **pnaindex,
+          l_int32  *psame)
+{
+l_int32   i, j, n, same, sameboxa;
+BOXA     *boxa1, *boxa2;
+NUMA     *na;
+PIX      *pix1, *pix2;
+
+    PROCNAME("pixaEqual");
+
+    if (pnaindex) *pnaindex = NULL;
+    if (!psame)
+        return ERROR_INT("&same not defined", procName, 1);
+    *psame = 0;
+    sameboxa = 0;
+    na = NULL;
+    if (!pixa1 || !pixa2)
+        return ERROR_INT("pixa1 and pixa2 not both defined", procName, 1);
+    n = pixaGetCount(pixa1);
+    if (n != pixaGetCount(pixa2))
+        return 0;
+    boxa1 = pixaGetBoxa(pixa1, L_CLONE);
+    boxa2 = pixaGetBoxa(pixa2, L_CLONE);
+    if (!boxa1 && !boxa2)
+        maxdist = 0;  /* exact ordering required */
+    if (boxa1 && !boxa2) {
+        boxaDestroy(&boxa1);
+        return 0;
+    }
+    if (!boxa1 && boxa2) {
+        boxaDestroy(&boxa2);
+        return 0;
+    }
+    if (boxa1 && boxa2) {
+        boxaEqual(boxa1, boxa2, maxdist, &na, &sameboxa);
+        boxaDestroy(&boxa1);
+        boxaDestroy(&boxa2);
+        if (!sameboxa) {
+            numaDestroy(&na);
+            return 0;
+        }
+    }
+
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixa1, i, L_CLONE);
+        if (na)
+            numaGetIValue(na, i, &j);
+        else
+            j = i;
+        pix2 = pixaGetPix(pixa2, j, L_CLONE);
+        pixEqual(pix1, pix2, &same);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+        if (!same) {
+            numaDestroy(&na);
+            return 0;
+        }
+    }
+
+    *psame = 1;
+    if (pnaindex)
+        *pnaindex = na;
+    else
+        numaDestroy(&na);
+    return 0;
+}
+
+
+/*!
+ *  pixaRotateOrth()
+ *
+ *      Input:  pixas
+ *              rotation (0 = noop, 1 = 90 deg, 2 = 180 deg, 3 = 270 deg;
+ *                        all rotations are clockwise)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) Rotates each pix in the pixa.  Rotates and saves the boxes in
+ *          the boxa if the boxa is full.
+ */
+PIXA *
+pixaRotateOrth(PIXA    *pixas,
+               l_int32  rotation)
+{
+l_int32  i, n, nb, w, h;
+BOX     *boxs, *boxd;
+PIX     *pixs, *pixd;
+PIXA    *pixad;
+
+    PROCNAME("pixaRotateOrth");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (rotation < 0 || rotation > 3)
+        return (PIXA *)ERROR_PTR("rotation not in {0,1,2,3}", procName, NULL);
+    if (rotation == 0)
+        return pixaCopy(pixas, L_COPY);
+
+    n = pixaGetCount(pixas);
+    nb = pixaGetBoxaCount(pixas);
+    if ((pixad = pixaCreate(n)) == NULL)
+        return (PIXA *)ERROR_PTR("pixad not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        if ((pixs = pixaGetPix(pixas, i, L_CLONE)) == NULL)
+            return (PIXA *)ERROR_PTR("pixs not found", procName, NULL);
+        pixd = pixRotateOrth(pixs, rotation);
+        pixaAddPix(pixad, pixd, L_INSERT);
+        if (n == nb) {
+            boxs = pixaGetBox(pixas, i, L_COPY);
+            pixGetDimensions(pixs, &w, &h, NULL);
+            boxd = boxRotateOrth(boxs, w, h, rotation);
+            pixaAddBox(pixad, boxd, L_INSERT);
+            boxDestroy(&boxs);
+        }
+        pixDestroy(&pixs);
+    }
+
+    return pixad;
+}
+
diff --git a/src/pixafunc2.c b/src/pixafunc2.c
new file mode 100644 (file)
index 0000000..d9ed33b
--- /dev/null
@@ -0,0 +1,1854 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   pixafunc2.c
+ *
+ *      Pixa Display (render into a pix)
+ *           PIX      *pixaDisplay()
+ *           PIX      *pixaDisplayOnColor()
+ *           PIX      *pixaDisplayRandomCmap()
+ *           PIX      *pixaDisplayLinearly()
+ *           PIX      *pixaDisplayOnLattice()
+ *           PIX      *pixaDisplayUnsplit()
+ *           PIX      *pixaDisplayTiled()
+ *           PIX      *pixaDisplayTiledInRows()
+ *           PIX      *pixaDisplayTiledAndScaled()
+ *           PIX      *pixaDisplayTiledWithText()
+ *           PIX      *pixaDisplayTiledByIndex()
+ *
+ *      Pixaa Display (render into a pix)
+ *           PIX      *pixaaDisplay()
+ *           PIX      *pixaaDisplayByPixa()
+ *           PIXA     *pixaaDisplayTiledAndScaled()
+ *
+ *      Conversion of all pix to specified type (e.g., depth)
+ *           PIXA     *pixaConvertTo1()
+ *           PIXA     *pixaConvertTo8()
+ *           PIXA     *pixaConvertTo8Color()
+ *           PIXA     *pixaConvertTo32()
+ *
+ *      Tile N-Up
+ *           l_int32   convertToNUpFiles()
+ *           PIXA     *convertToNUpPixa()
+ *
+ *  We give seven methods for displaying a pixa in a pix.
+ *  Some work for 1 bpp input; others for any input depth.
+ *  Some give an output depth that depends on the input depth;
+ *  others give a different output depth or allow you to choose it.
+ *  Some use a boxes to determine where each pix goes; others tile
+ *  onto a regular lattice; yet others tile onto an irregular lattice.
+ *
+ *  Here is a brief description of what the pixa display functions do.
+ *
+ *    pixaDisplay()
+ *        This uses the boxes to lay out each pix.  It is typically
+ *        used to reconstruct a pix that has been broken into components.
+ *    pixaDisplayOnColor()
+ *        pixaDisplay() with choice of background color
+ *    pixaDisplayRandomCmap()
+ *        This also uses the boxes to lay out each pix.  However, it creates
+ *        a colormapped dest, where each 1 bpp pix is given a randomly
+ *        generated color (up to 256 are used).
+ *    pixaDisplayLinearly()
+ *        This puts each pix, sequentially, in a line, either horizontally
+ *        or vertically.
+ *    pixaDisplayOnLattice()
+ *        This puts each pix, sequentially, onto a regular lattice,
+ *        omitting any pix that are too big for the lattice size.
+ *        This is useful, for example, to store bitmapped fonts,
+ *        where all the characters are stored in a single image.
+ *    pixaDisplayUnsplit()
+ *        This lays out a mosaic of tiles (the pix in the pixa) that
+ *        are all of equal size.  (Don't use this for unequal sized pix!)
+ *        For example, it can be used to invert the action of
+ *        pixaSplitPix().
+ *    pixaDisplayTiled()
+ *        Like pixaDisplayOnLattice(), this places each pix on a regular
+ *        lattice, but here the lattice size is determined by the
+ *        largest component, and no components are omitted.  This is
+ *        dangerous if there are thousands of small components and
+ *        one or more very large one, because the size of the resulting
+ *        pix can be huge!
+ *    pixaDisplayTiledInRows()
+ *        This puts each pix down in a series of rows, where the upper
+ *        edges of each pix in a row are aligned and there is a uniform
+ *        spacing between the pix.  The height of each row is determined
+ *        by the tallest pix that was put in the row.  This function
+ *        is a reasonably efficient way to pack the subimages.
+ *        A boxa of the locations of each input pix is stored in the output.
+ *    pixaDisplayTiledAndScaled()
+ *        This scales each pix to a given width and output depth, and then
+ *        tiles them in rows with a given number placed in each row.
+ *        This is useful for presenting a sequence of images that can be
+ *        at different resolutions, but which are derived from the same
+ *        initial image.
+ *    pixaDisplayTiledWithText()
+ *        This is a version of pixaDisplayTiledInRows() that prints, below
+ *        each pix, the text in the pix text field.  It renders a pixa
+ *        to an image with white background that does not exceed a
+ *        given value in width.
+ *    pixaDisplayTiledByIndex()
+ *        This scales each pix to a given width and output depth,
+ *        and then tiles them in columns corresponding to the value
+ *        in an associated numa.  All pix with the same index value are
+ *        rendered in the same column.  Text in the pix text field are
+ *        rendered below the pix.
+ */
+
+#include <string.h>
+#include <math.h>   /* for sqrt() */
+#include "allheaders.h"
+
+
+/*---------------------------------------------------------------------*
+ *                               Pixa Display                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaDisplay()
+ *
+ *      Input:  pixa
+ *              w, h (if set to 0, determines the size from the
+ *                    b.b. of the components in pixa)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) This uses the boxes to place each pix in the rendered composite.
+ *      (2) Set w = h = 0 to use the b.b. of the components to determine
+ *          the size of the returned pix.
+ *      (3) Uses the first pix in pixa to determine the depth.
+ *      (4) The background is written "white".  On 1 bpp, each successive
+ *          pix is "painted" (adding foreground), whereas for grayscale
+ *          or color each successive pix is blitted with just the src.
+ *      (5) If the pixa is empty, returns an empty 1 bpp pix.
+ */
+PIX *
+pixaDisplay(PIXA    *pixa,
+            l_int32  w,
+            l_int32  h)
+{
+l_int32  i, n, d, xb, yb, wb, hb;
+BOXA    *boxa;
+PIX     *pixt, *pixd;
+
+    PROCNAME("pixaDisplay");
+
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
+
+    n = pixaGetCount(pixa);
+    if (n == 0 && w == 0 && h == 0)
+        return (PIX *)ERROR_PTR("no components; no size", procName, NULL);
+    if (n == 0) {
+        L_WARNING("no components; returning empty 1 bpp pix\n", procName);
+        return pixCreate(w, h, 1);
+    }
+
+        /* If w and h not input, determine the minimum size required
+         * to contain the origin and all c.c. */
+    if (w == 0 || h == 0) {
+        boxa = pixaGetBoxa(pixa, L_CLONE);
+        boxaGetExtent(boxa, &w, &h, NULL);
+        boxaDestroy(&boxa);
+    }
+
+        /* Use the first pix in pixa to determine the depth.  */
+    pixt = pixaGetPix(pixa, 0, L_CLONE);
+    d = pixGetDepth(pixt);
+    pixDestroy(&pixt);
+
+    if ((pixd = pixCreate(w, h, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    if (d > 1)
+        pixSetAll(pixd);
+    for (i = 0; i < n; i++) {
+        if (pixaGetBoxGeometry(pixa, i, &xb, &yb, &wb, &hb)) {
+            L_WARNING("no box found!\n", procName);
+            continue;
+        }
+        pixt = pixaGetPix(pixa, i, L_CLONE);
+        if (d == 1)
+            pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pixt, 0, 0);
+        else
+            pixRasterop(pixd, xb, yb, wb, hb, PIX_SRC, pixt, 0, 0);
+        pixDestroy(&pixt);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixaDisplayOnColor()
+ *
+ *      Input:  pixa
+ *              w, h (if set to 0, determines the size from the
+ *                    b.b. of the components in pixa)
+ *              color (background color to use)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) This uses the boxes to place each pix in the rendered composite.
+ *      (2) Set w = h = 0 to use the b.b. of the components to determine
+ *          the size of the returned pix.
+ *      (3) If any pix in @pixa are colormapped, or if the pix have
+ *          different depths, it returns a 32 bpp pix.  Otherwise,
+ *          the depth of the returned pixa equals that of the pix in @pixa.
+ *      (4) If the pixa is empty, return null.
+ */
+PIX *
+pixaDisplayOnColor(PIXA     *pixa,
+                   l_int32   w,
+                   l_int32   h,
+                   l_uint32  bgcolor)
+{
+l_int32  i, n, xb, yb, wb, hb, hascmap, maxdepth, same;
+BOXA    *boxa;
+PIX     *pix1, *pix2, *pixd;
+PIXA    *pixat;
+
+    PROCNAME("pixaDisplayOnColor");
+
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
+    if ((n = pixaGetCount(pixa)) == 0)
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+
+        /* If w and h are not input, determine the minimum size
+         * required to contain the origin and all c.c. */
+    if (w == 0 || h == 0) {
+        boxa = pixaGetBoxa(pixa, L_CLONE);
+        boxaGetExtent(boxa, &w, &h, NULL);
+        boxaDestroy(&boxa);
+    }
+
+        /* If any pix have colormaps, or if they have different depths,
+         * generate rgb */
+    pixaAnyColormaps(pixa, &hascmap);
+    pixaGetDepthInfo(pixa, &maxdepth, &same);
+    if (hascmap || !same) {
+        maxdepth = 32;
+        pixat = pixaCreate(n);
+        for (i = 0; i < n; i++) {
+            pix1 = pixaGetPix(pixa, i, L_CLONE);
+            pix2 = pixConvertTo32(pix1);
+            pixaAddPix(pixat, pix2, L_INSERT);
+            pixDestroy(&pix1);
+        }
+    } else {
+        pixat = pixaCopy(pixa, L_CLONE);
+    }
+
+        /* Make the output pix and set the background color */
+    if ((pixd = pixCreate(w, h, maxdepth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    if ((maxdepth == 1 && bgcolor > 0) ||
+        (maxdepth == 2 && bgcolor >= 0x3) ||
+        (maxdepth == 4 && bgcolor >= 0xf) ||
+        (maxdepth == 8 && bgcolor >= 0xff) ||
+        (maxdepth == 16 && bgcolor >= 0xffff) ||
+        (maxdepth == 32 && bgcolor >= 0xffffff00)) {
+        pixSetAll(pixd);
+    } else if (bgcolor > 0) {
+        pixSetAllArbitrary(pixd, bgcolor);
+    }
+
+        /* Blit each pix into its place */
+    for (i = 0; i < n; i++) {
+        if (pixaGetBoxGeometry(pixat, i, &xb, &yb, &wb, &hb)) {
+            L_WARNING("no box found!\n", procName);
+            continue;
+        }
+        pix1 = pixaGetPix(pixat, i, L_CLONE);
+        pixRasterop(pixd, xb, yb, wb, hb, PIX_SRC, pix1, 0, 0);
+        pixDestroy(&pix1);
+    }
+
+    pixaDestroy(&pixat);
+    return pixd;
+}
+
+
+/*!
+ *  pixaDisplayRandomCmap()
+ *
+ *      Input:  pixa (of 1 bpp components, with boxa)
+ *              w, h (if set to 0, determines the size from the
+ *                    b.b. of the components in pixa)
+ *      Return: pix (8 bpp, cmapped, with random colors on the components),
+ *              or null on error
+ *
+ *  Notes:
+ *      (1) This uses the boxes to place each pix in the rendered composite.
+ *      (2) By default, the background color is: black, cmap index 0.
+ *          This can be changed by pixcmapResetColor()
+ */
+PIX *
+pixaDisplayRandomCmap(PIXA    *pixa,
+                      l_int32  w,
+                      l_int32  h)
+{
+l_int32   i, n, maxdepth, index, xb, yb, wb, hb;
+BOXA     *boxa;
+PIX      *pixs, *pixt, *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixaDisplayRandomCmap");
+
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
+
+    if ((n = pixaGetCount(pixa)) == 0)
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+    pixaVerifyDepth(pixa, &maxdepth);
+    if (maxdepth != 1)
+        return (PIX *)ERROR_PTR("not all components are 1 bpp", procName, NULL);
+
+        /* If w and h are not input, determine the minimum size required
+         * to contain the origin and all c.c. */
+    if (w == 0 || h == 0) {
+        boxa = pixaGetBoxa(pixa, L_CLONE);
+        boxaGetExtent(boxa, &w, &h, NULL);
+        boxaDestroy(&boxa);
+    }
+
+        /* Set up an 8 bpp dest pix, with a colormap with 254 random colors */
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    cmap = pixcmapCreateRandom(8, 1, 1);
+    pixSetColormap(pixd, cmap);
+
+        /* Color each component and blit it in */
+    for (i = 0; i < n; i++) {
+        index = 1 + (i % 254);
+        pixaGetBoxGeometry(pixa, i, &xb, &yb, &wb, &hb);
+        pixs = pixaGetPix(pixa, i, L_CLONE);
+        pixt = pixConvert1To8(NULL, pixs, 0, index);
+        pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pixt, 0, 0);
+        pixDestroy(&pixs);
+        pixDestroy(&pixt);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixaDisplayLinearly()
+ *
+ *      Input:  pixa
+ *              direction (L_HORIZ or L_VERT)
+ *              scalefactor (applied to every pix; use 1.0 for no scaling)
+ *              background (0 for white, 1 for black; this is the color
+ *                 of the spacing between the images)
+ *              spacing  (between images, and on outside)
+ *              border (width of black border added to each image;
+ *                      use 0 for no border)
+ *              &boxa (<optional return> location of images in output pix
+ *      Return: pix of composite images, or null on error
+ *
+ *  Notes:
+ *      (1) This puts each pix, sequentially, in a line, either horizontally
+ *          or vertically.
+ *      (2) If any pix has a colormap, all pix are rendered in rgb.
+ *      (3) The boxa gives the location of each image.
+ */
+PIX *
+pixaDisplayLinearly(PIXA      *pixas,
+                    l_int32    direction,
+                    l_float32  scalefactor,
+                    l_int32    background,  /* not used */
+                    l_int32    spacing,
+                    l_int32    border,
+                    BOXA     **pboxa)
+{
+l_int32  i, n, x, y, w, h, size, depth, bordval;
+BOX     *box;
+PIX     *pix1, *pix2, *pix3, *pixd;
+PIXA    *pixa1, *pixa2;
+
+    PROCNAME("pixaDisplayLinearly");
+
+    if (pboxa) *pboxa = NULL;
+    if (!pixas)
+        return (PIX *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (direction != L_HORIZ && direction != L_VERT)
+        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
+
+        /* Make sure all pix are at the same depth */
+    pixa1 = pixaConvertToSameDepth(pixas);
+    pixaGetDepthInfo(pixa1, &depth, NULL);
+
+        /* Scale and add border if requested */
+    n = pixaGetCount(pixa1);
+    pixa2 = pixaCreate(n);
+    bordval = (depth == 1) ? 1 : 0;
+    size = (n - 1) * spacing;
+    x = y = 0;
+    for (i = 0; i < n; i++) {
+        if ((pix1 = pixaGetPix(pixa1, i, L_CLONE)) == NULL) {
+            L_WARNING("missing pix at index %d\n", procName, i);
+            continue;
+        }
+
+        if (scalefactor != 1.0)
+            pix2 = pixScale(pix1, scalefactor, scalefactor);
+        else
+            pix2 = pixClone(pix1);
+        if (border)
+            pix3 = pixAddBorder(pix2, border, bordval);
+        else
+            pix3 = pixClone(pix2);
+
+        pixGetDimensions(pix3, &w, &h, NULL);
+        box = boxCreate(x, y, w, h);
+        if (direction == L_HORIZ) {
+            size += w;
+            x += w + spacing;
+        } else {  /* vertical */
+            size += h;
+            y += h + spacing;
+        }
+        pixaAddPix(pixa2, pix3, L_INSERT);
+        pixaAddBox(pixa2, box, L_INSERT);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+    pixd = pixaDisplay(pixa2, 0, 0);
+
+    if (pboxa)
+        *pboxa = pixaGetBoxa(pixa2, L_COPY);
+    pixaDestroy(&pixa1);
+    pixaDestroy(&pixa2);
+    return pixd;
+}
+
+
+/*!
+ *  pixaDisplayOnLattice()
+ *
+ *      Input:  pixa
+ *              cellw (lattice cell width)
+ *              cellh (lattice cell height)
+ *              &ncols (<optional return> number of columns in output lattice)
+ *              &boxa (<optional return> location of images in lattice)
+ *      Return: pix of composite images, or null on error
+ *
+ *  Notes:
+ *      (1) This places each pix on sequentially on a regular lattice
+ *          in the rendered composite.  If a pix is too large to fit in the
+ *          allocated lattice space, it is not rendered.
+ *      (2) If any pix has a colormap, all pix are rendered in rgb.
+ *      (3) This is useful when putting bitmaps of components,
+ *          such as characters, into a single image.
+ *      (4) The boxa gives the location of each image.  The UL corner
+ *          of each image is on a lattice cell corner.  Omitted images
+ *          (due to size) are assigned an invalid width and height of 0.
+ */
+PIX *
+pixaDisplayOnLattice(PIXA     *pixa,
+                     l_int32   cellw,
+                     l_int32   cellh,
+                     l_int32  *pncols,
+                     BOXA    **pboxa)
+{
+l_int32  n, nw, nh, w, h, d, wt, ht;
+l_int32  index, i, j, hascmap;
+BOX     *box;
+BOXA    *boxa;
+PIX     *pix, *pixt, *pixd;
+PIXA    *pixat;
+
+    PROCNAME("pixaDisplayOnLattice");
+
+    if (pncols) *pncols = 0;
+    if (pboxa) *pboxa = NULL;
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
+
+        /* If any pix have colormaps, generate rgb */
+    if ((n = pixaGetCount(pixa)) == 0)
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+    pixaAnyColormaps(pixa, &hascmap);
+    if (hascmap) {
+        pixat = pixaCreate(n);
+        for (i = 0; i < n; i++) {
+            pixt = pixaGetPix(pixa, i, L_CLONE);
+            pix = pixConvertTo32(pixt);
+            pixaAddPix(pixat, pix, L_INSERT);
+            pixDestroy(&pixt);
+        }
+    } else {
+        pixat = pixaCopy(pixa, L_CLONE);
+    }
+    boxa = boxaCreate(n);
+
+        /* Have number of rows and columns approximately equal */
+    nw = (l_int32)sqrt((l_float64)n);
+    nh = (n + nw - 1) / nw;
+    w = cellw * nw;
+    h = cellh * nh;
+
+        /* Use the first pix in pixa to determine the output depth.  */
+    pixaGetPixDimensions(pixat, 0, NULL, NULL, &d);
+    if ((pixd = pixCreate(w, h, d)) == NULL) {
+        pixaDestroy(&pixat);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    pixSetBlackOrWhite(pixd, L_SET_WHITE);
+
+        /* Tile the output */
+    index = 0;
+    for (i = 0; i < nh; i++) {
+        for (j = 0; j < nw && index < n; j++, index++) {
+            pixt = pixaGetPix(pixat, index, L_CLONE);
+            pixGetDimensions(pixt, &wt, &ht, NULL);
+            if (wt > cellw || ht > cellh) {
+                L_INFO("pix(%d) omitted; size %dx%x\n", procName, index,
+                       wt, ht);
+                box = boxCreate(0, 0, 0, 0);
+                boxaAddBox(boxa, box, L_INSERT);
+                pixDestroy(&pixt);
+                continue;
+            }
+            pixRasterop(pixd, j * cellw, i * cellh, wt, ht,
+                        PIX_SRC, pixt, 0, 0);
+            box = boxCreate(j * cellw, i * cellh, wt, ht);
+            boxaAddBox(boxa, box, L_INSERT);
+            pixDestroy(&pixt);
+        }
+    }
+
+    if (pncols) *pncols = nw;
+    if (pboxa)
+        *pboxa = boxa;
+    else
+        boxaDestroy(&boxa);
+    pixaDestroy(&pixat);
+    return pixd;
+}
+
+
+/*!
+ *  pixaDisplayUnsplit()
+ *
+ *      Input:  pixa
+ *              nx   (number of mosaic cells horizontally)
+ *              ny   (number of mosaic cells vertically)
+ *              borderwidth  (of added border on all sides)
+ *              bordercolor  (in our RGBA format: 0xrrggbbaa)
+ *      Return: pix of tiled images, or null on error
+ *
+ *  Notes:
+ *      (1) This is a logical inverse of pixaSplitPix().  It
+ *          constructs a pix from a mosaic of tiles, all of equal size.
+ *      (2) For added generality, a border of arbitrary color can
+ *          be added to each of the tiles.
+ *      (3) In use, pixa will typically have either been generated
+ *          from pixaSplitPix() or will derived from a pixa that
+ *          was so generated.
+ *      (4) All pix in the pixa must be of equal depth, and, if
+ *          colormapped, have the same colormap.
+ */
+PIX *
+pixaDisplayUnsplit(PIXA     *pixa,
+                   l_int32   nx,
+                   l_int32   ny,
+                   l_int32   borderwidth,
+                   l_uint32  bordercolor)
+{
+l_int32  w, h, d, wt, ht;
+l_int32  i, j, k, x, y, n;
+PIX     *pixt, *pixd;
+
+    PROCNAME("pixaDisplayUnsplit");
+
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (nx <= 0 || ny <= 0)
+        return (PIX *)ERROR_PTR("nx and ny must be > 0", procName, NULL);
+    if ((n = pixaGetCount(pixa)) == 0)
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+    if (n != nx * ny)
+        return (PIX *)ERROR_PTR("n != nx * ny", procName, NULL);
+    borderwidth = L_MAX(0, borderwidth);
+
+    pixaGetPixDimensions(pixa, 0, &wt, &ht, &d);
+    w = nx * (wt + 2 * borderwidth);
+    h = ny * (ht + 2 * borderwidth);
+
+    if ((pixd = pixCreate(w, h, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixt = pixaGetPix(pixa, 0, L_CLONE);
+    pixCopyColormap(pixd, pixt);
+    pixDestroy(&pixt);
+    if (borderwidth > 0)
+        pixSetAllArbitrary(pixd, bordercolor);
+
+    y = borderwidth;
+    for (i = 0, k = 0; i < ny; i++) {
+        x = borderwidth;
+        for (j = 0; j < nx; j++, k++) {
+            pixt = pixaGetPix(pixa, k, L_CLONE);
+            pixRasterop(pixd, x, y, wt, ht, PIX_SRC, pixt, 0, 0);
+            pixDestroy(&pixt);
+            x += wt + 2 * borderwidth;
+        }
+        y += ht + 2 * borderwidth;
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixaDisplayTiled()
+ *
+ *      Input:  pixa
+ *              maxwidth (of output image)
+ *              background (0 for white, 1 for black)
+ *              spacing
+ *      Return: pix of tiled images, or null on error
+ *
+ *  Notes:
+ *      (1) This renders a pixa to a single image of width not to
+ *          exceed maxwidth, with background color either white or black,
+ *          and with each subimage spaced on a regular lattice.
+ *      (2) The lattice size is determined from the largest width and height,
+ *          separately, of all pix in the pixa.
+ *      (3) All pix in the pixa must be of equal depth.
+ *      (4) If any pix has a colormap, all pix are rendered in rgb.
+ *      (5) Careful: because no components are omitted, this is
+ *          dangerous if there are thousands of small components and
+ *          one or more very large one, because the size of the
+ *          resulting pix can be huge!
+ */
+PIX *
+pixaDisplayTiled(PIXA    *pixa,
+                 l_int32  maxwidth,
+                 l_int32  background,
+                 l_int32  spacing)
+{
+l_int32  w, h, wmax, hmax, wd, hd, d, hascmap;
+l_int32  i, j, n, ni, ncols, nrows;
+l_int32  ystart, xstart, wt, ht;
+PIX     *pix, *pixt, *pixd;
+PIXA    *pixat;
+
+    PROCNAME("pixaDisplayTiled");
+
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
+
+        /* If any pix have colormaps, generate rgb */
+    if ((n = pixaGetCount(pixa)) == 0)
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+    pixaAnyColormaps(pixa, &hascmap);
+    if (hascmap) {
+        pixat = pixaCreate(n);
+        for (i = 0; i < n; i++) {
+            pixt = pixaGetPix(pixa, i, L_CLONE);
+            pix = pixConvertTo32(pixt);
+            pixaAddPix(pixat, pix, L_INSERT);
+            pixDestroy(&pixt);
+        }
+    } else {
+        pixat = pixaCopy(pixa, L_CLONE);
+    }
+
+        /* Find the largest width and height of the subimages */
+    wmax = hmax = 0;
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixat, i, L_CLONE);
+        pixGetDimensions(pix, &w, &h, NULL);
+        if (i == 0) {
+            d = pixGetDepth(pix);
+        } else if (d != pixGetDepth(pix)) {
+            pixDestroy(&pix);
+            pixaDestroy(&pixat);
+            return (PIX *)ERROR_PTR("depths not equal", procName, NULL);
+        }
+        if (w > wmax)
+            wmax = w;
+        if (h > hmax)
+            hmax = h;
+        pixDestroy(&pix);
+    }
+
+        /* Get the number of rows and columns and the output image size */
+    spacing = L_MAX(spacing, 0);
+    ncols = (l_int32)((l_float32)(maxwidth - spacing) /
+                      (l_float32)(wmax + spacing));
+    nrows = (n + ncols - 1) / ncols;
+    wd = wmax * ncols + spacing * (ncols + 1);
+    hd = hmax * nrows + spacing * (nrows + 1);
+    if ((pixd = pixCreate(wd, hd, d)) == NULL) {
+        pixaDestroy(&pixat);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+
+        /* Reset the background color if necessary */
+    if ((background == 1 && d == 1) || (background == 0 && d != 1))
+        pixSetAll(pixd);
+
+        /* Blit the images to the dest */
+    for (i = 0, ni = 0; i < nrows; i++) {
+        ystart = spacing + i * (hmax + spacing);
+        for (j = 0; j < ncols && ni < n; j++, ni++) {
+            xstart = spacing + j * (wmax + spacing);
+            pix = pixaGetPix(pixat, ni, L_CLONE);
+            wt = pixGetWidth(pix);
+            ht = pixGetHeight(pix);
+            pixRasterop(pixd, xstart, ystart, wt, ht, PIX_SRC, pix, 0, 0);
+            pixDestroy(&pix);
+        }
+    }
+
+    pixaDestroy(&pixat);
+    return pixd;
+}
+
+
+/*!
+ *  pixaDisplayTiledInRows()
+ *
+ *      Input:  pixa
+ *              outdepth (output depth: 1, 8 or 32 bpp)
+ *              maxwidth (of output image)
+ *              scalefactor (applied to every pix; use 1.0 for no scaling)
+ *              background (0 for white, 1 for black; this is the color
+ *                 of the spacing between the images)
+ *              spacing  (between images, and on outside)
+ *              border (width of black border added to each image;
+ *                      use 0 for no border)
+ *      Return: pixd (of tiled images), or null on error
+ *
+ *  Notes:
+ *      (1) This renders a pixa to a single image of width not to
+ *          exceed maxwidth, with background color either white or black,
+ *          and with each row tiled such that the top of each pix is
+ *          aligned and separated by 'spacing' from the next one.
+ *          A black border can be added to each pix.
+ *      (2) All pix are converted to outdepth; existing colormaps are removed.
+ *      (3) This does a reasonably spacewise-efficient job of laying
+ *          out the individual pix images into a tiled composite.
+ *      (4) A serialized boxa giving the location in pixd of each input
+ *          pix (without added border) is stored in the text string of pixd.
+ *          This allows, e.g., regeneration of a pixa from pixd, using
+ *          pixaCreateFromBoxa().  If there is no scaling and the depth of
+ *          each input pix in the pixa is the same, this tiling operation
+ *          can be inverted using the boxa (except for loss of text in
+ *          each of the input pix):
+ *            pix1 = pixaDisplayTiledInRows(pixa1, 1, 1500, 1.0, 0, 30, 0);
+ *            char *boxatxt = pixGetText(pix1);
+ *            boxa1 = boxaReadMem((l_uint8 *)boxatxt, strlen(boxatxt));
+ *            pixa2 = pixaCreateFromBoxa(pix1, boxa1, NULL);
+ */
+PIX *
+pixaDisplayTiledInRows(PIXA      *pixa,
+                       l_int32    outdepth,
+                       l_int32    maxwidth,
+                       l_float32  scalefactor,
+                       l_int32    background,
+                       l_int32    spacing,
+                       l_int32    border)
+{
+l_int32   h;  /* cumulative height over all the rows */
+l_int32   w;  /* cumulative height in the current row */
+l_int32   bordval, wtry, wt, ht;
+l_int32   irow;  /* index of current pix in current row */
+l_int32   wmaxrow;  /* width of the largest row */
+l_int32   maxh;  /* max height in row */
+l_int32   i, j, index, n, x, y, nrows, ninrow;
+size_t    size;
+l_uint8  *data;
+BOXA     *boxa;
+NUMA     *nainrow;  /* number of pix in the row */
+NUMA     *namaxh;  /* height of max pix in the row */
+PIX      *pix, *pixn, *pixt, *pixd;
+PIXA     *pixan;
+
+    PROCNAME("pixaDisplayTiledInRows");
+
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (outdepth != 1 && outdepth != 8 && outdepth != 32)
+        return (PIX *)ERROR_PTR("outdepth not in {1, 8, 32}", procName, NULL);
+    if (border < 0)
+        border = 0;
+    if (scalefactor <= 0.0) scalefactor = 1.0;
+
+    if ((n = pixaGetCount(pixa)) == 0)
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+
+        /* Normalize depths, scale, remove colormaps; optionally add border */
+    pixan = pixaCreate(n);
+    bordval = (outdepth == 1) ? 1 : 0;
+    for (i = 0; i < n; i++) {
+        if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL)
+            continue;
+
+        if (outdepth == 1)
+            pixn = pixConvertTo1(pix, 128);
+        else if (outdepth == 8)
+            pixn = pixConvertTo8(pix, FALSE);
+        else  /* outdepth == 32 */
+            pixn = pixConvertTo32(pix);
+        pixDestroy(&pix);
+
+        if (scalefactor != 1.0)
+            pixt = pixScale(pixn, scalefactor, scalefactor);
+        else
+            pixt = pixClone(pixn);
+        if (border)
+            pixd = pixAddBorder(pixt, border, bordval);
+        else
+            pixd = pixClone(pixt);
+        pixDestroy(&pixn);
+        pixDestroy(&pixt);
+
+        pixaAddPix(pixan, pixd, L_INSERT);
+    }
+    if (pixaGetCount(pixan) != n) {
+        n = pixaGetCount(pixan);
+        L_WARNING("only got %d components\n", procName, n);
+        if (n == 0) {
+            pixaDestroy(&pixan);
+            return (PIX *)ERROR_PTR("no components", procName, NULL);
+        }
+    }
+
+        /* Compute parameters for layout */
+    nainrow = numaCreate(0);
+    namaxh = numaCreate(0);
+    wmaxrow = 0;
+    w = h = spacing;
+    maxh = 0;  /* max height in row */
+    for (i = 0, irow = 0; i < n; i++, irow++) {
+        pixaGetPixDimensions(pixan, i, &wt, &ht, NULL);
+        wtry = w + wt + spacing;
+        if (wtry > maxwidth) {  /* end the current row and start next one */
+            numaAddNumber(nainrow, irow);
+            numaAddNumber(namaxh, maxh);
+            wmaxrow = L_MAX(wmaxrow, w);
+            h += maxh + spacing;
+            irow = 0;
+            w = wt + 2 * spacing;
+            maxh = ht;
+        } else {
+            w = wtry;
+            maxh = L_MAX(maxh, ht);
+        }
+    }
+
+        /* Enter the parameters for the last row */
+    numaAddNumber(nainrow, irow);
+    numaAddNumber(namaxh, maxh);
+    wmaxrow = L_MAX(wmaxrow, w);
+    h += maxh + spacing;
+
+    if ((pixd = pixCreate(wmaxrow, h, outdepth)) == NULL) {
+        numaDestroy(&nainrow);
+        numaDestroy(&namaxh);
+        pixaDestroy(&pixan);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+
+        /* Reset the background color if necessary */
+    if ((background == 1 && outdepth == 1) ||
+        (background == 0 && outdepth != 1))
+        pixSetAll(pixd);
+
+        /* Blit the images to the dest, and save the boxa identifying
+         * the image regions that do not include the borders. */
+    nrows = numaGetCount(nainrow);
+    y = spacing;
+    boxa = boxaCreate(n);
+    for (i = 0, index = 0; i < nrows; i++) {  /* over rows */
+        numaGetIValue(nainrow, i, &ninrow);
+        numaGetIValue(namaxh, i, &maxh);
+        x = spacing;
+        for (j = 0; j < ninrow; j++, index++) {   /* over pix in row */
+            pix = pixaGetPix(pixan, index, L_CLONE);
+            pixGetDimensions(pix, &wt, &ht, NULL);
+            boxaAddBox(boxa, boxCreate(x + border, y + border,
+                wt - 2 * border, ht - 2 *border), L_INSERT);
+            pixRasterop(pixd, x, y, wt, ht, PIX_SRC, pix, 0, 0);
+            pixDestroy(&pix);
+            x += wt + spacing;
+        }
+        y += maxh + spacing;
+    }
+    boxaWriteMem(&data, &size, boxa);
+    pixSetText(pixd, (char *)data);  /* data is ascii */
+    LEPT_FREE(data);
+    boxaDestroy(&boxa);
+
+    numaDestroy(&nainrow);
+    numaDestroy(&namaxh);
+    pixaDestroy(&pixan);
+    return pixd;
+}
+
+
+/*!
+ *  pixaDisplayTiledAndScaled()
+ *
+ *      Input:  pixa
+ *              outdepth (output depth: 1, 8 or 32 bpp)
+ *              tilewidth (each pix is scaled to this width)
+ *              ncols (number of tiles in each row)
+ *              background (0 for white, 1 for black; this is the color
+ *                 of the spacing between the images)
+ *              spacing  (between images, and on outside)
+ *              border (width of additional black border on each image;
+ *                      use 0 for no border)
+ *      Return: pix of tiled images, or null on error
+ *
+ *  Notes:
+ *      (1) This can be used to tile a number of renderings of
+ *          an image that are at different scales and depths.
+ *      (2) Each image, after scaling and optionally adding the
+ *          black border, has width 'tilewidth'.  Thus, the border does
+ *          not affect the spacing between the image tiles.  The
+ *          maximum allowed border width is tilewidth / 5.
+ */
+PIX *
+pixaDisplayTiledAndScaled(PIXA    *pixa,
+                          l_int32  outdepth,
+                          l_int32  tilewidth,
+                          l_int32  ncols,
+                          l_int32  background,
+                          l_int32  spacing,
+                          l_int32  border)
+{
+l_int32    x, y, w, h, wd, hd, d;
+l_int32    i, n, nrows, maxht, ninrow, irow, bordval;
+l_int32   *rowht;
+l_float32  scalefact;
+PIX       *pix, *pixn, *pixt, *pixb, *pixd;
+PIXA      *pixan;
+
+    PROCNAME("pixaDisplayTiledAndScaled");
+
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (outdepth != 1 && outdepth != 8 && outdepth != 32)
+        return (PIX *)ERROR_PTR("outdepth not in {1, 8, 32}", procName, NULL);
+    if (border < 0 || border > tilewidth / 5)
+        border = 0;
+
+    if ((n = pixaGetCount(pixa)) == 0)
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+
+        /* Normalize scale and depth for each pix; optionally add border */
+    pixan = pixaCreate(n);
+    bordval = (outdepth == 1) ? 1 : 0;
+    for (i = 0; i < n; i++) {
+        if ((pix = pixaGetPix(pixa, i, L_CLONE)) == NULL)
+            continue;
+
+        pixGetDimensions(pix, &w, &h, &d);
+        scalefact = (l_float32)(tilewidth - 2 * border) / (l_float32)w;
+        if (d == 1 && outdepth > 1 && scalefact < 1.0)
+            pixt = pixScaleToGray(pix, scalefact);
+        else
+            pixt = pixScale(pix, scalefact, scalefact);
+
+        if (outdepth == 1)
+            pixn = pixConvertTo1(pixt, 128);
+        else if (outdepth == 8)
+            pixn = pixConvertTo8(pixt, FALSE);
+        else  /* outdepth == 32 */
+            pixn = pixConvertTo32(pixt);
+        pixDestroy(&pixt);
+
+        if (border)
+            pixb = pixAddBorder(pixn, border, bordval);
+        else
+            pixb = pixClone(pixn);
+
+        pixaAddPix(pixan, pixb, L_INSERT);
+        pixDestroy(&pix);
+        pixDestroy(&pixn);
+    }
+    if ((n = pixaGetCount(pixan)) == 0) { /* should not have changed! */
+        pixaDestroy(&pixan);
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+    }
+
+        /* Determine the size of each row and of pixd */
+    wd = tilewidth * ncols + spacing * (ncols + 1);
+    nrows = (n + ncols - 1) / ncols;
+    if ((rowht = (l_int32 *)LEPT_CALLOC(nrows, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("rowht array not made", procName, NULL);
+    maxht = 0;
+    ninrow = 0;
+    irow = 0;
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixan, i, L_CLONE);
+        ninrow++;
+        pixGetDimensions(pix, &w, &h, NULL);
+        maxht = L_MAX(h, maxht);
+        if (ninrow == ncols) {
+            rowht[irow] = maxht;
+            maxht = ninrow = 0;  /* reset */
+            irow++;
+        }
+        pixDestroy(&pix);
+    }
+    if (ninrow > 0) {   /* last fencepost */
+        rowht[irow] = maxht;
+        irow++;  /* total number of rows */
+    }
+    nrows = irow;
+    hd = spacing * (nrows + 1);
+    for (i = 0; i < nrows; i++)
+        hd += rowht[i];
+
+    pixd = pixCreate(wd, hd, outdepth);
+    if ((background == 1 && outdepth == 1) ||
+        (background == 0 && outdepth != 1))
+        pixSetAll(pixd);
+
+        /* Now blit images to pixd */
+    x = y = spacing;
+    irow = 0;
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixan, i, L_CLONE);
+        pixGetDimensions(pix, &w, &h, NULL);
+        if (i && ((i % ncols) == 0)) {  /* start new row */
+            x = spacing;
+            y += spacing + rowht[irow];
+            irow++;
+        }
+        pixRasterop(pixd, x, y, w, h, PIX_SRC, pix, 0, 0);
+        x += tilewidth + spacing;
+        pixDestroy(&pix);
+    }
+
+    pixaDestroy(&pixan);
+    LEPT_FREE(rowht);
+    return pixd;
+}
+
+
+/*!
+ *  pixaDisplayTiledWithText()
+ *
+ *      Input:  pixa
+ *              maxwidth (of output image)
+ *              scalefactor (applied to every pix; use 1.0 for no scaling)
+ *              spacing  (between images, and on outside)
+ *              border (width of black border added to each image;
+ *                      use 0 for no border)
+ *              fontsize (4, 6, ... 20)
+ *              textcolor (0xrrggbb00)
+ *      Return: pixd (of tiled images), or null on error
+ *
+ *  Notes:
+ *      (1) This is a version of pixaDisplayTiledInRows() that prints, below
+ *          each pix, the text in the pix text field.  Up to 127 chars
+ *          of text in the pix text field are rendered below each pix.
+ *      (2) It renders a pixa to a single image of width not to
+ *          exceed @maxwidth, with white background color, with each row
+ *          tiled such that the top of each pix is aligned and separated
+ *          by @spacing from the next one.
+ *      (3) All pix are converted to 32 bpp.
+ *      (4) This does a reasonably spacewise-efficient job of laying
+ *          out the individual pix images into a tiled composite.
+ */
+PIX *
+pixaDisplayTiledWithText(PIXA      *pixa,
+                         l_int32    maxwidth,
+                         l_float32  scalefactor,
+                         l_int32    spacing,
+                         l_int32    border,
+                         l_int32    fontsize,
+                         l_uint32   textcolor)
+{
+char      buf[128];
+char     *textstr;
+l_int32   i, n, maxw;
+L_BMF    *bmf;
+PIX      *pix1, *pix2, *pix3, *pix4, *pixd;
+PIXA     *pixad;
+
+    PROCNAME("pixaDisplayTiledWithText");
+
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
+    if ((n = pixaGetCount(pixa)) == 0)
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+    if (maxwidth <= 0)
+        return (PIX *)ERROR_PTR("invalid maxwidth", procName, NULL);
+    if (border < 0)
+        border = 0;
+    if (scalefactor <= 0.0) {
+        L_WARNING("invalid scalefactor; setting to 1.0\n", procName);
+        scalefactor = 1.0;
+    }
+    if (fontsize < 4 || fontsize > 20 || (fontsize & 1)) {
+        l_int32 fsize = L_MAX(L_MIN(fontsize, 20), 4);
+        if (fsize & 1) fsize--;
+        L_WARNING("changed fontsize from %d to %d\n", procName,
+                  fontsize, fsize);
+        fontsize = fsize;
+    }
+
+        /* Be sure the width can accommodate a single column of images */
+    pixaSizeRange(pixa, NULL, NULL, &maxw, NULL);
+    maxwidth = L_MAX(maxwidth, scalefactor * (maxw + 2 * spacing + 2 * border));
+
+    bmf = bmfCreate(NULL, fontsize);
+    pixad = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixa, i, L_CLONE);
+        pix2 = pixConvertTo32(pix1);
+        pix3 = pixAddBorderGeneral(pix2, spacing, spacing, spacing,
+                                   spacing, 0xffffff00);
+        textstr = pixGetText(pix1);
+        if (textstr && strlen(textstr) > 0) {
+            snprintf(buf, sizeof(buf), "%s", textstr);
+            pix4 = pixAddSingleTextblock(pix3, bmf, buf, textcolor,
+                                     L_ADD_BELOW, NULL);
+        } else {
+            pix4 = pixClone(pix3);
+        }
+        pixaAddPix(pixad, pix4, L_INSERT);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+        pixDestroy(&pix3);
+    }
+    bmfDestroy(&bmf);
+
+    pixd = pixaDisplayTiledInRows(pixad, 32, maxwidth, scalefactor,
+                                  0, 10, border);
+    pixaDestroy(&pixad);
+    return pixd;
+}
+
+
+/*!
+ *  pixaDisplayTiledByIndex()
+ *
+ *      Input:  pixa
+ *              numa (with indices corresponding to the pix in pixa)
+ *              width (each pix is scaled to this width)
+ *              spacing  (between images, and on outside)
+ *              border (width of black border added to each image;
+ *                      use 0 for no border)
+ *              fontsize (4, 6, ... 20)
+ *              textcolor (0xrrggbb00)
+ *      Return: pixd (of tiled images), or null on error
+ *
+ *  Notes:
+ *      (1) This renders a pixa to a single image with white
+ *          background color, where the pix are placed in columns
+ *          given by the index value in the numa.  Each pix
+ *          is separated by @spacing from the adjacent ones, and
+ *          an optional border is placed around them.
+ *      (2) Up to 127 chars of text in the pix text field are rendered
+ *          below each pix.  Use newlines in the text field to write
+ *          the text in multiple lines that fit within the pix width.
+ *      (3) To avoid having empty columns, if there are N different
+ *          index values, they should be in [0 ... N-1].
+ *      (4) All pix are converted to 32 bpp.
+ */
+PIX *
+pixaDisplayTiledByIndex(PIXA     *pixa,
+                        NUMA     *na,
+                        l_int32   width,
+                        l_int32   spacing,
+                        l_int32   border,
+                        l_int32   fontsize,
+                        l_uint32  textcolor)
+{
+char      buf[128];
+char     *textstr;
+l_int32    i, n, x, y, w, h, yval, index;
+l_float32  maxindex;
+L_BMF     *bmf;
+BOX       *box;
+NUMA      *nay;  /* top of the next pix to add in that column */
+PIX       *pix1, *pix2, *pix3, *pix4, *pix5, *pixd;
+PIXA      *pixad;
+
+    PROCNAME("pixaDisplayTiledByIndex");
+
+    if (!pixa)
+        return (PIX *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (!na)
+        return (PIX *)ERROR_PTR("na not defined", procName, NULL);
+    if ((n = pixaGetCount(pixa)) == 0)
+        return (PIX *)ERROR_PTR("no pixa components", procName, NULL);
+    if (n != numaGetCount(na))
+        return (PIX *)ERROR_PTR("pixa and na counts differ", procName, NULL);
+    if (width <= 0)
+        return (PIX *)ERROR_PTR("invalid width", procName, NULL);
+    if (width < 20)
+        L_WARNING("very small width: %d\n", procName, width);
+    if (border < 0)
+        border = 0;
+    if (fontsize < 4 || fontsize > 20 || (fontsize & 1)) {
+        l_int32 fsize = L_MAX(L_MIN(fontsize, 20), 4);
+        if (fsize & 1) fsize--;
+        L_WARNING("changed fontsize from %d to %d\n", procName,
+                  fontsize, fsize);
+        fontsize = fsize;
+    }
+
+        /* The pix will be rendered in the order they occupy in pixa. */
+    bmf = bmfCreate(NULL, fontsize);
+    pixad = pixaCreate(n);
+    numaGetMax(na, &maxindex, NULL);
+    nay = numaMakeConstant(spacing, lept_roundftoi(maxindex) + 1);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(na, i, &index);
+        numaGetIValue(nay, index, &yval);
+        pix1 = pixaGetPix(pixa, i, L_CLONE);
+        pix2 = pixConvertTo32(pix1);
+        pix3 = pixScaleToSize(pix2, width, 0);
+        pix4 = pixAddBorderGeneral(pix3, border, border, border, border, 0);
+        textstr = pixGetText(pix1);
+        if (textstr && strlen(textstr) > 0) {
+            snprintf(buf, sizeof(buf), "%s", textstr);
+            pix5 = pixAddTextlines(pix4, bmf, textstr, textcolor, L_ADD_BELOW);
+        } else {
+            pix5 = pixClone(pix4);
+        }
+        pixaAddPix(pixad, pix5, L_INSERT);
+        x = spacing + border + index * (2 * border + width + spacing);
+        y = yval;
+        pixGetDimensions(pix5, &w, &h, NULL);
+        yval += h + spacing;
+        numaSetValue(nay, index, yval);
+        box = boxCreate(x, y, w, h);
+        pixaAddBox(pixad, box, L_INSERT);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+        pixDestroy(&pix3);
+        pixDestroy(&pix4);
+    }
+    numaDestroy(&nay);
+    bmfDestroy(&bmf);
+
+    pixd = pixaDisplay(pixad, 0, 0);
+    pixaDestroy(&pixad);
+    return pixd;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                              Pixaa Display                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaaDisplay()
+ *
+ *      Input:  paa
+ *              w, h (if set to 0, determines the size from the
+ *                    b.b. of the components in paa)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) Each pix of the paa is displayed at the location given by
+ *          its box, translated by the box of the containing pixa
+ *          if it exists.
+ */
+PIX *
+pixaaDisplay(PIXAA   *paa,
+             l_int32  w,
+             l_int32  h)
+{
+l_int32  i, j, n, nbox, na, d, wmax, hmax, x, y, xb, yb, wb, hb;
+BOXA    *boxa1;  /* top-level boxa */
+BOXA    *boxa;
+PIX     *pixt, *pixd;
+PIXA    *pixa;
+
+    PROCNAME("pixaaDisplay");
+
+    if (!paa)
+        return (PIX *)ERROR_PTR("paa not defined", procName, NULL);
+
+    n = pixaaGetCount(paa, NULL);
+    if (n == 0)
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+
+        /* If w and h not input, determine the minimum size required
+         * to contain the origin and all c.c. */
+    boxa1 = pixaaGetBoxa(paa, L_CLONE);
+    nbox = boxaGetCount(boxa1);
+    if (w == 0 || h == 0) {
+        if (nbox == n) {
+            boxaGetExtent(boxa1, &w, &h, NULL);
+        } else {  /* have to use the lower-level boxa for each pixa */
+            wmax = hmax = 0;
+            for (i = 0; i < n; i++) {
+                pixa = pixaaGetPixa(paa, i, L_CLONE);
+                boxa = pixaGetBoxa(pixa, L_CLONE);
+                boxaGetExtent(boxa, &w, &h, NULL);
+                wmax = L_MAX(wmax, w);
+                hmax = L_MAX(hmax, h);
+                pixaDestroy(&pixa);
+                boxaDestroy(&boxa);
+            }
+            w = wmax;
+            h = hmax;
+        }
+    }
+
+        /* Get depth from first pix */
+    pixa = pixaaGetPixa(paa, 0, L_CLONE);
+    pixt = pixaGetPix(pixa, 0, L_CLONE);
+    d = pixGetDepth(pixt);
+    pixaDestroy(&pixa);
+    pixDestroy(&pixt);
+
+    if ((pixd = pixCreate(w, h, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    x = y = 0;
+    for (i = 0; i < n; i++) {
+        pixa = pixaaGetPixa(paa, i, L_CLONE);
+        if (nbox == n)
+            boxaGetBoxGeometry(boxa1, i, &x, &y, NULL, NULL);
+        na = pixaGetCount(pixa);
+        for (j = 0; j < na; j++) {
+            pixaGetBoxGeometry(pixa, j, &xb, &yb, &wb, &hb);
+            pixt = pixaGetPix(pixa, j, L_CLONE);
+            pixRasterop(pixd, x + xb, y + yb, wb, hb, PIX_PAINT, pixt, 0, 0);
+            pixDestroy(&pixt);
+        }
+        pixaDestroy(&pixa);
+    }
+    boxaDestroy(&boxa1);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixaaDisplayByPixa()
+ *
+ *      Input:  paa (with pix that may have different depths)
+ *              xspace between pix in pixa
+ *              yspace between pixa
+ *              max width of output pix
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Displays each pixa on a line (or set of lines),
+ *          in order from top to bottom.  Within each pixa,
+ *          the pix are displayed in order from left to right.
+ *      (2) The sizes and depths of each pix can differ.  The output pix
+ *          has a depth equal to the max depth of all the pix.
+ *      (3) This ignores the boxa of the paa.
+ */
+PIX *
+pixaaDisplayByPixa(PIXAA   *paa,
+                   l_int32  xspace,
+                   l_int32  yspace,
+                   l_int32  maxw)
+{
+l_int32   i, j, npixa, npix, same, use_maxw, x, y, w, h, hindex;
+l_int32   maxwidth, maxdepth, width, lmaxh, lmaxw;
+l_int32  *harray;
+NUMA     *nah;
+PIX      *pix, *pixt, *pixd;
+PIXA     *pixa;
+
+    PROCNAME("pixaaDisplayByPixa");
+
+    if (!paa)
+        return (PIX *)ERROR_PTR("paa not defined", procName, NULL);
+
+    if ((npixa = pixaaGetCount(paa, NULL)) == 0)
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+    same = pixaaVerifyDepth(paa, &maxdepth);
+    if (!same && maxdepth < 8)
+        return (PIX *)ERROR_PTR("depths differ; max < 8", procName, NULL);
+
+        /* Be sure the widest box fits in the output pix */
+    pixaaSizeRange(paa, NULL, NULL, &maxwidth, NULL);
+    if (maxwidth > maxw) {
+        L_WARNING("maxwidth > maxw; using maxwidth\n", procName);
+        maxw = maxwidth;
+    }
+
+
+        /* Get size of output pix.  The width is the minimum of the
+         * maxw and the largest pixa line width.  The height is whatever
+         * it needs to be to accommodate all pixa. */
+    lmaxw = 0;  /* widest line found */
+    use_maxw = FALSE;
+    nah = numaCreate(0);  /* store height of each line */
+    y = yspace;
+    for (i = 0; i < npixa; i++) {
+        pixa = pixaaGetPixa(paa, i, L_CLONE);
+        npix = pixaGetCount(pixa);
+        if (npix == 0) {
+            pixaDestroy(&pixa);
+            continue;
+        }
+        x = xspace;
+        lmaxh = 0;  /* max height found in the line */
+        for (j = 0; j < npix; j++) {
+            pix = pixaGetPix(pixa, j, L_CLONE);
+            pixGetDimensions(pix, &w, &h, NULL);
+            if (x + w >= maxw) {  /* start new line */
+                x = xspace;
+                y += lmaxh + yspace;
+                numaAddNumber(nah, lmaxh);
+                lmaxh = 0;
+                use_maxw = TRUE;
+            }
+            x += w + xspace;
+            lmaxh = L_MAX(h, lmaxh);
+            lmaxw = L_MAX(lmaxw, x);
+            pixDestroy(&pix);
+        }
+        y += lmaxh + yspace;
+        numaAddNumber(nah, lmaxh);
+        pixaDestroy(&pixa);
+    }
+    width = (use_maxw) ? maxw : lmaxw;
+
+    if ((pixd = pixCreate(width, y, maxdepth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+        /* Now layout the pix by pixa */
+    y = yspace;
+    harray = numaGetIArray(nah);
+    hindex = 0;
+    for (i = 0; i < npixa; i++) {
+        x = xspace;
+        pixa = pixaaGetPixa(paa, i, L_CLONE);
+        npix = pixaGetCount(pixa);
+        if (npix == 0) {
+            pixaDestroy(&pixa);
+            continue;
+        }
+        for (j = 0; j < npix; j++) {
+            pix = pixaGetPix(pixa, j, L_CLONE);
+            if (pixGetDepth(pix) != maxdepth) {
+                if (maxdepth == 8)
+                     pixt = pixConvertTo8(pix, 0);
+                else  /* 32 bpp */
+                     pixt = pixConvertTo32(pix);
+            } else {
+                pixt = pixClone(pix);
+            }
+            pixGetDimensions(pixt, &w, &h, NULL);
+            if (x + w >= maxw) {  /* start new line */
+                x = xspace;
+                y += harray[hindex++] + yspace;
+            }
+            pixRasterop(pixd, x, y, w, h, PIX_PAINT, pixt, 0, 0);
+            pixDestroy(&pix);
+            pixDestroy(&pixt);
+            x += w + xspace;
+        }
+        y += harray[hindex++] + yspace;
+        pixaDestroy(&pixa);
+    }
+    LEPT_FREE(harray);
+
+    numaDestroy(&nah);
+    return pixd;
+}
+
+
+/*!
+ *  pixaaDisplayTiledAndScaled()
+ *
+ *      Input:  paa
+ *              outdepth (output depth: 1, 8 or 32 bpp)
+ *              tilewidth (each pix is scaled to this width)
+ *              ncols (number of tiles in each row)
+ *              background (0 for white, 1 for black; this is the color
+ *                 of the spacing between the images)
+ *              spacing  (between images, and on outside)
+ *              border (width of additional black border on each image;
+ *                      use 0 for no border)
+ *      Return: pixa (of tiled images, one image for each pixa in
+ *                    the paa), or null on error
+ *
+ *  Notes:
+ *      (1) For each pixa, this generates from all the pix a
+ *          tiled/scaled output pix, and puts it in the output pixa.
+ *      (2) See comments in pixaDisplayTiledAndScaled().
+ */
+PIXA *
+pixaaDisplayTiledAndScaled(PIXAA   *paa,
+                           l_int32  outdepth,
+                           l_int32  tilewidth,
+                           l_int32  ncols,
+                           l_int32  background,
+                           l_int32  spacing,
+                           l_int32  border)
+{
+l_int32  i, n;
+PIX     *pix;
+PIXA    *pixa, *pixad;
+
+    PROCNAME("pixaaDisplayTiledAndScaled");
+
+    if (!paa)
+        return (PIXA *)ERROR_PTR("paa not defined", procName, NULL);
+    if (outdepth != 1 && outdepth != 8 && outdepth != 32)
+        return (PIXA *)ERROR_PTR("outdepth not in {1, 8, 32}", procName, NULL);
+    if (border < 0 || border > tilewidth / 5)
+        border = 0;
+
+    if ((n = pixaaGetCount(paa, NULL)) == 0)
+        return (PIXA *)ERROR_PTR("no components", procName, NULL);
+
+    pixad = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pixa = pixaaGetPixa(paa, i, L_CLONE);
+        pix = pixaDisplayTiledAndScaled(pixa, outdepth, tilewidth, ncols,
+                                        background, spacing, border);
+        pixaAddPix(pixad, pix, L_INSERT);
+        pixaDestroy(&pixa);
+    }
+
+    return pixad;
+}
+
+
+/*---------------------------------------------------------------------*
+ *         Conversion of all pix to specified type (e.g., depth)       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaConvertTo1()
+ *
+ *      Input:  pixas
+ *              thresh (threshold for final binarization from 8 bpp gray)
+ *      Return: pixad, or null on error
+ */
+PIXA *
+pixaConvertTo1(PIXA    *pixas,
+               l_int32  thresh)
+{
+l_int32  i, n;
+BOXA    *boxa;
+PIX     *pix1, *pix2;
+PIXA    *pixad;
+
+    PROCNAME("pixaConvertTo1");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+
+    n = pixaGetCount(pixas);
+    pixad = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixas, i, L_CLONE);
+        pix2 = pixConvertTo1(pix1, thresh);
+        pixaAddPix(pixad, pix2, L_INSERT);
+        pixDestroy(&pix1);
+    }
+
+    boxa = pixaGetBoxa(pixas, L_COPY);
+    pixaSetBoxa(pixad, boxa, L_INSERT);
+    return pixad;
+}
+
+
+/*!
+ *  pixaConvertTo8()
+ *
+ *      Input:  pixas
+ *              cmapflag (1 to give pixd a colormap; 0 otherwise)
+ *      Return: pixad (each pix is 8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) See notes for pixConvertTo8(), applied to each pix in pixas.
+ */
+PIXA *
+pixaConvertTo8(PIXA    *pixas,
+               l_int32  cmapflag)
+{
+l_int32  i, n;
+BOXA    *boxa;
+PIX     *pix1, *pix2;
+PIXA    *pixad;
+
+    PROCNAME("pixaConvertTo8");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+
+    n = pixaGetCount(pixas);
+    pixad = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixas, i, L_CLONE);
+        pix2 = pixConvertTo8(pix1, cmapflag);
+        pixaAddPix(pixad, pix2, L_INSERT);
+        pixDestroy(&pix1);
+    }
+
+    boxa = pixaGetBoxa(pixas, L_COPY);
+    pixaSetBoxa(pixad, boxa, L_INSERT);
+    return pixad;
+}
+
+
+/*!
+ *  pixaConvertTo8Color()
+ *
+ *      Input:  pixas
+ *              ditherflag (1 to dither if necessary; 0 otherwise)
+ *      Return: pixad (each pix is 8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) See notes for pixConvertTo8Color(), applied to each pix in pixas.
+ */
+PIXA *
+pixaConvertTo8Color(PIXA    *pixas,
+                    l_int32  dither)
+{
+l_int32  i, n;
+BOXA    *boxa;
+PIX     *pix1, *pix2;
+PIXA    *pixad;
+
+    PROCNAME("pixaConvertTo8Color");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+
+    n = pixaGetCount(pixas);
+    pixad = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixas, i, L_CLONE);
+        pix2 = pixConvertTo8Color(pix1, dither);
+        pixaAddPix(pixad, pix2, L_INSERT);
+        pixDestroy(&pix1);
+    }
+
+    boxa = pixaGetBoxa(pixas, L_COPY);
+    pixaSetBoxa(pixad, boxa, L_INSERT);
+    return pixad;
+}
+
+
+/*!
+ *  pixaConvertTo32()
+ *
+ *      Input:  pixas
+ *      Return: pixad (32 bpp rgb), or null on error
+ *
+ *  Notes:
+ *      (1) See notes for pixConvertTo32(), applied to each pix in pixas.
+ */
+PIXA *
+pixaConvertTo32(PIXA    *pixas)
+{
+l_int32  i, n;
+BOXA    *boxa;
+PIX     *pix1, *pix2;
+PIXA    *pixad;
+
+    PROCNAME("pixaConvertTo32");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+
+    n = pixaGetCount(pixas);
+    pixad = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixas, i, L_CLONE);
+        pix2 = pixConvertTo32(pix1);
+        pixaAddPix(pixad, pix2, L_INSERT);
+        pixDestroy(&pix1);
+    }
+
+    boxa = pixaGetBoxa(pixas, L_COPY);
+    pixaSetBoxa(pixad, boxa, L_INSERT);
+    return pixad;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                               Tile N-Up                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  convertToNUpFiles()
+ *
+ *      Input:  indir (full path to directory of images)
+ *              substr (<optional> can be null)
+ *              nx, ny (in [1, ... 50], tiling factors in each direction)
+ *              tw (target width, in pixels; must be >= 20)
+ *              spacing  (between images, and on outside)
+ *              border (width of additional black border on each image;
+ *                      use 0 for no border)
+ *              fontsize (to print tail of filename with image.  Valid set is
+ *                        {4,6,8,10,12,14,16,18,20}.  Use 0 to disable.)
+ *              outdir (subdirectory of /tmp to put N-up tiled images)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Each set of nx*ny images is scaled and tiled into a single
+ *          image, that is written out to @outdir.
+ *      (2) All images in each nx*ny set are scaled to the same width.
+ *          This is typically used when all images are roughly the same
+ *          size.
+ *      (3) Typical values for nx and ny are in [2 ... 5].
+ *      (4) All images are scaled to a width @tw.  They are not rescaled
+ *          when placed in the (nx,ny) mosaic.
+ */
+l_int32
+convertToNUpFiles(const char  *dir,
+                  const char  *substr,
+                  l_int32      nx,
+                  l_int32      ny,
+                  l_int32      tw,
+                  l_int32      spacing,
+                  l_int32      border,
+                  l_int32      fontsize,
+                  const char  *outdir)
+{
+l_int32  d, format;
+char     rootpath[256];
+PIXA    *pixa;
+
+    PROCNAME("convertToNUpFiles");
+
+    if (!dir)
+        return ERROR_INT("dir not defined", procName, 1);
+    if (nx < 1 || ny < 1 || nx > 50 || ny > 50)
+        return ERROR_INT("invalid tiling N-factor", procName, 1);
+    if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2)
+        return ERROR_INT("invalid fontsize", procName, 1);
+    if (!outdir)
+        return ERROR_INT("outdir not defined", procName, 1);
+
+    pixa = convertToNUpPixa(dir, substr, nx, ny, tw, spacing, border,
+                            fontsize);
+    if (!pixa)
+        return ERROR_INT("pixa not made", procName, 1);
+
+    lept_rmdir(outdir);
+    lept_mkdir(outdir);
+    pixaGetRenderingDepth(pixa, &d);
+    format = (d == 1) ? IFF_TIFF_G4 : IFF_JFIF_JPEG;
+    makeTempDirname(rootpath, 256, outdir);
+    modifyTrailingSlash(rootpath, 256, L_ADD_TRAIL_SLASH);
+    pixaWriteFiles(rootpath, pixa, format);
+    pixaDestroy(&pixa);
+    return 0;
+}
+
+
+/*!
+ *  convertToNUpPixa()
+ *
+ *      Input:  dir (full path to directory of images)
+ *              substr (<optional> can be null)
+ *              nx, ny (in [1, ... 50], tiling factors in each direction)
+ *              tw (target width, in pixels; must be >= 20)
+ *              spacing  (between images, and on outside)
+ *              border (width of additional black border on each image;
+ *                      use 0 for no border)
+ *              fontsize (to print tail of filename with image.  Valid set is
+ *                        {4,6,8,10,12,14,16,18,20}.  Use 0 to disable.)
+ *      Return: pixad, or null on error
+ *
+ *  Notes:
+ *      (1) See notes for convertToNUpFiles()
+ */
+PIXA *
+convertToNUpPixa(const char  *dir,
+                 const char  *substr,
+                 l_int32      nx,
+                 l_int32      ny,
+                 l_int32      tw,
+                 l_int32      spacing,
+                 l_int32      border,
+                 l_int32      fontsize)
+{
+l_int32    i, j, k, nt, n2, nout, d;
+char      *fname, *tail;
+L_BMF     *bmf;
+PIX       *pix1, *pix2, *pix3, *pix4;
+PIXA      *pixat, *pixad;
+SARRAY    *sa;
+
+    PROCNAME("convertToNUpPixa");
+
+    if (!dir)
+        return (PIXA *)ERROR_PTR("dir not defined", procName, NULL);
+    if (nx < 1 || ny < 1 || nx > 50 || ny > 50)
+        return (PIXA *)ERROR_PTR("invalid tiling N-factor", procName, NULL);
+    if (tw < 20)
+        return (PIXA *)ERROR_PTR("tw must be >= 20", procName, NULL);
+    if (fontsize < 0 || fontsize > 20 || fontsize & 1 || fontsize == 2)
+        return (PIXA *)ERROR_PTR("invalid fontsize", procName, NULL);
+
+    sa = getSortedPathnamesInDirectory(dir, substr, 0, 0);
+    nt = sarrayGetCount(sa);
+    n2 = nx * ny;
+    nout = (nt + n2 - 1) / n2;
+    pixad = pixaCreate(nout);
+    bmf = (fontsize == 0) ? NULL : bmfCreate(NULL, fontsize);
+    for (i = 0, j = 0; i < nout; i++) {
+        pixat = pixaCreate(n2);
+        for (k = 0; k < n2 && j < nt; j++, k++) {
+            fname = sarrayGetString(sa, j, L_NOCOPY);
+            if ((pix1 = pixRead(fname)) == NULL) {
+                L_ERROR("image not read from %s\n", procName, fname);
+                continue;
+            }
+            pix2 = pixScaleToSize(pix1, tw, 0);  /* all images have width tw */
+            if (bmf) {
+                splitPathAtDirectory(fname, NULL, &tail);
+                pix3 = pixAddTextlines(pix2, bmf, tail, 0xff000000,
+                                       L_ADD_BELOW);
+                LEPT_FREE(tail);
+            } else {
+                pix3 = pixClone(pix2);
+            }
+            pixaAddPix(pixat, pix3, L_INSERT);
+            pixDestroy(&pix1);
+            pixDestroy(&pix2);
+        }
+        if (pixaGetCount(pixat) == 0) continue;
+        pixaGetRenderingDepth(pixat, &d);
+            /* add 2 * border to image width to prevent scaling */
+        pix4 = pixaDisplayTiledAndScaled(pixat, d, tw + 2 * border, nx, 0,
+                                         spacing, border);
+        pixaAddPix(pixad, pix4, L_INSERT);
+        pixaDestroy(&pixat);
+    }
+
+    sarrayDestroy(&sa);
+    bmfDestroy(&bmf);
+    return pixad;
+}
+
diff --git a/src/pixalloc.c b/src/pixalloc.c
new file mode 100644 (file)
index 0000000..f5086bf
--- /dev/null
@@ -0,0 +1,531 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pixalloc.c
+ *
+ *      Custom memory storage with allocator and deallocator
+ *
+ *          l_int32       pmsCreate()
+ *          void          pmsDestroy()
+ *          void         *pmsCustomAlloc()
+ *          void          pmsCustomDealloc()
+ *          void         *pmsGetAlloc()
+ *          l_int32       pmsGetLevelForAlloc()
+ *          l_int32       pmsGetLevelForDealloc()
+ *          void          pmsLogInfo()
+ */
+
+#include "allheaders.h"
+
+/*-------------------------------------------------------------------------*
+ *                          Pix Memory Storage                             *
+ *                                                                         *
+ *  This is a simple utility for handling pix memory storage.  It is       *
+ *  enabled by setting the PixMemoryManager allocators to the functions    *
+ *  that are defined here                                                  *
+ *        pmsCustomAlloc()                                                 *
+ *        pmsCustomDealloc()                                               *
+ *  Use pmsCreate() at the beginning to do the pre-allocation, and         *
+ *  pmsDestroy() at the end to clean it up.                                *
+ *-------------------------------------------------------------------------*/
+/*
+ *  In the following, the "memory" refers to the image data
+ *  field that is used within the pix.  The memory store is a
+ *  continuous block of memory, that is logically divided into
+ *  smaller "chunks" starting with a set at a minimum size, and
+ *  followed by sets of increasing size that are a power of 2 larger
+ *  than the minimum size.  You must specify the number of chunks
+ *  of each size.
+ *
+ *  A requested data chunk, if it exists, is borrowed from the memory
+ *  storage, and returned after use.  If the chunk is too small, or
+ *  too large, or if all chunks in the appropriate size range are
+ *  in use, the memory is allocated dynamically and freed after use.
+ *
+ *  There are four parameters that determine the use of pre-allocated memory:
+ *
+ *    minsize: any requested chunk smaller than this is allocated
+ *             dynamically and destroyed after use.  No preallocated
+ *             memory is used.
+ *    smallest: the size of the smallest pre-allocated memory chunk.
+ *    nlevels:  the number of different sizes of data chunks, each a
+ *              power of 2 larger than 'smallest'.
+ *    numalloc: a Numa of size 'nlevels' containing the number of data
+ *              chunks for each size that are in the memory store.
+ *
+ *  As an example, suppose:
+ *    minsize = 0.5MB
+ *    smallest = 1.0MB
+ *    nlevels = 4
+ *    numalloc = {10, 5, 5, 5}
+ *  Then the total amount of allocated memory (in MB) is
+ *    10 * 1 + 5 * 2 + 5 * 4 + 5 * 8 = 80 MB
+ *  Any pix requiring less than 0.5 MB or more than 8 MB of memory will
+ *  not come from the memory store.  Instead, it will be dynamically
+ *  allocated and freed after use.
+ *
+ *  How is this implemented?
+ *
+ *  At setup, the full data block size is computed and allocated.
+ *  The addresses of the individual chunks are found, and the pointers
+ *  are stored in a set of Ptra (generic pointer arrays), using one Ptra
+ *  for each of the sizes of the chunks.  When returning a chunk after
+ *  use, it is necessary to determine from the address which size level
+ *  (ptra) the chunk belongs to.  This is done by comparing the address
+ *  of the associated chunk.
+ *
+ *  In the event that memory chunks need to be dynamically allocated,
+ *  either (1) because they are too small or too large for the memory
+ *  store or (2) because all the pix of that size (i.e., in the
+ *  appropriate level) in the memory store are in use, the
+ *  addresses generated will be outside the pre-allocated block.
+ *  After use they won't be returned to a ptra; instead the deallocator
+ *  will free them.
+ */
+
+
+struct PixMemoryStore
+{
+    struct L_Ptraa  *paa;          /* Holds ptrs to allocated memory        */
+    size_t           minsize;      /* Pix smaller than this (in bytes)      */
+                                   /* are allocated dynamically             */
+    size_t           smallest;     /* Smallest mem (in bytes) alloc'd       */
+    size_t           largest;      /* Larest mem (in bytes) alloc'd         */
+    size_t           nbytes;       /* Size of allocated block w/ all chunks */
+    l_int32          nlevels;      /* Num of power-of-2 sizes pre-alloc'd   */
+    size_t          *sizes;        /* Mem sizes at each power-of-2 level    */
+    l_int32         *allocarray;   /* Number of mem alloc'd at each size    */
+    l_uint32        *baseptr;      /* ptr to allocated array                */
+    l_uint32        *maxptr;       /* ptr just beyond allocated memory      */
+    l_uint32       **firstptr;     /* array of ptrs to first chunk in size  */
+    l_int32         *memused;      /* log: total # of pix used (by level)   */
+    l_int32         *meminuse;     /* log: # of pix in use (by level)       */
+    l_int32         *memmax;       /* log: max # of pix in use (by level)   */
+    l_int32         *memempty;     /* log: # of pix alloc'd because         */
+                                   /*      the store was empty (by level)   */
+    char            *logfile;      /* log: set to null if no logging        */
+};
+typedef struct PixMemoryStore   L_PIX_MEM_STORE;
+
+static L_PIX_MEM_STORE  *CustomPMS = NULL;
+
+
+/*!
+ *  pmsCreate()
+ *
+ *      Input:  minsize (of data chunk that can be supplied by pms)
+ *              smallest (bytes of the smallest pre-allocated data chunk.
+ *              numalloc (array with the number of data chunks for each
+ *                        size that are in the memory store)
+ *              logfile (use for debugging; null otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This computes the size of the block of memory required
+ *          and allocates it.  Each chunk starts on a 32-bit word boundary.
+ *          The chunk sizes are in powers of 2, starting at @smallest,
+ *          and the number of levels and chunks at each level is
+ *          specified by @numalloc.
+ *      (2) This is intended to manage the image data for a small number
+ *          of relatively large pix.  The system malloc is expected to
+ *          handle very large numbers of small chunks efficiently.
+ *      (3) Important: set the allocators and call this function
+ *          before any pix have been allocated.  Destroy all the pix
+ *          in the normal way before calling pmsDestroy().
+ *      (4) The pms struct is stored in a static global, so this function
+ *          is not thread-safe.  When used, there must be only one thread
+ *          per process.
+ */
+l_int32
+pmsCreate(size_t       minsize,
+          size_t       smallest,
+          NUMA        *numalloc,
+          const char  *logfile)
+{
+l_int32           nlevels, i, j, nbytes;
+l_int32          *alloca;
+l_float32         nchunks;
+l_uint32         *baseptr, *data;
+l_uint32        **firstptr;
+size_t           *sizes;
+L_PIX_MEM_STORE  *pms;
+L_PTRA           *pa;
+L_PTRAA          *paa;
+
+    PROCNAME("createPMS");
+
+    if (!numalloc)
+        return ERROR_INT("numalloc not defined", procName, 1);
+    numaGetSum(numalloc, &nchunks);
+    if (nchunks > 1000.0)
+        L_WARNING("There are %.0f chunks\n", procName, nchunks);
+
+    if ((pms = (L_PIX_MEM_STORE *)LEPT_CALLOC(1, sizeof(L_PIX_MEM_STORE)))
+        == NULL)
+        return ERROR_INT("pms not made", procName, 1);
+    CustomPMS = pms;
+
+        /* Make sure that minsize and smallest are multiples of 32 bit words */
+    if (minsize % 4 != 0)
+        minsize -= minsize % 4;
+    pms->minsize = minsize;
+    nlevels = numaGetCount(numalloc);
+    pms->nlevels = nlevels;
+
+    if ((sizes = (size_t *)LEPT_CALLOC(nlevels, sizeof(size_t))) == NULL)
+        return ERROR_INT("sizes not made", procName, 1);
+    pms->sizes = sizes;
+    if (smallest % 4 != 0)
+        smallest += 4 - (smallest % 4);
+    pms->smallest = smallest;
+    for (i = 0; i < nlevels; i++)
+        sizes[i] = smallest * (1 << i);
+    pms->largest = sizes[nlevels - 1];
+
+    alloca = numaGetIArray(numalloc);
+    pms->allocarray = alloca;
+    if ((paa = ptraaCreate(nlevels)) == NULL)
+        return ERROR_INT("paa not made", procName, 1);
+    pms->paa = paa;
+
+    for (i = 0, nbytes = 0; i < nlevels; i++)
+        nbytes += alloca[i] * sizes[i];
+    pms->nbytes = nbytes;
+
+    if ((baseptr = (l_uint32 *)LEPT_CALLOC(nbytes / 4, sizeof(l_uint32)))
+        == NULL)
+        return ERROR_INT("calloc fail for baseptr", procName, 1);
+    pms->baseptr = baseptr;
+    pms->maxptr = baseptr + nbytes / 4;  /* just beyond the memory store */
+    if ((firstptr = (l_uint32 **)LEPT_CALLOC(nlevels, sizeof(l_uint32 *)))
+        == NULL)
+        return ERROR_INT("calloc fail for firstptr", procName, 1);
+    pms->firstptr = firstptr;
+
+    data = baseptr;
+    for (i = 0; i < nlevels; i++) {
+        if ((pa = ptraCreate(alloca[i])) == NULL)
+            return ERROR_INT("pa not made", procName, 1);
+        ptraaInsertPtra(paa, i, pa);
+        firstptr[i] = data;
+        for (j = 0; j < alloca[i]; j++) {
+            ptraAdd(pa, data);
+            data += sizes[i] / 4;
+        }
+    }
+
+    if (logfile) {
+        pms->memused = (l_int32 *)LEPT_CALLOC(nlevels, sizeof(l_int32));
+        pms->meminuse = (l_int32 *)LEPT_CALLOC(nlevels, sizeof(l_int32));
+        pms->memmax = (l_int32 *)LEPT_CALLOC(nlevels, sizeof(l_int32));
+        pms->memempty = (l_int32 *)LEPT_CALLOC(nlevels, sizeof(l_int32));
+        pms->logfile = stringNew(logfile);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pmsDestroy()
+ *
+ *      Input:  (none)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Important: call this function at the end of the program, after
+ *          the last pix has been destroyed.
+ */
+void
+pmsDestroy()
+{
+L_PIX_MEM_STORE  *pms;
+
+    if ((pms = CustomPMS) == NULL)
+        return;
+
+    ptraaDestroy(&pms->paa, FALSE, FALSE);  /* don't touch the ptrs */
+    LEPT_FREE(pms->baseptr);  /* free the memory */
+
+    if (pms->logfile) {
+        pmsLogInfo();
+        LEPT_FREE(pms->logfile);
+        LEPT_FREE(pms->memused);
+        LEPT_FREE(pms->meminuse);
+        LEPT_FREE(pms->memmax);
+        LEPT_FREE(pms->memempty);
+    }
+
+    LEPT_FREE(pms->sizes);
+    LEPT_FREE(pms->allocarray);
+    LEPT_FREE(pms->firstptr);
+    LEPT_FREE(pms);
+    CustomPMS = NULL;
+    return;
+}
+
+
+/*!
+ *  pmsCustomAlloc()
+ *
+ *      Input: nbytes (min number of bytes in the chunk to be retrieved)
+ *      Return: data (ptr to chunk)
+ *
+ *  Notes:
+ *      (1) This attempts to find a suitable pre-allocated chunk.
+ *          If not found, it dynamically allocates the chunk.
+ *      (2) If logging is turned on, the allocations that are not taken
+ *          from the memory store, and are at least as large as the
+ *          minimum size the store can handle, are logged to file.
+ */
+void *
+pmsCustomAlloc(size_t  nbytes)
+{
+l_int32           level;
+void             *data;
+L_PIX_MEM_STORE  *pms;
+L_PTRA           *pa;
+
+    PROCNAME("pmsCustomAlloc");
+
+    if ((pms = CustomPMS) == NULL)
+        return (void *)ERROR_PTR("pms not defined", procName, NULL);
+
+    pmsGetLevelForAlloc(nbytes, &level);
+
+    if (level < 0) {  /* size range invalid; must alloc */
+        if ((data = pmsGetAlloc(nbytes)) == NULL)
+            return (void *)ERROR_PTR("data not made", procName, NULL);
+    } else {  /* get from store */
+        pa = ptraaGetPtra(pms->paa, level, L_HANDLE_ONLY);
+        data = ptraRemoveLast(pa);
+        if (data && pms->logfile) {
+            pms->memused[level]++;
+            pms->meminuse[level]++;
+            if (pms->meminuse[level] > pms->memmax[level])
+                pms->memmax[level]++;
+        }
+        if (!data) {  /* none left at this level */
+            data = pmsGetAlloc(nbytes);
+            if (pms->logfile)
+                pms->memempty[level]++;
+        }
+    }
+
+    return data;
+}
+
+
+/*!
+ *  pmsCustomDealloc()
+ *
+ *      Input: data (to be freed or returned to the storage)
+ *      Return: void
+ */
+void
+pmsCustomDealloc(void  *data)
+{
+l_int32           level;
+L_PIX_MEM_STORE  *pms;
+L_PTRA           *pa;
+
+    PROCNAME("pmsCustomDealloc");
+
+    if ((pms = CustomPMS) == NULL) {
+        L_ERROR("pms not defined\n", procName);
+        return;
+    }
+
+    if (pmsGetLevelForDealloc(data, &level) == 1) {
+        L_ERROR("level not found\n", procName);
+        return;
+    }
+
+    if (level < 0) {  /* no logging; just free the data */
+        LEPT_FREE(data);
+    } else {  /* return the data to the store */
+        pa = ptraaGetPtra(pms->paa, level, L_HANDLE_ONLY);
+        ptraAdd(pa, data);
+        if (pms->logfile)
+            pms->meminuse[level]--;
+    }
+
+    return;
+}
+
+
+/*!
+ *  pmsGetAlloc()
+ *
+ *      Input:  nbytes
+ *      Return: data
+ *
+ *  Notes:
+ *      (1) This is called when a request for pix data cannot be
+ *          obtained from the preallocated memory store.  After use it
+ *          is freed like normal memory.
+ *      (2) If logging is on, only write out allocs that are as large as
+ *          the minimum size handled by the memory store.
+ *      (3) size_t is %lu on 64 bit platforms and %u on 32 bit platforms.
+ *          The C99 platform-independent format specifier for size_t is %zu,
+ *          but windows hasn't conformed, so we are forced to go back to
+ *          C89, use %lu, and cast to get platform-independence.  Ugh.
+ */
+void *
+pmsGetAlloc(size_t  nbytes)
+{
+void             *data;
+FILE             *fp;
+L_PIX_MEM_STORE  *pms;
+
+    PROCNAME("pmsGetAlloc");
+
+    if ((pms = CustomPMS) == NULL)
+        return (void *)ERROR_PTR("pms not defined", procName, NULL);
+
+    if ((data = (void *)LEPT_CALLOC(nbytes, sizeof(char))) == NULL)
+        return (void *)ERROR_PTR("data not made", procName, NULL);
+    if (pms->logfile && nbytes >= pms->smallest) {
+        fp = fopenWriteStream(pms->logfile, "a");
+        fprintf(fp, "Alloc %lu bytes at %p\n", (unsigned long)nbytes, data);
+        fclose(fp);
+    }
+
+    return data;
+}
+
+
+/*!
+ *  pmsGetLevelForAlloc()
+ *
+ *      Input: nbytes (min number of bytes in the chunk to be retrieved)
+ *             &level (<return>; -1 if either too small or too large)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pmsGetLevelForAlloc(size_t    nbytes,
+                    l_int32  *plevel)
+{
+l_int32           i;
+l_float64         ratio;
+L_PIX_MEM_STORE  *pms;
+
+    PROCNAME("pmsGetLevelForAlloc");
+
+    if (!plevel)
+        return ERROR_INT("&level not defined", procName, 1);
+    *plevel = -1;
+    if ((pms = CustomPMS) == NULL)
+        return ERROR_INT("pms not defined", procName, 1);
+
+    if (nbytes < pms->minsize || nbytes > pms->largest)
+        return 0;   /*  -1  */
+
+    ratio = (l_float64)nbytes / (l_float64)(pms->smallest);
+    for (i = 0; i < pms->nlevels; i++) {
+        if (ratio <= 1.0)
+            break;
+        ratio /= 2.0;
+    }
+    *plevel = i;
+
+    return 0;
+}
+
+
+/*!
+ *  pmsGetLevelForDealloc()
+ *
+ *      Input: data (ptr to memory chunk)
+ *             &level (<return> level in memory store; -1 if allocated
+ *                     outside the store)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pmsGetLevelForDealloc(void     *data,
+                      l_int32  *plevel)
+{
+l_int32           i;
+l_uint32         *first;
+L_PIX_MEM_STORE  *pms;
+
+    PROCNAME("pmsGetLevelForDealloc");
+
+    if (!plevel)
+        return ERROR_INT("&level not defined", procName, 1);
+    *plevel = -1;
+    if (!data)
+        return ERROR_INT("data not defined", procName, 1);
+    if ((pms = CustomPMS) == NULL)
+        return ERROR_INT("pms not defined", procName, 1);
+
+    if (data < (void *)pms->baseptr || data >= (void *)pms->maxptr)
+        return 0;   /*  -1  */
+
+    for (i = 1; i < pms->nlevels; i++) {
+        first = pms->firstptr[i];
+        if (data < (void *)first)
+            break;
+    }
+    *plevel = i - 1;
+
+    return 0;
+}
+
+
+/*!
+ *  pmsLogInfo()
+ *
+ *      Input:  (none)
+ *      Return: void
+ */
+void
+pmsLogInfo()
+{
+l_int32           i;
+L_PIX_MEM_STORE  *pms;
+
+    if ((pms = CustomPMS) == NULL)
+        return;
+
+    fprintf(stderr, "Total number of pix used at each level\n");
+    for (i = 0; i < pms->nlevels; i++)
+         fprintf(stderr, " Level %d (%lu bytes): %d\n", i,
+                 (unsigned long)pms->sizes[i], pms->memused[i]);
+
+    fprintf(stderr, "Max number of pix in use at any time in each level\n");
+    for (i = 0; i < pms->nlevels; i++)
+         fprintf(stderr, " Level %d (%lu bytes): %d\n", i,
+                 (unsigned long)pms->sizes[i], pms->memmax[i]);
+
+    fprintf(stderr, "Number of pix alloc'd because none were available\n");
+    for (i = 0; i < pms->nlevels; i++)
+         fprintf(stderr, " Level %d (%lu bytes): %d\n", i,
+                 (unsigned long)pms->sizes[i], pms->memempty[i]);
+
+    return;
+}
diff --git a/src/pixarith.c b/src/pixarith.c
new file mode 100644 (file)
index 0000000..9a576f9
--- /dev/null
@@ -0,0 +1,1341 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pixarith.c
+ *
+ *      One-image grayscale arithmetic operations (8, 16, 32 bpp)
+ *           l_int32     pixAddConstantGray()
+ *           l_int32     pixMultConstantGray()
+ *
+ *      Two-image grayscale arithmetic operations (8, 16, 32 bpp)
+ *           PIX        *pixAddGray()
+ *           PIX        *pixSubtractGray()
+ *
+ *      Grayscale threshold operation (8, 16, 32 bpp)
+ *           PIX        *pixThresholdToValue()
+ *
+ *      Image accumulator arithmetic operations
+ *           PIX        *pixInitAccumulate()
+ *           PIX        *pixFinalAccumulate()
+ *           PIX        *pixFinalAccumulateThreshold()
+ *           l_int32     pixAccumulate()
+ *           l_int32     pixMultConstAccumulate()
+ *
+ *      Absolute value of difference
+ *           PIX        *pixAbsDifference()
+ *
+ *      Sum of color images
+ *           PIX        *pixAddRGB()
+ *
+ *      Two-image min and max operations (8 and 16 bpp)
+ *           PIX        *pixMinOrMax()
+ *
+ *      Scale pix for maximum dynamic range in 8 bpp image:
+ *           PIX        *pixMaxDynamicRange()
+ *
+ *      Log base2 lookup
+ *           l_float32  *makeLogBase2Tab()
+ *           l_float32   getLogBase2()
+ *
+ *      The image accumulator operations are used when you expect
+ *      overflow from 8 bits on intermediate results.  For example,
+ *      you might want a tophat contrast operator which is
+ *         3*I - opening(I,S) - closing(I,S)
+ *      To use these operations, first use the init to generate
+ *      a 16 bpp image, use the accumulate to add or subtract 8 bpp
+ *      images from that, or the multiply constant to multiply
+ *      by a small constant (much less than 256 -- we don't want
+ *      overflow from the 16 bit images!), and when you're finished
+ *      use final to bring the result back to 8 bpp, clipped
+ *      if necessary.  There is also a divide function, which
+ *      can be used to divide one image by another, scaling the
+ *      result for maximum dynamic range, and giving back the
+ *      8 bpp result.
+ *
+ *      A simpler interface to the arithmetic operations is
+ *      provided in pixacc.c.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+
+/*-------------------------------------------------------------*
+ *          One-image grayscale arithmetic operations          *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixAddConstantGray()
+ *
+ *      Input:  pixs (8, 16 or 32 bpp)
+ *              val  (amount to add to each pixel)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) In-place operation.
+ *      (2) No clipping for 32 bpp.
+ *      (3) For 8 and 16 bpp, if val > 0 the result is clipped
+ *          to 0xff and 0xffff, rsp.
+ *      (4) For 8 and 16 bpp, if val < 0 the result is clipped to 0.
+ */
+l_int32
+pixAddConstantGray(PIX      *pixs,
+                   l_int32   val)
+{
+l_int32    i, j, w, h, d, wpl, pval;
+l_uint32  *data, *line;
+
+    PROCNAME("pixAddConstantGray");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 16 && d != 32)
+        return ERROR_INT("pixs not 8, 16 or 32 bpp", procName, 1);
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        if (d == 8) {
+            if (val < 0) {
+                for (j = 0; j < w; j++) {
+                    pval = GET_DATA_BYTE(line, j);
+                    pval = L_MAX(0, pval + val);
+                    SET_DATA_BYTE(line, j, pval);
+                }
+            } else {  /* val >= 0 */
+                for (j = 0; j < w; j++) {
+                    pval = GET_DATA_BYTE(line, j);
+                    pval = L_MIN(255, pval + val);
+                    SET_DATA_BYTE(line, j, pval);
+                }
+            }
+        } else if (d == 16) {
+            if (val < 0) {
+                for (j = 0; j < w; j++) {
+                    pval = GET_DATA_TWO_BYTES(line, j);
+                    pval = L_MAX(0, pval + val);
+                    SET_DATA_TWO_BYTES(line, j, pval);
+                }
+            } else {  /* val >= 0 */
+                for (j = 0; j < w; j++) {
+                    pval = GET_DATA_TWO_BYTES(line, j);
+                    pval = L_MIN(0xffff, pval + val);
+                    SET_DATA_TWO_BYTES(line, j, pval);
+                }
+            }
+        } else {  /* d == 32; no check for overflow (< 0 or > 0xffffffff) */
+            for (j = 0; j < w; j++)
+                *(line + j) += val;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixMultConstantGray()
+ *
+ *      Input:  pixs (8, 16 or 32 bpp)
+ *              val  (>= 0.0; amount to multiply by each pixel)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) In-place operation; val must be >= 0.
+ *      (2) No clipping for 32 bpp.
+ *      (3) For 8 and 16 bpp, the result is clipped to 0xff and 0xffff, rsp.
+ */
+l_int32
+pixMultConstantGray(PIX       *pixs,
+                    l_float32  val)
+{
+l_int32    i, j, w, h, d, wpl, pval;
+l_uint32   upval;
+l_uint32  *data, *line;
+
+    PROCNAME("pixMultConstantGray");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 16 && d != 32)
+        return ERROR_INT("pixs not 8, 16 or 32 bpp", procName, 1);
+    if (val < 0.0)
+        return ERROR_INT("val < 0.0", procName, 1);
+
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        if (d == 8) {
+            for (j = 0; j < w; j++) {
+                pval = GET_DATA_BYTE(line, j);
+                pval = (l_int32)(val * pval);
+                pval = L_MIN(255, pval);
+                SET_DATA_BYTE(line, j, pval);
+            }
+        } else if (d == 16) {
+            for (j = 0; j < w; j++) {
+                pval = GET_DATA_TWO_BYTES(line, j);
+                pval = (l_int32)(val * pval);
+                pval = L_MIN(0xffff, pval);
+                SET_DATA_TWO_BYTES(line, j, pval);
+            }
+        } else {  /* d == 32; no clipping */
+            for (j = 0; j < w; j++) {
+                upval = *(line + j);
+                upval = (l_uint32)(val * upval);
+                *(line + j) = upval;
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *             Two-image grayscale arithmetic ops              *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixAddGray()
+ *
+ *      Input:  pixd (<optional>; this can be null, equal to pixs1, or
+ *                    different from pixs1)
+ *              pixs1 (can be == to pixd)
+ *              pixs2
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) Arithmetic addition of two 8, 16 or 32 bpp images.
+ *      (2) For 8 and 16 bpp, we do explicit clipping to 0xff and 0xffff,
+ *          respectively.
+ *      (3) Alignment is to UL corner.
+ *      (4) There are 3 cases.  The result can go to a new dest,
+ *          in-place to pixs1, or to an existing input dest:
+ *          * pixd == null:   (src1 + src2) --> new pixd
+ *          * pixd == pixs1:  (src1 + src2) --> src1  (in-place)
+ *          * pixd != pixs1:  (src1 + src2) --> input pixd
+ *      (5) pixs2 must be different from both pixd and pixs1.
+ */
+PIX *
+pixAddGray(PIX  *pixd,
+           PIX  *pixs1,
+           PIX  *pixs2)
+{
+l_int32    i, j, d, ws, hs, w, h, wpls, wpld, val, sum;
+l_uint32  *datas, *datad, *lines, *lined;
+
+    PROCNAME("pixAddGray");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
+    if (pixs2 == pixs1)
+        return (PIX *)ERROR_PTR("pixs2 and pixs1 must differ", procName, pixd);
+    if (pixs2 == pixd)
+        return (PIX *)ERROR_PTR("pixs2 and pixd must differ", procName, pixd);
+    d = pixGetDepth(pixs1);
+    if (d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("pix are not 8, 16 or 32 bpp", procName, pixd);
+    if (pixGetDepth(pixs2) != d)
+        return (PIX *)ERROR_PTR("depths differ (pixs1, pixs2)", procName, pixd);
+    if (pixd && (pixGetDepth(pixd) != d))
+        return (PIX *)ERROR_PTR("depths differ (pixs1, pixd)", procName, pixd);
+
+    if (!pixSizesEqual(pixs1, pixs2))
+        L_WARNING("pixs1 and pixs2 not equal in size\n", procName);
+    if (pixd && !pixSizesEqual(pixs1, pixd))
+        L_WARNING("pixs1 and pixd not equal in size\n", procName);
+
+    if (pixs1 != pixd)
+        pixd = pixCopy(pixd, pixs1);
+
+        /* pixd + pixs2 ==> pixd  */
+    datas = pixGetData(pixs2);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs2);
+    wpld = pixGetWpl(pixd);
+    pixGetDimensions(pixs2, &ws, &hs, NULL);
+    pixGetDimensions(pixd, &w, &h, NULL);
+    w = L_MIN(ws, w);
+    h = L_MIN(hs, h);
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        lines = datas + i * wpls;
+        if (d == 8) {
+            for (j = 0; j < w; j++) {
+                sum = GET_DATA_BYTE(lines, j) + GET_DATA_BYTE(lined, j);
+                val = L_MIN(sum, 255);
+                SET_DATA_BYTE(lined, j, val);
+            }
+        } else if (d == 16) {
+            for (j = 0; j < w; j++) {
+                sum = GET_DATA_TWO_BYTES(lines, j)
+                    + GET_DATA_TWO_BYTES(lined, j);
+                val = L_MIN(sum, 0xffff);
+                SET_DATA_TWO_BYTES(lined, j, val);
+            }
+        } else {   /* d == 32; no clipping */
+            for (j = 0; j < w; j++)
+                *(lined + j) += *(lines + j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixSubtractGray()
+ *
+ *      Input:  pixd (<optional>; this can be null, equal to pixs1, or
+ *                    different from pixs1)
+ *              pixs1 (can be == to pixd)
+ *              pixs2
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) Arithmetic subtraction of two 8, 16 or 32 bpp images.
+ *      (2) Source pixs2 is always subtracted from source pixs1.
+ *      (3) Do explicit clipping to 0.
+ *      (4) Alignment is to UL corner.
+ *      (5) There are 3 cases.  The result can go to a new dest,
+ *          in-place to pixs1, or to an existing input dest:
+ *          (a) pixd == null   (src1 - src2) --> new pixd
+ *          (b) pixd == pixs1  (src1 - src2) --> src1  (in-place)
+ *          (d) pixd != pixs1  (src1 - src2) --> input pixd
+ *      (6) pixs2 must be different from both pixd and pixs1.
+ */
+PIX *
+pixSubtractGray(PIX  *pixd,
+                PIX  *pixs1,
+                PIX  *pixs2)
+{
+l_int32    i, j, w, h, ws, hs, d, wpls, wpld, val, diff;
+l_uint32  *datas, *datad, *lines, *lined;
+
+    PROCNAME("pixSubtractGray");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
+    if (pixs2 == pixs1)
+        return (PIX *)ERROR_PTR("pixs2 and pixs1 must differ", procName, pixd);
+    if (pixs2 == pixd)
+        return (PIX *)ERROR_PTR("pixs2 and pixd must differ", procName, pixd);
+    d = pixGetDepth(pixs1);
+    if (d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("pix are not 8, 16 or 32 bpp", procName, pixd);
+    if (pixGetDepth(pixs2) != d)
+        return (PIX *)ERROR_PTR("depths differ (pixs1, pixs2)", procName, pixd);
+    if (pixd && (pixGetDepth(pixd) != d))
+        return (PIX *)ERROR_PTR("depths differ (pixs1, pixd)", procName, pixd);
+
+    if (!pixSizesEqual(pixs1, pixs2))
+        L_WARNING("pixs1 and pixs2 not equal in size\n", procName);
+    if (pixd && !pixSizesEqual(pixs1, pixd))
+        L_WARNING("pixs1 and pixd not equal in size\n", procName);
+
+    if (pixs1 != pixd)
+        pixd = pixCopy(pixd, pixs1);
+
+        /* pixd - pixs2 ==> pixd  */
+    datas = pixGetData(pixs2);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs2);
+    wpld = pixGetWpl(pixd);
+    pixGetDimensions(pixs2, &ws, &hs, NULL);
+    pixGetDimensions(pixd, &w, &h, NULL);
+    w = L_MIN(ws, w);
+    h = L_MIN(hs, h);
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        lines = datas + i * wpls;
+        if (d == 8) {
+            for (j = 0; j < w; j++) {
+                diff = GET_DATA_BYTE(lined, j) - GET_DATA_BYTE(lines, j);
+                val = L_MAX(diff, 0);
+                SET_DATA_BYTE(lined, j, val);
+            }
+        } else if (d == 16) {
+            for (j = 0; j < w; j++) {
+                diff = GET_DATA_TWO_BYTES(lined, j)
+                       - GET_DATA_TWO_BYTES(lines, j);
+                val = L_MAX(diff, 0);
+                SET_DATA_TWO_BYTES(lined, j, val);
+            }
+        } else {  /* d == 32; no clipping */
+            for (j = 0; j < w; j++)
+                *(lined + j) -= *(lines + j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ *                Grayscale threshold operation                *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixThresholdToValue()
+ *
+ *      Input:  pixd (<optional>; if not null, must be equal to pixs)
+ *              pixs (8, 16, 32 bpp)
+ *              threshval
+ *              setval
+ *      Return: pixd always
+ *
+ *  Notes:
+ *    - operation can be in-place (pixs == pixd) or to a new pixd
+ *    - if setval > threshval, sets pixels with a value >= threshval to setval
+ *    - if setval < threshval, sets pixels with a value <= threshval to setval
+ *    - if setval == threshval, no-op
+ */
+PIX *
+pixThresholdToValue(PIX      *pixd,
+                    PIX      *pixs,
+                    l_int32   threshval,
+                    l_int32   setval)
+{
+l_int32    i, j, w, h, d, wpld, setabove;
+l_uint32  *datad, *lined;
+
+    PROCNAME("pixThresholdToValue");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8, 16 or 32 bpp", procName, pixd);
+    if (pixd && (pixs != pixd))
+        return (PIX *)ERROR_PTR("pixd exists and is not pixs", procName, pixd);
+    if (threshval < 0 || setval < 0)
+        return (PIX *)ERROR_PTR("threshval & setval not < 0", procName, pixd);
+    if (d == 8 && setval > 255)
+        return (PIX *)ERROR_PTR("setval > 255 for 8 bpp", procName, pixd);
+    if (d == 16 && setval > 0xffff)
+        return (PIX *)ERROR_PTR("setval > 0xffff for 16 bpp", procName, pixd);
+
+    if (!pixd)
+        pixd = pixCopy(NULL, pixs);
+    if (setval == threshval) {
+        L_WARNING("setval == threshval; no operation\n", procName);
+        return pixd;
+    }
+
+    datad = pixGetData(pixd);
+    pixGetDimensions(pixd, &w, &h, NULL);
+    wpld = pixGetWpl(pixd);
+    if (setval > threshval)
+        setabove = TRUE;
+    else
+        setabove = FALSE;
+
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        if (setabove == TRUE) {
+            if (d == 8) {
+                for (j = 0; j < w; j++) {
+                    if (GET_DATA_BYTE(lined, j) - threshval >= 0)
+                        SET_DATA_BYTE(lined, j, setval);
+                }
+            } else if (d == 16) {
+                for (j = 0; j < w; j++) {
+                    if (GET_DATA_TWO_BYTES(lined, j) - threshval >= 0)
+                        SET_DATA_TWO_BYTES(lined, j, setval);
+                }
+            } else {  /* d == 32 */
+                for (j = 0; j < w; j++) {
+                    if (*(lined + j) >= threshval)
+                        *(lined + j) = setval;
+                }
+            }
+        } else { /* set if below or at threshold */
+            if (d == 8) {
+                for (j = 0; j < w; j++) {
+                    if (GET_DATA_BYTE(lined, j) - threshval <= 0)
+                        SET_DATA_BYTE(lined, j, setval);
+                }
+            } else if (d == 16) {
+                for (j = 0; j < w; j++) {
+                    if (GET_DATA_TWO_BYTES(lined, j) - threshval <= 0)
+                        SET_DATA_TWO_BYTES(lined, j, setval);
+                }
+            } else {  /* d == 32 */
+                for (j = 0; j < w; j++) {
+                    if (*(lined + j) <= threshval)
+                        *(lined + j) = setval;
+                }
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ *            Image accumulator arithmetic operations          *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixInitAccumulate()
+ *
+ *      Input:  w, h (of accumulate array)
+ *              offset (initialize the 32 bpp to have this
+ *                      value; not more than 0x40000000)
+ *      Return: pixd (32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) The offset must be >= 0.
+ *      (2) The offset is used so that we can do arithmetic
+ *          with negative number results on l_uint32 data; it
+ *          prevents the l_uint32 data from going negative.
+ *      (3) Because we use l_int32 intermediate data results,
+ *          these should never exceed the max of l_int32 (0x7fffffff).
+ *          We do not permit the offset to be above 0x40000000,
+ *          which is half way between 0 and the max of l_int32.
+ *      (4) The same offset should be used for initialization,
+ *          multiplication by a constant, and final extraction!
+ *      (5) If you're only adding positive values, offset can be 0.
+ */
+PIX *
+pixInitAccumulate(l_int32   w,
+                  l_int32   h,
+                  l_uint32  offset)
+{
+PIX  *pixd;
+
+    PROCNAME("pixInitAccumulate");
+
+    if ((pixd = pixCreate(w, h, 32)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    if (offset > 0x40000000)
+        offset = 0x40000000;
+    pixSetAllArbitrary(pixd, offset);
+    return pixd;
+}
+
+
+/*!
+ *  pixFinalAccumulate()
+ *
+ *      Input:  pixs (32 bpp)
+ *              offset (same as used for initialization)
+ *              depth  (8, 16 or 32 bpp, of destination)
+ *      Return: pixd (8, 16 or 32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) The offset must be >= 0 and should not exceed 0x40000000.
+ *      (2) The offset is subtracted from the src 32 bpp image
+ *      (3) For 8 bpp dest, the result is clipped to [0, 0xff]
+ *      (4) For 16 bpp dest, the result is clipped to [0, 0xffff]
+ */
+PIX *
+pixFinalAccumulate(PIX      *pixs,
+                   l_uint32  offset,
+                   l_int32   depth)
+{
+l_int32    i, j, w, h, wpls, wpld, val;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixFinalAccumulate");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (depth != 8 && depth != 16 && depth != 32)
+        return (PIX *)ERROR_PTR("dest depth not 8, 16, 32 bpp", procName, NULL);
+    if (offset > 0x40000000)
+        offset = 0x40000000;
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w, h, depth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);  /* but how did pixs get it initially? */
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    if (depth == 8) {
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                val = lines[j] - offset;
+                val = L_MAX(0, val);
+                val = L_MIN(255, val);
+                SET_DATA_BYTE(lined, j, (l_uint8)val);
+            }
+        }
+    } else if (depth == 16) {
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                val = lines[j] - offset;
+                val = L_MAX(0, val);
+                val = L_MIN(0xffff, val);
+                SET_DATA_TWO_BYTES(lined, j, (l_uint16)val);
+            }
+        }
+    } else {  /* depth == 32 */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++)
+                lined[j] = lines[j] - offset;
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixFinalAccumulateThreshold()
+ *
+ *      Input:  pixs (32 bpp)
+ *              offset (same as used for initialization)
+ *              threshold (values less than this are set in the destination)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) The offset must be >= 0 and should not exceed 0x40000000.
+ *      (2) The offset is subtracted from the src 32 bpp image
+ */
+PIX *
+pixFinalAccumulateThreshold(PIX      *pixs,
+                            l_uint32  offset,
+                            l_uint32  threshold)
+{
+l_int32    i, j, w, h, wpls, wpld, val;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixFinalAccumulateThreshold");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (offset > 0x40000000)
+        offset = 0x40000000;
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w, h, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);  /* but how did pixs get it initially? */
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = lines[j] - offset;
+            if (val >= threshold) {
+                SET_DATA_BIT(lined, j);
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixAccumulate()
+ *
+ *      Input:  pixd (32 bpp)
+ *              pixs (1, 8, 16 or 32 bpp)
+ *              op  (L_ARITH_ADD or L_ARITH_SUBTRACT)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This adds or subtracts each pixs value from pixd.
+ *      (2) This clips to the minimum of pixs and pixd, so they
+ *          do not need to be the same size.
+ *      (3) The alignment is to the origin (UL corner) of pixs & pixd.
+ */
+l_int32
+pixAccumulate(PIX     *pixd,
+              PIX     *pixs,
+              l_int32  op)
+{
+l_int32    i, j, w, h, d, wd, hd, wpls, wpld;
+l_uint32  *datas, *datad, *lines, *lined;
+
+
+    PROCNAME("pixAccumulate");
+
+    if (!pixd || (pixGetDepth(pixd) != 32))
+        return ERROR_INT("pixd not defined or not 32 bpp", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    d = pixGetDepth(pixs);
+    if (d != 1 && d != 8 && d != 16 && d != 32)
+        return ERROR_INT("pixs not 1, 8, 16 or 32 bpp", procName, 1);
+    if (op != L_ARITH_ADD && op != L_ARITH_SUBTRACT)
+        return ERROR_INT("op must be in {L_ARITH_ADD, L_ARITH_SUBTRACT}",
+                         procName, 1);
+
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixGetDimensions(pixd, &wd, &hd, NULL);
+    w = L_MIN(w, wd);
+    h = L_MIN(h, hd);
+    if (d == 1) {
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            if (op == L_ARITH_ADD) {
+                for (j = 0; j < w; j++)
+                    lined[j] += GET_DATA_BIT(lines, j);
+            } else {  /* op == L_ARITH_SUBTRACT */
+                for (j = 0; j < w; j++)
+                    lined[j] -= GET_DATA_BIT(lines, j);
+            }
+        }
+    } else if (d == 8) {
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            if (op == L_ARITH_ADD) {
+                for (j = 0; j < w; j++)
+                    lined[j] += GET_DATA_BYTE(lines, j);
+            } else {  /* op == L_ARITH_SUBTRACT */
+                for (j = 0; j < w; j++)
+                    lined[j] -= GET_DATA_BYTE(lines, j);
+            }
+        }
+    } else if (d == 16) {
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            if (op == L_ARITH_ADD) {
+                for (j = 0; j < w; j++)
+                    lined[j] += GET_DATA_TWO_BYTES(lines, j);
+            } else {  /* op == L_ARITH_SUBTRACT */
+                for (j = 0; j < w; j++)
+                    lined[j] -= GET_DATA_TWO_BYTES(lines, j);
+            }
+        }
+    } else {  /* d == 32 */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            if (op == L_ARITH_ADD) {
+                for (j = 0; j < w; j++)
+                    lined[j] += lines[j];
+            } else {  /* op == L_ARITH_SUBTRACT */
+                for (j = 0; j < w; j++)
+                    lined[j] -= lines[j];
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixMultConstAccumulate()
+ *
+ *      Input:  pixs (32 bpp)
+ *              factor
+ *              offset (same as used for initialization)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The offset must be >= 0 and should not exceed 0x40000000.
+ *      (2) This multiplies each pixel, relative to offset, by the input factor
+ *      (3) The result is returned with the offset back in place.
+ */
+l_int32
+pixMultConstAccumulate(PIX       *pixs,
+                       l_float32  factor,
+                       l_uint32   offset)
+{
+l_int32    i, j, w, h, wpl, val;
+l_uint32  *data, *line;
+
+    PROCNAME("pixMultConstAccumulate");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not 32 bpp", procName, 1);
+    if (offset > 0x40000000)
+        offset = 0x40000000;
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < w; j++) {
+            val = line[j] - offset;
+            val = (l_int32)(val * factor);
+            val += offset;
+            line[j] = (l_uint32)val;
+        }
+    }
+
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                      Absolute value of difference                     *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixAbsDifference()
+ *
+ *      Input:  pixs1, pixs2  (both either 8 or 16 bpp gray, or 32 bpp RGB)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The depth of pixs1 and pixs2 must be equal.
+ *      (2) Clips computation to the min size, aligning the UL corners
+ *      (3) For 8 and 16 bpp, assumes one gray component.
+ *      (4) For 32 bpp, assumes 3 color components, and ignores the
+ *          LSB of each word (the alpha channel)
+ *      (5) Computes the absolute value of the difference between
+ *          each component value.
+ */
+PIX *
+pixAbsDifference(PIX  *pixs1,
+                 PIX  *pixs2)
+{
+l_int32    i, j, w, h, w2, h2, d, wpls1, wpls2, wpld, val1, val2, diff;
+l_int32    rval1, gval1, bval1, rval2, gval2, bval2, rdiff, gdiff, bdiff;
+l_uint32  *datas1, *datas2, *datad, *lines1, *lines2, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixAbsDifference");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, NULL);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, NULL);
+    d = pixGetDepth(pixs1);
+    if (d != pixGetDepth(pixs2))
+        return (PIX *)ERROR_PTR("src1 and src2 depths unequal", procName, NULL);
+    if (d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("depths not in {8, 16, 32}", procName, NULL);
+
+    pixGetDimensions(pixs1, &w, &h, NULL);
+    pixGetDimensions(pixs2, &w2, &h2, NULL);
+    w = L_MIN(w, w2);
+    h = L_MIN(h, h2);
+    if ((pixd = pixCreate(w, h, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs1);
+    datas1 = pixGetData(pixs1);
+    datas2 = pixGetData(pixs2);
+    datad = pixGetData(pixd);
+    wpls1 = pixGetWpl(pixs1);
+    wpls2 = pixGetWpl(pixs2);
+    wpld = pixGetWpl(pixd);
+    if (d == 8) {
+        for (i = 0; i < h; i++) {
+            lines1 = datas1 + i * wpls1;
+            lines2 = datas2 + i * wpls2;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                val1 = GET_DATA_BYTE(lines1, j);
+                val2 = GET_DATA_BYTE(lines2, j);
+                diff = L_ABS(val1 - val2);
+                SET_DATA_BYTE(lined, j, diff);
+            }
+        }
+    } else if (d == 16) {
+        for (i = 0; i < h; i++) {
+            lines1 = datas1 + i * wpls1;
+            lines2 = datas2 + i * wpls2;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                val1 = GET_DATA_TWO_BYTES(lines1, j);
+                val2 = GET_DATA_TWO_BYTES(lines2, j);
+                diff = L_ABS(val1 - val2);
+                SET_DATA_TWO_BYTES(lined, j, diff);
+            }
+        }
+    } else {  /* d == 32 */
+        for (i = 0; i < h; i++) {
+            lines1 = datas1 + i * wpls1;
+            lines2 = datas2 + i * wpls2;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                extractRGBValues(lines1[j], &rval1, &gval1, &bval1);
+                extractRGBValues(lines2[j], &rval2, &gval2, &bval2);
+                rdiff = L_ABS(rval1 - rval2);
+                gdiff = L_ABS(gval1 - gval2);
+                bdiff = L_ABS(bval1 - bval2);
+                composeRGBPixel(rdiff, gdiff, bdiff, lined + j);
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                           Sum of color images                         *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixAddRGB()
+ *
+ *      Input:  pixs1, pixs2  (32 bpp RGB, or colormapped)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Clips computation to the minimum size, aligning the UL corners.
+ *      (2) Removes any colormap to RGB, and ignores the LSB of each
+ *          pixel word (the alpha channel).
+ *      (3) Adds each component value, pixelwise, clipping to 255.
+ *      (4) This is useful to combine two images where most of the
+ *          pixels are essentially black, such as in pixPerceptualDiff().
+ */
+PIX *
+pixAddRGB(PIX  *pixs1,
+          PIX  *pixs2)
+{
+l_int32    i, j, w, h, d, w2, h2, d2, wplc1, wplc2, wpld;
+l_int32    rval1, gval1, bval1, rval2, gval2, bval2, rval, gval, bval;
+l_uint32  *datac1, *datac2, *datad, *linec1, *linec2, *lined;
+PIX       *pixc1, *pixc2, *pixd;
+
+    PROCNAME("pixAddRGB");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, NULL);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, NULL);
+    pixGetDimensions(pixs1, &w, &h, &d);
+    pixGetDimensions(pixs2, &w2, &h2, &d2);
+    if (!pixGetColormap(pixs1) && d != 32)
+        return (PIX *)ERROR_PTR("pixs1 not cmapped or rgb", procName, NULL);
+    if (!pixGetColormap(pixs2) && d2 != 32)
+        return (PIX *)ERROR_PTR("pixs2 not cmapped or rgb", procName, NULL);
+    if (pixGetColormap(pixs1))
+        pixc1 = pixRemoveColormap(pixs1, REMOVE_CMAP_TO_FULL_COLOR);
+    else
+        pixc1 = pixClone(pixs1);
+    if (pixGetColormap(pixs2))
+        pixc2 = pixRemoveColormap(pixs2, REMOVE_CMAP_TO_FULL_COLOR);
+    else
+        pixc2 = pixClone(pixs2);
+
+    w = L_MIN(w, w2);
+    h = L_MIN(h, h2);
+    pixd = pixCreate(w, h, 32);
+    pixCopyResolution(pixd, pixs1);
+    datac1 = pixGetData(pixc1);
+    datac2 = pixGetData(pixc2);
+    datad = pixGetData(pixd);
+    wplc1 = pixGetWpl(pixc1);
+    wplc2 = pixGetWpl(pixc2);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        linec1 = datac1 + i * wplc1;
+        linec2 = datac2 + i * wplc2;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(linec1[j], &rval1, &gval1, &bval1);
+            extractRGBValues(linec2[j], &rval2, &gval2, &bval2);
+            rval = L_MIN(255, rval1 + rval2);
+            gval = L_MIN(255, gval1 + gval2);
+            bval = L_MIN(255, bval1 + bval2);
+            composeRGBPixel(rval, gval, bval, lined + j);
+        }
+    }
+
+    pixDestroy(&pixc1);
+    pixDestroy(&pixc2);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *             Two-image min and max operations (8 and 16 bpp)           *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixMinOrMax()
+ *
+ *      Input:  pixd  (<optional> destination: this can be null,
+ *                     equal to pixs1, or different from pixs1)
+ *              pixs1 (can be == to pixd)
+ *              pixs2
+ *              type (L_CHOOSE_MIN, L_CHOOSE_MAX)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) This gives the min or max of two images, component-wise.
+ *      (2) The depth can be 8 or 16 bpp for 1 component, and 32 bpp
+ *          for a 3 component image.  For 32 bpp, ignore the LSB
+ *          of each word (the alpha channel)
+ *      (3) There are 3 cases:
+ *          -  if pixd == null,   Min(src1, src2) --> new pixd
+ *          -  if pixd == pixs1,  Min(src1, src2) --> src1  (in-place)
+ *          -  if pixd != pixs1,  Min(src1, src2) --> input pixd
+ */
+PIX *
+pixMinOrMax(PIX     *pixd,
+            PIX     *pixs1,
+            PIX     *pixs2,
+            l_int32  type)
+{
+l_int32    d, ws, hs, w, h, wpls, wpld, i, j, vals, vald, val;
+l_int32    rval1, gval1, bval1, rval2, gval2, bval2, rval, gval, bval;
+l_uint32  *datas, *datad, *lines, *lined;
+
+    PROCNAME("pixMinOrMax");
+
+    if (!pixs1)
+        return (PIX *)ERROR_PTR("pixs1 not defined", procName, pixd);
+    if (!pixs2)
+        return (PIX *)ERROR_PTR("pixs2 not defined", procName, pixd);
+    if (pixs1 == pixs2)
+        return (PIX *)ERROR_PTR("pixs1 and pixs2 must differ", procName, pixd);
+    if (type != L_CHOOSE_MIN && type != L_CHOOSE_MAX)
+        return (PIX *)ERROR_PTR("invalid type", procName, pixd);
+    d = pixGetDepth(pixs1);
+    if (pixGetDepth(pixs2) != d)
+        return (PIX *)ERROR_PTR("depths unequal", procName, pixd);
+    if (d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("depth not 8, 16 or 32 bpp", procName, pixd);
+
+    if (pixs1 != pixd)
+        pixd = pixCopy(pixd, pixs1);
+
+    pixGetDimensions(pixs2, &ws, &hs, NULL);
+    pixGetDimensions(pixd, &w, &h, NULL);
+    w = L_MIN(w, ws);
+    h = L_MIN(h, hs);
+    datas = pixGetData(pixs2);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs2);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        if (d == 8) {
+            for (j = 0; j < w; j++) {
+                vals = GET_DATA_BYTE(lines, j);
+                vald = GET_DATA_BYTE(lined, j);
+                if (type == L_CHOOSE_MIN)
+                    val = L_MIN(vals, vald);
+                else  /* type == L_CHOOSE_MAX */
+                    val = L_MAX(vals, vald);
+                SET_DATA_BYTE(lined, j, val);
+            }
+        } else if (d == 16) {
+            for (j = 0; j < w; j++) {
+                vals = GET_DATA_TWO_BYTES(lines, j);
+                vald = GET_DATA_TWO_BYTES(lined, j);
+                if (type == L_CHOOSE_MIN)
+                    val = L_MIN(vals, vald);
+                else  /* type == L_CHOOSE_MAX */
+                    val = L_MAX(vals, vald);
+                SET_DATA_TWO_BYTES(lined, j, val);
+            }
+        } else {  /* d == 32 */
+            for (j = 0; j < w; j++) {
+                extractRGBValues(lines[j], &rval1, &gval1, &bval1);
+                extractRGBValues(lined[j], &rval2, &gval2, &bval2);
+                if (type == L_CHOOSE_MIN) {
+                    rval = L_MIN(rval1, rval2);
+                    gval = L_MIN(gval1, gval2);
+                    bval = L_MIN(bval1, bval2);
+                } else {  /* type == L_CHOOSE_MAX */
+                    rval = L_MAX(rval1, rval2);
+                    gval = L_MAX(gval1, gval2);
+                    bval = L_MAX(bval1, bval2);
+                }
+                composeRGBPixel(rval, gval, bval, lined + j);
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *            Scale for maximum dynamic range in 8 bpp image             *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixMaxDynamicRange()
+ *
+ *      Input:  pixs  (4, 8, 16 or 32 bpp source)
+ *              type  (L_LINEAR_SCALE or L_LOG_SCALE)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) Scales pixel values to fit maximally within the dest 8 bpp pixd
+ *      (2) Uses a LUT for log scaling
+ */
+PIX *
+pixMaxDynamicRange(PIX     *pixs,
+                   l_int32  type)
+{
+l_uint8     dval;
+l_int32     i, j, w, h, d, wpls, wpld, max, sval;
+l_uint32   *datas, *datad;
+l_uint32    word;
+l_uint32   *lines, *lined;
+l_float32   factor;
+l_float32  *tab;
+PIX        *pixd;
+
+    PROCNAME("pixMaxDynamicRange");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 4 && d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not in {4,8,16,32} bpp", procName, NULL);
+    if (type != L_LINEAR_SCALE && type != L_LOG_SCALE)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+        /* Get max */
+    max = 0;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        for (j = 0; j < wpls; j++) {
+            word = *(lines + j);
+            if (d == 4) {
+                max = L_MAX(max, word >> 28);
+                max = L_MAX(max, (word >> 24) & 0xf);
+                max = L_MAX(max, (word >> 20) & 0xf);
+                max = L_MAX(max, (word >> 16) & 0xf);
+                max = L_MAX(max, (word >> 12) & 0xf);
+                max = L_MAX(max, (word >> 8) & 0xf);
+                max = L_MAX(max, (word >> 4) & 0xf);
+                max = L_MAX(max, word & 0xf);
+            } else if (d == 8) {
+                max = L_MAX(max, word >> 24);
+                max = L_MAX(max, (word >> 16) & 0xff);
+                max = L_MAX(max, (word >> 8) & 0xff);
+                max = L_MAX(max, word & 0xff);
+            } else if (d == 16) {
+                max = L_MAX(max, word >> 16);
+                max = L_MAX(max, word & 0xffff);
+            } else {  /* d == 32 */
+                max = L_MAX(max, word);
+            }
+        }
+    }
+
+        /* Map to the full dynamic range of 8 bpp output */
+    if (d == 4) {
+        if (type == L_LINEAR_SCALE) {
+            factor = 255. / (l_float32)max;
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    sval = GET_DATA_QBIT(lines, j);
+                    dval = (l_uint8)(factor * (l_float32)sval + 0.5);
+                    SET_DATA_QBIT(lined, j, dval);
+                }
+            }
+        } else {  /* type == L_LOG_SCALE) */
+            tab = makeLogBase2Tab();
+            factor = 255. / getLogBase2(max, tab);
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    sval = GET_DATA_QBIT(lines, j);
+                    dval = (l_uint8)(factor * getLogBase2(sval, tab) + 0.5);
+                    SET_DATA_BYTE(lined, j, dval);
+                }
+            }
+            LEPT_FREE(tab);
+        }
+    } else if (d == 8) {
+        if (type == L_LINEAR_SCALE) {
+            factor = 255. / (l_float32)max;
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    sval = GET_DATA_BYTE(lines, j);
+                    dval = (l_uint8)(factor * (l_float32)sval + 0.5);
+                    SET_DATA_BYTE(lined, j, dval);
+                }
+            }
+        } else {  /* type == L_LOG_SCALE) */
+            tab = makeLogBase2Tab();
+            factor = 255. / getLogBase2(max, tab);
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    sval = GET_DATA_BYTE(lines, j);
+                    dval = (l_uint8)(factor * getLogBase2(sval, tab) + 0.5);
+                    SET_DATA_BYTE(lined, j, dval);
+                }
+            }
+            LEPT_FREE(tab);
+        }
+    } else if (d == 16) {
+        if (type == L_LINEAR_SCALE) {
+            factor = 255. / (l_float32)max;
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    sval = GET_DATA_TWO_BYTES(lines, j);
+                    dval = (l_uint8)(factor * (l_float32)sval + 0.5);
+                    SET_DATA_BYTE(lined, j, dval);
+                }
+            }
+        } else {  /* type == L_LOG_SCALE) */
+            tab = makeLogBase2Tab();
+            factor = 255. / getLogBase2(max, tab);
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    sval = GET_DATA_TWO_BYTES(lines, j);
+                    dval = (l_uint8)(factor * getLogBase2(sval, tab) + 0.5);
+                    SET_DATA_BYTE(lined, j, dval);
+                }
+            }
+            LEPT_FREE(tab);
+        }
+    } else {  /* d == 32 */
+        if (type == L_LINEAR_SCALE) {
+            factor = 255. / (l_float32)max;
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    sval = lines[j];
+                    dval = (l_uint8)(factor * (l_float32)sval + 0.5);
+                    SET_DATA_BYTE(lined, j, dval);
+                }
+            }
+        } else {  /* type == L_LOG_SCALE) */
+            tab = makeLogBase2Tab();
+            factor = 255. / getLogBase2(max, tab);
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                for (j = 0; j < w; j++) {
+                    sval = lines[j];
+                    dval = (l_uint8)(factor * getLogBase2(sval, tab) + 0.5);
+                    SET_DATA_BYTE(lined, j, dval);
+                }
+            }
+            LEPT_FREE(tab);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                            Log base2 lookup                           *
+ *-----------------------------------------------------------------------*/
+/*
+ *  makeLogBase2Tab()
+ *
+ *      Input: void
+ *      Return: table (giving the log[base 2] of val)
+ */
+l_float32 *
+makeLogBase2Tab(void)
+{
+l_int32     i;
+l_float32   log2;
+l_float32  *tab;
+
+    PROCNAME("makeLogBase2Tab");
+
+    if ((tab = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32))) == NULL)
+        return (l_float32 *)ERROR_PTR("tab not made", procName, NULL);
+
+    log2 = (l_float32)log((l_float32)2);
+    for (i = 0; i < 256; i++)
+        tab[i] = (l_float32)log((l_float32)i) / log2;
+
+    return tab;
+}
+
+
+/*
+ * getLogBase2()
+ *
+ *     Input:  val
+ *             logtab (256-entry table of logs)
+ *     Return: logdist, or 0 on error
+ */
+l_float32
+getLogBase2(l_int32     val,
+            l_float32  *logtab)
+{
+    PROCNAME("getLogBase2");
+
+    if (!logtab)
+        return ERROR_INT("logtab not defined", procName, 0);
+
+    if (val < 0x100)
+        return logtab[val];
+    else if (val < 0x10000)
+        return 8.0 + logtab[val >> 8];
+    else if (val < 0x1000000)
+        return 16.0 + logtab[val >> 16];
+    else
+        return 24.0 + logtab[val >> 24];
+}
diff --git a/src/pixcomp.c b/src/pixcomp.c
new file mode 100644 (file)
index 0000000..ccaea03
--- /dev/null
@@ -0,0 +1,1930 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   pixcomp.c
+ *
+ *      Pixcomp creation and destruction
+ *           PIXC     *pixcompCreateFromPix()
+ *           PIXC     *pixcompCreateFromString()
+ *           PIXC     *pixcompCreateFromFile()
+ *           void      pixcompDestroy()
+
+ *      Pixcomp accessors
+ *           l_int32   pixcompGetDimensions()
+ *           l_int32   pixcompDetermineFormat()
+ *
+ *      Pixcomp conversion to Pix
+ *           PIX      *pixCreateFromPixcomp()
+ *
+ *      Pixacomp creation and destruction
+ *           PIXAC    *pixacompCreate()
+ *           PIXAC    *pixacompCreateWithInit()
+ *           PIXAC    *pixacompCreateFromPixa()
+ *           PIXAC    *pixacompCreateFromFiles()
+ *           PIXAC    *pixacompCreateFromSA()
+ *           void      pixacompDestroy()
+ *
+ *      Pixacomp addition/replacement
+ *           l_int32   pixacompAddPix()
+ *           l_int32   pixacompAddPixcomp()
+ *           static l_int32  pixacompExtendArray()
+ *           l_int32   pixacompReplacePix()
+ *           l_int32   pixacompReplacePixcomp()
+ *           l_int32   pixacompAddBox()
+ *
+ *      Pixacomp accessors
+ *           l_int32   pixacompGetCount()
+ *           PIXC     *pixacompGetPixcomp()
+ *           PIX      *pixacompGetPix()
+ *           l_int32   pixacompGetPixDimensions()
+ *           BOXA     *pixacompGetBoxa()
+ *           l_int32   pixacompGetBoxaCount()
+ *           BOX      *pixacompGetBox()
+ *           l_int32   pixacompGetBoxGeometry()
+ *           l_int32   pixacompGetOffset()
+ *           l_int32   pixacompSetOffset()
+ *
+ *      Pixacomp conversion to Pixa
+ *           PIXA     *pixaCreateFromPixacomp()
+ *
+ *      Pixacomp serialized I/O
+ *           PIXAC    *pixacompRead()
+ *           PIXAC    *pixacompReadStream()
+ *           l_int32   pixacompWrite()
+ *           l_int32   pixacompWriteStream()
+ *
+ *      Conversion to pdf
+ *           l_int32   pixacompConvertToPdf()
+ *           l_int32   pixacompConvertToPdfData()
+ *
+ *      Output for debugging
+ *           l_int32   pixacompWriteStreamInfo()
+ *           l_int32   pixcompWriteStreamInfo()
+ *           PIX      *pixacompDisplayTiledAndScaled()
+ *
+ *   The Pixacomp is an array of Pixcomp, where each Pixcomp is a compressed
+ *   string of the image.  We don't use reference counting here.
+ *   The basic application is to allow a large array of highly
+ *   compressible images to reside in memory.  We purposely don't
+ *   reuse the Pixa for this, to avoid confusion and programming errors.
+ *
+ *   Three compression formats are used: g4, png and jpeg.
+ *   The compression type can be either specified or defaulted.
+ *   If specified and it is not possible to compress (for example,
+ *   you specify a jpeg on a 1 bpp image or one with a colormap),
+ *   the compression type defaults to png.
+ *
+ *   The serialized version of the Pixacomp is similar to that for
+ *   a Pixa, except that each Pixcomp can be compressed by one of
+ *   tiffg4, png, or jpeg.  Unlike serialization of the Pixa,
+ *   serialization of the Pixacomp does not require any imaging
+ *   libraries because it simply reads and writes the compressed data.
+ *
+ *   There are two modes of use in accumulating images:
+ *     (1) addition to the end of the array
+ *     (2) random insertion (replacement) into the array
+ *
+ *   In use, we assume that the array is fully populated up to the
+ *   index value (n - 1), where n is the value of the pixcomp field n.
+ *   Addition can only be made to the end of the fully populated array,
+ *   at the index value n.  Insertion can be made randomly, but again
+ *   only within the array of pixcomps; i.e., within the set of
+ *   indices {0 .... n-1}.  The functions are pixacompReplacePix()
+ *   and pixacompReplacePixcomp(), and they destroy the existing pixcomp.
+ *
+ *   For addition to the end of the array, use pixacompCreate(), which
+ *   generates an initially empty array of pixcomps.  For random
+ *   insertion and replacement of pixcomp into a pixacomp,
+ *   initialize a fully populated array using pixacompCreateWithInit().
+ *
+ *   The offset field allows you to use an offset-based index to
+ *   access the 0-based ptr array in the pixacomp.  This would typically
+ *   be used to map the pixacomp array index to a page number, or v.v.
+ *   By default, the offset is 0.  For example, suppose you have 50 images,
+ *   corresponding to page numbers 10 - 59.  Then you would use
+ *      pixac = pixacompCreateWithInit(50, 10, ...);
+ *   This would allocate an array of 50 pixcomps, but if you asked for
+ *   the pix at index 10, using pixacompGetPix(pixac, 10), it would
+ *   apply the offset internally, returning the pix at index 0 in the array.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;   /* n'import quoi */
+
+    /* These two globals are defined in writefile.c */
+extern l_int32 NumImageFileFormatExtensions;
+extern const char *ImageFileFormatExtensions[];
+
+    /* Static function */
+static l_int32 pixacompExtendArray(PIXAC *pixac);
+
+
+/*---------------------------------------------------------------------*
+ *                  Pixcomp creation and destruction                   *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixcompCreateFromPix()
+ *
+ *      Input:  pix
+ *              comptype (IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG)
+ *      Return: pixc, or null on error
+ *
+ *  Notes:
+ *      (1) Use @comptype == IFF_DEFAULT to have the compression
+ *          type automatically determined.
+ */
+PIXC *
+pixcompCreateFromPix(PIX     *pix,
+                     l_int32  comptype)
+{
+size_t    size;
+char     *text;
+l_int32   ret, format;
+l_uint8  *data;
+PIXC     *pixc;
+
+    PROCNAME("pixcompCreateFromPix");
+
+    if (!pix)
+        return (PIXC *)ERROR_PTR("pix not defined", procName, NULL);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return (PIXC *)ERROR_PTR("invalid comptype", procName, NULL);
+
+    if ((pixc = (PIXC *)LEPT_CALLOC(1, sizeof(PIXC))) == NULL)
+        return (PIXC *)ERROR_PTR("pixc not made", procName, NULL);
+    pixGetDimensions(pix, &pixc->w, &pixc->h, &pixc->d);
+    pixGetResolution(pix, &pixc->xres, &pixc->yres);
+    if (pixGetColormap(pix))
+        pixc->cmapflag = 1;
+    if ((text = pixGetText(pix)) != NULL)
+        pixc->text = stringNew(text);
+
+    pixcompDetermineFormat(comptype, pixc->d, pixc->cmapflag, &format);
+    pixc->comptype = format;
+    ret = pixWriteMem(&data, &size, pix, format);
+    if (ret) {
+        L_ERROR("write to memory failed\n", procName);
+        pixcompDestroy(&pixc);
+        return NULL;
+    }
+    pixc->data = data;
+    pixc->size = size;
+
+    return pixc;
+}
+
+
+/*!
+ *  pixcompCreateFromString()
+ *
+ *      Input:  data (compressed string)
+ *              size (number of bytes)
+ *              copyflag (L_INSERT or L_COPY)
+ *      Return: pixc, or null on error
+ *
+ *  Notes:
+ *      (1) This works when the compressed string is png, jpeg or tiffg4.
+ *      (2) The copyflag determines if the data in the new Pixcomp is
+ *          a copy of the input data.
+ */
+PIXC *
+pixcompCreateFromString(l_uint8  *data,
+                        size_t    size,
+                        l_int32   copyflag)
+{
+l_int32  format, w, h, d, bps, spp, iscmap;
+PIXC    *pixc;
+
+    PROCNAME("pixcompCreateFromString");
+
+    if (!data)
+        return (PIXC *)ERROR_PTR("data not defined", procName, NULL);
+    if (copyflag != L_INSERT && copyflag != L_COPY)
+        return (PIXC *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    if (pixReadHeaderMem(data, size, &format, &w, &h, &bps, &spp, &iscmap) == 1)
+        return (PIXC *)ERROR_PTR("header data not read", procName, NULL);
+    if ((pixc = (PIXC *)LEPT_CALLOC(1, sizeof(PIXC))) == NULL)
+        return (PIXC *)ERROR_PTR("pixc not made", procName, NULL);
+    d = (spp == 3) ? 32 : bps * spp;
+    pixc->w = w;
+    pixc->h = h;
+    pixc->d = d;
+    pixc->comptype = format;
+    pixc->cmapflag = iscmap;
+    if (copyflag == L_INSERT)
+        pixc->data = data;
+    else
+        pixc->data = l_binaryCopy(data, size);
+    pixc->size = size;
+    return pixc;
+}
+
+
+/*!
+ *  pixcompCreateFromFile()
+ *
+ *      Input:  filename
+ *              comptype (IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG)
+ *      Return: pixc, or null on error
+ *
+ *  Notes:
+ *      (1) Use @comptype == IFF_DEFAULT to have the compression
+ *          type automatically determined.
+ *      (2) If the comptype is invalid for this file, the default will
+ *          be substituted.
+ */
+PIXC *
+pixcompCreateFromFile(const char  *filename,
+                      l_int32      comptype)
+{
+l_int32   format;
+size_t    nbytes;
+l_uint8  *data;
+PIX      *pix;
+PIXC     *pixc;
+
+    PROCNAME("pixcompCreateFromFile");
+
+    if (!filename)
+        return (PIXC *)ERROR_PTR("filename not defined", procName, NULL);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return (PIXC *)ERROR_PTR("invalid comptype", procName, NULL);
+
+    findFileFormat(filename, &format);
+    if (format == IFF_UNKNOWN) {
+        L_ERROR("unreadable file: %s\n", procName, filename);
+        return NULL;
+    }
+
+        /* Can we accept the encoded file directly?  Remember that
+         * png is the "universal" compression type, so if requested
+         * it takes precedence.  Otherwise, if the file is already
+         * compressed in g4 or jpeg, just accept the string. */
+    if ((format == IFF_TIFF_G4 && comptype != IFF_PNG) ||
+        (format == IFF_JFIF_JPEG && comptype != IFF_PNG))
+        comptype = format;
+    if (comptype != IFF_DEFAULT && comptype == format) {
+        data = l_binaryRead(filename, &nbytes);
+        if ((pixc = pixcompCreateFromString(data, nbytes, L_INSERT)) == NULL) {
+            LEPT_FREE(data);
+            return (PIXC *)ERROR_PTR("pixc not made (string)", procName, NULL);
+        }
+        return pixc;
+    }
+
+        /* Need to recompress in the default format */
+    if ((pix = pixRead(filename)) == NULL)
+        return (PIXC *)ERROR_PTR("pix not read", procName, NULL);
+    if ((pixc = pixcompCreateFromPix(pix, comptype)) == NULL) {
+        pixDestroy(&pix);
+        return (PIXC *)ERROR_PTR("pixc not made", procName, NULL);
+    }
+    pixDestroy(&pix);
+    return pixc;
+}
+
+
+/*!
+ *  pixcompDestroy()
+ *
+ *      Input:  &pixc <will be nulled>
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Always nulls the input ptr.
+ */
+void
+pixcompDestroy(PIXC  **ppixc)
+{
+PIXC  *pixc;
+
+    PROCNAME("pixcompDestroy");
+
+    if (!ppixc) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((pixc = *ppixc) == NULL)
+        return;
+
+    LEPT_FREE(pixc->data);
+    if (pixc->text)
+        LEPT_FREE(pixc->text);
+    LEPT_FREE(pixc);
+    *ppixc = NULL;
+    return;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                           Pixcomp accessors                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixcompGetDimensions()
+ *
+ *      Input:  pixc
+ *              &w, &h, &d (<optional return>)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixcompGetDimensions(PIXC     *pixc,
+                     l_int32  *pw,
+                     l_int32  *ph,
+                     l_int32  *pd)
+{
+    PROCNAME("pixcompGetDimensions");
+
+    if (!pixc)
+        return ERROR_INT("pixc not defined", procName, 1);
+    if (pw) *pw = pixc->w;
+    if (ph) *ph = pixc->h;
+    if (pd) *pd = pixc->d;
+    return 0;
+}
+
+
+/*!
+ *  pixcompDetermineFormat()
+ *
+ *      Input:  comptype (IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG)
+ *              d (pix depth)
+ *              cmapflag (1 if pix to be compressed as a colormap; 0 otherwise)
+ *              &format (return IFF_TIFF, IFF_PNG or IFF_JFIF_JPEG)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This determines the best format for a pix, given both
+ *          the request (@comptype) and the image characteristics.
+ *      (2) If @comptype == IFF_DEFAULT, this does not necessarily result
+ *          in png encoding.  Instead, it returns one of the three formats
+ *          that is both valid and most likely to give best compression.
+ *      (3) If the pix cannot be compressed by the input value of
+ *          @comptype, this selects IFF_PNG, which can compress all pix.
+ */
+l_int32
+pixcompDetermineFormat(l_int32   comptype,
+                       l_int32   d,
+                       l_int32   cmapflag,
+                       l_int32  *pformat)
+{
+
+    PROCNAME("pixcompDetermineFormat");
+
+    if (!pformat)
+        return ERROR_INT("&format not defined", procName, 1);
+    *pformat = IFF_PNG;  /* init value and default */
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return ERROR_INT("invalid comptype", procName, 1);
+
+    if (comptype == IFF_DEFAULT) {
+        if (d == 1)
+            *pformat = IFF_TIFF_G4;
+        else if (d == 16)
+            *pformat = IFF_PNG;
+        else if (d >= 8 && !cmapflag)
+            *pformat = IFF_JFIF_JPEG;
+    } else if (comptype == IFF_TIFF_G4 && d == 1) {
+        *pformat = IFF_TIFF_G4;
+    } else if (comptype == IFF_JFIF_JPEG && d >= 8 && !cmapflag) {
+        *pformat = IFF_JFIF_JPEG;
+    }
+
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                      Pixcomp conversion to Pix                      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixCreateFromPixcomp()
+ *
+ *      Input:  pixc
+ *      Return: pix, or null on error
+ */
+PIX *
+pixCreateFromPixcomp(PIXC  *pixc)
+{
+l_int32  w, h, d, cmapinpix, format;
+PIX     *pix;
+
+    PROCNAME("pixCreateFromPixcomp");
+
+    if (!pixc)
+        return (PIX *)ERROR_PTR("pixc not defined", procName, NULL);
+
+    if ((pix = pixReadMem(pixc->data, pixc->size)) == NULL)
+        return (PIX *)ERROR_PTR("pix not read", procName, NULL);
+    pixSetResolution(pix, pixc->xres, pixc->yres);
+    if (pixc->text)
+        pixSetText(pix, pixc->text);
+
+        /* Check fields for consistency */
+    pixGetDimensions(pix, &w, &h, &d);
+    if (pixc->w != w) {
+        L_INFO("pix width %d != pixc width %d\n", procName, w, pixc->w);
+        L_ERROR("pix width %d != pixc width\n", procName, w);
+    }
+    if (pixc->h != h)
+        L_ERROR("pix height %d != pixc height\n", procName, h);
+    if (pixc->d != d) {
+        if (pixc->d == 16)  /* we strip 16 --> 8 bpp by default */
+            L_WARNING("pix depth %d != pixc depth 16\n", procName, d);
+        else
+            L_ERROR("pix depth %d != pixc depth\n", procName, d);
+    }
+    cmapinpix = (pixGetColormap(pix) != NULL);
+    if ((cmapinpix && !pixc->cmapflag) || (!cmapinpix && pixc->cmapflag))
+        L_ERROR("pix cmap flag inconsistent\n", procName);
+    format = pixGetInputFormat(pix);
+    if (format != pixc->comptype) {
+        L_ERROR("pix comptype %d not equal to pixc comptype\n",
+                    procName, format);
+    }
+
+    return pix;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                Pixacomp creation and destruction                    *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixacompCreate()
+ *
+ *      Input:  n  (initial number of ptrs)
+ *      Return: pixac, or null on error
+ */
+PIXAC *
+pixacompCreate(l_int32  n)
+{
+PIXAC  *pixac;
+
+    PROCNAME("pixacompCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((pixac = (PIXAC *)LEPT_CALLOC(1, sizeof(PIXAC))) == NULL)
+        return (PIXAC *)ERROR_PTR("pixac not made", procName, NULL);
+    pixac->n = 0;
+    pixac->nalloc = n;
+    pixac->offset = 0;
+
+    if ((pixac->pixc = (PIXC **)LEPT_CALLOC(n, sizeof(PIXC *))) == NULL)
+        return (PIXAC *)ERROR_PTR("pixc ptrs not made", procName, NULL);
+    if ((pixac->boxa = boxaCreate(n)) == NULL)
+        return (PIXAC *)ERROR_PTR("boxa not made", procName, NULL);
+
+    return pixac;
+}
+
+
+/*!
+ *  pixacompCreateWithInit()
+ *
+ *      Input:  n  (initial number of ptrs)
+ *              offset (difference: accessor index - pixacomp array index)
+ *              pix (<optional> initialize each ptr in pixacomp to this pix;
+ *                   can be NULL)
+ *              comptype (IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG)
+ *      Return: pixac, or null on error
+ *
+ *  Notes:
+ *      (1) Initializes a pixacomp to be fully populated with @pix,
+ *          compressed using @comptype.  If @pix == NULL, @comptype
+ *          is ignored.
+ *      (2) Typically, the array is initialized with a tiny pix.
+ *          This is most easily done by setting @pix == NULL, causing
+ *          initialization of each array element with a tiny placeholder
+ *          pix (w = h = d = 1), using comptype = IFF_TIFF_G4 .
+ *      (3) Example usage:
+ *            // Generate pixacomp for pages 30 - 49.  This has an array
+ *            // size of 20 and the page number offset is 30.
+ *            PixaComp *pixac = pixacompCreateWithInit(20, 30, NULL,
+ *                                                     IFF_TIFF_G4);
+ *            // Now insert png-compressed images into the initialized array
+ *            for (pageno = 30; pageno < 50; pageno++) {
+ *                Pix *pixt = ...   // derived from image[pageno]
+ *                if (pixt)
+ *                    pixacompReplacePix(pixac, pageno, pixt, IFF_PNG);
+ *                pixDestroy(&pixt);
+ *            }
+ *          The result is a pixac with 20 compressed strings, and with
+ *          selected pixt replacing the placeholders.
+ *          To extract the image for page 38, which is decompressed
+ *          from element 8 in the array, use:
+ *            pixt = pixacompGetPix(pixac, 38);
+ */
+PIXAC *
+pixacompCreateWithInit(l_int32  n,
+                       l_int32  offset,
+                       PIX     *pix,
+                       l_int32  comptype)
+{
+l_int32  i;
+PIX     *pixt;
+PIXC    *pixc;
+PIXAC   *pixac;
+
+    PROCNAME("pixacompCreateWithInit");
+
+    if (n <= 0)
+        return (PIXAC *)ERROR_PTR("n must be > 0", procName, NULL);
+    if (pix) {
+        if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+            comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+            return (PIXAC *)ERROR_PTR("invalid comptype", procName, NULL);
+    } else {
+        comptype = IFF_TIFF_G4;
+    }
+    if (offset < 0) {
+        L_WARNING("offset < 0; setting to 0\n", procName);
+        offset = 0;
+    }
+
+    if ((pixac = pixacompCreate(n)) == NULL)
+        return (PIXAC *)ERROR_PTR("pixac not made", procName, NULL);
+    pixacompSetOffset(pixac, offset);
+    if (pix)
+        pixt = pixClone(pix);
+    else
+        pixt = pixCreate(1, 1, 1);
+    for (i = 0; i < n; i++) {
+        pixc = pixcompCreateFromPix(pixt, comptype);
+        pixacompAddPixcomp(pixac, pixc);
+    }
+    pixDestroy(&pixt);
+
+    return pixac;
+}
+
+
+/*!
+ *  pixacompCreateFromPixa()
+ *
+ *      Input:  pixa
+ *              comptype (IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG)
+ *              accesstype (L_COPY, L_CLONE, L_COPY_CLONE)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If @format == IFF_DEFAULT, the conversion format for each
+ *          image is chosen automatically.  Otherwise, we use the
+ *          specified format unless it can't be done (e.g., jpeg
+ *          for a 1, 2 or 4 bpp pix, or a pix with a colormap),
+ *          in which case we use the default (assumed best) compression.
+ *      (2) @accesstype is used to extract a boxa from @pixa.
+ */
+PIXAC *
+pixacompCreateFromPixa(PIXA    *pixa,
+                       l_int32  comptype,
+                       l_int32  accesstype)
+{
+l_int32  i, n;
+BOXA    *boxa;
+PIX     *pix;
+PIXAC   *pixac;
+
+    PROCNAME("pixacompCreateFromPixa");
+
+    if (!pixa)
+        return (PIXAC *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return (PIXAC *)ERROR_PTR("invalid comptype", procName, NULL);
+    if (accesstype != L_COPY && accesstype != L_CLONE &&
+        accesstype != L_COPY_CLONE)
+        return (PIXAC *)ERROR_PTR("invalid accesstype", procName, NULL);
+
+    n = pixaGetCount(pixa);
+    if ((pixac = pixacompCreate(n)) == NULL)
+        return (PIXAC *)ERROR_PTR("pixac not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        pixacompAddPix(pixac, pix, comptype);
+        pixDestroy(&pix);
+    }
+    if ((boxa = pixaGetBoxa(pixa, accesstype)) != NULL) {
+        if (pixac->boxa) {
+            boxaDestroy(&pixac->boxa);
+            pixac->boxa = boxa;
+        }
+    }
+
+    return pixac;
+}
+
+
+/*!
+ *  pixacompCreateFromFiles()
+ *
+ *      Input:  dirname
+ *              substr (<optional> substring filter on filenames; can be null)
+ *              comptype (IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG)
+ *      Return: pixac, or null on error
+ *
+ *  Notes:
+ *      (1) @dirname is the full path for the directory.
+ *      (2) @substr is the part of the file name (excluding
+ *          the directory) that is to be matched.  All matching
+ *          filenames are read into the Pixa.  If substr is NULL,
+ *          all filenames are read into the Pixa.
+ *      (3) Use @comptype == IFF_DEFAULT to have the compression
+ *          type automatically determined for each file.
+ *      (4) If the comptype is invalid for a file, the default will
+ *          be substituted.
+ */
+PIXAC *
+pixacompCreateFromFiles(const char  *dirname,
+                        const char  *substr,
+                        l_int32      comptype)
+{
+PIXAC    *pixac;
+SARRAY   *sa;
+
+    PROCNAME("pixacompCreateFromFiles");
+
+    if (!dirname)
+        return (PIXAC *)ERROR_PTR("dirname not defined", procName, NULL);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return (PIXAC *)ERROR_PTR("invalid comptype", procName, NULL);
+
+    if ((sa = getSortedPathnamesInDirectory(dirname, substr, 0, 0)) == NULL)
+        return (PIXAC *)ERROR_PTR("sa not made", procName, NULL);
+    pixac = pixacompCreateFromSA(sa, comptype);
+    sarrayDestroy(&sa);
+    return pixac;
+}
+
+
+/*!
+ *  pixacompCreateFromSA()
+ *
+ *      Input:  sarray (full pathnames for all files)
+ *              comptype (IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG)
+ *      Return: pixac, or null on error
+ *
+ *  Notes:
+ *      (1) Use @comptype == IFF_DEFAULT to have the compression
+ *          type automatically determined for each file.
+ *      (2) If the comptype is invalid for a file, the default will
+ *          be substituted.
+ */
+PIXAC *
+pixacompCreateFromSA(SARRAY  *sa,
+                     l_int32  comptype)
+{
+char    *str;
+l_int32  i, n;
+PIXC    *pixc;
+PIXAC   *pixac;
+
+    PROCNAME("pixacompCreateFromSA");
+
+    if (!sa)
+        return (PIXAC *)ERROR_PTR("sarray not defined", procName, NULL);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return (PIXAC *)ERROR_PTR("invalid comptype", procName, NULL);
+
+    n = sarrayGetCount(sa);
+    pixac = pixacompCreate(n);
+    for (i = 0; i < n; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        if ((pixc = pixcompCreateFromFile(str, comptype)) == NULL) {
+            L_ERROR("pixc not read from file: %s\n", procName, str);
+            continue;
+        }
+        pixacompAddPixcomp(pixac, pixc);
+    }
+    return pixac;
+}
+
+
+/*!
+ *  pixacompDestroy()
+ *
+ *      Input:  &pixac (<to be nulled>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Always nulls the input ptr.
+ */
+void
+pixacompDestroy(PIXAC  **ppixac)
+{
+l_int32  i;
+PIXAC   *pixac;
+
+    PROCNAME("pixacompDestroy");
+
+    if (ppixac == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+
+    if ((pixac = *ppixac) == NULL)
+        return;
+
+    for (i = 0; i < pixac->n; i++)
+        pixcompDestroy(&pixac->pixc[i]);
+    LEPT_FREE(pixac->pixc);
+    boxaDestroy(&pixac->boxa);
+    LEPT_FREE(pixac);
+
+    *ppixac = NULL;
+    return;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                          Pixacomp addition                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixacompAddPix()
+ *
+ *      Input:  pixac
+ *              pix  (to be added)
+ *              comptype (IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The array is filled up to the (n-1)-th element, and this
+ *          converts the input pix to a pixcomp and adds it at
+ *          the n-th position.
+ */
+l_int32
+pixacompAddPix(PIXAC   *pixac,
+               PIX     *pix,
+               l_int32  comptype)
+{
+l_int32  cmapflag, format;
+PIXC    *pixc;
+
+    PROCNAME("pixacompAddPix");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return ERROR_INT("invalid format", procName, 1);
+
+    cmapflag = pixGetColormap(pix) ? 1 : 0;
+    pixcompDetermineFormat(comptype, pixGetDepth(pix), cmapflag, &format);
+    if ((pixc = pixcompCreateFromPix(pix, format)) == NULL)
+        return ERROR_INT("pixc not made", procName, 1);
+    pixacompAddPixcomp(pixac, pixc);
+    return 0;
+}
+
+
+/*!
+ *  pixacompAddPixcomp()
+ *
+ *      Input:  pixac
+ *              pixc  (to be added by insertion)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixacompAddPixcomp(PIXAC  *pixac,
+                   PIXC   *pixc)
+{
+l_int32  n;
+
+    PROCNAME("pixacompAddPixcomp");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+    if (!pixc)
+        return ERROR_INT("pixc not defined", procName, 1);
+
+    n = pixac->n;
+    if (n >= pixac->nalloc)
+        pixacompExtendArray(pixac);
+    pixac->pixc[n] = pixc;
+    pixac->n++;
+
+    return 0;
+}
+
+
+/*!
+ *  pixacompExtendArray()
+ *
+ *      Input:  pixac
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) We extend the boxa array simultaneously.  This is
+ *          necessary in case we are NOT adding boxes simultaneously
+ *          with adding pixc.  We always want the sizes of the
+ *          pixac and boxa ptr arrays to be equal.
+ */
+static l_int32
+pixacompExtendArray(PIXAC  *pixac)
+{
+    PROCNAME("pixacompExtendArray");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+
+    if ((pixac->pixc = (PIXC **)reallocNew((void **)&pixac->pixc,
+                            sizeof(PIXC *) * pixac->nalloc,
+                            2 * sizeof(PIXC *) * pixac->nalloc)) == NULL)
+        return ERROR_INT("new ptr array not returned", procName, 1);
+    pixac->nalloc = 2 * pixac->nalloc;
+    boxaExtendArray(pixac->boxa);
+    return 0;
+}
+
+
+/*!
+ *  pixacompReplacePix()
+ *
+ *      Input:  pixac
+ *              index (caller's view of index within pixac; includes offset)
+ *              pix  (owned by the caller)
+ *              comptype (IFF_DEFAULT, IFF_TIFF_G4, IFF_PNG, IFF_JFIF_JPEG)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The @index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ *      (2) The input @pix is converted to a pixc, which is then inserted
+ *          into the pixac.
+ */
+l_int32
+pixacompReplacePix(PIXAC   *pixac,
+                   l_int32  index,
+                   PIX     *pix,
+                   l_int32  comptype)
+{
+l_int32  n, aindex;
+PIXC    *pixc;
+
+    PROCNAME("pixacompReplacePix");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+    n = pixacompGetCount(pixac);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= n)
+        return ERROR_INT("array index out of bounds", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (comptype != IFF_DEFAULT && comptype != IFF_TIFF_G4 &&
+        comptype != IFF_PNG && comptype != IFF_JFIF_JPEG)
+        return ERROR_INT("invalid format", procName, 1);
+
+    pixc = pixcompCreateFromPix(pix, comptype);
+    pixacompReplacePixcomp(pixac, index, pixc);
+    return 0;
+}
+
+
+/*!
+ *  pixacompReplacePixcomp()
+ *
+ *      Input:  pixac
+ *              index (caller's view of index within pixac; includes offset)
+ *              pixc  (to replace existing one, which is destroyed)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The @index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ *      (2) The inserted @pixc is now owned by the pixac.  The caller
+ *          must not destroy it.
+ */
+l_int32
+pixacompReplacePixcomp(PIXAC   *pixac,
+                       l_int32  index,
+                       PIXC    *pixc)
+{
+l_int32  n, aindex;
+PIXC    *pixct;
+
+    PROCNAME("pixacompReplacePixcomp");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+    n = pixacompGetCount(pixac);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= n)
+        return ERROR_INT("array index out of bounds", procName, 1);
+    if (!pixc)
+        return ERROR_INT("pixc not defined", procName, 1);
+
+    pixct = pixacompGetPixcomp(pixac, index);  /* use @index */
+    pixcompDestroy(&pixct);
+    pixac->pixc[aindex] = pixc;  /* replace; use array index */
+
+    return 0;
+}
+
+
+/*!
+ *  pixacompAddBox()
+ *
+ *      Input:  pixac
+ *              box
+ *              copyflag (L_INSERT, L_COPY)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixacompAddBox(PIXAC   *pixac,
+               BOX     *box,
+               l_int32  copyflag)
+{
+    PROCNAME("pixacompAddBox");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (copyflag != L_INSERT && copyflag != L_COPY)
+        return ERROR_INT("invalid copyflag", procName, 1);
+
+    boxaAddBox(pixac->boxa, box, copyflag);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Pixacomp accessors                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixacompGetCount()
+ *
+ *      Input:  pixac
+ *      Return: count, or 0 if no pixa
+ */
+l_int32
+pixacompGetCount(PIXAC  *pixac)
+{
+    PROCNAME("pixacompGetCount");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 0);
+
+    return pixac->n;
+}
+
+
+/*!
+ *  pixacompGetPixcomp()
+ *
+ *      Input:  pixac
+ *              index (caller's view of index within pixac; includes offset)
+ *      Return: pixc, or null on error
+ *
+ *  Notes:
+ *      (1) The @index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ *      (2) Important: this is just a ptr to the pixc owned by the pixac.
+ *          Do not destroy unless you are replacing the pixc.
+ */
+PIXC *
+pixacompGetPixcomp(PIXAC   *pixac,
+                   l_int32  index)
+{
+l_int32  aindex;
+
+    PROCNAME("pixacompGetPixcomp");
+
+    if (!pixac)
+        return (PIXC *)ERROR_PTR("pixac not defined", procName, NULL);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= pixac->n)
+        return (PIXC *)ERROR_PTR("array index not valid", procName, NULL);
+
+    return pixac->pixc[aindex];
+}
+
+
+/*!
+ *  pixacompGetPix()
+ *
+ *      Input:  pixac
+ *              index (caller's view of index within pixac; includes offset)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) The @index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ */
+PIX *
+pixacompGetPix(PIXAC   *pixac,
+               l_int32  index)
+{
+l_int32  aindex;
+PIXC    *pixc;
+
+    PROCNAME("pixacompGetPix");
+
+    if (!pixac)
+        return (PIX *)ERROR_PTR("pixac not defined", procName, NULL);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= pixac->n)
+        return (PIX *)ERROR_PTR("array index not valid", procName, NULL);
+
+    pixc = pixacompGetPixcomp(pixac, index);
+    return pixCreateFromPixcomp(pixc);
+}
+
+
+/*!
+ *  pixacompGetPixDimensions()
+ *
+ *      Input:  pixa
+ *              index (caller's view of index within pixac; includes offset)
+ *              &w, &h, &d (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The @index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ */
+l_int32
+pixacompGetPixDimensions(PIXAC    *pixac,
+                         l_int32   index,
+                         l_int32  *pw,
+                         l_int32  *ph,
+                         l_int32  *pd)
+{
+l_int32  aindex;
+PIXC    *pixc;
+
+    PROCNAME("pixacompGetPixDimensions");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= pixac->n)
+        return ERROR_INT("array index not valid", procName, 1);
+
+    if ((pixc = pixac->pixc[aindex]) == NULL)
+        return ERROR_INT("pixc not found!", procName, 1);
+    pixcompGetDimensions(pixc, pw, ph, pd);
+    return 0;
+}
+
+
+/*!
+ *  pixacompGetBoxa()
+ *
+ *      Input:  pixac
+ *              accesstype  (L_COPY, L_CLONE, L_COPY_CLONE)
+ *      Return: boxa, or null on error
+ */
+BOXA *
+pixacompGetBoxa(PIXAC   *pixac,
+                l_int32  accesstype)
+{
+    PROCNAME("pixacompGetBoxa");
+
+    if (!pixac)
+        return (BOXA *)ERROR_PTR("pixac not defined", procName, NULL);
+    if (!pixac->boxa)
+        return (BOXA *)ERROR_PTR("boxa not defined", procName, NULL);
+    if (accesstype != L_COPY && accesstype != L_CLONE &&
+        accesstype != L_COPY_CLONE)
+        return (BOXA *)ERROR_PTR("invalid accesstype", procName, NULL);
+
+    return boxaCopy(pixac->boxa, accesstype);
+}
+
+
+/*!
+ *  pixacompGetBoxaCount()
+ *
+ *      Input:  pixac
+ *      Return: count, or 0 on error
+ */
+l_int32
+pixacompGetBoxaCount(PIXAC  *pixac)
+{
+    PROCNAME("pixacompGetBoxaCount");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 0);
+
+    return boxaGetCount(pixac->boxa);
+}
+
+
+/*!
+ *  pixacompGetBox()
+ *
+ *      Input:  pixac
+ *              index (caller's view of index within pixac; includes offset)
+ *              accesstype  (L_COPY or L_CLONE)
+ *      Return: box (if null, not automatically an error), or null on error
+ *
+ *  Notes:
+ *      (1) The @index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ *      (2) There is always a boxa with a pixac, and it is initialized so
+ *          that each box ptr is NULL.
+ *      (3) In general, we expect that there is either a box associated
+ *          with each pixc, or no boxes at all in the boxa.
+ *      (4) Having no boxes is thus not an automatic error.  Whether it
+ *          is an actual error is determined by the calling program.
+ *          If the caller expects to get a box, it is an error; see, e.g.,
+ *          pixacGetBoxGeometry().
+ */
+BOX *
+pixacompGetBox(PIXAC    *pixac,
+               l_int32   index,
+               l_int32   accesstype)
+{
+l_int32  aindex;
+BOX     *box;
+
+    PROCNAME("pixacompGetBox");
+
+    if (!pixac)
+        return (BOX *)ERROR_PTR("pixac not defined", procName, NULL);
+    if (!pixac->boxa)
+        return (BOX *)ERROR_PTR("boxa not defined", procName, NULL);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= pixac->boxa->n)
+        return (BOX *)ERROR_PTR("array index not valid", procName, NULL);
+    if (accesstype != L_COPY && accesstype != L_CLONE)
+        return (BOX *)ERROR_PTR("invalid accesstype", procName, NULL);
+
+    box = pixac->boxa->box[aindex];
+    if (box) {
+        if (accesstype == L_COPY)
+            return boxCopy(box);
+        else  /* accesstype == L_CLONE */
+            return boxClone(box);
+    } else {
+        return NULL;
+    }
+}
+
+
+/*!
+ *  pixacompGetBoxGeometry()
+ *
+ *      Input:  pixac
+ *              index (caller's view of index within pixac; includes offset)
+ *              &x, &y, &w, &h (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The @index includes the offset, which must be subtracted
+ *          to get the actual index into the ptr array.
+ */
+l_int32
+pixacompGetBoxGeometry(PIXAC    *pixac,
+                       l_int32   index,
+                       l_int32  *px,
+                       l_int32  *py,
+                       l_int32  *pw,
+                       l_int32  *ph)
+{
+l_int32  aindex;
+BOX     *box;
+
+    PROCNAME("pixacompGetBoxGeometry");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+    aindex = index - pixac->offset;
+    if (aindex < 0 || aindex >= pixac->n)
+        return ERROR_INT("array index not valid", procName, 1);
+
+    if ((box = pixacompGetBox(pixac, aindex, L_CLONE)) == NULL)
+        return ERROR_INT("box not found!", procName, 1);
+    boxGetGeometry(box, px, py, pw, ph);
+    boxDestroy(&box);
+    return 0;
+}
+
+
+/*!
+ *  pixacompGetOffset()
+ *
+ *      Input:  pixac
+ *      Return: offset, or 0 on error
+ *
+ *  Notes:
+ *      (1) The offset is the difference between the caller's view of
+ *          the index into the array and the actual array index.
+ *          By default it is 0.
+ */
+l_int32
+pixacompGetOffset(PIXAC   *pixac)
+{
+    PROCNAME("pixacompGetOffset");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 0);
+    return pixac->offset;
+}
+
+
+/*!
+ *  pixacompSetOffset()
+ *
+ *      Input:  pixac
+ *              offset (non-negative)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The offset is the difference between the caller's view of
+ *          the index into the array and the actual array index.
+ *          By default it is 0.
+ */
+l_int32
+pixacompSetOffset(PIXAC   *pixac,
+                  l_int32  offset)
+{
+    PROCNAME("pixacompSetOffset");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+    pixac->offset = L_MAX(0, offset);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                      Pixacomp conversion to Pixa                    *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaCreateFromPixacomp()
+ *
+ *      Input:  pixac
+ *              accesstype (L_COPY, L_CLONE, L_COPY_CLONE; for boxa)
+ *      Return: pixa if OK, or null on error
+ */
+PIXA *
+pixaCreateFromPixacomp(PIXAC   *pixac,
+                       l_int32  accesstype)
+{
+l_int32  i, n;
+PIX     *pix;
+PIXA    *pixa;
+
+    PROCNAME("pixaCreateFromPixacomp");
+
+    if (!pixac)
+        return (PIXA *)ERROR_PTR("pixac not defined", procName, NULL);
+    if (accesstype != L_COPY && accesstype != L_CLONE &&
+        accesstype != L_COPY_CLONE)
+        return (PIXA *)ERROR_PTR("invalid accesstype", procName, NULL);
+
+    n = pixacompGetCount(pixac);
+    if ((pixa = pixaCreate(n)) == NULL)
+        return (PIXA *)ERROR_PTR("pixa not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        if ((pix = pixacompGetPix(pixac, i)) == NULL) {
+            L_WARNING("pix %d not made\n", procName, i);
+            continue;
+        }
+        pixaAddPix(pixa, pix, L_INSERT);
+    }
+    if (pixa->boxa) {
+        boxaDestroy(&pixa->boxa);
+        pixa->boxa = pixacompGetBoxa(pixac, accesstype);
+    }
+
+    return pixa;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                       Pixacomp serialized I/O                       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixacompRead()
+ *
+ *      Input:  filename
+ *      Return: pixac, or null on error
+ *
+ *  Notes:
+ *      (1) Unlike the situation with serialized Pixa, where the image
+ *          data is stored in png format, the Pixacomp image data
+ *          can be stored in tiffg4, png and jpg formats.
+ */
+PIXAC *
+pixacompRead(const char  *filename)
+{
+FILE   *fp;
+PIXAC  *pixac;
+
+    PROCNAME("pixacompRead");
+
+    if (!filename)
+        return (PIXAC *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PIXAC *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((pixac = pixacompReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (PIXAC *)ERROR_PTR("pixac not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return pixac;
+}
+
+
+/*!
+ *  pixacompReadStream()
+ *
+ *      Input:  stream
+ *      Return: pixac, or null on error
+ */
+PIXAC *
+pixacompReadStream(FILE  *fp)
+{
+char      buf[256];
+l_uint8  *data;
+l_int32   n, offset, i, w, h, d, ignore;
+l_int32   comptype, size, cmapflag, version, xres, yres;
+BOXA     *boxa;
+PIXC     *pixc;
+PIXAC    *pixac;
+
+    PROCNAME("pixacompReadStream");
+
+    if (!fp)
+        return (PIXAC *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nPixacomp Version %d\n", &version) != 1)
+        return (PIXAC *)ERROR_PTR("not a pixacomp file", procName, NULL);
+    if (version != PIXACOMP_VERSION_NUMBER)
+        return (PIXAC *)ERROR_PTR("invalid pixacomp version", procName, NULL);
+    if (fscanf(fp, "Number of pixcomp = %d", &n) != 1)
+        return (PIXAC *)ERROR_PTR("not a pixacomp file", procName, NULL);
+    if (fscanf(fp, "Offset of index into array = %d", &offset) != 1)
+        return (PIXAC *)ERROR_PTR("offset not read", procName, NULL);
+
+    if ((pixac = pixacompCreate(n)) == NULL)
+        return (PIXAC *)ERROR_PTR("pixac not made", procName, NULL);
+    if ((boxa = boxaReadStream(fp)) == NULL)
+        return (PIXAC *)ERROR_PTR("boxa not made", procName, NULL);
+    boxaDestroy(&pixac->boxa);  /* empty */
+    pixac->boxa = boxa;
+    pixacompSetOffset(pixac, offset);
+
+    for (i = 0; i < n; i++) {
+        if ((pixc = (PIXC *)LEPT_CALLOC(1, sizeof(PIXC))) == NULL)
+            return (PIXAC *)ERROR_PTR("pixc not made", procName, NULL);
+        if (fscanf(fp, "\nPixcomp[%d]: w = %d, h = %d, d = %d\n",
+                   &ignore, &w, &h, &d) != 4)
+            return (PIXAC *)ERROR_PTR("size reading", procName, NULL);
+        if (fscanf(fp, "  comptype = %d, size = %d, cmapflag = %d\n",
+                   &comptype, &size, &cmapflag) != 3)
+            return (PIXAC *)ERROR_PTR("comptype/size reading", procName, NULL);
+
+           /* Use fgets() and sscanf(); not fscanf(), for the last
+             * bit of header data before the binary data.  The reason is
+             * that fscanf throws away white space, and if the binary data
+             * happens to begin with ascii character(s) that are white
+             * space, it will swallow them and all will be lost!  */
+        if (fgets(buf, sizeof(buf), fp) == NULL)
+            return (PIXAC *)ERROR_PTR("fgets read fail", procName, NULL);
+        if (sscanf(buf, "  xres = %d, yres = %d\n", &xres, &yres) != 2)
+            return (PIXAC *)ERROR_PTR("read fail for res", procName, NULL);
+
+        if ((data = (l_uint8 *)LEPT_CALLOC(1, size)) == NULL)
+            return (PIXAC *)ERROR_PTR("calloc fail for data", procName, NULL);
+        if (fread(data, 1, size, fp) != size)
+            return (PIXAC *)ERROR_PTR("error reading data", procName, NULL);
+        fgetc(fp);  /* swallow the ending nl */
+        pixc->w = w;
+        pixc->h = h;
+        pixc->d = d;
+        pixc->xres = xres;
+        pixc->yres = yres;
+        pixc->comptype = comptype;
+        pixc->cmapflag = cmapflag;
+        pixc->data = data;
+        pixc->size = size;
+        pixacompAddPixcomp(pixac, pixc);
+    }
+    return pixac;
+}
+
+
+/*!
+ *  pixacompWrite()
+ *
+ *      Input:  filename
+ *              pixac
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Unlike the situation with serialized Pixa, where the image
+ *          data is stored in png format, the Pixacomp image data
+ *          can be stored in tiffg4, png and jpg formats.
+ */
+l_int32
+pixacompWrite(const char  *filename,
+              PIXAC       *pixac)
+{
+FILE  *fp;
+
+    PROCNAME("pixacompWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!pixac)
+        return ERROR_INT("pixacomp not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (pixacompWriteStream(fp, pixac))
+        return ERROR_INT("pixacomp not written to stream", procName, 1);
+    fclose(fp);
+    return 0;
+}
+
+
+/*!
+ *  pixacompWriteStream()
+ *
+ *      Input:  stream
+ *              pixac
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixacompWriteStream(FILE   *fp,
+                    PIXAC  *pixac)
+{
+l_int32  n, i;
+PIXC    *pixc;
+
+    PROCNAME("pixacompWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+
+    n = pixacompGetCount(pixac);
+    fprintf(fp, "\nPixacomp Version %d\n", PIXACOMP_VERSION_NUMBER);
+    fprintf(fp, "Number of pixcomp = %d", n);
+    fprintf(fp, "Offset of index into array = %d", pixac->offset);
+    boxaWriteStream(fp, pixac->boxa);
+    for (i = 0; i < n; i++) {
+        if ((pixc =
+             pixacompGetPixcomp(pixac, pixacompGetOffset(pixac) + i)) == NULL)
+            return ERROR_INT("pixc not found", procName, 1);
+        fprintf(fp, "\nPixcomp[%d]: w = %d, h = %d, d = %d\n",
+                i, pixc->w, pixc->h, pixc->d);
+        fprintf(fp, "  comptype = %d, size = %lu, cmapflag = %d\n",
+                pixc->comptype, (unsigned long)pixc->size, pixc->cmapflag);
+        fprintf(fp, "  xres = %d, yres = %d\n", pixc->xres, pixc->yres);
+        fwrite(pixc->data, 1, pixc->size, fp);
+        fprintf(fp, "\n");
+    }
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                         Conversion to pdf                          *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixacompConvertToPdf()
+ *
+ *      Input:  pixac (containing images all at the same resolution)
+ *              res (override the resolution of each input image, in ppi;
+ *                   use 0 to respect the resolution embedded in the input)
+ *              scalefactor (scaling factor applied to each image; > 0.0)
+ *              type (encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ *                    L_FLATE_ENCODE, or L_DEFAULT_ENCODE for default)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              title (<optional> pdf title)
+ *              fileout (pdf file of all images)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This follows closely the function pixaConvertToPdf() in pdfio.c.
+ *      (2) The images are encoded with G4 if 1 bpp; JPEG if 8 bpp without
+ *          colormap and many colors, or 32 bpp; FLATE for anything else.
+ *      (3) The scalefactor must be > 0.0; otherwise it is set to 1.0.
+ *      (4) Specifying one of the three encoding types for @type forces
+ *          all images to be compressed with that type.  Use 0 to have
+ *          the type determined for each image based on depth and whether
+ *          or not it has a colormap.
+ */
+l_int32
+pixacompConvertToPdf(PIXAC       *pixac,
+                     l_int32      res,
+                     l_float32    scalefactor,
+                     l_int32      type,
+                     l_int32      quality,
+                     const char  *title,
+                     const char  *fileout)
+{
+l_uint8  *data;
+l_int32   ret;
+size_t    nbytes;
+
+    PROCNAME("pixacompConvertToPdf");
+
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+
+    ret = pixacompConvertToPdfData(pixac, res, scalefactor, type, quality,
+                                   title, &data, &nbytes);
+    if (ret) {
+        LEPT_FREE(data);
+        return ERROR_INT("conversion to pdf failed", procName, 1);
+    }
+
+    ret = l_binaryWrite(fileout, "w", data, nbytes);
+    LEPT_FREE(data);
+    if (ret)
+        L_ERROR("pdf data not written to file\n", procName);
+    return ret;
+}
+
+
+/*!
+ *  pixacompConvertToPdfData()
+ *
+ *      Input:  pixac (containing images all at the same resolution)
+ *              res (input resolution of all images)
+ *              scalefactor (scaling factor applied to each image; > 0.0)
+ *              type (encoding type (L_JPEG_ENCODE, L_G4_ENCODE,
+ *                    L_FLATE_ENCODE, or L_DEFAULT_ENCODE for default)
+ *              quality (used for JPEG only; 0 for default (75))
+ *              title (<optional> pdf title)
+ *              &data (<return> output pdf data (of all images)
+ *              &nbytes (<return> size of output pdf data)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See pixacompConvertToPdf().
+ */
+l_int32
+pixacompConvertToPdfData(PIXAC       *pixac,
+                         l_int32      res,
+                         l_float32    scalefactor,
+                         l_int32      type,
+                         l_int32      quality,
+                         const char  *title,
+                         l_uint8    **pdata,
+                         size_t      *pnbytes)
+{
+l_uint8  *imdata;
+l_int32   i, n, ret, scaledres, pagetype;
+size_t    imbytes;
+L_BYTEA  *ba;
+PIX      *pixs, *pix;
+L_PTRA   *pa_data;
+
+    PROCNAME("pixacompConvertToPdfData");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    *pdata = NULL;
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+    if (scalefactor <= 0.0) scalefactor = 1.0;
+    if (type < L_DEFAULT_ENCODE || type > L_FLATE_ENCODE) {
+        L_WARNING("invalid compression type; using per-page default\n",
+                  procName);
+        type = L_DEFAULT_ENCODE;
+    }
+
+        /* Generate all the encoded pdf strings */
+    n = pixacompGetCount(pixac);
+    pa_data = ptraCreate(n);
+    for (i = 0; i < n; i++) {
+        if ((pixs =
+             pixacompGetPix(pixac, pixacompGetOffset(pixac) + i)) == NULL) {
+            L_ERROR("pix[%d] not retrieved\n", procName, i);
+            continue;
+        }
+        if (pixGetWidth(pixs) == 1) {  /* used sometimes as placeholders */
+            L_INFO("placeholder image[%d] has w = 1\n", procName, i);
+            pixDestroy(&pixs);
+            continue;
+        }
+        if (scalefactor != 1.0)
+            pix = pixScale(pixs, scalefactor, scalefactor);
+        else
+            pix = pixClone(pixs);
+        pixDestroy(&pixs);
+        scaledres = (l_int32)(res * scalefactor);
+        if (type != L_DEFAULT_ENCODE) {
+            pagetype = type;
+        } else if (selectDefaultPdfEncoding(pix, &pagetype) != 0) {
+            L_ERROR("encoding type selection failed for pix[%d]\n",
+                    procName, i);
+            pixDestroy(&pix);
+            continue;
+        }
+        ret = pixConvertToPdfData(pix, pagetype, quality, &imdata, &imbytes,
+                                  0, 0, scaledres, title, NULL, 0);
+        pixDestroy(&pix);
+        if (ret) {
+            L_ERROR("pdf encoding failed for pix[%d]\n", procName, i);
+            continue;
+        }
+        ba = l_byteaInitFromMem(imdata, imbytes);
+        if (imdata) LEPT_FREE(imdata);
+        ptraAdd(pa_data, ba);
+    }
+    ptraGetActualCount(pa_data, &n);
+    if (n == 0) {
+        L_ERROR("no pdf files made\n", procName);
+        ptraDestroy(&pa_data, FALSE, FALSE);
+        return 1;
+    }
+
+        /* Concatenate them */
+    ret = ptraConcatenatePdfToData(pa_data, NULL, pdata, pnbytes);
+
+    ptraGetActualCount(pa_data, &n);  /* recalculate in case it changes */
+    for (i = 0; i < n; i++) {
+        ba = (L_BYTEA *)ptraRemove(pa_data, i, L_NO_COMPACTION);
+        l_byteaDestroy(&ba);
+    }
+    ptraDestroy(&pa_data, FALSE, FALSE);
+    return ret;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                        Output for debugging                        *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixacompWriteStreamInfo()
+ *
+ *      Input:  fp (file stream)
+ *              pixac
+ *              text (<optional> identifying string; can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixacompWriteStreamInfo(FILE        *fp,
+                        PIXAC       *pixac,
+                        const char  *text)
+{
+l_int32  i, n, nboxes;
+PIXC    *pixc;
+
+    PROCNAME("pixacompWriteStreamInfo");
+
+    if (!fp)
+        return ERROR_INT("fp not defined", procName, 1);
+    if (!pixac)
+        return ERROR_INT("pixac not defined", procName, 1);
+
+    if (text)
+        fprintf(fp, "Pixacomp Info for %s:\n", text);
+    else
+        fprintf(fp, "Pixacomp Info:\n");
+    n = pixacompGetCount(pixac);
+    nboxes = pixacompGetBoxaCount(pixac);
+    fprintf(fp, "Number of pixcomp: %d\n", n);
+    fprintf(fp, "Size of pixcomp array alloc: %d\n", pixac->nalloc);
+    fprintf(fp, "Offset of index into array: %d\n", pixac->offset);
+    if (nboxes  > 0)
+        fprintf(fp, "Boxa has %d boxes\n", nboxes);
+    else
+        fprintf(fp, "Boxa is empty\n");
+    for (i = 0; i < n; i++) {
+        pixc = pixacompGetPixcomp(pixac, pixac->offset + i);
+        pixcompWriteStreamInfo(fp, pixc, NULL);
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixcompWriteStreamInfo()
+ *
+ *      Input:  fp (file stream)
+ *              pixc
+ *              text (<optional> identifying string; can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixcompWriteStreamInfo(FILE        *fp,
+                       PIXC        *pixc,
+                       const char  *text)
+{
+    PROCNAME("pixcompWriteStreamInfo");
+
+    if (!fp)
+        return ERROR_INT("fp not defined", procName, 1);
+    if (!pixc)
+        return ERROR_INT("pixc not defined", procName, 1);
+
+    if (text)
+        fprintf(fp, "  Pixcomp Info for %s:", text);
+    else
+        fprintf(fp, "  Pixcomp Info:");
+    fprintf(fp, " width = %d, height = %d, depth = %d\n",
+            pixc->w, pixc->h, pixc->d);
+    fprintf(fp, "    xres = %d, yres = %d, size in bytes = %lu\n",
+            pixc->xres, pixc->yres, (unsigned long)pixc->size);
+    if (pixc->cmapflag)
+        fprintf(fp, "    has colormap\n");
+    else
+        fprintf(fp, "    no colormap\n");
+    if (pixc->comptype < NumImageFileFormatExtensions) {
+        fprintf(fp, "    comptype = %s (%d)\n",
+                ImageFileFormatExtensions[pixc->comptype], pixc->comptype);
+    } else {
+        fprintf(fp, "    Error!! Invalid comptype index: %d\n", pixc->comptype);
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixacompDisplayTiledAndScaled()
+ *
+ *      Input:  pixac
+ *              outdepth (output depth: 1, 8 or 32 bpp)
+ *              tilewidth (each pix is scaled to this width)
+ *              ncols (number of tiles in each row)
+ *              background (0 for white, 1 for black; this is the color
+ *                 of the spacing between the images)
+ *              spacing  (between images, and on outside)
+ *              border (width of additional black border on each image;
+ *                      use 0 for no border)
+ *      Return: pix of tiled images, or null on error
+ *
+ *  Notes:
+ *      (1) This is the same function as pixaDisplayTiledAndScaled(),
+ *          except it works on a Pixacomp instead of a Pix.  It is particularly
+ *          useful for showing the images in a Pixacomp at reduced resolution.
+ *      (2) This can be used to tile a number of renderings of
+ *          an image that are at different scales and depths.
+ *      (3) Each image, after scaling and optionally adding the
+ *          black border, has width 'tilewidth'.  Thus, the border does
+ *          not affect the spacing between the image tiles.  The
+ *          maximum allowed border width is tilewidth / 5.
+ */
+PIX *
+pixacompDisplayTiledAndScaled(PIXAC   *pixac,
+                              l_int32  outdepth,
+                              l_int32  tilewidth,
+                              l_int32  ncols,
+                              l_int32  background,
+                              l_int32  spacing,
+                              l_int32  border)
+{
+l_int32    x, y, w, h, wd, hd, d;
+l_int32    i, n, nrows, maxht, ninrow, irow, bordval;
+l_int32   *rowht;
+l_float32  scalefact;
+PIX       *pix, *pixn, *pixt, *pixb, *pixd;
+PIXA      *pixan;
+
+    PROCNAME("pixacompDisplayTiledAndScaled");
+
+    if (!pixac)
+        return (PIX *)ERROR_PTR("pixac not defined", procName, NULL);
+    if (outdepth != 1 && outdepth != 8 && outdepth != 32)
+        return (PIX *)ERROR_PTR("outdepth not in {1, 8, 32}", procName, NULL);
+    if (border < 0 || border > tilewidth / 5)
+        border = 0;
+
+    if ((n = pixacompGetCount(pixac)) == 0)
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+
+        /* Normalize scale and depth for each pix; optionally add border */
+    pixan = pixaCreate(n);
+    bordval = (outdepth == 1) ? 1 : 0;
+    for (i = 0; i < n; i++) {
+        if ((pix =
+             pixacompGetPix(pixac, pixacompGetOffset(pixac) + i)) == NULL) {
+            L_WARNING("pix %d not made\n", procName, i);
+            continue;
+        }
+
+        pixGetDimensions(pix, &w, &h, &d);
+        scalefact = (l_float32)(tilewidth - 2 * border) / (l_float32)w;
+        if (d == 1 && outdepth > 1 && scalefact < 1.0)
+            pixt = pixScaleToGray(pix, scalefact);
+        else
+            pixt = pixScale(pix, scalefact, scalefact);
+
+        if (outdepth == 1)
+            pixn = pixConvertTo1(pixt, 128);
+        else if (outdepth == 8)
+            pixn = pixConvertTo8(pixt, FALSE);
+        else  /* outdepth == 32 */
+            pixn = pixConvertTo32(pixt);
+        pixDestroy(&pixt);
+
+        if (border)
+            pixb = pixAddBorder(pixn, border, bordval);
+        else
+            pixb = pixClone(pixn);
+
+        pixaAddPix(pixan, pixb, L_INSERT);
+        pixDestroy(&pix);
+        pixDestroy(&pixn);
+    }
+    if ((n = pixaGetCount(pixan)) == 0) { /* should not have changed! */
+        pixaDestroy(&pixan);
+        return (PIX *)ERROR_PTR("no components", procName, NULL);
+    }
+
+        /* Determine the size of each row and of pixd */
+    wd = tilewidth * ncols + spacing * (ncols + 1);
+    nrows = (n + ncols - 1) / ncols;
+    if ((rowht = (l_int32 *)LEPT_CALLOC(nrows, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("rowht array not made", procName, NULL);
+    maxht = 0;
+    ninrow = 0;
+    irow = 0;
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixan, i, L_CLONE);
+        ninrow++;
+        pixGetDimensions(pix, &w, &h, NULL);
+        maxht = L_MAX(h, maxht);
+        if (ninrow == ncols) {
+            rowht[irow] = maxht;
+            maxht = ninrow = 0;  /* reset */
+            irow++;
+        }
+        pixDestroy(&pix);
+    }
+    if (ninrow > 0) {   /* last fencepost */
+        rowht[irow] = maxht;
+        irow++;  /* total number of rows */
+    }
+    nrows = irow;
+    hd = spacing * (nrows + 1);
+    for (i = 0; i < nrows; i++)
+        hd += rowht[i];
+
+    pixd = pixCreate(wd, hd, outdepth);
+    if ((background == 1 && outdepth == 1) ||
+        (background == 0 && outdepth != 1))
+        pixSetAll(pixd);
+
+        /* Now blit images to pixd */
+    x = y = spacing;
+    irow = 0;
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixan, i, L_CLONE);
+        pixGetDimensions(pix, &w, &h, NULL);
+        if (i && ((i % ncols) == 0)) {  /* start new row */
+            x = spacing;
+            y += spacing + rowht[irow];
+            irow++;
+        }
+        pixRasterop(pixd, x, y, w, h, PIX_SRC, pix, 0, 0);
+        x += tilewidth + spacing;
+        pixDestroy(&pix);
+    }
+
+    pixaDestroy(&pixan);
+    LEPT_FREE(rowht);
+    return pixd;
+}
diff --git a/src/pixconv.c b/src/pixconv.c
new file mode 100644 (file)
index 0000000..342b4b0
--- /dev/null
@@ -0,0 +1,3615 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pixconv.c
+ *
+ *      These functions convert between images of different types
+ *      without scaling.
+ *
+ *      Conversion from 8 bpp grayscale to 1, 2, 4 and 8 bpp
+ *           PIX        *pixThreshold8()
+ *
+ *      Conversion from colormap to full color or grayscale
+ *           PIX        *pixRemoveColormapGeneral()
+ *           PIX        *pixRemoveColormap()
+ *
+ *      Add colormap losslessly (8 to 8)
+ *           l_int32     pixAddGrayColormap8()
+ *           PIX        *pixAddMinimalGrayColormap8()
+ *
+ *      Conversion from RGB color to grayscale
+ *           PIX        *pixConvertRGBToLuminance()
+ *           PIX        *pixConvertRGBToGray()
+ *           PIX        *pixConvertRGBToGrayFast()
+ *           PIX        *pixConvertRGBToGrayMinMax()
+ *           PIX        *pixConvertRGBToGraySatBoost()
+ *
+ *      Conversion from grayscale to colormap
+ *           PIX        *pixConvertGrayToColormap()  -- 2, 4, 8 bpp
+ *           PIX        *pixConvertGrayToColormap8()  -- 8 bpp only
+ *
+ *      Colorizing conversion from grayscale to color
+ *           PIX        *pixColorizeGray()  -- 8 bpp or cmapped
+ *
+ *      Conversion from RGB color to colormap
+ *           PIX        *pixConvertRGBToColormap()
+ *
+ *      Quantization for relatively small number of colors in source
+ *           l_int32     pixQuantizeIfFewColors()
+ *
+ *      Conversion from 16 bpp to 8 bpp
+ *           PIX        *pixConvert16To8()
+ *
+ *      Conversion from grayscale to false color
+ *           PIX        *pixConvertGrayToFalseColor()
+ *
+ *      Unpacking conversion from 1 bpp to 2, 4, 8, 16 and 32 bpp
+ *           PIX        *pixUnpackBinary()
+ *           PIX        *pixConvert1To16()
+ *           PIX        *pixConvert1To32()
+ *
+ *      Unpacking conversion from 1 bpp to 2 bpp
+ *           PIX        *pixConvert1To2Cmap()
+ *           PIX        *pixConvert1To2()
+ *
+ *      Unpacking conversion from 1 bpp to 4 bpp
+ *           PIX        *pixConvert1To4Cmap()
+ *           PIX        *pixConvert1To4()
+ *
+ *      Unpacking conversion from 1, 2 and 4 bpp to 8 bpp
+ *           PIX        *pixConvert1To8()
+ *           PIX        *pixConvert2To8()
+ *           PIX        *pixConvert4To8()
+ *
+ *      Unpacking conversion from 8 bpp to 16 bpp
+ *           PIX        *pixConvert8To16()
+ *
+ *      Top-level conversion to 1 bpp
+ *           PIX        *pixConvertTo1()
+ *           PIX        *pixConvertTo1BySampling()
+ *
+ *      Top-level conversion to 8 bpp
+ *           PIX        *pixConvertTo8()
+ *           PIX        *pixConvertTo8BySampling()
+ *           PIX        *pixConvertTo8Color()
+ *
+ *      Top-level conversion to 16 bpp
+ *           PIX        *pixConvertTo16()
+ *
+ *      Top-level conversion to 32 bpp (RGB)
+ *           PIX        *pixConvertTo32()   ***
+ *           PIX        *pixConvertTo32BySampling()   ***
+ *           PIX        *pixConvert8To32()  ***
+ *
+ *      Top-level conversion to 8 or 32 bpp, without colormap
+ *           PIX        *pixConvertTo8Or32
+ *
+ *      Conversion between 24 bpp and 32 bpp rgb
+ *           PIX        *pixConvert24To32()
+ *           PIX        *pixConvert32To24()
+ *
+ *      Conversion between 32 bpp (1 spp) and 16 or 8 bpp
+ *           PIX        *pixConvert32To16()
+ *           PIX        *pixConvert32To8()
+ *
+ *      Removal of alpha component by blending with white background
+ *           PIX        *pixRemoveAlpha()
+ *
+ *      Addition of alpha component to 1 bpp
+ *           PIX        *pixAddAlphaTo1bpp()
+ *
+ *      Lossless depth conversion (unpacking)
+ *           PIX        *pixConvertLossless()
+ *
+ *      Conversion for printing in PostScript
+ *           PIX        *pixConvertForPSWrap()
+ *
+ *      Scaling conversion to subpixel RGB
+ *           PIX        *pixConvertToSubpixelRGB()
+ *           PIX        *pixConvertGrayToSubpixelRGB()
+ *           PIX        *pixConvertColorToSubpixelRGB()
+ *
+ *      *** indicates implicit assumption about RGB component ordering
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+#ifndef  NO_CONSOLE_IO
+#define DEBUG_CONVERT_TO_COLORMAP  0
+#define DEBUG_UNROLLING 0
+#endif   /* ~NO_CONSOLE_IO */
+
+
+/*-------------------------------------------------------------*
+ *     Conversion from 8 bpp grayscale to 1, 2 4 and 8 bpp     *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixThreshold8()
+ *
+ *      Input:  pix (8 bpp grayscale)
+ *              d (destination depth: 1, 2, 4 or 8)
+ *              nlevels (number of levels to be used for colormap)
+ *              cmapflag (1 if makes colormap; 0 otherwise)
+ *      Return: pixd (thresholded with standard dest thresholds),
+ *              or null on error
+ *
+ *  Notes:
+ *      (1) This uses, by default, equally spaced "target" values
+ *          that depend on the number of levels, with thresholds
+ *          halfway between.  For N levels, with separation (N-1)/255,
+ *          there are N-1 fixed thresholds.
+ *      (2) For 1 bpp destination, the number of levels can only be 2
+ *          and if a cmap is made, black is (0,0,0) and white
+ *          is (255,255,255), which is opposite to the convention
+ *          without a colormap.
+ *      (3) For 1, 2 and 4 bpp, the nlevels arg is used if a colormap
+ *          is made; otherwise, we take the most significant bits
+ *          from the src that will fit in the dest.
+ *      (4) For 8 bpp, the input pixs is quantized to nlevels.  The
+ *          dest quantized with that mapping, either through a colormap
+ *          table or directly with 8 bit values.
+ *      (5) Typically you should not use make a colormap for 1 bpp dest.
+ *      (6) This is not dithering.  Each pixel is treated independently.
+ */
+PIX *
+pixThreshold8(PIX     *pixs,
+              l_int32  d,
+              l_int32  nlevels,
+              l_int32  cmapflag)
+{
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixThreshold8");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (cmapflag && nlevels < 2)
+        return (PIX *)ERROR_PTR("nlevels must be at least 2", procName, NULL);
+
+    switch (d) {
+    case 1:
+        pixd = pixThresholdToBinary(pixs, 128);
+        if (cmapflag) {
+            cmap = pixcmapCreateLinear(1, 2);
+            pixSetColormap(pixd, cmap);
+        }
+        break;
+    case 2:
+        pixd = pixThresholdTo2bpp(pixs, nlevels, cmapflag);
+        break;
+    case 4:
+        pixd = pixThresholdTo4bpp(pixs, nlevels, cmapflag);
+        break;
+    case 8:
+        pixd = pixThresholdOn8bpp(pixs, nlevels, cmapflag);
+        break;
+    default:
+        return (PIX *)ERROR_PTR("d must be in {1,2,4,8}", procName, NULL);
+    }
+
+    if (!pixd)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ *               Conversion from colormapped pix               *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixRemoveColormapGeneral()
+ *
+ *      Input:  pixs (any depth, with or without colormap)
+ *              type (REMOVE_CMAP_TO_BINARY,
+ *                    REMOVE_CMAP_TO_GRAYSCALE,
+ *                    REMOVE_CMAP_TO_FULL_COLOR,
+ *                    REMOVE_CMAP_WITH_ALPHA,
+ *                    REMOVE_CMAP_BASED_ON_SRC)
+ *              ifnocmap (L_CLONE, L_COPY)
+ *      Return: pixd (always a new pix; without colormap), or null on error
+ *
+ *  Notes:
+ *      (1) Convenience function that allows choice between returning
+ *          a clone or a copy if pixs does not have a colormap.
+ *      (2) See pixRemoveColormap().
+ */
+PIX *
+pixRemoveColormapGeneral(PIX     *pixs,
+                         l_int32  type,
+                         l_int32  ifnocmap)
+{
+    PROCNAME("pixRemoveColormapGeneral");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (ifnocmap != L_CLONE && ifnocmap != L_COPY)
+        return (PIX *)ERROR_PTR("invalid value for ifnocmap", procName, NULL);
+
+    if (pixGetColormap(pixs))
+        return pixRemoveColormap(pixs, type);
+
+    if (ifnocmap == L_CLONE)
+        return pixClone(pixs);
+    else
+        return pixCopy(NULL, pixs);
+}
+
+
+/*!
+ *  pixRemoveColormap()
+ *
+ *      Input:  pixs (see restrictions below)
+ *              type (REMOVE_CMAP_TO_BINARY,
+ *                    REMOVE_CMAP_TO_GRAYSCALE,
+ *                    REMOVE_CMAP_TO_FULL_COLOR,
+ *                    REMOVE_CMAP_WITH_ALPHA,
+ *                    REMOVE_CMAP_BASED_ON_SRC)
+ *      Return: pixd (without colormap), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs does not have a colormap, a clone is returned.
+ *      (2) Otherwise, the input pixs is restricted to 1, 2, 4 or 8 bpp.
+ *      (3) Use REMOVE_CMAP_TO_BINARY only on 1 bpp pix.
+ *      (4) For grayscale conversion from RGB, use a weighted average
+ *          of RGB values, and always return an 8 bpp pix, regardless
+ *          of whether the input pixs depth is 2, 4 or 8 bpp.
+ *      (5) REMOVE_CMAP_TO_FULL_COLOR ignores the alpha component and
+ *          returns a 32 bpp pix with spp == 3 and the alpha bytes are 0.
+ *      (6) For REMOVE_CMAP_BASED_ON_SRC, if there is no color, this
+ *          returns either a 1 bpp or 8 bpp grayscale pix.
+ *          If there is color, this returns a 32 bpp pix, with either:
+ *           * 3 spp, if the alpha values are all 255 (opaque), or
+ *           * 4 spp (preserving the alpha), if any alpha values are not 255.
+ */
+PIX *
+pixRemoveColormap(PIX     *pixs,
+                  l_int32  type)
+{
+l_int32    sval, rval, gval, bval, val0, val1;
+l_int32    i, j, k, w, h, d, wpls, wpld, ncolors, count;
+l_int32    opaque, colorfound, blackwhite;
+l_int32   *rmap, *gmap, *bmap, *amap, *graymap;
+l_uint32  *datas, *lines, *datad, *lined, *lut;
+l_uint32   sword, dword;
+PIXCMAP   *cmap;
+PIX       *pixd;
+
+    PROCNAME("pixRemoveColormap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if ((cmap = pixGetColormap(pixs)) == NULL)
+        return pixClone(pixs);
+
+    if (type != REMOVE_CMAP_TO_BINARY &&
+        type != REMOVE_CMAP_TO_GRAYSCALE &&
+        type != REMOVE_CMAP_TO_FULL_COLOR &&
+        type != REMOVE_CMAP_WITH_ALPHA &&
+        type != REMOVE_CMAP_BASED_ON_SRC) {
+        L_WARNING("Invalid type; converting based on src\n", procName);
+        type = REMOVE_CMAP_BASED_ON_SRC;
+    }
+
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8)
+        return (PIX *)ERROR_PTR("pixs must be {1,2,4,8} bpp", procName, NULL);
+
+    if (pixcmapToArrays(cmap, &rmap, &gmap, &bmap, &amap))
+        return (PIX *)ERROR_PTR("colormap arrays not made", procName, NULL);
+
+    if (d != 1 && type == REMOVE_CMAP_TO_BINARY) {
+        L_WARNING("not 1 bpp; can't remove cmap to binary\n", procName);
+        type = REMOVE_CMAP_BASED_ON_SRC;
+    }
+
+        /* Select output type depending on colormap content */
+    if (type == REMOVE_CMAP_BASED_ON_SRC) {
+        pixcmapIsOpaque(cmap, &opaque);
+        pixcmapHasColor(cmap, &colorfound);
+        pixcmapIsBlackAndWhite(cmap, &blackwhite);
+        if (!opaque) {  /* save the alpha */
+            type = REMOVE_CMAP_WITH_ALPHA;
+        } else if (colorfound) {
+            type = REMOVE_CMAP_TO_FULL_COLOR;
+        } else {  /* opaque and no color */
+            if (d == 1 && blackwhite)  /* can binarize without loss */
+                type = REMOVE_CMAP_TO_BINARY;
+            else
+                type = REMOVE_CMAP_TO_GRAYSCALE;
+        }
+    }
+
+    ncolors = pixcmapGetCount(cmap);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if (type == REMOVE_CMAP_TO_BINARY) {
+        if ((pixd = pixCopy(NULL, pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+        pixcmapGetColor(cmap, 0, &rval, &gval, &bval);
+        val0 = rval + gval + bval;
+        pixcmapGetColor(cmap, 1, &rval, &gval, &bval);
+        val1 = rval + gval + bval;
+        if (val0 < val1)  /* photometrically inverted from standard */
+            pixInvert(pixd, pixd);
+        pixDestroyColormap(pixd);
+    } else if (type == REMOVE_CMAP_TO_GRAYSCALE) {
+        if ((pixd = pixCreate(w, h, 8)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+        pixCopyResolution(pixd, pixs);
+        pixCopyInputFormat(pixd, pixs);
+        datad = pixGetData(pixd);
+        wpld = pixGetWpl(pixd);
+        if ((graymap = (l_int32 *)LEPT_CALLOC(ncolors, sizeof(l_int32)))
+            == NULL)
+            return (PIX *)ERROR_PTR("calloc fail for graymap", procName, NULL);
+        for (i = 0; i < pixcmapGetCount(cmap); i++) {
+            graymap[i] = (rmap[i] + 2 * gmap[i] + bmap[i]) / 4;
+        }
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            switch (d)   /* depth test above; no default permitted */
+            {
+                case 8:
+                        /* Unrolled 4x */
+                    for (j = 0, count = 0; j + 3 < w; j += 4, count++) {
+                        sword = lines[count];
+                        dword = (graymap[(sword >> 24) & 0xff] << 24) |
+                            (graymap[(sword >> 16) & 0xff] << 16) |
+                            (graymap[(sword >> 8) & 0xff] << 8) |
+                            graymap[sword & 0xff];
+                        lined[count] = dword;
+                    }
+                        /* Cleanup partial word */
+                    for (; j < w; j++) {
+                        sval = GET_DATA_BYTE(lines, j);
+                        gval = graymap[sval];
+                        SET_DATA_BYTE(lined, j, gval);
+                    }
+#if DEBUG_UNROLLING
+#define CHECK_VALUE(a, b, c) if (GET_DATA_BYTE(a, b) != c) { \
+    fprintf(stderr, "Error: mismatch at %d, %d vs %d\n", \
+            j, GET_DATA_BYTE(a, b), c); }
+                    for (j = 0; j < w; j++) {
+                        sval = GET_DATA_BYTE(lines, j);
+                        gval = graymap[sval];
+                        CHECK_VALUE(lined, j, gval);
+                    }
+#endif
+                    break;
+                case 4:
+                        /* Unrolled 8x */
+                    for (j = 0, count = 0; j + 7 < w; j += 8, count++) {
+                        sword = lines[count];
+                        dword = (graymap[(sword >> 28) & 0xf] << 24) |
+                            (graymap[(sword >> 24) & 0xf] << 16) |
+                            (graymap[(sword >> 20) & 0xf] << 8) |
+                            graymap[(sword >> 16) & 0xf];
+                        lined[2 * count] = dword;
+                        dword = (graymap[(sword >> 12) & 0xf] << 24) |
+                            (graymap[(sword >> 8) & 0xf] << 16) |
+                            (graymap[(sword >> 4) & 0xf] << 8) |
+                            graymap[sword & 0xf];
+                        lined[2 * count + 1] = dword;
+                    }
+                        /* Cleanup partial word */
+                    for (; j < w; j++) {
+                        sval = GET_DATA_QBIT(lines, j);
+                        gval = graymap[sval];
+                        SET_DATA_BYTE(lined, j, gval);
+                    }
+#if DEBUG_UNROLLING
+                    for (j = 0; j < w; j++) {
+                        sval = GET_DATA_QBIT(lines, j);
+                        gval = graymap[sval];
+                        CHECK_VALUE(lined, j, gval);
+                    }
+#endif
+                    break;
+                case 2:
+                        /* Unrolled 16x */
+                    for (j = 0, count = 0; j + 15 < w; j += 16, count++) {
+                        sword = lines[count];
+                        dword = (graymap[(sword >> 30) & 0x3] << 24) |
+                            (graymap[(sword >> 28) & 0x3] << 16) |
+                            (graymap[(sword >> 26) & 0x3] << 8) |
+                            graymap[(sword >> 24) & 0x3];
+                        lined[4 * count] = dword;
+                        dword = (graymap[(sword >> 22) & 0x3] << 24) |
+                            (graymap[(sword >> 20) & 0x3] << 16) |
+                            (graymap[(sword >> 18) & 0x3] << 8) |
+                            graymap[(sword >> 16) & 0x3];
+                        lined[4 * count + 1] = dword;
+                        dword = (graymap[(sword >> 14) & 0x3] << 24) |
+                            (graymap[(sword >> 12) & 0x3] << 16) |
+                            (graymap[(sword >> 10) & 0x3] << 8) |
+                            graymap[(sword >> 8) & 0x3];
+                        lined[4 * count + 2] = dword;
+                        dword = (graymap[(sword >> 6) & 0x3] << 24) |
+                            (graymap[(sword >> 4) & 0x3] << 16) |
+                            (graymap[(sword >> 2) & 0x3] << 8) |
+                            graymap[sword & 0x3];
+                        lined[4 * count + 3] = dword;
+                    }
+                        /* Cleanup partial word */
+                    for (; j < w; j++) {
+                        sval = GET_DATA_DIBIT(lines, j);
+                        gval = graymap[sval];
+                        SET_DATA_BYTE(lined, j, gval);
+                    }
+#if DEBUG_UNROLLING
+                    for (j = 0; j < w; j++) {
+                        sval = GET_DATA_DIBIT(lines, j);
+                        gval = graymap[sval];
+                        CHECK_VALUE(lined, j, gval);
+                    }
+#endif
+                    break;
+                case 1:
+                        /* Unrolled 8x */
+                    for (j = 0, count = 0; j + 31 < w; j += 32, count++) {
+                        sword = lines[count];
+                        for (k = 0; k < 4; k++) {
+                                /* The top byte is always the relevant one */
+                            dword = (graymap[(sword >> 31) & 0x1] << 24) |
+                                (graymap[(sword >> 30) & 0x1] << 16) |
+                                (graymap[(sword >> 29) & 0x1] << 8) |
+                                graymap[(sword >> 28) & 0x1];
+                            lined[8 * count + 2 * k] = dword;
+                            dword = (graymap[(sword >> 27) & 0x1] << 24) |
+                                (graymap[(sword >> 26) & 0x1] << 16) |
+                                (graymap[(sword >> 25) & 0x1] << 8) |
+                                graymap[(sword >> 24) & 0x1];
+                            lined[8 * count + 2 * k + 1] = dword;
+                            sword <<= 8;  /* Move up the next byte */
+                        }
+                    }
+                        /* Cleanup partial word */
+                    for (; j < w; j++) {
+                        sval = GET_DATA_BIT(lines, j);
+                        gval = graymap[sval];
+                        SET_DATA_BYTE(lined, j, gval);
+                    }
+#if DEBUG_UNROLLING
+                    for (j = 0; j < w; j++) {
+                        sval = GET_DATA_BIT(lines, j);
+                        gval = graymap[sval];
+                        CHECK_VALUE(lined, j, gval);
+                    }
+#undef CHECK_VALUE
+#endif
+                    break;
+                default:
+                    return NULL;
+            }
+        }
+        if (graymap)
+            LEPT_FREE(graymap);
+    } else {  /* type == REMOVE_CMAP_TO_FULL_COLOR or REMOVE_CMAP_WITH_ALPHA */
+        if ((pixd = pixCreate(w, h, 32)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+        pixCopyInputFormat(pixd, pixs);
+        pixCopyResolution(pixd, pixs);
+        if (type == REMOVE_CMAP_WITH_ALPHA)
+            pixSetSpp(pixd, 4);
+        datad = pixGetData(pixd);
+        wpld = pixGetWpl(pixd);
+        if ((lut = (l_uint32 *)LEPT_CALLOC(ncolors, sizeof(l_uint32))) == NULL)
+            return (PIX *)ERROR_PTR("calloc fail for lut", procName, NULL);
+        for (i = 0; i < ncolors; i++) {
+            if (type == REMOVE_CMAP_TO_FULL_COLOR)
+                composeRGBPixel(rmap[i], gmap[i], bmap[i], lut + i);
+            else  /* full color plus alpha */
+                composeRGBAPixel(rmap[i], gmap[i], bmap[i], amap[i], lut + i);
+        }
+
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                if (d == 8)
+                    sval = GET_DATA_BYTE(lines, j);
+                else if (d == 4)
+                    sval = GET_DATA_QBIT(lines, j);
+                else if (d == 2)
+                    sval = GET_DATA_DIBIT(lines, j);
+                else if (d == 1)
+                    sval = GET_DATA_BIT(lines, j);
+                else
+                    return NULL;
+                if (sval >= ncolors)
+                    L_WARNING("pixel value out of bounds\n", procName);
+                else
+                    lined[j] = lut[sval];
+            }
+        }
+        LEPT_FREE(lut);
+    }
+
+    LEPT_FREE(rmap);
+    LEPT_FREE(gmap);
+    LEPT_FREE(bmap);
+    LEPT_FREE(amap);
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ *              Add colormap losslessly (8 to 8)               *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixAddGrayColormap8()
+ *
+ *      Input:  pixs (8 bpp)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If pixs has a colormap, this is a no-op.
+ */
+l_int32
+pixAddGrayColormap8(PIX  *pixs)
+{
+PIXCMAP  *cmap;
+
+    PROCNAME("pixAddGrayColormap8");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (pixGetColormap(pixs))
+        return 0;
+
+    cmap = pixcmapCreateLinear(8, 256);
+    pixSetColormap(pixs, cmap);
+    return 0;
+}
+
+
+/*!
+ *  pixAddMinimalGrayColormap8()
+ *
+ *      Input:  pixs (8 bpp)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This generates a colormapped version of the input image
+ *          that has the same number of colormap entries as the
+ *          input image has unique gray levels.
+ */
+PIX *
+pixAddMinimalGrayColormap8(PIX  *pixs)
+{
+l_int32    ncolors, w, h, i, j, wplt, wpld, index, val;
+l_int32   *inta, *revmap;
+l_uint32  *datat, *datad, *linet, *lined;
+PIX       *pixt, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixAddMinimalGrayColormap8");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+
+        /* Eliminate the easy cases */
+    pixNumColors(pixs, 1, &ncolors);
+    cmap = pixGetColormap(pixs);
+    if (cmap) {
+        if (pixcmapGetCount(cmap) == ncolors)  /* irreducible */
+            return pixCopy(NULL, pixs);
+        else
+            pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    } else {
+        if (ncolors == 256) {
+            pixt = pixCopy(NULL, pixs);
+            pixAddGrayColormap8(pixt);
+            return pixt;
+        }
+        pixt = pixClone(pixs);
+    }
+
+        /* Find the gray levels and make a reverse map */
+    pixGetDimensions(pixt, &w, &h, NULL);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+    inta = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(linet, j);
+            inta[val] = 1;
+        }
+    }
+    cmap = pixcmapCreate(8);
+    revmap = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    for (i = 0, index = 0; i < 256; i++) {
+        if (inta[i]) {
+            pixcmapAddColor(cmap, i, i, i);
+            revmap[i] = index++;
+        }
+    }
+
+        /* Set all pixels in pixd to the colormap index */
+    pixd = pixCreateTemplate(pixt);
+    pixSetColormap(pixd, cmap);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(linet, j);
+            SET_DATA_BYTE(lined, j, revmap[val]);
+        }
+    }
+
+    pixDestroy(&pixt);
+    LEPT_FREE(inta);
+    LEPT_FREE(revmap);
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ *            Conversion from RGB color to grayscale           *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixConvertRGBToLuminance()
+ *
+ *      Input:  pix (32 bpp RGB)
+ *      Return: 8 bpp pix, or null on error
+ *
+ *  Notes:
+ *      (1) Use a standard luminance conversion.
+ */
+PIX *
+pixConvertRGBToLuminance(PIX *pixs)
+{
+  return pixConvertRGBToGray(pixs, 0.0, 0.0, 0.0);
+}
+
+
+/*!
+ *  pixConvertRGBToGray()
+ *
+ *      Input:  pix (32 bpp RGB)
+ *              rwt, gwt, bwt  (non-negative; these should add to 1.0,
+ *                              or use 0.0 for default)
+ *      Return: 8 bpp pix, or null on error
+ *
+ *  Notes:
+ *      (1) Use a weighted average of the RGB values.
+ */
+PIX *
+pixConvertRGBToGray(PIX       *pixs,
+                    l_float32  rwt,
+                    l_float32  gwt,
+                    l_float32  bwt)
+{
+l_int32    i, j, w, h, wpls, wpld, val;
+l_uint32   word;
+l_uint32  *datas, *lines, *datad, *lined;
+l_float32  sum;
+PIX       *pixd;
+
+    PROCNAME("pixConvertRGBToGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (rwt < 0.0 || gwt < 0.0 || bwt < 0.0)
+        return (PIX *)ERROR_PTR("weights not all >= 0.0", procName, NULL);
+
+        /* Make sure the sum of weights is 1.0; otherwise, you can get
+         * overflow in the gray value. */
+    if (rwt == 0.0 && gwt == 0.0 && bwt == 0.0) {
+        rwt = L_RED_WEIGHT;
+        gwt = L_GREEN_WEIGHT;
+        bwt = L_BLUE_WEIGHT;
+    }
+    sum = rwt + gwt + bwt;
+    if (L_ABS(sum - 1.0) > 0.0001) {  /* maintain ratios with sum == 1.0 */
+        L_WARNING("weights don't sum to 1; maintaining ratios\n", procName);
+        rwt = rwt / sum;
+        gwt = gwt / sum;
+        bwt = bwt / sum;
+    }
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            word = *(lines + j);
+            val = (l_int32)(rwt * ((word >> L_RED_SHIFT) & 0xff) +
+                            gwt * ((word >> L_GREEN_SHIFT) & 0xff) +
+                            bwt * ((word >> L_BLUE_SHIFT) & 0xff) + 0.5);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixConvertRGBToGrayFast()
+ *
+ *      Input:  pix (32 bpp RGB)
+ *      Return: 8 bpp pix, or null on error
+ *
+ *  Notes:
+ *      (1) This function should be used if speed of conversion
+ *          is paramount, and the green channel can be used as
+ *          a fair representative of the RGB intensity.  It is
+ *          several times faster than pixConvertRGBToGray().
+ *      (2) To combine RGB to gray conversion with subsampling,
+ *          use pixScaleRGBToGrayFast() instead.
+ */
+PIX *
+pixConvertRGBToGrayFast(PIX  *pixs)
+{
+l_int32    i, j, w, h, wpls, wpld, val;
+l_uint32  *datas, *lines, *datad, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixConvertRGBToGrayFast");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++, lines++) {
+            val = ((*lines) >> L_GREEN_SHIFT) & 0xff;
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixConvertRGBToGrayMinMax()
+ *
+ *      Input:  pix (32 bpp RGB)
+ *              type (L_CHOOSE_MIN, L_CHOOSE_MAX or L_CHOOSE_MAX_MIN_DIFF)
+ *      Return: 8 bpp pix, or null on error
+ *
+ *  Notes:
+ *      (1) This chooses the min, the max, or the difference between
+ *          the max and the min, of the three RGB sample values.
+ */
+PIX *
+pixConvertRGBToGrayMinMax(PIX     *pixs,
+                          l_int32  type)
+{
+l_int32    i, j, w, h, wpls, wpld, rval, gval, bval, val, minval, maxval;
+l_uint32  *datas, *lines, *datad, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixConvertRGBToGrayMinMax");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (type != L_CHOOSE_MIN && type != L_CHOOSE_MAX &&
+        type != L_CHOOSE_MAX_MIN_DIFF)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            if (type == L_CHOOSE_MIN) {
+                val = L_MIN(rval, gval);
+                val = L_MIN(val, bval);
+            } else if (type == L_CHOOSE_MAX) {
+                val = L_MAX(rval, gval);
+                val = L_MAX(val, bval);
+            } else {  /* L_CHOOSE_MAX_MIN_DIFF */
+                minval = L_MIN(rval, gval);
+                minval = L_MIN(minval, bval);
+                maxval = L_MAX(rval, gval);
+                maxval = L_MAX(maxval, bval);
+                val = maxval - minval;
+            }
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixConvertRGBToGraySatBoost()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              refval (between 1 and 255; typ. less than 128)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This returns the max component value, boosted by
+ *          the saturation. The maximum boost occurs where
+ *          the maximum component value is equal to some reference value.
+ *          This particular weighting is due to Dany Qumsiyeh.
+ *      (2) For gray pixels (zero saturation), this returns
+ *          the intensity of any component.
+ *      (3) For fully saturated pixels ('fullsat'), this rises linearly
+ *          with the max value and has a slope equal to 255 divided
+ *          by the reference value; for a max value greater than
+ *          the reference value, it is clipped to 255.
+ *      (4) For saturation values in between, the output is a linear
+ *          combination of (2) and (3), weighted by saturation.
+ *          It falls between these two curves, and does not exceed 255.
+ *      (5) This can be useful for distinguishing an object that has nonzero
+ *          saturation from a gray background.  For this, the refval
+ *          should be chosen near the expected value of the background,
+ *          to achieve maximum saturation boost there.
+ */
+PIX  *
+pixConvertRGBToGraySatBoost(PIX     *pixs,
+                            l_int32  refval)
+{
+l_int32     w, h, d, i, j, wplt, wpld;
+l_int32     rval, gval, bval, sval, minrg, maxrg, min, max, delta;
+l_int32     fullsat, newval;
+l_float32  *invmax, *ratio;
+l_uint32   *linet, *lined, *datat, *datad;
+PIX        *pixt, *pixd;
+
+    PROCNAME("pixConvertRGBToGraySatBoost");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32 && !pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs not cmapped or rgb", procName, NULL);
+    if (refval < 1 || refval > 255)
+        return (PIX *)ERROR_PTR("refval not in [1 ... 255]", procName, NULL);
+
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+    pixd = pixCreate(w, h, 8);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    wplt = pixGetWpl(pixt);
+    datat = pixGetData(pixt);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    invmax = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32));
+    ratio = (l_float32 *)LEPT_CALLOC(256, sizeof(l_float32));
+    for (i = 1; i < 256; i++) {  /* i == 0  --> delta = sval = newval = 0 */
+        invmax[i] = 1.0 / (l_float32)i;
+        ratio[i] = (l_float32)i / (l_float32)refval;
+    }
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            extractRGBValues(linet[j], &rval, &gval, &bval);
+            minrg = L_MIN(rval, gval);
+            min = L_MIN(minrg, bval);
+            maxrg = L_MAX(rval, gval);
+            max = L_MAX(maxrg, bval);
+            delta = max - min;
+            if (delta == 0)  /* gray; no chroma */
+                sval = 0;
+            else
+                sval = (l_int32)(255. * (l_float32)delta * invmax[max] + 0.5);
+
+            fullsat = L_MIN(255, 255 * ratio[max]);
+            newval = (sval * fullsat + (255 - sval) * max) / 255;
+            SET_DATA_BYTE(lined, j, newval);
+        }
+    }
+
+    pixDestroy(&pixt);
+    LEPT_FREE(invmax);
+    LEPT_FREE(ratio);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                  Conversion from grayscale to colormap                    *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertGrayToColormap()
+ *
+ *      Input:  pixs (2, 4 or 8 bpp grayscale)
+ *      Return: pixd (2, 4 or 8 bpp with colormap), or null on error
+ *
+ *  Notes:
+ *      (1) This is a simple interface for adding a colormap to a
+ *          2, 4 or 8 bpp grayscale image without causing any
+ *          quantization.  There is some similarity to operations
+ *          in grayquant.c, such as pixThresholdOn8bpp(), where
+ *          the emphasis is on quantization with an arbitrary number
+ *          of levels, and a colormap is an option.
+ *      (2) Returns a copy if pixs already has a colormap.
+ *      (3) For 8 bpp src, this is a lossless transformation.
+ *      (4) For 2 and 4 bpp src, this generates a colormap that
+ *          assumes full coverage of the gray space, with equally spaced
+ *          levels: 4 levels for d = 2 and 16 levels for d = 4.
+ *      (5) In all cases, the depth of the dest is the same as the src.
+ */
+PIX *
+pixConvertGrayToColormap(PIX  *pixs)
+{
+l_int32    d;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixConvertGrayToColormap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 2 && d != 4 && d != 8)
+        return (PIX *)ERROR_PTR("pixs not 2, 4 or 8 bpp", procName, NULL);
+
+    if (pixGetColormap(pixs)) {
+        L_WARNING("pixs already has a colormap\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+    if (d == 8)  /* lossless conversion */
+        return pixConvertGrayToColormap8(pixs, 2);
+
+        /* Build a cmap with equally spaced target values over the
+         * full 8 bpp range. */
+    pixd = pixCopy(NULL, pixs);
+    cmap = pixcmapCreateLinear(d, 1 << d);
+    pixSetColormap(pixd, cmap);
+    pixCopyInputFormat(pixd, pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvertGrayToColormap8()
+ *
+ *      Input:  pixs (8 bpp grayscale)
+ *              mindepth (of pixd; valid values are 2, 4 and 8)
+ *      Return: pixd (2, 4 or 8 bpp with colormap), or null on error
+ *
+ *  Notes:
+ *      (1) Returns a copy if pixs already has a colormap.
+ *      (2) This is a lossless transformation; there is no quantization.
+ *          We compute the number of different gray values in pixs,
+ *          and construct a colormap that has exactly these values.
+ *      (3) 'mindepth' is the minimum depth of pixd.  If mindepth == 8,
+ *          pixd will always be 8 bpp.  Let the number of different
+ *          gray values in pixs be ngray.  If mindepth == 4, we attempt
+ *          to save pixd as a 4 bpp image, but if ngray > 16,
+ *          pixd must be 8 bpp.  Likewise, if mindepth == 2,
+ *          the depth of pixd will be 2 if ngray <= 4 and 4 if ngray > 4
+ *          but <= 16.
+ */
+PIX *
+pixConvertGrayToColormap8(PIX     *pixs,
+                          l_int32  mindepth)
+{
+l_int32    ncolors, w, h, depth, i, j, wpls, wpld;
+l_int32    index, num, val, newval;
+l_int32    array[256];
+l_uint32  *lines, *lined, *datas, *datad;
+NUMA      *na;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixConvertGrayToColormap8");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (mindepth != 2 && mindepth != 4 && mindepth != 8) {
+        L_WARNING("invalid value of mindepth; setting to 8\n", procName);
+        mindepth = 8;
+    }
+
+    if (pixGetColormap(pixs)) {
+        L_WARNING("pixs already has a colormap\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+    na = pixGetGrayHistogram(pixs, 1);
+    numaGetCountRelativeToZero(na, L_GREATER_THAN_ZERO, &ncolors);
+    if (mindepth == 8 || ncolors > 16)
+        depth = 8;
+    else if (mindepth == 4 || ncolors > 4)
+        depth = 4;
+    else
+        depth = 2;
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixCreate(w, h, depth);
+    cmap = pixcmapCreate(depth);
+    pixSetColormap(pixd, cmap);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+
+    index = 0;
+    for (i = 0; i < 256; i++) {
+        numaGetIValue(na, i, &num);
+        if (num > 0) {
+            pixcmapAddColor(cmap, i, i, i);
+            array[i] = index;
+            index++;
+        }
+    }
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(lines, j);
+            newval = array[val];
+            if (depth == 2)
+                SET_DATA_DIBIT(lined, j, newval);
+            else if (depth == 4)
+                SET_DATA_QBIT(lined, j, newval);
+            else  /* depth == 8 */
+                SET_DATA_BYTE(lined, j, newval);
+        }
+    }
+
+    numaDestroy(&na);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                Colorizing conversion from grayscale to color              *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixColorizeGray()
+ *
+ *      Input:  pixs (8 bpp gray; 2, 4 or 8 bpp colormapped)
+ *              color (32 bit rgba pixel)
+ *              cmapflag (1 for result to have colormap; 0 for RGB)
+ *      Return: pixd (8 bpp colormapped or 32 bpp rgb), or null on error
+ *
+ *  Notes:
+ *      (1) This applies the specific color to the grayscale image.
+ *      (2) If pixs already has a colormap, it is removed to gray
+ *          before colorizing.
+ */
+PIX *
+pixColorizeGray(PIX      *pixs,
+                l_uint32  color,
+                l_int32   cmapflag)
+{
+l_int32    i, j, w, h, wplt, wpld, val8;
+l_uint32  *datad, *datat, *lined, *linet, *tab;
+PIX       *pixt, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixColorizeGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8 && !pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs not 8 bpp or cmapped", procName, NULL);
+
+    if (pixGetColormap(pixs))
+        pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else
+        pixt = pixClone(pixs);
+
+    cmap = pixcmapGrayToColor(color);
+    if (cmapflag) {
+        pixd = pixCopy(NULL, pixt);
+        pixSetColormap(pixd, cmap);
+        pixDestroy(&pixt);
+        return pixd;
+    }
+
+        /* Make an RGB pix */
+    pixcmapToRGBTable(cmap, &tab, NULL);
+    pixGetDimensions(pixt, &w, &h, NULL);
+    pixd = pixCreate(w, h, 32);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        linet = datat + i * wplt;
+        for (j = 0; j < w; j++) {
+            val8 = GET_DATA_BYTE(linet, j);
+            lined[j] = tab[val8];
+        }
+    }
+
+    pixDestroy(&pixt);
+    pixcmapDestroy(&cmap);
+    LEPT_FREE(tab);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                    Conversion from RGB color to colormap                  *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertRGBToColormap()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              ditherflag (1 to dither, 0 otherwise)
+ *      Return: pixd (2, 4 or 8 bpp with colormap), or null on error
+ *
+ *  Notes:
+ *      (1) This function has two relatively simple modes of color
+ *          quantization:
+ *            (a) If the image is made orthographically and has not more
+ *                than 256 'colors' at the level 4 octcube leaves,
+ *                it is quantized nearly exactly.  The ditherflag
+ *                is ignored.
+ *            (b) Most natural images have more than 256 different colors;
+ *                in that case we use adaptive octree quantization,
+ *                with dithering if requested.
+ *      (2) If there are not more than 256 occupied level 4 octcubes,
+ *          the color in the colormap that represents all pixels in
+ *          one of those octcubes is given by the first pixel that
+ *          falls into that octcube.
+ *      (3) If there are more than 256 colors, we use adaptive octree
+ *          color quantization.
+ *      (4) Dithering gives better visual results on images where
+ *          there is a color wash (a slow variation of color), but it
+ *          is about twice as slow and results in significantly larger
+ *          files when losslessly compressed (e.g., into png).
+ */
+PIX *
+pixConvertRGBToColormap(PIX     *pixs,
+                        l_int32  ditherflag)
+{
+l_int32  ncolors;
+NUMA    *na;
+PIX     *pixd;
+
+    PROCNAME("pixConvertRGBToColormap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (pixGetSpp(pixs) == 4)
+        L_WARNING("pixs has alpha; removing\n", procName);
+
+        /* Get the histogram and count the number of occupied level 4
+         * leaf octcubes.  We don't yet know if this is the number of
+         * actual colors, but if it's not, all pixels falling into
+         * the same leaf octcube will be assigned to the color of the
+         * first pixel that lands there. */
+    na = pixOctcubeHistogram(pixs, 4, &ncolors);
+
+        /* If there are too many occupied leaf octcubes to be
+         * represented directly in a colormap, fall back to octree
+         * quantization, optionally with dithering. */
+    if (ncolors > 256) {
+        numaDestroy(&na);
+        if (ditherflag)
+            L_INFO("More than 256 colors; using octree quant with dithering\n",
+                   procName);
+        else
+            L_INFO("More than 256 colors; using octree quant; no dithering\n",
+                   procName);
+        return pixOctreeColorQuant(pixs, 240, ditherflag);
+    }
+
+        /* There are not more than 256 occupied leaf octcubes.
+         * Quantize to those octcubes. */
+    pixd = pixFewColorsOctcubeQuant2(pixs, 4, na, ncolors, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    numaDestroy(&na);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *        Quantization for relatively small number of colors in source       *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixQuantizeIfFewColors()
+ *
+ *      Input:  pixs (8 bpp gray or 32 bpp rgb)
+ *              maxcolors (max number of colors allowed to be returned
+ *                         from pixColorsForQuantization(); use 0 for default)
+ *              mingraycolors (min number of gray levels that a grayscale
+ *                             image is quantized to; use 0 for default)
+ *              octlevel (for octcube quantization: 3 or 4)
+ *              &pixd (<return> 2,4 or 8 bpp quantized; null if too many colors)
+ *      Return: 0 if OK, 1 on error or if pixs can't be quantized into
+ *              a small number of colors.
+ *
+ *  Notes:
+ *      (1) This is a wrapper that tests if the pix can be quantized
+ *          with good quality using a small number of colors.  If so,
+ *          it does the quantization, defining a colormap and using
+ *          pixels whose value is an index into the colormap.
+ *      (2) If the image has color, it is quantized with 8 bpp pixels.
+ *          If the image is essentially grayscale, the pixels are
+ *          either 4 or 8 bpp, depending on the size of the required
+ *          colormap.
+ *      (3) @octlevel = 4 generates a larger colormap and larger
+ *          compressed image than @octlevel = 3.  If image quality is
+ *          important, you should use @octlevel = 4.
+ *      (4) If the image already has a colormap, it returns a clone.
+ */
+l_int32
+pixQuantizeIfFewColors(PIX     *pixs,
+                       l_int32  maxcolors,
+                       l_int32  mingraycolors,
+                       l_int32  octlevel,
+                       PIX    **ppixd)
+{
+l_int32  d, ncolors, iscolor, graycolors;
+PIX     *pixg, *pixd;
+
+    PROCNAME("pixQuantizeIfFewColors");
+
+    if (!ppixd)
+        return ERROR_INT("&pixd not defined", procName, 1);
+    *ppixd = NULL;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetColormap(pixs) != NULL) {
+        *ppixd = pixClone(pixs);
+        return 0;
+    }
+    if (maxcolors <= 0)
+        maxcolors = 15;  /* default */
+    if (maxcolors > 50)
+        L_WARNING("maxcolors > 50; very large!\n", procName);
+    if (mingraycolors <= 0)
+        mingraycolors = 10;  /* default */
+    if (mingraycolors > 30)
+        L_WARNING("mingraycolors > 30; very large!\n", procName);
+    if (octlevel != 3 && octlevel != 4) {
+        L_WARNING("invalid octlevel; setting to 3\n", procName);
+        octlevel = 3;
+    }
+
+        /* Test the number of colors.  For color, the octcube leaves
+         * are at level 4. */
+    pixColorsForQuantization(pixs, 0, &ncolors, &iscolor, 0);
+    if (ncolors > maxcolors)
+        return ERROR_INT("too many colors", procName, 1);
+
+        /* Quantize!
+         *  (1) For color:
+         *      If octlevel == 4, try to quantize to an octree where
+         *      the octcube leaves are at level 4. If that fails,
+         *      back off to level 3.
+         *      If octlevel == 3, quantize to level 3 directly.
+         *      For level 3, the quality is usually good enough and there
+         *      is negligible chance of getting more than 256 colors.
+         *  (2) For grayscale, multiply ncolors by 1.5 for extra quality,
+         *      but use at least mingraycolors and not more than 256. */
+    if (iscolor) {
+        pixd = pixFewColorsOctcubeQuant1(pixs, octlevel);
+        if (!pixd) {  /* backoff */
+            pixd = pixFewColorsOctcubeQuant1(pixs, octlevel - 1);
+            if (octlevel == 3)  /* shouldn't happen */
+                L_WARNING("quantized at level 2; low quality\n", procName);
+        }
+    } else { /* image is really grayscale */
+        if (d == 32)
+            pixg = pixConvertRGBToLuminance(pixs);
+        else
+            pixg = pixClone(pixs);
+        graycolors = L_MAX(mingraycolors, (l_int32)(1.5 * ncolors));
+        graycolors = L_MIN(graycolors, 256);
+        if (graycolors < 16)
+            pixd = pixThresholdTo4bpp(pixg, graycolors, 1);
+        else
+            pixd = pixThresholdOn8bpp(pixg, graycolors, 1);
+        pixDestroy(&pixg);
+    }
+    *ppixd = pixd;
+
+    if (!pixd)
+        return ERROR_INT("pixd not made", procName, 1);
+    pixCopyInputFormat(pixd, pixs);
+    return 0;
+}
+
+
+
+/*---------------------------------------------------------------------------*
+ *                    Conversion from 16 bpp to 8 bpp                        *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvert16To8()
+ *
+ *      Input:  pixs (16 bpp)
+ *              type (L_LS_BYTE, L_MS_BYTE, L_CLIP_TO_FF)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) For each dest pixel, use either the LSB, the MSB, or the
+ *          min(val, 255) for each 16-bit src pixel.
+ */
+PIX *
+pixConvert16To8(PIX     *pixs,
+                l_int32  type)
+{
+l_uint16   dword;
+l_int32    w, h, wpls, wpld, i, j;
+l_uint32   sword, first, second;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixConvert16To8");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 16)
+        return (PIX *)ERROR_PTR("pixs not 16 bpp", procName, NULL);
+    if (type != L_LS_BYTE && type != L_MS_BYTE && type != L_CLIP_TO_FF)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    wpls = pixGetWpl(pixs);
+    datas = pixGetData(pixs);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+
+        /* Convert 2 pixels at a time */
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        if (type == L_LS_BYTE) {
+            for (j = 0; j < wpls; j++) {
+                sword = *(lines + j);
+                dword = ((sword >> 8) & 0xff00) | (sword & 0xff);
+                SET_DATA_TWO_BYTES(lined, j, dword);
+            }
+        } else if (type == L_MS_BYTE) {
+            for (j = 0; j < wpls; j++) {
+                sword = *(lines + j);
+                dword = ((sword >> 16) & 0xff00) | ((sword >> 8) & 0xff);
+                SET_DATA_TWO_BYTES(lined, j, dword);
+            }
+        } else {  /* type == L_CLIP_TO_FF */
+            for (j = 0; j < wpls; j++) {
+                sword = *(lines + j);
+                first = (sword >> 24) ? 255 : ((sword >> 16) & 0xff);
+                second = ((sword >> 8) & 0xff) ? 255 : (sword & 0xff);
+                dword = (first << 8) | second;
+                SET_DATA_TWO_BYTES(lined, j, dword);
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+
+/*---------------------------------------------------------------------------*
+ *                Conversion from grayscale to false color
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertGrayToFalseColor()
+ *
+ *      Input:  pixs (8 or 16 bpp grayscale)
+ *              gamma factor (0.0 or 1.0 for default; > 1.0 for brighter;
+ *                            2.0 is quite nice)
+ *      Return: pixd (8 bpp with colormap), or null on error
+ *
+ *  Notes:
+ *      (1) For 8 bpp input, this simply adds a colormap to the input image.
+ *      (2) For 16 bpp input, it first converts to 8 bpp, using the MSB,
+ *          and then adds the colormap.
+ *      (3) The colormap is modeled after the Matlab "jet" configuration.
+ */
+PIX *
+pixConvertGrayToFalseColor(PIX       *pixs,
+                           l_float32  gamma)
+{
+l_int32    d, i, rval, bval, gval;
+l_int32   *curve;
+l_float32  invgamma, x;
+PIX       *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixConvertGrayToFalseColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 16)
+        return (PIX *)ERROR_PTR("pixs not 8 or 16 bpp", procName, NULL);
+
+    if (d == 16) {
+        pixd = pixConvert16To8(pixs, L_MS_BYTE);
+    } else {  /* d == 8 */
+        if (pixGetColormap(pixs))
+            pixd = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+        else
+            pixd = pixCopy(NULL, pixs);
+    }
+    if (!pixd)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    if ((cmap = pixcmapCreate(8)) == NULL)
+        return (PIX *)ERROR_PTR("cmap not made", procName, NULL);
+    pixSetColormap(pixd, cmap);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+        /* Generate curve for transition part of color map */
+    if ((curve = (l_int32 *)LEPT_CALLOC(64, sizeof(l_int32)))== NULL)
+        return (PIX *)ERROR_PTR("curve not made", procName, NULL);
+    if (gamma == 0.0) gamma = 1.0;
+    invgamma = 1. / gamma;
+    for (i = 0; i < 64; i++) {
+        x = (l_float32)i / 64.;
+        curve[i] = (l_int32)(255. * powf(x, invgamma) + 0.5);
+    }
+
+    for (i = 0; i < 256; i++) {
+        if (i < 32) {
+            rval = 0;
+            gval = 0;
+            bval = curve[i + 32];
+        } else if (i < 96) {   /* 32 - 95 */
+            rval = 0;
+            gval = curve[i - 32];
+            bval = 255;
+        } else if (i < 160) {  /* 96 - 159 */
+            rval = curve[i - 96];
+            gval = 255;
+            bval = curve[159 - i];
+        } else if (i < 224) {  /* 160 - 223 */
+            rval = 255;
+            gval = curve[223 - i];
+            bval = 0;
+        } else {  /* 224 - 255 */
+            rval = curve[287 - i];
+            gval = 0;
+            bval = 0;
+        }
+        pixcmapAddColor(cmap, rval, gval, bval);
+    }
+
+    LEPT_FREE(curve);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *         Unpacking conversion from 1 bpp to 2, 4, 8, 16 and 32 bpp         *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixUnpackBinary()
+ *
+ *      Input:  pixs (1 bpp)
+ *              depth (of destination: 2, 4, 8, 16 or 32 bpp)
+ *              invert (0:  binary 0 --> grayscale 0
+ *                          binary 1 --> grayscale 0xff...
+ *                      1:  binary 0 --> grayscale 0xff...
+ *                          binary 1 --> grayscale 0)
+ *      Return: pixd (2, 4, 8, 16 or 32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This function calls special cases of pixConvert1To*(),
+ *          for 2, 4, 8, 16 and 32 bpp destinations.
+ */
+PIX *
+pixUnpackBinary(PIX     *pixs,
+                l_int32  depth,
+                l_int32  invert)
+{
+PIX  *pixd;
+
+    PROCNAME("pixUnpackBinary");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (depth != 2 && depth != 4 && depth != 8 && depth != 16 && depth != 32)
+        return (PIX *)ERROR_PTR("depth not 2, 4, 8, 16 or 32 bpp",
+                                procName, NULL);
+
+    if (depth == 2) {
+        if (invert == 0)
+            pixd = pixConvert1To2(NULL, pixs, 0, 3);
+        else  /* invert bits */
+            pixd = pixConvert1To2(NULL, pixs, 3, 0);
+    } else if (depth == 4) {
+        if (invert == 0)
+            pixd = pixConvert1To4(NULL, pixs, 0, 15);
+        else  /* invert bits */
+            pixd = pixConvert1To4(NULL, pixs, 15, 0);
+    } else if (depth == 8) {
+        if (invert == 0)
+            pixd = pixConvert1To8(NULL, pixs, 0, 255);
+        else  /* invert bits */
+            pixd = pixConvert1To8(NULL, pixs, 255, 0);
+    } else if (depth == 16) {
+        if (invert == 0)
+            pixd = pixConvert1To16(NULL, pixs, 0, 0xffff);
+        else  /* invert bits */
+            pixd = pixConvert1To16(NULL, pixs, 0xffff, 0);
+    } else {
+        if (invert == 0)
+            pixd = pixConvert1To32(NULL, pixs, 0, 0xffffffff);
+        else  /* invert bits */
+            pixd = pixConvert1To32(NULL, pixs, 0xffffffff, 0);
+    }
+
+    pixCopyInputFormat(pixd, pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvert1To16()
+ *
+ *      Input:  pixd (<optional> 16 bpp, can be null)
+ *              pixs (1 bpp)
+ *              val0 (16 bit value to be used for 0s in pixs)
+ *              val1 (16 bit value to be used for 1s in pixs)
+ *      Return: pixd (16 bpp)
+ *
+ *  Notes:
+ *      (1) If pixd is null, a new pix is made.
+ *      (2) If pixd is not null, it must be of equal width and height
+ *          as pixs.  It is always returned.
+ */
+PIX *
+pixConvert1To16(PIX      *pixd,
+                PIX      *pixs,
+                l_uint16  val0,
+                l_uint16  val1)
+{
+l_int32    w, h, i, j, dibit, ndibits, wpls, wpld;
+l_uint16   val[2];
+l_uint32   index;
+l_uint32  *tab, *datas, *datad, *lines, *lined;
+
+    PROCNAME("pixConvert1To16");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (pixd) {
+        if (w != pixGetWidth(pixd) || h != pixGetHeight(pixd))
+            return (PIX *)ERROR_PTR("pix sizes unequal", procName, pixd);
+        if (pixGetDepth(pixd) != 16)
+            return (PIX *)ERROR_PTR("pixd not 16 bpp", procName, pixd);
+    } else {
+        if ((pixd = pixCreate(w, h, 16)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+        /* Use a table to convert 2 src bits at a time */
+    if ((tab = (l_uint32 *)LEPT_CALLOC(4, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("tab not made", procName, NULL);
+    val[0] = val0;
+    val[1] = val1;
+    for (index = 0; index < 4; index++) {
+        tab[index] = (val[(index >> 1) & 1] << 16) | val[index & 1];
+    }
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    ndibits = (w + 1) / 2;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < ndibits; j++) {
+            dibit = GET_DATA_DIBIT(lines, j);
+            lined[j] = tab[dibit];
+        }
+    }
+
+    LEPT_FREE(tab);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvert1To32()
+ *
+ *      Input:  pixd (<optional> 32 bpp, can be null)
+ *              pixs (1 bpp)
+ *              val0 (32 bit value to be used for 0s in pixs)
+ *              val1 (32 bit value to be used for 1s in pixs)
+ *      Return: pixd (32 bpp)
+ *
+ *  Notes:
+ *      (1) If pixd is null, a new pix is made.
+ *      (2) If pixd is not null, it must be of equal width and height
+ *          as pixs.  It is always returned.
+ */
+PIX *
+pixConvert1To32(PIX      *pixd,
+                PIX      *pixs,
+                l_uint32  val0,
+                l_uint32  val1)
+{
+l_int32    w, h, i, j, wpls, wpld, bit;
+l_uint32   val[2];
+l_uint32  *datas, *datad, *lines, *lined;
+
+    PROCNAME("pixConvert1To32");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (pixd) {
+        if (w != pixGetWidth(pixd) || h != pixGetHeight(pixd))
+            return (PIX *)ERROR_PTR("pix sizes unequal", procName, pixd);
+        if (pixGetDepth(pixd) != 32)
+            return (PIX *)ERROR_PTR("pixd not 32 bpp", procName, pixd);
+    } else {
+        if ((pixd = pixCreate(w, h, 32)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+    val[0] = val0;
+    val[1] = val1;
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j <w; j++) {
+            bit = GET_DATA_BIT(lines, j);
+            lined[j] = val[bit];
+        }
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                    Conversion from 1 bpp to 2 bpp                         *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvert1To2Cmap()
+ *
+ *      Input:  pixs (1 bpp)
+ *      Return: pixd (2 bpp, cmapped)
+ *
+ *  Notes:
+ *      (1) Input 0 is mapped to (255, 255, 255); 1 is mapped to (0, 0, 0)
+ */
+PIX *
+pixConvert1To2Cmap(PIX  *pixs)
+{
+PIX      *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixConvert1To2Cmap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+
+    if ((pixd = pixConvert1To2(NULL, pixs, 0, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    cmap = pixcmapCreate(2);
+    pixcmapAddColor(cmap, 255, 255, 255);
+    pixcmapAddColor(cmap, 0, 0, 0);
+    pixSetColormap(pixd, cmap);
+    pixCopyInputFormat(pixd, pixs);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixConvert1To2()
+ *
+ *      Input:  pixd (<optional> 2 bpp, can be null)
+ *              pixs (1 bpp)
+ *              val0 (2 bit value to be used for 0s in pixs)
+ *              val1 (2 bit value to be used for 1s in pixs)
+ *      Return: pixd (2 bpp)
+ *
+ *  Notes:
+ *      (1) If pixd is null, a new pix is made.
+ *      (2) If pixd is not null, it must be of equal width and height
+ *          as pixs.  It is always returned.
+ *      (3) A simple unpacking might use val0 = 0 and val1 = 3.
+ *      (4) If you want a colormapped pixd, use pixConvert1To2Cmap().
+ */
+PIX *
+pixConvert1To2(PIX     *pixd,
+               PIX     *pixs,
+               l_int32  val0,
+               l_int32  val1)
+{
+l_int32    w, h, i, j, byteval, nbytes, wpls, wpld;
+l_uint8    val[2];
+l_uint32   index;
+l_uint16  *tab;
+l_uint32  *datas, *datad, *lines, *lined;
+
+    PROCNAME("pixConvert1To2");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (pixd) {
+        if (w != pixGetWidth(pixd) || h != pixGetHeight(pixd))
+            return (PIX *)ERROR_PTR("pix sizes unequal", procName, pixd);
+        if (pixGetDepth(pixd) != 2)
+            return (PIX *)ERROR_PTR("pixd not 2 bpp", procName, pixd);
+    } else {
+        if ((pixd = pixCreate(w, h, 2)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+        /* Use a table to convert 8 src bits to 16 dest bits */
+    if ((tab = (l_uint16 *)LEPT_CALLOC(256, sizeof(l_uint16))) == NULL)
+        return (PIX *)ERROR_PTR("tab not made", procName, NULL);
+    val[0] = val0;
+    val[1] = val1;
+    for (index = 0; index < 256; index++) {
+        tab[index] = (val[(index >> 7) & 1] << 14) |
+                     (val[(index >> 6) & 1] << 12) |
+                     (val[(index >> 5) & 1] << 10) |
+                     (val[(index >> 4) & 1] << 8) |
+                     (val[(index >> 3) & 1] << 6) |
+                     (val[(index >> 2) & 1] << 4) |
+                     (val[(index >> 1) & 1] << 2) | val[index & 1];
+    }
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    nbytes = (w + 7) / 8;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < nbytes; j++) {
+            byteval = GET_DATA_BYTE(lines, j);
+            SET_DATA_TWO_BYTES(lined, j, tab[byteval]);
+        }
+    }
+
+    LEPT_FREE(tab);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                    Conversion from 1 bpp to 4 bpp                         *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvert1To4Cmap()
+ *
+ *      Input:  pixs (1 bpp)
+ *      Return: pixd (4 bpp, cmapped)
+ *
+ *  Notes:
+ *      (1) Input 0 is mapped to (255, 255, 255); 1 is mapped to (0, 0, 0)
+ */
+PIX *
+pixConvert1To4Cmap(PIX  *pixs)
+{
+PIX      *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixConvert1To4Cmap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+
+    if ((pixd = pixConvert1To4(NULL, pixs, 0, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    cmap = pixcmapCreate(4);
+    pixcmapAddColor(cmap, 255, 255, 255);
+    pixcmapAddColor(cmap, 0, 0, 0);
+    pixSetColormap(pixd, cmap);
+    pixCopyInputFormat(pixd, pixs);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixConvert1To4()
+ *
+ *      Input:  pixd (<optional> 4 bpp, can be null)
+ *              pixs (1 bpp)
+ *              val0 (4 bit value to be used for 0s in pixs)
+ *              val1 (4 bit value to be used for 1s in pixs)
+ *      Return: pixd (4 bpp)
+ *
+ *  Notes:
+ *      (1) If pixd is null, a new pix is made.
+ *      (2) If pixd is not null, it must be of equal width and height
+ *          as pixs.  It is always returned.
+ *      (3) A simple unpacking might use val0 = 0 and val1 = 15, or v.v.
+ *      (4) If you want a colormapped pixd, use pixConvert1To4Cmap().
+ */
+PIX *
+pixConvert1To4(PIX     *pixd,
+               PIX     *pixs,
+               l_int32  val0,
+               l_int32  val1)
+{
+l_int32    w, h, i, j, byteval, nbytes, wpls, wpld;
+l_uint8    val[2];
+l_uint32   index;
+l_uint32  *tab, *datas, *datad, *lines, *lined;
+
+    PROCNAME("pixConvert1To4");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (pixd) {
+        if (w != pixGetWidth(pixd) || h != pixGetHeight(pixd))
+            return (PIX *)ERROR_PTR("pix sizes unequal", procName, pixd);
+        if (pixGetDepth(pixd) != 4)
+            return (PIX *)ERROR_PTR("pixd not 4 bpp", procName, pixd);
+    } else {
+        if ((pixd = pixCreate(w, h, 4)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+        /* Use a table to convert 8 src bits to 32 bit dest word */
+    if ((tab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("tab not made", procName, NULL);
+    val[0] = val0;
+    val[1] = val1;
+    for (index = 0; index < 256; index++) {
+        tab[index] = (val[(index >> 7) & 1] << 28) |
+                     (val[(index >> 6) & 1] << 24) |
+                     (val[(index >> 5) & 1] << 20) |
+                     (val[(index >> 4) & 1] << 16) |
+                     (val[(index >> 3) & 1] << 12) |
+                     (val[(index >> 2) & 1] << 8) |
+                     (val[(index >> 1) & 1] << 4) | val[index & 1];
+    }
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    nbytes = (w + 7) / 8;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < nbytes; j++) {
+            byteval = GET_DATA_BYTE(lines, j);
+            lined[j] = tab[byteval];
+        }
+    }
+
+    LEPT_FREE(tab);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *               Conversion from 1, 2 and 4 bpp to 8 bpp                     *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvert1To8Cmap()
+ *
+ *      Input:  pixs (1 bpp)
+ *      Return: pixd (8 bpp, cmapped)
+ *
+ *  Notes:
+ *      (1) Input 0 is mapped to (255, 255, 255); 1 is mapped to (0, 0, 0)
+ */
+PIX *
+pixConvert1To8Cmap(PIX  *pixs)
+{
+PIX      *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixConvert1To8Cmap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+
+    if ((pixd = pixConvert1To8(NULL, pixs, 0, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    cmap = pixcmapCreate(8);
+    pixcmapAddColor(cmap, 255, 255, 255);
+    pixcmapAddColor(cmap, 0, 0, 0);
+    pixSetColormap(pixd, cmap);
+    pixCopyInputFormat(pixd, pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvert1To8()
+ *
+ *      Input:  pixd (<optional> 8 bpp, can be null)
+ *              pixs (1 bpp)
+ *              val0 (8 bit value to be used for 0s in pixs)
+ *              val1 (8 bit value to be used for 1s in pixs)
+ *      Return: pixd (8 bpp)
+ *
+ *  Notes:
+ *      (1) If pixd is null, a new pix is made.
+ *      (2) If pixd is not null, it must be of equal width and height
+ *          as pixs.  It is always returned.
+ *      (3) A simple unpacking might use val0 = 0 and val1 = 255, or v.v.
+ *      (4) To have a colormap associated with the 8 bpp pixd,
+ *          usepixConvert1To8Cmap().
+ */
+PIX *
+pixConvert1To8(PIX     *pixd,
+               PIX     *pixs,
+               l_uint8  val0,
+               l_uint8  val1)
+{
+l_int32    w, h, i, j, qbit, nqbits, wpls, wpld;
+l_uint8    val[2];
+l_uint32   index;
+l_uint32  *tab, *datas, *datad, *lines, *lined;
+
+    PROCNAME("pixConvert1To8");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, pixd);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (pixd) {
+        if (w != pixGetWidth(pixd) || h != pixGetHeight(pixd))
+            return (PIX *)ERROR_PTR("pix sizes unequal", procName, pixd);
+        if (pixGetDepth(pixd) != 8)
+            return (PIX *)ERROR_PTR("pixd not 8 bpp", procName, pixd);
+    } else {
+        if ((pixd = pixCreate(w, h, 8)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+        /* Use a table to convert 4 src bits at a time */
+    if ((tab = (l_uint32 *)LEPT_CALLOC(16, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("tab not made", procName, NULL);
+    val[0] = val0;
+    val[1] = val1;
+    for (index = 0; index < 16; index++) {
+        tab[index] = (val[(index >> 3) & 1] << 24) |
+                     (val[(index >> 2) & 1] << 16) |
+                     (val[(index >> 1) & 1] << 8) | val[index & 1];
+    }
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    nqbits = (w + 3) / 4;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < nqbits; j++) {
+            qbit = GET_DATA_QBIT(lines, j);
+            lined[j] = tab[qbit];
+        }
+    }
+
+    LEPT_FREE(tab);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvert2To8()
+ *
+ *      Input:  pixs (2 bpp)
+ *              val0 (8 bit value to be used for 00 in pixs)
+ *              val1 (8 bit value to be used for 01 in pixs)
+ *              val2 (8 bit value to be used for 10 in pixs)
+ *              val3 (8 bit value to be used for 11 in pixs)
+ *              cmapflag (TRUE if pixd is to have a colormap; FALSE otherwise)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *      - A simple unpacking might use val0 = 0,
+ *        val1 = 85 (0x55), val2 = 170 (0xaa), val3 = 255.
+ *      - If cmapflag is TRUE:
+ *          - The 8 bpp image is made with a colormap.
+ *          - If pixs has a colormap, the input values are ignored and
+ *            the 8 bpp image is made using the colormap
+ *          - If pixs does not have a colormap, the input values are
+ *            used to build the colormap.
+ *      - If cmapflag is FALSE:
+ *          - The 8 bpp image is made without a colormap.
+ *          - If pixs has a colormap, the input values are ignored,
+ *            the colormap is removed, and the values stored in the 8 bpp
+ *            image are from the colormap.
+ *          - If pixs does not have a colormap, the input values are
+ *            used to populate the 8 bpp image.
+ */
+PIX *
+pixConvert2To8(PIX     *pixs,
+               l_uint8  val0,
+               l_uint8  val1,
+               l_uint8  val2,
+               l_uint8  val3,
+               l_int32  cmapflag)
+{
+l_int32    w, h, i, j, nbytes, wpls, wpld, dibit, ncolor;
+l_int32    rval, gval, bval, byte;
+l_uint8    val[4];
+l_uint32   index;
+l_uint32  *tab, *datas, *datad, *lines, *lined;
+PIX       *pixd;
+PIXCMAP   *cmaps, *cmapd;
+
+    PROCNAME("pixConvert2To8");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 2)
+        return (PIX *)ERROR_PTR("pixs not 2 bpp", procName, NULL);
+
+    cmaps = pixGetColormap(pixs);
+    if (cmaps && cmapflag == FALSE)
+        return pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    if (cmapflag == TRUE) {  /* pixd will have a colormap */
+        cmapd = pixcmapCreate(8);  /* 8 bpp standard cmap */
+        if (cmaps) {  /* use the existing colormap from pixs */
+            ncolor = pixcmapGetCount(cmaps);
+            for (i = 0; i < ncolor; i++) {
+                pixcmapGetColor(cmaps, i, &rval, &gval, &bval);
+                pixcmapAddColor(cmapd, rval, gval, bval);
+            }
+        } else {  /* make a colormap from the input values */
+            pixcmapAddColor(cmapd, val0, val0, val0);
+            pixcmapAddColor(cmapd, val1, val1, val1);
+            pixcmapAddColor(cmapd, val2, val2, val2);
+            pixcmapAddColor(cmapd, val3, val3, val3);
+        }
+        pixSetColormap(pixd, cmapd);
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                dibit = GET_DATA_DIBIT(lines, j);
+                SET_DATA_BYTE(lined, j, dibit);
+            }
+        }
+        return pixd;
+    }
+
+        /* Last case: no colormap in either pixs or pixd.
+         * Use input values and build a table to convert 1 src byte
+         * (4 src pixels) at a time */
+    if ((tab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("tab not made", procName, NULL);
+    val[0] = val0;
+    val[1] = val1;
+    val[2] = val2;
+    val[3] = val3;
+    for (index = 0; index < 256; index++) {
+        tab[index] = (val[(index >> 6) & 3] << 24) |
+                     (val[(index >> 4) & 3] << 16) |
+                     (val[(index >> 2) & 3] << 8) | val[index & 3];
+    }
+
+    nbytes = (w + 3) / 4;
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < nbytes; j++) {
+            byte = GET_DATA_BYTE(lines, j);
+            lined[j] = tab[byte];
+        }
+    }
+
+    LEPT_FREE(tab);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvert4To8()
+ *
+ *      Input:  pixs (4 bpp)
+ *              cmapflag (TRUE if pixd is to have a colormap; FALSE otherwise)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *      - If cmapflag is TRUE:
+ *          - pixd is made with a colormap.
+ *          - If pixs has a colormap, it is copied and the colormap
+ *            index values are placed in pixd.
+ *          - If pixs does not have a colormap, a colormap with linear
+ *            trc is built and the pixel values in pixs are placed in
+ *            pixd as colormap index values.
+ *      - If cmapflag is FALSE:
+ *          - pixd is made without a colormap.
+ *          - If pixs has a colormap, it is removed and the values stored
+ *            in pixd are from the colormap (converted to gray).
+ *          - If pixs does not have a colormap, the pixel values in pixs
+ *            are used, with shift replication, to populate pixd.
+ */
+PIX *
+pixConvert4To8(PIX     *pixs,
+               l_int32  cmapflag)
+{
+l_int32    w, h, i, j, wpls, wpld, ncolor;
+l_int32    rval, gval, bval, byte, qbit;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+PIXCMAP   *cmaps, *cmapd;
+
+    PROCNAME("pixConvert4To8");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 4)
+        return (PIX *)ERROR_PTR("pixs not 4 bpp", procName, NULL);
+
+    cmaps = pixGetColormap(pixs);
+    if (cmaps && cmapflag == FALSE)
+        return pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w, h, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    if (cmapflag == TRUE) {  /* pixd will have a colormap */
+        cmapd = pixcmapCreate(8);
+        if (cmaps) {  /* use the existing colormap from pixs */
+            ncolor = pixcmapGetCount(cmaps);
+            for (i = 0; i < ncolor; i++) {
+                pixcmapGetColor(cmaps, i, &rval, &gval, &bval);
+                pixcmapAddColor(cmapd, rval, gval, bval);
+            }
+        } else {  /* make a colormap with a linear trc */
+            for (i = 0; i < 16; i++)
+                pixcmapAddColor(cmapd, 17 * i, 17 * i, 17 * i);
+        }
+        pixSetColormap(pixd, cmapd);
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {
+                qbit = GET_DATA_QBIT(lines, j);
+                SET_DATA_BYTE(lined, j, qbit);
+            }
+        }
+        return pixd;
+    }
+
+        /* Last case: no colormap in either pixs or pixd.
+         * Replicate the qbit value into 8 bits. */
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            qbit = GET_DATA_QBIT(lines, j);
+            byte = (qbit << 4) | qbit;
+            SET_DATA_BYTE(lined, j, byte);
+        }
+    }
+    return pixd;
+}
+
+
+
+/*---------------------------------------------------------------------------*
+ *               Unpacking conversion from 8 bpp to 16 bpp                   *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvert8To16()
+ *
+ *      Input:  pixs (8 bpp; colormap removed to gray)
+ *              leftshift (number of bits: 0 is no shift;
+ *                         8 replicates in MSB and LSB of dest)
+ *      Return: pixd (16 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) For left shift of 8, the 8 bit value is replicated in both
+ *          the MSB and the LSB of the pixels in pixd.  That way, we get
+ *          proportional mapping, with a correct map from 8 bpp white
+ *          (0xff) to 16 bpp white (0xffff).
+ */
+PIX *
+pixConvert8To16(PIX     *pixs,
+                l_int32  leftshift)
+{
+l_int32    i, j, w, h, d, wplt, wpld, val;
+l_uint32  *datat, *datad, *linet, *lined;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixConvert8To16");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (leftshift < 0 || leftshift > 8)
+        return (PIX *)ERROR_PTR("leftshift not in [0 ... 8]", procName, NULL);
+
+    if (pixGetColormap(pixs) != NULL)
+        pixt = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    else
+        pixt = pixClone(pixs);
+
+    pixd = pixCreate(w, h, 16);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datat = pixGetData(pixt);
+    datad = pixGetData(pixd);
+    wplt = pixGetWpl(pixt);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        linet = datat + i * wplt;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(linet, j);
+            if (leftshift == 8)
+                val = val | (val << leftshift);
+            else
+                val <<= leftshift;
+            SET_DATA_TWO_BYTES(lined, j, val);
+        }
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+
+/*---------------------------------------------------------------------------*
+ *                     Top-level conversion to 1 bpp                         *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertTo1()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 or 32 bpp)
+ *              threshold (for final binarization, relative to 8 bpp)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This is a top-level function, with simple default values
+ *          used in pixConvertTo8() if unpacking is necessary.
+ *      (2) Any existing colormap is removed.
+ *      (3) If the input image has 1 bpp and no colormap, the operation is
+ *          lossless and a copy is returned.
+ */
+PIX *
+pixConvertTo1(PIX     *pixs,
+              l_int32  threshold)
+{
+l_int32   d, color0, color1, rval, gval, bval;
+PIX      *pixg, *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixConvertTo1");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("depth not {1,2,4,8,16,32}", procName, NULL);
+
+    cmap = pixGetColormap(pixs);
+    if (d == 1) {
+        if (!cmap) {
+            return pixCopy(NULL, pixs);
+        } else {  /* strip the colormap off, and invert if reasonable
+                   for standard binary photometry.  */
+            pixcmapGetColor(cmap, 0, &rval, &gval, &bval);
+            color0 = rval + gval + bval;
+            pixcmapGetColor(cmap, 1, &rval, &gval, &bval);
+            color1 = rval + gval + bval;
+            pixd = pixCopy(NULL, pixs);
+            pixDestroyColormap(pixd);
+            if (color1 > color0)
+                pixInvert(pixd, pixd);
+            return pixd;
+        }
+    }
+
+        /* For all other depths, use 8 bpp as an intermediary */
+    pixg = pixConvertTo8(pixs, FALSE);
+    pixd = pixThresholdToBinary(pixg, threshold);
+    pixDestroy(&pixg);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvertTo1BySampling()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 or 32 bpp)
+ *              factor (submsampling factor; integer >= 1)
+ *              threshold (for final binarization, relative to 8 bpp)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This is a quick and dirty, top-level converter.
+ *      (2) See pixConvertTo1() for default values.
+ */
+PIX *
+pixConvertTo1BySampling(PIX     *pixs,
+                        l_int32  factor,
+                        l_int32  threshold)
+{
+l_float32  scalefactor;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixConvertTo1BySampling");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (factor < 1)
+        return (PIX *)ERROR_PTR("factor must be >= 1", procName, NULL);
+
+    scalefactor = 1. / (l_float32)factor;
+    pixt = pixScaleBySampling(pixs, scalefactor, scalefactor);
+    pixd = pixConvertTo1(pixt, threshold);
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                     Top-level conversion to 8 bpp                         *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertTo8()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 or 32 bpp)
+ *              cmapflag (TRUE if pixd is to have a colormap; FALSE otherwise)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This is a top-level function, with simple default values
+ *          for unpacking.
+ *      (2) The result, pixd, is made with a colormap if specified.
+ *          It is always a new image -- never a clone.  For example,
+ *          if d == 8, and cmapflag matches the existence of a cmap
+ *          in pixs, the operation is lossless and it returns a copy.
+ *      (3) The default values used are:
+ *          - 1 bpp: val0 = 255, val1 = 0
+ *          - 2 bpp: 4 bpp:  even increments over dynamic range
+ *          - 8 bpp: lossless if cmap matches cmapflag
+ *          - 16 bpp: use most significant byte
+ *      (4) If 32 bpp RGB, this is converted to gray.  If you want
+ *          to do color quantization, you must specify the type
+ *          explicitly, using the color quantization code.
+ */
+PIX *
+pixConvertTo8(PIX     *pixs,
+              l_int32  cmapflag)
+{
+l_int32   d;
+PIX      *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixConvertTo8");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("depth not {1,2,4,8,16,32}", procName, NULL);
+
+    if (d == 1) {
+        if (cmapflag)
+            return pixConvert1To8Cmap(pixs);
+        else
+            return pixConvert1To8(NULL, pixs, 255, 0);
+    } else if (d == 2) {
+        return pixConvert2To8(pixs, 0, 85, 170, 255, cmapflag);
+    } else if (d == 4) {
+        return pixConvert4To8(pixs, cmapflag);
+    } else if (d == 8) {
+        cmap = pixGetColormap(pixs);
+        if ((cmap && cmapflag) || (!cmap && !cmapflag)) {
+            return pixCopy(NULL, pixs);
+        } else if (cmap) {  /* !cmapflag */
+            return pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+        } else {  /* !cmap && cmapflag; add colormap to pixd */
+            pixd = pixCopy(NULL, pixs);
+            pixAddGrayColormap8(pixd);
+            return pixd;
+        }
+    } else if (d == 16) {
+        pixd = pixConvert16To8(pixs, L_MS_BYTE);
+        if (cmapflag)
+            pixAddGrayColormap8(pixd);
+        return pixd;
+    } else { /* d == 32 */
+        pixd = pixConvertRGBToLuminance(pixs);
+        if (cmapflag)
+            pixAddGrayColormap8(pixd);
+        return pixd;
+    }
+}
+
+
+/*!
+ *  pixConvertTo8BySampling()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 or 32 bpp)
+ *              factor (submsampling factor; integer >= 1)
+ *              cmapflag (TRUE if pixd is to have a colormap; FALSE otherwise)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This is a fast, quick/dirty, top-level converter.
+ *      (2) See pixConvertTo8() for default values.
+ */
+PIX *
+pixConvertTo8BySampling(PIX     *pixs,
+                        l_int32  factor,
+                        l_int32  cmapflag)
+{
+l_float32  scalefactor;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixConvertTo8BySampling");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (factor < 1)
+        return (PIX *)ERROR_PTR("factor must be >= 1", procName, NULL);
+
+    scalefactor = 1. / (l_float32)factor;
+    pixt = pixScaleBySampling(pixs, scalefactor, scalefactor);
+    pixd = pixConvertTo8(pixt, cmapflag);
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvertTo8Color()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 or 32 bpp)
+ *              dither (1 to dither if necessary; 0 otherwise)
+ *      Return: pixd (8 bpp, cmapped), or null on error
+ *
+ *  Notes:
+ *      (1) This is a top-level function, with simple default values
+ *          for unpacking.
+ *      (2) The result, pixd, is always made with a colormap.
+ *      (3) If d == 8, the operation is lossless and it returns a copy.
+ *      (4) The default values used for increasing depth are:
+ *          - 1 bpp: val0 = 255, val1 = 0
+ *          - 2 bpp: 4 bpp:  even increments over dynamic range
+ *      (5) For 16 bpp, use the most significant byte.
+ *      (6) For 32 bpp RGB, use octcube quantization with optional dithering.
+ */
+PIX *
+pixConvertTo8Color(PIX     *pixs,
+                   l_int32  dither)
+{
+l_int32  d;
+
+    PROCNAME("pixConvertTo8Color");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("depth not {1,2,4,8,16,32}", procName, NULL);
+
+    if (d != 32)
+        return pixConvertTo8(pixs, 1);
+
+    return pixConvertRGBToColormap(pixs, dither);
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                    Top-level conversion to 16 bpp                         *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertTo16()
+ *
+ *      Input:  pixs (1, 8 bpp)
+ *      Return: pixd (16 bpp), or null on error
+ *
+ *  Usage: Top-level function, with simple default values for unpacking.
+ *      1 bpp:  val0 = 0xffff, val1 = 0
+ *      8 bpp:  replicates the 8 bit value in both the MSB and LSB
+ *              of the 16 bit pixel.
+ */
+PIX *
+pixConvertTo16(PIX  *pixs)
+{
+l_int32  d;
+
+    PROCNAME("pixConvertTo16");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    d = pixGetDepth(pixs);
+    if (d == 1)
+        return pixConvert1To16(NULL, pixs, 0xffff, 0);
+    else if (d == 8)
+        return pixConvert8To16(pixs, 8);
+    else
+        return (PIX *)ERROR_PTR("src depth not 1 or 8 bpp", procName, NULL);
+}
+
+
+
+/*---------------------------------------------------------------------------*
+ *                    Top-level conversion to 32 bpp                         *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertTo32()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 or 32 bpp)
+ *      Return: pixd (32 bpp), or null on error
+ *
+ *  Usage: Top-level function, with simple default values for unpacking.
+ *      1 bpp:  val0 = 255, val1 = 0
+ *              and then replication into R, G and B components
+ *      2 bpp:  if colormapped, use the colormap values; otherwise,
+ *              use val0 = 0, val1 = 0x55, val2 = 0xaa, val3 = 255
+ *              and replicate gray into R, G and B components
+ *      4 bpp:  if colormapped, use the colormap values; otherwise,
+ *              replicate 2 nybs into a byte, and then into R,G,B components
+ *      8 bpp:  if colormapped, use the colormap values; otherwise,
+ *              replicate gray values into R, G and B components
+ *      16 bpp: replicate MSB into R, G and B components
+ *      24 bpp: unpack the pixels, maintaining word alignment on each scanline
+ *      32 bpp: makes a copy
+ *
+ *  Notes:
+ *      (1) Never returns a clone of pixs.
+ *      (2) Implicit assumption about RGB component ordering.
+ */
+PIX *
+pixConvertTo32(PIX  *pixs)
+{
+l_int32  d;
+PIX     *pixt, *pixd;
+
+    PROCNAME("pixConvertTo32");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    d = pixGetDepth(pixs);
+    if (d == 1) {
+        return pixConvert1To32(NULL, pixs, 0xffffffff, 0);
+    } else if (d == 2) {
+        pixt = pixConvert2To8(pixs, 0, 85, 170, 255, TRUE);
+        pixd = pixConvert8To32(pixt);
+        pixDestroy(&pixt);
+        return pixd;
+    } else if (d == 4) {
+        pixt = pixConvert4To8(pixs, TRUE);
+        pixd = pixConvert8To32(pixt);
+        pixDestroy(&pixt);
+        return pixd;
+    } else if (d == 8) {
+        return pixConvert8To32(pixs);
+    } else if (d == 16) {
+        pixt = pixConvert16To8(pixs, L_MS_BYTE);
+        pixd = pixConvert8To32(pixt);
+        pixDestroy(&pixt);
+        return pixd;
+    } else if (d == 24) {
+        return pixConvert24To32(pixs);
+    } else if (d == 32) {
+        return pixCopy(NULL, pixs);
+    } else {
+        return (PIX *)ERROR_PTR("depth not 1, 2, 4, 8, 16, 32 bpp",
+                                procName, NULL);
+    }
+}
+
+
+/*!
+ *  pixConvertTo32BySampling()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 or 32 bpp)
+ *              factor (submsampling factor; integer >= 1)
+ *      Return: pixd (32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This is a fast, quick/dirty, top-level converter.
+ *      (2) See pixConvertTo32() for default values.
+ */
+PIX *
+pixConvertTo32BySampling(PIX     *pixs,
+                         l_int32  factor)
+{
+l_float32  scalefactor;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixConvertTo32BySampling");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (factor < 1)
+        return (PIX *)ERROR_PTR("factor must be >= 1", procName, NULL);
+
+    scalefactor = 1. / (l_float32)factor;
+    pixt = pixScaleBySampling(pixs, scalefactor, scalefactor);
+    pixd = pixConvertTo32(pixt);
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvert8To32()
+ *
+ *      Input:  pix (8 bpp)
+ *      Return: 32 bpp rgb pix, or null on error
+ *
+ *  Notes:
+ *      (1) If there is no colormap, replicates the gray value
+ *          into the 3 MSB of the dest pixel.
+ *      (2) Implicit assumption about RGB component ordering.
+ */
+PIX *
+pixConvert8To32(PIX  *pixs)
+{
+l_int32    i, j, w, h, wpls, wpld, val;
+l_uint32  *datas, *datad, *lines, *lined;
+l_uint32  *tab;
+PIX       *pixd;
+
+    PROCNAME("pixConvert8To32");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+
+    if (pixGetColormap(pixs))
+        return pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+
+        /* Replication table */
+    if ((tab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("tab not made", procName, NULL);
+    for (i = 0; i < 256; i++)
+      tab[i] = (i << 24) | (i << 16) | (i << 8);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if ((pixd = pixCreate(w, h, 32)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val = GET_DATA_BYTE(lines, j);
+            lined[j] = tab[val];
+        }
+    }
+
+    LEPT_FREE(tab);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *           Top-level conversion to 8 or 32 bpp, without colormap           *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertTo8Or32()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16, with or without colormap; or 32 bpp rgb)
+ *              copyflag (use 0 to return clone if pixs does not need to
+ *                         be changed; 1 to return a copy in those situations)
+ *              warnflag (1 to issue warning if colormap is removed; else 0)
+ *      Return: pixd (8 bpp grayscale or 32 bpp rgb), or null on error
+ *
+ *  Notes:
+ *      (1) If there is a colormap, the colormap is removed to 8 or 32 bpp,
+ *          depending on whether the colors in the colormap are all gray.
+ *      (2) If the input is either rgb or 8 bpp without a colormap,
+ *          this returns either a clone or a copy, depending on @copyflag.
+ *      (3) Otherwise, the pix is converted to 8 bpp grayscale.
+ *          In all cases, pixd does not have a colormap.
+ */
+PIX *
+pixConvertTo8Or32(PIX     *pixs,
+                  l_int32  copyflag,
+                  l_int32  warnflag)
+{
+l_int32  d;
+PIX     *pixd;
+
+    PROCNAME("pixConvertTo8Or32");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    d = pixGetDepth(pixs);
+    if (pixGetColormap(pixs)) {
+        if (warnflag) L_WARNING("pix has colormap; removing\n", procName);
+        pixd = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    } else if (d == 8 || d == 32) {
+        if (copyflag == 0)
+            pixd = pixClone(pixs);
+        else
+            pixd = pixCopy(NULL, pixs);
+    } else {
+        pixd = pixConvertTo8(pixs, 0);
+    }
+
+        /* Sanity check on result */
+    d = pixGetDepth(pixd);
+    if (d != 8 && d != 32) {
+        pixDestroy(&pixd);
+        return (PIX *)ERROR_PTR("depth not 8 or 32 bpp", procName, NULL);
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                 Conversion between 24 bpp and 32 bpp rgb                  *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvert24To32()
+ *
+ *      Input:  pixs (24 bpp rgb)
+ *      Return: pixd (32 bpp rgb), or null on error
+ *
+ *  Notes:
+ *      (1) 24 bpp rgb pix are not supported in leptonica, except for a small
+ *          number of formatted write operations.  The data is a byte array,
+ *          with pixels in order r,g,b, and padded to 32 bit boundaries
+ *          in each line.
+ *      (2) Because 24 bpp rgb pix are conveniently generated by programs
+ *          such as xpdf (which has SplashBitmaps that store the raster
+ *          data in consecutive 24-bit rgb pixels), it is useful to provide
+ *          24 bpp pix that simply incorporate that data.  The only things
+ *          we can do with these are:
+ *            (a) write them to file in png, jpeg, tiff and pnm
+ *            (b) interconvert between 24 and 32 bpp in memory (for testing).
+ */
+PIX *
+pixConvert24To32(PIX  *pixs)
+{
+l_uint8   *lines;
+l_int32    w, h, d, i, j, wpls, wpld, rval, gval, bval;
+l_uint32   pixel;
+l_uint32  *datas, *datad, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixConvert24to32");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 24)
+        return (PIX *)ERROR_PTR("pixs not 24 bpp", procName, NULL);
+
+    pixd = pixCreateNoInit(w, h, 32);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = (l_uint8 *)(datas + i * wpls);
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            rval = *lines++;
+            gval = *lines++;
+            bval = *lines++;
+            composeRGBPixel(rval, gval, bval, &pixel);
+            lined[j] = pixel;
+        }
+    }
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvert32To24()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *      Return: pixd (24 bpp rgb), or null on error
+ *
+ *  Notes:
+ *      (1) See pixconvert24To32().
+ */
+PIX *
+pixConvert32To24(PIX  *pixs)
+{
+l_uint8   *rgbdata8;
+l_int32    w, h, d, i, j, wpls, wpld, rval, gval, bval;
+l_uint32  *datas, *lines, *rgbdata;
+PIX       *pixd;
+
+    PROCNAME("pixConvert32to24");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreateNoInit(w, h, 24);
+    rgbdata = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        rgbdata8 = (l_uint8 *)(rgbdata + i * wpld);
+        for (j = 0; j < w; j++) {
+            extractRGBValues(lines[j], &rval, &gval, &bval);
+            *rgbdata8++ = rval;
+            *rgbdata8++ = gval;
+            *rgbdata8++ = bval;
+        }
+    }
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *            Conversion between 32 bpp (1 spp) and 16 or 8 bpp              *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvert32To16()
+ *
+ *      Input:  pixs (32 bpp, single component)
+ *              type (L_LS_TWO_BYTES, L_MS_TWO_BYTES, L_CLIP_TO_FFFF)
+ *      Return: pixd (16 bpp ), or null on error
+ *
+ *  Notes:
+ *      (1) The data in pixs is typically used for labelling.
+ *          It is an array of l_uint32 values, not rgb or rgba.
+ */
+PIX *
+pixConvert32To16(PIX     *pixs,
+                 l_int32  type)
+{
+l_uint16   dword;
+l_int32    w, h, i, j, wpls, wpld;
+l_uint32   sword;
+l_uint32  *datas, *lines, *datad, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixConvert32to16");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+    if (type != L_LS_TWO_BYTES && type != L_MS_TWO_BYTES &&
+        type != L_CLIP_TO_FFFF)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w, h, 16)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    wpls = pixGetWpl(pixs);
+    datas = pixGetData(pixs);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        if (type == L_LS_TWO_BYTES) {
+            for (j = 0; j < wpls; j++) {
+                sword = *(lines + j);
+                dword = sword & 0xffff;
+                SET_DATA_TWO_BYTES(lined, j, dword);
+            }
+        } else if (type == L_MS_TWO_BYTES) {
+            for (j = 0; j < wpls; j++) {
+                sword = *(lines + j);
+                dword = sword >> 16;
+                SET_DATA_TWO_BYTES(lined, j, dword);
+            }
+        } else {  /* type == L_CLIP_TO_FFFF */
+            for (j = 0; j < wpls; j++) {
+                sword = *(lines + j);
+                dword = (sword >> 16) ? 0xffff : (sword & 0xffff);
+                SET_DATA_TWO_BYTES(lined, j, dword);
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixConvert32To8()
+ *
+ *      Input:  pixs (32 bpp, single component)
+ *              type16 (L_LS_TWO_BYTES, L_MS_TWO_BYTES, L_CLIP_TO_FFFF)
+ *              type8 (L_LS_BYTE, L_MS_BYTE, L_CLIP_TO_FF)
+ *      Return: pixd (8 bpp ), or null on error
+ */
+PIX *
+pixConvert32To8(PIX     *pixs,
+                l_int32  type16,
+                l_int32  type8)
+{
+PIX  *pix1, *pixd;
+
+    PROCNAME("pixConvert32to8");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+    if (type16 != L_LS_TWO_BYTES && type16 != L_MS_TWO_BYTES &&
+        type16 != L_CLIP_TO_FFFF)
+        return (PIX *)ERROR_PTR("invalid type16", procName, NULL);
+    if (type8 != L_LS_BYTE && type8 != L_MS_BYTE && type8 != L_CLIP_TO_FF)
+        return (PIX *)ERROR_PTR("invalid type8", procName, NULL);
+
+    pix1 = pixConvert32To16(pixs, type16);
+    pixd = pixConvert16To8(pix1, type8);
+    pixDestroy(&pix1);
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *        Removal of alpha component by blending with white background       *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixRemoveAlpha()
+ *
+ *      Input:  pixs (any depth)
+ *      Return: pixd (if 32 bpp rgba, pixs blended over a white background;
+ *                    a clone of pixs otherwise), and null on error
+ *
+ *  Notes:
+ *      (1) This is a wrapper on pixAlphaBlendUniform()
+ */
+PIX *
+pixRemoveAlpha(PIX *pixs)
+{
+    PROCNAME("pixRemoveAlpha");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    if (pixGetDepth(pixs) == 32 && pixGetSpp(pixs) == 4)
+        return pixAlphaBlendUniform(pixs, 0xffffff00);
+    else
+        return pixClone(pixs);
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                  Addition of alpha component to 1 bpp                     *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixAddAlphaTo1bpp()
+ *
+ *      Input:  pixd (<optional> 1 bpp, can be null or equal to pixs
+ *              pixs (1 bpp)
+ *      Return: pixd (1 bpp with colormap and non-opaque alpha),
+ *                    or null on error
+ *
+ *  Notes:
+ *      (1) We don't use 1 bpp colormapped images with alpha in leptonica,
+ *          but we support generating them (here), writing to png, and reading
+ *          the png.  On reading, they are converted to 32 bpp RGBA.
+ *      (2) The background (0) pixels in pixs become fully transparent, and the
+ *          foreground (1) pixels are fully opaque.  Thus, pixd is a 1 bpp
+ *          representation of a stencil, that can be used to paint over pixels
+ *          of a backing image that are masked by the foreground in pixs.
+ */
+PIX *
+pixAddAlphaTo1bpp(PIX  *pixd,
+                  PIX  *pixs)
+{
+PIXCMAP  *cmap;
+
+    PROCNAME("pixAddAlphaTo1bpp");
+
+    if (!pixs || (pixGetDepth(pixs) != 1))
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (pixd && (pixd != pixs))
+        return (PIX *)ERROR_PTR("pixd defined but != pixs", procName, NULL);
+
+    pixd = pixCopy(pixd, pixs);
+    cmap = pixcmapCreate(1);
+    pixSetColormap(pixd, cmap);
+    pixcmapAddRGBA(cmap, 255, 255, 255, 0);  /* 0 ==> white + transparent */
+    pixcmapAddRGBA(cmap, 0, 0, 0, 255);  /* 1 ==> black + opaque */
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                  Lossless depth conversion (unpacking)                    *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertLossless()
+ *
+ *      Input:  pixs (1, 2, 4, 8 bpp, not cmapped)
+ *              d (destination depth: 2, 4 or 8)
+ *      Return: pixd (2, 4 or 8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This is a lossless unpacking (depth-increasing)
+ *          conversion.  If ds is the depth of pixs, then
+ *           - if d < ds, returns NULL
+ *           - if d == ds, returns a copy
+ *           - if d > ds, does the unpacking conversion
+ *      (2) If pixs has a colormap, this is an error.
+ */
+PIX *
+pixConvertLossless(PIX     *pixs,
+                   l_int32  d)
+{
+l_int32    w, h, ds, wpls, wpld, i, j, val;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixConvertLossless");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
+    if (d != 2 && d != 4 && d != 8)
+        return (PIX *)ERROR_PTR("invalid dest depth", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, &ds);
+    if (d < ds)
+        return (PIX *)ERROR_PTR("depth > d", procName, NULL);
+    else if (d == ds)
+        return pixCopy(NULL, pixs);
+
+    if ((pixd = pixCreate(w, h, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+        /* Unpack the bits */
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        switch (ds)
+        {
+        case 1:
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_BIT(lines, j);
+                if (d == 8)
+                    SET_DATA_BYTE(lined, j, val);
+                else if (d == 4)
+                    SET_DATA_QBIT(lined, j, val);
+                else  /* d == 2 */
+                    SET_DATA_DIBIT(lined, j, val);
+            }
+            break;
+        case 2:
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_DIBIT(lines, j);
+                if (d == 8)
+                    SET_DATA_BYTE(lined, j, val);
+                else  /* d == 4 */
+                    SET_DATA_QBIT(lined, j, val);
+            }
+        case 4:
+            for (j = 0; j < w; j++) {
+                val = GET_DATA_DIBIT(lines, j);
+                SET_DATA_BYTE(lined, j, val);
+            }
+            break;
+        }
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                     Conversion for printing in PostScript                 *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertForPSWrap()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16, 32 bpp)
+ *      Return: pixd (1, 8, or 32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) For wrapping in PostScript, we convert pixs to
+ *          1 bpp, 8 bpp (gray) and 32 bpp (RGB color).
+ *      (2) Colormaps are removed.  For pixs with colormaps, the
+ *          images are converted to either 8 bpp gray or 32 bpp
+ *          RGB, depending on whether the colormap has color content.
+ *      (3) Images without colormaps, that are not 1 bpp or 32 bpp,
+ *          are converted to 8 bpp gray.
+ */
+PIX *
+pixConvertForPSWrap(PIX  *pixs)
+{
+l_int32   d;
+PIX      *pixd;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixConvertForPSWrap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    cmap = pixGetColormap(pixs);
+    d = pixGetDepth(pixs);
+    switch (d)
+    {
+    case 1:
+    case 32:
+        pixd = pixClone(pixs);
+        break;
+    case 2:
+        if (cmap)
+            pixd = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+        else
+            pixd = pixConvert2To8(pixs, 0, 0x55, 0xaa, 0xff, FALSE);
+        break;
+    case 4:
+        if (cmap)
+            pixd = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+        else
+            pixd = pixConvert4To8(pixs, FALSE);
+        break;
+    case 8:
+        pixd = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+        break;
+    case 16:
+        pixd = pixConvert16To8(pixs, L_MS_BYTE);
+        break;
+    default:
+        fprintf(stderr, "depth not in {1, 2, 4, 8, 16, 32}");
+        return NULL;
+   }
+
+   return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *                      Scaling conversion to subpixel RGB                   *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixConvertToSubpixelRGB()
+ *
+ *      Input:  pixs (8 bpp grayscale, 32 bpp rgb, or colormapped)
+ *              scalex, scaley (anisotropic scaling permitted between
+ *                              source and destination)
+ *              order (of subpixel rgb color components in composition of pixd:
+ *                     L_SUBPIXEL_ORDER_RGB, L_SUBPIXEL_ORDER_BGR,
+ *                     L_SUBPIXEL_ORDER_VRGB, L_SUBPIXEL_ORDER_VBGR)
+ *
+ *      Return: pixd (32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs has a colormap, it is removed based on its contents
+ *          to either 8 bpp gray or rgb.
+ *      (2) For horizontal subpixel splitting, the input image
+ *          is rescaled by @scaley vertically and by 3.0 times
+ *          @scalex horizontally.  Then each horizontal triplet
+ *          of pixels is mapped back to a single rgb pixel, with the
+ *          r, g and b values being assigned based on the pixel triplet.
+ *          For gray triplets, the r, g, and b values are set equal to
+ *          the three gray values.  For color triplets, the r, g and b
+ *          values are set equal to the components from the appropriate
+ *          subpixel.  Vertical subpixel splitting is handled similarly.
+ *      (3) See pixConvertGrayToSubpixelRGB() and
+ *          pixConvertColorToSubpixelRGB() for further details.
+ */
+PIX *
+pixConvertToSubpixelRGB(PIX       *pixs,
+                        l_float32  scalex,
+                        l_float32  scaley,
+                        l_int32    order)
+{
+l_int32    d;
+PIX       *pixt, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixConvertToSubpixelRGB");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    cmap = pixGetColormap(pixs);
+    if (d != 8 && d != 32 && !cmap)
+        return (PIX *)ERROR_PTR("pix not 8 or 32 bpp and not cmapped",
+                                procName, NULL);
+    if (scalex <= 0.0 || scaley <= 0.0)
+        return (PIX *)ERROR_PTR("scale factors must be > 0", procName, NULL);
+    if (order != L_SUBPIXEL_ORDER_RGB && order != L_SUBPIXEL_ORDER_BGR &&
+        order != L_SUBPIXEL_ORDER_VRGB && order != L_SUBPIXEL_ORDER_VBGR)
+        return (PIX *)ERROR_PTR("invalid subpixel order", procName, NULL);
+    if ((pixt = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+    d = pixGetDepth(pixt);
+    pixd = NULL;
+    if (d == 8)
+        pixd = pixConvertGrayToSubpixelRGB(pixt, scalex, scaley, order);
+    else if (d == 32)
+        pixd = pixConvertColorToSubpixelRGB(pixt, scalex, scaley, order);
+    else
+        L_ERROR("invalid depth %d\n", procName, d);
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvertGrayToSubpixelRGB()
+ *
+ *      Input:  pixs (8 bpp or colormapped)
+ *              scalex, scaley
+ *              order (of subpixel rgb color components in composition of pixd:
+ *                     L_SUBPIXEL_ORDER_RGB, L_SUBPIXEL_ORDER_BGR,
+ *                     L_SUBPIXEL_ORDER_VRGB, L_SUBPIXEL_ORDER_VBGR)
+ *
+ *      Return: pixd (32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs has a colormap, it is removed to 8 bpp.
+ *      (2) For horizontal subpixel splitting, the input gray image
+ *          is rescaled by @scaley vertically and by 3.0 times
+ *          @scalex horizontally.  Then each horizontal triplet
+ *          of pixels is mapped back to a single rgb pixel, with the
+ *          r, g and b values being assigned from the triplet of gray values.
+ *          Similar operations are used for vertical subpixel splitting.
+ *      (3) This is a form of subpixel rendering that tends to give the
+ *          resulting text a sharper and somewhat chromatic display.
+ *          For horizontal subpixel splitting, the observable difference
+ *          between @order=L_SUBPIXEL_ORDER_RGB and
+ *          @order=L_SUBPIXEL_ORDER_BGR is reduced by optical diffusers
+ *          in the display that make the pixel color appear to emerge
+ *          from the entire pixel.
+ */
+PIX *
+pixConvertGrayToSubpixelRGB(PIX       *pixs,
+                            l_float32  scalex,
+                            l_float32  scaley,
+                            l_int32    order)
+{
+l_int32    w, h, d, wd, hd, wplt, wpld, i, j, rval, gval, bval, direction;
+l_uint32  *datat, *datad, *linet, *lined;
+PIX       *pix1, *pix2, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixConvertGrayToSubpixelRGB");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    cmap = pixGetColormap(pixs);
+    if (d != 8 && !cmap)
+        return (PIX *)ERROR_PTR("pix not 8 bpp & not cmapped", procName, NULL);
+    if (scalex <= 0.0 || scaley <= 0.0)
+        return (PIX *)ERROR_PTR("scale factors must be > 0", procName, NULL);
+    if (order != L_SUBPIXEL_ORDER_RGB && order != L_SUBPIXEL_ORDER_BGR &&
+        order != L_SUBPIXEL_ORDER_VRGB && order != L_SUBPIXEL_ORDER_VBGR)
+        return (PIX *)ERROR_PTR("invalid subpixel order", procName, NULL);
+
+    direction =
+        (order == L_SUBPIXEL_ORDER_RGB || order == L_SUBPIXEL_ORDER_BGR)
+        ? L_HORIZ : L_VERT;
+    pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_GRAYSCALE);
+    if (direction == L_HORIZ)
+        pix2 = pixScale(pix1, 3.0 * scalex, scaley);
+    else  /* L_VERT */
+        pix2 = pixScale(pix1, scalex, 3.0 * scaley);
+
+    pixGetDimensions(pix2, &w, &h, NULL);
+    wd = (direction == L_HORIZ) ? w / 3 : w;
+    hd = (direction == L_VERT) ? h / 3 : h;
+    pixd = pixCreate(wd, hd, 32);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datat = pixGetData(pix2);
+    wplt = pixGetWpl(pix2);
+    if (direction == L_HORIZ) {
+        for (i = 0; i < hd; i++) {
+            linet = datat + i * wplt;
+            lined = datad + i * wpld;
+            for (j = 0; j < wd; j++) {
+                rval = GET_DATA_BYTE(linet, 3 * j);
+                gval = GET_DATA_BYTE(linet, 3 * j + 1);
+                bval = GET_DATA_BYTE(linet, 3 * j + 2);
+                if (order == L_SUBPIXEL_ORDER_RGB)
+                    composeRGBPixel(rval, gval, bval, &lined[j]);
+                else  /* order BGR */
+                    composeRGBPixel(bval, gval, rval, &lined[j]);
+            }
+        }
+    } else {  /* L_VERT */
+        for (i = 0; i < hd; i++) {
+            linet = datat + 3 * i * wplt;
+            lined = datad + i * wpld;
+            for (j = 0; j < wd; j++) {
+                rval = GET_DATA_BYTE(linet, j);
+                gval = GET_DATA_BYTE(linet + wplt, j);
+                bval = GET_DATA_BYTE(linet + 2 * wplt, j);
+                if (order == L_SUBPIXEL_ORDER_VRGB)
+                    composeRGBPixel(rval, gval, bval, &lined[j]);
+                else  /* order VBGR */
+                    composeRGBPixel(bval, gval, rval, &lined[j]);
+            }
+        }
+    }
+
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    return pixd;
+}
+
+
+/*!
+ *  pixConvertColorToSubpixelRGB()
+ *
+ *      Input:  pixs (32 bpp or colormapped)
+ *              scalex, scaley
+ *              order (of subpixel rgb color components in composition of pixd:
+ *                     L_SUBPIXEL_ORDER_RGB, L_SUBPIXEL_ORDER_BGR,
+ *                     L_SUBPIXEL_ORDER_VRGB, L_SUBPIXEL_ORDER_VBGR)
+ *
+ *      Return: pixd (32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) If pixs has a colormap, it is removed to 32 bpp rgb.
+ *          If the colormap has no color, pixConvertGrayToSubpixelRGB()
+ *          should be called instead, because it will give the same result
+ *          more efficiently.  The function pixConvertToSubpixelRGB()
+ *          will do the best thing for all cases.
+ *      (2) For horizontal subpixel splitting, the input rgb image
+ *          is rescaled by @scaley vertically and by 3.0 times
+ *          @scalex horizontally.  Then for each horizontal triplet
+ *          of pixels, the r component of the final pixel is selected
+ *          from the r component of the appropriate pixel in the triplet,
+ *          and likewise for g and b.  Vertical subpixel splitting is
+ *          handled similarly.
+ */
+PIX *
+pixConvertColorToSubpixelRGB(PIX       *pixs,
+                             l_float32  scalex,
+                             l_float32  scaley,
+                             l_int32    order)
+{
+l_int32    w, h, d, wd, hd, wplt, wpld, i, j, rval, gval, bval, direction;
+l_uint32  *datat, *datad, *linet, *lined;
+PIX       *pix1, *pix2, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixConvertColorToSubpixelRGB");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    cmap = pixGetColormap(pixs);
+    if (d != 32 && !cmap)
+        return (PIX *)ERROR_PTR("pix not 32 bpp & not cmapped", procName, NULL);
+    if (scalex <= 0.0 || scaley <= 0.0)
+        return (PIX *)ERROR_PTR("scale factors must be > 0", procName, NULL);
+    if (order != L_SUBPIXEL_ORDER_RGB && order != L_SUBPIXEL_ORDER_BGR &&
+        order != L_SUBPIXEL_ORDER_VRGB && order != L_SUBPIXEL_ORDER_VBGR)
+        return (PIX *)ERROR_PTR("invalid subpixel order", procName, NULL);
+
+    direction =
+        (order == L_SUBPIXEL_ORDER_RGB || order == L_SUBPIXEL_ORDER_BGR)
+        ? L_HORIZ : L_VERT;
+    pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR);
+    if (direction == L_HORIZ)
+        pix2 = pixScale(pix1, 3.0 * scalex, scaley);
+    else  /* L_VERT */
+        pix2 = pixScale(pix1, scalex, 3.0 * scaley);
+
+    pixGetDimensions(pix2, &w, &h, NULL);
+    wd = (direction == L_HORIZ) ? w / 3 : w;
+    hd = (direction == L_VERT) ? h / 3 : h;
+    pixd = pixCreate(wd, hd, 32);
+    pixCopyInputFormat(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    datat = pixGetData(pix2);
+    wplt = pixGetWpl(pix2);
+    if (direction == L_HORIZ) {
+        for (i = 0; i < hd; i++) {
+            linet = datat + i * wplt;
+            lined = datad + i * wpld;
+            for (j = 0; j < wd; j++) {
+                if (order == L_SUBPIXEL_ORDER_RGB) {
+                    extractRGBValues(linet[3 * j], &rval, NULL, NULL);
+                    extractRGBValues(linet[3 * j + 1], NULL, &gval, NULL);
+                    extractRGBValues(linet[3 * j + 2], NULL, NULL, &bval);
+                } else {  /* order BGR */
+                    extractRGBValues(linet[3 * j], NULL, NULL, &bval);
+                    extractRGBValues(linet[3 * j + 1], NULL, &gval, NULL);
+                    extractRGBValues(linet[3 * j + 2], &rval, NULL, NULL);
+                }
+                composeRGBPixel(rval, gval, bval, &lined[j]);
+            }
+        }
+    } else {  /* L_VERT */
+        for (i = 0; i < hd; i++) {
+            linet = datat + 3 * i * wplt;
+            lined = datad + i * wpld;
+            for (j = 0; j < wd; j++) {
+                if (order == L_SUBPIXEL_ORDER_VRGB) {
+                    extractRGBValues(linet[j], &rval, NULL, NULL);
+                    extractRGBValues((linet + wplt)[j], NULL, &gval, NULL);
+                    extractRGBValues((linet + 2 * wplt)[j], NULL, NULL, &bval);
+                } else {  /* order VBGR */
+                    extractRGBValues(linet[j], NULL, NULL, &bval);
+                    extractRGBValues((linet + wplt)[j], NULL, &gval, NULL);
+                    extractRGBValues((linet + 2 * wplt)[j], &rval, NULL, NULL);
+                }
+                composeRGBPixel(rval, gval, bval, &lined[j]);
+            }
+        }
+    }
+
+    if (pixGetSpp(pixs) == 4)
+        pixScaleAndTransferAlpha(pixd, pixs, scalex, scaley);
+
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    return pixd;
+}
diff --git a/src/pixlabel.c b/src/pixlabel.c
new file mode 100644 (file)
index 0000000..38e1470
--- /dev/null
@@ -0,0 +1,621 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pixlabel.c
+ *
+ *     Label pixels by an index for connected component membership
+ *           PIX         *pixConnCompTransform()
+ *
+ *     Label pixels by the area of their connected component
+ *           PIX         *pixConnCompAreaTransform()
+ *
+ *     Label pixels to allow incremental computation of connected components
+ *           l_int32      pixConnCompIncrInit()
+ *           l_int32      pixConnCompIncrAdd()
+ *           l_int32      pixGetSortedNeighborValues()
+ *
+ *     Label pixels with spatially-dependent color coding
+ *           PIX         *pixLocToColorTransform()
+ *
+ *  Pixels get labelled in various ways throughout the leptonica library,
+ *  but most of the labelling is implicit, where the new value isn't
+ *  even considered to be a label -- it is just a transformed pixel value
+ *  that may be transformed again by another operation.  Quantization
+ *  by thresholding, and dilation by a structuring element, are examples
+ *  of these typical image processing operations.
+ *
+ *  However, there are some explicit labelling procedures that are useful
+ *  as end-points of analysis, where it typically would not make sense
+ *  to do further image processing on the result.  Assigning false color
+ *  based on pixel properties is an example of such labelling operations.
+ *  Such operations typically have 1 bpp input images, and result
+ *  in grayscale or color images.
+ *
+ *  The procedures in this file are concerned with such explicit labelling.
+ *  Some of these labelling procedures are also in other places in leptonica:
+ *
+ *    runlength.c:
+ *       This file has two labelling transforms based on runlengths:
+ *       pixStrokeWidthTransform() and pixvRunlengthTransform().
+ *       The pixels are labelled based on the width of the "stroke" to
+ *       which they belong, or on the length of the horizontal or
+ *       vertical run in which they are a member.  Runlengths can easily
+ *       be filtered using a threshold.
+ *
+ *    pixafunc2.c:
+ *       This file has an operation, pixaDisplayRandomCmap(), that
+ *       randomly labels pix in a pixa (that are typically found using
+ *       pixConnComp) with up to 256 values, and assigns each value to
+ *       a random colormap color.
+ *
+ *    seedfill.c:
+ *       This file has pixDistanceFunction(), that labels each pixel with
+ *       its distance from either the foreground or the background.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+
+
+/*-----------------------------------------------------------------------*
+ *      Label pixels by an index for connected component membership      *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixConnCompTransform()
+ *
+ *      Input:   pixs (1 bpp)
+ *               connect (connectivity: 4 or 8)
+ *               depth (of pixd: 8 or 16 bpp; use 0 for auto determination)
+ *      Return:  pixd (8, 16 or 32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) pixd is 8, 16 or 32 bpp, and the pixel values label the
+ *          fg component, starting with 1.  Pixels in the bg are labelled 0.
+ *      (2) If @depth = 0, the depth of pixd is 8 if the number of c.c.
+ *          is less than 254, 16 if the number of c.c is less than 0xfffe,
+ *          and 32 otherwise.
+ *      (3) If @depth = 8, the assigned label for the n-th component is
+ *          1 + n % 254.  We use mod 254 because 0 is uniquely assigned
+ *          to black: e.g., see pixcmapCreateRandom().  Likewise,
+ *          if @depth = 16, the assigned label uses mod(2^16 - 2), and
+ *          if @depth = 32, no mod is taken.
+ */
+PIX *
+pixConnCompTransform(PIX     *pixs,
+                     l_int32  connect,
+                     l_int32  depth)
+{
+l_int32  i, n, index, w, h, xb, yb, wb, hb;
+BOXA    *boxa;
+PIX     *pix1, *pix2, *pixd;
+PIXA    *pixa;
+
+    PROCNAME("pixConnCompTransform");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (connect != 4 && connect != 8)
+        return (PIX *)ERROR_PTR("connectivity must be 4 or 8", procName, NULL);
+    if (depth != 0 && depth != 8 && depth != 16 && depth != 32)
+        return (PIX *)ERROR_PTR("depth must be 0, 8, 16 or 32", procName, NULL);
+
+    boxa = pixConnComp(pixs, &pixa, connect);
+    n = pixaGetCount(pixa);
+    boxaDestroy(&boxa);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (depth == 0) {
+        if (n < 254)
+            depth = 8;
+        else if (n < 0xfffe)
+            depth = 16;
+        else
+            depth = 32;
+    }
+    pixd = pixCreate(w, h, depth);
+    pixSetSpp(pixd, 1);
+    if (n == 0) {  /* no fg */
+        pixaDestroy(&pixa);
+        return pixd;
+    }
+
+       /* Label each component and blit it in */
+    for (i = 0; i < n; i++) {
+        pixaGetBoxGeometry(pixa, i, &xb, &yb, &wb, &hb);
+        pix1 = pixaGetPix(pixa, i, L_CLONE);
+        if (depth == 8) {
+            index = 1 + (i % 254);
+            pix2 = pixConvert1To8(NULL, pix1, 0, index);
+        } else if (depth == 16) {
+            index = 1 + (i % 0xfffe);
+            pix2 = pixConvert1To16(NULL, pix1, 0, index);
+        } else {  /* depth == 32 */
+            index = 1 + i;
+            pix2 = pixConvert1To32(NULL, pix1, 0, index);
+        }
+        pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pix2, 0, 0);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    pixaDestroy(&pixa);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *         Label pixels by the area of their connected component         *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixConnCompAreaTransform()
+ *
+ *      Input:   pixs (1 bpp)
+ *               connect (connectivity: 4 or 8)
+ *      Return:  pixd (32 bpp, 1 spp), or null on error
+ *
+ *  Notes:
+ *      (1) The pixel values in pixd label the area of the fg component
+ *          to which the pixel belongs.  Pixels in the bg are labelled 0.
+ *      (2) For purposes of visualization, the output can be converted
+ *          to 8 bpp, using pixConvert32To8() or pixMaxDynamicRange().
+ */
+PIX *
+pixConnCompAreaTransform(PIX     *pixs,
+                         l_int32  connect)
+{
+l_int32   i, n, npix, w, h, xb, yb, wb, hb;
+l_int32  *tab8;
+BOXA     *boxa;
+PIX      *pix1, *pix2, *pixd;
+PIXA     *pixa;
+
+    PROCNAME("pixConnCompAreaTransform");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (connect != 4 && connect != 8)
+        return (PIX *)ERROR_PTR("connectivity must be 4 or 8", procName, NULL);
+
+    boxa = pixConnComp(pixs, &pixa, connect);
+    n = pixaGetCount(pixa);
+    boxaDestroy(&boxa);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixCreate(w, h, 32);
+    pixSetSpp(pixd, 1);
+    if (n == 0) {  /* no fg */
+        pixaDestroy(&pixa);
+        return pixd;
+    }
+
+       /* Label each component and blit it in */
+    tab8 = makePixelSumTab8();
+    for (i = 0; i < n; i++) {
+        pixaGetBoxGeometry(pixa, i, &xb, &yb, &wb, &hb);
+        pix1 = pixaGetPix(pixa, i, L_CLONE);
+        pixCountPixels(pix1, &npix, tab8);
+        pix2 = pixConvert1To32(NULL, pix1, 0, npix);
+        pixRasterop(pixd, xb, yb, wb, hb, PIX_PAINT, pix2, 0, 0);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    pixaDestroy(&pixa);
+    LEPT_FREE(tab8);
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------------------*
+ *  Label pixels to allow incremental computation of connected components  *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  pixConnCompIncrInit()
+ *
+ *      Input:   pixs (1 bpp)
+ *               conn (connectivity: 4 or 8)
+ *               &pixd (<return> 32 bpp, with c.c. labelled)
+ *               &ptaa (<return> with pixel locations indexed by c.c.)
+ *               &ncc (<return> initial number of c.c.)
+ *      Return:  0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This labels the connected components in a 1 bpp pix, and
+ *          additionally sets up a ptaa that lists the locations of pixels
+ *          in each of the components.
+ *      (2) It can be used to initialize the output image and arrays for
+ *          an application that maintains information about connected
+ *          components incrementally as pixels are added.
+ *      (3) pixs can be empty or have some foreground pixels.
+ *      (4) The connectivity is stored in pixd->special.
+ *      (5) Always initialize with the first pta in ptaa being empty
+ *          and representing the background value (index 0) in the pix.
+ */
+l_int32
+pixConnCompIncrInit(PIX     *pixs,
+                    l_int32  conn,
+                    PIX    **ppixd,
+                    PTAA   **pptaa,
+                    l_int32 *pncc)
+{
+l_int32  empty, w, h, ncc;
+PIX     *pixd;
+PTA     *pta;
+PTAA    *ptaa;
+
+    PROCNAME("pixConnCompIncrInit");
+
+    if (ppixd) *ppixd = NULL;
+    if (pptaa) *pptaa = NULL;
+    if (pncc) *pncc = 0;
+    if (!ppixd || !pptaa || !pncc)
+        return ERROR_INT("&pixd, &ptaa, &ncc not all defined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs undefined or not 1 bpp", procName, 1);
+    if (conn != 4 && conn != 8)
+        return ERROR_INT("connectivity must be 4 or 8", procName, 1);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixZero(pixs, &empty);
+    if (empty) {
+        *ppixd = pixCreate(w, h, 32);
+        pixSetSpp(*ppixd, 1);
+        pixSetSpecial(*ppixd, conn);
+        *pptaa = ptaaCreate(0);
+        pta = ptaCreate(1);
+        ptaaAddPta(*pptaa, pta, L_INSERT);  /* reserve index 0 for background */
+        return 0;
+    }
+
+        /* Set up the initial labelled image and indexed pixel arrays */
+    if ((pixd = pixConnCompTransform(pixs, conn, 32)) == NULL)
+        return ERROR_INT("pixd not made", procName, 1);
+    pixSetSpecial(pixd, conn);
+    *ppixd = pixd;
+    if ((ptaa = ptaaIndexLabelledPixels(pixd, &ncc)) == NULL)
+        return ERROR_INT("ptaa not made", procName, 1);
+    *pptaa = ptaa;
+    *pncc = ncc;
+    return 0;
+}
+
+
+/*!
+ *  pixConnCompIncrAdd()
+ *
+ *      Input:   pixs (32 bpp, with pixels labelled by c.c.)
+ *               ptaa (with each pta of pixel locations indexed by c.c.)
+ *               &ncc (number of c.c)
+ *               x,y (location of added pixel)
+ *               debug (0 for no output; otherwise output whenever
+ *                      debug <= nvals, up to debug == 3)
+ *      Return:  -1 if nothing happens; 0 if a pixel is added; 1 on error
+ *
+ *  Notes:
+ *      (1) This adds a pixel and updates the labelled connected components.
+ *          Before calling this function, initialize the process using
+ *          pixConnCompIncrInit().
+ *      (2) As a result of adding a pixel, one of the following can happen,
+ *          depending on the number of neighbors with non-zero value:
+ *          (a) nothing: the pixel is already a member of a c.c.
+ *          (b) no neighbors: a new component is added, increasing the
+ *              number of c.c.
+ *          (c) one neighbor: the pixel is added to an existing c.c.
+ *          (d) more than one neighbor: the added pixel causes joining of
+ *              two or more c.c., reducing the number of c.c.  A maximum
+ *              of 4 c.c. can be joined.
+ *      (3) When two c.c. are joined, the pixels in the larger index are
+ *          relabelled to those of the smaller in pixs, and their locations
+ *          are transferred to the pta with the smaller index in the ptaa.
+ *          The pta corresponding to the larger index is then deleted.
+ *      (4) This is an efficient implementation of a "union-find" operation,
+ *          which supports the generation and merging of disjoint sets
+ *          of pixels.  This function can be called about 1.3 million times
+ *          per second.
+ */
+l_int32
+pixConnCompIncrAdd(PIX       *pixs,
+                   PTAA      *ptaa,
+                   l_int32   *pncc,
+                   l_float32  x,
+                   l_float32  y,
+                   l_int32    debug)
+{
+l_int32   conn, i, j, w, h, count, nvals, ns, firstindex;
+l_uint32  val;
+l_int32  *neigh;
+PTA      *ptas, *ptad;
+
+    PROCNAME("pixConnCompIncrAdd");
+
+    if (!pixs || pixGetDepth(pixs) != 32)
+        return ERROR_INT("pixs not defined or not 32 bpp", procName, 1);
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 1);
+    if (!pncc)
+        return ERROR_INT("&ncc not defined", procName, 1);
+    conn = pixs->special;
+    if (conn != 4 && conn != 8)
+        return ERROR_INT("connectivity must be 4 or 8", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (x < 0 || x >= w)
+        return ERROR_INT("invalid x pixel location", procName, 1);
+    if (y < 0 || y >= h)
+        return ERROR_INT("invalid y pixel location", procName, 1);
+
+    pixGetPixel(pixs, x, y, &val);
+    if (val > 0)  /* already belongs to a set */
+        return -1;
+
+        /* Find unique neighbor pixel values in increasing order of value.
+         * If @nvals > 0, these are returned in the @neigh array, which
+         * is of size @nvals.  Note that the pixel values in each
+         * connected component are used as the index into the pta
+         * array of the ptaa, giving the pixel locations. */
+    pixGetSortedNeighborValues(pixs, x, y, conn, &neigh, &nvals);
+
+        /* If there are no neighbors, just add a new component */
+    if (nvals == 0) {
+        count = ptaaGetCount(ptaa);
+        pixSetPixel(pixs, x, y, count);
+        ptas = ptaCreate(1);
+        ptaAddPt(ptas, x, y);
+        ptaaAddPta(ptaa, ptas, L_INSERT);
+        *pncc += 1;
+        return 0;
+    }
+
+        /* Otherwise, there is at least one neighbor.  Add the pixel
+         * to the first neighbor c.c. */
+    firstindex = neigh[0];
+    pixSetPixel(pixs, x, y, firstindex);
+    ptaaAddPt(ptaa, neigh[0], x, y);
+    if (nvals == 1) {
+        if (debug == 1)
+            fprintf(stderr, "nvals = %d: neigh = (%d)\n", nvals, neigh[0]);
+        LEPT_FREE(neigh);
+        return 0;
+    }
+
+        /* If nvals > 1, there are at least 2 neighbors, so this pixel
+         * joins at least one pair of existing c.c.  Join each component
+         * to the first component in the list, which is the one with
+         * the smallest integer label.  This is done in two steps:
+         *  (a) re-label the pixels in the component to the label of the
+         *      first component, and
+         *  (b) save the pixel locations in the pta for the first component. */
+    if (nvals == 2) {
+        if (debug >= 1 && debug <= 2) {
+            fprintf(stderr, "nvals = %d: neigh = (%d,%d)\n", nvals,
+                    neigh[0], neigh[1]);
+        }
+    } else if (nvals == 3) {
+        if (debug >= 1 && debug <= 3) {
+            fprintf(stderr, "nvals = %d: neigh = (%d,%d,%d)\n", nvals,
+                    neigh[0], neigh[1], neigh[2]);
+        }
+    } else {  /* nvals == 4 */
+        if (debug >= 1 && debug <= 4) {
+            fprintf(stderr, "nvals = %d: neigh = (%d,%d,%d,%d)\n", nvals,
+                    neigh[0], neigh[1], neigh[2], neigh[3]);
+        }
+    }
+    ptad = ptaaGetPta(ptaa, firstindex, L_CLONE);
+    for (i = 1; i < nvals; i++) {
+        ptas = ptaaGetPta(ptaa, neigh[i], L_CLONE);
+        ns = ptaGetCount(ptas);
+        for (j = 0; j < ns; j++) {  /* relabel pixels */
+            ptaGetPt(ptas, j, &x, &y);
+            pixSetPixel(pixs, x, y, firstindex);
+        }
+        ptaJoin(ptad, ptas, 0, -1);  /* add relabelled pixel locations */
+        *pncc -= 1;
+        ptaDestroy(&ptaa->pta[neigh[i]]);
+        ptaDestroy(&ptas);  /* the clone */
+    }
+    ptaDestroy(&ptad);  /* the clone */
+    LEPT_FREE(neigh);
+    return 0;
+}
+
+
+/*!
+ *  pixGetSortedNeighborValues()
+ *
+ *      Input:   pixs (8, 16 or 32 bpp, with pixels labelled by c.c.)
+ *               x, y (location of pixel)
+ *               conn (4 or 8 connected neighbors)
+ *               &neigh (<return> array of integers, to be filled with
+ *                      the values of the neighbors, if any)
+ *               &nvals (<return> the number of unique neighbor values found)
+ *      Return:  0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The returned @neigh array is the unique set of neighboring
+ *          pixel values, of size nvals, sorted from smallest to largest.
+ *          The value 0, which represents background pixels that do
+ *          not belong to any set of connected components, is discarded.
+ *      (2) If there are no neighbors, this returns @neigh = NULL; otherwise,
+ *          the caller must free the array.
+ *      (3) For either 4 or 8 connectivity, the maximum number of unique
+ *          neighbor values is 4.
+ */
+l_int32
+pixGetSortedNeighborValues(PIX       *pixs,
+                           l_int32    x,
+                           l_int32    y,
+                           l_int32    conn,
+                           l_int32  **pneigh,
+                           l_int32   *pnvals)
+{
+l_int32       i, npt, index;
+l_int32       neigh[4];
+l_uint32      val;
+l_float32     fx, fy;
+L_ASET       *aset;
+L_ASET_NODE  *node;
+PTA          *pta;
+RB_TYPE       key;
+
+    PROCNAME("pixGetSortedNeighborValues");
+
+    if (pneigh) *pneigh = NULL;
+    if (pnvals) *pnvals = 0;
+    if (!pneigh || !pnvals)
+        return ERROR_INT("&neigh and &nvals not both defined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) < 8)
+        return ERROR_INT("pixs not defined or depth < 8", procName, 1);
+
+        /* Identify the locations of nearest neighbor pixels */
+    if ((pta = ptaGetNeighborPixLocs(pixs, x, y, conn)) == NULL)
+        return ERROR_INT("pta of neighbors not made", procName, 1);
+
+        /* Find the pixel values and insert into a set as keys */
+    aset = l_asetCreate(L_UINT_TYPE);
+    npt = ptaGetCount(pta);
+    for (i = 0; i < npt; i++) {
+        ptaGetPt(pta, i, &fx, &fy);
+        pixGetPixel(pixs, (l_int32)fx, (l_int32)fy, &val);
+        key.utype = val;
+        l_asetInsert(aset, key);
+    }
+
+        /* Extract the set keys and put them into the @neigh array.
+         * Omit the value 0, which indicates the pixel doesn't
+         * belong to one of the sets of connected components. */
+    node = l_asetGetFirst(aset);
+    index = 0;
+    while (node) {
+        val = node->key.utype;
+        if (val > 0)
+            neigh[index++] = (l_int32)val;
+        node = l_asetGetNext(node);
+    }
+    *pnvals = index;
+    if (index > 0) {
+        *pneigh = (l_int32 *)LEPT_CALLOC(index, sizeof(l_int32));
+        for (i = 0; i < index; i++)
+            (*pneigh)[i] = neigh[i];
+    }
+
+    ptaDestroy(&pta);
+    l_asetDestroy(&aset);
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *          Label pixels with spatially-dependent color coding           *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixLocToColorTransform()
+ *
+ *      Input:   pixs (1 bpp)
+ *      Return:  pixd (32 bpp rgb), or null on error
+ *
+ *  Notes:
+ *      (1) This generates an RGB image where each component value
+ *          is coded depending on the (x.y) location and the size
+ *          of the fg connected component that the pixel in pixs belongs to.
+ *          It is independent of the 4-fold orthogonal orientation, and
+ *          only weakly depends on translations and small angle rotations.
+ *          Background pixels are black.
+ *      (2) Such encodings can be compared between two 1 bpp images
+ *          by performing this transform and calculating the
+ *          "earth-mover" distance on the resulting R,G,B histograms.
+ */
+PIX *
+pixLocToColorTransform(PIX  *pixs)
+{
+l_int32    w, h, w2, h2, wpls, wplr, wplg, wplb, wplcc, i, j, rval, gval, bval;
+l_float32  invw2, invh2;
+l_uint32  *datas, *datar, *datag, *datab, *datacc;
+l_uint32  *lines, *liner, *lineg, *lineb, *linecc;
+PIX       *pix1, *pixcc, *pixr, *pixg, *pixb, *pixd;
+
+    PROCNAME("pixLocToColorTransform");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+
+        /* Label each pixel with the area of the c.c. to which it belongs.
+         * Clip the result to 255 in an 8 bpp pix. This is used for
+         * the blue component of pixd.  */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    w2 = w / 2;
+    h2 = h / 2;
+    invw2 = 255.0 / (l_float32)w2;
+    invh2 = 255.0 / (l_float32)h2;
+    pix1 = pixConnCompAreaTransform(pixs, 8);
+    pixcc = pixConvert32To8(pix1, L_LS_TWO_BYTES, L_CLIP_TO_FF);
+    pixDestroy(&pix1);
+
+        /* Label the red and green components depending on the location
+         * of the fg pixels, in a way that is 4-fold rotationally invariant. */
+    pixr = pixCreate(w, h, 8);
+    pixg = pixCreate(w, h, 8);
+    pixb = pixCreate(w, h, 8);
+    wpls = pixGetWpl(pixs);
+    wplr = pixGetWpl(pixr);
+    wplg = pixGetWpl(pixg);
+    wplb = pixGetWpl(pixb);
+    wplcc = pixGetWpl(pixcc);
+    datas = pixGetData(pixs);
+    datar = pixGetData(pixr);
+    datag = pixGetData(pixg);
+    datab = pixGetData(pixb);
+    datacc = pixGetData(pixcc);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        liner = datar + i * wplr;
+        lineg = datag + i * wplg;
+        lineb = datab + i * wplb;
+        linecc = datacc+ i * wplcc;
+        for (j = 0; j < w; j++) {
+            if (GET_DATA_BIT(lines, j) == 0) continue;
+            if (w < h) {
+                rval = invh2 * L_ABS((l_float32)(i - h2));
+                gval = invw2 * L_ABS((l_float32)(j - w2));
+            } else {
+                rval = invw2 * L_ABS((l_float32)(j - w2));
+                gval = invh2 * L_ABS((l_float32)(i - h2));
+            }
+            bval = GET_DATA_BYTE(linecc, j);
+            SET_DATA_BYTE(liner, j, rval);
+            SET_DATA_BYTE(lineg, j, gval);
+            SET_DATA_BYTE(lineb, j, bval);
+        }
+    }
+    pixd = pixCreateRGBImage(pixr, pixg, pixb);
+
+    pixDestroy(&pixcc);
+    pixDestroy(&pixr);
+    pixDestroy(&pixg);
+    pixDestroy(&pixb);
+    return pixd;
+}
+
diff --git a/src/pixtiling.c b/src/pixtiling.c
new file mode 100644 (file)
index 0000000..2ba3653
--- /dev/null
@@ -0,0 +1,417 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *   pixtiling.c
+ *
+ *        PIXTILING       *pixTilingCreate()
+ *        void            *pixTilingDestroy()
+ *        l_int32          pixTilingGetCount()
+ *        l_int32          pixTilingGetSize()
+ *        PIX             *pixTilingGetTile()
+ *        l_int32          pixTilingNoStripOnPaint()
+ *        l_int32          pixTilingPaintTile()
+ *
+ *   This provides a simple way to split an image into tiles
+ *   and to perform operations independently on each tile.
+ *
+ *   The tile created with pixTilingGetTile() can have pixels in
+ *   adjacent tiles for computation.  The number of extra pixels
+ *   on each side of the tile is given by an 'overlap' parameter
+ *   to pixTilingCreate().  For tiles at the boundary of
+ *   the input image, quasi-overlap pixels are created by reflection
+ *   symmetry into the tile.
+ *
+ *   Here's a typical intended usage.  Suppose you want to parallelize
+ *   the operation on an image, by operating on tiles.  For each
+ *   tile, you want to generate an in-place image result at the same
+ *   resolution.  Suppose you choose a one-dimensional vertical tiling,
+ *   where the desired tile width is 256 pixels and the overlap is
+ *   30 pixels on left and right sides:
+ *
+ *     PIX *pixd = pixCreateTemplateNoInit(pixs);  // output
+ *     PIXTILING  *pt = pixTilingCreate(pixs, 0, 1, 256, 30, 0);
+ *     pixTilingGetCount(pt, &nx, NULL);
+ *     for (j = 0; j < nx; j++) {
+ *         PIX *pixt = pixTilingGetTile(pt, 0, j);
+ *         SomeInPlaceOperation(pixt, 30, 0, ...);
+ *         pixTilingPaintTile(pixd, 0, j, pixt, pt);
+ *         pixDestroy(&pixt);
+ *     }
+ *
+ *   In this example, note the following:
+ *    - The unspecfified in-place operation could instead generate
+ *      a new pix.  If this is done, the resulting pix must be the
+ *      same size as pixt, because pixTilingPaintTile() makes that
+ *      assumption, removing the overlap pixels before painting
+ *      into the destination.
+ *    - The 'overlap' parameters have been included in your function,
+ *      to indicate which pixels are not in the exterior overlap region.
+ *      You will need to change only pixels that are not in the overlap
+ *      region, because those are the pixels that will be painted
+ *      into the destination.
+ *    - For tiles on the outside of the image, mirrored pixels are
+ *      added to substitute for the overlap that is added to interior
+ *      tiles.  This allows you to implement your function without
+ *      reference to which tile it is; no special coding is necessary
+ *      for pixels that are near the image boundary.
+ *    - The tiles are labeled by (i, j) = (row, column),
+ *      and in this example there is one row and nx columns.
+ */
+
+#include "allheaders.h"
+
+
+/*!
+ *  pixTilingCreate()
+ *
+ *      Input:  pixs  (pix to be tiled; any depth; colormap OK)
+ *              nx    (number of tiles across image)
+ *              ny    (number of tiles down image)
+ *              w     (desired width of each tile)
+ *              h     (desired height of each tile)
+ *              xoverlap (overlap into neighboring tiles on each side)
+ *              yoverlap (overlap into neighboring tiles above and below)
+ *      Return: pixtiling, or null on error
+ *
+ *  Notes:
+ *      (1) We put a clone of pixs in the PixTiling.
+ *      (2) The input to pixTilingCreate() for horizontal tiling can be
+ *          either the number of tiles across the image or the approximate
+ *          width of the tiles.  If the latter, the actual width will be
+ *          determined by making all tiles but the last of equal width, and
+ *          making the last as close to the others as possible.  The same
+ *          consideration is applied independently to the vertical tiling.
+ *          To specify tile width, set nx = 0; to specify the number of
+ *          tiles horizontally across the image, set w = 0.
+ *      (3) If pixs is to be tiled in one-dimensional strips, use ny = 1 for
+ *          vertical strips and nx = 1 for horizontal strips.
+ *      (4) The overlap must not be larger than the width or height of
+ *          the leftmost or topmost tile(s).
+ */
+PIXTILING *
+pixTilingCreate(PIX     *pixs,
+                l_int32  nx,
+                l_int32  ny,
+                l_int32  w,
+                l_int32  h,
+                l_int32  xoverlap,
+                l_int32  yoverlap)
+{
+l_int32     width, height;
+PIXTILING  *pt;
+
+    PROCNAME("pixTilingCreate");
+
+    if (!pixs)
+        return (PIXTILING *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (nx < 1 && w < 1)
+        return (PIXTILING *)ERROR_PTR("invalid width spec", procName, NULL);
+    if (ny < 1 && h < 1)
+        return (PIXTILING *)ERROR_PTR("invalid height spec", procName, NULL);
+
+        /* Find the tile width and number of tiles.  All tiles except the
+         * rightmost ones have the same width.  The width of the
+         * rightmost ones are at least the width of the others and
+         * less than twice that width.  Ditto for tile height. */
+    pixGetDimensions(pixs, &width, &height, NULL);
+    if (nx == 0)
+        nx = L_MAX(1, width / w);
+    w = width / nx;  /* possibly reset */
+    if (ny == 0)
+        ny = L_MAX(1, height / h);
+    h = height / ny;  /* possibly reset */
+    if (xoverlap > w || yoverlap > h) {
+        L_INFO("tile width = %d, tile height = %d\n", procName, w, h);
+        return (PIXTILING *)ERROR_PTR("overlap too large", procName, NULL);
+    }
+
+    if ((pt = (PIXTILING *)LEPT_CALLOC(1, sizeof(PIXTILING))) == NULL)
+        return (PIXTILING *)ERROR_PTR("pt not made", procName, NULL);
+    pt->pix = pixClone(pixs);
+    pt->xoverlap = xoverlap;
+    pt->yoverlap = yoverlap;
+    pt->nx = nx;
+    pt->ny = ny;
+    pt->w = w;
+    pt->h = h;
+    pt->strip = TRUE;
+    return pt;
+}
+
+
+/*!
+ *  pixTilingDestroy()
+ *
+ *      Input:  &pt (<will be set to null before returning>)
+ *      Return: void
+ */
+void
+pixTilingDestroy(PIXTILING  **ppt)
+{
+PIXTILING  *pt;
+
+    PROCNAME("pixTilingDestroy");
+
+    if (ppt == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((pt = *ppt) == NULL)
+        return;
+
+    pixDestroy(&pt->pix);
+    LEPT_FREE(pt);
+    *ppt = NULL;
+    return;
+}
+
+
+/*!
+ *  pixTilingGetCount()
+ *
+ *      Input:  pt (pixtiling)
+ *              &nx (<optional return> nx; can be null)
+ *              &ny (<optional return> ny; can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixTilingGetCount(PIXTILING  *pt,
+                  l_int32    *pnx,
+                  l_int32    *pny)
+{
+    PROCNAME("pixTilingGetCount");
+
+    if (!pt)
+        return ERROR_INT("pt not defined", procName, 1);
+    if (pnx) *pnx = pt->nx;
+    if (pny) *pny = pt->ny;
+    return 0;
+}
+
+
+/*!
+ *  pixTilingGetSize()
+ *
+ *      Input:  pt (pixtiling)
+ *              &w (<optional return> tile width; can be null)
+ *              &h (<optional return> tile height; can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixTilingGetSize(PIXTILING  *pt,
+                 l_int32    *pw,
+                 l_int32    *ph)
+{
+    PROCNAME("pixTilingGetSize");
+
+    if (!pt)
+        return ERROR_INT("pt not defined", procName, 1);
+    if (pw) *pw = pt->w;
+    if (ph) *ph = pt->h;
+    return 0;
+}
+
+
+/*!
+ *  pixTilingGetTile()
+ *
+ *      Input:  pt (pixtiling)
+ *              i (tile row index)
+ *              j (tile column index)
+ *      Return: pixd (tile with appropriate boundary (overlap) pixels added),
+ *                    or null on error
+ */
+PIX *
+pixTilingGetTile(PIXTILING  *pt,
+                 l_int32     i,
+                 l_int32     j)
+{
+l_int32  wpix, hpix, wt, ht, nx, ny;
+l_int32  xoverlap, yoverlap, wtlast, htlast;
+l_int32  left, top, xtraleft, xtraright, xtratop, xtrabot, width, height;
+BOX     *box;
+PIX     *pixs, *pixt, *pixd;
+
+    PROCNAME("pixTilingGetTile");
+
+    if (!pt)
+        return (PIX *)ERROR_PTR("pt not defined", procName, NULL);
+    if ((pixs = pt->pix) == NULL)
+        return (PIX *)ERROR_PTR("pix not found", procName, NULL);
+    pixTilingGetCount(pt, &nx, &ny);
+    if (i < 0 || i >= ny)
+        return (PIX *)ERROR_PTR("invalid row index i", procName, NULL);
+    if (j < 0 || j >= nx)
+        return (PIX *)ERROR_PTR("invalid column index j", procName, NULL);
+
+        /* Grab the tile with as much overlap as exists within the
+         * input pix.   First, compute the (left, top) coordinates.  */
+    pixGetDimensions(pixs, &wpix, &hpix, NULL);
+    pixTilingGetSize(pt, &wt, &ht);
+    xoverlap = pt->xoverlap;
+    yoverlap = pt->yoverlap;
+    wtlast = wpix - wt * (nx - 1);
+    htlast = hpix - ht * (ny - 1);
+    left = L_MAX(0, j * wt - xoverlap);
+    top = L_MAX(0, i * ht - yoverlap);
+
+        /* Get the width and height of the tile, including whatever
+         * overlap is available. */
+    if (nx == 1)
+        width = wpix;
+    else if (j == 0)
+        width = wt + xoverlap;
+    else if (j == nx - 1)
+        width = wtlast + xoverlap;
+    else
+        width = wt + 2 * xoverlap;
+
+    if (ny == 1)
+        height = hpix;
+    else if (i == 0)
+        height = ht + yoverlap;
+    else if (i == ny - 1)
+        height = htlast + yoverlap;
+    else
+        height = ht + 2 * yoverlap;
+    box = boxCreate(left, top, width, height);
+    pixt = pixClipRectangle(pixs, box, NULL);
+    boxDestroy(&box);
+
+        /* If no overlap, do not add any special case borders */
+    if (xoverlap == 0 && yoverlap == 0)
+        return pixt;
+
+        /* Add overlap as a mirrored border, in the 8 special cases where
+         * the tile touches the border of the input pix.  The xtratop (etc)
+         * parameters are required where the tile is either full width
+         * or full height.  */
+    xtratop = xtrabot = xtraleft = xtraright = 0;
+    if (nx == 1)
+        xtraleft = xtraright = xoverlap;
+    if (ny == 1)
+        xtratop = xtrabot = yoverlap;
+    if (i == 0 && j == 0)
+        pixd = pixAddMirroredBorder(pixt, xoverlap, xtraright,
+                                    yoverlap, xtrabot);
+    else if (i == 0 && j == nx - 1)
+        pixd = pixAddMirroredBorder(pixt, xtraleft, xoverlap,
+                                    yoverlap, xtrabot);
+    else if (i == ny - 1 && j == 0)
+        pixd = pixAddMirroredBorder(pixt, xoverlap, xtraright,
+                                    xtratop, yoverlap);
+    else if (i == ny - 1 && j == nx - 1)
+        pixd = pixAddMirroredBorder(pixt, xtraleft, xoverlap,
+                                    xtratop, yoverlap);
+    else if (i == 0)
+        pixd = pixAddMirroredBorder(pixt, 0, 0, yoverlap, xtrabot);
+    else if (i == ny - 1)
+        pixd = pixAddMirroredBorder(pixt, 0, 0, xtratop, yoverlap);
+    else if (j == 0)
+        pixd = pixAddMirroredBorder(pixt, xoverlap, xtraright, 0, 0);
+    else if (j == nx - 1)
+        pixd = pixAddMirroredBorder(pixt, xtraleft, xoverlap, 0, 0);
+    else
+        pixd = pixClone(pixt);
+    pixDestroy(&pixt);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixTilingNoStripOnPaint()
+ *
+ *      Input:  pt (pixtiling)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The default for paint is to strip out the overlap pixels
+ *          that are added by pixTilingGetTile().  However, some
+ *          operations will generate an image with these pixels
+ *          stripped off.  This tells the paint operation not
+ *          to strip the added boundary pixels when painting.
+ */
+l_int32
+pixTilingNoStripOnPaint(PIXTILING  *pt)
+{
+    PROCNAME("pixTilingNoStripOnPaint");
+
+    if (!pt)
+        return ERROR_INT("pt not defined", procName, 1);
+    pt->strip = FALSE;
+    return 0;
+}
+
+
+/*!
+ *  pixTilingPaintTile()
+ *
+ *      Input:  pixd (dest: paint tile onto this, without overlap)
+ *              i (tile row index)
+ *              j (tile column index)
+ *              pixs (source: tile to be painted from)
+ *              pt (pixtiling struct)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixTilingPaintTile(PIX        *pixd,
+                   l_int32     i,
+                   l_int32     j,
+                   PIX        *pixs,
+                   PIXTILING  *pt)
+{
+l_int32  w, h;
+
+    PROCNAME("pixTilingPaintTile");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pt)
+        return ERROR_INT("pt not defined", procName, 1);
+    if (i < 0 || i >= pt->ny)
+        return ERROR_INT("invalid row index i", procName, 1);
+    if (j < 0 || j >= pt->nx)
+        return ERROR_INT("invalid column index j", procName, 1);
+
+        /* Strip added border pixels off if requested */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (pt->strip == TRUE) {
+        pixRasterop(pixd, j * pt->w, i * pt->h,
+                    w - 2 * pt->xoverlap, h - 2 * pt->yoverlap, PIX_SRC,
+                    pixs, pt->xoverlap, pt->yoverlap);
+    } else {
+        pixRasterop(pixd, j * pt->w, i * pt->h, w, h, PIX_SRC, pixs, 0, 0);
+    }
+
+    return 0;
+}
+
diff --git a/src/pngio.c b/src/pngio.c
new file mode 100644 (file)
index 0000000..1c30a68
--- /dev/null
@@ -0,0 +1,1352 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pngio.c
+ *
+ *    Read png from file
+ *          PIX        *pixReadStreamPng()
+ *          l_int32     readHeaderPng()
+ *          l_int32     freadHeaderPng()
+ *          l_int32     readHeaderMemPng()
+ *          l_int32     fgetPngResolution()
+ *          l_int32     isPngInterlaced()
+ *          l_int32     fgetPngColormapInfo()
+ *
+ *    Write png to file
+ *          l_int32     pixWritePng()  [ special top level ]
+ *          l_int32     pixWriteStreamPng()
+ *          l_int32     pixSetZlibCompression()
+ *
+ *    Setting flag for special read mode
+ *          void        l_pngSetReadStrip16To8()
+ *
+ *    Read/write to memory
+ *          PIX        *pixReadMemPng()
+ *          l_int32     pixWriteMemPng()
+ *
+ *    Documentation: libpng.txt and example.c
+ *
+ *    On input (decompression from file), palette color images
+ *    are read into an 8 bpp Pix with a colormap, and 24 bpp
+ *    3 component color images are read into a 32 bpp Pix with
+ *    rgb samples.  On output (compression to file), palette color
+ *    images are written as 8 bpp with the colormap, and 32 bpp
+ *    full color images are written compressed as a 24 bpp,
+ *    3 component color image.
+ *
+ *    In the following, we use these abbreviations:
+ *       bps == bit/sample
+ *       spp == samples/pixel
+ *       bpp == bits/pixel of image in Pix (memory)
+ *    where each component is referred to as a "sample".
+ *
+ *    For reading and writing rgb and rgba images, we read and write
+ *    alpha if it exists (spp == 4) and do not read or write if
+ *    it doesn't (spp == 3).  The alpha component can be 'removed'
+ *    simply by setting spp to 3.  In leptonica, we make relatively
+ *    little explicit use of the alpha sample.  Note that the alpha
+ *    sample in the image is also called "alpha transparency",
+ *    "alpha component" and "alpha layer."
+ *
+ *    To change the zlib compression level, use pixSetZlibCompression()
+ *    before writing the file.  The default is for standard png compression.
+ *    The zlib compression value can be set [0 ... 9], with
+ *         0     no compression (huge files)
+ *         1     fastest compression
+ *         -1    default compression  (equivalent to 6 in latest version)
+ *         9     best compression
+ *    Note that if you are using the defined constants in zlib instead
+ *    of the compression integers given above, you must include zlib.h.
+ *
+ *    There is global for determining the size of retained samples:
+ *             var_PNG_STRIP_16_to_8
+ *    and a function l_pngSetReadStrip16To8() for setting it.
+ *    The default is TRUE, which causes pixRead() to strip each 16 bit
+ *    sample down to 8 bps:
+ *     - For 16 bps rgb (16 bps, 3 spp) --> 32 bpp rgb Pix
+ *     - For 16 bps gray (16 bps, 1 spp) --> 8 bpp grayscale Pix
+ *    If the variable is set to FALSE, the 16 bit gray samples
+ *    are saved when read; the 16 bit rgb samples return an error.
+ *    Note: results can be non-deterministic if used with
+ *    multi-threaded applications.
+ *
+ *    On systems like windows without fmemopen() and open_memstream(),
+ *    we write data to a temp file and read it back for operations
+ *    between pix and compressed-data, such as pixReadMemPng() and
+ *    pixWriteMemPng().
+ */
+
+#ifdef  HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  HAVE_LIBPNG   /* defined in environ.h */
+/* --------------------------------------------*/
+
+#include "png.h"
+
+#if  HAVE_LIBZ
+#include "zlib.h"
+#else
+#define  Z_DEFAULT_COMPRESSION (-1)
+#endif  /* HAVE_LIBZ */
+
+/* ------------------ Set default for read option -------------------- */
+    /* Strip 16 bpp --> 8 bpp on reading png; default is for stripping.
+     * If you don't strip, you can't read the gray-alpha spp = 2 images. */
+static l_int32   var_PNG_STRIP_16_TO_8 = 1;
+
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_READ     0
+#define  DEBUG_WRITE    0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*---------------------------------------------------------------------*
+ *                              Reading png                            *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixReadStreamPng()
+ *
+ *      Input:  stream
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) If called from pixReadStream(), the stream is positioned
+ *          at the beginning of the file.
+ *      (2) To do sequential reads of png format images from a stream,
+ *          use pixReadStreamPng()
+ *      (3) Any image with alpha is converted to RGBA (spp = 4, with
+ *          equal red, green and blue channels) on reading.
+ *          There are three important cases with alpha:
+ *          (a) grayscale-with-alpha (spp = 2), where bpp = 8, and each
+ *              pixel has an associated alpha (transparency) value
+ *              in the second component of the image data.
+ *          (b) spp = 1, d = 1 with colormap and alpha in the trans array.
+ *              Transparency is usually associated with the white background.
+ *          (c) spp = 1, d = 8 with colormap and alpha in the trans array.
+ *              Each color in the colormap has a separate transparency value.
+ *      (4) We use the high level png interface, where the transforms are set
+ *          up in advance and the header and image are read with a single
+ *          call.  The more complicated interface, where the header is
+ *          read first and the buffers for the raster image are user-
+ *          allocated before reading the image, works for single images,
+ *          but I could not get it to work properly for the successive
+ *          png reads that are required by pixaReadStream().
+ */
+PIX *
+pixReadStreamPng(FILE  *fp)
+{
+l_uint8      byte;
+l_int32      rval, gval, bval;
+l_int32      i, j, k, index, ncolors, bitval;
+l_int32      wpl, d, spp, cindex, tRNS;
+l_uint32     png_transforms;
+l_uint32    *data, *line, *ppixel;
+int          num_palette, num_text, num_trans;
+png_byte     bit_depth, color_type, channels;
+png_uint_32  w, h, rowbytes;
+png_uint_32  xres, yres;
+png_bytep    rowptr, trans;
+png_bytep   *row_pointers;
+png_structp  png_ptr;
+png_infop    info_ptr, end_info;
+png_colorp   palette;
+png_textp    text_ptr;  /* ptr to text_chunk */
+PIX         *pix, *pixt;
+PIXCMAP     *cmap;
+
+    PROCNAME("pixReadStreamPng");
+
+    if (!fp)
+        return (PIX *)ERROR_PTR("fp not defined", procName, NULL);
+    pix = NULL;
+
+        /* Allocate the 3 data structures */
+    if ((png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+                   (png_voidp)NULL, NULL, NULL)) == NULL)
+        return (PIX *)ERROR_PTR("png_ptr not made", procName, NULL);
+
+    if ((info_ptr = png_create_info_struct(png_ptr)) == NULL) {
+        png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
+        return (PIX *)ERROR_PTR("info_ptr not made", procName, NULL);
+    }
+
+    if ((end_info = png_create_info_struct(png_ptr)) == NULL) {
+        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
+        return (PIX *)ERROR_PTR("end_info not made", procName, NULL);
+    }
+
+        /* Set up png setjmp error handling */
+    if (setjmp(png_jmpbuf(png_ptr))) {
+        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
+        return (PIX *)ERROR_PTR("internal png error", procName, NULL);
+    }
+
+    png_init_io(png_ptr, fp);
+
+        /* ---------------------------------------------------------- *
+         *  Set the transforms flags.  Whatever happens here,
+         *  NEVER invert 1 bpp using PNG_TRANSFORM_INVERT_MONO.
+         *  Also, do not use PNG_TRANSFORM_EXPAND, which would
+         *  expand all images with bpp < 8 to 8 bpp.
+         * ---------------------------------------------------------- */
+        /* To strip 16 --> 8 bit depth, use PNG_TRANSFORM_STRIP_16 */
+    if (var_PNG_STRIP_16_TO_8 == 1) {  /* our default */
+        png_transforms = PNG_TRANSFORM_STRIP_16;
+    } else {
+        png_transforms = PNG_TRANSFORM_IDENTITY;
+        L_INFO("not stripping 16 --> 8 in png reading\n", procName);
+    }
+
+        /* Read it */
+    png_read_png(png_ptr, info_ptr, png_transforms, NULL);
+
+    row_pointers = png_get_rows(png_ptr, info_ptr);
+    w = png_get_image_width(png_ptr, info_ptr);
+    h = png_get_image_height(png_ptr, info_ptr);
+    bit_depth = png_get_bit_depth(png_ptr, info_ptr);
+    rowbytes = png_get_rowbytes(png_ptr, info_ptr);
+    color_type = png_get_color_type(png_ptr, info_ptr);
+    channels = png_get_channels(png_ptr, info_ptr);
+    spp = channels;
+    tRNS = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ? 1 : 0;
+
+    if (spp == 1) {
+        d = bit_depth;
+    } else {  /* spp == 2 (gray + alpha), spp == 3 (rgb), spp == 4 (rgba) */
+        d = 4 * bit_depth;
+    }
+
+        /* Remove if/when this is implemented for all bit_depths */
+    if (spp == 3 && bit_depth != 8) {
+        fprintf(stderr, "Help: spp = 3 and depth = %d != 8\n!!", bit_depth);
+        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
+        return (PIX *)ERROR_PTR("not implemented for this depth",
+            procName, NULL);
+    }
+
+    if (color_type == PNG_COLOR_TYPE_PALETTE ||
+        color_type == PNG_COLOR_MASK_PALETTE) {   /* generate a colormap */
+        png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
+        cmap = pixcmapCreate(d);  /* spp == 1 */
+        for (cindex = 0; cindex < num_palette; cindex++) {
+            rval = palette[cindex].red;
+            gval = palette[cindex].green;
+            bval = palette[cindex].blue;
+            pixcmapAddColor(cmap, rval, gval, bval);
+        }
+    } else {
+        cmap = NULL;
+    }
+
+    if ((pix = pixCreate(w, h, d)) == NULL) {
+        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
+        return (PIX *)ERROR_PTR("pix not made", procName, NULL);
+    }
+    pixSetInputFormat(pix, IFF_PNG);
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    pixSetColormap(pix, cmap);
+    pixSetSpp(pix, spp);
+
+    if (spp == 1 && !tRNS) {  /* copy straight from buffer to pix */
+        for (i = 0; i < h; i++) {
+            line = data + i * wpl;
+            rowptr = row_pointers[i];
+            for (j = 0; j < rowbytes; j++) {
+                    SET_DATA_BYTE(line, j, rowptr[j]);
+            }
+        }
+    } else if (spp == 2) {  /* grayscale + alpha; convert to RGBA */
+        L_INFO("converting (gray + alpha) ==> RGBA\n", procName);
+        for (i = 0; i < h; i++) {
+            ppixel = data + i * wpl;
+            rowptr = row_pointers[i];
+            for (j = k = 0; j < w; j++) {
+                    /* Copy gray value into r, g and b */
+                SET_DATA_BYTE(ppixel, COLOR_RED, rowptr[k]);
+                SET_DATA_BYTE(ppixel, COLOR_GREEN, rowptr[k]);
+                SET_DATA_BYTE(ppixel, COLOR_BLUE, rowptr[k++]);
+                SET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL, rowptr[k++]);
+                ppixel++;
+            }
+        }
+        pixSetSpp(pix, 4);  /* we do not support 2 spp pix */
+    } else if (spp == 3 || spp == 4) {
+        for (i = 0; i < h; i++) {
+            ppixel = data + i * wpl;
+            rowptr = row_pointers[i];
+            for (j = k = 0; j < w; j++) {
+                SET_DATA_BYTE(ppixel, COLOR_RED, rowptr[k++]);
+                SET_DATA_BYTE(ppixel, COLOR_GREEN, rowptr[k++]);
+                SET_DATA_BYTE(ppixel, COLOR_BLUE, rowptr[k++]);
+                if (spp == 4)
+                    SET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL, rowptr[k++]);
+                ppixel++;
+            }
+        }
+    }
+
+        /* Special spp == 1 cases with transparency:
+         *    (1) 8 bpp without colormap; assume full transparency
+         *    (2) 1 bpp with colormap + trans array (for alpha)
+         *    (3) 8 bpp with colormap + trans array (for alpha)
+         * These all require converting to RGBA */
+    if (spp == 1 && tRNS) {
+        if (!cmap) {
+                /* Case 1: make fully transparent RGBA image */
+            L_INFO("transparency, 1 spp, no colormap, no transparency array: "
+                   "convention is fully transparent image\n", procName);
+            L_INFO("converting (fully transparent 1 spp) ==> RGBA\n", procName);
+            pixDestroy(&pix);
+            pix = pixCreate(w, h, 32);  /* init to alpha = 0 (transparent) */
+            pixSetSpp(pix, 4);
+        } else {
+            L_INFO("converting (cmap + alpha) ==> RGBA\n", procName);
+
+                /* Grab the transparency array */
+            png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
+            if (!trans) {  /* invalid png file */
+                pixDestroy(&pix);
+                png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
+                return (PIX *)ERROR_PTR("cmap, tRNS, but no transparency array",
+                                        procName, NULL);
+            }
+
+                /* Save the cmap and destroy the pix */
+            cmap = pixcmapCopy(pixGetColormap(pix));
+            ncolors = pixcmapGetCount(cmap);
+            pixDestroy(&pix);
+
+                /* Start over with 32 bit RGBA */
+            pix = pixCreate(w, h, 32);
+            wpl = pixGetWpl(pix);
+            data = pixGetData(pix);
+            pixSetSpp(pix, 4);
+
+#if DEBUG_READ
+            fprintf(stderr, "ncolors = %d, num_trans = %d\n",
+                    ncolors, num_trans);
+            for (i = 0; i < ncolors; i++) {
+                pixcmapGetColor(cmap, i, &rval, &gval, &bval);
+                if (i < num_trans) {
+                    fprintf(stderr, "(r,g,b,a) = (%d,%d,%d,%d)\n",
+                            rval, gval, bval, trans[i]);
+                } else {
+                    fprintf(stderr, "(r,g,b,a) = (%d,%d,%d,<<255>>)\n",
+                            rval, gval, bval);
+                }
+            }
+#endif  /* DEBUG_READ */
+
+                /* Extract the data and convert to RGBA */
+            if (d == 1) {
+                    /* Case 2: 1 bpp with transparency (usually) behind white */
+                L_INFO("converting 1 bpp cmap with alpha ==> RGBA\n", procName);
+                if (num_trans == 1)
+                    L_INFO("num_trans = 1; second color opaque by default\n",
+                           procName);
+                for (i = 0; i < h; i++) {
+                    ppixel = data + i * wpl;
+                    rowptr = row_pointers[i];
+                    for (j = 0, index = 0; j < rowbytes; j++) {
+                        byte = rowptr[j];
+                        for (k = 0; k < 8 && index < w; k++, index++) {
+                            bitval = (byte >> (7 - k)) & 1;
+                            pixcmapGetColor(cmap, bitval, &rval, &gval, &bval);
+                            composeRGBPixel(rval, gval, bval, ppixel);
+                            SET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL,
+                                      bitval < num_trans ? trans[bitval] : 255);
+                            ppixel++;
+                        }
+                    }
+                }
+            } else if (d == 8) {
+                    /* Case 3: 8 bpp with cmap and associated transparency */
+                L_INFO("converting 8 bpp cmap with alpha ==> RGBA\n", procName);
+                for (i = 0; i < h; i++) {
+                    ppixel = data + i * wpl;
+                    rowptr = row_pointers[i];
+                    for (j = 0; j < w; j++) {
+                        index = rowptr[j];
+                        pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+                        composeRGBPixel(rval, gval, bval, ppixel);
+                            /* Assume missing entries to be 255 (opaque)
+                             * according to the spec:
+                             * http://www.w3.org/TR/PNG/#11tRNS */
+                        SET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL,
+                                      index < num_trans ? trans[index] : 255);
+                        ppixel++;
+                    }
+                }
+            } else {
+                L_ERROR("spp == 1, cmap, trans array, invalid depth: %d\n",
+                        procName, d);
+            }
+            pixcmapDestroy(&cmap);
+        }
+    }
+
+#if  DEBUG_READ
+    if (cmap) {
+        for (i = 0; i < 16; i++) {
+            fprintf(stderr, "[%d] = %d\n", i,
+                   ((l_uint8 *)(cmap->array))[i]);
+        }
+    }
+#endif  /* DEBUG_READ */
+
+        /* Final adjustments for bpp = 1.
+         *   + If there is no colormap, the image must be inverted because
+         *     png stores black pixels as 0.
+         *   + We have already handled the case of cmapped, 1 bpp pix
+         *     with transparency, where the output pix is 32 bpp RGBA.
+         *     If there is no transparency but the pix has a colormap,
+         *     we remove the colormap, because functions operating on
+         *     1 bpp images in leptonica assume no colormap.
+         *   + The colormap must be removed in such a way that the pixel
+         *     values are not changed.  If the values are only black and
+         *     white, we return a 1 bpp image; if gray, return an 8 bpp pix;
+         *     otherwise, return a 32 bpp rgb pix.
+         *
+         * Note that we cannot use the PNG_TRANSFORM_INVERT_MONO flag
+         * to do the inversion, because that flag (since version 1.0.9)
+         * inverts 8 bpp grayscale as well, which we don't want to do.
+         * (It also doesn't work if there is a colormap.)
+         *
+         * Note that if the input png is a 1-bit with colormap and
+         * transparency, it has already been rendered as a 32 bpp,
+         * spp = 4 rgba pix.
+         */
+    if (pixGetDepth(pix) == 1) {
+        if (!cmap) {
+            pixInvert(pix, pix);
+        } else {
+            pixt = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+            pixDestroy(&pix);
+            pix = pixt;
+        }
+    }
+
+    xres = png_get_x_pixels_per_meter(png_ptr, info_ptr);
+    yres = png_get_y_pixels_per_meter(png_ptr, info_ptr);
+    pixSetXRes(pix, (l_int32)((l_float32)xres / 39.37 + 0.5));  /* to ppi */
+    pixSetYRes(pix, (l_int32)((l_float32)yres / 39.37 + 0.5));  /* to ppi */
+
+        /* Get the text if there is any */
+    png_get_text(png_ptr, info_ptr, &text_ptr, &num_text);
+    if (num_text && text_ptr)
+        pixSetText(pix, text_ptr->text);
+
+    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
+    return pix;
+}
+
+
+/*!
+ *  readHeaderPng()
+ *
+ *      Input:  filename
+ *              &w (<optional return>)
+ *              &h (<optional return>)
+ *              &bps (<optional return>, bits/sample)
+ *              &spp (<optional return>, samples/pixel)
+ *              &iscmap (<optional return>)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If there is a colormap, iscmap is returned as 1; else 0.
+ *      (2) For gray+alpha, although the png records bps = 16, we
+ *          consider this as two 8 bpp samples (gray and alpha).
+ *          When a gray+alpha is read, it is converted to 32 bpp RGBA.
+ */
+l_int32
+readHeaderPng(const char *filename,
+              l_int32    *pw,
+              l_int32    *ph,
+              l_int32    *pbps,
+              l_int32    *pspp,
+              l_int32    *piscmap)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("readHeaderPng");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pbps) *pbps = 0;
+    if (pspp) *pspp = 0;
+    if (piscmap) *piscmap = 0;
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT("image file not found", procName, 1);
+    ret = freadHeaderPng(fp, pw, ph, pbps, pspp, piscmap);
+    fclose(fp);
+    return ret;
+}
+
+
+/*!
+ *  freadHeaderPng()
+ *
+ *      Input:  stream
+ *              &w (<optional return>)
+ *              &h (<optional return>)
+ *              &bps (<optional return>, bits/sample)
+ *              &spp (<optional return>, samples/pixel)
+ *              &iscmap (<optional return>)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See readHeaderPng().  We only need the first 40 bytes in the file.
+ */
+l_int32
+freadHeaderPng(FILE     *fp,
+               l_int32  *pw,
+               l_int32  *ph,
+               l_int32  *pbps,
+               l_int32  *pspp,
+               l_int32  *piscmap)
+{
+l_int32   nbytes, ret;
+l_uint8  *data;
+
+    PROCNAME("freadHeaderPng");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pbps) *pbps = 0;
+    if (pspp) *pspp = 0;
+    if (piscmap) *piscmap = 0;
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+
+    nbytes = fnbytesInFile(fp);
+    if (nbytes < 40)
+        return ERROR_INT("file too small to be png", procName, 1);
+    if ((data = (l_uint8 *)LEPT_CALLOC(40, sizeof(l_uint8))) == NULL)
+        return ERROR_INT("LEPT_CALLOC fail for data", procName, 1);
+    if (fread(data, 1, 40, fp) != 40)
+        return ERROR_INT("error reading data", procName, 1);
+    ret = readHeaderMemPng(data, 40, pw, ph, pbps, pspp, piscmap);
+    LEPT_FREE(data);
+    return ret;
+}
+
+
+/*!
+ *  readHeaderMemPng()
+ *
+ *      Input:  data
+ *              size (40 bytes is sufficient)
+ *              &w (<optional return>)
+ *              &h (<optional return>)
+ *              &bps (<optional return>, bits/sample)
+ *              &spp (<optional return>, samples/pixel)
+ *              &iscmap (<optional return>; input NULL to ignore)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See readHeaderPng().
+ *      (2) png colortypes (see png.h: PNG_COLOR_TYPE_*):
+ *          0:  gray; fully transparent (with tRNS) (1 spp)
+ *          2:  RGB (3 spp)
+ *          3:  colormap; colormap+alpha (with tRNS) (1 spp)
+ *          4:  gray + alpha (2 spp)
+ *          6:  RGBA (4 spp)
+ *          Note:
+ *            0 and 3 have the alpha information in a tRNS chunk
+ *            4 and 6 have separate alpha samples with each pixel.
+ */
+l_int32
+readHeaderMemPng(const l_uint8  *data,
+                 size_t          size,
+                 l_int32        *pw,
+                 l_int32        *ph,
+                 l_int32        *pbps,
+                 l_int32        *pspp,
+                 l_int32        *piscmap)
+{
+l_uint16   twobytes;
+l_uint16  *pshort;
+l_int32    colortype, bps, spp;
+l_uint32  *pword;
+
+    PROCNAME("readHeaderMemPng");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pbps) *pbps = 0;
+    if (pspp) *pspp = 0;
+    if (piscmap) *piscmap = 0;
+    if (!data)
+        return ERROR_INT("data not defined", procName, 1);
+    if (size < 40)
+        return ERROR_INT("size < 40", procName, 1);
+
+        /* Check password */
+    if (data[0] != 137 || data[1] != 80 || data[2] != 78 ||
+        data[3] != 71 || data[4] != 13 || data[5] != 10 ||
+        data[6] != 26 || data[7] != 10)
+        return ERROR_INT("not a valid png file", procName, 1);
+
+    pword = (l_uint32 *)data;
+    pshort = (l_uint16 *)data;
+    if (pw) *pw = convertOnLittleEnd32(pword[4]);
+    if (ph) *ph = convertOnLittleEnd32(pword[5]);
+    twobytes = convertOnLittleEnd16(pshort[12]); /* contains depth/sample  */
+                                                 /* and the color type     */
+    colortype = twobytes & 0xff;  /* color type */
+    bps = twobytes >> 8;   /* bits/sample */
+
+        /* Special case with alpha that is extracted as RGBA.
+         * Note that the cmap+alpha is also extracted as RGBA,
+         * but only if the tRNS chunk exists, which we can't tell
+         * by this simple parser.*/
+    if (colortype == 4)
+        L_INFO("gray + alpha: will extract as RGBA (spp = 4)\n", procName);
+
+    if (colortype == 2) {  /* RGB */
+        spp = 3;
+    } else if (colortype == 6) {  /* RGBA */
+        spp = 4;
+    } else if (colortype == 4) {  /* gray + alpha */
+        spp = 2;
+        bps = 8;  /* both the gray and alpha are 8-bit samples */
+    } else {  /* gray (0) or cmap (3) or cmap+alpha (3) */
+        spp = 1;
+    }
+    if (pbps) *pbps = bps;
+    if (pspp) *pspp = spp;
+    if (piscmap) {
+        if (colortype & 1)  /* palette */
+            *piscmap = 1;
+        else
+            *piscmap = 0;
+    }
+
+    return 0;
+}
+
+
+/*
+ *  fgetPngResolution()
+ *
+ *      Input:  stream (opened for read)
+ *              &xres, &yres (<return> resolution in ppi)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) If neither resolution field is set, this is not an error;
+ *          the returned resolution values are 0 (designating 'unknown').
+ *      (2) Side-effect: this rewinds the stream.
+ */
+l_int32
+fgetPngResolution(FILE     *fp,
+                  l_int32  *pxres,
+                  l_int32  *pyres)
+{
+png_uint_32  xres, yres;
+png_structp  png_ptr;
+png_infop    info_ptr;
+
+    PROCNAME("fgetPngResolution");
+
+    if (pxres) *pxres = 0;
+    if (pyres) *pyres = 0;
+    if (!fp)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (!pxres || !pyres)
+        return ERROR_INT("&xres and &yres not both defined", procName, 1);
+
+       /* Make the two required structs */
+    if ((png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+                   (png_voidp)NULL, NULL, NULL)) == NULL)
+        return ERROR_INT("png_ptr not made", procName, 1);
+    if ((info_ptr = png_create_info_struct(png_ptr)) == NULL) {
+        png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
+        return ERROR_INT("info_ptr not made", procName, 1);
+    }
+
+        /* Set up png setjmp error handling.
+         * Without this, an error calls exit. */
+    if (setjmp(png_jmpbuf(png_ptr))) {
+        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
+        return ERROR_INT("internal png error", procName, 1);
+    }
+
+        /* Read the metadata */
+    rewind(fp);
+    png_init_io(png_ptr, fp);
+    png_read_png(png_ptr, info_ptr, 0, NULL);
+
+    xres = png_get_x_pixels_per_meter(png_ptr, info_ptr);
+    yres = png_get_y_pixels_per_meter(png_ptr, info_ptr);
+    *pxres = (l_int32)((l_float32)xres / 39.37 + 0.5);  /* to ppi */
+    *pyres = (l_int32)((l_float32)yres / 39.37 + 0.5);
+
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+    rewind(fp);
+    return 0;
+}
+
+
+/*!
+ *  isPngInterlaced()
+ *
+ *      Input:  filename
+ *              &interlaced (<return> 1 if interlaced png; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+isPngInterlaced(const char *filename,
+                l_int32    *pinterlaced)
+{
+l_uint8  buf[32];
+FILE    *fp;
+
+    PROCNAME("isPngInterlaced");
+
+    if (!pinterlaced)
+        return ERROR_INT("&interlaced not defined", procName, 1);
+    *pinterlaced = 0;
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (fread(buf, 1, 32, fp) != 32)
+        return ERROR_INT("data not read", procName, 1);
+    fclose(fp);
+
+    *pinterlaced = (buf[28] == 0) ? 0 : 1;
+    return 0;
+}
+
+
+/*
+ *  fgetPngColormapInfo()
+ *
+ *      Input:  stream (opened for read)
+ *              &cmap (optional <return>; use NULL to skip)
+ *              &transparency (optional <return> 1 if colormapped with
+ *                transparency, 0 otherwise; use NULL to skip)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The transparency information in a png is in the tRNA array,
+ *          which is separate from the colormap.  If this array exists
+ *          and if any element is less than 255, there exists some
+ *          transparency.
+ *      (2) Side-effect: this rewinds the stream.
+ */
+l_int32
+fgetPngColormapInfo(FILE      *fp,
+                    PIXCMAP  **pcmap,
+                    l_int32   *ptransparency)
+{
+l_int32      i, cindex, rval, gval, bval, num_palette, num_trans;
+png_byte     bit_depth, color_type;
+png_bytep    trans;
+png_colorp   palette;
+png_structp  png_ptr;
+png_infop    info_ptr;
+
+    PROCNAME("fgetPngColormapInfo");
+
+    if (pcmap) *pcmap = NULL;
+    if (ptransparency) *ptransparency = 0;
+    if (!pcmap && !ptransparency)
+        return ERROR_INT("no output defined", procName, 1);
+    if (!fp)
+        return ERROR_INT("stream not opened", procName, 1);
+
+       /* Make the two required structs */
+    if ((png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+                   (png_voidp)NULL, NULL, NULL)) == NULL)
+        return ERROR_INT("png_ptr not made", procName, 1);
+    if ((info_ptr = png_create_info_struct(png_ptr)) == NULL) {
+        png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
+        return ERROR_INT("info_ptr not made", procName, 1);
+    }
+
+        /* Set up png setjmp error handling.
+         * Without this, an error calls exit. */
+    if (setjmp(png_jmpbuf(png_ptr))) {
+        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+        if (pcmap && *pcmap) pixcmapDestroy(pcmap);
+        return ERROR_INT("internal png error", procName, 1);
+    }
+
+        /* Read the metadata and check if there is a colormap */
+    rewind(fp);
+    png_init_io(png_ptr, fp);
+    png_read_png(png_ptr, info_ptr, 0, NULL);
+    color_type = png_get_color_type(png_ptr, info_ptr);
+    if (color_type != PNG_COLOR_TYPE_PALETTE &&
+        color_type != PNG_COLOR_MASK_PALETTE) {
+        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+        return 0;
+    }
+
+        /* Optionally, read the colormap */
+    if (pcmap) {
+        bit_depth = png_get_bit_depth(png_ptr, info_ptr);
+        png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
+        *pcmap = pixcmapCreate(bit_depth);  /* spp == 1 */
+        for (cindex = 0; cindex < num_palette; cindex++) {
+            rval = palette[cindex].red;
+            gval = palette[cindex].green;
+            bval = palette[cindex].blue;
+            pixcmapAddColor(*pcmap, rval, gval, bval);
+        }
+    }
+
+        /* Optionally, look for transparency.  Note that the colormap
+         * has been initialized to fully opaque. */
+    if (ptransparency && png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+        png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
+        if (trans) {
+            for (i = 0; i < num_trans; i++) {
+                if (trans[i] < 255) {  /* not fully opaque */
+                    *ptransparency = 1;
+                    if (pcmap) pixcmapSetAlpha(*pcmap, i, trans[i]);
+                }
+            }
+        } else {
+            L_ERROR("transparency array not returned\n", procName);
+        }
+    }
+
+    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+    rewind(fp);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                              Writing png                            *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixWritePng()
+ *
+ *      Input:  filename
+ *              pix
+ *              gamma
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Special version for writing png with a specified gamma.
+ *          When using pixWrite(), no field is given for gamma.
+ */
+l_int32
+pixWritePng(const char  *filename,
+            PIX         *pix,
+            l_float32    gamma)
+{
+FILE  *fp;
+
+    PROCNAME("pixWritePng");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb+")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+
+    if (pixWriteStreamPng(fp, pix, gamma)) {
+        fclose(fp);
+        return ERROR_INT("pix not written to stream", procName, 1);
+    }
+
+    fclose(fp);
+    return 0;
+}
+
+
+/*!
+ *  pixWriteStreamPng()
+ *
+ *      Input:  stream
+ *              pix
+ *              gamma (use 0.0 if gamma is not defined)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) If called from pixWriteStream(), the stream is positioned
+ *          at the beginning of the file.
+ *      (2) To do sequential writes of png format images to a stream,
+ *          use pixWriteStreamPng() directly.
+ *      (3) gamma is an optional png chunk.  If no gamma value is to be
+ *          placed into the file, use gamma = 0.0.  Otherwise, if
+ *          gamma > 0.0, its value is written into the header.
+ *      (4) The use of gamma in png is highly problematic.  For an illuminating
+ *          discussion, see:  http://hsivonen.iki.fi/png-gamma/
+ *      (5) What is the effect/meaning of gamma in the png file?  This
+ *          gamma, which we can call the 'source' gamma, is the
+ *          inverse of the gamma that was used in enhance.c to brighten
+ *          or darken images.  The 'source' gamma is supposed to indicate
+ *          the intensity mapping that was done at the time the
+ *          image was captured.  Display programs typically apply a
+ *          'display' gamma of 2.2 to the output, which is intended
+ *          to linearize the intensity based on the response of
+ *          thermionic tubes (CRTs).  Flat panel LCDs have typically
+ *          been designed to give a similar response as CRTs (call it
+ *          "backward compatibility").  The 'display' gamma is
+ *          in some sense the inverse of the 'source' gamma.
+ *          jpeg encoders attached to scanners and cameras will lighten
+ *          the pixels, applying a gamma corresponding to approximately
+ *          a square-root relation of output vs input:
+ *                output = input^(gamma)
+ *          where gamma is often set near 0.4545  (1/gamma is 2.2).
+ *          This is stored in the image file.  Then if the display
+ *          program reads the gamma, it will apply a display gamma,
+ *          typically about 2.2; the product is 1.0, and the
+ *          display program produces a linear output.  This works because
+ *          the dark colors were appropriately boosted by the scanner,
+ *          as described by the 'source' gamma, so they should not
+ *          be further boosted by the display program.
+ *      (6) As an example, with xv and display, if no gamma is stored,
+ *          the program acts as if gamma were 0.4545, multiplies this by 2.2,
+ *          and does a linear rendering.  Taking this as a baseline
+ *          brightness, if the stored gamma is:
+ *              > 0.4545, the image is rendered lighter than baseline
+ *              < 0.4545, the image is rendered darker than baseline
+ *          In contrast, gqview seems to ignore the gamma chunk in png.
+ *      (7) The only valid pixel depths in leptonica are 1, 2, 4, 8, 16
+ *          and 32.  However, it is possible, and in some cases desirable,
+ *          to write out a png file using an rgb pix that has 24 bpp.
+ *          For example, the open source xpdf SplashBitmap class generates
+ *          24 bpp rgb images.  Consequently, we enable writing 24 bpp pix.
+ *          To generate such a pix, you can make a 24 bpp pix without data
+ *          and assign the data array to the pix; e.g.,
+ *              pix = pixCreateHeader(w, h, 24);
+ *              pixSetData(pix, rgbdata);
+ *          See pixConvert32To24() for an example, where we get rgbdata
+ *          from the 32 bpp pix.  Caution: do not call pixSetPadBits(),
+ *          because the alignment is wrong and you may erase part of the
+ *          last pixel on each line.
+ *      (8) If the pix has a colormap, it is written to file.  In most
+ *          situations, the alpha component is 255 for each colormap entry,
+ *          which is opaque and indicates that it should be ignored.
+ *          However, if any alpha component is not 255, it is assumed that
+ *          the alpha values are valid, and they are written to the png
+ *          file in a tRNS segment.  On readback, the tRNS segment is
+ *          identified, and the colormapped image with alpha is converted
+ *          to a 4 spp rgba image.
+ */
+l_int32
+pixWriteStreamPng(FILE      *fp,
+                  PIX       *pix,
+                  l_float32  gamma)
+{
+char         commentstring[] = "Comment";
+l_int32      i, j, k;
+l_int32      wpl, d, spp, cmflag, opaque;
+l_int32      ncolors, compval;
+l_int32     *rmap, *gmap, *bmap, *amap;
+l_uint32    *data, *ppixel;
+png_byte     bit_depth, color_type;
+png_byte     alpha[256];
+png_uint_32  w, h;
+png_uint_32  xres, yres;
+png_bytep   *row_pointers;
+png_bytep    rowbuffer;
+png_structp  png_ptr;
+png_infop    info_ptr;
+png_colorp   palette;
+PIX         *pixt;
+PIXCMAP     *cmap;
+char        *text;
+
+    PROCNAME("pixWriteStreamPng");
+
+    if (!fp)
+        return ERROR_INT("stream not open", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+        /* Allocate the 2 data structures */
+    if ((png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+                   (png_voidp)NULL, NULL, NULL)) == NULL)
+        return ERROR_INT("png_ptr not made", procName, 1);
+
+    if ((info_ptr = png_create_info_struct(png_ptr)) == NULL) {
+        png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+        return ERROR_INT("info_ptr not made", procName, 1);
+    }
+
+        /* Set up png setjmp error handling */
+    if (setjmp(png_jmpbuf(png_ptr))) {
+        png_destroy_write_struct(&png_ptr, &info_ptr);
+        return ERROR_INT("internal png error", procName, 1);
+    }
+
+    png_init_io(png_ptr, fp);
+
+        /* With best zlib compression (9), get between 1 and 10% improvement
+         * over default (6), but the compression is 3 to 10 times slower.
+         * Use the zlib default (6) as our default compression unless
+         * pix->special falls in the range [10 ... 19]; then subtract 10
+         * to get the compression value.  */
+    compval = Z_DEFAULT_COMPRESSION;
+    if (pix->special >= 10 && pix->special < 20)
+        compval = pix->special - 10;
+    png_set_compression_level(png_ptr, compval);
+
+    w = pixGetWidth(pix);
+    h = pixGetHeight(pix);
+    d = pixGetDepth(pix);
+    spp = pixGetSpp(pix);
+    if ((cmap = pixGetColormap(pix)))
+        cmflag = 1;
+    else
+        cmflag = 0;
+
+        /* Set the color type and bit depth. */
+    if (d == 32 && spp == 4) {
+        bit_depth = 8;
+        color_type = PNG_COLOR_TYPE_RGBA;   /* 6 */
+        cmflag = 0;  /* ignore if it exists */
+    } else if (d == 24 || d == 32) {
+        bit_depth = 8;
+        color_type = PNG_COLOR_TYPE_RGB;   /* 2 */
+        cmflag = 0;  /* ignore if it exists */
+    } else {
+        bit_depth = d;
+        color_type = PNG_COLOR_TYPE_GRAY;  /* 0 */
+    }
+    if (cmflag)
+        color_type = PNG_COLOR_TYPE_PALETTE;  /* 3 */
+
+#if  DEBUG_WRITE
+    fprintf(stderr, "cmflag = %d, bit_depth = %d, color_type = %d\n",
+            cmflag, bit_depth, color_type);
+#endif  /* DEBUG_WRITE */
+
+    png_set_IHDR(png_ptr, info_ptr, w, h, bit_depth, color_type,
+                 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
+                 PNG_FILTER_TYPE_BASE);
+
+        /* Store resolution in ppm, if known */
+    xres = (png_uint_32)(39.37 * (l_float32)pixGetXRes(pix) + 0.5);
+    yres = (png_uint_32)(39.37 * (l_float32)pixGetYRes(pix) + 0.5);
+    if ((xres == 0) || (yres == 0))
+        png_set_pHYs(png_ptr, info_ptr, 0, 0, PNG_RESOLUTION_UNKNOWN);
+    else
+        png_set_pHYs(png_ptr, info_ptr, xres, yres, PNG_RESOLUTION_METER);
+
+    if (cmflag) {
+        pixcmapToArrays(cmap, &rmap, &gmap, &bmap, &amap);
+        ncolors = pixcmapGetCount(cmap);
+        pixcmapIsOpaque(cmap, &opaque);
+
+            /* Make and save the palette */
+        if ((palette = (png_colorp)(LEPT_CALLOC(ncolors, sizeof(png_color))))
+                == NULL)
+            return ERROR_INT("palette not made", procName, 1);
+
+        for (i = 0; i < ncolors; i++) {
+            palette[i].red = (png_byte)rmap[i];
+            palette[i].green = (png_byte)gmap[i];
+            palette[i].blue = (png_byte)bmap[i];
+            alpha[i] = (png_byte)amap[i];
+        }
+
+        png_set_PLTE(png_ptr, info_ptr, palette, (int)ncolors);
+        if (!opaque)  /* alpha channel has some transparency; assume valid */
+            png_set_tRNS(png_ptr, info_ptr, (png_bytep)alpha,
+                         (int)ncolors, NULL);
+        LEPT_FREE(rmap);
+        LEPT_FREE(gmap);
+        LEPT_FREE(bmap);
+        LEPT_FREE(amap);
+    }
+
+        /* 0.4545 is treated as the default by some image
+         * display programs (not gqview).  A value > 0.4545 will
+         * lighten an image as displayed by xv, display, etc. */
+    if (gamma > 0.0)
+        png_set_gAMA(png_ptr, info_ptr, (l_float64)gamma);
+
+    if ((text = pixGetText(pix))) {
+        png_text text_chunk;
+        text_chunk.compression = PNG_TEXT_COMPRESSION_NONE;
+        text_chunk.key = commentstring;
+        text_chunk.text = text;
+        text_chunk.text_length = strlen(text);
+#ifdef PNG_ITXT_SUPPORTED
+        text_chunk.itxt_length = 0;
+        text_chunk.lang = NULL;
+        text_chunk.lang_key = NULL;
+#endif
+        png_set_text(png_ptr, info_ptr, &text_chunk, 1);
+    }
+
+        /* Write header and palette info */
+    png_write_info(png_ptr, info_ptr);
+
+    if ((d != 32) && (d != 24)) {  /* not rgb color */
+            /* Generate a temporary pix with bytes swapped.
+             * For writing a 1 bpp image as png:
+             *    - if no colormap, invert the data, because png writes
+             *      black as 0
+             *    - if colormapped, do not invert the data; the two RGBA
+             *      colors can have any value.  */
+        if (d == 1 && !cmap) {
+            pixt = pixInvert(NULL, pix);
+            pixEndianByteSwap(pixt);
+        } else {
+            pixt = pixEndianByteSwapNew(pix);
+        }
+        if (!pixt) {
+            png_destroy_write_struct(&png_ptr, &info_ptr);
+            return ERROR_INT("pixt not made", procName, 1);
+        }
+
+            /* Make and assign array of image row pointers */
+        if ((row_pointers = (png_bytep *)LEPT_CALLOC(h, sizeof(png_bytep)))
+            == NULL)
+            return ERROR_INT("row-pointers not made", procName, 1);
+        wpl = pixGetWpl(pixt);
+        data = pixGetData(pixt);
+        for (i = 0; i < h; i++)
+            row_pointers[i] = (png_bytep)(data + i * wpl);
+        png_set_rows(png_ptr, info_ptr, row_pointers);
+
+            /* Transfer the data */
+        png_write_image(png_ptr, row_pointers);
+        png_write_end(png_ptr, info_ptr);
+
+        if (cmflag)
+            LEPT_FREE(palette);
+        LEPT_FREE(row_pointers);
+        pixDestroy(&pixt);
+        png_destroy_write_struct(&png_ptr, &info_ptr);
+        return 0;
+    }
+
+        /* For rgb, compose and write a row at a time */
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    if (d == 24) {  /* See note 7 above: special case of 24 bpp rgb */
+        for (i = 0; i < h; i++) {
+            ppixel = data + i * wpl;
+            png_write_rows(png_ptr, (png_bytepp)&ppixel, 1);
+        }
+    } else {  /* 32 bpp rgb and rgba.  Write out the alpha channel if either
+             * the pix has 4 spp or writing it is requested anyway */
+        if ((rowbuffer = (png_bytep)LEPT_CALLOC(w, 4)) == NULL)
+            return ERROR_INT("rowbuffer not made", procName, 1);
+        for (i = 0; i < h; i++) {
+            ppixel = data + i * wpl;
+            for (j = k = 0; j < w; j++) {
+                rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_RED);
+                rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_GREEN);
+                rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_BLUE);
+                if (spp == 4)
+                    rowbuffer[k++] = GET_DATA_BYTE(ppixel, L_ALPHA_CHANNEL);
+                ppixel++;
+            }
+
+            png_write_rows(png_ptr, &rowbuffer, 1);
+        }
+        LEPT_FREE(rowbuffer);
+    }
+
+    png_write_end(png_ptr, info_ptr);
+
+    if (cmflag)
+        LEPT_FREE(palette);
+    png_destroy_write_struct(&png_ptr, &info_ptr);
+    return 0;
+
+}
+
+
+/*!
+ *  pixSetZlibCompression()
+ *
+ *      Input:  pix
+ *              compval (zlib compression value)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Valid zlib compression values are in the interval [0 ... 9],
+ *          where, as defined in zlib.h:
+ *            0         Z_NO_COMPRESSION
+ *            1         Z_BEST_SPEED    (poorest compression)
+ *            9         Z_BEST_COMPRESSION
+ *          For the default value, use either of these:
+ *            6         Z_DEFAULT_COMPRESSION
+ *           -1         (resolves to Z_DEFAULT_COMPRESSION)
+ *      (2) If you use the defined constants in zlib.h instead of the
+ *          compression integers given above, you must include zlib.h.
+ */
+l_int32
+pixSetZlibCompression(PIX     *pix,
+                      l_int32  compval)
+{
+    PROCNAME("pixSetZlibCompression");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (compval < 0 || compval > 9) {
+        L_ERROR("Invalid zlib comp val; using default\n", procName);
+        compval = Z_DEFAULT_COMPRESSION;
+    }
+    pixSetSpecial(pix, 10 + compval);  /* valid range [10 ... 19] */
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *             Setting flag for stripping 16 bits on reading           *
+ *---------------------------------------------------------------------*/
+/*!
+ *  l_pngSetReadStrip16To8()
+ *
+ *      Input:  flag (1 for stripping 16 bpp to 8 bpp on reading;
+ *                    0 for leaving 16 bpp)
+ *      Return: void
+ */
+void
+l_pngSetReadStrip16To8(l_int32  flag)
+{
+    var_PNG_STRIP_16_TO_8 = flag;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Read/write to memory                        *
+ *---------------------------------------------------------------------*/
+#if HAVE_FMEMOPEN
+extern FILE *open_memstream(char **data, size_t *size);
+extern FILE *fmemopen(void *data, size_t size, const char *mode);
+#endif  /* HAVE_FMEMOPEN */
+
+/*!
+ *  pixReadMemPng()
+ *
+ *      Input:  cdata (const; png-encoded)
+ *              size (of data)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) The @size byte of @data must be a null character.
+ */
+PIX *
+pixReadMemPng(const l_uint8  *cdata,
+              size_t          size)
+{
+FILE  *fp;
+PIX   *pix;
+
+    PROCNAME("pixReadMemPng");
+
+    if (!cdata)
+        return (PIX *)ERROR_PTR("cdata not defined", procName, NULL);
+
+#if HAVE_FMEMOPEN
+    if ((fp = fmemopen((void *)cdata, size, "rb")) == NULL)
+        return (PIX *)ERROR_PTR("stream not opened", procName, NULL);
+#else
+    L_WARNING("work-around: writing to a temp file\n", procName);
+    if ((fp = tmpfile()) == NULL)
+        return (PIX *)ERROR_PTR("tmpfile stream not opened", procName, NULL);
+    fwrite(cdata, 1, size, fp);
+    rewind(fp);
+#endif  /* HAVE_FMEMOPEN */
+    pix = pixReadStreamPng(fp);
+    fclose(fp);
+    if (!pix) L_ERROR("pix not read\n", procName);
+    return pix;
+}
+
+
+/*!
+ *  pixWriteMemPng()
+ *
+ *      Input:  &data (<return> data of tiff compressed image)
+ *              &size (<return> size of returned data)
+ *              pix
+ *              gamma (use 0.0 if gamma is not defined)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See pixWriteStreamPng() for usage.  This version writes to
+ *          memory instead of to a file stream.
+ */
+l_int32
+pixWriteMemPng(l_uint8  **pdata,
+               size_t    *psize,
+               PIX       *pix,
+               l_float32  gamma)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("pixWriteMemPng");
+
+    if (pdata) *pdata = NULL;
+    if (psize) *psize = 0;
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1 );
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1 );
+    if (!pix)
+        return ERROR_INT("&pix not defined", procName, 1 );
+
+#if HAVE_FMEMOPEN
+    if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    ret = pixWriteStreamPng(fp, pix, gamma);
+#else
+    L_WARNING("work-around: writing to a temp file\n", procName);
+    if ((fp = tmpfile()) == NULL)
+        return ERROR_INT("tmpfile stream not opened", procName, 1);
+    ret = pixWriteStreamPng(fp, pix, gamma);
+    rewind(fp);
+    *pdata = l_binaryReadStream(fp, psize);
+#endif  /* HAVE_FMEMOPEN */
+    fclose(fp);
+    return ret;
+}
+
+/* --------------------------------------------*/
+#endif  /* HAVE_LIBPNG */
+/* --------------------------------------------*/
diff --git a/src/pngiostub.c b/src/pngiostub.c
new file mode 100644 (file)
index 0000000..cf70671
--- /dev/null
@@ -0,0 +1,141 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pngiostub.c
+ *
+ *     Stubs for pngio.c functions
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !HAVE_LIBPNG   /* defined in environ.h */
+/* --------------------------------------------*/
+
+PIX * pixReadStreamPng(FILE *fp)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadStreamPng", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 readHeaderPng(const char *filename, l_int32 *pwidth, l_int32 *pheight,
+                      l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap)
+{
+    return ERROR_INT("function not present", "readHeaderPng", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 freadHeaderPng(FILE *fp, l_int32 *pwidth, l_int32 *pheight,
+                       l_int32 *pbps, l_int32 *pspp, l_int32 *piscmap)
+{
+    return ERROR_INT("function not present", "freadHeaderPng", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 readHeaderMemPng(const l_uint8 *data, size_t size, l_int32 *pwidth,
+                         l_int32 *pheight, l_int32 *pbps, l_int32 *pspp,
+                         l_int32 *piscmap)
+{
+    return ERROR_INT("function not present", "readHeaderMemPng", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 fgetPngResolution(FILE *fp, l_int32 *pxres, l_int32 *pyres)
+{
+    return ERROR_INT("function not present", "fgetPngResolution", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 isPngInterlaced(const char *filename, l_int32 *pinterlaced)
+{
+    return ERROR_INT("function not present", "isPngInterlaced", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 fgetPngColormapInfo(FILE *fp, PIXCMAP **pcmap, l_int32 *ptransparency)
+{
+    return ERROR_INT("function not present", "fgetPngColormapInfo", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWritePng(const char *filename, PIX *pix, l_float32 gamma)
+{
+    return ERROR_INT("function not present", "pixWritePng", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteStreamPng(FILE *fp, PIX *pix, l_float32 gamma)
+{
+    return ERROR_INT("function not present", "pixWriteStreamPng", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixSetZlibCompression(PIX *pix, l_int32 compval)
+
+{
+    return ERROR_INT("function not present", "pixSetZlibCompression", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+void l_pngSetReadStrip16To8(l_int32 flag)
+{
+    L_ERROR("function not present\n", "l_pngSetReadStrip16To8");
+    return;
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadMemPng(const l_uint8 *cdata, size_t size)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadMemPng", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteMemPng(l_uint8 **pdata, size_t *psize, PIX *pix,
+                       l_float32 gamma)
+{
+    return ERROR_INT("function not present", "pixWriteMemPng", 1);
+}
+
+/* --------------------------------------------*/
+#endif  /* !HAVE_LIBPNG */
+/* --------------------------------------------*/
diff --git a/src/pnmio.c b/src/pnmio.c
new file mode 100644 (file)
index 0000000..ceae4a8
--- /dev/null
@@ -0,0 +1,868 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pnmio.c
+ *
+ *      Stream interface
+ *          PIX             *pixReadStreamPnm()
+ *          l_int32          readHeaderPnm()
+ *          l_int32          freadHeaderPnm()
+ *          l_int32          pixWriteStreamPnm()
+ *          l_int32          pixWriteStreamAsciiPnm()
+ *
+ *      Read/write to memory
+ *          PIX             *pixReadMemPnm()
+ *          l_int32          readHeaderMemPnm()
+ *          l_int32          pixWriteMemPnm()
+ *
+ *      Local helpers
+ *          static l_int32   pnmReadNextAsciiValue();
+ *          static l_int32   pnmReadNextNumber();
+ *          static l_int32   pnmSkipCommentLines();
+ *
+ *      These are here by popular demand, with the help of Mattias
+ *      Kregert (mattias@kregert.se), who provided the first implementation.
+ *
+ *      The pnm formats are exceedingly simple, because they have
+ *      no compression and no colormaps.  They support images that
+ *      are 1 bpp; 2, 4, 8 and 16 bpp grayscale; and rgb.
+ *
+ *      The original pnm formats ("ascii") are included for completeness,
+ *      but their use is deprecated for all but tiny iconic images.
+ *      They are extremely wasteful of memory; for example, the P1 binary
+ *      ascii format is 16 times as big as the packed uncompressed
+ *      format, because 2 characters are used to represent every bit
+ *      (pixel) in the image.  Reading is slow because we check for extra
+ *      white space and EOL at every sample value.
+ *
+ *      The packed pnm formats ("raw") give file sizes similar to
+ *      bmp files, which are uncompressed packed.  However, bmp
+ *      are more flexible, because they can support colormaps.
+ *
+ *      We don't differentiate between the different types ("pbm",
+ *      "pgm", "ppm") at the interface level, because this is really a
+ *      "distinction without a difference."  You read a file, you get
+ *      the appropriate Pix.  You write a file from a Pix, you get the
+ *      appropriate type of file.  If there is a colormap on the Pix,
+ *      and the Pix is more than 1 bpp, you get either an 8 bpp pgm
+ *      or a 24 bpp RGB pnm, depending on whether the colormap colors
+ *      are gray or rgb, respectively.
+ *
+ *      This follows the general policy that the I/O routines don't
+ *      make decisions about the content of the image -- you do that
+ *      with image processing before you write it out to file.
+ *      The I/O routines just try to make the closest connection
+ *      possible between the file and the Pix in memory.
+ *
+ *      On systems like windows without fmemopen() and open_memstream(),
+ *      we write data to a temp file and read it back for operations
+ *      between pix and compressed-data, such as pixReadMemPnm() and
+ *      pixWriteMemPnm().
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <ctype.h>
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  USE_PNMIO   /* defined in environ.h */
+/* --------------------------------------------*/
+
+static l_int32 pnmReadNextAsciiValue(FILE  *fp, l_int32 *pval);
+static l_int32 pnmReadNextNumber(FILE *fp, l_int32 *pval);
+static l_int32 pnmSkipCommentLines(FILE  *fp);
+
+    /* a sanity check on the size read from file */
+static const l_int32  MAX_PNM_WIDTH = 100000;
+static const l_int32  MAX_PNM_HEIGHT = 100000;
+
+
+/*--------------------------------------------------------------------*
+ *                          Stream interface                          *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixReadStreamPnm()
+ *
+ *      Input:  stream opened for read
+ *      Return: pix, or null on error
+ */
+PIX *
+pixReadStreamPnm(FILE  *fp)
+{
+l_uint8    val8, rval8, gval8, bval8;
+l_uint16   val16;
+l_int32    w, h, d, bpl, wpl, i, j, type;
+l_int32    val, rval, gval, bval;
+l_uint32   rgbval;
+l_uint32  *line, *data;
+PIX       *pix;
+
+    PROCNAME("pixReadStreamPnm");
+
+    if (!fp)
+        return (PIX *)ERROR_PTR("fp not defined", procName, NULL);
+
+    if (freadHeaderPnm(fp, &w, &h, &d, &type, NULL, NULL))
+        return (PIX *)ERROR_PTR( "header read failed", procName, NULL);
+    if ((pix = pixCreate(w, h, d)) == NULL)
+        return (PIX *)ERROR_PTR( "pix not made", procName, NULL);
+    pixSetInputFormat(pix, IFF_PNM);
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+
+        /* Old "ascii" format */
+    if (type <= 3) {
+        for (i = 0; i < h; i++) {
+            for (j = 0; j < w; j++) {
+                if (type == 1 || type == 2) {
+                    if (pnmReadNextAsciiValue(fp, &val))
+                        return (PIX *)ERROR_PTR( "read abend", procName, pix);
+                    pixSetPixel(pix, j, i, val);
+                } else {  /* type == 3 */
+                    if (pnmReadNextAsciiValue(fp, &rval))
+                        return (PIX *)ERROR_PTR( "read abend", procName, pix);
+                    if (pnmReadNextAsciiValue(fp, &gval))
+                        return (PIX *)ERROR_PTR( "read abend", procName, pix);
+                    if (pnmReadNextAsciiValue(fp, &bval))
+                        return (PIX *)ERROR_PTR( "read abend", procName, pix);
+                    composeRGBPixel(rval, gval, bval, &rgbval);
+                    pixSetPixel(pix, j, i, rgbval);
+                }
+            }
+        }
+        return pix;
+    }
+
+        /* "raw" format for 1 bpp */
+    if (type == 4) {
+        bpl = (d * w + 7) / 8;
+        for (i = 0; i < h; i++) {
+            line = data + i * wpl;
+            for (j = 0; j < bpl; j++) {
+                if (fread(&val8, 1, 1, fp) != 1)
+                    return (PIX *)ERROR_PTR( "read error in 4", procName, pix);
+                SET_DATA_BYTE(line, j, val8);
+            }
+        }
+        return pix;
+    }
+
+        /* "raw" format for grayscale */
+    if (type == 5) {
+        bpl = (d * w + 7) / 8;
+        for (i = 0; i < h; i++) {
+            line = data + i * wpl;
+            if (d != 16) {
+                for (j = 0; j < w; j++) {
+                    if (fread(&val8, 1, 1, fp) != 1)
+                        return (PIX *)ERROR_PTR( "error in 5", procName, pix);
+                    if (d == 2)
+                        SET_DATA_DIBIT(line, j, val8);
+                    else if (d == 4)
+                        SET_DATA_QBIT(line, j, val8);
+                    else  /* d == 8 */
+                        SET_DATA_BYTE(line, j, val8);
+                }
+            } else {  /* d == 16 */
+                for (j = 0; j < w; j++) {
+                    if (fread(&val16, 2, 1, fp) != 1)
+                        return (PIX *)ERROR_PTR( "16 bpp error", procName, pix);
+                    SET_DATA_TWO_BYTES(line, j, val16);
+                }
+            }
+        }
+        return pix;
+    }
+
+        /* "raw" format, type == 6; rgb */
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        for (j = 0; j < wpl; j++) {
+            if (fread(&rval8, 1, 1, fp) != 1)
+                return (PIX *)ERROR_PTR( "read error type 6", procName, pix);
+            if (fread(&gval8, 1, 1, fp) != 1)
+                return (PIX *)ERROR_PTR( "read error type 6", procName, pix);
+            if (fread(&bval8, 1, 1, fp) != 1)
+                return (PIX *)ERROR_PTR( "read error type 6", procName, pix);
+            composeRGBPixel(rval8, gval8, bval8, &rgbval);
+            line[j] = rgbval;
+        }
+    }
+    return pix;
+}
+
+
+/*!
+ *  readHeaderPnm()
+ *
+ *      Input:  filename
+ *              &w (<optional return>)
+ *              &h (<optional return>)
+ *              &d (<optional return>)
+ *              &type (<optional return> pnm type)
+ *              &bps (<optional return>, bits/sample)
+ *              &spp (<optional return>, samples/pixel)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+readHeaderPnm(const char *filename,
+              l_int32    *pw,
+              l_int32    *ph,
+              l_int32    *pd,
+              l_int32    *ptype,
+              l_int32    *pbps,
+              l_int32    *pspp)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("readHeaderPnm");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pd) *pd = 0;
+    if (ptype) *ptype = 0;
+    if (pbps) *pbps = 0;
+    if (pspp) *pspp = 0;
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT("image file not found", procName, 1);
+    ret = freadHeaderPnm(fp, pw, ph, pd, ptype, pbps, pspp);
+    fclose(fp);
+    return ret;
+}
+
+
+/*!
+ *  freadHeaderPnm()
+ *
+ *      Input:  stream opened for read
+ *              &w (<optional return>)
+ *              &h (<optional return>)
+ *              &d (<optional return>)
+ *              &type (<optional return> pnm type)
+ *              &bps (<optional return>, bits/sample)
+ *              &spp (<optional return>, samples/pixel)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+freadHeaderPnm(FILE     *fp,
+               l_int32  *pw,
+               l_int32  *ph,
+               l_int32  *pd,
+               l_int32  *ptype,
+               l_int32  *pbps,
+               l_int32  *pspp)
+{
+l_int32  w, h, d, type;
+l_int32  maxval;
+
+    PROCNAME("freadHeaderPnm");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pd) *pd = 0;
+    if (ptype) *ptype = 0;
+    if (pbps) *pbps = 0;
+    if (pspp) *pspp = 0;
+    if (!fp)
+        return ERROR_INT("fp not defined", procName, 1);
+
+    if (fscanf(fp, "P%d\n", &type) != 1)
+        return ERROR_INT("invalid read for type", procName, 1);
+    if (type < 1 || type > 6)
+        return ERROR_INT("invalid pnm file", procName, 1);
+
+    if (pnmSkipCommentLines(fp))
+        return ERROR_INT("no data in file", procName, 1);
+
+    if (fscanf(fp, "%d %d\n", &w, &h) != 2)
+        return ERROR_INT("invalid read for w,h", procName, 1);
+    if (w <= 0 || h <= 0 || w > MAX_PNM_WIDTH || h > MAX_PNM_HEIGHT) {
+        L_INFO("invalid size: w = %d, h = %d\n", procName, w, h);
+        return 1;
+    }
+
+       /* Get depth of pix.  For types 2 and 5, we use the maxval.
+        * Important implementation note:
+        *   - You can't use fscanf(), which throws away whitespace,
+        *     and will discard binary data if it starts with whitespace(s).
+        *   - You can't use fgets(), which stops at newlines, but this
+        *     dumb format doesn't require a newline after the maxval
+        *     number -- it just requires one whitespace character.
+        *   - Which leaves repeated calls to fgetc, including swallowing
+        *     the single whitespace character. */
+    if (type == 1 || type == 4) {
+        d = 1;
+    } else if (type == 2 || type == 5) {
+        if (pnmReadNextNumber(fp, &maxval))
+            return ERROR_INT("invalid read for maxval (2,5)", procName, 1);
+        if (maxval == 3) {
+            d = 2;
+        } else if (maxval == 15) {
+            d = 4;
+        } else if (maxval == 255) {
+            d = 8;
+        } else if (maxval == 0xffff) {
+            d = 16;
+        } else {
+            fprintf(stderr, "maxval = %d\n", maxval);
+            return ERROR_INT("invalid maxval", procName, 1);
+        }
+    } else {  /* type == 3 || type == 6; this is rgb  */
+        if (pnmReadNextNumber(fp, &maxval))
+            return ERROR_INT("invalid read for maxval (3,6)", procName, 1);
+        if (maxval != 255)
+            L_WARNING("unexpected maxval = %d\n", procName, maxval);
+        d = 32;
+    }
+    if (pw) *pw = w;
+    if (ph) *ph = h;
+    if (pd) *pd = d;
+    if (ptype) *ptype = type;
+    if (pbps) *pbps = (d == 32) ? 8 : d;
+    if (pspp) *pspp = (d == 32) ? 3 : 1;
+    return 0;
+}
+
+
+/*!
+ *  pixWriteStreamPnm()
+ *
+ *      Input:  stream opened for write
+ *              pix
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This writes "raw" packed format only:
+ *          1 bpp --> pbm (P4)
+ *          2, 4, 8, 16 bpp, no colormap or grayscale colormap --> pgm (P5)
+ *          2, 4, 8 bpp with color-valued colormap, or rgb --> rgb ppm (P6)
+ *      (2) 24 bpp rgb are not supported in leptonica, but this will
+ *          write them out as a packed array of bytes (3 to a pixel).
+ */
+l_int32
+pixWriteStreamPnm(FILE  *fp,
+                  PIX   *pix)
+{
+l_uint8    val8;
+l_uint8    pel[4];
+l_uint16   val16;
+l_int32    h, w, d, ds, i, j, wpls, bpl, filebpl, writeerror, maxval;
+l_uint32  *pword, *datas, *lines;
+PIX       *pixs;
+
+    PROCNAME("pixWriteStreamPnm");
+
+    if (!fp)
+        return ERROR_INT("fp not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 24 && d != 32)
+        return ERROR_INT("d not in {1,2,4,8,16,24,32}", procName, 1);
+
+        /* If a colormap exists, remove and convert to grayscale or rgb */
+    if (pixGetColormap(pix) != NULL)
+        pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+    else
+        pixs = pixClone(pix);
+    ds =  pixGetDepth(pixs);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+
+    writeerror = 0;
+    if (ds == 1) {  /* binary */
+        fprintf(fp, "P4\n# Raw PBM file written by leptonica "
+                    "(www.leptonica.com)\n%d %d\n", w, h);
+
+        bpl = (w + 7) / 8;
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            for (j = 0; j < bpl; j++) {
+                val8 = GET_DATA_BYTE(lines, j);
+                fwrite(&val8, 1, 1, fp);
+            }
+        }
+    } else if (ds == 2 || ds == 4 || ds == 8 || ds == 16) {  /* grayscale */
+        maxval = (1 << ds) - 1;
+        fprintf(fp, "P5\n# Raw PGM file written by leptonica "
+                    "(www.leptonica.com)\n%d %d\n%d\n", w, h, maxval);
+
+        if (ds != 16) {
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                for (j = 0; j < w; j++) {
+                    if (ds == 2)
+                        val8 = GET_DATA_DIBIT(lines, j);
+                    else if (ds == 4)
+                        val8 = GET_DATA_QBIT(lines, j);
+                    else  /* ds == 8 */
+                        val8 = GET_DATA_BYTE(lines, j);
+                    fwrite(&val8, 1, 1, fp);
+                }
+            }
+        } else {  /* ds == 16 */
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                for (j = 0; j < w; j++) {
+                    val16 = GET_DATA_TWO_BYTES(lines, j);
+                    fwrite(&val16, 2, 1, fp);
+                }
+            }
+        }
+    } else {  /* rgb color */
+        fprintf(fp, "P6\n# Raw PPM file written by leptonica "
+                    "(www.leptonica.com)\n%d %d\n255\n", w, h);
+
+        if (d == 24) {   /* packed, 3 bytes to a pixel */
+            filebpl = 3 * w;
+            for (i = 0; i < h; i++) {  /* write out each raster line */
+                lines = datas + i * wpls;
+                if (fwrite(lines, 1, filebpl, fp) != filebpl)
+                    writeerror = 1;
+            }
+        } else {  /* 32 bpp rgb */
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                for (j = 0; j < wpls; j++) {
+                    pword = lines + j;
+                    pel[0] = GET_DATA_BYTE(pword, COLOR_RED);
+                    pel[1] = GET_DATA_BYTE(pword, COLOR_GREEN);
+                    pel[2] = GET_DATA_BYTE(pword, COLOR_BLUE);
+                    if (fwrite(pel, 1, 3, fp) != 3)
+                        writeerror = 1;
+                }
+            }
+        }
+    }
+
+    pixDestroy(&pixs);
+    if (writeerror)
+        return ERROR_INT("image write fail", procName, 1);
+    return 0;
+}
+
+
+/*!
+ *  pixWriteStreamAsciiPnm()
+ *
+ *      Input:  stream opened for write
+ *              pix
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Writes "ascii" format only:
+ *      1 bpp --> pbm (P1)
+ *      2, 4, 8, 16 bpp, no colormap or grayscale colormap --> pgm (P2)
+ *      2, 4, 8 bpp with color-valued colormap, or rgb --> rgb ppm (P3)
+ */
+l_int32
+pixWriteStreamAsciiPnm(FILE  *fp,
+                       PIX   *pix)
+{
+char       buffer[256];
+l_uint8    cval[3];
+l_int32    h, w, d, ds, i, j, k, maxval, count;
+l_uint32   val;
+PIX       *pixs;
+
+    PROCNAME("pixWriteStreamAsciiPnm");
+
+    if (!fp)
+        return ERROR_INT("fp not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return ERROR_INT("d not in {1,2,4,8,16,32}", procName, 1);
+
+        /* If a colormap exists, remove and convert to grayscale or rgb */
+    if (pixGetColormap(pix) != NULL)
+        pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+    else
+        pixs = pixClone(pix);
+    ds =  pixGetDepth(pixs);
+
+    if (ds == 1) {  /* binary */
+        fprintf(fp, "P1\n# Ascii PBM file written by leptonica "
+                    "(www.leptonica.com)\n%d %d\n", w, h);
+
+        count = 0;
+        for (i = 0; i < h; i++) {
+            for (j = 0; j < w; j++) {
+                pixGetPixel(pixs, j, i, &val);
+                if (val == 0)
+                    fputc('0', fp);
+                else  /* val == 1 */
+                    fputc('1', fp);
+                fputc(' ', fp);
+                count += 2;
+                if (count >= 70) {
+                    fputc('\n', fp);
+                    count = 0;
+                }
+            }
+        }
+    } else if (ds == 2 || ds == 4 || ds == 8 || ds == 16) {  /* grayscale */
+        maxval = (1 << ds) - 1;
+        fprintf(fp, "P2\n# Ascii PGM file written by leptonica "
+                    "(www.leptonica.com)\n%d %d\n%d\n", w, h, maxval);
+
+        count = 0;
+        for (i = 0; i < h; i++) {
+            for (j = 0; j < w; j++) {
+                pixGetPixel(pixs, j, i, &val);
+                if (ds == 2) {
+                    sprintf(buffer, "%1d ", val);
+                    fwrite(buffer, 1, 2, fp);
+                    count += 2;
+                } else if (ds == 4) {
+                    sprintf(buffer, "%2d ", val);
+                    fwrite(buffer, 1, 3, fp);
+                    count += 3;
+                } else if (ds == 8) {
+                    sprintf(buffer, "%3d ", val);
+                    fwrite(buffer, 1, 4, fp);
+                    count += 4;
+                } else {  /* ds == 16 */
+                    sprintf(buffer, "%5d ", val);
+                    fwrite(buffer, 1, 6, fp);
+                    count += 6;
+                }
+                if (count >= 60) {
+                    fputc('\n', fp);
+                    count = 0;
+                }
+            }
+        }
+    } else {  /* rgb color */
+        fprintf(fp, "P3\n# Ascii PPM file written by leptonica "
+                    "(www.leptonica.com)\n%d %d\n255\n", w, h);
+        count = 0;
+        for (i = 0; i < h; i++) {
+            for (j = 0; j < w; j++) {
+                pixGetPixel(pixs, j, i, &val);
+                cval[0] = GET_DATA_BYTE(&val, COLOR_RED);
+                cval[1] = GET_DATA_BYTE(&val, COLOR_GREEN);
+                cval[2] = GET_DATA_BYTE(&val, COLOR_BLUE);
+                for (k = 0; k < 3; k++) {
+                    sprintf(buffer, "%3d ", cval[k]);
+                    fwrite(buffer, 1, 4, fp);
+                    count += 4;
+                    if (count >= 60) {
+                        fputc('\n', fp);
+                        count = 0;
+                    }
+                }
+            }
+        }
+    }
+
+    pixDestroy(&pixs);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Read/write to memory                        *
+ *---------------------------------------------------------------------*/
+#if HAVE_FMEMOPEN
+extern FILE *open_memstream(char **data, size_t *size);
+extern FILE *fmemopen(void *data, size_t size, const char *mode);
+#endif  /* HAVE_FMEMOPEN */
+
+/*!
+ *  pixReadMemPnm()
+ *
+ *      Input:  cdata (const; pnm-encoded)
+ *              size (of data)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) The @size byte of @data must be a null character.
+ */
+PIX *
+pixReadMemPnm(const l_uint8  *cdata,
+              size_t          size)
+{
+FILE  *fp;
+PIX   *pix;
+
+    PROCNAME("pixReadMemPnm");
+
+    if (!cdata)
+        return (PIX *)ERROR_PTR("cdata not defined", procName, NULL);
+
+#if HAVE_FMEMOPEN
+    if ((fp = fmemopen((l_uint8 *)cdata, size, "rb")) == NULL)
+        return (PIX *)ERROR_PTR("stream not opened", procName, NULL);
+#else
+    L_WARNING("work-around: writing to a temp file\n", procName);
+    if ((fp = tmpfile()) == NULL)
+        return (PIX *)ERROR_PTR("tmpfile stream not opened", procName, NULL);
+    fwrite(cdata, 1, size, fp);
+    rewind(fp);
+#endif  /* HAVE_FMEMOPEN */
+    pix = pixReadStreamPnm(fp);
+    fclose(fp);
+    if (!pix) L_ERROR("pix not read\n", procName);
+    return pix;
+}
+
+
+/*!
+ *  readHeaderMemPnm()
+ *
+ *      Input:  cdata (const; pnm-encoded)
+ *              size (of data)
+ *              &w (<optional return>)
+ *              &h (<optional return>)
+ *              &d (<optional return>)
+ *              &type (<optional return> pnm type)
+ *              &bps (<optional return>, bits/sample)
+ *              &spp (<optional return>, samples/pixel)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+readHeaderMemPnm(const l_uint8  *cdata,
+                 size_t          size,
+                 l_int32        *pw,
+                 l_int32        *ph,
+                 l_int32        *pd,
+                 l_int32        *ptype,
+                 l_int32        *pbps,
+                 l_int32        *pspp)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("readHeaderMemPnm");
+
+    if (!cdata)
+        return ERROR_INT("cdata not defined", procName, 1);
+
+#if HAVE_FMEMOPEN
+    if ((fp = fmemopen((l_uint8 *)cdata, size, "rb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+#else
+    L_WARNING("work-around: writing to a temp file\n", procName);
+    if ((fp = tmpfile()) == NULL)
+        return ERROR_INT("tmpfile stream not opened", procName, 1);
+    fwrite(cdata, 1, size, fp);
+    rewind(fp);
+#endif  /* HAVE_FMEMOPEN */
+    ret = freadHeaderPnm(fp, pw, ph, pd, ptype, pbps, pspp);
+    fclose(fp);
+    if (ret)
+        return ERROR_INT("header data read failed", procName, 1);
+    return 0;
+}
+
+
+/*!
+ *  pixWriteMemPnm()
+ *
+ *      Input:  &data (<return> data of tiff compressed image)
+ *              &size (<return> size of returned data)
+ *              pix
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See pixWriteStreamPnm() for usage.  This version writes to
+ *          memory instead of to a file stream.
+ */
+l_int32
+pixWriteMemPnm(l_uint8  **pdata,
+               size_t    *psize,
+               PIX       *pix)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("pixWriteMemPnm");
+
+    if (pdata) *pdata = NULL;
+    if (psize) *psize = 0;
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1 );
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1 );
+    if (!pix)
+        return ERROR_INT("&pix not defined", procName, 1 );
+
+#if HAVE_FMEMOPEN
+    if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    ret = pixWriteStreamPnm(fp, pix);
+#else
+    L_WARNING("work-around: writing to a temp file\n", procName);
+    if ((fp = tmpfile()) == NULL)
+        return ERROR_INT("tmpfile stream not opened", procName, 1);
+    ret = pixWriteStreamPnm(fp, pix);
+    rewind(fp);
+    *pdata = l_binaryReadStream(fp, psize);
+#endif  /* HAVE_FMEMOPEN */
+    fclose(fp);
+    return ret;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                          Static helpers                            *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pnmReadNextAsciiValue()
+ *
+ *      Return: 0 if OK, 1 on error or EOF.
+ *
+ *  Notes:
+ *      (1) This reads the next sample value in ascii from the the file.
+ */
+static l_int32
+pnmReadNextAsciiValue(FILE     *fp,
+                      l_int32  *pval)
+{
+l_int32   c, ignore;
+
+    PROCNAME("pnmReadNextAsciiValue");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0;
+    if (!fp)
+        return ERROR_INT("stream not open", procName, 1);
+    do {  /* skip whitespace */
+        if ((c = fgetc(fp)) == EOF)
+            return 1;
+    } while (c == ' ' || c == '\t' || c == '\n' || c == '\r');
+
+    fseek(fp, -1L, SEEK_CUR);        /* back up one byte */
+    ignore = fscanf(fp, "%d", pval);
+    return 0;
+}
+
+
+/*!
+ *  pnmReadNextNumber()
+ *
+ *      Input:  file stream
+ *              &val (<return> value as an integer)
+ *      Return: 0 if OK, 1 on error or EOF.
+ *
+ *  Notes:
+ *      (1) This reads the next set of numeric chars, returning
+ *          the value and swallowing the trailing whitespace character.
+ *          This is needed to read the maxval in the header, which
+ *          precedes the binary data.
+ */
+static l_int32
+pnmReadNextNumber(FILE     *fp,
+                  l_int32  *pval)
+{
+char      buf[8];
+l_int32   i, c, foundws, ignore;
+
+    PROCNAME("pnmReadNextNumber");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0;
+    if (!fp)
+        return ERROR_INT("stream not open", procName, 1);
+
+        /* The ascii characters for the number are followed by exactly
+         * one whitespace character. */
+    foundws = FALSE;
+    for (i = 0; i < 8; i++)
+        buf[i] = '\0';
+    for (i = 0; i < 8; i++) {
+        if ((c = fgetc(fp)) == EOF)
+            return ERROR_INT("end of file reached", procName, 1);
+        if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
+            foundws = TRUE;
+            buf[i] = '\n';
+            break;
+        }
+        if (!isdigit(c))
+            return ERROR_INT("char read is not a digit", procName, 1);
+        buf[i] = c;
+    }
+    if (!foundws)
+        return ERROR_INT("no whitespace found", procName, 1);
+    if (sscanf(buf, "%d", pval) != 1)
+        return ERROR_INT("invalid read", procName, 1);
+    return 0;
+}
+
+
+/*!
+ *  pnmSkipCommentLines()
+ *
+ *      Return: 0 if OK, 1 on error or EOF
+ *
+ *  Notes:
+ *      (1) Comment lines begin with '#'
+ *      (2) Usage: caller should check return value for EOF
+ */
+static l_int32
+pnmSkipCommentLines(FILE  *fp)
+{
+l_int32  c;
+
+    PROCNAME("pnmSkipCommentLines");
+
+    if (!fp)
+        return ERROR_INT("stream not open", procName, 1);
+    if ((c = fgetc(fp)) == EOF)
+        return 1;
+    if (c == '#') {
+        do {  /* each line starting with '#' */
+            do {  /* this entire line */
+                if ((c = fgetc(fp)) == EOF)
+                    return 1;
+            } while (c != '\n');
+            if ((c = fgetc(fp)) == EOF)
+                return 1;
+        } while (c == '#');
+    }
+
+        /* Back up one byte */
+    fseek(fp, -1L, SEEK_CUR);
+    return 0;
+}
+
+/* --------------------------------------------*/
+#endif  /* USE_PNMIO */
+/* --------------------------------------------*/
diff --git a/src/pnmiostub.c b/src/pnmiostub.c
new file mode 100644 (file)
index 0000000..428c3b7
--- /dev/null
@@ -0,0 +1,100 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  pnmiostub.c
+ *
+ *     Stubs for pnmio.c functions
+ */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !USE_PNMIO   /* defined in environ.h */
+/* --------------------------------------------*/
+
+PIX * pixReadStreamPnm(FILE *fp)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadStreamPnm", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 readHeaderPnm(const char *filename, l_int32 *pw, l_int32 *ph,
+                      l_int32 *pd, l_int32 *ptype, l_int32 *pbps,
+                      l_int32 *pspp)
+{
+    return ERROR_INT("function not present", "readHeaderPnm", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 freadHeaderPnm(FILE *fp, l_int32 *pw, l_int32 *ph, l_int32 *pd,
+                       l_int32 *ptype, l_int32 *pbps, l_int32 *pspp)
+{
+    return ERROR_INT("function not present", "freadHeaderPnm", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteStreamPnm(FILE *fp, PIX *pix)
+{
+    return ERROR_INT("function not present", "pixWriteStreamPnm", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteStreamAsciiPnm(FILE *fp, PIX *pix)
+{
+    return ERROR_INT("function not present", "pixWriteStreamAsciiPnm", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadMemPnm(const l_uint8 *cdata, size_t size)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadMemPnm", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 readHeaderMemPnm(const l_uint8 *cdata, size_t size, l_int32 *pw,
+                         l_int32 *ph, l_int32 *pd, l_int32 *ptype,
+                         l_int32 *pbps, l_int32 *pspp)
+{
+    return ERROR_INT("function not present", "readHeaderMemPnm", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteMemPnm(l_uint8 **pdata, size_t *psize, PIX *pix)
+{
+    return ERROR_INT("function not present", "pixWriteMemPnm", 1);
+}
+
+/* --------------------------------------------*/
+#endif  /* !USE_PNMIO */
+/* --------------------------------------------*/
diff --git a/src/projective.c b/src/projective.c
new file mode 100644 (file)
index 0000000..4e94a0c
--- /dev/null
@@ -0,0 +1,911 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  projective.c
+ *
+ *      Projective (4 pt) image transformation using a sampled
+ *      (to nearest integer) transform on each dest point
+ *           PIX      *pixProjectiveSampledPta()
+ *           PIX      *pixProjectiveSampled()
+ *
+ *      Projective (4 pt) image transformation using interpolation
+ *      (or area mapping) for anti-aliasing images that are
+ *      2, 4, or 8 bpp gray, or colormapped, or 32 bpp RGB
+ *           PIX      *pixProjectivePta()
+ *           PIX      *pixProjective()
+ *           PIX      *pixProjectivePtaColor()
+ *           PIX      *pixProjectiveColor()
+ *           PIX      *pixProjectivePtaGray()
+ *           PIX      *pixProjectiveGray()
+ *
+ *      Projective transform including alpha (blend) component
+ *           PIX      *pixProjectivePtaWithAlpha()
+ *
+ *      Projective coordinate transformation
+ *           l_int32   getProjectiveXformCoeffs()
+ *           l_int32   projectiveXformSampledPt()
+ *           l_int32   projectiveXformPt()
+ *
+ *      A projective transform can be specified as a specific functional
+ *      mapping between 4 points in the source and 4 points in the dest.
+ *      It preserves straight lines, but is less stable than a bilinear
+ *      transform, because it contains a division by a quantity that
+ *      can get arbitrarily small.)
+ *
+ *      We give both a projective coordinate transformation and
+ *      two projective image transformations.
+ *
+ *      For the former, we ask for the coordinate value (x',y')
+ *      in the transformed space for any point (x,y) in the original
+ *      space.  The coefficients of the transformation are found by
+ *      solving 8 simultaneous equations for the 8 coordinates of
+ *      the 4 points in src and dest.  The transformation can then
+ *      be used to compute the associated image transform, by
+ *      computing, for each dest pixel, the relevant pixel(s) in
+ *      the source.  This can be done either by taking the closest
+ *      src pixel to each transformed dest pixel ("sampling") or
+ *      by doing an interpolation and averaging over 4 source
+ *      pixels with appropriate weightings ("interpolated").
+ *
+ *      A typical application would be to remove keystoning
+ *      due to a projective transform in the imaging system.
+ *
+ *      The projective transform is given by specifying two equations:
+ *
+ *          x' = (ax + by + c) / (gx + hy + 1)
+ *          y' = (dx + ey + f) / (gx + hy + 1)
+ *
+ *      where the eight coefficients have been computed from four
+ *      sets of these equations, each for two corresponding data pts.
+ *      In practice, once the coefficients are known, we use the
+ *      equations "backwards": for each point (x,y) in the dest image,
+ *      these two equations are used to compute the corresponding point
+ *      (x',y') in the src.  That computed point in the src is then used
+ *      to determine the corresponding dest pixel value in one of two ways:
+ *
+ *       - sampling: simply take the value of the src pixel in which this
+ *                   point falls
+ *       - interpolation: take appropriate linear combinations of the
+ *                        four src pixels that this dest pixel would
+ *                        overlap, with the coefficients proportional
+ *                        to the amount of overlap
+ *
+ *      For small warp where there is little scale change, (e.g.,
+ *      for rotation) area mapping is nearly equivalent to interpolation.
+ *
+ *      Typical relative timing of pointwise transforms (sampled = 1.0):
+ *      8 bpp:   sampled        1.0
+ *               interpolated   1.5
+ *      32 bpp:  sampled        1.0
+ *               interpolated   1.6
+ *      Additionally, the computation time/pixel is nearly the same
+ *      for 8 bpp and 32 bpp, for both sampled and interpolated.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+extern l_float32  AlphaMaskBorderVals[2];
+
+
+/*------------------------------------------------------------n
+ *            Sampled projective image transformation          *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixProjectiveSampledPta()
+ *
+ *      Input:  pixs (all depths)
+ *              ptad  (4 pts of final coordinate space)
+ *              ptas  (4 pts of initial coordinate space)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Brings in either black or white pixels from the boundary.
+ *      (2) Retains colormap, which you can do for a sampled transform..
+ *      (3) No 3 of the 4 points may be collinear.
+ *      (4) For 8 and 32 bpp pix, better quality is obtained by the
+ *          somewhat slower pixProjectivePta().  See that
+ *          function for relative timings between sampled and interpolated.
+ */
+PIX *
+pixProjectiveSampledPta(PIX     *pixs,
+                        PTA     *ptad,
+                        PTA     *ptas,
+                        l_int32  incolor)
+{
+l_float32  *vc;
+PIX        *pixd;
+
+    PROCNAME("pixProjectiveSampledPta");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+    if (ptaGetCount(ptas) != 4)
+        return (PIX *)ERROR_PTR("ptas count not 4", procName, NULL);
+    if (ptaGetCount(ptad) != 4)
+        return (PIX *)ERROR_PTR("ptad count not 4", procName, NULL);
+
+        /* Get backwards transform from dest to src, and apply it */
+    getProjectiveXformCoeffs(ptad, ptas, &vc);
+    pixd = pixProjectiveSampled(pixs, vc, incolor);
+    LEPT_FREE(vc);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixProjectiveSampled()
+ *
+ *      Input:  pixs (all depths)
+ *              vc  (vector of 8 coefficients for projective transformation)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Brings in either black or white pixels from the boundary.
+ *      (2) Retains colormap, which you can do for a sampled transform..
+ *      (3) For 8 or 32 bpp, much better quality is obtained by the
+ *          somewhat slower pixProjective().  See that function
+ *          for relative timings between sampled and interpolated.
+ */
+PIX *
+pixProjectiveSampled(PIX        *pixs,
+                     l_float32  *vc,
+                     l_int32     incolor)
+{
+l_int32     i, j, w, h, d, x, y, wpls, wpld, color, cmapindex;
+l_uint32    val;
+l_uint32   *datas, *datad, *lines, *lined;
+PIX        *pixd;
+PIXCMAP    *cmap;
+
+    PROCNAME("pixProjectiveSampled");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!vc)
+        return (PIX *)ERROR_PTR("vc not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("depth not 1, 2, 4, 8 or 16", procName, NULL);
+
+        /* Init all dest pixels to color to be brought in from outside */
+    pixd = pixCreateTemplate(pixs);
+    if ((cmap = pixGetColormap(pixs)) != NULL) {
+        if (incolor == L_BRING_IN_WHITE)
+            color = 1;
+        else
+            color = 0;
+        pixcmapAddBlackOrWhite(cmap, color, &cmapindex);
+        pixSetAllArbitrary(pixd, cmapindex);
+    } else {
+        if ((d == 1 && incolor == L_BRING_IN_WHITE) ||
+            (d > 1 && incolor == L_BRING_IN_BLACK)) {
+            pixClearAll(pixd);
+        } else {
+            pixSetAll(pixd);
+        }
+    }
+
+        /* Scan over the dest pixels */
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            projectiveXformSampledPt(vc, j, i, &x, &y);
+            if (x < 0 || y < 0 || x >=w || y >= h)
+                continue;
+            lines = datas + y * wpls;
+            if (d == 1) {
+                val = GET_DATA_BIT(lines, x);
+                SET_DATA_BIT_VAL(lined, j, val);
+            } else if (d == 8) {
+                val = GET_DATA_BYTE(lines, x);
+                SET_DATA_BYTE(lined, j, val);
+            } else if (d == 32) {
+                lined[j] = lines[x];
+            } else if (d == 2) {
+                val = GET_DATA_DIBIT(lines, x);
+                SET_DATA_DIBIT(lined, j, val);
+            } else if (d == 4) {
+                val = GET_DATA_QBIT(lines, x);
+                SET_DATA_QBIT(lined, j, val);
+            }
+        }
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------*
+ *            Interpolated projective image transformation             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixProjectivePta()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              ptad  (4 pts of final coordinate space)
+ *              ptas  (4 pts of initial coordinate space)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Brings in either black or white pixels from the boundary
+ *      (2) Removes any existing colormap, if necessary, before transforming
+ */
+PIX *
+pixProjectivePta(PIX     *pixs,
+                 PTA     *ptad,
+                 PTA     *ptas,
+                 l_int32  incolor)
+{
+l_int32   d;
+l_uint32  colorval;
+PIX      *pixt1, *pixt2, *pixd;
+
+    PROCNAME("pixProjectivePta");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+    if (ptaGetCount(ptas) != 4)
+        return (PIX *)ERROR_PTR("ptas count not 4", procName, NULL);
+    if (ptaGetCount(ptad) != 4)
+        return (PIX *)ERROR_PTR("ptad count not 4", procName, NULL);
+
+    if (pixGetDepth(pixs) == 1)
+        return pixProjectiveSampledPta(pixs, ptad, ptas, incolor);
+
+        /* Remove cmap if it exists, and unpack to 8 bpp if necessary */
+    pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    d = pixGetDepth(pixt1);
+    if (d < 8)
+        pixt2 = pixConvertTo8(pixt1, FALSE);
+    else
+        pixt2 = pixClone(pixt1);
+    d = pixGetDepth(pixt2);
+
+        /* Compute actual color to bring in from edges */
+    colorval = 0;
+    if (incolor == L_BRING_IN_WHITE) {
+        if (d == 8)
+            colorval = 255;
+        else  /* d == 32 */
+            colorval = 0xffffff00;
+    }
+
+    if (d == 8)
+        pixd = pixProjectivePtaGray(pixt2, ptad, ptas, colorval);
+    else  /* d == 32 */
+        pixd = pixProjectivePtaColor(pixt2, ptad, ptas, colorval);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    return pixd;
+}
+
+
+/*!
+ *  pixProjective()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              vc  (vector of 8 coefficients for projective transformation)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Brings in either black or white pixels from the boundary
+ *      (2) Removes any existing colormap, if necessary, before transforming
+ */
+PIX *
+pixProjective(PIX        *pixs,
+              l_float32  *vc,
+              l_int32     incolor)
+{
+l_int32   d;
+l_uint32  colorval;
+PIX      *pixt1, *pixt2, *pixd;
+
+    PROCNAME("pixProjective");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!vc)
+        return (PIX *)ERROR_PTR("vc not defined", procName, NULL);
+
+    if (pixGetDepth(pixs) == 1)
+        return pixProjectiveSampled(pixs, vc, incolor);
+
+        /* Remove cmap if it exists, and unpack to 8 bpp if necessary */
+    pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    d = pixGetDepth(pixt1);
+    if (d < 8)
+        pixt2 = pixConvertTo8(pixt1, FALSE);
+    else
+        pixt2 = pixClone(pixt1);
+    d = pixGetDepth(pixt2);
+
+        /* Compute actual color to bring in from edges */
+    colorval = 0;
+    if (incolor == L_BRING_IN_WHITE) {
+        if (d == 8)
+            colorval = 255;
+        else  /* d == 32 */
+            colorval = 0xffffff00;
+    }
+
+    if (d == 8)
+        pixd = pixProjectiveGray(pixt2, vc, colorval);
+    else  /* d == 32 */
+        pixd = pixProjectiveColor(pixt2, vc, colorval);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    return pixd;
+}
+
+
+/*!
+ *  pixProjectivePtaColor()
+ *
+ *      Input:  pixs (32 bpp)
+ *              ptad  (4 pts of final coordinate space)
+ *              ptas  (4 pts of initial coordinate space)
+ *              colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixProjectivePtaColor(PIX      *pixs,
+                      PTA      *ptad,
+                      PTA      *ptas,
+                      l_uint32  colorval)
+{
+l_float32  *vc;
+PIX        *pixd;
+
+    PROCNAME("pixProjectivePtaColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL);
+    if (ptaGetCount(ptas) != 4)
+        return (PIX *)ERROR_PTR("ptas count not 4", procName, NULL);
+    if (ptaGetCount(ptad) != 4)
+        return (PIX *)ERROR_PTR("ptad count not 4", procName, NULL);
+
+        /* Get backwards transform from dest to src, and apply it */
+    getProjectiveXformCoeffs(ptad, ptas, &vc);
+    pixd = pixProjectiveColor(pixs, vc, colorval);
+    LEPT_FREE(vc);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixProjectiveColor()
+ *
+ *      Input:  pixs (32 bpp)
+ *              vc  (vector of 8 coefficients for projective transformation)
+ *              colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixProjectiveColor(PIX        *pixs,
+                   l_float32  *vc,
+                   l_uint32    colorval)
+{
+l_int32    i, j, w, h, d, wpls, wpld;
+l_uint32   val;
+l_uint32  *datas, *datad, *lined;
+l_float32  x, y;
+PIX       *pix1, *pix2, *pixd;
+
+    PROCNAME("pixProjectiveColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 32)
+        return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL);
+    if (!vc)
+        return (PIX *)ERROR_PTR("vc not defined", procName, NULL);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreateTemplate(pixs);
+    pixSetAllArbitrary(pixd, colorval);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* Iterate over destination pixels */
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+                /* Compute float src pixel location corresponding to (i,j) */
+            projectiveXformPt(vc, j, i, &x, &y);
+            linearInterpolatePixelColor(datas, wpls, w, h, x, y, colorval,
+                                        &val);
+            *(lined + j) = val;
+        }
+    }
+
+        /* If rgba, transform the pixs alpha channel and insert in pixd */
+    if (pixGetSpp(pixs) == 4) {
+        pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+        pix2 = pixProjectiveGray(pix1, vc, 255);  /* bring in opaque */
+        pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixProjectivePtaGray()
+ *
+ *      Input:  pixs (8 bpp)
+ *              ptad  (4 pts of final coordinate space)
+ *              ptas  (4 pts of initial coordinate space)
+ *              grayval (0 to bring in BLACK, 255 for WHITE)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixProjectivePtaGray(PIX     *pixs,
+                     PTA     *ptad,
+                     PTA     *ptas,
+                     l_uint8  grayval)
+{
+l_float32  *vc;
+PIX        *pixd;
+
+    PROCNAME("pixProjectivePtaGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs must be 8 bpp", procName, NULL);
+    if (ptaGetCount(ptas) != 4)
+        return (PIX *)ERROR_PTR("ptas count not 4", procName, NULL);
+    if (ptaGetCount(ptad) != 4)
+        return (PIX *)ERROR_PTR("ptad count not 4", procName, NULL);
+
+        /* Get backwards transform from dest to src, and apply it */
+    getProjectiveXformCoeffs(ptad, ptas, &vc);
+    pixd = pixProjectiveGray(pixs, vc, grayval);
+    LEPT_FREE(vc);
+
+    return pixd;
+}
+
+
+
+/*!
+ *  pixProjectiveGray()
+ *
+ *      Input:  pixs (8 bpp)
+ *              vc  (vector of 8 coefficients for projective transformation)
+ *              grayval (0 to bring in BLACK, 255 for WHITE)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixProjectiveGray(PIX        *pixs,
+                  l_float32  *vc,
+                  l_uint8     grayval)
+{
+l_int32    i, j, w, h, wpls, wpld, val;
+l_uint32  *datas, *datad, *lined;
+l_float32  x, y;
+PIX       *pixd;
+
+    PROCNAME("pixProjectiveGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs must be 8 bpp", procName, NULL);
+    if (!vc)
+        return (PIX *)ERROR_PTR("vc not defined", procName, NULL);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreateTemplate(pixs);
+    pixSetAllArbitrary(pixd, grayval);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* Iterate over destination pixels */
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+                /* Compute float src pixel location corresponding to (i,j) */
+            projectiveXformPt(vc, j, i, &x, &y);
+            linearInterpolatePixelGray(datas, wpls, w, h, x, y, grayval, &val);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*---------------------------------------------------------------------------*
+ *            Projective transform including alpha (blend) component         *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixProjectivePtaWithAlpha()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              ptad  (4 pts of final coordinate space)
+ *              ptas  (4 pts of initial coordinate space)
+ *              pixg (<optional> 8 bpp, for alpha channel, can be null)
+ *              fract (between 0.0 and 1.0, with 0.0 fully transparent
+ *                     and 1.0 fully opaque)
+ *              border (of pixels added to capture transformed source pixels)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The alpha channel is transformed separately from pixs,
+ *          and aligns with it, being fully transparent outside the
+ *          boundary of the transformed pixs.  For pixels that are fully
+ *          transparent, a blending function like pixBlendWithGrayMask()
+ *          will give zero weight to corresponding pixels in pixs.
+ *      (2) If pixg is NULL, it is generated as an alpha layer that is
+ *          partially opaque, using @fract.  Otherwise, it is cropped
+ *          to pixs if required and @fract is ignored.  The alpha channel
+ *          in pixs is never used.
+ *      (3) Colormaps are removed.
+ *      (4) When pixs is transformed, it doesn't matter what color is brought
+ *          in because the alpha channel will be transparent (0) there.
+ *      (5) To avoid losing source pixels in the destination, it may be
+ *          necessary to add a border to the source pix before doing
+ *          the projective transformation.  This can be any non-negative
+ *          number.
+ *      (6) The input @ptad and @ptas are in a coordinate space before
+ *          the border is added.  Internally, we compensate for this
+ *          before doing the projective transform on the image after
+ *          the border is added.
+ *      (7) The default setting for the border values in the alpha channel
+ *          is 0 (transparent) for the outermost ring of pixels and
+ *          (0.5 * fract * 255) for the second ring.  When blended over
+ *          a second image, this
+ *          (a) shrinks the visible image to make a clean overlap edge
+ *              with an image below, and
+ *          (b) softens the edges by weakening the aliasing there.
+ *          Use l_setAlphaMaskBorder() to change these values.
+ */
+PIX *
+pixProjectivePtaWithAlpha(PIX       *pixs,
+                          PTA       *ptad,
+                          PTA       *ptas,
+                          PIX       *pixg,
+                          l_float32  fract,
+                          l_int32    border)
+{
+l_int32  ws, hs, d;
+PIX     *pixd, *pixb1, *pixb2, *pixg2, *pixga;
+PTA     *ptad2, *ptas2;
+
+    PROCNAME("pixProjectivePtaWithAlpha");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &ws, &hs, &d);
+    if (d != 32 && pixGetColormap(pixs) == NULL)
+        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
+    if (pixg && pixGetDepth(pixg) != 8) {
+        L_WARNING("pixg not 8 bpp; using @fract transparent alpha\n", procName);
+        pixg = NULL;
+    }
+    if (!pixg && (fract < 0.0 || fract > 1.0)) {
+        L_WARNING("invalid fract; using 1.0 (fully transparent)\n", procName);
+        fract = 1.0;
+    }
+    if (!pixg && fract == 0.0)
+        L_WARNING("fully opaque alpha; image will not be blended\n", procName);
+    if (!ptad)
+        return (PIX *)ERROR_PTR("ptad not defined", procName, NULL);
+    if (!ptas)
+        return (PIX *)ERROR_PTR("ptas not defined", procName, NULL);
+
+        /* Add border; the color doesn't matter */
+    pixb1 = pixAddBorder(pixs, border, 0);
+
+        /* Transform the ptr arrays to work on the bordered image */
+    ptad2 = ptaTransform(ptad, border, border, 1.0, 1.0);
+    ptas2 = ptaTransform(ptas, border, border, 1.0, 1.0);
+
+        /* Do separate projective transform of rgb channels of pixs
+         * and of pixg */
+    pixd = pixProjectivePtaColor(pixb1, ptad2, ptas2, 0);
+    if (!pixg) {
+        pixg2 = pixCreate(ws, hs, 8);
+        if (fract == 1.0)
+            pixSetAll(pixg2);
+        else
+            pixSetAllArbitrary(pixg2, (l_int32)(255.0 * fract));
+    } else {
+        pixg2 = pixResizeToMatch(pixg, NULL, ws, hs);
+    }
+    if (ws > 10 && hs > 10) {  /* see note 7 */
+        pixSetBorderRingVal(pixg2, 1,
+                            (l_int32)(255.0 * fract * AlphaMaskBorderVals[0]));
+        pixSetBorderRingVal(pixg2, 2,
+                            (l_int32)(255.0 * fract * AlphaMaskBorderVals[1]));
+
+    }
+    pixb2 = pixAddBorder(pixg2, border, 0);  /* must be black border */
+    pixga = pixProjectivePtaGray(pixb2, ptad2, ptas2, 0);
+    pixSetRGBComponent(pixd, pixga, L_ALPHA_CHANNEL);
+    pixSetSpp(pixd, 4);
+
+    pixDestroy(&pixg2);
+    pixDestroy(&pixb1);
+    pixDestroy(&pixb2);
+    pixDestroy(&pixga);
+    ptaDestroy(&ptad2);
+    ptaDestroy(&ptas2);
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------*
+ *                Projective coordinate transformation         *
+ *-------------------------------------------------------------*/
+/*!
+ *  getProjectiveXformCoeffs()
+ *
+ *      Input:  ptas  (source 4 points; unprimed)
+ *              ptad  (transformed 4 points; primed)
+ *              &vc   (<return> vector of coefficients of transform)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  We have a set of 8 equations, describing the projective
+ *  transformation that takes 4 points (ptas) into 4 other
+ *  points (ptad).  These equations are:
+ *
+ *          x1' = (c[0]*x1 + c[1]*y1 + c[2]) / (c[6]*x1 + c[7]*y1 + 1)
+ *          y1' = (c[3]*x1 + c[4]*y1 + c[5]) / (c[6]*x1 + c[7]*y1 + 1)
+ *          x2' = (c[0]*x2 + c[1]*y2 + c[2]) / (c[6]*x2 + c[7]*y2 + 1)
+ *          y2' = (c[3]*x2 + c[4]*y2 + c[5]) / (c[6]*x2 + c[7]*y2 + 1)
+ *          x3' = (c[0]*x3 + c[1]*y3 + c[2]) / (c[6]*x3 + c[7]*y3 + 1)
+ *          y3' = (c[3]*x3 + c[4]*y3 + c[5]) / (c[6]*x3 + c[7]*y3 + 1)
+ *          x4' = (c[0]*x4 + c[1]*y4 + c[2]) / (c[6]*x4 + c[7]*y4 + 1)
+ *          y4' = (c[3]*x4 + c[4]*y4 + c[5]) / (c[6]*x4 + c[7]*y4 + 1)
+ *
+ *  Multiplying both sides of each eqn by the denominator, we get
+ *
+ *           AC = B
+ *
+ *  where B and C are column vectors
+ *
+ *         B = [ x1' y1' x2' y2' x3' y3' x4' y4' ]
+ *         C = [ c[0] c[1] c[2] c[3] c[4] c[5] c[6] c[7] ]
+ *
+ *  and A is the 8x8 matrix
+ *
+ *             x1   y1     1     0   0    0   -x1*x1'  -y1*x1'
+ *              0    0     0    x1   y1   1   -x1*y1'  -y1*y1'
+ *             x2   y2     1     0   0    0   -x2*x2'  -y2*x2'
+ *              0    0     0    x2   y2   1   -x2*y2'  -y2*y2'
+ *             x3   y3     1     0   0    0   -x3*x3'  -y3*x3'
+ *              0    0     0    x3   y3   1   -x3*y3'  -y3*y3'
+ *             x4   y4     1     0   0    0   -x4*x4'  -y4*x4'
+ *              0    0     0    x4   y4   1   -x4*y4'  -y4*y4'
+ *
+ *  These eight equations are solved here for the coefficients C.
+ *
+ *  These eight coefficients can then be used to find the mapping
+ *  (x,y) --> (x',y'):
+ *
+ *           x' = (c[0]x + c[1]y + c[2]) / (c[6]x + c[7]y + 1)
+ *           y' = (c[3]x + c[4]y + c[5]) / (c[6]x + c[7]y + 1)
+ *
+ *  that is implemented in projectiveXformSampled() and
+ *  projectiveXFormInterpolated().
+ */
+l_int32
+getProjectiveXformCoeffs(PTA         *ptas,
+                         PTA         *ptad,
+                         l_float32  **pvc)
+{
+l_int32     i;
+l_float32   x1, y1, x2, y2, x3, y3, x4, y4;
+l_float32  *b;   /* rhs vector of primed coords X'; coeffs returned in *pvc */
+l_float32  *a[8];  /* 8x8 matrix A  */
+
+    PROCNAME("getProjectiveXformCoeffs");
+
+    if (!ptas)
+        return ERROR_INT("ptas not defined", procName, 1);
+    if (!ptad)
+        return ERROR_INT("ptad not defined", procName, 1);
+    if (!pvc)
+        return ERROR_INT("&vc not defined", procName, 1);
+
+    if ((b = (l_float32 *)LEPT_CALLOC(8, sizeof(l_float32))) == NULL)
+        return ERROR_INT("b not made", procName, 1);
+    *pvc = b;
+
+    ptaGetPt(ptas, 0, &x1, &y1);
+    ptaGetPt(ptas, 1, &x2, &y2);
+    ptaGetPt(ptas, 2, &x3, &y3);
+    ptaGetPt(ptas, 3, &x4, &y4);
+    ptaGetPt(ptad, 0, &b[0], &b[1]);
+    ptaGetPt(ptad, 1, &b[2], &b[3]);
+    ptaGetPt(ptad, 2, &b[4], &b[5]);
+    ptaGetPt(ptad, 3, &b[6], &b[7]);
+
+    for (i = 0; i < 8; i++) {
+        if ((a[i] = (l_float32 *)LEPT_CALLOC(8, sizeof(l_float32))) == NULL)
+            return ERROR_INT("a[i] not made", procName, 1);
+    }
+
+    a[0][0] = x1;
+    a[0][1] = y1;
+    a[0][2] = 1.;
+    a[0][6] = -x1 * b[0];
+    a[0][7] = -y1 * b[0];
+    a[1][3] = x1;
+    a[1][4] = y1;
+    a[1][5] = 1;
+    a[1][6] = -x1 * b[1];
+    a[1][7] = -y1 * b[1];
+    a[2][0] = x2;
+    a[2][1] = y2;
+    a[2][2] = 1.;
+    a[2][6] = -x2 * b[2];
+    a[2][7] = -y2 * b[2];
+    a[3][3] = x2;
+    a[3][4] = y2;
+    a[3][5] = 1;
+    a[3][6] = -x2 * b[3];
+    a[3][7] = -y2 * b[3];
+    a[4][0] = x3;
+    a[4][1] = y3;
+    a[4][2] = 1.;
+    a[4][6] = -x3 * b[4];
+    a[4][7] = -y3 * b[4];
+    a[5][3] = x3;
+    a[5][4] = y3;
+    a[5][5] = 1;
+    a[5][6] = -x3 * b[5];
+    a[5][7] = -y3 * b[5];
+    a[6][0] = x4;
+    a[6][1] = y4;
+    a[6][2] = 1.;
+    a[6][6] = -x4 * b[6];
+    a[6][7] = -y4 * b[6];
+    a[7][3] = x4;
+    a[7][4] = y4;
+    a[7][5] = 1;
+    a[7][6] = -x4 * b[7];
+    a[7][7] = -y4 * b[7];
+
+    gaussjordan(a, b, 8);
+
+    for (i = 0; i < 8; i++)
+        LEPT_FREE(a[i]);
+
+    return 0;
+}
+
+
+/*!
+ *  projectiveXformSampledPt()
+ *
+ *      Input:  vc (vector of 8 coefficients)
+ *              (x, y)  (initial point)
+ *              (&xp, &yp)   (<return> transformed point)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This finds the nearest pixel coordinates of the transformed point.
+ *      (2) It does not check ptrs for returned data!
+ */
+l_int32
+projectiveXformSampledPt(l_float32  *vc,
+                         l_int32     x,
+                         l_int32     y,
+                         l_int32    *pxp,
+                         l_int32    *pyp)
+{
+l_float32  factor;
+
+    PROCNAME("projectiveXformSampledPt");
+
+    if (!vc)
+        return ERROR_INT("vc not defined", procName, 1);
+
+    factor = 1. / (vc[6] * x + vc[7] * y + 1.);
+    *pxp = (l_int32)(factor * (vc[0] * x + vc[1] * y + vc[2]) + 0.5);
+    *pyp = (l_int32)(factor * (vc[3] * x + vc[4] * y + vc[5]) + 0.5);
+    return 0;
+}
+
+
+/*!
+ *  projectiveXformPt()
+ *
+ *      Input:  vc (vector of 8 coefficients)
+ *              (x, y)  (initial point)
+ *              (&xp, &yp)   (<return> transformed point)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This computes the floating point location of the transformed point.
+ *      (2) It does not check ptrs for returned data!
+ */
+l_int32
+projectiveXformPt(l_float32  *vc,
+                  l_int32     x,
+                  l_int32     y,
+                  l_float32  *pxp,
+                  l_float32  *pyp)
+{
+l_float32  factor;
+
+    PROCNAME("projectiveXformPt");
+
+    if (!vc)
+        return ERROR_INT("vc not defined", procName, 1);
+
+    factor = 1. / (vc[6] * x + vc[7] * y + 1.);
+    *pxp = factor * (vc[0] * x + vc[1] * y + vc[2]);
+    *pyp = factor * (vc[3] * x + vc[4] * y + vc[5]);
+    return 0;
+}
diff --git a/src/psio1.c b/src/psio1.c
new file mode 100644 (file)
index 0000000..06e606d
--- /dev/null
@@ -0,0 +1,987 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  psio1.c
+ *
+ *    |=============================================================|
+ *    |                         Important note                      |
+ *    |=============================================================|
+ *    | Some of these functions require libtiff, libjpeg and libz.  |
+ *    | If you do not have these libraries, you must set            |
+ *    |     #define  USE_PSIO     0                                 |
+ *    | in environ.h.  This will link psio1stub.c                   |
+ *    |=============================================================|
+ *
+ *     This is a PostScript "device driver" for wrapping images
+ *     in PostScript.  The images can be rendered by a PostScript
+ *     interpreter for viewing, using evince or gv.  They can also be
+ *     rasterized for printing, using gs or an embedded interpreter
+ *     in a PostScript printer.  And they can be converted to a pdf
+ *     using gs (ps2pdf).
+ *
+ *     Convert specified files to PS
+ *          l_int32          convertFilesToPS()
+ *          l_int32          sarrayConvertFilesToPS()
+ *          l_int32          convertFilesFittedToPS()
+ *          l_int32          sarrayConvertFilesFittedToPS()
+ *          l_int32          writeImageCompressedToPSFile()
+ *
+ *     Convert mixed text/image files to PS
+ *          l_int32          convertSegmentedPagesToPS()
+ *          l_int32          pixWriteSegmentedPageToPS()
+ *          l_int32          pixWriteMixedToPS()
+ *
+ *     Convert any image file to PS for embedding
+ *          l_int32          convertToPSEmbed()
+ *
+ *     Write all images in a pixa out to PS
+ *          l_int32          pixaWriteCompressedToPS()
+ *
+ *  These PostScript converters are used in three different ways.
+ *
+ *  (1) For embedding a PS file in a program like TeX.
+ *      convertToPSEmbed() handles this for levels 1, 2 and 3 output,
+ *      and prog/converttops wraps this in an executable.
+ *      converttops is a generalization of Thomas Merz's jpeg2ps wrapper,
+ *      in that it works for all types (formats, depth, colormap)
+ *      of input images and gives PS output in one of these formats
+ *        * level 1 (uncompressed)
+ *        * level 2 (compressed ccittg4 or dct)
+ *        * level 3 (compressed flate)
+ *
+ *  (2) For composing a set of pages with any number of images
+ *      painted on them, in either level 2 or level 3 formats.
+ *
+ *  (3) For printing a page image or a set of page images, at a
+ *      resolution that optimally fills the page, using
+ *      convertFilesFittedToPS().
+ *
+ *  The top-level calls of utilities in category 2, which can compose
+ *  multiple images on a page, and which generate a PostScript file for
+ *  printing or display (e.g., conversion to pdf), are:
+ *      convertFilesToPS()
+ *      convertFilesFittedToPS()
+ *      convertSegmentedPagesToPS()
+ *
+ *  All images are output with page numbers.  Bounding box hints are
+ *  more subtle.  They must be included for embeding images in
+ *  TeX, for example, and the low-level writers include bounding
+ *  box hints by default.  However, these hints should not be included for
+ *  multi-page PostScript that is composed of a sequence of images;
+ *  consequently, they are not written when calling higher level
+ *  functions such as convertFilesToPS(), convertFilesFittedToPS()
+ *  and convertSegmentedPagesToPS().  The function l_psWriteBoundingBox()
+ *  sets a flag to give low-level control over this.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  USE_PSIO   /* defined in environ.h */
+ /* --------------------------------------------*/
+
+/*-------------------------------------------------------------*
+ *                Convert files in a directory to PS           *
+ *-------------------------------------------------------------*/
+/*
+ *  convertFilesToPS()
+ *
+ *      Input:  dirin (input directory)
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              res (typ. 300 or 600 ppi)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This generates a PS file for all image files in a specified
+ *          directory that contain the substr pattern to be matched.
+ *      (2) Each image is written to a separate page in the output PS file.
+ *      (3) All images are written compressed:
+ *              * if tiffg4  -->  use ccittg4
+ *              * if jpeg    -->  use dct
+ *              * all others -->  use flate
+ *          If the image is jpeg or tiffg4, we use the existing compressed
+ *          strings for the encoding; otherwise, we read the image into
+ *          a pix and flate-encode the pieces.
+ *      (4) The resolution is often confusing.  It is interpreted
+ *          as the resolution of the output display device:  "If the
+ *          input image were digitized at 300 ppi, what would it
+ *          look like when displayed at res ppi."  So, for example,
+ *          if res = 100 ppi, then the display pixels are 3x larger
+ *          than the 300 ppi pixels, and the image will be rendered
+ *          3x larger.
+ *      (5) The size of the PostScript file is independent of the resolution,
+ *          because the entire file is encoded.  The res parameter just
+ *          tells the PS decomposer how to render the page.  Therefore,
+ *          for minimum file size without loss of visual information,
+ *          if the output res is less than 300, you should downscale
+ *          the image to the output resolution before wrapping in PS.
+ *      (6) The "canvas" on which the image is rendered, at the given
+ *          output resolution, is a standard page size (8.5 x 11 in).
+ */
+l_int32
+convertFilesToPS(const char  *dirin,
+                 const char  *substr,
+                 l_int32      res,
+                 const char  *fileout)
+{
+SARRAY  *sa;
+
+    PROCNAME("convertFilesToPS");
+
+    if (!dirin)
+        return ERROR_INT("dirin not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (res <= 0) {
+        L_INFO("setting res to 300 ppi\n", procName);
+        res = 300;
+    }
+    if (res < 10 || res > 4000)
+        L_WARNING("res is typically in the range 300-600 ppi\n", procName);
+
+        /* Get all filtered and sorted full pathnames. */
+    sa = getSortedPathnamesInDirectory(dirin, substr, 0, 0);
+
+        /* Generate the PS file.  Don't use bounding boxes. */
+    l_psWriteBoundingBox(FALSE);
+    sarrayConvertFilesToPS(sa, res, fileout);
+    l_psWriteBoundingBox(TRUE);
+    sarrayDestroy(&sa);
+    return 0;
+}
+
+
+/*
+ *  sarrayConvertFilesToPS()
+ *
+ *      Input:  sarray (of full path names)
+ *              res (typ. 300 or 600 ppi)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See convertFilesToPS()
+ */
+l_int32
+sarrayConvertFilesToPS(SARRAY      *sa,
+                       l_int32      res,
+                       const char  *fileout)
+{
+char    *fname;
+l_int32  i, nfiles, index, firstfile, ret, format;
+
+    PROCNAME("sarrayConvertFilesToPS");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (res <= 0) {
+        L_INFO("setting res to 300 ppi\n", procName);
+        res = 300;
+    }
+    if (res < 10 || res > 4000)
+        L_WARNING("res is typically in the range 300-600 ppi\n", procName);
+
+    nfiles = sarrayGetCount(sa);
+    firstfile = TRUE;
+    for (i = 0, index = 0; i < nfiles; i++) {
+        fname = sarrayGetString(sa, i, L_NOCOPY);
+        ret = pixReadHeader(fname, &format, NULL, NULL, NULL, NULL, NULL);
+        if (ret) continue;
+        if (format == IFF_UNKNOWN)
+            continue;
+
+        writeImageCompressedToPSFile(fname, fileout, res, &firstfile, &index);
+    }
+
+    return 0;
+}
+
+
+/*
+ *  convertFilesFittedToPS()
+ *
+ *      Input:  dirin (input directory)
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              xpts, ypts (desired size in printer points; use 0 for default)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This generates a PS file for all files in a specified directory
+ *          that contain the substr pattern to be matched.
+ *      (2) Each image is written to a separate page in the output PS file.
+ *      (3) All images are written compressed:
+ *              * if tiffg4  -->  use ccittg4
+ *              * if jpeg    -->  use dct
+ *              * all others -->  use flate
+ *          If the image is jpeg or tiffg4, we use the existing compressed
+ *          strings for the encoding; otherwise, we read the image into
+ *          a pix and flate-encode the pieces.
+ *      (4) The resolution is internally determined such that the images
+ *          are rendered, in at least one direction, at 100% of the given
+ *          size in printer points.  Use 0.0 for xpts or ypts to get
+ *          the default value, which is 612.0 or 792.0, rsp.
+ *      (5) The size of the PostScript file is independent of the resolution,
+ *          because the entire file is encoded.  The @xpts and @ypts
+ *          parameter tells the PS decomposer how to render the page.
+ */
+l_int32
+convertFilesFittedToPS(const char  *dirin,
+                       const char  *substr,
+                       l_float32    xpts,
+                       l_float32    ypts,
+                       const char  *fileout)
+{
+SARRAY  *sa;
+
+    PROCNAME("convertFilesFittedToPS");
+
+    if (!dirin)
+        return ERROR_INT("dirin not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (xpts <= 0.0) {
+        L_INFO("setting xpts to 612.0 ppi\n", procName);
+        xpts = 612.0;
+    }
+    if (ypts <= 0.0) {
+        L_INFO("setting ypts to 792.0 ppi\n", procName);
+        ypts = 792.0;
+    }
+    if (xpts < 100.0 || xpts > 2000.0 || ypts < 100.0 || ypts > 2000.0)
+        L_WARNING("xpts,ypts are typically in the range 500-800\n", procName);
+
+        /* Get all filtered and sorted full pathnames. */
+    sa = getSortedPathnamesInDirectory(dirin, substr, 0, 0);
+
+        /* Generate the PS file.  Don't use bounding boxes. */
+    l_psWriteBoundingBox(FALSE);
+    sarrayConvertFilesFittedToPS(sa, xpts, ypts, fileout);
+    l_psWriteBoundingBox(TRUE);
+    sarrayDestroy(&sa);
+    return 0;
+}
+
+
+/*
+ *  sarrayConvertFilesFittedToPS()
+ *
+ *      Input:  sarray (of full path names)
+ *              xpts, ypts (desired size in printer points; use 0 for default)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See convertFilesFittedToPS()
+ */
+l_int32
+sarrayConvertFilesFittedToPS(SARRAY      *sa,
+                             l_float32    xpts,
+                             l_float32    ypts,
+                             const char  *fileout)
+{
+char    *fname;
+l_int32  ret, i, w, h, nfiles, index, firstfile, format, res;
+
+    PROCNAME("sarrayConvertFilesFittedToPS");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (xpts <= 0.0) {
+        L_INFO("setting xpts to 612.0\n", procName);
+        xpts = 612.0;
+    }
+    if (ypts <= 0.0) {
+        L_INFO("setting ypts to 792.0\n", procName);
+        ypts = 792.0;
+    }
+    if (xpts < 100.0 || xpts > 2000.0 || ypts < 100.0 || ypts > 2000.0)
+        L_WARNING("xpts,ypts are typically in the range 500-800\n", procName);
+
+    nfiles = sarrayGetCount(sa);
+    firstfile = TRUE;
+    for (i = 0, index = 0; i < nfiles; i++) {
+        fname = sarrayGetString(sa, i, L_NOCOPY);
+        ret = pixReadHeader(fname, &format, &w, &h, NULL, NULL, NULL);
+        if (ret) continue;
+        if (format == IFF_UNKNOWN)
+            continue;
+
+            /* Be sure the entire image is wrapped */
+        if (xpts * h < ypts * w)
+            res = (l_int32)((l_float32)w * 72.0 / xpts);
+        else
+            res = (l_int32)((l_float32)h * 72.0 / ypts);
+
+        writeImageCompressedToPSFile(fname, fileout, res, &firstfile, &index);
+    }
+
+    return 0;
+}
+
+
+/*
+ *  writeImageCompressedToPSFile()
+ *
+ *      Input:  filein (input image file)
+ *              fileout (output ps file)
+ *              res (output printer resolution)
+ *              &firstfile (<input and return> 1 if the first image;
+ *                          0 otherwise)
+ *              &index (<input and return> index of image in output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This wraps a single page image in PS.
+ *      (2) The input file can be in any format.  It is compressed as follows:
+ *             * if in tiffg4  -->  use ccittg4
+ *             * if in jpeg    -->  use dct
+ *             * all others    -->  use flate
+ *      (3) Before the first call, set @firstpage = 1.  After writing
+ *          the first page, it will be set to 0.
+ *      (4) @index is incremented if the page is successfully written.
+ */
+l_int32
+writeImageCompressedToPSFile(const char  *filein,
+                             const char  *fileout,
+                             l_int32      res,
+                             l_int32     *pfirstfile,
+                             l_int32     *pindex)
+{
+const char  *op;
+l_int32      format, retval;
+
+    PROCNAME("writeImageCompressedToPSFile");
+
+    if (!pfirstfile || !pindex)
+        return ERROR_INT("&firstfile and &index not defined", procName, 1);
+
+    findFileFormat(filein, &format);
+    if (format == IFF_UNKNOWN) {
+        L_ERROR("format of %s not known\n", procName, filein);
+        return 1;
+    }
+
+    op = (*pfirstfile == TRUE) ? "w" : "a";
+    if (format == IFF_JFIF_JPEG) {
+        retval = convertJpegToPS(filein, fileout, op, 0, 0,
+                                 res, 1.0, *pindex + 1, TRUE);
+        if (retval == 0) {
+            *pfirstfile = FALSE;
+            (*pindex)++;
+        }
+    } else if (format == IFF_TIFF_G4) {
+        retval = convertG4ToPS(filein, fileout, op, 0, 0,
+                               res, 1.0, *pindex + 1, FALSE, TRUE);
+        if (retval == 0) {
+            *pfirstfile = FALSE;
+            (*pindex)++;
+        }
+    } else {  /* all other image formats */
+        retval = convertFlateToPS(filein, fileout, op, 0, 0,
+                                  res, 1.0, *pindex + 1, TRUE);
+        if (retval == 0) {
+            *pfirstfile = FALSE;
+            (*pindex)++;
+        }
+    }
+
+    return retval;
+}
+
+
+/*-------------------------------------------------------------*
+ *              Convert mixed text/image files to PS           *
+ *-------------------------------------------------------------*/
+/*
+ *  convertSegmentedPagesToPS()
+ *
+ *      Input:  pagedir (input page image directory)
+ *              pagestr (<optional> substring filter on page filenames;
+ *                       can be NULL)
+ *              page_numpre (number of characters in page name before number)
+ *              maskdir (input mask image directory)
+ *              maskstr (<optional> substring filter on mask filenames;
+ *                       can be NULL)
+ *              mask_numpre (number of characters in mask name before number)
+ *              numpost (number of characters in names after number)
+ *              maxnum (only consider page numbers up to this value)
+ *              textscale (scale of text output relative to pixs)
+ *              imagescale (scale of image output relative to pixs)
+ *              threshold (for binarization; typ. about 190; 0 for default)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This generates a PS file for all page image and mask files in two
+ *          specified directories and that contain the page numbers as
+ *          specified below.  The two directories can be the same, in which
+ *          case the page and mask files are differentiated by the two
+ *          substrings for string matches.
+ *      (2) The page images are taken in lexicographic order.
+ *          Mask images whose numbers match the page images are used to
+ *          segment the page images.  Page images without a matching
+ *          mask image are scaled, thresholded and rendered entirely as text.
+ *      (3) Each PS page is generated as a compressed representation of
+ *          the page image, where the part of the image under the mask
+ *          is suitably scaled and compressed as DCT (i.e., jpeg), and
+ *          the remaining part of the page is suitably scaled, thresholded,
+ *          compressed as G4 (i.e., tiff g4), and rendered by painting
+ *          black through the resulting text mask.
+ *      (4) The scaling is typically 2x down for the DCT component
+ *          (@imagescale = 0.5) and 2x up for the G4 component
+ *          (@textscale = 2.0).
+ *      (5) The resolution is automatically set to fit to a
+ *          letter-size (8.5 x 11 inch) page.
+ *      (6) Both the DCT and the G4 encoding are PostScript level 2.
+ *      (7) It is assumed that the page number is contained within
+ *          the basename (the filename without directory or extension).
+ *          @page_numpre is the number of characters in the page basename
+ *          preceding the actual page number; @mask_numpre is likewise for
+ *          the mask basename; @numpost is the number of characters
+ *          following the page number.  For example, for mask name
+ *          mask_006.tif, mask_numpre = 5 ("mask_).
+ *      (8) To render a page as is -- that is, with no thresholding
+ *          of any pixels -- use a mask in the mask directory that is
+ *          full size with all pixels set to 1.  If the page is 1 bpp,
+ *          it is not necessary to have a mask.
+ */
+l_int32
+convertSegmentedPagesToPS(const char  *pagedir,
+                          const char  *pagestr,
+                          l_int32      page_numpre,
+                          const char  *maskdir,
+                          const char  *maskstr,
+                          l_int32      mask_numpre,
+                          l_int32      numpost,
+                          l_int32      maxnum,
+                          l_float32    textscale,
+                          l_float32    imagescale,
+                          l_int32      threshold,
+                          const char  *fileout)
+{
+l_int32  pageno, i, npages;
+PIX     *pixs, *pixm;
+SARRAY  *sapage, *samask;
+
+    PROCNAME("convertSegmentedPagesToPS");
+
+    if (!pagedir)
+        return ERROR_INT("pagedir not defined", procName, 1);
+    if (!maskdir)
+        return ERROR_INT("maskdir not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (threshold <= 0) {
+        L_INFO("setting threshold to 190\n", procName);
+        threshold = 190;
+    }
+
+        /* Get numbered full pathnames; max size of sarray is maxnum */
+    sapage = getNumberedPathnamesInDirectory(pagedir, pagestr,
+                                             page_numpre, numpost, maxnum);
+    samask = getNumberedPathnamesInDirectory(maskdir, maskstr,
+                                             mask_numpre, numpost, maxnum);
+    sarrayPadToSameSize(sapage, samask, (char *)"");
+    if ((npages = sarrayGetCount(sapage)) == 0) {
+        sarrayDestroy(&sapage);
+        sarrayDestroy(&samask);
+        return ERROR_INT("no matching pages found", procName, 1);
+    }
+
+        /* Generate the PS file */
+    pageno = 1;
+    for (i = 0; i < npages; i++) {
+        if ((pixs = pixReadIndexed(sapage, i)) == NULL)
+            continue;
+        pixm = pixReadIndexed(samask, i);
+        pixWriteSegmentedPageToPS(pixs, pixm, textscale, imagescale,
+                                  threshold, pageno, fileout);
+        pixDestroy(&pixs);
+        pixDestroy(&pixm);
+        pageno++;
+    }
+
+    sarrayDestroy(&sapage);
+    sarrayDestroy(&samask);
+    return 0;
+}
+
+
+/*
+ *  pixWriteSegmentedPageToPS()
+ *
+ *      Input:  pixs (all depths; colormap ok)
+ *              pixm (<optional> 1 bpp segmentation mask over image region)
+ *              textscale (scale of text output relative to pixs)
+ *              imagescale (scale of image output relative to pixs)
+ *              threshold (threshold for binarization; typ. 190)
+ *              pageno (page number in set; use 1 for new output file)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This generates the PS string for a mixed text/image page,
+ *          and adds it to an existing file if @pageno > 1.
+ *          The PS output is determined by fitting the result to
+ *          a letter-size (8.5 x 11 inch) page.
+ *      (2) The two images (pixs and pixm) are at the same resolution
+ *          (typically 300 ppi).  They are used to generate two compressed
+ *          images, pixb and pixc, that are put directly into the output
+ *          PS file.
+ *      (3) pixb is the text component.  In the PostScript world, we think of
+ *          it as a mask through which we paint black.  It is produced by
+ *          scaling pixs by @textscale, and thresholding to 1 bpp.
+ *      (4) pixc is the image component, which is that part of pixs under
+ *          the mask pixm.  It is scaled from pixs by @imagescale.
+ *      (5) Typical values are textscale = 2.0 and imagescale = 0.5.
+ *      (6) If pixm == NULL, the page has only text.  If it is all black,
+ *          the page is all image and has no text.
+ *      (7) This can be used to write a multi-page PS file, by using
+ *          sequential page numbers with the same output file.  It can
+ *          also be used to write separate PS files for each page,
+ *          by using different output files with @pageno = 0 or 1.
+ */
+l_int32
+pixWriteSegmentedPageToPS(PIX         *pixs,
+                          PIX         *pixm,
+                          l_float32    textscale,
+                          l_float32    imagescale,
+                          l_int32      threshold,
+                          l_int32      pageno,
+                          const char  *fileout)
+{
+l_int32    alltext, notext, d, ret;
+l_uint32   val;
+l_float32  scaleratio;
+PIX       *pixmi, *pixmis, *pixt, *pixg, *pixsc, *pixb, *pixc;
+
+    PROCNAME("pixWriteSegmentedPageToPS");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (imagescale <= 0.0 || textscale <= 0.0)
+        return ERROR_INT("relative scales must be > 0.0", procName, 1);
+
+        /* Analyze the page.  Determine the ratio by which the
+         * binary text mask is scaled relative to the image part.
+         * If there is no image region (alltext == TRUE), the
+         * text mask will be rendered directly to fit the page,
+         * and scaleratio = 1.0.  */
+    alltext = TRUE;
+    notext = FALSE;
+    scaleratio = 1.0;
+    if (pixm) {
+        pixZero(pixm, &alltext);  /* pixm empty: all text */
+        if (alltext) {
+            pixm = NULL;  /* treat it as not existing here */
+        } else {
+            pixmi = pixInvert(NULL, pixm);
+            pixZero(pixmi, &notext);  /* pixm full; no text */
+            pixDestroy(&pixmi);
+            scaleratio = textscale / imagescale;
+        }
+    }
+
+    if (pixGetDepth(pixs) == 1) {  /* render tiff g4 */
+        pixb = pixClone(pixs);
+        pixc = NULL;
+    } else {
+        pixt = pixConvertTo8Or32(pixs, 0, 0);  /* this can be a clone of pixs */
+
+            /* Get the binary text mask.  Note that pixg cannot be a
+             * clone of pixs, because it may be altered by pixSetMasked(). */
+        pixb = NULL;
+        if (notext == FALSE) {
+            d = pixGetDepth(pixt);
+            if (d == 8)
+                pixg = pixCopy(NULL, pixt);
+            else  /* d == 32 */
+                pixg = pixConvertRGBToLuminance(pixt);
+            if (pixm)  /* clear out the image parts */
+                pixSetMasked(pixg, pixm, 255);
+            if (textscale == 1.0)
+                pixsc = pixClone(pixg);
+            else if (textscale >= 0.7)
+                pixsc = pixScaleGrayLI(pixg, textscale, textscale);
+            else
+                pixsc = pixScaleAreaMap(pixg, textscale, textscale);
+            pixb = pixThresholdToBinary(pixsc, threshold);
+            pixDestroy(&pixg);
+            pixDestroy(&pixsc);
+        }
+
+            /* Get the scaled image region */
+        pixc = NULL;
+        if (pixm) {
+            if (imagescale == 1.0)
+                pixsc = pixClone(pixt);  /* can possibly be a clone of pixs */
+            else
+                pixsc = pixScale(pixt, imagescale, imagescale);
+
+                /* If pixm is not full, clear the pixels in pixsc
+                 * corresponding to bg in pixm, where there can be text
+                 * that is written through the mask pixb.  Note that
+                 * we could skip this and use pixsc directly in
+                 * pixWriteMixedToPS(); however, clearing these
+                 * non-image regions to a white background will reduce
+                 * the size of pixc (relative to pixsc), and hence
+                 * reduce the size of the PS file that is generated.
+                 * Use a copy so that we don't accidentally alter pixs.  */
+            if (notext == FALSE) {
+                pixmis = pixScale(pixm, imagescale, imagescale);
+                pixmi = pixInvert(NULL, pixmis);
+                val = (d == 8) ? 0xff : 0xffffff00;
+                pixc = pixCopy(NULL, pixsc);
+                pixSetMasked(pixc, pixmi, val);  /* clear non-image part */
+                pixDestroy(&pixmis);
+                pixDestroy(&pixmi);
+            } else {
+                pixc = pixClone(pixsc);
+            }
+            pixDestroy(&pixsc);
+        }
+        pixDestroy(&pixt);
+    }
+
+        /* Generate the PS file.  Don't use bounding boxes. */
+    l_psWriteBoundingBox(FALSE);
+    ret = pixWriteMixedToPS(pixb, pixc, scaleratio, pageno, fileout);
+    l_psWriteBoundingBox(TRUE);
+    pixDestroy(&pixb);
+    pixDestroy(&pixc);
+    return ret;
+}
+
+
+/*
+ *  pixWriteMixedToPS()
+ *
+ *      Input:  pixb (<optionall> 1 bpp "mask"; typically for text)
+ *              pixc (<optional> 8 or 32 bpp image regions)
+ *              scale (relative scale factor for rendering pixb
+ *                    relative to pixc; typ. 4.0)
+ *              pageno (page number in set; use 1 for new output file)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This low level function generates the PS string for a mixed
+ *          text/image page, and adds it to an existing file if
+ *          @pageno > 1.
+ *      (2) The two images (pixb and pixc) are typically generated at the
+ *          resolution that they will be rendered in the PS file.
+ *      (3) pixb is the text component.  In the PostScript world, we think of
+ *          it as a mask through which we paint black.
+ *      (4) pixc is the (typically halftone) image component.  It is
+ *          white in the rest of the page.  To minimize the size of the
+ *          PS file, it should be rendered at a resolution that is at
+ *          least equal to its actual resolution.
+ *      (5) @scale gives the ratio of resolution of pixb to pixc.
+ *          Typical resolutions are: 600 ppi for pixb, 150 ppi for pixc;
+ *          so @scale = 4.0.  If one of the images is not defined,
+ *          the value of @scale is ignored.
+ *      (6) We write pixc with DCT compression (jpeg).  This is followed
+ *          by painting the text as black through the mask pixb.  If
+ *          pixc doesn't exist (alltext), we write the text with the
+ *          PS "image" operator instead of the "imagemask" operator,
+ *          because ghostscript's ps2pdf is flaky when the latter is used.
+ *      (7) The actual output resolution is determined by fitting the
+ *          result to a letter-size (8.5 x 11 inch) page.
+ */
+l_int32
+pixWriteMixedToPS(PIX         *pixb,
+                  PIX         *pixc,
+                  l_float32    scale,
+                  l_int32      pageno,
+                  const char  *fileout)
+{
+const char   tnameb[] = "/tmp/lept/psio/mixed.tif";
+const char   tnamec[] = "/tmp/lept/psio/mixed.jpg";
+const char  *op;
+l_int32      resb, resc, endpage, maskop, ret;
+
+    PROCNAME("pixWriteMixedToPS");
+
+    if (!pixb && !pixc)
+        return ERROR_INT("pixb and pixc both undefined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+        /* Compute the resolution that fills a letter-size page. */
+    if (!pixc) {
+       resb = getResLetterPage(pixGetWidth(pixb), pixGetHeight(pixb), 0);
+    } else {
+       resc = getResLetterPage(pixGetWidth(pixc), pixGetHeight(pixc), 0);
+       if (pixb)
+           resb = (l_int32)(scale * resc);
+    }
+
+        /* Write the jpeg image first */
+    lept_mkdir("lept/psio");
+    if (pixc) {
+        pixWrite(tnamec, pixc, IFF_JFIF_JPEG);
+        endpage = (pixb) ? FALSE : TRUE;
+        op = (pageno <= 1) ? "w" : "a";
+        ret = convertJpegToPS(tnamec, fileout, op, 0, 0, resc, 1.0,
+                              pageno, endpage);
+        if (ret)
+            return ERROR_INT("jpeg data not written", procName, 1);
+    }
+
+        /* Write the binary data, either directly or, if there is
+         * a jpeg image on the page, through the mask. */
+    if (pixb) {
+        pixWrite(tnameb, pixb, IFF_TIFF_G4);
+        op = (pageno <= 1 && !pixc) ? "w" : "a";
+        maskop = (pixc) ? 1 : 0;
+        ret = convertG4ToPS(tnameb, fileout, op, 0, 0, resb, 1.0,
+                            pageno, maskop, 1);
+        if (ret)
+            return ERROR_INT("tiff data not written", procName, 1);
+    }
+
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *            Convert any image file to PS for embedding       *
+ *-------------------------------------------------------------*/
+/*
+ *  convertToPSEmbed()
+ *
+ *      Input:  filein (input image file -- any format)
+ *              fileout (output ps file)
+ *              level (compression: 1 (uncompressed), 2 or 3)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a wrapper function that generates a PS file with
+ *          a bounding box, from any input image file.
+ *      (2) Do the best job of compression given the specified level.
+ *          @level=3 does flate compression on anything that is not
+ *          tiffg4 (1 bpp) or jpeg (8 bpp or rgb).
+ *      (3) If @level=2 and the file is not tiffg4 or jpeg, it will
+ *          first be written to file as jpeg with quality = 75.
+ *          This will remove the colormap and cause some degradation
+ *          in the image.
+ *      (4) The bounding box is required when a program such as TeX
+ *          (through epsf) places and rescales the image.  It is
+ *          sized for fitting the image to an 8.5 x 11.0 inch page.
+ */
+l_int32
+convertToPSEmbed(const char  *filein,
+                 const char  *fileout,
+                 l_int32      level)
+{
+const char  nametif[] = "/tmp/junk_convert_ps_embed.tif";
+const char  namejpg[] = "/tmp/junk_convert_ps_embed.jpg";
+l_int32     d, format;
+PIX        *pix, *pixs;
+
+    PROCNAME("convertToPSEmbed");
+
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (level != 1 && level != 2 && level != 3) {
+        L_ERROR("invalid level specified; using level 2\n", procName);
+        level = 2;
+    }
+
+    if (level == 1) {  /* no compression */
+        pixWritePSEmbed(filein, fileout);
+        return 0;
+    }
+
+        /* Find the format and write out directly if in jpeg or tiff g4 */
+    findFileFormat(filein, &format);
+    if (format == IFF_JFIF_JPEG) {
+        convertJpegToPSEmbed(filein, fileout);
+        return 0;
+    } else if (format == IFF_TIFF_G4) {
+        convertG4ToPSEmbed(filein, fileout);
+        return 0;
+    } else if (format == IFF_UNKNOWN) {
+        L_ERROR("format of %s not known\n", procName, filein);
+        return 1;
+    }
+
+        /* If level 3, flate encode. */
+    if (level == 3) {
+        convertFlateToPSEmbed(filein, fileout);
+        return 0;
+    }
+
+        /* OK, it's level 2, so we must convert to jpeg or tiff g4 */
+    if ((pixs = pixRead(filein)) == NULL)
+        return ERROR_INT("image not read from file", procName, 1);
+    d = pixGetDepth(pixs);
+    if ((d == 2 || d == 4) && !pixGetColormap(pixs))
+        pix = pixConvertTo8(pixs, 0);
+    else if (d == 16)
+        pix = pixConvert16To8(pixs, 1);
+    else
+        pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+
+    d = pixGetDepth(pix);
+    if (d == 1) {
+        pixWrite(nametif, pix, IFF_TIFF_G4);
+        convertG4ToPSEmbed(nametif, fileout);
+    } else {
+        pixWrite(namejpg, pix, IFF_JFIF_JPEG);
+        convertJpegToPSEmbed(namejpg, fileout);
+    }
+
+    pixDestroy(&pix);
+    pixDestroy(&pixs);
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *              Write all images in a pixa out to PS           *
+ *-------------------------------------------------------------*/
+/*
+ *  pixaWriteCompressedToPS()
+ *
+ *      Input:  pixa (any set of images)
+ *              fileout (output ps file)
+ *              res (of input image)
+ *              level (compression: 2 or 3)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This generates a PS file of multiple page images, all
+ *          with bounding boxes.
+ *      (2) It compresses to:
+ *              cmap + level2:        jpeg
+ *              cmap + level3:        flate
+ *              1 bpp:                tiffg4
+ *              2 or 4 bpp + level2:  jpeg
+ *              2 or 4 bpp + level3:  flate
+ *              8 bpp:                jpeg
+ *              16 bpp:               flate
+ *              32 bpp:               jpeg
+ *      (3) To generate a pdf, use: ps2pdf <infile.ps> <outfile.pdf>
+ */
+l_int32
+pixaWriteCompressedToPS(PIXA        *pixa,
+                        const char  *fileout,
+                        l_int32      res,
+                        l_int32      level)
+{
+char     *tname, *g4_name, *jpeg_name, *png_name;
+l_int32   i, n, firstfile, index, writeout, d;
+PIX      *pix, *pixt;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixaWriteCompressedToPS");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (level != 2 && level != 3) {
+        L_ERROR("only levels 2 and 3 permitted; using level 2\n", procName);
+        level = 2;
+    }
+
+    n = pixaGetCount(pixa);
+    firstfile = TRUE;
+    index = 0;
+    lept_mkdir("lept/comp");
+    g4_name = genTempFilename("/tmp/lept/comp", "temp.tif", 0, 0);
+    jpeg_name = genTempFilename("/tmp/lept/comp", "temp.jpg", 0, 0);
+    png_name = genTempFilename("/tmp/lept/comp", "temp.png", 0, 0);
+    for (i = 0; i < n; i++) {
+        writeout = TRUE;
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        d = pixGetDepth(pix);
+        cmap = pixGetColormap(pix);
+        if (d == 1) {
+            tname = g4_name;
+            pixWrite(tname, pix, IFF_TIFF_G4);
+        } else if (cmap) {
+            if (level == 2) {
+                pixt = pixConvertForPSWrap(pix);
+                tname = jpeg_name;
+                pixWrite(tname, pixt, IFF_JFIF_JPEG);
+                pixDestroy(&pixt);
+            } else {  /* level == 3 */
+                tname = png_name;
+                pixWrite(tname, pix, IFF_PNG);
+            }
+        } else if (d == 16) {
+            if (level == 2)
+                L_WARNING("d = 16; must write out flate\n", procName);
+            tname = png_name;
+            pixWrite(tname, pix, IFF_PNG);
+        } else if (d == 2 || d == 4) {
+            if (level == 2) {
+                pixt = pixConvertTo8(pix, 0);
+                tname = jpeg_name;
+                pixWrite(tname, pixt, IFF_JFIF_JPEG);
+                pixDestroy(&pixt);
+            } else {  /* level == 3 */
+                tname = png_name;
+                pixWrite(tname, pix, IFF_PNG);
+            }
+        } else if (d == 8 || d == 32) {
+            tname = jpeg_name;
+            pixWrite(tname, pix, IFF_JFIF_JPEG);
+        } else {  /* shouldn't happen */
+            L_ERROR("invalid depth: %d\n", procName, d);
+            writeout = FALSE;
+        }
+        pixDestroy(&pix);
+
+        if (writeout)
+            writeImageCompressedToPSFile(tname, fileout, res,
+                                         &firstfile, &index);
+    }
+
+    LEPT_FREE(g4_name);
+    LEPT_FREE(jpeg_name);
+    LEPT_FREE(png_name);
+    return 0;
+}
+
+
+/* --------------------------------------------*/
+#endif  /* USE_PSIO */
+/* --------------------------------------------*/
diff --git a/src/psio1stub.c b/src/psio1stub.c
new file mode 100644 (file)
index 0000000..97f5d01
--- /dev/null
@@ -0,0 +1,124 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  psio1stub.c
+ *
+ *     Stubs for psio1.c functions
+ */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !USE_PSIO   /* defined in environ.h */
+/* --------------------------------------------*/
+
+l_int32 convertFilesToPS(const char *dirin, const char *substr,
+                         l_int32 res, const char *fileout)
+{
+    return ERROR_INT("function not present", "convertFilesToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 sarrayConvertFilesToPS(SARRAY *sa, l_int32 res, const char *fileout)
+{
+    return ERROR_INT("function not present", "sarrayConvertFilesToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertFilesFittedToPS(const char *dirin, const char *substr,
+                               l_float32 xpts, l_float32 ypts,
+                               const char *fileout)
+{
+    return ERROR_INT("function not present", "convertFilesFittedToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 sarrayConvertFilesFittedToPS(SARRAY *sa, l_float32 xpts,
+                                     l_float32 ypts, const char *fileout)
+{
+    return ERROR_INT("function not present", "sarrayConvertFilesFittedToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 writeImageCompressedToPSFile(const char *filein, const char *fileout,
+                                     l_int32 res, l_int32 *pfirstfile,
+                                     l_int32 *pindex)
+{
+    return ERROR_INT("function not present", "writeImageCompressedToPSFile", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertSegmentedPagesToPS(const char *pagedir, const char *pagestr,
+                                  l_int32 page_numpre, const char *maskdir,
+                                  const char *maskstr, l_int32 mask_numpre,
+                                  l_int32 numpost, l_int32 maxnum,
+                                  l_float32 textscale, l_float32 imagescale,
+                                  l_int32 threshold, const char *fileout)
+{
+    return ERROR_INT("function not present", "convertSegmentedPagesToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteSegmentedPageToPS(PIX *pixs, PIX *pixm, l_float32 textscale,
+                                  l_float32 imagescale, l_int32 threshold,
+                                  l_int32 pageno, const char *fileout)
+{
+    return ERROR_INT("function not present", "pixWriteSegmentedPagesToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteMixedToPS(PIX *pixb, PIX *pixc, l_float32 scale,
+                          l_int32 pageno, const char *fileout)
+{
+    return ERROR_INT("function not present", "pixWriteMixedToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertToPSEmbed(const char *filein, const char *fileout, l_int32 level)
+{
+    return ERROR_INT("function not present", "convertToPSEmbed", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixaWriteCompressedToPS(PIXA *pixa, const char *fileout,
+                                l_int32 res, l_int32 level)
+{
+    return ERROR_INT("function not present", "pixaWriteCompressedtoPS", 1);
+}
+
+/* --------------------------------------------*/
+#endif  /* !USE_PSIO */
+/* --------------------------------------------*/
diff --git a/src/psio2.c b/src/psio2.c
new file mode 100644 (file)
index 0000000..0b47048
--- /dev/null
@@ -0,0 +1,1966 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  psio2.c
+ *
+ *    |=============================================================|
+ *    |                         Important note                      |
+ *    |=============================================================|
+ *    | Some of these functions require libtiff, libjpeg and libz.  |
+ *    | If you do not have these libraries, you must set            |
+ *    |     #define  USE_PSIO     0                                 |
+ *    | in environ.h.  This will link psio2stub.c                   |
+ *    |=============================================================|
+ *
+ *     These are lower-level functions that implement a PostScript
+ *     "device driver" for wrapping images in PostScript.  The images
+ *     can be rendered by a PostScript interpreter for viewing,
+ *     using evince or gv.  They can also be rasterized for printing,
+ *     using gs or an embedded interpreter in a PostScript printer.
+ *     And they can be converted to a pdf using gs (ps2pdf).
+ *
+ *     For uncompressed images
+ *          l_int32              pixWritePSEmbed()
+ *          l_int32              pixWriteStreamPS()
+ *          char                *pixWriteStringPS()
+ *          char                *generateUncompressedPS()
+ *          void                 getScaledParametersPS()
+ *          l_int32              convertByteToHexAscii()
+ *
+ *     For jpeg compressed images (use dct compression)
+ *          l_int32              convertJpegToPSEmbed()
+ *          l_int32              convertJpegToPS()
+ *          l_int32              convertJpegToPSString()
+ *          char                *generateJpegPS()
+ *
+ *     For g4 fax compressed images (use ccitt g4 compression)
+ *          l_int32              convertG4ToPSEmbed()
+ *          l_int32              convertG4ToPS()
+ *          l_int32              convertG4ToPSString()
+ *          char                *generateG4PS()
+ *
+ *     For multipage tiff images
+ *          l_int32              convertTiffMultipageToPS()
+ *
+ *     For flate (gzip) compressed images (e.g., png)
+ *          l_int32              convertFlateToPSEmbed()
+ *          l_int32              convertFlateToPS()
+ *          l_int32              convertFlateToPSString()
+ *          char                *generateFlatePS()
+ *
+ *     Write to memory
+ *          l_int32              pixWriteMemPS()
+ *
+ *     Converting resolution
+ *          l_int32              getResLetterPage()
+ *          l_int32              getResA4Page()
+ *
+ *     Setting flag for writing bounding box hint
+ *          void                 l_psWriteBoundingBox()
+ *
+ *  See psio1.c for higher-level functions and their usage.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  USE_PSIO   /* defined in environ.h */
+ /* --------------------------------------------*/
+
+    /* Set default for writing bounding box hint */
+static l_int32  var_PS_WRITE_BOUNDING_BOX = 1;
+
+static const l_int32  L_BUF_SIZE = 512;
+static const l_int32  DEFAULT_INPUT_RES   = 300;  /* typical scan res, ppi */
+static const l_int32  MIN_RES             = 5;
+static const l_int32  MAX_RES             = 3000;
+
+    /* For computing resolution that fills page to desired amount */
+static const l_int32  LETTER_WIDTH            = 612;   /* points */
+static const l_int32  LETTER_HEIGHT           = 792;   /* points */
+static const l_int32  A4_WIDTH                = 595;   /* points */
+static const l_int32  A4_HEIGHT               = 842;   /* points */
+static const l_float32  DEFAULT_FILL_FRACTION = 0.95;
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_JPEG       0
+#define  DEBUG_G4         0
+#define  DEBUG_FLATE      0
+#endif  /* ~NO_CONSOLE_IO */
+
+/* Note that the bounding box hint at the top of the generated PostScript
+ * file is required for the "*Embed" functions.  These generate a
+ * PostScript file for an individual image that can be translated and
+ * scaled by an application that embeds the image in its output
+ * (e.g., in the PS output from a TeX file).
+ * However, bounding box hints should not be embedded in any
+ * PostScript image that will be composited with other images,
+ * where more than one image may be placed in an arbitrary location
+ * on a page.  */
+
+
+/*-------------------------------------------------------------*
+ *                  For uncompressed images                    *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixWritePSEmbed()
+ *
+ *      Input:  filein (input file, all depths, colormap OK)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a simple wrapper function that generates an
+ *          uncompressed PS file, with a bounding box.
+ *      (2) The bounding box is required when a program such as TeX
+ *          (through epsf) places and rescales the image.
+ *      (3) The bounding box is sized for fitting the image to an
+ *          8.5 x 11.0 inch page.
+ */
+l_int32
+pixWritePSEmbed(const char  *filein,
+                const char  *fileout)
+{
+l_int32    w, h;
+l_float32  scale;
+FILE      *fp;
+PIX       *pix;
+
+    PROCNAME("pixWritePSEmbed");
+
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+    if ((pix = pixRead(filein)) == NULL)
+        return ERROR_INT("image not read from file", procName, 1);
+    w = pixGetWidth(pix);
+    h = pixGetHeight(pix);
+    if (w * 11.0 > h * 8.5)
+        scale = 8.5 * 300. / (l_float32)w;
+    else
+        scale = 11.0 * 300. / (l_float32)h;
+
+    if ((fp = fopenWriteStream(fileout, "wb")) == NULL)
+        return ERROR_INT("file not opened for write", procName, 1);
+    pixWriteStreamPS(fp, pix, NULL, 0, scale);
+    fclose(fp);
+
+    pixDestroy(&pix);
+    return 0;
+}
+
+
+/*!
+ *  pixWriteStreamPS()
+ *
+ *      Input:  stream
+ *              pix
+ *              box  (<optional>)
+ *              res  (can use 0 for default of 300 ppi)
+ *              scale (to prevent scaling, use either 1.0 or 0.0)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This writes image in PS format, optionally scaled,
+ *          adjusted for the printer resolution, and with
+ *          a bounding box.
+ *      (2) For details on use of parameters, see pixWriteStringPS().
+ */
+l_int32
+pixWriteStreamPS(FILE      *fp,
+                 PIX       *pix,
+                 BOX       *box,
+                 l_int32    res,
+                 l_float32  scale)
+{
+char    *outstr;
+l_int32  length;
+PIX     *pixc;
+
+    PROCNAME("pixWriteStreamPS");
+
+    if (!fp)
+        return (l_int32)ERROR_INT("stream not open", procName, 1);
+    if (!pix)
+        return (l_int32)ERROR_INT("pix not defined", procName, 1);
+
+    if ((pixc = pixConvertForPSWrap(pix)) == NULL)
+        return (l_int32)ERROR_INT("pixc not made", procName, 1);
+
+    outstr = pixWriteStringPS(pixc, box, res, scale);
+    length = strlen(outstr);
+    fwrite(outstr, 1, length, fp);
+    LEPT_FREE(outstr);
+    pixDestroy(&pixc);
+
+    return 0;
+}
+
+
+/*!
+ *  pixWriteStringPS()
+ *
+ *      Input:  pixs:  all depths, colormap OK
+ *              box:  (a) If box == null, image is placed, optionally scaled,
+ *                        in a standard b.b. at the center of the page.
+ *                        This is to be used when another program like
+ *                        TeX (through epsf) places the image.
+ *                    (b) If box != null, image is placed without a
+ *                        b.b. at the specified page location and with
+ *                        (optional) scaling.  This is to be used when
+ *                        you want to specify exactly where (and optionally
+ *                        how big) you want the image to be.
+ *                        Note that all coordinates are in PS convention,
+ *                        with (0,0) at LL corner of the page:
+ *                            (x,y)    location of LL corner of image, in mils.
+ *                            (w,h)    scaled size, in mils.  Use 0 to
+ *                                     scale with "scale" and "res" input.
+ *              res:  resolution, in printer ppi.  Use 0 for default (300 ppi).
+ *              scale: scale factor.  If no scaling is desired, use
+ *                     either 1.0 or 0.0.   Scaling just resets the resolution
+ *                     parameter; the actual scaling is done in the
+ *                     interpreter at rendering time.  This is important:
+ *                     it allows you to scale the image up without
+ *                     increasing the file size.
+ *      Return: ps string if OK, or null on error
+ *
+ *  Notes:
+ *      (1) OK, this seems a bit complicated, because there are various
+ *          ways to scale and not to scale.  Here's a summary:
+ *      (2) If you don't want any scaling at all:
+ *           * if you are using a box:
+ *               set w = 0, h = 0, and use scale = 1.0; it will print
+ *               each pixel unscaled at printer resolution
+ *           * if you are not using a box:
+ *               set scale = 1.0; it will print at printer resolution
+ *      (3) If you want the image to be a certain size in inches:
+ *           * you must use a box and set the box (w,h) in mils
+ *      (4) If you want the image to be scaled by a scale factor != 1.0:
+ *           * if you are using a box:
+ *               set w = 0, h = 0, and use the desired scale factor;
+ *               the higher the printer resolution, the smaller the
+ *               image will actually appear.
+ *           * if you are not using a box:
+ *               set the desired scale factor; the higher the printer
+ *               resolution, the smaller the image will actually appear.
+ *      (5) Another complication is the proliferation of distance units:
+ *           * The interface distances are in milli-inches.
+ *           * Three different units are used internally:
+ *              - pixels  (units of 1/res inch)
+ *              - printer pts (units of 1/72 inch)
+ *              - inches
+ *           * Here is a quiz on volume units from a reviewer:
+ *             How many UK milli-cups in a US kilo-teaspoon?
+ *               (Hint: 1.0 US cup = 0.75 UK cup + 0.2 US gill;
+ *                      1.0 US gill = 24.0 US teaspoons)
+ */
+char *
+pixWriteStringPS(PIX       *pixs,
+                 BOX       *box,
+                 l_int32    res,
+                 l_float32  scale)
+{
+char       nib1, nib2;
+char      *hexdata, *outstr;
+l_uint8    byteval;
+l_int32    i, j, k, w, h, d;
+l_float32  wpt, hpt, xpt, ypt;
+l_int32    wpl, psbpl, hexbytes, boxflag, bps;
+l_uint32  *line, *data;
+PIX       *pix;
+
+    PROCNAME("pixWriteStringPS");
+
+    if (!pixs)
+        return (char *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    if ((pix = pixConvertForPSWrap(pixs)) == NULL)
+        return (char *)ERROR_PTR("pix not made", procName, NULL);
+    pixGetDimensions(pix, &w, &h, &d);
+
+        /* Get the factors by which PS scales and translates, in pts */
+    if (!box)
+        boxflag = 0;  /* no scaling; b.b. at center */
+    else
+        boxflag = 1;  /* no b.b., specify placement and optional scaling */
+    getScaledParametersPS(box, w, h, res, scale, &xpt, &ypt, &wpt, &hpt);
+
+    if (d == 1)
+        bps = 1;  /* bits/sample */
+    else  /* d == 8 || d == 32 */
+        bps = 8;
+
+        /* Convert image data to hex string.  psbpl is the number of
+         * bytes in each raster line when it is packed to the byte
+         * boundary (not the 32 bit word boundary, as with the pix).
+         * When converted to hex, the hex string has 2 bytes for
+         * every byte of raster data. */
+    wpl = pixGetWpl(pix);
+    if (d == 1 || d == 8)
+        psbpl = (w * d + 7) / 8;
+    else /* d == 32 */
+        psbpl = 3 * w;
+    data = pixGetData(pix);
+    hexbytes = 2 * psbpl * h;  /* size of ps hex array */
+    if ((hexdata = (char *)LEPT_CALLOC(hexbytes + 1, sizeof(char))) == NULL)
+        return (char *)ERROR_PTR("hexdata not made", procName, NULL);
+    if (d == 1 || d == 8) {
+        for (i = 0, k = 0; i < h; i++) {
+            line = data + i * wpl;
+            for (j = 0; j < psbpl; j++) {
+                byteval = GET_DATA_BYTE(line, j);
+                convertByteToHexAscii(byteval, &nib1, &nib2);
+                hexdata[k++] = nib1;
+                hexdata[k++] = nib2;
+            }
+        }
+    } else  {  /* d == 32; hexdata bytes packed RGBRGB..., 2 per sample */
+        for (i = 0, k = 0; i < h; i++) {
+            line = data + i * wpl;
+            for (j = 0; j < w; j++) {
+                byteval = GET_DATA_BYTE(line + j, 0);  /* red */
+                convertByteToHexAscii(byteval, &nib1, &nib2);
+                hexdata[k++] = nib1;
+                hexdata[k++] = nib2;
+                byteval = GET_DATA_BYTE(line + j, 1);  /* green */
+                convertByteToHexAscii(byteval, &nib1, &nib2);
+                hexdata[k++] = nib1;
+                hexdata[k++] = nib2;
+                byteval = GET_DATA_BYTE(line + j, 2);  /* blue */
+                convertByteToHexAscii(byteval, &nib1, &nib2);
+                hexdata[k++] = nib1;
+                hexdata[k++] = nib2;
+            }
+        }
+    }
+    hexdata[k] = '\0';
+
+    outstr = generateUncompressedPS(hexdata, w, h, d, psbpl, bps,
+                                    xpt, ypt, wpt, hpt, boxflag);
+    if (!outstr)
+        return (char *)ERROR_PTR("outstr not made", procName, NULL);
+    pixDestroy(&pix);
+    return outstr;
+}
+
+
+/*!
+ *  generateUncompressedPS()
+ *
+ *      Input:  hexdata
+ *              w, h  (raster image size in pixels)
+ *              d (image depth in bpp; rgb is 32)
+ *              psbpl (raster bytes/line, when packed to the byte boundary)
+ *              bps (bits/sample: either 1 or 8)
+ *              xpt, ypt (location of LL corner of image, in pts, relative
+ *                    to the PostScript origin (0,0) at the LL corner
+ *                    of the page)
+ *              wpt, hpt (rendered image size in pts)
+ *              boxflag (1 to print out bounding box hint; 0 to skip)
+ *      Return: PS string, or null on error
+ *
+ *  Notes:
+ *      (1) Low-level function.
+ */
+char *
+generateUncompressedPS(char      *hexdata,
+                       l_int32    w,
+                       l_int32    h,
+                       l_int32    d,
+                       l_int32    psbpl,
+                       l_int32    bps,
+                       l_float32  xpt,
+                       l_float32  ypt,
+                       l_float32  wpt,
+                       l_float32  hpt,
+                       l_int32    boxflag)
+{
+char    *outstr;
+char     bigbuf[L_BUF_SIZE];
+SARRAY  *sa;
+
+    PROCNAME("generateUncompressedPS");
+
+    if (!hexdata)
+        return (char *)ERROR_PTR("hexdata not defined", procName, NULL);
+
+    if ((sa = sarrayCreate(0)) == NULL)
+        return (char *)ERROR_PTR("sa not made", procName, NULL);
+    sarrayAddString(sa, (char *)"%!Adobe-PS", L_COPY);
+    if (boxflag == 0) {
+        sprintf(bigbuf,
+            "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f",
+            xpt, ypt, xpt + wpt, ypt + hpt);
+        sarrayAddString(sa, bigbuf, L_COPY);
+    } else {  /* boxflag == 1 */
+        sarrayAddString(sa, (char *)"gsave", L_COPY);
+    }
+
+    if (d == 1)
+        sarrayAddString(sa,
+              (char *)"{1 exch sub} settransfer    %invert binary", L_COPY);
+
+    sprintf(bigbuf, "/bpl %d string def         %%bpl as a string", psbpl);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sprintf(bigbuf,
+           "%7.2f %7.2f translate         %%set image origin in pts", xpt, ypt);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sprintf(bigbuf,
+            "%7.2f %7.2f scale             %%set image size in pts", wpt, hpt);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sprintf(bigbuf,
+            "%d %d %d                 %%image dimensions in pixels", w, h, bps);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sprintf(bigbuf,
+            "[%d %d %d %d %d %d]     %%mapping matrix: [w 0 0 -h 0 h]",
+            w, 0, 0, -h, 0, h);
+    sarrayAddString(sa, bigbuf, L_COPY);
+
+    if (boxflag == 0) {
+        if (d == 1 || d == 8)
+            sarrayAddString(sa,
+                (char *)"{currentfile bpl readhexstring pop} image", L_COPY);
+        else  /* d == 32 */
+            sarrayAddString(sa,
+              (char *)"{currentfile bpl readhexstring pop} false 3 colorimage",
+              L_COPY);
+    } else {  /* boxflag == 1 */
+        if (d == 1 || d == 8)
+            sarrayAddString(sa,
+              (char *)"{currentfile bpl readhexstring pop} bind image", L_COPY);
+        else  /* d == 32 */
+            sarrayAddString(sa,
+          (char *)"{currentfile bpl readhexstring pop} bind false 3 colorimage",
+                 L_COPY);
+    }
+
+    sarrayAddString(sa, hexdata, L_INSERT);
+
+    if (boxflag == 0)
+        sarrayAddString(sa, (char *)"\nshowpage", L_COPY);
+    else  /* boxflag == 1 */
+        sarrayAddString(sa, (char *)"\ngrestore", L_COPY);
+
+    if ((outstr = sarrayToString(sa, 1)) == NULL)
+        return (char *)ERROR_PTR("outstr not made", procName, NULL);
+
+    sarrayDestroy(&sa);
+    return outstr;
+}
+
+
+/*!
+ *  getScaledParametersPS()
+ *
+ *      Input:  box (<optional> location of image in mils; with
+ *                   (x,y) being the LL corner)
+ *              wpix (pix width in pixels)
+ *              hpix (pix height in pixels)
+ *              res (of printer; use 0 for default)
+ *              scale (use 1.0 or 0.0 for no scaling)
+ *              &xpt (location of llx in pts)
+ *              &ypt (location of lly in pts)
+ *              &wpt (image width in pts)
+ *              &hpt (image height in pts)
+ *      Return: void (no arg checking)
+ *
+ *  Notes:
+ *      (1) The image is always scaled, depending on res and scale.
+ *      (2) If no box, the image is centered on the page.
+ *      (3) If there is a box, the image is placed within it.
+ */
+void
+getScaledParametersPS(BOX        *box,
+                      l_int32     wpix,
+                      l_int32     hpix,
+                      l_int32     res,
+                      l_float32   scale,
+                      l_float32  *pxpt,
+                      l_float32  *pypt,
+                      l_float32  *pwpt,
+                      l_float32  *phpt)
+{
+l_int32    bx, by, bw, bh;
+l_float32  winch, hinch, xinch, yinch, fres;
+
+    PROCNAME("getScaledParametersPS");
+
+    if (res == 0)
+        res = DEFAULT_INPUT_RES;
+    fres = (l_float32)res;
+
+        /* Allow the PS interpreter to scale the resolution */
+    if (scale == 0.0)
+        scale = 1.0;
+    if (scale != 1.0) {
+        fres = (l_float32)res / scale;
+        res = (l_int32)fres;
+    }
+
+        /* Limit valid resolution interval */
+    if (res < MIN_RES || res > MAX_RES) {
+        L_WARNING("res %d out of bounds; using default res; no scaling\n",
+                  procName, res);
+        res = DEFAULT_INPUT_RES;
+        fres = (l_float32)res;
+    }
+
+    if (!box) {  /* center on page */
+        winch = (l_float32)wpix / fres;
+        hinch = (l_float32)hpix / fres;
+        xinch = (8.5 - winch) / 2.;
+        yinch = (11.0 - hinch) / 2.;
+    } else {
+        boxGetGeometry(box, &bx, &by, &bw, &bh);
+        if (bw == 0)
+            winch = (l_float32)wpix / fres;
+        else
+            winch = (l_float32)bw / 1000.;
+        if (bh == 0)
+            hinch = (l_float32)hpix / fres;
+        else
+            hinch = (l_float32)bh / 1000.;
+        xinch = (l_float32)bx / 1000.;
+        yinch = (l_float32)by / 1000.;
+    }
+
+    if (xinch < 0)
+        L_WARNING("left edge < 0.0 inch\n", procName);
+    if (xinch + winch > 8.5)
+        L_WARNING("right edge > 8.5 inch\n", procName);
+    if (yinch < 0.0)
+        L_WARNING("bottom edge < 0.0 inch\n", procName);
+    if (yinch + hinch > 11.0)
+        L_WARNING("top edge > 11.0 inch\n", procName);
+
+    *pwpt = 72. * winch;
+    *phpt = 72. * hinch;
+    *pxpt = 72. * xinch;
+    *pypt = 72. * yinch;
+    return;
+}
+
+
+/*!
+ *  convertByteToHexAscii()
+ *
+ *      Input:  byteval  (input byte)
+ *              &nib1, &nib2  (<return> two hex ascii characters)
+ *      Return: void
+ */
+void
+convertByteToHexAscii(l_uint8  byteval,
+                      char    *pnib1,
+                      char    *pnib2)
+{
+l_uint8  nib;
+
+    nib = byteval >> 4;
+    if (nib < 10)
+        *pnib1 = '0' + nib;
+    else
+        *pnib1 = 'a' + (nib - 10);
+    nib = byteval & 0xf;
+    if (nib < 10)
+        *pnib2 = '0' + nib;
+    else
+        *pnib2 = 'a' + (nib - 10);
+
+    return;
+}
+
+
+/*-------------------------------------------------------------*
+ *                  For jpeg compressed images                 *
+ *-------------------------------------------------------------*/
+/*!
+ *  convertJpegToPSEmbed()
+ *
+ *      Input:  filein (input jpeg file)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function takes a jpeg file as input and generates a DCT
+ *          compressed, ascii85 encoded PS file, with a bounding box.
+ *      (2) The bounding box is required when a program such as TeX
+ *          (through epsf) places and rescales the image.
+ *      (3) The bounding box is sized for fitting the image to an
+ *          8.5 x 11.0 inch page.
+ */
+l_int32
+convertJpegToPSEmbed(const char  *filein,
+                     const char  *fileout)
+{
+char         *outstr;
+l_int32       w, h, nbytes;
+l_float32     xpt, ypt, wpt, hpt;
+L_COMP_DATA  *cid;
+
+    PROCNAME("convertJpegToPSEmbed");
+
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+        /* Generate the ascii encoded jpeg data */
+    if ((cid = l_generateJpegData(filein, 1)) == NULL)
+        return ERROR_INT("jpeg data not made", procName, 1);
+    w = cid->w;
+    h = cid->h;
+
+        /* Scale for 20 pt boundary and otherwise full filling
+         * in one direction on 8.5 x 11 inch device */
+    xpt = 20.0;
+    ypt = 20.0;
+    if (w * 11.0 > h * 8.5) {
+        wpt = 572.0;   /* 612 - 2 * 20 */
+        hpt = wpt * (l_float32)h / (l_float32)w;
+    } else {
+        hpt = 752.0;   /* 792 - 2 * 20 */
+        wpt = hpt * (l_float32)w / (l_float32)h;
+    }
+
+        /* Generate the PS.
+         * The bounding box information should be inserted (default). */
+    outstr = generateJpegPS(filein, cid, xpt, ypt, wpt, hpt, 1, 1);
+    if (!outstr)
+        return ERROR_INT("outstr not made", procName, 1);
+    nbytes = strlen(outstr);
+
+    if (l_binaryWrite(fileout, "w", outstr, nbytes))
+        return ERROR_INT("ps string not written to file", procName, 1);
+    LEPT_FREE(outstr);
+    l_CIDataDestroy(&cid);
+    return 0;
+}
+
+
+/*!
+ *  convertJpegToPS()
+ *
+ *      Input:  filein (input jpeg file)
+ *              fileout (output ps file)
+ *              operation ("w" for write; "a" for append)
+ *              x, y (location of LL corner of image, in pixels, relative
+ *                    to the PostScript origin (0,0) at the LL corner
+ *                    of the page)
+ *              res (resolution of the input image, in ppi; use 0 for default)
+ *              scale (scaling by printer; use 0.0 or 1.0 for no scaling)
+ *              pageno (page number; must start with 1; you can use 0
+ *                      if there is only one page)
+ *              endpage (boolean: use TRUE if this is the last image to be
+ *                       added to the page; FALSE otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is simpler to use than pixWriteStringPS(), and
+ *          it outputs in level 2 PS as compressed DCT (overlaid
+ *          with ascii85 encoding).
+ *      (2) An output file can contain multiple pages, each with
+ *          multiple images.  The arguments to convertJpegToPS()
+ *          allow you to control placement of jpeg images on multiple
+ *          pages within a PostScript file.
+ *      (3) For the first image written to a file, use "w", which
+ *          opens for write and clears the file.  For all subsequent
+ *          images written to that file, use "a".
+ *      (4) The (x, y) parameters give the LL corner of the image
+ *          relative to the LL corner of the page.  They are in
+ *          units of pixels if scale = 1.0.  If you use (e.g.)
+ *          scale = 2.0, the image is placed at (2x, 2y) on the page,
+ *          and the image dimensions are also doubled.
+ *      (5) Display vs printed resolution:
+ *           * If your display is 75 ppi and your image was created
+ *             at a resolution of 300 ppi, you can get the image
+ *             to print at the same size as it appears on your display
+ *             by either setting scale = 4.0 or by setting  res = 75.
+ *             Both tell the printer to make a 4x enlarged image.
+ *           * If your image is generated at 150 ppi and you use scale = 1,
+ *             it will be rendered such that 150 pixels correspond
+ *             to 72 pts (1 inch on the printer).  This function does
+ *             the conversion from pixels (with or without scaling) to
+ *             pts, which are the units that the printer uses.
+ *           * The printer will choose its own resolution to use
+ *             in rendering the image, which will not affect the size
+ *             of the rendered image.  That is because the output
+ *             PostScript file describes the geometry in terms of pts,
+ *             which are defined to be 1/72 inch.  The printer will
+ *             only see the size of the image in pts, through the
+ *             scale and translate parameters and the affine
+ *             transform (the ImageMatrix) of the image.
+ *      (6) To render multiple images on the same page, set
+ *          endpage = FALSE for each image until you get to the
+ *          last, for which you set endpage = TRUE.  This causes the
+ *          "showpage" command to be invoked.  Showpage outputs
+ *          the entire page and clears the raster buffer for the
+ *          next page to be added.  Without a "showpage",
+ *          subsequent images from the next page will overlay those
+ *          previously put down.
+ *      (7) For multiple pages, increment the page number, starting
+ *          with page 1.  This allows PostScript (and PDF) to build
+ *          a page directory, which viewers use for navigation.
+ */
+l_int32
+convertJpegToPS(const char  *filein,
+                const char  *fileout,
+                const char  *operation,
+                l_int32      x,
+                l_int32      y,
+                l_int32      res,
+                l_float32    scale,
+                l_int32      pageno,
+                l_int32      endpage)
+{
+char    *outstr;
+l_int32  nbytes;
+
+    PROCNAME("convertJpegToPS");
+
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (strcmp(operation, "w") && strcmp(operation, "a"))
+        return ERROR_INT("operation must be \"w\" or \"a\"", procName, 1);
+
+    if (convertJpegToPSString(filein, &outstr, &nbytes, x, y, res, scale,
+                          pageno, endpage))
+        return ERROR_INT("ps string not made", procName, 1);
+
+    if (l_binaryWrite(fileout, operation, outstr, nbytes))
+        return ERROR_INT("ps string not written to file", procName, 1);
+
+    LEPT_FREE(outstr);
+    return 0;
+}
+
+
+/*!
+ *  convertJpegToPSString()
+ *
+ *      Generates PS string in jpeg format from jpeg file
+ *
+ *      Input:  filein (input jpeg file)
+ *              &poutstr (<return> PS string)
+ *              &nbytes (<return> number of bytes in PS string)
+ *              x, y (location of LL corner of image, in pixels, relative
+ *                    to the PostScript origin (0,0) at the LL corner
+ *                    of the page)
+ *              res (resolution of the input image, in ppi; use 0 for default)
+ *              scale (scaling by printer; use 0.0 or 1.0 for no scaling)
+ *              pageno (page number; must start with 1; you can use 0
+ *                      if there is only one page)
+ *              endpage (boolean: use TRUE if this is the last image to be
+ *                       added to the page; FALSE otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For usage, see convertJpegToPS()
+ */
+l_int32
+convertJpegToPSString(const char  *filein,
+                      char       **poutstr,
+                      l_int32     *pnbytes,
+                      l_int32      x,
+                      l_int32      y,
+                      l_int32      res,
+                      l_float32    scale,
+                      l_int32      pageno,
+                      l_int32      endpage)
+{
+char         *outstr;
+l_float32     xpt, ypt, wpt, hpt;
+L_COMP_DATA  *cid;
+
+    PROCNAME("convertJpegToPSString");
+
+    if (!poutstr)
+        return ERROR_INT("&outstr not defined", procName, 1);
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *poutstr = NULL;
+    *pnbytes = 0;
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+
+        /* Generate the ascii encoded jpeg data */
+    if ((cid = l_generateJpegData(filein, 1)) == NULL)
+        return ERROR_INT("jpeg data not made", procName, 1);
+
+        /* Get scaled location in pts.  Guess the input scan resolution
+         * based on the input parameter @res, the resolution data in
+         * the pix, and the size of the image. */
+    if (scale == 0.0)
+        scale = 1.0;
+    if (res <= 0) {
+        if (cid->res > 0)
+            res = cid->res;
+        else
+            res = DEFAULT_INPUT_RES;
+    }
+
+        /* Get scaled location in pts */
+    if (scale == 0.0)
+        scale = 1.0;
+    if (res == 0)
+        res = DEFAULT_INPUT_RES;
+    xpt = scale * x * 72. / res;
+    ypt = scale * y * 72. / res;
+    wpt = scale * cid->w * 72. / res;
+    hpt = scale * cid->h * 72. / res;
+
+    if (pageno == 0)
+        pageno = 1;
+
+#if  DEBUG_JPEG
+    fprintf(stderr, "w = %d, h = %d, bps = %d, spp = %d\n",
+            cid->w, cid->h, cid->bps, cid->spp);
+    fprintf(stderr, "comp bytes = %ld, nbytes85 = %ld, ratio = %5.3f\n",
+            (unsigned long)cid->nbytescomp, (unsigned long)cid->nbytes85,
+           (l_float32)cid->nbytes85 / (l_float32)cid->nbytescomp);
+    fprintf(stderr, "xpt = %7.2f, ypt = %7.2f, wpt = %7.2f, hpt = %7.2f\n",
+             xpt, ypt, wpt, hpt);
+#endif   /* DEBUG_JPEG */
+
+        /* Generate the PS */
+    outstr = generateJpegPS(filein, cid, xpt, ypt, wpt, hpt, pageno, endpage);
+    if (!outstr)
+        return ERROR_INT("outstr not made", procName, 1);
+    *poutstr = outstr;
+    *pnbytes = strlen(outstr);
+    l_CIDataDestroy(&cid);
+    return 0;
+}
+
+
+/*!
+ *  generateJpegPS()
+ *
+ *      Input:  filein (<optional> input jpeg filename; can be null)
+ *              cid (jpeg compressed image data)
+ *              xpt, ypt (location of LL corner of image, in pts, relative
+ *                        to the PostScript origin (0,0) at the LL corner
+ *                        of the page)
+ *              wpt, hpt (rendered image size in pts)
+ *              pageno (page number; must start with 1; you can use 0
+ *                      if there is only one page.)
+ *              endpage (boolean: use TRUE if this is the last image to be
+ *                       added to the page; FALSE otherwise)
+ *      Return: PS string, or null on error
+ *
+ *  Notes:
+ *      (1) Low-level function.
+ */
+char *
+generateJpegPS(const char   *filein,
+               L_COMP_DATA  *cid,
+               l_float32     xpt,
+               l_float32     ypt,
+               l_float32     wpt,
+               l_float32     hpt,
+               l_int32       pageno,
+               l_int32       endpage)
+{
+l_int32  w, h, bps, spp;
+char    *outstr;
+char     bigbuf[L_BUF_SIZE];
+SARRAY  *sa;
+
+    PROCNAME("generateJpegPS");
+
+    if (!cid)
+        return (char *)ERROR_PTR("jpeg data not defined", procName, NULL);
+    w = cid->w;
+    h = cid->h;
+    bps = cid->bps;
+    spp = cid->spp;
+
+    if ((sa = sarrayCreate(50)) == NULL)
+        return (char *)ERROR_PTR("sa not made", procName, NULL);
+
+    sarrayAddString(sa, (char *)"%!PS-Adobe-3.0", L_COPY);
+    sarrayAddString(sa, (char *)"%%Creator: leptonica", L_COPY);
+    if (filein) {
+        sprintf(bigbuf, "%%%%Title: %s", filein);
+        sarrayAddString(sa, bigbuf, L_COPY);
+    }
+    sarrayAddString(sa, (char *)"%%DocumentData: Clean7Bit", L_COPY);
+
+    if (var_PS_WRITE_BOUNDING_BOX == 1) {
+        sprintf(bigbuf,
+            "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f",
+                       xpt, ypt, xpt + wpt, ypt + hpt);
+        sarrayAddString(sa, bigbuf, L_COPY);
+    }
+
+    sarrayAddString(sa, (char *)"%%LanguageLevel: 2", L_COPY);
+    sarrayAddString(sa, (char *)"%%EndComments", L_COPY);
+    sprintf(bigbuf, "%%%%Page: %d %d", pageno, pageno);
+    sarrayAddString(sa, bigbuf, L_COPY);
+
+    sarrayAddString(sa, (char *)"save", L_COPY);
+    sarrayAddString(sa,
+           (char *)"/RawData currentfile /ASCII85Decode filter def", L_COPY);
+    sarrayAddString(sa,
+           (char *)"/Data RawData << >> /DCTDecode filter def", L_COPY);
+
+    sprintf(bigbuf,
+        "%7.2f %7.2f translate         %%set image origin in pts", xpt, ypt);
+    sarrayAddString(sa, bigbuf, L_COPY);
+
+    sprintf(bigbuf,
+        "%7.2f %7.2f scale             %%set image size in pts", wpt, hpt);
+    sarrayAddString(sa, bigbuf, L_COPY);
+
+    if (spp == 1)
+        sarrayAddString(sa, (char *)"/DeviceGray setcolorspace", L_COPY);
+    else if (spp == 3)
+        sarrayAddString(sa, (char *)"/DeviceRGB setcolorspace", L_COPY);
+    else  /*spp == 4 */
+        sarrayAddString(sa, (char *)"/DeviceCMYK setcolorspace", L_COPY);
+
+    sarrayAddString(sa, (char *)"{ << /ImageType 1", L_COPY);
+    sprintf(bigbuf, "     /Width %d", w);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sprintf(bigbuf, "     /Height %d", h);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sprintf(bigbuf, "     /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sarrayAddString(sa, (char *)"     /DataSource Data", L_COPY);
+    sprintf(bigbuf, "     /BitsPerComponent %d", bps);
+    sarrayAddString(sa, bigbuf, L_COPY);
+
+    if (spp == 1)
+        sarrayAddString(sa, (char *)"     /Decode [0 1]", L_COPY);
+    else if (spp == 3)
+        sarrayAddString(sa, (char *)"     /Decode [0 1 0 1 0 1]", L_COPY);
+    else   /* spp == 4 */
+        sarrayAddString(sa, (char *)"     /Decode [0 1 0 1 0 1 0 1]", L_COPY);
+
+    sarrayAddString(sa, (char *)"  >> image", L_COPY);
+    sarrayAddString(sa, (char *)"  Data closefile", L_COPY);
+    sarrayAddString(sa, (char *)"  RawData flushfile", L_COPY);
+    if (endpage == TRUE)
+        sarrayAddString(sa, (char *)"  showpage", L_COPY);
+    sarrayAddString(sa, (char *)"  restore", L_COPY);
+    sarrayAddString(sa, (char *)"} exec", L_COPY);
+
+        /* Insert the ascii85 jpeg data; this is now owned by sa */
+    sarrayAddString(sa, cid->data85, L_INSERT);
+    cid->data85 = NULL;  /* it has been transferred and destroyed */
+
+        /* Generate and return the output string */
+    outstr = sarrayToString(sa, 1);
+    sarrayDestroy(&sa);
+    return outstr;
+}
+
+
+/*-------------------------------------------------------------*
+ *                  For ccitt g4 compressed images             *
+ *-------------------------------------------------------------*/
+/*!
+ *  convertG4ToPSEmbed()
+ *
+ *      Input:  filein (input tiff file)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function takes a g4 compressed tif file as input and
+ *          generates a g4 compressed, ascii85 encoded PS file, with
+ *          a bounding box.
+ *      (2) The bounding box is required when a program such as TeX
+ *          (through epsf) places and rescales the image.
+ *      (3) The bounding box is sized for fitting the image to an
+ *          8.5 x 11.0 inch page.
+ *      (4) We paint this through a mask, over whatever is below.
+ */
+l_int32
+convertG4ToPSEmbed(const char  *filein,
+                   const char  *fileout)
+{
+char         *outstr;
+l_int32       w, h, nbytes;
+l_float32     xpt, ypt, wpt, hpt;
+L_COMP_DATA  *cid;
+
+    PROCNAME("convertG4ToPSEmbed");
+
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+    if ((cid = l_generateG4Data(filein, 1)) == NULL)
+        return ERROR_INT("g4 data not made", procName, 1);
+    w = cid->w;
+    h = cid->h;
+
+        /* Scale for 20 pt boundary and otherwise full filling
+         * in one direction on 8.5 x 11 inch device */
+    xpt = 20.0;
+    ypt = 20.0;
+    if (w * 11.0 > h * 8.5) {
+        wpt = 572.0;   /* 612 - 2 * 20 */
+        hpt = wpt * (l_float32)h / (l_float32)w;
+    } else {
+        hpt = 752.0;   /* 792 - 2 * 20 */
+        wpt = hpt * (l_float32)w / (l_float32)h;
+    }
+
+        /* Generate the PS, painting through the image mask.
+         * The bounding box information should be inserted (default). */
+    outstr = generateG4PS(filein, cid, xpt, ypt, wpt, hpt, 1, 1, 1);
+    if (!outstr)
+        return ERROR_INT("outstr not made", procName, 1);
+    nbytes = strlen(outstr);
+
+    if (l_binaryWrite(fileout, "w", outstr, nbytes))
+        return ERROR_INT("ps string not written to file", procName, 1);
+    LEPT_FREE(outstr);
+    l_CIDataDestroy(&cid);
+    return 0;
+}
+
+
+/*!
+ *  convertG4ToPS()
+ *
+ *      Input:  filein (input tiff g4 file)
+ *              fileout (output ps file)
+ *              operation ("w" for write; "a" for append)
+ *              x, y (location of LL corner of image, in pixels, relative
+ *                    to the PostScript origin (0,0) at the LL corner
+ *                    of the page)
+ *              res (resolution of the input image, in ppi; typ. values
+ *                   are 300 and 600; use 0 for automatic determination
+ *                   based on image size)
+ *              scale (scaling by printer; use 0.0 or 1.0 for no scaling)
+ *              pageno (page number; must start with 1; you can use 0
+ *                      if there is only one page.)
+ *              maskflag (boolean: use TRUE if just painting through fg;
+ *                        FALSE if painting both fg and bg.
+ *              endpage (boolean: use TRUE if this is the last image to be
+ *                       added to the page; FALSE otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See the usage comments in convertJpegToPS(), some of
+ *          which are repeated here.
+ *      (2) This is a wrapper for tiff g4.  The PostScript that
+ *          is generated is expanded by about 5/4 (due to the
+ *          ascii85 encoding.  If you convert to pdf (ps2pdf), the
+ *          ascii85 decoder is automatically invoked, so that the
+ *          pdf wrapped g4 file is essentially the same size as
+ *          the original g4 file.  It's useful to have the PS
+ *          file ascii85 encoded, because many printers will not
+ *          print binary PS files.
+ *      (3) For the first image written to a file, use "w", which
+ *          opens for write and clears the file.  For all subsequent
+ *          images written to that file, use "a".
+ *      (4) To render multiple images on the same page, set
+ *          endpage = FALSE for each image until you get to the
+ *          last, for which you set endpage = TRUE.  This causes the
+ *          "showpage" command to be invoked.  Showpage outputs
+ *          the entire page and clears the raster buffer for the
+ *          next page to be added.  Without a "showpage",
+ *          subsequent images from the next page will overlay those
+ *          previously put down.
+ *      (5) For multiple images to the same page, where you are writing
+ *          both jpeg and tiff-g4, you have two options:
+ *           (a) write the g4 first, as either image (maskflag == FALSE)
+ *               or imagemask (maskflag == TRUE), and then write the
+ *               jpeg over it.
+ *           (b) write the jpeg first and as the last item, write
+ *               the g4 as an imagemask (maskflag == TRUE), to paint
+ *               through the foreground only.
+ *          We have this flexibility with the tiff-g4 because it is 1 bpp.
+ *      (6) For multiple pages, increment the page number, starting
+ *          with page 1.  This allows PostScript (and PDF) to build
+ *          a page directory, which viewers use for navigation.
+ */
+l_int32
+convertG4ToPS(const char  *filein,
+              const char  *fileout,
+              const char  *operation,
+              l_int32      x,
+              l_int32      y,
+              l_int32      res,
+              l_float32    scale,
+              l_int32      pageno,
+              l_int32      maskflag,
+              l_int32      endpage)
+{
+char    *outstr;
+l_int32  nbytes;
+
+    PROCNAME("convertG4ToPS");
+
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (strcmp(operation, "w") && strcmp(operation, "a"))
+        return ERROR_INT("operation must be \"w\" or \"a\"", procName, 1);
+
+    if (convertG4ToPSString(filein, &outstr, &nbytes, x, y, res, scale,
+                            pageno, maskflag, endpage))
+        return ERROR_INT("ps string not made", procName, 1);
+
+    if (l_binaryWrite(fileout, operation, outstr, nbytes))
+        return ERROR_INT("ps string not written to file", procName, 1);
+
+    LEPT_FREE(outstr);
+    return 0;
+}
+
+
+/*!
+ *  convertG4ToPSString()
+ *
+ *      Input:  filein (input tiff g4 file)
+ *              &poutstr (<return> PS string)
+ *              &nbytes (<return> number of bytes in PS string)
+ *              x, y (location of LL corner of image, in pixels, relative
+ *                    to the PostScript origin (0,0) at the LL corner
+ *                    of the page)
+ *              res (resolution of the input image, in ppi; typ. values
+ *                   are 300 and 600; use 0 for automatic determination
+ *                   based on image size)
+ *              scale (scaling by printer; use 0.0 or 1.0 for no scaling)
+ *              pageno (page number; must start with 1; you can use 0
+ *                      if there is only one page.)
+ *              maskflag (boolean: use TRUE if just painting through fg;
+ *                        FALSE if painting both fg and bg.
+ *              endpage (boolean: use TRUE if this is the last image to be
+ *                       added to the page; FALSE otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Generates PS string in G4 compressed tiff format from G4 tiff file.
+ *      (2) For usage, see convertG4ToPS().
+ */
+l_int32
+convertG4ToPSString(const char  *filein,
+                    char       **poutstr,
+                    l_int32     *pnbytes,
+                    l_int32      x,
+                    l_int32      y,
+                    l_int32      res,
+                    l_float32    scale,
+                    l_int32      pageno,
+                    l_int32      maskflag,
+                    l_int32      endpage)
+{
+char         *outstr;
+l_float32     xpt, ypt, wpt, hpt;
+L_COMP_DATA  *cid;
+
+    PROCNAME("convertG4ToPSString");
+
+    if (!poutstr)
+        return ERROR_INT("&outstr not defined", procName, 1);
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *poutstr = NULL;
+    *pnbytes = 0;
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+
+    if ((cid = l_generateG4Data(filein, 1)) == NULL)
+        return ERROR_INT("g4 data not made", procName, 1);
+
+        /* Get scaled location in pts.  Guess the input scan resolution
+         * based on the input parameter @res, the resolution data in
+         * the pix, and the size of the image. */
+    if (scale == 0.0)
+        scale = 1.0;
+    if (res <= 0) {
+        if (cid->res > 0) {
+            res = cid->res;
+        } else {
+            if (cid->h <= 3509)  /* A4 height at 300 ppi */
+                res = 300;
+            else
+                res = 600;
+        }
+    }
+    xpt = scale * x * 72. / res;
+    ypt = scale * y * 72. / res;
+    wpt = scale * cid->w * 72. / res;
+    hpt = scale * cid->h * 72. / res;
+
+    if (pageno == 0)
+        pageno = 1;
+
+#if  DEBUG_G4
+    fprintf(stderr, "w = %d, h = %d, minisblack = %d\n",
+            cid->w, cid->h, cid->minisblack);
+    fprintf(stderr, "comp bytes = %ld, nbytes85 = %ld\n",
+            (unsigned long)cid->nbytescomp, (unsigned long)cid->nbytes85);
+    fprintf(stderr, "xpt = %7.2f, ypt = %7.2f, wpt = %7.2f, hpt = %7.2f\n",
+             xpt, ypt, wpt, hpt);
+#endif   /* DEBUG_G4 */
+
+        /* Generate the PS */
+    outstr = generateG4PS(filein, cid, xpt, ypt, wpt, hpt,
+                          maskflag, pageno, endpage);
+    if (!outstr)
+        return ERROR_INT("outstr not made", procName, 1);
+    *poutstr = outstr;
+    *pnbytes = strlen(outstr);
+    l_CIDataDestroy(&cid);
+    return 0;
+}
+
+
+/*!
+ *  generateG4PS()
+ *
+ *      Input:  filein (<optional> input tiff g4 file; can be null)
+ *              cid (g4 compressed image data)
+ *              xpt, ypt (location of LL corner of image, in pts, relative
+ *                        to the PostScript origin (0,0) at the LL corner
+ *                        of the page)
+ *              wpt, hpt (rendered image size in pts)
+ *              maskflag (boolean: use TRUE if just painting through fg;
+ *                        FALSE if painting both fg and bg.
+ *              pageno (page number; must start with 1; you can use 0
+ *                      if there is only one page.)
+ *              endpage (boolean: use TRUE if this is the last image to be
+ *                       added to the page; FALSE otherwise)
+ *      Return: PS string, or null on error
+ *
+ *  Notes:
+ *      (1) Low-level function.
+ */
+char *
+generateG4PS(const char   *filein,
+             L_COMP_DATA  *cid,
+             l_float32     xpt,
+             l_float32     ypt,
+             l_float32     wpt,
+             l_float32     hpt,
+             l_int32       maskflag,
+             l_int32       pageno,
+             l_int32       endpage)
+{
+l_int32  w, h;
+char    *outstr;
+char     bigbuf[L_BUF_SIZE];
+SARRAY  *sa;
+
+    PROCNAME("generateG4PS");
+
+    if (!cid)
+        return (char *)ERROR_PTR("g4 data not defined", procName, NULL);
+    w = cid->w;
+    h = cid->h;
+
+    if ((sa = sarrayCreate(50)) == NULL)
+        return (char *)ERROR_PTR("sa not made", procName, NULL);
+
+    sarrayAddString(sa, (char *)"%!PS-Adobe-3.0", L_COPY);
+    sarrayAddString(sa, (char *)"%%Creator: leptonica", L_COPY);
+    if (filein) {
+        sprintf(bigbuf, "%%%%Title: %s", filein);
+        sarrayAddString(sa, bigbuf, L_COPY);
+    }
+    sarrayAddString(sa, (char *)"%%DocumentData: Clean7Bit", L_COPY);
+
+    if (var_PS_WRITE_BOUNDING_BOX == 1) {
+        sprintf(bigbuf,
+            "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f",
+                    xpt, ypt, xpt + wpt, ypt + hpt);
+        sarrayAddString(sa, bigbuf, L_COPY);
+    }
+
+    sarrayAddString(sa, (char *)"%%LanguageLevel: 2", L_COPY);
+    sarrayAddString(sa, (char *)"%%EndComments", L_COPY);
+    sprintf(bigbuf, "%%%%Page: %d %d", pageno, pageno);
+    sarrayAddString(sa, bigbuf, L_COPY);
+
+    sarrayAddString(sa, (char *)"save", L_COPY);
+    sarrayAddString(sa, (char *)"100 dict begin", L_COPY);
+
+    sprintf(bigbuf,
+        "%7.2f %7.2f translate         %%set image origin in pts", xpt, ypt);
+    sarrayAddString(sa, bigbuf, L_COPY);
+
+    sprintf(bigbuf,
+        "%7.2f %7.2f scale             %%set image size in pts", wpt, hpt);
+    sarrayAddString(sa, bigbuf, L_COPY);
+
+    sarrayAddString(sa, (char *)"/DeviceGray setcolorspace", L_COPY);
+
+    sarrayAddString(sa, (char *)"{", L_COPY);
+    sarrayAddString(sa,
+          (char *)"  /RawData currentfile /ASCII85Decode filter def", L_COPY);
+    sarrayAddString(sa, (char *)"  << ", L_COPY);
+    sarrayAddString(sa, (char *)"    /ImageType 1", L_COPY);
+    sprintf(bigbuf, "    /Width %d", w);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sprintf(bigbuf, "    /Height %d", h);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sprintf(bigbuf, "    /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sarrayAddString(sa, (char *)"    /BitsPerComponent 1", L_COPY);
+    sarrayAddString(sa, (char *)"    /Interpolate true", L_COPY);
+    if (cid->minisblack)
+        sarrayAddString(sa, (char *)"    /Decode [1 0]", L_COPY);
+    else  /* miniswhite; typical for 1 bpp */
+        sarrayAddString(sa, (char *)"    /Decode [0 1]", L_COPY);
+    sarrayAddString(sa, (char *)"    /DataSource RawData", L_COPY);
+    sarrayAddString(sa, (char *)"        <<", L_COPY);
+    sarrayAddString(sa, (char *)"          /K -1", L_COPY);
+    sprintf(bigbuf, "          /Columns %d", w);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sprintf(bigbuf, "          /Rows %d", h);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sarrayAddString(sa, (char *)"        >> /CCITTFaxDecode filter", L_COPY);
+    if (maskflag == TRUE)  /* just paint through the fg */
+        sarrayAddString(sa, (char *)"  >> imagemask", L_COPY);
+    else  /* Paint full image */
+        sarrayAddString(sa, (char *)"  >> image", L_COPY);
+    sarrayAddString(sa, (char *)"  RawData flushfile", L_COPY);
+    if (endpage == TRUE)
+        sarrayAddString(sa, (char *)"  showpage", L_COPY);
+    sarrayAddString(sa, (char *)"}", L_COPY);
+
+    sarrayAddString(sa, (char *)"%%BeginData:", L_COPY);
+    sarrayAddString(sa, (char *)"exec", L_COPY);
+
+        /* Insert the ascii85 ccittg4 data; this is now owned by sa */
+    sarrayAddString(sa, cid->data85, L_INSERT);
+
+        /* Concat the trailing data */
+    sarrayAddString(sa, (char *)"%%EndData", L_COPY);
+    sarrayAddString(sa, (char *)"end", L_COPY);
+    sarrayAddString(sa, (char *)"restore", L_COPY);
+
+    outstr = sarrayToString(sa, 1);
+    sarrayDestroy(&sa);
+    cid->data85 = NULL;  /* it has been transferred and destroyed */
+    return outstr;
+}
+
+
+/*-------------------------------------------------------------*
+ *                     For tiff multipage files                *
+ *-------------------------------------------------------------*/
+/*!
+ *  convertTiffMultipageToPS()
+ *
+ *      Input:  filein (input tiff multipage file)
+ *              fileout (output ps file)
+ *              tempfile (<optional> for temporary g4 tiffs;
+ *                        use NULL for default)
+ *              factor (for filling 8.5 x 11 inch page;
+ *                      use 0.0 for DEFAULT_FILL_FRACTION)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This converts a multipage tiff file of binary page images
+ *          into a ccitt g4 compressed PS file.
+ *      (2) If the images are generated from a standard resolution fax,
+ *          the vertical resolution is doubled to give a normal-looking
+ *          aspect ratio.
+ */
+l_int32
+convertTiffMultipageToPS(const char  *filein,
+                         const char  *fileout,
+                         const char  *tempfile,
+                         l_float32    fillfract)
+{
+const char   tempdefault[] = "/tmp/junk_temp_g4.tif";
+const char  *tempname;
+l_int32      i, npages, w, h, istiff;
+l_float32    scale;
+PIX         *pix, *pixs;
+FILE        *fp;
+
+    PROCNAME("convertTiffMultipageToPS");
+
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+    if ((fp = fopenReadStream(filein)) == NULL)
+        return ERROR_INT("file not found", procName, 1);
+    istiff = fileFormatIsTiff(fp);
+    if (!istiff) {
+        fclose(fp);
+        return ERROR_INT("file not tiff format", procName, 1);
+    }
+    tiffGetCount(fp, &npages);
+    fclose(fp);
+
+    if (tempfile)
+        tempname = tempfile;
+    else
+        tempname = tempdefault;
+
+    if (fillfract == 0.0)
+        fillfract = DEFAULT_FILL_FRACTION;
+
+    for (i = 0; i < npages; i++) {
+        if ((pix = pixReadTiff(filein, i)) == NULL)
+             return ERROR_INT("pix not made", procName, 1);
+
+        w = pixGetWidth(pix);
+        h = pixGetHeight(pix);
+        if (w == 1728 && h < w)   /* it's a std res fax */
+            pixs = pixScale(pix, 1.0, 2.0);
+        else
+            pixs = pixClone(pix);
+
+        pixWrite(tempname, pixs, IFF_TIFF_G4);
+        scale = L_MIN(fillfract * 2550 / w, fillfract * 3300 / h);
+        if (i == 0)
+            convertG4ToPS(tempname, fileout, "w", 0, 0, 300, scale,
+                          i + 1, FALSE, TRUE);
+        else
+            convertG4ToPS(tempname, fileout, "a", 0, 0, 300, scale,
+                          i + 1, FALSE, TRUE);
+        pixDestroy(&pix);
+        pixDestroy(&pixs);
+    }
+
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *            For flate (gzip) compressed images (e.g., png)           *
+ *---------------------------------------------------------------------*/
+/*!
+ *  convertFlateToPSEmbed()
+ *
+ *      Input:  filein (input file -- any format)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function takes any image file as input and generates a
+ *          flate-compressed, ascii85 encoded PS file, with a bounding box.
+ *      (2) The bounding box is required when a program such as TeX
+ *          (through epsf) places and rescales the image.
+ *      (3) The bounding box is sized for fitting the image to an
+ *          8.5 x 11.0 inch page.
+ */
+l_int32
+convertFlateToPSEmbed(const char  *filein,
+                      const char  *fileout)
+{
+char         *outstr;
+l_int32       w, h, nbytes;
+l_float32     xpt, ypt, wpt, hpt;
+L_COMP_DATA  *cid;
+
+    PROCNAME("convertFlateToPSEmbed");
+
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+    if ((cid = l_generateFlateData(filein, 1)) == NULL)
+        return ERROR_INT("flate data not made", procName, 1);
+    w = cid->w;
+    h = cid->h;
+
+        /* Scale for 20 pt boundary and otherwise full filling
+         * in one direction on 8.5 x 11 inch device */
+    xpt = 20.0;
+    ypt = 20.0;
+    if (w * 11.0 > h * 8.5) {
+        wpt = 572.0;   /* 612 - 2 * 20 */
+        hpt = wpt * (l_float32)h / (l_float32)w;
+    } else {
+        hpt = 752.0;   /* 792 - 2 * 20 */
+        wpt = hpt * (l_float32)w / (l_float32)h;
+    }
+
+        /* Generate the PS.
+         * The bounding box information should be inserted (default). */
+    outstr = generateFlatePS(filein, cid, xpt, ypt, wpt, hpt, 1, 1);
+    if (!outstr)
+        return ERROR_INT("outstr not made", procName, 1);
+    nbytes = strlen(outstr);
+
+    if (l_binaryWrite(fileout, "w", outstr, nbytes))
+        return ERROR_INT("ps string not written to file", procName, 1);
+    LEPT_FREE(outstr);
+    l_CIDataDestroy(&cid);
+    return 0;
+}
+
+
+/*!
+ *  convertFlateToPS()
+ *
+ *      Input:  filein (input file -- any format)
+ *              fileout (output ps file)
+ *              operation ("w" for write; "a" for append)
+ *              x, y (location of LL corner of image, in pixels, relative
+ *                    to the PostScript origin (0,0) at the LL corner
+ *                    of the page)
+ *              res (resolution of the input image, in ppi; use 0 for default)
+ *              scale (scaling by printer; use 0.0 or 1.0 for no scaling)
+ *              pageno (page number; must start with 1; you can use 0
+ *                      if there is only one page.)
+ *              endpage (boolean: use TRUE if this is the last image to be
+ *                       added to the page; FALSE otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This outputs level 3 PS as flate compressed (overlaid
+ *          with ascii85 encoding).
+ *      (2) An output file can contain multiple pages, each with
+ *          multiple images.  The arguments to convertFlateToPS()
+ *          allow you to control placement of png images on multiple
+ *          pages within a PostScript file.
+ *      (3) For the first image written to a file, use "w", which
+ *          opens for write and clears the file.  For all subsequent
+ *          images written to that file, use "a".
+ *      (4) The (x, y) parameters give the LL corner of the image
+ *          relative to the LL corner of the page.  They are in
+ *          units of pixels if scale = 1.0.  If you use (e.g.)
+ *          scale = 2.0, the image is placed at (2x, 2y) on the page,
+ *          and the image dimensions are also doubled.
+ *      (5) Display vs printed resolution:
+ *           * If your display is 75 ppi and your image was created
+ *             at a resolution of 300 ppi, you can get the image
+ *             to print at the same size as it appears on your display
+ *             by either setting scale = 4.0 or by setting  res = 75.
+ *             Both tell the printer to make a 4x enlarged image.
+ *           * If your image is generated at 150 ppi and you use scale = 1,
+ *             it will be rendered such that 150 pixels correspond
+ *             to 72 pts (1 inch on the printer).  This function does
+ *             the conversion from pixels (with or without scaling) to
+ *             pts, which are the units that the printer uses.
+ *           * The printer will choose its own resolution to use
+ *             in rendering the image, which will not affect the size
+ *             of the rendered image.  That is because the output
+ *             PostScript file describes the geometry in terms of pts,
+ *             which are defined to be 1/72 inch.  The printer will
+ *             only see the size of the image in pts, through the
+ *             scale and translate parameters and the affine
+ *             transform (the ImageMatrix) of the image.
+ *      (6) To render multiple images on the same page, set
+ *          endpage = FALSE for each image until you get to the
+ *          last, for which you set endpage = TRUE.  This causes the
+ *          "showpage" command to be invoked.  Showpage outputs
+ *          the entire page and clears the raster buffer for the
+ *          next page to be added.  Without a "showpage",
+ *          subsequent images from the next page will overlay those
+ *          previously put down.
+ *      (7) For multiple pages, increment the page number, starting
+ *          with page 1.  This allows PostScript (and PDF) to build
+ *          a page directory, which viewers use for navigation.
+ */
+l_int32
+convertFlateToPS(const char  *filein,
+                 const char  *fileout,
+                 const char  *operation,
+                 l_int32      x,
+                 l_int32      y,
+                 l_int32      res,
+                 l_float32    scale,
+                 l_int32      pageno,
+                 l_int32      endpage)
+{
+char    *outstr;
+l_int32  nbytes;
+
+    PROCNAME("convertFlateToPS");
+
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+    if (strcmp(operation, "w") && strcmp(operation, "a"))
+        return ERROR_INT("operation must be \"w\" or \"a\"", procName, 1);
+
+    if (convertFlateToPSString(filein, &outstr, &nbytes, x, y, res, scale,
+                               pageno, endpage))
+        return ERROR_INT("ps string not made", procName, 1);
+
+    if (l_binaryWrite(fileout, operation, outstr, nbytes))
+        return ERROR_INT("ps string not written to file", procName, 1);
+
+    LEPT_FREE(outstr);
+    return 0;
+}
+
+
+/*!
+ *  convertFlateToPSString()
+ *
+ *      Generates level 3 PS string in flate compressed format.
+ *
+ *      Input:  filein (input image file)
+ *              &poutstr (<return> PS string)
+ *              &nbytes (<return> number of bytes in PS string)
+ *              x, y (location of LL corner of image, in pixels, relative
+ *                    to the PostScript origin (0,0) at the LL corner
+ *                    of the page)
+ *              res (resolution of the input image, in ppi; use 0 for default)
+ *              scale (scaling by printer; use 0.0 or 1.0 for no scaling)
+ *              pageno (page number; must start with 1; you can use 0
+ *                      if there is only one page.)
+ *              endpage (boolean: use TRUE if this is the last image to be
+ *                       added to the page; FALSE otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The returned PS character array is a null-terminated
+ *          ascii string.  All the raster data is ascii85 encoded, so
+ *          there are no null bytes embedded in it.
+ *      (2) The raster encoding is made with gzip, the same as that
+ *          in a png file that is compressed without prediction.
+ *          The raster data itself is 25% larger than that in the
+ *          binary form, due to the ascii85 encoding.
+ *
+ *  Usage:  See convertFlateToPS()
+ */
+l_int32
+convertFlateToPSString(const char  *filein,
+                       char       **poutstr,
+                       l_int32     *pnbytes,
+                       l_int32      x,
+                       l_int32      y,
+                       l_int32      res,
+                       l_float32    scale,
+                       l_int32      pageno,
+                       l_int32      endpage)
+{
+char         *outstr;
+l_float32     xpt, ypt, wpt, hpt;
+L_COMP_DATA  *cid;
+
+    PROCNAME("convertFlateToPSString");
+
+    if (!poutstr)
+        return ERROR_INT("&outstr not defined", procName, 1);
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    *pnbytes = 0;
+    *poutstr = NULL;
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+
+    if ((cid = l_generateFlateData(filein, 1)) == NULL)
+        return ERROR_INT("flate data not made", procName, 1);
+
+        /* Get scaled location in pts.  Guess the input scan resolution
+         * based on the input parameter @res, the resolution data in
+         * the pix, and the size of the image. */
+    if (scale == 0.0)
+        scale = 1.0;
+    if (res <= 0) {
+        if (cid->res > 0)
+            res = cid->res;
+        else
+            res = DEFAULT_INPUT_RES;
+    }
+    xpt = scale * x * 72. / res;
+    ypt = scale * y * 72. / res;
+    wpt = scale * cid->w * 72. / res;
+    hpt = scale * cid->h * 72. / res;
+
+    if (pageno == 0)
+        pageno = 1;
+
+#if  DEBUG_FLATE
+    fprintf(stderr, "w = %d, h = %d, bps = %d, spp = %d\n",
+            cid->w, cid->h, cid->bps, cid->spp);
+    fprintf(stderr, "uncomp bytes = %ld, comp bytes = %ld, nbytes85 = %ld\n",
+            (unsigned long)cid->nbytes, (unsigned long)cid->nbytescomp,
+            (unsigned long)cid->nbytes85);
+    fprintf(stderr, "xpt = %7.2f, ypt = %7.2f, wpt = %7.2f, hpt = %7.2f\n",
+             xpt, ypt, wpt, hpt);
+#endif   /* DEBUG_FLATE */
+
+        /* Generate the PS */
+    outstr = generateFlatePS(filein, cid, xpt, ypt, wpt, hpt, pageno, endpage);
+    if (!outstr)
+        return ERROR_INT("outstr not made", procName, 1);
+    *poutstr = outstr;
+    *pnbytes = strlen(outstr);
+    l_CIDataDestroy(&cid);
+    return 0;
+}
+
+
+/*!
+ *  generateFlatePS()
+ *
+ *      Input:  filein (<optional> input filename; can be null)
+ *              cid (flate compressed image data)
+ *              xpt, ypt (location of LL corner of image, in pts, relative
+ *                        to the PostScript origin (0,0) at the LL corner
+ *                        of the page)
+ *              wpt, hpt (rendered image size in pts)
+ *              pageno (page number; must start with 1; you can use 0
+ *                      if there is only one page)
+ *              endpage (boolean: use TRUE if this is the last image to be
+ *                       added to the page; FALSE otherwise)
+ *      Return: PS string, or null on error
+ */
+char *
+generateFlatePS(const char   *filein,
+                L_COMP_DATA  *cid,
+                l_float32     xpt,
+                l_float32     ypt,
+                l_float32     wpt,
+                l_float32     hpt,
+                l_int32       pageno,
+                l_int32       endpage)
+{
+l_int32  w, h, bps, spp;
+char    *outstr;
+char     bigbuf[L_BUF_SIZE];
+SARRAY  *sa;
+
+    PROCNAME("generateFlatePS");
+
+    if (!cid)
+        return (char *)ERROR_PTR("flate data not defined", procName, NULL);
+    w = cid->w;
+    h = cid->h;
+    bps = cid->bps;
+    spp = cid->spp;
+
+    if ((sa = sarrayCreate(50)) == NULL)
+        return (char *)ERROR_PTR("sa not made", procName, NULL);
+
+    sarrayAddString(sa, (char *)"%!PS-Adobe-3.0 EPSF-3.0", L_COPY);
+    sarrayAddString(sa, (char *)"%%Creator: leptonica", L_COPY);
+    if (filein) {
+        sprintf(bigbuf, "%%%%Title: %s", filein);
+        sarrayAddString(sa, bigbuf, L_COPY);
+    }
+    sarrayAddString(sa, (char *)"%%DocumentData: Clean7Bit", L_COPY);
+
+    if (var_PS_WRITE_BOUNDING_BOX == 1) {
+        sprintf(bigbuf,
+            "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f",
+                       xpt, ypt, xpt + wpt, ypt + hpt);
+        sarrayAddString(sa, bigbuf, L_COPY);
+    }
+
+    sarrayAddString(sa, (char *)"%%LanguageLevel: 3", L_COPY);
+    sarrayAddString(sa, (char *)"%%EndComments", L_COPY);
+    sprintf(bigbuf, "%%%%Page: %d %d", pageno, pageno);
+    sarrayAddString(sa, bigbuf, L_COPY);
+
+    sarrayAddString(sa, (char *)"save", L_COPY);
+    sprintf(bigbuf,
+        "%7.2f %7.2f translate         %%set image origin in pts", xpt, ypt);
+    sarrayAddString(sa, bigbuf, L_COPY);
+
+    sprintf(bigbuf,
+        "%7.2f %7.2f scale             %%set image size in pts", wpt, hpt);
+    sarrayAddString(sa, bigbuf, L_COPY);
+
+        /* If there is a colormap, add the data; it is now owned by sa */
+    if (cid->cmapdata85) {
+        sprintf(bigbuf,
+             "[ /Indexed /DeviceRGB %d          %%set colormap type/size",
+             cid->ncolors - 1);
+        sarrayAddString(sa, bigbuf, L_COPY);
+        sarrayAddString(sa, (char *)"  <~", L_COPY);
+        sarrayAddString(sa, cid->cmapdata85, L_INSERT);
+        sarrayAddString(sa, (char *)"  ] setcolorspace", L_COPY);
+    } else if (spp == 1) {
+        sarrayAddString(sa, (char *)"/DeviceGray setcolorspace", L_COPY);
+    } else {  /* spp == 3 */
+        sarrayAddString(sa, (char *)"/DeviceRGB setcolorspace", L_COPY);
+    }
+
+    sarrayAddString(sa,
+              (char *)"/RawData currentfile /ASCII85Decode filter def", L_COPY);
+    sarrayAddString(sa,
+              (char *)"/Data RawData << >> /FlateDecode filter def", L_COPY);
+
+    sarrayAddString(sa, (char *)"{ << /ImageType 1", L_COPY);
+    sprintf(bigbuf, "     /Width %d", w);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sprintf(bigbuf, "     /Height %d", h);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sprintf(bigbuf, "     /BitsPerComponent %d", bps);
+    sarrayAddString(sa, bigbuf, L_COPY);
+    sprintf(bigbuf, "     /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h);
+    sarrayAddString(sa, bigbuf, L_COPY);
+
+    if (cid->cmapdata85) {
+        sarrayAddString(sa, (char *)"     /Decode [0 255]", L_COPY);
+    } else if (spp == 1) {
+        if (bps == 1)  /* miniswhite photometry */
+            sarrayAddString(sa, (char *)"     /Decode [1 0]", L_COPY);
+        else  /* bps > 1 */
+            sarrayAddString(sa, (char *)"     /Decode [0 1]", L_COPY);
+    } else {  /* spp == 3 */
+        sarrayAddString(sa, (char *)"     /Decode [0 1 0 1 0 1]", L_COPY);
+    }
+
+    sarrayAddString(sa, (char *)"     /DataSource Data", L_COPY);
+    sarrayAddString(sa, (char *)"  >> image", L_COPY);
+    sarrayAddString(sa, (char *)"  Data closefile", L_COPY);
+    sarrayAddString(sa, (char *)"  RawData flushfile", L_COPY);
+    if (endpage == TRUE)
+        sarrayAddString(sa, (char *)"  showpage", L_COPY);
+    sarrayAddString(sa, (char *)"  restore", L_COPY);
+    sarrayAddString(sa, (char *)"} exec", L_COPY);
+
+        /* Insert the ascii85 gzipped data; this is now owned by sa */
+    sarrayAddString(sa, cid->data85, L_INSERT);
+
+        /* Generate and return the output string */
+    outstr = sarrayToString(sa, 1);
+    sarrayDestroy(&sa);
+    cid->cmapdata85 = NULL;  /* it has been transferred to sa and destroyed */
+    cid->data85 = NULL;  /* it has been transferred to sa and destroyed */
+    return outstr;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                          Write to memory                            *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixWriteMemPS()
+ *
+ *      Input:  &data (<return> data of tiff compressed image)
+ *              &size (<return> size of returned data)
+ *              pix
+ *              box  (<optional>)
+ *              res  (can use 0 for default of 300 ppi)
+ *              scale (to prevent scaling, use either 1.0 or 0.0)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See pixWriteStringPS() for usage.
+ *      (2) This is just a wrapper for pixWriteStringPS(), which
+ *          writes uncompressed image data to memory.
+ */
+l_int32
+pixWriteMemPS(l_uint8  **pdata,
+              size_t    *psize,
+              PIX       *pix,
+              BOX       *box,
+              l_int32    res,
+              l_float32  scale)
+{
+    PROCNAME("pixWriteMemPS");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1 );
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1 );
+    if (!pix)
+        return ERROR_INT("&pix not defined", procName, 1 );
+
+    *pdata = (l_uint8 *)pixWriteStringPS(pix, box, res, scale);
+    *psize = strlen((char *)(*pdata));
+    return 0;
+}
+
+
+/*-------------------------------------------------------------*
+ *                    Converting resolution                    *
+ *-------------------------------------------------------------*/
+/*!
+ *  getResLetterPage()
+ *
+ *      Input:  w (image width, pixels)
+ *              h (image height, pixels)
+ *              fillfract (fraction in linear dimension of full page, not
+ *                         to be exceeded; use 0 for default)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+getResLetterPage(l_int32    w,
+                 l_int32    h,
+                 l_float32  fillfract)
+{
+l_int32  resw, resh, res;
+
+    if (fillfract == 0.0)
+        fillfract = DEFAULT_FILL_FRACTION;
+    resw = (l_int32)((w * 72.) / (LETTER_WIDTH * fillfract));
+    resh = (l_int32)((h * 72.) / (LETTER_HEIGHT * fillfract));
+    res = L_MAX(resw, resh);
+    return res;
+}
+
+
+/*!
+ *  getResA4Page()
+ *
+ *      Input:  w (image width, pixels)
+ *              h (image height, pixels)
+ *              fillfract (fraction in linear dimension of full page, not
+ *                        to be exceeded; use 0 for default)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+getResA4Page(l_int32    w,
+             l_int32    h,
+             l_float32  fillfract)
+{
+l_int32  resw, resh, res;
+
+    if (fillfract == 0.0)
+        fillfract = DEFAULT_FILL_FRACTION;
+    resw = (l_int32)((w * 72.) / (A4_WIDTH * fillfract));
+    resh = (l_int32)((h * 72.) / (A4_HEIGHT * fillfract));
+    res = L_MAX(resw, resh);
+    return res;
+}
+
+
+/*-------------------------------------------------------------*
+ *           Setting flag for writing bounding box hint        *
+ *-------------------------------------------------------------*/
+void
+l_psWriteBoundingBox(l_int32  flag)
+{
+    var_PS_WRITE_BOUNDING_BOX = flag;
+}
+
+
+/* --------------------------------------------*/
+#endif  /* USE_PSIO */
+/* --------------------------------------------*/
diff --git a/src/psio2stub.c b/src/psio2stub.c
new file mode 100644 (file)
index 0000000..a993f16
--- /dev/null
@@ -0,0 +1,236 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  psio2stub.c
+ *
+ *     Stubs for psio2.c functions
+ */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !USE_PSIO   /* defined in environ.h */
+/* --------------------------------------------*/
+
+l_int32 pixWritePSEmbed(const char *filein, const char *fileout)
+{
+    return ERROR_INT("function not present", "pixWritePSEmbed", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteStreamPS(FILE *fp, PIX *pix, BOX *box, l_int32 res,
+                         l_float32 scale)
+{
+    return ERROR_INT("function not present", "pixWriteStreamPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+char * pixWriteStringPS(PIX *pixs, BOX *box, l_int32 res, l_float32 scale)
+{
+    return (char *)ERROR_PTR("function not present", "pixWriteStringPS", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+char * generateUncompressedPS(char *hexdata, l_int32 w, l_int32 h, l_int32 d,
+                              l_int32 psbpl, l_int32 bps, l_float32 xpt,
+                              l_float32 ypt, l_float32 wpt, l_float32 hpt,
+                              l_int32 boxflag)
+{
+    return (char *)ERROR_PTR("function not present",
+                             "generateUncompressedPS", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+void getScaledParametersPS(BOX *box, l_int32 wpix, l_int32 hpix, l_int32 res,
+                           l_float32 scale, l_float32 *pxpt, l_float32 *pypt,
+                           l_float32 *pwpt, l_float32 *phpt)
+{
+    L_ERROR("function not present\n", "getScaledParametersPS");
+    return;
+}
+
+/* ----------------------------------------------------------------------*/
+
+void convertByteToHexAscii(l_uint8 byteval, char *pnib1, char *pnib2)
+{
+    L_ERROR("function not present\n", "convertByteToHexAscii");
+    return;
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertJpegToPSEmbed(const char *filein, const char *fileout)
+{
+    return ERROR_INT("function not present", "convertJpegToPSEmbed", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertJpegToPS(const char *filein, const char *fileout,
+                        const char *operation, l_int32 x, l_int32 y,
+                        l_int32 res, l_float32 scale, l_int32 pageno,
+                        l_int32 endpage)
+{
+    return ERROR_INT("function not present", "convertJpegToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertJpegToPSString(const char *filein, char **poutstr,
+                              l_int32 *pnbytes, l_int32 x, l_int32 y,
+                              l_int32 res, l_float32 scale, l_int32 pageno,
+                              l_int32 endpage)
+{
+    return ERROR_INT("function not present", "convertJpegToPSString", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+char * generateJpegPS(const char *filein, L_COMP_DATA *cid,
+                      l_float32 xpt, l_float32 ypt, l_float32 wpt,
+                      l_float32 hpt, l_int32 pageno, l_int32 endpage)
+{
+    return (char *)ERROR_PTR("function not present", "generateJpegPS", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertG4ToPSEmbed(const char *filein, const char *fileout)
+{
+    return ERROR_INT("function not present", "convertG4ToPSEmbed", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertG4ToPS(const char *filein, const char *fileout,
+                      const char *operation, l_int32 x, l_int32 y,
+                      l_int32 res, l_float32 scale, l_int32 pageno,
+                      l_int32 maskflag, l_int32 endpage)
+{
+    return ERROR_INT("function not present", "convertG4ToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertG4ToPSString(const char *filein, char **poutstr,
+                            l_int32 *pnbytes, l_int32 x, l_int32 y,
+                            l_int32 res, l_float32 scale, l_int32 pageno,
+                            l_int32 maskflag, l_int32 endpage)
+{
+    return ERROR_INT("function not present", "convertG4ToPSString", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+char * generateG4PS(const char *filein, L_COMP_DATA *cid, l_float32 xpt,
+                    l_float32 ypt, l_float32 wpt, l_float32 hpt,
+                    l_int32 maskflag, l_int32 pageno, l_int32 endpage)
+{
+    return (char *)ERROR_PTR("function not present", "generateG4PS", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertTiffMultipageToPS(const char *filein, const char *fileout,
+                                 const char *tempfile, l_float32 fillfract)
+{
+    return ERROR_INT("function not present", "convertTiffMultipageToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertFlateToPSEmbed(const char *filein, const char *fileout)
+{
+    return ERROR_INT("function not present", "convertFlateToPSEmbed", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertFlateToPS(const char *filein, const char *fileout,
+                         const char *operation, l_int32 x, l_int32 y,
+                         l_int32 res, l_float32 scale, l_int32 pageno,
+                         l_int32 endpage)
+{
+    return ERROR_INT("function not present", "convertFlateToPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 convertFlateToPSString(const char *filein, char **poutstr,
+                               l_int32 *pnbytes, l_int32 x, l_int32 y,
+                               l_int32 res, l_float32 scale,
+                               l_int32 pageno, l_int32 endpage)
+{
+    return ERROR_INT("function not present", "convertFlateToPSString", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+char * generateFlatePS(const char *filein, L_COMP_DATA *cid,
+                       l_float32 xpt, l_float32 ypt, l_float32 wpt,
+                       l_float32 hpt, l_int32 pageno, l_int32 endpage)
+{
+    return (char *)ERROR_PTR("function not present", "generateFlatePS", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteMemPS(l_uint8 **pdata, size_t *psize, PIX *pix, BOX *box,
+                      l_int32 res, l_float32 scale)
+{
+    return ERROR_INT("function not present", "pixWriteMemPS", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 getResLetterPage(l_int32 w, l_int32 h, l_float32 fillfract)
+{
+    return ERROR_INT("function not present", "getResLetterPage", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 getResA4Page(l_int32 w, l_int32 h, l_float32 fillfract)
+{
+    return ERROR_INT("function not present", "getResA4Page", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+void l_psWriteBoundingBox(l_int32 flag)
+{
+    L_ERROR("function not present\n", "l_psWriteBoundingBox");
+    return;
+}
+
+/* --------------------------------------------*/
+#endif  /* !USE_PSIO */
+/* --------------------------------------------*/
diff --git a/src/ptabasic.c b/src/ptabasic.c
new file mode 100644 (file)
index 0000000..a1aae0e
--- /dev/null
@@ -0,0 +1,1293 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *   ptabasic.c
+ *
+ *      Pta creation, destruction, copy, clone, empty
+ *           PTA            *ptaCreate()
+ *           PTA            *ptaCreateFromNuma()
+ *           void            ptaDestroy()
+ *           PTA            *ptaCopy()
+ *           PTA            *ptaCopyRange()
+ *           PTA            *ptaClone()
+ *           l_int32         ptaEmpty()
+ *
+ *      Pta array extension
+ *           l_int32         ptaAddPt()
+ *           static l_int32  ptaExtendArrays()
+ *
+ *      Pta insertion and removal
+ *           l_int32         ptaInsertPt()
+ *           l_int32         ptaRemovePt()
+ *
+ *      Pta accessors
+ *           l_int32         ptaGetRefcount()
+ *           l_int32         ptaChangeRefcount()
+ *           l_int32         ptaGetCount()
+ *           l_int32         ptaGetPt()
+ *           l_int32         ptaGetIPt()
+ *           l_int32         ptaSetPt()
+ *           l_int32         ptaGetArrays()
+ *
+ *      Pta serialized for I/O
+ *           PTA            *ptaRead()
+ *           PTA            *ptaReadStream()
+ *           l_int32         ptaWrite()
+ *           l_int32         ptaWriteStream()
+ *
+ *      Ptaa creation, destruction
+ *           PTAA           *ptaaCreate()
+ *           void            ptaaDestroy()
+ *
+ *      Ptaa array extension
+ *           l_int32         ptaaAddPta()
+ *           static l_int32  ptaaExtendArray()
+ *
+ *      Ptaa accessors
+ *           l_int32         ptaaGetCount()
+ *           l_int32         ptaaGetPta()
+ *           l_int32         ptaaGetPt()
+ *
+ *      Ptaa array modifiers
+ *           l_int32         ptaaInitFull()
+ *           l_int32         ptaaReplacePta()
+ *           l_int32         ptaaAddPt()
+ *           l_int32         ptaaTruncate()
+ *
+ *      Ptaa serialized for I/O
+ *           PTAA           *ptaaRead()
+ *           PTAA           *ptaaReadStream()
+ *           l_int32         ptaaWrite()
+ *           l_int32         ptaaWriteStream()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;   /* n'import quoi */
+
+    /* Static functions */
+static l_int32 ptaExtendArrays(PTA *pta);
+static l_int32 ptaaExtendArray(PTAA *ptaa);
+
+
+/*---------------------------------------------------------------------*
+ *                Pta creation, destruction, copy, clone               *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaCreate()
+ *
+ *      Input:  n  (initial array sizes)
+ *      Return: pta, or null on error.
+ */
+PTA *
+ptaCreate(l_int32  n)
+{
+PTA  *pta;
+
+    PROCNAME("ptaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((pta = (PTA *)LEPT_CALLOC(1, sizeof(PTA))) == NULL)
+        return (PTA *)ERROR_PTR("pta not made", procName, NULL);
+    pta->n = 0;
+    pta->nalloc = n;
+    ptaChangeRefcount(pta, 1);  /* sets to 1 */
+
+    if ((pta->x = (l_float32 *)LEPT_CALLOC(n, sizeof(l_float32))) == NULL)
+        return (PTA *)ERROR_PTR("x array not made", procName, NULL);
+    if ((pta->y = (l_float32 *)LEPT_CALLOC(n, sizeof(l_float32))) == NULL)
+        return (PTA *)ERROR_PTR("y array not made", procName, NULL);
+
+    return pta;
+}
+
+
+/*!
+ *  ptaCreateFromNuma()
+ *
+ *      Input:  nax (<optional> can be null)
+ *              nay
+ *      Return: pta, or null on error.
+ */
+PTA *
+ptaCreateFromNuma(NUMA  *nax,
+                  NUMA  *nay)
+{
+l_int32    i, n;
+l_float32  startx, delx, xval, yval;
+PTA       *pta;
+
+    PROCNAME("ptaCreateFromNuma");
+
+    if (!nay)
+        return (PTA *)ERROR_PTR("nay not defined", procName, NULL);
+    n = numaGetCount(nay);
+    if (nax && numaGetCount(nax) != n)
+        return (PTA *)ERROR_PTR("nax and nay sizes differ", procName, NULL);
+
+    pta = ptaCreate(n);
+    numaGetParameters(nay, &startx, &delx);
+    for (i = 0; i < n; i++) {
+        if (nax)
+            numaGetFValue(nax, i, &xval);
+        else  /* use implicit x values from nay */
+            xval = startx + i * delx;
+        numaGetFValue(nay, i, &yval);
+        ptaAddPt(pta, xval, yval);
+    }
+
+    return pta;
+}
+
+
+/*!
+ *  ptaDestroy()
+ *
+ *      Input:  &pta (<to be nulled>)
+ *      Return: void
+ *
+ *  Note:
+ *      - Decrements the ref count and, if 0, destroys the pta.
+ *      - Always nulls the input ptr.
+ */
+void
+ptaDestroy(PTA  **ppta)
+{
+PTA  *pta;
+
+    PROCNAME("ptaDestroy");
+
+    if (ppta == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+
+    if ((pta = *ppta) == NULL)
+        return;
+
+    ptaChangeRefcount(pta, -1);
+    if (ptaGetRefcount(pta) <= 0) {
+        LEPT_FREE(pta->x);
+        LEPT_FREE(pta->y);
+        LEPT_FREE(pta);
+    }
+
+    *ppta = NULL;
+    return;
+}
+
+
+/*!
+ *  ptaCopy()
+ *
+ *      Input:  pta
+ *      Return: copy of pta, or null on error
+ */
+PTA *
+ptaCopy(PTA  *pta)
+{
+l_int32    i;
+l_float32  x, y;
+PTA       *npta;
+
+    PROCNAME("ptaCopy");
+
+    if (!pta)
+        return (PTA *)ERROR_PTR("pta not defined", procName, NULL);
+
+    if ((npta = ptaCreate(pta->nalloc)) == NULL)
+        return (PTA *)ERROR_PTR("npta not made", procName, NULL);
+
+    for (i = 0; i < pta->n; i++) {
+        ptaGetPt(pta, i, &x, &y);
+        ptaAddPt(npta, x, y);
+    }
+
+    return npta;
+}
+
+
+/*!
+ *  ptaCopyRange()
+ *
+ *      Input:  ptas
+ *              istart  (starting index in ptas)
+ *              iend  (ending index in ptas; use 0 to copy to end)
+ *      Return: 0 if OK, 1 on error
+ */
+PTA *
+ptaCopyRange(PTA     *ptas,
+             l_int32  istart,
+             l_int32  iend)
+{
+l_int32  n, i, x, y;
+PTA     *ptad;
+
+    PROCNAME("ptaCopyRange");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+    n = ptaGetCount(ptas);
+    if (istart < 0)
+        istart = 0;
+    if (istart >= n)
+        return (PTA *)ERROR_PTR("istart out of bounds", procName, NULL);
+    if (iend <= 0 || iend >= n)
+        iend = n - 1;
+    if (istart > iend)
+        return (PTA *)ERROR_PTR("istart > iend; no pts", procName, NULL);
+
+    if ((ptad = ptaCreate(iend - istart + 1)) == NULL)
+        return (PTA *)ERROR_PTR("ptad not made", procName, NULL);
+    for (i = istart; i <= iend; i++) {
+        ptaGetIPt(ptas, i, &x, &y);
+        ptaAddPt(ptad, x, y);
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  ptaClone()
+ *
+ *      Input:  pta
+ *      Return: ptr to same pta, or null on error
+ */
+PTA *
+ptaClone(PTA  *pta)
+{
+    PROCNAME("ptaClone");
+
+    if (!pta)
+        return (PTA *)ERROR_PTR("pta not defined", procName, NULL);
+
+    ptaChangeRefcount(pta, 1);
+    return pta;
+}
+
+
+/*!
+ *  ptaEmpty()
+ *
+ *      Input:  pta
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Note: this only resets the "n" field, for reuse
+ */
+l_int32
+ptaEmpty(PTA  *pta)
+{
+    PROCNAME("ptaEmpty");
+
+    if (!pta)
+        return ERROR_INT("ptad not defined", procName, 1);
+    pta->n = 0;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Pta array extension                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaAddPt()
+ *
+ *      Input:  pta
+ *              x, y
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+ptaAddPt(PTA       *pta,
+         l_float32  x,
+         l_float32  y)
+{
+l_int32  n;
+
+    PROCNAME("ptaAddPt");
+
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+
+    n = pta->n;
+    if (n >= pta->nalloc)
+        ptaExtendArrays(pta);
+    pta->x[n] = x;
+    pta->y[n] = y;
+    pta->n++;
+
+    return 0;
+}
+
+
+/*!
+ *  ptaExtendArrays()
+ *
+ *      Input:  pta
+ *      Return: 0 if OK; 1 on error
+ */
+static l_int32
+ptaExtendArrays(PTA  *pta)
+{
+    PROCNAME("ptaExtendArrays");
+
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+
+    if ((pta->x = (l_float32 *)reallocNew((void **)&pta->x,
+                               sizeof(l_float32) * pta->nalloc,
+                               2 * sizeof(l_float32) * pta->nalloc)) == NULL)
+        return ERROR_INT("new x array not returned", procName, 1);
+    if ((pta->y = (l_float32 *)reallocNew((void **)&pta->y,
+                               sizeof(l_float32) * pta->nalloc,
+                               2 * sizeof(l_float32) * pta->nalloc)) == NULL)
+        return ERROR_INT("new y array not returned", procName, 1);
+
+    pta->nalloc = 2 * pta->nalloc;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                     Pta insertion and removal                       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaInsertPt()
+ *
+ *      Input:  pta
+ *              index (at which pt is to be inserted)
+ *              x, y (point values)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+ptaInsertPt(PTA     *pta,
+            l_int32  index,
+            l_int32  x,
+            l_int32  y)
+{
+l_int32  i, n;
+
+    PROCNAME("ptaInsertPt");
+
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    n = ptaGetCount(pta);
+    if (index < 0 || index > n)
+        return ERROR_INT("index not in {0...n}", procName, 1);
+
+    if (n > pta->nalloc)
+        ptaExtendArrays(pta);
+    pta->n++;
+    for (i = n; i > index; i--) {
+        pta->x[i] = pta->x[i - 1];
+        pta->y[i] = pta->y[i - 1];
+    }
+    pta->x[index] = x;
+    pta->y[index] = y;
+    return 0;
+}
+
+
+/*!
+ *  ptaRemovePt()
+ *
+ *      Input:  pta
+ *              index (of point to be removed)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This shifts pta[i] --> pta[i - 1] for all i > index.
+ *      (2) It should not be used repeatedly on large arrays,
+ *          because the function is O(n).
+ */
+l_int32
+ptaRemovePt(PTA     *pta,
+            l_int32  index)
+{
+l_int32  i, n;
+
+    PROCNAME("ptaRemovePt");
+
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    n = ptaGetCount(pta);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not in {0...n - 1}", procName, 1);
+
+        /* Remove the point */
+    for (i = index + 1; i < n; i++) {
+        pta->x[i - 1] = pta->x[i];
+        pta->y[i - 1] = pta->y[i];
+    }
+    pta->n--;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                           Pta accessors                             *
+ *---------------------------------------------------------------------*/
+l_int32
+ptaGetRefcount(PTA  *pta)
+{
+    PROCNAME("ptaGetRefcount");
+
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    return pta->refcount;
+}
+
+
+l_int32
+ptaChangeRefcount(PTA     *pta,
+                  l_int32  delta)
+{
+    PROCNAME("ptaChangeRefcount");
+
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    pta->refcount += delta;
+    return 0;
+}
+
+
+/*!
+ *  ptaGetCount()
+ *
+ *      Input:  pta
+ *      Return: count, or 0 if no pta
+ */
+l_int32
+ptaGetCount(PTA  *pta)
+{
+    PROCNAME("ptaGetCount");
+
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 0);
+
+    return pta->n;
+}
+
+
+/*!
+ *  ptaGetPt()
+ *
+ *      Input:  pta
+ *              index  (into arrays)
+ *              &x (<optional return> float x value)
+ *              &y (<optional return> float y value)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+ptaGetPt(PTA        *pta,
+         l_int32     index,
+         l_float32  *px,
+         l_float32  *py)
+{
+    PROCNAME("ptaGetPt");
+
+    if (px) *px = 0;
+    if (py) *py = 0;
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    if (index < 0 || index >= pta->n)
+        return ERROR_INT("invalid index", procName, 1);
+
+    if (px) *px = pta->x[index];
+    if (py) *py = pta->y[index];
+    return 0;
+}
+
+
+/*!
+ *  ptaGetIPt()
+ *
+ *      Input:  pta
+ *              index  (into arrays)
+ *              &x (<optional return> integer x value)
+ *              &y (<optional return> integer y value)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+ptaGetIPt(PTA      *pta,
+          l_int32   index,
+          l_int32  *px,
+          l_int32  *py)
+{
+    PROCNAME("ptaGetIPt");
+
+    if (px) *px = 0;
+    if (py) *py = 0;
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    if (index < 0 || index >= pta->n)
+        return ERROR_INT("invalid index", procName, 1);
+
+    if (px) *px = (l_int32)(pta->x[index] + 0.5);
+    if (py) *py = (l_int32)(pta->y[index] + 0.5);
+    return 0;
+}
+
+
+/*!
+ *  ptaSetPt()
+ *
+ *      Input:  pta
+ *              index  (into arrays)
+ *              x, y
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+ptaSetPt(PTA       *pta,
+         l_int32    index,
+         l_float32  x,
+         l_float32  y)
+{
+    PROCNAME("ptaSetPt");
+
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    if (index < 0 || index >= pta->n)
+        return ERROR_INT("invalid index", procName, 1);
+
+    pta->x[index] = x;
+    pta->y[index] = y;
+    return 0;
+}
+
+
+/*!
+ *  ptaGetArrays()
+ *
+ *      Input:  pta
+ *              &nax (<optional return> numa of x array)
+ *              &nay (<optional return> numa of y array)
+ *      Return: 0 if OK; 1 on error or if pta is empty
+ *
+ *  Notes:
+ *      (1) This copies the internal arrays into new Numas.
+ */
+l_int32
+ptaGetArrays(PTA    *pta,
+             NUMA  **pnax,
+             NUMA  **pnay)
+{
+l_int32  i, n;
+NUMA    *nax, *nay;
+
+    PROCNAME("ptaGetArrays");
+
+    if (!pnax && !pnay)
+        return ERROR_INT("no output requested", procName, 1);
+    if (pnax) *pnax = NULL;
+    if (pnay) *pnay = NULL;
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    if ((n = ptaGetCount(pta)) == 0)
+        return ERROR_INT("pta is empty", procName, 1);
+
+    if (pnax) {
+        if ((nax = numaCreate(n)) == NULL)
+            return ERROR_INT("nax not made", procName, 1);
+        *pnax = nax;
+        for (i = 0; i < n; i++)
+            nax->array[i] = pta->x[i];
+        nax->n = n;
+    }
+    if (pnay) {
+        if ((nay = numaCreate(n)) == NULL)
+            return ERROR_INT("nay not made", procName, 1);
+        *pnay = nay;
+        for (i = 0; i < n; i++)
+            nay->array[i] = pta->y[i];
+        nay->n = n;
+    }
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                       Pta serialized for I/O                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaRead()
+ *
+ *      Input:  filename
+ *      Return: pta, or null on error
+ */
+PTA *
+ptaRead(const char  *filename)
+{
+FILE  *fp;
+PTA   *pta;
+
+    PROCNAME("ptaRead");
+
+    if (!filename)
+        return (PTA *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PTA *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((pta = ptaReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (PTA *)ERROR_PTR("pta not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return pta;
+}
+
+
+/*!
+ *  ptaReadStream()
+ *
+ *      Input:  stream
+ *      Return: pta, or null on error
+ */
+PTA *
+ptaReadStream(FILE  *fp)
+{
+char       typestr[128];
+l_int32    i, n, ix, iy, type, version;
+l_float32  x, y;
+PTA       *pta;
+
+    PROCNAME("ptaReadStream");
+
+    if (!fp)
+        return (PTA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\n Pta Version %d\n", &version) != 1)
+        return (PTA *)ERROR_PTR("not a pta file", procName, NULL);
+    if (version != PTA_VERSION_NUMBER)
+        return (PTA *)ERROR_PTR("invalid pta version", procName, NULL);
+    if (fscanf(fp, " Number of pts = %d; format = %s\n", &n, typestr) != 2)
+        return (PTA *)ERROR_PTR("not a pta file", procName, NULL);
+    if (!strcmp(typestr, "float"))
+        type = 0;
+    else  /* typestr is "integer" */
+        type = 1;
+
+    if ((pta = ptaCreate(n)) == NULL)
+        return (PTA *)ERROR_PTR("pta not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        if (type == 0) {  /* data is float */
+            if (fscanf(fp, "   (%f, %f)\n", &x, &y) != 2)
+                return (PTA *)ERROR_PTR("error reading floats", procName, NULL);
+            ptaAddPt(pta, x, y);
+        } else {   /* data is integer */
+            if (fscanf(fp, "   (%d, %d)\n", &ix, &iy) != 2)
+                return (PTA *)ERROR_PTR("error reading ints", procName, NULL);
+            ptaAddPt(pta, ix, iy);
+        }
+    }
+
+    return pta;
+}
+
+
+/*!
+ *  ptaWrite()
+ *
+ *      Input:  filename
+ *              pta
+ *              type  (0 for float values; 1 for integer values)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+ptaWrite(const char  *filename,
+         PTA         *pta,
+         l_int32      type)
+{
+FILE  *fp;
+
+    PROCNAME("ptaWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "w")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (ptaWriteStream(fp, pta, type))
+        return ERROR_INT("pta not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  ptaWriteStream()
+ *
+ *      Input:  stream
+ *              pta
+ *              type  (0 for float values; 1 for integer values)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+ptaWriteStream(FILE    *fp,
+               PTA     *pta,
+               l_int32  type)
+{
+l_int32    i, n, ix, iy;
+l_float32  x, y;
+
+    PROCNAME("ptaWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+
+    n = ptaGetCount(pta);
+    fprintf(fp, "\n Pta Version %d\n", PTA_VERSION_NUMBER);
+    if (type == 0)
+        fprintf(fp, " Number of pts = %d; format = float\n", n);
+    else  /* type == 1 */
+        fprintf(fp, " Number of pts = %d; format = integer\n", n);
+    for (i = 0; i < n; i++) {
+        if (type == 0) {  /* data is float */
+            ptaGetPt(pta, i, &x, &y);
+            fprintf(fp, "   (%f, %f)\n", x, y);
+        } else {   /* data is integer */
+            ptaGetIPt(pta, i, &ix, &iy);
+            fprintf(fp, "   (%d, %d)\n", ix, iy);
+        }
+    }
+
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                     PTAA creation, destruction                      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaaCreate()
+ *
+ *      Input:  n  (initial number of ptrs)
+ *      Return: ptaa, or null on error
+ */
+PTAA *
+ptaaCreate(l_int32  n)
+{
+PTAA  *ptaa;
+
+    PROCNAME("ptaaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((ptaa = (PTAA *)LEPT_CALLOC(1, sizeof(PTAA))) == NULL)
+        return (PTAA *)ERROR_PTR("ptaa not made", procName, NULL);
+    ptaa->n = 0;
+    ptaa->nalloc = n;
+
+    if ((ptaa->pta = (PTA **)LEPT_CALLOC(n, sizeof(PTA *))) == NULL)
+        return (PTAA *)ERROR_PTR("pta ptrs not made", procName, NULL);
+
+    return ptaa;
+}
+
+
+/*!
+ *  ptaaDestroy()
+ *
+ *      Input:  &ptaa <to be nulled>
+ *      Return: void
+ */
+void
+ptaaDestroy(PTAA  **pptaa)
+{
+l_int32  i;
+PTAA    *ptaa;
+
+    PROCNAME("ptaaDestroy");
+
+    if (pptaa == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+
+    if ((ptaa = *pptaa) == NULL)
+        return;
+
+    for (i = 0; i < ptaa->n; i++)
+        ptaDestroy(&ptaa->pta[i]);
+    LEPT_FREE(ptaa->pta);
+
+    LEPT_FREE(ptaa);
+    *pptaa = NULL;
+    return;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                          PTAA array extension                       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaaAddPta()
+ *
+ *      Input:  ptaa
+ *              pta  (to be added)
+ *              copyflag  (L_INSERT, L_COPY, L_CLONE)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+ptaaAddPta(PTAA    *ptaa,
+           PTA     *pta,
+           l_int32  copyflag)
+{
+l_int32  n;
+PTA     *ptac;
+
+    PROCNAME("ptaaAddPta");
+
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+
+    if (copyflag == L_INSERT) {
+        ptac = pta;
+    } else if (copyflag == L_COPY) {
+        if ((ptac = ptaCopy(pta)) == NULL)
+            return ERROR_INT("ptac not made", procName, 1);
+    } else if (copyflag == L_CLONE) {
+        if ((ptac = ptaClone(pta)) == NULL)
+            return ERROR_INT("pta clone not made", procName, 1);
+    } else {
+        return ERROR_INT("invalid copyflag", procName, 1);
+    }
+
+    n = ptaaGetCount(ptaa);
+    if (n >= ptaa->nalloc)
+        ptaaExtendArray(ptaa);
+    ptaa->pta[n] = ptac;
+    ptaa->n++;
+
+    return 0;
+}
+
+
+/*!
+ *  ptaaExtendArray()
+ *
+ *      Input:  ptaa
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+ptaaExtendArray(PTAA  *ptaa)
+{
+    PROCNAME("ptaaExtendArray");
+
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 1);
+
+    if ((ptaa->pta = (PTA **)reallocNew((void **)&ptaa->pta,
+                             sizeof(PTA *) * ptaa->nalloc,
+                             2 * sizeof(PTA *) * ptaa->nalloc)) == NULL)
+        return ERROR_INT("new ptr array not returned", procName, 1);
+
+    ptaa->nalloc = 2 * ptaa->nalloc;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                          Ptaa accessors                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaaGetCount()
+ *
+ *      Input:  ptaa
+ *      Return: count, or 0 if no ptaa
+ */
+l_int32
+ptaaGetCount(PTAA  *ptaa)
+{
+    PROCNAME("ptaaGetCount");
+
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 0);
+
+    return ptaa->n;
+}
+
+
+/*!
+ *  ptaaGetPta()
+ *
+ *      Input:  ptaa
+ *              index  (to the i-th pta)
+ *              accessflag  (L_COPY or L_CLONE)
+ *      Return: pta, or null on error
+ */
+PTA *
+ptaaGetPta(PTAA    *ptaa,
+           l_int32  index,
+           l_int32  accessflag)
+{
+    PROCNAME("ptaaGetPta");
+
+    if (!ptaa)
+        return (PTA *)ERROR_PTR("ptaa not defined", procName, NULL);
+    if (index < 0 || index >= ptaa->n)
+        return (PTA *)ERROR_PTR("index not valid", procName, NULL);
+
+    if (accessflag == L_COPY)
+        return ptaCopy(ptaa->pta[index]);
+    else if (accessflag == L_CLONE)
+        return ptaClone(ptaa->pta[index]);
+    else
+        return (PTA *)ERROR_PTR("invalid accessflag", procName, NULL);
+}
+
+
+/*!
+ *  ptaaGetPt()
+ *
+ *      Input:  ptaa
+ *              ipta  (to the i-th pta)
+ *              jpt (index to the j-th pt in the pta)
+ *              &x (<optional return> float x value)
+ *              &y (<optional return> float y value)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+ptaaGetPt(PTAA       *ptaa,
+           l_int32     ipta,
+           l_int32     jpt,
+           l_float32  *px,
+           l_float32  *py)
+{
+PTA  *pta;
+
+    PROCNAME("ptaaGetPt");
+
+    if (px) *px = 0;
+    if (py) *py = 0;
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 1);
+    if (ipta < 0 || ipta >= ptaa->n)
+        return ERROR_INT("index ipta not valid", procName, 1);
+
+    pta = ptaaGetPta(ptaa, ipta, L_CLONE);
+    if (jpt < 0 || jpt >= pta->n) {
+        ptaDestroy(&pta);
+        return ERROR_INT("index jpt not valid", procName, 1);
+    }
+
+    ptaGetPt(pta, jpt, px, py);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Ptaa array modifiers                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaaInitFull()
+ *
+ *      Input:  ptaa (can have non-null ptrs in the ptr array)
+ *              pta (to be replicated into the entire ptr array)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+ptaaInitFull(PTAA  *ptaa,
+             PTA   *pta)
+{
+l_int32  n, i;
+PTA     *ptat;
+
+    PROCNAME("ptaaInitFull");
+
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+
+    n = ptaa->nalloc;
+    ptaa->n = n;
+    for (i = 0; i < n; i++) {
+        ptat = ptaCopy(pta);
+        ptaaReplacePta(ptaa, i, ptat);
+    }
+    return 0;
+}
+
+
+/*!
+ *  ptaaReplacePta()
+ *
+ *      Input:  ptaa
+ *              index  (to the index-th pta)
+ *              pta (insert and replace any existing one)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Any existing pta is destroyed, and the input one
+ *          is inserted in its place.
+ *      (2) If the index is invalid, return 1 (error)
+ */
+l_int32
+ptaaReplacePta(PTAA    *ptaa,
+               l_int32  index,
+               PTA     *pta)
+{
+l_int32  n;
+
+    PROCNAME("ptaaReplacePta");
+
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    n = ptaaGetCount(ptaa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("index not valid", procName, 1);
+
+    ptaDestroy(&ptaa->pta[index]);
+    ptaa->pta[index] = pta;
+    return 0;
+}
+
+
+/*!
+ *  ptaaAddPt()
+ *
+ *      Input:  ptaa
+ *              ipta  (to the i-th pta)
+ *              x,y (point coordinates)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+ptaaAddPt(PTAA      *ptaa,
+          l_int32    ipta,
+          l_float32  x,
+          l_float32  y)
+{
+PTA  *pta;
+
+    PROCNAME("ptaaAddPt");
+
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 1);
+    if (ipta < 0 || ipta >= ptaa->n)
+        return ERROR_INT("index ipta not valid", procName, 1);
+
+    pta = ptaaGetPta(ptaa, ipta, L_CLONE);
+    ptaAddPt(pta, x, y);
+    ptaDestroy(&pta);
+    return 0;
+}
+
+
+/*!
+ *  ptaaTruncate()
+ *
+ *      Input:  ptaa
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This identifies the largest index containing a pta that
+ *          has any points within it, destroys all pta above that index,
+ *          and resets the count.
+ */
+l_int32
+ptaaTruncate(PTAA  *ptaa)
+{
+l_int32  i, n, np;
+PTA     *pta;
+
+    PROCNAME("ptaaTruncate");
+
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 1);
+
+    n = ptaaGetCount(ptaa);
+    for (i = n - 1; i >= 0; i--) {
+        pta = ptaaGetPta(ptaa, i, L_CLONE);
+        if (!pta) {
+            ptaa->n--;
+            continue;
+        }
+        np = ptaGetCount(pta);
+        ptaDestroy(&pta);
+        if (np == 0) {
+            ptaDestroy(&ptaa->pta[i]);
+            ptaa->n--;
+        } else {
+            break;
+        }
+    }
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                       Ptaa serialized for I/O                       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaaRead()
+ *
+ *      Input:  filename
+ *      Return: ptaa, or null on error
+ */
+PTAA *
+ptaaRead(const char  *filename)
+{
+FILE  *fp;
+PTAA  *ptaa;
+
+    PROCNAME("ptaaRead");
+
+    if (!filename)
+        return (PTAA *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PTAA *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((ptaa = ptaaReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (PTAA *)ERROR_PTR("ptaa not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return ptaa;
+}
+
+
+/*!
+ *  ptaaReadStream()
+ *
+ *      Input:  stream
+ *      Return: ptaa, or null on error
+ */
+PTAA *
+ptaaReadStream(FILE  *fp)
+{
+l_int32  i, n, version;
+PTA     *pta;
+PTAA    *ptaa;
+
+    PROCNAME("ptaaReadStream");
+
+    if (!fp)
+        return (PTAA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nPtaa Version %d\n", &version) != 1)
+        return (PTAA *)ERROR_PTR("not a ptaa file", procName, NULL);
+    if (version != PTA_VERSION_NUMBER)
+        return (PTAA *)ERROR_PTR("invalid ptaa version", procName, NULL);
+    if (fscanf(fp, "Number of Pta = %d\n", &n) != 1)
+        return (PTAA *)ERROR_PTR("not a ptaa file", procName, NULL);
+
+    if ((ptaa = ptaaCreate(n)) == NULL)
+        return (PTAA *)ERROR_PTR("ptaa not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        if ((pta = ptaReadStream(fp)) == NULL)
+            return (PTAA *)ERROR_PTR("error reading pta", procName, NULL);
+        ptaaAddPta(ptaa, pta, L_INSERT);
+    }
+
+    return ptaa;
+}
+
+
+/*!
+ *  ptaaWrite()
+ *
+ *      Input:  filename
+ *              ptaa
+ *              type  (0 for float values; 1 for integer values)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+ptaaWrite(const char  *filename,
+          PTAA        *ptaa,
+          l_int32      type)
+{
+FILE  *fp;
+
+    PROCNAME("ptaaWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "w")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (ptaaWriteStream(fp, ptaa, type))
+        return ERROR_INT("ptaa not written to stream", procName, 1);
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  ptaaWriteStream()
+ *
+ *      Input:  stream
+ *              ptaa
+ *              type  (0 for float values; 1 for integer values)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+ptaaWriteStream(FILE    *fp,
+                PTAA    *ptaa,
+                l_int32  type)
+{
+l_int32  i, n;
+PTA     *pta;
+
+    PROCNAME("ptaaWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!ptaa)
+        return ERROR_INT("ptaa not defined", procName, 1);
+
+    n = ptaaGetCount(ptaa);
+    fprintf(fp, "\nPtaa Version %d\n", PTA_VERSION_NUMBER);
+    fprintf(fp, "Number of Pta = %d\n", n);
+    for (i = 0; i < n; i++) {
+        pta = ptaaGetPta(ptaa, i, L_CLONE);
+        ptaWriteStream(fp, pta, type);
+        ptaDestroy(&pta);
+    }
+
+    return 0;
+}
diff --git a/src/ptafunc1.c b/src/ptafunc1.c
new file mode 100644 (file)
index 0000000..93df994
--- /dev/null
@@ -0,0 +1,2901 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *   ptafunc1.c
+ *
+ *      Pta and Ptaa rearrangements
+ *           PTA      *ptaSubsample()
+ *           l_int32   ptaJoin()
+ *           l_int32   ptaaJoin()
+ *           PTA      *ptaReverse()
+ *           PTA      *ptaTranspose()
+ *           PTA      *ptaCyclicPerm()
+ *           PTA      *ptaSort()
+ *           l_int32   ptaGetSortIndex()
+ *           PTA      *ptaSortByIndex()
+ *           PTAA     *ptaaSortByIndex()
+ *
+ *      Union and intersection by aset (rbtree)
+ *           PTA        *ptaUnionByAset()
+ *           PTA        *ptaRemoveDupsByAset()
+ *           PTA        *ptaIntersectionByAset()
+ *           L_ASET     *l_asetCreateFromPta()
+ *
+ *      Union and intersection by hash
+ *           PTA        *ptaUnionByHash()
+ *           l_int32     ptaRemoveDupsByHash()
+ *           PTA        *ptaIntersectionByHash();
+ *           l_int32     ptaFindPtByHash()
+ *           L_DNAHASH  *l_dnaHashCreateFromPta()
+ *
+ *      Geometric
+ *           BOX      *ptaGetBoundingRegion()
+ *           l_int32  *ptaGetRange()
+ *           PTA      *ptaGetInsideBox()
+ *           PTA      *pixFindCornerPixels()
+ *           l_int32   ptaContainsPt()
+ *           l_int32   ptaTestIntersection()
+ *           PTA      *ptaTransform()
+ *           l_int32   ptaPtInsidePolygon()
+ *           l_float32 l_angleBetweenVectors()
+ *
+ *      Least Squares Fit
+ *           l_int32   ptaGetLinearLSF()
+ *           l_int32   ptaGetQuadraticLSF()
+ *           l_int32   ptaGetCubicLSF()
+ *           l_int32   ptaGetQuarticLSF()
+ *           l_int32   ptaNoisyLinearLSF()
+ *           l_int32   ptaNoisyQuadraticLSF()
+ *           l_int32   applyLinearFit()
+ *           l_int32   applyQuadraticFit()
+ *           l_int32   applyCubicFit()
+ *           l_int32   applyQuarticFit()
+ *
+ *      Interconversions with Pix
+ *           l_int32   pixPlotAlongPta()
+ *           PTA      *ptaGetPixelsFromPix()
+ *           PIX      *pixGenerateFromPta()
+ *           PTA      *ptaGetBoundaryPixels()
+ *           PTAA     *ptaaGetBoundaryPixels()
+ *           PTAA     *ptaaIndexLabelledPixels()
+ *           PTA      *ptaGetNeighborPixLocs()
+ *
+ *      Display Pta and Ptaa
+ *           PIX      *pixDisplayPta()
+ *           PIX      *pixDisplayPtaaPattern()
+ *           PIX      *pixDisplayPtaPattern()
+ *           PTA      *ptaReplicatePattern()
+ *           PIX      *pixDisplayPtaa()
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif  /* M_PI */
+
+
+static l_int32 l_dnaCopyUniquePts(L_DNA *da, PTA *ptas, PTA *ptad);
+
+
+/*---------------------------------------------------------------------*
+ *                           Pta rearrangements                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaSubsample()
+ *
+ *      Input:  ptas
+ *              subfactor (subsample factor, >= 1)
+ *      Return: ptad (evenly sampled pt values from ptas, or null on error
+ */
+PTA *
+ptaSubsample(PTA     *ptas,
+             l_int32  subfactor)
+{
+l_int32    n, i;
+l_float32  x, y;
+PTA       *ptad;
+
+    PROCNAME("pixSubsample");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (subfactor < 1)
+        return (PTA *)ERROR_PTR("subfactor < 1", procName, NULL);
+
+    ptad = ptaCreate(0);
+    n = ptaGetCount(ptas);
+    for (i = 0; i < n; i++) {
+        if (i % subfactor != 0) continue;
+        ptaGetPt(ptas, i, &x, &y);
+        ptaAddPt(ptad, x, y);
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  ptaJoin()
+ *
+ *      Input:  ptad  (dest pta; add to this one)
+ *              ptas  (source pta; add from this one)
+ *              istart  (starting index in ptas)
+ *              iend  (ending index in ptas; use -1 to cat all)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) istart < 0 is taken to mean 'read from the start' (istart = 0)
+ *      (2) iend < 0 means 'read to the end'
+ *      (3) if ptas == NULL, this is a no-op
+ */
+l_int32
+ptaJoin(PTA     *ptad,
+        PTA     *ptas,
+        l_int32  istart,
+        l_int32  iend)
+{
+l_int32  n, i, x, y;
+
+    PROCNAME("ptaJoin");
+
+    if (!ptad)
+        return ERROR_INT("ptad not defined", procName, 1);
+    if (!ptas)
+        return 0;
+
+    if (istart < 0)
+        istart = 0;
+    n = ptaGetCount(ptas);
+    if (iend < 0 || iend >= n)
+        iend = n - 1;
+    if (istart > iend)
+        return ERROR_INT("istart > iend; no pts", procName, 1);
+
+    for (i = istart; i <= iend; i++) {
+        ptaGetIPt(ptas, i, &x, &y);
+        ptaAddPt(ptad, x, y);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  ptaaJoin()
+ *
+ *      Input:  ptaad  (dest ptaa; add to this one)
+ *              ptaas  (source ptaa; add from this one)
+ *              istart  (starting index in ptaas)
+ *              iend  (ending index in ptaas; use -1 to cat all)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) istart < 0 is taken to mean 'read from the start' (istart = 0)
+ *      (2) iend < 0 means 'read to the end'
+ *      (3) if ptas == NULL, this is a no-op
+ */
+l_int32
+ptaaJoin(PTAA    *ptaad,
+         PTAA    *ptaas,
+         l_int32  istart,
+         l_int32  iend)
+{
+l_int32  n, i;
+PTA     *pta;
+
+    PROCNAME("ptaaJoin");
+
+    if (!ptaad)
+        return ERROR_INT("ptaad not defined", procName, 1);
+    if (!ptaas)
+        return 0;
+
+    if (istart < 0)
+        istart = 0;
+    n = ptaaGetCount(ptaas);
+    if (iend < 0 || iend >= n)
+        iend = n - 1;
+    if (istart > iend)
+        return ERROR_INT("istart > iend; no pts", procName, 1);
+
+    for (i = istart; i <= iend; i++) {
+        pta = ptaaGetPta(ptaas, i, L_CLONE);
+        ptaaAddPta(ptaad, pta, L_INSERT);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  ptaReverse()
+ *
+ *      Input:  ptas
+ *              type  (0 for float values; 1 for integer values)
+ *      Return: ptad (reversed pta), or null on error
+ */
+PTA  *
+ptaReverse(PTA     *ptas,
+           l_int32  type)
+{
+l_int32    n, i, ix, iy;
+l_float32  x, y;
+PTA       *ptad;
+
+    PROCNAME("ptaReverse");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+
+    n = ptaGetCount(ptas);
+    if ((ptad = ptaCreate(n)) == NULL)
+        return (PTA *)ERROR_PTR("ptad not made", procName, NULL);
+    for (i = n - 1; i >= 0; i--) {
+        if (type == 0) {
+            ptaGetPt(ptas, i, &x, &y);
+            ptaAddPt(ptad, x, y);
+        } else {  /* type == 1 */
+            ptaGetIPt(ptas, i, &ix, &iy);
+            ptaAddPt(ptad, ix, iy);
+        }
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  ptaTranspose()
+ *
+ *      Input:  ptas
+ *      Return: ptad (with x and y values swapped), or null on error
+ */
+PTA  *
+ptaTranspose(PTA  *ptas)
+{
+l_int32    n, i;
+l_float32  x, y;
+PTA       *ptad;
+
+    PROCNAME("ptaTranspose");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+
+    n = ptaGetCount(ptas);
+    if ((ptad = ptaCreate(n)) == NULL)
+        return (PTA *)ERROR_PTR("ptad not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        ptaGetPt(ptas, i, &x, &y);
+        ptaAddPt(ptad, y, x);
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  ptaCyclicPerm()
+ *
+ *      Input:  ptas
+ *              xs, ys  (start point; must be in ptas)
+ *      Return: ptad (cyclic permutation, starting and ending at (xs, ys),
+ *              or null on error
+ *
+ *  Notes:
+ *      (1) Check to insure that (a) ptas is a closed path where
+ *          the first and last points are identical, and (b) the
+ *          resulting pta also starts and ends on the same point
+ *          (which in this case is (xs, ys).
+ */
+PTA  *
+ptaCyclicPerm(PTA     *ptas,
+              l_int32  xs,
+              l_int32  ys)
+{
+l_int32  n, i, x, y, j, index, state;
+l_int32  x1, y1, x2, y2;
+PTA     *ptad;
+
+    PROCNAME("ptaCyclicPerm");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+
+    n = ptaGetCount(ptas);
+
+        /* Verify input data */
+    ptaGetIPt(ptas, 0, &x1, &y1);
+    ptaGetIPt(ptas, n - 1, &x2, &y2);
+    if (x1 != x2 || y1 != y2)
+        return (PTA *)ERROR_PTR("start and end pts not same", procName, NULL);
+    state = L_NOT_FOUND;
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(ptas, i, &x, &y);
+        if (x == xs && y == ys) {
+            state = L_FOUND;
+            break;
+        }
+    }
+    if (state == L_NOT_FOUND)
+        return (PTA *)ERROR_PTR("start pt not in ptas", procName, NULL);
+
+    if ((ptad = ptaCreate(n)) == NULL)
+        return (PTA *)ERROR_PTR("ptad not made", procName, NULL);
+    for (j = 0; j < n - 1; j++) {
+        if (i + j < n - 1)
+            index = i + j;
+        else
+            index = (i + j + 1) % n;
+        ptaGetIPt(ptas, index, &x, &y);
+        ptaAddPt(ptad, x, y);
+    }
+    ptaAddPt(ptad, xs, ys);
+
+    return ptad;
+}
+
+
+/*!
+ *  ptaSort()
+ *
+ *      Input:  ptas
+ *              sorttype (L_SORT_BY_X, L_SORT_BY_Y)
+ *              sortorder  (L_SORT_INCREASING, L_SORT_DECREASING)
+ *              &naindex (<optional return> index of sorted order into
+ *                        original array)
+ *      Return: ptad (sorted version of ptas), or null on error
+ */
+PTA *
+ptaSort(PTA     *ptas,
+        l_int32  sorttype,
+        l_int32  sortorder,
+        NUMA   **pnaindex)
+{
+PTA   *ptad;
+NUMA  *naindex;
+
+    PROCNAME("ptaSort");
+
+    if (pnaindex) *pnaindex = NULL;
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y)
+        return (PTA *)ERROR_PTR("invalid sort type", procName, NULL);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return (PTA *)ERROR_PTR("invalid sort order", procName, NULL);
+
+    if (ptaGetSortIndex(ptas, sorttype, sortorder, &naindex) != 0)
+        return (PTA *)ERROR_PTR("naindex not made", procName, NULL);
+
+    ptad = ptaSortByIndex(ptas, naindex);
+    if (pnaindex)
+        *pnaindex = naindex;
+    else
+        numaDestroy(&naindex);
+    if (!ptad)
+        return (PTA *)ERROR_PTR("ptad not made", procName, NULL);
+    return ptad;
+}
+
+
+/*!
+ *  ptaGetSortIndex()
+ *
+ *      Input:  ptas
+ *              sorttype (L_SORT_BY_X, L_SORT_BY_Y)
+ *              sortorder  (L_SORT_INCREASING, L_SORT_DECREASING)
+ *              &naindex (<return> index of sorted order into
+ *                        original array)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+ptaGetSortIndex(PTA     *ptas,
+                l_int32  sorttype,
+                l_int32  sortorder,
+                NUMA   **pnaindex)
+{
+l_int32    i, n;
+l_float32  x, y;
+NUMA      *na;
+
+    PROCNAME("ptaGetSortIndex");
+
+    if (!pnaindex)
+        return ERROR_INT("&naindex not defined", procName, 1);
+    *pnaindex = NULL;
+    if (!ptas)
+        return ERROR_INT("ptas not defined", procName, 1);
+    if (sorttype != L_SORT_BY_X && sorttype != L_SORT_BY_Y)
+        return ERROR_INT("invalid sort type", procName, 1);
+    if (sortorder != L_SORT_INCREASING && sortorder != L_SORT_DECREASING)
+        return ERROR_INT("invalid sort order", procName, 1);
+
+        /* Build up numa of specific data */
+    n = ptaGetCount(ptas);
+    if ((na = numaCreate(n)) == NULL)
+        return ERROR_INT("na not made", procName, 1);
+    for (i = 0; i < n; i++) {
+        ptaGetPt(ptas, i, &x, &y);
+        if (sorttype == L_SORT_BY_X)
+            numaAddNumber(na, x);
+        else
+            numaAddNumber(na, y);
+    }
+
+        /* Get the sort index for data array */
+    *pnaindex = numaGetSortIndex(na, sortorder);
+    numaDestroy(&na);
+    if (!*pnaindex)
+        return ERROR_INT("naindex not made", procName, 1);
+    return 0;
+}
+
+
+/*!
+ *  ptaSortByIndex()
+ *
+ *      Input:  ptas
+ *              naindex (na that maps from the new pta to the input pta)
+ *      Return: ptad (sorted), or null on  error
+ */
+PTA *
+ptaSortByIndex(PTA   *ptas,
+               NUMA  *naindex)
+{
+l_int32    i, index, n;
+l_float32  x, y;
+PTA       *ptad;
+
+    PROCNAME("ptaSortByIndex");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!naindex)
+        return (PTA *)ERROR_PTR("naindex not defined", procName, NULL);
+
+        /* Build up sorted pta using sort index */
+    n = numaGetCount(naindex);
+    if ((ptad = ptaCreate(n)) == NULL)
+        return (PTA *)ERROR_PTR("ptad not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(naindex, i, &index);
+        ptaGetPt(ptas, index, &x, &y);
+        ptaAddPt(ptad, x, y);
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  ptaaSortByIndex()
+ *
+ *      Input:  ptaas
+ *              naindex (na that maps from the new ptaa to the input ptaa)
+ *      Return: ptaad (sorted), or null on error
+ */
+PTAA *
+ptaaSortByIndex(PTAA  *ptaas,
+                NUMA  *naindex)
+{
+l_int32  i, n, index;
+PTA     *pta;
+PTAA    *ptaad;
+
+    PROCNAME("ptaaSortByIndex");
+
+    if (!ptaas)
+        return (PTAA *)ERROR_PTR("ptaas not defined", procName, NULL);
+    if (!naindex)
+        return (PTAA *)ERROR_PTR("naindex not defined", procName, NULL);
+
+    n = ptaaGetCount(ptaas);
+    if (numaGetCount(naindex) != n)
+        return (PTAA *)ERROR_PTR("numa and ptaa sizes differ", procName, NULL);
+    ptaad = ptaaCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(naindex, i, &index);
+        pta = ptaaGetPta(ptaas, index, L_COPY);
+        ptaaAddPta(ptaad, pta, L_INSERT);
+    }
+
+    return ptaad;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                 Union and intersection by aset (rbtree)             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaUnionByAset()
+ *
+ *      Input:  pta1, pta2
+ *      Return: ptad (with the union of the set of points), or null on error
+ *
+ *  Notes:
+ *      (1) See sarrayRemoveDupsByAset() for the approach.
+ *      (2) The key is a 64-bit hash from the (x,y) pair.
+ *      (3) This is slower than ptaUnionByHash(), mostly because of the
+ *          nlogn sort to build up the rbtree.  Do not use for large
+ *          numbers of points (say, > 1M).
+ *      (4) The *Aset() functions use the sorted l_Aset, which is just
+ *          an rbtree in disguise.
+ */
+PTA *
+ptaUnionByAset(PTA  *pta1,
+               PTA  *pta2)
+{
+PTA  *pta3, *ptad;
+
+    PROCNAME("ptaUnionByAset");
+
+    if (!pta1)
+        return (PTA *)ERROR_PTR("pta1 not defined", procName, NULL);
+    if (!pta2)
+        return (PTA *)ERROR_PTR("pta2 not defined", procName, NULL);
+
+        /* Join */
+    pta3 = ptaCopy(pta1);
+    ptaJoin(pta3, pta2, 0, -1);
+
+        /* Eliminate duplicates */
+    ptad = ptaRemoveDupsByAset(pta3);
+    ptaDestroy(&pta3);
+    return ptad;
+}
+
+
+/*!
+ *  ptaRemoveDupsByAset()
+ *
+ *      Input:  ptas (assumed to be integer values)
+ *      Return: ptad (with duplicates removed), or null on error
+ *
+ *  Notes:
+ *      (1) This is slower than ptaRemoveDupsByHash(), mostly because
+ *          of the nlogn sort to build up the rbtree.  Do not use for
+ *          large numbers of points (say, > 1M).
+ */
+PTA *
+ptaRemoveDupsByAset(PTA  *ptas)
+{
+l_int32   i, n, x, y;
+PTA      *ptad;
+l_uint64  hash;
+L_ASET   *set;
+RB_TYPE   key;
+
+    PROCNAME("ptaRemoveDupsByAset");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+
+    set = l_asetCreate(L_UINT_TYPE);
+    n = ptaGetCount(ptas);
+    ptad = ptaCreate(n);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(ptas, i, &x, &y);
+        l_hashPtToUint64(x, y, &hash);
+        key.utype = hash;
+        if (!l_asetFind(set, key)) {
+            ptaAddPt(ptad, x, y);
+            l_asetInsert(set, key);
+        }
+    }
+
+    l_asetDestroy(&set);
+    return ptad;
+}
+
+
+/*!
+ *  ptaIntersectionByAset()
+ *
+ *      Input:  pta1, pta2
+ *      Return: ptad (intersection of the point sets), or null on error
+ *
+ *  Notes:
+ *      (1) See sarrayIntersectionByAset() for the approach.
+ *      (2) The key is a 64-bit hash from the (x,y) pair.
+ *      (3) This is slower than ptaIntersectionByHash(), mostly because
+ *          of the nlogn sort to build up the rbtree.  Do not use for
+ *          large numbers of points (say, > 1M).
+ */
+PTA *
+ptaIntersectionByAset(PTA  *pta1,
+                      PTA  *pta2)
+{
+l_int32   n1, n2, i, n, x, y;
+l_uint64  hash;
+L_ASET   *set1, *set2;
+RB_TYPE   key;
+PTA      *pta_small, *pta_big, *ptad;
+
+    PROCNAME("ptaIntersectionByAset");
+
+    if (!pta1)
+        return (PTA *)ERROR_PTR("pta1 not defined", procName, NULL);
+    if (!pta2)
+        return (PTA *)ERROR_PTR("pta2 not defined", procName, NULL);
+
+        /* Put the elements of the biggest array into a set */
+    n1 = ptaGetCount(pta1);
+    n2 = ptaGetCount(pta2);
+    pta_small = (n1 < n2) ? pta1 : pta2;   /* do not destroy pta_small */
+    pta_big = (n1 < n2) ? pta2 : pta1;   /* do not destroy pta_big */
+    set1 = l_asetCreateFromPta(pta_big);
+
+        /* Build up the intersection of points */
+    ptad = ptaCreate(0);
+    n = ptaGetCount(pta_small);
+    set2 = l_asetCreate(L_UINT_TYPE);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta_small, i, &x, &y);
+        l_hashPtToUint64(x, y, &hash);
+        key.utype = hash;
+        if (l_asetFind(set1, key) && !l_asetFind(set2, key)) {
+            ptaAddPt(ptad, x, y);
+            l_asetInsert(set2, key);
+        }
+    }
+
+    l_asetDestroy(&set1);
+    l_asetDestroy(&set2);
+    return ptad;
+}
+
+
+/*!
+ *  l_asetCreateFromPta()
+ *
+ *      Input:  pta
+ *      Return: set (using a 64-bit hash of (x,y) as the key)
+ */
+L_ASET *
+l_asetCreateFromPta(PTA  *pta)
+{
+l_int32   i, n, x, y;
+l_uint64  hash;
+L_ASET   *set;
+RB_TYPE   key;
+
+    PROCNAME("l_asetCreateFromPta");
+
+    if (!pta)
+        return (L_ASET *)ERROR_PTR("pta not defined", procName, NULL);
+
+    set = l_asetCreate(L_UINT_TYPE);
+    n = ptaGetCount(pta);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        l_hashPtToUint64(x, y, &hash);
+        key.utype = hash;
+        l_asetInsert(set, key);
+    }
+
+    return set;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                    Union and intersection by hash                   *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaUnionByHash()
+ *
+ *      Input:  pta1, pta2
+ *      Return: ptad (with the union of the set of points), or null on error
+ *
+ *  Notes:
+ *      (1) This is faster than ptaUnionByAset(), because the
+ *          bucket lookup is O(n).  It should be used if the pts are
+ *          integers (e.g., representing pixel positions).
+ */
+PTA *
+ptaUnionByHash(PTA  *pta1,
+               PTA  *pta2)
+{
+PTA  *pta3, *ptad;
+
+    PROCNAME("ptaUnionByHash");
+
+    if (!pta1)
+        return (PTA *)ERROR_PTR("pta1 not defined", procName, NULL);
+    if (!pta2)
+        return (PTA *)ERROR_PTR("pta2 not defined", procName, NULL);
+
+        /* Join */
+    pta3 = ptaCopy(pta1);
+    ptaJoin(pta3, pta2, 0, -1);
+
+        /* Eliminate duplicates */
+    ptaRemoveDupsByHash(pta3, &ptad, NULL);
+    ptaDestroy(&pta3);
+    return ptad;
+}
+
+
+/*!
+ *  ptaRemoveDupsByHash()
+ *
+ *      Input:  ptas (assumed to be integer values)
+ *              &ptad (<return> unique set of pts; duplicates removed)
+ *              &dahash (<optional return> dnahash used for lookup)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Generates a pta with unique values.
+ *      (2) The dnahash is built up with ptad to assure uniqueness.
+ *          It can be used to find if a point is in the set:
+ *              ptaFindPtByHash(ptad, dahash, x, y, &index)
+ *      (3) The hash of the (x,y) location is simple and fast.  It scales
+ *          up with the number of buckets to insure a fairly random
+ *          bucket selection for adjacent points.
+ *      (4) A Dna is used rather than a Numa because we need accurate
+ *          representation of 32-bit integers that are indices into ptas.
+ *          Integer --> float --> integer conversion makes errors for
+ *          integers larger than 10M.
+ *      (5) This is faster than ptaRemoveDupsByAset(), because the
+ *          bucket lookup is O(n), although there is a double-loop
+ *          lookup within the dna in each bucket.
+ */
+l_int32
+ptaRemoveDupsByHash(PTA         *ptas,
+                    PTA        **pptad,
+                    L_DNAHASH  **pdahash)
+{
+l_int32     i, n, index, items, x, y;
+l_uint32    nsize;
+l_uint64    key;
+l_float64   val;
+PTA        *ptad;
+L_DNAHASH  *dahash;
+
+    PROCNAME("ptaRemoveDupsByHash");
+
+    if (pdahash) *pdahash = NULL;
+    if (!pptad)
+        return ERROR_INT("&ptad not defined", procName, 1);
+    *pptad = NULL;
+    if (!ptas)
+        return ERROR_INT("ptas not defined", procName, 1);
+
+    n = ptaGetCount(ptas);
+    findNextLargerPrime(n / 20, &nsize);  /* buckets in hash table */
+    dahash = l_dnaHashCreate(nsize, 8);
+    ptad = ptaCreate(n);
+    *pptad = ptad;
+    for (i = 0, items = 0; i < n; i++) {
+        ptaGetIPt(ptas, i, &x, &y);
+        ptaFindPtByHash(ptad, dahash, x, y, &index);
+        if (index < 0) {  /* not found */
+            l_hashPtToUint64Fast(nsize, x, y, &key);
+            l_dnaHashAdd(dahash, key, (l_float64)items);
+            ptaAddPt(ptad, x, y);
+            items++;
+        }
+    }
+
+    if (pdahash)
+        *pdahash = dahash;
+    else
+        l_dnaHashDestroy(&dahash);
+    return 0;
+}
+
+
+/*!
+ *  ptaIntersectionByHash()
+ *
+ *      Input:  pta1, pta2
+ *      Return: ptad (intersection of the point sets), or null on error
+ *
+ *  Notes:
+ *      (1) This is faster than ptaIntersectionByAset(), because the
+ *          bucket lookup is O(n).  It should be used if the pts are
+ *          integers (e.g., representing pixel positions).
+ */
+PTA *
+ptaIntersectionByHash(PTA  *pta1,
+                      PTA  *pta2)
+{
+l_int32     n1, n2, nsmall, i, x, y, index1, index2;
+l_uint32    nsize2;
+l_uint64    key;
+L_DNAHASH  *dahash1, *dahash2;
+PTA        *pta_small, *pta_big, *ptad;
+
+    PROCNAME("ptaIntersectionByHash");
+
+    if (!pta1)
+        return (PTA *)ERROR_PTR("pta1 not defined", procName, NULL);
+    if (!pta2)
+        return (PTA *)ERROR_PTR("pta2 not defined", procName, NULL);
+
+        /* Put the elements of the biggest pta into a dnahash */
+    n1 = ptaGetCount(pta1);
+    n2 = ptaGetCount(pta2);
+    pta_small = (n1 < n2) ? pta1 : pta2;   /* do not destroy pta_small */
+    pta_big = (n1 < n2) ? pta2 : pta1;   /* do not destroy pta_big */
+    dahash1 = l_dnaHashCreateFromPta(pta_big);
+
+        /* Build up the intersection of points.  Add to ptad
+         * if the point is in pta_big (using dahash1) but hasn't
+         * yet been seen in the traversal of pta_small (using dahash2). */
+    ptad = ptaCreate(0);
+    nsmall = ptaGetCount(pta_small);
+    findNextLargerPrime(nsmall / 20, &nsize2);  /* buckets in hash table */
+    dahash2 = l_dnaHashCreate(nsize2, 0);
+    for (i = 0; i < nsmall; i++) {
+        ptaGetIPt(pta_small, i, &x, &y);
+        ptaFindPtByHash(pta_big, dahash1, x, y, &index1);
+        if (index1 >= 0) {  /* found */
+            ptaFindPtByHash(pta_small, dahash2, x, y, &index2);
+            if (index2 == -1) {  /* not found */
+                ptaAddPt(ptad, x, y);
+                l_hashPtToUint64Fast(nsize2, x, y, &key);
+                l_dnaHashAdd(dahash2, key, (l_float64)i);
+            }
+        }
+    }
+
+    l_dnaHashDestroy(&dahash1);
+    l_dnaHashDestroy(&dahash2);
+    return ptad;
+}
+
+
+/*!
+ *  ptaFindPtByHash()
+ *
+ *      Input:  pta
+ *              dahash (built from pta)
+ *              x, y  (arbitrary points)
+ *              &index (<return> index into pta if (x,y) is in pta;
+ *                      -1 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Fast lookup in dnaHash associated with a pta, to see if a
+ *          random point (x,y) is already stored in the hash table.
+ */
+l_int32
+ptaFindPtByHash(PTA        *pta,
+                L_DNAHASH  *dahash,
+                l_int32     x,
+                l_int32     y,
+                l_int32    *pindex)
+{
+l_int32   i, nbuckets, nvals, index, xi, yi;
+l_uint64  key;
+L_DNA    *da;
+
+    PROCNAME("ptaFindPtByHash");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = -1;
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    if (!dahash)
+        return ERROR_INT("dahash not defined", procName, 1);
+
+    nbuckets = l_dnaHashGetCount(dahash);
+    l_hashPtToUint64Fast(nbuckets, x, y, &key);
+    da = l_dnaHashGetDna(dahash, key, L_NOCOPY);
+    if (!da) return 0;
+
+        /* Run through the da, looking for this point */
+    nvals = l_dnaGetCount(da);
+    for (i = 0; i < nvals; i++) {
+        l_dnaGetIValue(da, i, &index);
+        ptaGetIPt(pta, index, &xi, &yi);
+        if (x == xi && y == yi) {
+            *pindex = index;
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  l_dnaHashCreateFromPta()
+ *
+ *      Input:  pta
+ *      Return: dahash, or null on error
+ */
+L_DNAHASH *
+l_dnaHashCreateFromPta(PTA  *pta)
+{
+l_int32     i, n, x, y;
+l_uint32    nsize;
+l_uint64    key;
+L_DNAHASH  *dahash;
+
+    PROCNAME("l_dnaHashCreateFromPta");
+
+        /* Build up dnaHash of indices, hashed by a key that is
+         * a large linear combination of x and y values designed to
+         * randomize the key.  Having about 20 pts in each bucket is
+         * roughly optimal for speed for large sets. */
+    n = ptaGetCount(pta);
+    findNextLargerPrime(n / 20, &nsize);  /* buckets in hash table */
+/*    fprintf(stderr, "Prime used: %d\n", nsize); */
+
+        /* Add each point, using the hash as key and the index into
+         * @ptas as the value.  Storing the index enables operations
+         * that check for duplicates. */
+    dahash = l_dnaHashCreate(nsize, 8);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        l_hashPtToUint64Fast(nsize, x, y, &key);
+        l_dnaHashAdd(dahash, key, (l_float64)i);
+    }
+
+    return dahash;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                               Geometric                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaGetBoundingRegion()
+ *
+ *      Input:  pta
+ *      Return: box, or null on error
+ *
+ *  Notes:
+ *      (1) This is used when the pta represents a set of points in
+ *          a two-dimensional image.  It returns the box of minimum
+ *          size containing the pts in the pta.
+ */
+BOX *
+ptaGetBoundingRegion(PTA  *pta)
+{
+l_int32  n, i, x, y, minx, maxx, miny, maxy;
+
+    PROCNAME("ptaGetBoundingRegion");
+
+    if (!pta)
+        return (BOX *)ERROR_PTR("pta not defined", procName, NULL);
+
+    minx = 10000000;
+    miny = 10000000;
+    maxx = -10000000;
+    maxy = -10000000;
+    n = ptaGetCount(pta);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        if (x < minx) minx = x;
+        if (x > maxx) maxx = x;
+        if (y < miny) miny = y;
+        if (y > maxy) maxy = y;
+    }
+
+    return boxCreate(minx, miny, maxx - minx + 1, maxy - miny + 1);
+}
+
+
+/*!
+ *  ptaGetRange()
+ *
+ *      Input:  pta
+ *              &minx (<optional return> min value of x)
+ *              &maxx (<optional return> max value of x)
+ *              &miny (<optional return> min value of y)
+ *              &maxy (<optional return> max value of y)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) We can use pts to represent pairs of floating values, that
+ *          are not necessarily tied to a two-dimension region.  For
+ *          example, the pts can represent a general function y(x).
+ */
+l_int32
+ptaGetRange(PTA        *pta,
+            l_float32  *pminx,
+            l_float32  *pmaxx,
+            l_float32  *pminy,
+            l_float32  *pmaxy)
+{
+l_int32    n, i;
+l_float32  x, y, minx, maxx, miny, maxy;
+
+    PROCNAME("ptaGetRange");
+
+    if (!pminx && !pmaxx && !pminy && !pmaxy)
+        return ERROR_INT("no output requested", procName, 1);
+    if (pminx) *pminx = 0;
+    if (pmaxx) *pmaxx = 0;
+    if (pminy) *pminy = 0;
+    if (pmaxy) *pmaxy = 0;
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    if ((n = ptaGetCount(pta)) == 0)
+        return ERROR_INT("no points in pta", procName, 1);
+
+    ptaGetPt(pta, 0, &x, &y);
+    minx = x;
+    maxx = x;
+    miny = y;
+    maxy = y;
+    for (i = 1; i < n; i++) {
+        ptaGetPt(pta, i, &x, &y);
+        if (x < minx) minx = x;
+        if (x > maxx) maxx = x;
+        if (y < miny) miny = y;
+        if (y > maxy) maxy = y;
+    }
+    if (pminx) *pminx = minx;
+    if (pmaxx) *pmaxx = maxx;
+    if (pminy) *pminy = miny;
+    if (pmaxy) *pmaxy = maxy;
+    return 0;
+}
+
+
+/*!
+ *  ptaGetInsideBox()
+ *
+ *      Input:  ptas (input pts)
+ *              box
+ *      Return: ptad (of pts in ptas that are inside the box), or null on error
+ */
+PTA *
+ptaGetInsideBox(PTA  *ptas,
+                BOX  *box)
+{
+PTA       *ptad;
+l_int32    n, i, contains;
+l_float32  x, y;
+
+    PROCNAME("ptaGetInsideBox");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!box)
+        return (PTA *)ERROR_PTR("box not defined", procName, NULL);
+
+    n = ptaGetCount(ptas);
+    ptad = ptaCreate(0);
+    for (i = 0; i < n; i++) {
+        ptaGetPt(ptas, i, &x, &y);
+        boxContainsPt(box, x, y, &contains);
+        if (contains)
+            ptaAddPt(ptad, x, y);
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  pixFindCornerPixels()
+ *
+ *      Input:  pixs (1 bpp)
+ *      Return: pta, or null on error
+ *
+ *  Notes:
+ *      (1) Finds the 4 corner-most pixels, as defined by a search
+ *          inward from each corner, using a 45 degree line.
+ */
+PTA *
+pixFindCornerPixels(PIX  *pixs)
+{
+l_int32    i, j, x, y, w, h, wpl, mindim, found;
+l_uint32  *data, *line;
+PTA       *pta;
+
+    PROCNAME("pixFindCornerPixels");
+
+    if (!pixs)
+        return (PTA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PTA *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    mindim = L_MIN(w, h);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+
+    if ((pta = ptaCreate(4)) == NULL)
+        return (PTA *)ERROR_PTR("pta not made", procName, NULL);
+
+    for (found = FALSE, i = 0; i < mindim; i++) {
+        for (j = 0; j <= i; j++) {
+            y = i - j;
+            line = data + y * wpl;
+            if (GET_DATA_BIT(line, j)) {
+                ptaAddPt(pta, j, y);
+                found = TRUE;
+                break;
+            }
+        }
+        if (found == TRUE)
+            break;
+    }
+
+    for (found = FALSE, i = 0; i < mindim; i++) {
+        for (j = 0; j <= i; j++) {
+            y = i - j;
+            line = data + y * wpl;
+            x = w - 1 - j;
+            if (GET_DATA_BIT(line, x)) {
+                ptaAddPt(pta, x, y);
+                found = TRUE;
+                break;
+            }
+        }
+        if (found == TRUE)
+            break;
+    }
+
+    for (found = FALSE, i = 0; i < mindim; i++) {
+        for (j = 0; j <= i; j++) {
+            y = h - 1 - i + j;
+            line = data + y * wpl;
+            if (GET_DATA_BIT(line, j)) {
+                ptaAddPt(pta, j, y);
+                found = TRUE;
+                break;
+            }
+        }
+        if (found == TRUE)
+            break;
+    }
+
+    for (found = FALSE, i = 0; i < mindim; i++) {
+        for (j = 0; j <= i; j++) {
+            y = h - 1 - i + j;
+            line = data + y * wpl;
+            x = w - 1 - j;
+            if (GET_DATA_BIT(line, x)) {
+                ptaAddPt(pta, x, y);
+                found = TRUE;
+                break;
+            }
+        }
+        if (found == TRUE)
+            break;
+    }
+
+    return pta;
+}
+
+
+/*!
+ *  ptaContainsPt()
+ *
+ *      Input:  pta
+ *              x, y  (point)
+ *      Return: 1 if contained, 0 otherwise or on error
+ */
+l_int32
+ptaContainsPt(PTA     *pta,
+              l_int32  x,
+              l_int32  y)
+{
+l_int32  i, n, ix, iy;
+
+    PROCNAME("ptaContainsPt");
+
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 0);
+
+    n = ptaGetCount(pta);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &ix, &iy);
+        if (x == ix && y == iy)
+            return 1;
+    }
+    return 0;
+}
+
+
+/*!
+ *  ptaTestIntersection()
+ *
+ *      Input:  pta1, pta2
+ *      Return: bval which is 1 if they have any elements in common;
+ *              0 otherwise or on error.
+ */
+l_int32
+ptaTestIntersection(PTA  *pta1,
+                    PTA  *pta2)
+{
+l_int32  i, j, n1, n2, x1, y1, x2, y2;
+
+    PROCNAME("ptaTestIntersection");
+
+    if (!pta1)
+        return ERROR_INT("pta1 not defined", procName, 0);
+    if (!pta2)
+        return ERROR_INT("pta2 not defined", procName, 0);
+
+    n1 = ptaGetCount(pta1);
+    n2 = ptaGetCount(pta2);
+    for (i = 0; i < n1; i++) {
+        ptaGetIPt(pta1, i, &x1, &y1);
+        for (j = 0; j < n2; j++) {
+            ptaGetIPt(pta2, i, &x2, &y2);
+            if (x1 == x2 && y1 == y2)
+                return 1;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  ptaTransform()
+ *
+ *      Input:  pta
+ *              shiftx, shifty
+ *              scalex, scaley
+ *      Return: pta, or null on error
+ *
+ *  Notes:
+ *      (1) Shift first, then scale.
+ */
+PTA *
+ptaTransform(PTA       *ptas,
+             l_int32    shiftx,
+             l_int32    shifty,
+             l_float32  scalex,
+             l_float32  scaley)
+{
+l_int32  n, i, x, y;
+PTA     *ptad;
+
+    PROCNAME("ptaTransform");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+    n = ptaGetCount(ptas);
+    ptad = ptaCreate(n);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(ptas, i, &x, &y);
+        x = (l_int32)(scalex * (x + shiftx) + 0.5);
+        y = (l_int32)(scaley * (y + shifty) + 0.5);
+        ptaAddPt(ptad, x, y);
+    }
+
+    return ptad;
+}
+
+
+/*!
+ *  ptaPtInsidePolygon()
+ *
+ *      Input:  pta (vertices of a polygon)
+ *              x, y (point to be tested)
+ *              &inside (<return> 1 if inside; 0 if outside or on boundary)
+ *      Return: 1 if OK, 0 on error
+ *
+ *  The abs value of the sum of the angles subtended from a point by
+ *  the sides of a polygon, when taken in order traversing the polygon,
+ *  is 0 if the point is outside the polygon and 2*pi if inside.
+ *  The sign will be positive if traversed cw and negative if ccw.
+ */
+l_int32
+ptaPtInsidePolygon(PTA       *pta,
+                   l_float32  x,
+                   l_float32  y,
+                   l_int32   *pinside)
+{
+l_int32    i, n;
+l_float32  sum, x1, y1, x2, y2, xp1, yp1, xp2, yp2;
+
+    PROCNAME("ptaPtInsidePolygon");
+
+    if (!pinside)
+        return ERROR_INT("&inside not defined", procName, 1);
+    *pinside = 0;
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+
+        /* Think of (x1,y1) as the end point of a vector that starts
+         * from the origin (0,0), and ditto for (x2,y2). */
+    n = ptaGetCount(pta);
+    sum = 0.0;
+    for (i = 0; i < n; i++) {
+        ptaGetPt(pta, i, &xp1, &yp1);
+        ptaGetPt(pta, (i + 1) % n, &xp2, &yp2);
+        x1 = xp1 - x;
+        y1 = yp1 - y;
+        x2 = xp2 - x;
+        y2 = yp2 - y;
+        sum += l_angleBetweenVectors(x1, y1, x2, y2);
+    }
+
+    if (L_ABS(sum) > M_PI)
+        *pinside = 1;
+    return 0;
+}
+
+
+/*!
+ *  l_angleBetweenVectors()
+ *
+ *      Input:  x1, y1 (end point of first vector)
+ *              x2, y2 (end point of second vector)
+ *      Return: angle (radians), or 0.0 on error
+ *
+ *  Notes:
+ *      (1) This gives the angle between two vectors, going between
+ *          vector1 (x1,y1) and vector2 (x2,y2).  The angle is swept
+ *          out from 1 --> 2.  If this is clockwise, the angle is
+ *          positive, but the result is folded into the interval [-pi, pi].
+ */
+l_float32
+l_angleBetweenVectors(l_float32  x1,
+                      l_float32  y1,
+                      l_float32  x2,
+                      l_float32  y2)
+{
+l_float64  ang;
+
+    ang = atan2(y2, x2) - atan2(y1, x1);
+    if (ang > M_PI) ang -= 2.0 * M_PI;
+    if (ang < -M_PI) ang += 2.0 * M_PI;
+    return ang;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                            Least Squares Fit                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ptaGetLinearLSF()
+ *
+ *      Input:  pta
+ *              &a  (<optional return> slope a of least square fit: y = ax + b)
+ *              &b  (<optional return> intercept b of least square fit)
+ *              &nafit (<optional return> numa of least square fit)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Either or both &a and &b must be input.  They determine the
+ *          type of line that is fit.
+ *      (2) If both &a and &b are defined, this returns a and b that minimize:
+ *
+ *              sum (yi - axi -b)^2
+ *               i
+ *
+ *          The method is simple: differentiate this expression w/rt a and b,
+ *          and solve the resulting two equations for a and b in terms of
+ *          various sums over the input data (xi, yi).
+ *      (3) We also allow two special cases, where either a = 0 or b = 0:
+ *           (a) If &a is given and &b = null, find the linear LSF that
+ *               goes through the origin (b = 0).
+ *           (b) If &b is given and &a = null, find the linear LSF with
+ *               zero slope (a = 0).
+ *      (4) If @nafit is defined, this returns an array of fitted values,
+ *          corresponding to the two implicit Numa arrays (nax and nay) in pta.
+ *          Thus, just as you can plot the data in pta as nay vs. nax,
+ *          you can plot the linear least square fit as nafit vs. nax.
+ */
+l_int32
+ptaGetLinearLSF(PTA        *pta,
+                l_float32  *pa,
+                l_float32  *pb,
+                NUMA      **pnafit)
+{
+l_int32     n, i;
+l_float32   factor, sx, sy, sxx, sxy, val;
+l_float32  *xa, *ya;
+
+    PROCNAME("ptaGetLinearLSF");
+
+    if (!pa && !pb)
+        return ERROR_INT("neither &a nor &b are defined", procName, 1);
+    if (pa) *pa = 0.0;
+    if (pb) *pb = 0.0;
+    if (pnafit) *pnafit = NULL;
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    if ((n = ptaGetCount(pta)) < 2)
+        return ERROR_INT("less than 2 pts found", procName, 1);
+
+    xa = pta->x;  /* not a copy */
+    ya = pta->y;  /* not a copy */
+
+    sx = sy = sxx = sxy = 0.;
+    if (pa && pb) {  /* general line */
+        for (i = 0; i < n; i++) {
+            sx += xa[i];
+            sy += ya[i];
+            sxx += xa[i] * xa[i];
+            sxy += xa[i] * ya[i];
+        }
+        factor = n * sxx - sx * sx;
+        if (factor == 0.0)
+            return ERROR_INT("no solution found", procName, 1);
+        factor = 1. / factor;
+
+        *pa = factor * ((l_float32)n * sxy - sx * sy);
+        *pb = factor * (sxx * sy - sx * sxy);
+    } else if (pa) {  /* b = 0; line through origin */
+        for (i = 0; i < n; i++) {
+            sxx += xa[i] * xa[i];
+            sxy += xa[i] * ya[i];
+        }
+        if (sxx == 0.0)
+            return ERROR_INT("no solution found", procName, 1);
+        *pa = sxy / sxx;
+    } else {  /* a = 0; horizontal line */
+        for (i = 0; i < n; i++)
+            sy += ya[i];
+        *pb = sy / (l_float32)n;
+    }
+
+    if (pnafit) {
+        *pnafit = numaCreate(n);
+        for (i = 0; i < n; i++) {
+            val = (*pa) * xa[i] + *pb;
+            numaAddNumber(*pnafit, val);
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  ptaGetQuadraticLSF()
+ *
+ *      Input:  pta
+ *              &a  (<optional return> coeff a of LSF: y = ax^2 + bx + c)
+ *              &b  (<optional return> coeff b of LSF: y = ax^2 + bx + c)
+ *              &c  (<optional return> coeff c of LSF: y = ax^2 + bx + c)
+ *              &nafit (<optional return> numa of least square fit)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does a quadratic least square fit to the set of points
+ *          in @pta.  That is, it finds coefficients a, b and c that minimize:
+ *
+ *              sum (yi - a*xi*xi -b*xi -c)^2
+ *               i
+ *
+ *          The method is simple: differentiate this expression w/rt
+ *          a, b and c, and solve the resulting three equations for these
+ *          coefficients in terms of various sums over the input data (xi, yi).
+ *          The three equations are in the form:
+ *             f[0][0]a + f[0][1]b + f[0][2]c = g[0]
+ *             f[1][0]a + f[1][1]b + f[1][2]c = g[1]
+ *             f[2][0]a + f[2][1]b + f[2][2]c = g[2]
+ *      (2) If @nafit is defined, this returns an array of fitted values,
+ *          corresponding to the two implicit Numa arrays (nax and nay) in pta.
+ *          Thus, just as you can plot the data in pta as nay vs. nax,
+ *          you can plot the linear least square fit as nafit vs. nax.
+ */
+l_int32
+ptaGetQuadraticLSF(PTA        *pta,
+                   l_float32  *pa,
+                   l_float32  *pb,
+                   l_float32  *pc,
+                   NUMA      **pnafit)
+{
+l_int32     n, i, ret;
+l_float32   x, y, sx, sy, sx2, sx3, sx4, sxy, sx2y;
+l_float32  *xa, *ya;
+l_float32  *f[3];
+l_float32   g[3];
+NUMA       *nafit;
+
+    PROCNAME("ptaGetQuadraticLSF");
+
+    if (!pa && !pb && !pc && !pnafit)
+        return ERROR_INT("no output requested", procName, 1);
+    if (pa) *pa = 0.0;
+    if (pb) *pb = 0.0;
+    if (pc) *pc = 0.0;
+    if (pnafit) *pnafit = NULL;
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+
+    if ((n = ptaGetCount(pta)) < 3)
+        return ERROR_INT("less than 3 pts found", procName, 1);
+    xa = pta->x;  /* not a copy */
+    ya = pta->y;  /* not a copy */
+
+    sx = sy = sx2 = sx3 = sx4 = sxy = sx2y = 0.;
+    for (i = 0; i < n; i++) {
+        x = xa[i];
+        y = ya[i];
+        sx += x;
+        sy += y;
+        sx2 += x * x;
+        sx3 += x * x * x;
+        sx4 += x * x * x * x;
+        sxy += x * y;
+        sx2y += x * x * y;
+    }
+
+    for (i = 0; i < 3; i++)
+        f[i] = (l_float32 *)LEPT_CALLOC(3, sizeof(l_float32));
+    f[0][0] = sx4;
+    f[0][1] = sx3;
+    f[0][2] = sx2;
+    f[1][0] = sx3;
+    f[1][1] = sx2;
+    f[1][2] = sx;
+    f[2][0] = sx2;
+    f[2][1] = sx;
+    f[2][2] = n;
+    g[0] = sx2y;
+    g[1] = sxy;
+    g[2] = sy;
+
+        /* Solve for the unknowns, also putting f-inverse into f */
+    ret = gaussjordan(f, g, 3);
+    for (i = 0; i < 3; i++)
+        LEPT_FREE(f[i]);
+    if (ret)
+        return ERROR_INT("quadratic solution failed", procName, 1);
+
+    if (pa) *pa = g[0];
+    if (pb) *pb = g[1];
+    if (pc) *pc = g[2];
+    if (pnafit) {
+        nafit = numaCreate(n);
+        *pnafit = nafit;
+        for (i = 0; i < n; i++) {
+            x = xa[i];
+            y = g[0] * x * x + g[1] * x + g[2];
+            numaAddNumber(nafit, y);
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  ptaGetCubicLSF()
+ *
+ *      Input:  pta
+ *              &a  (<optional return> coeff a of LSF: y = ax^3 + bx^2 + cx + d)
+ *              &b  (<optional return> coeff b of LSF)
+ *              &c  (<optional return> coeff c of LSF)
+ *              &d  (<optional return> coeff d of LSF)
+ *              &nafit (<optional return> numa of least square fit)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does a cubic least square fit to the set of points
+ *          in @pta.  That is, it finds coefficients a, b, c and d
+ *          that minimize:
+ *
+ *              sum (yi - a*xi*xi*xi -b*xi*xi -c*xi - d)^2
+ *               i
+ *
+ *          Differentiate this expression w/rt a, b, c and d, and solve
+ *          the resulting four equations for these coefficients in
+ *          terms of various sums over the input data (xi, yi).
+ *          The four equations are in the form:
+ *             f[0][0]a + f[0][1]b + f[0][2]c + f[0][3] = g[0]
+ *             f[1][0]a + f[1][1]b + f[1][2]c + f[1][3] = g[1]
+ *             f[2][0]a + f[2][1]b + f[2][2]c + f[2][3] = g[2]
+ *             f[3][0]a + f[3][1]b + f[3][2]c + f[3][3] = g[3]
+ *      (2) If @nafit is defined, this returns an array of fitted values,
+ *          corresponding to the two implicit Numa arrays (nax and nay) in pta.
+ *          Thus, just as you can plot the data in pta as nay vs. nax,
+ *          you can plot the linear least square fit as nafit vs. nax.
+ */
+l_int32
+ptaGetCubicLSF(PTA        *pta,
+               l_float32  *pa,
+               l_float32  *pb,
+               l_float32  *pc,
+               l_float32  *pd,
+               NUMA      **pnafit)
+{
+l_int32     n, i, ret;
+l_float32   x, y, sx, sy, sx2, sx3, sx4, sx5, sx6, sxy, sx2y, sx3y;
+l_float32  *xa, *ya;
+l_float32  *f[4];
+l_float32   g[4];
+NUMA       *nafit;
+
+    PROCNAME("ptaGetCubicLSF");
+
+    if (!pa && !pb && !pc && !pd && !pnafit)
+        return ERROR_INT("no output requested", procName, 1);
+    if (pa) *pa = 0.0;
+    if (pb) *pb = 0.0;
+    if (pc) *pc = 0.0;
+    if (pd) *pd = 0.0;
+    if (pnafit) *pnafit = NULL;
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+
+    if ((n = ptaGetCount(pta)) < 4)
+        return ERROR_INT("less than 4 pts found", procName, 1);
+    xa = pta->x;  /* not a copy */
+    ya = pta->y;  /* not a copy */
+
+    sx = sy = sx2 = sx3 = sx4 = sx5 = sx6 = sxy = sx2y = sx3y = 0.;
+    for (i = 0; i < n; i++) {
+        x = xa[i];
+        y = ya[i];
+        sx += x;
+        sy += y;
+        sx2 += x * x;
+        sx3 += x * x * x;
+        sx4 += x * x * x * x;
+        sx5 += x * x * x * x * x;
+        sx6 += x * x * x * x * x * x;
+        sxy += x * y;
+        sx2y += x * x * y;
+        sx3y += x * x * x * y;
+    }
+
+    for (i = 0; i < 4; i++)
+        f[i] = (l_float32 *)LEPT_CALLOC(4, sizeof(l_float32));
+    f[0][0] = sx6;
+    f[0][1] = sx5;
+    f[0][2] = sx4;
+    f[0][3] = sx3;
+    f[1][0] = sx5;
+    f[1][1] = sx4;
+    f[1][2] = sx3;
+    f[1][3] = sx2;
+    f[2][0] = sx4;
+    f[2][1] = sx3;
+    f[2][2] = sx2;
+    f[2][3] = sx;
+    f[3][0] = sx3;
+    f[3][1] = sx2;
+    f[3][2] = sx;
+    f[3][3] = n;
+    g[0] = sx3y;
+    g[1] = sx2y;
+    g[2] = sxy;
+    g[3] = sy;
+
+        /* Solve for the unknowns, also putting f-inverse into f */
+    ret = gaussjordan(f, g, 4);
+    for (i = 0; i < 4; i++)
+        LEPT_FREE(f[i]);
+    if (ret)
+        return ERROR_INT("cubic solution failed", procName, 1);
+
+    if (pa) *pa = g[0];
+    if (pb) *pb = g[1];
+    if (pc) *pc = g[2];
+    if (pd) *pd = g[3];
+    if (pnafit) {
+        nafit = numaCreate(n);
+        *pnafit = nafit;
+        for (i = 0; i < n; i++) {
+            x = xa[i];
+            y = g[0] * x * x * x + g[1] * x * x + g[2] * x + g[3];
+            numaAddNumber(nafit, y);
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  ptaGetQuarticLSF()
+ *
+ *      Input:  pta
+ *              &a  (<optional return> coeff a of LSF:
+ *                        y = ax^4 + bx^3 + cx^2 + dx + e)
+ *              &b  (<optional return> coeff b of LSF)
+ *              &c  (<optional return> coeff c of LSF)
+ *              &d  (<optional return> coeff d of LSF)
+ *              &e  (<optional return> coeff e of LSF)
+ *              &nafit (<optional return> numa of least square fit)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does a quartic least square fit to the set of points
+ *          in @pta.  That is, it finds coefficients a, b, c, d and 3
+ *          that minimize:
+ *
+ *              sum (yi - a*xi*xi*xi*xi -b*xi*xi*xi -c*xi*xi - d*xi - e)^2
+ *               i
+ *
+ *          Differentiate this expression w/rt a, b, c, d and e, and solve
+ *          the resulting five equations for these coefficients in
+ *          terms of various sums over the input data (xi, yi).
+ *          The five equations are in the form:
+ *             f[0][0]a + f[0][1]b + f[0][2]c + f[0][3] + f[0][4] = g[0]
+ *             f[1][0]a + f[1][1]b + f[1][2]c + f[1][3] + f[1][4] = g[1]
+ *             f[2][0]a + f[2][1]b + f[2][2]c + f[2][3] + f[2][4] = g[2]
+ *             f[3][0]a + f[3][1]b + f[3][2]c + f[3][3] + f[3][4] = g[3]
+ *             f[4][0]a + f[4][1]b + f[4][2]c + f[4][3] + f[4][4] = g[4]
+ *      (2) If @nafit is defined, this returns an array of fitted values,
+ *          corresponding to the two implicit Numa arrays (nax and nay) in pta.
+ *          Thus, just as you can plot the data in pta as nay vs. nax,
+ *          you can plot the linear least square fit as nafit vs. nax.
+ */
+l_int32
+ptaGetQuarticLSF(PTA        *pta,
+                 l_float32  *pa,
+                 l_float32  *pb,
+                 l_float32  *pc,
+                 l_float32  *pd,
+                 l_float32  *pe,
+                 NUMA      **pnafit)
+{
+l_int32     n, i, ret;
+l_float32   x, y, sx, sy, sx2, sx3, sx4, sx5, sx6, sx7, sx8;
+l_float32   sxy, sx2y, sx3y, sx4y;
+l_float32  *xa, *ya;
+l_float32  *f[5];
+l_float32   g[5];
+NUMA       *nafit;
+
+    PROCNAME("ptaGetQuarticLSF");
+
+    if (!pa && !pb && !pc && !pd && !pe && !pnafit)
+        return ERROR_INT("no output requested", procName, 1);
+    if (pa) *pa = 0.0;
+    if (pb) *pb = 0.0;
+    if (pc) *pc = 0.0;
+    if (pd) *pd = 0.0;
+    if (pe) *pe = 0.0;
+    if (pnafit) *pnafit = NULL;
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+
+    if ((n = ptaGetCount(pta)) < 5)
+        return ERROR_INT("less than 5 pts found", procName, 1);
+    xa = pta->x;  /* not a copy */
+    ya = pta->y;  /* not a copy */
+
+    sx = sy = sx2 = sx3 = sx4 = sx5 = sx6 = sx7 = sx8 = 0;
+    sxy = sx2y = sx3y = sx4y = 0.;
+    for (i = 0; i < n; i++) {
+        x = xa[i];
+        y = ya[i];
+        sx += x;
+        sy += y;
+        sx2 += x * x;
+        sx3 += x * x * x;
+        sx4 += x * x * x * x;
+        sx5 += x * x * x * x * x;
+        sx6 += x * x * x * x * x * x;
+        sx7 += x * x * x * x * x * x * x;
+        sx8 += x * x * x * x * x * x * x * x;
+        sxy += x * y;
+        sx2y += x * x * y;
+        sx3y += x * x * x * y;
+        sx4y += x * x * x * x * y;
+    }
+
+    for (i = 0; i < 5; i++)
+        f[i] = (l_float32 *)LEPT_CALLOC(5, sizeof(l_float32));
+    f[0][0] = sx8;
+    f[0][1] = sx7;
+    f[0][2] = sx6;
+    f[0][3] = sx5;
+    f[0][4] = sx4;
+    f[1][0] = sx7;
+    f[1][1] = sx6;
+    f[1][2] = sx5;
+    f[1][3] = sx4;
+    f[1][4] = sx3;
+    f[2][0] = sx6;
+    f[2][1] = sx5;
+    f[2][2] = sx4;
+    f[2][3] = sx3;
+    f[2][4] = sx2;
+    f[3][0] = sx5;
+    f[3][1] = sx4;
+    f[3][2] = sx3;
+    f[3][3] = sx2;
+    f[3][4] = sx;
+    f[4][0] = sx4;
+    f[4][1] = sx3;
+    f[4][2] = sx2;
+    f[4][3] = sx;
+    f[4][4] = n;
+    g[0] = sx4y;
+    g[1] = sx3y;
+    g[2] = sx2y;
+    g[3] = sxy;
+    g[4] = sy;
+
+        /* Solve for the unknowns, also putting f-inverse into f */
+    ret = gaussjordan(f, g, 5);
+    for (i = 0; i < 5; i++)
+        LEPT_FREE(f[i]);
+    if (ret)
+        return ERROR_INT("quartic solution failed", procName, 1);
+
+    if (pa) *pa = g[0];
+    if (pb) *pb = g[1];
+    if (pc) *pc = g[2];
+    if (pd) *pd = g[3];
+    if (pe) *pe = g[4];
+    if (pnafit) {
+        nafit = numaCreate(n);
+        *pnafit = nafit;
+        for (i = 0; i < n; i++) {
+            x = xa[i];
+            y = g[0] * x * x * x * x + g[1] * x * x * x + g[2] * x * x
+                 + g[3] * x + g[4];
+            numaAddNumber(nafit, y);
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  ptaNoisyLinearLSF()
+ *
+ *      Input:  pta
+ *              factor (reject outliers with error greater than this
+ *                      number of medians; typically ~ 3)
+ *              &ptad (<optional return> with outliers removed)
+ *              &a  (<optional return> slope a of least square fit: y = ax + b)
+ *              &b  (<optional return> intercept b of least square fit)
+ *              &mederr (<optional return> median error)
+ *              &nafit (<optional return> numa of least square fit to ptad)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does a linear least square fit to the set of points
+ *          in @pta.  It then evaluates the errors and removes points
+ *          whose error is >= factor * median_error.  It then re-runs
+ *          the linear LSF on the resulting points.
+ *      (2) Either or both &a and &b must be input.  They determine the
+ *          type of line that is fit.
+ *      (3) The median error can give an indication of how good the fit
+ *          is likely to be.
+ */
+l_int32
+ptaNoisyLinearLSF(PTA        *pta,
+                  l_float32   factor,
+                  PTA       **pptad,
+                  l_float32  *pa,
+                  l_float32  *pb,
+                  l_float32  *pmederr,
+                  NUMA      **pnafit)
+{
+l_int32    n, i, ret;
+l_float32  x, y, yf, val, mederr;
+NUMA      *nafit, *naerror;
+PTA       *ptad;
+
+    PROCNAME("ptaNoisyLinearLSF");
+
+    if (!pa && !pb)
+        return ERROR_INT("neither &a nor &b are defined", procName, 1);
+    if (pptad) *pptad = NULL;
+    if (pa) *pa = 0.0;
+    if (pb) *pb = 0.0;
+    if (pmederr) *pmederr = 0.0;
+    if (pnafit) *pnafit = NULL;
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    if (factor <= 0.0)
+        return ERROR_INT("factor must be > 0.0", procName, 1);
+    if ((n = ptaGetCount(pta)) < 3)
+        return ERROR_INT("less than 2 pts found", procName, 1);
+
+    if (ptaGetLinearLSF(pta, pa, pb, &nafit) != 0)
+        return ERROR_INT("error in linear LSF", procName, 1);
+
+        /* Get the median error */
+    naerror = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        ptaGetPt(pta, i, &x, &y);
+        numaGetFValue(nafit, i, &yf);
+        numaAddNumber(naerror, L_ABS(y - yf));
+    }
+    numaGetMedian(naerror, &mederr);
+    if (pmederr) *pmederr = mederr;
+    numaDestroy(&nafit);
+
+        /* Remove outliers */
+    ptad = ptaCreate(n);
+    for (i = 0; i < n; i++) {
+        ptaGetPt(pta, i, &x, &y);
+        numaGetFValue(naerror, i, &val);
+        if (val <= factor * mederr)  /* <= in case mederr = 0 */
+            ptaAddPt(ptad, x, y);
+    }
+    numaDestroy(&naerror);
+
+       /* Do LSF again */
+    ret = ptaGetLinearLSF(ptad, pa, pb, pnafit);
+    if (pptad)
+        *pptad = ptad;
+    else
+        ptaDestroy(&ptad);
+
+    return ret;
+}
+
+
+/*!
+ *  ptaNoisyQuadraticLSF()
+ *
+ *      Input:  pta
+ *              factor (reject outliers with error greater than this
+ *                      number of medians; typically ~ 3)
+ *              &ptad (<optional return> with outliers removed)
+ *              &a  (<optional return> coeff a of LSF: y = ax^2 + bx + c)
+ *              &b  (<optional return> coeff b of LSF: y = ax^2 + bx + c)
+ *              &c  (<optional return> coeff c of LSF: y = ax^2 + bx + c)
+ *              &mederr (<optional return> median error)
+ *              &nafit (<optional return> numa of least square fit to ptad)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does a quadratic least square fit to the set of points
+ *          in @pta.  It then evaluates the errors and removes points
+ *          whose error is >= factor * median_error.  It then re-runs
+ *          a quadratic LSF on the resulting points.
+ */
+l_int32
+ptaNoisyQuadraticLSF(PTA        *pta,
+                     l_float32   factor,
+                     PTA       **pptad,
+                     l_float32  *pa,
+                     l_float32  *pb,
+                     l_float32  *pc,
+                     l_float32  *pmederr,
+                     NUMA      **pnafit)
+{
+l_int32    n, i, ret;
+l_float32  x, y, yf, val, mederr;
+NUMA      *nafit, *naerror;
+PTA       *ptad;
+
+    PROCNAME("ptaNoisyQuadraticLSF");
+
+    if (!pptad && !pa && !pb && !pc && !pnafit)
+        return ERROR_INT("no output requested", procName, 1);
+    if (pptad) *pptad = NULL;
+    if (pa) *pa = 0.0;
+    if (pb) *pb = 0.0;
+    if (pc) *pc = 0.0;
+    if (pmederr) *pmederr = 0.0;
+    if (pnafit) *pnafit = NULL;
+    if (factor <= 0.0)
+        return ERROR_INT("factor must be > 0.0", procName, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    if ((n = ptaGetCount(pta)) < 3)
+        return ERROR_INT("less than 3 pts found", procName, 1);
+
+    if (ptaGetQuadraticLSF(pta, NULL, NULL, NULL, &nafit) != 0)
+        return ERROR_INT("error in quadratic LSF", procName, 1);
+
+        /* Get the median error */
+    naerror = numaCreate(n);
+    for (i = 0; i < n; i++) {
+        ptaGetPt(pta, i, &x, &y);
+        numaGetFValue(nafit, i, &yf);
+        numaAddNumber(naerror, L_ABS(y - yf));
+    }
+    numaGetMedian(naerror, &mederr);
+    if (pmederr) *pmederr = mederr;
+    numaDestroy(&nafit);
+
+        /* Remove outliers */
+    ptad = ptaCreate(n);
+    for (i = 0; i < n; i++) {
+        ptaGetPt(pta, i, &x, &y);
+        numaGetFValue(naerror, i, &val);
+        if (val <= factor * mederr)  /* <= in case mederr = 0 */
+            ptaAddPt(ptad, x, y);
+    }
+    numaDestroy(&naerror);
+    n = ptaGetCount(ptad);
+    if ((n = ptaGetCount(ptad)) < 3) {
+        ptaDestroy(&ptad);
+        return ERROR_INT("less than 3 pts found", procName, 1);
+    }
+
+       /* Do LSF again */
+    ret = ptaGetQuadraticLSF(ptad, pa, pb, pc, pnafit);
+    if (pptad)
+        *pptad = ptad;
+    else
+        ptaDestroy(&ptad);
+
+    return ret;
+}
+
+
+/*!
+ *  applyLinearFit()
+ *
+ *      Input: a, b (linear fit coefficients)
+ *             x
+ *             &y (<return> y = a * x + b)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+applyLinearFit(l_float32   a,
+                  l_float32   b,
+                  l_float32   x,
+                  l_float32  *py)
+{
+    PROCNAME("applyLinearFit");
+
+    if (!py)
+        return ERROR_INT("&y not defined", procName, 1);
+
+    *py = a * x + b;
+    return 0;
+}
+
+
+/*!
+ *  applyQuadraticFit()
+ *
+ *      Input: a, b, c (quadratic fit coefficients)
+ *             x
+ *             &y (<return> y = a * x^2 + b * x + c)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+applyQuadraticFit(l_float32   a,
+                  l_float32   b,
+                  l_float32   c,
+                  l_float32   x,
+                  l_float32  *py)
+{
+    PROCNAME("applyQuadraticFit");
+
+    if (!py)
+        return ERROR_INT("&y not defined", procName, 1);
+
+    *py = a * x * x + b * x + c;
+    return 0;
+}
+
+
+/*!
+ *  applyCubicFit()
+ *
+ *      Input: a, b, c, d (cubic fit coefficients)
+ *             x
+ *             &y (<return> y = a * x^3 + b * x^2  + c * x + d)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+applyCubicFit(l_float32   a,
+              l_float32   b,
+              l_float32   c,
+              l_float32   d,
+              l_float32   x,
+              l_float32  *py)
+{
+    PROCNAME("applyCubicFit");
+
+    if (!py)
+        return ERROR_INT("&y not defined", procName, 1);
+
+    *py = a * x * x * x + b * x * x + c * x + d;
+    return 0;
+}
+
+
+/*!
+ *  applyQuarticFit()
+ *
+ *      Input: a, b, c, d, e (quartic fit coefficients)
+ *             x
+ *             &y (<return> y = a * x^4 + b * x^3  + c * x^2 + d * x + e)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+applyQuarticFit(l_float32   a,
+                l_float32   b,
+                l_float32   c,
+                l_float32   d,
+                l_float32   e,
+                l_float32   x,
+                l_float32  *py)
+{
+l_float32  x2;
+
+    PROCNAME("applyQuarticFit");
+
+    if (!py)
+        return ERROR_INT("&y not defined", procName, 1);
+
+    x2 = x * x;
+    *py = a * x2 * x2 + b * x2 * x + c * x2 + d * x + e;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Interconversions with Pix                    *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixPlotAlongPta()
+ *
+ *      Input: pixs (any depth)
+ *             pta (set of points on which to plot)
+ *             outformat (GPLOT_PNG, GPLOT_PS, GPLOT_EPS, GPLOT_X11,
+ *                        GPLOT_LATEX)
+ *             title (<optional> for plot; can be null)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) We remove any existing colormap and clip the pta to the input pixs.
+ *      (2) This is a debugging function, and does not remove temporary
+ *          plotting files that it generates.
+ *      (3) If the image is RGB, three separate plots are generated.
+ */
+l_int32
+pixPlotAlongPta(PIX         *pixs,
+                PTA         *pta,
+                l_int32      outformat,
+                const char  *title)
+{
+char            buffer[128];
+char           *rtitle, *gtitle, *btitle;
+static l_int32  count = 0;  /* require separate temp files for each call */
+l_int32         i, x, y, d, w, h, npts, rval, gval, bval;
+l_uint32        val;
+NUMA           *na, *nar, *nag, *nab;
+PIX            *pixt;
+
+    PROCNAME("pixPlotAlongPta");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pta)
+        return ERROR_INT("pta not defined", procName, 1);
+    if (outformat != GPLOT_PNG && outformat != GPLOT_PS &&
+        outformat != GPLOT_EPS && outformat != GPLOT_X11 &&
+        outformat != GPLOT_LATEX) {
+        L_WARNING("outformat invalid; using GPLOT_PNG\n", procName);
+        outformat = GPLOT_PNG;
+    }
+
+    pixt = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    d = pixGetDepth(pixt);
+    w = pixGetWidth(pixt);
+    h = pixGetHeight(pixt);
+    npts = ptaGetCount(pta);
+    if (d == 32) {
+        nar = numaCreate(npts);
+        nag = numaCreate(npts);
+        nab = numaCreate(npts);
+        for (i = 0; i < npts; i++) {
+            ptaGetIPt(pta, i, &x, &y);
+            if (x < 0 || x >= w)
+                continue;
+            if (y < 0 || y >= h)
+                continue;
+            pixGetPixel(pixt, x, y, &val);
+            rval = GET_DATA_BYTE(&val, COLOR_RED);
+            gval = GET_DATA_BYTE(&val, COLOR_GREEN);
+            bval = GET_DATA_BYTE(&val, COLOR_BLUE);
+            numaAddNumber(nar, rval);
+            numaAddNumber(nag, gval);
+            numaAddNumber(nab, bval);
+        }
+
+        sprintf(buffer, "/tmp/junkplot.%d", count++);
+        rtitle = stringJoin("Red: ", title);
+        gplotSimple1(nar, outformat, buffer, rtitle);
+        sprintf(buffer, "/tmp/junkplot.%d", count++);
+        gtitle = stringJoin("Green: ", title);
+        gplotSimple1(nag, outformat, buffer, gtitle);
+        sprintf(buffer, "/tmp/junkplot.%d", count++);
+        btitle = stringJoin("Blue: ", title);
+        gplotSimple1(nab, outformat, buffer, btitle);
+        numaDestroy(&nar);
+        numaDestroy(&nag);
+        numaDestroy(&nab);
+        LEPT_FREE(rtitle);
+        LEPT_FREE(gtitle);
+        LEPT_FREE(btitle);
+    } else {
+        na = numaCreate(npts);
+        for (i = 0; i < npts; i++) {
+            ptaGetIPt(pta, i, &x, &y);
+            if (x < 0 || x >= w)
+                continue;
+            if (y < 0 || y >= h)
+                continue;
+            pixGetPixel(pixt, x, y, &val);
+            numaAddNumber(na, (l_float32)val);
+        }
+
+        sprintf(buffer, "/tmp/junkplot.%d", count++);
+        gplotSimple1(na, outformat, buffer, title);
+        numaDestroy(&na);
+    }
+    pixDestroy(&pixt);
+    return 0;
+}
+
+
+/*!
+ *  ptaGetPixelsFromPix()
+ *
+ *      Input:  pixs (1 bpp)
+ *              box (<optional> can be null)
+ *      Return: pta, or null on error
+ *
+ *  Notes:
+ *      (1) Generates a pta of fg pixels in the pix, within the box.
+ *          If box == NULL, it uses the entire pix.
+ */
+PTA *
+ptaGetPixelsFromPix(PIX  *pixs,
+                    BOX  *box)
+{
+l_int32    i, j, w, h, wpl, xstart, xend, ystart, yend, bw, bh;
+l_uint32  *data, *line;
+PTA       *pta;
+
+    PROCNAME("ptaGetPixelsFromPix");
+
+    if (!pixs || (pixGetDepth(pixs) != 1))
+        return (PTA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    xstart = ystart = 0;
+    xend = w - 1;
+    yend = h - 1;
+    if (box) {
+        boxGetGeometry(box, &xstart, &ystart, &bw, &bh);
+        xend = xstart + bw - 1;
+        yend = ystart + bh - 1;
+    }
+
+    if ((pta = ptaCreate(0)) == NULL)
+        return (PTA *)ERROR_PTR("pta not made", procName, NULL);
+    for (i = ystart; i <= yend; i++) {
+        line = data + i * wpl;
+        for (j = xstart; j <= xend; j++) {
+            if (GET_DATA_BIT(line, j))
+                ptaAddPt(pta, j, i);
+        }
+    }
+
+    return pta;
+}
+
+
+/*!
+ *  pixGenerateFromPta()
+ *
+ *      Input:  pta
+ *              w, h (of pix)
+ *      Return: pix (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) Points are rounded to nearest ints.
+ *      (2) Any points outside (w,h) are silently discarded.
+ *      (3) Output 1 bpp pix has values 1 for each point in the pta.
+ */
+PIX *
+pixGenerateFromPta(PTA     *pta,
+                   l_int32  w,
+                   l_int32  h)
+{
+l_int32  n, i, x, y;
+PIX     *pix;
+
+    PROCNAME("pixGenerateFromPta");
+
+    if (!pta)
+        return (PIX *)ERROR_PTR("pta not defined", procName, NULL);
+
+    if ((pix = pixCreate(w, h, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pix not made", procName, NULL);
+    n = ptaGetCount(pta);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        if (x < 0 || x >= w || y < 0 || y >= h)
+            continue;
+        pixSetPixel(pix, x, y, 1);
+    }
+
+    return pix;
+}
+
+
+/*!
+ *  ptaGetBoundaryPixels()
+ *
+ *      Input:  pixs (1 bpp)
+ *              type (L_BOUNDARY_FG, L_BOUNDARY_BG)
+ *      Return: pta, or null on error
+ *
+ *  Notes:
+ *      (1) This generates a pta of either fg or bg boundary pixels.
+ *      (2) See also pixGeneratePtaBoundary() for rendering of
+ *          fg boundary pixels.
+ */
+PTA *
+ptaGetBoundaryPixels(PIX     *pixs,
+                     l_int32  type)
+{
+PIX  *pixt;
+PTA  *pta;
+
+    PROCNAME("ptaGetBoundaryPixels");
+
+    if (!pixs || (pixGetDepth(pixs) != 1))
+        return (PTA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (type != L_BOUNDARY_FG && type != L_BOUNDARY_BG)
+        return (PTA *)ERROR_PTR("invalid type", procName, NULL);
+
+    if (type == L_BOUNDARY_FG)
+        pixt = pixMorphSequence(pixs, "e3.3", 0);
+    else
+        pixt = pixMorphSequence(pixs, "d3.3", 0);
+    pixXor(pixt, pixt, pixs);
+    pta = ptaGetPixelsFromPix(pixt, NULL);
+
+    pixDestroy(&pixt);
+    return pta;
+}
+
+
+/*!
+ *  ptaaGetBoundaryPixels()
+ *
+ *      Input:  pixs (1 bpp)
+ *              type (L_BOUNDARY_FG, L_BOUNDARY_BG)
+ *              connectivity (4 or 8)
+ *              &boxa (<optional return> bounding boxes of the c.c.)
+ *              &pixa (<optional return> pixa of the c.c.)
+ *      Return: ptaa, or null on error
+ *
+ *  Notes:
+ *      (1) This generates a ptaa of either fg or bg boundary pixels,
+ *          where each pta has the boundary pixels for a connected
+ *          component.
+ *      (2) We can't simply find all the boundary pixels and then select
+ *          those within the bounding box of each component, because
+ *          bounding boxes can overlap.  It is necessary to extract and
+ *          dilate or erode each component separately.  Note also that
+ *          special handling is required for bg pixels when the
+ *          component touches the pix boundary.
+ */
+PTAA *
+ptaaGetBoundaryPixels(PIX     *pixs,
+                      l_int32  type,
+                      l_int32  connectivity,
+                      BOXA   **pboxa,
+                      PIXA   **ppixa)
+{
+l_int32  i, n, w, h, x, y, bw, bh, left, right, top, bot;
+BOXA    *boxa;
+PIX     *pixt1, *pixt2;
+PIXA    *pixa;
+PTA     *pta1, *pta2;
+PTAA    *ptaa;
+
+    PROCNAME("ptaaGetBoundaryPixels");
+
+    if (pboxa) *pboxa = NULL;
+    if (ppixa) *ppixa = NULL;
+    if (!pixs || (pixGetDepth(pixs) != 1))
+        return (PTAA *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (type != L_BOUNDARY_FG && type != L_BOUNDARY_BG)
+        return (PTAA *)ERROR_PTR("invalid type", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PTAA *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    boxa = pixConnComp(pixs, &pixa, connectivity);
+    n = boxaGetCount(boxa);
+    ptaa = ptaaCreate(0);
+    for (i = 0; i < n; i++) {
+        pixt1 = pixaGetPix(pixa, i, L_CLONE);
+        boxaGetBoxGeometry(boxa, i, &x, &y, &bw, &bh);
+        left = right = top = bot = 0;
+        if (type == L_BOUNDARY_BG) {
+            if (x > 0) left = 1;
+            if (y > 0) top = 1;
+            if (x + bw < w) right = 1;
+            if (y + bh < h) bot = 1;
+            pixt2 = pixAddBorderGeneral(pixt1, left, right, top, bot, 0);
+        } else {
+            pixt2 = pixClone(pixt1);
+        }
+        pta1 = ptaGetBoundaryPixels(pixt2, type);
+        pta2 = ptaTransform(pta1, x - left, y - top, 1.0, 1.0);
+        ptaaAddPta(ptaa, pta2, L_INSERT);
+        ptaDestroy(&pta1);
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt2);
+    }
+
+    if (pboxa)
+        *pboxa = boxa;
+    else
+        boxaDestroy(&boxa);
+    if (ppixa)
+        *ppixa = pixa;
+    else
+        pixaDestroy(&pixa);
+    return ptaa;
+}
+
+
+/*!
+ *  ptaaIndexLabelledPixels()
+ *
+ *      Input:  pixs (32 bpp, of indices of c.c.)
+ *              &ncc (<optional return> number of connected components)
+ *      Return: ptaa, or null on error
+ *
+ *  Notes:
+ *      (1) The pixel values in @pixs are the index of the connected component
+ *          to which the pixel belongs; @pixs is typically generated from
+ *          a 1 bpp pix by pixConnCompTransform().  Background pixels in
+ *          the generating 1 bpp pix are represented in @pixs by 0.
+ *          We do not check that the pixel values are correctly labelled.
+ *      (2) Each pta in the returned ptaa gives the pixel locations
+ *          correspnding to a connected component, with the label of each
+ *          given by the index of the pta into the ptaa.
+ *      (3) Initialize with the first pta in ptaa being empty and
+ *          representing the background value (index 0) in the pix.
+ */
+PTAA *
+ptaaIndexLabelledPixels(PIX      *pixs,
+                        l_int32  *pncc)
+{
+l_int32    wpl, index, i, j, w, h;
+l_uint32   maxval;
+l_uint32  *data, *line;
+PTA       *pta;
+PTAA      *ptaa;
+
+    PROCNAME("ptaaIndexLabelledPixels");
+
+    if (pncc) *pncc = 0;
+    if (!pixs || (pixGetDepth(pixs) != 32))
+        return (PTAA *)ERROR_PTR("pixs undef or not 32 bpp", procName, NULL);
+
+        /* The number of c.c. is the maximum pixel value.  Use this to
+         * initialize ptaa with sufficient pta arrays */
+    pixGetMaxValueInRect(pixs, NULL, &maxval, NULL, NULL);
+    if (pncc) *pncc = maxval;
+    pta = ptaCreate(1);
+    ptaa = ptaaCreate(maxval + 1);
+    ptaaInitFull(ptaa, pta);
+    ptaDestroy(&pta);
+
+        /* Sweep over @pixs, saving the pixel coordinates of each pixel
+         * with nonzero value in the appropriate pta, indexed by that value. */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    data = pixGetData(pixs);
+    wpl = pixGetWpl(pixs);
+    for (i = 0; i < h; i++) {
+        line = data + wpl * i;
+        for (j = 0; j < w; j++) {
+            index = line[j];
+            if (index > 0)
+                ptaaAddPt(ptaa, index, j, i);
+        }
+    }
+
+    return ptaa;
+}
+
+
+/*!
+ *  ptaGetNeighborPixLocs()
+ *
+ *      Input:  pixs (any depth)
+ *              x, y (pixel from which we search for nearest neighbors
+ *              conn (4 or 8 connectivity)
+ *      Return: pta, or null on error
+ *
+ *  Notes:
+ *      (1) Generates a pta of all valid neighbor pixel locations,
+ *          or null on error.
+ */
+PTA *
+ptaGetNeighborPixLocs(PIX  *pixs,
+                      l_int32  x,
+                      l_int32  y,
+                      l_int32  conn)
+{
+l_int32  w, h;
+PTA     *pta;
+
+    PROCNAME("ptaGetNeighborPixLocs");
+
+    if (!pixs)
+        return (PTA *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (x < 0 || x >= w || y < 0 || y >= h)
+        return (PTA *)ERROR_PTR("(x,y) not in pixs", procName, NULL);
+    if (conn != 4 && conn != 8)
+        return (PTA *)ERROR_PTR("conn not 4 or 8", procName, NULL);
+
+    pta = ptaCreate(conn);
+    if (x > 0)
+        ptaAddPt(pta, x - 1, y);
+    if (x < w - 1)
+        ptaAddPt(pta, x + 1, y);
+    if (y > 0)
+        ptaAddPt(pta, x, y - 1);
+    if (y < h - 1)
+        ptaAddPt(pta, x, y + 1);
+    if (conn == 8) {
+        if (x > 0) {
+            if (y > 0)
+                ptaAddPt(pta, x - 1, y - 1);
+            if (y < h - 1)
+                ptaAddPt(pta, x - 1, y + 1);
+        }
+        if (x < w - 1) {
+            if (y > 0)
+                ptaAddPt(pta, x + 1, y - 1);
+            if (y < h - 1)
+                ptaAddPt(pta, x + 1, y + 1);
+        }
+    }
+
+    return pta;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                          Display Pta and Ptaa                       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixDisplayPta()
+ *
+ *      Input:  pixd (can be same as pixs or null; 32 bpp if in-place)
+ *              pixs (1, 2, 4, 8, 16 or 32 bpp)
+ *              pta (of path to be plotted)
+ *      Return: pixd (32 bpp RGB version of pixs, with path in green).
+ *
+ *  Notes:
+ *      (1) To write on an existing pixs, pixs must be 32 bpp and
+ *          call with pixd == pixs:
+ *             pixDisplayPta(pixs, pixs, pta);
+ *          To write to a new pix, use pixd == NULL and call:
+ *             pixd = pixDisplayPta(NULL, pixs, pta);
+ *      (2) On error, returns pixd to avoid losing pixs if called as
+ *             pixs = pixDisplayPta(pixs, pixs, pta);
+ */
+PIX *
+pixDisplayPta(PIX  *pixd,
+              PIX  *pixs,
+              PTA  *pta)
+{
+l_int32   i, n, w, h, x, y;
+l_uint32  rpixel, gpixel, bpixel;
+
+    PROCNAME("pixDisplayPta");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (!pta)
+        return (PIX *)ERROR_PTR("pta not defined", procName, pixd);
+    if (pixd && (pixd != pixs || pixGetDepth(pixd) != 32))
+        return (PIX *)ERROR_PTR("invalid pixd", procName, pixd);
+
+    if (!pixd)
+        pixd = pixConvertTo32(pixs);
+    pixGetDimensions(pixd, &w, &h, NULL);
+    composeRGBPixel(255, 0, 0, &rpixel);  /* start point */
+    composeRGBPixel(0, 255, 0, &gpixel);
+    composeRGBPixel(0, 0, 255, &bpixel);  /* end point */
+
+    n = ptaGetCount(pta);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        if (x < 0 || x >= w || y < 0 || y >= h)
+            continue;
+        if (i == 0)
+            pixSetPixel(pixd, x, y, rpixel);
+        else if (i < n - 1)
+            pixSetPixel(pixd, x, y, gpixel);
+        else
+            pixSetPixel(pixd, x, y, bpixel);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixDisplayPtaaPattern()
+ *
+ *      Input:  pixd (32 bpp)
+ *              pixs (1, 2, 4, 8, 16 or 32 bpp; 32 bpp if in place)
+ *              ptaa (giving locations at which the pattern is displayed)
+ *              pixp (1 bpp pattern to be placed such that its reference
+ *                    point co-locates with each point in pta)
+ *              cx, cy (reference point in pattern)
+ *      Return: pixd (32 bpp RGB version of pixs).
+ *
+ *  Notes:
+ *      (1) To write on an existing pixs, pixs must be 32 bpp and
+ *          call with pixd == pixs:
+ *             pixDisplayPtaPattern(pixs, pixs, pta, ...);
+ *          To write to a new pix, use pixd == NULL and call:
+ *             pixd = pixDisplayPtaPattern(NULL, pixs, pta, ...);
+ *      (2) Puts a random color on each pattern associated with a pta.
+ *      (3) On error, returns pixd to avoid losing pixs if called as
+ *             pixs = pixDisplayPtaPattern(pixs, pixs, pta, ...);
+ *      (4) A typical pattern to be used is a circle, generated with
+ *             generatePtaFilledCircle()
+ */
+PIX *
+pixDisplayPtaaPattern(PIX      *pixd,
+                      PIX      *pixs,
+                      PTAA     *ptaa,
+                      PIX      *pixp,
+                      l_int32   cx,
+                      l_int32   cy)
+{
+l_int32   i, n;
+l_uint32  color;
+PIXCMAP  *cmap;
+PTA      *pta;
+
+    PROCNAME("pixDisplayPtaaPattern");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (!ptaa)
+        return (PIX *)ERROR_PTR("ptaa not defined", procName, pixd);
+    if (pixd && (pixd != pixs || pixGetDepth(pixd) != 32))
+        return (PIX *)ERROR_PTR("invalid pixd", procName, pixd);
+    if (!pixp)
+        return (PIX *)ERROR_PTR("pixp not defined", procName, pixd);
+
+    if (!pixd)
+        pixd = pixConvertTo32(pixs);
+
+        /* Use 256 random colors */
+    cmap = pixcmapCreateRandom(8, 0, 0);
+    n = ptaaGetCount(ptaa);
+    for (i = 0; i < n; i++) {
+        pixcmapGetColor32(cmap, i % 256, &color);
+        pta = ptaaGetPta(ptaa, i, L_CLONE);
+        pixDisplayPtaPattern(pixd, pixd, pta, pixp, cx, cy, color);
+        ptaDestroy(&pta);
+    }
+
+    pixcmapDestroy(&cmap);
+    return pixd;
+}
+
+
+/*!
+ *  pixDisplayPtaPattern()
+ *
+ *      Input:  pixd (can be same as pixs or null; 32 bpp if in-place)
+ *              pixs (1, 2, 4, 8, 16 or 32 bpp)
+ *              pta (giving locations at which the pattern is displayed)
+ *              pixp (1 bpp pattern to be placed such that its reference
+ *                    point co-locates with each point in pta)
+ *              cx, cy (reference point in pattern)
+ *              color (in 0xrrggbb00 format)
+ *      Return: pixd (32 bpp RGB version of pixs).
+ *
+ *  Notes:
+ *      (1) To write on an existing pixs, pixs must be 32 bpp and
+ *          call with pixd == pixs:
+ *             pixDisplayPtaPattern(pixs, pixs, pta, ...);
+ *          To write to a new pix, use pixd == NULL and call:
+ *             pixd = pixDisplayPtaPattern(NULL, pixs, pta, ...);
+ *      (2) On error, returns pixd to avoid losing pixs if called as
+ *             pixs = pixDisplayPtaPattern(pixs, pixs, pta, ...);
+ *      (3) A typical pattern to be used is a circle, generated with
+ *             generatePtaFilledCircle()
+ */
+PIX *
+pixDisplayPtaPattern(PIX      *pixd,
+                     PIX      *pixs,
+                     PTA      *pta,
+                     PIX      *pixp,
+                     l_int32   cx,
+                     l_int32   cy,
+                     l_uint32  color)
+{
+l_int32  i, n, w, h, x, y;
+PTA     *ptat;
+
+    PROCNAME("pixDisplayPtaPattern");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (!pta)
+        return (PIX *)ERROR_PTR("pta not defined", procName, pixd);
+    if (pixd && (pixd != pixs || pixGetDepth(pixd) != 32))
+        return (PIX *)ERROR_PTR("invalid pixd", procName, pixd);
+    if (!pixp)
+        return (PIX *)ERROR_PTR("pixp not defined", procName, pixd);
+
+    if (!pixd)
+        pixd = pixConvertTo32(pixs);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    ptat = ptaReplicatePattern(pta, pixp, NULL, cx, cy, w, h);
+
+    n = ptaGetCount(ptat);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(ptat, i, &x, &y);
+        if (x < 0 || x >= w || y < 0 || y >= h)
+            continue;
+        pixSetPixel(pixd, x, y, color);
+    }
+
+    ptaDestroy(&ptat);
+    return pixd;
+}
+
+
+/*!
+ *  ptaReplicatePattern()
+ *
+ *      Input:  ptas ("sparse" input pta)
+ *              pixp (<optional> 1 bpp pattern, to be replicated in output pta)
+ *              ptap (<optional> set of pts, to be replicated in output pta)
+ *              cx, cy (reference point in pattern)
+ *              w, h (clipping sizes for output pta)
+ *      Return: ptad (with all points of replicated pattern), or null on error
+ *
+ *  Notes:
+ *      (1) You can use either the image @pixp or the set of pts @ptap.
+ *      (2) The pattern is placed with its reference point at each point
+ *          in ptas, and all the fg pixels are colleced into ptad.
+ *          For @pixp, this is equivalent to blitting pixp at each point
+ *          in ptas, and then converting the resulting pix to a pta.
+ */
+PTA *
+ptaReplicatePattern(PTA     *ptas,
+                    PIX     *pixp,
+                    PTA     *ptap,
+                    l_int32  cx,
+                    l_int32  cy,
+                    l_int32  w,
+                    l_int32  h)
+{
+l_int32  i, j, n, np, x, y, xp, yp, xf, yf;
+PTA     *ptat, *ptad;
+
+    PROCNAME("ptaReplicatePattern");
+
+    if (!ptas)
+        return (PTA *)ERROR_PTR("ptas not defined", procName, NULL);
+    if (!pixp && !ptap)
+        return (PTA *)ERROR_PTR("no pattern is defined", procName, NULL);
+    if (pixp && ptap)
+        L_WARNING("pixp and ptap defined; using ptap\n", procName);
+
+    n = ptaGetCount(ptas);
+    ptad = ptaCreate(n);
+    if (ptap)
+        ptat = ptaClone(ptap);
+    else
+        ptat = ptaGetPixelsFromPix(pixp, NULL);
+    np = ptaGetCount(ptat);
+    for (i = 0; i < n; i++) {
+        ptaGetIPt(ptas, i, &x, &y);
+        for (j = 0; j < np; j++) {
+            ptaGetIPt(ptat, j, &xp, &yp);
+            xf = x - cx + xp;
+            yf = y - cy + yp;
+            if (xf >= 0 && xf < w && yf >= 0 && yf < h)
+                ptaAddPt(ptad, xf, yf);
+        }
+    }
+
+    ptaDestroy(&ptat);
+    return ptad;
+}
+
+
+/*!
+ *  pixDisplayPtaa()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 or 32 bpp)
+ *              ptaa (array of paths to be plotted)
+ *      Return: pixd (32 bpp RGB version of pixs, with paths plotted
+ *                    in different colors), or null on error
+ */
+PIX *
+pixDisplayPtaa(PIX   *pixs,
+               PTAA  *ptaa)
+{
+l_int32    i, j, w, h, npta, npt, x, y, rv, gv, bv;
+l_uint32  *pixela;
+NUMA      *na1, *na2, *na3;
+PIX       *pixd;
+PTA       *pta;
+
+    PROCNAME("pixDisplayPtaa");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (!ptaa)
+        return (PIX *)ERROR_PTR("ptaa not defined", procName, NULL);
+    npta = ptaaGetCount(ptaa);
+    if (npta == 0)
+        return (PIX *)ERROR_PTR("no pta", procName, NULL);
+
+    if ((pixd = pixConvertTo32(pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixGetDimensions(pixd, &w, &h, NULL);
+
+        /* Make a colormap for the paths */
+    if ((pixela = (l_uint32 *)LEPT_CALLOC(npta, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("calloc fail for pixela", procName, NULL);
+    na1 = numaPseudorandomSequence(256, 14657);
+    na2 = numaPseudorandomSequence(256, 34631);
+    na3 = numaPseudorandomSequence(256, 54617);
+    for (i = 0; i < npta; i++) {
+        numaGetIValue(na1, i % 256, &rv);
+        numaGetIValue(na2, i % 256, &gv);
+        numaGetIValue(na3, i % 256, &bv);
+        composeRGBPixel(rv, gv, bv, &pixela[i]);
+    }
+    numaDestroy(&na1);
+    numaDestroy(&na2);
+    numaDestroy(&na3);
+
+    for (i = 0; i < npta; i++) {
+        pta = ptaaGetPta(ptaa, i, L_CLONE);
+        npt = ptaGetCount(pta);
+        for (j = 0; j < npt; j++) {
+            ptaGetIPt(pta, j, &x, &y);
+            if (x < 0 || x >= w || y < 0 || y >= h)
+                continue;
+            pixSetPixel(pixd, x, y, pixela[i]);
+        }
+        ptaDestroy(&pta);
+    }
+
+    LEPT_FREE(pixela);
+    return pixd;
+}
diff --git a/src/ptra.c b/src/ptra.c
new file mode 100644 (file)
index 0000000..1cd9897
--- /dev/null
@@ -0,0 +1,975 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   ptra.c
+ *
+ *      Ptra creation and destruction
+ *          L_PTRA      *ptraCreate()
+ *          void        *ptraDestroy()
+ *
+ *      Add/insert/remove/replace generic ptr object
+ *          l_int32      ptraAdd()
+ *          static l_int32  ptraExtendArray()
+ *          l_int32      ptraInsert()
+ *          void        *ptraRemove()
+ *          void        *ptraRemoveLast()
+ *          void        *ptraReplace()
+ *          l_int32      ptraSwap()
+ *          l_int32      ptraCompactArray()
+ *
+ *      Other array operations
+ *          l_int32      ptraReverse()
+ *          l_int32      ptraJoin()
+ *
+ *      Simple Ptra accessors
+ *          l_int32      ptraGetMaxIndex()
+ *          l_int32      ptraGetActualCount()
+ *          void        *ptraGetPtrToItem()
+ *
+ *      Ptraa creation and destruction
+ *          L_PTRAA     *ptraaCreate()
+ *          void        *ptraaDestroy()
+ *
+ *      Ptraa accessors
+ *          l_int32      ptraaGetSize()
+ *          l_int32      ptraaInsertPtra()
+ *          L_PTRA      *ptraaGetPtra()
+ *
+ *      Ptraa conversion
+ *          L_PTRA      *ptraaFlattenToPtra()
+ *
+ *    Notes on the Ptra:
+ *
+ *    (1) The Ptra is a struct, not an array.  Always use the accessors
+ *        in this file, never the fields directly.
+ *    (2) Items can be placed anywhere in the allocated ptr array,
+ *        including one index beyond the last ptr (in which case the
+ *        ptr array is realloc'd).
+ *    (3) Thus, the items on the ptr array need not be compacted.  In
+ *        general there will be null pointers in the ptr array.
+ *    (4) A compacted array will remain compacted on removal if
+ *        arbitrary items are removed with compaction, or if items
+ *        are removed from the end of the array.
+ *    (5) For addition to and removal from the end of the array, this
+ *        functions exactly like a stack, and with the same O(1) cost.
+ *    (6) This differs from the generic stack in that we allow
+ *        random access for insertion, removal and replacement.
+ *        Removal can be done without compacting the array.
+ *        Insertion into a null ptr in the array has no effect on
+ *        the other pointers, but insertion into a location already
+ *        occupied by an item has a cost proportional to the
+ *        distance to the next null ptr in the array.
+ *    (7) Null ptrs are valid input args for both insertion and
+ *        replacement; this allows arbitrary swapping.
+ *    (8) The item in the array with the largest index is at pa->imax.
+ *        This can be any value from -1 (initialized; all array ptrs
+ *        are null) up to pa->nalloc - 1 (the last ptr in the array).
+ *    (9) In referring to the array: the first ptr is the "top" or
+ *        "beginning"; the last pointer is the "bottom" or "end";
+ *        items are shifted "up" towards the top when compaction occurs;
+ *        and items are shifted "down" towards the bottom when forced to
+ *        move due to an insertion.
+ *   (10) It should be emphasized that insertion, removal and replacement
+ *        are general:
+ *         * You can insert an item into any ptr location in the
+ *           allocated ptr array, as well as into the next ptr address
+ *           beyond the allocated array (in which case a realloc will occur).
+ *         * You can remove or replace an item from any ptr location
+ *           in the allocated ptr array.
+ *         * When inserting into an occupied location, you have
+ *           three options for downshifting.
+ *         * When removing, you can either leave the ptr null or
+ *           compact the array.
+ *
+ *    Notes on the Ptraa:
+ *
+ *    (1) The Ptraa is a fixed size ptr array for holding Ptra.
+ *        In that respect, it is different from other pointer arrays, which
+ *        are extensible and grow using the *Add*() functions.
+ *    (2) In general, the Ptra ptrs in the Ptraa can be randomly occupied.
+ *        A typical usage is to allow an O(n) horizontal sort of Pix,
+ *        where the size of the Ptra array is the width of the image,
+ *        and each Ptra is an array of all the Pix at a specific x location.
+ */
+
+#include "allheaders.h"
+
+static const l_int32 INITIAL_PTR_ARRAYSIZE = 20;      /* n'importe quoi */
+
+    /* Static function */
+static l_int32 ptraExtendArray(L_PTRA *pa);
+
+
+/*--------------------------------------------------------------------------*
+ *                       Ptra creation and destruction                      *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  ptraCreate()
+ *
+ *      Input:  size of ptr array to be alloc'd (0 for default)
+ *      Return: pa, or null on error
+ */
+L_PTRA *
+ptraCreate(l_int32  n)
+{
+L_PTRA  *pa;
+
+    PROCNAME("ptraCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((pa = (L_PTRA *)LEPT_CALLOC(1, sizeof(L_PTRA))) == NULL)
+        return (L_PTRA *)ERROR_PTR("pa not made", procName, NULL);
+    if ((pa->array = (void **)LEPT_CALLOC(n, sizeof(void *))) == NULL)
+        return (L_PTRA *)ERROR_PTR("ptr array not made", procName, NULL);
+
+    pa->nalloc = n;
+    pa->imax = -1;
+    pa->nactual = 0;
+
+    return pa;
+}
+
+
+/*!
+ *  ptraDestroy()
+ *
+ *      Input:  &ptra (<to be nulled>)
+ *              freeflag (TRUE to free each remaining item in the array)
+ *              warnflag (TRUE to warn if any remaining items are not destroyed)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) If @freeflag == TRUE, frees each item in the array.
+ *      (2) If @freeflag == FALSE and warnflag == TRUE, and there are
+ *          items on the array, this gives a warning and destroys the array.
+ *          If these items are not owned elsewhere, this will cause
+ *          a memory leak of all the items that were on the array.
+ *          So if the items are not owned elsewhere and require their
+ *          own destroy function, they must be destroyed before the ptra.
+ *      (3) If warnflag == FALSE, no warnings will be issued.  This is
+ *          useful if the items are owned elsewhere, such as a
+ *          PixMemoryStore().
+ *      (4) To destroy the ptra, we destroy the ptr array, then
+ *          the ptra, and then null the contents of the input ptr.
+ */
+void
+ptraDestroy(L_PTRA  **ppa,
+            l_int32   freeflag,
+            l_int32   warnflag)
+{
+l_int32  i, nactual;
+void    *item;
+L_PTRA  *pa;
+
+    PROCNAME("ptraDestroy");
+
+    if (ppa == NULL) {
+        L_WARNING("ptr address is NULL\n", procName);
+        return;
+    }
+    if ((pa = *ppa) == NULL)
+        return;
+
+    ptraGetActualCount(pa, &nactual);
+    if (nactual > 0) {
+        if (freeflag) {
+            for (i = 0; i <= pa->imax; i++) {
+                if ((item = ptraRemove(pa, i, L_NO_COMPACTION)) != NULL)
+                    LEPT_FREE(item);
+            }
+        } else if (warnflag) {
+            L_WARNING("potential memory leak of %d items in ptra\n",
+                      procName, nactual);
+        }
+    }
+
+    LEPT_FREE(pa->array);
+    LEPT_FREE(pa);
+    *ppa = NULL;
+    return;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *               Add/insert/remove/replace generic ptr object               *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  ptraAdd()
+ *
+ *      Input:  ptra
+ *              item  (generic ptr to a struct)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This adds the element to the next location beyond imax,
+ *          which is the largest occupied ptr in the array.  This is
+ *          what you expect from a stack, where all ptrs up to and
+ *          including imax are occupied, but here the occuption of
+ *          items in the array is entirely arbitrary.
+ */
+l_int32
+ptraAdd(L_PTRA  *pa,
+        void    *item)
+{
+l_int32  imax;
+
+    PROCNAME("ptraAdd");
+
+    if (!pa)
+        return ERROR_INT("pa not defined", procName, 1);
+    if (!item)
+        return ERROR_INT("item not defined", procName, 1);
+
+    ptraGetMaxIndex(pa, &imax);
+    if (imax >= pa->nalloc - 1 && ptraExtendArray(pa))
+        return ERROR_INT("extension failure", procName, 1);
+    pa->array[imax + 1] = (void *)item;
+    pa->imax++;
+    pa->nactual++;
+    return 0;
+}
+
+
+/*!
+ *  ptraExtendArray()
+ *
+ *      Input:  ptra
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+ptraExtendArray(L_PTRA  *pa)
+{
+    PROCNAME("ptraExtendArray");
+
+    if (!pa)
+        return ERROR_INT("pa not defined", procName, 1);
+
+    if ((pa->array = (void **)reallocNew((void **)&pa->array,
+                                sizeof(void *) * pa->nalloc,
+                                2 * sizeof(void *) * pa->nalloc)) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+
+    pa->nalloc *= 2;
+    return 0;
+}
+
+
+/*!
+ *  ptraInsert()
+ *
+ *      Input:  ptra
+ *              index (location in ptra to insert new value)
+ *              item  (generic ptr to a struct; can be null)
+ *              shiftflag (L_AUTO_DOWNSHIFT, L_MIN_DOWNSHIFT, L_FULL_DOWNSHIFT)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This checks first to see if the location is valid, and
+ *          then if there is presently an item there.  If there is not,
+ *          it is simply inserted into that location.
+ *      (2) If there is an item at the insert location, items must be
+ *          moved down to make room for the insert.  In the downward
+ *          shift there are three options, given by @shiftflag.
+ *            - If @shiftflag == L_AUTO_DOWNSHIFT, a decision is made
+ *              whether, in a cascade of items, to downshift a minimum
+ *              amount or for all items above @index.  The decision is
+ *              based on the expectation of finding holes (null ptrs)
+ *              between @index and the bottom of the array.
+ *              Assuming the holes are distributed uniformly, if 2 or more
+ *              holes are expected, we do a minimum shift.
+ *            - If @shiftflag == L_MIN_DOWNSHIFT, the downward shifting
+ *              cascade of items progresses a minimum amount, until
+ *              the first empty slot is reached.  This mode requires
+ *              some computation before the actual shifting is done.
+ *            - If @shiftflag == L_FULL_DOWNSHIFT, a shifting cascade is
+ *              performed where pa[i] --> pa[i + 1] for all i >= index.
+ *              Then, the item is inserted at pa[index].
+ *      (3) If you are not using L_AUTO_DOWNSHIFT, the rule of thumb is
+ *          to use L_FULL_DOWNSHIFT if the array is compacted (each
+ *          element points to an item), and to use L_MIN_DOWNSHIFT
+ *          if there are a significant number of null pointers.
+ *          There is no penalty to using L_MIN_DOWNSHIFT for a
+ *          compacted array, however, because the full shift is required
+ *          and we don't do the O(n) computation to look for holes.
+ *      (4) This should not be used repeatedly on large arrays,
+ *          because the function is generally O(n).
+ *      (5) However, it can be used repeatedly if we start with an empty
+ *          ptr array and insert only once at each location.  For example,
+ *          you can support an array of Numa, where at each ptr location
+ *          you store either 0 or 1 Numa, and the Numa can be added
+ *          randomly to the ptr array.
+ */
+l_int32
+ptraInsert(L_PTRA  *pa,
+           l_int32  index,
+           void    *item,
+           l_int32  shiftflag)
+{
+l_int32    i, ihole, imax;
+l_float32  nexpected;
+
+    PROCNAME("ptraInsert");
+
+    if (!pa)
+        return ERROR_INT("pa not defined", procName, 1);
+    if (index < 0 || index > pa->nalloc)
+        return ERROR_INT("index not in [0 ... nalloc]", procName, 1);
+    if (shiftflag != L_AUTO_DOWNSHIFT && shiftflag != L_MIN_DOWNSHIFT &&
+        shiftflag != L_FULL_DOWNSHIFT)
+        return ERROR_INT("invalid shiftflag", procName, 1);
+
+    if (item) pa->nactual++;
+    if (index == pa->nalloc) {  /* can happen when index == n */
+        if (ptraExtendArray(pa))
+            return ERROR_INT("extension failure", procName, 1);
+    }
+
+        /* We are inserting into a hole or adding to the end of the array.
+         * No existing items are moved. */
+    ptraGetMaxIndex(pa, &imax);
+    if (pa->array[index] == NULL) {
+        pa->array[index] = item;
+        if (item && index > imax)  /* new item put beyond max so far */
+            pa->imax = index;
+        return 0;
+    }
+
+        /* We are inserting at the location of an existing item,
+         * forcing the existing item and those below to shift down.
+         * First, extend the array automatically if the last element
+         * (nalloc - 1) is occupied (imax).  This may not be necessary
+         * in every situation, but only an anomalous sequence of insertions
+         * into the array would cause extra ptr allocation.  */
+    if (imax >= pa->nalloc - 1 && ptraExtendArray(pa))
+        return ERROR_INT("extension failure", procName, 1);
+
+        /* If there are no holes, do a full downshift.
+         * Otherwise, if L_AUTO_DOWNSHIFT, use the expected number
+         * of holes between index and n to determine the shift mode */
+    if (imax + 1 == pa->nactual) {
+        shiftflag = L_FULL_DOWNSHIFT;
+    } else if (shiftflag == L_AUTO_DOWNSHIFT) {
+        if (imax < 10) {
+            shiftflag = L_FULL_DOWNSHIFT;  /* no big deal */
+        } else {
+            nexpected = (l_float32)(imax - pa->nactual) *
+                         (l_float32)((imax - index) / imax);
+            shiftflag = (nexpected > 2.0) ? L_MIN_DOWNSHIFT : L_FULL_DOWNSHIFT;
+        }
+    }
+
+    if (shiftflag == L_MIN_DOWNSHIFT) {  /* run down looking for a hole */
+        for (ihole = index + 1; ihole <= imax; ihole++) {
+             if (pa->array[ihole] == NULL)
+                 break;
+        }
+    } else {  /* L_FULL_DOWNSHIFT */
+        ihole = imax + 1;
+    }
+
+    for (i = ihole; i > index; i--)
+        pa->array[i] = pa->array[i - 1];
+    pa->array[index] = (void *)item;
+    if (ihole == imax + 1)  /* the last item was shifted down */
+        pa->imax++;
+
+    return 0;
+}
+
+
+/*!
+ *  ptraRemove()
+ *
+ *      Input:  ptra
+ *              index (element to be removed)
+ *              flag (L_NO_COMPACTION, L_COMPACTION)
+ *      Return: item, or null on error
+ *
+ *  Notes:
+ *      (1) If flag == L_NO_COMPACTION, this removes the item and
+ *          nulls the ptr on the array.  If it takes the last item
+ *          in the array, pa->n is reduced to the next item.
+ *      (2) If flag == L_COMPACTION, this compacts the array for
+ *          for all i >= index.  It should not be used repeatedly on
+ *          large arrays, because compaction is O(n).
+ *      (3) The ability to remove without automatic compaction allows
+ *          removal with cost O(1).
+ */
+void *
+ptraRemove(L_PTRA  *pa,
+           l_int32  index,
+           l_int32  flag)
+{
+l_int32  i, imax, fromend, icurrent;
+void    *item;
+
+    PROCNAME("ptraRemove");
+
+    if (!pa)
+        return (void *)ERROR_PTR("pa not defined", procName, NULL);
+    ptraGetMaxIndex(pa, &imax);
+    if (index < 0 || index > imax)
+        return (void *)ERROR_PTR("index not in [0 ... imax]", procName, NULL);
+
+    item = pa->array[index];
+    if (item)
+        pa->nactual--;
+    pa->array[index] = NULL;
+
+        /* If we took the last item, need to reduce pa->n */
+    fromend = (index == imax);
+    if (fromend) {
+        for (i = index - 1; i >= 0; i--) {
+            if (pa->array[i])
+                break;
+        }
+        pa->imax = i;
+    }
+
+        /* Compact from index to the end of the array */
+    if (!fromend && flag == L_COMPACTION) {
+        for (icurrent = index, i = index + 1; i <= imax; i++) {
+            if (pa->array[i])
+                pa->array[icurrent++] = pa->array[i];
+        }
+        pa->imax = icurrent - 1;
+    }
+    return item;
+}
+
+
+/*!
+ *  ptraRemoveLast()
+ *
+ *      Input:  ptra
+ *      Return: item, or null on error or if the array is empty
+ */
+void *
+ptraRemoveLast(L_PTRA  *pa)
+{
+l_int32  imax;
+
+    PROCNAME("ptraRemoveLast");
+
+    if (!pa)
+        return (void *)ERROR_PTR("pa not defined", procName, NULL);
+
+        /* Remove the last item in the array.  No compaction is required. */
+    ptraGetMaxIndex(pa, &imax);
+    if (imax >= 0)
+        return ptraRemove(pa, imax, L_NO_COMPACTION);
+    else  /* empty */
+        return NULL;
+}
+
+
+/*!
+ *  ptraReplace()
+ *
+ *      Input:  ptra
+ *              index (element to be replaced)
+ *              item  (new generic ptr to a struct; can be null)
+ *              freeflag (TRUE to free old item; FALSE to return it)
+ *      Return: item  (old item, if it exists and is not freed),
+ *                     or null on error
+ */
+void *
+ptraReplace(L_PTRA  *pa,
+            l_int32  index,
+            void    *item,
+            l_int32  freeflag)
+{
+l_int32  imax;
+void    *olditem;
+
+    PROCNAME("ptraReplace");
+
+    if (!pa)
+        return (void *)ERROR_PTR("pa not defined", procName, NULL);
+    ptraGetMaxIndex(pa, &imax);
+    if (index < 0 || index > imax)
+        return (void *)ERROR_PTR("index not in [0 ... imax]", procName, NULL);
+
+    olditem = pa->array[index];
+    pa->array[index] = item;
+    if (!item && olditem)
+        pa->nactual--;
+    else if (item && !olditem)
+        pa->nactual++;
+
+    if (freeflag == FALSE)
+        return olditem;
+
+    if (olditem)
+        LEPT_FREE(olditem);
+    return NULL;
+}
+
+
+/*!
+ *  ptraSwap()
+ *
+ *      Input:  ptra
+ *              index1
+ *              index2
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+ptraSwap(L_PTRA  *pa,
+         l_int32  index1,
+         l_int32  index2)
+{
+l_int32  imax;
+void    *item;
+
+    PROCNAME("ptraSwap");
+
+    if (!pa)
+        return ERROR_INT("pa not defined", procName, 1);
+    if (index1 == index2)
+        return 0;
+    ptraGetMaxIndex(pa, &imax);
+    if (index1 < 0 || index1 > imax || index2 < 0 || index2 > imax)
+        return ERROR_INT("invalid index: not in [0 ... imax]", procName, 1);
+
+    item = ptraRemove(pa, index1, L_NO_COMPACTION);
+    item = ptraReplace(pa, index2, item, FALSE);
+    ptraInsert(pa, index1, item, L_MIN_DOWNSHIFT);
+    return 0;
+}
+
+
+/*!
+ *  ptraCompactArray()
+ *
+ *      Input:  ptra
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This compacts the items on the array, filling any empty ptrs.
+ *      (2) This does not change the size of the array of ptrs.
+ */
+l_int32
+ptraCompactArray(L_PTRA  *pa)
+{
+l_int32  i, imax, nactual, index;
+
+    PROCNAME("ptraCompactArray");
+
+    if (!pa)
+        return ERROR_INT("pa not defined", procName, 1);
+    ptraGetMaxIndex(pa, &imax);
+    ptraGetActualCount(pa, &nactual);
+    if (imax + 1 == nactual) return 0;
+
+        /* Compact the array */
+    for (i = 0, index = 0; i <= imax; i++) {
+        if (pa->array[i])
+             pa->array[index++] = pa->array[i];
+    }
+    pa->imax = index - 1;
+    if (nactual != index)
+        L_ERROR("index = %d; != nactual\n", procName, index);
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                        Other array operations                        *
+ *----------------------------------------------------------------------*/
+/*!
+ *  ptraReverse()
+ *
+ *      Input:  ptra
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+ptraReverse(L_PTRA  *pa)
+{
+l_int32  i, imax;
+
+    PROCNAME("ptraReverse");
+
+    if (!pa)
+        return ERROR_INT("pa not defined", procName, 1);
+    ptraGetMaxIndex(pa, &imax);
+
+    for (i = 0; i < (imax + 1) / 2; i++)
+        ptraSwap(pa, i, imax - i);
+    return 0;
+}
+
+
+/*!
+ *  ptraJoin()
+ *
+ *      Input:  ptra1 (add to this one)
+ *              ptra2 (appended to ptra1, and emptied of items; can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+ptraJoin(L_PTRA  *pa1,
+         L_PTRA  *pa2)
+{
+l_int32  i, imax;
+void    *item;
+
+    PROCNAME("ptraJoin");
+
+    if (!pa1)
+        return ERROR_INT("pa1 not defined", procName, 1);
+    if (!pa2)
+        return 0;
+
+    ptraGetMaxIndex(pa2, &imax);
+    for (i = 0; i <= imax; i++) {
+        item = ptraRemove(pa2, i, L_NO_COMPACTION);
+        ptraAdd(pa1, item);
+    }
+
+    return 0;
+}
+
+
+
+/*----------------------------------------------------------------------*
+ *                        Simple ptra accessors                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  ptraGetMaxIndex()
+ *
+ *      Input:  ptra
+ *              &maxindex (<return> index of last item in the array);
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The largest index to an item in the array is @maxindex.
+ *          @maxindex is one less than the number of items that would be
+ *          in the array if there were no null pointers between 0
+ *          and @maxindex - 1.  However, because the internal ptr array
+ *          need not be compacted, there may be null pointers at
+ *          indices below @maxindex; for example, if items have
+ *          been removed.
+ *      (2) When an item is added to the end of the array, it goes
+ *          into pa->array[maxindex + 1], and maxindex is then
+ *          incremented by 1.
+ *      (3) If there are no items in the array, this returns @maxindex = -1.
+ */
+l_int32
+ptraGetMaxIndex(L_PTRA   *pa,
+                l_int32  *pmaxindex)
+{
+    PROCNAME("ptraGetMaxIndex");
+
+    if (!pa)
+        return ERROR_INT("pa not defined", procName, 1);
+    if (!pmaxindex)
+        return ERROR_INT("&maxindex not defined", procName, 1);
+    *pmaxindex = pa->imax;
+    return 0;
+}
+
+
+/*!
+ *  ptraGetActualCount()
+ *
+ *      Input:  ptra
+ *              &count (<return> actual number of items on the ptr array)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The actual number of items on the ptr array, pa->nactual,
+ *          will be smaller than pa->n if the array is not compacted.
+ */
+l_int32
+ptraGetActualCount(L_PTRA   *pa,
+                   l_int32  *pcount)
+{
+    PROCNAME("ptraGetActualCount");
+
+    if (!pa)
+        return ERROR_INT("pa not defined", procName, 1);
+    if (!pcount)
+        return ERROR_INT("&count not defined", procName, 1);
+    *pcount = pa->nactual;
+
+    return 0;
+}
+
+
+/*!
+ *  ptraGetPtrToItem()
+ *
+ *      Input:  ptra
+ *              index (of element to be retrieved)
+ *      Return: a ptr to the element, or null on error
+ *
+ *  Notes:
+ *      (1) This returns a ptr to the item.  You must cast it to
+ *          the type of item.  Do not destroy it; the item belongs
+ *          to the Ptra.
+ *      (2) This can access all possible items on the ptr array.
+ *          If an item doesn't exist, it returns null.
+ */
+void *
+ptraGetPtrToItem(L_PTRA  *pa,
+                 l_int32  index)
+{
+    PROCNAME("ptraGetPtrToItem");
+
+    if (!pa)
+        return (void *)ERROR_PTR("pa not defined", procName, NULL);
+    if (index < 0 || index >= pa->nalloc)
+        return (void *)ERROR_PTR("index not in [0 ... nalloc-1]",
+                                 procName, NULL);
+
+    return pa->array[index];
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                      Ptraa creation and destruction                      *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  ptraaCreate()
+ *
+ *      Input:  size of ptr array to be alloc'd
+ *      Return: paa, or null on error
+ *
+ *  Notes:
+ *      (1) The ptraa is generated with a fixed size, that can not change.
+ *          The ptra can be generated and inserted randomly into this array.
+ */
+L_PTRAA *
+ptraaCreate(l_int32  n)
+{
+L_PTRAA  *paa;
+
+    PROCNAME("ptraaCreate");
+
+    if (n <= 0)
+        return (L_PTRAA *)ERROR_PTR("n must be > 0", procName, NULL);
+
+    if ((paa = (L_PTRAA *)LEPT_CALLOC(1, sizeof(L_PTRAA))) == NULL)
+        return (L_PTRAA *)ERROR_PTR("paa not made", procName, NULL);
+    if ((paa->ptra = (L_PTRA **)LEPT_CALLOC(n, sizeof(L_PTRA *))) == NULL)
+        return (L_PTRAA *)ERROR_PTR("ptr array not made", procName, NULL);
+
+    paa->nalloc = n;
+    return paa;
+}
+
+
+/*!
+ *  ptraaDestroy()
+ *
+ *      Input:  &paa (<to be nulled>)
+ *              freeflag (TRUE to free each remaining item in each ptra)
+ *              warnflag (TRUE to warn if any remaining items are not destroyed)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) See ptraDestroy() for use of @freeflag and @warnflag.
+ *      (2) To destroy the ptraa, we destroy each ptra, then the ptr array,
+ *          then the ptraa, and then null the contents of the input ptr.
+ */
+void
+ptraaDestroy(L_PTRAA  **ppaa,
+             l_int32    freeflag,
+             l_int32    warnflag)
+{
+l_int32   i, n;
+L_PTRA   *pa;
+L_PTRAA  *paa;
+
+    PROCNAME("ptraaDestroy");
+
+    if (ppaa == NULL) {
+        L_WARNING("ptr address is NULL\n", procName);
+        return;
+    }
+    if ((paa = *ppaa) == NULL)
+        return;
+
+    ptraaGetSize(paa, &n);
+    for (i = 0; i < n; i++) {
+        pa = ptraaGetPtra(paa, i, L_REMOVE);
+        ptraDestroy(&pa, freeflag, warnflag);
+    }
+
+    LEPT_FREE(paa->ptra);
+    LEPT_FREE(paa);
+    *ppaa = NULL;
+    return;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                             Ptraa accessors                              *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  ptraaGetSize()
+ *
+ *      Input:  ptraa
+ *              &size (<return> size of ptr array)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+ptraaGetSize(L_PTRAA  *paa,
+             l_int32  *psize)
+{
+    PROCNAME("ptraaGetSize");
+
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1);
+    *psize = paa->nalloc;
+
+    return 0;
+}
+
+
+/*!
+ *  ptraaInsertPtra()
+ *
+ *      Input:  ptraa
+ *              index (location in array for insertion)
+ *              ptra (to be inserted)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Caller should check return value.  On success, the Ptra
+ *          is inserted in the Ptraa and is owned by it.  However,
+ *          on error, the Ptra remains owned by the caller.
+ */
+l_int32
+ptraaInsertPtra(L_PTRAA  *paa,
+                l_int32   index,
+                L_PTRA   *pa)
+{
+l_int32  n;
+
+    PROCNAME("ptraaInsertPtra");
+
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+    if (!pa)
+        return ERROR_INT("pa not defined", procName, 1);
+    ptraaGetSize(paa, &n);
+    if (index < 0 || index >= n)
+        return ERROR_INT("invalid index", procName, 1);
+    if (paa->ptra[index] != NULL)
+        return ERROR_INT("ptra alread stored at index", procName, 1);
+
+    paa->ptra[index] = pa;
+    return 0;
+}
+
+
+/*!
+ *  ptraaGetPtra()
+ *
+ *      Input:  ptraa
+ *              index (location in array)
+ *              accessflag (L_HANDLE_ONLY, L_REMOVE)
+ *      Return: ptra (at index location), or NULL on error or if there
+ *              is no ptra there.
+ *
+ *  Notes:
+ *      (1) This returns the ptra ptr.  If @accessflag == L_HANDLE_ONLY,
+ *          the ptra is left on the ptraa.  If @accessflag == L_REMOVE,
+ *          the ptr in the ptraa is set to NULL, and the caller
+ *          is responsible for disposing of the ptra (either putting it
+ *          back on the ptraa, or destroying it).
+ *      (2) This returns NULL if there is no Ptra at the index location.
+ */
+L_PTRA *
+ptraaGetPtra(L_PTRAA  *paa,
+             l_int32   index,
+             l_int32   accessflag)
+{
+l_int32  n;
+L_PTRA  *pa;
+
+    PROCNAME("ptraaGetPtra");
+
+    if (!paa)
+        return (L_PTRA *)ERROR_PTR("paa not defined", procName, NULL);
+    ptraaGetSize(paa, &n);
+    if (index < 0 || index >= n)
+        return (L_PTRA *)ERROR_PTR("invalid index", procName, NULL);
+    if (accessflag != L_HANDLE_ONLY && accessflag != L_REMOVE)
+        return (L_PTRA *)ERROR_PTR("invalid accessflag", procName, NULL);
+
+    pa = paa->ptra[index];
+    if (accessflag == L_REMOVE)
+        paa->ptra[index] = NULL;
+    return pa;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                             Ptraa conversion                             *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  ptraaFlattenToPtra()
+ *
+ *      Input:  ptraa
+ *      Return: ptra, or null on error
+ *
+ *  Notes:
+ *      (1) This 'flattens' the ptraa to a ptra, taking the items in
+ *          each ptra, in order, starting with the first ptra, etc.
+ *      (2) As a side-effect, the ptra are all removed from the ptraa
+ *          and destroyed, leaving an empty ptraa.
+ */
+L_PTRA *
+ptraaFlattenToPtra(L_PTRAA  *paa)
+{
+l_int32  i, n;
+L_PTRA    *pat, *pad;
+
+    PROCNAME("ptraaFlattenToPtra");
+
+    if (!paa)
+        return (L_PTRA *)ERROR_PTR("paa not defined", procName, NULL);
+
+    pad = ptraCreate(0);
+    ptraaGetSize(paa, &n);
+    for (i = 0; i < n; i++) {
+        pat = ptraaGetPtra(paa, i, L_REMOVE);
+        if (!pat) continue;
+        ptraJoin(pad, pat);
+        ptraDestroy(&pat, FALSE, FALSE);  /* they're all empty */
+    }
+
+    return pad;
+}
diff --git a/src/ptra.h b/src/ptra.h
new file mode 100644 (file)
index 0000000..17ab2b2
--- /dev/null
@@ -0,0 +1,91 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_PTRA_H
+#define  LEPTONICA_PTRA_H
+
+/*
+ *  Contains the following structs:
+ *      struct L_Ptra
+ *      struct L_Ptraa
+ *
+ *  Contains definitions for:
+ *      L_Ptra compaction flags for removal
+ *      L_Ptra shifting flags for insert
+ *      L_Ptraa accessor flags
+ */
+
+
+/*------------------------------------------------------------------------* 
+ *                     Generic Ptr Array Structs                          *
+ *------------------------------------------------------------------------*/
+
+    /* Generic pointer array */
+struct L_Ptra
+{
+    l_int32          nalloc;    /* size of allocated ptr array         */
+    l_int32          imax;      /* greatest valid index                */
+    l_int32          nactual;   /* actual number of stored elements    */
+    void           **array;     /* ptr array                           */
+};
+typedef struct L_Ptra  L_PTRA;
+
+
+    /* Array of generic pointer arrays */
+struct L_Ptraa
+{
+    l_int32          nalloc;    /* size of allocated ptr array         */
+    struct L_Ptra  **ptra;      /* array of ptra                       */
+};
+typedef struct L_Ptraa  L_PTRAA;
+
+
+
+/*------------------------------------------------------------------------* 
+ *                              Array flags                               *
+ *------------------------------------------------------------------------*/
+
+    /* Flags for removal from L_Ptra */
+enum {
+    L_NO_COMPACTION = 1,        /* null the pointer only  */
+    L_COMPACTION = 2            /* compact the array      */
+};
+
+    /* Flags for insertion into L_Ptra */
+enum {
+    L_AUTO_DOWNSHIFT = 0,       /* choose based on number of holes        */
+    L_MIN_DOWNSHIFT = 1,        /* downshifts min # of ptrs below insert  */
+    L_FULL_DOWNSHIFT = 2        /* downshifts all ptrs below insert       */
+};
+
+    /* Accessor flags for L_Ptraa */
+enum {
+    L_HANDLE_ONLY = 0,          /* ptr to L_Ptra; caller can inspect only    */
+    L_REMOVE = 1                /* caller owns; destroy or save in L_Ptraa   */
+};
+
+
+#endif  /* LEPTONICA_PTRA_H */
diff --git a/src/quadtree.c b/src/quadtree.c
new file mode 100644 (file)
index 0000000..5a69aaa
--- /dev/null
@@ -0,0 +1,674 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  quadtree.c
+ *
+ *      Top level quadtree linear statistics
+ *          l_int32   pixQuadtreeMean()
+ *          l_int32   pixQuadtreeVariance()
+ *
+ *      Statistics in an arbitrary rectangle
+ *          l_int32   pixMeanInRectangle()
+ *          l_int32   pixVarianceInRectangle()
+ *
+ *      Quadtree regions
+ *          BOXAA    *boxaaQuadtreeRegions()
+ *
+ *      Quadtree access
+ *          l_int32   quadtreeGetParent()
+ *          l_int32   quadtreeGetChildren()
+ *          l_int32   quadtreeMaxLevels()
+ *
+ *      Display quadtree
+ *          PIX      *fpixaDisplayQuadtree()
+ *
+ *
+ *  There are many other statistical quantities that can be computed
+ *  in a quadtree, such as rank values, and these can be added as
+ *  the need arises.
+ *
+ *  Similar results that can approximate a single level of the quadtree
+ *  can be generated by pixGetAverageTiled().  There we specify the
+ *  tile size over which the mean, mean square, and root variance
+ *  are generated; the results are saved in a (reduced size) pix.
+ *  Because the tile dimensions are integers, it is usually not possible
+ *  to obtain tilings that are a power of 2, as required for quadtrees.
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_BOXES       0
+#endif  /* !NO_CONSOLE_IO */
+
+
+/*----------------------------------------------------------------------*
+ *                  Top-level quadtree linear statistics                *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixQuadtreeMean()
+ *
+ *      Input:  pixs (8 bpp, no colormap)
+ *              nlevels (in quadtree; max allowed depends on image size)
+ *             *pix_ma (input mean accumulator; can be null)
+ *             *pfpixa (<return> mean values in quadtree)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The returned fpixa has @nlevels of fpix, each containing
+ *          the mean values at its level.  Level 0 has a
+ *          single value; level 1 has 4 values; level 2 has 16; etc.
+ */
+l_int32
+pixQuadtreeMean(PIX     *pixs,
+                l_int32  nlevels,
+                PIX     *pix_ma,
+                FPIXA  **pfpixa)
+{
+l_int32    i, j, w, h, size, n;
+l_float32  val;
+BOX       *box;
+BOXA      *boxa;
+BOXAA     *baa;
+FPIX      *fpix;
+PIX       *pix_mac;
+
+    PROCNAME("pixQuadtreeMean");
+
+    if (!pfpixa)
+        return ERROR_INT("&fpixa not defined", procName, 1);
+    *pfpixa = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (nlevels > quadtreeMaxLevels(w, h))
+        return ERROR_INT("nlevels too large for image", procName, 1);
+
+    if (!pix_ma)
+        pix_mac = pixBlockconvAccum(pixs);
+    else
+        pix_mac = pixClone(pix_ma);
+    if (!pix_mac)
+        return ERROR_INT("pix_mac not made", procName, 1);
+
+    if ((baa = boxaaQuadtreeRegions(w, h, nlevels)) == NULL) {
+        pixDestroy(&pix_mac);
+        return ERROR_INT("baa not made", procName, 1);
+    }
+
+    *pfpixa = fpixaCreate(nlevels);
+    for (i = 0; i < nlevels; i++) {
+        boxa = boxaaGetBoxa(baa, i, L_CLONE);
+        size = 1 << i;
+        n = boxaGetCount(boxa);  /* n == size * size */
+        fpix = fpixCreate(size, size);
+        for (j = 0; j < n; j++) {
+            box = boxaGetBox(boxa, j, L_CLONE);
+            pixMeanInRectangle(pixs, box, pix_mac, &val);
+            fpixSetPixel(fpix, j % size, j / size, val);
+            boxDestroy(&box);
+        }
+        fpixaAddFPix(*pfpixa, fpix, L_INSERT);
+        boxaDestroy(&boxa);
+    }
+
+    pixDestroy(&pix_mac);
+    boxaaDestroy(&baa);
+    return 0;
+}
+
+
+/*!
+ *  pixQuadtreeVariance()
+ *
+ *      Input:  pixs (8 bpp, no colormap)
+ *              nlevels (in quadtree)
+ *             *pix_ma (input mean accumulator; can be null)
+ *             *dpix_msa (input mean square accumulator; can be null)
+ *             *pfpixa_v (<optional return> variance values in quadtree)
+ *             *pfpixa_rv (<optional return> root variance values in quadtree)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The returned fpixav and fpixarv have @nlevels of fpix,
+ *          each containing at the respective levels the variance
+ *          and root variance values.
+ */
+l_int32
+pixQuadtreeVariance(PIX     *pixs,
+                    l_int32  nlevels,
+                    PIX     *pix_ma,
+                    DPIX    *dpix_msa,
+                    FPIXA  **pfpixa_v,
+                    FPIXA  **pfpixa_rv)
+{
+l_int32    i, j, w, h, size, n;
+l_float32  var, rvar;
+BOX       *box;
+BOXA      *boxa;
+BOXAA     *baa;
+FPIX      *fpixv, *fpixrv;
+PIX       *pix_mac;  /* copy of mean accumulator */
+DPIX      *dpix_msac;  /* msa clone */
+
+    PROCNAME("pixQuadtreeVariance");
+
+    if (!pfpixa_v && !pfpixa_rv)
+        return ERROR_INT("neither &fpixav nor &fpixarv defined", procName, 1);
+    if (pfpixa_v) *pfpixa_v = NULL;
+    if (pfpixa_rv) *pfpixa_rv = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (nlevels > quadtreeMaxLevels(w, h))
+        return ERROR_INT("nlevels too large for image", procName, 1);
+
+    if (!pix_ma)
+        pix_mac = pixBlockconvAccum(pixs);
+    else
+        pix_mac = pixClone(pix_ma);
+    if (!pix_mac)
+        return ERROR_INT("pix_mac not made", procName, 1);
+    if (!dpix_msa)
+        dpix_msac = pixMeanSquareAccum(pixs);
+    else
+        dpix_msac = dpixClone(dpix_msa);
+    if (!dpix_msac)
+        return ERROR_INT("dpix_msac not made", procName, 1);
+
+    if ((baa = boxaaQuadtreeRegions(w, h, nlevels)) == NULL) {
+        pixDestroy(&pix_mac);
+        dpixDestroy(&dpix_msac);
+        return ERROR_INT("baa not made", procName, 1);
+    }
+
+    if (pfpixa_v) *pfpixa_v = fpixaCreate(nlevels);
+    if (pfpixa_rv) *pfpixa_rv = fpixaCreate(nlevels);
+    for (i = 0; i < nlevels; i++) {
+        boxa = boxaaGetBoxa(baa, i, L_CLONE);
+        size = 1 << i;
+        n = boxaGetCount(boxa);  /* n == size * size */
+        if (pfpixa_v) fpixv = fpixCreate(size, size);
+        if (pfpixa_rv) fpixrv = fpixCreate(size, size);
+        for (j = 0; j < n; j++) {
+            box = boxaGetBox(boxa, j, L_CLONE);
+            pixVarianceInRectangle(pixs, box, pix_mac, dpix_msac, &var, &rvar);
+            if (pfpixa_v) fpixSetPixel(fpixv, j % size, j / size, var);
+            if (pfpixa_rv) fpixSetPixel(fpixrv, j % size, j / size, rvar);
+            boxDestroy(&box);
+        }
+        if (pfpixa_v) fpixaAddFPix(*pfpixa_v, fpixv, L_INSERT);
+        if (pfpixa_rv) fpixaAddFPix(*pfpixa_rv, fpixrv, L_INSERT);
+        boxaDestroy(&boxa);
+    }
+
+    pixDestroy(&pix_mac);
+    dpixDestroy(&dpix_msac);
+    boxaaDestroy(&baa);
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                  Statistics in an arbitrary rectangle                *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixMeanInRectangle()
+ *
+ *      Input:  pix (8 bpp)
+ *              box (region to compute mean value)
+ *              pixma (mean accumulator)
+ *              &val (<return> mean value
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function is intended to be used for many rectangles
+ *          on the same image.  It can find the mean within a
+ *          rectangle in O(1), independent of the size of the rectangle.
+ */
+l_int32
+pixMeanInRectangle(PIX        *pixs,
+                   BOX        *box,
+                   PIX        *pixma,
+                   l_float32  *pval)
+{
+l_int32    w, h, bx, by, bw, bh;
+l_uint32   val00, val01, val10, val11;
+l_float32  norm;
+BOX       *boxc;
+
+    PROCNAME("pixMeanInRectangle");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0.0;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (!pixma)
+        return ERROR_INT("pixma not defined", procName, 1);
+
+        /* Clip rectangle to image */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    boxc = boxClipToRectangle(box, w, h);
+    boxGetGeometry(boxc, &bx, &by, &bw, &bh);
+    boxDestroy(&boxc);
+
+    if (bw == 0 || bh == 0)
+        return ERROR_INT("no pixels in box", procName, 1);
+
+        /* Use up to 4 points in the accumulator */
+    norm = 1.0 / (bw * bh);
+    if (bx > 0 && by > 0) {
+        pixGetPixel(pixma, bx + bw - 1, by + bh - 1, &val11);
+        pixGetPixel(pixma, bx + bw - 1, by - 1, &val10);
+        pixGetPixel(pixma, bx - 1, by + bh - 1, &val01);
+        pixGetPixel(pixma, bx - 1, by - 1, &val00);
+        *pval = norm * (val11 - val01 + val00 - val10);
+    } else if (by > 0) {  /* bx == 0 */
+        pixGetPixel(pixma, bw - 1, by + bh - 1, &val11);
+        pixGetPixel(pixma, bw - 1, by - 1, &val10);
+        *pval = norm * (val11 - val10);
+    } else if (bx > 0) {  /* by == 0 */
+        pixGetPixel(pixma, bx + bw - 1, bh - 1, &val11);
+        pixGetPixel(pixma, bx - 1, bh - 1, &val01);
+        *pval = norm * (val11 - val01);
+    } else {  /* bx == 0 && by == 0 */
+        pixGetPixel(pixma, bw - 1, bh - 1, &val11);
+        *pval = norm * val11;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixVarianceInRectangle()
+ *
+ *      Input:  pix (8 bpp)
+ *              box (region to compute variance and/or root variance)
+ *              pix_ma (mean accumulator)
+ *              dpix_msa (mean square accumulator)
+ *              &var (<optional return> variance)
+ *              &rvar (<optional return> root variance)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function is intended to be used for many rectangles
+ *          on the same image.  It can find the variance and/or the
+ *          square root of the variance within a rectangle in O(1),
+ *          independent of the size of the rectangle.
+ */
+l_int32
+pixVarianceInRectangle(PIX        *pixs,
+                       BOX        *box,
+                       PIX        *pix_ma,
+                       DPIX       *dpix_msa,
+                       l_float32  *pvar,
+                       l_float32  *prvar)
+{
+l_int32    w, h, bx, by, bw, bh;
+l_uint32   val00, val01, val10, val11;
+l_float64  dval00, dval01, dval10, dval11, mval, msval, var, norm;
+BOX       *boxc;
+
+    PROCNAME("pixVarianceInRectangle");
+
+    if (!pvar && !prvar)
+        return ERROR_INT("neither &var nor &rvar defined", procName, 1);
+    if (pvar) *pvar = 0.0;
+    if (prvar) *prvar = 0.0;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!box)
+        return ERROR_INT("box not defined", procName, 1);
+    if (!pix_ma)
+        return ERROR_INT("pix_ma not defined", procName, 1);
+    if (!dpix_msa)
+        return ERROR_INT("dpix_msa not defined", procName, 1);
+
+        /* Clip rectangle to image */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    boxc = boxClipToRectangle(box, w, h);
+    boxGetGeometry(boxc, &bx, &by, &bw, &bh);
+    boxDestroy(&boxc);
+
+    if (bw == 0 || bh == 0)
+        return ERROR_INT("no pixels in box", procName, 1);
+
+        /* Use up to 4 points in the accumulators */
+    norm = 1.0 / (bw * bh);
+    if (bx > 0 && by > 0) {
+        pixGetPixel(pix_ma, bx + bw - 1, by + bh - 1, &val11);
+        pixGetPixel(pix_ma, bx + bw - 1, by - 1, &val10);
+        pixGetPixel(pix_ma, bx - 1, by + bh - 1, &val01);
+        pixGetPixel(pix_ma, bx - 1, by - 1, &val00);
+        dpixGetPixel(dpix_msa, bx + bw - 1, by + bh - 1, &dval11);
+        dpixGetPixel(dpix_msa, bx + bw - 1, by - 1, &dval10);
+        dpixGetPixel(dpix_msa, bx - 1, by + bh - 1, &dval01);
+        dpixGetPixel(dpix_msa, bx - 1, by - 1, &dval00);
+        mval = norm * (val11 - val01 + val00 - val10);
+        msval = norm * (dval11 - dval01 + dval00 - dval10);
+        var = (msval - mval * mval);
+        if (pvar) *pvar = (l_float32)var;
+        if (prvar) *prvar = (l_float32)(sqrt(var));
+    } else if (by > 0) {  /* bx == 0 */
+        pixGetPixel(pix_ma, bw - 1, by + bh - 1, &val11);
+        pixGetPixel(pix_ma, bw - 1, by - 1, &val10);
+        dpixGetPixel(dpix_msa, bw - 1, by + bh - 1, &dval11);
+        dpixGetPixel(dpix_msa, bw - 1, by - 1, &dval10);
+        mval = norm * (val11 - val10);
+        msval = norm * (dval11 - dval10);
+        var = (msval - mval * mval);
+        if (pvar) *pvar = (l_float32)var;
+        if (prvar) *prvar = (l_float32)(sqrt(var));
+    } else if (bx > 0) {  /* by == 0 */
+        pixGetPixel(pix_ma, bx + bw - 1, bh - 1, &val11);
+        pixGetPixel(pix_ma, bx - 1, bh - 1, &val01);
+        dpixGetPixel(dpix_msa, bx + bw - 1, bh - 1, &dval11);
+        dpixGetPixel(dpix_msa, bx - 1, bh - 1, &dval01);
+        mval = norm * (val11 - val01);
+        msval = norm * (dval11 - dval01);
+        var = (msval - mval * mval);
+        if (pvar) *pvar = (l_float32)var;
+        if (prvar) *prvar = (l_float32)(sqrt(var));
+    } else {  /* bx == 0 && by == 0 */
+        pixGetPixel(pix_ma, bw - 1, bh - 1, &val11);
+        dpixGetPixel(dpix_msa, bw - 1, bh - 1, &dval11);
+        mval = norm * val11;
+        msval = norm * dval11;
+        var = (msval - mval * mval);
+        if (pvar) *pvar = (l_float32)var;
+        if (prvar) *prvar = (l_float32)(sqrt(var));
+    }
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                            Quadtree regions                          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  boxaaQuadtreeRegions()
+ *
+ *      Input:  w, h (of pix that is being quadtree-ized)
+ *              nlevels (in quadtree)
+ *      Return: baa (for quadtree regions at each level), or null on error
+ *
+ *  Notes:
+ *      (1) The returned boxaa has @nlevels of boxa, each containing
+ *          the set of rectangles at that level.  The rectangle at
+ *          level 0 is the entire region; at level 1 the region is
+ *          divided into 4 rectangles, and at level n there are n^4
+ *          rectangles.
+ *      (2) At each level, the rectangles in the boxa are in "raster"
+ *          order, with LR (fast scan) and TB (slow scan).
+ */
+BOXAA *
+boxaaQuadtreeRegions(l_int32  w,
+                     l_int32  h,
+                     l_int32  nlevels)
+{
+l_int32   i, j, k, maxpts, nside, nbox, bw, bh;
+l_int32  *xstart, *xend, *ystart, *yend;
+BOX      *box;
+BOXA     *boxa;
+BOXAA    *baa;
+
+    PROCNAME("boxaaQuadtreeRegions");
+
+    if (nlevels < 1)
+        return (BOXAA *)ERROR_PTR("nlevels must be >= 1", procName, NULL);
+    if (w < (1 << (nlevels - 1)))
+        return (BOXAA *)ERROR_PTR("w doesn't support nlevels", procName, NULL);
+    if (h < (1 << (nlevels - 1)))
+        return (BOXAA *)ERROR_PTR("h doesn't support nlevels", procName, NULL);
+
+    baa = boxaaCreate(nlevels);
+    maxpts = 1 << (nlevels - 1);
+    xstart = (l_int32 *)LEPT_CALLOC(maxpts, sizeof(l_int32));
+    xend = (l_int32 *)LEPT_CALLOC(maxpts, sizeof(l_int32));
+    ystart = (l_int32 *)LEPT_CALLOC(maxpts, sizeof(l_int32));
+    yend = (l_int32 *)LEPT_CALLOC(maxpts, sizeof(l_int32));
+    for (k = 0; k < nlevels; k++) {
+        nside = 1 << k;  /* number of boxes in each direction */
+        for (i = 0; i < nside; i++) {
+            xstart[i] = (w - 1) * i / nside;
+            if (i > 0) xstart[i]++;
+            xend[i] = (w - 1) * (i + 1) / nside;
+            ystart[i] = (h - 1) * i / nside;
+            if (i > 0) ystart[i]++;
+            yend[i] = (h - 1) * (i + 1) / nside;
+#if DEBUG_BOXES
+            fprintf(stderr,
+               "k = %d, xs[%d] = %d, xe[%d] = %d, ys[%d] = %d, ye[%d] = %d\n",
+                    k, i, xstart[i], i, xend[i], i, ystart[i], i, yend[i]);
+#endif  /* DEBUG_BOXES */
+        }
+        nbox = 1 << (2 * k);
+        boxa = boxaCreate(nbox);
+        for (i = 0; i < nside; i++) {
+            bh = yend[i] - ystart[i] + 1;
+            for (j = 0; j < nside; j++) {
+                bw = xend[j] - xstart[j] + 1;
+                box = boxCreate(xstart[j], ystart[i], bw, bh);
+                boxaAddBox(boxa, box, L_INSERT);
+            }
+        }
+        boxaaAddBoxa(baa, boxa, L_INSERT);
+    }
+
+    LEPT_FREE(xstart);
+    LEPT_FREE(xend);
+    LEPT_FREE(ystart);
+    LEPT_FREE(yend);
+    return baa;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                            Quadtree access                           *
+ *----------------------------------------------------------------------*/
+/*!
+ *  quadtreeGetParent()
+ *
+ *      Input:  fpixa (mean, variance or root variance)
+ *              level, x, y (of current pixel)
+ *              &val (<return> parent pixel value), or 0.0 on error.
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Check return value for error.  On error, val is returned as 0.0.
+ *      (2) The parent is located at:
+ *             level - 1
+ *             (x/2, y/2)
+ */
+l_int32
+quadtreeGetParent(FPIXA      *fpixa,
+                  l_int32     level,
+                  l_int32     x,
+                  l_int32     y,
+                  l_float32  *pval)
+{
+l_int32  n;
+
+    PROCNAME("quadtreeGetParent");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0.0;
+    if (!fpixa)
+        return ERROR_INT("fpixa not defined", procName, 1);
+    n = fpixaGetCount(fpixa);
+    if (level < 1 || level >= n)
+        return ERROR_INT("invalid level", procName, 1);
+
+    if (fpixaGetPixel(fpixa, level - 1, x / 2, y / 2, pval) != 0)
+        return ERROR_INT("invalid coordinates", procName, 1);
+    return 0;
+}
+
+
+/*!
+ *  quadtreeGetChildren()
+ *
+ *      Input:  fpixa (mean, variance or root variance)
+ *              level, x, y (of current pixel)
+ *              &val00, val01, val10, val11  (<return> child pixel values)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Check return value for error.  On error, all return vals are 0.0.
+ *      (2) The returned child pixels are located at:
+ *             level + 1
+ *             (2x, 2y), (2x+1, 2y), (2x, 2y+1), (2x+1, 2y+1)
+ */
+l_int32
+quadtreeGetChildren(FPIXA      *fpixa,
+                    l_int32     level,
+                    l_int32     x,
+                    l_int32     y,
+                    l_float32  *pval00,
+                    l_float32  *pval10,
+                    l_float32  *pval01,
+                    l_float32  *pval11)
+{
+l_int32  n;
+
+    PROCNAME("quadtreeGetChildren");
+
+    if (!pval00 || !pval01 || !pval10 || !pval11)
+        return ERROR_INT("&val* not all defined", procName, 1);
+    *pval00 = *pval10 = *pval01 = *pval11 = 0.0;
+    if (!fpixa)
+        return ERROR_INT("fpixa not defined", procName, 1);
+    n = fpixaGetCount(fpixa);
+    if (level < 0 || level >= n - 1)
+        return ERROR_INT("invalid level", procName, 1);
+
+    if (fpixaGetPixel(fpixa, level + 1, 2 * x, 2 * y, pval00) != 0)
+        return ERROR_INT("invalid coordinates", procName, 1);
+    fpixaGetPixel(fpixa, level + 1, 2 * x + 1, 2 * y, pval10);
+    fpixaGetPixel(fpixa, level + 1, 2 * x, 2 * y + 1, pval01);
+    fpixaGetPixel(fpixa, level + 1, 2 * x + 1, 2 * y + 1, pval11);
+    return 0;
+}
+
+
+/*!
+ *  quadtreeMaxLevels()
+ *
+ *      Input:  w, h (of image)
+ *      Return: maxlevels (maximum number of levels allowed), or -1 on error
+ *
+ *  Notes:
+ *      (1) The criterion for maxlevels is that the subdivision not
+ *          go down below the single pixel level.  The 1.5 factor
+ *          is intended to keep any rectangle from accidentally
+ *          having zero dimension due to integer truncation.
+ */
+l_int32
+quadtreeMaxLevels(l_int32  w,
+                  l_int32  h)
+{
+l_int32  i, minside;
+
+    minside = L_MIN(w, h);
+    for (i = 0; i < 20; i++) {  /* 2^10 = one million */
+        if (minside < (1.5 * (1 << i)))
+            return i - 1;
+    }
+
+    return -1;  /* fail if the image has over a trillion pixels! */
+}
+
+
+/*----------------------------------------------------------------------*
+ *                            Display quadtree                          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  fpixaDisplayQuadtree()
+ *
+ *      Input:  fpixa (mean, variance or root variance)
+ *              factor (replication factor at lowest level)
+ *              fontdir (directory for text fonts; e.g., ./fonts)
+ *      Return: pixd (8 bpp, mosaic of quadtree images), or null on error
+ *
+ *  Notes:
+ *      (1) The mean and root variance fall naturally in the 8 bpp range,
+ *          but the variance is typically outside the range.  This
+ *          function displays 8 bpp pix clipped to 255, so the image
+ *          pixels will mostly be 255 (white).
+ */
+PIX *
+fpixaDisplayQuadtree(FPIXA       *fpixa,
+                     l_int32      factor,
+                     const char  *fontdir)
+{
+char       buf[256];
+l_int32    nlevels, i, mag, w;
+L_BMF     *bmf;
+FPIX      *fpix;
+PIX       *pixt1, *pixt2, *pixt3, *pixt4, *pixd;
+PIXA      *pixat;
+
+    PROCNAME("fpixaDisplayQuadtree");
+
+    if (!fpixa)
+        return (PIX *)ERROR_PTR("fpixa not defined", procName, NULL);
+
+    if ((nlevels = fpixaGetCount(fpixa)) == 0)
+        return (PIX *)ERROR_PTR("pixas empty", procName, NULL);
+
+    if ((bmf = bmfCreate(fontdir, 6)) == NULL)
+        L_ERROR("bmf not made; text will not be added", procName);
+    pixat = pixaCreate(nlevels);
+    for (i = 0; i < nlevels; i++) {
+        fpix = fpixaGetFPix(fpixa, i, L_CLONE);
+        pixt1 = fpixConvertToPix(fpix, 8, L_CLIP_TO_ZERO, 0);
+        mag = factor * (1 << (nlevels - i - 1));
+        pixt2 = pixExpandReplicate(pixt1, mag);
+        pixt3 = pixConvertTo32(pixt2);
+        snprintf(buf, sizeof(buf), "Level %d\n", i);
+        pixt4 = pixAddSingleTextblock(pixt3, bmf, buf, 0xff000000,
+                                      L_ADD_BELOW, NULL);
+        pixaAddPix(pixat, pixt4, L_INSERT);
+        fpixDestroy(&fpix);
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt2);
+        pixDestroy(&pixt3);
+    }
+    w = pixGetWidth(pixt4);
+    pixd = pixaDisplayTiledInRows(pixat, 32, nlevels * (w + 80), 1.0, 0, 30, 2);
+
+    pixaDestroy(&pixat);
+    bmfDestroy(&bmf);
+    return pixd;
+}
diff --git a/src/queue.c b/src/queue.c
new file mode 100644 (file)
index 0000000..d0c3f72
--- /dev/null
@@ -0,0 +1,312 @@
+/*====================================================================*
+ -  Copyright (C) 2001 Leptonica.  All rights reserved.
+ -
+ -  Redistribution and use in source and binary forms, with or without
+ -  modification, are permitted provided that the following conditions
+ -  are met:
+ -  1. Redistributions of source code must retain the above copyright
+ -     notice, this list of conditions and the following disclaimer.
+ -  2. Redistributions in binary form must reproduce the above
+ -     copyright notice, this list of conditions and the following
+ -     disclaimer in the documentation and/or other materials
+ -     provided with the distribution.
+ -
+ -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
+ -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *====================================================================*/
+
+/*
+ *   queue.c
+ *
+ *      Create/Destroy L_Queue
+ *          L_QUEUE        *lqueueCreate()
+ *          void           *lqueueDestroy()
+ *
+ *      Operations to add/remove to/from a L_Queue
+ *          l_int32         lqueueAdd()
+ *          static l_int32  lqueueExtendArray()
+ *          void           *lqueueRemove()
+ *
+ *      Accessors
+ *          l_int32         lqueueGetCount()
+ *
+ *      Debug output
+ *          l_int32         lqueuePrint()
+ *
+ *    The lqueue is a fifo that implements a queue of void* pointers.
+ *    It can be used to hold a queue of any type of struct.
+ *    Internally, it maintains two counters:
+ *        nhead:  location of head (in ptrs) from the beginning
+ *                of the buffer
+ *        nelem:  number of ptr elements stored in the queue
+ *    As items are added to the queue, nelem increases.
+ *    As items are removed, nhead increases and nelem decreases.
+ *    Any time the tail reaches the end of the allocated buffer,
+ *      all the pointers are shifted to the left, so that the head
+ *      is at the beginning of the array.
+ *    If the buffer becomes more than 3/4 full, it doubles in size.
+ *
+ *    [A circular queue would allow us to skip the shifting and
+ *    to resize only when the buffer is full.  For most applications,
+ *    the extra work we do for a linear queue is not significant.]
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  MIN_BUFFER_SIZE = 20;             /* n'importe quoi */
+static const l_int32  INITIAL_BUFFER_ARRAYSIZE = 1024;  /* n'importe quoi */
+
+    /* Static function */
+static l_int32 lqueueExtendArray(L_QUEUE *lq);
+
+
+/*--------------------------------------------------------------------------*
+ *                         L_Queue create/destroy                           *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  lqueueCreate()
+ *
+ *      Input:  size of ptr array to be alloc'd (0 for default)
+ *      Return: lqueue, or null on error
+ *
+ *  Notes:
+ *      (1) Allocates a ptr array of given size, and initializes counters.
+ */
+L_QUEUE *
+lqueueCreate(l_int32  nalloc)
+{
+L_QUEUE  *lq;
+
+    PROCNAME("lqueueCreate");
+
+    if (nalloc < MIN_BUFFER_SIZE)
+        nalloc = INITIAL_BUFFER_ARRAYSIZE;
+
+    if ((lq = (L_QUEUE *)LEPT_CALLOC(1, sizeof(L_QUEUE))) == NULL)
+        return (L_QUEUE *)ERROR_PTR("lq not made", procName, NULL);
+    if ((lq->array = (void **)LEPT_CALLOC(nalloc, sizeof(void *))) == NULL)
+        return (L_QUEUE *)ERROR_PTR("ptr array not made", procName, NULL);
+    lq->nalloc = nalloc;
+    lq->nhead = lq->nelem = 0;
+    return lq;
+}
+
+
+/*!
+ *  lqueueDestroy()
+ *
+ *      Input:  &lqueue  (<to be nulled>)
+ *              freeflag (TRUE to free each remaining struct in the array)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) If freeflag is TRUE, frees each struct in the array.
+ *      (2) If freeflag is FALSE but there are elements on the array,
+ *          gives a warning and destroys the array.  This will
+ *          cause a memory leak of all the items that were on the queue.
+ *          So if the items require their own destroy function, they
+ *          must be destroyed before the queue.  The same applies to the
+ *          auxiliary stack, if it is used.
+ *      (3) To destroy the L_Queue, we destroy the ptr array, then
+ *          the lqueue, and then null the contents of the input ptr.
+ */
+void
+lqueueDestroy(L_QUEUE  **plq,
+              l_int32    freeflag)
+{
+void     *item;
+L_QUEUE  *lq;
+
+    PROCNAME("lqueueDestroy");
+
+    if (plq == NULL) {
+        L_WARNING("ptr address is NULL\n", procName);
+        return;
+    }
+    if ((lq = *plq) == NULL)
+        return;
+
+    if (freeflag) {
+        while(lq->nelem > 0) {
+            item = lqueueRemove(lq);
+            LEPT_FREE(item);
+        }
+    } else if (lq->nelem > 0) {
+        L_WARNING("memory leak of %d items in lqueue!\n", procName, lq->nelem);
+    }
+
+    if (lq->array)
+        LEPT_FREE(lq->array);
+    if (lq->stack)
+        lstackDestroy(&lq->stack, freeflag);
+    LEPT_FREE(lq);
+    *plq = NULL;
+
+    return;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *                                  Accessors                               *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  lqueueAdd()
+ *
+ *      Input:  lqueue
+ *              item to be added to the tail of the queue
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The algorithm is as follows.  If the queue is populated
+ *          to the end of the allocated array, shift all ptrs toward
+ *          the beginning of the array, so that the head of the queue
+ *          is at the beginning of the array.  Then, if the array is
+ *          more than 0.75 full, realloc with double the array size.
+ *          Finally, add the item to the tail of the queue.
+ */
+l_int32
+lqueueAdd(L_QUEUE  *lq,
+          void     *item)
+{
+    PROCNAME("lqueueAdd");
+
+    if (!lq)
+        return ERROR_INT("lq not defined", procName, 1);
+    if (!item)
+        return ERROR_INT("item not defined", procName, 1);
+
+        /* If filled to the end and the ptrs can be shifted to the left,
+         * shift them. */
+    if ((lq->nhead + lq->nelem >= lq->nalloc) && (lq->nhead != 0)) {
+        memmove(lq->array, lq->array + lq->nhead, sizeof(void *) * lq->nelem);
+        lq->nhead = 0;
+    }
+
+        /* If necessary, expand the allocated array by a factor of 2 */
+    if (lq->nelem > 0.75 * lq->nalloc)
+        lqueueExtendArray(lq);
+
+        /* Now add the item */
+    lq->array[lq->nhead + lq->nelem] = (void *)item;
+    lq->nelem++;
+
+    return 0;
+}
+
+
+/*!
+ *  lqueueExtendArray()
+ *
+ *      Input:  lqueue
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+lqueueExtendArray(L_QUEUE  *lq)
+{
+    PROCNAME("lqueueExtendArray");
+
+    if (!lq)
+        return ERROR_INT("lq not defined", procName, 1);
+
+    if ((lq->array = (void **)reallocNew((void **)&lq->array,
+                                sizeof(void *) * lq->nalloc,
+                                2 * sizeof(void *) * lq->nalloc)) == NULL)
+        return ERROR_INT("new ptr array not returned", procName, 1);
+
+    lq->nalloc = 2 * lq->nalloc;
+    return 0;
+}
+
+
+/*!
+ *  lqueueRemove()
+ *
+ *      Input:  lqueue
+ *      Return: ptr to item popped from the head of the queue,
+ *              or null if the queue is empty or on error
+ *
+ *  Notes:
+ *      (1) If this is the last item on the queue, so that the queue
+ *          becomes empty, nhead is reset to the beginning of the array.
+ */
+void *
+lqueueRemove(L_QUEUE  *lq)
+{
+void  *item;
+
+    PROCNAME("lqueueRemove");
+
+    if (!lq)
+        return (void *)ERROR_PTR("lq not defined", procName, NULL);
+
+    if (lq->nelem == 0)
+        return NULL;
+    item = lq->array[lq->nhead];
+    lq->array[lq->nhead] = NULL;
+    if (lq->nelem == 1)
+        lq->nhead = 0;  /* reset head ptr */
+    else
+        (lq->nhead)++;  /* can't go off end of array because nelem > 1 */
+    lq->nelem--;
+    return item;
+}
+
+
+/*!
+ *  lqueueGetCount()
+ *
+ *      Input:  lqueue
+ *      Return: count, or 0 on error
+ */
+l_int32
+lqueueGetCount(L_QUEUE  *lq)
+{
+    PROCNAME("lqueueGetCount");
+
+    if (!lq)
+        return ERROR_INT("lq not defined", procName, 0);
+
+    return lq->nelem;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                            Debug output                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  lqueuePrint()
+ *
+ *      Input:  stream
+ *              lqueue
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+lqueuePrint(FILE     *fp,
+            L_QUEUE  *lq)
+{
+l_int32  i;
+
+    PROCNAME("lqueuePrint");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!lq)
+        return ERROR_INT("lq not defined", procName, 1);
+
+    fprintf(fp, "\n L_Queue: nalloc = %d, nhead = %d, nelem = %d, array = %p\n",
+            lq->nalloc, lq->nhead, lq->nelem, lq->array);
+    for (i = lq->nhead; i < lq->nhead + lq->nelem; i++)
+    fprintf(fp,   "array[%d] = %p\n", i, lq->array[i]);
+
+    return 0;
+}
diff --git a/src/queue.h b/src/queue.h
new file mode 100644 (file)
index 0000000..57d84d8
--- /dev/null
@@ -0,0 +1,74 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_QUEUE_H
+#define  LEPTONICA_QUEUE_H
+
+/*
+ *  queue.h
+ *
+ *      Expandable pointer queue for arbitrary void* data.
+ *
+ *      The L_Queue is a fifo that implements a queue of void* pointers.
+ *      It can be used to hold a queue of any type of struct.
+ *
+ *      Internally, it maintains two counters:
+ *          nhead:  location of head (in ptrs) from the beginning
+ *                  of the array.
+ *          nelem:  number of ptr elements stored in the queue.
+ *
+ *      The element at the head of the queue, which is the next to
+ *      be removed, is array[nhead].  The location at the tail of the
+ *      queue to which the next element will be added is
+ *      array[nhead + nelem].
+ *
+ *      As items are added to the queue, nelem increases.
+ *      As items are removed, nhead increases and nelem decreases.
+ *      Any time the tail reaches the end of the allocated array,
+ *      all the pointers are shifted to the left, so that the head
+ *      is at the beginning of the array.
+ *      If the array becomes more than 3/4 full, it doubles in size.
+ *
+ *      The auxiliary stack can be used in a wrapper for re-using
+ *      items popped from the queue.  It is not made by default.
+ *
+ *      For further implementation details, see queue.c.
+ */
+
+struct L_Queue
+{
+    l_int32          nalloc;     /* size of allocated ptr array            */
+    l_int32          nhead;      /* location of head (in ptrs) from the    */
+                                 /* beginning of the array                 */
+    l_int32          nelem;      /* number of elements stored in the queue */
+    void           **array;      /* ptr array                              */
+    struct L_Stack  *stack;      /* auxiliary stack                        */
+
+};
+typedef struct L_Queue L_QUEUE;
+
+
+#endif  /* LEPTONICA_QUEUE_H */
diff --git a/src/rank.c b/src/rank.c
new file mode 100644 (file)
index 0000000..2cfd849
--- /dev/null
@@ -0,0 +1,520 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  rank.c
+ *
+ *      Rank filter (gray and rgb)
+ *          PIX      *pixRankFilter()
+ *          PIX      *pixRankFilterRGB()
+ *          PIX      *pixRankFilterGray()
+ *
+ *      Median filter
+ *          PIX      *pixMedianFilter()
+ *
+ *      Rank filter (accelerated with downscaling)
+ *          PIX      *pixRankFilterWithScaling()
+ *
+ *  What is a brick rank filter?
+ *
+ *    A brick rank order filter evaluates, for every pixel in the image,
+ *    a rectangular set of n = wf x hf pixels in its neighborhood (where the
+ *    pixel in question is at the "center" of the rectangle and is
+ *    included in the evaluation).  It determines the value of the
+ *    neighboring pixel that is the r-th smallest in the set,
+ *    where r is some integer between 1 and n.  The input rank parameter
+ *    is a fraction between 0.0 and 1.0, where 0.0 represents the
+ *    smallest value (r = 1) and 1.0 represents the largest value (r = n).
+ *    A median filter is a rank filter where rank = 0.5.
+ *
+ *    It is important to note that grayscale erosion is equivalent
+ *    to rank = 0.0, and grayscale dilation is equivalent to rank = 1.0.
+ *    These are much easier to calculate than the general rank value,
+ *    thanks to the van Herk/Gil-Werman algorithm:
+ *       http://www.leptonica.com/grayscale-morphology.html
+ *    so you should use pixErodeGray() and pixDilateGray() for
+ *    rank 0.0 and 1.0, rsp.  See notes below in the function header.
+ *
+ *  How is a rank filter implemented efficiently on an image?
+ *
+ *    Sorting will not work.
+ *
+ *      * The best sort algorithms are O(n*logn), where n is the number
+ *        of values to be sorted (the area of the filter).  For large
+ *        filters this is an impractically large number.
+ *
+ *      * Selection of the rank value is O(n).  (To understand why it's not
+ *        O(n*logn), see Numerical Recipes in C, 2nd edition, 1992,  p. 355ff).
+ *        This also still far too much computation for large filters.
+ *
+ *      * Suppose we get clever.  We really only need to do an incremental
+ *        selection or sorting, because, for example, moving the filter
+ *        down by one pixel causes one filter width of pixels to be added
+ *        and another to be removed.  Can we do this incrementally in
+ *        an efficient way?  Unfortunately, no.  The sorted values will be
+ *        in an array.  Even if the filter width is 1, we can expect to
+ *        have to move O(n) pixels, because insertion and deletion can happen
+ *        anywhere in the array.  By comparison, heapsort is excellent for
+ *        incremental sorting, where the cost for insertion or deletion
+ *        is O(logn), because the array itself doesn't need to
+ *        be sorted into strictly increasing order.  However, heapsort
+ *        only gives the max (or min) value, not the general rank value.
+ *
+ *    This leaves histograms.
+ *
+ *      * Represented as an array.  The problem with an array of 256
+ *        bins is that, in general, a significant fraction of the
+ *        entire histogram must be summed to find the rank value bin.
+ *        Suppose the filter size is 5x5.  You spend most of your time
+ *        adding zeroes.  Ouch!
+ *
+ *      * Represented as a linked list.  This would overcome the
+ *        summing-over-empty-bin problem, but you lose random access
+ *        for insertions and deletions.  No way.
+ *
+ *      * Two histogram solution.  Maintain two histograms with
+ *        bin sizes of 1 and 16.  Proceed from coarse to fine.
+ *        First locate the coarse bin for the given rank, of which
+ *        there are only 16.  Then, in the 256 entry (fine) histogram,
+ *        you need look at a maximum of 16 bins.  For each output
+ *        pixel, the average number of bins summed over, both in the
+ *        coarse and fine histograms, is thus 16.
+ *
+ *  If someone has a better method, please let me know!
+ *
+ *  The rank filtering operation is relatively expensive, compared to most
+ *  of the other imaging operations.  The speed is only weakly dependent
+ *  on the size of the rank filter.  On standard hardware, it runs at
+ *  about 10 Mpix/sec for a 50 x 50 filter, and 25 Mpix/sec for
+ *  a 5 x 5 filter.   For applications where the rank filter can be
+ *  performed on a downscaled image, significant speedup can be
+ *  achieved because the time goes as the square of the scaling factor.
+ *  We provide an interface that handles the details, and only
+ *  requires the amount of downscaling to be input.
+ */
+
+#include "allheaders.h"
+
+/*----------------------------------------------------------------------*
+ *                           Rank order filter                          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixRankFilter()
+ *
+ *      Input:  pixs (8 or 32 bpp; no colormap)
+ *              wf, hf  (width and height of filter; each is >= 1)
+ *              rank (in [0.0 ... 1.0])
+ *      Return: pixd (of rank values), or null on error
+ *
+ *  Notes:
+ *      (1) This defines, for each pixel in pixs, a neighborhood of
+ *          pixels given by a rectangle "centered" on the pixel.
+ *          This set of wf*hf pixels has a distribution of values.
+ *          For each component, if the values are sorted in increasing
+ *          order, we choose the component such that rank*(wf*hf-1)
+ *          pixels have a lower or equal value and
+ *          (1-rank)*(wf*hf-1) pixels have an equal or greater value.
+ *      (2) See notes in pixRankFilterGray() for further details.
+ */
+PIX  *
+pixRankFilter(PIX       *pixs,
+              l_int32    wf,
+              l_int32    hf,
+              l_float32  rank)
+{
+l_int32  d;
+
+    PROCNAME("pixRankFilter");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs) != NULL)
+        return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+    if (wf < 1 || hf < 1)
+        return (PIX *)ERROR_PTR("wf < 1 || hf < 1", procName, NULL);
+    if (rank < 0.0 || rank > 1.0)
+        return (PIX *)ERROR_PTR("rank must be in [0.0, 1.0]", procName, NULL);
+    if (wf == 1 && hf == 1)   /* no-op */
+        return pixCopy(NULL, pixs);
+
+    if (d == 8)
+        return pixRankFilterGray(pixs, wf, hf, rank);
+    else  /* d == 32 */
+        return pixRankFilterRGB(pixs, wf, hf, rank);
+}
+
+
+/*!
+ *  pixRankFilterRGB()
+ *
+ *      Input:  pixs (32 bpp)
+ *              wf, hf  (width and height of filter; each is >= 1)
+ *              rank (in [0.0 ... 1.0])
+ *      Return: pixd (of rank values), or null on error
+ *
+ *  Notes:
+ *      (1) This defines, for each pixel in pixs, a neighborhood of
+ *          pixels given by a rectangle "centered" on the pixel.
+ *          This set of wf*hf pixels has a distribution of values.
+ *          For each component, if the values are sorted in increasing
+ *          order, we choose the component such that rank*(wf*hf-1)
+ *          pixels have a lower or equal value and
+ *          (1-rank)*(wf*hf-1) pixels have an equal or greater value.
+ *      (2) Apply gray rank filtering to each component independently.
+ *      (3) See notes in pixRankFilterGray() for further details.
+ */
+PIX  *
+pixRankFilterRGB(PIX       *pixs,
+                 l_int32    wf,
+                 l_int32    hf,
+                 l_float32  rank)
+{
+PIX  *pixr, *pixg, *pixb, *pixrf, *pixgf, *pixbf, *pixd;
+
+    PROCNAME("pixRankFilterRGB");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (wf < 1 || hf < 1)
+        return (PIX *)ERROR_PTR("wf < 1 || hf < 1", procName, NULL);
+    if (rank < 0.0 || rank > 1.0)
+        return (PIX *)ERROR_PTR("rank must be in [0.0, 1.0]", procName, NULL);
+    if (wf == 1 && hf == 1)   /* no-op */
+        return pixCopy(NULL, pixs);
+
+    pixr = pixGetRGBComponent(pixs, COLOR_RED);
+    pixg = pixGetRGBComponent(pixs, COLOR_GREEN);
+    pixb = pixGetRGBComponent(pixs, COLOR_BLUE);
+
+    pixrf = pixRankFilterGray(pixr, wf, hf, rank);
+    pixgf = pixRankFilterGray(pixg, wf, hf, rank);
+    pixbf = pixRankFilterGray(pixb, wf, hf, rank);
+
+    pixd = pixCreateRGBImage(pixrf, pixgf, pixbf);
+    pixDestroy(&pixr);
+    pixDestroy(&pixg);
+    pixDestroy(&pixb);
+    pixDestroy(&pixrf);
+    pixDestroy(&pixgf);
+    pixDestroy(&pixbf);
+    return pixd;
+}
+
+
+/*!
+ *  pixRankFilterGray()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              wf, hf  (width and height of filter; each is >= 1)
+ *              rank (in [0.0 ... 1.0])
+ *      Return: pixd (of rank values), or null on error
+ *
+ *  Notes:
+ *      (1) This defines, for each pixel in pixs, a neighborhood of
+ *          pixels given by a rectangle "centered" on the pixel.
+ *          This set of wf*hf pixels has a distribution of values,
+ *          and if they are sorted in increasing order, we choose
+ *          the pixel such that rank*(wf*hf-1) pixels have a lower
+ *          or equal value and (1-rank)*(wf*hf-1) pixels have an equal
+ *          or greater value.
+ *      (2) By this definition, the rank = 0.0 pixel has the lowest
+ *          value, and the rank = 1.0 pixel has the highest value.
+ *      (3) We add mirrored boundary pixels to avoid boundary effects,
+ *          and put the filter center at (0, 0).
+ *      (4) This dispatches to grayscale erosion or dilation if the
+ *          filter dimensions are odd and the rank is 0.0 or 1.0, rsp.
+ *      (5) Returns a copy if both wf and hf are 1.
+ *      (6) Uses row-major or column-major incremental updates to the
+ *          histograms depending on whether hf > wf or hv <= wf, rsp.
+ */
+PIX  *
+pixRankFilterGray(PIX       *pixs,
+                  l_int32    wf,
+                  l_int32    hf,
+                  l_float32  rank)
+{
+l_int32    w, h, d, i, j, k, m, n, rankloc, wplt, wpld, val, sum;
+l_int32   *histo, *histo16;
+l_uint32  *datat, *linet, *datad, *lined;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixRankFilterGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs) != NULL)
+        return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (wf < 1 || hf < 1)
+        return (PIX *)ERROR_PTR("wf < 1 || hf < 1", procName, NULL);
+    if (rank < 0.0 || rank > 1.0)
+        return (PIX *)ERROR_PTR("rank must be in [0.0, 1.0]", procName, NULL);
+    if (wf == 1 && hf == 1)   /* no-op */
+        return pixCopy(NULL, pixs);
+
+        /* For rank = 0.0, this is a grayscale erosion, and for rank = 1.0,
+         * a dilation.  Grayscale morphology operations are implemented
+         * for filters of odd dimension, so we dispatch to grayscale
+         * morphology if both wf and hf are odd.  Otherwise, we
+         * slightly adjust the rank (to get the correct behavior) and
+         * use the slower rank filter here. */
+    if (wf % 2 && hf % 2) {
+        if (rank == 0.0)
+            return pixErodeGray(pixs, wf, hf);
+        else if (rank == 1.0)
+            return pixDilateGray(pixs, wf, hf);
+    }
+    if (rank == 0.0) rank = 0.0001;
+    if (rank == 1.0) rank = 0.9999;
+
+        /* Add wf/2 to each side, and hf/2 to top and bottom of the
+         * image, mirroring for accuracy and to avoid special-casing
+         * the boundary. */
+    if ((pixt = pixAddMirroredBorder(pixs, wf / 2, wf / 2, hf / 2, hf / 2))
+        == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+        /* Set up the two histogram arrays. */
+    histo = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32));
+    histo16 = (l_int32 *)LEPT_CALLOC(16, sizeof(l_int32));
+    rankloc = (l_int32)(rank * wf * hf);
+
+        /* Place the filter center at (0, 0).  This is just a
+         * convenient location, because it allows us to perform
+         * the rank filter over x:(0 ... w - 1) and y:(0 ... h - 1). */
+    pixd = pixCreateTemplate(pixs);
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* If hf > wf, it's more efficient to use row-major scanning.
+         * Otherwise, traverse the image in use column-major order.  */
+    if (hf > wf) {
+        for (j = 0; j < w; j++) {  /* row-major */
+                /* Start each column with clean histogram arrays. */
+            for (n = 0; n < 256; n++)
+                histo[n] = 0;
+            for (n = 0; n < 16; n++)
+                histo16[n] = 0;
+
+            for (i = 0; i < h; i++) {  /* fast scan on columns */
+                    /* Update the histos for the new location */
+                lined = datad + i * wpld;
+                if (i == 0) {  /* do full histo */
+                    for (k = 0; k < hf; k++) {
+                        linet = datat + (i + k) * wplt;
+                        for (m = 0; m < wf; m++) {
+                            val = GET_DATA_BYTE(linet, j + m);
+                            histo[val]++;
+                            histo16[val >> 4]++;
+                        }
+                    }
+                } else {  /* incremental update */
+                    linet = datat + (i - 1) * wplt;
+                    for (m = 0; m < wf; m++) {  /* remove top line */
+                        val = GET_DATA_BYTE(linet, j + m);
+                        histo[val]--;
+                        histo16[val >> 4]--;
+                    }
+                    linet = datat + (i + hf -  1) * wplt;
+                    for (m = 0; m < wf; m++) {  /* add bottom line */
+                        val = GET_DATA_BYTE(linet, j + m);
+                        histo[val]++;
+                        histo16[val >> 4]++;
+                    }
+                }
+
+                    /* Find the rank value */
+                sum = 0;
+                for (n = 0; n < 16; n++) {  /* search over coarse histo */
+                    sum += histo16[n];
+                    if (sum > rankloc) {
+                        sum -= histo16[n];
+                        break;
+                    }
+                }
+                k = 16 * n;  /* starting value in fine histo */
+                for (m = 0; m < 16; m++) {
+                    sum += histo[k];
+                    if (sum > rankloc) {
+                        SET_DATA_BYTE(lined, j, k);
+                        break;
+                    }
+                    k++;
+                }
+            }
+        }
+    } else {  /* wf >= hf */
+        for (i = 0; i < h; i++) {  /* column-major */
+                /* Start each row with clean histogram arrays. */
+            for (n = 0; n < 256; n++)
+                histo[n] = 0;
+            for (n = 0; n < 16; n++)
+                histo16[n] = 0;
+            lined = datad + i * wpld;
+            for (j = 0; j < w; j++) {  /* fast scan on rows */
+                    /* Update the histos for the new location */
+                if (j == 0) {  /* do full histo */
+                    for (k = 0; k < hf; k++) {
+                        linet = datat + (i + k) * wplt;
+                        for (m = 0; m < wf; m++) {
+                            val = GET_DATA_BYTE(linet, j + m);
+                            histo[val]++;
+                            histo16[val >> 4]++;
+                        }
+                    }
+                } else {  /* incremental update at left and right sides */
+                    for (k = 0; k < hf; k++) {
+                        linet = datat + (i + k) * wplt;
+                        val = GET_DATA_BYTE(linet, j - 1);
+                        histo[val]--;
+                        histo16[val >> 4]--;
+                        val = GET_DATA_BYTE(linet, j + wf - 1);
+                        histo[val]++;
+                        histo16[val >> 4]++;
+                    }
+                }
+
+                    /* Find the rank value */
+                sum = 0;
+                for (n = 0; n < 16; n++) {  /* search over coarse histo */
+                    sum += histo16[n];
+                    if (sum > rankloc) {
+                        sum -= histo16[n];
+                        break;
+                    }
+                }
+                k = 16 * n;  /* starting value in fine histo */
+                for (m = 0; m < 16; m++) {
+                    sum += histo[k];
+                    if (sum > rankloc) {
+                        SET_DATA_BYTE(lined, j, k);
+                        break;
+                    }
+                    k++;
+                }
+            }
+        }
+    }
+
+    pixDestroy(&pixt);
+    LEPT_FREE(histo);
+    LEPT_FREE(histo16);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                             Median filter                            *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixMedianFilter()
+ *
+ *      Input:  pixs (8 or 32 bpp; no colormap)
+ *              wf, hf  (width and height of filter; each is >= 1)
+ *      Return: pixd (of median values), or null on error
+ */
+PIX  *
+pixMedianFilter(PIX     *pixs,
+                l_int32  wf,
+                l_int32  hf)
+{
+    PROCNAME("pixMedianFilter");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    return pixRankFilter(pixs, wf, hf, 0.5);
+}
+
+
+/*----------------------------------------------------------------------*
+ *                Rank filter (accelerated with downscaling)            *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixRankFilterWithScaling()
+ *
+ *      Input:  pixs (8 or 32 bpp; no colormap)
+ *              wf, hf  (width and height of filter; each is >= 1)
+ *              rank (in [0.0 ... 1.0])
+ *              scalefactor (scale factor; must be >= 0.2 and <= 0.7)
+ *      Return: pixd (of rank values), or null on error
+ *
+ *  Notes:
+ *      (1) This is a convenience function that downscales, does
+ *          the rank filtering, and upscales.  Because the down-
+ *          and up-scaling functions are very fast compared to
+ *          rank filtering, the time it takes is reduced from that
+ *          for the simple rank filtering operation by approximately
+ *          the square of the scaling factor.
+ */
+PIX  *
+pixRankFilterWithScaling(PIX       *pixs,
+                         l_int32    wf,
+                         l_int32    hf,
+                         l_float32  rank,
+                         l_float32  scalefactor)
+{
+l_int32  w, h, d, wfs, hfs;
+PIX     *pix1, *pix2, *pixd;
+
+    PROCNAME("pixRankFilterWithScaling");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetColormap(pixs) != NULL)
+        return (PIX *)ERROR_PTR("pixs has colormap", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+    if (wf < 1 || hf < 1)
+        return (PIX *)ERROR_PTR("wf < 1 || hf < 1", procName, NULL);
+    if (rank < 0.0 || rank > 1.0)
+        return (PIX *)ERROR_PTR("rank must be in [0.0, 1.0]", procName, NULL);
+    if (wf == 1 && hf == 1)   /* no-op */
+        return pixCopy(NULL, pixs);
+    if (scalefactor < 0.2 || scalefactor > 0.7) {
+        L_ERROR("invalid scale factor; no scaling used\n", procName);
+        return pixRankFilter(pixs, wf, hf, rank);
+    }
+
+    pix1 = pixScaleAreaMap(pixs, scalefactor, scalefactor);
+    wfs = L_MAX(1, (l_int32)(scalefactor * wf + 0.5));
+    hfs = L_MAX(1, (l_int32)(scalefactor * hf + 0.5));
+    pix2 = pixRankFilter(pix1, wfs, hfs, rank);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixd = pixScaleToSize(pix2, w, h);
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    return pixd;
+}
diff --git a/src/rbtree.c b/src/rbtree.c
new file mode 100644 (file)
index 0000000..048d54d
--- /dev/null
@@ -0,0 +1,874 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ * Modified from the excellent code here:
+ *     http://en.literateprograms.org/Red-black_tree_(C)?oldid=19567
+ * which has been placed in the public domain under the Creative Commons
+ * CC0 1.0 waiver (http://creativecommons.org/publicdomain/zero/1.0/).
+ */
+
+/*
+ *  rbtree.c
+ *
+ *  Basic functions for using red-black trees.  These are "nearly" balanced
+ *  sorted trees with ordering by key that allows insertion, lookup and
+ *  deletion of key/value pairs in log(n) time.
+ *
+ *  We use red-black trees to implement our version of:
+ *    * a map: a function that maps keys to values (int32).
+ *    * a set: a collection that is sorted by unique keys (without
+ *      associated values)
+ *
+ *  There are 5 invariant properties of RB trees:
+ *  (1) Each node is either red or black.
+ *  (2) The root node is black.
+ *  (3) All leaves are black and contain no data (null).
+ *  (4) Every red node has two children and both are black.  This is
+ *      equivalent to requiring the parent of every red node to be black.
+ *  (5) All paths from any given node to its leaf nodes contain the
+ *      same number of black nodes.
+ *
+ *  Interface to red-black tree
+ *           L_RBTREE       *l_rbtreeCreate()
+ *           RB_TYPE        *l_rbtreeLookup()
+ *           void            l_rbtreeInsert()
+ *           void            l_rbtreeDelete()
+ *           void            l_rbtreeDestroy()
+ *           L_RBTREE_NODE  *l_rbtreeGetFirst()
+ *           L_RBTREE_NODE  *l_rbtreeGetNext()
+ *           L_RBTREE_NODE  *l_rbtreeGetLast()
+ *           L_RBTREE_NODE  *l_rbtreeGetPrev()
+ *           l_int32         l_rbtreeGetCount()
+ *           void            l_rbtreePrint()
+ *
+ *  General comparison function
+ *           l_int32         l_compareKeys()
+ */
+
+#include "allheaders.h"
+
+    /* The node color enum is only needed in the rbtree implementation */
+enum {
+    L_RED_NODE = 1,
+    L_BLACK_NODE = 2
+};
+
+    /* The makes it simpler to read the code */
+typedef L_RBTREE_NODE node;
+
+    /* Lots of static helper functions */
+static void destroy_helper(node *n);
+static void count_helper(node *n, l_int32 *pcount);
+static void print_tree_helper(FILE *fp, node *n, l_int32 keytype,
+                              l_int32 indent);
+static node *grandparent(node *n);
+static node *sibling(node *n);
+static node *uncle(node *n);
+static void verify_properties(L_RBTREE *t);
+static void verify_property_1(node *root);
+static void verify_property_2(node *root);
+static l_int32 node_color(node *n);
+static void verify_property_4(node *root);
+static void verify_property_5(node *root);
+static void verify_property_5_helper(node *n, int black_count,
+                                     int* black_count_path);
+static node *new_node(RB_TYPE key, RB_TYPE value, l_int32 node_color,
+                      node *left, node *right);
+static node *lookup_node(L_RBTREE *t, RB_TYPE key);
+static void rotate_left(L_RBTREE *t, node *n);
+static void rotate_right(L_RBTREE *t, node *n);
+static void replace_node(L_RBTREE *t, node *oldn, node *newn);
+static void insert_case1(L_RBTREE *t, node *n);
+static void insert_case2(L_RBTREE *t, node *n);
+static void insert_case3(L_RBTREE *t, node *n);
+static void insert_case4(L_RBTREE *t, node *n);
+static void insert_case5(L_RBTREE *t, node *n);
+static node *maximum_node(node *root);
+static void delete_case1(L_RBTREE *t, node *n);
+static void delete_case2(L_RBTREE *t, node *n);
+static void delete_case3(L_RBTREE *t, node *n);
+static void delete_case4(L_RBTREE *t, node *n);
+static void delete_case5(L_RBTREE *t, node *n);
+static void delete_case6(L_RBTREE *t, node *n);
+
+#ifndef  NO_CONSOLE_IO
+#define  VERIFY_RBTREE     0   /* only for debugging */
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/* ------------------------------------------------------------- *
+ *                   Interface to Red-black Tree                 *
+ * ------------------------------------------------------------- */
+/*
+ *  l_rbtreeCreate()
+ *
+ *      Input:  keytype (defined by an enum for an RB_TYPE union)
+ *      Return: rbtree (container with empty ptr to the root)
+ */
+L_RBTREE *
+l_rbtreeCreate(l_int32  keytype)
+{
+    PROCNAME("l_rbtreeCreate");
+
+    if (keytype != L_INT_TYPE && keytype != L_UINT_TYPE &&
+        keytype != L_FLOAT_TYPE && keytype)
+        return (L_RBTREE *)ERROR_PTR("invalid keytype", procName, NULL);
+
+    L_RBTREE *t = (L_RBTREE *)LEPT_CALLOC(1, sizeof(L_RBTREE));
+    t->keytype = keytype;
+    verify_properties(t);
+    return t;
+}
+
+/*
+ *  l_rbtreeLookup()
+ *
+ *      Input:  t (rbtree, including root node)
+ *              key (find a node with this key)
+ *      Return: &value (a pointer to a union, if the node exists; else NULL)
+ */
+RB_TYPE *
+l_rbtreeLookup(L_RBTREE  *t,
+               RB_TYPE    key)
+{
+    PROCNAME("l_rbtreeLookup");
+
+    if (!t)
+        return (RB_TYPE *)ERROR_PTR("tree is null\n", procName, NULL);
+
+    node *n = lookup_node(t, key);
+    return n == NULL ? NULL : &n->value;
+}
+
+/*
+ *  l_rbtreeInsert()
+ *
+ *      Input:  t (rbtree, including root node)
+ *              key (insert a node with this key, if the key does not already
+ *                   exist in the tree)
+ *              value (typically an int, used for an index)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) If a node with the key already exists, this just updates the value.
+ */
+void
+l_rbtreeInsert(L_RBTREE     *t,
+               RB_TYPE       key,
+               RB_TYPE       value)
+{
+node  *n, *inserted_node;
+
+    PROCNAME("l_rbtreeInsert");
+
+    if (!t) {
+        L_ERROR("tree is null\n", procName);
+        return;
+    }
+
+    inserted_node = new_node(key, value, L_RED_NODE, NULL, NULL);
+    if (t->root == NULL) {
+        t->root = inserted_node;
+    } else {
+        n = t->root;
+        while (1) {
+            int comp_result = l_compareKeys(t->keytype, key, n->key);
+            if (comp_result == 0) {
+                n->value = value;
+                LEPT_FREE(inserted_node);
+                return;
+            } else if (comp_result < 0) {
+                if (n->left == NULL) {
+                    n->left = inserted_node;
+                    break;
+                } else {
+                    n = n->left;
+                }
+            } else {  /* comp_result > 0 */
+                if (n->right == NULL) {
+                    n->right = inserted_node;
+                    break;
+                } else {
+                    n = n->right;
+                }
+            }
+        }
+        inserted_node->parent = n;
+    }
+    insert_case1(t, inserted_node);
+    verify_properties(t);
+}
+
+/*
+ *  l_rbtreeDelete()
+ *
+ *      Input:  t (rbtree, including root node)
+ *              key (delete the node with this key)
+ *      Return: void
+ */
+void
+l_rbtreeDelete(L_RBTREE  *t,
+               RB_TYPE    key)
+{
+node  *n, *child;
+
+    PROCNAME("l_rbtreeDelete");
+
+    if (!t) {
+        L_ERROR("tree is null\n", procName);
+        return;
+    }
+
+    n = lookup_node(t, key);
+    if (n == NULL) return;  /* Key not found, do nothing */
+    if (n->left != NULL && n->right != NULL) {
+            /* Copy key/value from predecessor and then delete it instead */
+        node *pred = maximum_node(n->left);
+        n->key   = pred->key;
+        n->value = pred->value;
+        n = pred;
+    }
+
+        /* n->left == NULL || n->right == NULL */
+    child = n->right == NULL ? n->left  : n->right;
+    if (node_color(n) == L_BLACK_NODE) {
+        n->color = node_color(child);
+        delete_case1(t, n);
+    }
+    replace_node(t, n, child);
+    if (n->parent == NULL && child != NULL)  /* root should be black */
+        child->color = L_BLACK_NODE;
+    LEPT_FREE(n);
+
+    verify_properties(t);
+}
+
+/*
+ *  l_rbtreeDestroy()
+ *
+ *      Input:  &t (ptr to rbtree)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Destroys the tree and nulls the input tree ptr.
+ */
+void
+l_rbtreeDestroy(L_RBTREE  **pt)
+{
+node    *n;
+
+    if (!pt) return;
+    if (*pt == NULL) return;
+    n = (*pt)->root;
+    destroy_helper(n);
+    LEPT_FREE(*pt);
+    *pt = NULL;
+    return;
+}
+
+    /* postorder DFS */
+static void
+destroy_helper(node  *n)
+{
+    if (!n) return;
+    destroy_helper(n->left);
+    destroy_helper(n->right);
+    LEPT_FREE(n);
+}
+
+/*
+ *  l_rbtreeGetFirst()
+ *
+ *      Input:  t (rbtree, including root node)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This is the first node in an in-order traversal.
+ */
+L_RBTREE_NODE *
+l_rbtreeGetFirst(L_RBTREE  *t)
+{
+node  *n;
+
+    PROCNAME("l_rbtreeGetFirst");
+
+    if (!t)
+        return (L_RBTREE_NODE *)ERROR_PTR("tree is null", procName, NULL);
+    if (t->root == NULL) {
+        L_INFO("tree is empty\n", procName);
+        return NULL;
+    }
+
+        /* Just go down the left side as far as possible */
+    n = t->root;
+    while (n && n->left)
+        n = n->left;
+    return n;
+}
+
+/*
+ *  l_rbtreeGetNext()
+ *
+ *      Input:  n (current node)
+ *      Return: next node (or NULL if it's the last node)
+ *
+ *  Notes:
+ *      (1) This finds the next node, in an in-order traversal, from
+ *          the current node.
+ *      (2) It is useful as an iterator for a map.
+ *      (3) Call l_rbtreeGetFirst() to get the first node.
+ */
+L_RBTREE_NODE *
+l_rbtreeGetNext(L_RBTREE_NODE  *n)
+{
+    PROCNAME("l_rbtreeGetNext");
+
+    if (!n)
+        return (L_RBTREE_NODE *)ERROR_PTR("n not defined", procName, NULL);
+
+        /* If there is a right child, go to it, and then go left all the
+         * way to the end.  Otherwise go up to the parent; continue upward
+         * as long as you're on the right branch, but stop at the parent
+         * when you hit it from the left branch. */
+    if (n->right) {
+        n = n->right;
+        while (n->left)
+            n = n->left;
+        return n;
+    } else {
+        while (n->parent && n->parent->right == n)
+            n = n->parent;
+        return n->parent;
+    }
+}
+
+/*
+ *  l_rbtreeGetLast()
+ *
+ *      Input:  t (rbtree, including root node)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This is the last node in an in-order traversal.
+ */
+L_RBTREE_NODE *
+l_rbtreeGetLast(L_RBTREE  *t)
+{
+node  *n;
+
+    PROCNAME("l_rbtreeGetLast");
+
+    if (!t)
+        return (L_RBTREE_NODE *)ERROR_PTR("tree is null", procName, NULL);
+    if (t->root == NULL) {
+        L_INFO("tree is empty\n", procName);
+        return NULL;
+    }
+
+        /* Just go down the right side as far as possible */
+    n = t->root;
+    while (n && n->right)
+        n = n->right;
+    return n;
+}
+
+/*
+ *  l_rbtreeGetPrev()
+ *
+ *      Input:  n (current node)
+ *      Return: next node (or NULL if it's the first node)
+ *
+ *  Notes:
+ *      (1) This finds the previous node, in an in-order traversal, from
+ *          the current node.
+ *      (2) It is useful as an iterator for a map.
+ *      (3) Call l_rbtreeGetLast() to get the last node.
+ */
+L_RBTREE_NODE *
+l_rbtreeGetPrev(L_RBTREE_NODE  *n)
+{
+    PROCNAME("l_rbtreeGetPrev");
+
+    if (!n)
+        return (L_RBTREE_NODE *)ERROR_PTR("n not defined", procName, NULL);
+
+        /* If there is a left child, go to it, and then go right all the
+         * way to the end.  Otherwise go up to the parent; continue upward
+         * as long as you're on the left branch, but stop at the parent
+         * when you hit it from the right branch. */
+    if (n->left) {
+        n = n->left;
+        while (n->right)
+            n = n->right;
+        return n;
+    } else {
+        while (n->parent && n->parent->left == n)
+            n = n->parent;
+        return n->parent;
+    }
+}
+
+/*
+ *  l_rbtreeGetCount()
+ *
+ *      Input:  t (rbtree)
+ *      Return: count (the number of nodes in the tree); 0 on error
+ */
+l_int32
+l_rbtreeGetCount(L_RBTREE  *t)
+{
+l_int32  count = 0;
+node    *n;
+
+    if (!t) return 0;
+    n = t->root;
+    count_helper(n, &count);
+    return count;
+}
+
+    /* preorder DFS */
+static void
+count_helper(node  *n, l_int32  *pcount)
+{
+    if (n)
+        (*pcount)++;
+    else
+        return;
+
+    count_helper(n->left, pcount);
+    count_helper(n->right, pcount);
+}
+
+
+/*
+ *  l_rbtreePrint()
+ *
+ *      Input:  stream
+ *              t (rbtree)
+ *      Return: null
+ */
+void
+l_rbtreePrint(FILE      *fp,
+              L_RBTREE  *t)
+{
+    PROCNAME("l_rbtreePrint");
+    if (!fp) {
+        L_ERROR("stream not defined\n", procName);
+        return;
+    }
+    if (!t) {
+        L_ERROR("tree not defined\n", procName);
+        return;
+    }
+
+    print_tree_helper(fp, t->root, t->keytype, 0);
+    fprintf(fp, "\n");
+}
+
+#define INDENT_STEP  4
+
+static void
+print_tree_helper(FILE    *fp,
+                  node    *n,
+                  l_int32  keytype,
+                  l_int32  indent)
+{
+l_int32  i;
+
+    if (n == NULL) {
+        fprintf(fp, "<empty tree>");
+        return;
+    }
+    if (n->right != NULL) {
+        print_tree_helper(fp, n->right, keytype, indent + INDENT_STEP);
+    }
+    for (i = 0; i < indent; i++)
+        fprintf(fp, " ");
+    if (n->color == L_BLACK_NODE) {
+        if (keytype == L_INT_TYPE)
+            fprintf(fp, "%lld\n", n->key.itype);
+        else if (keytype == L_UINT_TYPE)
+            fprintf(fp, "%llx\n", n->key.utype);
+        else if (keytype == L_FLOAT_TYPE)
+            fprintf(fp, "%f\n", n->key.ftype);
+    } else {
+        if (keytype == L_INT_TYPE)
+            fprintf(fp, "<%lld>\n", n->key.itype);
+        else if (keytype == L_UINT_TYPE)
+            fprintf(fp, "<%llx>\n", n->key.utype);
+        else if (keytype == L_FLOAT_TYPE)
+            fprintf(fp, "<%f>\n", n->key.ftype);
+    }
+    if (n->left != NULL) {
+        print_tree_helper(fp, n->left, keytype, indent + INDENT_STEP);
+    }
+}
+
+
+/* ------------------------------------------------------------- *
+ *                   General comparison function                 *
+ * ------------------------------------------------------------- */
+l_int32
+l_compareKeys(l_int32  keytype,
+              RB_TYPE  left,
+              RB_TYPE  right)
+{
+static char  procName[] = "l_compareKeys";
+
+    if (keytype == L_INT_TYPE) {
+        if (left.itype < right.itype)
+            return -1;
+        else if (left.itype > right.itype)
+            return 1;
+        else {  /* equality */
+            return 0;
+        }
+    } else if (keytype == L_UINT_TYPE) {
+        if (left.utype < right.utype)
+            return -1;
+        else if (left.utype > right.utype)
+            return 1;
+        else {  /* equality */
+            return 0;
+        }
+    } else if (keytype == L_FLOAT_TYPE) {
+        if (left.ftype < right.ftype)
+            return -1;
+        else if (left.ftype > right.ftype)
+            return 1;
+        else {  /* equality */
+            return 0;
+        }
+    } else {
+        L_ERROR("unknown keytype %d\n", procName, keytype);
+        return 0;
+    }
+}
+
+
+/* ------------------------------------------------------------- *
+ *                  Static red-black tree helpers                *
+ * ------------------------------------------------------------- */
+static node *grandparent(node *n) {
+    if (!n || !n->parent || !n->parent->parent) {
+        L_ERROR("root and child of root have no grandparent\n", "grandparent");
+        return NULL;
+    }
+    return n->parent->parent;
+}
+
+static node *sibling(node *n) {
+    if (!n || !n->parent) {
+        L_ERROR("root has no sibling\n", "sibling");
+        return NULL;
+    }
+    if (n == n->parent->left)
+        return n->parent->right;
+    else
+        return n->parent->left;
+}
+
+static node *uncle(node *n) {
+    if (!n || !n->parent || !n->parent->parent) {
+        L_ERROR("root and child of root have no uncle\n", "uncle");
+        return NULL;
+    }
+    return sibling(n->parent);
+}
+
+
+static void verify_properties(L_RBTREE *t) {
+#if VERIFY_RBTREE
+    verify_property_1(t->root);
+    verify_property_2(t->root);
+    /* Property 3 is implicit */
+    verify_property_4(t->root);
+    verify_property_5(t->root);
+#endif
+}
+
+static void verify_property_1(node *n) {
+    if (node_color(n) != L_RED_NODE && node_color(n) != L_BLACK_NODE) {
+        L_ERROR("color neither RED nor BLACK\n", "verify_property_1");
+        return;
+    }
+    if (n == NULL) return;
+    verify_property_1(n->left);
+    verify_property_1(n->right);
+}
+
+static void verify_property_2(node *root) {
+    if (node_color(root) != L_BLACK_NODE)
+        L_ERROR("root is not black!\n", "verify_property_2");
+}
+
+static l_int32 node_color(node *n) {
+    return n == NULL ? L_BLACK_NODE : n->color;
+}
+
+static void verify_property_4(node *n) {
+    if (node_color(n) == L_RED_NODE) {
+        if (node_color(n->left) != L_BLACK_NODE ||
+            node_color(n->right) != L_BLACK_NODE ||
+            node_color(n->parent) != L_BLACK_NODE) {
+            L_ERROR("children & parent not all BLACK", "verify_property_4");
+            return;
+        }
+    }
+    if (n == NULL) return;
+    verify_property_4(n->left);
+    verify_property_4(n->right);
+}
+
+static void verify_property_5(node *root) {
+    int black_count_path = -1;
+    verify_property_5_helper(root, 0, &black_count_path);
+}
+
+static void verify_property_5_helper(node *n, int black_count,
+                                     int* path_black_count) {
+    if (node_color(n) == L_BLACK_NODE) {
+        black_count++;
+    }
+    if (n == NULL) {
+        if (*path_black_count == -1) {
+            *path_black_count = black_count;
+        } else if (*path_black_count != black_count) {
+            L_ERROR("incorrect black count", "verify_property_5_helper");
+        }
+        return;
+    }
+    verify_property_5_helper(n->left,  black_count, path_black_count);
+    verify_property_5_helper(n->right, black_count, path_black_count);
+}
+
+static node *new_node(RB_TYPE key, RB_TYPE value, l_int32 node_color,
+                      node *left, node *right) {
+    node *result = (node *)LEPT_CALLOC(1, sizeof(node));
+    result->key = key;
+    result->value = value;
+    result->color = node_color;
+    result->left = left;
+    result->right = right;
+    if (left != NULL) left->parent = result;
+    if (right != NULL) right->parent = result;
+    result->parent = NULL;
+    return result;
+}
+
+static node *lookup_node(L_RBTREE *t, RB_TYPE key) {
+    node *n = t->root;
+    while (n != NULL) {
+        int comp_result = l_compareKeys(t->keytype, key, n->key);
+        if (comp_result == 0) {
+            return n;
+        } else if (comp_result < 0) {
+            n = n->left;
+        } else {  /* comp_result > 0 */
+            n = n->right;
+        }
+    }
+    return n;
+}
+
+static void rotate_left(L_RBTREE *t, node *n) {
+    node *r = n->right;
+    replace_node(t, n, r);
+    n->right = r->left;
+    if (r->left != NULL) {
+        r->left->parent = n;
+    }
+    r->left = n;
+    n->parent = r;
+}
+
+static void rotate_right(L_RBTREE *t, node *n) {
+    node *L = n->left;
+    replace_node(t, n, L);
+    n->left = L->right;
+    if (L->right != NULL) {
+        L->right->parent = n;
+    }
+    L->right = n;
+    n->parent = L;
+}
+
+static void replace_node(L_RBTREE *t, node *oldn, node *newn) {
+    if (oldn->parent == NULL) {
+        t->root = newn;
+    } else {
+        if (oldn == oldn->parent->left)
+            oldn->parent->left = newn;
+        else
+            oldn->parent->right = newn;
+    }
+    if (newn != NULL) {
+        newn->parent = oldn->parent;
+    }
+}
+
+static void insert_case1(L_RBTREE *t, node *n) {
+    if (n->parent == NULL)
+        n->color = L_BLACK_NODE;
+    else
+        insert_case2(t, n);
+}
+
+static void insert_case2(L_RBTREE *t, node *n) {
+    if (node_color(n->parent) == L_BLACK_NODE)
+        return; /* Tree is still valid */
+    else
+        insert_case3(t, n);
+}
+
+static void insert_case3(L_RBTREE *t, node *n) {
+    if (node_color(uncle(n)) == L_RED_NODE) {
+        n->parent->color = L_BLACK_NODE;
+        uncle(n)->color = L_BLACK_NODE;
+        grandparent(n)->color = L_RED_NODE;
+        insert_case1(t, grandparent(n));
+    } else {
+        insert_case4(t, n);
+    }
+}
+
+static void insert_case4(L_RBTREE *t, node *n) {
+    if (n == n->parent->right && n->parent == grandparent(n)->left) {
+        rotate_left(t, n->parent);
+        n = n->left;
+    } else if (n == n->parent->left && n->parent == grandparent(n)->right) {
+        rotate_right(t, n->parent);
+        n = n->right;
+    }
+    insert_case5(t, n);
+}
+
+static void insert_case5(L_RBTREE *t, node *n) {
+    n->parent->color = L_BLACK_NODE;
+    grandparent(n)->color = L_RED_NODE;
+    if (n == n->parent->left && n->parent == grandparent(n)->left) {
+        rotate_right(t, grandparent(n));
+    } else if (n == n->parent->right && n->parent == grandparent(n)->right) {
+        rotate_left(t, grandparent(n));
+    } else {
+        L_ERROR("identity confusion\n", "insert_case5");
+    }
+}
+
+static node *maximum_node(node *n) {
+    if (!n) {
+        L_ERROR("n not defined\n", "maximum_node");
+        return NULL;
+    }
+    while (n->right != NULL) {
+        n = n->right;
+    }
+    return n;
+}
+
+static void delete_case1(L_RBTREE *t, node *n) {
+    if (n->parent == NULL)
+        return;
+    else
+        delete_case2(t, n);
+}
+
+static void delete_case2(L_RBTREE *t, node *n) {
+    if (node_color(sibling(n)) == L_RED_NODE) {
+        n->parent->color = L_RED_NODE;
+        sibling(n)->color = L_BLACK_NODE;
+        if (n == n->parent->left)
+            rotate_left(t, n->parent);
+        else
+            rotate_right(t, n->parent);
+    }
+    delete_case3(t, n);
+}
+
+static void delete_case3(L_RBTREE *t, node *n) {
+    if (node_color(n->parent) == L_BLACK_NODE &&
+        node_color(sibling(n)) == L_BLACK_NODE &&
+        node_color(sibling(n)->left) == L_BLACK_NODE &&
+        node_color(sibling(n)->right) == L_BLACK_NODE) {
+        sibling(n)->color = L_RED_NODE;
+        delete_case1(t, n->parent);
+    } else {
+        delete_case4(t, n);
+    }
+}
+
+static void delete_case4(L_RBTREE *t, node *n) {
+    if (node_color(n->parent) == L_RED_NODE &&
+        node_color(sibling(n)) == L_BLACK_NODE &&
+        node_color(sibling(n)->left) == L_BLACK_NODE &&
+        node_color(sibling(n)->right) == L_BLACK_NODE) {
+        sibling(n)->color = L_RED_NODE;
+        n->parent->color = L_BLACK_NODE;
+    } else {
+        delete_case5(t, n);
+    }
+}
+
+static void delete_case5(L_RBTREE *t, node *n) {
+    if (n == n->parent->left &&
+        node_color(sibling(n)) == L_BLACK_NODE &&
+        node_color(sibling(n)->left) == L_RED_NODE &&
+        node_color(sibling(n)->right) == L_BLACK_NODE) {
+        sibling(n)->color = L_RED_NODE;
+        sibling(n)->left->color = L_BLACK_NODE;
+        rotate_right(t, sibling(n));
+    } else if (n == n->parent->right &&
+               node_color(sibling(n)) == L_BLACK_NODE &&
+               node_color(sibling(n)->right) == L_RED_NODE &&
+               node_color(sibling(n)->left) == L_BLACK_NODE) {
+        sibling(n)->color = L_RED_NODE;
+        sibling(n)->right->color = L_BLACK_NODE;
+        rotate_left(t, sibling(n));
+    }
+    delete_case6(t, n);
+}
+
+static void delete_case6(L_RBTREE *t, node *n) {
+    sibling(n)->color = node_color(n->parent);
+    n->parent->color = L_BLACK_NODE;
+    if (n == n->parent->left) {
+        if (node_color(sibling(n)->right) != L_RED_NODE) {
+            L_ERROR("right sibling is not RED", "delete_case6");
+            return;
+        }
+        sibling(n)->right->color = L_BLACK_NODE;
+        rotate_left(t, n->parent);
+    } else {
+        if (node_color(sibling(n)->left) != L_RED_NODE) {
+            L_ERROR("left sibling is not RED", "delete_case6");
+            return;
+        }
+        sibling(n)->left->color = L_BLACK_NODE;
+        rotate_right(t, n->parent);
+    }
+}
+
diff --git a/src/rbtree.h b/src/rbtree.h
new file mode 100644 (file)
index 0000000..3da7828
--- /dev/null
@@ -0,0 +1,87 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ * Modified from the excellent code here:
+ *     http://en.literateprograms.org/Red-black_tree_(C)?oldid=19567
+ * which has been placed in the public domain under the Creative Commons
+ * CC0 1.0 waiver (http://creativecommons.org/publicdomain/zero/1.0/).
+ *
+ * When the key is generated from a hash (e.g., string --> uint64),
+ * there is always the possibility of having collisions, but to make
+ * the collision probability very low requires using a large hash.
+ * For that reason, the key types are 64 bit quantities, which will result
+ * in a negligible probabililty of collisions for millions of hashed values.
+ * Using 8 byte keys instead of 4 byte keys requires a little more
+ * storage, but the simplification in being able to ignore collisions
+ * with the red-black trees for most applications is worth it.
+ */
+
+#ifndef  LEPTONICA_RBTREE_H
+#define  LEPTONICA_RBTREE_H
+
+    /* The three valid key types for red-black trees, maps and sets. */
+enum {
+    L_INT_TYPE = 1,
+    L_UINT_TYPE = 2,
+    L_FLOAT_TYPE = 3
+};
+
+    /* Storage for keys and values for red-black trees, maps and sets.
+     * Note:
+     *   (1) Keys and values of the valid key types are all 64-bit
+     *   (2) (void *) can be used for values but not for keys.
+     */
+union Rb_Type {
+    l_int64    itype;
+    l_uint64   utype;
+    l_float64  ftype;
+    void      *ptype;
+};
+typedef union Rb_Type RB_TYPE;
+
+struct L_Rbtree {
+    struct L_Rbtree_Node  *root;
+    l_int32                keytype;
+};
+typedef struct L_Rbtree L_RBTREE;
+typedef struct L_Rbtree L_AMAP;  /* hide underlying implementation for map */
+typedef struct L_Rbtree L_ASET;  /* hide underlying implementation for set */
+
+struct L_Rbtree_Node {
+    union Rb_Type          key;
+    union Rb_Type          value;
+    struct L_Rbtree_Node  *left;
+    struct L_Rbtree_Node  *right;
+    struct L_Rbtree_Node  *parent;
+    l_int32                color;
+};
+typedef struct L_Rbtree_Node L_RBTREE_NODE;
+typedef struct L_Rbtree_Node L_AMAP_NODE;  /* hide tree implementation */
+typedef struct L_Rbtree_Node L_ASET_NODE;  /* hide tree implementation */
+
+
+#endif  /* LEPTONICA_RBTREE_H */
diff --git a/src/readbarcode.c b/src/readbarcode.c
new file mode 100644 (file)
index 0000000..c4a607a
--- /dev/null
@@ -0,0 +1,1486 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  readbarcode.c
+ *
+ *      Basic operations to locate and identify the line widths
+ *      in 1D barcodes.
+ *
+ *      Top level
+ *          SARRAY          *pixProcessBarcodes()
+ *
+ *      Next levels
+ *          PIXA            *pixExtractBarcodes()
+ *          SARRAY          *pixReadBarcodes()
+ *          l_int32          pixReadBarcodeWidths()
+ *
+ *      Location
+ *          BOXA            *pixLocateBarcodes()
+ *          static PIX      *pixGenerateBarcodeMask()
+ *
+ *      Extraction and deskew
+ *          PIXA            *pixDeskewBarcodes()
+ *
+ *      Process to get line widths
+ *          NUMA            *pixExtractBarcodeWidths1()
+ *          NUMA            *pixExtractBarcodeWidths2()
+ *          NUMA            *pixExtractBarcodeCrossings()
+ *
+ *      Average adjacent rasters
+ *          static NUMA     *pixAverageRasterScans()
+ *
+ *      Signal processing for barcode widths
+ *          NUMA            *numaQuantizeCrossingsByWidth()
+ *          static l_int32   numaGetCrossingDistances()
+ *          static NUMA     *numaLocatePeakRanges()
+ *          static NUMA     *numaGetPeakCentroids()
+ *          static NUMA     *numaGetPeakWidthLUT()
+ *          NUMA            *numaQuantizeCrossingsByWindow()
+ *          static l_int32   numaEvalBestWidthAndShift()
+ *          static l_int32   numaEvalSyncError()
+ *
+ *
+ *  NOTE CAREFULLY: This is "early beta" code.  It has not been tuned
+ *  to work robustly on a large database of barcode images.  I'm putting
+ *  it out so that people can play with it, find out how it breaks, and
+ *  contribute decoders for other barcode formats.  Both the functional
+ *  interfaces and ABI will almost certainly change in the coming
+ *  few months.  The actual decoder, in bardecode.c, at present only
+ *  works on the following codes: Code I2of5, Code 2of5, Code 39, Code 93
+ *  Codabar and UPCA.  To add another barcode format, it is necessary
+ *  to make changes in readbarcode.h and bardecode.c.
+ *  The program prog/barcodetest shows how to run from the top level
+ *  (image --> decoded data).
+ */
+
+#include <string.h>
+#include "allheaders.h"
+#include "readbarcode.h"
+
+    /* Parameters for pixGenerateBarcodeMask() */
+static const l_int32  MAX_SPACE_WIDTH = 19;  /* was 15 */
+static const l_int32  MAX_NOISE_WIDTH = 50;  /* smaller than barcode width */
+static const l_int32  MAX_NOISE_HEIGHT = 30;  /* smaller than barcode height */
+
+    /* Static functions */
+static PIX *pixGenerateBarcodeMask(PIX *pixs, l_int32 maxspace,
+                                   l_int32 nwidth, l_int32 nheight);
+static NUMA *pixAverageRasterScans(PIX *pixs, l_int32 nscans);
+static l_int32 numaGetCrossingDistances(NUMA *nas, NUMA **pnaedist,
+                                        NUMA **pnaodist, l_float32 *pmindist,
+                                        l_float32 *pmaxdist);
+static NUMA *numaLocatePeakRanges(NUMA *nas, l_float32 minfirst,
+                                  l_float32 minsep, l_float32 maxmin);
+static NUMA *numaGetPeakCentroids(NUMA *nahist, NUMA *narange);
+static NUMA *numaGetPeakWidthLUT(NUMA *narange, NUMA *nacent);
+static l_int32 numaEvalBestWidthAndShift(NUMA *nas, l_int32 nwidth,
+                                         l_int32 nshift, l_float32 minwidth,
+                                         l_float32 maxwidth,
+                                         l_float32 *pbestwidth,
+                                         l_float32 *pbestshift,
+                                         l_float32 *pbestscore);
+static l_int32 numaEvalSyncError(NUMA *nas, l_int32 ifirst, l_int32 ilast,
+                                 l_float32 width, l_float32 shift,
+                                 l_float32 *pscore, NUMA **pnad);
+
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_DESKEW     1
+#define  DEBUG_WIDTHS     0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*------------------------------------------------------------------------*
+ *                               Top level                                *
+ *------------------------------------------------------------------------*/
+/*!
+ *  pixProcessBarcodes()
+ *
+ *      Input:  pixs (any depth)
+ *              format (L_BF_ANY, L_BF_CODEI2OF5, L_BF_CODE93, ...)
+ *              method (L_USE_WIDTHS, L_USE_WINDOWS)
+ *              &saw (<optional return> sarray of bar widths)
+ *              debugflag (use 1 to generate debug output)
+ *      Return: sarray (text of barcodes), or null if none found or on error
+ */
+SARRAY *
+pixProcessBarcodes(PIX      *pixs,
+                   l_int32   format,
+                   l_int32   method,
+                   SARRAY  **psaw,
+                   l_int32   debugflag)
+{
+PIX     *pixg;
+PIXA    *pixa;
+SARRAY  *sad;
+
+    PROCNAME("pixProcessBarcodes");
+
+    if (psaw) *psaw = NULL;
+    if (!pixs)
+        return (SARRAY *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (format != L_BF_ANY && !barcodeFormatIsSupported(format))
+        return (SARRAY *)ERROR_PTR("unsupported format", procName, NULL);
+    if (method != L_USE_WIDTHS && method != L_USE_WINDOWS)
+        return (SARRAY *)ERROR_PTR("invalid method", procName, NULL);
+
+        /* Get an 8 bpp image, no cmap */
+    if (pixGetDepth(pixs) == 8 && !pixGetColormap(pixs))
+        pixg = pixClone(pixs);
+    else
+        pixg = pixConvertTo8(pixs, 0);
+
+    if ((pixa = pixExtractBarcodes(pixg, debugflag)) == NULL) {
+        pixDestroy(&pixg);
+        return (SARRAY *)ERROR_PTR("no barcode(s) found", procName, NULL);
+    }
+
+    sad = pixReadBarcodes(pixa, format, method, psaw, debugflag);
+
+    pixDestroy(&pixg);
+    pixaDestroy(&pixa);
+    return sad;
+}
+
+
+/*!
+ *  pixExtractBarcodes()
+ *
+ *      Input:  pixs (8 bpp, no colormap)
+ *              debugflag (use 1 to generate debug output)
+ *      Return: pixa (deskewed and cropped barcodes), or null if
+ *                    none found or on error
+ */
+PIXA *
+pixExtractBarcodes(PIX     *pixs,
+                   l_int32  debugflag)
+{
+l_int32    i, n;
+l_float32  angle, conf;
+BOX       *box;
+BOXA      *boxa;
+PIX       *pixb, *pixm, *pixt;
+PIXA      *pixa;
+
+    PROCNAME("pixExtractBarcodes");
+
+    if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
+        return (PIXA *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+
+        /* Locate them; use small threshold for edges. */
+    boxa = pixLocateBarcodes(pixs, 20, &pixb, &pixm);
+    n = boxaGetCount(boxa);
+    L_INFO("%d possible barcode(s) found\n", procName, n);
+    if (n == 0) {
+        boxaDestroy(&boxa);
+        pixDestroy(&pixb);
+        pixDestroy(&pixm);
+        return NULL;
+    }
+
+    if (debugflag) {
+        boxaWriteStream(stderr, boxa);
+        pixDisplay(pixb, 100, 100);
+        pixDisplay(pixm, 800, 100);
+    }
+
+        /* Deskew each barcode individually */
+    pixa = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        box = boxaGetBox(boxa, i, L_CLONE);
+        pixt = pixDeskewBarcode(pixs, pixb, box, 15, 20, &angle, &conf);
+        L_INFO("angle = %6.2f, conf = %6.2f\n", procName, angle, conf);
+        if (conf > 5.0) {
+            pixaAddPix(pixa, pixt, L_INSERT);
+            pixaAddBox(pixa, box, L_INSERT);
+        } else {
+            pixDestroy(&pixt);
+            boxDestroy(&box);
+        }
+    }
+
+#if  DEBUG_DESKEW
+    pixt = pixaDisplayTiledInRows(pixa, 8, 1000, 1.0, 0, 30, 2);
+    pixWrite("junkpixt", pixt, IFF_PNG);
+    pixDestroy(&pixt);
+#endif  /* DEBUG_DESKEW */
+
+    pixDestroy(&pixb);
+    pixDestroy(&pixm);
+    boxaDestroy(&boxa);
+    return pixa;
+}
+
+
+/*!
+ *  pixReadBarcodes()
+ *
+ *      Input:  pixa (of 8 bpp deskewed and cropped barcodes)
+ *              format (L_BF_ANY, L_BF_CODEI2OF5, L_BF_CODE93, ...)
+ *              method (L_USE_WIDTHS, L_USE_WINDOWS);
+ *              &saw (<optional return> sarray of bar widths)
+ *              debugflag (use 1 to generate debug output)
+ *      Return: sa (sarray of widths, one string for each barcode found),
+ *                  or null on error
+ */
+SARRAY *
+pixReadBarcodes(PIXA     *pixa,
+                l_int32   format,
+                l_int32   method,
+                SARRAY  **psaw,
+                l_int32   debugflag)
+{
+char      *barstr, *data;
+char       emptystring[] = "";
+l_int32    i, j, n, nbars, ival;
+NUMA      *na;
+PIX       *pixt;
+SARRAY    *saw, *sad;
+
+    PROCNAME("pixReadBarcodes");
+
+    if (psaw) *psaw = NULL;
+    if (!pixa)
+        return (SARRAY *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (format != L_BF_ANY && !barcodeFormatIsSupported(format))
+        return (SARRAY *)ERROR_PTR("unsupported format", procName, NULL);
+    if (method != L_USE_WIDTHS && method != L_USE_WINDOWS)
+        return (SARRAY *)ERROR_PTR("invalid method", procName, NULL);
+
+    n = pixaGetCount(pixa);
+    saw = sarrayCreate(n);
+    sad = sarrayCreate(n);
+    for (i = 0; i < n; i++) {
+            /* Extract the widths of the lines in each barcode */
+        pixt = pixaGetPix(pixa, i, L_CLONE);
+        na = pixReadBarcodeWidths(pixt, method, debugflag);
+        pixDestroy(&pixt);
+        if (!na) {
+            ERROR_INT("valid barcode widths not returned", procName, 1);
+            continue;
+        }
+
+            /* Save the widths as a string */
+        nbars = numaGetCount(na);
+        barstr = (char *)LEPT_CALLOC(nbars + 1, sizeof(char));
+        for (j = 0; j < nbars; j++) {
+            numaGetIValue(na, j, &ival);
+            barstr[j] = 0x30 + ival;
+        }
+        sarrayAddString(saw, barstr, L_INSERT);
+        numaDestroy(&na);
+
+            /* Decode the width strings */
+        data = barcodeDispatchDecoder(barstr, format, debugflag);
+        if (!data) {
+            ERROR_INT("barcode not decoded", procName, 1);
+            sarrayAddString(sad, emptystring, L_COPY);
+            continue;
+        }
+        sarrayAddString(sad, data, L_INSERT);
+    }
+
+        /* If nothing found, clean up */
+    if (sarrayGetCount(saw) == 0) {
+        sarrayDestroy(&saw);
+        sarrayDestroy(&sad);
+        return (SARRAY *)ERROR_PTR("no valid barcode data", procName, NULL);
+    }
+
+    if (psaw)
+        *psaw = saw;
+    else
+        sarrayDestroy(&saw);
+
+    return sad;
+}
+
+
+/*!
+ *  pixReadBarcodeWidths()
+ *
+ *      Input:  pixs (of 8 bpp deskewed and cropped barcode)
+ *              method (L_USE_WIDTHS, L_USE_WINDOWS);
+ *              debugflag (use 1 to generate debug output)
+ *      Return: na (numa of widths (each in set {1,2,3,4}), or null on error
+ */
+NUMA *
+pixReadBarcodeWidths(PIX     *pixs,
+                     l_int32  method,
+                     l_int32  debugflag)
+{
+l_float32  winwidth;
+NUMA      *na;
+
+    PROCNAME("pixReadBarcodeWidths");
+
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (NUMA *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+    if (method != L_USE_WIDTHS && method != L_USE_WINDOWS)
+        return (NUMA *)ERROR_PTR("invalid method", procName, NULL);
+
+        /* Extract the widths of the lines in each barcode */
+    if (method == L_USE_WIDTHS)
+        na = pixExtractBarcodeWidths1(pixs, 120, 0.25, NULL, NULL,
+                                      debugflag);
+    else  /* method == L_USE_WINDOWS */
+        na = pixExtractBarcodeWidths2(pixs, 120, &winwidth,
+                                      NULL, debugflag);
+#if  DEBUG_WIDTHS
+        if (method == L_USE_WINDOWS)
+            fprintf(stderr, "Window width for barcode: %7.3f\n", winwidth);
+        numaWriteStream(stderr, na);
+#endif  /* DEBUG_WIDTHS */
+
+    if (!na)
+        return (NUMA *)ERROR_PTR("barcode widths invalid", procName, NULL);
+
+    return na;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                        Locate barcode in image                         *
+ *------------------------------------------------------------------------*/
+/*!
+ *  pixLocateBarcodes()
+ *
+ *      Input:  pixs (any depth)
+ *              thresh (for binarization of edge filter output; typ. 20)
+ *              &pixb (<optional return> binarized edge filtered input image)
+ *              &pixm (<optional return> mask over barcodes)
+ *      Return: boxa (location of barcodes), or null if none found or on error
+ */
+BOXA *
+pixLocateBarcodes(PIX     *pixs,
+                  l_int32  thresh,
+                  PIX    **ppixb,
+                  PIX    **ppixm)
+{
+BOXA  *boxa;
+PIX   *pix8, *pixe, *pixb, *pixm;
+
+    PROCNAME("pixLocateBarcodes");
+
+    if (!pixs)
+        return (BOXA *)ERROR_PTR("pixs not defined", procName, NULL);
+
+        /* Get an 8 bpp image, no cmap */
+    if (pixGetDepth(pixs) == 8 && !pixGetColormap(pixs))
+        pix8 = pixClone(pixs);
+    else
+        pix8 = pixConvertTo8(pixs, 0);
+
+        /* Get a 1 bpp image of the edges */
+    pixe = pixSobelEdgeFilter(pix8, L_ALL_EDGES);
+    pixb = pixThresholdToBinary(pixe, thresh);
+    pixInvert(pixb, pixb);
+    pixDestroy(&pix8);
+    pixDestroy(&pixe);
+
+    pixm = pixGenerateBarcodeMask(pixb, MAX_SPACE_WIDTH, MAX_NOISE_WIDTH,
+                                  MAX_NOISE_HEIGHT);
+    boxa = pixConnComp(pixm, NULL, 8);
+
+    if (ppixb)
+        *ppixb = pixb;
+    else
+        pixDestroy(&pixb);
+    if (ppixm)
+        *ppixm = pixm;
+    else
+        pixDestroy(&pixm);
+
+    return boxa;
+}
+
+
+/*!
+ *  pixGenerateBarcodeMask()
+ *
+ *      Input:  pixs (1 bpp)
+ *              maxspace (largest space in the barcode, in pixels)
+ *              nwidth (opening 'width' to remove noise)
+ *              nheight (opening 'height' to remove noise)
+ *      Return: pixm (mask over barcodes), or null if none found or on error
+ *
+ *  Notes:
+ *      (1) For noise removal, 'width' and 'height' are referred to the
+ *          barcode orientation.
+ *      (2) If there is skew, the mask will not cover the barcode corners.
+ */
+static PIX *
+pixGenerateBarcodeMask(PIX     *pixs,
+                       l_int32  maxspace,
+                       l_int32  nwidth,
+                       l_int32  nheight)
+{
+PIX  *pixt1, *pixt2, *pixd;
+
+    PROCNAME("pixGenerateBarcodeMask");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+        /* Identify horizontal barcodes */
+    pixt1 = pixCloseBrick(NULL, pixs, maxspace + 1, 1);
+    pixt2 = pixOpenBrick(NULL, pixs, maxspace + 1, 1);
+    pixXor(pixt2, pixt2, pixt1);
+    pixOpenBrick(pixt2, pixt2, nwidth, nheight);
+    pixDestroy(&pixt1);
+
+        /* Identify vertical barcodes */
+    pixt1 = pixCloseBrick(NULL, pixs, 1, maxspace + 1);
+    pixd = pixOpenBrick(NULL, pixs, 1, maxspace + 1);
+    pixXor(pixd, pixd, pixt1);
+    pixOpenBrick(pixd, pixd, nheight, nwidth);
+    pixDestroy(&pixt1);
+
+        /* Combine to get all barcodes */
+    pixOr(pixd, pixd, pixt2);
+    pixDestroy(&pixt2);
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                        Extract and deskew barcode                      *
+ *------------------------------------------------------------------------*/
+/*!
+ *  pixDeskewBarcode()
+ *
+ *      Input:  pixs (input image; 8 bpp)
+ *              pixb (binarized edge-filtered input image)
+ *              box (identified region containing barcode)
+ *              margin (of extra pixels around box to extract)
+ *              threshold (for binarization; ~20)
+ *              &angle (<optional return> in degrees, clockwise is positive)
+ *              &conf (<optional return> confidence)
+ *      Return: pixd (deskewed barcode), or null on error
+ *
+ *  Note:
+ *     (1) The (optional) angle returned is the angle in degrees (cw positive)
+ *         necessary to rotate the image so that it is deskewed.
+ */
+PIX *
+pixDeskewBarcode(PIX        *pixs,
+                 PIX        *pixb,
+                 BOX        *box,
+                 l_int32     margin,
+                 l_int32     threshold,
+                 l_float32  *pangle,
+                 l_float32  *pconf)
+{
+l_int32    x, y, w, h, n;
+l_float32  angle, angle1, angle2, conf, conf1, conf2, score1, score2, deg2rad;
+BOX       *boxe, *boxt;
+BOXA      *boxa, *boxat;
+PIX       *pixt1, *pixt2, *pixt3, *pixt4, *pixt5, *pixt6, *pixd;
+
+    PROCNAME("pixDeskewBarcode");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+    if (!pixb || pixGetDepth(pixb) != 1)
+        return (PIX *)ERROR_PTR("pixb undefined or not 1 bpp", procName, NULL);
+    if (!box)
+        return (PIX *)ERROR_PTR("box not defined or 1 bpp", procName, NULL);
+
+        /* Clip out */
+    deg2rad = 3.1415926535 / 180.;
+    boxGetGeometry(box, &x, &y, &w, &h);
+    boxe = boxCreate(x - 25, y - 25, w + 51, h + 51);
+    pixt1 = pixClipRectangle(pixb, boxe, NULL);
+    pixt2 = pixClipRectangle(pixs, boxe, NULL);
+    boxDestroy(&boxe);
+
+        /* Deskew, looking at all possible orientations over 180 degrees */
+    pixt3 = pixRotateOrth(pixt1, 1);  /* look for vertical bar lines */
+    pixt4 = pixClone(pixt1);   /* look for horizontal bar lines */
+    pixFindSkewSweepAndSearchScore(pixt3, &angle1, &conf1, &score1,
+                                   1, 1, 0.0, 45.0, 2.5, 0.01);
+    pixFindSkewSweepAndSearchScore(pixt4, &angle2, &conf2, &score2,
+                                   1, 1, 0.0, 45.0, 2.5, 0.01);
+
+        /* Because we're using the boundary pixels of the barcodes,
+         * the peak can be sharper (and the confidence ratio higher)
+         * from the signal across the top and bottom of the barcode.
+         * However, the max score, which is the magnitude of the signal
+         * at the optimum skew angle, will be smaller, so we use the
+         * max score as the primary indicator of orientation. */
+    if (score1 >= score2) {
+        conf = conf1;
+        if (conf1 > 6.0 && L_ABS(angle1) > 0.1) {
+            angle = angle1;
+            pixt5 = pixRotate(pixt2, deg2rad * angle1, L_ROTATE_AREA_MAP,
+                              L_BRING_IN_WHITE, 0, 0);
+        } else {
+            angle = 0.0;
+            pixt5 = pixClone(pixt2);
+        }
+    } else {  /* score2 > score1 */
+        conf = conf2;
+        pixt6 = pixRotateOrth(pixt2, 1);
+        if (conf2 > 6.0 && L_ABS(angle2) > 0.1) {
+            angle = 90.0 + angle2;
+            pixt5 = pixRotate(pixt6, deg2rad * angle2, L_ROTATE_AREA_MAP,
+                              L_BRING_IN_WHITE, 0, 0);
+        } else {
+            angle = 90.0;
+            pixt5 = pixClone(pixt6);
+        }
+        pixDestroy(&pixt6);
+    }
+    pixDestroy(&pixt3);
+    pixDestroy(&pixt4);
+
+        /* Extract barcode plus a margin around it */
+    boxa = pixLocateBarcodes(pixt5, threshold, 0, 0);
+    if ((n = boxaGetCount(boxa)) != 1) {
+        L_WARNING("barcode mask in %d components\n", procName, n);
+        boxat = boxaSort(boxa, L_SORT_BY_AREA, L_SORT_DECREASING, NULL);
+    } else {
+        boxat = boxaCopy(boxa, L_CLONE);
+    }
+    boxt = boxaGetBox(boxat, 0, L_CLONE);
+    boxGetGeometry(boxt, &x, &y, &w, &h);
+    boxe = boxCreate(x - margin, y - margin, w + 2 * margin,
+                     h + 2 * margin);
+    pixd = pixClipRectangle(pixt5, boxe, NULL);
+    boxDestroy(&boxt);
+    boxDestroy(&boxe);
+    boxaDestroy(&boxa);
+    boxaDestroy(&boxat);
+
+    if (pangle) *pangle = angle;
+    if (pconf) *pconf = conf;
+
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt5);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                        Process to get line widths                      *
+ *------------------------------------------------------------------------*/
+/*!
+ *  pixExtractBarcodeWidths1()
+ *
+ *      Input:  pixs (input image; 8 bpp)
+ *              thresh (estimated pixel threshold for crossing
+ *                      white <--> black; typ. ~120)
+ *              binfract (histo binsize as a fraction of minsize; e.g., 0.25)
+ *              &naehist (<optional return> histogram of black widths; NULL ok)
+ *              &naohist (<optional return> histogram of white widths; NULL ok)
+ *              debugflag (use 1 to generate debug output)
+ *      Return: nad (numa of barcode widths in encoded integer units),
+ *                  or null on error
+ *
+ *  Note:
+ *     (1) The widths are alternating black/white, starting with black
+ *         and ending with black.
+ *     (2) This method uses the widths of the bars directly, in terms
+ *         of the (float) number of pixels between transitions.
+ *         The histograms of these widths for black and white bars is
+ *         generated and interpreted.
+ */
+NUMA *
+pixExtractBarcodeWidths1(PIX      *pixs,
+                        l_float32  thresh,
+                        l_float32  binfract,
+                        NUMA     **pnaehist,
+                        NUMA     **pnaohist,
+                        l_int32    debugflag)
+{
+NUMA  *nac, *nad;
+
+    PROCNAME("pixExtractBarcodeWidths1");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+
+        /* Get the best estimate of the crossings, in pixel units */
+    nac = pixExtractBarcodeCrossings(pixs, thresh, debugflag);
+
+        /* Get the array of bar widths, starting with a black bar  */
+    nad = numaQuantizeCrossingsByWidth(nac, binfract, pnaehist,
+                                       pnaohist, debugflag);
+
+    numaDestroy(&nac);
+    return nad;
+}
+
+
+/*!
+ *  pixExtractBarcodeWidths2()
+ *
+ *      Input:  pixs (input image; 8 bpp)
+ *              thresh (estimated pixel threshold for crossing
+ *                      white <--> black; typ. ~120)
+ *              &width (<optional return> best decoding window width, in pixels)
+ *              &nac (<optional return> number of transitions in each window)
+ *              debugflag (use 1 to generate debug output)
+ *      Return: nad (numa of barcode widths in encoded integer units),
+ *                  or null on error
+ *
+ *  Notes:
+ *      (1) The widths are alternating black/white, starting with black
+ *          and ending with black.
+ *      (2) The optional best decoding window width is the width of the window
+ *          that is used to make a decision about whether a transition occurs.
+ *          It is approximately the average width in pixels of the narrowest
+ *          white and black bars (i.e., those corresponding to unit width).
+ *      (3) The optional return signal @nac is a sequence of 0s, 1s,
+ *          and perhaps a few 2s, giving the number of crossings in each window.
+ *          On the occasion where there is a '2', it is interpreted as
+ *          as ending two runs: the previous one and another one that has length 1.
+ */
+NUMA *
+pixExtractBarcodeWidths2(PIX        *pixs,
+                         l_float32   thresh,
+                         l_float32  *pwidth,
+                         NUMA      **pnac,
+                         l_int32     debugflag)
+{
+NUMA  *nacp, *nad;
+
+    PROCNAME("pixExtractBarcodeWidths2");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+
+        /* Get the best estimate of the crossings, in pixel units */
+    nacp = pixExtractBarcodeCrossings(pixs, thresh, debugflag);
+
+        /* Quantize the crossings to get actual windowed data */
+    nad = numaQuantizeCrossingsByWindow(nacp, 2.0, pwidth, NULL, pnac, debugflag);
+
+    numaDestroy(&nacp);
+    return nad;
+}
+
+
+/*!
+ *  pixExtractBarcodeCrossings()
+ *
+ *      Input:  pixs (input image; 8 bpp)
+ *              thresh (estimated pixel threshold for crossing
+ *                      white <--> black; typ. ~120)
+ *              debugflag (use 1 to generate debug output)
+ *      Return: numa (of crossings, in pixel units), or null on error
+ */
+NUMA *
+pixExtractBarcodeCrossings(PIX       *pixs,
+                           l_float32  thresh,
+                           l_int32    debugflag)
+{
+l_int32    w;
+l_float32  bestthresh;
+NUMA      *nas, *nax, *nay, *nad;
+
+    PROCNAME("pixExtractBarcodeCrossings");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+
+        /* Scan pixels horizontally and average results */
+    nas = pixAverageRasterScans(pixs, 51);
+
+        /* Interpolate to get 4x the number of values */
+    w = pixGetWidth(pixs);
+    numaInterpolateEqxInterval(0.0, 1.0, nas, L_QUADRATIC_INTERP, 0.0,
+                               (l_float32)(w - 1), 4 * w + 1, &nax, &nay);
+
+    if (debugflag) {
+        GPLOT *gplot = gplotCreate("junksignal", GPLOT_X11, "Pixel values",
+                                   "dist in pixels", "value");
+        gplotAddPlot(gplot, nax, nay, GPLOT_LINES, "plot 1");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+    }
+
+        /* Locate the crossings.  Run multiple times with different
+         * thresholds, and choose a threshold in the center of the
+         * run of thresholds that all give the maximum number of crossings. */
+    numaSelectCrossingThreshold(nax, nay, thresh, &bestthresh);
+
+        /* Get the crossings with the best threshold. */
+    nad = numaCrossingsByThreshold(nax, nay, bestthresh);
+
+    numaDestroy(&nas);
+    numaDestroy(&nax);
+    numaDestroy(&nay);
+    return nad;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                         Average adjacent rasters                       *
+ *------------------------------------------------------------------------*/
+/*!
+ *  pixAverageRasterScans()
+ *
+ *      Input:  pixs (input image; 8 bpp)
+ *              nscans (number of adjacent scans, about the center vertically)
+ *      Return: numa (of average pixel values across image), or null on error
+ */
+static NUMA *
+pixAverageRasterScans(PIX     *pixs,
+                      l_int32  nscans)
+{
+l_int32     w, h, first, last, i, j, wpl, val;
+l_uint32   *line, *data;
+l_float32  *array;
+NUMA       *nad;
+
+    PROCNAME("pixAverageRasterScans");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (NUMA *)ERROR_PTR("pixs undefined or not 8 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (nscans <= h) {
+        first = 0;
+        last = h - 1;
+        nscans = h;
+    } else {
+        first = (h - nscans) / 2;
+        last = first + nscans - 1;
+    }
+
+    nad = numaCreate(w);
+    numaSetCount(nad, w);
+    array = numaGetFArray(nad, L_NOCOPY);
+    wpl = pixGetWpl(pixs);
+    data = pixGetData(pixs);
+    for (j = 0; j < w; j++) {
+        for (i = first; i <= last; i++) {
+            line = data + i * wpl;
+            val = GET_DATA_BYTE(line, j);
+            array[j] += val;
+        }
+        array[j] = array[j] / (l_float32)nscans;
+    }
+
+    return nad;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                   Signal processing for barcode widths                 *
+ *------------------------------------------------------------------------*/
+/*!
+ *  numaQuantizeCrossingsByWidth()
+ *
+ *      Input:  nas (numa of crossing locations, in pixel units)
+ *              binfract (histo binsize as a fraction of minsize; e.g., 0.25)
+ *              &naehist (<optional return> histo of even (black) bar widths)
+ *              &naohist (<optional return> histo of odd (white) bar widths)
+ *              debugflag (1 to generate plots of histograms of bar widths)
+ *      Return: nad (sequence of widths, in unit sizes), or null on error
+ *
+ *  Notes:
+ *      (1) This first computes the histogram of black and white bar widths,
+ *          binned in appropriate units.  There should be well-defined
+ *          peaks, each corresponding to a specific width.  The sequence
+ *          of barcode widths (namely, the integers from the set {1,2,3,4})
+ *          is returned.
+ *      (2) The optional returned histograms are binned in width units
+ *          that are inversely proportional to @binfract.  For example,
+ *          if @binfract = 0.25, there are 4.0 bins in the distance of
+ *          the width of the narrowest bar.
+ */
+NUMA *
+numaQuantizeCrossingsByWidth(NUMA       *nas,
+                             l_float32   binfract,
+                             NUMA      **pnaehist,
+                             NUMA      **pnaohist,
+                             l_int32     debugflag)
+{
+l_int32    i, n, ned, nod, iw, width;
+l_float32  val, minsize, maxsize, factor;
+GPLOT     *gplot;
+NUMA      *naedist, *naodist, *naehist, *naohist, *naecent, *naocent;
+NUMA      *naerange, *naorange, *naelut, *naolut, *nad;
+
+    PROCNAME("numaQuantizeCrossingsByWidth");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    n = numaGetCount(nas);
+    if (n < 2)
+        return (NUMA *)ERROR_PTR("n < 2", procName, NULL);
+    if (binfract <= 0.0)
+        return (NUMA *)ERROR_PTR("binfract <= 0.0", procName, NULL);
+
+        /* Get even and odd crossing distances */
+    numaGetCrossingDistances(nas, &naedist, &naodist, &minsize, &maxsize);
+
+        /* Bin the spans in units of binfract * minsize.  These
+         * units are convenient because they scale to make at least
+         * 1/binfract bins in the smallest span (width).  We want this
+         * number to be large enough to clearly separate the
+         * widths, but small enough so that the histogram peaks
+         * have very few if any holes (zeroes) within them.  */
+    naehist = numaMakeHistogramClipped(naedist, binfract * minsize,
+                                       (1.25 / binfract) * maxsize);
+    naohist = numaMakeHistogramClipped(naodist, binfract * minsize,
+                                       (1.25 / binfract) * maxsize);
+
+    if (debugflag) {
+        gplot = gplotCreate("junkhistw", GPLOT_X11,
+                                   "Raw width histogram", "Width", "Number");
+        gplotAddPlot(gplot, NULL, naehist, GPLOT_LINES, "plot black");
+        gplotAddPlot(gplot, NULL, naohist, GPLOT_LINES, "plot white");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+    }
+
+        /* Compute the peak ranges, still in units of binfract * minsize.  */
+    naerange = numaLocatePeakRanges(naehist, 1.0 / binfract,
+                                    1.0 / binfract, 0.0);
+    naorange = numaLocatePeakRanges(naohist, 1.0 / binfract,
+                                    1.0 / binfract, 0.0);
+
+        /* Find the centroid values of each peak */
+    naecent = numaGetPeakCentroids(naehist, naerange);
+    naocent = numaGetPeakCentroids(naohist, naorange);
+
+        /* Generate the lookup tables that map from the bar width, in
+         * units of (binfract * minsize), to the integerized barcode
+         * units (1, 2, 3, 4), which are the output integer widths
+         * between transitions. */
+    naelut = numaGetPeakWidthLUT(naerange, naecent);
+    naolut = numaGetPeakWidthLUT(naorange, naocent);
+
+        /* Get the widths.  Because the LUT accepts our funny units,
+         * we first must convert the pixel widths to these units,
+         * which is what 'factor' does. */
+    nad = numaCreate(0);
+    ned = numaGetCount(naedist);
+    nod = numaGetCount(naodist);
+    if (nod != ned - 1)
+        L_WARNING("ned != nod + 1\n", procName);
+    factor = 1.0 / (binfract * minsize);  /* for converting units */
+    for (i = 0; i < ned - 1; i++) {
+        numaGetFValue(naedist, i, &val);
+        width = (l_int32)(factor * val);
+        numaGetIValue(naelut, width, &iw);
+        numaAddNumber(nad, iw);
+/*        fprintf(stderr, "even: val = %7.3f, width = %d, iw = %d\n",
+                val, width, iw); */
+        numaGetFValue(naodist, i, &val);
+        width = (l_int32)(factor * val);
+        numaGetIValue(naolut, width, &iw);
+        numaAddNumber(nad, iw);
+/*        fprintf(stderr, "odd: val = %7.3f, width = %d, iw = %d\n",
+                val, width, iw); */
+    }
+    numaGetFValue(naedist, ned - 1, &val);
+    width = (l_int32)(factor * val);
+    numaGetIValue(naelut, width, &iw);
+    numaAddNumber(nad, iw);
+
+    if (debugflag) {
+        fprintf(stderr, " ---- Black bar widths (pixels) ------ \n");
+        numaWriteStream(stderr, naedist);
+    }
+    if (debugflag) {
+        fprintf(stderr, " ---- Histogram of black bar widths ------ \n");
+        numaWriteStream(stderr, naehist);
+    }
+    if (debugflag) {
+        fprintf(stderr, " ---- Peak ranges in black bar histogram bins ------ \n");
+        numaWriteStream(stderr, naerange);
+    }
+    if (debugflag) {
+        fprintf(stderr, " ---- Peak black bar centroid width values ------ \n");
+        numaWriteStream(stderr, naecent);
+    }
+    if (debugflag) {
+        fprintf(stderr, " ---- Black bar lookup table ------ \n");
+        numaWriteStream(stderr, naelut);
+    }
+    if (debugflag) {
+        fprintf(stderr, " ---- White bar widths (pixels) ------ \n");
+        numaWriteStream(stderr, naodist);
+    }
+    if (debugflag) {
+        fprintf(stderr, " ---- Histogram of white bar widths ------ \n");
+        numaWriteStream(stderr, naohist);
+    }
+    if (debugflag) {
+        fprintf(stderr, " ---- Peak ranges in white bar histogram bins ------ \n");
+        numaWriteStream(stderr, naorange);
+    }
+    if (debugflag) {
+        fprintf(stderr, " ---- Peak white bar centroid width values ------ \n");
+        numaWriteStream(stderr, naocent);
+    }
+    if (debugflag) {
+        fprintf(stderr, " ---- White bar lookup table ------ \n");
+        numaWriteStream(stderr, naolut);
+    }
+
+    numaDestroy(&naedist);
+    numaDestroy(&naodist);
+    numaDestroy(&naerange);
+    numaDestroy(&naorange);
+    numaDestroy(&naecent);
+    numaDestroy(&naocent);
+    numaDestroy(&naelut);
+    numaDestroy(&naolut);
+    if (pnaehist)
+        *pnaehist = naehist;
+    else
+        numaDestroy(&naehist);
+    if (pnaohist)
+        *pnaohist = naohist;
+    else
+        numaDestroy(&naohist);
+    return nad;
+}
+
+
+/*!
+ *  numaGetCrossingDistances()
+ *
+ *      Input:  nas (numa of crossing locations)
+ *              &naedist (<optional return> even distances between crossings)
+ *              &naodist (<optional return> odd distances between crossings)
+ *              &mindist (<optional return> min distance between crossings)
+ *              &maxdist (<optional return> max distance between crossings)
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+numaGetCrossingDistances(NUMA       *nas,
+                         NUMA      **pnaedist,
+                         NUMA      **pnaodist,
+                         l_float32  *pmindist,
+                         l_float32  *pmaxdist)
+{
+l_int32    i, n;
+l_float32  val, newval, mindist, maxdist, dist;
+NUMA      *naedist, *naodist;
+
+    PROCNAME("numaGetCrossingDistances");
+
+    if (pnaedist) *pnaedist = NULL;
+    if (pnaodist) *pnaodist = NULL;
+    if (pmindist) *pmindist = 0.0;
+    if (pmaxdist) *pmaxdist = 0.0;
+    if (!nas)
+        return ERROR_INT("nas not defined", procName, 1);
+    if ((n = numaGetCount(nas)) < 2)
+        return ERROR_INT("n < 2", procName, 1);
+
+        /* Get numas of distances between crossings.  Separate these
+         * into even (e.g., black) and odd (e.g., white) spans.
+         * For barcodes, the black spans are 0, 2, etc.  These
+         * distances are in pixel units.  */
+    naedist = numaCreate(n / 2 + 1);
+    naodist = numaCreate(n / 2);
+    numaGetFValue(nas, 0, &val);
+    for (i = 1; i < n; i++) {
+        numaGetFValue(nas, i, &newval);
+        if (i % 2)
+            numaAddNumber(naedist, newval - val);
+        else
+            numaAddNumber(naodist, newval - val);
+        val = newval;
+    }
+
+        /* The mindist and maxdist of the spans are in pixel units. */
+    numaGetMin(naedist, &mindist, NULL);
+    numaGetMin(naodist, &dist, NULL);
+    mindist = L_MIN(dist, mindist);
+    numaGetMax(naedist, &maxdist, NULL);
+    numaGetMax(naodist, &dist, NULL);
+    maxdist = L_MAX(dist, maxdist);
+    L_INFO("mindist = %7.3f, maxdist = %7.3f\n", procName, mindist, maxdist);
+
+    if (pnaedist)
+        *pnaedist = naedist;
+    else
+        numaDestroy(&naedist);
+    if (pnaodist)
+        *pnaodist = naodist;
+    else
+        numaDestroy(&naodist);
+    if (pmindist) *pmindist = mindist;
+    if (pmaxdist) *pmaxdist = maxdist;
+    return 0;
+}
+
+
+/*!
+ *  numaLocatePeakRanges()
+ *
+ *      Input:  nas (numa of histogram of crossing widths)
+ *              minfirst (min location of center of first peak)
+ *              minsep (min separation between peak range centers)
+ *              maxmin (max allowed value for min histo value between peaks)
+ *      Return: nad (ranges for each peak found, in pairs), or null on error
+ *
+ *  Notes:
+ *      (1) Units of @minsep are the index into nas.
+ *          This puts useful constraints on peak-finding.
+ *      (2) If maxmin == 0.0, the value of nas[i] must go to 0.0 (or less)
+ *          between peaks.
+ *      (3) All calculations are done in units of the index into nas.
+ *          The resulting ranges are therefore integers.
+ *      (4) The output nad gives pairs of range values for successive peaks.
+ *          Any location [i] for which maxmin = nas[i] = 0.0 will NOT be
+ *          included in a peak range.  This works fine for histograms where
+ *          if nas[i] == 0.0, it means that there are no samples at [i].
+ *      (5) For barcodes, when this is used on a histogram of barcode
+ *          widths, use maxmin = 0.0.  This requires that there is at
+ *          least one histogram bin corresponding to a width value between
+ *          adjacent peak ranges that is unpopulated, making the separation
+ *          of the histogram peaks unambiguous.
+ */
+static NUMA *
+numaLocatePeakRanges(NUMA      *nas,
+                     l_float32  minfirst,
+                     l_float32  minsep,
+                     l_float32  maxmin)
+{
+l_int32    i, n, inpeak, left;
+l_float32  center, prevcenter, val;
+NUMA      *nad;
+
+    PROCNAME("numaLocatePeakRanges");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    n = numaGetCount(nas);
+    nad = numaCreate(0);
+
+    inpeak = FALSE;
+    prevcenter = minfirst - minsep - 1.0;
+    for (i = 0; i < n; i++) {
+        numaGetFValue(nas, i, &val);
+        if (inpeak == FALSE && val > maxmin) {
+            inpeak = TRUE;
+            left = i;
+        } else if (inpeak == TRUE && val <= maxmin) {  /* end peak */
+            center = (left + i - 1.0) / 2.0;
+            if (center - prevcenter >= minsep) {  /* save new peak */
+                inpeak = FALSE;
+                numaAddNumber(nad, left);
+                numaAddNumber(nad, i - 1);
+                prevcenter = center;
+            } else {  /* attach to previous peak; revise the right edge */
+                numaSetValue(nad, numaGetCount(nad) - 1, i - 1);
+            }
+        }
+    }
+    if (inpeak == TRUE) {  /* save the last peak */
+        numaAddNumber(nad, left);
+        numaAddNumber(nad, n - 1);
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaGetPeakCentroids()
+ *
+ *      Input:  nahist (numa of histogram of crossing widths)
+ *              narange (numa of ranges of x-values for the peaks in @nahist)
+ *      Return: nad (centroids for each peak found; max of 4, corresponding
+ *                   to 4 different barcode line widths), or null on error
+ */
+static NUMA *
+numaGetPeakCentroids(NUMA  *nahist,
+                     NUMA  *narange)
+{
+l_int32    i, j, nr, low, high;
+l_float32  cent, sum, val;
+NUMA      *nad;
+
+    PROCNAME("numaGetPeakCentroids");
+
+    if (!nahist)
+        return (NUMA *)ERROR_PTR("nahist not defined", procName, NULL);
+    if (!narange)
+        return (NUMA *)ERROR_PTR("narange not defined", procName, NULL);
+    nr = numaGetCount(narange) / 2;
+
+    nad = numaCreate(4);
+    for (i = 0; i < nr; i++) {
+        numaGetIValue(narange, 2 * i, &low);
+        numaGetIValue(narange, 2 * i + 1, &high);
+        cent = 0.0;
+        sum = 0.0;
+        for (j = low; j <= high; j++) {
+            numaGetFValue(nahist, j, &val);
+            cent += j * val;
+            sum += val;
+        }
+        numaAddNumber(nad, cent / sum);
+    }
+
+    return nad;
+}
+
+
+/*!
+ *  numaGetPeakWidthLUT()
+ *
+ *      Input:  narange (numa of x-val ranges for the histogram width peaks)
+ *              nacent (numa of centroids of each peak -- up to 4)
+ *      Return: nalut (lookup table from the width of a bar to one of the four
+ *                     integerized barcode units), or null on error
+ *
+ *  Notes:
+ *      (1) This generates the lookup table that maps from a sequence of widths
+ *          (in some units) to the integerized barcode units (1, 2, 3, 4),
+ *          which are the output integer widths between transitions.
+ *      (2) The smallest width can be lost in float roundoff.  To avoid
+ *          losing it, we expand the peak range of the smallest width.
+ */
+static NUMA *
+numaGetPeakWidthLUT(NUMA  *narange,
+                    NUMA  *nacent)
+{
+l_int32     i, j, nc, low, high, imax;
+l_int32     assign[4];
+l_float32  *warray;
+l_float32   max, rat21, rat32, rat42;
+NUMA       *nalut;
+
+    PROCNAME("numaGetPeakWidthLUT");
+
+    if (!narange)
+        return (NUMA *)ERROR_PTR("narange not defined", procName, NULL);
+    if (!nacent)
+        return (NUMA *)ERROR_PTR("nacent not defined", procName, NULL);
+    nc = numaGetCount(nacent);  /* half the size of narange */
+    if (nc < 1 || nc > 4)
+        return (NUMA *)ERROR_PTR("nc must be 1, 2, 3, or 4", procName, NULL);
+
+        /* Check the peak centroids for consistency with bar widths.
+         * The third peak can correspond to a width of either 3 or 4.
+         * Use ratios 3/2 and 4/2 instead of 3/1 and 4/1 because the
+         * former are more stable and closer to the expected ratio.  */
+    if (nc > 1) {
+        warray = numaGetFArray(nacent, L_NOCOPY);
+        if (warray[0] == 0)
+            return (NUMA *)ERROR_PTR("first peak has width 0.0",
+                                     procName, NULL);
+        rat21 = warray[1] / warray[0];
+        if (rat21 < 1.5 || rat21 > 2.6)
+            L_WARNING("width ratio 2/1 = %f\n", procName, rat21);
+        if (nc > 2) {
+            rat32 = warray[2] / warray[1];
+            if (rat32 < 1.3 || rat32 > 2.25)
+                L_WARNING("width ratio 3/2 = %f\n", procName, rat32);
+        }
+        if (nc == 4) {
+            rat42 = warray[3] / warray[1];
+            if (rat42 < 1.7 || rat42 > 2.3)
+                L_WARNING("width ratio 4/2 = %f\n", procName, rat42);
+        }
+    }
+
+        /* Set width assignments.
+         * The only possible ambiguity is with nc = 3 */
+    for (i = 0; i < 4; i++)
+        assign[i] = i + 1;
+    if (nc == 3) {
+        if (rat32 > 1.75)
+            assign[2] = 4;
+    }
+
+        /* Put widths into the LUT */
+    numaGetMax(narange, &max, NULL);
+    imax = (l_int32)max;
+    nalut = numaCreate(imax + 1);
+    numaSetCount(nalut, imax + 1);  /* fill the array with zeroes */
+    for (i = 0; i < nc; i++) {
+        numaGetIValue(narange, 2 * i, &low);
+        if (i == 0) low--;  /* catch smallest width */
+        numaGetIValue(narange, 2 * i + 1, &high);
+        for (j = low; j <= high; j++)
+            numaSetValue(nalut, j, assign[i]);
+    }
+
+    return nalut;
+}
+
+
+/*!
+ *  numaQuantizeCrossingsByWindow()
+ *
+ *      Input:  nas (numa of crossing locations)
+ *              ratio (of max window size over min window size in search;
+ *                     typ. 2.0)
+ *              &width (<optional return> best window width)
+ *              &firstloc (<optional return> center of window for first xing)
+ *              &nac (<optional return> array of window crossings (0, 1, 2))
+ *              debugflag (1 to generate various plots of intermediate results)
+ *      Return: nad (sequence of widths, in unit sizes), or null on error
+ *
+ *  Notes:
+ *      (1) The minimum size of the window is set by the minimum
+ *          distance between zero crossings.
+ *      (2) The optional return signal @nac is a sequence of 0s, 1s,
+ *          and perhaps a few 2s, giving the number of crossings in each window.
+ *          On the occasion where there is a '2', it is interpreted as
+ *          ending two runs: the previous one and another one that has length 1.
+ */
+NUMA *
+numaQuantizeCrossingsByWindow(NUMA       *nas,
+                              l_float32   ratio,
+                              l_float32  *pwidth,
+                              l_float32  *pfirstloc,
+                              NUMA      **pnac,
+                              l_int32     debugflag)
+{
+l_int32    i, nw, started, count, trans;
+l_float32  minsize, minwidth, minshift, xfirst;
+NUMA      *nac, *nad;
+
+    PROCNAME("numaQuantizeCrossingsByWindow");
+
+    if (!nas)
+        return (NUMA *)ERROR_PTR("nas not defined", procName, NULL);
+    if (numaGetCount(nas) < 2)
+        return (NUMA *)ERROR_PTR("nas size < 2", procName, NULL);
+
+        /* Get the minsize, which is needed for the search for
+         * the window width (ultimately found as 'minwidth') */
+    numaGetCrossingDistances(nas, NULL, NULL, &minsize, NULL);
+
+        /* Compute the width and shift increments; start at minsize
+         * and go up to ratio * minsize  */
+    numaEvalBestWidthAndShift(nas, 100, 10, minsize, ratio * minsize,
+                              &minwidth, &minshift, NULL);
+
+        /* Refine width and shift calculation */
+    numaEvalBestWidthAndShift(nas, 100, 10, 0.98 * minwidth, 1.02 * minwidth,
+                              &minwidth, &minshift, NULL);
+
+    L_INFO("best width = %7.3f, best shift = %7.3f\n",
+           procName, minwidth, minshift);
+
+        /* Get the crossing array (0,1,2) for the best window width and shift */
+    numaEvalSyncError(nas, 0, 0, minwidth, minshift, NULL, &nac);
+    if (pwidth) *pwidth = minwidth;
+    if (pfirstloc) {
+        numaGetFValue(nas, 0, &xfirst);
+        *pfirstloc = xfirst + minshift;
+    }
+
+        /* Get the array of bar widths, starting with a black bar  */
+    nad = numaCreate(0);
+    nw = numaGetCount(nac);  /* number of window measurements */
+    started = FALSE;
+    count = 0;  /* unnecessary init */
+    for (i = 0; i < nw; i++) {
+        numaGetIValue(nac, i, &trans);
+        if (trans > 2)
+            L_WARNING("trans = %d > 2 !!!\n", procName, trans);
+        if (started) {
+            if (trans > 1) {  /* i.e., when trans == 2 */
+                numaAddNumber(nad, count);
+                trans--;
+                count = 1;
+            }
+            if (trans == 1) {
+                numaAddNumber(nad, count);
+                count = 1;
+            } else {
+                count++;
+            }
+        }
+        if (!started && trans) {
+            started = TRUE;
+            if (trans == 2)  /* a whole bar in this window */
+                numaAddNumber(nad, 1);
+            count = 1;
+        }
+    }
+
+    if (pnac)
+        *pnac = nac;
+    else
+        numaDestroy(&nac);
+    return nad;
+}
+
+
+/*!
+ *  numaEvalBestWidthAndShift()
+ *
+ *      Input:  nas (numa of crossing locations)
+ *              nwidth (number of widths to consider)
+ *              nshift (number of shifts to consider for each width)
+ *              minwidth (smallest width to consider)
+ *              maxwidth (largest width to consider)
+ *              &bestwidth (<return> best size of window)
+ *              &bestshift (<return> best shift for the window)
+ *              &bestscore (<optional return> average squared error of dist
+ *                          of crossing signal from the center of the window)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does a linear sweep of widths, evaluating at @nshift
+ *          shifts for each width, finding the (width, shift) pair that
+ *          gives the minimum score.
+ */
+static l_int32
+numaEvalBestWidthAndShift(NUMA       *nas,
+                          l_int32     nwidth,
+                          l_int32     nshift,
+                          l_float32   minwidth,
+                          l_float32   maxwidth,
+                          l_float32  *pbestwidth,
+                          l_float32  *pbestshift,
+                          l_float32  *pbestscore)
+{
+l_int32    i, j;
+l_float32  delwidth, delshift, width, shift, score;
+l_float32  bestwidth, bestshift, bestscore;
+
+    PROCNAME("numaEvalBestWidthAndShift");
+
+    if (!nas)
+        return ERROR_INT("nas not defined", procName, 1);
+    if (!pbestwidth || !pbestshift)
+        return ERROR_INT("&bestwidth and &bestshift not defined", procName, 1);
+
+    bestscore = 1.0;
+    delwidth = (maxwidth - minwidth) / (nwidth - 1.0);
+    for (i = 0; i < nwidth; i++) {
+        width = minwidth + delwidth * i;
+        delshift = width / (l_float32)(nshift);
+        for (j = 0; j < nshift; j++) {
+            shift = -0.5 * (width - delshift) + j * delshift;
+            numaEvalSyncError(nas, 0, 0, width, shift, &score, NULL);
+            if (score < bestscore) {
+                bestscore = score;
+                bestwidth = width;
+                bestshift = shift;
+#if  DEBUG_FREQUENCY
+                fprintf(stderr, "width = %7.3f, shift = %7.3f, score = %7.3f\n",
+                        width, shift, score);
+#endif  /* DEBUG_FREQUENCY */
+            }
+        }
+    }
+
+    *pbestwidth = bestwidth;
+    *pbestshift = bestshift;
+    if (pbestscore)
+        *pbestscore = bestscore;
+    return 0;
+}
+
+
+/*!
+ *  numaEvalSyncError()
+ *
+ *      Input:  nas (numa of crossing locations)
+ *              ifirst (first crossing to use)
+ *              ilast (last crossing to use; use 0 for all crossings)
+ *              width (size of window)
+ *              shift (of center of window w/rt first crossing)
+ *              &score (<optional return> average squared error of dist
+ *                      of crossing signal from the center of the window)
+ *              &nad (<optional return> numa of 1s and 0s for crossings)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The score is computed only on the part of the signal from the
+ *          @ifirst to @ilast crossings.  Use 0 for both of these to
+ *          use all the crossings.  The score is normalized for
+ *          the number of crossings and with half-width of the window.
+ *      (2) The optional return @nad is a sequence of 0s and 1s, where a '1'
+ *          indicates a crossing in the window.
+ */
+static l_int32
+numaEvalSyncError(NUMA       *nas,
+                  l_int32     ifirst,
+                  l_int32     ilast,
+                  l_float32   width,
+                  l_float32   shift,
+                  l_float32  *pscore,
+                  NUMA      **pnad)
+{
+l_int32    i, n, nc, nw, ival;
+l_int32    iw;  /* cell in which transition occurs */
+l_float32  score, xfirst, xlast, xleft, xc, xwc;
+NUMA      *nad;
+
+    PROCNAME("numaEvalSyncError");
+
+    if (!nas)
+        return ERROR_INT("nas not defined", procName, 1);
+    if ((n = numaGetCount(nas)) < 2)
+        return ERROR_INT("nas size < 2", procName, 1);
+    if (ifirst < 0) ifirst = 0;
+    if (ilast <= 0) ilast = n - 1;
+    if (ifirst >= ilast)
+        return ERROR_INT("ifirst not < ilast", procName, 1);
+    nc = ilast - ifirst + 1;
+
+        /* Set up an array corresponding to the (shifted) windows,
+         * and fill in the crossings. */
+    score = 0.0;
+    numaGetFValue(nas, ifirst, &xfirst);
+    numaGetFValue(nas, ilast, &xlast);
+    nw = (l_int32) ((xlast - xfirst + 2.0 * width) / width);
+    nad = numaCreate(nw);
+    numaSetCount(nad, nw);  /* init to all 0.0 */
+    xleft = xfirst - width / 2.0 + shift;  /* left edge of first window */
+    for (i = ifirst; i <= ilast; i++) {
+        numaGetFValue(nas, i, &xc);
+        iw = (l_int32)((xc - xleft) / width);
+        xwc = xleft + (iw + 0.5) * width;  /* center of cell iw */
+        score += (xwc - xc) * (xwc - xc);
+        numaGetIValue(nad, iw, &ival);
+        numaSetValue(nad, iw, ival + 1);
+    }
+
+    if (pscore)
+        *pscore = 4.0 * score / (width * width * (l_float32)nc);
+    if (pnad)
+        *pnad = nad;
+    else
+        numaDestroy(&nad);
+
+    return 0;
+}
diff --git a/src/readbarcode.h b/src/readbarcode.h
new file mode 100644 (file)
index 0000000..4cfb9d5
--- /dev/null
@@ -0,0 +1,231 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_READBARCODE_H
+#define  LEPTONICA_READBARCODE_H
+
+    /* ----------------------------------------------------------------- *
+     *            Flags for method of extracting barcode widths          *
+     * ----------------------------------------------------------------- */
+enum {
+    L_USE_WIDTHS = 1,           /* use histogram of barcode widths           */
+    L_USE_WINDOWS = 2           /* find best window for decoding transitions */
+};
+
+    /* ----------------------------------------------------------------- *
+     *                     Flags for barcode formats                     *
+     * These are used both to identify a barcode format and to identify  *
+     * the decoding method to use on a barcode.                          *
+     * ----------------------------------------------------------------- */
+enum {
+    L_BF_UNKNOWN = 0,           /* unknown format                            */
+    L_BF_ANY = 1,               /* try decoding with all known formats       */
+    L_BF_CODE128 = 2,           /* decode with Code128 format                */
+    L_BF_EAN8 = 3,              /* decode with EAN8 format                   */
+    L_BF_EAN13 = 4,             /* decode with EAN13 format                  */
+    L_BF_CODE2OF5 = 5,          /* decode with Code 2 of 5 format            */
+    L_BF_CODEI2OF5 = 6,         /* decode with Interleaved 2 of 5 format     */
+    L_BF_CODE39 = 7,            /* decode with Code39 format                 */
+    L_BF_CODE93 = 8,            /* decode with Code93 format                 */
+    L_BF_CODABAR = 9,           /* decode with Code93 format                 */
+    L_BF_UPCA = 10              /* decode with UPC A format                  */
+};
+
+    /* ----------------------------------------------------------------- *
+     *                  Currently supported formats                      *
+     *            Update these arrays as new formats are added.          *
+     * ----------------------------------------------------------------- */
+static const l_int32  SupportedBarcodeFormat[] = {
+    L_BF_CODE2OF5,
+    L_BF_CODEI2OF5,
+    L_BF_CODE93,
+    L_BF_CODE39,
+    L_BF_CODABAR,
+    L_BF_UPCA,
+    L_BF_EAN13
+};
+static const char  *SupportedBarcodeFormatName[] = {
+    "Code2of5",
+    "CodeI2of5",
+    "Code93",
+    "Code39",
+    "Codabar",
+    "Upca",
+    "Ean13"
+};
+static const l_int32  NumSupportedBarcodeFormats = 7;
+
+
+    /* ----------------------------------------------------------------- *
+     *                       Code 2 of 5 symbology                       *
+     * ----------------------------------------------------------------- */
+static const char *Code2of5[] = {
+    "111121211", "211111112", "112111112", "212111111",   /* 0 - 3 */
+    "111121112", "211121111", "112121111", "111111212",   /* 4 - 7 */
+    "211111211", "112111211",                             /* 8 - 9 */
+    "21211", "21112"                                      /* Start, Stop */
+};
+
+static const l_int32  C25_START = 10;
+static const l_int32  C25_STOP =  11;
+
+
+    /* ----------------------------------------------------------------- *
+     *                Code Interleaved 2 of 5 symbology                  *
+     * ----------------------------------------------------------------- */
+static const char *CodeI2of5[] = {
+    "11221", "21112", "12112", "22111", "11212",    /*  0 - 4 */
+    "21211", "12211", "11122", "21121", "12121",    /*  5 - 9 */
+    "1111", "211"                                   /*  start, stop */
+};
+
+static const l_int32  CI25_START = 10;
+static const l_int32  CI25_STOP =  11;
+
+
+    /* ----------------------------------------------------------------- *
+     *                         Code 93 symbology                         *
+     * ----------------------------------------------------------------- */
+static const char *Code93[] = {
+    "131112", "111213", "111312", "111411", "121113", /* 0: 0 - 4 */
+    "121212", "121311", "111114", "131211", "141111", /* 5: 5 - 9 */
+    "211113", "211212", "211311", "221112", "221211", /* 10: A - E */
+    "231111", "112113", "112212", "112311", "122112", /* 15: F - J */
+    "132111", "111123", "111222", "111321", "121122", /* 20: K - O */
+    "131121", "212112", "212211", "211122", "211221", /* 25: P - T */
+    "221121", "222111", "112122", "112221", "122121", /* 30: U - Y */
+    "123111", "121131", "311112", "311211", "321111", /* 35: Z,-,.,SP,$ */
+    "112131", "113121", "211131", "131221", "312111", /* 40: /,+,%,($),(%) */
+    "311121", "122211", "111141"                      /* 45: (/),(+), Start */
+};
+
+    /* Use "[]{}#" to represent special codes 43-47 */
+static const char Code93Val[] =
+    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%[]{}#";
+
+static const l_int32  C93_START = 47;
+static const l_int32  C93_STOP =  47;
+
+
+    /* ----------------------------------------------------------------- *
+     *                         Code 39 symbology                         *
+     * ----------------------------------------------------------------- */
+static const char *Code39[] = {
+    "111221211", "211211112", "112211112", "212211111",  /* 0: 0 - 3      */
+    "111221112", "211221111", "112221111", "111211212",  /* 4: 4 - 7      */
+    "211211211", "112211211", "211112112", "112112112",  /* 8: 8 - B      */
+    "212112111", "111122112", "211122111", "112122111",  /* 12: C - F     */
+    "111112212", "211112211", "112112211", "111122211",  /* 16: G - J     */
+    "211111122", "112111122", "212111121", "111121122",  /* 20: K - N     */
+    "211121121", "112121121", "111111222", "211111221",  /* 24: O - R     */
+    "112111221", "111121221", "221111112", "122111112",  /* 28: S - V     */
+    "222111111", "121121112", "221121111", "122121111",  /* 32: W - Z     */
+    "121111212", "221111211", "122111211", "121212111",  /* 36: -,.,SP,$  */
+    "121211121", "121112121", "111212121", "121121211"   /* 40: /,+,%,*   */
+};
+
+    /* Use "*" to represent the Start and Stop codes (43) */
+static const char Code39Val[] =
+    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%*";
+
+static const l_int32  C39_START = 43;
+static const l_int32  C39_STOP =  43;
+
+
+    /* ----------------------------------------------------------------- *
+     *                         Codabar symbology                         *
+     * ----------------------------------------------------------------- */
+static const char *Codabar[] = {
+    "1111122", "1111221", "1112112", "2211111", "1121121", /* 0: 0 - 4      */
+    "2111121", "1211112", "1211211", "1221111", "2112111", /* 5: 5 - 9      */
+    "1112211", "1122111", "2111212", "2121112", "2121211", /* 10: -,$,:,/,. */
+    "1121212", "1122121", "1212112", "1112122", "1112221"  /* 15: +,A,B,C,D */
+};
+
+    /* Ascii representations for codes 16-19: (A or T), (B or N), (C or *),
+     * (D or E).  These are used in pairs for the Start and Stop codes. */
+static const char CodabarVal[] = "0123456789-$:/.+ABCD";
+
+
+    /* ----------------------------------------------------------------- *
+     *                          UPC-A symbology                          *
+     * ----------------------------------------------------------------- */
+static const char *Upca[] = {
+    "3211", "2221", "2122", "1411", "1132",  /* 0: 0 - 4              */
+    "1231", "1114", "1312", "1213", "3112",  /* 5: 5 - 9              */
+    "111", "111", "11111"                    /* 10: Start, Stop, Mid  */
+};
+
+static const l_int32  UPCA_START = 10;
+static const l_int32  UPCA_STOP =  11;
+static const l_int32  UPCA_MID =   12;
+
+
+    /* ----------------------------------------------------------------- *
+     *                         Code128 symbology                         *
+     * ----------------------------------------------------------------- */
+static const char *Code128[] = {
+    "212222", "222122", "222221", "121223", "121322",    /*  0 - 4 */
+    "131222", "122213", "122312", "132212", "221213",    /*  5 - 9 */
+    "221312", "231212", "112232", "122132", "122231",    /* 10 - 14 */
+    "113222", "123122", "123221", "223211", "221132",    /* 15 - 19 */
+    "221231", "213212", "223112", "312131", "311222",    /* 20 - 24 */
+    "321122", "321221", "312212", "322112", "322211",    /* 25 - 29 */
+    "212123", "212321", "232121", "111323", "131123",    /* 30 - 34 */
+    "131321", "112313", "132113", "132311", "211313",    /* 35 - 39 */
+    "231113", "231311", "112133", "112331", "132131",    /* 40 - 44 */
+    "113123", "113321", "133121", "313121", "211331",    /* 45 - 49 */
+    "231131", "213113", "213311", "213131", "311123",    /* 50 - 54 */
+    "311321", "331121", "312113", "312311", "332111",    /* 55 - 59 */
+    "314111", "221411", "431111", "111224", "111422",    /* 60 - 64 */
+    "121124", "121421", "141122", "141221", "112214",    /* 65 - 69 */
+    "112412", "122114", "122411", "142112", "142211",    /* 70 - 74 */
+    "241211", "221114", "413111", "241112", "134111",    /* 75 - 79 */
+    "111242", "121142", "121241", "114212", "124112",    /* 80 - 84 */
+    "124211", "411212", "421112", "421211", "212141",    /* 85 - 89 */
+    "214121", "412121", "111143", "111341", "131141",    /* 90 - 94 */
+    "114113", "114311", "411113", "411311", "113141",    /* 95 - 99 */
+    "114131", "311141", "411131", "211412", "211214",    /* 100 - 104 */
+    "211232", "2331112"                                  /* 105 - 106 */
+};
+
+static const l_int32  C128_FUN_3 =    96;   /* in A or B only; in C it is 96 */
+static const l_int32  C128_FUNC_2 =   97;   /* in A or B only; in C it is 97 */
+static const l_int32  C128_SHIFT =    98;   /* in A or B only; in C it is 98 */
+static const l_int32  C128_GOTO_C =   99;   /* in A or B only; in C it is 99 */
+static const l_int32  C128_GOTO_B =  100;
+static const l_int32  C128_GOTO_A =  101;
+static const l_int32  C128_FUNC_1 =  102;
+static const l_int32  C128_START_A = 103;
+static const l_int32  C128_START_B = 104;
+static const l_int32  C128_START_C = 105;
+static const l_int32  C128_STOP =    106;
+    /* code 128 symbols are 11 units */
+static const l_int32  C128_SYMBOL_WIDTH = 11;
+
+
+
+#endif  /* LEPTONICA_READBARCODE_H */
diff --git a/src/readfile.c b/src/readfile.c
new file mode 100644 (file)
index 0000000..0bd2733
--- /dev/null
@@ -0,0 +1,1331 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  readfile.c:  reads image on file into memory
+ *
+ *      Top-level functions for reading images from file
+ *           PIXA      *pixaReadFiles()
+ *           PIXA      *pixaReadFilesSA()
+ *           PIX       *pixRead()
+ *           PIX       *pixReadWithHint()
+ *           PIX       *pixReadIndexed()
+ *           PIX       *pixReadStream()
+ *
+ *      Read header information from file
+ *           l_int32    pixReadHeader()
+ *
+ *      Format finders
+ *           l_int32    findFileFormat()
+ *           l_int32    findFileFormatStream()
+ *           l_int32    findFileFormatBuffer()
+ *           l_int32    fileFormatIsTiff()
+ *
+ *      Read from memory
+ *           PIX       *pixReadMem()
+ *           l_int32    pixReadHeaderMem()
+ *
+ *      Test function for I/O with different formats
+ *           l_int32    ioFormatTest()
+ *
+ *  Supported file formats:
+ *  (1) Reading is supported without any external libraries:
+ *          bmp
+ *          pnm   (including pbm, pgm, etc)
+ *          spix  (raw serialized)
+ *  (2) Reading is supported with installation of external libraries:
+ *          png
+ *          jpg   (standard jfif version)
+ *          tiff  (including most varieties of compression)
+ *          gif
+ *          webp
+ *          jp2 (jpeg 2000)
+ *  (3) Other file types will get an "unknown format" error.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+
+    /* Output files for ioFormatTest(). */
+static const char *FILE_BMP  =  "/tmp/lept/format/file.bmp";
+static const char *FILE_PNG  =  "/tmp/lept/format/file.png";
+static const char *FILE_PNM  =  "/tmp/lept/format/file.pnm";
+static const char *FILE_G3   =  "/tmp/lept/format/file_g3.tif";
+static const char *FILE_G4   =  "/tmp/lept/format/file_g4.tif";
+static const char *FILE_RLE  =  "/tmp/lept/format/file_rle.tif";
+static const char *FILE_PB   =  "/tmp/lept/format/file_packbits.tif";
+static const char *FILE_LZW  =  "/tmp/lept/format/file_lzw.tif";
+static const char *FILE_ZIP  =  "/tmp/lept/format/file_zip.tif";
+static const char *FILE_TIFF =  "/tmp/lept/format/file.tif";
+static const char *FILE_JPG  =  "/tmp/lept/format/file.jpg";
+static const char *FILE_GIF  =  "/tmp/lept/format/file.gif";
+static const char *FILE_WEBP =  "/tmp/lept/format/file.webp";
+static const char *FILE_JP2K =  "/tmp/lept/format/file.jp2";
+
+static const unsigned char JP2K_CODESTREAM[4] = { 0xff, 0x4f, 0xff, 0x51 };
+static const unsigned char JP2K_IMAGE_DATA[12] = { 0x00, 0x00, 0x00, 0x0C,
+                                                   0x6A, 0x50, 0x20, 0x20,
+                                                   0x0D, 0x0A, 0x87, 0x0A };
+
+
+/*---------------------------------------------------------------------*
+ *          Top-level functions for reading images from file           *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaReadFiles()
+ *
+ *      Input:  dirname
+ *              substr (<optional> substring filter on filenames; can be null)
+ *      Return: pixa, or null on error
+ *
+ *  Notes:
+ *      (1) @dirname is the full path for the directory.
+ *      (2) @substr is the part of the file name (excluding
+ *          the directory) that is to be matched.  All matching
+ *          filenames are read into the Pixa.  If substr is NULL,
+ *          all filenames are read into the Pixa.
+ */
+PIXA *
+pixaReadFiles(const char  *dirname,
+              const char  *substr)
+{
+PIXA    *pixa;
+SARRAY  *sa;
+
+    PROCNAME("pixaReadFiles");
+
+    if (!dirname)
+        return (PIXA *)ERROR_PTR("dirname not defined", procName, NULL);
+
+    if ((sa = getSortedPathnamesInDirectory(dirname, substr, 0, 0)) == NULL)
+        return (PIXA *)ERROR_PTR("sa not made", procName, NULL);
+
+    pixa = pixaReadFilesSA(sa);
+    sarrayDestroy(&sa);
+    return pixa;
+}
+
+
+/*!
+ *  pixaReadFilesSA()
+ *
+ *      Input:  sarray (full pathnames for all files)
+ *      Return: pixa, or null on error
+ */
+PIXA *
+pixaReadFilesSA(SARRAY  *sa)
+{
+char    *str;
+l_int32  i, n;
+PIX     *pix;
+PIXA    *pixa;
+
+    PROCNAME("pixaReadFilesSA");
+
+    if (!sa)
+        return (PIXA *)ERROR_PTR("sa not defined", procName, NULL);
+
+    n = sarrayGetCount(sa);
+    pixa = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        if ((pix = pixRead(str)) == NULL) {
+            L_WARNING("pix not read from file %s\n", procName, str);
+            continue;
+        }
+        pixaAddPix(pixa, pix, L_INSERT);
+    }
+
+    return pixa;
+}
+
+
+/*!
+ *  pixRead()
+ *
+ *      Input:  filename (with full pathname or in local directory)
+ *      Return: pix if OK; null on error
+ *
+ *  Notes:
+ *      (1) See at top of file for supported formats.
+ */
+PIX *
+pixRead(const char  *filename)
+{
+FILE  *fp;
+PIX   *pix;
+
+    PROCNAME("pixRead");
+
+    if (!filename)
+        return (PIX *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL) {
+        L_ERROR("image file not found: %s\n", procName, filename);
+        return NULL;
+    }
+    if ((pix = pixReadStream(fp, 0)) == NULL) {
+        fclose(fp);
+        return (PIX *)ERROR_PTR("pix not read", procName, NULL);
+    }
+    fclose(fp);
+
+    return pix;
+}
+
+
+/*!
+ *  pixReadWithHint()
+ *
+ *      Input:  filename (with full pathname or in local directory)
+ *              hint (bitwise OR of L_HINT_* values for jpeg; use 0 for no hint)
+ *      Return: pix if OK; null on error
+ *
+ *  Notes:
+ *      (1) The hint is not binding, but may be used to optimize jpeg decoding.
+ *          Use 0 for no hinting.
+ */
+PIX *
+pixReadWithHint(const char  *filename,
+                l_int32      hint)
+{
+FILE  *fp;
+PIX   *pix;
+
+    PROCNAME("pixReadWithHint");
+
+    if (!filename)
+        return (PIX *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PIX *)ERROR_PTR("image file not found", procName, NULL);
+    pix = pixReadStream(fp, hint);
+    fclose(fp);
+
+    if (!pix)
+        return (PIX *)ERROR_PTR("image not returned", procName, NULL);
+    return pix;
+}
+
+
+/*!
+ *  pixReadIndexed()
+ *
+ *      Input:  sarray (of full pathnames)
+ *              index (into pathname array)
+ *      Return: pix if OK; null if not found
+ *
+ *  Notes:
+ *      (1) This function is useful for selecting image files from a
+ *          directory, where the integer @index is embedded into
+ *          the file name.
+ *      (2) This is typically done by generating the sarray using
+ *          getNumberedPathnamesInDirectory(), so that the @index
+ *          pathname would have the number @index in it.  The size
+ *          of the sarray should be the largest number (plus 1) appearing
+ *          in the file names, respecting the constraints in the
+ *          call to getNumberedPathnamesInDirectory().
+ *      (3) Consequently, for some indices into the sarray, there may
+ *          be no pathnames in the directory containing that number.
+ *          By convention, we place empty C strings ("") in those
+ *          locations in the sarray, and it is not an error if such
+ *          a string is encountered and no pix is returned.
+ *          Therefore, the caller must verify that a pix is returned.
+ *      (4) See convertSegmentedPagesToPS() in src/psio1.c for an
+ *          example of usage.
+ */
+PIX *
+pixReadIndexed(SARRAY  *sa,
+               l_int32  index)
+{
+char    *fname;
+l_int32  n;
+PIX     *pix;
+
+    PROCNAME("pixReadIndexed");
+
+    if (!sa)
+        return (PIX *)ERROR_PTR("sa not defined", procName, NULL);
+    n = sarrayGetCount(sa);
+    if (index < 0 || index >= n)
+        return (PIX *)ERROR_PTR("index out of bounds", procName, NULL);
+
+    fname = sarrayGetString(sa, index, L_NOCOPY);
+    if (fname[0] == '\0')
+        return NULL;
+
+    if ((pix = pixRead(fname)) == NULL) {
+        L_ERROR("pix not read from file %s\n", procName, fname);
+        return NULL;
+    }
+
+    return pix;
+}
+
+
+/*!
+ *  pixReadStream()
+ *
+ *      Input:  fp (file stream)
+ *              hint (bitwise OR of L_HINT_* values for jpeg; use 0 for no hint)
+ *      Return: pix if OK; null on error
+ *
+ *  Notes:
+ *      (1) The hint only applies to jpeg.
+ */
+PIX *
+pixReadStream(FILE    *fp,
+              l_int32  hint)
+{
+l_int32   format, ret;
+l_uint8  *comment;
+PIX      *pix;
+
+    PROCNAME("pixReadStream");
+
+    if (!fp)
+        return (PIX *)ERROR_PTR("stream not defined", procName, NULL);
+    pix = NULL;
+
+    findFileFormatStream(fp, &format);
+    switch (format)
+    {
+    case IFF_BMP:
+        if ((pix = pixReadStreamBmp(fp)) == NULL )
+            return (PIX *)ERROR_PTR( "bmp: no pix returned", procName, NULL);
+        break;
+
+    case IFF_JFIF_JPEG:
+        if ((pix = pixReadStreamJpeg(fp, 0, 1, NULL, hint)) == NULL)
+            return (PIX *)ERROR_PTR( "jpeg: no pix returned", procName, NULL);
+        ret = fgetJpegComment(fp, &comment);
+        if (!ret && comment)
+            pixSetText(pix, (char *)comment);
+        LEPT_FREE(comment);
+        break;
+
+    case IFF_PNG:
+        if ((pix = pixReadStreamPng(fp)) == NULL)
+            return (PIX *)ERROR_PTR("png: no pix returned", procName, NULL);
+        break;
+
+    case IFF_TIFF:
+    case IFF_TIFF_PACKBITS:
+    case IFF_TIFF_RLE:
+    case IFF_TIFF_G3:
+    case IFF_TIFF_G4:
+    case IFF_TIFF_LZW:
+    case IFF_TIFF_ZIP:
+        if ((pix = pixReadStreamTiff(fp, 0)) == NULL)  /* page 0 by default */
+            return (PIX *)ERROR_PTR("tiff: no pix returned", procName, NULL);
+        break;
+
+    case IFF_PNM:
+        if ((pix = pixReadStreamPnm(fp)) == NULL)
+            return (PIX *)ERROR_PTR("pnm: no pix returned", procName, NULL);
+        break;
+
+    case IFF_GIF:
+        if ((pix = pixReadStreamGif(fp)) == NULL)
+            return (PIX *)ERROR_PTR("gif: no pix returned", procName, NULL);
+        break;
+
+    case IFF_JP2:
+        if ((pix = pixReadStreamJp2k(fp, 1, NULL, 0, 0)) == NULL)
+            return (PIX *)ERROR_PTR("jp2: no pix returned", procName, NULL);
+        break;
+
+    case IFF_WEBP:
+        if ((pix = pixReadStreamWebP(fp)) == NULL)
+            return (PIX *)ERROR_PTR("webp: no pix returned", procName, NULL);
+        break;
+
+    case IFF_SPIX:
+        if ((pix = pixReadStreamSpix(fp)) == NULL)
+            return (PIX *)ERROR_PTR("spix: no pix returned", procName, NULL);
+        break;
+
+    case IFF_UNKNOWN:
+        return (PIX *)ERROR_PTR( "Unknown format: no pix returned",
+                procName, NULL);
+        break;
+    }
+
+    if (pix)
+        pixSetInputFormat(pix, format);
+    return pix;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                     Read header information from file               *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixReadHeader()
+ *
+ *      Input:  filename (with full pathname or in local directory)
+ *              &format (<optional return> file format)
+ *              &w, &h (<optional returns> width and height)
+ *              &bps <optional return> bits/sample
+ *              &spp <optional return> samples/pixel (1, 3 or 4)
+ *              &iscmap (<optional return> 1 if cmap exists; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This reads the actual headers for jpeg, png, tiff and pnm.
+ *          For bmp and gif, we cheat and read the entire file into a pix,
+ *          from which we extract the "header" information.
+ */
+l_int32
+pixReadHeader(const char  *filename,
+              l_int32     *pformat,
+              l_int32     *pw,
+              l_int32     *ph,
+              l_int32     *pbps,
+              l_int32     *pspp,
+              l_int32     *piscmap)
+{
+l_int32  format, ret, w, h, d, bps, spp, iscmap;
+l_int32  type;  /* ignored */
+FILE    *fp;
+PIX     *pix;
+
+    PROCNAME("pixReadHeader");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pbps) *pbps = 0;
+    if (pspp) *pspp = 0;
+    if (piscmap) *piscmap = 0;
+    if (pformat) *pformat = 0;
+    iscmap = 0;  /* init to false */
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT("image file not found", procName, 1);
+    findFileFormatStream(fp, &format);
+    fclose(fp);
+
+    switch (format)
+    {
+    case IFF_BMP:  /* cheating: reading the entire file */
+        if ((pix = pixRead(filename)) == NULL)
+            return ERROR_INT( "bmp: pix not read", procName, 1);
+        pixGetDimensions(pix, &w, &h, &d);
+        if (pixGetColormap(pix))
+            iscmap = 1;
+        pixDestroy(&pix);
+        bps = (d == 32) ? 8 : d;
+        spp = (d == 32) ? 3 : 1;
+        break;
+
+    case IFF_JFIF_JPEG:
+        ret = readHeaderJpeg(filename, &w, &h, &spp, NULL, NULL);
+        bps = 8;
+        if (ret)
+            return ERROR_INT( "jpeg: no header info returned", procName, 1);
+        break;
+
+    case IFF_PNG:
+        ret = readHeaderPng(filename, &w, &h, &bps, &spp, &iscmap);
+        if (ret)
+            return ERROR_INT( "png: no header info returned", procName, 1);
+        break;
+
+    case IFF_TIFF:
+    case IFF_TIFF_PACKBITS:
+    case IFF_TIFF_RLE:
+    case IFF_TIFF_G3:
+    case IFF_TIFF_G4:
+    case IFF_TIFF_LZW:
+    case IFF_TIFF_ZIP:
+            /* Reading page 0 by default; possibly redefine format */
+        ret = readHeaderTiff(filename, 0, &w, &h, &bps, &spp, NULL, &iscmap,
+                             &format);
+        if (ret)
+            return ERROR_INT( "tiff: no header info returned", procName, 1);
+        break;
+
+    case IFF_PNM:
+        ret = readHeaderPnm(filename, &w, &h, &d, &type, &bps, &spp);
+        if (ret)
+            return ERROR_INT( "pnm: no header info returned", procName, 1);
+        break;
+
+    case IFF_GIF:  /* cheating: reading the entire file */
+        if ((pix = pixRead(filename)) == NULL)
+            return ERROR_INT( "gif: pix not read", procName, 1);
+        pixGetDimensions(pix, &w, &h, &d);
+        pixDestroy(&pix);
+        iscmap = 1;  /* always colormapped; max 256 colors */
+        spp = 1;
+        bps = d;
+        break;
+
+    case IFF_JP2:
+        ret = readHeaderJp2k(filename, &w, &h, &bps, &spp);
+        break;
+
+    case IFF_WEBP:
+        if (readHeaderWebP(filename, &w, &h, &spp))
+            return ERROR_INT( "webp: no header info returned", procName, 1);
+        bps = 8;
+        break;
+
+    case IFF_SPIX:
+        ret = readHeaderSpix(filename, &w, &h, &bps, &spp, &iscmap);
+        if (ret)
+            return ERROR_INT( "spix: no header info returned", procName, 1);
+        break;
+
+    case IFF_UNKNOWN:
+        L_ERROR("unknown format in file %s\n", procName, filename);
+        return 1;
+        break;
+    }
+
+    if (pw) *pw = w;
+    if (ph) *ph = h;
+    if (pbps) *pbps = bps;
+    if (pspp) *pspp = spp;
+    if (piscmap) *piscmap = iscmap;
+    if (pformat) *pformat = format;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                            Format finders                           *
+ *---------------------------------------------------------------------*/
+/*!
+ *  findFileFormat()
+ *
+ *      Input:  filename
+ *              &format (<return>)
+ *      Return: 0 if OK, 1 on error or if format is not recognized
+ */
+l_int32
+findFileFormat(const char  *filename,
+               l_int32     *pformat)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("findFileFormat");
+
+    if (!pformat)
+        return ERROR_INT("&format not defined", procName, 1);
+    *pformat = IFF_UNKNOWN;
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT("image file not found", procName, 1);
+    ret = findFileFormatStream(fp, pformat);
+    fclose(fp);
+    return ret;
+}
+
+
+/*!
+ *  findFileFormatStream()
+ *
+ *      Input:  fp (file stream)
+ *              &format (<return>)
+ *      Return: 0 if OK, 1 on error or if format is not recognized
+ *
+ *  Notes:
+ *      (1) Important: Side effect -- this resets fp to BOF.
+ */
+l_int32
+findFileFormatStream(FILE     *fp,
+                     l_int32  *pformat)
+{
+l_uint8  firstbytes[12];
+l_int32  format;
+
+    PROCNAME("findFileFormatStream");
+
+    if (!pformat)
+        return ERROR_INT("&format not defined", procName, 1);
+    *pformat = IFF_UNKNOWN;
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+
+    rewind(fp);
+    if (fnbytesInFile(fp) < 12)
+        return ERROR_INT("truncated file", procName, 1);
+
+    if (fread((char *)&firstbytes, 1, 12, fp) != 12)
+        return ERROR_INT("failed to read first 12 bytes of file", procName, 1);
+    rewind(fp);
+
+    findFileFormatBuffer(firstbytes, &format);
+    if (format == IFF_TIFF) {
+        findTiffCompression(fp, &format);
+        rewind(fp);
+    }
+    *pformat = format;
+    if (format == IFF_UNKNOWN)
+        return 1;
+    else
+        return 0;
+}
+
+
+/*!
+ *  findFileFormatBuffer()
+ *
+ *      Input:  byte buffer (at least 12 bytes in size; we can't check)
+ *              &format (<return>)
+ *      Return: 0 if OK, 1 on error or if format is not recognized
+ *
+ *  Notes:
+ *      (1) This determines the file format from the first 12 bytes in
+ *          the compressed data stream, which are stored in memory.
+ *      (2) For tiff files, this returns IFF_TIFF.  The specific tiff
+ *          compression is then determined using findTiffCompression().
+ */
+l_int32
+findFileFormatBuffer(const l_uint8  *buf,
+                     l_int32        *pformat)
+{
+l_uint16  twobytepw;
+
+    PROCNAME("findFileFormatBuffer");
+
+    if (!pformat)
+        return ERROR_INT("&format not defined", procName, 1);
+    *pformat = IFF_UNKNOWN;
+    if (!buf)
+        return ERROR_INT("byte buffer not defined", procName, 0);
+
+        /* Check the bmp and tiff 2-byte header ids */
+    ((char *)(&twobytepw))[0] = buf[0];
+    ((char *)(&twobytepw))[1] = buf[1];
+
+    if (convertOnBigEnd16(twobytepw) == BMP_ID) {
+        *pformat = IFF_BMP;
+        return 0;
+    }
+
+    if (twobytepw == TIFF_BIGEND_ID || twobytepw == TIFF_LITTLEEND_ID) {
+        *pformat = IFF_TIFF;
+        return 0;
+    }
+
+        /* Check for the p*m 2-byte header ids */
+    if ((buf[0] == 'P' && buf[1] == '4') || /* newer packed */
+        (buf[0] == 'P' && buf[1] == '1')) {  /* old format */
+        *pformat = IFF_PNM;
+        return 0;
+    }
+
+    if ((buf[0] == 'P' && buf[1] == '5') || /* newer */
+        (buf[0] == 'P' && buf[1] == '2')) {  /* old */
+        *pformat = IFF_PNM;
+        return 0;
+    }
+
+    if ((buf[0] == 'P' && buf[1] == '6') || /* newer */
+        (buf[0] == 'P' && buf[1] == '3')) {  /* old */
+        *pformat = IFF_PNM;
+        return 0;
+    }
+
+        /*  Consider the first 11 bytes of the standard JFIF JPEG header:
+         *    - The first two bytes are the most important:  0xffd8.
+         *    - The next two bytes are the jfif marker: 0xffe0.
+         *      Not all jpeg files have this marker.
+         *    - The next two bytes are the header length.
+         *    - The next 5 bytes are a null-terminated string.
+         *      For JFIF, the string is "JFIF", naturally.  For others it
+         *      can be "Exif" or just about anything else.
+         *    - Because of all this variability, we only check the first
+         *      two byte marker.  All jpeg files are identified as
+         *      IFF_JFIF_JPEG.  */
+    if (buf[0] == 0xff && buf[1] == 0xd8) {
+        *pformat = IFF_JFIF_JPEG;
+        return 0;
+    }
+
+        /* Check for the 8 byte PNG signature (png_signature in png.c):
+         *       {137, 80, 78, 71, 13, 10, 26, 10}      */
+    if (buf[0] == 137 && buf[1] == 80  && buf[2] == 78  && buf[3] == 71  &&
+        buf[4] == 13  && buf[5] == 10  && buf[6] == 26  && buf[7] == 10) {
+        *pformat = IFF_PNG;
+        return 0;
+    }
+
+        /* Look for "GIF87a" or "GIF89a" */
+    if (buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F' && buf[3] == '8' &&
+        (buf[4] == '7' || buf[4] == '9') && buf[5] == 'a') {
+        *pformat = IFF_GIF;
+        return 0;
+    }
+
+        /* Check for both types of jp2k file */
+    if (strncmp((const char *)buf, (char *)JP2K_CODESTREAM, 4) == 0 ||
+        strncmp((const char *)buf, (char *)JP2K_IMAGE_DATA, 12) == 0) {
+        *pformat = IFF_JP2;
+        return 0;
+    }
+
+        /* Check for webp */
+    if (buf[0] == 'R' && buf[1] == 'I' && buf[2] == 'F' && buf[3] == 'F' &&
+        buf[8] == 'W' && buf[9] == 'E' && buf[10] == 'B' && buf[11] == 'P') {
+        *pformat = IFF_WEBP;
+        return 0;
+    }
+
+        /* Check for "spix" serialized pix */
+    if (buf[0] == 's' && buf[1] == 'p' && buf[2] == 'i' && buf[3] == 'x') {
+        *pformat = IFF_SPIX;
+        return 0;
+    }
+
+        /* File format identifier not found; unknown */
+    return 1;
+}
+
+
+/*!
+ *  fileFormatIsTiff()
+ *
+ *      Input:  fp (file stream)
+ *      Return: 1 if file is tiff; 0 otherwise or on error
+ */
+l_int32
+fileFormatIsTiff(FILE  *fp)
+{
+l_int32  format;
+
+    PROCNAME("fileFormatIsTiff");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 0);
+
+    findFileFormatStream(fp, &format);
+    if (format == IFF_TIFF || format == IFF_TIFF_PACKBITS ||
+        format == IFF_TIFF_RLE || format == IFF_TIFF_G3 ||
+        format == IFF_TIFF_G4 || format == IFF_TIFF_LZW ||
+        format == IFF_TIFF_ZIP)
+        return 1;
+    else
+        return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                            Read from memory                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixReadMem()
+ *
+ *      Input:  data (const; encoded)
+ *              datasize (size of data)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) This is a variation of pixReadStream(), where the data is read
+ *          from a memory buffer rather than a file.
+ *      (2) On windows, this only reads tiff formatted files directly from
+ *          memory.  For other formats, it write to a temp file and
+ *          decompress from file.
+ *      (3) findFileFormatBuffer() requires up to 12 bytes to decide on
+ *          the format.  That determines the constraint here.  But in
+ *          fact the data must contain the entire compressed string for
+ *          the image.
+ */
+PIX *
+pixReadMem(const l_uint8  *data,
+           size_t          size)
+{
+l_int32  format;
+PIX     *pix;
+
+    PROCNAME("pixReadMem");
+
+    if (!data)
+        return (PIX *)ERROR_PTR("data not defined", procName, NULL);
+    if (size < 12)
+        return (PIX *)ERROR_PTR("size < 12", procName, NULL);
+    pix = NULL;
+
+    findFileFormatBuffer(data, &format);
+    switch (format)
+    {
+    case IFF_BMP:
+        if ((pix = pixReadMemBmp(data, size)) == NULL )
+            return (PIX *)ERROR_PTR( "bmp: no pix returned", procName, NULL);
+        break;
+
+    case IFF_JFIF_JPEG:
+        if ((pix = pixReadMemJpeg(data, size, 0, 1, NULL, 0)) == NULL)
+            return (PIX *)ERROR_PTR( "jpeg: no pix returned", procName, NULL);
+        break;
+
+    case IFF_PNG:
+        if ((pix = pixReadMemPng(data, size)) == NULL)
+            return (PIX *)ERROR_PTR("png: no pix returned", procName, NULL);
+        break;
+
+    case IFF_TIFF:
+    case IFF_TIFF_PACKBITS:
+    case IFF_TIFF_RLE:
+    case IFF_TIFF_G3:
+    case IFF_TIFF_G4:
+    case IFF_TIFF_LZW:
+    case IFF_TIFF_ZIP:
+            /* Reading page 0 by default */
+        if ((pix = pixReadMemTiff(data, size, 0)) == NULL)
+            return (PIX *)ERROR_PTR("tiff: no pix returned", procName, NULL);
+        break;
+
+    case IFF_PNM:
+        if ((pix = pixReadMemPnm(data, size)) == NULL)
+            return (PIX *)ERROR_PTR("pnm: no pix returned", procName, NULL);
+        break;
+
+    case IFF_GIF:
+        if ((pix = pixReadMemGif(data, size)) == NULL)
+            return (PIX *)ERROR_PTR("gif: no pix returned", procName, NULL);
+        break;
+
+    case IFF_JP2:
+        if ((pix = pixReadMemJp2k(data, size, 1, NULL, 0, 0)) == NULL)
+            return (PIX *)ERROR_PTR("jp2k: no pix returned", procName, NULL);
+        break;
+
+    case IFF_WEBP:
+        if ((pix = pixReadMemWebP(data, size)) == NULL)
+            return (PIX *)ERROR_PTR("webp: no pix returned", procName, NULL);
+        break;
+
+    case IFF_SPIX:
+        if ((pix = pixReadMemSpix(data, size)) == NULL)
+            return (PIX *)ERROR_PTR("spix: no pix returned", procName, NULL);
+        break;
+
+    case IFF_UNKNOWN:
+        return (PIX *)ERROR_PTR("Unknown format: no pix returned",
+                procName, NULL);
+        break;
+    }
+
+        /* Set the input format.  For tiff reading from memory we lose
+         * the actual input format; for 1 bpp, default to G4.  */
+    if (pix) {
+        if (format == IFF_TIFF && pixGetDepth(pix) == 1)
+            format = IFF_TIFF_G4;
+        pixSetInputFormat(pix, format);
+    }
+
+    return pix;
+}
+
+
+/*!
+ *  pixReadHeaderMem()
+ *
+ *      Input:  data (const; encoded)
+ *              datasize (size of data)
+ *              &format (<optional returns> image format)
+ *              &w, &h (<optional returns> width and height)
+ *              &bps <optional return> bits/sample
+ *              &spp <optional return> samples/pixel (1, 3 or 4)
+ *              &iscmap (<optional return> 1 if cmap exists; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This reads the actual headers for jpeg, png, tiff, jp2k and pnm.
+ *          For bmp and gif, we cheat and read all the data into a pix,
+ *          from which we extract the "header" information.
+ *      (2) The amount of data required depends on the format.  For
+ *          png, it requires less than 30 bytes, but for jpeg it can
+ *          require most of the compressed file.  In practice, the data
+ *          is typically the entire compressed file in memory.
+ *      (3) findFileFormatBuffer() requires up to 8 bytes to decide on
+ *          the format, which we require.
+ */
+l_int32
+pixReadHeaderMem(const l_uint8  *data,
+                 size_t          size,
+                 l_int32        *pformat,
+                 l_int32        *pw,
+                 l_int32        *ph,
+                 l_int32        *pbps,
+                 l_int32        *pspp,
+                 l_int32        *piscmap)
+{
+l_int32  format, ret, w, h, d, bps, spp, iscmap;
+l_int32  type;  /* not used */
+PIX     *pix;
+
+    PROCNAME("pixReadHeaderMem");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pbps) *pbps = 0;
+    if (pspp) *pspp = 0;
+    if (piscmap) *piscmap = 0;
+    if (pformat) *pformat = 0;
+    iscmap = 0;  /* init to false */
+    if (!data)
+        return ERROR_INT("data not defined", procName, 1);
+    if (size < 8)
+        return ERROR_INT("size < 8", procName, 1);
+
+    findFileFormatBuffer(data, &format);
+
+    switch (format)
+    {
+    case IFF_BMP:  /* cheating: read the pix */
+        if ((pix = pixReadMemBmp(data, size)) == NULL)
+            return ERROR_INT( "bmp: pix not read", procName, 1);
+        pixGetDimensions(pix, &w, &h, &d);
+        pixDestroy(&pix);
+        bps = (d == 32) ? 8 : d;
+        spp = (d == 32) ? 3 : 1;
+        break;
+
+    case IFF_JFIF_JPEG:
+        ret = readHeaderMemJpeg(data, size, &w, &h, &spp, NULL, NULL);
+        bps = 8;
+        if (ret)
+            return ERROR_INT( "jpeg: no header info returned", procName, 1);
+        break;
+
+    case IFF_PNG:
+        ret = readHeaderMemPng(data, size, &w, &h, &bps, &spp, &iscmap);
+        if (ret)
+            return ERROR_INT( "png: no header info returned", procName, 1);
+        break;
+
+    case IFF_TIFF:
+    case IFF_TIFF_PACKBITS:
+    case IFF_TIFF_RLE:
+    case IFF_TIFF_G3:
+    case IFF_TIFF_G4:
+    case IFF_TIFF_LZW:
+    case IFF_TIFF_ZIP:
+            /* Reading page 0 by default; possibly redefine format */
+        ret = readHeaderMemTiff(data, size, 0, &w, &h, &bps, &spp,
+                                NULL, &iscmap, &format);
+        if (ret)
+            return ERROR_INT( "tiff: no header info returned", procName, 1);
+        break;
+
+    case IFF_PNM:
+        ret = readHeaderMemPnm(data, size, &w, &h, &d, &type, &bps, &spp);
+        if (ret)
+            return ERROR_INT( "pnm: no header info returned", procName, 1);
+        break;
+
+    case IFF_GIF:  /* cheating: read the pix */
+        if ((pix = pixReadMemGif(data, size)) == NULL)
+            return ERROR_INT( "gif: pix not read", procName, 1);
+        pixGetDimensions(pix, &w, &h, &d);
+        pixDestroy(&pix);
+        iscmap = 1;  /* always colormapped; max 256 colors */
+        spp = 1;
+        bps = d;
+        break;
+
+    case IFF_JP2:
+        ret = readHeaderMemJp2k(data, size, &w, &h, &bps, &spp);
+        break;
+
+    case IFF_WEBP:
+        bps = 8;
+        ret = readHeaderMemWebP(data, size, &w, &h, &spp);
+        break;
+
+    case IFF_SPIX:
+        ret = sreadHeaderSpix((l_uint32 *)data, &w, &h, &bps,
+                               &spp, &iscmap);
+        if (ret)
+            return ERROR_INT( "pnm: no header info returned", procName, 1);
+        break;
+
+    case IFF_UNKNOWN:
+        return ERROR_INT("unknown format; no data returned", procName, 1);
+        break;
+    }
+
+    if (pw) *pw = w;
+    if (ph) *ph = h;
+    if (pbps) *pbps = bps;
+    if (pspp) *pspp = spp;
+    if (piscmap) *piscmap = iscmap;
+    if (pformat) *pformat = format;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *             Test function for I/O with different formats            *
+ *---------------------------------------------------------------------*/
+/*!
+ *  ioFormatTest()
+ *
+ *      Input:  filename (input file)
+ *      Return: 0 if OK; 1 on error or if the test fails
+ *
+ *  Notes:
+ *      (1) This writes and reads a set of output files losslessly
+ *          in different formats to /tmp/format/, and tests that the
+ *          result before and after is unchanged.
+ *      (2) This should work properly on input images of any depth,
+ *          with and without colormaps.
+ *      (3) All supported formats are tested for bmp, png, tiff and
+ *          non-ascii pnm.  Ascii pnm also works (but who'd ever want
+ *          to use it?)   We allow 2 bpp bmp, although it's not
+ *          supported elsewhere.  And we don't support reading
+ *          16 bpp png, although this can be turned on in pngio.c.
+ *      (4) This silently skips png or tiff testing if HAVE_LIBPNG
+ *          or HAVE_LIBTIFF are 0, respectively.
+ */
+l_int32
+ioFormatTest(const char  *filename)
+{
+l_int32    w, h, d, depth, equal, problems;
+l_float32  diff;
+BOX       *box;
+PIX       *pixs, *pixc, *pix1, *pix2;
+PIXCMAP   *cmap;
+
+    PROCNAME("ioFormatTest");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+        /* Read the input file and limit the size */
+    if ((pix1 = pixRead(filename)) == NULL)
+        return ERROR_INT("pix1 not made", procName, 1);
+    pixGetDimensions(pix1, &w, &h, NULL);
+    if (w > 250 && h > 250) {  /* take the central 250 x 250 region */
+        box = boxCreate(w / 2 - 125, h / 2 - 125, 250, 250);
+        pixs = pixClipRectangle(pix1, box, NULL);
+        boxDestroy(&box);
+    } else {
+        pixs = pixClone(pix1);
+    }
+    pixDestroy(&pix1);
+
+    lept_mkdir("lept/format");
+
+        /* Note that the reader automatically removes colormaps
+         * from 1 bpp BMP images, but not from 8 bpp BMP images.
+         * Therefore, if our 8 bpp image initially doesn't have a
+         * colormap, we are going to need to remove it from any
+         * pix read from a BMP file. */
+    pixc = pixClone(pixs);  /* laziness */
+
+        /* This does not test the alpha layer pixels, because most
+         * formats don't support it.  Remove any alpha.  */
+    if (pixGetSpp(pixc) == 4)
+        pixSetSpp(pixc, 3);
+    cmap = pixGetColormap(pixc);  /* colormap; can be NULL */
+    d = pixGetDepth(pixc);
+
+    problems = FALSE;
+
+        /* ----------------------- BMP -------------------------- */
+
+        /* BMP works for 1, 2, 4, 8 and 32 bpp images.
+         * It always writes colormaps for 1 and 8 bpp, so we must
+         * remove it after readback if the input image doesn't have
+         * a colormap.  Although we can write/read 2 bpp BMP, nobody
+         * else can read them! */
+    if (d == 1 || d == 8) {
+        L_INFO("write/read bmp\n", procName);
+        pixWrite(FILE_BMP, pixc, IFF_BMP);
+        pix1 = pixRead(FILE_BMP);
+        if (!cmap)
+            pix2 = pixRemoveColormap(pix1, REMOVE_CMAP_BASED_ON_SRC);
+        else
+            pix2 = pixClone(pix1);
+        pixEqual(pixc, pix2, &equal);
+        if (!equal) {
+            L_INFO("   **** bad bmp image: d = %d ****\n", procName, d);
+            problems = TRUE;
+        }
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    if (d == 2 || d == 4 || d == 32) {
+        L_INFO("write/read bmp\n", procName);
+        pixWrite(FILE_BMP, pixc, IFF_BMP);
+        pix1 = pixRead(FILE_BMP);
+        pixEqual(pixc, pix1, &equal);
+        if (!equal) {
+            L_INFO("   **** bad bmp image: d = %d ****\n", procName, d);
+            problems = TRUE;
+        }
+        pixDestroy(&pix1);
+    }
+
+        /* ----------------------- PNG -------------------------- */
+#if HAVE_LIBPNG
+        /* PNG works for all depths, but here, because we strip
+         * 16 --> 8 bpp on reading, we don't test png for 16 bpp. */
+    if (d != 16) {
+        L_INFO("write/read png\n", procName);
+        pixWrite(FILE_PNG, pixc, IFF_PNG);
+        pix1 = pixRead(FILE_PNG);
+        pixEqual(pixc, pix1, &equal);
+        if (!equal) {
+            L_INFO("   **** bad png image: d = %d ****\n", procName, d);
+            problems = TRUE;
+        }
+        pixDestroy(&pix1);
+    }
+#endif  /* HAVE_LIBPNG */
+
+        /* ----------------------- TIFF -------------------------- */
+#if HAVE_LIBTIFF
+        /* TIFF works for 1, 2, 4, 8, 16 and 32 bpp images.
+         * Because 8 bpp tiff always writes 256 entry colormaps, the
+         * colormap sizes may be different for 8 bpp images with
+         * colormap; we are testing if the image content is the same.
+         * Likewise, the 2 and 4 bpp tiff images with colormaps
+         * have colormap sizes 4 and 16, rsp.  This test should
+         * work properly on the content, regardless of the number
+         * of color entries in pixc. */
+
+        /* tiff uncompressed works for all pixel depths */
+    L_INFO("write/read uncompressed tiff\n", procName);
+    pixWrite(FILE_TIFF, pixc, IFF_TIFF);
+    pix1 = pixRead(FILE_TIFF);
+    pixEqual(pixc, pix1, &equal);
+    if (!equal) {
+        L_INFO("   **** bad tiff uncompressed image: d = %d ****\n",
+               procName, d);
+        problems = TRUE;
+    }
+    pixDestroy(&pix1);
+
+        /* tiff lzw works for all pixel depths */
+    L_INFO("write/read lzw compressed tiff\n", procName);
+    pixWrite(FILE_LZW, pixc, IFF_TIFF_LZW);
+    pix1 = pixRead(FILE_LZW);
+    pixEqual(pixc, pix1, &equal);
+    if (!equal) {
+        L_INFO("   **** bad tiff lzw compressed image: d = %d ****\n",
+               procName, d);
+        problems = TRUE;
+    }
+    pixDestroy(&pix1);
+
+        /* tiff adobe deflate (zip) works for all pixel depths */
+    L_INFO("write/read zip compressed tiff\n", procName);
+    pixWrite(FILE_ZIP, pixc, IFF_TIFF_ZIP);
+    pix1 = pixRead(FILE_ZIP);
+    pixEqual(pixc, pix1, &equal);
+    if (!equal) {
+        L_INFO("   **** bad tiff zip compressed image: d = %d ****\n",
+               procName, d);
+        problems = TRUE;
+    }
+    pixDestroy(&pix1);
+
+        /* tiff g4, g3, rle and packbits work for 1 bpp */
+    if (d == 1) {
+        L_INFO("write/read g4 compressed tiff\n", procName);
+        pixWrite(FILE_G4, pixc, IFF_TIFF_G4);
+        pix1 = pixRead(FILE_G4);
+        pixEqual(pixc, pix1, &equal);
+        if (!equal) {
+            L_INFO("   **** bad tiff g4 image ****\n", procName);
+            problems = TRUE;
+        }
+        pixDestroy(&pix1);
+
+        L_INFO("write/read g3 compressed tiff\n", procName);
+        pixWrite(FILE_G3, pixc, IFF_TIFF_G3);
+        pix1 = pixRead(FILE_G3);
+        pixEqual(pixc, pix1, &equal);
+        if (!equal) {
+            L_INFO("   **** bad tiff g3 image ****\n", procName);
+            problems = TRUE;
+        }
+        pixDestroy(&pix1);
+
+        L_INFO("write/read rle compressed tiff\n", procName);
+        pixWrite(FILE_RLE, pixc, IFF_TIFF_RLE);
+        pix1 = pixRead(FILE_RLE);
+        pixEqual(pixc, pix1, &equal);
+        if (!equal) {
+            L_INFO("   **** bad tiff rle image: d = %d ****\n", procName, d);
+            problems = TRUE;
+        }
+        pixDestroy(&pix1);
+
+        L_INFO("write/read packbits compressed tiff\n", procName);
+        pixWrite(FILE_PB, pixc, IFF_TIFF_PACKBITS);
+        pix1 = pixRead(FILE_PB);
+        pixEqual(pixc, pix1, &equal);
+        if (!equal) {
+            L_INFO("   **** bad tiff packbits image: d = %d ****\n",
+                   procName, d);
+            problems = TRUE;
+        }
+        pixDestroy(&pix1);
+    }
+#endif  /* HAVE_LIBTIFF */
+
+        /* ----------------------- PNM -------------------------- */
+
+        /* pnm works for 1, 2, 4, 8, 16 and 32 bpp.
+         * pnm doesn't have colormaps, so when we write colormapped
+         * pix out as pnm, the colormap is removed.  Thus for the test,
+         * we must remove the colormap from pixc before testing.  */
+    L_INFO("write/read pnm\n", procName);
+    pixWrite(FILE_PNM, pixc, IFF_PNM);
+    pix1 = pixRead(FILE_PNM);
+    if (cmap)
+        pix2 = pixRemoveColormap(pixc, REMOVE_CMAP_BASED_ON_SRC);
+    else
+        pix2 = pixClone(pixc);
+    pixEqual(pix1, pix2, &equal);
+    if (!equal) {
+        L_INFO("   **** bad pnm image: d = %d ****\n", procName, d);
+        problems = TRUE;
+    }
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+
+        /* ----------------------- GIF -------------------------- */
+#if HAVE_LIBGIF
+        /* GIF works for only 1 and 8 bpp, colormapped */
+    if (d != 8 || !cmap)
+        pix1 = pixConvertTo8(pixc, 1);
+    else
+        pix1 = pixClone(pixc);
+    L_INFO("write/read gif\n", procName);
+    pixWrite(FILE_GIF, pix1, IFF_GIF);
+    pix2 = pixRead(FILE_GIF);
+    pixEqual(pix1, pix2, &equal);
+    if (!equal) {
+        L_INFO("   **** bad gif image: d = %d ****\n", procName, d);
+        problems = TRUE;
+    }
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+#endif  /* HAVE_LIBGIF */
+
+        /* ----------------------- JPEG ------------------------- */
+#if HAVE_LIBJPEG
+        /* JPEG works for only 8 bpp gray and rgb */
+    if (cmap || d > 8)
+        pix1 = pixConvertTo32(pixc);
+    else
+        pix1 = pixConvertTo8(pixc, 0);
+    depth = pixGetDepth(pix1);
+    L_INFO("write/read jpeg\n", procName);
+    pixWrite(FILE_JPG, pix1, IFF_JFIF_JPEG);
+    pix2 = pixRead(FILE_JPG);
+    if (depth == 8) {
+        pixCompareGray(pix1, pix2, L_COMPARE_ABS_DIFF, 0, NULL, &diff,
+                       NULL, NULL);
+    } else {
+        pixCompareRGB(pix1, pix2, L_COMPARE_ABS_DIFF, 0, NULL, &diff,
+                      NULL, NULL);
+    }
+    if (diff > 8.0) {
+        L_INFO("   **** bad jpeg image: d = %d, diff = %5.2f ****\n",
+               procName, depth, diff);
+        problems = TRUE;
+    }
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+#endif  /* HAVE_LIBJPEG */
+
+        /* ----------------------- WEBP ------------------------- */
+#if HAVE_LIBWEBP
+        /* WEBP works for rgb and rgba */
+    if (cmap || d <= 16)
+        pix1 = pixConvertTo32(pixc);
+    else
+        pix1 = pixClone(pixc);
+    depth = pixGetDepth(pix1);
+    L_INFO("write/read webp\n", procName);
+    pixWrite(FILE_WEBP, pix1, IFF_WEBP);
+    pix2 = pixRead(FILE_WEBP);
+    pixCompareRGB(pix1, pix2, L_COMPARE_ABS_DIFF, 0, NULL, &diff, NULL, NULL);
+    if (diff > 5.0) {
+        L_INFO("   **** bad webp image: d = %d, diff = %5.2f ****\n",
+               procName, depth, diff);
+        problems = TRUE;
+    }
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+#endif  /* HAVE_LIBWEBP */
+
+        /* ----------------------- JP2K ------------------------- */
+#if HAVE_LIBJP2K
+        /* JP2K works for only 8 bpp gray, rgb and rgba */
+    if (cmap || d > 8)
+        pix1 = pixConvertTo32(pixc);
+    else
+        pix1 = pixConvertTo8(pixc, 0);
+    depth = pixGetDepth(pix1);
+    L_INFO("write/read jp2k\n", procName);
+    pixWrite(FILE_JP2K, pix1, IFF_JP2);
+    pix2 = pixRead(FILE_JP2K);
+    if (depth == 8) {
+        pixCompareGray(pix1, pix2, L_COMPARE_ABS_DIFF, 0, NULL, &diff,
+                       NULL, NULL);
+    } else {
+        pixCompareRGB(pix1, pix2, L_COMPARE_ABS_DIFF, 0, NULL, &diff,
+                      NULL, NULL);
+    }
+    fprintf(stderr, "diff = %7.3f\n", diff);
+    if (diff > 7.0) {
+        L_INFO("   **** bad jp2k image: d = %d, diff = %5.2f ****\n",
+               procName, depth, diff);
+        problems = TRUE;
+    }
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+#endif  /* HAVE_LIBJP2K */
+
+    if (problems == FALSE)
+        L_INFO("All formats read and written OK!\n", procName);
+
+    pixDestroy(&pixc);
+    pixDestroy(&pixs);
+    return problems;
+}
+
diff --git a/src/recog.h b/src/recog.h
new file mode 100644 (file)
index 0000000..9845c9f
--- /dev/null
@@ -0,0 +1,259 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_RECOG_H
+#define  LEPTONICA_RECOG_H
+
+/*
+ *  recog.h
+ *
+ *     A simple utility for training and recognizing individual
+ *     machine-printed text characters.  In an application, one can
+ *     envision using a number of these, one for each trained set.
+ *
+ *     In training mode, a set of labelled bitmaps is presented, either
+ *     one at a time, or in a directory, or in a pixa.  If in a directory,
+ *     or a pixa, the labelling text string must be embedded in the
+ *     text field of the image file.
+ *
+ *     Any number of recognizers (L_Recog) can be trained and then used
+ *     together in an array (L_Recoga).  All these trained structures
+ *     can be serialized to file and read back.  The serialized version
+ *     holds all the bitmaps used for training, plus, for arbitrary
+ *     character sets, the UTF8 representation and the lookup table
+ *     mapping from the character representation to index.
+ *
+ *     There are three levels of "sets" here:
+ *
+ *       (1) Example set: the examples representing a character that
+ *           were printed in the same way, so that they can be combined
+ *           without scaling to form an "average" template for the character.
+ *           In the recognition phase, we use either this aligned average,
+ *           or the individual bitmaps.  All examples in the set are given
+ *           the same character label.   Example: the letter 'a' in the
+ *           predominant font in a book.
+ *
+ *       (2) Character set (represented by L_Recog, a single recognizer):
+ *           The set of different characters, each of which is described
+ *           by (1).  Each element of the set has a different character
+ *           label.  Example: the digits '0' through '9' that are used for
+ *           page numbering in a book.
+ *
+ *       (3) Recognizer set (represented by L_Recoga, an array of recogs):
+ *           A set of recognizers, each of which is described by (2).
+ *           In general, we do not want to combine the character sets
+ *           with the same labels within different recognizer sets,
+ *           because the bitmaps can differ in font type, style or size.
+ *           Example 1: the letter 'a' can be printed in two very different
+ *           ways (either with a large loop or with a smaller loop in
+ *           the lower half); both share the same label but need to be
+ *           distinguished so that they are not mixed when averaging.
+ *           Example 2: a recognizer trained for a book may be missing
+ *           some characters, so we need to supplement it with another
+ *           "generic" or "bootstrap" recognizer that has the additional
+ *           characters from a variety of sources.  Bootstrap recognizers
+ *           must be run in a mode where all characters are scaled.
+ *
+ *     In the recognition process, for each component in an input image,
+ *     each recognizer (L_Recog) records the best match (highest
+ *     correlation score).  If there is more than one recognizer, these
+ *     results are aggregated to find the best match for each character
+ *     for all the recognizers, and this is stored in L_Recoga.
+ */
+
+#define  RECOG_VERSION_NUMBER      1
+
+struct L_Recoga {
+    l_int32              n;      /* number of recogs                         */
+    l_int32              nalloc; /* number of recog ptrs allocated           */
+    struct L_Recog     **recog;  /* recog ptr array                          */
+    struct L_Rcha       *rcha;   /* stores the array of best chars           */
+};
+typedef struct L_Recoga L_RECOGA;
+
+
+struct L_Recog {
+    l_int32        scalew;       /* scale all examples to this width;        */
+                                 /* use 0 prevent horizontal scaling         */
+    l_int32        scaleh;       /* scale all examples to this height;       */
+                                 /* use 0 prevent vertical scaling           */
+    l_int32        templ_type;   /* template type: either an average of      */
+                                 /* examples (L_USE_AVERAGE) or the set      */
+                                 /* of all examples (L_USE_ALL)              */
+    l_int32        maxarraysize; /* initialize container arrays to this      */
+    l_int32        setsize;      /* size of character set                    */
+    l_int32        threshold;    /* for binarizing if depth > 1              */
+    l_int32        maxyshift;    /* vertical jiggle on nominal centroid      */
+                                 /* alignment; typically 0 or 1              */
+    l_float32      asperity_fr;  /* +- allowed fractional asperity ratio     */
+    l_int32        charset_type; /* one of L_ARABIC_NUMERALS, etc.           */
+    l_int32        charset_size; /* expected number of classes in charset    */
+    char          *bootdir;      /* dir with bootstrap pixa charsets         */
+    char          *bootpattern;  /* file pattern for bootstrap pixa charsets */
+    char          *bootpath;     /* path for single bootstrap pixa charset   */
+    l_int32        boot_iters;   /* number of 2x2 erosion iters on boot pixa */
+    l_int32        min_nopad;    /* min number of samples without padding    */
+    l_int32        max_afterpad; /* max number of samples after padding      */
+    l_int32        min_samples;  /* min num of total samples; else use boot  */
+    l_int32        num_samples;  /* keep track of number of training samples */
+    l_int32        minwidth_u;   /* min width of averaged unscaled templates */
+    l_int32        maxwidth_u;   /* max width of averaged unscaled templates */
+    l_int32        minheight_u;  /* min height of averaged unscaled templates */
+    l_int32        maxheight_u;  /* max height of averaged unscaled templates */
+    l_int32        minwidth;     /* min width of averaged scaled templates   */
+    l_int32        maxwidth;     /* max width of averaged scaled templates   */
+    l_int32        ave_done;     /* set to 1 when averaged bitmaps are made  */
+    l_int32        train_done;   /* set to 1 when training is complete or    */
+                                 /* identification has started               */
+    l_int32        min_splitw;   /* min component width kept in splitting    */
+    l_int32        min_splith;   /* min component height kept in splitting   */
+    l_int32        max_splith;   /* max component height kept in splitting   */
+    struct Sarray *sa_text;      /* text array for arbitrary char set        */
+    struct L_Dna  *dna_tochar;   /* index-to-char lut for arbitrary char set */
+    l_int32       *centtab;      /* table for finding centroids              */
+    l_int32       *sumtab;       /* table for finding pixel sums             */
+    char          *fname;        /* serialized filename (if read)            */
+    struct Pixaa  *pixaa_u;      /* all unscaled bitmaps for each class      */
+    struct Pixa   *pixa_u;       /* averaged unscaled bitmaps for each class */
+    struct Ptaa   *ptaa_u;       /* centroids of all unscaled bitmaps        */
+    struct Pta    *pta_u;        /* centroids of unscaled averaged bitmaps   */
+    struct Numaa  *naasum_u;     /* area of all unscaled bitmap examples     */
+    struct Numa   *nasum_u;      /* area of unscaled averaged bitmaps        */
+    struct Pixaa  *pixaa;        /* all bitmap examples for each class       */
+    struct Pixa   *pixa;         /* averaged bitmaps for each class          */
+    struct Ptaa   *ptaa;         /* centroids of all bitmap examples         */
+    struct Pta    *pta;          /* centroids of averaged bitmaps            */
+    struct Numaa  *naasum;       /* area of all bitmap examples              */
+    struct Numa   *nasum;        /* area of averaged bitmaps                 */
+    struct Pixa   *pixa_tr;      /* input training images                    */
+    struct Pixa   *pixadb_ave;   /* unscaled and scaled averaged bitmaps     */
+    struct Pixa   *pixa_id;      /* input images for identifying             */
+    struct Pix    *pixdb_ave;    /* debug: best match of input against ave.  */
+    struct Pix    *pixdb_range;  /* debug: best matches within range         */
+    struct Pixa   *pixadb_boot;  /* debug: bootstrap training results        */
+    struct Pixa   *pixadb_split; /* debug: splitting results                 */
+    struct L_Bmf  *bmf;          /* bmf fonts                                */
+    l_int32        bmf_size;     /* font size of bmf; default is 6 pt        */
+    struct L_Rdid *did;          /* temp data used for image decoding        */
+    struct L_Rch  *rch;          /* temp data used for holding best char     */
+    struct L_Rcha *rcha;         /* temp data used for array of best chars   */
+    l_int32        bootrecog;    /* 1 if using bootstrap samples; else 0     */
+    l_int32        index;        /* recog index in recoga; -1 if no parent   */
+    struct L_Recoga  *parent;    /* ptr to parent array; can be null         */
+
+};
+typedef struct L_Recog L_RECOG;
+
+/*
+ *  Data returned from correlation matching on a single character
+ */
+struct L_Rch {
+    l_int32        index;        /* index of best template                   */
+    l_float32      score;        /* correlation score of best template       */
+    char          *text;         /* character string of best template        */
+    l_int32        sample;       /* index of best sample (within the best    */
+                                 /* template class, if all samples are used) */
+    l_int32        xloc;         /* x-location of template (delx + shiftx)   */
+    l_int32        yloc;         /* y-location of template (dely + shifty)   */
+    l_int32        width;        /* width of best template                   */
+};
+typedef struct L_Rch L_RCH;
+
+/*
+ *  Data returned from correlation matching on an array of characters
+ */
+struct L_Rcha {
+    struct Numa   *naindex;      /* indices of best templates                */
+    struct Numa   *nascore;      /* correlation scores of best templates     */
+    struct Sarray *satext;       /* character strings of best templates      */
+    struct Numa   *nasample;     /* indices of best samples                  */
+    struct Numa   *naxloc;       /* x-locations of templates (delx + shiftx) */
+    struct Numa   *nayloc;       /* y-locations of templates (dely + shifty) */
+    struct Numa   *nawidth;      /* widths of best templates                 */
+};
+typedef struct L_Rcha L_RCHA;
+
+/*
+ *  Data used for decoding a line of characters.
+ */
+struct L_Rdid {
+    struct Pix    *pixs;         /* clone of pix to be decoded               */
+    l_int32      **counta;       /* count array for each averaged template   */
+    l_int32      **delya;        /* best y-shift array per averaged template */
+    l_int32        narray;       /* number of averaged templates             */
+    l_int32        size;         /* size of count array (width of pixs)      */
+    l_int32       *setwidth;     /* setwidths for each template              */
+    struct Numa   *nasum;        /* pixel count in pixs by column            */
+    struct Numa   *namoment;     /* first moment of pixels in pixs by column */
+    l_int32        fullarrays;   /* 1 if full arrays are made; 0 otherwise   */
+    l_float32     *beta;         /* channel coeffs for template fg term      */
+    l_float32     *gamma;        /* channel coeffs for bit-and term          */
+    l_float32     *trellisscore; /* score on trellis                         */
+    l_int32       *trellistempl; /* template on trellis (for backtrack)      */
+    struct Numa   *natempl;      /* indices of best path templates           */
+    struct Numa   *naxloc;       /* x locations of best path templates       */
+    struct Numa   *nadely;       /* y locations of best path templates       */
+    struct Numa   *nawidth;      /* widths of best path templates            */
+    struct Numa   *nascore;      /* correlation scores: best path templates  */
+    struct Numa   *natempl_r;    /* indices of best rescored templates       */
+    struct Numa   *naxloc_r;     /* x locations of best rescoredtemplates    */
+    struct Numa   *nadely_r;     /* y locations of best rescoredtemplates    */
+    struct Numa   *nawidth_r;    /* widths of best rescoredtemplates         */
+    struct Numa   *nascore_r;    /* correlation scores: rescored templates   */
+};
+typedef struct L_Rdid L_RDID;
+
+
+/*-------------------------------------------------------------------------*
+ *                    Flags for selecting processing                       *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_SELECT_UNSCALED = 0,       /* select the unscaled bitmaps            */
+    L_SELECT_SCALED = 1,         /* select the scaled bitmaps              */
+    L_SELECT_BOTH = 2            /* select both unscaled and scaled        */
+};
+
+/*-------------------------------------------------------------------------*
+ *                Flags for determining what to test against               *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_USE_AVERAGE = 0,         /* form template from class average         */
+    L_USE_ALL = 1              /* match against all elements of each class */
+};
+
+/*-------------------------------------------------------------------------*
+ *             Flags for describing limited character sets                 *
+ *-------------------------------------------------------------------------*/
+enum {
+    L_UNKNOWN = 0,             /* character set type is not specified      */
+    L_ARABIC_NUMERALS = 1,     /* 10 digits                                */
+    L_LC_ROMAN_NUMERALS = 2,   /* 7 lower-case letters (i,v,x,l,c,d,m)     */
+    L_UC_ROMAN_NUMERALS = 3,   /* 7 upper-case letters (I,V,X,L,C,D,M)     */
+    L_LC_ALPHA = 4,            /* 26 lower-case letters                    */
+    L_UC_ALPHA = 5             /* 26 upper-case letters                    */
+};
+
+#endif  /* LEPTONICA_RECOG_H */
diff --git a/src/recogbasic.c b/src/recogbasic.c
new file mode 100644 (file)
index 0000000..3cc6f36
--- /dev/null
@@ -0,0 +1,1563 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  recogbasic.c
+ *
+ *      Recoga creation, destruction and access
+ *         L_RECOGA           *recogaCreateFromRecog()
+ *         L_RECOGA           *recogaCreateFromPixaa()
+ *         L_RECOGA           *recogaCreate()
+ *         void                recogaDestroy()
+ *         l_int32             recogaAddRecog()
+ *         static l_int32      recogaExtendArray()
+ *         l_int32             recogReplaceInRecoga()
+ *         L_RECOG            *recogaGetRecog()
+ *         l_int32             recogaGetCount()
+ *         l_int32             recogGetCount()
+ *         l_int32             recogGetIndex()
+ *         l_int32             recogGetParent()
+ *         l_int32             recogSetBootflag()
+ *
+ *      Recog initialization and destruction
+ *         L_RECOG            *recogCreateFromRecog()
+ *         L_RECOG            *recogCreateFromPixa()
+ *         L_RECOG            *recogCreate()
+ *         void                recogDestroy()
+ *
+ *      Appending (combining two recogs into one)
+ *         l_int32             recogAppend()
+ *
+ *      Character/index lookup
+ *         l_int32             recogGetClassIndex()
+ *         l_int32             recogStringToIndex()
+ *         l_int32             recogGetClassString()
+ *         l_int32             l_convertCharstrToInt()
+ *
+ *      Serialization
+ *         L_RECOGA           *recogaRead()
+ *         L_RECOGA           *recogaReadStream()
+ *         l_int32             recogaWrite()
+ *         l_int32             recogaWriteStream()
+ *         l_int32             recogaWritePixaa()
+ *         L_RECOG            *recogRead()
+ *         L_RECOG            *recogReadStream()
+ *         l_int32             recogWrite()
+ *         l_int32             recogWriteStream()
+ *         l_int32             recogWritePixa()
+ *         static l_int32      recogAddCharstrLabels()
+ *         static l_int32      recogAddAllSamples()
+ *
+ *  The recognizer functionality is split into four files:
+ *    recogbasic.c: create, destroy, access, serialize
+ *    recogtrain.c: training on labelled and unlabelled data
+ *    recogident.c: running the recognizer(s) on input
+ *    recogdid.c:   running the recognizer(s) on input using a
+ *                  document image decoding (DID) hidden markov model
+ *
+ *  This is a content-adapted (or book-adapted) recognizer (BAR) application.
+ *  The recognizers here are typically bootstrapped from data that has
+ *  been labelled by a generic recognition system, such as Tesseract.
+ *  The general procedure to create a recognizer (recog) from labelled data is
+ *  to add the labelled character bitmaps, and call recogTrainingFinished()
+ *  when done.
+ *
+ *  Typically, the recog is added to a recoga (an array of recognizers)
+ *  before use.  However, for identifying single characters, it is possible
+ *  to use a single recog.
+ *
+ *  If there is more than one recog, the usage options are:
+ *  (1) To join the two together (e.g., if they're from the same source)
+ *  (2) To put them separately into a recoga (recognizer array).
+ *
+ *  For training numeric input, an example set of calls that scales
+ *  each training input to (w, h) and will use the averaged
+ *  templates for identifying unknown characters is:
+ *         L_Recog  *rec = recogCreate(w, h, L_USE_AVERAGE, 128, 1);
+ *         for (i = 0; i < n; i++) {  // read in n training digits
+ *             Pix *pix = ...
+ *             recogTrainLabelled(rec, pix, NULL, text[i], 0, 0);
+ *         }
+ *         recogTrainingFinished(rec, 0);  // required
+ *
+ *  It is an error if any function that computes averages, removes
+ *  outliers or requests identification of an unlabelled character,
+ *  such as:
+ *         (1) computing the sample averages: recogAverageSamples()
+ *         (2) removing outliers: recogRemoveOutliers()
+ *         (3) requesting identification of an unlabeled character:
+ *                 recogIdentifyPix()
+ *  is called before an explicit call to finish training.  Note that
+ *  to do further training on a "finished" recognizer, just set
+ *         recog->train_done = FALSE;
+ *  add the new training samples, and again call
+ *         recogTrainingFinished(rec, 0);  // required
+ *
+ *  If using all examples for identification, all scaled to (w, h),
+ *  and with outliers removed, do something like this:
+ *         L_Recog  *rec = recogCreate(w, h, L_USE_ALL, 128, 1);
+ *         for (i = 0; i < n; i++) {  // read in n training characters
+ *             Pix *pix = ...
+ *             recogTrainLabelled(rec, pix, NULL, text[i], 0, 0);
+ *         }
+ *         recogTrainingFinished(rec, 0);
+ *         // remove anything with correlation less than 0.7 with average
+ *         recogRemoveOutliers(rec, 0.7, 0.5, 0);
+ *
+ *  You can train a recognizer from a pixa where the text field in each
+ *  pix is the character string:
+ *
+ *         L_Recog  *recboot = recogCreateFromPixa(pixa, w, h, L_USE_AVERAGE,
+ *                                                 128, 1);
+ *
+ *  This is useful as a "bootstrap" recognizer for training a new
+ *  recognizer (rec) on an unlabelled data set that has a different
+ *  origin from recboot.  To do this, the new recognizer must be
+ *  initialized to use the same (w,h) scaling as the bootstrap recognizer.
+ *  If the new recognizer is to be used without scaling (e.g., on images
+ *  from a single source, like a book), call recogSetScaling() to
+ *  regenerate all the scaled samples and averages:
+ *
+ *         L_Recog  *rec = recogCreate(w, h, L_USE_ALL, 128, 1);
+ *         for (i = 0; i < n; i++) {  // read in n training characters
+ *             Pix *pix = ...
+ *             recogTrainUnlabelled(rec, recboot, pix, NULL, 1, 0.75, 0);
+ *         }
+ *         recogTrainingFinished(rec, 0);
+ *         recogSetScaling(rec, 0, 0);  // use with no scaling
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;  /* n'import quoi */
+static const l_int32  MAX_EXAMPLES_IN_CLASS = 256;
+
+    /* Static functions */
+static l_int32 recogaExtendArray(L_RECOGA *recoga);
+static l_int32 recogAddCharstrLabels(L_RECOG *recog);
+static l_int32 recogAddAllSamples(L_RECOG *recog, PIXAA *paa, l_int32 debug);
+
+    /* Tolerance (+-) in asperity ratio between unknown and known */
+static l_float32  DEFAULT_ASPERITY_FRACT = 0.25;
+
+
+/*------------------------------------------------------------------------*
+ *                Recoga: creation, destruction, access                   *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogaCreateFromRecog()
+ *
+ *      Input:  recog
+ *      Return: recoga, or null on error
+ *
+ *  Notes:
+ *      (1) This is a convenience function for making a recoga after
+ *          you have a recog.  The recog is owned by the recoga.
+ *      (2) For splitting connected components, the
+ *          input recog must be from the material to be identified,
+ *          and not a generic bootstrap recog.  Those can be added later.
+ */
+L_RECOGA *
+recogaCreateFromRecog(L_RECOG  *recog)
+{
+L_RECOGA  *recoga;
+
+    PROCNAME("recogaCreateFromRecog");
+
+    if (!recog)
+        return (L_RECOGA *)ERROR_PTR("recog not defined", procName, NULL);
+
+    recoga = recogaCreate(1);
+    recogaAddRecog(recoga, recog);
+    return recoga;
+}
+
+
+/*!
+ *  recogaCreateFromPixaa()
+ *
+ *      Input:  paa (of labelled, 1 bpp images)
+ *              scalew  (scale all widths to this; use 0 for no scaling)
+ *              scaleh  (scale all heights to this; use 0 for no scaling)
+ *              templ_type (L_USE_AVERAGE or L_USE_ALL)
+ *              threshold (for binarization; typically ~128)
+ *              maxyshift (from nominal centroid alignment; typically 0 or 1)
+ *      Return: recoga, or null on error
+ *
+ *  Notes:
+ *      (1) This is a convenience function for training from labelled data.
+ *      (2) Each pixa in the paa is a set of labelled data that is used
+ *          to train a recognizer (e.g., for a set of characters in a font).
+ *          Each image example in the pixa is put into a class in its
+ *          recognizer, defined by its character label.  All examples in
+ *          the same class should be similar.
+ *      (3) The pixaa can be written by recogaWritePixaa(), and must contain
+ *          the unscaled bitmaps used for training.
+ */
+L_RECOGA *
+recogaCreateFromPixaa(PIXAA       *paa,
+                      l_int32      scalew,
+                      l_int32      scaleh,
+                      l_int32      templ_type,
+                      l_int32      threshold,
+                      l_int32      maxyshift)
+{
+l_int32    n, i, full;
+L_RECOG   *recog;
+L_RECOGA  *recoga;
+PIXA      *pixa;
+
+    PROCNAME("recogaCreateFromPixaa");
+
+    if (!paa)
+        return (L_RECOGA *)ERROR_PTR("paa not defined", procName, NULL);
+    if (pixaaVerifyDepth(paa, NULL) != 1)
+        return (L_RECOGA *)ERROR_PTR("all pix not 1 bpp", procName, NULL);
+    pixaaIsFull(paa, &full);
+    if (!full)
+        return (L_RECOGA *)ERROR_PTR("all pix not present", procName, NULL);
+
+    n = pixaaGetCount(paa, NULL);
+    recoga = recogaCreate(n);
+    for (i = 0; i < n; i++) {
+        pixa = pixaaGetPixa(paa, i, L_CLONE);
+        recog = recogCreateFromPixa(pixa, scalew, scaleh, templ_type,
+                                    threshold, maxyshift);
+        recogaAddRecog(recoga, recog);
+        pixaDestroy(&pixa);
+    }
+
+    return recoga;
+}
+
+
+/*!
+ *  recogaCreate()
+ *
+ *      Input:  n (initial number of recog ptrs)
+ *      Return: recoga, or null on error
+ */
+L_RECOGA *
+recogaCreate(l_int32  n)
+{
+L_RECOGA  *recoga;
+
+    PROCNAME("recogaCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((recoga = (L_RECOGA *)LEPT_CALLOC(1, sizeof(L_RECOGA))) == NULL)
+        return (L_RECOGA *)ERROR_PTR("recoga not made", procName, NULL);
+    recoga->n = 0;
+    recoga->nalloc = n;
+
+    if ((recoga->recog = (L_RECOG **)LEPT_CALLOC(n, sizeof(L_RECOG *))) == NULL)
+        return (L_RECOGA *)ERROR_PTR("recoga ptrs not made", procName, NULL);
+
+    return recoga;
+}
+
+
+/*!
+ *  recogaDestroy()
+ *
+ *      Input:  &recoga (<will be set to null before returning>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) If a recog has a parent, the parent owns it.  To destroy
+ *          a recog, it must first be "orphaned".
+ */
+void
+recogaDestroy(L_RECOGA  **precoga)
+{
+l_int32    i;
+L_RECOG   *recog;
+L_RECOGA  *recoga;
+
+    PROCNAME("recogaDestroy");
+
+    if (precoga == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((recoga = *precoga) == NULL)
+        return;
+
+    rchaDestroy(&recoga->rcha);
+    for (i = 0; i < recoga->n; i++) {
+        if ((recog = recoga->recog[i]) == NULL) {
+            L_ERROR("recog not found for index %d\n", procName, i);
+            continue;
+        }
+        recog->parent = NULL;  /* orphan it */
+        recogDestroy(&recog);
+    }
+    LEPT_FREE(recoga->recog);
+    LEPT_FREE(recoga);
+    *precoga = NULL;
+    return;
+}
+
+
+/*!
+ *  recogaAddRecog()
+ *
+ *      Input:  recoga
+ *              recog (to be added and owned by the recoga; not a copy)
+ *      Return: recoga, or null on error
+ */
+l_int32
+recogaAddRecog(L_RECOGA  *recoga,
+               L_RECOG   *recog)
+{
+l_int32  n;
+
+    PROCNAME("recogaAddRecog");
+
+    if (!recoga)
+        return ERROR_INT("recoga not defined", procName, 1);
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+
+    n = recoga->n;
+    if (n >= recoga->nalloc)
+        recogaExtendArray(recoga);
+    recoga->recog[n] = recog;
+    recog->index = n;
+    recog->parent = recoga;
+    recoga->n++;
+    return 0;
+}
+
+
+/*!
+ *  recogaExtendArray()
+ *
+ *      Input:  recoga
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+recogaExtendArray(L_RECOGA  *recoga)
+{
+    PROCNAME("recogaExtendArray");
+
+    if (!recoga)
+        return ERROR_INT("recogaa not defined", procName, 1);
+
+    if ((recoga->recog = (L_RECOG **)reallocNew((void **)&recoga->recog,
+                              sizeof(L_RECOG *) * recoga->nalloc,
+                              2 * sizeof(L_RECOG *) * recoga->nalloc)) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+
+    recoga->nalloc *= 2;
+    return 0;
+}
+
+
+/*!
+ *  recogReplaceInRecoga()
+ *
+ *      Input:  &recog1 (old recog, to be destroyed)
+ *              recog2 (new recog, to be inserted in place of @recog1)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This always destroys recog1.
+ *      (2) If recog1 belongs to a recoga, this inserts recog2 into
+ *          the slot that recog1 previously occupied.
+ */
+l_int32
+recogReplaceInRecoga(L_RECOG  **precog1,
+                     L_RECOG   *recog2)
+{
+l_int32    n, index;
+L_RECOG   *recog1;
+L_RECOGA  *recoga;
+
+    PROCNAME("recogReplaceInRecoga");
+
+    if (!precog1)
+        return ERROR_INT("&recog1 not defined", procName, 1);
+    if (!recog2)
+        return ERROR_INT("recog2 not defined", procName, 1);
+    if ((recog1 = *precog1) == NULL)
+        return ERROR_INT("recog1 not defined", procName, 1);
+
+    if ((recoga = recogGetParent(recog1)) == NULL) {
+        recogDestroy(precog1);
+        return 0;
+    }
+
+    n = recogaGetCount(recoga);
+    recogGetIndex(recog1, &index);
+    if (index >= n) {
+        L_ERROR("invalid index %d in recog1; no replacement\n", procName,
+                recog1->index);
+        recogDestroy(precog1);
+        return 1;
+    }
+
+    recog1->parent = NULL;  /* necessary to destroy recog1 */
+    recogDestroy(precog1);
+    recoga->recog[index] = recog2;
+    recog2->index = index;
+    recog2->parent = recoga;
+    return 0;
+}
+
+
+/*!
+ *  recogaGetRecog()
+ *
+ *      Input:  recoga
+ *              index (to the index-th recog)
+ *      Return: recog, or null on error
+ *
+ *  Notes:
+ *      (1) This returns a ptr to the recog, which is still owned by
+ *          the recoga.  Do not destroy it.
+ */
+L_RECOG *
+recogaGetRecog(L_RECOGA  *recoga,
+               l_int32    index)
+{
+L_RECOG  *recog;
+
+    PROCNAME("recogaAddRecog");
+
+    if (!recoga)
+        return (L_RECOG *)ERROR_PTR("recoga not defined", procName, NULL);
+    if (index < 0 || index >= recoga->n)
+        return (L_RECOG *)ERROR_PTR("index not valid", procName, NULL);
+
+    recog = recoga->recog[index];
+    return recog;
+}
+
+
+/*!
+ *  recogaGetCount()
+ *
+ *      Input:  recoga
+ *      Return: count of recog in array; 0 if no recog or on error
+ */
+l_int32
+recogaGetCount(L_RECOGA  *recoga)
+{
+    PROCNAME("recogaGetCount");
+
+    if (!recoga)
+        return ERROR_INT("recoga not defined", procName, 0);
+    return recoga->n;
+}
+
+
+/*!
+ *  recogGetCount()
+ *
+ *      Input:  recog
+ *      Return: count of classes in recog; 0 if no recog or on error
+ */
+l_int32
+recogGetCount(L_RECOG  *recog)
+{
+    PROCNAME("recogGetCount");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 0);
+    return recog->setsize;
+}
+
+
+/*!
+ *  recogGetIndex()
+ *
+ *      Input:  recog
+ *             &index (into the parent recoga; -1 if no parent)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogGetIndex(L_RECOG  *recog,
+              l_int32  *pindex)
+{
+    PROCNAME("recogGetIndex");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = -1;
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    *pindex = recog->index;
+    return 0;
+}
+
+/*!
+ *  recogGetParent()
+ *
+ *      Input:  recog
+ *      Return: recoga (back-pointer to parent); can be null
+ */
+L_RECOGA *
+recogGetParent(L_RECOG  *recog)
+{
+    PROCNAME("recogGetParent");
+
+    if (!recog)
+        return (L_RECOGA *)ERROR_PTR("recog not defined", procName, NULL);
+    return recog->parent;
+}
+
+
+/*!
+ *  recogSetBootflag()
+ *
+ *      Input:  recog
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This must be set for any bootstrap recog, where the samples
+ *          are not from the media being identified.
+ *      (2) It is used to enforce scaled bitmaps for identification,
+ *          and to prevent the recog from being used to split touching
+ *          characters (which requires unscaled samples from the
+ *          material being identified).
+ */
+l_int32
+recogSetBootflag(L_RECOG  *recog)
+{
+    PROCNAME("recogSetBootflag");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    recog->bootrecog = 1;
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                Recog: initialization and destruction                   *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogCreateFromRecog()
+ *
+ *      Input:  recs (source recog with arbitrary input parameters)
+ *              scalew  (scale all widths to this; use 0 for no scaling)
+ *              scaleh  (scale all heights to this; use 0 for no scaling)
+ *              templ_type (L_USE_AVERAGE or L_USE_ALL)
+ *              threshold (for binarization; typically ~128)
+ *              maxyshift (from nominal centroid alignment; typically 0 or 1)
+ *      Return: recd, or null on error
+ *
+ *  Notes:
+ *      (1) This is a convenience function that generates a recog using
+ *          the unscaled training data in an existing recog.
+ */
+L_RECOG *
+recogCreateFromRecog(L_RECOG     *recs,
+                     l_int32      scalew,
+                     l_int32      scaleh,
+                     l_int32      templ_type,
+                     l_int32      threshold,
+                     l_int32      maxyshift)
+{
+L_RECOG  *recd;
+PIXA     *pixa;
+
+    PROCNAME("recogCreateFromRecog");
+
+    if (!recs)
+        return (L_RECOG *)ERROR_PTR("recs not defined", procName, NULL);
+
+    pixa = pixaaFlattenToPixa(recs->pixaa_u, NULL, L_CLONE);
+    recd = recogCreateFromPixa(pixa, scalew, scaleh, templ_type, threshold,
+                               maxyshift);
+    pixaDestroy(&pixa);
+    return recd;
+}
+
+
+/*!
+ *  recogCreateFromPixa()
+ *
+ *      Input:  pixa (of labelled, 1 bpp images)
+ *              scalew  (scale all widths to this; use 0 for no scaling)
+ *              scaleh  (scale all heights to this; use 0 for no scaling)
+ *              templ_type (L_USE_AVERAGE or L_USE_ALL)
+ *              threshold (for binarization; typically ~128)
+ *              maxyshift (from nominal centroid alignment; typically 0 or 1)
+ *      Return: recog, or null on error
+ *
+ *  Notes:
+ *      (1) This is a convenience function for training from labelled data.
+ *          The pixa can be read from file.
+ *      (2) The pixa should contain the unscaled bitmaps used for training.
+ *      (3) The characters here should work as a single "font", because
+ *          each image example is put into a class defined by its
+ *          character label.  All examples in the same class should be
+ *          similar.
+ */
+L_RECOG *
+recogCreateFromPixa(PIXA        *pixa,
+                    l_int32      scalew,
+                    l_int32      scaleh,
+                    l_int32      templ_type,
+                    l_int32      threshold,
+                    l_int32      maxyshift)
+{
+char     *text;
+l_int32   full, n, i, ntext;
+L_RECOG  *recog;
+PIX      *pix;
+
+    PROCNAME("recogCreateFromPixa");
+
+    if (!pixa)
+        return (L_RECOG *)ERROR_PTR("pixa not defined", procName, NULL);
+    if (pixaVerifyDepth(pixa, NULL) != 1)
+        return (L_RECOG *)ERROR_PTR("not all pix are 1 bpp", procName, NULL);
+
+    pixaIsFull(pixa, &full, NULL);
+    if (!full)
+        return (L_RECOG *)ERROR_PTR("not all pix are present", procName, NULL);
+
+    n = pixaGetCount(pixa);
+    pixaCountText(pixa, &ntext);
+    if (ntext == 0)
+        return (L_RECOG *)ERROR_PTR("no pix have text strings", procName, NULL);
+    if (ntext < n)
+        L_ERROR("%d text strings < %d pix\n", procName, ntext, n);
+
+    recog = recogCreate(scalew, scaleh, templ_type, threshold,
+                        maxyshift);
+    if (!recog)
+        return (L_RECOG *)ERROR_PTR("recog not made", procName, NULL);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        text = pixGetText(pix);
+        if (!text || strlen(text) == 0) {
+            L_ERROR("pix[%d] has no text\n", procName, i);
+            pixDestroy(&pix);
+            continue;
+        }
+        recogTrainLabelled(recog, pix, NULL, text, 0, 0);
+        pixDestroy(&pix);
+    }
+
+    recogTrainingFinished(recog, 0);
+    return recog;
+}
+
+
+/*!
+ *  recogCreate()
+ *
+ *      Input:  scalew  (scale all widths to this; use 0 for no scaling)
+ *              scaleh  (scale all heights to this; use 0 for no scaling)
+ *              templ_type (L_USE_AVERAGE or L_USE_ALL)
+ *              threshold (for binarization; typically ~128)
+ *              maxyshift (from nominal centroid alignment; typically 0 or 1)
+ *      Return: recog, or null on error
+ *
+ *  Notes:
+ *      (1) For a set trained on one font, such as numbers in a book,
+ *          it is sensible to set scalew = scaleh = 0.
+ *      (2) For a mixed training set, scaling to a fixed height,
+ *          such as 32 pixels, but leaving the width unscaled, is effective.
+ *      (3) The storage for most of the arrays is allocated when training
+ *          is finished.
+ */
+L_RECOG *
+recogCreate(l_int32      scalew,
+            l_int32      scaleh,
+            l_int32      templ_type,
+            l_int32      threshold,
+            l_int32      maxyshift)
+{
+L_RECOG  *recog;
+PIXA     *pixa;
+PIXAA    *paa;
+
+    PROCNAME("recogCreate");
+
+    if (scalew < 0 || scaleh < 0)
+        return (L_RECOG *)ERROR_PTR("invalid scalew or scaleh", procName, NULL);
+    if (templ_type != L_USE_AVERAGE && templ_type != L_USE_ALL)
+        return (L_RECOG *)ERROR_PTR("invalid templ_type flag", procName, NULL);
+    if (threshold < 1 || threshold > 255)
+        return (L_RECOG *)ERROR_PTR("invalid threshold", procName, NULL);
+
+    if ((recog = (L_RECOG *)LEPT_CALLOC(1, sizeof(L_RECOG))) == NULL)
+        return (L_RECOG *)ERROR_PTR("rec not made", procName, NULL);
+    recog->templ_type = templ_type;
+    recog->threshold = threshold;
+    recog->scalew = scalew;
+    recog->scaleh = scaleh;
+    recog->maxyshift = maxyshift;
+    recog->asperity_fr = DEFAULT_ASPERITY_FRACT;
+    recogSetPadParams(recog, NULL, NULL, NULL, 0, -1, -1, -1, -1);
+    recog->bmf = bmfCreate(NULL, 6);
+    recog->bmf_size = 6;
+    recog->maxarraysize = MAX_EXAMPLES_IN_CLASS;
+    recog->index = -1;
+
+        /* Generate the LUTs */
+    recog->centtab = makePixelCentroidTab8();
+    recog->sumtab = makePixelSumTab8();
+    recog->sa_text = sarrayCreate(0);
+    recog->dna_tochar = l_dnaCreate(0);
+
+        /* Input default values for min component size for splitting.
+         * These are overwritten when pixTrainingFinished() is called. */
+    recog->min_splitw = 6;
+    recog->min_splith = 6;
+    recog->max_splith = 60;
+
+        /* Generate the storage for the unscaled training bitmaps */
+    paa = pixaaCreate(recog->maxarraysize);
+    pixa = pixaCreate(1);
+    pixaaInitFull(paa, pixa);
+    pixaDestroy(&pixa);
+    recog->pixaa_u = paa;
+
+        /* Generate the storage for debugging */
+    recog->pixadb_boot = pixaCreate(2);
+    recog->pixadb_split = pixaCreate(2);
+    return recog;
+}
+
+
+/*!
+ *  recogDestroy()
+ *
+ *      Input:  &recog (<will be set to null before returning>)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) If a recog has a parent, the parent owns it.  A recogDestroy()
+ *          will fail if there is a parent.
+ */
+void
+recogDestroy(L_RECOG  **precog)
+{
+L_RECOG  *recog;
+
+    PROCNAME("recogDestroy");
+
+    if (!precog) {
+        L_WARNING("ptr address is null\n", procName);
+        return;
+    }
+
+    if ((recog = *precog) == NULL) return;
+    if (recogGetParent(recog) != NULL) {
+        L_ERROR("recog has parent; can't be destroyed\n", procName);
+        return;
+    }
+
+    LEPT_FREE(recog->bootdir);
+    LEPT_FREE(recog->bootpattern);
+    LEPT_FREE(recog->bootpath);
+    LEPT_FREE(recog->centtab);
+    LEPT_FREE(recog->sumtab);
+    LEPT_FREE(recog->fname);
+    sarrayDestroy(&recog->sa_text);
+    l_dnaDestroy(&recog->dna_tochar);
+    pixaaDestroy(&recog->pixaa_u);
+    pixaDestroy(&recog->pixa_u);
+    ptaaDestroy(&recog->ptaa_u);
+    ptaDestroy(&recog->pta_u);
+    numaDestroy(&recog->nasum_u);
+    numaaDestroy(&recog->naasum_u);
+    pixaaDestroy(&recog->pixaa);
+    pixaDestroy(&recog->pixa);
+    ptaaDestroy(&recog->ptaa);
+    ptaDestroy(&recog->pta);
+    numaDestroy(&recog->nasum);
+    numaaDestroy(&recog->naasum);
+    pixaDestroy(&recog->pixa_tr);
+    pixaDestroy(&recog->pixadb_ave);
+    pixaDestroy(&recog->pixa_id);
+    pixDestroy(&recog->pixdb_ave);
+    pixDestroy(&recog->pixdb_range);
+    pixaDestroy(&recog->pixadb_boot);
+    pixaDestroy(&recog->pixadb_split);
+    bmfDestroy(&recog->bmf);
+    rchDestroy(&recog->rch);
+    rchaDestroy(&recog->rcha);
+    recogDestroyDid(recog);
+    LEPT_FREE(recog);
+    *precog = NULL;
+    return;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                                Appending                               *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogAppend()
+ *
+ *      Input:  recog1
+ *              recog2 (gets added to recog1)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is used to make a training recognizer from more than
+ *          one trained recognizer source.  It should only be used
+ *          when the bitmaps for corresponding character classes are
+ *          very similar.  That constraint does not arise when
+ *          the character classes are disjoint; e.g., if recog1 is
+ *          digits and recog2 is alphabetical.
+ *      (2) This is done by appending recog2 to recog1.  Averages are
+ *          computed for each recognizer, if necessary, before appending.
+ *      (3) Non-array fields are combined using the appropriate min and max.
+ */
+l_int32
+recogAppend(L_RECOG  *recog1,
+            L_RECOG  *recog2)
+{
+    PROCNAME("recogAppend");
+
+    if (!recog1)
+        return ERROR_INT("recog1 not defined", procName, 1);
+    if (!recog2)
+        return ERROR_INT("recog2 not defined", procName, 1);
+
+        /* Make sure both are finalized with all arrays computed */
+    recogAverageSamples(recog1, 0);
+    recogAverageSamples(recog2, 0);
+
+        /* Combine non-array field values */
+    recog1->minwidth_u = L_MIN(recog1->minwidth_u, recog2->minwidth_u);
+    recog1->maxwidth_u = L_MAX(recog1->maxwidth_u, recog2->maxwidth_u);
+    recog1->minheight_u = L_MIN(recog1->minheight_u, recog2->minheight_u);
+    recog1->maxheight_u = L_MAX(recog1->maxheight_u, recog2->maxheight_u);
+    recog1->minwidth = L_MIN(recog1->minwidth, recog2->minwidth);
+    recog1->maxwidth = L_MAX(recog1->maxwidth, recog2->maxwidth);
+    recog1->min_splitw = L_MIN(recog1->min_splitw, recog2->min_splitw);
+    recog1->min_splith = L_MIN(recog1->min_splith, recog2->min_splith);
+    recog1->max_splith = L_MAX(recog1->max_splith, recog2->max_splith);
+
+        /* Combine array field values */
+    recog1->setsize += recog2->setsize;
+    sarrayAppendRange(recog1->sa_text, recog2->sa_text, 0, -1);
+    l_dnaJoin(recog1->dna_tochar, recog2->dna_tochar, 0, -1);
+    pixaaJoin(recog1->pixaa_u, recog2->pixaa_u, 0, -1);
+    pixaJoin(recog1->pixa_u, recog2->pixa_u, 0, -1);
+    ptaaJoin(recog1->ptaa_u, recog2->ptaa_u, 0, -1);
+    ptaJoin(recog1->pta_u, recog2->pta_u, 0, -1);
+    numaaJoin(recog1->naasum_u, recog2->naasum_u, 0, -1);
+    numaJoin(recog1->nasum_u, recog2->nasum_u, 0, -1);
+    pixaaJoin(recog1->pixaa, recog2->pixaa, 0, -1);
+    pixaJoin(recog1->pixa, recog2->pixa, 0, -1);
+    ptaaJoin(recog1->ptaa, recog2->ptaa, 0, -1);
+    ptaJoin(recog1->pta, recog2->pta, 0, -1);
+    numaaJoin(recog1->naasum, recog2->naasum, 0, -1);
+    numaJoin(recog1->nasum, recog2->nasum, 0, -1);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                         Character/index lookup                         *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogGetClassIndex()
+ *
+ *      Input:  recog (with LUT's pre-computed)
+ *              val (integer value; can be up to 3 bytes for UTF-8)
+ *              text (text from which @val was derived; used if not found)
+ *              &index (<return> index into dna_tochar)
+ *      Return: 0 if found; 1 if not found and added; 2 on error.
+ *
+ *  Notes:
+ *      (1) This is used during training.  It searches the
+ *          dna character array for @val.  If not found, it increments
+ *          the setsize by 1, augmenting both the index and text arrays.
+ *      (2) Returns the index in &index, except on error.
+ *      (3) Caller must check the function return value.
+ */
+l_int32
+recogGetClassIndex(L_RECOG  *recog,
+                   l_int32   val,
+                   char     *text,
+                   l_int32  *pindex)
+{
+l_int32  i, n, ival;
+
+    PROCNAME("recogGetClassIndex");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 2);
+    *pindex = 0;
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 2);
+    if (!text)
+        return ERROR_INT("text not defined", procName, 2);
+
+        /* Search existing characters */
+    n = l_dnaGetCount(recog->dna_tochar);
+    for (i = 0; i < n; i++) {
+        l_dnaGetIValue(recog->dna_tochar, i, &ival);
+        if (val == ival) {  /* found */
+            *pindex = i;
+            return 0;
+        }
+    }
+
+       /* If not found... */
+    l_dnaAddNumber(recog->dna_tochar, val);
+    sarrayAddString(recog->sa_text, text, L_COPY);
+    recog->setsize++;
+    *pindex = n;
+    return 1;
+}
+
+
+/*!
+ *  recogStringToIndex()
+ *
+ *      Input:  recog
+ *              text (text string for some class)
+ *              &index (<return> index for that class; -1 if not found)
+ *      Return: 0 if OK, 1 on error (not finding the string is an error)
+ */
+l_int32
+recogStringToIndex(L_RECOG  *recog,
+                   char     *text,
+                   l_int32  *pindex)
+{
+char    *charstr;
+l_int32  i, n, diff;
+
+    PROCNAME("recogStringtoIndex");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = -1;
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!text)
+        return ERROR_INT("text not defined", procName, 1);
+
+        /* Search existing characters */
+    n = recog->setsize;
+    for (i = 0; i < n; i++) {
+        recogGetClassString(recog, i, &charstr);
+        if (!charstr) {
+            L_ERROR("string not found for index %d\n", procName, i);
+            continue;
+        }
+        diff = strcmp(text, charstr);
+        LEPT_FREE(charstr);
+        if (diff) continue;
+        *pindex = i;
+        return 0;
+    }
+
+    return 1;  /* not found */
+}
+
+
+/*!
+ *  recogGetClassString()
+ *
+ *      Input:  recog
+ *              index (into array of char types)
+ *              &charstr (<return> string representation;
+ *                        returns an empty string on error)
+ *      Return: 0 if found, 1 on error
+ *
+ *  Notes:
+ *      (1) Extracts a copy of the string from sa_text, which
+ *          the caller must free.
+ *      (2) Caller must check the function return value.
+ */
+l_int32
+recogGetClassString(L_RECOG  *recog,
+                    l_int32   index,
+                    char    **pcharstr)
+{
+    PROCNAME("recogGetClassString");
+
+    if (!pcharstr)
+        return ERROR_INT("&charstr not defined", procName, 1);
+    *pcharstr = stringNew("");
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 2);
+
+    if (index < 0 || index >= recog->setsize)
+        return ERROR_INT("invalid index", procName, 1);
+    LEPT_FREE(*pcharstr);
+    *pcharstr = sarrayGetString(recog->sa_text, index, L_COPY);
+    return 0;
+}
+
+
+/*!
+ *  l_convertCharstrToInt()
+ *
+ *      Input:  str (input string representing one UTF-8 character;
+ *                   not more than 4 bytes)
+ *              &val (<return> integer value for the input.  Think of it
+ *                    as a 1-to-1 hash code.)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+l_convertCharstrToInt(const char  *str,
+                      l_int32     *pval)
+{
+l_int32  size, val;
+
+    PROCNAME("l_convertCharstrToInt");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0;
+    if (!str)
+        return ERROR_INT("str not defined", procName, 1);
+    size = strlen(str);
+    if (size == 0)
+        return ERROR_INT("empty string", procName, 1);
+    if (size > 4)
+        return ERROR_INT("invalid string: > 4 bytes", procName, 1);
+
+    val = (l_int32)str[0];
+    if (size > 1)
+        val = (val << 8) + (l_int32)str[1];
+    if (size > 2)
+        val = (val << 8) + (l_int32)str[2];
+    if (size > 3)
+        val = (val << 8) + (l_int32)str[3];
+    *pval = val;
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                             Serialization                              *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogaRead()
+ *
+ *      Input:  filename
+ *      Return: recoga, or null on error
+ *
+ *  Notes:
+ *      (1) This allows serialization of an array of recognizers, each of which
+ *          can be used for different fonts, font styles, etc.
+ */
+L_RECOGA *
+recogaRead(const char  *filename)
+{
+FILE     *fp;
+L_RECOGA  *recoga;
+
+    PROCNAME("recogaRead");
+
+    if (!filename)
+        return (L_RECOGA *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (L_RECOGA *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((recoga = recogaReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (L_RECOGA *)ERROR_PTR("recoga not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return recoga;
+}
+
+
+/*!
+ *  recogaReadStream()
+ *
+ *      Input:  stream
+ *      Return: recog, or null on error
+ */
+L_RECOGA *
+recogaReadStream(FILE  *fp)
+{
+l_int32    version, i, nrec, ignore;
+L_RECOG   *recog;
+L_RECOGA  *recoga;
+
+    PROCNAME("recogaReadStream");
+
+    if (!fp)
+        return (L_RECOGA *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nRecoga Version %d\n", &version) != 1)
+        return (L_RECOGA *)ERROR_PTR("not a recog file", procName, NULL);
+    if (version != RECOG_VERSION_NUMBER)
+        return (L_RECOGA *)ERROR_PTR("invalid recog version", procName, NULL);
+    if (fscanf(fp, "Number of recognizers = %d\n\n", &nrec) != 1)
+        return (L_RECOGA *)ERROR_PTR("nrec not read", procName, NULL);
+
+    recoga = recogaCreate(nrec);
+    for (i = 0; i < nrec; i++) {
+        ignore = fscanf(fp, "==============================\n");
+        if (fscanf(fp, "Recognizer %d\n", &ignore) != 1)
+            return (L_RECOGA *)ERROR_PTR("malformed file", procName, NULL);
+        if ((recog = recogReadStream(fp)) == NULL) {
+            recogaDestroy(&recoga);
+            L_ERROR("recog read failed for recog %d\n", procName, i);
+            return NULL;
+        }
+        ignore = fscanf(fp, "\n");
+        recogaAddRecog(recoga, recog);
+    }
+    return recoga;
+}
+
+
+/*!
+ *  recogaWrite()
+ *
+ *      Input:  filename
+ *              recoga
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogaWrite(const char  *filename,
+            L_RECOGA    *recoga)
+{
+FILE  *fp;
+
+    PROCNAME("recogaWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!recoga)
+        return ERROR_INT("recoga not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (recogaWriteStream(fp, recoga, filename))
+        return ERROR_INT("recoga not written to stream", procName, 1);
+    fclose(fp);
+    return 0;
+}
+
+
+/*!
+ *  recogaWriteStream()
+ *
+ *      Input:  stream (opened for "wb")
+ *              recoga
+ *              filename (output serialized filename; embedded in file)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogaWriteStream(FILE        *fp,
+                  L_RECOGA    *recoga,
+                  const char  *filename)
+{
+l_int32   i;
+L_RECOG  *recog;
+
+    PROCNAME("recogaWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!recoga)
+        return ERROR_INT("recoga not defined", procName, 1);
+
+    fprintf(fp, "\nRecoga Version %d\n", RECOG_VERSION_NUMBER);
+    fprintf(fp, "Number of recognizers = %d\n\n", recoga->n);
+
+    for (i = 0; i < recoga->n; i++) {
+        fprintf(fp, "==============================\n");
+        fprintf(fp, "Recognizer %d\n", i);
+        recog = recogaGetRecog(recoga, i);
+        recogWriteStream(fp, recog, filename);
+        fprintf(fp, "\n");
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  recogaWritePixaa()
+ *
+ *      Input:  filename
+ *              recoga
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For each recognizer, this generates a pixa of all the
+ *          unscaled images.  They are combined into a pixaa for
+ *          the set of recognizers.  Each pix has has its character
+ *          string in the pix text field.
+ *      (2) As a side-effect, the character class label is written
+ *          into each pix in recog.
+ */
+l_int32
+recogaWritePixaa(const char  *filename,
+                 L_RECOGA    *recoga)
+{
+l_int32   i;
+PIXA     *pixa;
+PIXAA    *paa;
+L_RECOG  *recog;
+
+    PROCNAME("recogaWritePixaa");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!recoga)
+        return ERROR_INT("recoga not defined", procName, 1);
+
+    paa = pixaaCreate(recoga->n);
+    for (i = 0; i < recoga->n; i++) {
+        recog = recogaGetRecog(recoga, i);
+        recogAddCharstrLabels(recog);
+        pixa = pixaaFlattenToPixa(recog->pixaa_u, NULL, L_CLONE);
+        pixaaAddPixa(paa, pixa, L_INSERT);
+    }
+    pixaaWrite(filename, paa);
+    pixaaDestroy(&paa);
+    return 0;
+}
+
+
+/*!
+ *  recogRead()
+ *
+ *      Input:  filename
+ *      Return: recog, or null on error
+ *
+ *  Notes:
+ *      (1) Serialization can be applied to any recognizer, including
+ *          one with more than one "font".  That is, it can have
+ *          multiple character classes with the same character set
+ *          description, where each of those classes contains characters
+ *          that are very similar in size and shape.  Each pixa in
+ *          the serialized pixaa contains images for a single character
+ *          class.
+ */
+L_RECOG *
+recogRead(const char  *filename)
+{
+FILE     *fp;
+L_RECOG  *recog;
+
+    PROCNAME("recogRead");
+
+    if (!filename)
+        return (L_RECOG *)ERROR_PTR("filename not defined", procName, NULL);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (L_RECOG *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((recog = recogReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (L_RECOG *)ERROR_PTR("recog not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return recog;
+}
+
+
+/*!
+ *  recogReadStream()
+ *
+ *      Input:  stream
+ *      Return: recog, or null on error
+ */
+L_RECOG *
+recogReadStream(FILE  *fp)
+{
+char      fname[256];
+l_int32   version, setsize, templ_type, threshold, scalew, scaleh;
+l_int32   maxyshift, nc;
+L_DNA    *dna_tochar;
+PIXAA    *paa;
+L_RECOG  *recog;
+SARRAY   *sa_text;
+
+    PROCNAME("recogReadStream");
+
+    if (!fp)
+        return (L_RECOG *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nRecog Version %d\n", &version) != 1)
+        return (L_RECOG *)ERROR_PTR("not a recog file", procName, NULL);
+    if (version != RECOG_VERSION_NUMBER)
+        return (L_RECOG *)ERROR_PTR("invalid recog version", procName, NULL);
+    if (fscanf(fp, "Size of character set = %d\n", &setsize) != 1)
+        return (L_RECOG *)ERROR_PTR("setsize not read", procName, NULL);
+    if (fscanf(fp, "Template type = %d\n", &templ_type) != 1)
+        return (L_RECOG *)ERROR_PTR("template type not read", procName, NULL);
+    if (fscanf(fp, "Binarization threshold = %d\n", &threshold) != 1)
+        return (L_RECOG *)ERROR_PTR("binary thresh not read", procName, NULL);
+    if (fscanf(fp, "Maxyshift = %d\n", &maxyshift) != 1)
+        return (L_RECOG *)ERROR_PTR("maxyshift not read", procName, NULL);
+    if (fscanf(fp, "Scale to width = %d\n", &scalew) != 1)
+        return (L_RECOG *)ERROR_PTR("width not read", procName, NULL);
+    if (fscanf(fp, "Scale to height = %d\n", &scaleh) != 1)
+        return (L_RECOG *)ERROR_PTR("height not read", procName, NULL);
+    if ((recog = recogCreate(scalew, scaleh, templ_type, threshold,
+                             maxyshift)) == NULL)
+        return (L_RECOG *)ERROR_PTR("recog not made", procName, NULL);
+
+    if (fscanf(fp, "Serialized filename: %s\n", fname) != 1)
+        return (L_RECOG *)ERROR_PTR("filename not read", procName, NULL);
+
+    if (fscanf(fp, "\nLabels for character set:\n") != 0)
+        return (L_RECOG *)ERROR_PTR("label intro not read", procName, NULL);
+    l_dnaDestroy(&recog->dna_tochar);
+    sarrayDestroy(&recog->sa_text);
+    if ((dna_tochar = l_dnaReadStream(fp)) == NULL)
+        return (L_RECOG *)ERROR_PTR("dna_tochar not read", procName, NULL);
+    if ((sa_text = sarrayReadStream(fp)) == NULL)
+        return (L_RECOG *)ERROR_PTR("sa_text not read", procName, NULL);
+    recog->sa_text = sa_text;
+    recog->dna_tochar = dna_tochar;
+
+    if (fscanf(fp, "\nPixaa of all samples in the training set:\n") != 0)
+        return (L_RECOG *)ERROR_PTR("pixaa intro not read", procName, NULL);
+    if ((paa = pixaaReadStream(fp)) == NULL)
+        return (L_RECOG *)ERROR_PTR("pixaa not read", procName, NULL);
+    recog->fname = stringNew(fname);
+    recog->setsize = setsize;
+    nc = pixaaGetCount(paa, NULL);
+    if (nc != setsize) {
+        L_ERROR("(setsize = %d) != (paa count = %d)\n", procName,
+                     setsize, nc);
+        return NULL;
+    }
+
+    recogAddAllSamples(recog, paa, 0);  /* this finishes */
+    pixaaDestroy(&paa);
+    return recog;
+}
+
+
+/*!
+ *  recogWrite()
+ *
+ *      Input:  filename
+ *              recog
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogWrite(const char  *filename,
+           L_RECOG     *recog)
+{
+FILE  *fp;
+
+    PROCNAME("recogWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (recogWriteStream(fp, recog, filename))
+        return ERROR_INT("recog not written to stream", procName, 1);
+    fclose(fp);
+    return 0;
+}
+
+
+/*!
+ *  recogWriteStream()
+ *
+ *      Input:  stream (opened for "wb")
+ *              recog
+ *              filename (output serialized filename; embedded in file)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogWriteStream(FILE        *fp,
+                 L_RECOG     *recog,
+                 const char  *filename)
+{
+    PROCNAME("recogWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    fprintf(fp, "\nRecog Version %d\n", RECOG_VERSION_NUMBER);
+    fprintf(fp, "Size of character set = %d\n", recog->setsize);
+    fprintf(fp, "Template type = %d\n", recog->templ_type);
+    fprintf(fp, "Binarization threshold = %d\n", recog->threshold);
+    fprintf(fp, "Maxyshift = %d\n", recog->maxyshift);
+    fprintf(fp, "Scale to width = %d\n", recog->scalew);
+    fprintf(fp, "Scale to height = %d\n", recog->scaleh);
+    fprintf(fp, "Serialized filename: %s\n", filename);
+    fprintf(fp, "\nLabels for character set:\n");
+    l_dnaWriteStream(fp, recog->dna_tochar);
+    sarrayWriteStream(fp, recog->sa_text);
+    fprintf(fp, "\nPixaa of all samples in the training set:\n");
+    pixaaWriteStream(fp, recog->pixaa);
+
+    return 0;
+}
+
+
+/*!
+ *  recogWritePixa()
+ *
+ *      Input:  filename
+ *              recog
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This generates a pixa of all the unscaled images in the
+ *          recognizer, where each one has its character string in
+ *          the pix text field, by flattening pixaa_u to a pixa.
+ *      (2) As a side-effect, the character class label is written
+ *          into each pix in recog.
+ */
+l_int32
+recogWritePixa(const char  *filename,
+               L_RECOG     *recog)
+{
+PIXA  *pixa;
+
+    PROCNAME("recogWritePixa");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+
+    recogAddCharstrLabels(recog);
+    pixa = pixaaFlattenToPixa(recog->pixaa_u, NULL, L_CLONE);
+    pixaWrite(filename, pixa);
+    pixaDestroy(&pixa);
+    return 0;
+}
+
+
+/*!
+ *  recogAddCharstrLabels()
+ *
+ *      Input:  filename
+ *              recog
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+recogAddCharstrLabels(L_RECOG  *recog)
+{
+char    *text;
+l_int32  i, j, n1, n2;
+PIX     *pix;
+PIXA    *pixa;
+PIXAA   *paa;
+
+    PROCNAME("recogAddCharstrLabels");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+
+        /* Add the labels to each unscaled pix */
+    paa = recog->pixaa_u;
+    n1 = pixaaGetCount(paa, NULL);
+    for (i = 0; i < n1; i++) {
+        pixa = pixaaGetPixa(paa, i, L_CLONE);
+        text = sarrayGetString(recog->sa_text, i, L_NOCOPY);
+        n2 = pixaGetCount(pixa);
+        for (j = 0; j < n2; j++) {
+             pix = pixaGetPix(pixa, j, L_CLONE);
+             pixSetText(pix, text);
+             pixDestroy(&pix);
+        }
+        pixaDestroy(&pixa);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  recogAddAllSamples()
+ *
+ *      Input:  recog
+ *              paa (pixaa from previously trained recog)
+ *              debug
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is used with the serialization routine recogRead(),
+ *          where each pixa in the pixaa represents a set of characters
+ *          in a different class.  Two different pixa may represent
+ *          characters with the same label.  Before calling this
+ *          function, we verify that the number of character classes,
+ *          given by the setsize field in recog, equals the number of
+ *          pixa in the paa.  The character labels for each set are
+ *          in the sa_text field.
+ */
+static l_int32
+recogAddAllSamples(L_RECOG  *recog,
+                   PIXAA    *paa,
+                   l_int32   debug)
+{
+char    *text;
+l_int32  i, j, nc, ns;
+PIX     *pix;
+PIXA    *pixa;
+
+    PROCNAME("recogAddAllSamples");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!paa)
+        return ERROR_INT("paa not defined", procName, 1);
+
+    nc = pixaaGetCount(paa, NULL);
+    for (i = 0; i < nc; i++) {
+        pixa = pixaaGetPixa(paa, i, L_CLONE);
+        ns = pixaGetCount(pixa);
+        text = sarrayGetString(recog->sa_text, i, L_NOCOPY);
+        for (j = 0; j < ns; j++) {
+            pix = pixaGetPix(pixa, j, L_CLONE);
+            if (debug) {
+                fprintf(stderr, "pix[%d,%d]: text = %s\n", i, j, text);
+            }
+            pixaaAddPix(recog->pixaa_u, i, pix, NULL, L_INSERT);
+        }
+        pixaDestroy(&pixa);
+    }
+
+    recogTrainingFinished(recog, debug);
+    return 0;
+}
+
diff --git a/src/recogdid.c b/src/recogdid.c
new file mode 100644 (file)
index 0000000..603df44
--- /dev/null
@@ -0,0 +1,998 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  recogdid.c
+ *
+ *      Top-level identification
+ *         l_int32           recogDecode()
+ *
+ *      Generate decoding arrays
+ *         l_int32           recogMakeDecodingArrays()
+ *         static l_int32    recogMakeDecodingArray()
+ *
+ *      Dynamic programming for best path
+ *         l_int32           recogRunViterbi()
+ *         l_int32           recogRescoreDidResult()
+ *         static PIX       *recogShowPath()
+ *
+ *      Create/destroy temporary DID data
+ *         l_int32           recogCreateDid()
+ *         l_int32           recogDestroyDid()
+ *
+ *      Various helpers
+ *         l_int32           recogDidExists()
+ *         L_RDID           *recogGetDid()
+ *         static l_int32    recogGetWindowedArea()
+ *         l_int32           recogSetChannelParams()
+ *         static l_int32    recogTransferRchToDid()
+ *
+ *  See recogbasic.c for examples of training a recognizer, which is
+ *  required before it can be used for document image decoding.
+ *
+ *  Gary Kopec pioneered this hidden markov approach to "Document Image
+ *  Decoding" (DID) in the early 1990s.  It is based on estimation
+ *  using a generative model of the image generation process, and
+ *  provides the most likely decoding of an image if the model is correct.
+ *  Given the model, it finds the maximum a posteriori (MAP) "message"
+ *  given the observed image.  The model describes how to generate
+ *  an image from a message, and the MAP message is derived from the
+ *  observed image using Bayes' theorem.  This approach can also be used
+ *  to build the model, using the iterative expectation/maximization
+ *  method from labelled but errorful data.
+ *
+ *  In a little more detail: The model comprises three things: the ideal
+ *  printed character templates, the independent bit-flip noise model, and
+ *  the character setwidths.  When a character is printed, the setwidth
+ *  is the distance in pixels that you move forward before being able
+ *  to print the next character.  It is typically slightly less than the
+ *  width of the character template: if too small, an extra character can be
+ *  hallucinated; if too large, it will not be able to match the next
+ *  character template on the line.  The model assumes that the probabilities
+ *  of bit flip depend only on the assignment of the pixel to background
+ *  or template foreground.  The multilevel templates have different
+ *  bit flip probabilities for each level.  Because a character image
+ *  is composed of many pixels, each of which can be independently flipped,
+ *  the actual probability of seeing any rendering is exceedingly small,
+ *  being composed of the product of the probabilities for each pixel.
+ *  The log likelihood is used both to avoid numeric underflow and,
+ *  more importantly, because it results in a summation of independent
+ *  pixel probabilities.  That summation can be shown, in Kopec's
+ *  original paper, to consist of a sum of two terms: (a) the number of
+ *  fg pixels in the bit-and of the observed image with the ideal
+ *  template and (b) the number of fg pixels in the template.  Each
+ *  has a coefficient that depends only on the bit-flip probabilities
+ *  for the fg and bg.  A beautiful result, and computationally simple!
+ *  One nice feature of this approach is that the result of the decoding
+ *  is not very sensitive to the values  used for the bit flip probabilities.
+ *
+ *  The procedure for finding the best decoding (MAP) for a given image goes
+ *  under several names: Viterbi, dynamic programming, hidden markov model.
+ *  It is called a "hidden markov model" because the templates are assumed
+ *  to be printed serially and we don't know what they are -- the identity
+ *  of the templates must be inferred from the observed image.
+ *  The possible decodings form a dense trellis over the pixel positions,
+ *  where at each pixel position you have the possibility of having any
+ *  of the characters printed there (with some reference point) or having
+ *  a single pixel wide space inserted there.  Thus, before the trellis
+ *  can be traversed, we must do the work of finding the log probability,
+ *  at each pixel location, that each of the templates was printed there.
+ *  Armed with those arrays of data, the dynamic programming procedure
+ *  moves from left to right, one pixel at a time, recursively finding
+ *  the path with the highest log probability that gets to that pixel
+ *  position (and noting which template was printed to arrive there).
+ *  After reaching the right side of the image, we can simply backtrack
+ *  along the path, jumping over each template that lies on the highest
+ *  scoring path.  This best path thus only goes through a few of the
+ *  pixel positions.
+ *
+ *  There are two refinements to the original Kopec paper.  In the first,
+ *  one uses multiple, non-overlapping fg templates, each with its own
+ *  bit flip probability.  This makes sense, because the probability
+ *  that a fg boundary pixel flips to bg is greater than that of a fg
+ *  pixel not on the boundary.  And the flip probability of a fg boundary
+ *  pixel is smaller than that of a bg boundary pixel, which in turn
+ *  is greater than that of a bg pixel not on a boundary (the latter
+ *  is taken to be the true background).  Then the simplest realistic
+ *  multiple template model has three templates that are not background.
+ *
+ *  In the second refinement, a heuristic (strict upper bound) is used
+ *  iteratively in the Viterbi process to compute the log probabilities.
+ *  Using the heuristic, you find the best path, and then score all nodes
+ *  on that path with the actual probability, which is guaranteed to
+ *  be a smaller number.  You run this iteratively, rescoring just the best
+ *  found path each time.  After each rescoring, the path may change because
+ *  the local scores have been reduced.  However, the process converges
+ *  rapidly, and when it doesn't change, it must be the best path because
+ *  it is properly scored (even if neighboring paths are heuristically
+ *  scored).  The heuristic score is found column-wise by assuming
+ *  that all the fg pixels in the template are on fg pixels in the image --
+ *  we just take the minimum of the number of pixels in the template
+ *  and image column.  This can easily give a 10-fold reduction in
+ *  computation because the heuristic score can be computed much faster
+ *  than the exact score.
+ *
+ *  For reference, the classic paper on the approach by Kopec is:
+ *  * "Document Image Decoding Using Markov Source Models", IEEE Trans.
+ *    PAMI, Vol 16, No. 6, June 1994, pp 602-617.
+ *  A refinement of the method for multilevel templates by Kopec is:
+ *  * "Multilevel Character Templates for Document Image Decoding",
+ *    Proc. SPIE 3027, Document Recognition IV, p. 168ff, 1997.
+ *  Further refinements for more efficient decoding are given in these
+ *  two papers, which are both stored on leptonica.org:
+ *  * "Document Image Decoding using Iterated Complete Path Search", Minka,
+ *    Bloomberg and Popat, Proc. SPIE Vol 4307, p. 250-258, Document
+ *    Recognition and Retrieval VIII, San Jose, CA 2001.
+ *  * "Document Image Decoding using Iterated Complete Path Search with
+ *    Subsampled Heuristic Scoring", Bloomberg, Minka and Popat, ICDAR 2001,
+ *    p. 344-349, Sept. 2001, Seattle.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+static l_int32 recogMakeDecodingArray(L_RECOG *recog, l_int32 index,
+                                      l_int32 debug);
+static l_int32 recogRescoreDidResult(L_RECOG *recog, PIX **ppixdb);
+static PIX *recogShowPath(L_RECOG *recog, l_int32 select);
+static l_int32 recogGetWindowedArea(L_RECOG *recog, l_int32 index,
+                                    l_int32 x, l_int32 *pdely, l_int32 *pwsum);
+static l_int32 recogTransferRchToDid(L_RECOG *recog, l_int32 x, l_int32 y);
+
+    /* Parameters for modeling the decoding */
+static const l_float32  SetwidthFraction = 0.95;
+static const l_int32    MaxYShift = 1;
+
+    /* Channel parameters.  alpha[0] is the probability that a bg pixel
+     * is OFF.  alpha[1] is the probability that level 1 fg is ON.
+     * The actual values are not too critical, but they must be larger
+     * than 0.5 and smaller than 1.0.  For more accuracy in template
+     * matching, use a 4-level template, where levels 2 and 3 are
+     * boundary pixels in the fg and bg, respectively. */
+static const l_float32  DefaultAlpha2[] = {0.95, 0.9};
+static const l_float32  DefaultAlpha4[] = {0.95, 0.9, 0.75, 0.25};
+
+
+/*------------------------------------------------------------------------*
+ *                       Top-level identification                         *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogDecode()
+ *
+ *      Input:  recog (with LUT's pre-computed)
+ *              pixs (typically of multiple touching characters, 1 bpp)
+ *              nlevels (of templates; 2 for now)
+ *              &pixdb (<optional return> debug result; can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogDecode(L_RECOG  *recog,
+            PIX      *pixs,
+            l_int32   nlevels,
+            PIX     **ppixdb)
+{
+l_int32  debug;
+PIX     *pixt;
+PIXA    *pixa;
+
+    PROCNAME("recogDecode");
+
+    if (ppixdb) *ppixdb = NULL;
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (!recog->train_done)
+        return ERROR_INT("training not finished", procName, 1);
+    if (nlevels != 2)
+        return ERROR_INT("nlevels != 2 (for now)", procName, 1);
+
+    pixa = (ppixdb) ? pixaCreate(2) : NULL;
+    debug = (ppixdb) ? 1 : 0;
+    if (recogMakeDecodingArrays(recog, pixs, debug))
+        return ERROR_INT("error making arrays", procName, 1);
+
+    recogSetChannelParams(recog, nlevels);
+
+    if (recogRunViterbi(recog, &pixt))
+        return ERROR_INT("error in viterbi", procName, 1);
+    if (ppixdb) pixaAddPix(pixa, pixt, L_INSERT);
+
+    if (recogRescoreDidResult(recog, &pixt))
+        return ERROR_INT("error in rescoring", procName, 1);
+    if (ppixdb) pixaAddPix(pixa, pixt, L_INSERT);
+
+    *ppixdb = pixaDisplayTiledInRows(pixa, 32, 2 * pixGetWidth(pixt) + 100,
+                                     1.0, 0, 30, 2);
+    pixaDestroy(&pixa);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                       Generate decoding arrays                         *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogMakeDecodingArrays()
+ *
+ *      Input:  recog (with LUT's pre-computed)
+ *              pixs (typically of multiple touching characters, 1 bpp)
+ *              debug (1 for debug output; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Generates the bit-and sum arrays for each character template
+ *          along pixs.  These are used in the dynamic programming step.
+ *      (2) Previous arrays are destroyed and the new arrays are allocated.
+ *      (3) The values are saved in the scoring arrays at the left edge
+ *          of the template.  They are used in the viterbi process
+ *          at the setwidth position (which is near the RHS of the template
+ *          as it is positioned on pixs) in the generated trellis.
+ */
+l_int32
+recogMakeDecodingArrays(L_RECOG  *recog,
+                        PIX      *pixs,
+                        l_int32   debug)
+{
+l_int32  i;
+PIX     *pix1;
+L_RDID  *did;
+
+    PROCNAME("recogMakeDecodingArrays");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (!recog->train_done)
+        return ERROR_INT("training not finished", procName, 1);
+
+        /* Binarize and crop to foreground if necessary */
+    if ((pix1 = recogProcessToIdentify(recog, pixs, 0)) == NULL)
+        return ERROR_INT("pix1 not made", procName, 1);
+
+        /* Remove any existing RecogDID and set up a new one */
+    recogDestroyDid(recog);
+    if (recogCreateDid(recog, pix1)) {
+        pixDestroy(&pix1);
+        return ERROR_INT("decoder not made", procName, 1);
+    }
+
+        /* Compute vertical sum and first moment arrays */
+    did = recogGetDid(recog);  /* owned by recog */
+    did->nasum = pixCountPixelsByColumn(pix1);
+    did->namoment = pixGetMomentByColumn(pix1, 1);
+
+        /* Generate the arrays */
+    for (i = 0; i < recog->did->narray; i++)
+        recogMakeDecodingArray(recog, i, debug);
+
+    pixDestroy(&pix1);
+    return 0;
+}
+
+
+/*!
+ *  recogMakeDecodingArray()
+ *
+ *      Input:  recog
+ *              index (of averaged template)
+ *              debug (1 for debug output; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Generates the bit-and sum array for a character template along pixs.
+ *      (2) The values are saved in the scoring arrays at the left edge
+ *          of the template as it is positioned on pixs.
+ */
+static l_int32
+recogMakeDecodingArray(L_RECOG  *recog,
+                       l_int32   index,
+                       l_int32   debug)
+{
+l_int32   i, j, w1, h1, w2, h2, nx, ycent2, count, maxcount, maxdely;
+l_int32   sum, moment, dely, shifty;
+l_int32  *counta, *delya, *ycent1, *arraysum, *arraymoment, *sumtab;
+NUMA     *nasum, *namoment;
+PIX      *pix1, *pix2, *pix3;
+L_RDID   *did;
+
+    PROCNAME("recogMakeDecodingArray");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if ((did = recogGetDid(recog)) == NULL)
+        return ERROR_INT("did not defined", procName, 1);
+    if (index < 0 || index >= did->narray)
+        return ERROR_INT("invalid index", procName, 1);
+
+        /* Check that pix1 is large enough for this template. */
+    pix1 = did->pixs;  /* owned by did; do not destroy */
+    pixGetDimensions(pix1, &w1, &h1, NULL);
+    pix2 = pixaGetPix(recog->pixa_u, index, L_CLONE);
+    pixGetDimensions(pix2, &w2, &h2, NULL);
+    if (w1 < w2) {
+        L_INFO("w1 = %d < w2 = %d for index %d\n", procName, w1, w2, index);
+        pixDestroy(&pix2);
+        return 0;
+    }
+
+    nasum = did->nasum;
+    namoment = did->namoment;
+    ptaGetIPt(recog->pta_u, index, NULL, &ycent2);
+    sumtab = recog->sumtab;
+    counta = did->counta[index];
+    delya = did->delya[index];
+
+        /* Set up the array for ycent1.  This gives the y-centroid location
+         * for a window of width w2, starting at location i. */
+    nx = w1 - w2 + 1;  /* number of positions w2 can be placed in w1 */
+    ycent1 = (l_int32 *)LEPT_CALLOC(nx, sizeof(l_int32));
+    arraysum = numaGetIArray(nasum);
+    arraymoment = numaGetIArray(namoment);
+    for (i = 0, sum = 0, moment = 0; i < w2; i++) {
+        sum += arraysum[i];
+        moment += arraymoment[i];
+    }
+    for (i = 0; i < nx - 1; i++) {
+        ycent1[i] = (sum == 0) ? ycent2 : (l_float32)moment / (l_float32)sum;
+        sum += arraysum[w2 + i] - arraysum[i];
+        moment += arraymoment[w2 + i] - arraymoment[i];
+    }
+    ycent1[nx - 1] = (sum == 0) ? ycent2 : (l_float32)moment / (l_float32)sum;
+
+        /* Compute the bit-and sum between the template pix2 and pix1, at
+         * locations where the left side of pix2 goes from 0 to nx - 1
+         * in pix1.  Do this around the vertical alignment of the pix2
+         * centroid and the windowed pix1 centroid.
+         *  (1) Start with pix3 cleared and approximately equal in size to pix1.
+         *  (2) Blit the y-shifted pix2 onto pix3.  Then all ON pixels
+         *      are within the intersection of pix1 and the shifted pix2.
+         *  (3) AND pix1 with pix3. */
+    pix3 = pixCreate(w2, h1, 1);
+    for (i = 0; i < nx; i++) {
+        shifty = (l_int32)(ycent1[i] - ycent2 + 0.5);
+        maxcount = 0;
+        for (j = -MaxYShift; j <= MaxYShift; j++) {
+            pixClearAll(pix3);
+            dely = shifty + j;  /* amount pix2 is shifted relative to pix1 */
+            pixRasterop(pix3, 0, dely, w2, h2, PIX_SRC, pix2, 0, 0);
+            pixRasterop(pix3, 0, 0, w2, h1, PIX_SRC & PIX_DST, pix1, i, 0);
+            pixCountPixels(pix3, &count, sumtab);
+            if (count > maxcount) {
+                maxcount = count;
+                maxdely = dely;
+            }
+        }
+        counta[i] = maxcount;
+        delya[i] = maxdely;
+    }
+    did->fullarrays = TRUE;
+
+    pixDestroy(&pix2);
+    pixDestroy(&pix3);
+    LEPT_FREE(ycent1);
+    LEPT_FREE(arraysum);
+    LEPT_FREE(arraymoment);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                  Dynamic programming for best path
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogRunViterbi()
+ *
+ *      Input:  recog (with LUT's pre-computed)
+ *              &pixdb (<optional return> debug result; can be null)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is recursive, in that
+ *          (a) we compute the score successively at all pixel positions x,
+ *          (b) to compute the score at x in the trellis, for each
+ *              template we look backwards to (x - setwidth) to get the
+ *              score if that template were to be printed with its
+ *              setwidth location at x.  We save at x the template and
+ *              score that maximizes the sum of the score at (x - setwidth)
+ *              and the log-likelihood for the template to be printed with
+ *              its LHS there.
+ */
+l_int32
+recogRunViterbi(L_RECOG  *recog,
+                PIX     **ppixdb)
+{
+l_int32     i, w1, x, narray, minsetw, first, templ, xloc, dely, counts, area1;
+l_int32     besttempl, spacetempl;
+l_int32    *setw, *didtempl;
+l_int32    *area2;  /* must be freed */
+l_float32   prevscore, matchscore, maxscore, correl;
+l_float32  *didscore;
+PIX        *pixt;
+L_RDID     *did;
+
+    PROCNAME("recogRunViterbi");
+
+    if (ppixdb) *ppixdb = NULL;
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if ((did = recogGetDid(recog)) == NULL)
+        return ERROR_INT("did not defined", procName, 1);
+    if (did->fullarrays == 0)
+        return ERROR_INT("did full arrays not made", procName, 1);
+
+        /* The score array is initialized to 0.0.  As we proceed to
+         * the left, the log likelihood for the partial paths goes
+         * negative, and we prune for the max (least negative) path.
+         * No matches will be computed until we reach x = min(setwidth);
+         * until then first == TRUE after looping over templates. */
+    w1 = did->size;
+    narray = did->narray;
+    spacetempl = narray;
+    setw = did->setwidth;
+    minsetw = 100000;
+    for (i = 0; i < narray; i++) {
+        if (setw[i] < minsetw)
+            minsetw = setw[i];
+    }
+    if (minsetw <= 0) {
+        L_ERROR("minsetw <= 0; shouldn't happen\n", procName);
+        minsetw = 1;
+    }
+    didscore = did->trellisscore;
+    didtempl = did->trellistempl;
+    area2 = numaGetIArray(recog->nasum_u);
+    for (x = minsetw; x < w1; x++) {  /* will always get a score */
+        first = TRUE;
+        for (i = 0; i < narray; i++) {
+            if (x - setw[i] < 0) continue;
+            matchscore = didscore[x - setw[i]] +
+                         did->gamma[1] * did->counta[i][x - setw[i]] +
+                         did->beta[1] * area2[i];
+            if (first) {
+                maxscore = matchscore;
+                besttempl = i;
+                first = FALSE;
+            } else {
+                if (matchscore > maxscore) {
+                    maxscore = matchscore;
+                    besttempl = i;
+                }
+            }
+        }
+
+            /* We can also put down a single pixel space, with no cost
+             * because all pixels are bg. */
+        prevscore = didscore[x - 1];
+        if (prevscore > maxscore) {  /* 1 pixel space is best */
+            maxscore = prevscore;
+            besttempl = spacetempl;
+        }
+        didscore[x] = maxscore;
+        didtempl[x] = besttempl;
+    }
+
+        /* Backtrack to get the best path.
+         * Skip over (i.e., ignore) all single pixel spaces. */
+    for (x = w1 - 1; x >= 0; x--) {
+        if (didtempl[x] != spacetempl) break;
+    }
+    while (x > 0) {
+        if (didtempl[x] == spacetempl) {  /* skip over spaces */
+            x--;
+            continue;
+        }
+        templ = didtempl[x];
+        xloc = x - setw[templ];
+        if (xloc < 0) break;
+        counts = did->counta[templ][xloc];  /* bit-and counts */
+        recogGetWindowedArea(recog, templ, xloc, &dely, &area1);
+        correl = (counts * counts) / (l_float32)(area2[templ] * area1);
+        pixt = pixaGetPix(recog->pixa_u, templ, L_CLONE);
+        numaAddNumber(did->natempl, templ);
+        numaAddNumber(did->naxloc, xloc);
+        numaAddNumber(did->nadely, dely);
+        numaAddNumber(did->nawidth, pixGetWidth(pixt));
+        numaAddNumber(did->nascore, correl);
+        pixDestroy(&pixt);
+        x = xloc;
+    }
+
+    if (ppixdb) {
+        numaWriteStream(stderr, did->natempl);
+        numaWriteStream(stderr, did->naxloc);
+        numaWriteStream(stderr, did->nadely);
+        numaWriteStream(stderr, did->nawidth);
+        numaWriteStream(stderr, did->nascore);
+        *ppixdb = recogShowPath(recog, 0);
+    }
+
+    LEPT_FREE(area2);
+    return 0;
+}
+
+
+/*!
+ *  recogRescoreDidResult()
+ *
+ *      Input:  recog (with LUT's pre-computed)
+ *              &pixdb (<optional return> debug result; can be null)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does correlation matching with all templates using the
+ *          viterbi path segmentation.
+ */
+static l_int32
+recogRescoreDidResult(L_RECOG  *recog,
+                      PIX     **ppixdb)
+{
+l_int32    i, n, w2, h1, templ, x, xloc, dely, index;
+char      *text;
+l_float32  score;
+BOX       *box1;
+PIX       *pixs, *pix1;
+L_RDID    *did;
+
+    PROCNAME("recogRescoreDidResult");
+
+    if (ppixdb) *ppixdb = NULL;
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if ((did = recogGetDid(recog)) == NULL)
+        return ERROR_INT("did not defined", procName, 1);
+    if (did->fullarrays == 0)
+        return ERROR_INT("did full arrays not made", procName, 1);
+    if ((n = numaGetCount(did->naxloc)) == 0)
+        return ERROR_INT("no elements in path", procName, 1);
+
+    pixs = did->pixs;
+    h1 = pixGetHeight(pixs);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(did->natempl, i, &templ);
+        numaGetIValue(did->naxloc, i, &xloc);
+        numaGetIValue(did->nadely, i, &dely);
+        pixaGetPixDimensions(recog->pixa_u, templ, &w2, NULL, NULL);
+        /* TODO: try to fix xloc - 4, etc. */
+        x = L_MAX(xloc, 0);
+        box1 = boxCreate(x, dely, w2, h1);
+        pix1 = pixClipRectangle(pixs, box1, NULL);
+        recogIdentifyPix(recog, pix1, NULL);
+        recogTransferRchToDid(recog, x, dely);
+        if (ppixdb) {
+            rchExtract(recog->rch, &index, &score, &text,
+                       NULL, NULL, NULL, NULL);
+            fprintf(stderr, "text = %s, index = %d, score = %5.3f\n",
+                    text, index, score);
+        }
+        pixDestroy(&pix1);
+        boxDestroy(&box1);
+        LEPT_FREE(text);
+    }
+
+/*    numaWriteStream(stderr, recog->did->nadely_r);  */
+
+    if (ppixdb)
+        *ppixdb = recogShowPath(recog, 1);
+
+    return 0;
+}
+
+
+/*!
+ *  recogShowPath()
+ *
+ *      Input:  recog (with LUT's pre-computed)
+ *              select (0 for Viterbi; 1 for rescored)
+ *      Return: pix (debug output), or null on error)
+ */
+static PIX *
+recogShowPath(L_RECOG  *recog,
+              l_int32   select)
+{
+char       textstr[16];
+l_int32    i, n, index, xloc, dely;
+l_float32  score;
+L_BMF     *bmf;
+NUMA      *natempl_s, *nascore_s, *naxloc_s, *nadely_s;
+PIX       *pixs, *pix0, *pix1, *pix2, *pix3, *pix4, *pix5;
+L_RDID    *did;
+
+    PROCNAME("recogShowPath");
+
+    if (!recog)
+        return (PIX *)ERROR_PTR("recog not defined", procName, NULL);
+    if ((did = recogGetDid(recog)) == NULL)
+        return (PIX *)ERROR_PTR("did not defined", procName, NULL);
+
+    bmf = bmfCreate(NULL, 8);
+    pixs = pixScale(did->pixs, 4.0, 4.0);
+    pix0 = pixAddBorderGeneral(pixs, 0, 0, 0, 40, 0);
+    pix1 = pixConvertTo32(pix0);
+    if (select == 0) {  /* Viterbi */
+        natempl_s = did->natempl;
+        nascore_s = did->nascore;
+        naxloc_s = did->naxloc;
+        nadely_s = did->nadely;
+    } else {  /* rescored */
+        natempl_s = did->natempl_r;
+        nascore_s = did->nascore_r;
+        naxloc_s = did->naxloc_r;
+        nadely_s = did->nadely_r;
+    }
+
+    n = numaGetCount(natempl_s);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(natempl_s, i, &index);
+        pix2 = pixaGetPix(recog->pixa_u, index, L_CLONE);
+        pix3 = pixScale(pix2, 4.0, 4.0);
+        pix4 = pixErodeBrick(NULL, pix3, 5, 5);
+        pixXor(pix4, pix4, pix3);
+        numaGetFValue(nascore_s, i, &score);
+        snprintf(textstr, sizeof(textstr), "%5.3f", score);
+        pix5 = pixAddTextlines(pix4, bmf, textstr, 1, L_ADD_BELOW);
+        numaGetIValue(naxloc_s, i, &xloc);
+        numaGetIValue(nadely_s, i, &dely);
+        pixPaintThroughMask(pix1, pix5, 4 * xloc, 4 * dely, 0xff000000);
+        pixDestroy(&pix2);
+        pixDestroy(&pix3);
+        pixDestroy(&pix4);
+        pixDestroy(&pix5);
+    }
+    pixDestroy(&pixs);
+    pixDestroy(&pix0);
+    bmfDestroy(&bmf);
+    return pix1;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                  Create/destroy temporary DID data                     *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogCreateDid()
+ *
+ *      Input:  recog
+ *              pixs (of 1 bpp image to match)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogCreateDid(L_RECOG  *recog,
+               PIX      *pixs)
+{
+l_int32      i;
+PIX         *pixt;
+L_RDID  *did;
+
+    PROCNAME("recogCreateDid");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    recogDestroyDid(recog);
+
+    did = (L_RDID *)LEPT_CALLOC(1, sizeof(L_RDID));
+    recog->did = did;
+    did->pixs = pixClone(pixs);
+    did->narray = recog->setsize;
+    did->size = pixGetWidth(pixs);
+    did->natempl = numaCreate(5);
+    did->naxloc = numaCreate(5);
+    did->nadely = numaCreate(5);
+    did->nawidth = numaCreate(5);
+    did->nascore = numaCreate(5);
+    did->natempl_r = numaCreate(5);
+    did->naxloc_r = numaCreate(5);
+    did->nadely_r = numaCreate(5);
+    did->nawidth_r = numaCreate(5);
+    did->nascore_r = numaCreate(5);
+
+        /* Make the arrays */
+    did->setwidth = (l_int32 *)LEPT_CALLOC(did->narray, sizeof(l_int32));
+    did->counta = (l_int32 **)LEPT_CALLOC(did->narray, sizeof(l_int32 *));
+    did->delya = (l_int32 **)LEPT_CALLOC(did->narray, sizeof(l_int32 *));
+    did->beta = (l_float32 *)LEPT_CALLOC(5, sizeof(l_float32));
+    did->gamma = (l_float32 *)LEPT_CALLOC(5, sizeof(l_float32));
+    did->trellisscore = (l_float32 *)LEPT_CALLOC(did->size, sizeof(l_float32));
+    did->trellistempl = (l_int32 *)LEPT_CALLOC(did->size, sizeof(l_int32));
+    for (i = 0; i < did->narray; i++) {
+        did->counta[i] = (l_int32 *)LEPT_CALLOC(did->size, sizeof(l_int32));
+        did->delya[i] = (l_int32 *)LEPT_CALLOC(did->size, sizeof(l_int32));
+    }
+
+        /* Populate the setwidth array */
+    for (i = 0; i < did->narray; i++) {
+        pixt = pixaGetPix(recog->pixa_u, i, L_CLONE);
+        did->setwidth[i] = (l_int32)(SetwidthFraction * pixGetWidth(pixt));
+        pixDestroy(&pixt);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  recogDestroyDid()
+ *
+ *      Input:  recog
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) As the signature indicates, this is owned by the recog, and can
+ *          only be destroyed using this function.
+ */
+l_int32
+recogDestroyDid(L_RECOG  *recog)
+{
+l_int32  i;
+L_RDID  *did;
+
+    PROCNAME("recogDestroyDid");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+
+    if ((did = recog->did) == NULL) return 0;
+    if (!did->counta || !did->delya)
+        return ERROR_INT("ptr array is null; shouldn't happen!", procName, 1);
+
+    for (i = 0; i < did->narray; i++) {
+        LEPT_FREE(did->counta[i]);
+        LEPT_FREE(did->delya[i]);
+    }
+    LEPT_FREE(did->setwidth);
+    LEPT_FREE(did->counta);
+    LEPT_FREE(did->delya);
+    LEPT_FREE(did->beta);
+    LEPT_FREE(did->gamma);
+    LEPT_FREE(did->trellisscore);
+    LEPT_FREE(did->trellistempl);
+    pixDestroy(&did->pixs);
+    numaDestroy(&did->nasum);
+    numaDestroy(&did->namoment);
+    numaDestroy(&did->natempl);
+    numaDestroy(&did->naxloc);
+    numaDestroy(&did->nadely);
+    numaDestroy(&did->nawidth);
+    numaDestroy(&did->nascore);
+    numaDestroy(&did->natempl_r);
+    numaDestroy(&did->naxloc_r);
+    numaDestroy(&did->nadely_r);
+    numaDestroy(&did->nawidth_r);
+    numaDestroy(&did->nascore_r);
+    LEPT_FREE(did);
+    recog->did = NULL;
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                            Various helpers                             *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogDidExists()
+ *
+ *      Input:  recog
+ *      Return: 1 if recog->did exists; 0 if not or on error.
+ */
+l_int32
+recogDidExists(L_RECOG  *recog)
+{
+    PROCNAME("recogDidExists");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 0);
+    return (recog->did) ? 1 : 0;
+}
+
+
+/*!
+ *  recogGetDid()
+ *
+ *      Input:  recog
+ *      Return: did (still owned by the recog), or null on error
+ *
+ *  Notes:
+ *      (1) This also makes sure the arrays are defined.
+ */
+L_RDID *
+recogGetDid(L_RECOG  *recog)
+{
+l_int32  i;
+L_RDID  *did;
+
+    PROCNAME("recogGetDid");
+
+    if (!recog)
+        return (L_RDID *)ERROR_PTR("recog not defined", procName, NULL);
+    if ((did = recog->did) == NULL)
+        return (L_RDID *)ERROR_PTR("did not defined", procName, NULL);
+    if (!did->counta || !did->delya)
+        return (L_RDID *)ERROR_PTR("did array ptrs not defined",
+                                   procName, NULL);
+    for (i = 0; i < did->narray; i++) {
+        if (!did->counta[i] || !did->delya[i])
+            return (L_RDID *)ERROR_PTR("did arrays not defined",
+                                       procName, NULL);
+    }
+
+    return did;
+}
+
+
+/*!
+ *  recogGetWindowedArea()
+ *
+ *      Input:  recog
+ *              index (of template)
+ *              x (pixel position of left hand edge of template)
+ *              &dely (<return> y shift of template relative to pix1)
+ *              &wsum (<return> number of fg pixels in window of pixs)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is called after the best path has been found through
+ *          the trellis, in order to produce a correlation that can be used
+ *          to evaluate the confidence we have in the identification.
+ *          The correlation is |1 & 2|^2 / (|1| * |2|).
+ *          |1 & 2| is given by the count array, |2| is found from
+ *          nasum_u[], and |1| is wsum returned from this function.
+ */
+static l_int32
+recogGetWindowedArea(L_RECOG  *recog,
+                     l_int32   index,
+                     l_int32   x,
+                     l_int32  *pdely,
+                     l_int32  *pwsum)
+{
+l_int32  w1, h1, w2, h2;
+PIX     *pix1, *pix2, *pixt;
+L_RDID  *did;
+
+    PROCNAME("recogGetWindowedArea");
+
+    if (pdely) *pdely = 0;
+    if (pwsum) *pwsum = 0;
+    if (!pdely || !pwsum)
+        return ERROR_INT("&dely and &wsum not both defined", procName, 1);
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if ((did = recogGetDid(recog)) == NULL)
+        return ERROR_INT("did not defined", procName, 1);
+    if (index < 0 || index >= did->narray)
+        return ERROR_INT("invalid index", procName, 1);
+    pix1 = did->pixs;
+    pixGetDimensions(pix1, &w1, &h1, NULL);
+    if (x >= w1)
+        return ERROR_INT("invalid x position", procName, 1);
+
+    pix2 = pixaGetPix(recog->pixa_u, index, L_CLONE);
+    pixGetDimensions(pix2, &w2, &h2, NULL);
+    if (w1 < w2) {
+        L_INFO("template %d too small\n", procName, index);
+        pixDestroy(&pix2);
+        return 0;
+    }
+
+    *pdely = did->delya[index][x];
+    pixt = pixCreate(w2, h1, 1);
+    pixRasterop(pixt, 0, *pdely, w2, h2, PIX_SRC, pix2, 0, 0);
+    pixRasterop(pixt, 0, 0, w2, h1, PIX_SRC & PIX_DST, pix1, x, 0);
+    pixCountPixels(pixt, pwsum, recog->sumtab);
+    pixDestroy(&pix2);
+    pixDestroy(&pixt);
+    return 0;
+}
+
+
+/*!
+ *  recogSetChannelParams()
+ *
+ *      Input:  recog
+ *              nlevels
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This converts the independent bit-flip probabilities in the
+ *          "channel" into log-likelihood coefficients on image sums.
+ *          These coefficients are only defined for the non-background
+ *          template levels.  Thus for nlevels = 2 (one fg, one bg),
+ *          only beta[1] and gamma[1] are used.  For nlevels = 4 (three
+ *          fg templates), we use beta[1-3] and gamma[1-3].
+ */
+l_int32
+recogSetChannelParams(L_RECOG  *recog,
+                      l_int32   nlevels)
+{
+l_int32           i;
+const l_float32  *da;
+L_RDID           *did;
+
+    PROCNAME("recogSetChannelParams");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if ((did = recogGetDid(recog)) == NULL)
+        return ERROR_INT("did not defined", procName, 1);
+    if (nlevels == 2)
+        da = DefaultAlpha2;
+    else if (nlevels == 4)
+        da = DefaultAlpha4;
+    else
+        return ERROR_INT("nlevels not 2 or 4", procName, 1);
+
+    for (i = 1; i < nlevels; i++) {
+        did->beta[i] = log((1.0 - da[i]) / da[0]);
+        did->gamma[i] = log(da[0] * da[i] / ((1.0 - da[0]) * (1.0 - da[i])));
+        fprintf(stderr, "beta[%d] = %7.3f, gamma[%d] = %7.3f\n",
+                i, did->beta[i], i, did->gamma[i]);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  recogTransferRchToDid()
+ *
+ *      Input:  recog (with rch and did defined)
+ *              x (left edge of extracted region, relative to decoded line)
+ *              y (top edge of extracted region, relative to input image)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is used to transfer the results for a single character match
+ *          to the rescored did arrays.
+ */
+static l_int32
+recogTransferRchToDid(L_RECOG  *recog,
+                      l_int32   x,
+                      l_int32   y)
+{
+L_RDID  *did;
+L_RCH   *rch;
+
+    PROCNAME("recogTransferRchToDid");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if ((did = recogGetDid(recog)) == NULL)
+        return ERROR_INT("did not defined", procName, 1);
+    if ((rch = recog->rch) == NULL)
+        return ERROR_INT("rch not defined", procName, 1);
+
+    numaAddNumber(did->natempl_r, rch->index);
+    numaAddNumber(did->naxloc_r, rch->xloc + x);
+    numaAddNumber(did->nadely_r, rch->yloc + y);
+    numaAddNumber(did->nawidth_r, rch->width);
+    numaAddNumber(did->nascore_r, rch->score);
+    return 0;
+}
+
diff --git a/src/recogident.c b/src/recogident.c
new file mode 100644 (file)
index 0000000..ef2e0df
--- /dev/null
@@ -0,0 +1,2074 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  recogident.c
+ *
+ *      Top-level identification
+ *         l_int32             recogaIdentifyMultiple()
+ *
+ *      Segmentation and noise removal
+ *         l_int32             recogSplitIntoCharacters()
+ *         l_int32             recogCorrelationBestRow()
+ *         l_int32             recogCorrelationBestChar()
+ *         static l_int32      pixCorrelationBestShift()
+ *
+ *      Low-level identification of single characters
+ *         l_int32             recogaIdentifyPixa()
+ *         l_int32             recogIdentifyPixa()
+ *         l_int32             recogIdentifyPix()
+ *         l_int32             recogSkipIdentify()
+ *
+ *      Operations for handling identification results
+ *         static L_RCHA      *rchaCreate()
+ *         l_int32            *rchaDestroy()
+ *         static L_RCH       *rchCreate()
+ *         l_int32            *rchDestroy()
+ *         l_int32             rchaExtract()
+ *         l_int32             rchExtract()
+ *         static l_int32      transferRchToRcha()
+ *         static l_int32      recogaSaveBestRcha()
+ *         static l_int32      recogaTransferRch()
+ *         l_int32             recogTransferRchToDid()
+ *
+ *      Preprocessing and filtering
+ *         l_int32             recogProcessToIdentify()
+ *         PIX                *recogPreSplittingFilter()
+ *         PIX                *recogSplittingFilter()
+ *
+ *      Postprocessing
+ *         SARRAY             *recogExtractNumbers()
+ *
+ *      Modifying recog behavior
+ *         l_int32             recogSetTemplateType()
+ *         l_int32             recogSetScaling()
+ *
+ *      Static debug helper
+ *         static void         l_showIndicatorSplitValues()
+ *
+ *  See recogbasic.c for examples of training a recognizer, which is
+ *  required before it can be used for identification.
+ *
+ *  The character splitter repeatedly does a greedy correlation with each
+ *  averaged unscaled template, at all pixel locations along the text to
+ *  be identified.  The vertical alignment is between the template
+ *  centroid and the (moving) windowed centroid, including a delta of
+ *  1 pixel above and below.  The best match then removes part of the
+ *  input image, leaving 1 or 2 pieces, which, after filtering,
+ *  are put in a queue.  The process ends when the queue is empty.
+ *  The filtering is based on the size and aspect ratio of the
+ *  remaining pieces; the intent is to remove anything that is
+ *  unlikely to be text, such as small pieces and line graphics.
+ *
+ *  After splitting, the selected segments are identified using
+ *  the input parameters that were initially specified for the
+ *  recognizer.  Unlike the splitter, which uses the averaged
+ *  templates from the unscaled input, the recognizer can use
+ *  either all training examples or averaged templates, and these
+ *  can be either scaled or unscaled.  These choices are specified
+ *  when the recognizer is constructed.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+    /* Padding on pix1: added before correlations and removed from result */
+static const l_int32    LeftRightPadding = 32;
+
+    /* Parameters for filtering and sorting connected components in splitter */
+static const l_float32  MaxAspectRatio = 6.0;
+static const l_float32  MinFillFactor = 0.10;
+static const l_int32  MinOverlap1 = 6;  /* in pass 1 of boxaSort2d() */
+static const l_int32  MinOverlap2 = 6;  /* in pass 2 of boxaSort2d() */
+static const l_int32  MinHeightPass1 = 5;  /* min height to start pass 1 */
+
+
+static l_int32 pixCorrelationBestShift(PIX *pix1, PIX *pix2, NUMA *nasum1,
+                                       NUMA *namoment1, l_int32 area2,
+                                       l_int32 ycent2, l_int32 maxyshift,
+                                       l_int32 *tab8, l_int32 *pdelx,
+                                       l_int32 *pdely, l_float32 *pscore,
+                                       l_int32 debugflag );
+static L_RCH *rchCreate(l_int32 index, l_float32 score, char *text,
+                        l_int32 sample, l_int32 xloc, l_int32 yloc,
+                        l_int32 width);
+static L_RCHA *rchaCreate();
+static l_int32 transferRchToRcha(L_RCH *rch, L_RCHA *rcha);
+static void l_showIndicatorSplitValues(NUMA *na1, NUMA *na2, NUMA *na3,
+                                       NUMA *na4, NUMA *na5, NUMA *na6);
+static l_int32 recogaSaveBestRcha(L_RECOGA *recoga, PIXA *pixa);
+static l_int32 recogaTransferRch(L_RECOGA *recoga, L_RECOG *recog,
+                                 l_int32 index);
+
+/*------------------------------------------------------------------------*
+ *                             Identification
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogaIdentifyMultiple()
+ *
+ *      Input:  recoga (with training finished)
+ *              pixs (containing typically a small number of characters)
+ *              nitems (to be identified in pix; use 0 if not known)
+ *              minw (remove components with width less than this;
+ *                    use -1 for removing all noise components)
+ *              minh (remove components with height less than this;
+ *                    use -1 for removing all noise components)
+ *              &boxa (<optional return> locations of identified components)
+ *              &pixa (<optional return> images of identified components)
+ *              &pixdb (<optional return> debug pix: inputs and best fits)
+ *              debugsplit (1 returns pix split debugging images)
+ *      Return: 0 if OK; 1 if nothing is found; 2 for other errors.
+ *              (Get a warning if nitems and the number found are both > 0,
+ *              but not equal to each other.)
+ *
+ *  Notes:
+ *      (1) This filters the input pixa, looking for @nitems if requested.
+ *          Set @nitems == 0 if you don't know how many chars to expect.
+ *      (2) This bundles the filtered components into a pixa and calls
+ *          recogIdentifyPixa().  If @nitems > 0, use @minw = -1 and
+ *          @minh = -1 to remove all noise components.
+ *      (3) Set @minw = 0 and @minh = 0 to get all noise components.
+ *          Set @minw > 0 and/or @minh > 0 to retain selected noise components.
+ *          All noise components are recognized as an empty string with
+ *          a score of 0.0.
+ *      (4) An attempt is made to order the (optionally) returned images
+ *          and boxes in 2-dimensional sorted order.  These can then
+ *          be used to aggregate identified characters into numbers or words.
+ *          One typically wants the pixa, which contains a boxa of the
+ *          extracted subimages.
+ */
+l_int32
+recogaIdentifyMultiple(L_RECOGA  *recoga,
+                       PIX       *pixs,
+                       l_int32    nitems,
+                       l_int32    minw,
+                       l_int32    minh,
+                       BOXA     **pboxa,
+                       PIXA     **ppixa,
+                       PIX      **ppixdb,
+                       l_int32    debugsplit)
+{
+l_int32   n, done;
+BOXA     *boxa;
+PIX      *pixb;
+PIXA     *pixa;
+NUMA     *naid;
+L_RECOG  *recog;
+
+    PROCNAME("recogaIdentifyMultiple");
+
+    if (pboxa) *pboxa = NULL;
+    if (ppixa) *ppixa = NULL;
+    if (ppixdb) *ppixdb = NULL;
+    if (!recoga || recoga->n == 0)
+        return ERROR_INT("recog not defined or empty", procName, 2);
+    recogaTrainingDone(recoga, &done);
+    if (!done)
+        return ERROR_INT("training not finished", procName, 2);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 2);
+
+        /* Binarize if necessary */
+    recog = recogaGetRecog(recoga, 0);  /* use the first one */
+    if (pixGetDepth(pixs) > 1)
+        pixb = pixConvertTo1(pixs, recog->threshold);
+    else
+        pixb = pixClone(pixs);
+
+        /* Noise removal and splitting of touching characters */
+    recogSplitIntoCharacters(recog, pixb, minw, minh, &boxa, &pixa,
+                             &naid, debugsplit);
+    pixDestroy(&pixb);
+    if (!pixa || (n = pixaGetCount(pixa)) == 0) {
+        pixaDestroy(&pixa);
+        boxaDestroy(&boxa);
+        numaDestroy(&naid);
+        L_WARNING("nothing found\n", procName);
+        return 1;
+    }
+
+    if (nitems > 0 && n != nitems)
+        L_WARNING("Expected %d items; found %d\n", procName, nitems, n);
+
+    recogaIdentifyPixa(recoga, pixa, naid, ppixdb);
+    if (pboxa)
+        *pboxa = boxa;
+    else
+        boxaDestroy(&boxa);
+    if (ppixa)
+        *ppixa = pixa;
+    else
+        pixaDestroy(&pixa);
+
+    numaDestroy(&naid);
+    return 0;
+}
+
+
+/*!
+ *  recogSplitIntoCharacters()
+ *
+ *      Input:  recog
+ *              pixs (1 bpp, contains only mostly deskewed text)
+ *              minw (remove components with width less than this;
+ *                    use -1 for default removing out of band components)
+ *              minh (remove components with height less than this;
+ *                    use -1 for default removing out of band components)
+ *              &boxa (<return> character bounding boxes)
+ *              &pixa (<return> character images)
+ *              &naid (<return> indices of components to identify)
+ *              debug (1 for results written to pixadb_split)
+ *      Return: 0 if OK, 1 on error or if no components are returned
+ *
+ *  Notes:
+ *      (1) This can be given an image that has an arbitrary number
+ *          of text characters.  It does splitting of connected
+ *          components based on greedy correlation matching in
+ *          recogCorrelationBestRow().  The returned pixa includes
+ *          the boxes from which the (possibly split) components
+ *          are extracted.
+ *      (2) If either @minw < 0 or @minh < 0, noise components are
+ *          filtered out, and the returned @naid array is all 1.
+ *          Otherwise, some noise components whose dimensions (w,h)
+ *          satisfy w >= @minw and h >= @minh are allowed through, but
+ *          they are identified in the returned @naid, where they are
+ *          labelled by 0 to indicate that they are not to be run
+ *          through identification.  Retaining the noise components
+ *          provides spatial information that can help applications
+ *          interpret the results.
+ *      (3) In addition to optional filtering of the noise, the
+ *          resulting components are put in row-major (2D) order,
+ *          and the smaller of overlapping components are removed if
+ *          they satisfy conditions of relative size and fractional overlap.
+ *      (4) Note that the spliting function uses unscaled templates
+ *          and does not bother returning the class results and scores.
+ *          Thes are more accurately found later using the scaled templates.
+ */
+l_int32
+recogSplitIntoCharacters(L_RECOG  *recog,
+                         PIX      *pixs,
+                         l_int32   minw,
+                         l_int32   minh,
+                         BOXA    **pboxa,
+                         PIXA    **ppixa,
+                         NUMA    **pnaid,
+                         l_int32   debug)
+{
+l_int32  empty, maxw, bw, ncomp, same, savenoise, scaling;
+l_int32  i, j, n, n3, xoff, yoff;
+BOX     *box, *box3;
+BOXA    *boxa1, *boxa2, *boxa3, *boxa4, *boxat1, *boxat2, *boxad;
+BOXAA   *baa;
+NUMA    *naid;
+PIX     *pix, *pix1, *pix2;
+
+    PROCNAME("recogSplitIntoCharacters");
+
+    if (pboxa) *pboxa = NULL;
+    if (ppixa) *ppixa = NULL;
+    if (pnaid) *pnaid = NULL;
+    if (!pboxa || !ppixa || !pnaid)
+        return ERROR_INT("&boxa, &pixa and &naid not defined", procName, 1);
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!recog->train_done)
+        return ERROR_INT("training not finished", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    pixZero(pixs, &empty);
+    if (empty) return 1;
+
+        /* Small vertical close for consolidation.  Don't do a horizontal
+         * closing, because it might join separate characters. */
+    pix1 = pixMorphSequence(pixs, "c1.3", 0);
+
+        /* Carefully filter out noise */
+    pix2 = recogPreSplittingFilter(recog, pix1, MaxAspectRatio,
+                                   MinFillFactor, debug);
+
+        /* Optionally, save a boxa of noise components, filtered
+         * according to input parameters @minw and @minh */
+    boxa3 = NULL;
+    savenoise = (minw >= 0 && minh >= 0);
+    if (savenoise) {  /* accept some noise comonents */
+        pixXor(pix1, pix1, pix2);  /* leave noise components only in pix1 */
+        pixZero(pix1, &empty);
+        if (!empty) {
+            boxat1 = pixConnComp(pix1, NULL, 8);
+            boxa3 = boxaSelectBySize(boxat1, minw, minh, L_SELECT_BOTH,
+                                     L_SELECT_IF_GTE, NULL);
+            boxaDestroy(&boxat1);
+        }
+    }
+    pixDestroy(&pix1);
+
+        /* Get the 8-connected non-noise components to be split/identified */
+    boxa1 = pixConnComp(pix2, NULL, 8);
+    pixDestroy(&pix2);
+    ncomp = boxaGetCount(boxa1);
+    if (ncomp == 0) {
+        boxaDestroy(&boxa1);
+        boxaDestroy(&boxa3);
+        L_WARNING("all components removed\n", procName);
+        return 1;
+    }
+
+        /* Save everything and split the large non-noise components */
+    boxa2 = boxaCreate(ncomp);
+    maxw = recog->maxwidth_u + 5;
+    scaling = (recog->scalew > 0 || recog->scaleh > 0) ? TRUE : FALSE;
+    for (i = 0; i < ncomp; i++) {
+        box = boxaGetBox(boxa1, i, L_CLONE);
+        boxGetGeometry(box, &xoff, &yoff, &bw, NULL);
+        if (bw <= maxw || scaling) {  /* assume it's just one character */
+            boxaAddBox(boxa2, box, L_INSERT);
+        } else {  /* need to try to split the component */
+            pix = pixClipRectangle(pixs, box, NULL);
+            recogCorrelationBestRow(recog, pix, &boxat1, NULL, NULL,
+                                    NULL, debug);
+            pixDestroy(&pix);
+            boxDestroy(&box);
+            if (!boxat1) {
+              L_ERROR("boxat1 not found for component %d\n", procName, i);
+            } else {
+              boxat2 = boxaTransform(boxat1, xoff, yoff, 1.0, 1.0);
+              boxaJoin(boxa2, boxat2, 0, -1);
+              boxaDestroy(&boxat1);
+              boxaDestroy(&boxat2);
+            }
+        }
+    }
+    boxaDestroy(&boxa1);
+
+        /* If the noise boxa was retained, add it back in, so we have
+         * a mixture of non-noise and noise components. */
+    if (boxa3)
+        boxaJoin(boxa2, boxa3, 0, -1);
+
+        /* Do a 2D sort on the bounding boxes, and flatten the result to 1D.
+         * For the 2D sort, to add a box to an existing boxa, we require
+         * specified minimum vertical overlaps for the first two passes
+         * of the 2D sort.  In pass 1, only components with sufficient
+         * height can start a new boxa. */
+    baa = boxaSort2d(boxa2, NULL, MinOverlap1, MinOverlap2, MinHeightPass1);
+    boxa4 = boxaaFlattenToBoxa(baa, NULL, L_CLONE);
+    boxaaDestroy(&baa);
+    boxaDestroy(&boxa2);
+
+        /* Remove smaller components of overlapping pairs.
+         * We only remove the small component if the overlap is
+         * at least half its area and if its area is no more
+         * than 30% of the area of the large component.  Because the
+         * components are in a flattened 2D sort, we don't need to
+         * look far ahead in the array to find all overlapping boxes;
+         * 10 boxes is plenty. */
+    boxad = boxaHandleOverlaps(boxa4, L_COMBINE, 10, 0.5, 0.3, NULL);
+    boxaDestroy(&boxa4);
+
+        /* If savenoise == true and there are components in boxa3,
+         * use the full set of noise components in boxa3 to identify
+         * the remaining ones in boxad. */
+    n = boxaGetCount(boxad);
+    naid = numaMakeConstant(1, n);
+    if (savenoise && boxa3) {
+        n3 = boxaGetCount(boxa3);
+        for (i = 0; i < n; i++) {
+            box = boxaGetBox(boxad, i, L_CLONE);
+            for (j = 0; j < n3; j++) {
+                box3 = boxaGetBox(boxa3, j, L_CLONE);
+                boxEqual(box, box3, &same);
+                boxDestroy(&box3);
+                if (same) {
+                    numaSetValue(naid, i, 0);  /* label noise 0 */
+                    break;
+                }
+            }
+            boxDestroy(&box);
+        }
+    }
+    boxaDestroy(&boxa3);
+
+        /* Extract and save the image pieces from the input image. */
+    *ppixa = pixClipRectangles(pixs, boxad);
+    *pboxa = boxad;
+    *pnaid = naid;
+    return 0;
+}
+
+
+/*!
+ *  recogCorrelationBestRow()
+ *
+ *      Input:  recog (with LUT's pre-computed)
+ *              pixs (typically of multiple touching characters, 1 bpp)
+ *              &boxa (<return> bounding boxs of best fit character)
+ *              &nascores (<optional return> correlation scores)
+ *              &naindex (<optional return> indices of classes)
+ *              &sachar (<optional return> array of character strings)
+ *              debug (1 for results written to pixadb_split)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Supervises character matching for (in general) a c.c with
+ *          multiple touching characters.  Finds the best match greedily.
+ *          Rejects small parts that are left over after splitting.
+ *      (2) Matching is to the average, and without character scaling.
+ */
+l_int32
+recogCorrelationBestRow(L_RECOG  *recog,
+                        PIX      *pixs,
+                        BOXA    **pboxa,
+                        NUMA    **pnascore,
+                        NUMA    **pnaindex,
+                        SARRAY  **psachar,
+                        l_int32   debug)
+{
+char      *charstr;
+l_int32    index, remove, w, h, bx, bw, bxc, bwc, w1, w2, w3;
+l_float32  score;
+BOX       *box, *boxc, *boxtrans, *boxl, *boxr, *boxlt, *boxrt;
+BOXA      *boxat;
+NUMA      *nascoret, *naindext, *nasort;
+PIX       *pixb, *pixc, *pixl, *pixr, *pixdb, *pixd;
+PIXA      *pixar, *pixadb;
+SARRAY    *sachart;
+
+l_int32    iter;
+
+    PROCNAME("recogCorrelationBestRow");
+
+    if (pnascore) *pnascore = NULL;
+    if (pnaindex) *pnaindex = NULL;
+    if (psachar) *psachar = NULL;
+    if (!pboxa)
+        return ERROR_INT("&boxa not defined", procName, 1);
+    *pboxa = NULL;
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (pixGetWidth(pixs) < recog->minwidth_u - 4)
+        return ERROR_INT("pixs too narrow", procName, 1);
+    if (!recog->train_done)
+        return ERROR_INT("training not finished", procName, 1);
+
+        /* Binarize and crop to foreground if necessary */
+    pixb = recogProcessToIdentify(recog, pixs, 0);
+
+        /* Initialize the arrays */
+    boxat = boxaCreate(4);
+    nascoret = numaCreate(4);
+    naindext = numaCreate(4);
+    sachart = sarrayCreate(4);
+    pixadb = (debug) ? pixaCreate(4) : NULL;
+
+        /* Initialize the images remaining to be processed with the input.
+         * These are stored in pixar, which is used here as a queue,
+         * on which we only put image fragments that are large enough to
+         * contain at least one character.  */
+    pixar = pixaCreate(1);
+    pixGetDimensions(pixb, &w, &h, NULL);
+    box = boxCreate(0, 0, w, h);
+    pixaAddPix(pixar, pixb, L_INSERT);
+    pixaAddBox(pixar, box, L_INSERT);
+
+        /* Successively split on the best match until nothing is left.
+         * To be safe, we limit the search to 10 characters. */
+    for (iter = 0; iter < 11; iter++) {
+        if (pixaGetCount(pixar) == 0)
+            break;
+        if (iter == 10) {
+            L_WARNING("more than 10 chars; ending search\n", procName);
+            break;
+        }
+
+            /* Pop one from the queue */
+        pixaRemovePixAndSave(pixar, 0, &pixc, &boxc);
+        boxGetGeometry(boxc, &bxc, NULL, &bwc, NULL);
+
+            /* This is a single component; if noise, remove it */
+        recogSplittingFilter(recog, pixc, MaxAspectRatio, MinFillFactor,
+                             &remove, debug);
+        if (debug)
+            fprintf(stderr, "iter = %d, removed = %d\n", iter, remove);
+        if (remove) {
+            pixDestroy(&pixc);
+            boxDestroy(&boxc);
+            continue;
+        }
+
+            /* Find the best character match */
+        if (debug) {
+            recogCorrelationBestChar(recog, pixc, &box, &score,
+                                     &index, &charstr, &pixdb);
+            pixaAddPix(pixadb, pixdb, L_INSERT);
+        } else {
+            recogCorrelationBestChar(recog, pixc, &box, &score,
+                                     &index, &charstr, NULL);
+        }
+
+            /* Find the box in original coordinates, and append
+             * the results to the arrays. */
+        boxtrans = boxTransform(box, bxc, 0, 1.0, 1.0);
+        boxaAddBox(boxat, boxtrans, L_INSERT);
+        numaAddNumber(nascoret, score);
+        numaAddNumber(naindext, index);
+        sarrayAddString(sachart, charstr, L_INSERT);
+
+            /* Split the current pixc into three regions and save
+             * each region if it is large enough. */
+        boxGetGeometry(box, &bx, NULL, &bw, NULL);
+        w1 = bx;
+        w2 = bw;
+        w3 = bwc - bx - bw;
+        if (debug)
+            fprintf(stderr, " w1 = %d, w2 = %d, w3 = %d\n", w1, w2, w3);
+        if (w1 < recog->minwidth_u - 4) {
+            if (debug) L_INFO("discarding width %d on left\n", procName, w1);
+        } else {  /* extract and save left region */
+            boxl = boxCreate(0, 0, bx + 1, h);
+            pixl = pixClipRectangle(pixc, boxl, NULL);
+            boxlt = boxTransform(boxl, bxc, 0, 1.0, 1.0);
+            pixaAddPix(pixar, pixl, L_INSERT);
+            pixaAddBox(pixar, boxlt, L_INSERT);
+            boxDestroy(&boxl);
+        }
+        if (w3 < recog->minwidth_u - 4) {
+            if (debug) L_INFO("discarding width %d on right\n", procName, w3);
+        } else {  /* extract and save left region */
+            boxr = boxCreate(bx + bw - 1, 0, w3 + 1, h);
+            pixr = pixClipRectangle(pixc, boxr, NULL);
+            boxrt = boxTransform(boxr, bxc, 0, 1.0, 1.0);
+            pixaAddPix(pixar, pixr, L_INSERT);
+            pixaAddBox(pixar, boxrt, L_INSERT);
+            boxDestroy(&boxr);
+        }
+        pixDestroy(&pixc);
+        boxDestroy(&box);
+        boxDestroy(&boxc);
+    }
+    pixaDestroy(&pixar);
+
+
+        /* Sort the output results by left-to-right in the boxa */
+    *pboxa = boxaSort(boxat, L_SORT_BY_X, L_SORT_INCREASING, &nasort);
+    if (pnascore)
+        *pnascore = numaSortByIndex(nascoret, nasort);
+    if (pnaindex)
+        *pnaindex = numaSortByIndex(naindext, nasort);
+    if (psachar)
+        *psachar = sarraySortByIndex(sachart, nasort);
+    numaDestroy(&nasort);
+    boxaDestroy(&boxat);
+    numaDestroy(&nascoret);
+    numaDestroy(&naindext);
+    sarrayDestroy(&sachart);
+
+        /* Final debug output */
+    if (debug) {
+        pixd = pixaDisplayTiledInRows(pixadb, 32, 2000, 1.0, 0, 15, 2);
+        pixDisplay(pixd, 400, 400);
+        pixaAddPix(recog->pixadb_split, pixd, L_INSERT);
+        pixaDestroy(&pixadb);
+    }
+    return 0;
+}
+
+
+/*!
+ *  recogCorrelationBestChar()
+ *
+ *      Input:  recog (with LUT's pre-computed)
+ *              pixs (can be of multiple touching characters, 1 bpp)
+ *              &box (<return> bounding box of best fit character)
+ *              &score (<return> correlation score)
+ *              &index (<optional return> index of class)
+ *              &charstr (<optional return> character string of class)
+ *              &pixdb (<optional return> debug pix showing input and best fit)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Basic matching character splitter.  Finds the best match among
+ *          all templates to some region of the image.  This can result
+ *          in splitting the image into two parts.  This is "image decoding"
+ *          without dynamic programming, because we don't use a setwidth
+ *          and compute the best matching score for the entire image.
+ *      (2) Matching is to the average templates, without character scaling.
+ */
+l_int32
+recogCorrelationBestChar(L_RECOG    *recog,
+                         PIX        *pixs,
+                         BOX       **pbox,
+                         l_float32  *pscore,
+                         l_int32    *pindex,
+                         char      **pcharstr,
+                         PIX       **ppixdb)
+{
+l_int32    i, n, w1, h1, w2, area2, ycent2, delx, dely;
+l_int32    bestdelx, bestdely, bestindex;
+l_float32  score, bestscore;
+BOX       *box;
+BOXA      *boxa;
+NUMA      *nasum, *namoment;
+PIX       *pix1, *pix2;
+
+    PROCNAME("recogCorrelationBestChar");
+
+    if (pindex) *pindex = 0;
+    if (pcharstr) *pcharstr = NULL;
+    if (ppixdb) *ppixdb = NULL;
+    if (pbox) *pbox = NULL;
+    if (pscore) *pscore = 0.0;
+    if (!pbox || !pscore)
+        return ERROR_INT("&box and &score not both defined", procName, 1);
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (!recog->train_done)
+        return ERROR_INT("training not finished", procName, 1);
+
+        /* Binarize and crop to foreground if necessary.  Add padding
+         * to both the left and right side; this is compensated for
+         * when reporting the bounding box of the best matched character. */
+    pix1 = recogProcessToIdentify(recog, pixs, LeftRightPadding);
+    pixGetDimensions(pix1, &w1, &h1, NULL);
+
+        /* Compute vertical sum and moment arrays */
+    nasum = pixCountPixelsByColumn(pix1);
+    namoment = pixGetMomentByColumn(pix1, 1);
+
+        /* Do shifted correlation against all averaged templates. */
+    n = recog->setsize;
+    boxa = boxaCreate(n);  /* location of best fits for each character */
+    bestscore = 0.0;
+    for (i = 0; i < n; i++) {
+        pix2 = pixaGetPix(recog->pixa_u, i, L_CLONE);
+        w2 = pixGetWidth(pix2);
+            /* Note that the slightly expended w1 is typically larger
+             * than w2 (the template). */
+        if (w1 >= w2) {
+            numaGetIValue(recog->nasum_u, i, &area2);
+            ptaGetIPt(recog->pta_u, i, NULL, &ycent2);
+            pixCorrelationBestShift(pix1, pix2, nasum, namoment, area2, ycent2,
+                                    recog->maxyshift, recog->sumtab, &delx,
+                                    &dely, &score, 1);
+            if (ppixdb) {
+                fprintf(stderr,
+                    "Best match template %d: (x,y) = (%d,%d), score = %5.3f\n",
+                    i, delx, dely, score);
+            }
+                  /* Compensate for padding */
+            box = boxCreate(delx - LeftRightPadding, 0, w2, h1);
+            if (score > bestscore) {
+                bestscore = score;
+                bestdelx = delx - LeftRightPadding;
+                bestdely = dely;
+                bestindex = i;
+            }
+        } else {
+            box = boxCreate(0, 0, 1, 1);  /* placeholder */
+            if (ppixdb)
+                fprintf(stderr, "Component too thin: w1 = %d, w2 = %d\n",
+                        w1, w2);
+        }
+        boxaAddBox(boxa, box, L_INSERT);
+        pixDestroy(&pix2);
+    }
+
+    *pscore = bestscore;
+    *pbox = boxaGetBox(boxa, bestindex, L_COPY);
+    if (pindex) *pindex = bestindex;
+    if (pcharstr)
+        recogGetClassString(recog, bestindex, pcharstr);
+
+    if (ppixdb) {
+        L_INFO("Best match: class %d; shifts (%d, %d)\n",
+               procName, bestindex, bestdelx, bestdely);
+        pix2 = pixaGetPix(recog->pixa_u, bestindex, L_CLONE);
+        *ppixdb = recogShowMatch(recog, pix1, pix2, NULL, -1, 0.0);
+        pixDestroy(&pix2);
+    }
+
+    pixDestroy(&pix1);
+    boxaDestroy(&boxa);
+    numaDestroy(&nasum);
+    numaDestroy(&namoment);
+    return 0;
+}
+
+
+/*!
+ *  pixCorrelationBestShift()
+ *
+ *      Input:  pix1   (1 bpp, the unknown image; typically larger)
+ *              pix2   (1 bpp, the matching template image))
+ *              nasum1 (vertical column pixel sums for pix1)
+ *              namoment1  (vertical column first moment of pixels for pix1)
+ *              area2  (number of on pixels in pix2)
+ *              ycent2  (y component of centroid of pix2)
+ *              maxyshift  (max y shift of pix2 around the location where
+ *                          the centroids of pix2 and a windowed part of pix1
+ *                          are vertically aligned)
+ *              tab8 (<optional> sum tab for ON pixels in byte; can be NULL)
+ *              &delx (<optional return> best x shift of pix2 relative to pix1
+ *              &dely (<optional return> best y shift of pix2 relative to pix1
+ *              &score (<optional return> maximum score found; can be NULL)
+ *              debugflag (<= 0 to skip; positive to generate output.
+ *                         The integer is used to label the debug image.)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This maximizes the correlation score between two 1 bpp images,
+ *          one of which is typically wider.  In a typical example,
+ *          pix1 is a bitmap of 2 or more touching characters and pix2 is
+ *          a single character template.  This finds the location of pix2
+ *          that gives the largest correlation.
+ *      (2) The windowed area of fg pixels and windowed first moment
+ *          in the y direction are computed from the input sum and moment
+ *          column arrays, @nasum1 and @namoment1
+ *      (3) This is a brute force operation.  We compute the correlation
+ *          at every x shift for which pix2 fits entirely within pix1,
+ *          and where the centroid of pix2 is aligned, within +-maxyshift,
+ *          with the centroid of a window of pix1 of the same width.
+ *          The correlation is taken over the full height of pix1.
+ *          This can be made more efficient.
+ */
+static l_int32
+pixCorrelationBestShift(PIX        *pix1,
+                        PIX        *pix2,
+                        NUMA       *nasum1,
+                        NUMA       *namoment1,
+                        l_int32     area2,
+                        l_int32     ycent2,
+                        l_int32     maxyshift,
+                        l_int32    *tab8,
+                        l_int32    *pdelx,
+                        l_int32    *pdely,
+                        l_float32  *pscore,
+                        l_int32     debugflag)
+{
+l_int32     w1, w2, h1, h2, i, j, nx, shifty, delx, dely;
+l_int32     sum, moment, count;
+l_int32    *tab, *area1, *arraysum, *arraymoment;
+l_float32   maxscore, score;
+l_float32  *ycent1;
+FPIX       *fpix;
+PIX        *pixt, *pixt1, *pixt2;
+
+    PROCNAME("pixCorrelationBestShift");
+
+    if (pdelx) *pdelx = 0;
+    if (pdely) *pdely = 0;
+    if (pscore) *pscore = 0.0;
+    if (!pix1 || pixGetDepth(pix1) != 1)
+        return ERROR_INT("pix1 not defined or not 1 bpp", procName, 1);
+    if (!pix2 || pixGetDepth(pix2) != 1)
+        return ERROR_INT("pix2 not defined or not 1 bpp", procName, 1);
+    if (!nasum1 || !namoment1)
+        return ERROR_INT("nasum1 and namoment1 not both defined", procName, 1);
+    if (area2 <= 0 || ycent2 <= 0)
+        return ERROR_INT("area2 and ycent2 must be > 0", procName, 1);
+
+       /* If pix1 (the unknown image) is narrower than pix2,
+        * don't bother to try the match.  pix1 is already padded with
+        * 2 pixels on each side. */
+    pixGetDimensions(pix1, &w1, &h1, NULL);
+    pixGetDimensions(pix2, &w2, &h2, NULL);
+    if (w1 < w2) {
+        if (debugflag > 0) {
+            L_INFO("skipping match with w1 = %d and w2 = %d\n",
+                   procName, w1, w2);
+        }
+        return 0;
+    }
+    nx = w1 - w2 + 1;
+
+    if (debugflag > 0)
+        fpix = fpixCreate(nx, 2 * maxyshift + 1);
+    if (!tab8)
+        tab = makePixelSumTab8();
+    else
+        tab = tab8;
+
+        /* Set up the arrays for area1 and ycent1.  We have to do this
+         * for each template (pix2) because the window width is w2. */
+    area1 = (l_int32 *)LEPT_CALLOC(nx, sizeof(l_int32));
+    ycent1 = (l_float32 *)LEPT_CALLOC(nx, sizeof(l_int32));
+    arraysum = numaGetIArray(nasum1);
+    arraymoment = numaGetIArray(namoment1);
+    for (i = 0, sum = 0, moment = 0; i < w2; i++) {
+        sum += arraysum[i];
+        moment += arraymoment[i];
+    }
+    for (i = 0; i < nx - 1; i++) {
+        area1[i] = sum;
+        ycent1[i] = (sum == 0) ? ycent2 : (l_float32)moment / (l_float32)sum;
+        sum += arraysum[w2 + i] - arraysum[i];
+        moment += arraymoment[w2 + i] - arraymoment[i];
+    }
+    area1[nx - 1] = sum;
+    ycent1[nx - 1] = (sum == 0) ? ycent2 : (l_float32)moment / (l_float32)sum;
+
+        /* Find the best match location for pix2.  At each location,
+         * to insure that pixels are ON only within the intersection of
+         * pix and the shifted pix2:
+         *  (1) Start with pixt cleared and equal in size to pix1.
+         *  (2) Blit the shifted pix2 onto pixt.  Then all ON pixels
+         *      are within the intersection of pix1 and the shifted pix2.
+         *  (3) AND pix1 with pixt. */
+    pixt = pixCreate(w2, h1, 1);
+    maxscore = 0;
+    delx = 0;
+    dely = 0;  /* amount to shift pix2 relative to pix1 to get alignment */
+    for (i = 0; i < nx; i++) {
+        shifty = (l_int32)(ycent1[i] - ycent2 + 0.5);
+        for (j = -maxyshift; j <= maxyshift; j++) {
+            pixClearAll(pixt);
+            pixRasterop(pixt, 0, shifty + j, w2, h2, PIX_SRC, pix2, 0, 0);
+            pixRasterop(pixt, 0, 0, w2, h1, PIX_SRC & PIX_DST, pix1, i, 0);
+            pixCountPixels(pixt, &count, tab);
+            score = (l_float32)count * (l_float32)count /
+                    ((l_float32)area1[i] * (l_float32)area2);
+            if (score > maxscore) {
+                maxscore = score;
+                delx = i;
+                dely = shifty + j;
+            }
+
+            if (debugflag > 0)
+                fpixSetPixel(fpix, i, maxyshift + j, 1000.0 * score);
+        }
+    }
+
+    if (debugflag > 0) {
+        lept_mkdir("lept/recog");
+        char  buf[128];
+        pixt1 = fpixDisplayMaxDynamicRange(fpix);
+        pixt2 = pixExpandReplicate(pixt1, 5);
+        snprintf(buf, sizeof(buf), "/tmp/lept/recog/junkbs_%d.png", debugflag);
+        pixWrite(buf, pixt2, IFF_PNG);
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt2);
+        fpixDestroy(&fpix);
+    }
+
+    if (pdelx) *pdelx = delx;
+    if (pdely) *pdely = dely;
+    if (pscore) *pscore = maxscore;
+    if (!tab8) LEPT_FREE(tab);
+    LEPT_FREE(area1);
+    LEPT_FREE(ycent1);
+    LEPT_FREE(arraysum);
+    LEPT_FREE(arraymoment);
+    pixDestroy(&pixt);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                          Low-level identification                      *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogaIdentifyPixa()
+ *
+ *      Input:  recoga
+ *              pixa (of 1 bpp images to match)
+ *              naid (<optional> indices of components to identify; can be null)
+ *              &pixdb (<optional return> pix showing inputs and best fits)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See recogIdentifyPixa().  This does the same operation
+ *          for each recog, returning the arrays of results (scores,
+ *          class index and character string) for the best correlation match.
+ */
+l_int32
+recogaIdentifyPixa(L_RECOGA  *recoga,
+                   PIXA      *pixa,
+                   NUMA      *naid,
+                   PIX      **ppixdb)
+{
+l_int32    done, i, n, nrec;
+PIX       *pix1;
+PIXA      *pixadb;
+L_RECOG   *recog;
+
+    PROCNAME("recogaIdentifyPixa");
+
+    if (ppixdb) *ppixdb = NULL;
+    if (!recoga)
+        return ERROR_INT("recoga not defined", procName, 2);
+    if ((nrec = recogaGetCount(recoga)) == 0)
+        return ERROR_INT("recoga empty", procName, 2);
+    recogaTrainingDone(recoga, &done);
+    if (!done)
+        return ERROR_INT("training not finished", procName, 1);
+    if (!pixa || (pixaGetCount(pixa) == 0))
+        return ERROR_INT("pixa not defined", procName, 1);
+    if ((n = pixaGetCount(pixa)) == 0)
+        return ERROR_INT("pixa is empty", procName, 1);
+
+        /* Run each recognizer on the set of images.  This writes
+         * the text string into each pix of the pixa_id copy. */
+    rchaDestroy(&recoga->rcha);
+    recoga->rcha = rchaCreate();
+    pixadb = (ppixdb) ? pixaCreate(n) : NULL;
+    for (i = 0; i < nrec; i++) {
+        recog = recogaGetRecog(recoga, i);
+        if (!ppixdb) {
+            recogIdentifyPixa(recog, pixa, naid, NULL);
+        } else {
+            recogIdentifyPixa(recog, pixa, naid, &pix1);
+            pixaAddPix(pixadb, pix1, L_INSERT);
+        }
+    }
+
+        /* Accumulate the best results in the cha of the recoga.  This
+         * also writes the text string into each pix of the input pixa. */
+    recogaSaveBestRcha(recoga, pixa);
+
+        /* Package the images for debug */
+    if (pixadb)
+        *ppixdb = pixaDisplayLinearly(pixadb, L_VERT, 1.0, 0, 20, 2, NULL);
+    pixaDestroy(&pixadb);
+    return 0;
+}
+
+
+/*!
+ *  recogIdentifyPixa()
+ *
+ *      Input:  recog
+ *              pixa (of 1 bpp images to match)
+ *              naid (<optional> indices of components to identify; can be null)
+ *              &pixdb (<optional return> pix showing inputs and best fits)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See recogIdentifyPix().  This does the same operation
+ *          for each pix in a pixa, and optionally returns the arrays
+ *          of results (scores, class index and character string)
+ *          for the best correlation match.
+ */
+l_int32
+recogIdentifyPixa(L_RECOG  *recog,
+                  PIXA     *pixa,
+                  NUMA     *naid,
+                  PIX     **ppixdb)
+{
+char      *text;
+l_int32    i, n, doit, fail, index, depth;
+l_float32  score;
+NUMA      *naidt;
+PIX       *pix1, *pix2, *pix3;
+PIXA      *pixa1;
+L_RCH     *rch;
+
+    PROCNAME("recogIdentifyPixa");
+
+    if (ppixdb) *ppixdb = NULL;
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+        /* Set up the components to run through the recognizer */
+    n = pixaGetCount(pixa);
+    if (naid)
+        naidt = numaClone(naid);
+    else
+        naidt = numaMakeConstant(1, n);
+
+        /* Run the recognizer on the set of images.  This writes
+         * the text string into each pix in pixa.  If this is called
+         * multiple times for different recognizers, the text string
+         * will be overwritten, but it will be finalized with the correct
+         * string from the cha in the recoga, using recogaSaveBestCha(). */
+    rchaDestroy(&recog->rcha);
+    recog->rcha = rchaCreate();
+    pixa1 = (ppixdb) ? pixaCreate(n) : NULL;
+    depth = 1;
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixa, i, L_CLONE);
+        pix2 = NULL;
+        fail = FALSE;
+        numaGetIValue(naidt, i, &doit);
+        if (!doit)
+            recogSkipIdentify(recog);
+        else if (!ppixdb)
+            fail = recogIdentifyPix(recog, pix1, NULL);
+        else
+            fail = recogIdentifyPix(recog, pix1, &pix2);
+        if (fail)
+            recogSkipIdentify(recog);
+        if ((rch = recog->rch) == NULL) {
+            L_ERROR("rch not found for char %d\n", procName, i);
+            pixDestroy(&pix1);
+            pixDestroy(&pix2);
+            continue;
+        }
+        rchExtract(rch, NULL, NULL, &text, NULL, NULL, NULL, NULL);
+        pixSetText(pix1, text);
+        LEPT_FREE(text);
+        if (ppixdb && doit) {
+            rchExtract(rch, &index, &score, NULL, NULL, NULL, NULL, NULL);
+            pix3 = recogShowMatch(recog, pix2, NULL, NULL, index, score);
+            if (i == 0) depth = pixGetDepth(pix3);
+            pixaAddPix(pixa1, pix3, L_INSERT);
+            pixDestroy(&pix2);
+        }
+        transferRchToRcha(rch, recog->rcha);
+        pixDestroy(&pix1);
+    }
+    numaDestroy(&naidt);
+
+        /* Package the images for debug */
+    if (ppixdb) {
+        *ppixdb = pixaDisplayTiledInRows(pixa1, depth, 2500, 1.0, 0, 20, 1);
+        pixaDestroy(&pixa1);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  recogIdentifyPix()
+ *
+ *      Input:  recog (with LUT's pre-computed)
+ *              pixs (of a single character, 1 bpp)
+ *              &pixdb (<optional return> debug pix showing input and best fit)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Basic recognition function for a single character.
+ *      (2) If L_USE_ALL, matching is attempted to every bitmap in the recog,
+ *          and the identify of the best match is returned.  However,
+ *          if L_USE_AVERAGE, the matching is only to the averaged bitmaps,
+ *          and the index of the bestsample is meaningless (0 is returned
+ *          if requested).
+ *      (3) The score is related to the confidence (probability of correct
+ *          identification), in that a higher score is correlated with
+ *          a higher probability.  However, the actual relation between
+ *          the correlation (score) and the probability is not known;
+ *          we call this a "score" because "confidence" can be misinterpreted
+ *          as an actual probability.
+ */
+l_int32
+recogIdentifyPix(L_RECOG  *recog,
+                 PIX      *pixs,
+                 PIX     **ppixdb)
+{
+char      *text;
+l_int32    i, j, n, bestindex, bestsample, area1, area2;
+l_int32    shiftx, shifty, bestdelx, bestdely, bestwidth, maxyshift;
+l_float32  x1, y1, x2, y2, delx, dely, score, maxscore;
+NUMA      *numa;
+PIX       *pix0, *pix1, *pix2;
+PIXA      *pixa;
+PTA       *pta;
+
+    PROCNAME("recogIdentifyPix");
+
+    if (ppixdb) *ppixdb = NULL;
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+        /* Do the averaging if not yet done.  This will also
+         * call recogFinishTraining(), if necessary. */
+    if (!recog->ave_done)
+        recogAverageSamples(recog, 0);
+
+        /* Binarize and crop to foreground if necessary */
+    if ((pix0 = recogProcessToIdentify(recog, pixs, 0)) == NULL)
+        return ERROR_INT("no fg pixels in pix0", procName, 1);
+
+        /* Do correlation at all positions within +-maxyshift of
+         * the nominal centroid alignment. */
+    pix1 = recogScaleCharacter(recog, pix0);
+    pixCountPixels(pix1, &area1, recog->sumtab);
+    pixCentroid(pix1, recog->centtab, recog->sumtab, &x1, &y1);
+    bestindex = bestsample = bestdelx = bestdely = bestwidth = 0;
+    maxscore = 0.0;
+    maxyshift = recog->maxyshift;
+    if (recog->templ_type == L_USE_AVERAGE) {
+        for (i = 0; i < recog->setsize; i++) {
+            numaGetIValue(recog->nasum, i, &area2);
+            if (area2 == 0) continue;  /* no template available */
+            pix2 = pixaGetPix(recog->pixa, i, L_CLONE);
+            ptaGetPt(recog->pta, i, &x2, &y2);
+            delx = x1 - x2;
+            dely = y1 - y2;
+            for (shifty = -maxyshift; shifty <= maxyshift; shifty++) {
+                for (shiftx = -maxyshift; shiftx <= maxyshift; shiftx++) {
+                    pixCorrelationScoreSimple(pix1, pix2, area1, area2,
+                                              delx + shiftx, dely + shifty,
+                                              5, 5, recog->sumtab, &score);
+                    if (score > maxscore) {
+                        bestindex = i;
+                        bestdelx = delx + shiftx;
+                        bestdely = dely + shifty;
+                        maxscore = score;
+                    }
+                }
+            }
+            pixDestroy(&pix2);
+        }
+    } else {  /* use all the samples */
+        for (i = 0; i < recog->setsize; i++) {
+            pixa = pixaaGetPixa(recog->pixaa, i, L_CLONE);
+            n = pixaGetCount(pixa);
+            if (n == 0) {
+                pixaDestroy(&pixa);
+                continue;
+            }
+            numa = numaaGetNuma(recog->naasum, i, L_CLONE);
+            pta = ptaaGetPta(recog->ptaa, i, L_CLONE);
+            for (j = 0; j < n; j++) {
+                pix2 = pixaGetPix(pixa, j, L_CLONE);
+                numaGetIValue(numa, j, &area2);
+                ptaGetPt(pta, j, &x2, &y2);
+                delx = x1 - x2;
+                dely = y1 - y2;
+                for (shifty = -maxyshift; shifty <= maxyshift; shifty++) {
+                    for (shiftx = -maxyshift; shiftx <= maxyshift; shiftx++) {
+                        pixCorrelationScoreSimple(pix1, pix2, area1, area2,
+                                                  delx + shiftx, dely + shifty,
+                                                  5, 5, recog->sumtab, &score);
+                        if (score > maxscore) {
+                            bestindex = i;
+                            bestsample = j;
+                            bestdelx = delx + shiftx;
+                            bestdely = dely + shifty;
+                            maxscore = score;
+                            bestwidth = pixGetWidth(pix2);
+                        }
+                    }
+                }
+                pixDestroy(&pix2);
+            }
+            pixaDestroy(&pixa);
+            numaDestroy(&numa);
+            ptaDestroy(&pta);
+        }
+    }
+
+        /* Package up the results */
+    recogGetClassString(recog, bestindex, &text);
+    rchDestroy(&recog->rch);
+    recog->rch = rchCreate(bestindex, maxscore, text, bestsample,
+                           bestdelx, bestdely, bestwidth);
+
+    if (ppixdb) {
+        if (recog->templ_type == L_USE_AVERAGE) {
+            L_INFO("Best match: str %s; class %d; shifts (%d, %d)\n",
+                   procName, text, bestindex, bestdelx, bestdely);
+            pix2 = pixaGetPix(recog->pixa, bestindex, L_CLONE);
+        } else {  /* L_USE_ALL */
+            L_INFO("Best match: str %s; sample %d in class %d\n",
+                   procName, text, bestsample, bestindex);
+            if (maxyshift > 0) {
+                L_INFO("  Best shift: (%d, %d)\n",
+                       procName, bestdelx, bestdely);
+            }
+            pix2 = pixaaGetPix(recog->pixaa, bestindex, bestsample, L_CLONE);
+        }
+        *ppixdb = recogShowMatch(recog, pix1, pix2, NULL, -1, 0.0);
+        pixDestroy(&pix2);
+    }
+
+    pixDestroy(&pix0);
+    pixDestroy(&pix1);
+    return 0;
+}
+
+
+/*!
+ *  recogSkipIdentify()
+ *
+ *      Input:  recog
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This just writes a "dummy" result with 0 score and empty
+ *          string id into the rch.
+ */
+l_int32
+recogSkipIdentify(L_RECOG  *recog)
+{
+    PROCNAME("recogSkipIdentify");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+
+        /* Package up placeholder results */
+    rchDestroy(&recog->rch);
+    recog->rch = rchCreate(0, 0.0, stringNew(""), 0, 0, 0, 0);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *             Operations for handling identification results             *
+ *------------------------------------------------------------------------*/
+/*!
+ *  rchaCreate()
+ *
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Be sure to destroy any existing rcha before assigning this.
+ */
+static L_RCHA *
+rchaCreate()
+{
+L_RCHA  *rcha;
+
+    rcha = (L_RCHA *)LEPT_CALLOC(1, sizeof(L_RCHA));
+    rcha->naindex = numaCreate(0);
+    rcha->nascore = numaCreate(0);
+    rcha->satext = sarrayCreate(0);
+    rcha->nasample = numaCreate(0);
+    rcha->naxloc = numaCreate(0);
+    rcha->nayloc = numaCreate(0);
+    rcha->nawidth = numaCreate(0);
+    return rcha;
+}
+
+
+/*!
+ *  rchaDestroy()
+ *
+ *      Input:  &rcha
+ *      Return: void
+ */
+void
+rchaDestroy(L_RCHA  **prcha)
+{
+L_RCHA  *rcha;
+
+    PROCNAME("rchaDestroy");
+
+    if (prcha == NULL) {
+        L_WARNING("&rcha is null!\n", procName);
+        return;
+    }
+    if ((rcha = *prcha) == NULL)
+        return;
+
+    numaDestroy(&rcha->naindex);
+    numaDestroy(&rcha->nascore);
+    sarrayDestroy(&rcha->satext);
+    numaDestroy(&rcha->nasample);
+    numaDestroy(&rcha->naxloc);
+    numaDestroy(&rcha->nayloc);
+    numaDestroy(&rcha->nawidth);
+    LEPT_FREE(rcha);
+    *prcha = NULL;
+    return;
+}
+
+
+/*!
+ *  rchCreate()
+ *
+ *      Input:  index (index of best template)
+ *              score (correlation score of best template)
+ *              text (character string of best template)
+ *              sample (index of best sample; -1 if averages are used)
+ *              xloc (x-location of template: delx + shiftx)
+ *              yloc (y-location of template: dely + shifty)
+ *              width (width of best template)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Be sure to destroy any existing rch before assigning this.
+ *      (2) This stores the text string, not a copy of it, so the
+ *          caller must not destroy the string.
+ */
+static L_RCH *
+rchCreate(l_int32    index,
+          l_float32  score,
+          char      *text,
+          l_int32    sample,
+          l_int32    xloc,
+          l_int32    yloc,
+          l_int32    width)
+{
+L_RCH  *rch;
+
+    rch = (L_RCH *)LEPT_CALLOC(1, sizeof(L_RCH));
+    rch->index = index;
+    rch->score = score;
+    rch->text = text;
+    rch->sample = sample;
+    rch->xloc = xloc;
+    rch->yloc = yloc;
+    rch->width = width;
+    return rch;
+}
+
+
+/*!
+ *  rchDestroy()
+ *
+ *      Input:  &rch
+ *      Return: void
+ */
+void
+rchDestroy(L_RCH  **prch)
+{
+L_RCH  *rch;
+
+    PROCNAME("rchDestroy");
+
+    if (prch == NULL) {
+        L_WARNING("&rch is null!\n", procName);
+        return;
+    }
+    if ((rch = *prch) == NULL)
+        return;
+    LEPT_FREE(rch->text);
+    LEPT_FREE(rch);
+    *prch = NULL;
+    return;
+}
+
+
+/*!
+ *  rchaExtract()
+ *
+ *      Input:  rcha
+ *              &naindex (<optional return> indices of best templates)
+ *              &nascore (<optional return> correl scores of best templates)
+ *              &satext (<optional return> character strings of best templates)
+ *              &nasample (<optional return> indices of best samples)
+ *              &naxloc (<optional return> x-locations of templates)
+ *              &nayloc (<optional return> y-locations of templates)
+ *              &nawidth (<optional return> widths of best templates)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This returns clones of the number and string arrays.  They must
+ *          be destroyed by the caller.
+ */
+l_int32
+rchaExtract(L_RCHA   *rcha,
+            NUMA    **pnaindex,
+            NUMA    **pnascore,
+            SARRAY  **psatext,
+            NUMA    **pnasample,
+            NUMA    **pnaxloc,
+            NUMA    **pnayloc,
+            NUMA    **pnawidth)
+{
+    PROCNAME("rchaExtract");
+
+    if (pnaindex) *pnaindex = NULL;
+    if (pnascore) *pnascore = NULL;
+    if (psatext) *psatext = NULL;
+    if (pnasample) *pnasample = NULL;
+    if (pnaxloc) *pnaxloc = NULL;
+    if (pnayloc) *pnayloc = NULL;
+    if (pnawidth) *pnawidth = NULL;
+    if (!rcha)
+        return ERROR_INT("rcha not defined", procName, 1);
+
+    if (pnaindex) *pnaindex = numaClone(rcha->naindex);
+    if (pnascore) *pnascore = numaClone(rcha->nascore);
+    if (psatext) *psatext = sarrayClone(rcha->satext);
+    if (pnasample) *pnasample = numaClone(rcha->nasample);
+    if (pnaxloc) *pnaxloc = numaClone(rcha->naxloc);
+    if (pnayloc) *pnayloc = numaClone(rcha->nayloc);
+    if (pnawidth) *pnawidth = numaClone(rcha->nawidth);
+    return 0;
+}
+
+
+/*!
+ *  rchExtract()
+ *
+ *      Input:  rch
+ *              &index (<optional return> index of best template)
+ *              &score (<optional return> correlation score of best template)
+ *              &text (<optional return> character string of best template)
+ *              &sample (<optional return> index of best sample)
+ *              &xloc (<optional return> x-location of template)
+ *              &yloc (<optional return> y-location of template)
+ *              &width (<optional return> width of best template)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+rchExtract(L_RCH      *rch,
+           l_int32    *pindex,
+           l_float32  *pscore,
+           char      **ptext,
+           l_int32    *psample,
+           l_int32    *pxloc,
+           l_int32    *pyloc,
+           l_int32    *pwidth)
+{
+    PROCNAME("rchExtract");
+
+    if (pindex) *pindex = 0;
+    if (pscore) *pscore = 0.0;
+    if (ptext) *ptext = NULL;
+    if (psample) *psample = 0;
+    if (pxloc) *pxloc = 0;
+    if (pyloc) *pyloc = 0;
+    if (pwidth) *pwidth = 0;
+    if (!rch)
+        return ERROR_INT("rch not defined", procName, 1);
+
+    if (pindex) *pindex = rch->index;
+    if (pscore) *pscore = rch->score;
+    if (ptext) *ptext = stringNew(rch->text);  /* new string: owned by caller */
+    if (psample) *psample = rch->sample;
+    if (pxloc) *pxloc = rch->xloc;
+    if (pyloc) *pyloc = rch->yloc;
+    if (pwidth) *pwidth = rch->width;
+    return 0;
+}
+
+
+/*!
+ *  transferRchToRcha()
+ *
+ *      Input:  rch (source of data)
+ *              rcha (append to arrays in this destination)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is used to transfer the results of a single character
+ *          identification to an rcha array for the array of characters.
+ */
+static l_int32
+transferRchToRcha(L_RCH   *rch,
+                  L_RCHA  *rcha)
+{
+
+    PROCNAME("transferRchToRcha");
+
+    if (!rch)
+        return ERROR_INT("rch not defined", procName, 1);
+    if (!rcha)
+        return ERROR_INT("rcha not defined", procName, 1);
+
+    numaAddNumber(rcha->naindex, rch->index);
+    numaAddNumber(rcha->nascore, rch->score);
+    sarrayAddString(rcha->satext, rch->text, L_COPY);
+    numaAddNumber(rcha->nasample, rch->sample);
+    numaAddNumber(rcha->naxloc, rch->xloc);
+    numaAddNumber(rcha->nayloc, rch->yloc);
+    numaAddNumber(rcha->nawidth, rch->width);
+    return 0;
+}
+
+
+/*!
+ *  recogaSaveBestRcha()
+ *
+ *      Input:  recoga
+ *              pixa (with all components having been identified)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Finds the best score among the recognizers for each character,
+ *          and puts the rch data into a rcha in the recoga.  This is
+ *          run after all recognizers have been applied to the pixa.
+ *      (2) This also writes the best text id for each pix into its text field.
+ */
+static l_int32
+recogaSaveBestRcha(L_RECOGA  *recoga,
+                   PIXA      *pixa)
+{
+char      *text;
+l_int32    i, j, npix, nrec, jmax;
+l_float32  score, maxscore;
+L_RECOG   *recog;
+PIX       *pix;
+L_RCHA    *rcha;
+SARRAY    *satext;
+
+    PROCNAME("recogaSaveBestRcha");
+
+    if (!recoga)
+        return ERROR_INT("recoga not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+        /* Make a clean rcha to accept the results */
+    rchaDestroy(&recoga->rcha);
+    recoga->rcha = rchaCreate();
+
+    npix = pixaGetCount(pixa);
+    nrec = recogaGetCount(recoga);
+    for (i = 0; i < npix; i++) {
+             /* Find the recog in the recoga with the best score */
+        maxscore = 0.0;
+        jmax = 0;
+        for (j = 0; j < nrec; j++) {
+            if ((recog = recogaGetRecog(recoga, j)) == NULL) {
+                L_ERROR("recog %d not found\n", procName, j);
+                continue;
+            }
+            if ((rcha = recog->rcha) == NULL) {
+                L_ERROR("rcha not found for recog %d\n", procName, j);
+                continue;
+            }
+            numaGetFValue(rcha->nascore, i, &score);
+            if (score > maxscore) {
+                maxscore = score;
+                jmax = j;
+            }
+        }
+        recog = recogaGetRecog(recoga, jmax);
+
+            /* Transfer the data for this char to the recoga */
+        recogaTransferRch(recoga, recog, i);
+    }
+
+        /* Write the best text string for each pix into the pixa */
+    if ((rcha = recoga->rcha) == NULL)
+        return ERROR_INT("rcha not found!", procName, 1);
+    rchaExtract(rcha, NULL, NULL, &satext, NULL, NULL, NULL, NULL);
+    for (i = 0; i < npix; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        text = sarrayGetString(satext, i, L_NOCOPY);
+        pixSetText(pix, text);
+        pixDestroy(&pix);
+    }
+    sarrayDestroy(&satext);  /* it's a clone */
+
+    return 0;
+}
+
+
+/*!
+ *  recogaTransferRch()
+ *
+ *      Input:  recoga (destination, with rcha defined)
+ *              recog (source, with best scoring char in its rcha)
+ *              index (index of component in the original pixa)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is called by recogaGetBestRcha() to transfer the results
+ *          of a single character identification in a selected recog to the
+ *          rcha array in the recoga, which holds the best scoring characters.
+ */
+static l_int32
+recogaTransferRch(L_RECOGA  *recoga,
+                  L_RECOG   *recog,
+                  l_int32    index)
+{
+char      *str;
+l_int32    ival;
+l_float32  fval;
+L_RCHA    *rchas, *rchad;
+
+    PROCNAME("recogaTransferRch");
+
+    if (!recoga)
+        return ERROR_INT("recoga not defined", procName, 1);
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+
+    rchas = recog->rcha;
+    rchad = recoga->rcha;
+    numaGetIValue(rchas->naindex, index, &ival);
+    numaAddNumber(rchad->naindex, ival);
+    numaGetFValue(rchas->nascore, index, &fval);
+    numaAddNumber(rchad->nascore, fval);
+    str = sarrayGetString(rchas->satext, index, L_COPY);
+    sarrayAddString(rchad->satext, str, L_INSERT);
+    numaGetIValue(rchas->nasample, index, &ival);
+    numaAddNumber(rchad->nasample, ival);
+    numaGetIValue(rchas->naxloc, index, &ival);
+    numaAddNumber(rchad->naxloc, ival);
+    numaGetIValue(rchas->nayloc, index, &ival);
+    numaAddNumber(rchad->nayloc, ival);
+    numaGetIValue(rchas->nawidth, index, &ival);
+    numaAddNumber(rchad->nawidth, ival);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                        Preprocessing and filtering                     *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogProcessToIdentify()
+ *
+ *      Input:  recog (with LUT's pre-computed)
+ *              pixs (typ. single character, possibly d > 1 and uncropped)
+ *              pad (extra pixels added to left and right sides)
+ *      Return: pixd (1 bpp, clipped to foreground), or null if there
+ *                    are no fg pixels or on error.
+ *
+ *  Notes:
+ *      (1) This is a lightweight operation to insure that the input
+ *          image is 1 bpp, properly cropped, and padded on each side.
+ *          If bpp > 1, the image is thresholded.
+ */
+PIX *
+recogProcessToIdentify(L_RECOG  *recog,
+                       PIX      *pixs,
+                       l_int32   pad)
+{
+l_int32  canclip;
+PIX     *pix1, *pix2, *pixd;
+
+    PROCNAME("recogProcessToIdentify");
+
+    if (!recog)
+        return (PIX *)ERROR_PTR("recog not defined", procName, NULL);
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    if (pixGetDepth(pixs) != 1)
+        pix1 = pixThresholdToBinary(pixs, recog->threshold);
+    else
+        pix1 = pixClone(pixs);
+    pixTestClipToForeground(pix1, &canclip);
+    if (canclip)
+        pixClipToForeground(pix1, &pix2, NULL);
+    else
+        pix2 = pixClone(pix1);
+    pixDestroy(&pix1);
+    if (!pix2)
+        return (PIX *)ERROR_PTR("no foreground pixels", procName, NULL);
+
+    pixd = pixAddBorderGeneral(pix2, pad, pad, 0, 0, 0);
+    pixDestroy(&pix2);
+    return pixd;
+}
+
+
+/*!
+ *  recogPreSplittingFilter()
+ *
+ *      Input:  recog
+ *              pixs (1 bpp, single connected component)
+ *              maxasp (maximum asperity ratio (width/height) to be retained)
+ *              minaf (minimum area fraction (|fg|/(w*h)) to be retained)
+ *              debug (1 to output indicator arrays)
+ *      Return: pixd (with filtered components removed) or null on error
+ */
+PIX *
+recogPreSplittingFilter(L_RECOG   *recog,
+                        PIX       *pixs,
+                        l_float32  maxasp,
+                        l_float32  minaf,
+                        l_int32    debug)
+{
+l_int32  scaling, minsplitw, minsplith, maxsplith;
+BOXA    *boxas;
+NUMA    *naw, *nah, *na1, *na1c, *na2, *na3, *na4, *na5, *na6, *na7;
+PIX     *pixd;
+PIXA    *pixas;
+
+    PROCNAME("recogPreSplittingFilter");
+
+    if (!recog)
+        return (PIX *)ERROR_PTR("recog not defined", procName, NULL);
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+        /* If there is scaling, do not remove components based on the
+         * values of min_splitw, min_splith and max_splith. */
+    scaling = (recog->scalew > 0 || recog->scaleh > 0) ? TRUE : FALSE;
+    minsplitw = (scaling) ? 1 : recog->min_splitw - 3;
+    minsplith = (scaling) ? 1 : recog->min_splith - 3;
+    maxsplith = (scaling) ? 200 : recog->max_splith;
+
+        /* Generate an indicator array of connected components to remove:
+         *    small stuff
+         *    tall stuff
+         *    components with large width/height ratio
+         *    components with small area fill fraction  */
+    boxas = pixConnComp(pixs, &pixas, 8);
+    pixaFindDimensions(pixas, &naw, &nah);
+    na1 = numaMakeThresholdIndicator(naw, minsplitw, L_SELECT_IF_LT);
+    na1c = numaCopy(na1);
+    na2 = numaMakeThresholdIndicator(nah, minsplith, L_SELECT_IF_LT);
+    na3 = numaMakeThresholdIndicator(nah, maxsplith, L_SELECT_IF_GT);
+    na4 = pixaFindWidthHeightRatio(pixas);
+    na5 = numaMakeThresholdIndicator(na4, maxasp, L_SELECT_IF_GT);
+    na6 = pixaFindAreaFraction(pixas);
+    na7 = numaMakeThresholdIndicator(na6, minaf, L_SELECT_IF_LT);
+    numaLogicalOp(na1, na1, na2, L_UNION);
+    numaLogicalOp(na1, na1, na3, L_UNION);
+    numaLogicalOp(na1, na1, na5, L_UNION);
+    numaLogicalOp(na1, na1, na7, L_UNION);
+    pixd = pixCopy(NULL, pixs);
+    pixRemoveWithIndicator(pixd, pixas, na1);
+    if (debug)
+        l_showIndicatorSplitValues(na1c, na2, na3, na5, na7, na1);
+    numaDestroy(&naw);
+    numaDestroy(&nah);
+    numaDestroy(&na1);
+    numaDestroy(&na1c);
+    numaDestroy(&na2);
+    numaDestroy(&na3);
+    numaDestroy(&na4);
+    numaDestroy(&na5);
+    numaDestroy(&na6);
+    numaDestroy(&na7);
+    boxaDestroy(&boxas);
+    pixaDestroy(&pixas);
+    return pixd;
+}
+
+
+/*!
+ *  recogSplittingFilter()
+ *
+ *      Input:  recog
+ *              pixs (1 bpp, single connected component)
+ *              maxasp (maximum asperity ratio (width/height) to be retained)
+ *              minaf (minimum area fraction (|fg|/(w*h)) to be retained)
+ *              &remove (<return> 0 to save, 1 to remove)
+ *              debug (1 to output indicator arrays)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogSplittingFilter(L_RECOG   *recog,
+                     PIX       *pixs,
+                     l_float32  maxasp,
+                     l_float32  minaf,
+                     l_int32   *premove,
+                     l_int32    debug)
+{
+l_int32    w, h;
+l_float32  aspratio, fract;
+
+    PROCNAME("recogSplittingFilter");
+
+    if (!premove)
+        return ERROR_INT("&remove not defined", procName, 1);
+    *premove = 0;
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+        /* Remove from further consideration:
+         *    small stuff
+         *    components with large width/height ratio
+         *    components with small area fill fraction */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (w < recog->min_splitw) {
+        if (debug) L_INFO("w = %d < %d\n", procName, w, recog->min_splitw);
+        *premove = 1;
+        return 0;
+    }
+    if (h < recog->min_splith) {
+        if (debug) L_INFO("h = %d < %d\n", procName, h, recog->min_splith);
+        *premove = 1;
+        return 0;
+    }
+    aspratio = (l_float32)w / (l_float32)h;
+    if (aspratio > maxasp) {
+        if (debug) L_INFO("w/h = %5.3f too large\n", procName, aspratio);
+        *premove = 1;
+        return 0;
+    }
+    pixFindAreaFraction(pixs, recog->sumtab, &fract);
+    if (fract < minaf) {
+        if (debug) L_INFO("area fill fract %5.3f < %5.3f\n",
+                          procName, fract, minaf);
+        *premove = 1;
+        return 0;
+    }
+
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                              Postprocessing                            *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogaExtractNumbers()
+ *
+ *      Input:  recoga
+ *              boxas (location of components)
+ *              scorethresh (min score for which we accept a component)
+ *              spacethresh (max horizontal distance allowed between digits,
+ *                           use -1 for default)
+ *              &baa (<optional return> bounding boxes of identified numbers)
+ *              &naa (<optional return> scores of identified digits)
+ *      Return: sa (of identified numbers), or null on error
+ *
+ *  Notes:
+ *      (1) This extracts digit data after recogaIdentifyMultiple() or
+ *          lower-level identification has taken place.
+ *      (2) Each string in the returned sa contains a sequence of ascii
+ *          digits in a number.
+ *      (3) The horizontal distance between boxes (limited by @spacethresh)
+ *          is the negative of the horizontal overlap.
+ *      (4) Components with a score less than @scorethresh, which may
+ *          be hyphens or other small characters, will signal the
+ *          end of the current sequence of digits in the number.  A typical
+ *          value for @scorethresh is 0.60.
+ *      (5) We allow two digits to be combined if these conditions apply:
+ *            (a) the first is to the left of the second
+ *            (b) the second has a horizontal separation less than @spacethresh
+ *            (c) the vertical overlap >= 0 (vertical separation < 0)
+ *            (d) both have a score that exceeds @scorethresh
+ *      (6) Each numa in the optionally returned naa contains the digit
+ *          scores of a number.  Each boxa in the optionally returned baa
+ *          contains the bounding boxes of the digits in the number.
+ */
+SARRAY *
+recogaExtractNumbers(L_RECOGA  *recoga,
+                     BOXA      *boxas,
+                     l_float32  scorethresh,
+                     l_int32    spacethresh,
+                     BOXAA    **pbaa,
+                     NUMAA    **pnaa)
+{
+char      *str, *text;
+l_int32    i, n, x1, x2, h_sep, v_sep;
+l_float32  score;
+BOX       *box, *prebox;
+BOXA      *ba;
+BOXAA     *baa;
+NUMA      *nascore, *na;
+NUMAA     *naa;
+L_RECOG   *recog;
+SARRAY    *satext, *sa, *saout;
+
+    PROCNAME("recogaExtractNumbers");
+
+    if (pbaa) *pbaa = NULL;
+    if (pnaa) *pnaa = NULL;
+    if (!recoga || !recoga->rcha)
+        return (SARRAY *)ERROR_PTR("recoga and rcha not both defined",
+                                   procName, NULL);
+    if (!boxas)
+        return (SARRAY *)ERROR_PTR("boxas not defined", procName, NULL);
+
+    if (spacethresh < 0) {
+        if ((recog = recogaGetRecog(recoga, 0)) == NULL)
+            return (SARRAY *)ERROR_PTR("recog not found", procName, NULL);
+        spacethresh = L_MAX(recog->maxwidth_u, 20);
+    }
+    rchaExtract(recoga->rcha, NULL, &nascore, &satext, NULL, NULL, NULL, NULL);
+    if (!nascore || !satext) {
+        numaDestroy(&nascore);
+        sarrayDestroy(&satext);
+        return (SARRAY *)ERROR_PTR("nascore and satext not both returned",
+                                   procName, NULL);
+    }
+
+    saout = sarrayCreate(0);
+    naa = numaaCreate(0);
+    baa = boxaaCreate(0);
+    prebox = NULL;
+    n = numaGetCount(nascore);
+    for (i = 0; i < n; i++) {
+        numaGetFValue(nascore, i, &score);
+        text = sarrayGetString(satext, i, L_NOCOPY);
+        if (prebox == NULL) {  /* no current run */
+            if (score < scorethresh) {
+                continue;
+            } else {  /* start a number run */
+                sa = sarrayCreate(0);
+                ba = boxaCreate(0);
+                na = numaCreate(0);
+                sarrayAddString(sa, text, L_COPY);
+                prebox = boxaGetBox(boxas, i, L_CLONE);
+                boxaAddBox(ba, prebox, L_COPY);
+                numaAddNumber(na, score);
+            }
+        } else {  /* in a current number run */
+            box = boxaGetBox(boxas, i, L_CLONE);
+            boxGetGeometry(prebox, &x1, NULL, NULL, NULL);
+            boxGetGeometry(box, &x2, NULL, NULL, NULL);
+            boxSeparationDistance(box, prebox, &h_sep, &v_sep);
+            boxDestroy(&prebox);
+            if (x1 < x2 && h_sep <= spacethresh &&
+                v_sep < 0 && score >= scorethresh) {  /* add to number */
+                sarrayAddString(sa, text, L_COPY);
+                boxaAddBox(ba, box, L_COPY);
+                numaAddNumber(na, score);
+                prebox = box;
+            } else {  /* save the completed number */
+                str = sarrayToString(sa, 0);
+                sarrayAddString(saout, str, L_INSERT);
+                sarrayDestroy(&sa);
+                boxaaAddBoxa(baa, ba, L_INSERT);
+                numaaAddNuma(naa, na, L_INSERT);
+                boxDestroy(&box);
+                if (score >= scorethresh) {  /* start a new number */
+                    i--;
+                    continue;
+                }
+            }
+        }
+    }
+
+    if (prebox) {  /* save the last number */
+        str = sarrayToString(sa, 0);
+        sarrayAddString(saout, str, L_INSERT);
+        boxaaAddBoxa(baa, ba, L_INSERT);
+        numaaAddNuma(naa, na, L_INSERT);
+        sarrayDestroy(&sa);
+        boxDestroy(&prebox);
+    }
+
+    numaDestroy(&nascore);
+    sarrayDestroy(&satext);
+    if (sarrayGetCount(saout) == 0) {
+        sarrayDestroy(&saout);
+        boxaaDestroy(&baa);
+        numaaDestroy(&naa);
+        return (SARRAY *)ERROR_PTR("saout has no strings", procName, NULL);
+    }
+
+    if (pbaa)
+        *pbaa = baa;
+    else
+        boxaaDestroy(&baa);
+    if (pnaa)
+        *pnaa = naa;
+    else
+        numaaDestroy(&naa);
+    return saout;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                         Modifying recog behavior                       *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogSetTemplateType()
+ *
+ *      Input:  recog
+ *              templ_type (L_USE_AVERAGE or L_USE_ALL)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogSetTemplateType(L_RECOG  *recog,
+                     l_int32   templ_type)
+{
+    PROCNAME("recogSetTemplateType");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (templ_type != L_USE_AVERAGE && templ_type != L_USE_ALL)
+        return ERROR_INT("invalid templ_type", procName, 1);
+
+    recog->templ_type = templ_type;
+    return 0;
+}
+
+
+/*!
+ *  recogSetScaling()
+ *
+ *      Input:  recog
+ *              scalew  (scale all widths to this; use 0 for no scaling)
+ *              scaleh  (scale all heights to this; use 0 for no scaling)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogSetScaling(L_RECOG  *recog,
+                l_int32   scalew,
+                l_int32   scaleh)
+{
+    PROCNAME("recogSetScaling");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (scalew < 0 || scaleh < 0)
+        return ERROR_INT("invalid scalew or scaleh", procName, 1);
+    if (scalew == recog->scalew && scaleh == recog->scaleh) {
+        L_INFO("scaling factors not changed\n", procName);
+        return 0;
+    }
+
+    recog->scalew = scalew;
+    recog->scaleh = scaleh;
+    recog->train_done = FALSE;
+
+        /* Restock the scaled character images and recompute all averages */
+    recogTrainingFinished(recog, 0);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                        Static debug helper                             *
+ *------------------------------------------------------------------------*/
+/*!
+ *  l_showIndicatorSplitValues()
+ *
+ *      Input:  6 indicator array
+ *
+ *  Notes:
+ *      (1) The values indicate that specific criteria has been met
+ *          for component removal by pre-splitting filter..
+ *          The 'result' line shows which components have been removed.
+ */
+static void
+l_showIndicatorSplitValues(NUMA  *na1,
+                           NUMA  *na2,
+                           NUMA  *na3,
+                           NUMA  *na4,
+                           NUMA  *na5,
+                           NUMA  *na6)
+{
+l_int32  i, n;
+
+    n = numaGetCount(na1);
+    fprintf(stderr, "================================================\n");
+    fprintf(stderr, "lt minw:    ");
+    for (i = 0; i < n; i++)
+        fprintf(stderr, "%4d ", (l_int32)na1->array[i]);
+    fprintf(stderr, "\nlt minh:    ");
+    for (i = 0; i < n; i++)
+        fprintf(stderr, "%4d ", (l_int32)na2->array[i]);
+    fprintf(stderr, "\ngt maxh:    ");
+    for (i = 0; i < n; i++)
+        fprintf(stderr, "%4d ", (l_int32)na3->array[i]);
+    fprintf(stderr, "\ngt maxasp:  ");
+    for (i = 0; i < n; i++)
+        fprintf(stderr, "%4d ", (l_int32)na4->array[i]);
+    fprintf(stderr, "\nlt minaf:   ");
+    for (i = 0; i < n; i++)
+        fprintf(stderr, "%4d ", (l_int32)na5->array[i]);
+    fprintf(stderr, "\n------------------------------------------------");
+    fprintf(stderr, "\nresult:     ");
+    for (i = 0; i < n; i++)
+        fprintf(stderr, "%4d ", (l_int32)na6->array[i]);
+    fprintf(stderr, "\n================================================\n");
+}
diff --git a/src/recogtrain.c b/src/recogtrain.c
new file mode 100644 (file)
index 0000000..4714ff0
--- /dev/null
@@ -0,0 +1,2419 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  recogtrain.c
+ *
+ *      Training on labelled data
+ *         l_int32             recogTrainLabelled()
+ *         l_int32             recogProcessMultLabelled()
+ *         PIX                *recogProcessSingleLabelled()
+ *         l_int32             recogAddSamples()
+ *         PIX                *recogScaleCharacter()
+ *         l_int32             recogAverageSamples()
+ *         l_int32             pixaAccumulateSamples()
+ *         l_int32             recogTrainingFinished()
+ *         l_int32             recogRemoveOutliers()
+ *
+ *      Evaluate training status
+ *         l_int32             recogaTrainingDone()
+ *         l_int32             recogaFinishAveraging()
+ *
+ *      Training on unlabelled data
+ *         l_int32             recogTrainUnlabelled()
+ *
+ *      Padding the training set
+ *         l_int32             recogPadTrainingSet()
+ *         l_int32            *recogMapIndexToIndex()
+ *         static l_int32      recogAverageClassGeom()
+ *         l_int32             recogaBestCorrelForPadding()
+ *         l_int32             recogCorrelAverages()
+ *         l_int32             recogSetPadParams()
+ *         static l_int32      recogGetCharsetSize()
+ *         static l_int32      recogCharsetAvailable()
+ *
+ *      Debugging
+ *         l_int32             recogaShowContent()
+ *         l_int32             recogShowContent()
+ *         l_int32             recogDebugAverages()
+ *         l_int32             recogShowAverageTemplates()
+ *         PIX                *recogShowMatchesInRange()
+ *         PIX                *recogShowMatch()
+ *         l_int32             recogResetBmf()
+ *
+ *      Static helpers
+ *         static char        *l_charToString()
+ *         static void         addDebugImage1()
+ *         static void         addDebugImage2()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+
+    /* Static functions */
+static l_int32 *recogMapIndexToIndex(L_RECOG *recog1, L_RECOG *recog2);
+static l_int32 recogAverageClassGeom(L_RECOG *recog, NUMA **pnaw, NUMA **pnah);
+static l_int32 recogGetCharsetSize(l_int32 type);
+static l_int32 recogCharsetAvailable(l_int32 type);
+static char *l_charToString(char byte);
+static void debugAddImage1(PIXA *pixa1, PIX *pix1, PIX *pix2, L_BMF *bmf,
+                           l_float32 score);
+static void debugAddImage2(PIXA *pixadb, PIXA *pixa1, L_BMF *bmf,
+                           l_int32 index);
+
+    /* Defaults in pixRemoveOutliers() */
+static const l_float32  DEFAULT_TARGET_SCORE = 0.75; /* keep everything above */
+static const l_float32  DEFAULT_MIN_FRACTION = 0.5;  /* to be kept */
+
+    /* Padding parameters for recognizer */
+static const char *     DEFAULT_BOOT_DIR = "recog/digits";
+static const char *     DEFAULT_BOOT_PATTERN = "digit_set";
+static const l_int32    DEFAULT_CHARSET_TYPE = L_ARABIC_NUMERALS;
+static const l_int32    DEFAULT_MIN_NOPAD = 3;
+static const l_int32    DEFAULT_MAX_AFTERPAD = 15;
+static const l_int32    DEFAULT_MIN_SAMPLES = 10;  /* char samples in recog */
+
+
+/*------------------------------------------------------------------------*
+ *                                Training                                *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogTrainLabelled()
+ *
+ *      Input:  recog (in training mode)
+ *              pixs (if depth > 1, will be thresholded to 1 bpp)
+ *              box (<optional> cropping box)
+ *              text (<optional> if null, use text field in pix)
+ *              multflag (1 if one or more contiguous ascii characters;
+ *                        0 for a single arbitrary character)
+ *              debug (1 to display images of samples not captured)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Training is restricted to the addition of either:
+ *          (a) multflag == 0: a single character in an arbitrary
+ *              (e.g., UTF8) charset
+ *          (b) multflag == 1: one or more ascii characters rendered
+ *              contiguously in pixs
+ *      (2) If box != null, it should represent the cropped location of
+ *          the character image.
+ *      (3) If multflag == 1, samples will be rejected if the number of
+ *          connected components does not equal to the number of ascii
+ *          characters in the textstring.  In that case, if debug == 1,
+ *          the rejected samples will be displayed.
+ */
+l_int32
+recogTrainLabelled(L_RECOG  *recog,
+                   PIX      *pixs,
+                   BOX      *box,
+                   char     *text,
+                   l_int32   multflag,
+                   l_int32   debug)
+{
+l_int32  ret;
+PIXA    *pixa;
+
+    PROCNAME("recogTrainLabelled");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    if (multflag == 0) {
+        ret = recogProcessSingleLabelled(recog, pixs, box, text, &pixa);
+    } else {
+        ret = recogProcessMultLabelled(recog, pixs, box, text, &pixa, debug);
+    }
+    if (ret)
+        return ERROR_INT("failure to add training data", procName, 1);
+    recogAddSamples(recog, pixa, -1, debug);
+    pixaDestroy(&pixa);
+    return 0;
+}
+
+
+/*!
+ *  recogProcessMultLabelled()
+ *
+ *      Input:  recog (in training mode)
+ *              pixs (if depth > 1, will be thresholded to 1 bpp)
+ *              box (<optional> cropping box)
+ *              text (<optional> if null, use text field in pix)
+ *              &pixa (<return> of split and thresholded characters)
+ *              debug (1 to display images of samples not captured)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This crops and segments one or more labelled and contiguous
+ *          ascii characters, for input in training.  It is a special case.
+ *      (2) The character images are bundled into a pixa with the
+ *          character text data embedded in each pix.
+ *      (3) Where there is more than one character, this does some
+ *          noise reduction and extracts the resulting character images
+ *          from left to right.  No scaling is performed.
+ */
+l_int32
+recogProcessMultLabelled(L_RECOG  *recog,
+                         PIX      *pixs,
+                         BOX      *box,
+                         char     *text,
+                         PIXA    **ppixa,
+                         l_int32   debug)
+{
+char      *textdata, *textstr;
+l_int32    textinpix, textin, nchars, ncomp, i;
+BOX       *box2;
+BOXA      *boxa1, *boxa2, *boxa3, *boxa4;
+PIX       *pixc, *pixb, *pixt, *pix1, *pix2;
+
+    PROCNAME("recogProcessMultLabelled");
+
+    if (!ppixa)
+        return ERROR_INT("&pixa not defined", procName, 1);
+    *ppixa = NULL;
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+        /* Find the text; this will be stored with the output images */
+    textin = text && (text[0] != '\0');
+    textinpix = pixs->text && (pixs->text[0] != '\0');
+    if (!textin && !textinpix) {
+        L_ERROR("no text: %d\n", procName, recog->num_samples);
+        return 1;
+    }
+    textdata = (textin) ? text : pixs->text;  /* do not free */
+
+        /* Crop and binarize if necessary */
+    if (box)
+        pixc = pixClipRectangle(pixs, box, NULL);
+    else
+        pixc = pixClone(pixs);
+    if (pixGetDepth(pixc) > 1)
+        pixb = pixConvertTo1(pixc, recog->threshold);
+    else
+        pixb = pixClone(pixc);
+    pixDestroy(&pixc);
+
+        /* We segment the set of characters as follows:
+         * (1) A large vertical closing should consolidate most characters.
+               Do not attempt to split touching characters using openings,
+               because this is likely to break actual characters. */
+    pix1 = pixMorphSequence(pixb, "c1.70", 0);
+
+        /* (2) Include overlapping components and remove small ones */
+    boxa1 = pixConnComp(pix1, NULL, 8);
+    boxa2 = boxaCombineOverlaps(boxa1);
+    boxa3 = boxaSelectBySize(boxa2, 2, 8, L_SELECT_IF_BOTH,
+                             L_SELECT_IF_GT, NULL);
+    pixDestroy(&pix1);
+    boxaDestroy(&boxa1);
+    boxaDestroy(&boxa2);
+
+        /* (3) Make sure the components equal the number of text characters */
+    ncomp = boxaGetCount(boxa3);
+    nchars = strlen(textdata);
+    if (ncomp != nchars) {
+        L_ERROR("ncomp (%d) != nchars (%d); num samples = %d\n",
+                procName, ncomp, nchars, recog->num_samples);
+        if (debug) {
+            pixt = pixConvertTo32(pixb);
+            pixRenderBoxaArb(pixt, boxa3, 1, 255, 0, 0);
+            pixDisplay(pixt, 10 * recog->num_samples, 100);
+            pixDestroy(&pixt);
+        }
+        pixDestroy(&pixb);
+        boxaDestroy(&boxa3);
+        return 1;
+    }
+
+        /* (4) Sort the components from left to right and extract them */
+    boxa4 = boxaSort(boxa3, L_SORT_BY_X, L_SORT_INCREASING, NULL);
+    boxaDestroy(&boxa3);
+
+        /* Save the results, with one character in each pix */
+    *ppixa = pixaCreate(ncomp);
+    for (i = 0; i < ncomp; i++) {
+        box2 = boxaGetBox(boxa4, i, L_CLONE);
+        pix2 = pixClipRectangle(pixb, box2, NULL);
+        textstr = l_charToString(textdata[i]);
+        pixSetText(pix2, textstr);  /* inserts a copy */
+        pixaAddPix(*ppixa, pix2, L_INSERT);
+        boxDestroy(&box2);
+        LEPT_FREE(textstr);
+    }
+
+    pixDestroy(&pixb);
+    boxaDestroy(&boxa4);
+    return 0;
+}
+
+
+/*!
+ *  recogProcessSingleLabelled()
+ *
+ *      Input:  recog (in training mode)
+ *              pixs (if depth > 1, will be thresholded to 1 bpp)
+ *              box (<optional> cropping box)
+ *              text (<optional> if null, use text field in pix)
+ *              &pixa (<return> one pix, 1 bpp, labelled)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This crops and binarizes the input image, generating a pix
+ *          of one character where the charval is inserted into the pix.
+ */
+l_int32
+recogProcessSingleLabelled(L_RECOG  *recog,
+                           PIX      *pixs,
+                           BOX      *box,
+                           char     *text,
+                           PIXA    **ppixa)
+{
+char    *textdata;
+l_int32  textinpix, textin;
+PIX     *pixc, *pixb, *pixd;
+
+    PROCNAME("recogProcessSingleLabelled");
+
+    if (!ppixa)
+        return ERROR_INT("&pixa not defined", procName, 1);
+    *ppixa = NULL;
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+        /* Find the text; this will be stored with the output images */
+    textin = text && (text[0] != '\0');
+    textinpix = (pixs->text && (pixs->text[0] != '\0'));
+    if (!textin && !textinpix) {
+        L_ERROR("no text: %d\n", procName, recog->num_samples);
+        return 1;
+    }
+    textdata = (textin) ? text : pixs->text;  /* do not free */
+
+        /* Crop and binarize if necessary */
+    if (box)
+        pixc = pixClipRectangle(pixs, box, NULL);
+    else
+        pixc = pixClone(pixs);
+    if (pixGetDepth(pixc) > 1)
+        pixb = pixConvertTo1(pixc, recog->threshold);
+    else
+        pixb = pixClone(pixc);
+    pixDestroy(&pixc);
+
+        /* Clip to foreground and save */
+    pixClipToForeground(pixb, &pixd, NULL);
+    pixDestroy(&pixb);
+    if (!pixd)
+        return ERROR_INT("pixd is empty", procName, 1);
+    pixSetText(pixd, textdata);
+    *ppixa = pixaCreate(1);
+    pixaAddPix(*ppixa, pixd, L_INSERT);
+    return 0;
+}
+
+
+/*!
+ *  recogAddSamples()
+ *
+ *      Input:  recog
+ *              pixa (1 or more characters)
+ *              classindex (use -1 if not forcing into a specified class)
+ *              debug
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The pix in the pixa are all 1 bpp, and the character string
+ *          labels are embedded in the pix.
+ *      (2) Note: this function decides what class each pix belongs in.
+ *          When input is from a multifont pixaa, with a valid value
+ *          for @classindex, the character string label in each pix
+ *          is ignored, and @classindex is used as the class index
+ *          for all the pix in the pixa.  Thus, for that situation we
+ *          use this class index to avoid making the decision through a
+ *          lookup based on the character strings embedded in the pix.
+ *      (3) When a recog is initially filled with samples, the pixaa_u
+ *          array is initialized to accept up to 256 different classes.
+ *          When training is finished, the arrays are truncated to the
+ *          actual number of classes.  To pad an existing recog from
+ *          the boot recognizers, training is started again; if samples
+ *          from a new class are added, the pixaa_u array must be
+ *          extended by adding a pixa to hold them.
+ */
+l_int32
+recogAddSamples(L_RECOG  *recog,
+                PIXA     *pixa,
+                l_int32   classindex,
+                l_int32   debug)
+{
+char    *text;
+l_int32  i, n, npa, charint, index;
+PIX     *pixb;
+PIXA    *pixa1;
+PIXAA   *paa;
+
+    PROCNAME("recogAddSamples");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixa) {
+        L_ERROR("pixa not defined: %d\n", procName, recog->num_samples);
+        return 1;
+    }
+    if (recog->train_done)
+        return ERROR_INT("training has been completed", procName, 1);
+    if ((n = pixaGetCount(pixa)) == 0)
+        ERROR_INT("no pix in the pixa", procName, 1);
+    paa = recog->pixaa_u;
+
+    for (i = 0; i < n; i++) {
+        pixb = pixaGetPix(pixa, i, L_CLONE);
+        if (classindex < 0) {
+                /* Determine the class array index.  Check if the class
+                 * alreadly exists, and if not, add it. */
+            text = pixGetText(pixb);
+            if (l_convertCharstrToInt(text, &charint) == 1) {
+                L_ERROR("invalid text: %s\n", procName, text);
+                pixDestroy(&pixb);
+                continue;
+            }
+            if (recogGetClassIndex(recog, charint, text, &index) == 1) {
+                    /* New class must be added */
+                npa = pixaaGetCount(paa, NULL);
+                if (index > npa)
+                    L_ERROR("index %d > npa %d!!\n", procName, index, npa);
+                if (index == npa) {  /* paa needs to be extended */
+                    L_INFO("Adding new class and pixa with index %d\n",
+                           procName, index);
+                    pixa1 = pixaCreate(10);
+                    pixaaAddPixa(paa, pixa1, L_INSERT);
+                }
+            }
+            if (debug) {
+                L_INFO("Identified text label: %s\n", procName, text);
+                L_INFO("Identified: charint = %d, index = %d\n",
+                       procName, charint, index);
+            }
+        } else {
+            index = classindex;
+        }
+
+            /* Insert the unscaled character image into the right pixa.
+             * (Unscaled images are required to split touching characters.) */
+        recog->num_samples++;
+        pixaaAddPix(paa, index, pixb, NULL, L_INSERT);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  recogScaleCharacter()
+ *
+ *      Input:  recog
+ *              pixs (1 bpp, to be scaled)
+ *      Return: pixd (scaled) if OK, null on error
+ */
+PIX *
+recogScaleCharacter(L_RECOG  *recog,
+                    PIX      *pixs)
+{
+l_int32  w, h;
+
+    PROCNAME("recogScaleCharacter");
+
+    if (!recog)
+        return (PIX *)ERROR_PTR("recog not defined", procName, NULL);
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((recog->scalew == 0 || recog->scalew == w) &&
+        (recog->scaleh == 0 || recog->scaleh == h))  /* no scaling */
+        return pixClone(pixs);
+    else
+        return pixScaleToSize(pixs, recog->scalew, recog->scaleh);
+}
+
+
+/*!
+ *  recogAverageSamples()
+ *
+ *      Input:  recog
+ *              debug
+ *      Return: 0 on success, 1 on failure
+ *
+ *  Notes:
+ *      (1) This is called when training is finished, and after
+ *          outliers have been removed.
+ *          Both unscaled and scaled inputs are averaged.
+ *          Averages must be computed before any identification is done.
+ *      (2) Set debug = 1 to view the resulting templates
+ *          and their centroids.
+ */
+l_int32
+recogAverageSamples(L_RECOG  *recog,
+                    l_int32   debug)
+{
+l_int32    i, nsamp, size, area;
+l_float32  x, y;
+PIXA      *pixat, *pixa_sel;
+PIX       *pix1, *pix2;
+PTA       *ptat;
+
+    PROCNAME("recogAverageSamples");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+
+    if (recog->ave_done) {
+        if (debug)  /* always do this if requested */
+            recogShowAverageTemplates(recog);
+        return 0;
+    }
+
+        /* Remove any previous averaging data */
+    size = recog->setsize;
+    pixaDestroy(&recog->pixa_u);
+    ptaDestroy(&recog->pta_u);
+    numaDestroy(&recog->nasum_u);
+    recog->pixa_u = pixaCreate(size);
+    recog->pta_u = ptaCreate(size);
+    recog->nasum_u = numaCreate(size);
+
+    pixaDestroy(&recog->pixa);
+    ptaDestroy(&recog->pta);
+    numaDestroy(&recog->nasum);
+    recog->pixa = pixaCreate(size);
+    recog->pta = ptaCreate(size);
+    recog->nasum = numaCreate(size);
+
+        /* Unscaled bitmaps: compute averaged bitmap, centroid, and fg area */
+    for (i = 0; i < size; i++) {
+        pixat = pixaaGetPixa(recog->pixaa_u, i, L_CLONE);
+        ptat = ptaaGetPta(recog->ptaa_u, i, L_CLONE);
+        nsamp = pixaGetCount(pixat);
+        nsamp = L_MIN(nsamp, 256);  /* we only use the first 256 */
+        if (nsamp == 0) {  /* no information for this class */
+            pix1 = pixCreate(1, 1, 1);
+            pixaAddPix(recog->pixa_u, pix1, L_INSERT);
+            ptaAddPt(recog->pta_u, 0, 0);
+            numaAddNumber(recog->nasum_u, 0);
+        } else {
+            pixaAccumulateSamples(pixat, ptat, &pix1, &x, &y);
+            nsamp = (nsamp == 1) ? 2 : nsamp;  /* special case thresh */
+            pix2 = pixThresholdToBinary(pix1, nsamp / 2);
+            pixInvert(pix2, pix2);
+            pixaAddPix(recog->pixa_u, pix2, L_INSERT);
+            ptaAddPt(recog->pta_u, x, y);
+            pixCountPixels(pix2, &area, recog->sumtab);
+            numaAddNumber(recog->nasum_u, area);  /* foreground */
+            pixDestroy(&pix1);
+        }
+        pixaDestroy(&pixat);
+        ptaDestroy(&ptat);
+    }
+
+        /* Any classes for which there are no samples will have a 1x1
+         * pix as a placeholder.  This must not be included when
+         * finding the size range of the averaged templates. */
+    pixa_sel = pixaSelectBySize(recog->pixa_u, 5, 5, L_SELECT_IF_BOTH,
+                                L_SELECT_IF_GTE, NULL);
+    pixaSizeRange(pixa_sel, &recog->minwidth_u, &recog->minheight_u,
+                  &recog->maxwidth_u, &recog->maxheight_u);
+    pixaDestroy(&pixa_sel);
+
+        /* Scaled bitmaps: compute averaged bitmap, centroid, and fg area */
+    for (i = 0; i < size; i++) {
+        pixat = pixaaGetPixa(recog->pixaa, i, L_CLONE);
+        ptat = ptaaGetPta(recog->ptaa, i, L_CLONE);
+        nsamp = pixaGetCount(pixat);
+        nsamp = L_MIN(nsamp, 256);  /* we only use the first 256 */
+        if (nsamp == 0) {  /* no information for this class */
+            pix1 = pixCreate(1, 1, 1);
+            pixaAddPix(recog->pixa, pix1, L_INSERT);
+            ptaAddPt(recog->pta, 0, 0);
+            numaAddNumber(recog->nasum, 0);
+        } else {
+            pixaAccumulateSamples(pixat, ptat, &pix1, &x, &y);
+            nsamp = (nsamp == 1) ? 2 : nsamp;  /* special case thresh */
+            pix2 = pixThresholdToBinary(pix1, nsamp / 2);
+            pixInvert(pix2, pix2);
+            pixaAddPix(recog->pixa, pix2, L_INSERT);
+            ptaAddPt(recog->pta, x, y);
+            pixCountPixels(pix2, &area, recog->sumtab);
+            numaAddNumber(recog->nasum, area);  /* foreground */
+            pixDestroy(&pix1);
+        }
+        pixaDestroy(&pixat);
+        ptaDestroy(&ptat);
+    }
+    pixa_sel = pixaSelectBySize(recog->pixa, 5, 5, L_SELECT_IF_BOTH,
+                                L_SELECT_IF_GTE, NULL);
+    pixaSizeRange(pixa_sel, &recog->minwidth, NULL, &recog->maxwidth, NULL);
+    pixaDestroy(&pixa_sel);
+
+       /* Get min and max splitting dimensions */
+    recog->min_splitw = L_MAX(5, recog->minwidth_u - 5);
+    recog->min_splith = L_MAX(5, recog->minheight_u - 5);
+    recog->max_splith = recog->maxheight_u + 12;  /* allow for skew */
+
+    if (debug)
+        recogShowAverageTemplates(recog);
+
+    recog->ave_done = TRUE;
+    return 0;
+}
+
+
+/*!
+ *  pixaAccumulateSamples()
+ *
+ *      Input:  pixa (of samples from the same class, 1 bpp)
+ *              pta (<optional> of centroids of the samples)
+ *              &ppixd (<return> accumulated samples, 8 bpp)
+ *              &px (<optional return> average x coordinate of centroids)
+ *              &py (<optional return> average y coordinate of centroids)
+ *      Return: 0 on success, 1 on failure
+ *
+ *  Notes:
+ *      (1) This generates an aligned (by centroid) sum of the input pix.
+ *      (2) We use only the first 256 samples; that's plenty.
+ *      (3) If pta is not input, we generate two tables, and discard
+ *          after use.  If this is called many times, it is better
+ *          to precompute the pta.
+ */
+l_int32
+pixaAccumulateSamples(PIXA       *pixa,
+                      PTA        *pta,
+                      PIX       **ppixd,
+                      l_float32  *px,
+                      l_float32  *py)
+{
+l_int32    i, n, maxw, maxh, xdiff, ydiff;
+l_int32   *centtab, *sumtab;
+l_float32  x, y, xave, yave;
+PIX       *pix1, *pix2, *pixsum;
+PTA       *ptac;
+
+    PROCNAME("pixaAccumulateSamples");
+
+    if (px) *px = 0;
+    if (py) *py = 0;
+    if (!ppixd)
+        return ERROR_INT("&pixd not defined", procName, 1);
+    *ppixd = NULL;
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    n = pixaGetCount(pixa);
+    if (pta && ptaGetCount(pta) != n)
+        return ERROR_INT("pta count differs from pixa count", procName, 1);
+    n = L_MIN(n, 256);  /* take the first 256 only */
+    if (n == 0)
+        return ERROR_INT("pixa array empty", procName, 1);
+
+    if (pta) {
+        ptac = ptaClone(pta);
+    } else {  /* generate them here */
+        ptac = ptaCreate(n);
+        centtab = makePixelCentroidTab8();
+        sumtab = makePixelSumTab8();
+        for (i = 0; i < n; i++) {
+            pix1 = pixaGetPix(pixa, i, L_CLONE);
+            pixCentroid(pix1, centtab, sumtab, &xave, &yave);
+            ptaAddPt(ptac, xave, yave);
+        }
+        LEPT_FREE(centtab);
+        LEPT_FREE(sumtab);
+    }
+
+        /* Find the average value of the centroids */
+    xave = yave = 0;
+    for (i = 0; i < n; i++) {
+        ptaGetPt(pta, i, &x, &y);
+        xave += x;
+        yave += y;
+    }
+    xave = xave / (l_float32)n;
+    yave = yave / (l_float32)n;
+    if (px) *px = xave;
+    if (py) *py = yave;
+
+        /* Place all centroids at their average value and sum the results */
+    pixaSizeRange(pixa, NULL, NULL, &maxw, &maxh);
+    pixsum = pixInitAccumulate(maxw, maxh, 0);
+    pix1 = pixCreate(maxw, maxh, 1);
+
+    for (i = 0; i < n; i++) {
+        pix2 = pixaGetPix(pixa, i, L_CLONE);
+        ptaGetPt(ptac, i, &x, &y);
+        xdiff = (l_int32)(x - xave);
+        ydiff = (l_int32)(y - yave);
+        pixClearAll(pix1);
+        pixRasterop(pix1, xdiff, ydiff, maxw, maxh, PIX_SRC,
+                    pix2, 0, 0);
+        pixAccumulate(pixsum, pix1, L_ARITH_ADD);
+        pixDestroy(&pix2);
+    }
+    *ppixd = pixFinalAccumulate(pixsum, 0, 8);
+
+    pixDestroy(&pix1);
+    pixDestroy(&pixsum);
+    ptaDestroy(&ptac);
+    return 0;
+}
+
+
+/*!
+ *  recogTrainingFinished()
+ *
+ *      Input:  recog
+ *              debug
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This must be called after all training samples have been added.
+ *      (2) Set debug = 1 to view the resulting templates
+ *          and their centroids.
+ *      (3) The following things are done here:
+ *          (a) Allocate (or reallocate) storage for (possibly) scaled
+ *              bitmaps, centroids, and fg areas.
+ *          (b) Generate the (possibly) scaled bitmaps.
+ *          (c) Compute centroid and fg area data for both unscaled and
+ *              scaled bitmaps.
+ *          (d) Compute the averages for both scaled and unscaled bitmaps
+ *          (e) Truncate the pixaa, ptaa and numaa arrays down from
+ *              256 to the actual size.
+ *      (4) Putting these operations here makes it simple to recompute
+ *          the recog with different scaling on the bitmaps.
+ *      (5) Removal of outliers must happen after this is called.
+ */
+l_int32
+recogTrainingFinished(L_RECOG  *recog,
+                      l_int32   debug)
+{
+l_int32    i, j, size, nc, ns, area;
+l_float32  xave, yave;
+PIX       *pix, *pixd;
+PIXA      *pixa;
+PIXAA     *paa;
+PTA       *pta;
+PTAA      *ptaa;
+
+    PROCNAME("recogTrainingFinished");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (recog->train_done) return 0;
+
+        /* Generate the storage for the possibly-scaled training bitmaps */
+    size = recog->maxarraysize;
+    paa = pixaaCreate(size);
+    pixa = pixaCreate(1);
+    pixaaInitFull(paa, pixa);
+    pixaDestroy(&pixa);
+    pixaaDestroy(&recog->pixaa);
+    recog->pixaa = paa;
+
+        /* Generate the storage for the unscaled centroid training data */
+    ptaa = ptaaCreate(size);
+    pta = ptaCreate(0);
+    ptaaInitFull(ptaa, pta);
+    ptaaDestroy(&recog->ptaa_u);
+    recog->ptaa_u = ptaa;
+
+        /* Generate the storage for the possibly-scaled centroid data */
+    ptaa = ptaaCreate(size);
+    ptaaInitFull(ptaa, pta);
+    ptaDestroy(&pta);
+    ptaaDestroy(&recog->ptaa);
+    recog->ptaa = ptaa;
+
+        /* Generate the storage for the fg area data */
+    numaaDestroy(&recog->naasum_u);
+    numaaDestroy(&recog->naasum);
+    recog->naasum_u = numaaCreateFull(size, 0);
+    recog->naasum = numaaCreateFull(size, 0);
+
+    paa = recog->pixaa_u;
+    nc = recog->setsize;
+    for (i = 0; i < nc; i++) {
+        pixa = pixaaGetPixa(paa, i, L_CLONE);
+        ns = pixaGetCount(pixa);
+        for (j = 0; j < ns; j++) {
+                /* Save centroid and area data for the unscaled pix */
+            pix = pixaGetPix(pixa, j, L_CLONE);
+            pixCentroid(pix, recog->centtab, recog->sumtab, &xave, &yave);
+            ptaaAddPt(recog->ptaa_u, i, xave, yave);
+            pixCountPixels(pix, &area, recog->sumtab);
+            numaaAddNumber(recog->naasum_u, i, area);  /* foreground */
+
+                /* Insert the (optionally) scaled character image, and
+                 * save centroid and area data for it */
+            pixd = recogScaleCharacter(recog, pix);
+            pixaaAddPix(recog->pixaa, i, pixd, NULL, L_INSERT);
+            pixCentroid(pixd, recog->centtab, recog->sumtab, &xave, &yave);
+            ptaaAddPt(recog->ptaa, i, xave, yave);
+            pixCountPixels(pixd, &area, recog->sumtab);
+            numaaAddNumber(recog->naasum, i, area);
+            pixDestroy(&pix);
+        }
+        pixaDestroy(&pixa);
+    }
+
+        /* Get the template averages */
+    recog->ave_done = FALSE;
+    recogAverageSamples(recog, debug);
+
+        /* Truncate the arrays to those with non-empty containers */
+    pixaaTruncate(recog->pixaa_u);
+    pixaaTruncate(recog->pixaa);
+    ptaaTruncate(recog->ptaa_u);
+    ptaaTruncate(recog->ptaa);
+    numaaTruncate(recog->naasum_u);
+    numaaTruncate(recog->naasum);
+
+    recog->train_done = TRUE;
+    return 0;
+}
+
+
+/*!
+ *  recogRemoveOutliers()
+ *
+ *      Input:  recog (after training samples are entered)
+ *              targetscore (keep everything with at least this score)
+ *              minfract (minimum fraction to retain)
+ *              debug (1 for debug output)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Removing outliers is particularly important when recognition
+ *          goes against all the samples in the training set, as opposed
+ *          to the averages for each class.  The reason is that we get
+ *          an identification error if a mislabeled sample is a best
+ *          match for an input bitmap.
+ *      (2) However, the score values depend strongly on the quality
+ *          of the character images.  To avoid losing too many samples,
+ *          we supplement a target score for retention with a minimum
+ *          fraction that we must keep.  With poor quality images, we
+ *          may keep samples with a score less than the targetscore,
+ *          in order to satisfy the @minfract requirement.
+ *      (3) We always require that at least one sample will be retained.
+ *      (4) Where the training set is from the same source (e.g., the
+ *          same book), use a relatively large minscore; say, ~0.8.
+ *      (5) Method: for each class, generate the averages and match each
+ *          scaled sample against the average.  Decide which
+ *          samples will be ejected, and throw out both the
+ *          scaled and unscaled samples and associated data.
+ *          Recompute the average without the poor matches.
+ */
+l_int32
+recogRemoveOutliers(L_RECOG    *recog,
+                    l_float32   targetscore,
+                    l_float32   minfract,
+                    l_int32     debug)
+{
+l_int32    i, j, nremoved, n, nkeep, ngood, ival, area1, area2;
+l_float32  x1, y1, x2, y2, score, val;
+NUMA      *nasum, *nasum_u, *nascore, *nainvert, *nasort;
+PIX       *pix1, *pix2;
+PIXA      *pixa, *pixa_u;
+PTA       *pta, *pta_u;
+
+    PROCNAME("recogRemoveOutliers");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (recog->train_done == FALSE)
+        return ERROR_INT("recog training is not finished", procName, 1);
+    targetscore = L_MIN(targetscore, 1.0);
+    if (targetscore <= 0.0)
+        targetscore = DEFAULT_TARGET_SCORE;
+    minfract = L_MIN(minfract, 1.0);
+    if (minfract <= 0.0)
+        minfract = DEFAULT_MIN_FRACTION;
+
+    nremoved = 0;
+    for (i = 0; i < recog->setsize; i++) {
+            /* Access the average template and values for scaled
+             * images in this class */
+        pix1 = pixaGetPix(recog->pixa, i, L_CLONE);
+        ptaGetPt(recog->pta, i, &x1, &y1);
+        numaGetIValue(recog->nasum, i, &area1);
+
+            /* Get the sorted scores for each sample in the class */
+        pixa = pixaaGetPixa(recog->pixaa, i, L_CLONE);
+        pta = ptaaGetPta(recog->ptaa, i, L_CLONE);
+        nasum = numaaGetNuma(recog->naasum, i, L_CLONE);
+        n = pixaGetCount(pixa);
+        nascore = numaCreate(n);
+        for (j = 0; j < n; j++) {
+            pix2 = pixaGetPix(pixa, j, L_CLONE);
+            ptaGetPt(pta, j, &x2, &y2);
+            numaGetIValue(nasum, j, &area2);
+            pixCorrelationScoreSimple(pix1, pix2, area1, area2,
+                                      x1 - x2, y1 - y2, 5, 5,
+                                      recog->sumtab, &score);
+            numaAddNumber(nascore, score);
+            if (score == 0.0)  /* typ. large size difference */
+                fprintf(stderr, "Got 0 score for i = %d, j = %d\n", i, j);
+            pixDestroy(&pix2);
+        }
+        pixDestroy(&pix1);
+            /* Symbolically, na[i] = nasort[nainvert[i]]  */
+        numaSortGeneral(nascore, &nasort, NULL, &nainvert,
+                        L_SORT_DECREASING, L_SHELL_SORT);
+
+            /* Determine the cutoff in samples to keep */
+        nkeep = (l_int32)(minfract * n + 0.5);
+        ngood = n;
+        for (j = 0; j < n; j++) {
+            numaGetFValue(nasort, j, &val);
+            if (val < targetscore) {
+                ngood = j + 1;
+                break;
+            }
+        }
+        nkeep = L_MAX(1, L_MAX(nkeep, ngood));
+        nremoved += (n - nkeep);
+        if (debug && nkeep < n) {
+            fprintf(stderr, "Removing %d of %d items from class %d\n",
+                    n - nkeep, n, i);
+        }
+
+            /* Remove the samples with low scores.  Iterate backwards
+             * in the original arrays, because we're compressing them
+             * in place as elements are removed, and we must preserve
+             * the indexing of elements not yet removed. */
+        if (nkeep < n) {
+            pixa_u = pixaaGetPixa(recog->pixaa_u, i, L_CLONE);
+            pta_u = ptaaGetPta(recog->ptaa_u, i, L_CLONE);
+            nasum_u = numaaGetNuma(recog->naasum_u, i, L_CLONE);
+            for (j = n - 1; j >= 0; j--) {
+                    /* ival is nainvert[j], which is the index into
+                     * nasort that corresponds to the same element in
+                     * na that is indexed by j (i.e., na[j]).  We retain
+                     * the first nkeep elements in nasort. */
+                numaGetIValue(nainvert, j, &ival);
+                if (ival < nkeep) continue;
+                pixaRemovePix(pixa, j);
+                ptaRemovePt(pta, j);
+                numaRemoveNumber(nasum, j);
+                pixaRemovePix(pixa_u, j);
+                ptaRemovePt(pta_u, j);
+                numaRemoveNumber(nasum_u, j);
+                if (debug) {
+                    numaGetFValue(nascore, j, &val);
+                    fprintf(stderr,
+                            " removed item %d: score %7.3f\n", ival, val);
+                }
+            }
+            pixaDestroy(&pixa_u);
+            ptaDestroy(&pta_u);
+            numaDestroy(&nasum_u);
+        }
+
+        pixaDestroy(&pixa);
+        ptaDestroy(&pta);
+        numaDestroy(&nasum);
+        numaDestroy(&nascore);
+        numaDestroy(&nainvert);
+        numaDestroy(&nasort);
+    }
+
+        /* If anything was removed, recompute the average templates */
+    if (nremoved > 0) {
+        recog->num_samples -= nremoved;
+        recog->ave_done = FALSE;  /* force recomputation */
+        recogAverageSamples(recog, debug);
+    }
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                        Evaluate training status                        *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogaTrainingDone()
+ *
+ *      Input:  recoga
+ *              &done (<return> 1 if training finished on all recog; else 0)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogaTrainingDone(L_RECOGA  *recoga,
+                   l_int32   *pdone)
+{
+l_int32   i;
+L_RECOG  *recog;
+
+    PROCNAME("recogaTrainingDone");
+
+    if (!pdone)
+        return ERROR_INT("&done not defined", procName, 1);
+    *pdone = 0;
+    if (!recoga)
+        return ERROR_INT("recoga not defined", procName, 1);
+
+    for (i = 0; i < recoga->n; i++) {
+        if ((recog = recogaGetRecog(recoga, i)) == NULL)
+            return ERROR_INT("recog not found", procName, 1);
+        if (!recog->train_done)
+            return 0;
+    }
+
+    *pdone = 1;
+    return 0;
+}
+
+
+/*!
+ *  recogaFinishAveraging()
+ *
+ *      Input:  recoga
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogaFinishAveraging(L_RECOGA  *recoga)
+{
+l_int32   i;
+L_RECOG  *recog;
+
+    PROCNAME("recogaFinishAveraging");
+
+    if (!recoga)
+        return ERROR_INT("recoga not defined", procName, 1);
+
+    for (i = 0; i < recoga->n; i++) {
+        if ((recog = recogaGetRecog(recoga, i)) == NULL)
+            return ERROR_INT("recog not found", procName, 1);
+        if (!recog->ave_done)
+            recogAverageSamples(recog, 0);
+    }
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                       Training on unlabelled data                      *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogTrainUnlabelled()
+ *
+ *      Input:  recog (in training mode: the input characters in pixs are
+ *                     inserted after labelling)
+ *              recogboot (labels the input)
+ *              pixs (if depth > 1, will be thresholded to 1 bpp)
+ *              box (<optional> cropping box)
+ *              singlechar (1 if pixs is a single character; 0 otherwise)
+ *              minscore (min score for accepting the example; e.g., 0.75)
+ *              debug (1 for debug output saved to recog; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This trains on one or several characters of unlabelled data,
+ *          using a bootstrap recognizer to apply the labels.  In this
+ *          way, we can build a recognizer using a source of unlabelled data.
+ *      (2) The input pix can have several (non-touching) characters.
+ *          If box != NULL, we treat the region in the box as a single char
+ *          If box == NULL, use all of pixs:
+ *             if singlechar == 0, we identify each c.c. as a single character
+ *             if singlechar == 1, we treat pixs as a single character
+ *          Multiple chars are identified separately by recogboot and
+ *          inserted into recog.
+ *      (3) recogboot is a trained recognizer.  It would typically be
+ *          constructed from a variety of sources, and use the individual
+ *          templates (not the averages) for scoring.  It must be used
+ *          in scaled mode; typically with width = 20 and height = 32.
+ *      (4) For debugging, if bmf is defined in the recog, the correlation
+ *          scores are generated and saved (by adding to the pixadb_boot
+ *          field) with the matching images.
+ */
+l_int32
+recogTrainUnlabelled(L_RECOG   *recog,
+                     L_RECOG   *recogboot,
+                     PIX       *pixs,
+                     BOX       *box,
+                     l_int32    singlechar,
+                     l_float32  minscore,
+                     l_int32    debug)
+{
+char      *text;
+l_float32  score;
+NUMA      *nascore, *na;
+PIX       *pixc, *pixb, *pixdb;
+PIXA      *pixa, *pixaf;
+
+    PROCNAME("recogTrainUnlabelled");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!recogboot)
+        return ERROR_INT("recogboot not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+        /* Optionally crop */
+    if (box)
+        pixc = pixClipRectangle(pixs, box, NULL);
+    else
+        pixc = pixClone(pixs);
+
+        /* Binarize if necessary */
+    if (pixGetDepth(pixc) > 1)
+        pixb = pixConvertTo1(pixc, recog->threshold);
+    else
+        pixb = pixClone(pixc);
+    pixDestroy(&pixc);
+
+        /* Identify the components using recogboot */
+    if (singlechar == 1) {
+        if (!debug) {
+            recogIdentifyPix(recogboot, pixb, NULL);
+        } else {
+            recogIdentifyPix(recogboot, pixb, &pixdb);
+            pixaAddPix(recog->pixadb_boot, pixdb, L_INSERT);
+        }
+        rchExtract(recogboot->rch, NULL, &score, &text, NULL, NULL, NULL, NULL);
+
+            /* Threshold based on the score, and insert in a pixa */
+        pixaf = pixaCreate(1);
+        if (score >= minscore) {
+            pixSetText(pixb, text);
+            pixaAddPix(pixaf, pixb, L_CLONE);
+            LEPT_FREE(text);
+                /* In use pixs is "unlabelled", so we only find a text
+                 * string in the input pixs when testing with labelled data. */
+            if (debug && ((text = pixGetText(pixs)) != NULL))
+                L_INFO("Testing: input pix has character label: %s\n",
+                       procName, text);
+        }
+    } else {  /* possibly multiple characters */
+            /* Split into characters */
+        pixSplitIntoCharacters(pixb, 5, 5, NULL, &pixa, NULL);
+
+        if (!debug) {
+            recogIdentifyPixa(recogboot, pixa, NULL, NULL);
+        } else {
+            recogIdentifyPixa(recogboot, pixa, NULL, &pixdb);
+            pixaAddPix(recog->pixadb_boot, pixdb, L_INSERT);
+        }
+            /* Threshold the pixa based on the score */
+        rchaExtract(recogboot->rcha, NULL, &nascore, NULL, NULL, NULL,
+                    NULL, NULL);
+        na = numaMakeThresholdIndicator(nascore, minscore, L_SELECT_IF_GTE);
+        pixaf = pixaSelectWithIndicator(pixa, na, NULL);
+        pixaDestroy(&pixa);
+        numaDestroy(&nascore);
+        numaDestroy(&na);
+    }
+    pixDestroy(&pixb);
+
+        /* Insert the labelled components */
+    recogAddSamples(recog, pixaf, -1, debug);
+    pixaDestroy(&pixaf);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                         Padding the training set                       *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogPadTrainingSet()
+ *
+ *      Input:  &recog (to be replaced if padding or more drastic measures
+ *                      are necessary; otherwise, it is unchanged.)
+ *              debug (1 for debug output saved to recog; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function does either padding of the recognizer, or
+ *          its complete replacement.  In both cases, we use a "boot"
+ *          recognizer to provide the sample images.
+ *      (2) Before calling this, call recogSetPadParams() if you want
+ *          non-default values for the character set type, min_nopad
+ *          and max_afterpad values, paths for labelled bitmap
+ *          character sets that can be used to augment an input
+ *          recognizer, and optional augmentation of the input
+ *          training set using eroded versions of the bitmaps.
+ *      (3) If all classes in @recog have at least min_nopad samples,
+ *          nothing is done.  If the total number of samples in @recog
+ *          is very small, @recog is replaced in its entirety by a "boot"
+ *          recog, either from the specified bootpath, or by a default
+ *          boot recognizer for that character type.  Otherwise (the
+ *          intermediate case), @recog is replaced by one with scaling
+ *          to fixed height, where an array of recog are used to
+ *          augment the input recog.
+ *      (4) If padding or total replacement is done, this destroys
+ *          the input recog and replaces it by a new one.  If the recog
+ *          belongs to a recoga, the replacement is also done in the recoga.
+ */
+l_int32
+recogPadTrainingSet(L_RECOG  **precog,
+                    l_int32    debug)
+{
+const char  *bootdir, *bootpattern, *bootpath;
+char        *boottext;
+l_int32      i, k, min_nopad, npix, nclass, nboot, nsamp, nextra, ntoadd;
+l_int32      ave_height, targeth, setid, index, allclasses;
+l_int32     *lut;
+l_float32    minval, sum;
+NUMA        *naclass, *naheight, *naset, *naindex, *nascore, *naave;
+PIX         *pix1, *pix2;
+PIXA        *pixaboot1, *pixaboot2, *pixa1, *pixa2, *pixadb;
+PIXAA       *paa, *paa1;
+L_RECOG     *rec1, *recog, *recogboot;
+L_RECOGA    *recoga;
+
+    PROCNAME("recogPadTrainingSet");
+
+    if (!precog)
+        return ERROR_INT("&recog not defined", procName, 1);
+    if ((recog = *precog) == NULL)
+        return ERROR_INT("recog not defined", procName, 1);
+
+    /* ---------------------------------------------------------
+     * If there are too few characters in the input recog, don't
+     * bother with padding.  Destroy the input recog and return a
+     * boot recognizer that will be run using scaling in both width
+     * and height.  This recognizer can be used to train a
+     * book-adapted recognizer.  If no boot recognizer is specified,
+     * use the compiled generic digit one for now.
+     * ----------------------------------------------------------*/
+    if (recog->num_samples < recog->min_samples) {
+        L_WARNING("too few samples in recog; using bootrecog only\n", procName);
+        if (recogCharsetAvailable(recog->charset_type) == FALSE)
+            return ERROR_INT("boot charset type not available", procName, 1);
+        if ((bootpath = recog->bootpath) == NULL) {
+            L_INFO("no boot path; using generic digits\n", procName);
+            pixaboot1 = (PIXA *)l_bootnum_gen1();
+        } else {
+            L_INFO("boot path = %s\n", procName, bootpath);
+            if ((pixaboot1 = pixaRead(bootpath)) == NULL)
+                return ERROR_INT("pixaboot not read", procName, 1);
+        }
+        pixaboot2 = pixaExtendIterative(pixaboot1, L_MORPH_ERODE,
+                                        recog->boot_iters, NULL, 1);
+        rec1 = recogCreateFromPixa(pixaboot2, 20, 32, L_USE_AVERAGE, 100, 1);
+        recogReplaceInRecoga(&recog, rec1);  /* destroys recog */
+        *precog = rec1;
+        pixaDestroy(&pixaboot1);
+        pixaDestroy(&pixaboot2);
+        return 0;
+    }
+
+    /* ---------------------------------------------------
+     * Test if we need to do anything here: are we asking
+     * for padding, or do we have enough samples already?
+     * --------------------------------------------------- */
+    min_nopad = recog->min_nopad;
+    if (min_nopad <= 0)
+        return 0;
+
+        /* Do we have samples from all classes? */
+    paa = recog->pixaa_u;  /* unscaled bitmaps */
+    nclass = pixaaGetCount(paa, &naclass);
+    allclasses = (nclass == recog->charset_size) ? 1 : 0;
+
+        /* Are there enough samples in each class already? */
+    numaGetMin(naclass, &minval, NULL);
+    numaDestroy(&naclass);
+    if (allclasses && minval >= min_nopad)
+        return 0;
+
+    /* ---------------------------------------------------------
+     * We need to pad the input recog.  Do this with an array of
+     * boot recog with different 'fonts'.  For each class that must
+     * be padded with samples, choose the boot recog from the recog
+     * array that has the best correlation to samples of that class.
+     * --------------------------------------------------------- */
+         /* Do we have boot recog for this charset?
+          * TODO: add some more of 'em */
+    if (recogCharsetAvailable(recog->charset_type) == FALSE)
+        return ERROR_INT("charset type not available", procName, 1);
+    bootdir = recog->bootdir;
+    bootpattern = recog->bootpattern;
+    L_INFO("dir = %s; pattern = %s\n", procName, bootdir, bootpattern);
+    L_INFO("min_nopad = %d; max_afterpad = %d\n", procName,
+           min_nopad, recog->max_afterpad);
+
+        /* To pad the input recognizer, use an array of recog, generated
+         * from pixa with files specified by bootdir and bootpattern.
+         * The samples are scaled to h = 32 to allow correlation with the
+         * averages from a copy of the input recog, also scaled to h = 32. */
+    if ((paa1 = pixaaReadFromFiles(bootdir, bootpattern, 0, 0)) == NULL)
+        return ERROR_INT("boot recog files not found", procName, 1);
+    recoga = recogaCreateFromPixaa(paa1, 0, 32, L_USE_AVERAGE, 100, 1);
+    pixaaDestroy(&paa1);
+    if (!recoga)
+        return ERROR_INT("recoga not made", procName, 1);
+
+        /* The parameters of the input recog must match those of the
+         * boot array.  Replace the input recog with a new one, that
+         * uses the average templates for matching, scaled to h = 32. */
+    rec1 = recogCreateFromRecog(recog, 0, 32, L_USE_AVERAGE, 100, 1);
+    recogReplaceInRecoga(&recog, rec1);  /* destroys recog */
+    *precog = rec1;
+    recog = rec1;
+
+        /* Now for each class in the recog, decide which recog in recoga
+         * should be used to select samples for padding the recog. */
+    pixadb = (debug) ? pixaCreate(0) : NULL;
+    recogBestCorrelForPadding(rec1, recoga, &naset, &naindex, &nascore,
+                              &naave, pixadb);
+    if (pixadb) {
+        lept_mkdir("lept/recog");
+        numaWriteStream(stderr, naset);
+        numaWriteStream(stderr, naindex);
+        numaWriteStream(stderr, nascore);
+        numaWriteStream(stderr, naave);
+        pix1 = pixaDisplayLinearly(pixadb, L_VERT, 1.0, 0, 20, 0, NULL);
+        pixWrite("/tmp/lept/recog/padmatch.png", pix1, IFF_PNG);
+        pixDestroy(&pix1);
+        pixaDestroy(&pixadb);
+    }
+
+        /* Allow more examples to be added to the input/returned recog */
+    recog->train_done = FALSE;
+
+    /* ---------------------------------------------------------
+     * For the existing classes in recog, add samples from the boot
+     * recognizer where needed.  For each sample, scale isotropically
+     * to the average unscaled height for the given class.
+     * ----------------------------------------------------------*/
+    recogAverageClassGeom(recog, NULL, &naheight);
+    numaGetSum(naheight, &sum);
+    paa = recog->pixaa_u;
+    pixaaGetCount(paa, &naclass);
+    ave_height = (l_int32)(sum / nclass);
+    for (i = 0; i < nclass; i++) {
+        numaGetIValue(naclass, i, &npix);
+        if (npix >= min_nopad) continue;
+        numaGetIValue(naheight, i, &targeth);
+
+            /* Locate the images to be added */
+        numaGetIValue(naset, i, &setid);
+        if ((rec1 = recogaGetRecog(recoga, setid)) == NULL) {
+            L_ERROR("For class %d, didn't find recog %d\n", procName, i, setid);
+            continue;
+        }
+        numaGetIValue(naindex, i, &index);  /* class index in rec */
+        if ((pixa1 = pixaaGetPixa(rec1->pixaa_u, index, L_CLONE)) == NULL) {
+            L_ERROR("For recog %d, didn't find class %d\n", procName,
+                    setid, index);
+            continue;
+        }
+
+            /* Decide how many of them to scale and add */
+        nboot = pixaGetCount(pixa1);
+        nextra = recog->max_afterpad - npix;
+        if (nextra <= 0) continue;  /* this should not be triggered */
+        ntoadd = L_MIN(nextra, nboot);
+        L_INFO("For class %d, using %d samples from index %d in recog %d\n",
+               procName, i, ntoadd, index, setid);
+
+            /* Add them */
+        pixa2 = pixaCreate(ntoadd);
+        boottext = sarrayGetString(rec1->sa_text, index, L_NOCOPY);
+        for (k = 0; k < ntoadd; k++) {
+             pix1 = pixaGetPix(pixa1, k, L_CLONE);
+             pix2 = pixScaleToSize(pix1, 0, targeth);
+             pixSetText(pix2, boottext);
+             pixaAddPix(pixa2, pix2, L_INSERT);
+             pixDestroy(&pix1);
+        }
+        recogAddSamples(recog, pixa2, i, 0);
+        pixaDestroy(&pixa1);
+        pixaDestroy(&pixa2);
+    }
+
+    /* ---------------------------------------------------------------
+     * Check if there are missing classes.  If there are, add these
+     * classes and use samples from the bootrecog with the highest
+     * overall correlation with the input recog.  Use the average
+     * unscaled height of all the recog classes, which should be fine
+     * for all-caps, where character heights are similar, but may give
+     * bad estimates for old digit fonts where the digits "0", "1" and
+     * "2" are often shorter.
+     * ------------------------------------------------------------ */
+    numaGetMax(naave, NULL, &index);  /* best of the recoga set */
+    recogboot = recogaGetRecog(recoga, index);
+    nboot = recogGetCount(recogboot);
+    L_INFO("nboot = %d, nclass = %d, best index = %d\n",
+           procName, nboot, nclass, index);
+    if (nboot > nclass) {  /* missing some classes in recog */
+        L_INFO("Adding %d classes to the recog\n", procName, nboot - nclass);
+        targeth = ave_height;
+        if ((lut = recogMapIndexToIndex(recogboot, recog)) == NULL)
+            return ERROR_INT("index-to-index lut not made", procName, 1);
+        for (i = 0; i < nboot; i++) {
+            if (lut[i] >= 0)  /* already have this class */
+                continue;
+            pixaboot1 = pixaaGetPixa(recogboot->pixaa_u, i, L_CLONE);
+            nsamp = pixaGetCount(pixaboot1);
+            ntoadd = L_MIN(recog->max_afterpad, nsamp);
+            pixa1 = pixaCreate(ntoadd);
+            boottext = sarrayGetString(recogboot->sa_text, i, L_NOCOPY);
+            L_INFO("Adding %d chars of type '%s' from recog %d\n", procName,
+                   ntoadd, boottext, index);
+            for (k = 0; k < ntoadd; k++) {
+                 pix1 = pixaGetPix(pixaboot1, k, L_CLONE);
+                 pix2 = pixScaleToSize(pix1, 0, targeth);
+                 pixSetText(pix2, boottext);
+                 pixaAddPix(pixa1, pix2, L_INSERT);
+                 pixDestroy(&pix1);
+            }
+            recogAddSamples(recog, pixa1, -1, debug);
+            pixaDestroy(&pixaboot1);
+            pixaDestroy(&pixa1);
+        }
+        LEPT_FREE(lut);
+    }
+    recogTrainingFinished(recog, 0);
+
+    if (debug) {
+        recogShowContent(stderr, recog, 1);
+        recogDebugAverages(recog, 1);
+    }
+
+    numaDestroy(&naclass);
+    numaDestroy(&naheight);
+    numaDestroy(&naset);
+    numaDestroy(&naindex);
+    numaDestroy(&nascore);
+    numaDestroy(&naave);
+    recogaDestroy(&recoga);
+    return 0;
+}
+
+
+/*!
+ *  recogMapIndexToIndex()
+ *
+ *      Input:  recog1
+ *              recog2
+ *      Return: lut (from recog1 --> recog2), or null on error
+ *
+ *  Notes:
+ *      (1) This returns a map from each index in recog1 to the
+ *          corresponding index in recog2.  Caller must free.
+ *      (2) If the character string doesn't exist in any of the classes
+ *          in recog2, the value -1 is inserted in the lut.
+ */
+static l_int32 *
+recogMapIndexToIndex(L_RECOG  *recog1,
+                     L_RECOG  *recog2)
+{
+char     *charstr;
+l_int32   index1, index2, n1;
+l_int32  *lut;
+
+    PROCNAME("recogMapIndexToIndex");
+
+    if (!recog1 || !recog2)
+        return (l_int32 *)ERROR_PTR("recog1 and recog2 not both defined",
+                                    procName, NULL);
+
+    n1 = recog1->setsize;
+    if ((lut = (l_int32 *)LEPT_CALLOC(n1, sizeof(l_int32))) == NULL)
+        return (l_int32 *)ERROR_PTR("lut not made", procName, NULL);
+    for (index1 = 0; index1 < n1; index1++) {
+        recogGetClassString(recog1, index1, &charstr);
+        if (!charstr) {
+            L_ERROR("string not found for index %d\n", procName, index1);
+            lut[index1] = -1;
+            continue;
+        }
+        recogStringToIndex(recog2, charstr, &index2);
+        lut[index1] = index2;
+        LEPT_FREE(charstr);
+    }
+
+    return lut;
+}
+
+
+/*!
+ *  recogAverageClassGeom()
+ *
+ *      Input:  recog
+ *              &naw (<optional return> average widths for each class)
+ *              &nah (<optional return> average heights for each class)
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+recogAverageClassGeom(L_RECOG  *recog,
+                      NUMA    **pnaw,
+                      NUMA    **pnah)
+{
+l_int32  i, j, w, h, sumw, sumh, npix, nclass;
+NUMA    *naw, *nah;
+PIXA    *pixa;
+
+    PROCNAME("recogAverageClassGeom");
+
+    if (pnaw) *pnaw = NULL;
+    if (pnah) *pnah = NULL;
+    if (!pnaw && !pnah)
+        return ERROR_INT("no output requested", procName, 1);
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+
+    if ((nclass = pixaaGetCount(recog->pixaa_u, NULL)) == 0)
+        return ERROR_INT("no classes", procName, 1);
+    naw = numaCreate(nclass);
+    nah = numaCreate(nclass);
+    for (i = 0; i < nclass; i++) {
+        if ((pixa = pixaaGetPixa(recog->pixaa_u, i, L_CLONE)) == NULL) {
+            L_WARNING("pixa[%d] not found\n", procName, i);
+            continue;
+        }
+        sumw = sumh = 0;
+        npix = pixaGetCount(pixa);
+        for (j = 0; j < npix; j++) {
+            pixaGetPixDimensions(pixa, j, &w, &h, NULL);
+            sumw += w;
+            sumh += h;
+        }
+        numaAddNumber(naw, (l_int32)((l_float32)sumw / npix + 0.5));
+        numaAddNumber(nah, (l_int32)((l_float32)sumh / npix + 0.5));
+        pixaDestroy(&pixa);
+    }
+
+    if (pnaw)
+        *pnaw = naw;
+    else
+        numaDestroy(&naw);
+    if (pnah)
+        *pnah = nah;
+    else
+        numaDestroy(&nah);
+    return 0;
+}
+
+
+/*!
+ *  recogBestCorrelForPadding()
+ *
+ *      Input:  recog (typically the recog to be padded)
+ *              recoga (array of recogs for potentially providing the padding)
+ *              &naset (<return> of indices into the sets to be matched)
+ *              &naindex (<return> of matching indices into the best set)
+ *              &nascore (<return> of best correlation scores)
+ *              &naave (<return> average of correlation scores from each recog)
+ *              pixadb (<optional> debug images; use NULL for no debug)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This finds, for each class in recog, the best matching template
+ *          in the recoga.  For that best match, it returns:
+ *            * the recog set index in the recoga,
+ *            * the index in that recog for the class,
+ *            * the score for the best match
+ *      (2) It also returns in @naave, for each recog in recoga, the
+ *          average overall correlation for all averaged templates to
+ *          those in the input recog.  The recog with the largest average
+ *          can supply templates in cases where the input recog has
+ *          no examples.
+ *      (3) For classes in recog1 for which no corresponding class
+ *          is found in any recog in recoga, the index -1 is stored
+ *          in both naset and naindex, and 0.0 is stored in nascore.
+ *      (4) Both recog and all the recog in recoga should be generated
+ *          with isotropic scaling to the same character height (e.g., 30).
+ */
+l_int32
+recogBestCorrelForPadding(L_RECOG   *recog,
+                          L_RECOGA  *recoga,
+                          NUMA     **pnaset,
+                          NUMA     **pnaindex,
+                          NUMA     **pnascore,
+                          NUMA     **pnasum,
+                          PIXA      *pixadb)
+{
+l_int32    i, j, n, nrec, index, maxindex, maxset;
+l_float32  score, maxscore;
+NUMA      *nain, *nasc, *naset, *naindex, *nascore, *nasum;
+NUMAA     *naain, *naasc;
+L_RECOG   *rec;
+
+    PROCNAME("recogBestCorrelForPadding");
+
+    if (pnaset) *pnaset = NULL;
+    if (pnaindex) *pnaindex = NULL;
+    if (pnascore) *pnascore = NULL;
+    if (pnasum) *pnasum = NULL;
+    if (!pnaset || !pnaindex || !pnascore || !pnasum)
+        return ERROR_INT("&naset, &naindex, &nasore, &nasum not all defined",
+                         procName, 1);
+    if (!recog)
+        return ERROR_INT("recog is not defined", procName, 1);
+    if (!recoga)
+        return ERROR_INT("recoga is not defined", procName, 1);
+    if (!recog->train_done)
+        return ERROR_INT("recog training is not finished", procName, 1);
+
+        /* Gather the correlation data */
+    n = recog->setsize;
+    nrec = recogaGetCount(recoga);
+    if (n == 0 || nrec == 0)
+        return ERROR_INT("recog or recoga is empty", procName, 1);
+    naain = numaaCreate(nrec);
+    naasc = numaaCreate(nrec);
+    for (i = 0; i < nrec; i++) {
+        rec = recogaGetRecog(recoga, i);
+        recogCorrelAverages(recog, rec, &nain, &nasc, pixadb);
+        numaaAddNuma(naain, nain, L_INSERT);
+        numaaAddNuma(naasc, nasc, L_INSERT);
+    }
+
+        /* Find the best matches */
+    naset = numaCreate(n);
+    naindex = numaCreate(n);
+    nascore = numaCreate(n);
+    nasum = numaMakeConstant(0.0, nrec);  /* accumulate sum over recognizers */
+    for (i = 0; i < n; i++) {  /* over classes in recog */
+        maxscore = 0.0;
+        maxindex = -1;
+        maxset = -1;
+        for (j = 0; j < nrec; j++) {  /* over recognizers */
+            numaaGetValue(naain, j, i, NULL, &index);  /* index in j to i */
+            if (index == -1) continue;
+            numaaGetValue(naasc, j, i, &score, NULL);  /* score in j to i */
+            numaAddToNumber(nasum, j, score);
+            if (score > maxscore) {
+                maxscore = score;
+                maxindex = index;
+                maxset = j;
+            }
+        }
+        numaAddNumber(naset, maxset);
+        numaAddNumber(naindex, maxindex);
+        numaAddNumber(nascore, maxscore);
+    }
+
+    *pnaset = naset;
+    *pnaindex = naindex;
+    *pnascore = nascore;
+    *pnasum = numaTransform(nasum, 0.0, 1. / (l_float32)n);
+    numaDestroy(&nasum);
+    numaaDestroy(&naain);
+    numaaDestroy(&naasc);
+    return 0;
+}
+
+
+/*!
+ *  recogCorrelAverages()
+ *
+ *      Input:  recog1 (typically the recog to be padded)
+ *              recog2 (potentially providing the padding)
+ *              &naindex (<return> of classes in 2 with respect to classes in 1)
+ *              &nascore (<return> correlation scores of corresponding classes)
+ *              pixadb (<optional> debug images)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Use this for potentially padding recog1 with instances in recog2.
+ *          The recog have been generated with isotropic scaling to the
+ *          same fixed height (e.g., 30).  The training has been "finished"
+ *          in the sense that all arrays have been computed and they
+ *          could potentially be used as they are.  This is necessary
+ *          for doing the correlation between scaled images.
+ *          However, this function is called when there is a request to
+ *          augument some of the examples in classes in recog1.
+ *      (2) Iterate over classes in recog1, finding the corresponding
+ *          class in recog2 and computing the correlation score between
+ *          the average templates of the two.  naindex is a LUT between
+ *          the index of a class in recog1 and the corresponding one in recog2.
+ *      (3) For classes in recog1 that do not exist in recog2, the index
+ *          -1 is stored in naindex, and 0.0 is stored in the score.
+ */
+l_int32
+recogCorrelAverages(L_RECOG  *recog1,
+                    L_RECOG  *recog2,
+                    NUMA    **pnaindex,
+                    NUMA    **pnascore,
+                    PIXA     *pixadb)
+{
+l_int32    i1, i2, n1, area1, area2, wvar;
+l_int32   *lut;
+l_float32  x1, y1, x2, y2, score;
+PIX       *pix1, *pix2;
+PIXA      *pixa1;
+
+    PROCNAME("recogCorrelAverages");
+
+    if (pnaindex) *pnaindex = NULL;
+    if (pnascore) *pnascore = NULL;
+    if (!pnaindex || !pnascore)
+        return ERROR_INT("&naindex and &nascore not defined", procName, 1);
+    if (!recog1 || !recog2)
+        return ERROR_INT("recog1 and recog2 not both defined", procName, 1);
+    if (!recog1->train_done || !recog2->train_done)
+        return ERROR_INT("recog training is not finished", procName, 1);
+
+    if ((lut = recogMapIndexToIndex(recog1, recog2)) == NULL)
+        return ERROR_INT("index-to-index lut not made", procName, 1);
+    n1 = recog1->setsize;
+    *pnaindex = numaCreateFromIArray(lut, n1);
+    *pnascore = numaMakeConstant(0.0, n1);
+
+    pixa1 = (pixadb) ? pixaCreate(n1) : NULL;
+    for (i1 = 0; i1 < n1; i1++) {
+            /* Access the average templates and values for this class */
+        if ((i2 = lut[i1]) == -1) {
+            L_INFO("no class in 2 corresponds to index %d in 1\n",
+                   procName, i1);
+            continue;
+        }
+        pix1 = pixaGetPix(recog1->pixa, i1, L_CLONE);
+        ptaGetPt(recog1->pta, i1, &x1, &y1);
+        numaGetIValue(recog1->nasum, i1, &area1);
+        pix2 = pixaGetPix(recog2->pixa, i2, L_CLONE);
+        ptaGetPt(recog2->pta, i2, &x2, &y2);
+        numaGetIValue(recog2->nasum, i2, &area2);
+
+            /* Find their correlation and save the results.
+             * The heights should all be scaled to the same value (e.g., 30),
+             * but the widths can vary, so we need a large tolerance (wvar)
+             * to force correlation between all templates. */
+        wvar = 0.6 * recog1->scaleh;
+        pixCorrelationScoreSimple(pix1, pix2, area1, area2,
+                                  x1 - x2, y1 - y2, wvar, 0,
+                                  recog1->sumtab, &score);
+        numaSetValue(*pnascore, i1, score);
+        debugAddImage1(pixa1, pix1, pix2, recog1->bmf, score);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+    debugAddImage2(pixadb, pixa1, recog1->bmf, recog2->index);
+
+    pixaDestroy(&pixa1);
+    LEPT_FREE(lut);
+    return 0;
+}
+
+
+/*!
+ *  recogSetPadParams()
+ *
+ *      Input:  recog (to be padded, if necessary)
+ *              bootdir (<optional> directory to bootstrap labelled pixa)
+ *              bootpattern (<optional> pattern for bootstrap labelled pixa)
+ *              bootpath (<optional> path to single bootstrap labelled pixa)
+ *              boot_iters (number of 2x2 erosions for extension of boot pixa)
+ *              type (character set type; -1 for default; see enum in recog.h)
+ *              size (character set size; -1 for default)
+ *              min_nopad (min number in a class without padding; -1 default)
+ *              max_afterpad (max number of samples in padded classes;
+ *                            -1 for default)
+ *              min_samples (use boot if total num samples is less than this;
+ *                           -1 for default)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is used to augment or replace a book-adapted recognizer (BAR).
+ *          It is called when the recognizer is created, and must be
+ *          called again before recogPadTrainingSet() if non-default
+ *          values are to be used.
+ *      (2) Default values allow for some padding.  To disable padding,
+ *          set @min_nopad = 0.
+ *      (3) Constraint on @min_nopad and @max_afterpad guarantees that
+ *          padding will be allowed if requested.
+ *      (4) The file directory (@bootdir) and tail pattern (@bootpattern)
+ *          are used to identify serialized pixa, from which we can
+ *          generate an array of recog.  These can be used to augment
+ *          an input but incomplete BAR (book adapted recognizer).
+ *      (5) The boot recog can be extended using erosions.  Set boot_iters
+ *          to the number of 2x2 erosions desired.  For a typical
+ *          font size, @boot_iters <= 2.
+ *      (6) If the BAR is very sparse, with num_samples < min_samples,
+ *          we will destroy it and use the generic bootstrap recognizer
+ *          given at @bootpath.
+ */
+l_int32
+recogSetPadParams(L_RECOG     *recog,
+                  const char  *bootdir,
+                  const char  *bootpattern,
+                  const char  *bootpath,
+                  l_int32      boot_iters,
+                  l_int32      type,
+                  l_int32      min_nopad,
+                  l_int32      max_afterpad,
+                  l_int32      min_samples)
+{
+
+    PROCNAME("recogSetPadParams");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (min_nopad >= 0 && max_afterpad >= 0 && min_nopad >= max_afterpad)
+        return ERROR_INT("min_ must be less than max_", procName, 1);
+
+    LEPT_FREE(recog->bootdir);
+    LEPT_FREE(recog->bootpattern);
+    LEPT_FREE(recog->bootpath);
+    recog->bootdir = (bootdir) ? stringNew(bootdir)
+                               : stringNew(DEFAULT_BOOT_DIR);
+    recog->bootpattern = (bootpattern) ? stringNew(bootpattern)
+                                       : stringNew(DEFAULT_BOOT_PATTERN);
+    recog->bootpath = (bootpath) ? stringNew(bootpath) : NULL;
+    recog->boot_iters = L_MAX(0, boot_iters);
+    recog->charset_type = (type >= 0) ? type : DEFAULT_CHARSET_TYPE;
+    recog->charset_size = recogGetCharsetSize(recog->charset_type);
+    recog->min_nopad = (min_nopad >= 0) ? min_nopad : DEFAULT_MIN_NOPAD;
+    recog->max_afterpad =
+        (max_afterpad >= 0) ? max_afterpad : DEFAULT_MAX_AFTERPAD;
+    recog->min_samples = (min_samples >= 0) ? min_samples
+                                            : DEFAULT_MIN_SAMPLES;
+    return 0;
+}
+
+
+/*!
+ *  recogGetCharsetSize()
+ *
+ *      Input:  type (of charset)
+ *      Return: size of charset, or 0 if unknown or on error
+ */
+static l_int32
+recogGetCharsetSize(l_int32  type)
+{
+    PROCNAME("recogGetCharsetSize");
+
+    switch (type) {
+    case L_UNKNOWN:
+        return 0;
+    case L_ARABIC_NUMERALS:
+        return 10;
+    case L_LC_ROMAN_NUMERALS:
+        return 7;
+    case L_UC_ROMAN_NUMERALS:
+        return 7;
+    case L_LC_ALPHA:
+        return 26;
+    case L_UC_ALPHA:
+        return 26;
+    default:
+        L_ERROR("invalid charset_type %d\n", procName, type);
+        return 0;
+    }
+    return 0;  /* shouldn't happen */
+}
+
+
+/*!
+ *  recogCharsetAvailable()
+ *
+ *      Input:  type (of charset for padding)
+ *      Return: 1 if available; 0 if not.
+ */
+static l_int32
+recogCharsetAvailable(l_int32  type)
+{
+l_int32  ret;
+
+    PROCNAME("recogCharsetAvailable");
+
+    switch (type)
+    {
+    case L_ARABIC_NUMERALS:
+        ret = TRUE;
+        break;
+    case L_LC_ROMAN_NUMERALS:
+    case L_UC_ROMAN_NUMERALS:
+    case L_LC_ALPHA:
+    case L_UC_ALPHA:
+        L_INFO("charset type %d not available", procName, type);
+        ret = FALSE;
+        break;
+    default:
+        L_INFO("charset type %d is unknown", procName, type);
+        ret = FALSE;
+        break;
+    }
+
+    return ret;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                               Debugging                                *
+ *------------------------------------------------------------------------*/
+/*!
+ *  recogaShowContent()
+ *
+ *      Input:  stream
+ *              recoga
+ *              display (1 for showing template images, 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogaShowContent(FILE      *fp,
+                  L_RECOGA  *recoga,
+                  l_int32    display)
+{
+l_int32   i, n;
+L_RECOG  *recog;
+
+    PROCNAME("recogaShowContent");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!recoga)
+        return ERROR_INT("recog not defined", procName, 1);
+    if ((n = recogaGetCount(recoga)) == 0)
+        return ERROR_INT("no recog found", procName, 1);
+
+    fprintf(fp, "\nDebug print of recoga contents:\n");
+    for (i = 0; i < n; i++) {
+        if ((recog = recogaGetRecog(recoga, i)) == NULL) {
+            L_ERROR("recog %d not found!\n", procName, i);
+            continue;
+        }
+        fprintf(fp, "\nRecog %d:\n", i);
+        if (recog->train_done == FALSE)
+            L_WARNING("training for recog %d is not finished\n", procName, i);
+        recogShowContent(fp, recog, display);
+    }
+    return 0;
+}
+
+
+/*!
+ *  recogShowContent()
+ *
+ *      Input:  stream
+ *              recog
+ *              display (1 for showing template images, 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+recogShowContent(FILE     *fp,
+                 L_RECOG  *recog,
+                 l_int32   display)
+{
+l_int32  i, val, count;
+PIX     *pix;
+NUMA    *na;
+
+    PROCNAME("recogShowContent");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+
+    fprintf(fp, "Debug print of recog contents\n");
+    fprintf(fp, "  Setsize: %d\n", recog->setsize);
+    fprintf(fp, "  Binarization threshold: %d\n", recog->threshold);
+    fprintf(fp, "  Maximum matching y-jiggle: %d\n", recog->maxyshift);
+    if (recog->templ_type == L_USE_ALL)
+        fprintf(fp, "  Using all samples for matching\n");
+    else
+        fprintf(fp, "  Using averaged template for matching\n");
+    if (recog->scalew == 0)
+        fprintf(fp, "  No width scaling of templates\n");
+    else
+        fprintf(fp, "  Template width scaled to %d\n", recog->scalew);
+    if (recog->scaleh == 0)
+        fprintf(fp, "  No height scaling of templates\n");
+    else
+        fprintf(fp, "  Template height scaled to %d\n", recog->scaleh);
+    fprintf(fp, "  Number of samples in each class:\n");
+    pixaaGetCount(recog->pixaa_u, &na);
+    for (i = 0; i < recog->setsize; i++) {
+        l_dnaGetIValue(recog->dna_tochar, i, &val);
+        numaGetIValue(na, i, &count);
+        if (val < 128)
+            fprintf(fp, "    class %d, char %c:   %d\n", i, val, count);
+        else
+            fprintf(fp, "    class %d, val %d:   %d\n", i, val, count);
+    }
+    numaDestroy(&na);
+
+    if (display) {
+        pix = pixaaDisplayByPixa(recog->pixaa_u, 20, 20, 1000);
+        pixDisplay(pix, 0, 0);
+        pixDestroy(&pix);
+        if (recog->train_done) {
+            pix = pixaaDisplayByPixa(recog->pixaa, 20, 20, 1000);
+            pixDisplay(pix, 800, 0);
+            pixDestroy(&pix);
+        }
+    }
+    return 0;
+}
+
+
+/*!
+ *  recogDebugAverages()
+ *
+ *      Input:  recog
+ *              debug (0 no output; 1 for images; 2 for text; 3 for both)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Generates an image that pairs each of the input images used
+ *          in training with the average template that it is best
+ *          correlated to.  This is written into the recog.
+ *      (2) It also generates pixa_tr of all the input training images,
+ *          which can be used, e.g., in recogShowMatchesInRange().
+ */
+l_int32
+recogDebugAverages(L_RECOG  *recog,
+                   l_int32   debug)
+{
+l_int32    i, j, n, np, index;
+l_float32  score;
+PIX       *pix1, *pix2, *pix3;
+PIXA      *pixa, *pixat;
+PIXAA     *paa1, *paa2;
+
+    PROCNAME("recogDebugAverages");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+
+        /* Mark the training as finished if necessary, and make sure
+         * that the average templates have been built. */
+    recogAverageSamples(recog, 0);
+    paa1 = recog->pixaa;
+
+        /* Save a pixa of all the training examples */
+    if (!recog->pixa_tr)
+        recog->pixa_tr = pixaaFlattenToPixa(paa1, NULL, L_CLONE);
+
+        /* Destroy any existing image and make a new one */
+    if (recog->pixdb_ave)
+        pixDestroy(&recog->pixdb_ave);
+    n = pixaaGetCount(paa1, NULL);
+    paa2 = pixaaCreate(n);
+    for (i = 0; i < n; i++) {
+        pixa = pixaCreate(0);
+        pixat = pixaaGetPixa(paa1, i, L_CLONE);
+        np = pixaGetCount(pixat);
+        for (j = 0; j < np; j++) {
+            pix1 = pixaaGetPix(paa1, i, j, L_CLONE);
+            recogIdentifyPix(recog, pix1, &pix2);
+            rchExtract(recog->rch, &index, &score, NULL, NULL, NULL,
+                       NULL, NULL);
+            if (debug >= 2)
+                fprintf(stderr, "index = %d, score = %7.3f\n", index, score);
+            pix3 = pixAddBorder(pix2, 2, 1);
+            pixaAddPix(pixa, pix3, L_INSERT);
+            pixDestroy(&pix1);
+            pixDestroy(&pix2);
+        }
+        pixaaAddPixa(paa2, pixa, L_INSERT);
+        pixaDestroy(&pixat);
+    }
+    recog->pixdb_ave = pixaaDisplayByPixa(paa2, 20, 20, 2500);
+    if (debug % 2) pixDisplay(recog->pixdb_ave, 100, 100);
+
+    pixaaDestroy(&paa2);
+    return 0;
+}
+
+
+/*!
+ *  recogShowAverageTemplates()
+ *
+ *      Input:  recog
+ *      Return: 0 on success, 1 on failure
+ *
+ *  Notes:
+ *      (1) This debug routine generates a display of the averaged templates,
+ *          both scaled and unscaled, with the centroid visible in red.
+ */
+l_int32
+recogShowAverageTemplates(L_RECOG  *recog)
+{
+l_int32    i, size;
+l_float32  x, y;
+PIX       *pix1, *pix2, *pixr;
+PIXA      *pixat, *pixadb;
+
+    PROCNAME("recogShowAverageTemplates");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+
+    fprintf(stderr, "minwidth_u = %d, minheight_u = %d, maxheight_u = %d\n",
+            recog->minwidth_u, recog->minheight_u, recog->maxheight_u);
+    fprintf(stderr, "minw = %d, minh = %d, maxh = %d\n",
+            recog->min_splitw, recog->min_splith, recog->max_splith);
+
+    pixaDestroy(&recog->pixadb_ave);
+
+    pixr = pixCreate(3, 3, 32);  /* 3x3 red square for centroid location */
+    pixSetAllArbitrary(pixr, 0xff000000);
+    pixadb = pixaCreate(2);
+
+        /* Unscaled bitmaps */
+    size = recog->setsize;
+    pixat = pixaCreate(size);
+    for (i = 0; i < size; i++) {
+        if ((pix1 = pixaGetPix(recog->pixa_u, i, L_CLONE)) == NULL)
+            continue;
+        pix2 = pixConvertTo32(pix1);
+        ptaGetPt(recog->pta_u, i, &x, &y);
+        pixRasterop(pix2, (l_int32)(x - 0.5), (l_int32)(y - 0.5), 3, 3,
+                    PIX_SRC, pixr, 0, 0);
+        pixaAddPix(pixat, pix2, L_INSERT);
+        pixDestroy(&pix1);
+    }
+    pix1 = pixaDisplayTiledInRows(pixat, 32, 3000, 1.0, 0, 20, 0);
+    pixaAddPix(pixadb, pix1, L_INSERT);
+    pixDisplay(pix1, 100, 100);
+    pixaDestroy(&pixat);
+
+        /* Scaled bitmaps */
+    pixat = pixaCreate(size);
+    for (i = 0; i < size; i++) {
+        if ((pix1 = pixaGetPix(recog->pixa, i, L_CLONE)) == NULL)
+            continue;
+        pix2 = pixConvertTo32(pix1);
+        ptaGetPt(recog->pta, i, &x, &y);
+        pixRasterop(pix2, (l_int32)(x - 0.5), (l_int32)(y - 0.5), 3, 3,
+                    PIX_SRC, pixr, 0, 0);
+        pixaAddPix(pixat, pix2, L_INSERT);
+        pixDestroy(&pix1);
+    }
+    pix1 = pixaDisplayTiledInRows(pixat, 32, 3000, 1.0, 0, 20, 0);
+    pixaAddPix(pixadb, pix1, L_INSERT);
+    pixDisplay(pix1, 100, 100);
+    pixaDestroy(&pixat);
+    pixDestroy(&pixr);
+    recog->pixadb_ave = pixadb;
+    return 0;
+}
+
+
+/*!
+ *  recogShowMatchesInRange()
+ *
+ *      Input:  recog
+ *              pixa (of 1 bpp images to match)
+ *              minscore, maxscore (range to include output)
+ *              display (to display the result)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This gives a visual output of the best matches for a given
+ *          range of scores.  Each pair of images can optionally be
+ *          labelled with the index of the best match and the correlation.
+ *          If the bmf has been previously made, it will be used here.
+ *      (2) To use this, save a set of 1 bpp images (labelled or
+ *          unlabelled) that can be given to a recognizer in a pixa.
+ *          Then call this function with the pixa and parameters
+ *          to filter a range of score.
+ */
+l_int32
+recogShowMatchesInRange(L_RECOG     *recog,
+                        PIXA        *pixa,
+                        l_float32    minscore,
+                        l_float32    maxscore,
+                        l_int32      display)
+{
+l_int32    i, n, index, depth;
+l_float32  score;
+NUMA      *nascore, *naindex;
+PIX       *pix1, *pix2;
+PIXA      *pixa1, *pixa2;
+
+    PROCNAME("recogShowMatchesInRange");
+
+    if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+        /* Run the recognizer on the set of images */
+    n = pixaGetCount(pixa);
+    nascore = numaCreate(n);
+    naindex = numaCreate(n);
+    pixa1 = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixa, i, L_CLONE);
+        recogIdentifyPix(recog, pix1, &pix2);
+        rchExtract(recog->rch, &index, &score, NULL, NULL, NULL, NULL, NULL);
+        numaAddNumber(nascore, score);
+        numaAddNumber(naindex, index);
+        pixaAddPix(pixa1, pix2, L_INSERT);
+        pixDestroy(&pix1);
+    }
+
+        /* Filter the set and optionally add text to each */
+    pixa2 = pixaCreate(n);
+    depth = 1;
+    for (i = 0; i < n; i++) {
+        numaGetFValue(nascore, i, &score);
+        if (score < minscore || score > maxscore) continue;
+        pix1 = pixaGetPix(pixa1, i, L_CLONE);
+        numaGetIValue(naindex, i, &index);
+        pix2 = recogShowMatch(recog, pix1, NULL, NULL, index, score);
+        if (i == 0) depth = pixGetDepth(pix2);
+        pixaAddPix(pixa2, pix2, L_INSERT);
+        pixDestroy(&pix1);
+    }
+
+        /* Package it up */
+    pixDestroy(&recog->pixdb_range);
+    if (pixaGetCount(pixa2) > 0) {
+        recog->pixdb_range =
+            pixaDisplayTiledInRows(pixa2, depth, 2500, 1.0, 0, 20, 1);
+        if (display)
+            pixDisplay(recog->pixdb_range, 300, 100);
+    } else {
+        L_INFO("no character matches in the range of scores\n", procName);
+    }
+
+    pixaDestroy(&pixa1);
+    pixaDestroy(&pixa2);
+    numaDestroy(&nascore);
+    numaDestroy(&naindex);
+    return 0;
+}
+
+
+/*!
+ *  recogShowMatch()
+ *
+ *      Input:  recog
+ *              pix1  (input pix; several possibilities)
+ *              pix2  (<optional> matching template)
+ *              box  (<optional> region in pix1 for which pix2 matches)
+ *              index  (index of matching template; use -1 to disable printing)
+ *              score  (score of match)
+ *      Return: pixd (pair of images, showing input pix and best template,
+ *                    optionally with matching information), or null on error.
+ *
+ *  Notes:
+ *      (1) pix1 can be one of these:
+ *          (a) The input pix alone, which can be either a single character
+ *              (box == NULL) or several characters that need to be
+ *              segmented.  If more than character is present, the box
+ *              region is displayed with an outline.
+ *          (b) Both the input pix and the matching template.  In this case,
+ *              pix2 and box will both be null.
+ *      (2) If the bmf has been made (by a call to recogMakeBmf())
+ *          and the index >= 0, the text field, match score and index
+ *          will be rendered; otherwise their values will be ignored.
+ */
+PIX *
+recogShowMatch(L_RECOG   *recog,
+               PIX       *pix1,
+               PIX       *pix2,
+               BOX       *box,
+               l_int32    index,
+               l_float32  score)
+{
+char    buf[32];
+char   *text;
+L_BMF  *bmf;
+PIX    *pix3, *pix4, *pix5, *pixd;
+PIXA   *pixa;
+
+    PROCNAME("recogShowMatch");
+
+    if (!recog)
+        return (PIX *)ERROR_PTR("recog not defined", procName, NULL);
+    if (!pix1)
+        return (PIX *)ERROR_PTR("pix1 not defined", procName, NULL);
+
+    bmf = (recog->bmf && index >= 0) ? recog->bmf : NULL;
+    if (!pix2 && !box && !bmf)  /* nothing to do */
+        return pixCopy(NULL, pix1);
+
+    pix3 = pixConvertTo32(pix1);
+    if (box)
+        pixRenderBoxArb(pix3, box, 1, 255, 0, 0);
+
+    if (pix2) {
+        pixa = pixaCreate(2);
+        pixaAddPix(pixa, pix3, L_CLONE);
+        pixaAddPix(pixa, pix2, L_CLONE);
+        pix4 = pixaDisplayTiledInRows(pixa, 1, 500, 1.0, 0, 15, 0);
+        pixaDestroy(&pixa);
+    } else {
+        pix4 = pixCopy(NULL, pix3);
+    }
+    pixDestroy(&pix3);
+
+    if (bmf) {
+        pix5 = pixAddBorderGeneral(pix4, 55, 55, 0, 0, 0xffffff00);
+        recogGetClassString(recog, index, &text);
+        snprintf(buf, sizeof(buf), "C=%s, S=%4.3f, I=%d", text, score, index);
+        pixd = pixAddSingleTextblock(pix5, bmf, buf, 0xff000000,
+                                     L_ADD_BELOW, NULL);
+        pixDestroy(&pix5);
+        LEPT_FREE(text);
+    } else {
+        pixd = pixClone(pix4);
+    }
+    pixDestroy(&pix4);
+
+    return pixd;
+}
+
+
+/*!
+ *  recogResetBmf()
+ *
+ *      Input:  recog
+ *              size  (of font; even integer between 4 and 20; default is 6)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Use this to reset the size of the font used for debug labelling.
+ */
+l_int32
+recogResetBmf(L_RECOG     *recog,
+             l_int32      size)
+{
+   PROCNAME("recogResetBmf");
+
+   if (!recog)
+        return ERROR_INT("recog not defined", procName, 1);
+   if (size < 4 || size > 20 || (size % 2)) size = 6;
+   if (size == recog->bmf_size) return 0;  /* no change */
+
+   bmfDestroy(&recog->bmf);
+   recog->bmf = bmfCreate(NULL, size);
+   recog->bmf_size = size;
+   return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *                             Static helpers                             *
+ *------------------------------------------------------------------------*/
+static char *
+l_charToString(char byte)
+{
+char  *str;
+
+  str = (char *)LEPT_CALLOC(2, sizeof(char));
+  str[0] = byte;
+  return str;
+}
+
+
+/*
+ *  debugAddImage1()
+ *
+ *      Input:  pixa1 (<optional> for accumulating pairs of images
+ *              pix1, pix2
+ *              bmf
+ *              score (rendered using the bmf)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) If pixa1 is NULL, do nothing.  Otherwise, we add a pair of
+ *          images with a score.  This is accumulated for each
+ *          corresponding class templates in the two recog.
+ */
+static void
+debugAddImage1(PIXA      *pixa1,
+               PIX       *pix1,
+               PIX       *pix2,
+               L_BMF     *bmf,
+               l_float32  score)
+{
+char   buf[16];
+PIX   *pix3, *pix4, *pix5;
+PIXA  *pixa2;
+
+    if (!pixa1) return;
+    pixa2 = pixaCreate(2);
+    pix3 = pixAddBorder(pix1, 5, 0);
+    pixaAddPix(pixa2, pix3, L_INSERT);
+    pix3 = pixAddBorder(pix2, 5, 0);
+    pixaAddPix(pixa2, pix3, L_INSERT);
+    pix4 = pixaDisplayTiledInRows(pixa2, 32, 1000, 1.0, 0, 20, 2);
+    snprintf(buf, sizeof(buf), "%5.3f", score);
+    pix5 = pixAddTextlines(pix4, bmf, buf, 0xff000000, L_ADD_BELOW);
+    pixaAddPix(pixa1, pix5, L_INSERT);
+    pixDestroy(&pix4);
+    pixaDestroy(&pixa2);
+    return;
+}
+
+
+/*
+ *  debugAddImage2()
+ *
+ *      Input:  pixadb (pre-allocated debug pixa; if null, this is a no-op)
+ *              pixa1 (<optional> accumulated pairs of images)
+ *              bmf
+ *              index (of recog in recoga)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This displays pixa1 into a pix and adds it to pixadb.
+ *      (2) If pixa1 or pixadb are NULL, do nothing.
+ *      (3) To get a result, pixadb needs to be initialized:
+ *            Pixa *pixadb = pixaCreate(0);
+ *          Then each call:
+ *            debugAddImage2(pixadb, pixa1, ...);
+ *          will add data (here from pixa1) to pixadb.
+ */
+static void
+debugAddImage2(PIXA    *pixadb,
+               PIXA    *pixa1,
+               L_BMF   *bmf,
+               l_int32  index)
+{
+char   buf[16];
+PIX   *pix1, *pix2, *pix3, *pix4;
+
+    if (!pixa1) return;
+    if (!pixadb) return;
+    pix1 = pixaDisplayTiledInRows(pixa1, 32, 2000, 1.0, 0, 20, 0);
+    snprintf(buf, sizeof(buf), "Recog %d", index);
+    pix2 = pixAddTextlines(pix1, bmf, buf, 0xff000000, L_ADD_BELOW);
+    pix3 = pixAddBorder(pix2, 5, 0);
+    pix4 = pixAddBorder(pix3, 2, 1);
+    pixaAddPix(pixadb, pix4, L_INSERT);
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    pixDestroy(&pix3);
+    return;
+}
+
+
diff --git a/src/regutils.c b/src/regutils.c
new file mode 100644 (file)
index 0000000..abd1dfc
--- /dev/null
@@ -0,0 +1,751 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  regutils.c
+ *
+ *       Regression test utilities
+ *           l_int32    regTestSetup()
+ *           l_int32    regTestCleanup()
+ *           l_int32    regTestCompareValues()
+ *           l_int32    regTestCompareStrings()
+ *           l_int32    regTestComparePix()
+ *           l_int32    regTestCompareSimilarPix()
+ *           l_int32    regTestCheckFile()
+ *           l_int32    regTestCompareFiles()
+ *           l_int32    regTestWritePixAndCheck()
+ *
+ *       Static function
+ *           char      *getRootNameFromArgv0()
+ *
+ *  See regutils.h for how to use this.  Here is a minimal setup:
+ *
+ *  main(int argc, char **argv) {
+ *  ...
+ *  L_REGPARAMS  *rp;
+ *
+ *      if (regTestSetup(argc, argv, &rp))
+ *          return 1;
+ *      ...
+ *      regTestWritePixAndCheck(rp, pix, IFF_PNG);  // 0
+ *      ...
+ *      return regTestCleanup(rp);
+ *  }
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+extern l_int32 NumImageFileFormatExtensions;
+extern const char *ImageFileFormatExtensions[];
+
+static char *getRootNameFromArgv0(const char *argv0);
+
+/*--------------------------------------------------------------------*
+ *                      Regression test utilities                     *
+ *--------------------------------------------------------------------*/
+/*!
+ *  regTestSetup()
+ *
+ *      Input:  argc (from invocation; can be either 1 or 2)
+ *              argv (to regtest: @argv[1] is one of these:
+ *                    "generate", "compare", "display")
+ *              &rp (<return> all regression params)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Call this function with the args to the reg test.  The first arg
+ *          is the name of the reg test.  There are three cases:
+ *          Case 1:
+ *              There is either only one arg, or the second arg is "compare".
+ *              This is the mode in which you run a regression test
+ *              (or a set of them), looking for failures and logging
+ *              the results to a file.  The output, which includes
+ *              logging of all reg test failures plus a SUCCESS or
+ *              FAILURE summary for each test, is appended to the file
+ *              "/tmp/lept/reg_results.txt.  For this case, as in Case 2,
+ *              the display field in rp is set to FALSE, preventing
+ *              image display.
+ *          Case 2:
+ *              The second arg is "generate".  This will cause
+ *              generation of new golden files for the reg test.
+ *              The results of the reg test are not recorded, and
+ *              the display field in rp is set to FALSE.
+ *          Case 3:
+ *              The second arg is "display".  The test will run and
+ *              files will be written.  Comparisons with golden files
+ *              will not be carried out, so the only notion of success
+ *              or failure is with tests that do not involve golden files.
+ *              The display field in rp is TRUE, and this is used by
+ *              pixDisplayWithTitle().
+ *      (2) See regutils.h for examples of usage.
+ */
+l_int32
+regTestSetup(l_int32        argc,
+             char         **argv,
+             L_REGPARAMS  **prp)
+{
+char         *testname, *vers;
+char          errormsg[64];
+L_REGPARAMS  *rp;
+
+    PROCNAME("regTestSetup");
+
+    if (argc != 1 && argc != 2) {
+        snprintf(errormsg, sizeof(errormsg),
+            "Syntax: %s [ [compare] | generate | display ]", argv[0]);
+        return ERROR_INT(errormsg, procName, 1);
+    }
+
+    if ((testname = getRootNameFromArgv0(argv[0])) == NULL)
+        return ERROR_INT("invalid root", procName, 1);
+
+    if ((rp = (L_REGPARAMS *)LEPT_CALLOC(1, sizeof(L_REGPARAMS))) == NULL)
+        return ERROR_INT("rp not made", procName, 1);
+    *prp = rp;
+    rp->testname = testname;
+    rp->index = -1;  /* increment before each test */
+
+        /* Initialize to true.  A failure in any test is registered
+         * as a failure of the regression test. */
+    rp->success = TRUE;
+
+        /* Make sure the lept/regout subdirectory exists */
+    lept_mkdir("lept/regout");
+
+        /* Only open a stream to a temp file for the 'compare' case */
+    if (argc == 1 || !strcmp(argv[1], "compare")) {
+        rp->mode = L_REG_COMPARE;
+        rp->tempfile = genPathname("/tmp/lept/regout", "regtest_output.txt");
+        rp->fp = fopenWriteStream(rp->tempfile, "wb");
+        if (rp->fp == NULL) {
+            rp->success = FALSE;
+            return ERROR_INT("stream not opened for tempfile", procName, 1);
+        }
+    } else if (!strcmp(argv[1], "generate")) {
+        rp->mode = L_REG_GENERATE;
+        lept_mkdir("lept/golden");
+    } else if (!strcmp(argv[1], "display")) {
+        rp->mode = L_REG_DISPLAY;
+        rp->display = TRUE;
+    } else {
+        LEPT_FREE(rp);
+        snprintf(errormsg, sizeof(errormsg),
+            "Syntax: %s [ [generate] | compare | display ]", argv[0]);
+        return ERROR_INT(errormsg, procName, 1);
+    }
+
+        /* Print out test name and both the leptonica and
+         * image libarary versions */
+    fprintf(stderr, "\n################   %s_reg   ###############\n",
+            rp->testname);
+    vers = getLeptonicaVersion();
+    fprintf(stderr, "%s\n", vers);
+    LEPT_FREE(vers);
+    vers = getImagelibVersions();
+    fprintf(stderr, "%s\n", vers);
+    LEPT_FREE(vers);
+
+    rp->tstart = startTimerNested();
+    return 0;
+}
+
+
+/*!
+ *  regTestCleanup()
+ *
+ *      Input:  rp (regression test parameters)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This copies anything written to the temporary file to the
+ *          output file /tmp/lept/reg_results.txt.
+ */
+l_int32
+regTestCleanup(L_REGPARAMS  *rp)
+{
+char     result[512];
+char    *results_file;  /* success/failure output in 'compare' mode */
+char    *text, *message;
+l_int32  retval;
+size_t   nbytes;
+
+    PROCNAME("regTestCleanup");
+
+    if (!rp)
+        return ERROR_INT("rp not defined", procName, 1);
+
+    fprintf(stderr, "Time: %7.3f sec\n", stopTimerNested(rp->tstart));
+    fprintf(stderr, "################################################\n");
+
+        /* If generating golden files or running in display mode, release rp */
+    if (!rp->fp) {
+        LEPT_FREE(rp->testname);
+        LEPT_FREE(rp->tempfile);
+        LEPT_FREE(rp);
+        return 0;
+    }
+
+        /* Compare mode: read back data from temp file */
+    fclose(rp->fp);
+    text = (char *)l_binaryRead(rp->tempfile, &nbytes);
+    LEPT_FREE(rp->tempfile);
+    if (!text) {
+        rp->success = FALSE;
+        LEPT_FREE(rp->testname);
+        LEPT_FREE(rp);
+        return ERROR_INT("text not returned", procName, 1);
+    }
+
+        /* Prepare result message */
+    if (rp->success)
+        snprintf(result, sizeof(result), "SUCCESS: %s_reg\n", rp->testname);
+    else
+        snprintf(result, sizeof(result), "FAILURE: %s_reg\n", rp->testname);
+    message = stringJoin(text, result);
+    LEPT_FREE(text);
+    results_file = genPathname("/tmp/lept", "reg_results.txt");
+    fileAppendString(results_file, message);
+    retval = (rp->success) ? 0 : 1;
+    LEPT_FREE(results_file);
+    LEPT_FREE(message);
+
+    LEPT_FREE(rp->testname);
+    LEPT_FREE(rp);
+    return retval;
+}
+
+
+/*!
+ *  regTestCompareValues()
+ *
+ *      Input:  rp (regtest parameters)
+ *              val1 (typ. the golden value)
+ *              val2 (typ. the value computed)
+ *              delta (allowed max absolute difference)
+ *      Return: 0 if OK, 1 on error (a failure in comparison is not an error)
+ */
+l_int32
+regTestCompareValues(L_REGPARAMS  *rp,
+                     l_float32     val1,
+                     l_float32     val2,
+                     l_float32     delta)
+{
+l_float32  diff;
+
+    PROCNAME("regTestCompareValues");
+
+    if (!rp)
+        return ERROR_INT("rp not defined", procName, 1);
+
+    rp->index++;
+    diff = L_ABS(val2 - val1);
+
+        /* Record on failure */
+    if (diff > delta) {
+        if (rp->fp) {
+            fprintf(rp->fp,
+                    "Failure in %s_reg: value comparison for index %d\n"
+                    "difference = %f but allowed delta = %f\n",
+                    rp->testname, rp->index, diff, delta);
+        }
+        fprintf(stderr,
+                    "Failure in %s_reg: value comparison for index %d\n"
+                    "difference = %f but allowed delta = %f\n",
+                    rp->testname, rp->index, diff, delta);
+        rp->success = FALSE;
+    }
+    return 0;
+}
+
+
+/*!
+ *  regTestCompareStrings()
+ *
+ *      Input:  rp (regtest parameters)
+ *              string1 (typ. the expected string)
+ *              bytes1 (size of string1)
+ *              string2 (typ. the computed string)
+ *              bytes2 (size of string2)
+ *      Return: 0 if OK, 1 on error (a failure in comparison is not an error)
+ */
+l_int32
+regTestCompareStrings(L_REGPARAMS  *rp,
+                      l_uint8      *string1,
+                      size_t        bytes1,
+                      l_uint8      *string2,
+                      size_t        bytes2)
+{
+l_int32  i, fail;
+char     buf[256];
+
+    PROCNAME("regTestCompareValues");
+
+    if (!rp)
+        return ERROR_INT("rp not defined", procName, 1);
+
+    rp->index++;
+    fail = FALSE;
+    if (bytes1 != bytes2) fail = TRUE;
+    if (fail == FALSE) {
+        for (i = 0; i < bytes1; i++) {
+            if (string1[i] != string2[i]) {
+                fail = TRUE;
+                break;
+            }
+        }
+    }
+
+        /* Output on failure */
+    if (fail == TRUE) {
+            /* Write the two strings to file */
+        snprintf(buf, sizeof(buf), "/tmp/lept/regout/string1_%d_%lu", rp->index,
+                 (unsigned long)bytes1);
+        l_binaryWrite(buf, "w", string1, bytes1);
+        snprintf(buf, sizeof(buf), "/tmp/lept/regout/string2_%d_%lu", rp->index,
+                 (unsigned long)bytes2);
+        l_binaryWrite(buf, "w", string2, bytes2);
+
+            /* Report comparison failure */
+        snprintf(buf, sizeof(buf), "/tmp/lept/regout/string*_%d_*", rp->index);
+        if (rp->fp) {
+            fprintf(rp->fp,
+                    "Failure in %s_reg: string comp for index %d; "
+                    "written to %s\n", rp->testname, rp->index, buf);
+        }
+        fprintf(stderr,
+                    "Failure in %s_reg: string comp for index %d; "
+                    "written to %s\n", rp->testname, rp->index, buf);
+        rp->success = FALSE;
+    }
+    return 0;
+}
+
+
+/*!
+ *  regTestComparePix()
+ *
+ *      Input:  rp (regtest parameters)
+ *              pix1, pix2 (to be tested for equality)
+ *      Return: 0 if OK, 1 on error (a failure in comparison is not an error)
+ *
+ *  Notes:
+ *      (1) This function compares two pix for equality.  On failure,
+ *          this writes to stderr.
+ */
+l_int32
+regTestComparePix(L_REGPARAMS  *rp,
+                  PIX          *pix1,
+                  PIX          *pix2)
+{
+l_int32  same;
+
+    PROCNAME("regTestComparePix");
+
+    if (!rp)
+        return ERROR_INT("rp not defined", procName, 1);
+    if (!pix1 || !pix2) {
+        rp->success = FALSE;
+        return ERROR_INT("pix1 and pix2 not both defined", procName, 1);
+    }
+
+    rp->index++;
+    pixEqual(pix1, pix2, &same);
+
+        /* Record on failure */
+    if (!same) {
+        if (rp->fp) {
+            fprintf(rp->fp, "Failure in %s_reg: pix comparison for index %d\n",
+                    rp->testname, rp->index);
+        }
+        fprintf(stderr, "Failure in %s_reg: pix comparison for index %d\n",
+                rp->testname, rp->index);
+        rp->success = FALSE;
+    }
+    return 0;
+}
+
+
+/*!
+ *  regTestCompareSimilarPix()
+ *
+ *      Input:  rp (regtest parameters)
+ *              pix1, pix2 (to be tested for near equality)
+ *              mindiff (minimum pixel difference to be counted; > 0)
+ *              maxfract (maximum fraction of pixels allowed to have
+ *                        diff greater than or equal to mindiff)
+ *              printstats (use 1 to print normalized histogram to stderr)
+ *      Return: 0 if OK, 1 on error (a failure in similarity comparison
+ *              is not an error)
+ *
+ *  Notes:
+ *      (1) This function compares two pix for near equality.  On failure,
+ *          this writes to stderr.
+ *      (2) The pix are similar if the fraction of non-conforming pixels
+ *          does not exceed @maxfract.  Pixels are non-conforming if
+ *          the difference in pixel values equals or exceeds @mindiff.
+ *          Typical values might be @mindiff = 15 and @maxfract = 0.01.
+ *      (3) The input images must have the same size and depth.  The
+ *          pixels for comparison are typically subsampled from the images.
+ *      (4) Normally, use @printstats = 0.  In debugging mode, to see
+ *          the relation between @mindiff and the minimum value of
+ *          @maxfract for success, set this to 1.
+ */
+l_int32
+regTestCompareSimilarPix(L_REGPARAMS  *rp,
+                         PIX          *pix1,
+                         PIX          *pix2,
+                         l_int32       mindiff,
+                         l_float32     maxfract,
+                         l_int32       printstats)
+{
+l_int32  w, h, factor, similar;
+
+    PROCNAME("regTestCompareSimilarPix");
+
+    if (!rp)
+        return ERROR_INT("rp not defined", procName, 1);
+    if (!pix1 || !pix2) {
+        rp->success = FALSE;
+        return ERROR_INT("pix1 and pix2 not both defined", procName, 1);
+    }
+
+    rp->index++;
+    pixGetDimensions(pix1, &w, &h, NULL);
+    factor = L_MAX(w, h) / 400;
+    factor = L_MAX(1, L_MIN(factor, 4));   /* between 1 and 4 */
+    pixTestForSimilarity(pix1, pix2, factor, mindiff, maxfract, 0.0,
+                         &similar, printstats);
+
+        /* Record on failure */
+    if (!similar) {
+        if (rp->fp) {
+            fprintf(rp->fp,
+                    "Failure in %s_reg: pix similarity comp for index %d\n",
+                    rp->testname, rp->index);
+        }
+        fprintf(stderr, "Failure in %s_reg: pix similarity comp for index %d\n",
+                rp->testname, rp->index);
+        rp->success = FALSE;
+    }
+    return 0;
+}
+
+
+/*!
+ *  regTestCheckFile()
+ *
+ *      Input:  rp (regtest parameters)
+ *              localname (name of output file from reg test)
+ *      Return: 0 if OK, 1 on error (a failure in comparison is not an error)
+ *
+ *  Notes:
+ *      (1) This function does one of three things, depending on the mode:
+ *           * "generate": makes a "golden" file as a copy @localname.
+ *           * "compare": compares @localname contents with the golden file
+ *           * "display": makes the @localname file but does no comparison
+ *      (2) The canonical format of the golden filenames is:
+ *            /tmp/lept/golden/<root of main name>_golden.<index>.
+ *                                                       <ext of localname>
+ *          e.g.,
+ *             /tmp/lept/golden/maze_golden.0.png
+ *          It is important to add an extension to the local name, because
+ *          the extension is added to the name of the golden file.
+ */
+l_int32
+regTestCheckFile(L_REGPARAMS  *rp,
+                 const char   *localname)
+{
+char    *ext;
+char     namebuf[256];
+l_int32  ret, same, format;
+PIX     *pix1, *pix2;
+
+    PROCNAME("regTestCheckFile");
+
+    if (!rp)
+        return ERROR_INT("rp not defined", procName, 1);
+    if (!localname) {
+        rp->success = FALSE;
+        return ERROR_INT("local name not defined", procName, 1);
+    }
+    if (rp->mode != L_REG_GENERATE && rp->mode != L_REG_COMPARE &&
+        rp->mode != L_REG_DISPLAY) {
+        rp->success = FALSE;
+        return ERROR_INT("invalid mode", procName, 1);
+    }
+    rp->index++;
+
+        /* If display mode, no generation and no testing */
+    if (rp->mode == L_REG_DISPLAY) return 0;
+
+        /* Generate the golden file name; used in 'generate' and 'compare' */
+    splitPathAtExtension(localname, NULL, &ext);
+    snprintf(namebuf, sizeof(namebuf), "/tmp/lept/golden/%s_golden.%02d%s",
+             rp->testname, rp->index, ext);
+    LEPT_FREE(ext);
+
+        /* Generate mode.  No testing. */
+    if (rp->mode == L_REG_GENERATE) {
+            /* Save the file as a golden file */
+        ret = fileCopy(localname, namebuf);
+#if 0       /* Enable for details on writing of golden files */
+        if (!ret) {
+            char *local = genPathname(localname, NULL);
+            char *golden = genPathname(namebuf, NULL);
+            L_INFO("Copy: %s to %s\n", procName, local, golden);
+            LEPT_FREE(local);
+            LEPT_FREE(golden);
+        }
+#endif
+        return ret;
+    }
+
+        /* Compare mode: test and record on failure.  GIF compression
+         * is lossless for images with up to 8 bpp (but not for RGB
+         * because it must generate a 256 color palette).  Although
+         * the read/write cycle for GIF is idempotent in the image
+         * pixels for bpp <= 8, it is not idempotent in the actual
+         * file bytes.  Tests comparing file bytes before and after
+         * a GIF read/write cycle will fail.  So for GIF we uncompress
+         * the two images and compare the actual pixels.  From my tests,
+         * PNG, in addition to being lossless, is idempotent in file
+         * bytes on read/write, so comparing the pixels is not necessary.
+         * (It also increases the regression test time by an an average
+         * of about 8%.)  JPEG is lossy and not idempotent in the image
+         * pixels, so no tests are constructed that would require it. */
+    findFileFormat(localname, &format);
+    if (format == IFF_GIF) {
+        same = 0;
+        pix1 = pixRead(localname);
+        pix2 = pixRead(namebuf);
+        pixEqual(pix1, pix2, &same);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    } else {
+        filesAreIdentical(localname, namebuf, &same);
+    }
+    if (!same) {
+        fprintf(rp->fp, "Failure in %s_reg, index %d: comparing %s with %s\n",
+                rp->testname, rp->index, localname, namebuf);
+        fprintf(stderr, "Failure in %s_reg, index %d: comparing %s with %s\n",
+                rp->testname, rp->index, localname, namebuf);
+        rp->success = FALSE;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  regTestCompareFiles()
+ *
+ *      Input:  rp (regtest parameters)
+ *              index1 (of one output file from reg test)
+ *              index2 (of another output file from reg test)
+ *      Return: 0 if OK, 1 on error (a failure in comparison is not an error)
+ *
+ *  Notes:
+ *      (1) This only does something in "compare" mode.
+ *      (2) The canonical format of the golden filenames is:
+ *            /tmp/lept/golden/<root of main name>_golden.<index>.
+ *                                                      <ext of localname>
+ *          e.g.,
+ *            /tmp/lept/golden/maze_golden.0.png
+ */
+l_int32
+regTestCompareFiles(L_REGPARAMS  *rp,
+                    l_int32       index1,
+                    l_int32       index2)
+{
+char    *name1, *name2;
+char     namebuf[256];
+l_int32  same;
+SARRAY  *sa;
+
+    PROCNAME("regTestCompareFiles");
+
+    if (!rp)
+        return ERROR_INT("rp not defined", procName, 1);
+    if (index1 < 0 || index2 < 0) {
+        rp->success = FALSE;
+        return ERROR_INT("index1 and/or index2 is negative", procName, 1);
+    }
+    if (index1 == index2) {
+        rp->success = FALSE;
+        return ERROR_INT("index1 must differ from index2", procName, 1);
+    }
+
+    rp->index++;
+    if (rp->mode != L_REG_COMPARE) return 0;
+
+        /* Generate the golden file names */
+    snprintf(namebuf, sizeof(namebuf), "%s_golden.%02d.", rp->testname, index1);
+    sa = getSortedPathnamesInDirectory("/tmp/lept/golden", namebuf, 0, 0);
+    if (sarrayGetCount(sa) != 1) {
+        sarrayDestroy(&sa);
+        rp->success = FALSE;
+        L_ERROR("golden file %s not found\n", procName, namebuf);
+        return 1;
+    }
+    name1 = sarrayGetString(sa, 0, L_COPY);
+    sarrayDestroy(&sa);
+
+    snprintf(namebuf, sizeof(namebuf), "%s_golden.%02d.", rp->testname, index2);
+    sa = getSortedPathnamesInDirectory("/tmp/lept/golden", namebuf, 0, 0);
+    if (sarrayGetCount(sa) != 1) {
+        sarrayDestroy(&sa);
+        rp->success = FALSE;
+        LEPT_FREE(name1);
+        L_ERROR("golden file %s not found\n", procName, namebuf);
+        return 1;
+    }
+    name2 = sarrayGetString(sa, 0, L_COPY);
+    sarrayDestroy(&sa);
+
+        /* Test and record on failure */
+    filesAreIdentical(name1, name2, &same);
+    if (!same) {
+        fprintf(rp->fp,
+                "Failure in %s_reg, index %d: comparing %s with %s\n",
+                rp->testname, rp->index, name1, name2);
+        fprintf(stderr,
+                "Failure in %s_reg, index %d: comparing %s with %s\n",
+                rp->testname, rp->index, name1, name2);
+        rp->success = FALSE;
+    }
+
+    LEPT_FREE(name1);
+    LEPT_FREE(name2);
+    return 0;
+}
+
+
+/*!
+ *  regTestWritePixAndCheck()
+ *
+ *      Input:  rp (regtest parameters)
+ *              pix (to be written)
+ *              format (of output pix)
+ *      Return: 0 if OK, 1 on error (a failure in comparison is not an error)
+ *
+ *  Notes:
+ *      (1) This function makes it easy to write the pix in a numbered
+ *          sequence of files, and either to:
+ *             (a) write the golden file ("generate" arg to regression test)
+ *             (b) make a local file and "compare" with the golden file
+ *             (c) make a local file and "display" the results
+ *      (3) The canonical format of the local filename is:
+ *            /tmp/lept/regout/<root of main name>.<count>.<format extension>
+ *          e.g., for scale_reg,
+ *            /tmp/lept/regout/scale.0.png
+ */
+l_int32
+regTestWritePixAndCheck(L_REGPARAMS  *rp,
+                        PIX          *pix,
+                        l_int32       format)
+{
+char   namebuf[256];
+
+    PROCNAME("regTestWritePixAndCheck");
+
+    if (!rp)
+        return ERROR_INT("rp not defined", procName, 1);
+    if (!pix) {
+        rp->success = FALSE;
+        return ERROR_INT("pix not defined", procName, 1);
+    }
+    if (format < 0 || format >= NumImageFileFormatExtensions) {
+        rp->success = FALSE;
+        return ERROR_INT("invalid format", procName, 1);
+    }
+
+        /* Generate the local file name */
+    snprintf(namebuf, sizeof(namebuf), "/tmp/lept/regout/%s.%02d.%s",
+             rp->testname, rp->index + 1, ImageFileFormatExtensions[format]);
+
+        /* Write the local file */
+    if (pixGetDepth(pix) < 8)
+        pixSetPadBits(pix, 0);
+    pixWrite(namebuf, pix, format);
+
+        /* Either write the golden file ("generate") or check the
+           local file against an existing golden file ("compare") */
+    regTestCheckFile(rp, namebuf);
+
+    return 0;
+}
+
+
+/*!
+ *  getRootNameFromArgv0()
+ *
+ *      Input:  argv0
+ *      Return: root name (without the '_reg'), or null on error
+ *
+ *  Notes:
+ *      (1) For example, from psioseg_reg, we want to extract
+ *          just 'psioseg' as the root.
+ *      (2) In unix with autotools, the executable is not X,
+ *          but ./.libs/lt-X.   So in addition to stripping out the
+ *          last 4 characters of the tail, we have to check for
+ *          the '-' and strip out the "lt-" prefix if we find it.
+ */
+static char *
+getRootNameFromArgv0(const char  *argv0)
+{
+l_int32  len;
+char    *root;
+
+    PROCNAME("getRootNameFromArgv0");
+
+    splitPathAtDirectory(argv0, NULL, &root);
+    if ((len = strlen(root)) <= 4) {
+        LEPT_FREE(root);
+        return (char *)ERROR_PTR("invalid argv0; too small", procName, NULL);
+    }
+
+#ifndef _WIN32
+    {
+        char    *newroot;
+        l_int32  loc;
+        if (stringFindSubstr(root, "-", &loc)) {
+            newroot = stringNew(root + loc + 1);  /* strip out "lt-" */
+            LEPT_FREE(root);
+            root = newroot;
+            len = strlen(root);
+        }
+    }
+#else
+    if (strstr(root, ".exe") != NULL)
+        len -= 4;
+#endif  /* ! _WIN32 */
+
+    root[len - 4] = '\0';  /* remove the suffix */
+    return root;
+}
+
diff --git a/src/regutils.h b/src/regutils.h
new file mode 100644 (file)
index 0000000..15ea0aa
--- /dev/null
@@ -0,0 +1,136 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_REGUTILS_H
+#define  LEPTONICA_REGUTILS_H
+
+/*
+ *   regutils.h
+ *
+ *   Contains this regression test parameter packaging struct
+ *       struct L_RegParams
+ *
+ *   The regression test utility allows you to write regression tests
+ *   that compare results with existing "golden files" and with
+ *   compiled in data.
+ *
+ *   Regression tests can be called in three ways.
+ *   For example, for distance_reg:
+ *
+ *       Case 1: distance_reg [compare]
+ *           This runs the test against the set of golden files.  It
+ *           appends to 'outfile.txt' either "SUCCESS" or "FAILURE",
+ *           as well as the details of any parts of the test that failed.
+ *           It writes to a temporary file stream (fp).
+ *           Using 'compare' on the command line is optional.
+ *
+ *       Case 2: distance_reg generate
+ *           This generates golden files in /tmp for the reg test.
+ *
+ *       Case 3: distance_reg display
+ *           This runs the test but makes no comparison of the output
+ *           against the set of golden files.  In addition, this displays
+ *           images and plots that are specified in the test under
+ *           control of the display variable.  Display is enabled only
+ *           for this case.
+ *
+ *   Regression tests follow the pattern given below.  Tests are
+ *   automatically numbered sequentially, and it is convenient to
+ *   comment each with a number to keep track (for comparison tests
+ *   and for debugging).  In an actual case, comparisons of pix and
+ *   of files can occur in any order.  We give a specific order here
+ *   for clarity.
+ *
+ *       L_REGPARAMS  *rp;  // holds data required by the test functions
+ *
+ *       // Setup variables; optionally open stream
+ *       if (regTestSetup(argc, argv, &rp))
+ *           return 1;
+ *
+ *       // Test pairs of generated pix for identity.  This compares
+ *       // two pix; no golden file is generated.
+ *       regTestComparePix(rp, pix1, pix2);  // 0
+ *
+ *       // Test pairs of generated pix for similarity.  This compares
+ *       // two pix; no golden file is generated.  The last arg determines
+ *       // if stats are to be written to stderr.
+ *       regTestCompareSimilarPix(rp, pix1, pix2, 15, 0.001, 0);  // 1
+ *
+ *       // Generation of <newfile*> outputs and testing for identity
+ *       // These files can be anything, of course.
+ *       regTestCheckFile(rp, <newfile0>);  // 2
+ *       regTestCheckFile(rp, <newfile1>);  // 3
+ *
+ *       // Test pairs of output golden files for identity.  Here we
+ *       // are comparing golden files 2 and 3.
+ *       regTestCompareFiles(rp, 2, 3);  // 4
+ *
+ *       // "Write and check".  This writes a pix using a canonical
+ *       // formulation for the local filename and either:
+ *       //     case 1: generates a golden file
+ *       //     case 2: compares the local file with a golden file
+ *       //     case 3: generates local files and displays
+ *       // Here we write the pix compressed with png and jpeg, respectively;
+ *       // Then check against the golden file.  The internal @index
+ *       // is incremented; it is embedded in the local filename and,
+ *       // if generating, in the golden file as well.
+ *       regTestWritePixAndCheck(rp, pix1, IFF_PNG);  // 5
+ *       regTestWritePixAndCheck(rp, pix2, IFF_JFIF_JPEG);  // 6
+ *
+ *       // Display if reg test was called in 'display' mode
+ *       pixDisplayWithTitle(pix1, 100, 100, NULL, rp->display);
+ *
+ *       // Clean up and output result
+ *       regTestCleanup(rp);
+ */
+
+/*-------------------------------------------------------------------------*
+ *                     Regression test parameter packer                    *
+ *-------------------------------------------------------------------------*/
+struct L_RegParams
+{
+    FILE    *fp;        /* stream to temporary output file for compare mode */
+    char    *testname;  /* name of test, without '_reg'                     */
+    char    *tempfile;  /* name of temp file for compare mode output        */
+    l_int32  mode;      /* generate, compare or display                     */
+    l_int32  index;     /* index into saved files for this test; 0-based    */
+    l_int32  success;   /* overall result of the test                       */
+    l_int32  display;   /* 1 if in display mode; 0 otherwise                */
+    L_TIMER  tstart;    /* marks beginning of the reg test                  */
+};
+typedef struct L_RegParams  L_REGPARAMS;
+
+
+    /* Running modes for the test */
+enum {
+    L_REG_GENERATE = 0,
+    L_REG_COMPARE = 1,
+    L_REG_DISPLAY = 2
+};
+
+
+#endif  /* LEPTONICA_REGUTILS_H */
+
diff --git a/src/rop.c b/src/rop.c
new file mode 100644 (file)
index 0000000..f691b7d
--- /dev/null
+++ b/src/rop.c
@@ -0,0 +1,506 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  rop.c
+ *
+ *      General rasterop
+ *           l_int32    pixRasterop()
+ *
+ *      In-place full band translation
+ *           l_int32    pixRasteropVip()
+ *           l_int32    pixRasteropHip()
+ *
+ *      Full image translation (general and in-place)
+ *           l_int32    pixTranslate()
+ *           l_int32    pixRasteropIP()
+ *
+ *      Full image rasterop with no translation
+ *           l_int32    pixRasteropFullImage()
+ */
+
+
+#include <string.h>
+#include "allheaders.h"
+
+/*--------------------------------------------------------------------*
+ *                General rasterop (basic pix interface)              *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixRasterop()
+ *
+ *      Input:  pixd   (dest pix)
+ *              dx     (x val of UL corner of dest rectangle)
+ *              dy     (y val of UL corner of dest rectangle)
+ *              dw     (width of dest rectangle)
+ *              dh     (height of dest rectangle)
+ *              op     (op code)
+ *              pixs   (src pix)
+ *              sx     (x val of UL corner of src rectangle)
+ *              sy     (y val of UL corner of src rectangle)
+ *      Return: 0 if OK; 1 on error.
+ *
+ *  Notes:
+ *      (1) This has the standard set of 9 args for rasterop.
+ *          This function is your friend; it is worth memorizing!
+ *      (2) If the operation involves only dest, this calls
+ *          rasteropUniLow().  Otherwise, checks depth of the
+ *          src and dest, and if they match, calls rasteropLow().
+ *      (3) For the two-image operation, where both pixs and pixd
+ *          are defined, they are typically different images.  However
+ *          there are cases, such as pixSetMirroredBorder(), where
+ *          in-place operations can be done, blitting pixels from
+ *          one part of pixd to another.  Consequently, we permit
+ *          such operations.  If you use them, be sure that there
+ *          is no overlap between the source and destination rectangles
+ *          in pixd (!)
+ *
+ *  Background:
+ *  -----------
+ *
+ *  There are 18 operations, described by the op codes in pix.h.
+ *
+ *  One, PIX_DST, is a no-op.
+ *
+ *  Three, PIX_CLR, PIX_SET, and PIX_NOT(PIX_DST) operate only on the dest.
+ *  These are handled by the low-level rasteropUniLow().
+ *
+ *  The other 14 involve the both the src and the dest, and depend on
+ *  the bit values of either just the src or the bit values of both
+ *  src and dest.  They are handled by rasteropLow():
+ *
+ *          PIX_SRC                             s
+ *          PIX_NOT(PIX_SRC)                   ~s
+ *          PIX_SRC | PIX_DST                   s | d
+ *          PIX_SRC & PIX_DST                   s & d
+ *          PIX_SRC ^ PIX_DST                   s ^ d
+ *          PIX_NOT(PIX_SRC) | PIX_DST         ~s | d
+ *          PIX_NOT(PIX_SRC) & PIX_DST         ~s & d
+ *          PIX_NOT(PIX_SRC) ^ PIX_DST         ~s ^ d
+ *          PIX_SRC | PIX_NOT(PIX_DST)          s | ~d
+ *          PIX_SRC & PIX_NOT(PIX_DST)          s & ~d
+ *          PIX_SRC ^ PIX_NOT(PIX_DST)          s ^ ~d
+ *          PIX_NOT(PIX_SRC | PIX_DST)         ~(s | d)
+ *          PIX_NOT(PIX_SRC & PIX_DST)         ~(s & d)
+ *          PIX_NOT(PIX_SRC ^ PIX_DST)         ~(s ^ d)
+ *
+ *  Each of these is implemented with one of three low-level
+ *  functions, depending on the alignment of the left edge
+ *  of the src and dest rectangles:
+ *      * a fastest implementation if both left edges are
+ *        (32-bit) word aligned
+ *      * a very slightly slower implementation if both left
+ *        edges have the same relative (32-bit) word alignment
+ *      * the general routine that is invoked when
+ *        both left edges have different word alignment
+ *
+ *  Of the 14 binary rasterops above, only 12 are unique
+ *  logical combinations (out of a possible 16) of src
+ *  and dst bits:
+ *
+ *        (sd)         (11)   (10)   (01)   (00)
+ *   -----------------------------------------------
+ *         s            1      1      0      0
+ *        ~s            0      1      0      1
+ *       s | d          1      1      1      0
+ *       s & d          1      0      0      0
+ *       s ^ d          0      1      1      0
+ *      ~s | d          1      0      1      1
+ *      ~s & d          0      0      1      0
+ *      ~s ^ d          1      0      0      1
+ *       s | ~d         1      1      0      1
+ *       s & ~d         0      1      0      0
+ *       s ^ ~d         1      0      0      1
+ *      ~(s | d)        0      0      0      1
+ *      ~(s & d)        0      1      1      1
+ *      ~(s ^ d)        1      0      0      1
+ *
+ *  Note that the following three operations are equivalent:
+ *      ~(s ^ d)
+ *      ~s ^ d
+ *      s ^ ~d
+ *  and in the implementation, we call them out with the first form;
+ *  namely, ~(s ^ d).
+ *
+ *  Of the 16 possible binary combinations of src and dest bits,
+ *  the remaining 4 unique ones are independent of the src bit.
+ *  They depend on either just the dest bit or on neither
+ *  the src nor dest bits:
+ *
+ *         d            1      0      1      0    (indep. of s)
+ *        ~d            0      1      0      1    (indep. of s)
+ *        CLR           0      0      0      0    (indep. of both s & d)
+ *        SET           1      1      1      1    (indep. of both s & d)
+ *
+ *  As mentioned above, three of these are implemented by
+ *  rasteropUniLow(), and one is a no-op.
+ *
+ *  How can these operation codes be represented by bits
+ *  in such a way that when the basic operations are performed
+ *  on the bits the results are unique for unique
+ *  operations, and mimic the logic table given above?
+ *
+ *  The answer is to choose a particular order of the pairings:
+ *         (sd)         (11)   (10)   (01)   (00)
+ *  (which happens to be the same as in the above table)
+ *  and to translate the result into 4-bit representations
+ *  of s and d.  For example, the Sun rasterop choice
+ *  (omitting the extra bit for clipping) is
+ *
+ *      PIX_SRC      0xc
+ *      PIX_DST      0xa
+ *
+ *  This corresponds to our pairing order given above:
+ *         (sd)         (11)   (10)   (01)   (00)
+ *  where for s = 1 we get the bit pattern
+ *       PIX_SRC:        1      1      0      0     (0xc)
+ *  and for d = 1 we get the pattern
+ *       PIX_DST:         1      0      1      0    (0xa)
+ *
+ *  OK, that's the pairing order that Sun chose.  How many different
+ *  ways can we assign bit patterns to PIX_SRC and PIX_DST to get
+ *  the boolean ops to work out?  Any of the 4 pairs can be put
+ *  in the first position, any of the remaining 3 pairs can go
+ *  in the second; and one of the remaining 2 pairs can go the the third.
+ *  There is a total of 4*3*2 = 24 ways these pairs can be permuted.
+ */
+l_int32
+pixRasterop(PIX     *pixd,
+            l_int32  dx,
+            l_int32  dy,
+            l_int32  dw,
+            l_int32  dh,
+            l_int32  op,
+            PIX     *pixs,
+            l_int32  sx,
+            l_int32  sy)
+{
+l_int32  dd;
+
+    PROCNAME("pixRasterop");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+
+    if (op == PIX_DST)   /* no-op */
+        return 0;
+
+        /* Check if operation is only on dest */
+    dd = pixGetDepth(pixd);
+    if (op == PIX_CLR || op == PIX_SET || op == PIX_NOT(PIX_DST)) {
+        rasteropUniLow(pixGetData(pixd),
+                       pixGetWidth(pixd), pixGetHeight(pixd), dd,
+                        pixGetWpl(pixd),
+                       dx, dy, dw, dh,
+                       op);
+        return 0;
+    }
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+        /* Check depth of src and dest; these must agree */
+    if (dd != pixGetDepth(pixs))
+        return ERROR_INT("depths of pixs and pixd differ", procName, 1);
+
+    rasteropLow(pixGetData(pixd),
+                pixGetWidth(pixd), pixGetHeight(pixd), dd,
+                pixGetWpl(pixd),
+                dx, dy, dw, dh,
+                op,
+                pixGetData(pixs),
+                pixGetWidth(pixs), pixGetHeight(pixs),
+                pixGetWpl(pixs),
+                sx, sy);
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                    In-place full band translation                  *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixRasteropVip()
+ *
+ *      Input:  pixd (in-place)
+ *              bx  (left edge of vertical band)
+ *              bw  (width of vertical band)
+ *              vshift (vertical shift of band; vshift > 0 is down)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This rasterop translates a vertical band of the
+ *          image either up or down, bringing in either white
+ *          or black pixels from outside the image.
+ *      (2) The vertical band extends the full height of pixd.
+ *      (3) If a colormap exists, the nearest color to white or black
+ *          is brought in.
+ */
+l_int32
+pixRasteropVip(PIX     *pixd,
+               l_int32  bx,
+               l_int32  bw,
+               l_int32  vshift,
+               l_int32  incolor)
+{
+l_int32   w, h, d, index, op;
+PIX      *pixt;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixRasteropVip");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return ERROR_INT("invalid value for incolor", procName, 1);
+    if (bw <= 0)
+        return ERROR_INT("bw must be > 0", procName, 1);
+
+    if (vshift == 0)
+        return 0;
+
+    pixGetDimensions(pixd, &w, &h, &d);
+    rasteropVipLow(pixGetData(pixd), w, h, d, pixGetWpl(pixd), bx, bw, vshift);
+
+    cmap = pixGetColormap(pixd);
+    if (!cmap) {
+        if ((d == 1 && incolor == L_BRING_IN_BLACK) ||
+            (d > 1 && incolor == L_BRING_IN_WHITE))
+            op = PIX_SET;
+        else
+            op = PIX_CLR;
+
+            /* Set the pixels brought in at top or bottom */
+        if (vshift > 0)
+            pixRasterop(pixd, bx, 0, bw, vshift, op, NULL, 0, 0);
+        else  /* vshift < 0 */
+            pixRasterop(pixd, bx, h + vshift, bw, -vshift, op, NULL, 0, 0);
+        return 0;
+    }
+
+        /* Get the nearest index and fill with that */
+    if (incolor == L_BRING_IN_BLACK)
+        pixcmapGetRankIntensity(cmap, 0.0, &index);
+    else  /* white */
+        pixcmapGetRankIntensity(cmap, 1.0, &index);
+    pixt = pixCreate(bw, L_ABS(vshift), d);
+    pixSetAllArbitrary(pixt, index);
+    if (vshift > 0)
+        pixRasterop(pixd, bx, 0, bw, vshift, PIX_SRC, pixt, 0, 0);
+    else  /* vshift < 0 */
+        pixRasterop(pixd, bx, h + vshift, bw, -vshift, PIX_SRC, pixt, 0, 0);
+    pixDestroy(&pixt);
+    return 0;
+}
+
+
+/*!
+ *  pixRasteropHip()
+ *
+ *      Input:  pixd (in-place operation)
+ *              by  (top of horizontal band)
+ *              bh  (height of horizontal band)
+ *              hshift (horizontal shift of band; hshift > 0 is to right)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This rasterop translates a horizontal band of the
+ *          image either left or right, bringing in either white
+ *          or black pixels from outside the image.
+ *      (2) The horizontal band extends the full width of pixd.
+ *      (3) If a colormap exists, the nearest color to white or black
+ *          is brought in.
+ */
+l_int32
+pixRasteropHip(PIX     *pixd,
+               l_int32  by,
+               l_int32  bh,
+               l_int32  hshift,
+               l_int32  incolor)
+{
+l_int32   w, h, d, index, op;
+PIX      *pixt;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixRasteropHip");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return ERROR_INT("invalid value for incolor", procName, 1);
+    if (bh <= 0)
+        return ERROR_INT("bh must be > 0", procName, 1);
+
+    if (hshift == 0)
+        return 0;
+
+    pixGetDimensions(pixd, &w, &h, &d);
+    rasteropHipLow(pixGetData(pixd), h, d, pixGetWpl(pixd), by, bh, hshift);
+
+    cmap = pixGetColormap(pixd);
+    if (!cmap) {
+        if ((d == 1 && incolor == L_BRING_IN_BLACK) ||
+            (d > 1 && incolor == L_BRING_IN_WHITE))
+            op = PIX_SET;
+        else
+            op = PIX_CLR;
+
+            /* Set the pixels brought in at left or right */
+        if (hshift > 0)
+            pixRasterop(pixd, 0, by, hshift, bh, op, NULL, 0, 0);
+        else  /* hshift < 0 */
+            pixRasterop(pixd, w + hshift, by, -hshift, bh, op, NULL, 0, 0);
+        return 0;
+    }
+
+        /* Get the nearest index and fill with that */
+    if (incolor == L_BRING_IN_BLACK)
+        pixcmapGetRankIntensity(cmap, 0.0, &index);
+    else  /* white */
+        pixcmapGetRankIntensity(cmap, 1.0, &index);
+    pixt = pixCreate(L_ABS(hshift), bh, d);
+    pixSetAllArbitrary(pixt, index);
+    if (hshift > 0)
+        pixRasterop(pixd, 0, by, hshift, bh, PIX_SRC, pixt, 0, 0);
+    else  /* hshift < 0 */
+        pixRasterop(pixd, w + hshift, by, -hshift, bh, PIX_SRC, pixt, 0, 0);
+    pixDestroy(&pixt);
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *             Full image translation (general and in-place)          *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixTranslate()
+ *
+ *      Input:  pixd (<optional> destination: this can be null,
+ *                    equal to pixs, or different from pixs)
+ *              pixs
+ *              hshift (horizontal shift; hshift > 0 is to right)
+ *              vshift (vertical shift; vshift > 0 is down)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error.
+ *
+ *  Notes:
+ *      (1) The general pattern is:
+ *            pixd = pixTranslate(pixd, pixs, ...);
+ *          For clarity, when you know the case, use one of these:
+ *            pixd = pixTranslate(NULL, pixs, ...);  // new
+ *            pixTranslate(pixs, pixs, ...);         // in-place
+ *            pixTranslate(pixd, pixs, ...);         // to existing pixd
+ *      (2) If an existing pixd is not the same size as pixs, the
+ *          image data will be reallocated.
+ */
+PIX *
+pixTranslate(PIX     *pixd,
+             PIX     *pixs,
+             l_int32  hshift,
+             l_int32  vshift,
+             l_int32  incolor)
+{
+    PROCNAME("pixTranslate");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+        /* Prepare pixd for in-place operation */
+    if ((pixd = pixCopy(pixd, pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    pixRasteropIP(pixd, hshift, vshift, incolor);
+    return pixd;
+}
+
+
+/*!
+ *  pixRasteropIP()
+ *
+ *      Input:  pixd (in-place translation)
+ *              hshift (horizontal shift; hshift > 0 is to right)
+ *              vshift (vertical shift; vshift > 0 is down)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixRasteropIP(PIX     *pixd,
+              l_int32  hshift,
+              l_int32  vshift,
+              l_int32  incolor)
+{
+l_int32  w, h;
+
+    PROCNAME("pixRasteropIP");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+
+    pixGetDimensions(pixd, &w, &h, NULL);
+    pixRasteropHip(pixd, 0, h, hshift, incolor);
+    pixRasteropVip(pixd, 0, w, vshift, incolor);
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                 Full image rasterop with no shifts                 *
+ *--------------------------------------------------------------------*/
+/*!
+ *  pixRasteropFullImage()
+ *
+ *      Input:  pixd
+ *              pixs
+ *              op (any of the op-codes)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      - this is a wrapper for a common 2-image raster operation
+ *      - both pixs and pixd must be defined
+ *      - the operation is performed with aligned UL corners of pixs and pixd
+ *      - the operation clips to the smallest pix; if the width or height
+ *        of pixd is larger than pixs, some pixels in pixd will be unchanged
+ */
+l_int32
+pixRasteropFullImage(PIX     *pixd,
+                     PIX     *pixs,
+                     l_int32  op)
+{
+    PROCNAME("pixRasteropFullImage");
+
+    if (!pixd)
+        return ERROR_INT("pixd not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    pixRasterop(pixd, 0, 0, pixGetWidth(pixd), pixGetHeight(pixd), op,
+                pixs, 0, 0);
+    return 0;
+}
diff --git a/src/ropiplow.c b/src/ropiplow.c
new file mode 100644 (file)
index 0000000..e4e2058
--- /dev/null
@@ -0,0 +1,428 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  ropiplow.c
+ *
+ *      Low level in-place full height vertical block transfer
+ *
+ *           void     rasteropVipLow()
+ *
+ *      Low level in-place full width horizontal block transfer
+ *
+ *           void     rasteropHipLow()
+ *           void     shiftDataHorizontalLow()
+ */
+
+
+#include <string.h>
+#include "allheaders.h"
+
+#define COMBINE_PARTIAL(d, s, m)     ( ((d) & ~(m)) | ((s) & (m)) )
+
+static const l_uint32 lmask32[] = {0x0,
+    0x80000000, 0xc0000000, 0xe0000000, 0xf0000000,
+    0xf8000000, 0xfc000000, 0xfe000000, 0xff000000,
+    0xff800000, 0xffc00000, 0xffe00000, 0xfff00000,
+    0xfff80000, 0xfffc0000, 0xfffe0000, 0xffff0000,
+    0xffff8000, 0xffffc000, 0xffffe000, 0xfffff000,
+    0xfffff800, 0xfffffc00, 0xfffffe00, 0xffffff00,
+    0xffffff80, 0xffffffc0, 0xffffffe0, 0xfffffff0,
+    0xfffffff8, 0xfffffffc, 0xfffffffe, 0xffffffff};
+
+static const l_uint32 rmask32[] = {0x0,
+    0x00000001, 0x00000003, 0x00000007, 0x0000000f,
+    0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
+    0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff,
+    0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff,
+    0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff,
+    0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff,
+    0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff,
+    0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff};
+
+
+/*--------------------------------------------------------------------*
+ *                 Low-level Vertical In-place Rasterop               *
+ *--------------------------------------------------------------------*/
+/*!
+ *  rasteropVipLow()
+ *
+ *      Input:  data   (ptr to image data)
+ *              pixw   (width)
+ *              pixh   (height)
+ *              depth  (depth)
+ *              wpl    (wpl)
+ *              x      (x val of UL corner of rectangle)
+ *              w      (width of rectangle)
+ *              shift  (+ shifts data downward in vertical column)
+ *      Return: 0 if OK; 1 on error.
+ *
+ *  Notes:
+ *      (1) This clears the pixels that are left exposed after the
+ *          translation.  You can consider them as pixels that are
+ *          shifted in from outside the image.  This can be later
+ *          overridden by the incolor parameter in higher-level functions
+ *          that call this.  For example, for images with depth > 1,
+ *          these pixels are cleared to black; to be white they
+ *          must later be SET to white.  See, e.g., pixRasteropVip().
+ *      (2) This function scales the width to accommodate any depth,
+ *          performs clipping, and then does the in-place rasterop.
+ */
+void
+rasteropVipLow(l_uint32  *data,
+               l_int32    pixw,
+               l_int32    pixh,
+               l_int32    depth,
+               l_int32    wpl,
+               l_int32    x,
+               l_int32    w,
+               l_int32    shift)
+{
+l_int32    fwpartb;    /* boolean (1, 0) if first word is partial */
+l_int32    fwpart2b;   /* boolean (1, 0) if first word is doubly partial */
+l_uint32   fwmask;     /* mask for first partial word */
+l_int32    fwbits;     /* first word bits in ovrhang */
+l_uint32  *pdfwpart;   /* ptr to first partial dest word */
+l_uint32  *psfwpart;   /* ptr to first partial src word */
+l_int32    fwfullb;    /* boolean (1, 0) if there exists a full word */
+l_int32    nfullw;     /* number of full words */
+l_uint32  *pdfwfull;   /* ptr to first full dest word */
+l_uint32  *psfwfull;   /* ptr to first full src word */
+l_int32    lwpartb;    /* boolean (1, 0) if last word is partial */
+l_uint32   lwmask;     /* mask for last partial word */
+l_int32    lwbits;     /* last word bits in ovrhang */
+l_uint32  *pdlwpart;   /* ptr to last partial dest word */
+l_uint32  *pslwpart;   /* ptr to last partial src word */
+l_int32    dirwpl;     /* directed wpl (-wpl * sign(shift)) */
+l_int32    absshift;   /* absolute value of shift; for use in iterator */
+l_int32    vlimit;     /* vertical limit value for iterations */
+l_int32    i, j;
+
+
+   /*--------------------------------------------------------*
+    *            Scale horizontal dimensions by depth        *
+    *--------------------------------------------------------*/
+    if (depth != 1) {
+        pixw *= depth;
+        x *= depth;
+        w *= depth;
+    }
+
+
+   /*--------------------------------------------------------*
+    *                   Clip horizontally                    *
+    *--------------------------------------------------------*/
+    if (x < 0) {
+        w += x;    /* reduce w */
+        x = 0;     /* clip to x = 0 */
+    }
+    if (x >= pixw || w <= 0)  /* no part of vertical slice is in the image */
+        return;
+
+    if (x + w > pixw)
+        w = pixw - x;   /* clip to x + w = pixw */
+
+    /*--------------------------------------------------------*
+     *                Preliminary calculations                *
+     *--------------------------------------------------------*/
+        /* is the first word partial? */
+    if ((x & 31) == 0) {  /* if not */
+        fwpartb = 0;
+        fwbits = 0;
+    } else {  /* if so */
+        fwpartb = 1;
+        fwbits = 32 - (x & 31);
+        fwmask = rmask32[fwbits];
+        if (shift >= 0) { /* go up from bottom */
+            pdfwpart = data + wpl * (pixh - 1) + (x >> 5);
+            psfwpart = data + wpl * (pixh - 1 - shift) + (x >> 5);
+        } else {  /* go down from top */
+            pdfwpart = data + (x >> 5);
+            psfwpart = data - wpl * shift + (x >> 5);
+        }
+    }
+
+        /* is the first word doubly partial? */
+    if (w >= fwbits) {  /* if not */
+        fwpart2b = 0;
+    } else {  /* if so */
+        fwpart2b = 1;
+        fwmask &= lmask32[32 - fwbits + w];
+    }
+
+        /* is there a full dest word? */
+    if (fwpart2b == 1) {  /* not */
+        fwfullb = 0;
+        nfullw = 0;
+    } else {
+        nfullw = (w - fwbits) >> 5;
+        if (nfullw == 0) {  /* if not */
+            fwfullb = 0;
+        } else {  /* if so */
+            fwfullb = 1;
+            if (fwpartb) {
+                pdfwfull = pdfwpart + 1;
+                psfwfull = psfwpart + 1;
+            } else if (shift >= 0) { /* go up from bottom */
+                pdfwfull = data + wpl * (pixh - 1) + (x >> 5);
+                psfwfull = data + wpl * (pixh - 1 - shift) + (x >> 5);
+            } else {  /* go down from top */
+                pdfwfull = data + (x >> 5);
+                psfwfull = data - wpl * shift + (x >> 5);
+            }
+        }
+    }
+
+        /* is the last word partial? */
+    lwbits = (x + w) & 31;
+    if (fwpart2b == 1 || lwbits == 0) {  /* if not */
+        lwpartb = 0;
+    } else {
+        lwpartb = 1;
+        lwmask = lmask32[lwbits];
+        if (fwpartb) {
+            pdlwpart = pdfwpart + 1 + nfullw;
+            pslwpart = psfwpart + 1 + nfullw;
+        } else if (shift >= 0) { /* go up from bottom */
+            pdlwpart = data + wpl * (pixh - 1) + (x >> 5) + nfullw;
+            pslwpart = data + wpl * (pixh - 1 - shift) + (x >> 5) + nfullw;
+        } else {  /* go down from top */
+            pdlwpart = data + (x >> 5) + nfullw;
+            pslwpart = data - wpl * shift + (x >> 5) + nfullw;
+        }
+    }
+
+        /* determine the direction of flow from the shift
+         * If the shift >= 0, data flows downard from src
+         * to dest, starting at the bottom and working up.
+         * If shift < 0, data flows upward from src to
+         * dest, starting at the top and working down. */
+    dirwpl = (shift >= 0) ? -wpl : wpl;
+    absshift = L_ABS(shift);
+    vlimit = L_MAX(0, pixh - absshift);
+
+
+/*--------------------------------------------------------*
+ *            Now we're ready to do the ops               *
+ *--------------------------------------------------------*/
+
+        /* Do the first partial word */
+    if (fwpartb) {
+        for (i = 0; i < vlimit; i++) {
+            *pdfwpart = COMBINE_PARTIAL(*pdfwpart, *psfwpart, fwmask);
+            pdfwpart += dirwpl;
+            psfwpart += dirwpl;
+        }
+
+            /* Clear the incoming pixels */
+        for (i = vlimit; i < pixh; i++) {
+            *pdfwpart = COMBINE_PARTIAL(*pdfwpart, 0x0, fwmask);
+            pdfwpart += dirwpl;
+        }
+    }
+
+        /* Do the full words */
+    if (fwfullb) {
+        for (i = 0; i < vlimit; i++) {
+            for (j = 0; j < nfullw; j++)
+                *(pdfwfull + j) = *(psfwfull + j);
+            pdfwfull += dirwpl;
+            psfwfull += dirwpl;
+        }
+
+            /* Clear the incoming pixels */
+        for (i = vlimit; i < pixh; i++) {
+            for (j = 0; j < nfullw; j++)
+                *(pdfwfull + j) = 0x0;
+            pdfwfull += dirwpl;
+        }
+    }
+
+        /* Do the last partial word */
+    if (lwpartb) {
+        for (i = 0; i < vlimit; i++) {
+            *pdlwpart = COMBINE_PARTIAL(*pdlwpart, *pslwpart, lwmask);
+            pdlwpart += dirwpl;
+            pslwpart += dirwpl;
+        }
+
+            /* Clear the incoming pixels */
+        for (i = vlimit; i < pixh; i++) {
+            *pdlwpart = COMBINE_PARTIAL(*pdlwpart, 0x0, lwmask);
+            pdlwpart += dirwpl;
+        }
+    }
+
+    return;
+}
+
+
+
+/*--------------------------------------------------------------------*
+ *                 Low-level Horizontal In-place Rasterop             *
+ *--------------------------------------------------------------------*/
+/*!
+ *  rasteropHipLow()
+ *
+ *      Input:  data   (ptr to image data)
+ *              pixh   (height)
+ *              depth  (depth)
+ *              wpl    (wpl)
+ *              y      (y val of UL corner of rectangle)
+ *              h      (height of rectangle)
+ *              shift  (+ shifts data to the left in a horizontal column)
+ *      Return: 0 if OK; 1 on error.
+ *
+ *  Notes:
+ *      (1) This clears the pixels that are left exposed after the rasterop.
+ *          Therefore, for Pix with depth > 1, these pixels become black,
+ *          and must be subsequently SET if they are to be white.
+ *          For example, see pixRasteropHip().
+ *      (2) This function performs clipping and calls shiftDataHorizontalLine()
+ *          to do the in-place rasterop on each line.
+ */
+void
+rasteropHipLow(l_uint32  *data,
+               l_int32    pixh,
+               l_int32    depth,
+               l_int32    wpl,
+               l_int32    y,
+               l_int32    h,
+               l_int32    shift)
+{
+l_int32    i;
+l_uint32  *line;
+
+        /* clip band if necessary */
+    if (y < 0) {
+        h += y;  /* reduce h */
+        y = 0;   /* clip to y = 0 */
+    }
+    if (h <= 0 || y > pixh)  /* no part of horizontal slice is in the image */
+        return;
+
+    if (y + h > pixh)
+        h = pixh - y;   /* clip to y + h = pixh */
+
+    for (i = y; i < y + h; i++) {
+        line = data + i * wpl;
+        shiftDataHorizontalLow(line, wpl, line, wpl, shift * depth);
+    }
+}
+
+
+/*!
+ *  shiftDataHorizontalLow()
+ *
+ *      Input:  datad  (ptr to beginning of dest line)
+ *              wpld   (wpl of dest)
+ *              datas  (ptr to beginning of src line)
+ *              wpls   (wpl of src)
+ *              shift  (horizontal shift of block; >0 is to right)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This can also be used for in-place operation; see, e.g.,
+ *          rasteropHipLow().
+ *      (2) We are clearing the pixels that are shifted in from
+ *          outside the image.  This can be overridden by the
+ *          incolor parameter in higher-level functions that call this.
+ */
+void
+shiftDataHorizontalLow(l_uint32  *datad,
+                       l_int32    wpld,
+                       l_uint32  *datas,
+                       l_int32    wpls,
+                       l_int32    shift)
+{
+l_int32    j, firstdw, wpl, rshift, lshift;
+l_uint32  *lined, *lines;
+
+    lined = datad;
+    lines = datas;
+
+    if (shift >= 0) {   /* src shift to right; data flows to
+                         * right, starting at right edge and
+                         * progressing leftward. */
+        firstdw = shift / 32;
+        wpl = L_MIN(wpls, wpld - firstdw);
+        lined += firstdw + wpl - 1;
+        lines += wpl - 1;
+        rshift = shift & 31;
+        if (rshift == 0) {
+            for (j = 0; j < wpl; j++)
+                *lined-- = *lines--;
+
+                /* clear out the rest to the left edge */
+            for (j = 0; j < firstdw; j++)
+                *lined-- = 0;
+        } else {
+            lshift = 32 - rshift;
+            for (j = 1; j < wpl; j++) {
+                *lined-- = *(lines - 1) << lshift | *lines >> rshift;
+                lines--;
+            }
+            *lined = *lines >> rshift;  /* partial first */
+
+                /* clear out the rest to the left edge */
+            *lined &= ~lmask32[rshift];
+            lined--;
+            for (j = 0; j < firstdw; j++)
+                *lined-- = 0;
+        }
+    } else {  /* src shift to left; data flows to left, starting
+             * at left edge and progressing rightward. */
+        firstdw = (-shift) / 32;
+        wpl = L_MIN(wpls - firstdw, wpld);
+        lines += firstdw;
+        lshift = (-shift) & 31;
+        if (lshift == 0) {
+            for (j = 0; j < wpl; j++)
+                *lined++ = *lines++;
+
+                /* clear out the rest to the right edge */
+            for (j = 0; j < firstdw; j++)
+                *lined++ = 0;
+        } else {
+            rshift = 32 - lshift;
+            for (j = 1; j < wpl; j++) {
+                *lined++ = *lines << lshift | *(lines + 1) >> rshift;
+                lines++;
+            }
+            *lined = *lines << lshift;  /* partial last */
+
+                /* clear out the rest to the right edge */
+                /* first clear the lshift pixels of this partial word */
+            *lined &= ~rmask32[lshift];
+            lined++;
+                /* then the remaining words to the right edge */
+            for (j = 0; j < firstdw; j++)
+                *lined++ = 0;
+        }
+    }
+
+    return;
+}
diff --git a/src/roplow.c b/src/roplow.c
new file mode 100644 (file)
index 0000000..270b1f9
--- /dev/null
@@ -0,0 +1,2118 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  roplow.c
+ *
+ *      Low level dest-only
+ *           void            rasteropUniLow()
+ *           static void     rasteropUniWordAlignedlLow()
+ *           static void     rasteropUniGeneralLow()
+ *
+ *      Low level src and dest
+ *           void            rasteropLow()
+ *           static void     rasteropWordAlignedLow()
+ *           static void     rasteropVAlignedLow()
+ *           static void     rasteropGeneralLow()
+ *
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+#define COMBINE_PARTIAL(d, s, m)     ( ((d) & ~(m)) | ((s) & (m)) )
+
+static const l_int32  SHIFT_LEFT  = 0;
+static const l_int32  SHIFT_RIGHT = 1;
+
+static void rasteropUniWordAlignedLow(l_uint32 *datad, l_int32 dwpl, l_int32 dx,
+                                      l_int32 dy, l_int32  dw, l_int32 dh,
+                                      l_int32 op);
+
+static void rasteropUniGeneralLow(l_uint32 *datad, l_int32 dwpl, l_int32 dx,
+                                  l_int32 dy, l_int32 dw, l_int32  dh,
+                                  l_int32 op);
+
+static void rasteropWordAlignedLow(l_uint32 *datad, l_int32 dwpl, l_int32 dx,
+                                   l_int32 dy, l_int32 dw, l_int32 dh,
+                                   l_int32 op, l_uint32 *datas, l_int32 swpl,
+                                   l_int32 sx, l_int32 sy);
+
+static void rasteropVAlignedLow(l_uint32 *datad, l_int32 dwpl, l_int32 dx,
+                                l_int32 dy, l_int32 dw, l_int32 dh,
+                                l_int32 op, l_uint32 *datas, l_int32 swpl,
+                                l_int32 sx, l_int32 sy);
+
+static void rasteropGeneralLow(l_uint32 *datad, l_int32 dwpl, l_int32 dx,
+                               l_int32 dy, l_int32 dw, l_int32 dh,
+                               l_int32 op, l_uint32 *datas, l_int32 swpl,
+                               l_int32 sx, l_int32 sy);
+
+
+static const l_uint32 lmask32[] = {0x0,
+    0x80000000, 0xc0000000, 0xe0000000, 0xf0000000,
+    0xf8000000, 0xfc000000, 0xfe000000, 0xff000000,
+    0xff800000, 0xffc00000, 0xffe00000, 0xfff00000,
+    0xfff80000, 0xfffc0000, 0xfffe0000, 0xffff0000,
+    0xffff8000, 0xffffc000, 0xffffe000, 0xfffff000,
+    0xfffff800, 0xfffffc00, 0xfffffe00, 0xffffff00,
+    0xffffff80, 0xffffffc0, 0xffffffe0, 0xfffffff0,
+    0xfffffff8, 0xfffffffc, 0xfffffffe, 0xffffffff};
+
+static const l_uint32 rmask32[] = {0x0,
+    0x00000001, 0x00000003, 0x00000007, 0x0000000f,
+    0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
+    0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff,
+    0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff,
+    0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff,
+    0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff,
+    0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff,
+    0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff};
+
+
+/*--------------------------------------------------------------------*
+ *                     Low-level dest-only rasterops                  *
+ *--------------------------------------------------------------------*/
+/*!
+ *  rasteropUniLow()
+ *
+ *      Input:  datad  (ptr to dest image data)
+ *              dpixw  (width of dest)
+ *              dpixh  (height of dest)
+ *              depth  (depth of src and dest)
+ *              dwpl   (wpl of dest)
+ *              dx     (x val of UL corner of dest rectangle)
+ *              dy     (y val of UL corner of dest rectangle)
+ *              dw     (width of dest rectangle)
+ *              dh     (height of dest rectangle)
+ *              op     (op code)
+ *      Return: void
+ *
+ *  Action: scales width, performs clipping, checks alignment, and
+ *          dispatches for the rasterop.
+ */
+void
+rasteropUniLow(l_uint32  *datad,
+               l_int32    dpixw,
+               l_int32    dpixh,
+               l_int32    depth,
+               l_int32    dwpl,
+               l_int32    dx,
+               l_int32    dy,
+               l_int32    dw,
+               l_int32    dh,
+               l_int32    op)
+{
+l_int32  dhangw, dhangh;
+
+   /* -------------------------------------------------------*
+    *            scale horizontal dimensions by depth
+    * -------------------------------------------------------*/
+    if (depth != 1) {
+        dpixw *= depth;
+        dx *= depth;
+        dw *= depth;
+    }
+
+   /* -------------------------------------------------------*
+    *            clip rectangle to dest image
+    * -------------------------------------------------------*/
+       /* first, clip horizontally (dx, dw) */
+    if (dx < 0) {
+        dw += dx;  /* reduce dw */
+        dx = 0;
+    }
+    dhangw = dx + dw - dpixw;  /* rect ovhang dest to right */
+    if (dhangw > 0)
+        dw -= dhangw;  /* reduce dw */
+
+       /* then, clip vertically (dy, dh) */
+    if (dy < 0) {
+        dh += dy;  /* reduce dh */
+        dy = 0;
+    }
+    dhangh = dy + dh - dpixh;  /* rect ovhang dest below */
+    if (dhangh > 0)
+        dh -= dhangh;  /* reduce dh */
+
+        /* if clipped entirely, quit */
+    if ((dw <= 0) || (dh <= 0))
+        return;
+
+   /* -------------------------------------------------------*
+    *       dispatch to aligned or non-aligned blitters
+    * -------------------------------------------------------*/
+    if ((dx & 31) == 0)
+        rasteropUniWordAlignedLow(datad, dwpl, dx, dy, dw, dh, op);
+    else
+        rasteropUniGeneralLow(datad, dwpl, dx, dy, dw, dh, op);
+    return;
+}
+
+
+
+/*--------------------------------------------------------------------*
+ *           Static low-level uni rasterop with word alignment        *
+ *--------------------------------------------------------------------*/
+/*!
+ *  rasteropUniWordAlignedLow()
+ *
+ *      Input:  datad  (ptr to dest image data)
+ *              dwpl   (wpl of dest)
+ *              dx     (x val of UL corner of dest rectangle)
+ *              dy     (y val of UL corner of dest rectangle)
+ *              dw     (width of dest rectangle)
+ *              dh     (height of dest rectangle)
+ *              op     (op code)
+ *      Return: void
+ *
+ *  This is called when the dest rect is left aligned
+ *  on (32-bit) word boundaries.   That is: dx & 31 == 0.
+ *
+ *  We make an optimized implementation of this because
+ *  it is a common case: e.g., operating on a full dest image.
+ */
+static void
+rasteropUniWordAlignedLow(l_uint32  *datad,
+                          l_int32    dwpl,
+                          l_int32    dx,
+                          l_int32    dy,
+                          l_int32    dw,
+                          l_int32    dh,
+                          l_int32    op)
+{
+l_int32    nfullw;     /* number of full words */
+l_uint32  *pfword;     /* ptr to first word */
+l_int32    lwbits;     /* number of ovrhang bits in last partial word */
+l_uint32   lwmask;     /* mask for last partial word */
+l_uint32  *lined;
+l_int32    i, j;
+
+    /*--------------------------------------------------------*
+     *                Preliminary calculations                *
+     *--------------------------------------------------------*/
+    nfullw = dw >> 5;
+    lwbits = dw & 31;
+    if (lwbits)
+        lwmask = lmask32[lwbits];
+    pfword = datad + dwpl * dy + (dx >> 5);
+
+
+    /*--------------------------------------------------------*
+     *            Now we're ready to do the ops               *
+     *--------------------------------------------------------*/
+    switch (op)
+    {
+    case PIX_CLR:
+        for (i = 0; i < dh; i++) {
+            lined = pfword + i * dwpl;
+            for (j = 0; j < nfullw; j++)
+                *lined++ = 0x0;
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, 0x0, lwmask);
+        }
+        break;
+    case PIX_SET:
+        for (i = 0; i < dh; i++) {
+            lined = pfword + i * dwpl;
+            for (j = 0; j < nfullw; j++)
+                *lined++ = 0xffffffff;
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, 0xffffffff, lwmask);
+        }
+        break;
+    case PIX_NOT(PIX_DST):
+        for (i = 0; i < dh; i++) {
+            lined = pfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = ~(*lined);
+                lined++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, ~(*lined), lwmask);
+        }
+        break;
+    default:
+        fprintf(stderr, "Operation %d not permitted here!\n", op);
+    }
+
+    return;
+}
+
+
+/*--------------------------------------------------------------------*
+ *        Static low-level uni rasterop without word alignment        *
+ *--------------------------------------------------------------------*/
+/*!
+ *  rasteropUniGeneralLow()
+ *
+ *      Input:  datad  (ptr to dest image data)
+ *              dwpl   (wpl of dest)
+ *              dx     (x val of UL corner of dest rectangle)
+ *              dy     (y val of UL corner of dest rectangle)
+ *              dw     (width of dest rectangle)
+ *              dh     (height of dest rectangle)
+ *              op     (op code)
+ *      Return: void
+ */
+static void
+rasteropUniGeneralLow(l_uint32  *datad,
+                      l_int32    dwpl,
+                      l_int32    dx,
+                      l_int32    dy,
+                      l_int32    dw,
+                      l_int32    dh,
+                      l_int32    op)
+{
+l_int32    dfwpartb;   /* boolean (1, 0) if first dest word is partial */
+l_int32    dfwpart2b;  /* boolean (1, 0) if first dest word is doubly partial */
+l_uint32   dfwmask;    /* mask for first partial dest word */
+l_int32    dfwbits;    /* first word dest bits in ovrhang */
+l_uint32  *pdfwpart;   /* ptr to first partial dest word */
+l_int32    dfwfullb;   /* boolean (1, 0) if there exists a full dest word */
+l_int32    dnfullw;    /* number of full words in dest */
+l_uint32  *pdfwfull;   /* ptr to first full dest word */
+l_int32    dlwpartb;   /* boolean (1, 0) if last dest word is partial */
+l_uint32   dlwmask;    /* mask for last partial dest word */
+l_int32    dlwbits;    /* last word dest bits in ovrhang */
+l_uint32  *pdlwpart;   /* ptr to last partial dest word */
+l_int32    i, j;
+
+
+    /*--------------------------------------------------------*
+     *                Preliminary calculations                *
+     *--------------------------------------------------------*/
+        /* is the first word partial? */
+    if ((dx & 31) == 0) {  /* if not */
+        dfwpartb = 0;
+        dfwbits = 0;
+    } else {  /* if so */
+        dfwpartb = 1;
+        dfwbits = 32 - (dx & 31);
+        dfwmask = rmask32[dfwbits];
+        pdfwpart = datad + dwpl * dy + (dx >> 5);
+    }
+
+        /* is the first word doubly partial? */
+    if (dw >= dfwbits) {  /* if not */
+        dfwpart2b = 0;
+    } else {  /* if so */
+        dfwpart2b = 1;
+        dfwmask &= lmask32[32 - dfwbits + dw];
+    }
+
+        /* is there a full dest word? */
+    if (dfwpart2b == 1) {  /* not */
+        dfwfullb = 0;
+        dnfullw = 0;
+    } else {
+        dnfullw = (dw - dfwbits) >> 5;
+        if (dnfullw == 0) {  /* if not */
+            dfwfullb = 0;
+        } else {  /* if so */
+            dfwfullb = 1;
+            if (dfwpartb)
+                pdfwfull = pdfwpart + 1;
+            else
+                pdfwfull = datad + dwpl * dy + (dx >> 5);
+        }
+    }
+
+        /* is the last word partial? */
+    dlwbits = (dx + dw) & 31;
+    if (dfwpart2b == 1 || dlwbits == 0) {  /* if not */
+        dlwpartb = 0;
+    } else {
+        dlwpartb = 1;
+        dlwmask = lmask32[dlwbits];
+        if (dfwpartb)
+            pdlwpart = pdfwpart + 1 + dnfullw;
+        else
+            pdlwpart = datad + dwpl * dy + (dx >> 5) + dnfullw;
+    }
+
+
+    /*--------------------------------------------------------*
+     *            Now we're ready to do the ops               *
+     *--------------------------------------------------------*/
+    switch (op)
+    {
+    case PIX_CLR:
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart, 0x0, dfwmask);
+                pdfwpart += dwpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) = 0x0;
+                pdfwfull += dwpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart, 0x0, dlwmask);
+                pdlwpart += dwpl;
+            }
+        }
+        break;
+    case PIX_SET:
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart, 0xffffffff, dfwmask);
+                pdfwpart += dwpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) = 0xffffffff;
+                pdfwfull += dwpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart, 0xffffffff, dlwmask);
+                pdlwpart += dwpl;
+            }
+        }
+        break;
+    case PIX_NOT(PIX_DST):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart, ~(*pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) = ~(*(pdfwfull + j));
+                pdfwfull += dwpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart, ~(*pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+            }
+        }
+        break;
+    default:
+        fprintf(stderr, "Operation %d not permitted here!\n", op);
+    }
+
+    return;
+}
+
+
+
+/*--------------------------------------------------------------------*
+ *                   Low-level src and dest rasterops                 *
+ *--------------------------------------------------------------------*/
+/*!
+ *  rasteropLow()
+ *
+ *      Input:  datad  (ptr to dest image data)
+ *              dpixw  (width of dest)
+ *              dpixh  (height of dest)
+ *              depth  (depth of src and dest)
+ *              dwpl   (wpl of dest)
+ *              dx     (x val of UL corner of dest rectangle)
+ *              dy     (y val of UL corner of dest rectangle)
+ *              dw     (width of dest rectangle)
+ *              dh     (height of dest rectangle)
+ *              op     (op code)
+ *              datas  (ptr to src image data)
+ *              spixw  (width of src)
+ *              spixh  (height of src)
+ *              swpl   (wpl of src)
+ *              sx     (x val of UL corner of src rectangle)
+ *              sy     (y val of UL corner of src rectangle)
+ *      Return: void
+ *
+ *  Action: Scales width, performs clipping, checks alignment, and
+ *          dispatches for the rasterop.
+ *
+ *  Warning: the two images must have equal depth.  This is not checked.
+ */
+void
+rasteropLow(l_uint32  *datad,
+            l_int32    dpixw,
+            l_int32    dpixh,
+            l_int32    depth,
+            l_int32    dwpl,
+            l_int32    dx,
+            l_int32    dy,
+            l_int32    dw,
+            l_int32    dh,
+            l_int32    op,
+            l_uint32  *datas,
+            l_int32    spixw,
+            l_int32    spixh,
+            l_int32    swpl,
+            l_int32    sx,
+            l_int32    sy)
+{
+l_int32  dhangw, shangw, dhangh, shangh;
+
+   /* -------------------------------------------------------*
+    *            scale horizontal dimensions by depth
+    * -------------------------------------------------------*/
+    if (depth != 1) {
+        dpixw *= depth;
+        dx *= depth;
+        dw *= depth;
+        spixw *= depth;
+        sx *= depth;
+    }
+
+
+   /* -------------------------------------------------------*
+    *      clip to max rectangle within both src and dest
+    * -------------------------------------------------------*/
+       /* first, clip horizontally (sx, dx, dw) */
+    if (dx < 0) {
+        sx -= dx;  /* increase sx */
+        dw += dx;  /* reduce dw */
+        dx = 0;
+    }
+    if (sx < 0) {
+        dx -= sx;  /* increase dx */
+        dw += sx;  /* reduce dw */
+        sx = 0;
+    }
+    dhangw = dx + dw - dpixw;  /* rect ovhang dest to right */
+    if (dhangw > 0)
+        dw -= dhangw;  /* reduce dw */
+    shangw = sx + dw - spixw;   /* rect ovhang src to right */
+    if (shangw > 0)
+        dw -= shangw;  /* reduce dw */
+
+       /* then, clip vertically (sy, dy, dh) */
+    if (dy < 0) {
+        sy -= dy;  /* increase sy */
+        dh += dy;  /* reduce dh */
+        dy = 0;
+    }
+    if (sy < 0) {
+        dy -= sy;  /* increase dy */
+        dh += sy;  /* reduce dh */
+        sy = 0;
+    }
+    dhangh = dy + dh - dpixh;  /* rect ovhang dest below */
+    if (dhangh > 0)
+        dh -= dhangh;  /* reduce dh */
+    shangh = sy + dh - spixh;  /* rect ovhang src below */
+    if (shangh > 0)
+        dh -= shangh;  /* reduce dh */
+
+        /* if clipped entirely, quit */
+    if ((dw <= 0) || (dh <= 0))
+        return;
+
+   /* -------------------------------------------------------*
+    *       dispatch to aligned or non-aligned blitters
+    * -------------------------------------------------------*/
+    if (((dx & 31) == 0) && ((sx & 31) == 0))
+        rasteropWordAlignedLow(datad, dwpl, dx, dy, dw, dh, op,
+                               datas, swpl, sx, sy);
+    else if ((dx & 31) == (sx & 31))
+        rasteropVAlignedLow(datad, dwpl, dx, dy, dw, dh, op,
+                            datas, swpl, sx, sy);
+    else
+        rasteropGeneralLow(datad, dwpl, dx, dy, dw, dh, op,
+                           datas, swpl, sx, sy);
+
+    return;
+}
+
+
+/*--------------------------------------------------------------------*
+ *        Static low-level rasterop with vertical word alignment      *
+ *--------------------------------------------------------------------*/
+/*!
+ *  rasteropWordAlignedLow()
+ *
+ *      Input:  datad  (ptr to dest image data)
+ *              dwpl   (wpl of dest)
+ *              dx     (x val of UL corner of dest rectangle)
+ *              dy     (y val of UL corner of dest rectangle)
+ *              dw     (width of dest rectangle)
+ *              dh     (height of dest rectangle)
+ *              op     (op code)
+ *              datas  (ptr to src image data)
+ *              swpl   (wpl of src)
+ *              sx     (x val of UL corner of src rectangle)
+ *              sy     (y val of UL corner of src rectangle)
+ *      Return: void
+ *
+ *  This is called when both the src and dest rects
+ *  are left aligned on (32-bit) word boundaries.
+ *  That is: dx & 31 == 0 and sx & 31 == 0
+ *
+ *  We make an optimized implementation of this because
+ *  it is a common case: e.g., two images are rasterop'd
+ *  starting from their UL corners (0,0).
+ */
+static void
+rasteropWordAlignedLow(l_uint32  *datad,
+                       l_int32    dwpl,
+                       l_int32    dx,
+                       l_int32    dy,
+                       l_int32    dw,
+                       l_int32    dh,
+                       l_int32    op,
+                       l_uint32  *datas,
+                       l_int32    swpl,
+                       l_int32    sx,
+                       l_int32    sy)
+{
+l_int32    nfullw;     /* number of full words */
+l_uint32  *psfword;    /* ptr to first src word */
+l_uint32  *pdfword;    /* ptr to first dest word */
+l_int32    lwbits;     /* number of ovrhang bits in last partial word */
+l_uint32   lwmask;     /* mask for last partial word */
+l_uint32  *lines, *lined;
+l_int32    i, j;
+
+
+    /*--------------------------------------------------------*
+     *                Preliminary calculations                *
+     *--------------------------------------------------------*/
+    nfullw = dw >> 5;
+    lwbits = dw & 31;
+    if (lwbits)
+        lwmask = lmask32[lwbits];
+    psfword = datas + swpl * sy + (sx >> 5);
+    pdfword = datad + dwpl * dy + (dx >> 5);
+
+    /*--------------------------------------------------------*
+     *            Now we're ready to do the ops               *
+     *--------------------------------------------------------*/
+    switch (op)
+    {
+    case PIX_SRC:
+        for (i = 0; i < dh; i++) {
+            lines = psfword + i * swpl;
+            lined = pdfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = *lines;
+                lined++;
+                lines++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, *lines, lwmask);
+        }
+        break;
+    case PIX_NOT(PIX_SRC):
+        for (i = 0; i < dh; i++) {
+            lines = psfword + i * swpl;
+            lined = pdfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = ~(*lines);
+                lined++;
+                lines++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, ~(*lines), lwmask);
+        }
+        break;
+    case (PIX_SRC | PIX_DST):
+        for (i = 0; i < dh; i++) {
+            lines = psfword + i * swpl;
+            lined = pdfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = (*lines | *lined);
+                lined++;
+                lines++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, (*lines | *lined), lwmask);
+        }
+        break;
+    case (PIX_SRC & PIX_DST):
+        for (i = 0; i < dh; i++) {
+            lines = psfword + i * swpl;
+            lined = pdfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = (*lines & *lined);
+                lined++;
+                lines++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, (*lines & *lined), lwmask);
+        }
+        break;
+    case (PIX_SRC ^ PIX_DST):
+        for (i = 0; i < dh; i++) {
+            lines = psfword + i * swpl;
+            lined = pdfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = (*lines ^ *lined);
+                lined++;
+                lines++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, (*lines ^ *lined), lwmask);
+        }
+        break;
+    case (PIX_NOT(PIX_SRC) | PIX_DST):
+        for (i = 0; i < dh; i++) {
+            lines = psfword + i * swpl;
+            lined = pdfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = (~(*lines) | *lined);
+                lined++;
+                lines++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, (~(*lines) | *lined), lwmask);
+        }
+        break;
+    case (PIX_NOT(PIX_SRC) & PIX_DST):
+        for (i = 0; i < dh; i++) {
+            lines = psfword + i * swpl;
+            lined = pdfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = (~(*lines) & *lined);
+                lined++;
+                lines++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, (~(*lines) & *lined), lwmask);
+        }
+        break;
+    case (PIX_SRC | PIX_NOT(PIX_DST)):
+        for (i = 0; i < dh; i++) {
+            lines = psfword + i * swpl;
+            lined = pdfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = (*lines | ~(*lined));
+                lined++;
+                lines++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, (*lines | ~(*lined)), lwmask);
+        }
+        break;
+    case (PIX_SRC & PIX_NOT(PIX_DST)):
+        for (i = 0; i < dh; i++) {
+            lines = psfword + i * swpl;
+            lined = pdfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = (*lines & ~(*lined));
+                lined++;
+                lines++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, (*lines & ~(*lined)), lwmask);
+        }
+        break;
+    case (PIX_NOT(PIX_SRC | PIX_DST)):
+        for (i = 0; i < dh; i++) {
+            lines = psfword + i * swpl;
+            lined = pdfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = ~(*lines  | *lined);
+                lined++;
+                lines++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, ~(*lines  | *lined), lwmask);
+        }
+        break;
+    case (PIX_NOT(PIX_SRC & PIX_DST)):
+        for (i = 0; i < dh; i++) {
+            lines = psfword + i * swpl;
+            lined = pdfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = ~(*lines  & *lined);
+                lined++;
+                lines++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, ~(*lines  & *lined), lwmask);
+        }
+        break;
+        /* this is three cases: ~(s ^ d), ~s ^ d, s ^ ~d  */
+    case (PIX_NOT(PIX_SRC ^ PIX_DST)):
+        for (i = 0; i < dh; i++) {
+            lines = psfword + i * swpl;
+            lined = pdfword + i * dwpl;
+            for (j = 0; j < nfullw; j++) {
+                *lined = ~(*lines ^ *lined);
+                lined++;
+                lines++;
+            }
+            if (lwbits)
+                *lined = COMBINE_PARTIAL(*lined, ~(*lines ^ *lined), lwmask);
+        }
+        break;
+    default:
+        fprintf(stderr, "Operation %d invalid\n", op);
+    }
+
+    return;
+}
+
+
+
+/*--------------------------------------------------------------------*
+ *        Static low-level rasterop with vertical word alignment      *
+ *--------------------------------------------------------------------*/
+/*!
+ *  rasteropVAlignedLow()
+ *
+ *      Input:  datad  (ptr to dest image data)
+ *              dwpl   (wpl of dest)
+ *              dx     (x val of UL corner of dest rectangle)
+ *              dy     (y val of UL corner of dest rectangle)
+ *              dw     (width of dest rectangle)
+ *              dh     (height of dest rectangle)
+ *              op     (op code)
+ *              datas  (ptr to src image data)
+ *              swpl   (wpl of src)
+ *              sx     (x val of UL corner of src rectangle)
+ *              sy     (y val of UL corner of src rectangle)
+ *      Return: void
+ *
+ *  This is called when the left side of the src and dest
+ *  rects have the same alignment relative to (32-bit) word
+ *  boundaries; i.e., (dx & 31) == (sx & 31)
+ */
+static void
+rasteropVAlignedLow(l_uint32  *datad,
+                    l_int32    dwpl,
+                    l_int32    dx,
+                    l_int32    dy,
+                    l_int32    dw,
+                    l_int32    dh,
+                    l_int32    op,
+                    l_uint32  *datas,
+                    l_int32    swpl,
+                    l_int32    sx,
+                    l_int32    sy)
+{
+l_int32    dfwpartb;   /* boolean (1, 0) if first dest word is partial */
+l_int32    dfwpart2b;  /* boolean (1, 0) if first dest word is doubly partial */
+l_uint32   dfwmask;    /* mask for first partial dest word */
+l_int32    dfwbits;    /* first word dest bits in ovrhang */
+l_uint32  *pdfwpart;   /* ptr to first partial dest word */
+l_uint32  *psfwpart;   /* ptr to first partial src word */
+l_int32    dfwfullb;   /* boolean (1, 0) if there exists a full dest word */
+l_int32    dnfullw;    /* number of full words in dest */
+l_uint32  *pdfwfull;   /* ptr to first full dest word */
+l_uint32  *psfwfull;   /* ptr to first full src word */
+l_int32    dlwpartb;   /* boolean (1, 0) if last dest word is partial */
+l_uint32   dlwmask;    /* mask for last partial dest word */
+l_int32    dlwbits;    /* last word dest bits in ovrhang */
+l_uint32  *pdlwpart;   /* ptr to last partial dest word */
+l_uint32  *pslwpart;   /* ptr to last partial src word */
+l_int32    i, j;
+
+
+    /*--------------------------------------------------------*
+     *                Preliminary calculations                *
+     *--------------------------------------------------------*/
+        /* is the first word partial? */
+    if ((dx & 31) == 0) {  /* if not */
+        dfwpartb = 0;
+        dfwbits = 0;
+    } else {  /* if so */
+        dfwpartb = 1;
+        dfwbits = 32 - (dx & 31);
+        dfwmask = rmask32[dfwbits];
+        pdfwpart = datad + dwpl * dy + (dx >> 5);
+        psfwpart = datas + swpl * sy + (sx >> 5);
+    }
+
+        /* is the first word doubly partial? */
+    if (dw >= dfwbits) {  /* if not */
+        dfwpart2b = 0;
+    } else {  /* if so */
+        dfwpart2b = 1;
+        dfwmask &= lmask32[32 - dfwbits + dw];
+    }
+
+        /* is there a full dest word? */
+    if (dfwpart2b == 1) {  /* not */
+        dfwfullb = 0;
+        dnfullw = 0;
+    } else {
+        dnfullw = (dw - dfwbits) >> 5;
+        if (dnfullw == 0) {  /* if not */
+            dfwfullb = 0;
+        } else {  /* if so */
+            dfwfullb = 1;
+            if (dfwpartb) {
+                pdfwfull = pdfwpart + 1;
+                psfwfull = psfwpart + 1;
+            } else {
+                pdfwfull = datad + dwpl * dy + (dx >> 5);
+                psfwfull = datas + swpl * sy + (sx >> 5);
+            }
+        }
+    }
+
+        /* is the last word partial? */
+    dlwbits = (dx + dw) & 31;
+    if (dfwpart2b == 1 || dlwbits == 0) {  /* if not */
+        dlwpartb = 0;
+    } else {
+        dlwpartb = 1;
+        dlwmask = lmask32[dlwbits];
+        if (dfwpartb) {
+            pdlwpart = pdfwpart + 1 + dnfullw;
+            pslwpart = psfwpart + 1 + dnfullw;
+        } else {
+            pdlwpart = datad + dwpl * dy + (dx >> 5) + dnfullw;
+            pslwpart = datas + swpl * sy + (sx >> 5) + dnfullw;
+        }
+    }
+
+
+    /*--------------------------------------------------------*
+     *            Now we're ready to do the ops               *
+     *--------------------------------------------------------*/
+    switch (op)
+    {
+    case PIX_SRC:
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart, *psfwpart, dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) = *(psfwfull + j);
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart, *pslwpart, dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case PIX_NOT(PIX_SRC):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart, ~(*psfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) = ~(*(psfwfull + j));
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart, ~(*pslwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_SRC | PIX_DST):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                    (*psfwpart | *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) |= *(psfwfull + j);
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                                     (*pslwpart | *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_SRC & PIX_DST):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                    (*psfwpart & *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) &= *(psfwfull + j);
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                                     (*pslwpart & *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_SRC ^ PIX_DST):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                    (*psfwpart ^ *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) ^= *(psfwfull + j);
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                                     (*pslwpart ^ *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_NOT(PIX_SRC) | PIX_DST):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                    (~(*psfwpart) | *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) |= ~(*(psfwfull + j));
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                                     (~(*pslwpart) | *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_NOT(PIX_SRC) & PIX_DST):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                    (~(*psfwpart) & *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) &= ~(*(psfwfull + j));
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                                     (~(*pslwpart) & *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_SRC | PIX_NOT(PIX_DST)):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                    (*psfwpart | ~(*pdfwpart)), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) = *(psfwfull + j) | ~(*(pdfwfull + j));
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                                     (*pslwpart | ~(*pdlwpart)), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_SRC & PIX_NOT(PIX_DST)):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                    (*psfwpart & ~(*pdfwpart)), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) = *(psfwfull + j) & ~(*(pdfwfull + j));
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                                     (*pslwpart & ~(*pdlwpart)), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_NOT(PIX_SRC | PIX_DST)):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                    ~(*psfwpart | *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) = ~(*(psfwfull + j) | *(pdfwfull + j));
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                                     ~(*pslwpart | *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_NOT(PIX_SRC & PIX_DST)):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                    ~(*psfwpart & *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) = ~(*(psfwfull + j) & *(pdfwfull + j));
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                                     ~(*pslwpart & *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+        /* this is three cases: ~(s ^ d), ~s ^ d, s ^ ~d  */
+    case (PIX_NOT(PIX_SRC ^ PIX_DST)):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                    ~(*psfwpart ^ *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++)
+                    *(pdfwfull + j) = ~(*(psfwfull + j) ^ *(pdfwfull + j));
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                                     ~(*pslwpart ^ *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    default:
+        fprintf(stderr, "Operation %x invalid\n", op);
+    }
+
+    return;
+}
+
+
+/*--------------------------------------------------------------------*
+ *     Static low-level rasterop without vertical word alignment      *
+ *--------------------------------------------------------------------*/
+/*!
+ *  rasteropGeneralLow()
+ *
+ *      Input:  datad  (ptr to dest image data)
+ *              dwpl   (wpl of dest)
+ *              dx     (x val of UL corner of dest rectangle)
+ *              dy     (y val of UL corner of dest rectangle)
+ *              dw     (width of dest rectangle)
+ *              dh     (height of dest rectangle)
+ *              op     (op code)
+ *              datas  (ptr to src image data)
+ *              swpl   (wpl of src)
+ *              sx     (x val of UL corner of src rectangle)
+ *              sy     (y val of UL corner of src rectangle)
+ *      Return: void
+ *
+ *  This is called when the src and dest rects are
+ *  do not have the same (32-bit) word alignment.
+ *
+ *  The method is a generalization of rasteropVAlignLow().
+ *  There, the src image pieces were directly merged
+ *  with the dest.  Here, we shift the source bits
+ *  to fill words that are aligned with the dest, and
+ *  then use those "source words" exactly in place
+ *  of the source words that were used in rasteropVAlignLow().
+ *
+ *  The critical parameter is thus the shift required
+ *  for the src.  Consider the left edge of the rectangle.
+ *  The overhang into the src and dest words are found,
+ *  and the difference is exactly this shift.  There are
+ *  two separate cases, depending on whether the src pixels
+ *  are shifted left or right.  If the src overhang is
+ *  larger than the dest overhang, the src is shifted to
+ *  the right, a number of pixels equal to the shift are
+ *  left over for filling the next dest word, if necessary.
+ *  But if the dest overhang is larger than the src,
+ *  the src is shifted to the left, and it may also be
+ *  necessary to shift an equal number of pixels in from
+ *  the next src word.  However, in both cases, after
+ *  the first partial (or complete) dest word has been
+ *  filled, the next src pixels will come from a left
+ *  shift that exhausts the pixels in the src word.
+ */
+static void
+rasteropGeneralLow(l_uint32  *datad,
+                   l_int32    dwpl,
+                   l_int32    dx,
+                   l_int32    dy,
+                   l_int32    dw,
+                   l_int32    dh,
+                   l_int32    op,
+                   l_uint32  *datas,
+                   l_int32    swpl,
+                   l_int32    sx,
+                   l_int32    sy)
+{
+l_int32    dfwpartb;    /* boolean (1, 0) if first dest word is partial      */
+l_int32    dfwpart2b;   /* boolean (1, 0) if 1st dest word is doubly partial */
+l_uint32   dfwmask;     /* mask for first partial dest word                  */
+l_int32    dfwbits;     /* first word dest bits in overhang; 0-31            */
+l_int32    dhang;       /* dest overhang in first partial word,              */
+                        /* or 0 if dest is word aligned (same as dfwbits)    */
+l_uint32  *pdfwpart;    /* ptr to first partial dest word                    */
+l_uint32  *psfwpart;    /* ptr to first partial src word                     */
+l_int32    dfwfullb;    /* boolean (1, 0) if there exists a full dest word   */
+l_int32    dnfullw;     /* number of full words in dest                      */
+l_uint32  *pdfwfull;    /* ptr to first full dest word                       */
+l_uint32  *psfwfull;    /* ptr to first full src word                        */
+l_int32    dlwpartb;    /* boolean (1, 0) if last dest word is partial       */
+l_uint32   dlwmask;     /* mask for last partial dest word                   */
+l_int32    dlwbits;     /* last word dest bits in ovrhang                    */
+l_uint32  *pdlwpart;    /* ptr to last partial dest word                     */
+l_uint32  *pslwpart;    /* ptr to last partial src word                      */
+l_uint32   sword;       /* compose src word aligned with the dest words      */
+l_int32    sfwbits;     /* first word src bits in overhang (1-32),           */
+                        /* or 32 if src is word aligned                      */
+l_int32    shang;       /* source overhang in the first partial word,        */
+                        /* or 0 if src is word aligned (not same as sfwbits) */
+l_int32    sleftshift;  /* bits to shift left for source word to align       */
+                        /* with the dest.  Also the number of bits that      */
+                        /* get shifted to the right to align with the dest.  */
+l_int32    srightshift; /* bits to shift right for source word to align      */
+                        /* with dest.  Also, the number of bits that get     */
+                        /* shifted left to align with the dest.              */
+l_int32    srightmask;  /* mask for selecting sleftshift bits that have      */
+                        /* been shifted right by srightshift bits            */
+l_int32    sfwshiftdir; /* either SHIFT_LEFT or SHIFT_RIGHT                  */
+l_int32    sfwaddb;     /* boolean: do we need an additional sfw right shift? */
+l_int32    slwaddb;     /* boolean: do we need an additional slw right shift? */
+l_int32    i, j;
+
+
+    /*--------------------------------------------------------*
+     *                Preliminary calculations                *
+     *--------------------------------------------------------*/
+        /* To get alignment of src with dst (e.g., in the
+         * full words) the src must do a left shift of its
+         * relative overhang in the current src word,
+         * and OR that with a right shift of
+         * (31 -  relative overhang) from the next src word.
+         * We find the absolute overhangs, the relative overhangs,
+         * the required shifts and the src mask */
+    if ((sx & 31) == 0)
+        shang = 0;
+    else
+        shang = 32 - (sx & 31);
+    if ((dx & 31) == 0)
+        dhang = 0;
+    else
+        dhang = 32 - (dx & 31);
+
+    if (shang == 0 && dhang == 0) {  /* this should be treated by an
+                                        aligned operation, not by
+                                        this general rasterop! */
+        sleftshift = 0;
+        srightshift = 0;
+        srightmask = rmask32[0];
+    } else {
+        if (dhang > shang)
+            sleftshift = dhang - shang;
+        else
+            sleftshift = 32 - (shang - dhang);
+        srightshift = 32 - sleftshift;
+        srightmask = rmask32[sleftshift];
+    }
+
+        /* is the first dest word partial? */
+    if ((dx & 31) == 0) {  /* if not */
+        dfwpartb = 0;
+        dfwbits = 0;
+    } else {  /* if so */
+        dfwpartb = 1;
+        dfwbits = 32 - (dx & 31);
+        dfwmask = rmask32[dfwbits];
+        pdfwpart = datad + dwpl * dy + (dx >> 5);
+        psfwpart = datas + swpl * sy + (sx >> 5);
+        sfwbits = 32 - (sx & 31);
+        if (dfwbits > sfwbits) {
+            sfwshiftdir = SHIFT_LEFT;  /* and shift by sleftshift */
+            if (dw < shang)
+                sfwaddb = 0;
+            else
+                sfwaddb = 1;   /* and rshift in next src word by srightshift */
+        } else {
+            sfwshiftdir = SHIFT_RIGHT;  /* and shift by srightshift */
+        }
+    }
+
+        /* is the first dest word doubly partial? */
+    if (dw >= dfwbits) {  /* if not */
+        dfwpart2b = 0;
+    } else {  /* if so */
+        dfwpart2b = 1;
+        dfwmask &= lmask32[32 - dfwbits + dw];
+    }
+
+        /* is there a full dest word? */
+    if (dfwpart2b == 1) {  /* not */
+        dfwfullb = 0;
+        dnfullw = 0;
+    } else {
+        dnfullw = (dw - dfwbits) >> 5;
+        if (dnfullw == 0) {  /* if not */
+            dfwfullb = 0;
+        } else {  /* if so */
+            dfwfullb = 1;
+            pdfwfull = datad + dwpl * dy + ((dx + dhang) >> 5);
+            psfwfull = datas + swpl * sy + ((sx + dhang) >> 5); /* yes, dhang */
+        }
+    }
+
+        /* is the last dest word partial? */
+    dlwbits = (dx + dw) & 31;
+    if (dfwpart2b == 1 || dlwbits == 0) {  /* if not */
+        dlwpartb = 0;
+    } else {
+        dlwpartb = 1;
+        dlwmask = lmask32[dlwbits];
+        pdlwpart = datad + dwpl * dy + ((dx + dhang) >> 5) + dnfullw;
+        pslwpart = datas + swpl * sy + ((sx + dhang) >> 5) + dnfullw;
+        if (dlwbits <= srightshift)   /* must be <= here !!! */
+            slwaddb = 0;  /* we got enough bits from current src word */
+        else
+            slwaddb = 1;   /* must rshift in next src word by srightshift */
+    }
+
+
+    /*--------------------------------------------------------*
+     *            Now we're ready to do the ops               *
+     *--------------------------------------------------------*/
+    switch (op)
+    {
+    case PIX_SRC:
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++)
+            {
+                if (sfwshiftdir == SHIFT_LEFT) {
+                    sword = *psfwpart << sleftshift;
+                    if (sfwaddb)
+                        sword = COMBINE_PARTIAL(sword,
+                                      *(psfwpart + 1) >> srightshift,
+                                       srightmask);
+                } else {  /* shift right */
+                    sword = *psfwpart >> srightshift;
+                }
+
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart, sword, dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++) {
+                    sword = COMBINE_PARTIAL(*(psfwfull + j) << sleftshift,
+                                   *(psfwfull + j + 1) >> srightshift,
+                                   srightmask);
+                    *(pdfwfull + j) = sword;
+                }
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                sword = *pslwpart << sleftshift;
+                if (slwaddb)
+                    sword = COMBINE_PARTIAL(sword,
+                                  *(pslwpart + 1) >> srightshift,
+                                  srightmask);
+
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart, sword, dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case PIX_NOT(PIX_SRC):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++)
+            {
+                if (sfwshiftdir == SHIFT_LEFT) {
+                    sword = *psfwpart << sleftshift;
+                    if (sfwaddb)
+                        sword = COMBINE_PARTIAL(sword,
+                                      *(psfwpart + 1) >> srightshift,
+                                       srightmask);
+                } else {  /* shift right */
+                    sword = *psfwpart >> srightshift;
+                }
+
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart, ~sword, dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++) {
+                    sword = COMBINE_PARTIAL(*(psfwfull + j) << sleftshift,
+                                   *(psfwfull + j + 1) >> srightshift,
+                                   srightmask);
+                    *(pdfwfull + j) = ~sword;
+                }
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                sword = *pslwpart << sleftshift;
+                if (slwaddb)
+                    sword = COMBINE_PARTIAL(sword,
+                                  *(pslwpart + 1) >> srightshift,
+                                  srightmask);
+
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart, ~sword, dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_SRC | PIX_DST):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++)
+            {
+                if (sfwshiftdir == SHIFT_LEFT) {
+                    sword = *psfwpart << sleftshift;
+                    if (sfwaddb)
+                        sword = COMBINE_PARTIAL(sword,
+                                      *(psfwpart + 1) >> srightshift,
+                                       srightmask);
+                } else {  /* shift right */
+                    sword = *psfwpart >> srightshift;
+                }
+
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                 (sword | *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++) {
+                    sword = COMBINE_PARTIAL(*(psfwfull + j) << sleftshift,
+                                   *(psfwfull + j + 1) >> srightshift,
+                                   srightmask);
+                    *(pdfwfull + j) |= sword;
+                }
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                sword = *pslwpart << sleftshift;
+                if (slwaddb)
+                    sword = COMBINE_PARTIAL(sword,
+                                  *(pslwpart + 1) >> srightshift,
+                                  srightmask);
+
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                               (sword | *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_SRC & PIX_DST):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++)
+            {
+                if (sfwshiftdir == SHIFT_LEFT) {
+                    sword = *psfwpart << sleftshift;
+                    if (sfwaddb)
+                        sword = COMBINE_PARTIAL(sword,
+                                      *(psfwpart + 1) >> srightshift,
+                                       srightmask);
+                } else {  /* shift right */
+                    sword = *psfwpart >> srightshift;
+                }
+
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                 (sword & *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++) {
+                    sword = COMBINE_PARTIAL(*(psfwfull + j) << sleftshift,
+                                   *(psfwfull + j + 1) >> srightshift,
+                                   srightmask);
+                    *(pdfwfull + j) &= sword;
+                }
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                sword = *pslwpart << sleftshift;
+                if (slwaddb)
+                    sword = COMBINE_PARTIAL(sword,
+                                  *(pslwpart + 1) >> srightshift,
+                                  srightmask);
+
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                               (sword & *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_SRC ^ PIX_DST):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++)
+            {
+                if (sfwshiftdir == SHIFT_LEFT) {
+                    sword = *psfwpart << sleftshift;
+                    if (sfwaddb)
+                        sword = COMBINE_PARTIAL(sword,
+                                      *(psfwpart + 1) >> srightshift,
+                                       srightmask);
+                } else {  /* shift right */
+                    sword = *psfwpart >> srightshift;
+                }
+
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                 (sword ^ *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++) {
+                    sword = COMBINE_PARTIAL(*(psfwfull + j) << sleftshift,
+                                   *(psfwfull + j + 1) >> srightshift,
+                                   srightmask);
+                    *(pdfwfull + j) ^= sword;
+                }
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                sword = *pslwpart << sleftshift;
+                if (slwaddb)
+                    sword = COMBINE_PARTIAL(sword,
+                                  *(pslwpart + 1) >> srightshift,
+                                  srightmask);
+
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                               (sword ^ *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_NOT(PIX_SRC) | PIX_DST):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++)
+            {
+                if (sfwshiftdir == SHIFT_LEFT) {
+                    sword = *psfwpart << sleftshift;
+                    if (sfwaddb)
+                        sword = COMBINE_PARTIAL(sword,
+                                      *(psfwpart + 1) >> srightshift,
+                                       srightmask);
+                } else {  /* shift right */
+                    sword = *psfwpart >> srightshift;
+                }
+
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                 (~sword | *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++) {
+                    sword = COMBINE_PARTIAL(*(psfwfull + j) << sleftshift,
+                                   *(psfwfull + j + 1) >> srightshift,
+                                   srightmask);
+                    *(pdfwfull + j) |= ~sword;
+                }
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                sword = *pslwpart << sleftshift;
+                if (slwaddb)
+                    sword = COMBINE_PARTIAL(sword,
+                                  *(pslwpart + 1) >> srightshift,
+                                  srightmask);
+
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                               (~sword | *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_NOT(PIX_SRC) & PIX_DST):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++)
+            {
+                if (sfwshiftdir == SHIFT_LEFT) {
+                    sword = *psfwpart << sleftshift;
+                    if (sfwaddb)
+                        sword = COMBINE_PARTIAL(sword,
+                                      *(psfwpart + 1) >> srightshift,
+                                       srightmask);
+                } else {  /* shift right */
+                    sword = *psfwpart >> srightshift;
+                }
+
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                 (~sword & *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++) {
+                    sword = COMBINE_PARTIAL(*(psfwfull + j) << sleftshift,
+                                   *(psfwfull + j + 1) >> srightshift,
+                                   srightmask);
+                    *(pdfwfull + j) &= ~sword;
+                }
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                sword = *pslwpart << sleftshift;
+                if (slwaddb)
+                    sword = COMBINE_PARTIAL(sword,
+                                  *(pslwpart + 1) >> srightshift,
+                                  srightmask);
+
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                               (~sword & *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_SRC | PIX_NOT(PIX_DST)):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++)
+            {
+                if (sfwshiftdir == SHIFT_LEFT) {
+                    sword = *psfwpart << sleftshift;
+                    if (sfwaddb)
+                        sword = COMBINE_PARTIAL(sword,
+                                      *(psfwpart + 1) >> srightshift,
+                                       srightmask);
+                } else {  /* shift right */
+                    sword = *psfwpart >> srightshift;
+                }
+
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                 (sword | ~(*pdfwpart)), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++) {
+                    sword = COMBINE_PARTIAL(*(psfwfull + j) << sleftshift,
+                                   *(psfwfull + j + 1) >> srightshift,
+                                   srightmask);
+                    *(pdfwfull + j) = sword | ~(*(pdfwfull + j));
+                }
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                sword = *pslwpart << sleftshift;
+                if (slwaddb)
+                    sword = COMBINE_PARTIAL(sword,
+                                  *(pslwpart + 1) >> srightshift,
+                                  srightmask);
+
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                               (sword | ~(*pdlwpart)), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_SRC & PIX_NOT(PIX_DST)):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++)
+            {
+                if (sfwshiftdir == SHIFT_LEFT) {
+                    sword = *psfwpart << sleftshift;
+                    if (sfwaddb)
+                        sword = COMBINE_PARTIAL(sword,
+                                      *(psfwpart + 1) >> srightshift,
+                                       srightmask);
+                } else {  /* shift right */
+                    sword = *psfwpart >> srightshift;
+                }
+
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                 (sword & ~(*pdfwpart)), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++) {
+                    sword = COMBINE_PARTIAL(*(psfwfull + j) << sleftshift,
+                                   *(psfwfull + j + 1) >> srightshift,
+                                   srightmask);
+                    *(pdfwfull + j) = sword & ~(*(pdfwfull + j));
+                }
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                sword = *pslwpart << sleftshift;
+                if (slwaddb)
+                    sword = COMBINE_PARTIAL(sword,
+                                  *(pslwpart + 1) >> srightshift,
+                                  srightmask);
+
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                               (sword & ~(*pdlwpart)), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_NOT(PIX_SRC | PIX_DST)):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++)
+            {
+                if (sfwshiftdir == SHIFT_LEFT) {
+                    sword = *psfwpart << sleftshift;
+                    if (sfwaddb)
+                        sword = COMBINE_PARTIAL(sword,
+                                      *(psfwpart + 1) >> srightshift,
+                                       srightmask);
+                } else {  /* shift right */
+                    sword = *psfwpart >> srightshift;
+                }
+
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                 ~(sword | *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++) {
+                    sword = COMBINE_PARTIAL(*(psfwfull + j) << sleftshift,
+                                   *(psfwfull + j + 1) >> srightshift,
+                                   srightmask);
+                    *(pdfwfull + j) = ~(sword | *(pdfwfull + j));
+                }
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                sword = *pslwpart << sleftshift;
+                if (slwaddb)
+                    sword = COMBINE_PARTIAL(sword,
+                                  *(pslwpart + 1) >> srightshift,
+                                  srightmask);
+
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                               ~(sword | *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    case (PIX_NOT(PIX_SRC & PIX_DST)):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++)
+            {
+                if (sfwshiftdir == SHIFT_LEFT) {
+                    sword = *psfwpart << sleftshift;
+                    if (sfwaddb)
+                        sword = COMBINE_PARTIAL(sword,
+                                      *(psfwpart + 1) >> srightshift,
+                                       srightmask);
+                } else {  /* shift right */
+                    sword = *psfwpart >> srightshift;
+                }
+
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                 ~(sword & *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++) {
+                    sword = COMBINE_PARTIAL(*(psfwfull + j) << sleftshift,
+                                   *(psfwfull + j + 1) >> srightshift,
+                                   srightmask);
+                    *(pdfwfull + j) = ~(sword & *(pdfwfull + j));
+                }
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                sword = *pslwpart << sleftshift;
+                if (slwaddb)
+                    sword = COMBINE_PARTIAL(sword,
+                                  *(pslwpart + 1) >> srightshift,
+                                  srightmask);
+
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                               ~(sword & *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+        /* this is three cases: ~(s ^ d), ~s ^ d, s ^ ~d  */
+    case (PIX_NOT(PIX_SRC ^ PIX_DST)):
+            /* do the first partial word */
+        if (dfwpartb) {
+            for (i = 0; i < dh; i++)
+            {
+                if (sfwshiftdir == SHIFT_LEFT) {
+                    sword = *psfwpart << sleftshift;
+                    if (sfwaddb)
+                        sword = COMBINE_PARTIAL(sword,
+                                      *(psfwpart + 1) >> srightshift,
+                                       srightmask);
+                } else {  /* shift right */
+                    sword = *psfwpart >> srightshift;
+                }
+
+                *pdfwpart = COMBINE_PARTIAL(*pdfwpart,
+                                 ~(sword ^ *pdfwpart), dfwmask);
+                pdfwpart += dwpl;
+                psfwpart += swpl;
+            }
+        }
+
+            /* do the full words */
+        if (dfwfullb) {
+            for (i = 0; i < dh; i++) {
+                for (j = 0; j < dnfullw; j++) {
+                    sword = COMBINE_PARTIAL(*(psfwfull + j) << sleftshift,
+                                   *(psfwfull + j + 1) >> srightshift,
+                                   srightmask);
+                    *(pdfwfull + j) = ~(sword ^ *(pdfwfull + j));
+                }
+                pdfwfull += dwpl;
+                psfwfull += swpl;
+            }
+        }
+
+            /* do the last partial word */
+        if (dlwpartb) {
+            for (i = 0; i < dh; i++) {
+                sword = *pslwpart << sleftshift;
+                if (slwaddb)
+                    sword = COMBINE_PARTIAL(sword,
+                                  *(pslwpart + 1) >> srightshift,
+                                  srightmask);
+
+                *pdlwpart = COMBINE_PARTIAL(*pdlwpart,
+                               ~(sword ^ *pdlwpart), dlwmask);
+                pdlwpart += dwpl;
+                pslwpart += swpl;
+            }
+        }
+        break;
+    default:
+        fprintf(stderr, "Operation %x invalid\n", op);
+    }
+
+    return;
+}
diff --git a/src/rotate.c b/src/rotate.c
new file mode 100644 (file)
index 0000000..b4c3e5b
--- /dev/null
@@ -0,0 +1,588 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  rotate.c
+ *
+ *     General rotation about image center
+ *              PIX     *pixRotate()
+ *              PIX     *pixEmbedForRotation()
+ *
+ *     General rotation by sampling
+ *              PIX     *pixRotateBySampling()
+ *
+ *     Nice (slow) rotation of 1 bpp image
+ *              PIX     *pixRotateBinaryNice()
+ *
+ *     Rotation including alpha (blend) component
+ *              PIX     *pixRotateWithAlpha()
+ *
+ *     Rotations are measured in radians; clockwise is positive.
+ *
+ *     The general rotation pixRotate() does the best job for
+ *     rotating about the image center.  For 1 bpp, it uses shear;
+ *     for others, it uses either shear or area mapping.
+ *     If requested, it expands the output image so that no pixels are lost
+ *     in the rotation, and this can be done on multiple successive shears
+ *     without expanding beyond the maximum necessary size.
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+extern l_float32  AlphaMaskBorderVals[2];
+static const l_float32  MIN_ANGLE_TO_ROTATE = 0.001;  /* radians; ~0.06 deg */
+static const l_float32  MAX_1BPP_SHEAR_ANGLE = 0.06;  /* radians; ~3 deg    */
+static const l_float32  LIMIT_SHEAR_ANGLE = 0.35;     /* radians; ~20 deg   */
+
+
+/*------------------------------------------------------------------*
+ *                  General rotation about the center               *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotate()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 32 bpp rgb)
+ *              angle (radians; clockwise is positive)
+ *              type (L_ROTATE_AREA_MAP, L_ROTATE_SHEAR, L_ROTATE_SAMPLING)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *              width (original width; use 0 to avoid embedding)
+ *              height (original height; use 0 to avoid embedding)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This is a high-level, simple interface for rotating images
+ *          about their center.
+ *      (2) For very small rotations, just return a clone.
+ *      (3) Rotation brings either white or black pixels in
+ *          from outside the image.
+ *      (4) The rotation type is adjusted if necessary for the image
+ *          depth and size of rotation angle.  For 1 bpp images, we
+ *          rotate either by shear or sampling.
+ *      (5) Colormaps are removed for rotation by area mapping.
+ *      (6) The dest can be expanded so that no image pixels
+ *          are lost.  To invoke expansion, input the original
+ *          width and height.  For repeated rotation, use of the
+ *          original width and height allows the expansion to
+ *          stop at the maximum required size, which is a square
+ *          with side = sqrt(w*w + h*h).
+ *
+ *  *** Warning: implicit assumption about RGB component ordering ***
+ */
+PIX *
+pixRotate(PIX       *pixs,
+          l_float32  angle,
+          l_int32    type,
+          l_int32    incolor,
+          l_int32    width,
+          l_int32    height)
+{
+l_int32    w, h, d;
+l_uint32   fillval;
+PIX       *pixt1, *pixt2, *pixt3, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixRotate");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (type != L_ROTATE_SHEAR && type != L_ROTATE_AREA_MAP &&
+        type != L_ROTATE_SAMPLING)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+
+        /* Adjust rotation type if necessary:
+         *  - If d == 1 bpp and the angle is more than about 6 degrees,
+         *    rotate by sampling; otherwise rotate by shear.
+         *  - If d > 1, only allow shear rotation up to about 20 degrees;
+         *    beyond that, default a shear request to sampling. */
+    if (pixGetDepth(pixs) == 1) {
+        if (L_ABS(angle) > MAX_1BPP_SHEAR_ANGLE) {
+            if (type != L_ROTATE_SAMPLING)
+                L_INFO("1 bpp, large angle; rotate by sampling\n", procName);
+            type = L_ROTATE_SAMPLING;
+        } else if (type != L_ROTATE_SHEAR) {
+            L_INFO("1 bpp; rotate by shear\n", procName);
+            type = L_ROTATE_SHEAR;
+        }
+    } else if (L_ABS(angle) > LIMIT_SHEAR_ANGLE && type == L_ROTATE_SHEAR) {
+        L_INFO("large angle; rotate by sampling\n", procName);
+        type = L_ROTATE_SAMPLING;
+    }
+
+        /* Remove colormap if we rotate by area mapping. */
+    cmap = pixGetColormap(pixs);
+    if (cmap && type == L_ROTATE_AREA_MAP)
+        pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    else
+        pixt1 = pixClone(pixs);
+    cmap = pixGetColormap(pixt1);
+
+        /* Otherwise, if there is a colormap and we're not embedding,
+         * add white color if it doesn't exist. */
+    if (cmap && width == 0) {  /* no embedding; generate @incolor */
+        if (incolor == L_BRING_IN_BLACK)
+            pixcmapAddBlackOrWhite(cmap, 0, NULL);
+        else  /* L_BRING_IN_WHITE */
+            pixcmapAddBlackOrWhite(cmap, 1, NULL);
+    }
+
+        /* Request to embed in a larger image; do if necessary */
+    pixt2 = pixEmbedForRotation(pixt1, angle, incolor, width, height);
+
+        /* Area mapping requires 8 or 32 bpp.  If less than 8 bpp and
+         * area map rotation is requested, convert to 8 bpp. */
+    d = pixGetDepth(pixt2);
+    if (type == L_ROTATE_AREA_MAP && d < 8)
+        pixt3 = pixConvertTo8(pixt2, FALSE);
+    else
+        pixt3 = pixClone(pixt2);
+
+        /* Do the rotation: shear, sampling or area mapping */
+    pixGetDimensions(pixt3, &w, &h, &d);
+    if (type == L_ROTATE_SHEAR) {
+        pixd = pixRotateShearCenter(pixt3, angle, incolor);
+    } else if (type == L_ROTATE_SAMPLING) {
+        pixd = pixRotateBySampling(pixt3, w / 2, h / 2, angle, incolor);
+    } else {  /* rotate by area mapping */
+        fillval = 0;
+        if (incolor == L_BRING_IN_WHITE) {
+            if (d == 8)
+                fillval = 255;
+            else  /* d == 32 */
+                fillval = 0xffffff00;
+        }
+        if (d == 8)
+            pixd = pixRotateAMGray(pixt3, angle, fillval);
+        else  /* d == 32 */
+            pixd = pixRotateAMColor(pixt3, angle, fillval);
+    }
+
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+    return pixd;
+}
+
+
+/*!
+ *  pixEmbedForRotation()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 32 bpp rgb)
+ *              angle (radians; clockwise is positive)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *              width (original width; use 0 to avoid embedding)
+ *              height (original height; use 0 to avoid embedding)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) For very small rotations, just return a clone.
+ *      (2) Generate larger image to embed pixs if necessary, and
+ *          place the center of the input image in the center.
+ *      (3) Rotation brings either white or black pixels in
+ *          from outside the image.  For colormapped images where
+ *          there is no white or black, a new color is added if
+ *          possible for these pixels; otherwise, either the
+ *          lightest or darkest color is used.  In most cases,
+ *          the colormap will be removed prior to rotation.
+ *      (4) The dest is to be expanded so that no image pixels
+ *          are lost after rotation.  Input of the original width
+ *          and height allows the expansion to stop at the maximum
+ *          required size, which is a square with side equal to
+ *          sqrt(w*w + h*h).
+ *      (5) For an arbitrary angle, the expansion can be found by
+ *          considering the UL and UR corners.  As the image is
+ *          rotated, these move in an arc centered at the center of
+ *          the image.  Normalize to a unit circle by dividing by half
+ *          the image diagonal.  After a rotation of T radians, the UL
+ *          and UR corners are at points T radians along the unit
+ *          circle.  Compute the x and y coordinates of both these
+ *          points and take the max of absolute values; these represent
+ *          the half width and half height of the containing rectangle.
+ *          The arithmetic is done using formulas for sin(a+b) and cos(a+b),
+ *          where b = T.  For the UR corner, sin(a) = h/d and cos(a) = w/d.
+ *          For the UL corner, replace a by (pi - a), and you have
+ *          sin(pi - a) = h/d, cos(pi - a) = -w/d.  The equations
+ *          given below follow directly.
+ */
+PIX *
+pixEmbedForRotation(PIX       *pixs,
+                    l_float32  angle,
+                    l_int32    incolor,
+                    l_int32    width,
+                    l_int32    height)
+{
+l_int32    w, h, d, w1, h1, w2, h2, maxside, wnew, hnew, xoff, yoff, setcolor;
+l_float64  sina, cosa, fw, fh;
+PIX       *pixd;
+
+    PROCNAME("pixEmbedForRotation");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+
+        /* Test if big enough to hold any rotation of the original image */
+    pixGetDimensions(pixs, &w, &h, &d);
+    maxside = (l_int32)(sqrt((l_float64)(width * width) +
+                             (l_float64)(height * height)) + 0.5);
+    if (w >= maxside && h >= maxside)  /* big enough */
+        return pixClone(pixs);
+
+        /* Find the new sizes required to hold the image after rotation.
+         * Note that the new dimensions must be at least as large as those
+         * of pixs, because we're rasterop-ing into it before rotation. */
+    cosa = cos(angle);
+    sina = sin(angle);
+    fw = (l_float64)w;
+    fh = (l_float64)h;
+    w1 = (l_int32)(L_ABS(fw * cosa - fh * sina) + 0.5);
+    w2 = (l_int32)(L_ABS(-fw * cosa - fh * sina) + 0.5);
+    h1 = (l_int32)(L_ABS(fw * sina + fh * cosa) + 0.5);
+    h2 = (l_int32)(L_ABS(-fw * sina + fh * cosa) + 0.5);
+    wnew = L_MAX(w, L_MAX(w1, w2));
+    hnew = L_MAX(h, L_MAX(h1, h2));
+
+    if ((pixd = pixCreate(wnew, hnew, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyColormap(pixd, pixs);
+    pixCopySpp(pixd, pixs);
+    pixCopyText(pixd, pixs);
+    xoff = (wnew - w) / 2;
+    yoff = (hnew - h) / 2;
+
+        /* Set background to color to be rotated in */
+    setcolor = (incolor == L_BRING_IN_BLACK) ? L_SET_BLACK : L_SET_WHITE;
+    pixSetBlackOrWhite(pixd, setcolor);
+
+        /* Rasterop automatically handles all 4 channels for rgba */
+    pixRasterop(pixd, xoff, yoff, w, h, PIX_SRC, pixs, 0, 0);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                    General rotation by sampling                  *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotateBySampling()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16, 32 bpp rgb; can be cmapped)
+ *              xcen (x value of center of rotation)
+ *              ycen (y value of center of rotation)
+ *              angle (radians; clockwise is positive)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) For very small rotations, just return a clone.
+ *      (2) Rotation brings either white or black pixels in
+ *          from outside the image.
+ *      (3) Colormaps are retained.
+ */
+PIX *
+pixRotateBySampling(PIX       *pixs,
+                    l_int32    xcen,
+                    l_int32    ycen,
+                    l_float32  angle,
+                    l_int32    incolor)
+{
+l_int32    w, h, d, i, j, x, y, xdif, ydif, wm1, hm1, wpld;
+l_uint32   val;
+l_float32  sina, cosa;
+l_uint32  *datad, *lined;
+void     **lines;
+PIX       *pixd;
+
+    PROCNAME("pixRotateBySampling");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("invalid depth", procName, NULL);
+
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+
+    if ((pixd = pixCreateTemplateNoInit(pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixSetBlackOrWhite(pixd, incolor);
+
+    sina = sin(angle);
+    cosa = cos(angle);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    wm1 = w - 1;
+    hm1 = h - 1;
+    lines = pixGetLinePtrs(pixs, NULL);
+
+        /* Treat 1 bpp case specially */
+    if (d == 1) {
+        for (i = 0; i < h; i++) {  /* scan over pixd */
+            lined = datad + i * wpld;
+            ydif = ycen - i;
+            for (j = 0; j < w; j++) {
+                xdif = xcen - j;
+                x = xcen + (l_int32)(-xdif * cosa - ydif * sina);
+                if (x < 0 || x > wm1) continue;
+                y = ycen + (l_int32)(-ydif * cosa + xdif * sina);
+                if (y < 0 || y > hm1) continue;
+                if (incolor == L_BRING_IN_WHITE) {
+                    if (GET_DATA_BIT(lines[y], x))
+                        SET_DATA_BIT(lined, j);
+                } else {
+                    if (!GET_DATA_BIT(lines[y], x))
+                        CLEAR_DATA_BIT(lined, j);
+                }
+            }
+        }
+        LEPT_FREE(lines);
+        return pixd;
+    }
+
+    for (i = 0; i < h; i++) {  /* scan over pixd */
+        lined = datad + i * wpld;
+        ydif = ycen - i;
+        for (j = 0; j < w; j++) {
+            xdif = xcen - j;
+            x = xcen + (l_int32)(-xdif * cosa - ydif * sina);
+            if (x < 0 || x > wm1) continue;
+            y = ycen + (l_int32)(-ydif * cosa + xdif * sina);
+            if (y < 0 || y > hm1) continue;
+            switch (d)
+            {
+            case 8:
+                val = GET_DATA_BYTE(lines[y], x);
+                SET_DATA_BYTE(lined, j, val);
+                break;
+            case 32:
+                val = GET_DATA_FOUR_BYTES(lines[y], x);
+                SET_DATA_FOUR_BYTES(lined, j, val);
+                break;
+            case 2:
+                val = GET_DATA_DIBIT(lines[y], x);
+                SET_DATA_DIBIT(lined, j, val);
+                break;
+            case 4:
+                val = GET_DATA_QBIT(lines[y], x);
+                SET_DATA_QBIT(lined, j, val);
+                break;
+            case 16:
+                val = GET_DATA_TWO_BYTES(lines[y], x);
+                SET_DATA_TWO_BYTES(lined, j, val);
+                break;
+            default:
+                return (PIX *)ERROR_PTR("invalid depth", procName, NULL);
+            }
+        }
+    }
+
+    LEPT_FREE(lines);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                 Nice (slow) rotation of 1 bpp image              *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotateBinaryNice()
+ *
+ *      Input:  pixs (1 bpp)
+ *              angle (radians; clockwise is positive; about the center)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) For very small rotations, just return a clone.
+ *      (2) This does a computationally expensive rotation of 1 bpp images.
+ *          The fastest rotators (using shears or subsampling) leave
+ *          visible horizontal and vertical shear lines across which
+ *          the image shear changes by one pixel.  To ameliorate the
+ *          visual effect one can introduce random dithering.  One
+ *          way to do this in a not-too-random fashion is given here.
+ *          We convert to 8 bpp, do a very small blur, rotate using
+ *          linear interpolation (same as area mapping), do a
+ *          small amount of sharpening to compensate for the initial
+ *          blur, and threshold back to binary.  The shear lines
+ *          are magically removed.
+ *      (3) This operation is about 5x slower than rotation by sampling.
+ */
+PIX *
+pixRotateBinaryNice(PIX       *pixs,
+                    l_float32  angle,
+                    l_int32    incolor)
+{
+PIX  *pixt1, *pixt2, *pixt3, *pixt4, *pixd;
+
+    PROCNAME("pixRotateBinaryNice");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+
+    pixt1 = pixConvertTo8(pixs, 0);
+    pixt2 = pixBlockconv(pixt1, 1, 1);  /* smallest blur allowed */
+    pixt3 = pixRotateAM(pixt2, angle, incolor);
+    pixt4 = pixUnsharpMasking(pixt3, 1, 1.0);  /* sharpen a bit */
+    pixd = pixThresholdToBinary(pixt4, 128);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    pixDestroy(&pixt3);
+    pixDestroy(&pixt4);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *             Rotation including alpha (blend) component           *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotateWithAlpha()
+ *
+ *      Input:  pixs (32 bpp rgb or cmapped)
+ *              angle (radians; clockwise is positive)
+ *              pixg (<optional> 8 bpp, can be null)
+ *              fract (between 0.0 and 1.0, with 0.0 fully transparent
+ *                     and 1.0 fully opaque)
+ *      Return: pixd (32 bpp rgba), or null on error
+ *
+ *  Notes:
+ *      (1) The alpha channel is transformed separately from pixs,
+ *          and aligns with it, being fully transparent outside the
+ *          boundary of the transformed pixs.  For pixels that are fully
+ *          transparent, a blending function like pixBlendWithGrayMask()
+ *          will give zero weight to corresponding pixels in pixs.
+ *      (2) Rotation is about the center of the image; for very small
+ *          rotations, just return a clone.  The dest is automatically
+ *          expanded so that no image pixels are lost.
+ *      (3) Rotation is by area mapping.  It doesn't matter what
+ *          color is brought in because the alpha channel will
+ *          be transparent (black) there.
+ *      (4) If pixg is NULL, it is generated as an alpha layer that is
+ *          partially opaque, using @fract.  Otherwise, it is cropped
+ *          to pixs if required and @fract is ignored.  The alpha
+ *          channel in pixs is never used.
+ *      (4) Colormaps are removed to 32 bpp.
+ *      (5) The default setting for the border values in the alpha channel
+ *          is 0 (transparent) for the outermost ring of pixels and
+ *          (0.5 * fract * 255) for the second ring.  When blended over
+ *          a second image, this
+ *          (a) shrinks the visible image to make a clean overlap edge
+ *              with an image below, and
+ *          (b) softens the edges by weakening the aliasing there.
+ *          Use l_setAlphaMaskBorder() to change these values.
+ *      (6) A subtle use of gamma correction is to remove gamma correction
+ *          before rotation and restore it afterwards.  This is done
+ *          by sandwiching this function between a gamma/inverse-gamma
+ *          photometric transform:
+ *              pixt = pixGammaTRCWithAlpha(NULL, pixs, 1.0 / gamma, 0, 255);
+ *              pixd = pixRotateWithAlpha(pixt, angle, NULL, fract);
+ *              pixGammaTRCWithAlpha(pixd, pixd, gamma, 0, 255);
+ *              pixDestroy(&pixt);
+ *          This has the side-effect of producing artifacts in the very
+ *          dark regions.
+ *
+ *  *** Warning: implicit assumption about RGB component ordering ***
+ */
+PIX *
+pixRotateWithAlpha(PIX       *pixs,
+                   l_float32  angle,
+                   PIX       *pixg,
+                   l_float32  fract)
+{
+l_int32  ws, hs, d, spp;
+PIX     *pixd, *pix32, *pixg2, *pixgr;
+
+    PROCNAME("pixRotateWithAlpha");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &ws, &hs, &d);
+    if (d != 32 && pixGetColormap(pixs) == NULL)
+        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
+    if (pixg && pixGetDepth(pixg) != 8) {
+        L_WARNING("pixg not 8 bpp; using @fract transparent alpha\n", procName);
+        pixg = NULL;
+    }
+    if (!pixg && (fract < 0.0 || fract > 1.0)) {
+        L_WARNING("invalid fract; using fully opaque\n", procName);
+        fract = 1.0;
+    }
+    if (!pixg && fract == 0.0)
+        L_WARNING("transparent alpha; image will not be blended\n", procName);
+
+        /* Make sure input to rotation is 32 bpp rgb, and rotate it */
+    if (d != 32)
+        pix32 = pixConvertTo32(pixs);
+    else
+        pix32 = pixClone(pixs);
+    spp = pixGetSpp(pix32);
+    pixSetSpp(pix32, 3);  /* ignore the alpha channel for the rotation */
+    pixd = pixRotate(pix32, angle, L_ROTATE_AREA_MAP, L_BRING_IN_WHITE, ws, hs);
+    pixSetSpp(pix32, spp);  /* restore initial value in case it's a clone */
+    pixDestroy(&pix32);
+
+        /* Set up alpha layer with a fading border and rotate it */
+    if (!pixg) {
+        pixg2 = pixCreate(ws, hs, 8);
+        if (fract == 1.0)
+            pixSetAll(pixg2);
+        else if (fract > 0.0)
+            pixSetAllArbitrary(pixg2, (l_int32)(255.0 * fract));
+    } else {
+        pixg2 = pixResizeToMatch(pixg, NULL, ws, hs);
+    }
+    if (ws > 10 && hs > 10) {  /* see note 8 */
+        pixSetBorderRingVal(pixg2, 1,
+                            (l_int32)(255.0 * fract * AlphaMaskBorderVals[0]));
+        pixSetBorderRingVal(pixg2, 2,
+                            (l_int32)(255.0 * fract * AlphaMaskBorderVals[1]));
+    }
+    pixgr = pixRotate(pixg2, angle, L_ROTATE_AREA_MAP,
+                      L_BRING_IN_BLACK, ws, hs);
+
+        /* Combine into a 4 spp result */
+    pixSetRGBComponent(pixd, pixgr, L_ALPHA_CHANNEL);
+
+    pixDestroy(&pixg2);
+    pixDestroy(&pixgr);
+    return pixd;
+}
+
diff --git a/src/rotateam.c b/src/rotateam.c
new file mode 100644 (file)
index 0000000..7b3aea9
--- /dev/null
@@ -0,0 +1,475 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  rotateam.c
+ *
+ *     Grayscale and color rotation for area mapping (== interpolation)
+ *
+ *         Rotation about the image center
+ *                  PIX     *pixRotateAM()
+ *                  PIX     *pixRotateAMColor()
+ *                  PIX     *pixRotateAMGray()
+ *
+ *         Rotation about the UL corner of the image
+ *                  PIX     *pixRotateAMCorner()
+ *                  PIX     *pixRotateAMColorCorner()
+ *                  PIX     *pixRotateAMGrayCorner()
+ *
+ *         Faster color rotation about the image center
+ *                  PIX     *pixRotateAMColorFast()
+ *
+ *     Rotations are measured in radians; clockwise is positive.
+ *
+ *     The basic area mapping grayscale rotation works on 8 bpp images.
+ *     For color, the same method is applied to each color separately.
+ *     This can be done in two ways: (1) as here, computing each dest
+ *     rgb pixel from the appropriate four src rgb pixels, or (2) separating
+ *     the color image into three 8 bpp images, rotate each of these,
+ *     and then combine the result.  Method (1) is about 2.5x faster.
+ *     We have also implemented a fast approximation for color area-mapping
+ *     rotation (pixRotateAMColorFast()), which is about 25% faster
+ *     than the standard color rotator.  If you need the extra speed,
+ *     use it.
+ *
+ *     Area mapping works as follows.  For each dest
+ *     pixel you find the 4 source pixels that it partially
+ *     covers.  You then compute the dest pixel value as
+ *     the area-weighted average of those 4 source pixels.
+ *     We make two simplifying approximations:
+ *
+ *       -  For simplicity, compute the areas as if the dest
+ *          pixel were translated but not rotated.
+ *
+ *       -  Compute area overlaps on a discrete sub-pixel grid.
+ *          Because we are using 8 bpp images with 256 levels,
+ *          it is convenient to break each pixel into a
+ *          16x16 sub-pixel grid, and count the number of
+ *          overlapped sub-pixels.
+ *
+ *     It is interesting to note that the digital filter that
+ *     implements the area mapping algorithm for rotation
+ *     is identical to the digital filter used for linear
+ *     interpolation when arbitrarily scaling grayscale images.
+ *
+ *     The advantage of area mapping over pixel sampling
+ *     in grayscale rotation is that the former naturally
+ *     blurs sharp edges ("anti-aliasing"), so that stair-step
+ *     artifacts are not introduced.  The disadvantage is that
+ *     it is significantly slower.
+ *
+ *     But it is still pretty fast.  With standard 3 GHz hardware,
+ *     the anti-aliased (area-mapped) color rotation speed is
+ *     about 15 million pixels/sec.
+ *
+ *     The function pixRotateAMColorFast() is about 10-20% faster
+ *     than pixRotateAMColor().  The quality is slightly worse,
+ *     and if you make many successive small rotations, with a
+ *     total angle of 360 degrees, it has been noted that the
+ *     center wanders -- it seems to be doing a 1 pixel translation
+ *     in addition to the rotation.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_float32  MIN_ANGLE_TO_ROTATE = 0.001;  /* radians; ~0.06 deg */
+
+
+/*------------------------------------------------------------------*
+ *                     Rotation about the center                    *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotateAM()
+ *
+ *      Input:  pixs (2, 4, 8 bpp gray or colormapped, or 32 bpp RGB)
+ *              angle (radians; clockwise is positive)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Rotates about image center.
+ *      (2) A positive angle gives a clockwise rotation.
+ *      (3) Brings in either black or white pixels from the boundary.
+ */
+PIX *
+pixRotateAM(PIX       *pixs,
+            l_float32  angle,
+            l_int32    incolor)
+{
+l_int32   d;
+l_uint32  fillval;
+PIX      *pixt1, *pixt2, *pixd;
+
+    PROCNAME("pixRotateAM");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) == 1)
+        return (PIX *)ERROR_PTR("pixs is 1 bpp", procName, NULL);
+
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+
+        /* Remove cmap if it exists, and unpack to 8 bpp if necessary */
+    pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    d = pixGetDepth(pixt1);
+    if (d < 8)
+        pixt2 = pixConvertTo8(pixt1, FALSE);
+    else
+        pixt2 = pixClone(pixt1);
+    d = pixGetDepth(pixt2);
+
+        /* Compute actual incoming color */
+    fillval = 0;
+    if (incolor == L_BRING_IN_WHITE) {
+        if (d == 8)
+            fillval = 255;
+        else  /* d == 32 */
+            fillval = 0xffffff00;
+    }
+
+    if (d == 8)
+        pixd = pixRotateAMGray(pixt2, angle, fillval);
+    else   /* d == 32 */
+        pixd = pixRotateAMColor(pixt2, angle, fillval);
+
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    return pixd;
+}
+
+
+/*!
+ *  pixRotateAMColor()
+ *
+ *      Input:  pixs (32 bpp)
+ *              angle (radians; clockwise is positive)
+ *              colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Rotates about image center.
+ *      (2) A positive angle gives a clockwise rotation.
+ *      (3) Specify the color to be brought in from outside the image.
+ */
+PIX *
+pixRotateAMColor(PIX       *pixs,
+                 l_float32  angle,
+                 l_uint32   colorval)
+{
+l_int32    w, h, wpls, wpld;
+l_uint32  *datas, *datad;
+PIX       *pix1, *pix2, *pixd;
+
+    PROCNAME("pixRotateAMColor");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL);
+
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreateTemplate(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    rotateAMColorLow(datad, w, h, wpld, datas, wpls, angle, colorval);
+    if (pixGetSpp(pixs) == 4) {
+        pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+        pix2 = pixRotateAMGray(pix1, angle, 255);  /* bring in opaque */
+        pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixRotateAMGray()
+ *
+ *      Input:  pixs (8 bpp)
+ *              angle (radians; clockwise is positive)
+ *              grayval (0 to bring in BLACK, 255 for WHITE)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Rotates about image center.
+ *      (2) A positive angle gives a clockwise rotation.
+ *      (3) Specify the grayvalue to be brought in from outside the image.
+ */
+PIX *
+pixRotateAMGray(PIX       *pixs,
+                l_float32  angle,
+                l_uint8    grayval)
+{
+l_int32    w, h, wpls, wpld;
+l_uint32  *datas, *datad;
+PIX        *pixd;
+
+    PROCNAME("pixRotateAMGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs must be 8 bpp", procName, NULL);
+
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreateTemplate(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    rotateAMGrayLow(datad, w, h, wpld, datas, wpls, angle, grayval);
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                    Rotation about the UL corner                  *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotateAMCorner()
+ *
+ *      Input:  pixs (1, 2, 4, 8 bpp gray or colormapped, or 32 bpp RGB)
+ *              angle (radians; clockwise is positive)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Rotates about the UL corner of the image.
+ *      (2) A positive angle gives a clockwise rotation.
+ *      (3) Brings in either black or white pixels from the boundary.
+ */
+PIX *
+pixRotateAMCorner(PIX       *pixs,
+                  l_float32  angle,
+                  l_int32    incolor)
+{
+l_int32   d;
+l_uint32  fillval;
+PIX      *pixt1, *pixt2, *pixd;
+
+    PROCNAME("pixRotateAMCorner");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+
+        /* Remove cmap if it exists, and unpack to 8 bpp if necessary */
+    pixt1 = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    d = pixGetDepth(pixt1);
+    if (d < 8)
+        pixt2 = pixConvertTo8(pixt1, FALSE);
+    else
+        pixt2 = pixClone(pixt1);
+    d = pixGetDepth(pixt2);
+
+        /* Compute actual incoming color */
+    fillval = 0;
+    if (incolor == L_BRING_IN_WHITE) {
+        if (d == 8)
+            fillval = 255;
+        else  /* d == 32 */
+            fillval = 0xffffff00;
+    }
+
+    if (d == 8)
+        pixd = pixRotateAMGrayCorner(pixt2, angle, fillval);
+    else   /* d == 32 */
+        pixd = pixRotateAMColorCorner(pixt2, angle, fillval);
+
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    return pixd;
+}
+
+
+/*!
+ *  pixRotateAMColorCorner()
+ *
+ *      Input:  pixs
+ *              angle (radians; clockwise is positive)
+ *              colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Rotates the image about the UL corner.
+ *      (2) A positive angle gives a clockwise rotation.
+ *      (3) Specify the color to be brought in from outside the image.
+ */
+PIX *
+pixRotateAMColorCorner(PIX       *pixs,
+                       l_float32  angle,
+                       l_uint32   fillval)
+{
+l_int32    w, h, wpls, wpld;
+l_uint32  *datas, *datad;
+PIX       *pix1, *pix2, *pixd;
+
+    PROCNAME("pixRotateAMColorCorner");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL);
+
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreateTemplate(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    rotateAMColorCornerLow(datad, w, h, wpld, datas, wpls, angle, fillval);
+    if (pixGetSpp(pixs) == 4) {
+        pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+        pix2 = pixRotateAMGrayCorner(pix1, angle, 255);  /* bring in opaque */
+        pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixRotateAMGrayCorner()
+ *
+ *      Input:  pixs
+ *              angle (radians; clockwise is positive)
+ *              grayval (0 to bring in BLACK, 255 for WHITE)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Rotates the image about the UL corner.
+ *      (2) A positive angle gives a clockwise rotation.
+ *      (3) Specify the grayvalue to be brought in from outside the image.
+ */
+PIX *
+pixRotateAMGrayCorner(PIX       *pixs,
+                      l_float32  angle,
+                      l_uint8    grayval)
+{
+l_int32    w, h, wpls, wpld;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixRotateAMGrayCorner");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("pixs must be 8 bpp", procName, NULL);
+
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreateTemplate(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    rotateAMGrayCornerLow(datad, w, h, wpld, datas, wpls, angle, grayval);
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                    Fast rotation about the center                *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotateAMColorFast()
+ *
+ *      Input:  pixs
+ *              angle (radians; clockwise is positive)
+ *              colorval (e.g., 0 to bring in BLACK, 0xffffff00 for WHITE)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This rotates a color image about the image center.
+ *      (2) A positive angle gives a clockwise rotation.
+ *      (3) It uses area mapping, dividing each pixel into
+ *          16 subpixels.
+ *      (4) It is about 10% to 20% faster than the more accurate linear
+ *          interpolation function pixRotateAMColor(),
+ *          which uses 256 subpixels.
+ *      (5) For some reason it shifts the image center.
+ *          No attempt is made to rotate the alpha component.
+ *
+ *  *** Warning: implicit assumption about RGB component ordering ***
+ */
+PIX *
+pixRotateAMColorFast(PIX       *pixs,
+                     l_float32  angle,
+                     l_uint32   colorval)
+{
+l_int32    w, h, wpls, wpld;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixRotateAMColorFast");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL);
+
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreateTemplate(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    rotateAMColorFastLow(datad, w, h, wpld, datas, wpls, angle, colorval);
+    return pixd;
+}
diff --git a/src/rotateamlow.c b/src/rotateamlow.c
new file mode 100644 (file)
index 0000000..b9e1820
--- /dev/null
@@ -0,0 +1,675 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  rotateamlow.c
+ *
+ *      Grayscale and color rotation (area mapped)
+ *
+ *          32 bpp grayscale rotation about image center
+ *               void    rotateAMColorLow()
+ *
+ *          8 bpp grayscale rotation about image center
+ *               void    rotateAMGrayLow()
+ *
+ *          32 bpp grayscale rotation about UL corner of image
+ *               void    rotateAMColorCornerLow()
+ *
+ *          8 bpp grayscale rotation about UL corner of image
+ *               void    rotateAMGrayCornerLow()
+ *
+ *          Fast RGB color rotation about center:
+ *               void    rotateAMColorFastLow()
+ *
+ */
+
+#include <string.h>
+#include <math.h>   /* required for sin and tan */
+#include "allheaders.h"
+
+
+/*------------------------------------------------------------------*
+ *             32 bpp grayscale rotation about the center           *
+ *------------------------------------------------------------------*/
+void
+rotateAMColorLow(l_uint32  *datad,
+                 l_int32    w,
+                 l_int32    h,
+                 l_int32    wpld,
+                 l_uint32  *datas,
+                 l_int32    wpls,
+                 l_float32  angle,
+                 l_uint32   colorval)
+{
+l_int32    i, j, xcen, ycen, wm2, hm2;
+l_int32    xdif, ydif, xpm, ypm, xp, yp, xf, yf;
+l_int32    rval, gval, bval;
+l_uint32   word00, word01, word10, word11;
+l_uint32  *lines, *lined;
+l_float32  sina, cosa;
+
+    xcen = w / 2;
+    wm2 = w - 2;
+    ycen = h / 2;
+    hm2 = h - 2;
+    sina = 16. * sin(angle);
+    cosa = 16. * cos(angle);
+
+    for (i = 0; i < h; i++) {
+        ydif = ycen - i;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            xdif = xcen - j;
+            xpm = (l_int32)(-xdif * cosa - ydif * sina);
+            ypm = (l_int32)(-ydif * cosa + xdif * sina);
+            xp = xcen + (xpm >> 4);
+            yp = ycen + (ypm >> 4);
+            xf = xpm & 0x0f;
+            yf = ypm & 0x0f;
+
+                /* if off the edge, write input colorval */
+            if (xp < 0 || yp < 0 || xp > wm2 || yp > hm2) {
+                *(lined + j) = colorval;
+                continue;
+            }
+
+            lines = datas + yp * wpls;
+
+                /* do area weighting.  Without this, we would
+                 * simply do:
+                 *   *(lined + j) = *(lines + xp);
+                 * which is faster but gives lousy results!
+                 */
+            word00 = *(lines + xp);
+            word10 = *(lines + xp + 1);
+            word01 = *(lines + wpls + xp);
+            word11 = *(lines + wpls + xp + 1);
+            rval = ((16 - xf) * (16 - yf) * ((word00 >> L_RED_SHIFT) & 0xff) +
+                    xf * (16 - yf) * ((word10 >> L_RED_SHIFT) & 0xff) +
+                    (16 - xf) * yf * ((word01 >> L_RED_SHIFT) & 0xff) +
+                    xf * yf * ((word11 >> L_RED_SHIFT) & 0xff) + 128) / 256;
+            gval = ((16 - xf) * (16 - yf) * ((word00 >> L_GREEN_SHIFT) & 0xff) +
+                    xf * (16 - yf) * ((word10 >> L_GREEN_SHIFT) & 0xff) +
+                    (16 - xf) * yf * ((word01 >> L_GREEN_SHIFT) & 0xff) +
+                    xf * yf * ((word11 >> L_GREEN_SHIFT) & 0xff) + 128) / 256;
+            bval = ((16 - xf) * (16 - yf) * ((word00 >> L_BLUE_SHIFT) & 0xff) +
+                    xf * (16 - yf) * ((word10 >> L_BLUE_SHIFT) & 0xff) +
+                    (16 - xf) * yf * ((word01 >> L_BLUE_SHIFT) & 0xff) +
+                    xf * yf * ((word11 >> L_BLUE_SHIFT) & 0xff) + 128) / 256;
+            composeRGBPixel(rval, gval, bval, lined + j);
+        }
+    }
+
+    return;
+}
+
+
+/*------------------------------------------------------------------*
+ *             8 bpp grayscale rotation about the center            *
+ *------------------------------------------------------------------*/
+void
+rotateAMGrayLow(l_uint32  *datad,
+                l_int32    w,
+                l_int32    h,
+                l_int32    wpld,
+                l_uint32  *datas,
+                l_int32    wpls,
+                l_float32  angle,
+                l_uint8    grayval)
+{
+l_int32    i, j, xcen, ycen, wm2, hm2;
+l_int32    xdif, ydif, xpm, ypm, xp, yp, xf, yf;
+l_int32    v00, v01, v10, v11;
+l_uint8    val;
+l_uint32  *lines, *lined;
+l_float32  sina, cosa;
+
+    xcen = w / 2;
+    wm2 = w - 2;
+    ycen = h / 2;
+    hm2 = h - 2;
+    sina = 16. * sin(angle);
+    cosa = 16. * cos(angle);
+
+    for (i = 0; i < h; i++) {
+        ydif = ycen - i;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            xdif = xcen - j;
+            xpm = (l_int32)(-xdif * cosa - ydif * sina);
+            ypm = (l_int32)(-ydif * cosa + xdif * sina);
+            xp = xcen + (xpm >> 4);
+            yp = ycen + (ypm >> 4);
+            xf = xpm & 0x0f;
+            yf = ypm & 0x0f;
+
+                /* if off the edge, write input grayval */
+            if (xp < 0 || yp < 0 || xp > wm2 || yp > hm2) {
+                SET_DATA_BYTE(lined, j, grayval);
+                continue;
+            }
+
+            lines = datas + yp * wpls;
+
+                /* do area weighting.  Without this, we would
+                 * simply do:
+                 *   SET_DATA_BYTE(lined, j, GET_DATA_BYTE(lines, xp));
+                 * which is faster but gives lousy results!
+                 */
+            v00 = (16 - xf) * (16 - yf) * GET_DATA_BYTE(lines, xp);
+            v10 = xf * (16 - yf) * GET_DATA_BYTE(lines, xp + 1);
+            v01 = (16 - xf) * yf * GET_DATA_BYTE(lines + wpls, xp);
+            v11 = xf * yf * GET_DATA_BYTE(lines + wpls, xp + 1);
+            val = (l_uint8)((v00 + v01 + v10 + v11 + 128) / 256);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    return;
+}
+
+
+/*------------------------------------------------------------------*
+ *           32 bpp grayscale rotation about the UL corner          *
+ *------------------------------------------------------------------*/
+void
+rotateAMColorCornerLow(l_uint32  *datad,
+                       l_int32    w,
+                       l_int32    h,
+                       l_int32    wpld,
+                       l_uint32  *datas,
+                       l_int32    wpls,
+                       l_float32  angle,
+                       l_uint32   colorval)
+{
+l_int32    i, j, wm2, hm2;
+l_int32    xpm, ypm, xp, yp, xf, yf;
+l_int32    rval, gval, bval;
+l_uint32   word00, word01, word10, word11;
+l_uint32  *lines, *lined;
+l_float32  sina, cosa;
+
+    wm2 = w - 2;
+    hm2 = h - 2;
+    sina = 16. * sin(angle);
+    cosa = 16. * cos(angle);
+
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            xpm = (l_int32)(j * cosa + i * sina);
+            ypm = (l_int32)(i * cosa - j * sina);
+            xp = xpm >> 4;
+            yp = ypm >> 4;
+            xf = xpm & 0x0f;
+            yf = ypm & 0x0f;
+
+                /* if off the edge, write input colorval */
+            if (xp < 0 || yp < 0 || xp > wm2 || yp > hm2) {
+                *(lined + j) = colorval;
+                continue;
+            }
+
+            lines = datas + yp * wpls;
+
+                /* do area weighting.  Without this, we would
+                 * simply do:
+                 *   *(lined + j) = *(lines + xp);
+                 * which is faster but gives lousy results!
+                 */
+            word00 = *(lines + xp);
+            word10 = *(lines + xp + 1);
+            word01 = *(lines + wpls + xp);
+            word11 = *(lines + wpls + xp + 1);
+            rval = ((16 - xf) * (16 - yf) * ((word00 >> L_RED_SHIFT) & 0xff) +
+                    xf * (16 - yf) * ((word10 >> L_RED_SHIFT) & 0xff) +
+                    (16 - xf) * yf * ((word01 >> L_RED_SHIFT) & 0xff) +
+                    xf * yf * ((word11 >> L_RED_SHIFT) & 0xff) + 128) / 256;
+            gval = ((16 - xf) * (16 - yf) * ((word00 >> L_GREEN_SHIFT) & 0xff) +
+                    xf * (16 - yf) * ((word10 >> L_GREEN_SHIFT) & 0xff) +
+                    (16 - xf) * yf * ((word01 >> L_GREEN_SHIFT) & 0xff) +
+                    xf * yf * ((word11 >> L_GREEN_SHIFT) & 0xff) + 128) / 256;
+            bval = ((16 - xf) * (16 - yf) * ((word00 >> L_BLUE_SHIFT) & 0xff) +
+                    xf * (16 - yf) * ((word10 >> L_BLUE_SHIFT) & 0xff) +
+                    (16 - xf) * yf * ((word01 >> L_BLUE_SHIFT) & 0xff) +
+                    xf * yf * ((word11 >> L_BLUE_SHIFT) & 0xff) + 128) / 256;
+            composeRGBPixel(rval, gval, bval, lined + j);
+        }
+    }
+
+    return;
+}
+
+
+
+/*------------------------------------------------------------------*
+ *            8 bpp grayscale rotation about the UL corner          *
+ *------------------------------------------------------------------*/
+void
+rotateAMGrayCornerLow(l_uint32  *datad,
+                      l_int32    w,
+                      l_int32    h,
+                      l_int32    wpld,
+                      l_uint32  *datas,
+                      l_int32    wpls,
+                      l_float32  angle,
+                      l_uint8    grayval)
+{
+l_int32    i, j, wm2, hm2;
+l_int32    xpm, ypm, xp, yp, xf, yf;
+l_int32    v00, v01, v10, v11;
+l_uint8    val;
+l_uint32  *lines, *lined;
+l_float32  sina, cosa;
+
+    wm2 = w - 2;
+    hm2 = h - 2;
+    sina = 16. * sin(angle);
+    cosa = 16. * cos(angle);
+
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            xpm = (l_int32)(j * cosa + i * sina);
+            ypm = (l_int32)(i * cosa - j * sina);
+            xp = xpm >> 4;
+            yp = ypm >> 4;
+            xf = xpm & 0x0f;
+            yf = ypm & 0x0f;
+
+                /* if off the edge, write input grayval */
+            if (xp < 0 || yp < 0 || xp > wm2 || yp > hm2) {
+                SET_DATA_BYTE(lined, j, grayval);
+                continue;
+            }
+
+            lines = datas + yp * wpls;
+
+                /* do area weighting.  Without this, we would
+                 * simply do:
+                 *   SET_DATA_BYTE(lined, j, GET_DATA_BYTE(lines, xp));
+                 * which is faster but gives lousy results!
+                 */
+            v00 = (16 - xf) * (16 - yf) * GET_DATA_BYTE(lines, xp);
+            v10 = xf * (16 - yf) * GET_DATA_BYTE(lines, xp + 1);
+            v01 = (16 - xf) * yf * GET_DATA_BYTE(lines + wpls, xp);
+            v11 = xf * yf * GET_DATA_BYTE(lines + wpls, xp + 1);
+            val = (l_uint8)((v00 + v01 + v10 + v11 + 128) / 256);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    return;
+}
+
+
+/*------------------------------------------------------------------*
+ *               Fast RGB color rotation about center               *
+ *------------------------------------------------------------------*/
+/*!
+ *  rotateAMColorFastLow()
+ *
+ *     This is a special simplification of area mapping with division
+ *     of each pixel into 16 sub-pixels.  The exact coefficients that
+ *     should be used are the same as for the 4x linear interpolation
+ *     scaling case, and are given there.  I tried to approximate these
+ *     as weighted coefficients with a maximum sum of 4, which
+ *     allows us to do the arithmetic in parallel for the R, G and B
+ *     components in a 32 bit pixel.  However, there are three reasons
+ *     for not doing that:
+ *        (1) the loss of accuracy in the parallel implementation
+ *            is visually significant
+ *        (2) the parallel implementation (described below) is slower
+ *        (3) the parallel implementation requires allocation of
+ *            a temporary color image
+ *
+ *     There are 16 cases for the choice of the subpixel, and
+ *     for each, the mapping to the relevant source
+ *     pixels is as follows:
+ *
+ *      subpixel      src pixel weights
+ *      --------      -----------------
+ *         0          sp1
+ *         1          (3 * sp1 + sp2) / 4
+ *         2          (sp1 + sp2) / 2
+ *         3          (sp1 + 3 * sp2) / 4
+ *         4          (3 * sp1 + sp3) / 4
+ *         5          (9 * sp1 + 3 * sp2 + 3 * sp3 + sp4) / 16
+ *         6          (3 * sp1 + 3 * sp2 + sp3 + sp4) / 8
+ *         7          (3 * sp1 + 9 * sp2 + sp3 + 3 * sp4) / 16
+ *         8          (sp1 + sp3) / 2
+ *         9          (3 * sp1 + sp2 + 3 * sp3 + sp4) / 8
+ *         10         (sp1 + sp2 + sp3 + sp4) / 4
+ *         11         (sp1 + 3 * sp2 + sp3 + 3 * sp4) / 8
+ *         12         (sp1 + 3 * sp3) / 4
+ *         13         (3 * sp1 + sp2 + 9 * sp3 + 3 * sp4) / 16
+ *         14         (sp1 + sp2 + 3 * sp3 + 3 * sp4) / 8
+ *         15         (sp1 + 3 * sp2 + 3 * sp3 + 9 * sp4) / 16
+ *
+ *     Another way to visualize this is to consider the area mapping
+ *     (or linear interpolation) coefficients  for the pixel sp1.
+ *     Expressed in fourths, they can be written as asymmetric matrix:
+ *
+ *           4      3      2      1
+ *           3      2.25   1.5    0.75
+ *           2      1.5    1      0.5
+ *           1      0.75   0.5    0.25
+ *
+ *     The coefficients for the three neighboring pixels can be
+ *     similarly written.
+ *
+ *     This is implemented here, where, for each color component,
+ *     we inline its extraction from each participating word,
+ *     construct the linear combination, and combine the results
+ *     into the destination 32 bit RGB pixel, using the appropriate shifts.
+ *
+ *     It is interesting to note that an alternative method, where
+ *     we do the arithmetic on the 32 bit pixels directly (after
+ *     shifting the components so they won't overflow into each other)
+ *     is significantly inferior.  Because we have only 8 bits for
+ *     internal overflows, which can be distributed as 2, 3, 3, it
+ *     is impossible to add these with the correct linear
+ *     interpolation coefficients, which require a sum of up to 16.
+ *     Rounding off to a sum of 4 causes appreciable visual artifacts
+ *     in the rotated image.  The code for the inferior method
+ *     can be found in prog/rotatefastalt.c, for reference.
+ *
+ *     *** Warning: explicit assumption about RGB component ordering ***
+ */
+void
+rotateAMColorFastLow(l_uint32  *datad,
+                     l_int32    w,
+                     l_int32    h,
+                     l_int32    wpld,
+                     l_uint32  *datas,
+                     l_int32    wpls,
+                     l_float32  angle,
+                     l_uint32   colorval)
+{
+l_int32    i, j, xcen, ycen, wm2, hm2;
+l_int32    xdif, ydif, xpm, ypm, xp, yp, xf, yf;
+l_uint32   word1, word2, word3, word4, red, blue, green;
+l_uint32  *pword, *lines, *lined;
+l_float32  sina, cosa;
+
+    xcen = w / 2;
+    wm2 = w - 2;
+    ycen = h / 2;
+    hm2 = h - 2;
+    sina = 4. * sin(angle);
+    cosa = 4. * cos(angle);
+
+    for (i = 0; i < h; i++) {
+        ydif = ycen - i;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            xdif = xcen - j;
+            xpm = (l_int32)(-xdif * cosa - ydif * sina);
+            ypm = (l_int32)(-ydif * cosa + xdif * sina);
+            xp = xcen + (xpm >> 2);
+            yp = ycen + (ypm >> 2);
+            xf = xpm & 0x03;
+            yf = ypm & 0x03;
+
+                /* if off the edge, write input grayval */
+            if (xp < 0 || yp < 0 || xp > wm2 || yp > hm2) {
+                *(lined + j) = colorval;
+                continue;
+            }
+
+            lines = datas + yp * wpls;
+            pword = lines + xp;
+
+            switch (xf + 4 * yf)
+            {
+            case 0:
+                *(lined + j) = *pword;
+                break;
+            case 1:
+                word1 = *pword;
+                word2 = *(pword + 1);
+                red = 3 * (word1 >> 24) + (word2 >> 24);
+                green = 3 * ((word1 >> 16) & 0xff) +
+                            ((word2 >> 16) & 0xff);
+                blue = 3 * ((word1 >> 8) & 0xff) +
+                            ((word2 >> 8) & 0xff);
+                *(lined + j) = ((red << 22) & 0xff000000) |
+                               ((green << 14) & 0x00ff0000) |
+                               ((blue << 6) & 0x0000ff00);
+                break;
+            case 2:
+                word1 = *pword;
+                word2 = *(pword + 1);
+                red = (word1 >> 24) + (word2 >> 24);
+                green = ((word1 >> 16) & 0xff) + ((word2 >> 16) & 0xff);
+                blue = ((word1 >> 8) & 0xff) + ((word2 >> 8) & 0xff);
+                *(lined + j) = ((red << 23) & 0xff000000) |
+                               ((green << 15) & 0x00ff0000) |
+                               ((blue << 7) & 0x0000ff00);
+                break;
+            case 3:
+                word1 = *pword;
+                word2 = *(pword + 1);
+                red = (word1 >> 24) + 3 * (word2 >> 24);
+                green = ((word1 >> 16) & 0xff) +
+                          3 * ((word2 >> 16) & 0xff);
+                blue = ((word1 >> 8) & 0xff) +
+                          3 * ((word2 >> 8) & 0xff);
+                *(lined + j) = ((red << 22) & 0xff000000) |
+                               ((green << 14) & 0x00ff0000) |
+                               ((blue << 6) & 0x0000ff00);
+                break;
+            case 4:
+                word1 = *pword;
+                word3 = *(pword + wpls);
+                red = 3 * (word1 >> 24) + (word3 >> 24);
+                green = 3 * ((word1 >> 16) & 0xff) +
+                            ((word3 >> 16) & 0xff);
+                blue = 3 * ((word1 >> 8) & 0xff) +
+                            ((word3 >> 8) & 0xff);
+                *(lined + j) = ((red << 22) & 0xff000000) |
+                               ((green << 14) & 0x00ff0000) |
+                               ((blue << 6) & 0x0000ff00);
+                break;
+            case 5:
+                word1 = *pword;
+                word2 = *(pword + 1);
+                word3 = *(pword + wpls);
+                word4 = *(pword + wpls + 1);
+                red = 9 * (word1 >> 24) + 3 * (word2 >> 24) +
+                      3 * (word3 >> 24) + (word4 >> 24);
+                green = 9 * ((word1 >> 16) & 0xff) +
+                        3 * ((word2 >> 16) & 0xff) +
+                        3 * ((word3 >> 16) & 0xff) +
+                        ((word4 >> 16) & 0xff);
+                blue = 9 * ((word1 >> 8) & 0xff) +
+                       3 * ((word2 >> 8) & 0xff) +
+                       3 * ((word3 >> 8) & 0xff) +
+                       ((word4 >> 8) & 0xff);
+                *(lined + j) = ((red << 20) & 0xff000000) |
+                               ((green << 12) & 0x00ff0000) |
+                               ((blue << 4) & 0x0000ff00);
+                break;
+            case 6:
+                word1 = *pword;
+                word2 = *(pword + 1);
+                word3 = *(pword + wpls);
+                word4 = *(pword + wpls + 1);
+                red = 3 * (word1 >> 24) +  3 * (word2 >> 24) +
+                      (word3 >> 24) + (word4 >> 24);
+                green = 3 * ((word1 >> 16) & 0xff) +
+                        3 * ((word2 >> 16) & 0xff) +
+                        ((word3 >> 16) & 0xff) +
+                        ((word4 >> 16) & 0xff);
+                blue = 3 * ((word1 >> 8) & 0xff) +
+                       3 * ((word2 >> 8) & 0xff) +
+                       ((word3 >> 8) & 0xff) +
+                       ((word4 >> 8) & 0xff);
+                *(lined + j) = ((red << 21) & 0xff000000) |
+                               ((green << 13) & 0x00ff0000) |
+                               ((blue << 5) & 0x0000ff00);
+                break;
+            case 7:
+                word1 = *pword;
+                word2 = *(pword + 1);
+                word3 = *(pword + wpls);
+                word4 = *(pword + wpls + 1);
+                red = 3 * (word1 >> 24) + 9 * (word2 >> 24) +
+                      (word3 >> 24) + 3 * (word4 >> 24);
+                green = 3 * ((word1 >> 16) & 0xff) +
+                        9 * ((word2 >> 16) & 0xff) +
+                        ((word3 >> 16) & 0xff) +
+                        3 * ((word4 >> 16) & 0xff);
+                blue = 3 * ((word1 >> 8) & 0xff) +
+                       9 * ((word2 >> 8) & 0xff) +
+                         ((word3 >> 8) & 0xff) +
+                         3 * ((word4 >> 8) & 0xff);
+                *(lined + j) = ((red << 20) & 0xff000000) |
+                               ((green << 12) & 0x00ff0000) |
+                               ((blue << 4) & 0x0000ff00);
+                break;
+            case 8:
+                word1 = *pword;
+                word3 = *(pword + wpls);
+                red = (word1 >> 24) + (word3 >> 24);
+                green = ((word1 >> 16) & 0xff) + ((word3 >> 16) & 0xff);
+                blue = ((word1 >> 8) & 0xff) + ((word3 >> 8) & 0xff);
+                *(lined + j) = ((red << 23) & 0xff000000) |
+                               ((green << 15) & 0x00ff0000) |
+                               ((blue << 7) & 0x0000ff00);
+                break;
+            case 9:
+                word1 = *pword;
+                word2 = *(pword + 1);
+                word3 = *(pword + wpls);
+                word4 = *(pword + wpls + 1);
+                red = 3 * (word1 >> 24) + (word2 >> 24) +
+                      3 * (word3 >> 24) + (word4 >> 24);
+                green = 3 * ((word1 >> 16) & 0xff) + ((word2 >> 16) & 0xff) +
+                        3 * ((word3 >> 16) & 0xff) + ((word4 >> 16) & 0xff);
+                blue = 3 * ((word1 >> 8) & 0xff) + ((word2 >> 8) & 0xff) +
+                       3 * ((word3 >> 8) & 0xff) + ((word4 >> 8) & 0xff);
+                *(lined + j) = ((red << 21) & 0xff000000) |
+                               ((green << 13) & 0x00ff0000) |
+                               ((blue << 5) & 0x0000ff00);
+                break;
+            case 10:
+                word1 = *pword;
+                word2 = *(pword + 1);
+                word3 = *(pword + wpls);
+                word4 = *(pword + wpls + 1);
+                red = (word1 >> 24) + (word2 >> 24) +
+                      (word3 >> 24) + (word4 >> 24);
+                green = ((word1 >> 16) & 0xff) + ((word2 >> 16) & 0xff) +
+                        ((word3 >> 16) & 0xff) + ((word4 >> 16) & 0xff);
+                blue = ((word1 >> 8) & 0xff) + ((word2 >> 8) & 0xff) +
+                       ((word3 >> 8) & 0xff) + ((word4 >> 8) & 0xff);
+                *(lined + j) = ((red << 22) & 0xff000000) |
+                               ((green << 14) & 0x00ff0000) |
+                               ((blue << 6) & 0x0000ff00);
+                break;
+            case 11:
+                word1 = *pword;
+                word2 = *(pword + 1);
+                word3 = *(pword + wpls);
+                word4 = *(pword + wpls + 1);
+                red = (word1 >> 24) + 3 * (word2 >> 24) +
+                      (word3 >> 24) + 3 * (word4 >> 24);
+                green = ((word1 >> 16) & 0xff) + 3 * ((word2 >> 16) & 0xff) +
+                        ((word3 >> 16) & 0xff) + 3 * ((word4 >> 16) & 0xff);
+                blue = ((word1 >> 8) & 0xff) + 3 * ((word2 >> 8) & 0xff) +
+                       ((word3 >> 8) & 0xff) + 3 * ((word4 >> 8) & 0xff);
+                *(lined + j) = ((red << 21) & 0xff000000) |
+                               ((green << 13) & 0x00ff0000) |
+                               ((blue << 5) & 0x0000ff00);
+                break;
+            case 12:
+                word1 = *pword;
+                word3 = *(pword + wpls);
+                red = (word1 >> 24) + 3 * (word3 >> 24);
+                green = ((word1 >> 16) & 0xff) +
+                          3 * ((word3 >> 16) & 0xff);
+                blue = ((word1 >> 8) & 0xff) +
+                          3 * ((word3 >> 8) & 0xff);
+                *(lined + j) = ((red << 22) & 0xff000000) |
+                               ((green << 14) & 0x00ff0000) |
+                               ((blue << 6) & 0x0000ff00);
+                break;
+            case 13:
+                word1 = *pword;
+                word2 = *(pword + 1);
+                word3 = *(pword + wpls);
+                word4 = *(pword + wpls + 1);
+                red = 3 * (word1 >> 24) + (word2 >> 24) +
+                      9 * (word3 >> 24) + 3 * (word4 >> 24);
+                green = 3 * ((word1 >> 16) & 0xff) + ((word2 >> 16) & 0xff) +
+                        9 * ((word3 >> 16) & 0xff) + 3 * ((word4 >> 16) & 0xff);
+                blue = 3 *((word1 >> 8) & 0xff) + ((word2 >> 8) & 0xff) +
+                       9 * ((word3 >> 8) & 0xff) + 3 * ((word4 >> 8) & 0xff);
+                *(lined + j) = ((red << 20) & 0xff000000) |
+                               ((green << 12) & 0x00ff0000) |
+                               ((blue << 4) & 0x0000ff00);
+                break;
+            case 14:
+                word1 = *pword;
+                word2 = *(pword + 1);
+                word3 = *(pword + wpls);
+                word4 = *(pword + wpls + 1);
+                red = (word1 >> 24) + (word2 >> 24) +
+                      3 * (word3 >> 24) + 3 * (word4 >> 24);
+                green = ((word1 >> 16) & 0xff) +((word2 >> 16) & 0xff) +
+                        3 * ((word3 >> 16) & 0xff) + 3 * ((word4 >> 16) & 0xff);
+                blue = ((word1 >> 8) & 0xff) + ((word2 >> 8) & 0xff) +
+                       3 * ((word3 >> 8) & 0xff) + 3 * ((word4 >> 8) & 0xff);
+                *(lined + j) = ((red << 21) & 0xff000000) |
+                               ((green << 13) & 0x00ff0000) |
+                               ((blue << 5) & 0x0000ff00);
+                break;
+            case 15:
+                word1 = *pword;
+                word2 = *(pword + 1);
+                word3 = *(pword + wpls);
+                word4 = *(pword + wpls + 1);
+                red = (word1 >> 24) + 3 * (word2 >> 24) +
+                      3 * (word3 >> 24) + 9 * (word4 >> 24);
+                green = ((word1 >> 16) & 0xff) + 3 * ((word2 >> 16) & 0xff) +
+                        3 * ((word3 >> 16) & 0xff) + 9 * ((word4 >> 16) & 0xff);
+                blue = ((word1 >> 8) & 0xff) + 3 * ((word2 >> 8) & 0xff) +
+                       3 * ((word3 >> 8) & 0xff) + 9 * ((word4 >> 8) & 0xff);
+                *(lined + j) = ((red << 20) & 0xff000000) |
+                               ((green << 12) & 0x00ff0000) |
+                               ((blue << 4) & 0x0000ff00);
+                break;
+            default:
+                fprintf(stderr, "shouldn't get here\n");
+                break;
+            }
+        }
+    }
+
+    return;
+}
diff --git a/src/rotateorth.c b/src/rotateorth.c
new file mode 100644 (file)
index 0000000..b5bb884
--- /dev/null
@@ -0,0 +1,712 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  rotateorth.c
+ *
+ *      Top-level rotation by multiples of 90 degrees
+ *            PIX             *pixRotateOrth()
+ *
+ *      180-degree rotation
+ *            PIX             *pixRotate180()
+ *
+ *      90-degree rotation (both directions)
+ *            PIX             *pixRotate90()
+ *
+ *      Left-right flip
+ *            PIX             *pixFlipLR()
+ *
+ *      Top-bottom flip
+ *            PIX             *pixFlipTB()
+ *
+ *      Byte reverse tables
+ *            static l_uint8  *makeReverseByteTab1()
+ *            static l_uint8  *makeReverseByteTab2()
+ *            static l_uint8  *makeReverseByteTab4()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static l_uint8 *makeReverseByteTab1(void);
+static l_uint8 *makeReverseByteTab2(void);
+static l_uint8 *makeReverseByteTab4(void);
+
+
+/*------------------------------------------------------------------*
+ *           Top-level rotation by multiples of 90 degrees          *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotateOrth()
+ *
+ *      Input:  pixs (all depths)
+ *              quads (0-3; number of 90 degree cw rotations)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixRotateOrth(PIX     *pixs,
+              l_int32  quads)
+{
+    PROCNAME("pixRotateOrth");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (quads < 0 || quads > 3)
+        return (PIX *)ERROR_PTR("quads not in {0,1,2,3}", procName, NULL);
+
+    if (quads == 0)
+        return pixCopy(NULL, pixs);
+    else if (quads == 1)
+        return pixRotate90(pixs, 1);
+    else if (quads == 2)
+        return pixRotate180(NULL, pixs);
+    else /* quads == 3 */
+        return pixRotate90(pixs, -1);
+}
+
+
+/*------------------------------------------------------------------*
+ *                          180 degree rotation                     *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotate180()
+ *
+ *      Input:  pixd  (<optional>; can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (all depths)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This does a 180 rotation of the image about the center,
+ *          which is equivalent to a left-right flip about a vertical
+ *          line through the image center, followed by a top-bottom
+ *          flip about a horizontal line through the image center.
+ *      (2) There are 3 cases for input:
+ *          (a) pixd == null (creates a new pixd)
+ *          (b) pixd == pixs (in-place operation)
+ *          (c) pixd != pixs (existing pixd)
+ *      (3) For clarity, use these three patterns, respectively:
+ *          (a) pixd = pixRotate180(NULL, pixs);
+ *          (b) pixRotate180(pixs, pixs);
+ *          (c) pixRotate180(pixd, pixs);
+ */
+PIX *
+pixRotate180(PIX  *pixd,
+             PIX  *pixs)
+{
+l_int32  d;
+
+    PROCNAME("pixRotate180");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not in {1,2,4,8,16,32} bpp",
+                                procName, NULL);
+
+        /* Prepare pixd for in-place operation */
+    if ((pixd = pixCopy(pixd, pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    pixFlipLR(pixd, pixd);
+    pixFlipTB(pixd, pixd);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                           90 degree rotation                     *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotate90()
+ *
+ *      Input:  pixs (all depths)
+ *              direction (1 = clockwise,  -1 = counter-clockwise)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This does a 90 degree rotation of the image about the center,
+ *          either cw or ccw, returning a new pix.
+ *      (2) The direction must be either 1 (cw) or -1 (ccw).
+ */
+PIX *
+pixRotate90(PIX     *pixs,
+            l_int32  direction)
+{
+l_int32    wd, hd, d, wpls, wpld;
+l_int32    i, j, k, m, iend, nswords;
+l_uint32   val, word;
+l_uint32  *lines, *datas, *lined, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixRotate90");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &hd, &wd, &d);  /* note: reversed */
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not in {1,2,4,8,16,32} bpp",
+                                procName, NULL);
+    if (direction != 1 && direction != -1)
+        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
+
+    if ((pixd = pixCreate(wd, hd, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyColormap(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    if (direction == 1) {  /* clockwise */
+        switch (d)
+        {
+            case 32:
+                for (i = 0; i < hd; i++) {
+                    lined = datad + i * wpld;
+                    lines = datas + (wd - 1) * wpls;
+                    for (j = 0; j < wd; j++) {
+                        lined[j] = lines[i];
+                        lines -= wpls;
+                    }
+                }
+                break;
+            case 16:
+                for (i = 0; i < hd; i++) {
+                    lined = datad + i * wpld;
+                    lines = datas + (wd - 1) * wpls;
+                    for (j = 0; j < wd; j++) {
+                        if ((val = GET_DATA_TWO_BYTES(lines, i)))
+                            SET_DATA_TWO_BYTES(lined, j, val);
+                        lines -= wpls;
+                    }
+                }
+                break;
+            case 8:
+                for (i = 0; i < hd; i++) {
+                    lined = datad + i * wpld;
+                    lines = datas + (wd - 1) * wpls;
+                    for (j = 0; j < wd; j++) {
+                        if ((val = GET_DATA_BYTE(lines, i)))
+                            SET_DATA_BYTE(lined, j, val);
+                        lines -= wpls;
+                    }
+                }
+                break;
+            case 4:
+                for (i = 0; i < hd; i++) {
+                    lined = datad + i * wpld;
+                    lines = datas + (wd - 1) * wpls;
+                    for (j = 0; j < wd; j++) {
+                        if ((val = GET_DATA_QBIT(lines, i)))
+                            SET_DATA_QBIT(lined, j, val);
+                        lines -= wpls;
+                    }
+                }
+                break;
+            case 2:
+                for (i = 0; i < hd; i++) {
+                    lined = datad + i * wpld;
+                    lines = datas + (wd - 1) * wpls;
+                    for (j = 0; j < wd; j++) {
+                        if ((val = GET_DATA_DIBIT(lines, i)))
+                            SET_DATA_DIBIT(lined, j, val);
+                        lines -= wpls;
+                    }
+                }
+                break;
+            case 1:
+                nswords = hd / 32;
+                for (j = 0; j < wd; j++) {
+                    lined = datad;
+                    lines = datas + (wd - 1 - j) * wpls;
+                    for (k = 0; k < nswords; k++) {
+                        word = lines[k];
+                        if (!word) {
+                            lined += 32 * wpld;
+                            continue;
+                        } else {
+                            iend = 32 * (k + 1);
+                            for (m = 0, i = 32 * k; i < iend; i++, m++) {
+                                if ((word << m) & 0x80000000)
+                                    SET_DATA_BIT(lined, j);
+                                lined += wpld;
+                            }
+                        }
+                    }
+                    for (i = 32 * nswords; i < hd; i++) {
+                        if (GET_DATA_BIT(lines, i))
+                            SET_DATA_BIT(lined, j);
+                        lined += wpld;
+                    }
+                }
+                break;
+            default:
+                pixDestroy(&pixd);
+                L_ERROR("illegal depth: %d\n", procName, d);
+                break;
+        }
+    } else  {     /* direction counter-clockwise */
+        switch (d)
+        {
+            case 32:
+                for (i = 0; i < hd; i++) {
+                    lined = datad + i * wpld;
+                    lines = datas;
+                    for (j = 0; j < wd; j++) {
+                        lined[j] = lines[hd - 1 - i];
+                        lines += wpls;
+                    }
+                }
+                break;
+            case 16:
+                for (i = 0; i < hd; i++) {
+                    lined = datad + i * wpld;
+                    lines = datas;
+                    for (j = 0; j < wd; j++) {
+                        if ((val = GET_DATA_TWO_BYTES(lines, hd - 1 - i)))
+                            SET_DATA_TWO_BYTES(lined, j, val);
+                        lines += wpls;
+                    }
+                }
+                break;
+            case 8:
+                for (i = 0; i < hd; i++) {
+                    lined = datad + i * wpld;
+                    lines = datas;
+                    for (j = 0; j < wd; j++) {
+                        if ((val = GET_DATA_BYTE(lines, hd - 1 - i)))
+                            SET_DATA_BYTE(lined, j, val);
+                        lines += wpls;
+                    }
+                }
+                break;
+            case 4:
+                for (i = 0; i < hd; i++) {
+                    lined = datad + i * wpld;
+                    lines = datas;
+                    for (j = 0; j < wd; j++) {
+                        if ((val = GET_DATA_QBIT(lines, hd - 1 - i)))
+                            SET_DATA_QBIT(lined, j, val);
+                        lines += wpls;
+                    }
+                }
+                break;
+            case 2:
+                for (i = 0; i < hd; i++) {
+                    lined = datad + i * wpld;
+                    lines = datas;
+                    for (j = 0; j < wd; j++) {
+                        if ((val = GET_DATA_DIBIT(lines, hd - 1 - i)))
+                            SET_DATA_DIBIT(lined, j, val);
+                        lines += wpls;
+                    }
+                }
+                break;
+            case 1:
+                nswords = hd / 32;
+                for (j = 0; j < wd; j++) {
+                    lined = datad + (hd - 1) * wpld;
+                    lines = datas + (wd - 1 - j) * wpls;
+                    for (k = 0; k < nswords; k++) {
+                        word = lines[k];
+                        if (!word) {
+                            lined -= 32 * wpld;
+                            continue;
+                        } else {
+                            iend = 32 * (k + 1);
+                            for (m = 0, i = 32 * k; i < iend; i++, m++) {
+                                if ((word << m) & 0x80000000)
+                                    SET_DATA_BIT(lined, wd - 1 - j);
+                                lined -= wpld;
+                            }
+                        }
+                    }
+                    for (i = 32 * nswords; i < hd; i++) {
+                        if (GET_DATA_BIT(lines, i))
+                            SET_DATA_BIT(lined, wd - 1 - j);
+                        lined -= wpld;
+                    }
+                }
+                break;
+            default:
+                pixDestroy(&pixd);
+                L_ERROR("illegal depth: %d\n", procName, d);
+                break;
+        }
+    }
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                            Left-right flip                       *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixFlipLR()
+ *
+ *      Input:  pixd  (<optional>; can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (all depths)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This does a left-right flip of the image, which is
+ *          equivalent to a rotation out of the plane about a
+ *          vertical line through the image center.
+ *      (2) There are 3 cases for input:
+ *          (a) pixd == null (creates a new pixd)
+ *          (b) pixd == pixs (in-place operation)
+ *          (c) pixd != pixs (existing pixd)
+ *      (3) For clarity, use these three patterns, respectively:
+ *          (a) pixd = pixFlipLR(NULL, pixs);
+ *          (b) pixFlipLR(pixs, pixs);
+ *          (c) pixFlipLR(pixd, pixs);
+ *      (4) If an existing pixd is not the same size as pixs, the
+ *          image data will be reallocated.
+ *      (5) The pixel access routines allow a trivial implementation.
+ *          However, for d < 8, it is more efficient to right-justify
+ *          each line to a 32-bit boundary and then extract bytes and
+ *          do pixel reversing.   In those cases, as in the 180 degree
+ *          rotation, we right-shift the data (if necessary) to
+ *          right-justify on the 32 bit boundary, and then read the
+ *          bytes off each raster line in reverse order, reversing
+ *          the pixels in each byte using a table.  These functions
+ *          for 1, 2 and 4 bpp were tested against the "trivial"
+ *          version (shown here for 4 bpp):
+ *              for (i = 0; i < h; i++) {
+ *                  line = data + i * wpl;
+ *                  memcpy(buffer, line, bpl);
+ *                    for (j = 0; j < w; j++) {
+ *                      val = GET_DATA_QBIT(buffer, w - 1 - j);
+ *                        SET_DATA_QBIT(line, j, val);
+ *                  }
+ *              }
+ */
+PIX *
+pixFlipLR(PIX  *pixd,
+          PIX  *pixs)
+{
+l_uint8   *tab;
+l_int32    w, h, d, wpl;
+l_int32    extra, shift, databpl, bpl, i, j;
+l_uint32   val;
+l_uint32  *line, *data, *buffer;
+
+    PROCNAME("pixFlipLR");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not in {1,2,4,8,16,32} bpp",
+                                procName, NULL);
+
+        /* Prepare pixd for in-place operation */
+    if ((pixd = pixCopy(pixd, pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    data = pixGetData(pixd);
+    wpl = pixGetWpl(pixd);
+    switch (d)
+    {
+    case 1:
+        tab = makeReverseByteTab1();
+        break;
+    case 2:
+        tab = makeReverseByteTab2();
+        break;
+    case 4:
+        tab = makeReverseByteTab4();
+        break;
+    default:
+        tab = NULL;
+        break;
+    }
+
+        /* Possibly inplace assigning return val, so on failure return pixd */
+    if ((buffer = (l_uint32 *)LEPT_CALLOC(wpl, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("buffer not made", procName, pixd);
+
+    bpl = 4 * wpl;
+    switch (d)
+    {
+        case 32:
+            for (i = 0; i < h; i++) {
+                line = data + i * wpl;
+                memcpy(buffer, line, bpl);
+                for (j = 0; j < w; j++)
+                    line[j] = buffer[w - 1 - j];
+            }
+            break;
+        case 16:
+            for (i = 0; i < h; i++) {
+                line = data + i * wpl;
+                memcpy(buffer, line, bpl);
+                for (j = 0; j < w; j++) {
+                    val = GET_DATA_TWO_BYTES(buffer, w - 1 - j);
+                    SET_DATA_TWO_BYTES(line, j, val);
+                }
+            }
+            break;
+        case 8:
+            for (i = 0; i < h; i++) {
+                line = data + i * wpl;
+                memcpy(buffer, line, bpl);
+                for (j = 0; j < w; j++) {
+                    val = GET_DATA_BYTE(buffer, w - 1 - j);
+                    SET_DATA_BYTE(line, j, val);
+                }
+            }
+            break;
+        case 4:
+            extra = (w * d) & 31;
+            if (extra)
+                shift = 8 - extra / 4;
+            else
+                shift = 0;
+            if (shift)
+                rasteropHipLow(data, h, d, wpl, 0, h, shift);
+
+            databpl = (w + 1) / 2;
+            for (i = 0; i < h; i++) {
+                line = data + i * wpl;
+                memcpy(buffer, line, bpl);
+                for (j = 0; j < databpl; j++) {
+                    val = GET_DATA_BYTE(buffer, bpl - 1 - j);
+                    SET_DATA_BYTE(line, j, tab[val]);
+                }
+            }
+            break;
+        case 2:
+            extra = (w * d) & 31;
+            if (extra)
+                shift = 16 - extra / 2;
+            else
+                shift = 0;
+            if (shift)
+                rasteropHipLow(data, h, d, wpl, 0, h, shift);
+
+            databpl = (w + 3) / 4;
+            for (i = 0; i < h; i++) {
+                line = data + i * wpl;
+                memcpy(buffer, line, bpl);
+                for (j = 0; j < databpl; j++) {
+                    val = GET_DATA_BYTE(buffer, bpl - 1 - j);
+                    SET_DATA_BYTE(line, j, tab[val]);
+                }
+            }
+            break;
+        case 1:
+            extra = (w * d) & 31;
+            if (extra)
+                shift = 32 - extra;
+            else
+                shift = 0;
+            if (shift)
+                rasteropHipLow(data, h, d, wpl, 0, h, shift);
+
+            databpl = (w + 7) / 8;
+            for (i = 0; i < h; i++) {
+                line = data + i * wpl;
+                memcpy(buffer, line, bpl);
+                for (j = 0; j < databpl; j++) {
+                    val = GET_DATA_BYTE(buffer, bpl - 1 - j);
+                    SET_DATA_BYTE(line, j, tab[val]);
+                }
+            }
+            break;
+        default:
+            pixDestroy(&pixd);
+            L_ERROR("illegal depth: %d\n", procName, d);
+            break;
+    }
+
+    LEPT_FREE(buffer);
+    if (tab) LEPT_FREE(tab);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                            Top-bottom flip                       *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixFlipTB()
+ *
+ *      Input:  pixd  (<optional>; can be null, equal to pixs,
+ *                     or different from pixs)
+ *              pixs (all depths)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This does a top-bottom flip of the image, which is
+ *          equivalent to a rotation out of the plane about a
+ *          horizontal line through the image center.
+ *      (2) There are 3 cases for input:
+ *          (a) pixd == null (creates a new pixd)
+ *          (b) pixd == pixs (in-place operation)
+ *          (c) pixd != pixs (existing pixd)
+ *      (3) For clarity, use these three patterns, respectively:
+ *          (a) pixd = pixFlipTB(NULL, pixs);
+ *          (b) pixFlipTB(pixs, pixs);
+ *          (c) pixFlipTB(pixd, pixs);
+ *      (4) If an existing pixd is not the same size as pixs, the
+ *          image data will be reallocated.
+ *      (5) This is simple and fast.  We use the memcpy function
+ *          to do all the work on aligned data, regardless of pixel
+ *          depth.
+ */
+PIX *
+pixFlipTB(PIX  *pixd,
+          PIX  *pixs)
+{
+l_int32    h, d, wpl, i, k, h2, bpl;
+l_uint32  *linet, *lineb;
+l_uint32  *data, *buffer;
+
+    PROCNAME("pixFlipTB");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, NULL, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not in {1,2,4,8,16,32} bpp",
+                                procName, NULL);
+
+        /* Prepare pixd for in-place operation */
+    if ((pixd = pixCopy(pixd, pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+    data = pixGetData(pixd);
+    wpl = pixGetWpl(pixd);
+    if ((buffer = (l_uint32 *)LEPT_CALLOC(wpl, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("buffer not made", procName, pixd);
+
+    h2 = h / 2;
+    bpl = 4 * wpl;
+    for (i = 0, k = h - 1; i < h2; i++, k--) {
+        linet = data + i * wpl;
+        lineb = data + k * wpl;
+        memcpy(buffer, linet, bpl);
+        memcpy(linet, lineb, bpl);
+        memcpy(lineb, buffer, bpl);
+    }
+
+    LEPT_FREE(buffer);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                      Static byte reverse tables                  *
+ *------------------------------------------------------------------*/
+/*!
+ *  makeReverseByteTab1()
+ *
+ *  Notes:
+ *      (1) This generates an 8 bit lookup table for reversing
+ *          the order of eight 1-bit pixels.
+ */
+static l_uint8 *
+makeReverseByteTab1(void)
+{
+l_int32   i;
+l_uint8  *tab;
+
+    PROCNAME("makeReverseByteTab1");
+
+    if ((tab = (l_uint8 *)LEPT_CALLOC(256, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+
+    for (i = 0; i < 256; i++)
+        tab[i] = ((0x80 & i) >> 7) |
+                 ((0x40 & i) >> 5) |
+                 ((0x20 & i) >> 3) |
+                 ((0x10 & i) >> 1) |
+                 ((0x08 & i) << 1) |
+                 ((0x04 & i) << 3) |
+                 ((0x02 & i) << 5) |
+                 ((0x01 & i) << 7);
+
+    return tab;
+}
+
+
+/*!
+ *  makeReverseByteTab2()
+ *
+ *  Notes:
+ *      (1) This generates an 8 bit lookup table for reversing
+ *          the order of four 2-bit pixels.
+ */
+static l_uint8 *
+makeReverseByteTab2(void)
+{
+l_int32   i;
+l_uint8  *tab;
+
+    PROCNAME("makeReverseByteTab2");
+
+    if ((tab = (l_uint8 *)LEPT_CALLOC(256, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+
+    for (i = 0; i < 256; i++)
+        tab[i] = ((0xc0 & i) >> 6) |
+                 ((0x30 & i) >> 2) |
+                 ((0x0c & i) << 2) |
+                 ((0x03 & i) << 6);
+    return tab;
+}
+
+
+/*!
+ *  makeReverseByteTab4()
+ *
+ *  Notes:
+ *      (1) This generates an 8 bit lookup table for reversing
+ *          the order of two 4-bit pixels.
+ */
+static l_uint8 *
+makeReverseByteTab4(void)
+{
+l_int32   i;
+l_uint8  *tab;
+
+    PROCNAME("makeReverseByteTab4");
+
+    if ((tab = (l_uint8 *)LEPT_CALLOC(256, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+
+    for (i = 0; i < 256; i++)
+        tab[i] = ((0xf0 & i) >> 4) | ((0x0f & i) << 4);
+    return tab;
+}
diff --git a/src/rotateshear.c b/src/rotateshear.c
new file mode 100644 (file)
index 0000000..c31e592
--- /dev/null
@@ -0,0 +1,460 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  rotateshear.c
+ *
+ *      Shear rotation about arbitrary point using 2 and 3 shears
+ *
+ *              PIX      *pixRotateShear()
+ *              PIX      *pixRotate2Shear()
+ *              PIX      *pixRotate3Shear()
+ *
+ *      Shear rotation in-place about arbitrary point using 3 shears
+ *              l_int32   pixRotateShearIP()
+ *
+ *      Shear rotation around the image center
+ *              PIX      *pixRotateShearCenter()    (2 or 3 shears)
+ *              l_int32   pixRotateShearCenterIP()  (3 shears)
+ *
+ *  Rotation is measured in radians; clockwise rotations are positive.
+ *
+ *  Rotation by shear works on images of any depth,
+ *  including 8 bpp color paletted images and 32 bpp
+ *  rgb images.  It works by translating each src pixel
+ *  value to the appropriate pixel in the rotated dest.
+ *  For 8 bpp grayscale images, it is about 10-15x faster
+ *  than rotation by area-mapping.
+ *
+ *  This speed and flexibility comes at the following cost,
+ *  relative to area-mapped rotation:
+ *
+ *    -  Jaggies are created on edges of straight lines
+ *
+ *    -  For large angles, where you must use 3 shears,
+ *       there is some extra clipping from the shears.
+ *
+ *  For small angles, typically less than 0.05 radians,
+ *  rotation can be done with 2 orthogonal shears.
+ *  Two such continuous shears (as opposed to the discrete
+ *  shears on a pixel lattice that we have here) give
+ *  a rotated image that has a distortion in the lengths
+ *  of the two rotated and still-perpendicular axes.  The
+ *  length/width ratio changes by a fraction
+ *
+ *       0.5 * (angle)**2
+ *
+ *  For an angle of 0.05 radians, this is about 1 part in
+ *  a thousand.  This distortion is absent when you use
+ *  3 continuous shears with the correct angles (see below).
+ *
+ *  Of course, the image is on a discrete pixel lattice.
+ *  Rotation by shear gives an approximation to a continuous
+ *  rotation, leaving pixel jaggies at sharp boundaries.
+ *  For very small rotations, rotating from a corner gives
+ *  better sensitivity than rotating from the image center.
+ *  Here's why.  Define the shear "center" to be the line such
+ *  that the image is sheared in opposite directions on
+ *  each side of and parallel to the line.  For small
+ *  rotations there is a "dead space" on each side of the
+ *  shear center of width equal to half the shear angle,
+ *  in radians.  Thus, when the image is sheared about the center,
+ *  the dead space width equals the shear angle, but when
+ *  the image is sheared from a corner, the dead space
+ *  width is only half the shear angle.
+ *
+ *  All horizontal and vertical shears are implemented by
+ *  rasterop.  The in-place rotation uses special in-place
+ *  shears that copy rows sideways or columns vertically
+ *  without buffering, and then rewrite old pixels that are
+ *  no longer covered by sheared pixels.  For that rewriting,
+ *  you have the choice of using white or black pixels.
+ *  (Note that this may give undesirable results for colormapped
+ *  images, where the white and black values are arbitrary
+ *  indexes into the colormap, and may not even exist.)
+ *
+ *  Rotation by shear is fast and depth-independent.  However, it
+ *  does not work well for large rotation angles.  In fact, for
+ *  rotation angles greater than about 7 degrees, more pixels are
+ *  lost at the edges than when using pixRotationBySampling(), which
+ *  only loses pixels because they are rotated out of the image.
+ *  For larger rotations, use pixRotationBySampling() or, for
+ *  more accuracy when d > 1 bpp, pixRotateAM().
+ *
+ *  For small angles, when comparing the quality of rotation by
+ *  sampling and by shear, you can see that rotation by sampling
+ *  is slightly more accurate.  However, the difference in
+ *  accuracy of rotation by sampling when compared to 3-shear and
+ *  (for angles less than 2 degrees, when compared to 2-shear) is
+ *  less than 1 pixel at any point.  For very small angles, rotation by
+ *  sampling is much slower than rotation by shear.  The speed difference
+ *  depends on the pixel depth and the rotation angle.  Rotation
+ *  by shear is very fast for small angles and for small depth (esp. 1 bpp).
+ *  Rotation by sampling speed is independent of angle and relatively
+ *  more efficient for 8 and 32 bpp images.  Here are some timings
+ *  for the ratio of rotation times: (time for sampling)/ (time for shear)
+  *
+ *       depth (bpp)       ratio (2 deg)       ratio (10 deg)
+ *       -----------------------------------------------------
+ *          1                  25                  6
+ *          8                   5                  2.6
+ *          32                  1.6                1.0
+ *
+ *  In summary:
+ *    * For d == 1 and small angles, use rotation by shear.  By default
+ *      this will use 2-shear rotations, because 3-shears cause more
+ *      visible artifacts in straight lines and, for small angles, the
+ *      distortion in asperity ratio is small.
+ *    * For d > 1, shear is faster than sampling, which is faster than
+ *      area mapping.  However, area mapping gives the best results.
+ *  These results are used in selecting the rotation methods in
+ *  pixRotateShear().
+ *
+ *  There has been some work on what is called a "quasishear
+ *  rotation" ("The Quasi-Shear Rotation, Eric Andres,
+ *  DGCI 1996, pp. 307-314).  I believe they use a 3-shear
+ *  approximation to the continuous rotation, exactly as
+ *  we do here.  The approximation is due to being on
+ *  a square pixel lattice.  They also use integers to specify
+ *  the rotation angle and center offset, but that makes
+ *  little sense on a machine where you have a few GFLOPS
+ *  and only a few hundred floating point operations to do (!)
+ *  They also allow subpixel specification of the center of
+ *  rotation, which I haven't bothered with, and claim that
+ *  better results are possible if each of the 4 quadrants is
+ *  handled separately.
+ *
+ *  But the bottom line is that you are going to see shear lines when
+ *  you rotate 1 bpp images.  Although the 3-shear rotation is
+ *  mathematically exact in the limit of infinitesimal pixels, artifacts
+ *  will be evident in real images.  One might imagine using dithering
+ *  to break up the horizontal and vertical shear lines, but this
+ *  is hard with block shears, where you need to dither on the block
+ *  boundaries.  Dithering (by accumulation of 'error') with sampling
+ *  makes more sense, but I haven't tried to do this.  There is only
+ *  so much you can do with 1 bpp images!
+ */
+
+#include <math.h>
+#include <string.h>
+#include "allheaders.h"
+
+static const l_float32  MIN_ANGLE_TO_ROTATE = 0.001;  /* radians; ~0.06 deg */
+static const l_float32  MAX_2_SHEAR_ANGLE = 0.06;     /* radians; ~3 deg    */
+static const l_float32  LIMIT_SHEAR_ANGLE = 0.35;     /* radians; ~20 deg   */
+
+/*------------------------------------------------------------------*
+ *                Rotations about an arbitrary point                *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotateShear()
+ *
+ *      Input:  pixs
+ *              xcen (x value for which there is no horizontal shear)
+ *              ycen (y value for which there is no vertical shear)
+ *              angle (radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: pixd, or null on error.
+ *
+ *  Notes:
+ *      (1) This rotates an image about the given point, using
+ *          either 2 or 3 shears.
+ *      (2) A positive angle gives a clockwise rotation.
+ *      (3) This brings in 'incolor' pixels from outside the image.
+ *      (4) For rotation angles larger than about 0.35 radians, we issue
+ *          a warning because you should probably be using another method
+ *          (either sampling or area mapping)
+ */
+PIX *
+pixRotateShear(PIX       *pixs,
+               l_int32    xcen,
+               l_int32    ycen,
+               l_float32  angle,
+               l_int32    incolor)
+{
+    PROCNAME("pixRotateShear");
+
+    if (!pixs)
+        return (PIX *)(PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)(PIX *)ERROR_PTR("invalid incolor value", procName, NULL);
+
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+
+    if (L_ABS(angle) <= MAX_2_SHEAR_ANGLE)
+        return pixRotate2Shear(pixs, xcen, ycen, angle, incolor);
+
+    if (L_ABS(angle) > LIMIT_SHEAR_ANGLE)
+        L_WARNING("%6.2f radians; large angle for shear rotation\n",
+                  procName, L_ABS(angle));
+    return pixRotate3Shear(pixs, xcen, ycen, angle, incolor);
+}
+
+
+/*!
+ *  pixRotate2Shear()
+ *
+ *      Input:  pixs
+ *              xcen, ycen (center of rotation)
+ *              angle (radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: pixd, or null on error.
+ *
+ *  Notes:
+ *      (1) This rotates the image about the given point, using the 2-shear
+ *          method.  It should only be used for angles smaller than
+ *          MAX_2_SHEAR_ANGLE.  For larger angles, a warning is issued.
+ *      (2) A positive angle gives a clockwise rotation.
+ *      (3) 2-shear rotation by a specified angle is equivalent
+ *          to the sequential transformations
+ *             x' = x + tan(angle) * (y - ycen)     for x-shear
+ *             y' = y + tan(angle) * (x - xcen)     for y-shear
+ *      (4) Computation of tan(angle) is performed within the shear operation.
+ *      (5) This brings in 'incolor' pixels from outside the image.
+ *      (6) If the image has an alpha layer, it is rotated separately by
+ *          two shears.
+ */
+PIX *
+pixRotate2Shear(PIX       *pixs,
+                l_int32    xcen,
+                l_int32    ycen,
+                l_float32  angle,
+                l_int32    incolor)
+{
+PIX  *pix1, *pix2, *pixd;
+
+    PROCNAME("pixRotate2Shear");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)(PIX *)ERROR_PTR("invalid incolor value", procName, NULL);
+
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+    if (L_ABS(angle) > MAX_2_SHEAR_ANGLE)
+        L_WARNING("%6.2f radians; large angle for 2-shear rotation\n",
+                  procName, L_ABS(angle));
+
+    if ((pix1 = pixHShear(NULL, pixs, ycen, angle, incolor)) == NULL)
+        return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+    if ((pixd = pixVShear(NULL, pix1, xcen, angle, incolor)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixDestroy(&pix1);
+
+    if (pixGetDepth(pixs) == 32 && pixGetSpp(pixs) == 4) {
+        pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+            /* L_BRING_IN_WHITE brings in opaque for the alpha component */
+        pix2 = pixRotate2Shear(pix1, xcen, ycen, angle, L_BRING_IN_WHITE);
+        pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+    return pixd;
+}
+
+/*!
+ *  pixRotate3Shear()
+ *
+ *      Input:  pixs
+ *              xcen, ycen (center of rotation)
+ *              angle (radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: pixd, or null on error.
+ *
+ *  Notes:
+ *      (1) This rotates the image about the given point, using the 3-shear
+ *          method.  It should only be used for angles smaller than
+ *          LIMIT_SHEAR_ANGLE.  For larger angles, a warning is issued.
+ *      (2) A positive angle gives a clockwise rotation.
+ *      (3) 3-shear rotation by a specified angle is equivalent
+ *          to the sequential transformations
+ *            y' = y + tan(angle/2) * (x - xcen)     for first y-shear
+ *            x' = x + sin(angle) * (y - ycen)       for x-shear
+ *            y' = y + tan(angle/2) * (x - xcen)     for second y-shear
+ *      (4) Computation of tan(angle) is performed in the shear operations.
+ *      (5) This brings in 'incolor' pixels from outside the image.
+ *      (6) If the image has an alpha layer, it is rotated separately by
+ *          two shears.
+ *      (7) The algorithm was published by Alan Paeth: "A Fast Algorithm
+ *          for General Raster Rotation," Graphics Interface '86,
+ *          pp. 77-81, May 1986.  A description of the method, along with
+ *          an implementation, can be found in Graphics Gems, p. 179,
+ *          edited by Andrew Glassner, published by Academic Press, 1990.
+ */
+PIX *
+pixRotate3Shear(PIX       *pixs,
+                l_int32    xcen,
+                l_int32    ycen,
+                l_float32  angle,
+                l_int32    incolor)
+{
+l_float32  hangle;
+PIX       *pix1, *pix2, *pixd;
+
+    PROCNAME("pixRotate3Shear");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)(PIX *)ERROR_PTR("invalid incolor value", procName, NULL);
+
+    if (L_ABS(angle) < MIN_ANGLE_TO_ROTATE)
+        return pixClone(pixs);
+    if (L_ABS(angle) > LIMIT_SHEAR_ANGLE) {
+        L_WARNING("%6.2f radians; large angle for 3-shear rotation\n",
+                  procName, L_ABS(angle));
+    }
+
+    hangle = atan(sin(angle));
+    if ((pixd = pixVShear(NULL, pixs, xcen, angle / 2., incolor)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    if ((pix1 = pixHShear(NULL, pixd, ycen, hangle, incolor)) == NULL)
+        return (PIX *)ERROR_PTR("pix1 not made", procName, NULL);
+    pixVShear(pixd, pix1, xcen, angle / 2., incolor);
+    pixDestroy(&pix1);
+
+    if (pixGetDepth(pixs) == 32 && pixGetSpp(pixs) == 4) {
+        pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+            /* L_BRING_IN_WHITE brings in opaque for the alpha component */
+        pix2 = pixRotate3Shear(pix1, xcen, ycen, angle, L_BRING_IN_WHITE);
+        pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL);
+        pixDestroy(&pix1);
+        pixDestroy(&pix2);
+    }
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *             Rotations in-place about an arbitrary point          *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotateShearIP()
+ *
+ *      Input:  pixs (any depth; not colormapped)
+ *              xcen, ycen (center of rotation)
+ *              angle (radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This does an in-place rotation of the image about the
+ *          specified point, using the 3-shear method.  It should only
+ *          be used for angles smaller than LIMIT_SHEAR_ANGLE.
+ *          For larger angles, a warning is issued.
+ *      (2) A positive angle gives a clockwise rotation.
+ *      (3) 3-shear rotation by a specified angle is equivalent
+ *          to the sequential transformations
+ *            y' = y + tan(angle/2) * (x - xcen)      for first y-shear
+ *            x' = x + sin(angle) * (y - ycen)        for x-shear
+ *            y' = y + tan(angle/2) * (x - xcen)      for second y-shear
+ *      (4) Computation of tan(angle) is performed in the shear operations.
+ *      (5) This brings in 'incolor' pixels from outside the image.
+ *      (6) The pix cannot be colormapped, because the in-place operation
+ *          only blits in 0 or 1 bits, not an arbitrary colormap index.
+ */
+l_int32
+pixRotateShearIP(PIX       *pixs,
+                 l_int32    xcen,
+                 l_int32    ycen,
+                 l_float32  angle,
+                 l_int32    incolor)
+{
+l_float32  hangle;
+
+    PROCNAME("pixRotateShearIP");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return ERROR_INT("invalid value for incolor", procName, 1);
+    if (pixGetColormap(pixs) != NULL)
+        return ERROR_INT("pixs is colormapped", procName, 1);
+
+    if (angle == 0.0)
+        return 0;
+    if (L_ABS(angle) > LIMIT_SHEAR_ANGLE) {
+        L_WARNING("%6.2f radians; large angle for in-place 3-shear rotation\n",
+                  procName, L_ABS(angle));
+    }
+
+    hangle = atan(sin(angle));
+    pixHShearIP(pixs, ycen, angle / 2., incolor);
+    pixVShearIP(pixs, xcen, hangle, incolor);
+    pixHShearIP(pixs, ycen, angle / 2., incolor);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *                    Rotations about the image center              *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixRotateShearCenter()
+ *
+ *      Input:  pixs
+ *              angle (radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: pixd, or null on error
+ */
+PIX *
+pixRotateShearCenter(PIX       *pixs,
+                     l_float32  angle,
+                     l_int32    incolor)
+{
+    PROCNAME("pixRotateShearCenter");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+    return pixRotateShear(pixs, pixGetWidth(pixs) / 2,
+                          pixGetHeight(pixs) / 2, angle, incolor);
+}
+
+
+/*!
+ *  pixRotateShearCenterIP()
+ *
+ *      Input:  pixs
+ *              angle (radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixRotateShearCenterIP(PIX       *pixs,
+                       l_float32  angle,
+                       l_int32    incolor)
+{
+    PROCNAME("pixRotateShearCenterIP");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    return pixRotateShearIP(pixs, pixGetWidth(pixs) / 2,
+                            pixGetHeight(pixs) / 2, angle, incolor);
+}
diff --git a/src/runlength.c b/src/runlength.c
new file mode 100644 (file)
index 0000000..67ce1a6
--- /dev/null
@@ -0,0 +1,795 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  runlength.c
+ *
+ *     Label pixels by membership in runs
+ *           PIX         *pixStrokeWidthTransform()
+ *           static PIX  *pixFindMinRunsOrthogonal()
+ *           PIX         *pixRunlengthTransform()
+ *
+ *     Find runs along horizontal and vertical lines
+ *           l_int32      pixFindHorizontalRuns()
+ *           l_int32      pixFindVerticalRuns()
+ *
+ *     Find max runs along horizontal and vertical lines
+ *           l_int32      pixFindMaxRuns()
+ *           l_int32      pixFindMaxHorizontalRunOnLine()
+ *           l_int32      pixFindMaxVerticalRunOnLine()
+ *
+ *     Compute runlength-to-membership transform on a line
+ *           l_int32      runlengthMembershipOnLine()
+ *
+ *     Make byte position LUT
+ *           l_int32      makeMSBitLocTab()
+ *
+ *  Here we're handling runs of either black or white pixels on 1 bpp
+ *  images.  The directions of the runs in the stroke width transform
+ *  are selectable from given sets of angles.  Most of the other runs
+ *  are oriented either horizontally along the raster lines or
+ *  vertically along pixel columns.
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+static PIX *pixFindMinRunsOrthogonal(PIX *pixs, l_float32 angle, l_int32 depth);
+
+
+/*-----------------------------------------------------------------------*
+ *                   Label pixels by membership in runs                  *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixStrokeWidthTransform()
+ *
+ *      Input:   pixs (1 bpp)
+ *               color (0 for white runs, 1 for black runs)
+ *               depth (of pixd: 8 or 16 bpp)
+ *               nangles (2, 4, 6 or 8)
+ *      Return:  pixd (8 or 16 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) The dest Pix is 8 or 16 bpp, with the pixel values
+ *          equal to the stroke width in which it is a member.
+ *          The values are clipped to the max pixel value if necessary.
+ *      (2) The color determines if we're labelling white or black strokes.
+ *      (3) A pixel that is not a member of the chosen color gets
+ *          value 0; it belongs to a width of length 0 of the
+ *          chosen color.
+ *      (4) This chooses, for each dest pixel, the minimum of sets
+ *          of runlengths through each pixel.  Here are the sets:
+ *            nangles    increment          set
+ *            -------    ---------    --------------------------------
+ *               2          90       {0, 90}
+ *               4          45       {0, 45, 90, 135}
+ *               6          30       {0, 30, 60, 90, 120, 150}
+ *               8          22.5     {0, 22.5, 45, 67.5, 90, 112.5, 135, 157.5}
+ *      (5) Runtime scales linearly with (nangles - 2).
+ */
+PIX *
+pixStrokeWidthTransform(PIX     *pixs,
+                        l_int32  color,
+                        l_int32  depth,
+                        l_int32  nangles)
+{
+l_float32  angle, pi;
+PIX       *pixh, *pixv, *pixt, *pixg1, *pixg2, *pixg3, *pixg4;
+
+    PROCNAME("pixStrokeWidthTransform");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (depth != 8 && depth != 16)
+        return (PIX *)ERROR_PTR("depth must be 8 or 16 bpp", procName, NULL);
+    if (nangles != 2 && nangles != 4 && nangles != 6 && nangles != 8)
+        return (PIX *)ERROR_PTR("nangles not in {2,4,6,8}", procName, NULL);
+
+        /* Use fg runs for evaluation */
+    if (color == 0)
+        pixt = pixInvert(NULL, pixs);
+    else
+        pixt = pixClone(pixs);
+
+        /* Find min length at 0 and 90 degrees */
+    pixh = pixRunlengthTransform(pixt, 1, L_HORIZONTAL_RUNS, depth);
+    pixv = pixRunlengthTransform(pixt, 1, L_VERTICAL_RUNS, depth);
+    pixg1 = pixMinOrMax(NULL, pixh, pixv, L_CHOOSE_MIN);
+    pixDestroy(&pixh);
+    pixDestroy(&pixv);
+
+    pixg2 = pixg3 = pixg4 = NULL;
+    pi = 3.1415926535;
+    if (nangles == 4 || nangles == 8) {
+            /* Find min length at +45 and -45 degrees */
+        angle = pi / 4.0;
+        pixg2 = pixFindMinRunsOrthogonal(pixt, angle, depth);
+    }
+
+    if (nangles == 6) {
+            /* Find min length at +30 and -60 degrees */
+        angle = pi / 6.0;
+        pixg2 = pixFindMinRunsOrthogonal(pixt, angle, depth);
+
+            /* Find min length at +60 and -30 degrees */
+        angle = pi / 3.0;
+        pixg3 = pixFindMinRunsOrthogonal(pixt, angle, depth);
+    }
+
+    if (nangles == 8) {
+            /* Find min length at +22.5 and -67.5 degrees */
+        angle = pi / 8.0;
+        pixg3 = pixFindMinRunsOrthogonal(pixt, angle, depth);
+
+            /* Find min length at +67.5 and -22.5 degrees */
+        angle = 3.0 * pi / 8.0;
+        pixg4 = pixFindMinRunsOrthogonal(pixt, angle, depth);
+    }
+    pixDestroy(&pixt);
+
+    if (nangles > 2)
+        pixMinOrMax(pixg1, pixg1, pixg2, L_CHOOSE_MIN);
+    if (nangles > 4)
+        pixMinOrMax(pixg1, pixg1, pixg3, L_CHOOSE_MIN);
+    if (nangles > 6)
+        pixMinOrMax(pixg1, pixg1, pixg4, L_CHOOSE_MIN);
+    pixDestroy(&pixg2);
+    pixDestroy(&pixg3);
+    pixDestroy(&pixg4);
+    return pixg1;
+}
+
+
+/*!
+ *  pixFindMinRunsOrthogonal()
+ *
+ *      Input:   pixs (1 bpp)
+ *               angle (in radians)
+ *               depth (of pixd: 8 or 16 bpp)
+ *      Return:  pixd (8 or 16 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This computes, for each fg pixel in pixs, the minimum of
+ *          the runlengths going through that pixel in two orthogonal
+ *          directions: at @angle and at (90 + @angle).
+ *      (2) We use rotation by shear because the forward and backward
+ *          rotations by the same angle are exact inverse operations.
+ *          As a result, the nonzero pixels in pixd correspond exactly
+ *          to the fg pixels in pixs.  This is not the case with
+ *          sampled rotation, due to spatial quantization.  Nevertheless,
+ *          the result suffers from lack of exact correspondence
+ *          between original and rotated pixels, also due to spatial
+ *          quantization, causing some boundary pixels to be
+ *          shifted from bg to fg or v.v.
+ */
+static PIX *
+pixFindMinRunsOrthogonal(PIX       *pixs,
+                         l_float32  angle,
+                         l_int32    depth)
+{
+l_int32  w, h, diag, xoff, yoff;
+PIX     *pixb, *pixr, *pixh, *pixv, *pixg1, *pixg2, *pixd;
+BOX     *box;
+
+    PROCNAME("pixFindMinRunsOrthogonal");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+
+        /* Rasterop into the center of a sufficiently large image
+         * so we don't lose pixels for any rotation angle. */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    diag = (l_int32)(sqrt((l_float64)(w * w + h * h)) + 2.5);
+    xoff = (diag - w) / 2;
+    yoff = (diag - h) / 2;
+    pixb = pixCreate(diag, diag, 1);
+    pixRasterop(pixb, xoff, yoff, w, h, PIX_SRC, pixs, 0, 0);
+
+        /* Rotate about the 'center', get the min of orthogonal transforms,
+         * rotate back, and crop the part corresponding to pixs.  */
+    pixr = pixRotateShear(pixb, diag / 2, diag / 2, angle, L_BRING_IN_WHITE);
+    pixh = pixRunlengthTransform(pixr, 1, L_HORIZONTAL_RUNS, depth);
+    pixv = pixRunlengthTransform(pixr, 1, L_VERTICAL_RUNS, depth);
+    pixg1 = pixMinOrMax(NULL, pixh, pixv, L_CHOOSE_MIN);
+    pixg2 = pixRotateShear(pixg1, diag / 2, diag / 2, -angle, L_BRING_IN_WHITE);
+    box = boxCreate(xoff, yoff, w, h);
+    pixd = pixClipRectangle(pixg2, box, NULL);
+
+    pixDestroy(&pixb);
+    pixDestroy(&pixr);
+    pixDestroy(&pixh);
+    pixDestroy(&pixv);
+    pixDestroy(&pixg1);
+    pixDestroy(&pixg2);
+    boxDestroy(&box);
+    return pixd;
+}
+
+
+/*!
+ *  pixRunlengthTransform()
+ *
+ *      Input:   pixs (1 bpp)
+ *               color (0 for white runs, 1 for black runs)
+ *               direction (L_HORIZONTAL_RUNS, L_VERTICAL_RUNS)
+ *               depth (8 or 16 bpp)
+ *      Return:  pixd (8 or 16 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) The dest Pix is 8 or 16 bpp, with the pixel values
+ *          equal to the runlength in which it is a member.
+ *          The length is clipped to the max pixel value if necessary.
+ *      (2) The color determines if we're labelling white or black runs.
+ *      (3) A pixel that is not a member of the chosen color gets
+ *          value 0; it belongs to a run of length 0 of the
+ *          chosen color.
+ *      (4) To convert for maximum dynamic range, either linear or
+ *          log, use pixMaxDynamicRange().
+ */
+PIX *
+pixRunlengthTransform(PIX     *pixs,
+                      l_int32  color,
+                      l_int32  direction,
+                      l_int32  depth)
+{
+l_int32    i, j, w, h, wpld, bufsize, maxsize, n;
+l_int32   *start, *end, *buffer;
+l_uint32  *datad, *lined;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixRunlengthTransform");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (depth != 8 && depth != 16)
+        return (PIX *)ERROR_PTR("depth must be 8 or 16 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (direction == L_HORIZONTAL_RUNS)
+        maxsize = 1 + w / 2;
+    else if (direction == L_VERTICAL_RUNS)
+        maxsize = 1 + h / 2;
+    else
+        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
+    bufsize = L_MAX(w, h);
+
+    if ((pixd = pixCreate(w, h, depth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    if ((start = (l_int32 *)LEPT_CALLOC(maxsize, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("start not made", procName, NULL);
+    if ((end = (l_int32 *)LEPT_CALLOC(maxsize, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("end not made", procName, NULL);
+    if ((buffer = (l_int32 *)LEPT_CALLOC(bufsize, sizeof(l_int32))) == NULL)
+        return (PIX *)ERROR_PTR("buffer not made", procName, NULL);
+
+        /* Use fg runs for evaluation */
+    if (color == 0)
+        pixt = pixInvert(NULL, pixs);
+    else
+        pixt = pixClone(pixs);
+
+    if (direction == L_HORIZONTAL_RUNS) {
+        for (i = 0; i < h; i++) {
+            pixFindHorizontalRuns(pixt, i, start, end, &n);
+            runlengthMembershipOnLine(buffer, w, depth, start, end, n);
+            lined = datad + i * wpld;
+            if (depth == 8) {
+                for (j = 0; j < w; j++)
+                    SET_DATA_BYTE(lined, j, buffer[j]);
+            } else {  /* depth == 16 */
+                for (j = 0; j < w; j++)
+                    SET_DATA_TWO_BYTES(lined, j, buffer[j]);
+            }
+        }
+    } else {  /* L_VERTICAL_RUNS */
+        for (j = 0; j < w; j++) {
+            pixFindVerticalRuns(pixt, j, start, end, &n);
+            runlengthMembershipOnLine(buffer, h, depth, start, end, n);
+            if (depth == 8) {
+                for (i = 0; i < h; i++) {
+                    lined = datad + i * wpld;
+                    SET_DATA_BYTE(lined, j, buffer[i]);
+                }
+            } else {  /* depth == 16 */
+                for (i = 0; i < h; i++) {
+                    lined = datad + i * wpld;
+                    SET_DATA_TWO_BYTES(lined, j, buffer[i]);
+                }
+            }
+        }
+    }
+
+    pixDestroy(&pixt);
+    LEPT_FREE(start);
+    LEPT_FREE(end);
+    LEPT_FREE(buffer);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *               Find runs along horizontal and vertical lines           *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixFindHorizontalRuns()
+ *
+ *      Input:  pix (1 bpp)
+ *              y (line to traverse)
+ *              xstart (returns array of start positions for fg runs)
+ *              xend (returns array of end positions for fg runs)
+ *              &n  (<return> the number of runs found)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This finds foreground horizontal runs on a single scanline.
+ *      (2) To find background runs, use pixInvert() before applying
+ *          this function.
+ *      (3) The xstart and xend arrays are input.  They should be
+ *          of size w/2 + 1 to insure that they can hold
+ *          the maximum number of runs in the raster line.
+ */
+l_int32
+pixFindHorizontalRuns(PIX      *pix,
+                      l_int32   y,
+                      l_int32  *xstart,
+                      l_int32  *xend,
+                      l_int32  *pn)
+{
+l_int32    inrun;  /* boolean */
+l_int32    index, w, h, d, j, wpl, val;
+l_uint32  *line;
+
+    PROCNAME("pixFindHorizontalRuns");
+
+    if (!pn)
+        return ERROR_INT("&n not defined", procName, 1);
+    *pn = 0;
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 1)
+        return ERROR_INT("pix not 1 bpp", procName, 1);
+    if (y < 0 || y >= h)
+        return ERROR_INT("y not in [0 ... h - 1]", procName, 1);
+    if (!xstart)
+        return ERROR_INT("xstart not defined", procName, 1);
+    if (!xend)
+        return ERROR_INT("xend not defined", procName, 1);
+
+    wpl = pixGetWpl(pix);
+    line = pixGetData(pix) + y * wpl;
+
+    inrun = FALSE;
+    index = 0;
+    for (j = 0; j < w; j++) {
+        val = GET_DATA_BIT(line, j);
+        if (!inrun) {
+            if (val) {
+                xstart[index] = j;
+                inrun = TRUE;
+            }
+        } else {
+            if (!val) {
+                xend[index++] = j - 1;
+                inrun = FALSE;
+            }
+        }
+    }
+
+        /* Finish last run if necessary */
+    if (inrun)
+        xend[index++] = w - 1;
+
+    *pn = index;
+    return 0;
+}
+
+
+/*!
+ *  pixFindVerticalRuns()
+ *
+ *      Input:  pix (1 bpp)
+ *              x (line to traverse)
+ *              ystart (returns array of start positions for fg runs)
+ *              yend (returns array of end positions for fg runs)
+ *              &n   (<return> the number of runs found)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This finds foreground vertical runs on a single scanline.
+ *      (2) To find background runs, use pixInvert() before applying
+ *          this function.
+ *      (3) The ystart and yend arrays are input.  They should be
+ *          of size h/2 + 1 to insure that they can hold
+ *          the maximum number of runs in the raster line.
+ */
+l_int32
+pixFindVerticalRuns(PIX      *pix,
+                    l_int32   x,
+                    l_int32  *ystart,
+                    l_int32  *yend,
+                    l_int32  *pn)
+{
+l_int32    inrun;  /* boolean */
+l_int32    index, w, h, d, i, wpl, val;
+l_uint32  *data, *line;
+
+    PROCNAME("pixFindVerticalRuns");
+
+    if (!pn)
+        return ERROR_INT("&n not defined", procName, 1);
+    *pn = 0;
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    pixGetDimensions(pix, &w, &h, &d);
+    if (d != 1)
+        return ERROR_INT("pix not 1 bpp", procName, 1);
+    if (x < 0 || x >= w)
+        return ERROR_INT("x not in [0 ... w - 1]", procName, 1);
+    if (!ystart)
+        return ERROR_INT("ystart not defined", procName, 1);
+    if (!yend)
+        return ERROR_INT("yend not defined", procName, 1);
+
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+
+    inrun = FALSE;
+    index = 0;
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        val = GET_DATA_BIT(line, x);
+        if (!inrun) {
+            if (val) {
+                ystart[index] = i;
+                inrun = TRUE;
+            }
+        } else {
+            if (!val) {
+                yend[index++] = i - 1;
+                inrun = FALSE;
+            }
+        }
+    }
+
+        /* Finish last run if necessary */
+    if (inrun)
+        yend[index++] = h - 1;
+
+    *pn = index;
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *            Find max runs along horizontal and vertical lines          *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixFindMaxRuns()
+ *
+ *      Input:  pix (1 bpp)
+ *              direction (L_HORIZONTAL_RUNS or L_VERTICAL_RUNS)
+ *              &nastart (<optional return> start locations of longest runs)
+ *      Return: na (of lengths of runs), or null on error
+ *
+ *  Notes:
+ *      (1) This finds the longest foreground runs by row or column
+ *      (2) To find background runs, use pixInvert() before applying
+ *          this function.
+ */
+NUMA *
+pixFindMaxRuns(PIX     *pix,
+               l_int32  direction,
+               NUMA   **pnastart)
+{
+l_int32  w, h, i, start, size;
+NUMA    *nasize;
+
+    PROCNAME("pixFindMaxRuns");
+
+    if (pnastart) *pnastart = NULL;
+    if (direction != L_HORIZONTAL_RUNS && direction != L_VERTICAL_RUNS)
+        return (NUMA *)ERROR_PTR("direction invalid", procName, NULL);
+    if (!pix || pixGetDepth(pix) != 1)
+        return (NUMA *)ERROR_PTR("pix undefined or not 1 bpp", procName, NULL);
+
+    pixGetDimensions(pix, &w, &h, NULL);
+    nasize = numaCreate(w);
+    if (pnastart) *pnastart = numaCreate(w);
+    if (direction == L_HORIZONTAL_RUNS) {
+        for (i = 0; i < h; i++) {
+            pixFindMaxHorizontalRunOnLine(pix, i, &start, &size);
+            numaAddNumber(nasize, size);
+            if (pnastart) numaAddNumber(*pnastart, start);
+        }
+    } else {  /* vertical scans */
+        for (i = 0; i < w; i++) {
+            pixFindMaxVerticalRunOnLine(pix, i, &start, &size);
+            numaAddNumber(nasize, size);
+            if (pnastart) numaAddNumber(*pnastart, start);
+        }
+    }
+
+    return nasize;
+}
+
+
+/*!
+ *  pixFindMaxHorizontalRunOnLine()
+ *
+ *      Input:  pix (1 bpp)
+ *              y (line to traverse)
+ *              &xstart (<optional return> start position)
+ *              &size  (<return> the size of the run)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This finds the longest foreground horizontal run on a scanline.
+ *      (2) To find background runs, use pixInvert() before applying
+ *          this function.
+ */
+l_int32
+pixFindMaxHorizontalRunOnLine(PIX      *pix,
+                              l_int32   y,
+                              l_int32  *pxstart,
+                              l_int32  *psize)
+{
+l_int32    inrun;  /* boolean */
+l_int32    w, h, j, wpl, val, maxstart, maxsize, length, start;
+l_uint32  *line;
+
+    PROCNAME("pixFindMaxHorizontalRunOnLine");
+
+    if (pxstart) *pxstart = 0;
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1);
+    *psize = 0;
+    if (!pix || pixGetDepth(pix) != 1)
+        return ERROR_INT("pix not defined or not 1 bpp", procName, 1);
+    pixGetDimensions(pix, &w, &h, NULL);
+    if (y < 0 || y >= h)
+        return ERROR_INT("y not in [0 ... h - 1]", procName, 1);
+
+    wpl = pixGetWpl(pix);
+    line = pixGetData(pix) + y * wpl;
+    inrun = FALSE;
+    start = 0;
+    maxstart = 0;
+    maxsize = 0;
+    for (j = 0; j < w; j++) {
+        val = GET_DATA_BIT(line, j);
+        if (!inrun) {
+            if (val) {
+                start = j;
+                inrun = TRUE;
+            }
+        } else if (!val) {  /* run just ended */
+            length = j - start;
+            if (length > maxsize) {
+                maxsize = length;
+                maxstart = start;
+            }
+            inrun = FALSE;
+        }
+    }
+
+    if (inrun) {  /* a run has continued to the end of the row */
+        length = j - start;
+        if (length > maxsize) {
+            maxsize = length;
+            maxstart = start;
+        }
+    }
+    if (pxstart) *pxstart = maxstart;
+    *psize = maxsize;
+    return 0;
+}
+
+
+/*!
+ *  pixFindMaxVerticalRunOnLine()
+ *
+ *      Input:  pix (1 bpp)
+ *              x (column to traverse)
+ *              &ystart (<optional return> start position)
+ *              &size  (<return> the size of the run)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This finds the longest foreground vertical run on a scanline.
+ *      (2) To find background runs, use pixInvert() before applying
+ *          this function.
+ */
+l_int32
+pixFindMaxVerticalRunOnLine(PIX      *pix,
+                            l_int32   x,
+                            l_int32  *pystart,
+                            l_int32  *psize)
+{
+l_int32    inrun;  /* boolean */
+l_int32    w, h, i, wpl, val, maxstart, maxsize, length, start;
+l_uint32  *data, *line;
+
+    PROCNAME("pixFindMaxVerticalRunOnLine");
+
+    if (pystart) *pystart = 0;
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1);
+    *psize = 0;
+    if (!pix || pixGetDepth(pix) != 1)
+        return ERROR_INT("pix not defined or not 1 bpp", procName, 1);
+    pixGetDimensions(pix, &w, &h, NULL);
+    if (x < 0 || x >= w)
+        return ERROR_INT("x not in [0 ... w - 1]", procName, 1);
+
+    wpl = pixGetWpl(pix);
+    data = pixGetData(pix);
+    inrun = FALSE;
+    start = 0;
+    maxstart = 0;
+    maxsize = 0;
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        val = GET_DATA_BIT(line, x);
+        if (!inrun) {
+            if (val) {
+                start = i;
+                inrun = TRUE;
+            }
+        } else if (!val) {  /* run just ended */
+            length = i - start;
+            if (length > maxsize) {
+                maxsize = length;
+                maxstart = start;
+            }
+            inrun = FALSE;
+        }
+    }
+
+    if (inrun) {  /* a run has continued to the end of the column */
+        length = i - start;
+        if (length > maxsize) {
+            maxsize = length;
+            maxstart = start;
+        }
+    }
+    if (pystart) *pystart = maxstart;
+    *psize = maxsize;
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *            Compute runlength-to-membership transform on a line        *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  runlengthMembershipOnLine()
+ *
+ *      Input:   buffer (into which full line of data is placed)
+ *               size (full size of line; w or h)
+ *               depth (8 or 16 bpp)
+ *               start (array of start positions for fg runs)
+ *               end (array of end positions for fg runs)
+ *               n   (the number of runs)
+ *      Return:  0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Converts a set of runlengths into a buffer of
+ *          runlength membership values.
+ *      (2) Initialization of the array gives pixels that are
+ *          not within a run the value 0.
+ */
+l_int32
+runlengthMembershipOnLine(l_int32  *buffer,
+                          l_int32   size,
+                          l_int32   depth,
+                          l_int32  *start,
+                          l_int32  *end,
+                          l_int32   n)
+{
+l_int32  i, j, first, last, diff, max;
+
+    PROCNAME("runlengthMembershipOnLine");
+
+    if (!buffer)
+        return ERROR_INT("buffer not defined", procName, 1);
+    if (!start)
+        return ERROR_INT("start not defined", procName, 1);
+    if (!end)
+        return ERROR_INT("end not defined", procName, 1);
+
+    if (depth == 8)
+        max = 0xff;
+    else  /* depth == 16 */
+        max = 0xffff;
+
+    memset(buffer, 0, 4 * size);
+    for (i = 0; i < n; i++) {
+        first = start[i];
+        last = end[i];
+        diff = last - first + 1;
+        diff = L_MIN(diff, max);
+        for (j = first; j <= last; j++)
+            buffer[j] = diff;
+    }
+
+    return 0;
+}
+
+
+
+/*-----------------------------------------------------------------------*
+ *                       Make byte position LUT                          *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  makeMSBitLocTab()
+ *
+ *      Input:  bitval (either 0 or 1)
+ *      Return: table (giving, for an input byte, the MS bit location,
+ *                     starting at 0 with the MSBit in the byte),
+ *                     or null on error.
+ *
+ *  Notes:
+ *      (1) If bitval == 1, it finds the leftmost ON pixel in a byte;
+ *          otherwise if bitval == 0, it finds the leftmost OFF pixel.
+ *      (2) If there are no pixels of the indicated color in the byte,
+ *          this returns 8.
+ */
+l_int32 *
+makeMSBitLocTab(l_int32  bitval)
+{
+l_int32   i, j;
+l_int32  *tab;
+l_uint8   byte, mask;
+
+    PROCNAME("makeMSBitLocTab");
+
+    if ((tab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32))) == NULL)
+        return (l_int32 *)ERROR_PTR("tab not made", procName, NULL);
+
+    for (i = 0; i < 256; i++) {
+        byte = (l_uint8)i;
+        if (bitval == 0)
+            byte = ~byte;
+        tab[i] = 8;
+        mask = 0x80;
+        for (j = 0; j < 8; j++) {
+            if (byte & mask) {
+                tab[i] = j;
+                break;
+            }
+            mask >>= 1;
+        }
+    }
+
+    return tab;
+}
diff --git a/src/sarray.c b/src/sarray.c
new file mode 100644 (file)
index 0000000..f6d139e
--- /dev/null
@@ -0,0 +1,2372 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *   sarray.c
+ *
+ *      Create/Destroy/Copy
+ *          SARRAY    *sarrayCreate()
+ *          SARRAY    *sarrayCreateInitialized()
+ *          SARRAY    *sarrayCreateWordsFromString()
+ *          SARRAY    *sarrayCreateLinesFromString()
+ *          void      *sarrayDestroy()
+ *          SARRAY    *sarrayCopy()
+ *          SARRAY    *sarrayClone()
+ *
+ *      Add/Remove string
+ *          l_int32    sarrayAddString()
+ *          static l_int32  sarrayExtendArray()
+ *          char      *sarrayRemoveString()
+ *          l_int32    sarrayReplaceString()
+ *          l_int32    sarrayClear()
+ *
+ *      Accessors
+ *          l_int32    sarrayGetCount()
+ *          char     **sarrayGetArray()
+ *          char      *sarrayGetString()
+ *          l_int32    sarrayGetRefcount()
+ *          l_int32    sarrayChangeRefcount()
+ *
+ *      Conversion back to string
+ *          char      *sarrayToString()
+ *          char      *sarrayToStringRange()
+ *
+ *      Join 2 sarrays
+ *          l_int32    sarrayJoin()
+ *          l_int32    sarrayAppendRange()
+ *
+ *      Pad an sarray to be the same size as another sarray
+ *          l_int32    sarrayPadToSameSize()
+ *
+ *      Convert word sarray to (formatted) line sarray
+ *          SARRAY    *sarrayConvertWordsToLines()
+ *
+ *      Split string on separator list
+ *          SARRAY    *sarraySplitString()
+ *
+ *      Filter sarray
+ *          SARRAY    *sarraySelectBySubstring()
+ *          SARRAY    *sarraySelectByRange()
+ *          l_int32    sarrayParseRange()
+ *
+ *      Sort
+ *          SARRAY    *sarraySort()
+ *          SARRAY    *sarraySortByIndex()
+ *          l_int32    stringCompareLexical()
+ *
+ *      Operations by aset (rbtree)
+ *          SARRAY    *sarrayUnionByAset()
+ *          SARRAY    *sarrayRemoveDupsByAset()
+ *          SARRAY    *sarrayIntersectionByAset()
+ *          L_ASET    *l_asetCreateFromSarray()
+ *
+ *      Operations by hashmap (dnahash)
+ *          l_int32    sarrayRemoveDupsByHash()
+ *          SARRAY     *sarrayIntersectionByHash()
+ *          l_int32     sarrayFindStringByHash()
+ *          L_DNAHASH  *l_dnaHashCreateFromSarray()
+ *
+ *      Serialize for I/O
+ *          SARRAY    *sarrayRead()
+ *          SARRAY    *sarrayReadStream()
+ *          l_int32    sarrayWrite()
+ *          l_int32    sarrayWriteStream()
+ *          l_int32    sarrayAppend()
+ *
+ *      Directory filenames
+ *          SARRAY    *getNumberedPathnamesInDirectory()
+ *          SARRAY    *getSortedPathnamesInDirectory()
+ *          SARRAY    *convertSortedToNumberedPathnames()
+ *          SARRAY    *getFilenamesInDirectory()
+ *
+ *      These functions are important for efficient manipulation
+ *      of string data, and they have found widespread use in
+ *      leptonica.  For example:
+ *         (1) to generate text files: e.g., PostScript and PDF
+ *             wrappers around sets of images
+ *         (2) to parse text files: e.g., extracting prototypes
+ *             from the source to generate allheaders.h
+ *         (3) to generate code for compilation: e.g., the fast
+ *             dwa code for arbitrary structuring elements.
+ *
+ *      Comments on usage:
+ *
+ *          The user is responsible for correctly disposing of strings
+ *          that have been extracted from sarrays:
+ *            - When you want a string from an Sarray to inspect it, or
+ *              plan to make a copy of it later, use sarrayGetString()
+ *              with copyflag = 0.  In this case, you must neither free
+ *              the string nor put it directly in another array.
+ *              We provide the copyflag constant L_NOCOPY, which is 0,
+ *              for this purpose:
+ *                 str-not-owned = sarrayGetString(sa, index, L_NOCOPY);
+ *              To extract a copy of a string, use:
+ *                 str-owned = sarrayGetString(sa, index, L_COPY);
+ *
+ *            - When you want to insert a string that is in one
+ *              array into another array (always leaving the first
+ *              array intact), you have two options:
+ *                 (1) use copyflag = L_COPY to make an immediate copy,
+ *                     which you must then add to the second array
+ *                     by insertion; namely,
+ *                       str-owned = sarrayGetString(sa, index, L_COPY);
+ *                       sarrayAddString(sa, str-owned, L_INSERT);
+ *                 (2) use copyflag = L_NOCOPY to get another handle to
+ *                     the string, in which case you must add
+ *                     a copy of it to the second string array:
+ *                       str-not-owned = sarrayGetString(sa, index, L_NOCOPY);
+ *                       sarrayAddString(sa, str-not-owned, L_COPY).
+ *
+ *              In all cases, when you use copyflag = L_COPY to extract
+ *              a string from an array, you must either free it
+ *              or insert it in an array that will be freed later.
+ */
+
+#include <string.h>
+#ifndef _WIN32
+#include <dirent.h>     /* unix only */
+#endif  /* ! _WIN32 */
+#include "allheaders.h"
+
+static const l_int32  INITIAL_PTR_ARRAYSIZE = 50;     /* n'importe quoi */
+static const l_int32  L_BUF_SIZE = 512;
+
+    /* Static functions */
+static l_int32 sarrayExtendArray(SARRAY *sa);
+
+
+/*--------------------------------------------------------------------------*
+ *                   String array create/destroy/copy/extend                *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  sarrayCreate()
+ *
+ *      Input:  size of string ptr array to be alloc'd
+ *              (use 0 for default)
+ *      Return: sarray, or null on error
+ */
+SARRAY *
+sarrayCreate(l_int32  n)
+{
+SARRAY  *sa;
+
+    PROCNAME("sarrayCreate");
+
+    if (n <= 0)
+        n = INITIAL_PTR_ARRAYSIZE;
+
+    if ((sa = (SARRAY *)LEPT_CALLOC(1, sizeof(SARRAY))) == NULL)
+        return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+    if ((sa->array = (char **)LEPT_CALLOC(n, sizeof(char *))) == NULL)
+        return (SARRAY *)ERROR_PTR("ptr array not made", procName, NULL);
+
+    sa->nalloc = n;
+    sa->n = 0;
+    sa->refcount = 1;
+    return sa;
+}
+
+
+/*!
+ *  sarrayCreateInitialized()
+ *
+ *      Input:  n (size of string ptr array to be alloc'd)
+ *              initstr (string to be initialized on the full array)
+ *      Return: sarray, or null on error
+ */
+SARRAY *
+sarrayCreateInitialized(l_int32  n,
+                        char    *initstr)
+{
+l_int32  i;
+SARRAY  *sa;
+
+    PROCNAME("sarrayCreateInitialized");
+
+    if (n <= 0)
+        return (SARRAY *)ERROR_PTR("n must be > 0", procName, NULL);
+    if (!initstr)
+        return (SARRAY *)ERROR_PTR("initstr not defined", procName, NULL);
+
+    sa = sarrayCreate(n);
+    for (i = 0; i < n; i++)
+        sarrayAddString(sa, initstr, L_COPY);
+    return sa;
+}
+
+
+/*!
+ *  sarrayCreateWordsFromString()
+ *
+ *      Input:  string
+ *      Return: sarray, or null on error
+ *
+ *  Notes:
+ *      (1) This finds the number of word substrings, creates an sarray
+ *          of this size, and puts copies of each substring into the sarray.
+ */
+SARRAY *
+sarrayCreateWordsFromString(const char  *string)
+{
+char     separators[] = " \n\t";
+l_int32  i, nsub, size, inword;
+SARRAY  *sa;
+
+    PROCNAME("sarrayCreateWordsFromString");
+
+    if (!string)
+        return (SARRAY *)ERROR_PTR("textstr not defined", procName, NULL);
+
+        /* Find the number of words */
+    size = strlen(string);
+    nsub = 0;
+    inword = FALSE;
+    for (i = 0; i < size; i++) {
+        if (inword == FALSE &&
+           (string[i] != ' ' && string[i] != '\t' && string[i] != '\n')) {
+           inword = TRUE;
+           nsub++;
+        } else if (inword == TRUE &&
+           (string[i] == ' ' || string[i] == '\t' || string[i] == '\n')) {
+           inword = FALSE;
+        }
+    }
+
+    if ((sa = sarrayCreate(nsub)) == NULL)
+        return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+    sarraySplitString(sa, string, separators);
+
+    return sa;
+}
+
+
+/*!
+ *  sarrayCreateLinesFromString()
+ *
+ *      Input:  string
+ *              blankflag  (0 to exclude blank lines; 1 to include)
+ *      Return: sarray, or null on error
+ *
+ *  Notes:
+ *      (1) This finds the number of line substrings, each of which
+ *          ends with a newline, and puts a copy of each substring
+ *          in a new sarray.
+ *      (2) The newline characters are removed from each substring.
+ */
+SARRAY *
+sarrayCreateLinesFromString(const char  *string,
+                            l_int32      blankflag)
+{
+l_int32  i, nsub, size, startptr;
+char    *cstring, *substring;
+SARRAY  *sa;
+
+    PROCNAME("sarrayCreateLinesFromString");
+
+    if (!string)
+        return (SARRAY *)ERROR_PTR("textstr not defined", procName, NULL);
+
+        /* find the number of lines */
+    size = strlen(string);
+    nsub = 0;
+    for (i = 0; i < size; i++) {
+        if (string[i] == '\n')
+            nsub++;
+    }
+
+    if ((sa = sarrayCreate(nsub)) == NULL)
+        return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+
+    if (blankflag) {  /* keep blank lines as null strings */
+            /* Make a copy for munging */
+        if ((cstring = stringNew(string)) == NULL)
+            return (SARRAY *)ERROR_PTR("cstring not made", procName, NULL);
+            /* We'll insert nulls like strtok */
+        startptr = 0;
+        for (i = 0; i < size; i++) {
+            if (cstring[i] == '\n') {
+                cstring[i] = '\0';
+                if (i > 0 && cstring[i - 1] == '\r')
+                    cstring[i - 1] = '\0';  /* also remove Windows CR */
+                if ((substring = stringNew(cstring + startptr)) == NULL)
+                    return (SARRAY *)ERROR_PTR("substring not made",
+                                                procName, NULL);
+                sarrayAddString(sa, substring, L_INSERT);
+/*                fprintf(stderr, "substring = %s\n", substring); */
+                startptr = i + 1;
+            }
+        }
+        if (startptr < size) {  /* no newline at end of last line */
+            if ((substring = stringNew(cstring + startptr)) == NULL)
+                return (SARRAY *)ERROR_PTR("substring not made",
+                                            procName, NULL);
+            sarrayAddString(sa, substring, L_INSERT);
+/*            fprintf(stderr, "substring = %s\n", substring); */
+        }
+        LEPT_FREE(cstring);
+    } else {  /* remove blank lines; use strtok */
+        sarraySplitString(sa, string, "\r\n");
+    }
+
+    return sa;
+}
+
+
+/*!
+ *  sarrayDestroy()
+ *
+ *      Input:  &sarray <to be nulled>
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Decrements the ref count and, if 0, destroys the sarray.
+ *      (2) Always nulls the input ptr.
+ */
+void
+sarrayDestroy(SARRAY  **psa)
+{
+l_int32  i;
+SARRAY  *sa;
+
+    PROCNAME("sarrayDestroy");
+
+    if (psa == NULL) {
+        L_WARNING("ptr address is NULL!\n", procName);
+        return;
+    }
+    if ((sa = *psa) == NULL)
+        return;
+
+    sarrayChangeRefcount(sa, -1);
+    if (sarrayGetRefcount(sa) <= 0) {
+        if (sa->array) {
+            for (i = 0; i < sa->n; i++) {
+                if (sa->array[i])
+                    LEPT_FREE(sa->array[i]);
+            }
+            LEPT_FREE(sa->array);
+        }
+        LEPT_FREE(sa);
+    }
+
+    *psa = NULL;
+    return;
+}
+
+
+/*!
+ *  sarrayCopy()
+ *
+ *      Input:  sarray
+ *      Return: copy of sarray, or null on error
+ */
+SARRAY *
+sarrayCopy(SARRAY  *sa)
+{
+l_int32  i;
+SARRAY  *csa;
+
+    PROCNAME("sarrayCopy");
+
+    if (!sa)
+        return (SARRAY *)ERROR_PTR("sa not defined", procName, NULL);
+
+    if ((csa = sarrayCreate(sa->nalloc)) == NULL)
+        return (SARRAY *)ERROR_PTR("csa not made", procName, NULL);
+
+    for (i = 0; i < sa->n; i++)
+        sarrayAddString(csa, sa->array[i], L_COPY);
+
+    return csa;
+}
+
+
+/*!
+ *  sarrayClone()
+ *
+ *      Input:  sarray
+ *      Return: ptr to same sarray, or null on error
+ */
+SARRAY *
+sarrayClone(SARRAY  *sa)
+{
+    PROCNAME("sarrayClone");
+
+    if (!sa)
+        return (SARRAY *)ERROR_PTR("sa not defined", procName, NULL);
+    sarrayChangeRefcount(sa, 1);
+    return sa;
+}
+
+
+/*!
+ *  sarrayAddString()
+ *
+ *      Input:  sarray
+ *              string  (string to be added)
+ *              copyflag (L_INSERT, L_COPY)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Legacy usage decrees that we always use 0 to insert a string
+ *          directly and 1 to insert a copy of the string.  The
+ *          enums for L_INSERT and L_COPY agree with this convention,
+ *          and will not change in the future.
+ *      (2) See usage comments at the top of this file.
+ */
+l_int32
+sarrayAddString(SARRAY  *sa,
+                char    *string,
+                l_int32  copyflag)
+{
+l_int32  n;
+
+    PROCNAME("sarrayAddString");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!string)
+        return ERROR_INT("string not defined", procName, 1);
+    if (copyflag != L_INSERT && copyflag != L_COPY)
+        return ERROR_INT("invalid copyflag", procName, 1);
+
+    n = sarrayGetCount(sa);
+    if (n >= sa->nalloc)
+        sarrayExtendArray(sa);
+
+    if (copyflag == L_INSERT)
+        sa->array[n] = string;
+    else  /* L_COPY */
+        sa->array[n] = stringNew(string);
+    sa->n++;
+
+    return 0;
+}
+
+
+/*!
+ *  sarrayExtendArray()
+ *
+ *      Input:  sarray
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+sarrayExtendArray(SARRAY  *sa)
+{
+    PROCNAME("sarrayExtendArray");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+
+    if ((sa->array = (char **)reallocNew((void **)&sa->array,
+                              sizeof(char *) * sa->nalloc,
+                              2 * sizeof(char *) * sa->nalloc)) == NULL)
+            return ERROR_INT("new ptr array not returned", procName, 1);
+
+    sa->nalloc *= 2;
+    return 0;
+}
+
+
+/*!
+ *  sarrayRemoveString()
+ *
+ *      Input:  sarray
+ *              index (of string within sarray)
+ *      Return: removed string, or null on error
+ */
+char *
+sarrayRemoveString(SARRAY  *sa,
+                   l_int32  index)
+{
+char    *string;
+char   **array;
+l_int32  i, n, nalloc;
+
+    PROCNAME("sarrayRemoveString");
+
+    if (!sa)
+        return (char *)ERROR_PTR("sa not defined", procName, NULL);
+
+    if ((array = sarrayGetArray(sa, &nalloc, &n)) == NULL)
+        return (char *)ERROR_PTR("array not returned", procName, NULL);
+
+    if (index < 0 || index >= n)
+        return (char *)ERROR_PTR("array index out of bounds", procName, NULL);
+
+    string = array[index];
+
+        /* If removed string is not at end of array, shift
+         * to fill in, maintaining original ordering.
+         * Note: if we didn't care about the order, we could
+         * put the last string array[n - 1] directly into the hole.  */
+    for (i = index; i < n - 1; i++)
+        array[i] = array[i + 1];
+
+    sa->n--;
+    return string;
+}
+
+
+/*!
+ *  sarrayReplaceString()
+ *
+ *      Input:  sarray
+ *              index (of string within sarray to be replaced)
+ *              newstr (string to replace existing one)
+ *              copyflag (L_INSERT, L_COPY)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This destroys an existing string and replaces it with
+ *          the new string or a copy of it.
+ *      (2) By design, an sarray is always compacted, so there are
+ *          never any holes (null ptrs) in the ptr array up to the
+ *          current count.
+ */
+l_int32
+sarrayReplaceString(SARRAY  *sa,
+                    l_int32  index,
+                    char    *newstr,
+                    l_int32  copyflag)
+{
+char    *str;
+l_int32  n;
+
+    PROCNAME("sarrayReplaceString");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    n = sarrayGetCount(sa);
+    if (index < 0 || index >= n)
+        return ERROR_INT("array index out of bounds", procName, 1);
+    if (!newstr)
+        return ERROR_INT("newstr not defined", procName, 1);
+    if (copyflag != L_INSERT && copyflag != L_COPY)
+        return ERROR_INT("invalid copyflag", procName, 1);
+
+    LEPT_FREE(sa->array[index]);
+    if (copyflag == L_INSERT)
+        str = newstr;
+    else  /* L_COPY */
+        str = stringNew(newstr);
+    sa->array[index] = str;
+    return 0;
+}
+
+
+/*!
+ *  sarrayClear()
+ *
+ *      Input:  sarray
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+sarrayClear(SARRAY  *sa)
+{
+l_int32  i;
+
+    PROCNAME("sarrayClear");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    for (i = 0; i < sa->n; i++) {  /* free strings and null ptrs */
+        LEPT_FREE(sa->array[i]);
+        sa->array[i] = NULL;
+    }
+    sa->n = 0;
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                               Accessors                              *
+ *----------------------------------------------------------------------*/
+/*!
+ *  sarrayGetCount()
+ *
+ *      Input:  sarray
+ *      Return: count, or 0 if no strings or on error
+ */
+l_int32
+sarrayGetCount(SARRAY  *sa)
+{
+    PROCNAME("sarrayGetCount");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 0);
+    return sa->n;
+}
+
+
+/*!
+ *  sarrayGetArray()
+ *
+ *      Input:  sarray
+ *              &nalloc  (<optional return> number allocated string ptrs)
+ *              &n  (<optional return> number allocated strings)
+ *      Return: ptr to string array, or null on error
+ *
+ *  Notes:
+ *      (1) Caution: the returned array is not a copy, so caller
+ *          must not destroy it!
+ */
+char **
+sarrayGetArray(SARRAY   *sa,
+               l_int32  *pnalloc,
+               l_int32  *pn)
+{
+char  **array;
+
+    PROCNAME("sarrayGetArray");
+
+    if (!sa)
+        return (char **)ERROR_PTR("sa not defined", procName, NULL);
+
+    array = sa->array;
+    if (pnalloc) *pnalloc = sa->nalloc;
+    if (pn) *pn = sa->n;
+
+    return array;
+}
+
+
+/*!
+ *  sarrayGetString()
+ *
+ *      Input:  sarray
+ *              index   (to the index-th string)
+ *              copyflag  (L_NOCOPY or L_COPY)
+ *      Return: string, or null on error
+ *
+ *  Notes:
+ *      (1) Legacy usage decrees that we always use 0 to get the
+ *          pointer to the string itself, and 1 to get a copy of
+ *          the string.
+ *      (2) See usage comments at the top of this file.
+ *      (3) To get a pointer to the string itself, use for copyflag:
+ *             L_NOCOPY or 0 or FALSE
+ *          To get a copy of the string, use for copyflag:
+ *             L_COPY or 1 or TRUE
+ *          The const values of L_NOCOPY and L_COPY are guaranteed not
+ *          to change.
+ */
+char *
+sarrayGetString(SARRAY  *sa,
+                l_int32  index,
+                l_int32  copyflag)
+{
+    PROCNAME("sarrayGetString");
+
+    if (!sa)
+        return (char *)ERROR_PTR("sa not defined", procName, NULL);
+    if (index < 0 || index >= sa->n)
+        return (char *)ERROR_PTR("index not valid", procName, NULL);
+    if (copyflag != L_NOCOPY && copyflag != L_COPY)
+        return (char *)ERROR_PTR("invalid copyflag", procName, NULL);
+
+    if (copyflag == L_NOCOPY)
+        return sa->array[index];
+    else  /* L_COPY */
+        return stringNew(sa->array[index]);
+}
+
+
+/*!
+ *  sarrayGetRefCount()
+ *
+ *      Input:  sarray
+ *      Return: refcount, or UNDEF on error
+ */
+l_int32
+sarrayGetRefcount(SARRAY  *sa)
+{
+    PROCNAME("sarrayGetRefcount");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, UNDEF);
+    return sa->refcount;
+}
+
+
+/*!
+ *  sarrayChangeRefCount()
+ *
+ *      Input:  sarray
+ *              delta (change to be applied)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+sarrayChangeRefcount(SARRAY  *sa,
+                     l_int32  delta)
+{
+    PROCNAME("sarrayChangeRefcount");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, UNDEF);
+    sa->refcount += delta;
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                      Conversion to string                           *
+ *----------------------------------------------------------------------*/
+/*!
+ *  sarrayToString()
+ *
+ *      Input:  sarray
+ *              addnlflag (flag: 0 adds nothing to each substring
+ *                               1 adds '\n' to each substring
+ *                               2 adds ' ' to each substring)
+ *      Return: dest string, or null on error
+ *
+ *  Notes:
+ *      (1) Concatenates all the strings in the sarray, preserving
+ *          all white space.
+ *      (2) If addnlflag != 0, adds either a '\n' or a ' ' after
+ *          each substring.
+ *      (3) This function was NOT implemented as:
+ *            for (i = 0; i < n; i++)
+ *                     strcat(dest, sarrayGetString(sa, i, L_NOCOPY));
+ *          Do you see why?
+ */
+char *
+sarrayToString(SARRAY  *sa,
+               l_int32  addnlflag)
+{
+    PROCNAME("sarrayToString");
+
+    if (!sa)
+        return (char *)ERROR_PTR("sa not defined", procName, NULL);
+
+    return sarrayToStringRange(sa, 0, 0, addnlflag);
+}
+
+
+/*!
+ *  sarrayToStringRange()
+ *
+ *      Input: sarray
+ *             first  (index of first string to use; starts with 0)
+ *             nstrings (number of strings to append into the result; use
+ *                       0 to append to the end of the sarray)
+ *             addnlflag (flag: 0 adds nothing to each substring
+ *                              1 adds '\n' to each substring
+ *                              2 adds ' ' to each substring)
+ *      Return: dest string, or null on error
+ *
+ *  Notes:
+ *      (1) Concatenates the specified strings inthe sarray, preserving
+ *          all white space.
+ *      (2) If addnlflag != 0, adds either a '\n' or a ' ' after
+ *          each substring.
+ *      (3) If the sarray is empty, this returns a string with just
+ *          the character corresponding to @addnlflag.
+ */
+char *
+sarrayToStringRange(SARRAY  *sa,
+                    l_int32  first,
+                    l_int32  nstrings,
+                    l_int32  addnlflag)
+{
+char    *dest, *src, *str;
+l_int32  n, i, last, size, index, len;
+
+    PROCNAME("sarrayToStringRange");
+
+    if (!sa)
+        return (char *)ERROR_PTR("sa not defined", procName, NULL);
+    if (addnlflag != 0 && addnlflag != 1 && addnlflag != 2)
+        return (char *)ERROR_PTR("invalid addnlflag", procName, NULL);
+
+    n = sarrayGetCount(sa);
+
+        /* Empty sa; return char corresponding to addnlflag only */
+    if (n == 0) {
+        if (first == 0) {
+            if (addnlflag == 0)
+                return stringNew("");
+            if (addnlflag == 1)
+                return stringNew("\n");
+            else  /* addnlflag == 2) */
+                return stringNew(" ");
+        } else {
+            return (char *)ERROR_PTR("first not valid", procName, NULL);
+        }
+    }
+
+    if (first < 0 || first >= n)
+        return (char *)ERROR_PTR("first not valid", procName, NULL);
+    if (nstrings == 0 || (nstrings > n - first))
+        nstrings = n - first;  /* no overflow */
+    last = first + nstrings - 1;
+
+    size = 0;
+    for (i = first; i <= last; i++) {
+        if ((str = sarrayGetString(sa, i, L_NOCOPY)) == NULL)
+            return (char *)ERROR_PTR("str not found", procName, NULL);
+        size += strlen(str) + 2;
+    }
+
+    if ((dest = (char *)LEPT_CALLOC(size + 1, sizeof(char))) == NULL)
+        return (char *)ERROR_PTR("dest not made", procName, NULL);
+
+    index = 0;
+    for (i = first; i <= last; i++) {
+        src = sarrayGetString(sa, i, L_NOCOPY);
+        len = strlen(src);
+        memcpy(dest + index, src, len);
+        index += len;
+        if (addnlflag == 1) {
+            dest[index] = '\n';
+            index++;
+        } else if (addnlflag == 2) {
+            dest[index] = ' ';
+            index++;
+        }
+    }
+
+    return dest;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                           Join 2 sarrays                             *
+ *----------------------------------------------------------------------*/
+/*!
+ *  sarrayJoin()
+ *
+ *      Input:  sa1  (to be added to)
+ *              sa2  (append to sa1)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Copies of the strings in sarray2 are added to sarray1.
+ */
+l_int32
+sarrayJoin(SARRAY  *sa1,
+           SARRAY  *sa2)
+{
+char    *str;
+l_int32  n, i;
+
+    PROCNAME("sarrayJoin");
+
+    if (!sa1)
+        return ERROR_INT("sa1 not defined", procName, 1);
+    if (!sa2)
+        return ERROR_INT("sa2 not defined", procName, 1);
+
+    n = sarrayGetCount(sa2);
+    for (i = 0; i < n; i++) {
+        str = sarrayGetString(sa2, i, L_NOCOPY);
+        sarrayAddString(sa1, str, L_COPY);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  sarrayAppendRange()
+ *
+ *      Input:  sa1  (to be added to)
+ *              sa2  (append specified range of strings in sa2 to sa1)
+ *              start (index of first string of sa2 to append)
+ *              end (index of last string of sa2 to append; -1 to end of array)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Copies of the strings in sarray2 are added to sarray1.
+ *      (2) The [start ... end] range is truncated if necessary.
+ *      (3) Use end == -1 to append to the end of sa2.
+ */
+l_int32
+sarrayAppendRange(SARRAY  *sa1,
+                  SARRAY  *sa2,
+                  l_int32  start,
+                  l_int32  end)
+{
+char    *str;
+l_int32  n, i;
+
+    PROCNAME("sarrayAppendRange");
+
+    if (!sa1)
+        return ERROR_INT("sa1 not defined", procName, 1);
+    if (!sa2)
+        return ERROR_INT("sa2 not defined", procName, 1);
+
+    if (start < 0)
+        start = 0;
+    n = sarrayGetCount(sa2);
+    if (end < 0 || end >= n)
+        end = n - 1;
+    if (start > end)
+        return ERROR_INT("start > end", procName, 1);
+
+    for (i = start; i <= end; i++) {
+        str = sarrayGetString(sa2, i, L_NOCOPY);
+        sarrayAddString(sa1, str, L_COPY);
+    }
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *          Pad an sarray to be the same size as another sarray         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  sarrayPadToSameSize()
+ *
+ *      Input:  sa1, sa2
+ *              padstring
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If two sarrays have different size, this adds enough
+ *          instances of @padstring to the smaller so that they are
+ *          the same size.  It is useful when two or more sarrays
+ *          are being sequenced in parallel, and it is necessary to
+ *          find a valid string at each index.
+ */
+l_int32
+sarrayPadToSameSize(SARRAY  *sa1,
+                    SARRAY  *sa2,
+                    char    *padstring)
+{
+l_int32  i, n1, n2;
+
+    PROCNAME("sarrayPadToSameSize");
+
+    if (!sa1 || !sa2)
+        return ERROR_INT("both sa1 and sa2 not defined", procName, 1);
+
+    n1 = sarrayGetCount(sa1);
+    n2 = sarrayGetCount(sa2);
+    if (n1 < n2) {
+        for (i = n1; i < n2; i++)
+            sarrayAddString(sa1, padstring, L_COPY);
+    } else if (n1 > n2) {
+        for (i = n2; i < n1; i++)
+            sarrayAddString(sa2, padstring, L_COPY);
+    }
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                   Convert word sarray to line sarray                 *
+ *----------------------------------------------------------------------*/
+/*!
+ *  sarrayConvertWordsToLines()
+ *
+ *      Input:  sa  (sa of individual words)
+ *              linesize  (max num of chars in each line)
+ *      Return: saout (sa of formatted lines), or null on error
+ *
+ *  This is useful for re-typesetting text to a specific maximum
+ *  line length.  The individual words in the input sarray
+ *  are concatenated into textlines.  An input word string of zero
+ *  length is taken to be a paragraph separator.  Each time
+ *  such a string is found, the current line is ended and
+ *  a new line is also produced that contains just the
+ *  string of zero length ("").  When the output sarray
+ *  of lines is eventually converted to a string with newlines
+ *  (typically) appended to each line string, the empty
+ *  strings are just converted to newlines, producing the visible
+ *  paragraph separation.
+ *
+ *  What happens when a word is larger than linesize?
+ *  We write it out as a single line anyway!  Words preceding
+ *  or following this long word are placed on lines preceding
+ *  or following the line with the long word.  Why this choice?
+ *  Long "words" found in text documents are typically URLs, and
+ *  it's often desirable not to put newlines in the middle of a URL.
+ *  The text display program (e.g., text editor) will typically
+ *  wrap the long "word" to fit in the window.
+ */
+SARRAY *
+sarrayConvertWordsToLines(SARRAY  *sa,
+                          l_int32  linesize)
+{
+char    *wd, *strl;
+char     emptystring[] = "";
+l_int32  n, i, len, totlen;
+SARRAY  *sal, *saout;
+
+    PROCNAME("sarrayConvertWordsToLines");
+
+    if (!sa)
+        return (SARRAY *)ERROR_PTR("sa not defined", procName, NULL);
+
+    if ((saout = sarrayCreate(0)) == NULL)
+        return (SARRAY *)ERROR_PTR("saout not defined", procName, NULL);
+
+    n = sarrayGetCount(sa);
+    totlen = 0;
+    sal = NULL;
+    for (i = 0; i < n; i++) {
+        if (!sal) {
+            if ((sal = sarrayCreate(0)) == NULL)
+                return (SARRAY *)ERROR_PTR("sal not made", procName, NULL);
+        }
+        wd = sarrayGetString(sa, i, L_NOCOPY);
+        len = strlen(wd);
+        if (len == 0) {  /* end of paragraph: end line & insert blank line */
+            if (totlen > 0) {
+                strl = sarrayToString(sal, 2);
+                sarrayAddString(saout, strl, L_INSERT);
+            }
+            sarrayAddString(saout, emptystring, L_COPY);
+            sarrayDestroy(&sal);
+            totlen = 0;
+        } else if (totlen == 0 && len + 1 > linesize) {  /* long word! */
+            sarrayAddString(saout, wd, L_COPY);  /* copy to one line */
+        } else if (totlen + len + 1 > linesize) {  /* end line & start new */
+            strl = sarrayToString(sal, 2);
+            sarrayAddString(saout, strl, L_INSERT);
+            sarrayDestroy(&sal);
+            if ((sal = sarrayCreate(0)) == NULL)
+                return (SARRAY *)ERROR_PTR("sal not made", procName, NULL);
+            sarrayAddString(sal, wd, L_COPY);
+            totlen = len + 1;
+        } else {  /* add to current line */
+            sarrayAddString(sal, wd, L_COPY);
+            totlen += len + 1;
+        }
+    }
+    if (totlen > 0) {   /* didn't end with blank line; output last line */
+        strl = sarrayToString(sal, 2);
+        sarrayAddString(saout, strl, L_INSERT);
+        sarrayDestroy(&sal);
+    }
+
+    return saout;
+
+}
+
+
+/*----------------------------------------------------------------------*
+ *                    Split string on separator list                    *
+ *----------------------------------------------------------------------*/
+/*
+ *  sarraySplitString()
+ *
+ *      Input:  sa (to append to; typically empty initially)
+ *              str (string to split; not changed)
+ *              separators (characters that split input string)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) This uses strtokSafe().  See the notes there in utils.c.
+ */
+l_int32
+sarraySplitString(SARRAY      *sa,
+                  const char  *str,
+                  const char  *separators)
+{
+char  *cstr, *substr, *saveptr;
+
+    PROCNAME("sarraySplitString");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!str)
+        return ERROR_INT("str not defined", procName, 1);
+    if (!separators)
+        return ERROR_INT("separators not defined", procName, 1);
+
+    cstr = stringNew(str);  /* preserves const-ness of input str */
+    substr = strtokSafe(cstr, separators, &saveptr);
+    if (substr)
+        sarrayAddString(sa, substr, L_INSERT);
+    while ((substr = strtokSafe(NULL, separators, &saveptr)))
+        sarrayAddString(sa, substr, L_INSERT);
+    LEPT_FREE(cstr);
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                              Filter sarray                           *
+ *----------------------------------------------------------------------*/
+/*!
+ *  sarraySelectBySubstring()
+ *
+ *      Input:  sain (input sarray)
+ *              substr (<optional> substring for matching; can be NULL)
+ *      Return: saout (output sarray, filtered with substring) or null on error
+ *
+ *  Notes:
+ *      (1) This selects all strings in sain that have substr as a substring.
+ *          Note that we can't use strncmp() because we're looking for
+ *          a match to the substring anywhere within each filename.
+ *      (2) If substr == NULL, returns a copy of the sarray.
+ */
+SARRAY *
+sarraySelectBySubstring(SARRAY      *sain,
+                        const char  *substr)
+{
+char    *str;
+l_int32  n, i, offset, found;
+SARRAY  *saout;
+
+    PROCNAME("sarraySelectBySubstring");
+
+    if (!sain)
+        return (SARRAY *)ERROR_PTR("sain not defined", procName, NULL);
+
+    n = sarrayGetCount(sain);
+    if (!substr || n == 0)
+        return sarrayCopy(sain);
+
+    saout = sarrayCreate(n);
+    for (i = 0; i < n; i++) {
+        str = sarrayGetString(sain, i, L_NOCOPY);
+        arrayFindSequence((l_uint8 *)str, strlen(str), (l_uint8 *)substr,
+                          strlen(substr), &offset, &found);
+        if (found)
+            sarrayAddString(saout, str, L_COPY);
+    }
+
+    return saout;
+}
+
+
+/*!
+ *  sarraySelectByRange()
+ *
+ *      Input:  sain (input sarray)
+ *              first (index of first string to be selected)
+ *              last (index of last string to be selected; use 0 to go to the
+ *                    end of the sarray)
+ *      Return: saout (output sarray), or null on error
+ *
+ *  Notes:
+ *      (1) This makes @saout consisting of copies of all strings in @sain
+ *          in the index set [first ... last].  Use @last == 0 to get all
+ *          strings from @first to the last string in the sarray.
+ */
+SARRAY *
+sarraySelectByRange(SARRAY  *sain,
+                    l_int32  first,
+                    l_int32  last)
+{
+char    *str;
+l_int32  n, i;
+SARRAY  *saout;
+
+    PROCNAME("sarraySelectByRange");
+
+    if (!sain)
+        return (SARRAY *)ERROR_PTR("sain not defined", procName, NULL);
+    if (first < 0) first = 0;
+    n = sarrayGetCount(sain);
+    if (last <= 0) last = n - 1;
+    if (last >= n) {
+        L_WARNING("@last > n - 1; setting to n - 1\n", procName);
+        last = n - 1;
+    }
+    if (first > last)
+        return (SARRAY *)ERROR_PTR("first must be >= last", procName, NULL);
+
+    saout = sarrayCreate(0);
+    for (i = first; i <= last; i++) {
+        str = sarrayGetString(sain, i, L_COPY);
+        sarrayAddString(saout, str, L_INSERT);
+    }
+
+    return saout;
+}
+
+
+/*!
+ *  sarrayParseRange()
+ *
+ *      Input:  sa (input sarray)
+ *              start (index to start range search)
+ *             &actualstart (<return> index of actual start; may be > 'start')
+ *             &end (<return> index of end)
+ *             &newstart (<return> index of start of next range)
+ *              substr (substring for matching at beginning of string)
+ *              loc (byte offset within the string for the pattern; use
+ *                   -1 if the location does not matter);
+ *      Return: 0 if valid range found; 1 otherwise
+ *
+ *  Notes:
+ *      (1) This finds the range of the next set of strings in SA,
+ *          beginning the search at 'start', that does NOT have
+ *          the substring 'substr' either at the indicated location
+ *          in the string or anywhere in the string.  The input
+ *          variable 'loc' is the specified offset within the string;
+ *          use -1 to indicate 'anywhere in the string'.
+ *      (2) Always check the return value to verify that a valid range
+ *          was found.
+ *      (3) If a valid range is not found, the values of actstart,
+ *          end and newstart are all set to the size of sa.
+ *      (4) If this is the last valid range, newstart returns the value n.
+ *          In use, this should be tested before calling the function.
+ *      (5) Usage example.  To find all the valid ranges in a file
+ *          where the invalid lines begin with two dashes, copy each
+ *          line in the file to a string in an sarray, and do:
+ *             start = 0;
+ *             while (!sarrayParseRange(sa, start, &actstart, &end, &start,
+ *                    "--", 0))
+ *                 fprintf(stderr, "start = %d, end = %d\n", actstart, end);
+ */
+l_int32
+sarrayParseRange(SARRAY      *sa,
+                 l_int32      start,
+                 l_int32     *pactualstart,
+                 l_int32     *pend,
+                 l_int32     *pnewstart,
+                 const char  *substr,
+                 l_int32      loc)
+{
+char    *str;
+l_int32  n, i, offset, found;
+
+    PROCNAME("sarrayParseRange");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!pactualstart || !pend || !pnewstart)
+        return ERROR_INT("not all range addresses defined", procName, 1);
+    n = sarrayGetCount(sa);
+    *pactualstart = *pend = *pnewstart = n;
+    if (!substr)
+        return ERROR_INT("substr not defined", procName, 1);
+
+        /* Look for the first string without the marker */
+    if (start < 0 || start >= n)
+        return 1;
+    for (i = start; i < n; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        arrayFindSequence((l_uint8 *)str, strlen(str), (l_uint8 *)substr,
+                          strlen(substr), &offset, &found);
+        if (loc < 0) {
+            if (!found) break;
+        } else {
+            if (!found || offset != loc) break;
+        }
+    }
+    start = i;
+    if (i == n)  /* couldn't get started */
+        return 1;
+
+        /* Look for the last string without the marker */
+    *pactualstart = start;
+    for (i = start + 1; i < n; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        arrayFindSequence((l_uint8 *)str, strlen(str), (l_uint8 *)substr,
+                          strlen(substr), &offset, &found);
+        if (loc < 0) {
+            if (found) break;
+        } else {
+            if (found && offset == loc) break;
+        }
+    }
+    *pend = i - 1;
+    start = i;
+    if (i == n)  /* no further range */
+        return 0;
+
+        /* Look for the first string after *pend without the marker.
+         * This will start the next run of strings, if it exists. */
+    for (i = start; i < n; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        arrayFindSequence((l_uint8 *)str, strlen(str), (l_uint8 *)substr,
+                          strlen(substr), &offset, &found);
+        if (loc < 0) {
+            if (!found) break;
+        } else {
+            if (!found || offset != loc) break;
+        }
+    }
+    if (i < n)
+        *pnewstart = i;
+
+    return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                                   Sort                               *
+ *----------------------------------------------------------------------*/
+/*!
+ *  sarraySort()
+ *
+ *      Input:  saout (output sarray; can be NULL or equal to sain)
+ *              sain (input sarray)
+ *              sortorder (L_SORT_INCREASING or L_SORT_DECREASING)
+ *      Return: saout (output sarray, sorted by ascii value), or null on error
+ *
+ *  Notes:
+ *      (1) Set saout = sain for in-place; otherwise, set naout = NULL.
+ *      (2) Shell sort, modified from K&R, 2nd edition, p.62.
+ *          Slow but simple O(n logn) sort.
+ */
+SARRAY *
+sarraySort(SARRAY  *saout,
+           SARRAY  *sain,
+           l_int32  sortorder)
+{
+char   **array;
+char    *tmp;
+l_int32  n, i, j, gap;
+
+    PROCNAME("sarraySort");
+
+    if (!sain)
+        return (SARRAY *)ERROR_PTR("sain not defined", procName, NULL);
+
+        /* Make saout if necessary; otherwise do in-place */
+    if (!saout)
+        saout = sarrayCopy(sain);
+    else if (sain != saout)
+        return (SARRAY *)ERROR_PTR("invalid: not in-place", procName, NULL);
+    array = saout->array;  /* operate directly on the array */
+    n = sarrayGetCount(saout);
+
+        /* Shell sort */
+    for (gap = n/2; gap > 0; gap = gap / 2) {
+        for (i = gap; i < n; i++) {
+            for (j = i - gap; j >= 0; j -= gap) {
+                if ((sortorder == L_SORT_INCREASING &&
+                     stringCompareLexical(array[j], array[j + gap])) ||
+                    (sortorder == L_SORT_DECREASING &&
+                     stringCompareLexical(array[j + gap], array[j])))
+                {
+                    tmp = array[j];
+                    array[j] = array[j + gap];
+                    array[j + gap] = tmp;
+                }
+            }
+        }
+    }
+
+    return saout;
+}
+
+
+/*!
+ *  sarraySortByIndex()
+ *
+ *      Input:  sain
+ *              naindex (na that maps from the new sarray to the input sarray)
+ *      Return: saout (sorted), or null on error
+ */
+SARRAY *
+sarraySortByIndex(SARRAY  *sain,
+                  NUMA    *naindex)
+{
+char    *str;
+l_int32  i, n, index;
+SARRAY  *saout;
+
+    PROCNAME("sarraySortByIndex");
+
+    if (!sain)
+        return (SARRAY *)ERROR_PTR("sain not defined", procName, NULL);
+    if (!naindex)
+        return (SARRAY *)ERROR_PTR("naindex not defined", procName, NULL);
+
+    n = sarrayGetCount(sain);
+    saout = sarrayCreate(n);
+    for (i = 0; i < n; i++) {
+        numaGetIValue(naindex, i, &index);
+        str = sarrayGetString(sain, index, L_COPY);
+        sarrayAddString(saout, str, L_INSERT);
+    }
+
+    return saout;
+}
+
+
+/*!
+ *  stringCompareLexical()
+ *
+ *      Input:  str1
+ *              str2
+ *      Return: 1 if str1 > str2 (lexically); 0 otherwise
+ *
+ *  Notes:
+ *      (1) If the lexical values are identical, return a 0, to
+ *          indicate that no swapping is required to sort the strings.
+ */
+l_int32
+stringCompareLexical(const char *str1,
+                     const char *str2)
+{
+l_int32  i, len1, len2, len;
+
+    PROCNAME("sarrayCompareLexical");
+
+    if (!str1)
+        return ERROR_INT("str1 not defined", procName, 1);
+    if (!str2)
+        return ERROR_INT("str2 not defined", procName, 1);
+
+    len1 = strlen(str1);
+    len2 = strlen(str2);
+    len = L_MIN(len1, len2);
+
+    for (i = 0; i < len; i++) {
+        if (str1[i] == str2[i])
+            continue;
+        if (str1[i] > str2[i])
+            return 1;
+        else
+            return 0;
+    }
+
+    if (len1 > len2)
+        return 1;
+    else
+        return 0;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                       Operations by aset (rbtree)                    *
+ *----------------------------------------------------------------------*/
+/*!
+ *  sarrayUnionByAset()
+ *
+ *      Input:  sa1, sa2
+ *      Return: sad (with the union of the string set), or null on error
+ *
+ *  Notes:
+ *      (1) Duplicates are removed from the concatenation of the two arrays.
+ *      (2) The key for each string is a 64-bit hash.
+ *      (2) Algorithm: Concatenate the two sarrays.  Then build a set,
+ *          using hashed strings as keys.  As the set is built, first do
+ *          a find; if not found, add the key to the set and add the string
+ *          to the output sarray.  This is O(nlogn).
+ */
+SARRAY *
+sarrayUnionByAset(SARRAY  *sa1,
+                  SARRAY  *sa2)
+{
+SARRAY  *sa3, *sad;
+
+    PROCNAME("sarrayUnionByAset");
+
+    if (!sa1)
+        return (SARRAY *)ERROR_PTR("sa1 not defined", procName, NULL);
+    if (!sa2)
+        return (SARRAY *)ERROR_PTR("sa2 not defined", procName, NULL);
+
+        /* Join */
+    sa3 = sarrayCopy(sa1);
+    sarrayJoin(sa3, sa2);
+
+        /* Eliminate duplicates */
+    sad = sarrayRemoveDupsByAset(sa3);
+    sarrayDestroy(&sa3);
+    return sad;
+}
+
+
+/*!
+ *  sarrayRemoveDupsByAset()
+ *
+ *      Input:  sas
+ *      Return: sad (with duplicates removed), or null on error
+ *
+ *  Notes:
+ *      (1) This is O(nlogn), considerably slower than
+ *          sarrayRemoveDupsByHash() for large string arrays.
+ *      (2) The key for each string is a 64-bit hash.
+ *      (3) Build a set, using hashed strings as keys.  As the set is
+ *          built, first do a find; if not found, add the key to the
+ *          set and add the string to the output sarray.
+ */
+SARRAY *
+sarrayRemoveDupsByAset(SARRAY  *sas)
+{
+char     *str;
+l_int32   i, n;
+l_uint64  hash;
+L_ASET   *set;
+RB_TYPE   key;
+SARRAY   *sad;
+
+    PROCNAME("sarrayRemoveDupsByAset");
+
+    if (!sas)
+        return (SARRAY *)ERROR_PTR("sas not defined", procName, NULL);
+
+    set = l_asetCreate(L_UINT_TYPE);
+    sad = sarrayCreate(0);
+    n = sarrayGetCount(sas);
+    for (i = 0; i < n; i++) {
+        str = sarrayGetString(sas, i, L_NOCOPY);
+        l_hashStringToUint64(str, &hash);
+        key.utype = hash;
+        if (!l_asetFind(set, key)) {
+            sarrayAddString(sad, str, L_COPY);
+            l_asetInsert(set, key);
+        }
+    }
+
+    l_asetDestroy(&set);
+    return sad;
+}
+
+
+/*!
+ *  sarrayIntersectionByAset()
+ *
+ *      Input:  sa1, sa2
+ *      Return: sad (with the intersection of the string set), or null on error
+ *
+ *  Notes:
+ *      (1) Algorithm: put the smaller sarray into a set, using the string
+ *          hashes as the key values.  Then run through the larger sarray,
+ *          building an output sarray and a second set from the strings
+ *          in the larger array: if a string is in the first set but
+ *          not in the second, add the string to the output sarray and hash
+ *          it into the second set.  The second set is required to make
+ *          sure only one instance of each string is put into the output sarray.
+ *          This is O(mlogn), {m,n} = sizes of {smaller,larger} input arrays.
+ */
+SARRAY *
+sarrayIntersectionByAset(SARRAY  *sa1,
+                         SARRAY  *sa2)
+{
+char     *str;
+l_int32   n1, n2, i, n;
+l_uint64  hash;
+L_ASET   *set1, *set2;
+RB_TYPE   key;
+SARRAY   *sa_small, *sa_big, *sad;
+
+    PROCNAME("sarrayIntersectionByAset");
+
+    if (!sa1)
+        return (SARRAY *)ERROR_PTR("sa1 not defined", procName, NULL);
+    if (!sa2)
+        return (SARRAY *)ERROR_PTR("sa2 not defined", procName, NULL);
+
+        /* Put the elements of the biggest array into a set */
+    n1 = sarrayGetCount(sa1);
+    n2 = sarrayGetCount(sa2);
+    sa_small = (n1 < n2) ? sa1 : sa2;   /* do not destroy sa_small */
+    sa_big = (n1 < n2) ? sa2 : sa1;   /* do not destroy sa_big */
+    set1 = l_asetCreateFromSarray(sa_big);
+
+        /* Build up the intersection of strings */
+    sad = sarrayCreate(0);
+    n = sarrayGetCount(sa_small);
+    set2 = l_asetCreate(L_UINT_TYPE);
+    for (i = 0; i < n; i++) {
+        str = sarrayGetString(sa_small, i, L_NOCOPY);
+        l_hashStringToUint64(str, &hash);
+        key.utype = hash;
+        if (l_asetFind(set1, key) && !l_asetFind(set2, key)) {
+            sarrayAddString(sad, str, L_COPY);
+            l_asetInsert(set2, key);
+        }
+    }
+
+    l_asetDestroy(&set1);
+    l_asetDestroy(&set2);
+    return sad;
+}
+
+
+/*!
+ *  l_asetCreateFromSarray()
+ *
+ *      Input:  sa
+ *      Return: set (using a string hash into a uint32 as the key)
+ */
+L_ASET *
+l_asetCreateFromSarray(SARRAY  *sa)
+{
+char     *str;
+l_int32   i, n;
+l_uint64  hash;
+L_ASET   *set;
+RB_TYPE   key;
+
+    PROCNAME("l_asetCreateFromSarray");
+
+    if (!sa)
+        return (L_ASET *)ERROR_PTR("sa not defined", procName, NULL);
+
+    set = l_asetCreate(L_UINT_TYPE);
+    n = sarrayGetCount(sa);
+    for (i = 0; i < n; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        l_hashStringToUint64(str, &hash);
+        key.utype = hash;
+        l_asetInsert(set, key);
+    }
+
+    return set;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                    Operations by hashmap (dnahash)                   *
+ *----------------------------------------------------------------------*/
+/*!
+ *  sarrayRemoveDupsByHash()
+ *
+ *      Input:  sas
+ *              &sad (<return> unique set of strings; duplicates removed)
+ *              &dahash (<optional return> dnahash used for lookup)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Generates a sarray with unique values.
+ *      (2) The dnahash is built up with sad to assure uniqueness.
+ *          It can be used to find if a string is in the set:
+ *              sarrayFindValByHash(sad, dahash, str, &index)
+ *      (3) The hash of the string location is simple and fast.  It scales
+ *          up with the number of buckets to insure a fairly random
+ *          bucket selection input strings.
+ *      (4) This is faster than sarrayRemoveDupsByAset(), because the
+ *          bucket lookup is O(n), although there is a double-loop
+ *          lookup within the dna in each bucket.
+ */
+l_int32
+sarrayRemoveDupsByHash(SARRAY      *sas,
+                       SARRAY     **psad,
+                       L_DNAHASH  **pdahash)
+{
+char       *str;
+l_int32     i, n, index, items;
+l_uint32    nsize;
+l_uint64    key;
+l_float64   val;
+SARRAY     *sad;
+L_DNAHASH  *dahash;
+
+    PROCNAME("sarrayRemoveDupsByHash");
+
+    if (pdahash) *pdahash = NULL;
+    if (!psad)
+        return ERROR_INT("&sad not defined", procName, 1);
+    *psad = NULL;
+    if (!sas)
+        return ERROR_INT("sas not defined", procName, 1);
+
+    n = sarrayGetCount(sas);
+    findNextLargerPrime(n / 20, &nsize);  /* buckets in hash table */
+    dahash = l_dnaHashCreate(nsize, 8);
+    sad = sarrayCreate(n);
+    *psad = sad;
+    for (i = 0, items = 0; i < n; i++) {
+        str = sarrayGetString(sas, i, L_NOCOPY);
+        sarrayFindStringByHash(sad, dahash, str, &index);
+        if (index < 0) {  /* not found */
+            l_hashStringToUint64(str, &key);
+            l_dnaHashAdd(dahash, key, (l_float64)items);
+            sarrayAddString(sad, str, L_COPY);
+            items++;
+        }
+    }
+
+    if (pdahash)
+        *pdahash = dahash;
+    else
+        l_dnaHashDestroy(&dahash);
+    return 0;
+}
+
+
+/*!
+ *  sarrayIntersectionByHash()
+ *
+ *      Input:  sa1, sa2
+ *      Return: sad (intersection of the strings), or null on error
+ *
+ *  Notes:
+ *      (1) This is faster than sarrayIntersectionByAset(), because the
+ *          bucket lookup is O(n).
+ */
+SARRAY *
+sarrayIntersectionByHash(SARRAY  *sa1,
+                         SARRAY  *sa2)
+{
+char       *str;
+l_int32     n1, n2, nsmall, i, index1, index2;
+l_uint32    nsize2;
+l_uint64    key;
+L_DNAHASH  *dahash1, *dahash2;
+SARRAY     *sa_small, *sa_big, *sad;
+
+    PROCNAME("sarrayIntersectionByHash");
+
+    if (!sa1)
+        return (SARRAY *)ERROR_PTR("sa1 not defined", procName, NULL);
+    if (!sa2)
+        return (SARRAY *)ERROR_PTR("sa2 not defined", procName, NULL);
+
+        /* Put the elements of the biggest sarray into a dnahash */
+    n1 = sarrayGetCount(sa1);
+    n2 = sarrayGetCount(sa2);
+    sa_small = (n1 < n2) ? sa1 : sa2;   /* do not destroy sa_small */
+    sa_big = (n1 < n2) ? sa2 : sa1;   /* do not destroy sa_big */
+    dahash1 = l_dnaHashCreateFromSarray(sa_big);
+
+        /* Build up the intersection of strings.  Add to @sad
+         * if the string is in sa_big (using dahash1) but hasn't
+         * yet been seen in the traversal of sa_small (using dahash2). */
+    sad = sarrayCreate(0);
+    nsmall = sarrayGetCount(sa_small);
+    findNextLargerPrime(nsmall / 20, &nsize2);  /* buckets in hash table */
+    dahash2 = l_dnaHashCreate(nsize2, 0);
+    for (i = 0; i < nsmall; i++) {
+        str = sarrayGetString(sa_small, i, L_NOCOPY);
+        sarrayFindStringByHash(sa_big, dahash1, str, &index1);
+        if (index1 >= 0) {
+            sarrayFindStringByHash(sa_small, dahash2, str, &index2);
+            if (index2 == -1) {
+                sarrayAddString(sad, str, L_COPY);
+                l_hashStringToUint64(str, &key);
+                l_dnaHashAdd(dahash2, key, (l_float64)i);
+            }
+        }
+    }
+
+    l_dnaHashDestroy(&dahash1);
+    l_dnaHashDestroy(&dahash2);
+    return sad;
+}
+
+
+/*!
+ *  sarrayFindStringByHash()
+ *
+ *      Input:  sa
+ *              dahash (built from sa)
+ *              str  (arbitrary string)
+ *              &index (<return> index into @sa if @str is in @sa;
+ *              -1 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Fast lookup in dnaHash associated with a sarray, to see if a
+ *          random string @str is already stored in the hash table.
+ */
+l_int32
+sarrayFindStringByHash(SARRAY      *sa,
+                       L_DNAHASH   *dahash,
+                       const char  *str,
+                       l_int32     *pindex)
+{
+char     *stri;
+l_int32   i, nvals, index;
+l_uint64  key;
+L_DNA    *da;
+
+    PROCNAME("sarrayFindStringByHash");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = -1;
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!dahash)
+        return ERROR_INT("dahash not defined", procName, 1);
+
+    l_hashStringToUint64(str, &key);
+    da = l_dnaHashGetDna(dahash, key, L_NOCOPY);
+    if (!da) return 0;
+
+        /* Run through the da, looking for this string */
+    nvals = l_dnaGetCount(da);
+    for (i = 0; i < nvals; i++) {
+        l_dnaGetIValue(da, i, &index);
+        stri = sarrayGetString(sa, index, L_NOCOPY);
+        if (!strcmp(str, stri)) {  /* duplicate */
+            *pindex = index;
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  l_dnaHashCreateFromSarray()
+ *
+ *      Input:  sa
+ *      Return: dahash, or null on error
+ */
+L_DNAHASH *
+l_dnaHashCreateFromSarray(SARRAY  *sa)
+{
+char       *str;
+l_int32     i, n;
+l_uint32    nsize;
+l_uint64    key;
+L_DNAHASH  *dahash;
+
+        /* Build up dnaHash of indices, hashed by a 64-bit key that
+         * should randomize the lower bits used in bucket selection.
+         * Having about 20 pts in each bucket is roughly optimal. */
+    n = sarrayGetCount(sa);
+    findNextLargerPrime(n / 20, &nsize);  /* buckets in hash table */
+/*    fprintf(stderr, "Prime used: %d\n", nsize); */
+
+        /* Add each string, using the hash as key and the index into @sa
+         * as the value.  Storing the index enables operations that check
+         * for duplicates.  */
+    dahash = l_dnaHashCreate(nsize, 8);
+    for (i = 0; i < n; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        l_hashStringToUint64(str, &key);
+        l_dnaHashAdd(dahash, key, (l_float64)i);
+    }
+
+    return dahash;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                           Serialize for I/O                          *
+ *----------------------------------------------------------------------*/
+/*!
+ *  sarrayRead()
+ *
+ *      Input:  filename
+ *      Return: sarray, or null on error
+ */
+SARRAY *
+sarrayRead(const char  *filename)
+{
+FILE    *fp;
+SARRAY  *sa;
+
+    PROCNAME("sarrayRead");
+
+    if (!filename)
+        return (SARRAY *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (SARRAY *)ERROR_PTR("stream not opened", procName, NULL);
+
+    if ((sa = sarrayReadStream(fp)) == NULL) {
+        fclose(fp);
+        return (SARRAY *)ERROR_PTR("sa not read", procName, NULL);
+    }
+
+    fclose(fp);
+    return sa;
+}
+
+
+/*!
+ *  sarrayReadStream()
+ *
+ *      Input:  stream
+ *      Return: sarray, or null on error
+ *
+ *  Notes:
+ *      (1) We store the size of each string along with the string.
+ *      (2) This allows a string to have embedded newlines.  By reading
+ *          the entire string, as determined by its size, we are
+ *          not affected by any number of embedded newlines.
+ */
+SARRAY *
+sarrayReadStream(FILE  *fp)
+{
+char    *stringbuf;
+l_int32  i, n, size, index, bufsize, version, ignore;
+SARRAY  *sa;
+
+    PROCNAME("sarrayReadStream");
+
+    if (!fp)
+        return (SARRAY *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if (fscanf(fp, "\nSarray Version %d\n", &version) != 1)
+        return (SARRAY *)ERROR_PTR("not an sarray file", procName, NULL);
+    if (version != SARRAY_VERSION_NUMBER)
+        return (SARRAY *)ERROR_PTR("invalid sarray version", procName, NULL);
+    if (fscanf(fp, "Number of strings = %d\n", &n) != 1)
+        return (SARRAY *)ERROR_PTR("error on # strings", procName, NULL);
+
+    if ((sa = sarrayCreate(n)) == NULL)
+        return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+    bufsize = L_BUF_SIZE + 1;
+    if ((stringbuf = (char *)LEPT_CALLOC(bufsize, sizeof(char))) == NULL)
+        return (SARRAY *)ERROR_PTR("stringbuf not made", procName, NULL);
+
+    for (i = 0; i < n; i++) {
+            /* Get the size of the stored string */
+        if (fscanf(fp, "%d[%d]:", &index, &size) != 2)
+            return (SARRAY *)ERROR_PTR("error on string size", procName, NULL);
+            /* Expand the string buffer if necessary */
+        if (size > bufsize - 5) {
+            LEPT_FREE(stringbuf);
+            bufsize = (l_int32)(1.5 * size);
+            stringbuf = (char *)LEPT_CALLOC(bufsize, sizeof(char));
+        }
+            /* Read the stored string, plus leading spaces and trailing \n */
+        if (fread(stringbuf, 1, size + 3, fp) != size + 3)
+            return (SARRAY *)ERROR_PTR("error reading string", procName, NULL);
+            /* Remove the \n that was added by sarrayWriteStream() */
+        stringbuf[size + 2] = '\0';
+            /* Copy it in, skipping the 2 leading spaces */
+        sarrayAddString(sa, stringbuf + 2, L_COPY);
+    }
+    ignore = fscanf(fp, "\n");
+
+    LEPT_FREE(stringbuf);
+    return sa;
+}
+
+
+/*!
+ *  sarrayWrite()
+ *
+ *      Input:  filename
+ *              sarray
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+sarrayWrite(const char  *filename,
+            SARRAY      *sa)
+{
+FILE  *fp;
+
+    PROCNAME("sarrayWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "w")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+
+    if (sarrayWriteStream(fp, sa))
+        return ERROR_INT("sa not written to stream", procName, 1);
+
+    fclose(fp);
+    return 0;
+}
+
+
+/*!
+ *  sarrayWriteStream()
+ *
+ *      Input:  stream
+ *              sarray
+ *      Returns 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This appends a '\n' to each string, which is stripped
+ *          off by sarrayReadStream().
+ */
+l_int32
+sarrayWriteStream(FILE    *fp,
+                  SARRAY  *sa)
+{
+l_int32  i, n, len;
+
+    PROCNAME("sarrayWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+
+    n = sarrayGetCount(sa);
+    fprintf(fp, "\nSarray Version %d\n", SARRAY_VERSION_NUMBER);
+    fprintf(fp, "Number of strings = %d\n", n);
+    for (i = 0; i < n; i++) {
+        len = strlen(sa->array[i]);
+        fprintf(fp, "  %d[%d]:  %s\n", i, len, sa->array[i]);
+    }
+    fprintf(fp, "\n");
+
+    return 0;
+}
+
+
+/*!
+ *  sarrayAppend()
+ *
+ *      Input:  filename
+ *              sarray
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+sarrayAppend(const char  *filename,
+             SARRAY      *sa)
+{
+FILE  *fp;
+
+    PROCNAME("sarrayAppend");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "a")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+
+    if (sarrayWriteStream(fp, sa))
+        return ERROR_INT("sa not appended to stream", procName, 1);
+
+    fclose(fp);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                           Directory filenames                       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  getNumberedPathnamesInDirectory()
+ *
+ *      Input:  directory name
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              numpre (number of characters in name before number)
+ *              numpost (number of characters in name after the number,
+ *                       up to a dot before an extension)
+ *              maxnum (only consider page numbers up to this value)
+ *      Return: sarray of numbered pathnames, or NULL on error
+ *
+ *  Notes:
+ *      (1) Returns the full pathnames of the numbered filenames in
+ *          the directory.  The number in the filename is the index
+ *          into the sarray.  For indices for which there are no filenames,
+ *          an empty string ("") is placed into the sarray.
+ *          This makes reading numbered files very simple.  For example,
+ *          the image whose filename includes number N can be retrieved using
+ *               pixReadIndexed(sa, N);
+ *      (2) If @substr is not NULL, only filenames that contain
+ *          the substring can be included.  If @substr is NULL,
+ *          all matching filenames are used.
+ *      (3) If no numbered files are found, it returns an empty sarray,
+ *          with no initialized strings.
+ *      (4) It is assumed that the page number is contained within
+ *          the basename (the filename without directory or extension).
+ *          @numpre is the number of characters in the basename
+ *          preceding the actual page number; @numpost is the number
+ *          following the page number, up to either the end of the
+ *          basename or a ".", whichever comes first.
+ *      (5) This is useful when all filenames contain numbers that are
+ *          not necessarily consecutive.  0-padding is not required.
+ *      (6) To use a O(n) matching algorithm, the largest page number
+ *          is found and two internal arrays of this size are created.
+ *          This maximum is constrained not to exceed @maxsum,
+ *          to make sure that an unrealistically large number is not
+ *          accidentally used to determine the array sizes.
+ */
+SARRAY *
+getNumberedPathnamesInDirectory(const char  *dirname,
+                                const char  *substr,
+                                l_int32      numpre,
+                                l_int32      numpost,
+                                l_int32      maxnum)
+{
+l_int32  nfiles;
+SARRAY  *sa, *saout;
+
+    PROCNAME("getNumberedPathnamesInDirectory");
+
+    if (!dirname)
+        return (SARRAY *)ERROR_PTR("dirname not defined", procName, NULL);
+
+    if ((sa = getSortedPathnamesInDirectory(dirname, substr, 0, 0)) == NULL)
+        return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+    if ((nfiles = sarrayGetCount(sa)) == 0)
+        return sarrayCreate(1);
+
+    saout = convertSortedToNumberedPathnames(sa, numpre, numpost, maxnum);
+    sarrayDestroy(&sa);
+    return saout;
+}
+
+
+/*!
+ *  getSortedPathnamesInDirectory()
+ *
+ *      Input:  directory name
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              first (0-based)
+ *              nfiles (use 0 for all to the end)
+ *      Return: sarray of sorted pathnames, or NULL on error
+ *
+ *  Notes:
+ *      (1) Use @substr to filter filenames in the directory.  If
+ *          @substr == NULL, this takes all files.
+ *      (2) The files in the directory, after optional filtering by
+ *          the substring, are lexically sorted in increasing order.
+ *          Use @first and @nfiles to select a contiguous set of files.
+ *      (3) The full pathnames are returned for the requested sequence.
+ *          If no files are found after filtering, returns an empty sarray.
+ */
+SARRAY *
+getSortedPathnamesInDirectory(const char  *dirname,
+                              const char  *substr,
+                              l_int32      first,
+                              l_int32      nfiles)
+{
+char    *fname, *fullname;
+l_int32  i, n, last;
+SARRAY  *sa, *safiles, *saout;
+
+    PROCNAME("getSortedPathnamesInDirectory");
+
+    if (!dirname)
+        return (SARRAY *)ERROR_PTR("dirname not defined", procName, NULL);
+
+    if ((sa = getFilenamesInDirectory(dirname)) == NULL)
+        return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+    safiles = sarraySelectBySubstring(sa, substr);
+    sarrayDestroy(&sa);
+    n = sarrayGetCount(safiles);
+    if (n == 0) {
+        L_WARNING("no files found\n", procName);
+        return safiles;
+    }
+
+    sarraySort(safiles, safiles, L_SORT_INCREASING);
+
+    first = L_MIN(L_MAX(first, 0), n - 1);
+    if (nfiles == 0)
+        nfiles = n - first;
+    last = L_MIN(first + nfiles - 1, n - 1);
+
+    saout = sarrayCreate(last - first + 1);
+    for (i = first; i <= last; i++) {
+        fname = sarrayGetString(safiles, i, L_NOCOPY);
+        fullname = genPathname(dirname, fname);
+        sarrayAddString(saout, fullname, L_INSERT);
+    }
+
+    sarrayDestroy(&safiles);
+    return saout;
+}
+
+
+/*!
+ *  convertSortedToNumberedPathnames()
+ *
+ *      Input:  sorted pathnames (including zero-padded integers)
+ *              numpre (number of characters in name before number)
+ *              numpost (number of characters in name after the number,
+ *                       up to a dot before an extension)
+ *              maxnum (only consider page numbers up to this value)
+ *      Return: sarray of numbered pathnames, or NULL on error
+ *
+ *  Notes:
+ *      (1) Typically, numpre = numpost = 0; e.g., when the filename
+ *          just has a number followed by an optional extension.
+ */
+SARRAY *
+convertSortedToNumberedPathnames(SARRAY   *sa,
+                                 l_int32   numpre,
+                                 l_int32   numpost,
+                                 l_int32   maxnum)
+{
+char    *fname, *str;
+l_int32  i, nfiles, num, index;
+SARRAY  *saout;
+
+    PROCNAME("convertSortedToNumberedPathnames");
+
+    if (!sa)
+        return (SARRAY *)ERROR_PTR("sa not defined", procName, NULL);
+    if ((nfiles = sarrayGetCount(sa)) == 0)
+        return sarrayCreate(1);
+
+        /* Find the last file in the sorted array that has a number
+         * that (a) matches the count pattern and (b) does not
+         * exceed @maxnum.  @maxnum sets an upper limit on the size
+         * of the sarray.  */
+    num = 0;
+    for (i = nfiles - 1; i >= 0; i--) {
+      fname = sarrayGetString(sa, i, L_NOCOPY);
+      num = extractNumberFromFilename(fname, numpre, numpost);
+      if (num < 0) continue;
+      num = L_MIN(num + 1, maxnum);
+      break;
+    }
+
+    if (num <= 0)  /* none found */
+        return sarrayCreate(1);
+
+        /* Insert pathnames into the output sarray.
+         * Ignore numbers that are out of the range of sarray. */
+    saout = sarrayCreateInitialized(num, (char *)"");
+    for (i = 0; i < nfiles; i++) {
+      fname = sarrayGetString(sa, i, L_NOCOPY);
+      index = extractNumberFromFilename(fname, numpre, numpost);
+      if (index < 0 || index >= num) continue;
+      str = sarrayGetString(saout, index, L_NOCOPY);
+      if (str[0] != '\0')
+          L_WARNING("\n  Multiple files with same number: %d\n",
+                    procName, index);
+      sarrayReplaceString(saout, index, fname, L_COPY);
+    }
+
+    return saout;
+}
+
+
+/*!
+ *  getFilenamesInDirectory()
+ *
+ *      Input:  directory name
+ *      Return: sarray of file names, or NULL on error
+ *
+ *  Notes:
+ *      (1) The versions compiled under unix and cygwin use the POSIX C
+ *          library commands for handling directories.  For windows,
+ *          there is a separate implementation.
+ *      (2) It returns an array of filename tails; i.e., only the part of
+ *          the path after the last slash.
+ *      (3) Use of the d_type field of dirent is not portable:
+ *          "According to POSIX, the dirent structure contains a field
+ *          char d_name[] of unspecified size, with at most NAME_MAX
+ *          characters preceding the terminating null character.  Use
+ *          of other fields will harm the portability of your programs."
+ *      (4) As a consequence of (3), we note several things:
+ *           - MINGW doesn't have a d_type member.
+ *           - Older versions of gcc (e.g., 2.95.3) return DT_UNKNOWN
+ *             for d_type from all files.
+ *          On these systems, this function will return directories
+ *          (except for '.' and '..', which are eliminated using
+ *          the d_name field).
+ */
+
+#ifndef _WIN32
+
+SARRAY *
+getFilenamesInDirectory(const char  *dirname)
+{
+char           *realdir, *name;
+l_int32         len;
+SARRAY         *safiles;
+DIR            *pdir;
+struct dirent  *pdirentry;
+
+    PROCNAME("getFilenamesInDirectory");
+
+    if (!dirname)
+        return (SARRAY *)ERROR_PTR("dirname not defined", procName, NULL);
+
+    realdir = genPathname(dirname, NULL);
+    pdir = opendir(realdir);
+    LEPT_FREE(realdir);
+    if (!pdir)
+        return (SARRAY *)ERROR_PTR("pdir not opened", procName, NULL);
+    if ((safiles = sarrayCreate(0)) == NULL)
+        return (SARRAY *)ERROR_PTR("safiles not made", procName, NULL);
+    while ((pdirentry = readdir(pdir))) {
+
+        /* It's nice to ignore directories.  For this it is necessary to
+         * define _BSD_SOURCE in the CC command, because the DT_DIR
+         * flag is non-standard.  */
+#if !defined(__SOLARIS__)
+        if (pdirentry->d_type == DT_DIR)
+            continue;
+#endif
+
+            /* Filter out "." and ".." if they're passed through */
+        name = pdirentry->d_name;
+        len = strlen(name);
+        if (len == 1 && name[len - 1] == '.') continue;
+        if (len == 2 && name[len - 1] == '.' && name[len - 2] == '.') continue;
+        sarrayAddString(safiles, name, L_COPY);
+    }
+    closedir(pdir);
+
+    return safiles;
+}
+
+#else  /* _WIN32 */
+
+    /* http://msdn2.microsoft.com/en-us/library/aa365200(VS.85).aspx */
+#include <windows.h>
+
+SARRAY *
+getFilenamesInDirectory(const char  *dirname)
+{
+char             *pszDir;
+char             *realdir;
+HANDLE            hFind = INVALID_HANDLE_VALUE;
+SARRAY           *safiles;
+WIN32_FIND_DATAA  ffd;
+
+    PROCNAME("getFilenamesInDirectory");
+
+    if (!dirname)
+        return (SARRAY *)ERROR_PTR("dirname not defined", procName, NULL);
+
+    realdir = genPathname(dirname, NULL);
+    pszDir = stringJoin(realdir, "\\*");
+    LEPT_FREE(realdir);
+
+    if (strlen(pszDir) + 1 > MAX_PATH) {
+        LEPT_FREE(pszDir);
+        return (SARRAY *)ERROR_PTR("dirname is too long", procName, NULL);
+    }
+
+    if ((safiles = sarrayCreate(0)) == NULL) {
+        LEPT_FREE(pszDir);
+        return (SARRAY *)ERROR_PTR("safiles not made", procName, NULL);
+    }
+
+    hFind = FindFirstFileA(pszDir, &ffd);
+    if (INVALID_HANDLE_VALUE == hFind) {
+        sarrayDestroy(&safiles);
+        LEPT_FREE(pszDir);
+        return (SARRAY *)ERROR_PTR("hFind not opened", procName, NULL);
+    }
+
+    while (FindNextFileA(hFind, &ffd) != 0) {
+        if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)  /* skip dirs */
+            continue;
+        convertSepCharsInPath(ffd.cFileName, UNIX_PATH_SEPCHAR);
+        sarrayAddString(safiles, ffd.cFileName, L_COPY);
+    }
+
+    FindClose(hFind);
+    LEPT_FREE(pszDir);
+    return safiles;
+}
+
+#endif  /* _WIN32 */
diff --git a/src/scale.c b/src/scale.c
new file mode 100644 (file)
index 0000000..29720cc
--- /dev/null
@@ -0,0 +1,3280 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  scale.c
+ *
+ *         Top-level scaling
+ *               PIX      *pixScale()     ***
+ *               PIX      *pixScaleToSize()     ***
+ *               PIX      *pixScaleGeneral()     ***
+ *
+ *         Linearly interpreted (usually up-) scaling
+ *               PIX      *pixScaleLI()     ***
+ *               PIX      *pixScaleColorLI()
+ *               PIX      *pixScaleColor2xLI()   ***
+ *               PIX      *pixScaleColor4xLI()   ***
+ *               PIX      *pixScaleGrayLI()
+ *               PIX      *pixScaleGray2xLI()
+ *               PIX      *pixScaleGray4xLI()
+ *
+ *         Scaling by closest pixel sampling
+ *               PIX      *pixScaleBySampling()
+ *               PIX      *pixScaleBySamplingToSize()
+ *               PIX      *pixScaleByIntSampling()
+ *
+ *         Fast integer factor subsampling RGB to gray and to binary
+ *               PIX      *pixScaleRGBToGrayFast()
+ *               PIX      *pixScaleRGBToBinaryFast()
+ *               PIX      *pixScaleGrayToBinaryFast()
+ *
+ *         Downscaling with (antialias) smoothing
+ *               PIX      *pixScaleSmooth() ***
+ *               PIX      *pixScaleRGBToGray2()   [special 2x reduction to gray]
+ *
+ *         Downscaling with (antialias) area mapping
+ *               PIX      *pixScaleAreaMap()     ***
+ *               PIX      *pixScaleAreaMap2()
+ *
+ *         Binary scaling by closest pixel sampling
+ *               PIX      *pixScaleBinary()
+ *
+ *         Scale-to-gray (1 bpp --> 8 bpp; arbitrary downscaling)
+ *               PIX      *pixScaleToGray()
+ *               PIX      *pixScaleToGrayFast()
+ *
+ *         Scale-to-gray (1 bpp --> 8 bpp; integer downscaling)
+ *               PIX      *pixScaleToGray2()
+ *               PIX      *pixScaleToGray3()
+ *               PIX      *pixScaleToGray4()
+ *               PIX      *pixScaleToGray6()
+ *               PIX      *pixScaleToGray8()
+ *               PIX      *pixScaleToGray16()
+ *
+ *         Scale-to-gray by mipmap(1 bpp --> 8 bpp, arbitrary reduction)
+ *               PIX      *pixScaleToGrayMipmap()
+ *
+ *         Grayscale scaling using mipmap
+ *               PIX      *pixScaleMipmap()
+ *
+ *         Replicated (integer) expansion (all depths)
+ *               PIX      *pixExpandReplicate()
+ *
+ *         Upscale 2x followed by binarization
+ *               PIX      *pixScaleGray2xLIThresh()
+ *               PIX      *pixScaleGray2xLIDither()
+ *
+ *         Upscale 4x followed by binarization
+ *               PIX      *pixScaleGray4xLIThresh()
+ *               PIX      *pixScaleGray4xLIDither()
+ *
+ *         Grayscale downscaling using min and max
+ *               PIX      *pixScaleGrayMinMax()
+ *               PIX      *pixScaleGrayMinMax2()
+ *
+ *         Grayscale downscaling using rank value
+ *               PIX      *pixScaleGrayRankCascade()
+ *               PIX      *pixScaleGrayRank2()
+ *
+ *         Helper function for transferring alpha with scaling
+ *               l_int32   pixScaleAndTransferAlpha()
+ *
+ *         RGB scaling including alpha (blend) component
+ *               PIX      *pixScaleWithAlpha()   ***
+ *
+ *  *** Note: these functions make an implicit assumption about RGB
+ *            component ordering.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+extern l_float32  AlphaMaskBorderVals[2];
+
+
+/*------------------------------------------------------------------*
+ *                    Top level scaling dispatcher                  *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixScale()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 and 32 bpp)
+ *              scalex, scaley
+ *      Return: pixd, or null on error
+ *
+ *  This function scales 32 bpp RGB; 2, 4 or 8 bpp palette color;
+ *  2, 4, 8 or 16 bpp gray; and binary images.
+ *
+ *  When the input has palette color, the colormap is removed and
+ *  the result is either 8 bpp gray or 32 bpp RGB, depending on whether
+ *  the colormap has color entries.  Images with 2, 4 or 16 bpp are
+ *  converted to 8 bpp.
+ *
+ *  Because pixScale() is meant to be a very simple interface to a
+ *  number of scaling functions, including the use of unsharp masking,
+ *  the type of scaling and the sharpening parameters are chosen
+ *  by default.  Grayscale and color images are scaled using one
+ *  of four methods, depending on the scale factors:
+ *   (1) antialiased subsampling (lowpass filtering followed by
+ *       subsampling, implemented here by area mapping), for scale factors
+ *       less than 0.2
+ *   (2) antialiased subsampling with sharpening, for scale factors
+ *       between 0.2 and 0.7
+ *   (3) linear interpolation with sharpening, for scale factors between
+ *       0.7 and 1.4
+ *   (4) linear interpolation without sharpening, for scale factors >= 1.4.
+ *
+ *  One could use subsampling for scale factors very close to 1.0,
+ *  because it preserves sharp edges.  Linear interpolation blurs
+ *  edges because the dest pixels will typically straddle two src edge
+ *  pixels.  Subsmpling removes entire columns and rows, so the edge is
+ *  not blurred.  However, there are two reasons for not doing this.
+ *  First, it moves edges, so that a straight line at a large angle to
+ *  both horizontal and vertical will have noticeable kinks where
+ *  horizontal and vertical rasters are removed.  Second, although it
+ *  is very fast, you get good results on sharp edges by applying
+ *  a sharpening filter.
+ *
+ *  For images with sharp edges, sharpening substantially improves the
+ *  image quality for scale factors between about 0.2 and about 2.0.
+ *  pixScale() uses a small amount of sharpening by default because
+ *  it strengthens edge pixels that are weak due to anti-aliasing.
+ *  The default sharpening factors are:
+ *      * for scaling factors < 0.7:   sharpfract = 0.2    sharpwidth = 1
+ *      * for scaling factors >= 0.7:  sharpfract = 0.4    sharpwidth = 2
+ *  The cases where the sharpening halfwidth is 1 or 2 have special
+ *  implementations and are about twice as fast as the general case.
+ *
+ *  However, sharpening is computationally expensive, and one needs
+ *  to consider the speed-quality tradeoff:
+ *      * For upscaling of RGB images, linear interpolation plus default
+ *        sharpening is about 5 times slower than upscaling alone.
+ *      * For downscaling, area mapping plus default sharpening is
+ *        about 10 times slower than downscaling alone.
+ *  When the scale factor is larger than 1.4, the cost of sharpening,
+ *  which is proportional to image area, is very large compared to the
+ *  incremental quality improvement, so we cut off the default use of
+ *  sharpening at 1.4.  Thus, for scale factors greater than 1.4,
+ *  pixScale() only does linear interpolation.
+ *
+ *  In many situations you will get a satisfactory result by scaling
+ *  without sharpening: call pixScaleGeneral() with @sharpfract = 0.0.
+ *  Alternatively, if you wish to sharpen but not use the default
+ *  value, first call pixScaleGeneral() with @sharpfract = 0.0, and
+ *  then sharpen explicitly using pixUnsharpMasking().
+ *
+ *  Binary images are scaled to binary by sampling the closest pixel,
+ *  without any low-pass filtering (averaging of neighboring pixels).
+ *  This will introduce aliasing for reductions.  Aliasing can be
+ *  prevented by using pixScaleToGray() instead.
+ *
+ *  *** Warning: implicit assumption about RGB component order
+ *               for LI color scaling
+ */
+PIX *
+pixScale(PIX       *pixs,
+         l_float32  scalex,
+         l_float32  scaley)
+{
+l_int32    sharpwidth;
+l_float32  maxscale, sharpfract;
+
+    PROCNAME("pixScale");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+        /* Reduce the default sharpening factors by 2 if maxscale < 0.7 */
+    maxscale = L_MAX(scalex, scaley);
+    sharpfract = (maxscale < 0.7) ? 0.2 : 0.4;
+    sharpwidth = (maxscale < 0.7) ? 1 : 2;
+
+    return pixScaleGeneral(pixs, scalex, scaley, sharpfract, sharpwidth);
+}
+
+
+/*!
+ *  pixScaleToSize()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 and 32 bpp)
+ *              wd  (target width; use 0 if using height as target)
+ *              hd  (target height; use 0 if using width as target)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This guarantees that the output scaled image has the
+ *          dimension(s) you specify.
+ *           - To specify the width with isotropic scaling, set @hd = 0.
+ *           - To specify the height with isotropic scaling, set @wd = 0.
+ *           - If both @wd and @hd are specified, the image is scaled
+ *             (in general, anisotropically) to that size.
+ *           - It is an error to set both @wd and @hd to 0.
+ */
+PIX *
+pixScaleToSize(PIX     *pixs,
+               l_int32  wd,
+               l_int32  hd)
+{
+l_int32    w, h;
+l_float32  scalex, scaley;
+
+    PROCNAME("pixScaleToSize");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (wd <= 0 && hd <= 0)
+        return (PIX *)ERROR_PTR("neither wd nor hd > 0", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (wd <= 0) {
+        scaley = (l_float32)hd / (l_float32)h;
+        scalex = scaley;
+    } else if (hd <= 0) {
+        scalex = (l_float32)wd / (l_float32)w;
+        scaley = scalex;
+    } else {
+        scalex = (l_float32)wd / (l_float32)w;
+        scaley = (l_float32)hd / (l_float32)h;
+    }
+
+    return pixScale(pixs, scalex, scaley);
+}
+
+
+/*!
+ *  pixScaleGeneral()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 and 32 bpp)
+ *              scalex, scaley (both > 0.0)
+ *              sharpfract (use 0.0 to skip sharpening)
+ *              sharpwidth (halfwidth of low-pass filter; typ. 1 or 2)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) See pixScale() for usage.
+ *      (2) This interface may change in the future, as other special
+ *          cases are added.
+ *      (3) The actual sharpening factors used depend on the maximum
+ *          of the two scale factors (maxscale):
+ *            maxscale <= 0.2:        no sharpening
+ *            0.2 < maxscale < 1.4:   uses the input parameters
+ *            maxscale >= 1.4:        no sharpening
+ *      (4) To avoid sharpening for grayscale and color images with
+ *          scaling factors between 0.2 and 1.4, call this function
+ *          with @sharpfract == 0.0.
+ *      (5) To use arbitrary sharpening in conjunction with scaling,
+ *          call this function with @sharpfract = 0.0, and follow this
+ *          with a call to pixUnsharpMasking() with your chosen parameters.
+ */
+PIX *
+pixScaleGeneral(PIX       *pixs,
+                l_float32  scalex,
+                l_float32  scaley,
+                l_float32  sharpfract,
+                l_int32    sharpwidth)
+{
+l_int32    d;
+l_float32  maxscale;
+PIX       *pixt, *pixt2, *pixd;
+
+    PROCNAME("pixScaleGeneral");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not {1,2,4,8,16,32} bpp", procName, NULL);
+    if (scalex <= 0.0 || scaley <= 0.0)
+        return (PIX *)ERROR_PTR("scale factor <= 0", procName, NULL);
+    if (scalex == 1.0 && scaley == 1.0)
+        return pixCopy(NULL, pixs);
+
+    if (d == 1)
+        return pixScaleBinary(pixs, scalex, scaley);
+
+        /* Remove colormap; clone if possible; result is either 8 or 32 bpp */
+    if ((pixt = pixConvertTo8Or32(pixs, 0, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+        /* Scale (up or down) */
+    d = pixGetDepth(pixt);
+    maxscale = L_MAX(scalex, scaley);
+    if (maxscale < 0.7) {  /* area mapping for anti-aliasing */
+        pixt2 = pixScaleAreaMap(pixt, scalex, scaley);
+        if (maxscale > 0.2 && sharpfract > 0.0 && sharpwidth > 0)
+            pixd = pixUnsharpMasking(pixt2, sharpwidth, sharpfract);
+        else
+            pixd = pixClone(pixt2);
+    } else {  /* use linear interpolation */
+        if (d == 8)
+            pixt2 = pixScaleGrayLI(pixt, scalex, scaley);
+        else  /* d == 32 */
+            pixt2 = pixScaleColorLI(pixt, scalex, scaley);
+        if (maxscale < 1.4 && sharpfract > 0.0 && sharpwidth > 0)
+            pixd = pixUnsharpMasking(pixt2, sharpwidth, sharpfract);
+        else
+            pixd = pixClone(pixt2);
+    }
+
+    pixDestroy(&pixt);
+    pixDestroy(&pixt2);
+    pixCopyInputFormat(pixd, pixs);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                  Scaling by linear interpolation                 *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixScaleLI()
+ *
+ *      Input:  pixs (2, 4, 8 or 32 bpp; with or without colormap)
+ *              scalex, scaley (must both be >= 0.7)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This function should only be used when the scale factors are
+ *          greater than or equal to 0.7, and typically greater than 1.
+ *          If either scale factor is smaller than 0.7, we issue a warning
+ *          and invoke pixScale().
+ *      (2) This works on 2, 4, 8, 16 and 32 bpp images, as well as on
+ *          2, 4 and 8 bpp images that have a colormap.  If there is a
+ *          colormap, it is removed to either gray or RGB, depending
+ *          on the colormap.
+ *      (3) This does a linear interpolation on the src image.
+ *      (4) It dispatches to much faster implementations for
+ *          the special cases of 2x and 4x expansion.
+ *
+ *  *** Warning: implicit assumption about RGB component ordering ***
+ */
+PIX *
+pixScaleLI(PIX       *pixs,
+           l_float32  scalex,
+           l_float32  scaley)
+{
+l_int32    d;
+l_float32  maxscale;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixScaleLI");
+
+    if (!pixs || (pixGetDepth(pixs) == 1))
+        return (PIX *)ERROR_PTR("pixs not defined or 1 bpp", procName, NULL);
+    maxscale = L_MAX(scalex, scaley);
+    if (maxscale < 0.7) {
+        L_WARNING("scaling factors < 0.7; do regular scaling\n", procName);
+        return pixScale(pixs, scalex, scaley);
+    }
+    d = pixGetDepth(pixs);
+    if (d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not {2,4,8,16,32} bpp", procName, NULL);
+
+        /* Remove colormap; clone if possible; result is either 8 or 32 bpp */
+    if ((pixt = pixConvertTo8Or32(pixs, 0, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+
+    d = pixGetDepth(pixt);
+    if (d == 8)
+        pixd = pixScaleGrayLI(pixt, scalex, scaley);
+    else if (d == 32)
+        pixd = pixScaleColorLI(pixt, scalex, scaley);
+
+    pixDestroy(&pixt);
+    pixCopyInputFormat(pixd, pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleColorLI()
+ *
+ *      Input:  pixs  (32 bpp, representing rgb)
+ *              scalex, scaley (must both be >= 0.7)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) If this is used for scale factors less than 0.7,
+ *          it will suffer from antialiasing.  A warning is issued.
+ *          Particularly for document images with sharp edges,
+ *          use pixScaleSmooth() or pixScaleAreaMap() instead.
+ *      (2) For the general case, it's about 4x faster to manipulate
+ *          the color pixels directly, rather than to make images
+ *          out of each of the 3 components, scale each component
+ *          using the pixScaleGrayLI(), and combine the results back
+ *          into an rgb image.
+ *      (3) The speed on intel hardware for the general case (not 2x)
+ *          is about 10 * 10^6 dest-pixels/sec/GHz.  (The special 2x
+ *          case runs at about 80 * 10^6 dest-pixels/sec/GHz.)
+ */
+PIX *
+pixScaleColorLI(PIX      *pixs,
+               l_float32  scalex,
+               l_float32  scaley)
+{
+l_int32    ws, hs, wpls, wd, hd, wpld;
+l_uint32  *datas, *datad;
+l_float32  maxscale;
+PIX       *pixd;
+
+    PROCNAME("pixScaleColorLI");
+
+    if (!pixs || (pixGetDepth(pixs) != 32))
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+    maxscale = L_MAX(scalex, scaley);
+    if (maxscale < 0.7) {
+        L_WARNING("scaling factors < 0.7; do regular scaling\n", procName);
+        return pixScale(pixs, scalex, scaley);
+    }
+
+        /* Do fast special cases if possible */
+    if (scalex == 1.0 && scaley == 1.0)
+        return pixCopy(NULL, pixs);
+    if (scalex == 2.0 && scaley == 2.0)
+        return pixScaleColor2xLI(pixs);
+    if (scalex == 4.0 && scaley == 4.0)
+        return pixScaleColor4xLI(pixs);
+
+        /* General case */
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    wd = (l_int32)(scalex * (l_float32)ws + 0.5);
+    hd = (l_int32)(scaley * (l_float32)hs + 0.5);
+    if ((pixd = pixCreate(wd, hd, 32)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, scalex, scaley);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    scaleColorLILow(datad, wd, hd, wpld, datas, ws, hs, wpls);
+    if (pixGetSpp(pixs) == 4)
+        pixScaleAndTransferAlpha(pixd, pixs, scalex, scaley);
+
+    pixCopyInputFormat(pixd, pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleColor2xLI()
+ *
+ *      Input:  pixs  (32 bpp, representing rgb)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This is a special case of linear interpolated scaling,
+ *          for 2x upscaling.  It is about 8x faster than using
+ *          the generic pixScaleColorLI(), and about 4x faster than
+ *          using the special 2x scale function pixScaleGray2xLI()
+ *          on each of the three components separately.
+ *      (2) The speed on intel hardware is about
+ *          80 * 10^6 dest-pixels/sec/GHz.
+ *
+ *  *** Warning: implicit assumption about RGB component ordering ***
+ */
+PIX *
+pixScaleColor2xLI(PIX  *pixs)
+{
+l_int32    ws, hs, wpls, wpld;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleColor2xLI");
+
+    if (!pixs || (pixGetDepth(pixs) != 32))
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if ((pixd = pixCreate(2 * ws, 2 * hs, 32)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 2.0, 2.0);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    scaleColor2xLILow(datad, wpld, datas, ws, hs, wpls);
+    if (pixGetSpp(pixs) == 4)
+        pixScaleAndTransferAlpha(pixd, pixs, 2.0, 2.0);
+
+    pixCopyInputFormat(pixd, pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleColor4xLI()
+ *
+ *      Input:  pixs  (32 bpp, representing rgb)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This is a special case of color linear interpolated scaling,
+ *          for 4x upscaling.  It is about 3x faster than using
+ *          the generic pixScaleColorLI().
+ *      (2) The speed on intel hardware is about
+ *          30 * 10^6 dest-pixels/sec/GHz
+ *      (3) This scales each component separately, using pixScaleGray4xLI().
+ *          It would be about 4x faster to inline the color code properly,
+ *          in analogy to scaleColor4xLILow(), and I leave this as
+ *          an exercise for someone who really needs it.
+ */
+PIX *
+pixScaleColor4xLI(PIX  *pixs)
+{
+PIX  *pixr, *pixg, *pixb;
+PIX  *pixrs, *pixgs, *pixbs;
+PIX  *pixd;
+
+    PROCNAME("pixScaleColor4xLI");
+
+    if (!pixs || (pixGetDepth(pixs) != 32))
+        return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL);
+
+    pixr = pixGetRGBComponent(pixs, COLOR_RED);
+    pixrs = pixScaleGray4xLI(pixr);
+    pixDestroy(&pixr);
+    pixg = pixGetRGBComponent(pixs, COLOR_GREEN);
+    pixgs = pixScaleGray4xLI(pixg);
+    pixDestroy(&pixg);
+    pixb = pixGetRGBComponent(pixs, COLOR_BLUE);
+    pixbs = pixScaleGray4xLI(pixb);
+    pixDestroy(&pixb);
+
+    if ((pixd = pixCreateRGBImage(pixrs, pixgs, pixbs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    if (pixGetSpp(pixs) == 4)
+        pixScaleAndTransferAlpha(pixd, pixs, 4.0, 4.0);
+    pixCopyInputFormat(pixd, pixs);
+
+    pixDestroy(&pixrs);
+    pixDestroy(&pixgs);
+    pixDestroy(&pixbs);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleGrayLI()
+ *
+ *      Input:  pixs (8 bpp grayscale, no cmap)
+ *              scalex, scaley (must both be >= 0.7)
+ *      Return: pixd, or null on error
+ *
+ *  This function is appropriate for upscaling
+ *  (magnification: scale factors > 1), and for a
+ *  small amount of downscaling (reduction: scale
+ *  factors > 0.5).   For scale factors less than 0.5,
+ *  the best result is obtained by area mapping,
+ *  but this is very expensive.  So for such large
+ *  reductions, it is more appropriate to do low pass
+ *  filtering followed by subsampling, a combination
+ *  which is effectively a cheap form of area mapping.
+ *
+ *  Some details follow.
+ *
+ *  For each pixel in the dest, this does a linear
+ *  interpolation of 4 neighboring pixels in the src.
+ *  Specifically, consider the UL corner of src and
+ *  dest pixels.  The UL corner of the dest falls within
+ *  a src pixel, whose four corners are the UL corners
+ *  of 4 adjacent src pixels.  The value of the dest
+ *  is taken by linear interpolation using the values of
+ *  the four src pixels and the distance of the UL corner
+ *  of the dest from each corner.
+ *
+ *  If the image is expanded so that the dest pixel is
+ *  smaller than the src pixel, such interpolation
+ *  is a reasonable approach.  This interpolation is
+ *  also good for a small image reduction factor that
+ *  is not more than a 2x reduction.
+ *
+ *  Note that the linear interpolation algorithm for scaling
+ *  is identical in form to the area-mapping algorithm
+ *  for grayscale rotation.  The latter corresponds to a
+ *  translation of each pixel without scaling.
+ *
+ *  This function is NOT optimal if the scaling involves
+ *  a large reduction.    If the image is significantly
+ *  reduced, so that the dest pixel is much larger than
+ *  the src pixels, this interpolation, which is over src
+ *  pixels only near the UL corner of the dest pixel,
+ *  is not going to give a good area-mapping average.
+ *  Because area mapping for image scaling is considerably
+ *  more computationally intensive than linear interpolation,
+ *  we choose not to use it.   For large image reduction,
+ *  linear interpolation over adjacent src pixels
+ *  degenerates asymptotically to subsampling.  But
+ *  subsampling without a low-pass pre-filter causes
+ *  aliasing by the nyquist theorem.  To avoid aliasing,
+ *  a low-pass filter (e.g., an averaging filter) of
+ *  size roughly equal to the dest pixel (i.e., the
+ *  reduction factor) should be applied to the src before
+ *  subsampling.
+ *
+ *  As an alternative to low-pass filtering and subsampling
+ *  for large reduction factors, linear interpolation can
+ *  also be done between the (widely separated) src pixels in
+ *  which the corners of the dest pixel lie.  This also is
+ *  not optimal, as it samples src pixels only near the
+ *  corners of the dest pixel, and it is not implemented.
+ *
+ *  Summary:
+ *    (1) If this is used for scale factors less than 0.7,
+ *        it will suffer from antialiasing.  A warning is issued.
+ *        Particularly for document images with sharp edges,
+ *        use pixScaleSmooth() or pixScaleAreaMap() instead.
+ *    (2) The speed on intel hardware for the general case (not 2x)
+ *        is about 13 * 10^6 dest-pixels/sec/GHz.  (The special 2x
+ *        case runs at about 100 * 10^6 dest-pixels/sec/GHz.)
+ */
+PIX *
+pixScaleGrayLI(PIX       *pixs,
+               l_float32  scalex,
+               l_float32  scaley)
+{
+l_int32    ws, hs, wpls, wd, hd, wpld;
+l_uint32  *datas, *datad;
+l_float32  maxscale;
+PIX       *pixd;
+
+    PROCNAME("pixScaleGrayLI");
+
+    if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs undefined, cmapped or not 8 bpp",
+                                procName, NULL);
+    maxscale = L_MAX(scalex, scaley);
+    if (maxscale < 0.7) {
+        L_WARNING("scaling factors < 0.7; do regular scaling\n", procName);
+        return pixScale(pixs, scalex, scaley);
+    }
+
+        /* Do fast special cases if possible */
+    if (scalex == 1.0 && scaley == 1.0)
+        return pixCopy(NULL, pixs);
+    if (scalex == 2.0 && scaley == 2.0)
+        return pixScaleGray2xLI(pixs);
+    if (scalex == 4.0 && scaley == 4.0)
+        return pixScaleGray4xLI(pixs);
+
+        /* General case */
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    wd = (l_int32)(scalex * (l_float32)ws + 0.5);
+    hd = (l_int32)(scaley * (l_float32)hs + 0.5);
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyText(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    pixScaleResolution(pixd, scalex, scaley);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    scaleGrayLILow(datad, wd, hd, wpld, datas, ws, hs, wpls);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleGray2xLI()
+ *
+ *      Input:  pixs (8 bpp grayscale, not cmapped)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This is a special case of gray linear interpolated scaling,
+ *          for 2x upscaling.  It is about 6x faster than using
+ *          the generic pixScaleGrayLI().
+ *      (2) The speed on intel hardware is about
+ *          100 * 10^6 dest-pixels/sec/GHz
+ */
+PIX *
+pixScaleGray2xLI(PIX  *pixs)
+{
+l_int32    ws, hs, wpls, wpld;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleGray2xLI");
+
+    if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs undefined, cmapped or not 8 bpp",
+                                procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if ((pixd = pixCreate(2 * ws, 2 * hs, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    pixScaleResolution(pixd, 2.0, 2.0);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    scaleGray2xLILow(datad, wpld, datas, ws, hs, wpls);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleGray4xLI()
+ *
+ *      Input:  pixs (8 bpp grayscale, not cmapped)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This is a special case of gray linear interpolated scaling,
+ *          for 4x upscaling.  It is about 12x faster than using
+ *          the generic pixScaleGrayLI().
+ *      (2) The speed on intel hardware is about
+ *          160 * 10^6 dest-pixels/sec/GHz.
+ */
+PIX *
+pixScaleGray4xLI(PIX  *pixs)
+{
+l_int32    ws, hs, wpls, wpld;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleGray4xLI");
+
+    if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs undefined, cmapped or not 8 bpp",
+                                procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    if ((pixd = pixCreate(4 * ws, 4 * hs, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    pixScaleResolution(pixd, 4.0, 4.0);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    scaleGray4xLILow(datad, wpld, datas, ws, hs, wpls);
+    return pixd;
+}
+
+
+
+/*------------------------------------------------------------------*
+ *                  Scaling by closest pixel sampling               *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixScaleBySampling()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16, 32 bpp)
+ *              scalex, scaley (both > 0.0)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This function samples from the source without
+ *          filtering.  As a result, aliasing will occur for
+ *          subsampling (@scalex and/or @scaley < 1.0).
+ *      (2) If @scalex == 1.0 and @scaley == 1.0, returns a copy.
+ */
+PIX *
+pixScaleBySampling(PIX       *pixs,
+                   l_float32  scalex,
+                   l_float32  scaley)
+{
+l_int32    ws, hs, d, wpls, wd, hd, wpld;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleBySampling");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (scalex <= 0.0 || scaley <= 0.0)
+        return (PIX *)ERROR_PTR("scale factor <= 0", procName, NULL);
+    if (scalex == 1.0 && scaley == 1.0)
+        return pixCopy(NULL, pixs);
+    if ((d = pixGetDepth(pixs)) == 1)
+        return pixScaleBinary(pixs, scalex, scaley);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    wd = (l_int32)(scalex * (l_float32)ws + 0.5);
+    hd = (l_int32)(scaley * (l_float32)hs + 0.5);
+    if ((pixd = pixCreate(wd, hd, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, scalex, scaley);
+    pixCopyColormap(pixd, pixs);
+    pixCopyText(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopySpp(pixd, pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    scaleBySamplingLow(datad, wd, hd, wpld, datas, ws, hs, d, wpls);
+    if (d == 32 && pixGetSpp(pixs) == 4)
+        pixScaleAndTransferAlpha(pixd, pixs, scalex, scaley);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleBySamplingToSize()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16 and 32 bpp)
+ *              wd  (target width; use 0 if using height as target)
+ *              hd  (target height; use 0 if using width as target)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This guarantees that the output scaled image has the
+ *          dimension(s) you specify.
+ *           - To specify the width with isotropic scaling, set @hd = 0.
+ *           - To specify the height with isotropic scaling, set @wd = 0.
+ *           - If both @wd and @hd are specified, the image is scaled
+ *             (in general, anisotropically) to that size.
+ *           - It is an error to set both @wd and @hd to 0.
+ */
+PIX *
+pixScaleBySamplingToSize(PIX     *pixs,
+                         l_int32  wd,
+                         l_int32  hd)
+{
+l_int32    w, h;
+l_float32  scalex, scaley;
+
+    PROCNAME("pixScaleBySamplingToSize");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (wd <= 0 && hd <= 0)
+        return (PIX *)ERROR_PTR("neither wd nor hd > 0", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (wd <= 0) {
+        scaley = (l_float32)hd / (l_float32)h;
+        scalex = scaley;
+    } else if (hd <= 0) {
+        scalex = (l_float32)wd / (l_float32)w;
+        scaley = scalex;
+    } else {
+        scalex = (l_float32)wd / (l_float32)w;
+        scaley = (l_float32)hd / (l_float32)h;
+    }
+
+    return pixScaleBySampling(pixs, scalex, scaley);
+}
+
+
+/*!
+ *  pixScaleByIntSampling()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16, 32 bpp)
+ *              factor (integer subsampling)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) Simple interface to pixScaleBySampling(), for
+ *          isotropic integer reduction.
+ *      (2) If @factor == 1, returns a copy.
+ */
+PIX *
+pixScaleByIntSampling(PIX     *pixs,
+                      l_int32  factor)
+{
+l_float32  scale;
+
+    PROCNAME("pixScaleByIntSampling");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (factor <= 1) {
+        if (factor < 1)
+            L_ERROR("factor must be >= 1; returning a copy\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+    scale = 1. / (l_float32)factor;
+    return pixScaleBySampling(pixs, scale, scale);
+}
+
+
+/*------------------------------------------------------------------*
+ *            Fast integer factor subsampling RGB to gray           *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixScaleRGBToGrayFast()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              factor (integer reduction factor >= 1)
+ *              color (one of COLOR_RED, COLOR_GREEN, COLOR_BLUE)
+ *      Return: pixd (8 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This does simultaneous subsampling by an integer factor and
+ *          extraction of the color from the RGB pix.
+ *      (2) It is designed for maximum speed, and is used for quickly
+ *          generating a downsized grayscale image from a higher resolution
+ *          RGB image.  This would typically be used for image analysis.
+ *      (3) The standard color byte order (RGBA) is assumed.
+ */
+PIX *
+pixScaleRGBToGrayFast(PIX     *pixs,
+                      l_int32  factor,
+                      l_int32  color)
+{
+l_int32    byteval, shift;
+l_int32    i, j, ws, hs, wd, hd, wpls, wpld;
+l_uint32  *datas, *words, *datad, *lined;
+l_float32  scale;
+PIX       *pixd;
+
+    PROCNAME("pixScaleRGBToGrayFast");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("depth not 32 bpp", procName, NULL);
+    if (factor < 1)
+        return (PIX *)ERROR_PTR("factor must be >= 1", procName, NULL);
+
+    if (color == COLOR_RED)
+        shift = L_RED_SHIFT;
+    else if (color == COLOR_GREEN)
+        shift = L_GREEN_SHIFT;
+    else if (color == COLOR_BLUE)
+        shift = L_BLUE_SHIFT;
+    else
+        return (PIX *)ERROR_PTR("invalid color", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+
+    wd = ws / factor;
+    hd = hs / factor;
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    scale = 1. / (l_float32) factor;
+    pixScaleResolution(pixd, scale, scale);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    for (i = 0; i < hd; i++) {
+        words = datas + i * factor * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < wd; j++, words += factor) {
+            byteval = ((*words) >> shift) & 0xff;
+            SET_DATA_BYTE(lined, j, byteval);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleRGBToBinaryFast()
+ *
+ *      Input:  pixs (32 bpp RGB)
+ *              factor (integer reduction factor >= 1)
+ *              thresh (binarization threshold)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This does simultaneous subsampling by an integer factor and
+ *          conversion from RGB to gray to binary.
+ *      (2) It is designed for maximum speed, and is used for quickly
+ *          generating a downsized binary image from a higher resolution
+ *          RGB image.  This would typically be used for image analysis.
+ *      (3) It uses the green channel to represent the RGB pixel intensity.
+ */
+PIX *
+pixScaleRGBToBinaryFast(PIX     *pixs,
+                        l_int32  factor,
+                        l_int32  thresh)
+{
+l_int32    byteval;
+l_int32    i, j, ws, hs, wd, hd, wpls, wpld;
+l_uint32  *datas, *words, *datad, *lined;
+l_float32  scale;
+PIX       *pixd;
+
+    PROCNAME("pixScaleRGBToBinaryFast");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (factor < 1)
+        return (PIX *)ERROR_PTR("factor must be >= 1", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("depth not 32 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+
+    wd = ws / factor;
+    hd = hs / factor;
+    if ((pixd = pixCreate(wd, hd, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    scale = 1. / (l_float32) factor;
+    pixScaleResolution(pixd, scale, scale);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    for (i = 0; i < hd; i++) {
+        words = datas + i * factor * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < wd; j++, words += factor) {
+            byteval = ((*words) >> L_GREEN_SHIFT) & 0xff;
+            if (byteval < thresh)
+                SET_DATA_BIT(lined, j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleGrayToBinaryFast()
+ *
+ *      Input:  pixs (8 bpp grayscale)
+ *              factor (integer reduction factor >= 1)
+ *              thresh (binarization threshold)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This does simultaneous subsampling by an integer factor and
+ *          thresholding from gray to binary.
+ *      (2) It is designed for maximum speed, and is used for quickly
+ *          generating a downsized binary image from a higher resolution
+ *          gray image.  This would typically be used for image analysis.
+ */
+PIX *
+pixScaleGrayToBinaryFast(PIX     *pixs,
+                         l_int32  factor,
+                         l_int32  thresh)
+{
+l_int32    byteval;
+l_int32    i, j, ws, hs, wd, hd, wpls, wpld, sj;
+l_uint32  *datas, *datad, *lines, *lined;
+l_float32  scale;
+PIX       *pixd;
+
+    PROCNAME("pixScaleGrayToBinaryFast");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (factor < 1)
+        return (PIX *)ERROR_PTR("factor must be >= 1", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("depth not 8 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+
+    wd = ws / factor;
+    hd = hs / factor;
+    if ((pixd = pixCreate(wd, hd, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    scale = 1. / (l_float32) factor;
+    pixScaleResolution(pixd, scale, scale);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    for (i = 0; i < hd; i++) {
+        lines = datas + i * factor * wpls;
+        lined = datad + i * wpld;
+        for (j = 0, sj = 0; j < wd; j++, sj += factor) {
+            byteval = GET_DATA_BYTE(lines, sj);
+            if (byteval < thresh)
+                SET_DATA_BIT(lined, j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *               Downscaling with (antialias) smoothing             *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixScaleSmooth()
+ *
+ *      Input:  pixs (2, 4, 8 or 32 bpp; and 2, 4, 8 bpp with colormap)
+ *              scalex, scaley (must both be < 0.7)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This function should only be used when the scale factors are less
+ *          than or equal to 0.7 (i.e., more than about 1.42x reduction).
+ *          If either scale factor is larger than 0.7, we issue a warning
+ *          and invoke pixScale().
+ *      (2) This works only on 2, 4, 8 and 32 bpp images, and if there is
+ *          a colormap, it is removed by converting to RGB.  In other
+ *          cases, we issue a warning and invoke pixScale().
+ *      (3) It does simple (flat filter) convolution, with a filter size
+ *          commensurate with the amount of reduction, to avoid antialiasing.
+ *      (4) It does simple subsampling after smoothing, which is appropriate
+ *          for this range of scaling.  Linear interpolation gives essentially
+ *          the same result with more computation for these scale factors,
+ *          so we don't use it.
+ *      (5) The result is the same as doing a full block convolution followed by
+ *          subsampling, but this is faster because the results of the block
+ *          convolution are only computed at the subsampling locations.
+ *          In fact, the computation time is approximately independent of
+ *          the scale factor, because the convolution kernel is adjusted
+ *          so that each source pixel is summed approximately once.
+ *
+ *  *** Warning: implicit assumption about RGB component ordering ***
+ */
+PIX *
+pixScaleSmooth(PIX       *pix,
+               l_float32  scalex,
+               l_float32  scaley)
+{
+l_int32    ws, hs, d, wd, hd, wpls, wpld, isize;
+l_uint32  *datas, *datad;
+l_float32  minscale, size;
+PIX       *pixs, *pixd;
+
+    PROCNAME("pixScaleSmooth");
+
+    if (!pix)
+        return (PIX *)ERROR_PTR("pix not defined", procName, NULL);
+    if (scalex >= 0.7 || scaley >= 0.7) {
+        L_WARNING("scaling factor not < 0.7; do regular scaling\n", procName);
+        return pixScale(pix, scalex, scaley);
+    }
+
+        /* Remove colormap if necessary.
+         * If 2 bpp or 4 bpp gray, convert to 8 bpp */
+    d = pixGetDepth(pix);
+    if ((d == 2 || d == 4 || d == 8) && pixGetColormap(pix)) {
+        L_WARNING("pix has colormap; removing\n", procName);
+        pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+        d = pixGetDepth(pixs);
+    } else if (d == 2 || d == 4) {
+        pixs = pixConvertTo8(pix, FALSE);
+        d = 8;
+    } else {
+        pixs = pixClone(pix);
+    }
+
+    if (d != 8 && d != 32) {   /* d == 1 or d == 16 */
+        L_WARNING("depth not 8 or 32 bpp; do regular scaling\n", procName);
+        pixDestroy(&pixs);
+        return pixScale(pix, scalex, scaley);
+    }
+
+        /* If 1.42 < 1/minscale < 2.5, use isize = 2
+         * If 2.5 =< 1/minscale < 3.5, use isize = 3, etc.
+         * Under no conditions use isize < 2  */
+    minscale = L_MIN(scalex, scaley);
+    size = 1.0 / minscale;   /* ideal filter full width */
+    isize = L_MAX(2, (l_int32)(size + 0.5));
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    if ((ws < isize) || (hs < isize)) {
+        pixDestroy(&pixs);
+        return (PIX *)ERROR_PTR("pixs too small", procName, NULL);
+    }
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    wd = (l_int32)(scalex * (l_float32)ws + 0.5);
+    hd = (l_int32)(scaley * (l_float32)hs + 0.5);
+    if (wd < 1 || hd < 1) {
+        pixDestroy(&pixs);
+        return (PIX *)ERROR_PTR("pixd too small", procName, NULL);
+    }
+    if ((pixd = pixCreate(wd, hd, d)) == NULL) {
+        pixDestroy(&pixs);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    pixScaleResolution(pixd, scalex, scaley);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    scaleSmoothLow(datad, wd, hd, wpld, datas, ws, hs, d, wpls, isize);
+    if (d == 32 && pixGetSpp(pixs) == 4)
+        pixScaleAndTransferAlpha(pixd, pixs, scalex, scaley);
+
+    pixDestroy(&pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleRGBToGray2()
+ *
+ *      Input:  pixs (32 bpp rgb)
+ *              rwt, gwt, bwt (must sum to 1.0)
+ *      Return: pixd, (8 bpp, 2x reduced), or null on error
+ */
+PIX *
+pixScaleRGBToGray2(PIX       *pixs,
+                   l_float32  rwt,
+                   l_float32  gwt,
+                   l_float32  bwt)
+{
+l_int32    wd, hd, wpls, wpld;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleRGBToGray2");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 32)
+        return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL);
+    if (rwt + gwt + bwt < 0.98 || rwt + gwt + bwt > 1.02)
+        return (PIX *)ERROR_PTR("sum of wts should be 1.0", procName, NULL);
+
+    wd = pixGetWidth(pixs) / 2;
+    hd = pixGetHeight(pixs) / 2;
+    wpls = pixGetWpl(pixs);
+    datas = pixGetData(pixs);
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyResolution(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    pixScaleResolution(pixd, 0.5, 0.5);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+    scaleRGBToGray2Low(datad, wd, hd, wpld, datas, wpls, rwt, gwt, bwt);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *             Downscaling with (antialias) area mapping            *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixScaleAreaMap()
+ *
+ *      Input:  pixs (2, 4, 8 or 32 bpp; and 2, 4, 8 bpp with colormap)
+ *              scalex, scaley (must both be <= 0.7)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This function should only be used when the scale factors are less
+ *          than or equal to 0.7 (i.e., more than about 1.42x reduction).
+ *          If either scale factor is larger than 0.7, we issue a warning
+ *          and invoke pixScale().
+ *      (2) This works only on 2, 4, 8 and 32 bpp images.  If there is
+ *          a colormap, it is removed by converting to RGB.  In other
+ *          cases, we issue a warning and invoke pixScale().
+ *      (3) It does a relatively expensive area mapping computation, to
+ *          avoid antialiasing.  It is about 2x slower than pixScaleSmooth(),
+ *          but the results are much better on fine text.
+ *      (4) This is typically about 20% faster for the special cases of
+ *          2x, 4x, 8x and 16x reduction.
+ *      (5) Surprisingly, there is no speedup (and a slight quality
+ *          impairment) if you do as many successive 2x reductions as
+ *          possible, ending with a reduction with a scale factor larger
+ *          than 0.5.
+ *
+ *  *** Warning: implicit assumption about RGB component ordering ***
+ */
+PIX *
+pixScaleAreaMap(PIX       *pix,
+                l_float32  scalex,
+                l_float32  scaley)
+{
+l_int32    ws, hs, d, wd, hd, wpls, wpld;
+l_uint32  *datas, *datad;
+l_float32  maxscale;
+PIX       *pixs, *pixd, *pixt1, *pixt2, *pixt3;
+
+    PROCNAME("pixScaleAreaMap");
+
+    if (!pix)
+        return (PIX *)ERROR_PTR("pix not defined", procName, NULL);
+    d = pixGetDepth(pix);
+    if (d != 2 && d != 4 && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pix not 2, 4, 8 or 32 bpp", procName, NULL);
+    maxscale = L_MAX(scalex, scaley);
+    if (maxscale >= 0.7) {
+        L_WARNING("scaling factors not < 0.7; do regular scaling\n", procName);
+        return pixScale(pix, scalex, scaley);
+    }
+
+        /* Special cases: 2x, 4x, 8x, 16x reduction */
+    if (scalex == 0.5 && scaley == 0.5)
+        return pixScaleAreaMap2(pix);
+    if (scalex == 0.25 && scaley == 0.25) {
+        pixt1 = pixScaleAreaMap2(pix);
+        pixd = pixScaleAreaMap2(pixt1);
+        pixDestroy(&pixt1);
+        return pixd;
+    }
+    if (scalex == 0.125 && scaley == 0.125) {
+        pixt1 = pixScaleAreaMap2(pix);
+        pixt2 = pixScaleAreaMap2(pixt1);
+        pixd = pixScaleAreaMap2(pixt2);
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt2);
+        return pixd;
+    }
+    if (scalex == 0.0625 && scaley == 0.0625) {
+        pixt1 = pixScaleAreaMap2(pix);
+        pixt2 = pixScaleAreaMap2(pixt1);
+        pixt3 = pixScaleAreaMap2(pixt2);
+        pixd = pixScaleAreaMap2(pixt3);
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt2);
+        pixDestroy(&pixt3);
+        return pixd;
+    }
+
+        /* Remove colormap if necessary.
+         * If 2 bpp or 4 bpp gray, convert to 8 bpp */
+    if ((d == 2 || d == 4 || d == 8) && pixGetColormap(pix)) {
+        L_WARNING("pix has colormap; removing\n", procName);
+        pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+        d = pixGetDepth(pixs);
+    } else if (d == 2 || d == 4) {
+        pixs = pixConvertTo8(pix, FALSE);
+        d = 8;
+    } else {
+        pixs = pixClone(pix);
+    }
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    wd = (l_int32)(scalex * (l_float32)ws + 0.5);
+    hd = (l_int32)(scaley * (l_float32)hs + 0.5);
+    if (wd < 1 || hd < 1) {
+        pixDestroy(&pixs);
+        return (PIX *)ERROR_PTR("pixd too small", procName, NULL);
+    }
+    if ((pixd = pixCreate(wd, hd, d)) == NULL) {
+        pixDestroy(&pixs);
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    }
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, scalex, scaley);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    if (d == 8) {
+        scaleGrayAreaMapLow(datad, wd, hd, wpld, datas, ws, hs, wpls);
+    } else {  /* RGB, d == 32 */
+        scaleColorAreaMapLow(datad, wd, hd, wpld, datas, ws, hs, wpls);
+        if (pixGetSpp(pixs) == 4)
+            pixScaleAndTransferAlpha(pixd, pixs, scalex, scaley);
+    }
+
+    pixDestroy(&pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleAreaMap2()
+ *
+ *      Input:  pixs (2, 4, 8 or 32 bpp; and 2, 4, 8 bpp with colormap)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This function does an area mapping (average) for 2x
+ *          reduction.
+ *      (2) This works only on 2, 4, 8 and 32 bpp images.  If there is
+ *          a colormap, it is removed by converting to RGB.
+ *      (3) Speed on 3 GHz processor:
+ *             Color: 160 Mpix/sec
+ *             Gray: 700 Mpix/sec
+ *          This contrasts with the speed of the general pixScaleAreaMap():
+ *             Color: 35 Mpix/sec
+ *             Gray: 50 Mpix/sec
+ *      (4) From (3), we see that this special function is about 4.5x
+ *          faster for color and 14x faster for grayscale
+ *      (5) Consequently, pixScaleAreaMap2() is incorporated into the
+ *          general area map scaling function, for the special cases
+ *          of 2x, 4x, 8x and 16x reduction.
+ */
+PIX *
+pixScaleAreaMap2(PIX  *pix)
+{
+l_int32    wd, hd, d, wpls, wpld;
+l_uint32  *datas, *datad;
+PIX       *pixs, *pixd;
+
+    PROCNAME("pixScaleAreaMap2");
+
+    if (!pix)
+        return (PIX *)ERROR_PTR("pix not defined", procName, NULL);
+    d = pixGetDepth(pix);
+    if (d != 2 && d != 4 && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pix not 2, 4, 8 or 32 bpp", procName, NULL);
+
+        /* Remove colormap if necessary.
+         * If 2 bpp or 4 bpp gray, convert to 8 bpp */
+    if ((d == 2 || d == 4 || d == 8) && pixGetColormap(pix)) {
+        L_WARNING("pix has colormap; removing\n", procName);
+        pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+        d = pixGetDepth(pixs);
+    } else if (d == 2 || d == 4) {
+        pixs = pixConvertTo8(pix, FALSE);
+        d = 8;
+    } else {
+        pixs = pixClone(pix);
+    }
+
+    wd = pixGetWidth(pixs) / 2;
+    hd = pixGetHeight(pixs) / 2;
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    pixd = pixCreate(wd, hd, d);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 0.5, 0.5);
+    scaleAreaMapLow2(datad, wd, hd, wpld, datas, d, wpls);
+    if (pixGetSpp(pixs) == 4)
+        pixScaleAndTransferAlpha(pixd, pixs, 0.5, 0.5);
+    pixDestroy(&pixs);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *               Binary scaling by closest pixel sampling           *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixScaleBinary()
+ *
+ *      Input:  pixs (1 bpp)
+ *              scalex, scaley (both > 0.0)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This function samples from the source without
+ *          filtering.  As a result, aliasing will occur for
+ *          subsampling (scalex and scaley < 1.0).
+ */
+PIX *
+pixScaleBinary(PIX       *pixs,
+               l_float32  scalex,
+               l_float32  scaley)
+{
+l_int32    ws, hs, wpls, wd, hd, wpld;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleBinary");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, NULL);
+    if (scalex <= 0.0 || scaley <= 0.0)
+        return (PIX *)ERROR_PTR("scale factor <= 0", procName, NULL);
+    if (scalex == 1.0 && scaley == 1.0)
+        return pixCopy(NULL, pixs);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    wd = (l_int32)(scalex * (l_float32)ws + 0.5);
+    hd = (l_int32)(scaley * (l_float32)hs + 0.5);
+    if ((pixd = pixCreate(wd, hd, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyColormap(pixd, pixs);
+    pixCopyText(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, scalex, scaley);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+    scaleBinaryLow(datad, wd, hd, wpld, datas, ws, hs, wpls);
+    return pixd;
+}
+
+
+
+/*------------------------------------------------------------------*
+ *      Scale-to-gray (1 bpp --> 8 bpp; arbitrary downscaling)      *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixScaleToGray()
+ *
+ *      Input:  pixs (1 bpp)
+ *              scalefactor (reduction: must be > 0.0 and < 1.0)
+ *      Return: pixd (8 bpp), scaled down by scalefactor in each direction,
+ *              or NULL on error.
+ *
+ *  Notes:
+ *
+ *  For faster scaling in the range of scalefactors from 0.0625 to 0.5,
+ *  with very little difference in quality, use pixScaleToGrayFast().
+ *
+ *  Binary images have sharp edges, so they intrinsically have very
+ *  high frequency content.  To avoid aliasing, they must be low-pass
+ *  filtered, which tends to blur the edges.  How can we keep relatively
+ *  crisp edges without aliasing?  The trick is to do binary upscaling
+ *  followed by a power-of-2 scaleToGray.  For large reductions, where
+ *  you don't end up with much detail, some corners can be cut.
+ *
+ *  The intent here is to get high quality reduced grayscale
+ *  images with relatively little computation.  We do binary
+ *  pre-scaling followed by scaleToGrayN() for best results,
+ *  esp. to avoid excess blur when the scale factor is near
+ *  an inverse power of 2.  Where a low-pass filter is required,
+ *  we use simple convolution kernels: either the hat filter for
+ *  linear interpolation or a flat filter for larger downscaling.
+ *  Other choices, such as a perfect bandpass filter with infinite extent
+ *  (the sinc) or various approximations to it (e.g., lanczos), are
+ *  unnecessarily expensive.
+ *
+ *  The choices made are as follows:
+ *      (1) Do binary upscaling before scaleToGrayN() for scalefactors > 1/8
+ *      (2) Do binary downscaling before scaleToGray8() for scalefactors
+ *          between 1/16 and 1/8.
+ *      (3) Use scaleToGray16() before grayscale downscaling for
+ *          scalefactors less than 1/16
+ *  Another reasonable choice would be to start binary downscaling
+ *  for scalefactors below 1/4, rather than below 1/8 as we do here.
+ *
+ *  The general scaling rules, not all of which are used here, go as follows:
+ *      (1) For grayscale upscaling, use pixScaleGrayLI().  However,
+ *          note that edges will be visibly blurred for scalefactors
+ *          near (but above) 1.0.  Replication will avoid edge blur,
+ *          and should be considered for factors very near 1.0.
+ *      (2) For grayscale downscaling with a scale factor larger than
+ *          about 0.7, use pixScaleGrayLI().  For scalefactors near
+ *          (but below) 1.0, you tread between Scylla and Charybdis.
+ *          pixScaleGrayLI() again gives edge blurring, but
+ *          pixScaleBySampling() gives visible aliasing.
+ *      (3) For grayscale downscaling with a scale factor smaller than
+ *          about 0.7, use pixScaleSmooth()
+ *      (4) For binary input images, do as much scale to gray as possible
+ *          using the special integer functions (2, 3, 4, 8 and 16).
+ *      (5) It is better to upscale in binary, followed by scaleToGrayN()
+ *          than to do scaleToGrayN() followed by an upscale using either
+ *          LI or oversampling.
+ *      (6) It may be better to downscale in binary, followed by
+ *          scaleToGrayN() than to first use scaleToGrayN() followed by
+ *          downscaling.  For downscaling between 8x and 16x, this is
+ *          a reasonable option.
+ *      (7) For reductions greater than 16x, it's reasonable to use
+ *          scaleToGray16() followed by further grayscale downscaling.
+ */
+PIX *
+pixScaleToGray(PIX       *pixs,
+               l_float32  scalefactor)
+{
+l_int32    w, h, minsrc, mindest;
+l_float32  mag, red;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixScaleToGray");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (scalefactor <= 0.0)
+        return (PIX *)ERROR_PTR("scalefactor <= 0.0", procName, NULL);
+    if (scalefactor >= 1.0)
+        return (PIX *)ERROR_PTR("scalefactor >= 1.0", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    minsrc = L_MIN(w, h);
+    mindest = (l_int32)((l_float32)minsrc * scalefactor);
+    if (mindest < 2)
+        return (PIX *)ERROR_PTR("scalefactor too small", procName, NULL);
+
+    if (scalefactor > 0.5) {   /* see note (5) */
+        mag = 2.0 * scalefactor;  /* will be < 2.0 */
+/*        fprintf(stderr, "2x with mag %7.3f\n", mag);  */
+        if ((pixt = pixScaleBinary(pixs, mag, mag)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        pixd = pixScaleToGray2(pixt);
+    } else if (scalefactor == 0.5) {
+        return pixd = pixScaleToGray2(pixs);
+    } else if (scalefactor > 0.33333) {   /* see note (5) */
+        mag = 3.0 * scalefactor;   /* will be < 1.5 */
+/*        fprintf(stderr, "3x with mag %7.3f\n", mag);  */
+        if ((pixt = pixScaleBinary(pixs, mag, mag)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        pixd = pixScaleToGray3(pixt);
+    } else if (scalefactor > 0.25) {  /* see note (5) */
+        mag = 4.0 * scalefactor;   /* will be < 1.3333 */
+/*        fprintf(stderr, "4x with mag %7.3f\n", mag);  */
+        if ((pixt = pixScaleBinary(pixs, mag, mag)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        pixd = pixScaleToGray4(pixt);
+    } else if (scalefactor == 0.25) {
+        return pixd = pixScaleToGray4(pixs);
+    } else if (scalefactor > 0.16667) {  /* see note (5) */
+        mag = 6.0 * scalefactor;   /* will be < 1.5 */
+/*        fprintf(stderr, "6x with mag %7.3f\n", mag); */
+        if ((pixt = pixScaleBinary(pixs, mag, mag)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        pixd = pixScaleToGray6(pixt);
+    } else if (scalefactor == 0.16667) {
+        return pixd = pixScaleToGray6(pixs);
+    } else if (scalefactor > 0.125) {  /* see note (5) */
+        mag = 8.0 * scalefactor;   /*  will be < 1.3333  */
+/*        fprintf(stderr, "8x with mag %7.3f\n", mag);  */
+        if ((pixt = pixScaleBinary(pixs, mag, mag)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        pixd = pixScaleToGray8(pixt);
+    } else if (scalefactor == 0.125) {
+        return pixd = pixScaleToGray8(pixs);
+    } else if (scalefactor > 0.0625) {  /* see note (6) */
+        red = 8.0 * scalefactor;   /* will be > 0.5 */
+/*        fprintf(stderr, "8x with red %7.3f\n", red);  */
+        if ((pixt = pixScaleBinary(pixs, red, red)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        pixd = pixScaleToGray8(pixt);
+    } else if (scalefactor == 0.0625) {
+        return pixd = pixScaleToGray16(pixs);
+    } else {  /* see note (7) */
+        red = 16.0 * scalefactor;  /* will be <= 1.0 */
+/*        fprintf(stderr, "16x with red %7.3f\n", red);  */
+        if ((pixt = pixScaleToGray16(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        if (red < 0.7)
+            pixd = pixScaleSmooth(pixt, red, red);  /* see note (3) */
+        else
+            pixd = pixScaleGrayLI(pixt, red, red);  /* see note (2) */
+    }
+
+    pixDestroy(&pixt);
+    if (!pixd)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleToGrayFast()
+ *
+ *      Input:  pixs (1 bpp)
+ *              scalefactor (reduction: must be > 0.0 and < 1.0)
+ *      Return: pixd (8 bpp), scaled down by scalefactor in each direction,
+ *              or NULL on error.
+ *
+ *  Notes:
+ *      (1) See notes in pixScaleToGray() for the basic approach.
+ *      (2) This function is considerably less expensive than pixScaleToGray()
+ *          for scalefactor in the range (0.0625 ... 0.5), and the
+ *          quality is nearly as good.
+ *      (3) Unlike pixScaleToGray(), which does binary upscaling before
+ *          downscaling for scale factors >= 0.0625, pixScaleToGrayFast()
+ *          first downscales in binary for all scale factors < 0.5, and
+ *          then does a 2x scale-to-gray as the final step.  For
+ *          scale factors < 0.0625, both do a 16x scale-to-gray, followed
+ *          by further grayscale reduction.
+ */
+PIX *
+pixScaleToGrayFast(PIX       *pixs,
+                   l_float32  scalefactor)
+{
+l_int32    w, h, minsrc, mindest;
+l_float32  eps, factor;
+PIX       *pixt, *pixd;
+
+    PROCNAME("pixScaleToGrayFast");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (scalefactor <= 0.0)
+        return (PIX *)ERROR_PTR("scalefactor <= 0.0", procName, NULL);
+    if (scalefactor >= 1.0)
+        return (PIX *)ERROR_PTR("scalefactor >= 1.0", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    minsrc = L_MIN(w, h);
+    mindest = (l_int32)((l_float32)minsrc * scalefactor);
+    if (mindest < 2)
+        return (PIX *)ERROR_PTR("scalefactor too small", procName, NULL);
+    eps = 0.0001;
+
+        /* Handle the special cases */
+    if (scalefactor > 0.5 - eps && scalefactor < 0.5 + eps)
+        return pixScaleToGray2(pixs);
+    else if (scalefactor > 0.33333 - eps && scalefactor < 0.33333 + eps)
+        return pixScaleToGray3(pixs);
+    else if (scalefactor > 0.25 - eps && scalefactor < 0.25 + eps)
+        return pixScaleToGray4(pixs);
+    else if (scalefactor > 0.16666 - eps && scalefactor < 0.16666 + eps)
+        return pixScaleToGray6(pixs);
+    else if (scalefactor > 0.125 - eps && scalefactor < 0.125 + eps)
+        return pixScaleToGray8(pixs);
+    else if (scalefactor > 0.0625 - eps && scalefactor < 0.0625 + eps)
+        return pixScaleToGray16(pixs);
+
+    if (scalefactor > 0.0625) {  /* scale binary first */
+        factor = 2.0 * scalefactor;
+        if ((pixt = pixScaleBinary(pixs, factor, factor)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        pixd = pixScaleToGray2(pixt);
+    } else {  /* scalefactor < 0.0625; scale-to-gray first */
+        factor = 16.0 * scalefactor;  /* will be < 1.0 */
+        if ((pixt = pixScaleToGray16(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        if (factor < 0.7)
+            pixd = pixScaleSmooth(pixt, factor, factor);
+        else
+            pixd = pixScaleGrayLI(pixt, factor, factor);
+    }
+    pixDestroy(&pixt);
+    if (!pixd)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *          Scale-to-gray (1 bpp --> 8 bpp; integer downscaling)         *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixScaleToGray2()
+ *
+ *      Input:  pixs (1 bpp)
+ *      Return: pixd (8 bpp), scaled down by 2x in each direction,
+ *              or null on error.
+ */
+PIX *
+pixScaleToGray2(PIX  *pixs)
+{
+l_uint8   *valtab;
+l_int32    ws, hs, wd, hd;
+l_int32    wpld, wpls;
+l_uint32  *sumtab;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleToGray2");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    wd = ws / 2;
+    hd = hs / 2;
+    if (wd == 0 || hd == 0)
+        return (PIX *)ERROR_PTR("pixs too small", procName, NULL);
+
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 0.5, 0.5);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+    if ((sumtab = makeSumTabSG2()) == NULL)
+        return (PIX *)ERROR_PTR("sumtab not made", procName, NULL);
+    if ((valtab = makeValTabSG2()) == NULL)
+        return (PIX *)ERROR_PTR("valtab not made", procName, NULL);
+
+    scaleToGray2Low(datad, wd, hd, wpld, datas, wpls, sumtab, valtab);
+
+    LEPT_FREE(sumtab);
+    LEPT_FREE(valtab);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleToGray3()
+ *
+ *      Input:  pixs (1 bpp)
+ *      Return: pixd (8 bpp), scaled down by 3x in each direction,
+ *              or null on error.
+ *
+ *  Notes:
+ *      (1) Speed is about 100 x 10^6 src-pixels/sec/GHz.
+ *          Another way to express this is it processes 1 src pixel
+ *          in about 10 cycles.
+ *      (2) The width of pixd is truncated is truncated to a factor of 8.
+ */
+PIX *
+pixScaleToGray3(PIX  *pixs)
+{
+l_uint8   *valtab;
+l_int32    ws, hs, wd, hd;
+l_int32    wpld, wpls;
+l_uint32  *sumtab;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleToGray3");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    wd = (ws / 3) & 0xfffffff8;    /* truncate to factor of 8 */
+    hd = hs / 3;
+    if (wd == 0 || hd == 0)
+        return (PIX *)ERROR_PTR("pixs too small", procName, NULL);
+
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 0.33333, 0.33333);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+    if ((sumtab = makeSumTabSG3()) == NULL)
+        return (PIX *)ERROR_PTR("sumtab not made", procName, NULL);
+    if ((valtab = makeValTabSG3()) == NULL)
+        return (PIX *)ERROR_PTR("valtab not made", procName, NULL);
+
+    scaleToGray3Low(datad, wd, hd, wpld, datas, wpls, sumtab, valtab);
+
+    LEPT_FREE(sumtab);
+    LEPT_FREE(valtab);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleToGray4()
+ *
+ *      Input:  pixs (1 bpp)
+ *      Return: pixd (8 bpp), scaled down by 4x in each direction,
+ *              or null on error.
+ *
+ *  Notes:
+ *      (1) The width of pixd is truncated is truncated to a factor of 2.
+ */
+PIX *
+pixScaleToGray4(PIX  *pixs)
+{
+l_uint8   *valtab;
+l_int32    ws, hs, wd, hd;
+l_int32    wpld, wpls;
+l_uint32  *sumtab;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleToGray4");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    wd = (ws / 4) & 0xfffffffe;    /* truncate to factor of 2 */
+    hd = hs / 4;
+    if (wd == 0 || hd == 0)
+        return (PIX *)ERROR_PTR("pixs too small", procName, NULL);
+
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 0.25, 0.25);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+    if ((sumtab = makeSumTabSG4()) == NULL)
+        return (PIX *)ERROR_PTR("sumtab not made", procName, NULL);
+    if ((valtab = makeValTabSG4()) == NULL)
+        return (PIX *)ERROR_PTR("valtab not made", procName, NULL);
+
+    scaleToGray4Low(datad, wd, hd, wpld, datas, wpls, sumtab, valtab);
+
+    LEPT_FREE(sumtab);
+    LEPT_FREE(valtab);
+    return pixd;
+}
+
+
+
+/*!
+ *  pixScaleToGray6()
+ *
+ *      Input:  pixs (1 bpp)
+ *      Return: pixd (8 bpp), scaled down by 6x in each direction,
+ *              or null on error.
+ *
+ *  Notes:
+ *      (1) The width of pixd is truncated is truncated to a factor of 8.
+ */
+PIX *
+pixScaleToGray6(PIX  *pixs)
+{
+l_uint8   *valtab;
+l_int32    ws, hs, wd, hd, wpld, wpls;
+l_int32   *tab8;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleToGray6");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    wd = (ws / 6) & 0xfffffff8;    /* truncate to factor of 8 */
+    hd = hs / 6;
+    if (wd == 0 || hd == 0)
+        return (PIX *)ERROR_PTR("pixs too small", procName, NULL);
+
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 0.16667, 0.16667);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+    if ((tab8 = makePixelSumTab8()) == NULL)
+        return (PIX *)ERROR_PTR("tab8 not made", procName, NULL);
+    if ((valtab = makeValTabSG6()) == NULL)
+        return (PIX *)ERROR_PTR("valtab not made", procName, NULL);
+
+    scaleToGray6Low(datad, wd, hd, wpld, datas, wpls, tab8, valtab);
+
+    LEPT_FREE(tab8);
+    LEPT_FREE(valtab);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleToGray8()
+ *
+ *      Input:  pixs (1 bpp)
+ *      Return: pixd (8 bpp), scaled down by 8x in each direction,
+ *              or null on error
+ */
+PIX *
+pixScaleToGray8(PIX  *pixs)
+{
+l_uint8   *valtab;
+l_int32    ws, hs, wd, hd;
+l_int32    wpld, wpls;
+l_int32   *tab8;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleToGray8");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    wd = ws / 8;  /* truncate to nearest dest byte */
+    hd = hs / 8;
+    if (wd == 0 || hd == 0)
+        return (PIX *)ERROR_PTR("pixs too small", procName, NULL);
+
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 0.125, 0.125);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+    if ((tab8 = makePixelSumTab8()) == NULL)
+        return (PIX *)ERROR_PTR("tab8 not made", procName, NULL);
+    if ((valtab = makeValTabSG8()) == NULL)
+        return (PIX *)ERROR_PTR("valtab not made", procName, NULL);
+
+    scaleToGray8Low(datad, wd, hd, wpld, datas, wpls, tab8, valtab);
+
+    LEPT_FREE(tab8);
+    LEPT_FREE(valtab);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleToGray16()
+ *
+ *      Input:  pixs (1 bpp)
+ *      Return: pixd (8 bpp), scaled down by 16x in each direction,
+ *              or null on error.
+ */
+PIX *
+pixScaleToGray16(PIX  *pixs)
+{
+l_int32    ws, hs, wd, hd;
+l_int32    wpld, wpls;
+l_int32   *tab8;
+l_uint32  *datas, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleToGray16");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs must be 1 bpp", procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    wd = ws / 16;
+    hd = hs / 16;
+    if (wd == 0 || hd == 0)
+        return (PIX *)ERROR_PTR("pixs too small", procName, NULL);
+
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 0.0625, 0.0625);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+
+    if ((tab8 = makePixelSumTab8()) == NULL)
+        return (PIX *)ERROR_PTR("tab8 not made", procName, NULL);
+
+    scaleToGray16Low(datad, wd, hd, wpld, datas, wpls, tab8);
+
+    LEPT_FREE(tab8);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *    Scale-to-gray mipmap(1 bpp --> 8 bpp, arbitrary reduction)    *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixScaleToGrayMipmap()
+ *
+ *      Input:  pixs (1 bpp)
+ *              scalefactor (reduction: must be > 0.0 and < 1.0)
+ *      Return: pixd (8 bpp), scaled down by scalefactor in each direction,
+ *              or NULL on error.
+ *
+ *  Notes:
+ *
+ *  This function is here mainly for pedagogical reasons.
+ *  Mip-mapping is widely used in graphics for texture mapping, because
+ *  the texture changes smoothly with scale.  This is accomplished by
+ *  constructing a multiresolution pyramid and, for each pixel,
+ *  doing a linear interpolation between corresponding pixels in
+ *  the two planes of the pyramid that bracket the desired resolution.
+ *  The computation is very efficient, and is implemented in hardware
+ *  in high-end graphics cards.
+ *
+ *  We can use mip-mapping for scale-to-gray by using two scale-to-gray
+ *  reduced images (we don't need the entire pyramid) selected from
+ *  the set {2x, 4x, ... 16x}, and interpolating.  However, we get
+ *  severe aliasing, probably because we are subsampling from the
+ *  higher resolution image.  The method is very fast, but the result
+ *  is very poor.  In fact, the results don't look any better than
+ *  either subsampling off the higher-res grayscale image or oversampling
+ *  on the lower-res image.  Consequently, this method should NOT be used
+ *  for generating reduced images, scale-to-gray or otherwise.
+ */
+PIX *
+pixScaleToGrayMipmap(PIX       *pixs,
+                     l_float32  scalefactor)
+{
+l_int32    w, h, minsrc, mindest;
+l_float32  red;
+PIX       *pixs1, *pixs2, *pixt, *pixd;
+
+    PROCNAME("pixScaleToGrayMipmap");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (scalefactor <= 0.0)
+        return (PIX *)ERROR_PTR("scalefactor <= 0.0", procName, NULL);
+    if (scalefactor >= 1.0)
+        return (PIX *)ERROR_PTR("scalefactor >= 1.0", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    minsrc = L_MIN(w, h);
+    mindest = (l_int32)((l_float32)minsrc * scalefactor);
+    if (mindest < 2)
+        return (PIX *)ERROR_PTR("scalefactor too small", procName, NULL);
+
+    if (scalefactor > 0.5) {
+        pixs1 = pixConvert1To8(NULL, pixs, 255, 0);
+        pixs2 = pixScaleToGray2(pixs);
+        red = scalefactor;
+    } else if (scalefactor == 0.5) {
+        return pixScaleToGray2(pixs);
+    } else if (scalefactor > 0.25) {
+        pixs1 = pixScaleToGray2(pixs);
+        pixs2 = pixScaleToGray4(pixs);
+        red = 2. * scalefactor;
+    } else if (scalefactor == 0.25) {
+        return pixScaleToGray4(pixs);
+    } else if (scalefactor > 0.125) {
+        pixs1 = pixScaleToGray4(pixs);
+        pixs2 = pixScaleToGray8(pixs);
+        red = 4. * scalefactor;
+    } else if (scalefactor == 0.125) {
+        return pixScaleToGray8(pixs);
+    } else if (scalefactor > 0.0625) {
+        pixs1 = pixScaleToGray8(pixs);
+        pixs2 = pixScaleToGray16(pixs);
+        red = 8. * scalefactor;
+    } else if (scalefactor == 0.0625) {
+        return pixScaleToGray16(pixs);
+    } else {  /* end of the pyramid; just do it */
+        red = 16.0 * scalefactor;  /* will be <= 1.0 */
+        if ((pixt = pixScaleToGray16(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixt not made", procName, NULL);
+        if (red < 0.7)
+            pixd = pixScaleSmooth(pixt, red, red);
+        else
+            pixd = pixScaleGrayLI(pixt, red, red);
+        pixDestroy(&pixt);
+        return pixd;
+    }
+
+    pixd = pixScaleMipmap(pixs1, pixs2, red);
+    pixCopyInputFormat(pixd, pixs);
+
+    pixDestroy(&pixs1);
+    pixDestroy(&pixs2);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                  Grayscale scaling using mipmap                  *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixScaleMipmap()
+ *
+ *      Input:  pixs1 (high res 8 bpp, no cmap)
+ *              pixs2 (low res -- 2x reduced -- 8 bpp, no cmap)
+ *              scale (reduction with respect to high res image, > 0.5)
+ *      Return: 8 bpp pix, scaled down by reduction in each direction,
+ *              or NULL on error.
+ *
+ *  Notes:
+ *      (1) See notes in pixScaleToGrayMipmap().
+ *      (2) This function suffers from aliasing effects that are
+ *          easily seen in document images.
+ */
+PIX *
+pixScaleMipmap(PIX       *pixs1,
+               PIX       *pixs2,
+               l_float32  scale)
+{
+l_int32    ws1, hs1, ws2, hs2, wd, hd, wpls1, wpls2, wpld;
+l_uint32  *datas1, *datas2, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixScaleMipmap");
+
+    if (!pixs1 || pixGetDepth(pixs1) != 8 || pixGetColormap(pixs1))
+        return (PIX *)ERROR_PTR("pixs1 underdefined, not 8 bpp, or cmapped",
+                                procName, NULL);
+    if (!pixs2 || pixGetDepth(pixs2) != 8 || pixGetColormap(pixs2))
+        return (PIX *)ERROR_PTR("pixs2 underdefined, not 8 bpp, or cmapped",
+                                procName, NULL);
+    pixGetDimensions(pixs1, &ws1, &hs1, NULL);
+    pixGetDimensions(pixs2, &ws2, &hs2, NULL);
+    if (scale > 1.0 || scale < 0.5)
+        return (PIX *)ERROR_PTR("scale not in [0.5, 1.0]", procName, NULL);
+    if (ws1 < 2 * ws2)
+        return (PIX *)ERROR_PTR("invalid width ratio", procName, NULL);
+    if (hs1 < 2 * hs2)
+        return (PIX *)ERROR_PTR("invalid height ratio", procName, NULL);
+
+        /* Generate wd and hd from the lower resolution dimensions,
+         * to guarantee staying within both src images */
+    datas1 = pixGetData(pixs1);
+    wpls1 = pixGetWpl(pixs1);
+    datas2 = pixGetData(pixs2);
+    wpls2 = pixGetWpl(pixs2);
+    wd = (l_int32)(2. * scale * pixGetWidth(pixs2));
+    hd = (l_int32)(2. * scale * pixGetHeight(pixs2));
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs1);
+    pixCopyResolution(pixd, pixs1);
+    pixScaleResolution(pixd, scale, scale);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    scaleMipmapLow(datad, wd, hd, wpld, datas1, wpls1, datas2, wpls2, scale);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                  Replicated (integer) expansion                  *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixExpandReplicate()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 16, 32 bpp)
+ *              factor (integer scale factor for replicative expansion)
+ *      Return: pixd (scaled up), or null on error.
+ */
+PIX *
+pixExpandReplicate(PIX     *pixs,
+                   l_int32  factor)
+{
+l_int32    w, h, d, wd, hd, wpls, wpld, start, i, j, k;
+l_uint8    sval;
+l_uint16   sval16;
+l_uint32   sval32;
+l_uint32  *lines, *datas, *lined, *datad;
+PIX       *pixd;
+
+    PROCNAME("pixExpandReplicate");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
+        return (PIX *)ERROR_PTR("depth not in {1,2,4,8,16,32}", procName, NULL);
+    if (factor <= 0)
+        return (PIX *)ERROR_PTR("factor <= 0; invalid", procName, NULL);
+    if (factor == 1)
+        return pixCopy(NULL, pixs);
+
+    if (d == 1)
+        return pixExpandBinaryReplicate(pixs, factor);
+
+    wd = factor * w;
+    hd = factor * h;
+    if ((pixd = pixCreate(wd, hd, d)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyColormap(pixd, pixs);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, (l_float32)factor, (l_float32)factor);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    switch (d) {
+    case 2:
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + factor * i * wpld;
+            for (j = 0; j < w; j++) {
+                sval = GET_DATA_DIBIT(lines, j);
+                start = factor * j;
+                for (k = 0; k < factor; k++)
+                    SET_DATA_DIBIT(lined, start + k, sval);
+            }
+            for (k = 1; k < factor; k++)
+                memcpy(lined + k * wpld, lined, 4 * wpld);
+        }
+        break;
+    case 4:
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + factor * i * wpld;
+            for (j = 0; j < w; j++) {
+                sval = GET_DATA_QBIT(lines, j);
+                start = factor * j;
+                for (k = 0; k < factor; k++)
+                    SET_DATA_QBIT(lined, start + k, sval);
+            }
+            for (k = 1; k < factor; k++)
+                memcpy(lined + k * wpld, lined, 4 * wpld);
+        }
+        break;
+    case 8:
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + factor * i * wpld;
+            for (j = 0; j < w; j++) {
+                sval = GET_DATA_BYTE(lines, j);
+                start = factor * j;
+                for (k = 0; k < factor; k++)
+                    SET_DATA_BYTE(lined, start + k, sval);
+            }
+            for (k = 1; k < factor; k++)
+                memcpy(lined + k * wpld, lined, 4 * wpld);
+        }
+        break;
+    case 16:
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + factor * i * wpld;
+            for (j = 0; j < w; j++) {
+                sval16 = GET_DATA_TWO_BYTES(lines, j);
+                start = factor * j;
+                for (k = 0; k < factor; k++)
+                    SET_DATA_TWO_BYTES(lined, start + k, sval16);
+            }
+            for (k = 1; k < factor; k++)
+                memcpy(lined + k * wpld, lined, 4 * wpld);
+        }
+        break;
+    case 32:
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            lined = datad + factor * i * wpld;
+            for (j = 0; j < w; j++) {
+                sval32 = *(lines + j);
+                start = factor * j;
+                for (k = 0; k < factor; k++)
+                    *(lined + start + k) = sval32;
+            }
+            for (k = 1; k < factor; k++)
+                memcpy(lined + k * wpld, lined, 4 * wpld);
+        }
+        break;
+    default:
+        fprintf(stderr, "invalid depth\n");
+    }
+
+    if (d == 32 && pixGetSpp(pixs) == 4)
+        pixScaleAndTransferAlpha(pixd, pixs, (l_float32)factor,
+                                 (l_float32)factor);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                Scale 2x followed by binarization                 *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixScaleGray2xLIThresh()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *              thresh  (between 0 and 256)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This does 2x upscale on pixs, using linear interpolation,
+ *          followed by thresholding to binary.
+ *      (2) Buffers are used to avoid making a large grayscale image.
+ */
+PIX *
+pixScaleGray2xLIThresh(PIX     *pixs,
+                       l_int32  thresh)
+{
+l_int32    i, ws, hs, hsm, wd, hd, wpls, wplb, wpld;
+l_uint32  *datas, *datad, *lines, *lined, *lineb;
+PIX       *pixd;
+
+    PROCNAME("pixScaleGray2xLIThresh");
+
+    if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs undefined, not 8 bpp, or cmapped",
+                                procName, NULL);
+    if (thresh < 0 || thresh > 256)
+        return (PIX *)ERROR_PTR("thresh must be in [0, ... 256]",
+            procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    wd = 2 * ws;
+    hd = 2 * hs;
+    hsm = hs - 1;
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+
+        /* Make line buffer for 2 lines of virtual intermediate image */
+    wplb = (wd + 3) / 4;
+    if ((lineb = (l_uint32 *)LEPT_CALLOC(2 * wplb, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("lineb not made", procName, NULL);
+
+        /* Make dest binary image */
+    if ((pixd = pixCreate(wd, hd, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 2.0, 2.0);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+
+        /* Do all but last src line */
+    for (i = 0; i < hsm; i++) {
+        lines = datas + i * wpls;
+        lined = datad + 2 * i * wpld;  /* do 2 dest lines at a time */
+        scaleGray2xLILineLow(lineb, wplb, lines, ws, wpls, 0);
+        thresholdToBinaryLineLow(lined, wd, lineb, 8, thresh);
+        thresholdToBinaryLineLow(lined + wpld, wd, lineb + wplb, 8, thresh);
+    }
+
+        /* Do last src line */
+    lines = datas + hsm * wpls;
+    lined = datad + 2 * hsm * wpld;
+    scaleGray2xLILineLow(lineb, wplb, lines, ws, wpls, 1);
+    thresholdToBinaryLineLow(lined, wd, lineb, 8, thresh);
+    thresholdToBinaryLineLow(lined + wpld, wd, lineb + wplb, 8, thresh);
+
+    LEPT_FREE(lineb);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleGray2xLIDither()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This does 2x upscale on pixs, using linear interpolation,
+ *          followed by Floyd-Steinberg dithering to binary.
+ *      (2) Buffers are used to avoid making a large grayscale image.
+ *          - Two line buffers are used for the src, required for the 2x
+ *            LI upscale.
+ *          - Three line buffers are used for the intermediate image.
+ *            Two are filled with each 2xLI row operation; the third is
+ *            needed because the upscale and dithering ops are out of sync.
+ */
+PIX *
+pixScaleGray2xLIDither(PIX  *pixs)
+{
+l_int32    i, ws, hs, hsm, wd, hd, wpls, wplb, wpld;
+l_uint32  *datas, *datad;
+l_uint32  *lined;
+l_uint32  *lineb;   /* 2 intermediate buffer lines */
+l_uint32  *linebp;  /* 1 intermediate buffer line */
+l_uint32  *bufs;    /* 2 source buffer lines */
+PIX       *pixd;
+
+    PROCNAME("pixScaleGray2xLIDither");
+
+    if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs undefined, not 8 bpp, or cmapped",
+                                procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    wd = 2 * ws;
+    hd = 2 * hs;
+    hsm = hs - 1;
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+
+        /* Make line buffers for 2 lines of src image */
+    if ((bufs = (l_uint32 *)LEPT_CALLOC(2 * wpls, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("bufs not made", procName, NULL);
+
+        /* Make line buffer for 2 lines of virtual intermediate image */
+    wplb = (wd + 3) / 4;
+    if ((lineb = (l_uint32 *)LEPT_CALLOC(2 * wplb, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("lineb not made", procName, NULL);
+
+        /* Make line buffer for 1 line of virtual intermediate image */
+    if ((linebp = (l_uint32 *)LEPT_CALLOC(wplb, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("linebp not made", procName, NULL);
+
+        /* Make dest binary image */
+    if ((pixd = pixCreate(wd, hd, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 2.0, 2.0);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+
+        /* Start with the first src and the first dest line */
+    memcpy(bufs, datas, 4 * wpls);   /* first src line */
+    memcpy(bufs + wpls, datas + wpls, 4 * wpls);  /* 2nd src line */
+    scaleGray2xLILineLow(lineb, wplb, bufs, ws, wpls, 0);  /* 2 i lines */
+    lined = datad;
+    ditherToBinaryLineLow(lined, wd, lineb, lineb + wplb,
+                          DEFAULT_CLIP_LOWER_1, DEFAULT_CLIP_UPPER_1, 0);
+                                                    /* 1st d line */
+
+        /* Do all but last src line */
+    for (i = 1; i < hsm; i++) {
+        memcpy(bufs, datas + i * wpls, 4 * wpls);  /* i-th src line */
+        memcpy(bufs + wpls, datas + (i + 1) * wpls, 4 * wpls);
+        memcpy(linebp, lineb + wplb, 4 * wplb);
+        scaleGray2xLILineLow(lineb, wplb, bufs, ws, wpls, 0);  /* 2 i lines */
+        lined = datad + 2 * i * wpld;
+        ditherToBinaryLineLow(lined - wpld, wd, linebp, lineb,
+                              DEFAULT_CLIP_LOWER_1, DEFAULT_CLIP_UPPER_1, 0);
+                                                   /* odd dest line */
+        ditherToBinaryLineLow(lined, wd, lineb, lineb + wplb,
+                              DEFAULT_CLIP_LOWER_1, DEFAULT_CLIP_UPPER_1, 0);
+                                                   /* even dest line */
+    }
+
+        /* Do the last src line and the last 3 dest lines */
+    memcpy(bufs, datas + hsm * wpls, 4 * wpls);  /* hsm-th src line */
+    memcpy(linebp, lineb + wplb, 4 * wplb);   /* 1 i line */
+    scaleGray2xLILineLow(lineb, wplb, bufs, ws, wpls, 1);  /* 2 i lines */
+    ditherToBinaryLineLow(lined + wpld, wd, linebp, lineb,
+                          DEFAULT_CLIP_LOWER_1, DEFAULT_CLIP_UPPER_1, 0);
+                                                   /* odd dest line */
+    ditherToBinaryLineLow(lined + 2 * wpld, wd, lineb, lineb + wplb,
+                          DEFAULT_CLIP_LOWER_1, DEFAULT_CLIP_UPPER_1, 0);
+                                                   /* even dest line */
+    ditherToBinaryLineLow(lined + 3 * wpld, wd, lineb + wplb, NULL,
+                          DEFAULT_CLIP_LOWER_1, DEFAULT_CLIP_UPPER_1, 1);
+                                                   /* last dest line */
+
+    LEPT_FREE(bufs);
+    LEPT_FREE(lineb);
+    LEPT_FREE(linebp);
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------*
+ *                Scale 4x followed by binarization                 *
+ *------------------------------------------------------------------*/
+/*!
+ *  pixScaleGray4xLIThresh()
+ *
+ *      Input:  pixs (8 bpp)
+ *              thresh  (between 0 and 256)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This does 4x upscale on pixs, using linear interpolation,
+ *          followed by thresholding to binary.
+ *      (2) Buffers are used to avoid making a large grayscale image.
+ *      (3) If a full 4x expanded grayscale image can be kept in memory,
+ *          this function is only about 10% faster than separately doing
+ *          a linear interpolation to a large grayscale image, followed
+ *          by thresholding to binary.
+ */
+PIX *
+pixScaleGray4xLIThresh(PIX     *pixs,
+                       l_int32  thresh)
+{
+l_int32    i, j, ws, hs, hsm, wd, hd, wpls, wplb, wpld;
+l_uint32  *datas, *datad, *lines, *lined, *lineb;
+PIX       *pixd;
+
+    PROCNAME("pixScaleGray4xLIThresh");
+
+    if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs undefined, not 8 bpp, or cmapped",
+                                procName, NULL);
+    if (thresh < 0 || thresh > 256)
+        return (PIX *)ERROR_PTR("thresh must be in [0, ... 256]",
+            procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    wd = 4 * ws;
+    hd = 4 * hs;
+    hsm = hs - 1;
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+
+        /* Make line buffer for 4 lines of virtual intermediate image */
+    wplb = (wd + 3) / 4;
+    if ((lineb = (l_uint32 *)LEPT_CALLOC(4 * wplb, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("lineb not made", procName, NULL);
+
+        /* Make dest binary image */
+    if ((pixd = pixCreate(wd, hd, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 4.0, 4.0);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+
+        /* Do all but last src line */
+    for (i = 0; i < hsm; i++) {
+        lines = datas + i * wpls;
+        lined = datad + 4 * i * wpld;  /* do 4 dest lines at a time */
+        scaleGray4xLILineLow(lineb, wplb, lines, ws, wpls, 0);
+        for (j = 0; j < 4; j++) {
+            thresholdToBinaryLineLow(lined + j * wpld, wd,
+                                     lineb + j * wplb, 8, thresh);
+        }
+    }
+
+        /* Do last src line */
+    lines = datas + hsm * wpls;
+    lined = datad + 4 * hsm * wpld;
+    scaleGray4xLILineLow(lineb, wplb, lines, ws, wpls, 1);
+    for (j = 0; j < 4; j++) {
+        thresholdToBinaryLineLow(lined + j * wpld, wd,
+                                 lineb + j * wplb, 8, thresh);
+    }
+
+    LEPT_FREE(lineb);
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleGray4xLIDither()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *      Return: pixd (1 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This does 4x upscale on pixs, using linear interpolation,
+ *          followed by Floyd-Steinberg dithering to binary.
+ *      (2) Buffers are used to avoid making a large grayscale image.
+ *          - Two line buffers are used for the src, required for the
+ *            4xLI upscale.
+ *          - Five line buffers are used for the intermediate image.
+ *            Four are filled with each 4xLI row operation; the fifth
+ *            is needed because the upscale and dithering ops are
+ *            out of sync.
+ *      (3) If a full 4x expanded grayscale image can be kept in memory,
+ *          this function is only about 5% faster than separately doing
+ *          a linear interpolation to a large grayscale image, followed
+ *          by error-diffusion dithering to binary.
+ */
+PIX *
+pixScaleGray4xLIDither(PIX  *pixs)
+{
+l_int32    i, j, ws, hs, hsm, wd, hd, wpls, wplb, wpld;
+l_uint32  *datas, *datad;
+l_uint32  *lined;
+l_uint32  *lineb;   /* 4 intermediate buffer lines */
+l_uint32  *linebp;  /* 1 intermediate buffer line */
+l_uint32  *bufs;    /* 2 source buffer lines */
+PIX       *pixd;
+
+    PROCNAME("pixScaleGray4xLIDither");
+
+    if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs undefined, not 8 bpp, or cmapped",
+                                procName, NULL);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    wd = 4 * ws;
+    hd = 4 * hs;
+    hsm = hs - 1;
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+
+        /* Make line buffers for 2 lines of src image */
+    if ((bufs = (l_uint32 *)LEPT_CALLOC(2 * wpls, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("bufs not made", procName, NULL);
+
+        /* Make line buffer for 4 lines of virtual intermediate image */
+    wplb = (wd + 3) / 4;
+    if ((lineb = (l_uint32 *)LEPT_CALLOC(4 * wplb, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("lineb not made", procName, NULL);
+
+        /* Make line buffer for 1 line of virtual intermediate image */
+    if ((linebp = (l_uint32 *)LEPT_CALLOC(wplb, sizeof(l_uint32))) == NULL)
+        return (PIX *)ERROR_PTR("linebp not made", procName, NULL);
+
+        /* Make dest binary image */
+    if ((pixd = pixCreate(wd, hd, 1)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    pixCopyResolution(pixd, pixs);
+    pixScaleResolution(pixd, 4.0, 4.0);
+    wpld = pixGetWpl(pixd);
+    datad = pixGetData(pixd);
+
+        /* Start with the first src and the first 3 dest lines */
+    memcpy(bufs, datas, 4 * wpls);   /* first src line */
+    memcpy(bufs + wpls, datas + wpls, 4 * wpls);  /* 2nd src line */
+    scaleGray4xLILineLow(lineb, wplb, bufs, ws, wpls, 0);  /* 4 b lines */
+    lined = datad;
+    for (j = 0; j < 3; j++) {  /* first 3 d lines of Q */
+        ditherToBinaryLineLow(lined + j * wpld, wd, lineb + j * wplb,
+                              lineb + (j + 1) * wplb,
+                              DEFAULT_CLIP_LOWER_1, DEFAULT_CLIP_UPPER_1, 0);
+    }
+
+        /* Do all but last src line */
+    for (i = 1; i < hsm; i++) {
+        memcpy(bufs, datas + i * wpls, 4 * wpls);  /* i-th src line */
+        memcpy(bufs + wpls, datas + (i + 1) * wpls, 4 * wpls);
+        memcpy(linebp, lineb + 3 * wplb, 4 * wplb);
+        scaleGray4xLILineLow(lineb, wplb, bufs, ws, wpls, 0);  /* 4 b lines */
+        lined = datad + 4 * i * wpld;
+        ditherToBinaryLineLow(lined - wpld, wd, linebp, lineb,
+                              DEFAULT_CLIP_LOWER_1, DEFAULT_CLIP_UPPER_1, 0);
+                                                     /* 4th dest line of Q */
+        for (j = 0; j < 3; j++) {  /* next 3 d lines of Quad */
+            ditherToBinaryLineLow(lined + j * wpld, wd, lineb + j * wplb,
+                                  lineb + (j + 1) * wplb,
+                                 DEFAULT_CLIP_LOWER_1, DEFAULT_CLIP_UPPER_1, 0);
+        }
+    }
+
+        /* Do the last src line and the last 5 dest lines */
+    memcpy(bufs, datas + hsm * wpls, 4 * wpls);  /* hsm-th src line */
+    memcpy(linebp, lineb + 3 * wplb, 4 * wplb);   /* 1 b line */
+    scaleGray4xLILineLow(lineb, wplb, bufs, ws, wpls, 1);  /* 4 b lines */
+    lined = datad + 4 * hsm * wpld;
+    ditherToBinaryLineLow(lined - wpld, wd, linebp, lineb,
+                          DEFAULT_CLIP_LOWER_1, DEFAULT_CLIP_UPPER_1, 0);
+                                                   /* 4th dest line of Q */
+    for (j = 0; j < 3; j++) {  /* next 3 d lines of Quad */
+        ditherToBinaryLineLow(lined + j * wpld, wd, lineb + j * wplb,
+                              lineb + (j + 1) * wplb,
+                              DEFAULT_CLIP_LOWER_1, DEFAULT_CLIP_UPPER_1, 0);
+    }
+        /* And finally, the last dest line */
+    ditherToBinaryLineLow(lined + 3 * wpld, wd, lineb + 3 * wplb, NULL,
+                              DEFAULT_CLIP_LOWER_1, DEFAULT_CLIP_UPPER_1, 1);
+
+    LEPT_FREE(bufs);
+    LEPT_FREE(lineb);
+    LEPT_FREE(linebp);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                    Downscaling using min or max                       *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixScaleGrayMinMax()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *              xfact (x downscaling factor; integer)
+ *              yfact (y downscaling factor; integer)
+ *              type (L_CHOOSE_MIN, L_CHOOSE_MAX, L_CHOOSE_MAX_MIN_DIFF)
+ *      Return: pixd (8 bpp)
+ *
+ *  Notes:
+ *      (1) The downscaled pixels in pixd are the min, max or (max - min)
+ *          of the corresponding set of xfact * yfact pixels in pixs.
+ *      (2) Using L_CHOOSE_MIN is equivalent to a grayscale erosion,
+ *          using a brick Sel of size (xfact * yfact), followed by
+ *          subsampling within each (xfact * yfact) cell.  Using
+ *          L_CHOOSE_MAX is equivalent to the corresponding dilation.
+ *      (3) Using L_CHOOSE_MAX_MIN_DIFF finds the difference between max
+ *          and min values in each cell.
+ *      (4) For the special case of downscaling by 2x in both directions,
+ *          pixScaleGrayMinMax2() is about 2x more efficient.
+ */
+PIX *
+pixScaleGrayMinMax(PIX     *pixs,
+                   l_int32  xfact,
+                   l_int32  yfact,
+                   l_int32  type)
+{
+l_int32    ws, hs, wd, hd, wpls, wpld, i, j, k, m;
+l_int32    minval, maxval, val;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixScaleGrayMinMax");
+
+    if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs undefined, not 8 bpp, or cmapped",
+                                procName, NULL);
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    if (type != L_CHOOSE_MIN && type != L_CHOOSE_MAX &&
+        type != L_CHOOSE_MAX_MIN_DIFF)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    if (xfact < 1 || yfact < 1)
+        return (PIX *)ERROR_PTR("xfact and yfact must be >= 1", procName, NULL);
+
+    if (xfact == 2 && yfact == 2)
+        return pixScaleGrayMinMax2(pixs, type);
+
+    wd = ws / xfact;
+    if (wd == 0) {  /* single tile */
+        wd = 1;
+        xfact = ws;
+    }
+    hd = hs / yfact;
+    if (hd == 0) {  /* single tile */
+        hd = 1;
+        yfact = hs;
+    }
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < hd; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < wd; j++) {
+            if (type == L_CHOOSE_MIN || type == L_CHOOSE_MAX_MIN_DIFF) {
+                minval = 255;
+                for (k = 0; k < yfact; k++) {
+                    lines = datas + (yfact * i + k) * wpls;
+                    for (m = 0; m < xfact; m++) {
+                        val = GET_DATA_BYTE(lines, xfact * j + m);
+                        if (val < minval)
+                            minval = val;
+                    }
+                }
+            }
+            if (type == L_CHOOSE_MAX || type == L_CHOOSE_MAX_MIN_DIFF) {
+                maxval = 0;
+                for (k = 0; k < yfact; k++) {
+                    lines = datas + (yfact * i + k) * wpls;
+                    for (m = 0; m < xfact; m++) {
+                        val = GET_DATA_BYTE(lines, xfact * j + m);
+                        if (val > maxval)
+                            maxval = val;
+                    }
+                }
+            }
+            if (type == L_CHOOSE_MIN)
+                SET_DATA_BYTE(lined, j, minval);
+            else if (type == L_CHOOSE_MAX)
+                SET_DATA_BYTE(lined, j, maxval);
+            else  /* type == L_CHOOSE_MAX_MIN_DIFF */
+                SET_DATA_BYTE(lined, j, maxval - minval);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixScaleGrayMinMax2()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *              type (L_CHOOSE_MIN, L_CHOOSE_MAX, L_CHOOSE_MAX_MIN_DIFF)
+ *      Return: pixd (8 bpp downscaled by 2x)
+ *
+ *  Notes:
+ *      (1) Special version for 2x reduction.  The downscaled pixels
+ *          in pixd are the min, max or (max - min) of the corresponding
+ *          set of 4 pixels in pixs.
+ *      (2) The max and min operations are a special case (for levels 1
+ *          and 4) of grayscale analog to the binary rank scaling operation
+ *          pixReduceRankBinary2().  Note, however, that because of
+ *          the photometric definition that higher gray values are
+ *          lighter, the erosion-like L_CHOOSE_MIN will darken
+ *          the resulting image, corresponding to a threshold level 1
+ *          in the binary case.  Likewise, L_CHOOSE_MAX will lighten
+ *          the pixd, corresponding to a threshold level of 4.
+ *      (3) To choose any of the four rank levels in a 2x grayscale
+ *          reduction, use pixScaleGrayRank2().
+ *      (4) This runs at about 70 MPix/sec/GHz of source data for
+ *          erosion and dilation.
+ */
+PIX *
+pixScaleGrayMinMax2(PIX     *pixs,
+                    l_int32  type)
+{
+l_int32    ws, hs, wd, hd, wpls, wpld, i, j, k;
+l_int32    minval, maxval;
+l_int32    val[4];
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixScaleGrayMinMax2");
+
+    if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs undefined, not 8 bpp, or cmapped",
+                                procName, NULL);
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    if (ws < 2 || hs < 2)
+        return (PIX *)ERROR_PTR("too small: ws < 2 or hs < 2", procName, NULL);
+    if (type != L_CHOOSE_MIN && type != L_CHOOSE_MAX &&
+        type != L_CHOOSE_MAX_MIN_DIFF)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+
+    wd = ws / 2;
+    hd = hs / 2;
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < hd; i++) {
+        lines = datas + 2 * i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < wd; j++) {
+            val[0] = GET_DATA_BYTE(lines, 2 * j);
+            val[1] = GET_DATA_BYTE(lines, 2 * j + 1);
+            val[2] = GET_DATA_BYTE(lines + wpls, 2 * j);
+            val[3] = GET_DATA_BYTE(lines + wpls, 2 * j + 1);
+            if (type == L_CHOOSE_MIN || type == L_CHOOSE_MAX_MIN_DIFF) {
+                minval = 255;
+                for (k = 0; k < 4; k++) {
+                    if (val[k] < minval)
+                        minval = val[k];
+                }
+            }
+            if (type == L_CHOOSE_MAX || type == L_CHOOSE_MAX_MIN_DIFF) {
+                maxval = 0;
+                for (k = 0; k < 4; k++) {
+                    if (val[k] > maxval)
+                        maxval = val[k];
+                }
+            }
+            if (type == L_CHOOSE_MIN)
+                SET_DATA_BYTE(lined, j, minval);
+            else if (type == L_CHOOSE_MAX)
+                SET_DATA_BYTE(lined, j, maxval);
+            else  /* type == L_CHOOSE_MAX_MIN_DIFF */
+                SET_DATA_BYTE(lined, j, maxval - minval);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                  Grayscale downscaling using rank value               *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixScaleGrayRankCascade()
+ *
+ *      Input:  pixs (8 bpp, not cmapped)
+ *              level1, ... level4 (rank thresholds, in set {0, 1, 2, 3, 4})
+ *      Return: pixd (8 bpp, downscaled by up to 16x)
+ *
+ *  Notes:
+ *      (1) This performs up to four cascaded 2x rank reductions.
+ *      (2) Use level = 0 to truncate the cascade.
+ */
+PIX *
+pixScaleGrayRankCascade(PIX     *pixs,
+                        l_int32  level1,
+                        l_int32  level2,
+                        l_int32  level3,
+                        l_int32  level4)
+{
+PIX  *pixt1, *pixt2, *pixt3, *pixt4;
+
+    PROCNAME("pixScaleGrayRankCascade");
+
+    if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs undefined, not 8 bpp, or cmapped",
+                                procName, NULL);
+    if (level1 > 4 || level2 > 4 || level3 > 4 || level4 > 4)
+        return (PIX *)ERROR_PTR("levels must not exceed 4", procName, NULL);
+
+    if (level1 <= 0) {
+        L_WARNING("no reduction because level1 not > 0\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+    pixt1 = pixScaleGrayRank2(pixs, level1);
+    if (level2 <= 0)
+        return pixt1;
+
+    pixt2 = pixScaleGrayRank2(pixt1, level2);
+    pixDestroy(&pixt1);
+    if (level3 <= 0)
+        return pixt2;
+
+    pixt3 = pixScaleGrayRank2(pixt2, level3);
+    pixDestroy(&pixt2);
+    if (level4 <= 0)
+        return pixt3;
+
+    pixt4 = pixScaleGrayRank2(pixt3, level4);
+    pixDestroy(&pixt3);
+    return pixt4;
+}
+
+
+/*!
+ *  pixScaleGrayRank2()
+ *
+ *      Input:  pixs (8 bpp, no cmap)
+ *              rank (1 (darkest), 2, 3, 4 (lightest))
+ *      Return: pixd (8 bpp, downscaled by 2x)
+ *
+ *  Notes:
+ *      (1) Rank 2x reduction.  If rank == 1(4), the downscaled pixels
+ *          in pixd are the min(max) of the corresponding set of
+ *          4 pixels in pixs.  Values 2 and 3 are intermediate.
+ *      (2) This is the grayscale analog to the binary rank scaling operation
+ *          pixReduceRankBinary2().  Here, because of the photometric
+ *          definition that higher gray values are lighter, rank 1 gives
+ *          the darkest pixel, whereas rank 4 gives the lightest pixel.
+ *          This is opposite to the binary rank operation.
+ *      (3) For rank = 1 and 4, this calls pixScaleGrayMinMax2(),
+ *          which runs at about 70 MPix/sec/GHz of source data.
+ *          For rank 2 and 3, this runs 3x slower, at about 25 MPix/sec/GHz.
+ */
+PIX *
+pixScaleGrayRank2(PIX     *pixs,
+                  l_int32  rank)
+{
+l_int32    ws, hs, wd, hd, wpls, wpld, i, j, k, m;
+l_int32    minval, maxval, rankval, minindex, maxindex;
+l_int32    val[4];
+l_int32    midval[4];  /* should only use 2 of these */
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixScaleGrayRank2");
+
+    if (!pixs || pixGetDepth(pixs) != 8 || pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs undefined, not 8 bpp, or cmapped",
+                                procName, NULL);
+    if (rank < 1 || rank > 4)
+        return (PIX *)ERROR_PTR("invalid rank", procName, NULL);
+
+    if (rank == 1)
+        return pixScaleGrayMinMax2(pixs, L_CHOOSE_MIN);
+    if (rank == 4)
+        return pixScaleGrayMinMax2(pixs, L_CHOOSE_MAX);
+
+    pixGetDimensions(pixs, &ws, &hs, NULL);
+    wd = ws / 2;
+    hd = hs / 2;
+    if ((pixd = pixCreate(wd, hd, 8)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixCopyInputFormat(pixd, pixs);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    for (i = 0; i < hd; i++) {
+        lines = datas + 2 * i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < wd; j++) {
+            val[0] = GET_DATA_BYTE(lines, 2 * j);
+            val[1] = GET_DATA_BYTE(lines, 2 * j + 1);
+            val[2] = GET_DATA_BYTE(lines + wpls, 2 * j);
+            val[3] = GET_DATA_BYTE(lines + wpls, 2 * j + 1);
+            minval = maxval = val[0];
+            minindex = maxindex = 0;
+            for (k = 1; k < 4; k++) {
+                if (val[k] < minval) {
+                    minval = val[k];
+                    minindex = k;
+                    continue;
+                }
+                if (val[k] > maxval) {
+                    maxval = val[k];
+                    maxindex = k;
+                }
+            }
+            for (k = 0, m = 0; k < 4; k++) {
+                if (k == minindex || k == maxindex)
+                    continue;
+                midval[m++] = val[k];
+            }
+            if (m > 2)  /* minval == maxval; all val[k] are the same */
+                rankval = minval;
+            else if (rank == 2)
+                rankval = L_MIN(midval[0], midval[1]);
+            else  /* rank == 3 */
+                rankval = L_MAX(midval[0], midval[1]);
+            SET_DATA_BYTE(lined, j, rankval);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*------------------------------------------------------------------------*
+ *           Helper function for transferring alpha with scaling          *
+ *------------------------------------------------------------------------*/
+/*!
+ *  pixScaleAndTransferAlpha()
+ *
+ *      Input:  pixd  (32 bpp, scaled image)
+ *              pixs  (32 bpp, original unscaled image)
+ *              scalex, scaley (both > 0.0)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This scales the alpha component of pixs and inserts into pixd.
+ */
+l_int32
+pixScaleAndTransferAlpha(PIX       *pixd,
+                         PIX       *pixs,
+                         l_float32  scalex,
+                         l_float32  scaley)
+{
+PIX  *pix1, *pix2;
+
+    PROCNAME("pixScaleAndTransferAlpha");
+
+    if (!pixs || !pixd)
+        return ERROR_INT("pixs and pixd not both defined", procName, 1);
+    if (pixGetDepth(pixs) != 32 || pixGetSpp(pixs) != 4)
+        return ERROR_INT("pixs not 32 bpp and 4 spp", procName, 1);
+    if (pixGetDepth(pixd) != 32)
+        return ERROR_INT("pixd not 32 bpp", procName, 1);
+
+    if (scalex == 1.0 && scaley == 1.0) {
+        pixCopyRGBComponent(pixd, pixs, L_ALPHA_CHANNEL);
+        return 0;
+    }
+
+    pix1 = pixGetRGBComponent(pixs, L_ALPHA_CHANNEL);
+    pix2 = pixScale(pix1, scalex, scaley);
+    pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL);
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------------*
+ *    RGB scaling including alpha (blend) component and gamma transform   *
+ *------------------------------------------------------------------------*/
+/*!
+ *  pixScaleWithAlpha()
+ *
+ *      Input:  pixs (32 bpp rgb or cmapped)
+ *              scalex, scaley (must be > 0.0)
+ *              pixg (<optional> 8 bpp, can be null)
+ *              fract (between 0.0 and 1.0, with 0.0 fully transparent
+ *                     and 1.0 fully opaque)
+ *      Return: pixd (32 bpp rgba), or null on error
+ *
+ *  Notes:
+ *      (1) The alpha channel is transformed separately from pixs,
+ *          and aligns with it, being fully transparent outside the
+ *          boundary of the transformed pixs.  For pixels that are fully
+ *          transparent, a blending function like pixBlendWithGrayMask()
+ *          will give zero weight to corresponding pixels in pixs.
+ *      (2) Scaling is done with area mapping or linear interpolation,
+ *          depending on the scale factors.  Default sharpening is done.
+ *      (3) If pixg is NULL, it is generated as an alpha layer that is
+ *          partially opaque, using @fract.  Otherwise, it is cropped
+ *          to pixs if required, and @fract is ignored.  The alpha
+ *          channel in pixs is never used.
+ *      (4) Colormaps are removed to 32 bpp.
+ *      (5) The default setting for the border values in the alpha channel
+ *          is 0 (transparent) for the outermost ring of pixels and
+ *          (0.5 * fract * 255) for the second ring.  When blended over
+ *          a second image, this
+ *          (a) shrinks the visible image to make a clean overlap edge
+ *              with an image below, and
+ *          (b) softens the edges by weakening the aliasing there.
+ *          Use l_setAlphaMaskBorder() to change these values.
+ *      (6) A subtle use of gamma correction is to remove gamma correction
+ *          before scaling and restore it afterwards.  This is done
+ *          by sandwiching this function between a gamma/inverse-gamma
+ *          photometric transform:
+ *              pixt = pixGammaTRCWithAlpha(NULL, pixs, 1.0 / gamma, 0, 255);
+ *              pixd = pixScaleWithAlpha(pixt, scalex, scaley, NULL, fract);
+ *              pixGammaTRCWithAlpha(pixd, pixd, gamma, 0, 255);
+ *              pixDestroy(&pixt);
+ *          This has the side-effect of producing artifacts in the very
+ *          dark regions.
+ *
+ *  *** Warning: implicit assumption about RGB component ordering ***
+ */
+PIX *
+pixScaleWithAlpha(PIX       *pixs,
+                  l_float32  scalex,
+                  l_float32  scaley,
+                  PIX       *pixg,
+                  l_float32  fract)
+{
+l_int32  ws, hs, d, spp;
+PIX     *pixd, *pix32, *pixg2, *pixgs;
+
+    PROCNAME("pixScaleWithAlpha");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &ws, &hs, &d);
+    if (d != 32 && !pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs not cmapped or 32 bpp", procName, NULL);
+    if (scalex <= 0.0 || scaley <= 0.0)
+        return (PIX *)ERROR_PTR("scale factor <= 0.0", procName, NULL);
+    if (pixg && pixGetDepth(pixg) != 8) {
+        L_WARNING("pixg not 8 bpp; using @fract transparent alpha\n", procName);
+        pixg = NULL;
+    }
+    if (!pixg && (fract < 0.0 || fract > 1.0)) {
+        L_WARNING("invalid fract; using fully opaque\n", procName);
+        fract = 1.0;
+    }
+    if (!pixg && fract == 0.0)
+        L_WARNING("transparent alpha; image will not be blended\n", procName);
+
+        /* Make sure input to scaling is 32 bpp rgb, and scale it */
+    if (d != 32)
+        pix32 = pixConvertTo32(pixs);
+    else
+        pix32 = pixClone(pixs);
+    spp = pixGetSpp(pix32);
+    pixSetSpp(pix32, 3);  /* ignore the alpha channel for scaling */
+    pixd = pixScale(pix32, scalex, scaley);
+    pixSetSpp(pix32, spp);  /* restore initial value in case it's a clone */
+    pixDestroy(&pix32);
+
+        /* Set up alpha layer with a fading border and scale it */
+    if (!pixg) {
+        pixg2 = pixCreate(ws, hs, 8);
+        if (fract == 1.0)
+            pixSetAll(pixg2);
+        else if (fract > 0.0)
+            pixSetAllArbitrary(pixg2, (l_int32)(255.0 * fract));
+    } else {
+        pixg2 = pixResizeToMatch(pixg, NULL, ws, hs);
+    }
+    if (ws > 10 && hs > 10) {  /* see note 4 */
+        pixSetBorderRingVal(pixg2, 1,
+                            (l_int32)(255.0 * fract * AlphaMaskBorderVals[0]));
+        pixSetBorderRingVal(pixg2, 2,
+                            (l_int32)(255.0 * fract * AlphaMaskBorderVals[1]));
+    }
+    pixgs = pixScaleGeneral(pixg2, scalex, scaley, 0.0, 0);
+
+        /* Combine into a 4 spp result */
+    pixSetRGBComponent(pixd, pixgs, L_ALPHA_CHANNEL);
+    pixCopyInputFormat(pixd, pixs);
+
+    pixDestroy(&pixg2);
+    pixDestroy(&pixgs);
+    return pixd;
+}
+
diff --git a/src/scalelow.c b/src/scalelow.c
new file mode 100644 (file)
index 0000000..c25b742
--- /dev/null
@@ -0,0 +1,2449 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  scalelow.c
+ *
+ *         Color (interpolated) scaling: general case
+ *                  void       scaleColorLILow()
+ *
+ *         Grayscale (interpolated) scaling: general case
+ *                  void       scaleGrayLILow()
+ *
+ *         Color (interpolated) scaling: 2x upscaling
+ *                  void       scaleColor2xLILow()
+ *                  void       scaleColor2xLILineLow()
+ *
+ *         Grayscale (interpolated) scaling: 2x upscaling
+ *                  void       scaleGray2xLILow()
+ *                  void       scaleGray2xLILineLow()
+ *
+ *         Grayscale (interpolated) scaling: 4x upscaling
+ *                  void       scaleGray4xLILow()
+ *                  void       scaleGray4xLILineLow()
+ *
+ *         Grayscale and color scaling by closest pixel sampling
+ *                  l_int32    scaleBySamplingLow()
+ *
+ *         Color and grayscale downsampling with (antialias) lowpass filter
+ *                  l_int32    scaleSmoothLow()
+ *                  void       scaleRGBToGray2Low()
+ *
+ *         Color and grayscale downsampling with (antialias) area mapping
+ *                  l_int32    scaleColorAreaMapLow()
+ *                  l_int32    scaleGrayAreaMapLow()
+ *                  l_int32    scaleAreaMapLow2()
+ *
+ *         Binary scaling by closest pixel sampling
+ *                  l_int32    scaleBinaryLow()
+ *
+ *         Scale-to-gray 2x
+ *                  void       scaleToGray2Low()
+ *                  l_uint32  *makeSumTabSG2()
+ *                  l_uint8   *makeValTabSG2()
+ *
+ *         Scale-to-gray 3x
+ *                  void       scaleToGray3Low()
+ *                  l_uint32  *makeSumTabSG3()
+ *                  l_uint8   *makeValTabSG3()
+ *
+ *         Scale-to-gray 4x
+ *                  void       scaleToGray4Low()
+ *                  l_uint32  *makeSumTabSG4()
+ *                  l_uint8   *makeValTabSG4()
+ *
+ *         Scale-to-gray 6x
+ *                  void       scaleToGray6Low()
+ *                  l_uint8   *makeValTabSG6()
+ *
+ *         Scale-to-gray 8x
+ *                  void       scaleToGray8Low()
+ *                  l_uint8   *makeValTabSG8()
+ *
+ *         Scale-to-gray 16x
+ *                  void       scaleToGray16Low()
+ *
+ *         Grayscale mipmap
+ *                  l_int32    scaleMipmapLow()
+ *
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_OVERFLOW   0
+#define  DEBUG_UNROLLING  0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*------------------------------------------------------------------*
+ *            General linear interpolated color scaling             *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleColorLILow()
+ *
+ *  We choose to divide each pixel into 16 x 16 sub-pixels.
+ *  Linear interpolation is equivalent to finding the
+ *  fractional area (i.e., number of sub-pixels divided
+ *  by 256) associated with each of the four nearest src pixels,
+ *  and weighting each pixel value by this fractional area.
+ *
+ *  P3 speed is about 7 x 10^6 dst pixels/sec/GHz
+ */
+void
+scaleColorLILow(l_uint32  *datad,
+               l_int32    wd,
+               l_int32    hd,
+               l_int32    wpld,
+               l_uint32  *datas,
+               l_int32    ws,
+               l_int32    hs,
+               l_int32    wpls)
+{
+l_int32    i, j, wm2, hm2;
+l_int32    xpm, ypm;  /* location in src image, to 1/16 of a pixel */
+l_int32    xp, yp, xf, yf;  /* src pixel and pixel fraction coordinates */
+l_int32    v00r, v01r, v10r, v11r, v00g, v01g, v10g, v11g;
+l_int32    v00b, v01b, v10b, v11b, area00, area01, area10, area11;
+l_uint32   pixels1, pixels2, pixels3, pixels4, pixel;
+l_uint32  *lines, *lined;
+l_float32  scx, scy;
+
+        /* (scx, scy) are scaling factors that are applied to the
+         * dest coords to get the corresponding src coords.
+         * We need them because we iterate over dest pixels
+         * and must find the corresponding set of src pixels. */
+    scx = 16. * (l_float32)ws / (l_float32)wd;
+    scy = 16. * (l_float32)hs / (l_float32)hd;
+    wm2 = ws - 2;
+    hm2 = hs - 2;
+
+        /* Iterate over the destination pixels */
+    for (i = 0; i < hd; i++) {
+        ypm = (l_int32)(scy * (l_float32)i);
+        yp = ypm >> 4;
+        yf = ypm & 0x0f;
+        lined = datad + i * wpld;
+        lines = datas + yp * wpls;
+        for (j = 0; j < wd; j++) {
+            xpm = (l_int32)(scx * (l_float32)j);
+            xp = xpm >> 4;
+            xf = xpm & 0x0f;
+
+                /* Do bilinear interpolation.  This is a simple
+                 * generalization of the calculation in scaleGrayLILow().
+                 * Without this, we could simply subsample:
+                 *     *(lined + j) = *(lines + xp);
+                 * which is faster but gives lousy results!  */
+            pixels1 = *(lines + xp);
+
+            if (xp > wm2 || yp > hm2) {
+                if (yp > hm2 && xp <= wm2) {  /* pixels near bottom */
+                    pixels2 = *(lines + xp + 1);
+                    pixels3 = pixels1;
+                    pixels4 = pixels2;
+                } else if (xp > wm2 && yp <= hm2) {  /* pixels near rt side */
+                    pixels2 = pixels1;
+                    pixels3 = *(lines + wpls + xp);
+                    pixels4 = pixels3;
+                } else {  /* pixels at LR corner */
+                    pixels4 = pixels3 = pixels2 = pixels1;
+                }
+            } else {
+                pixels2 = *(lines + xp + 1);
+                pixels3 = *(lines + wpls + xp);
+                pixels4 = *(lines + wpls + xp + 1);
+            }
+
+            area00 = (16 - xf) * (16 - yf);
+            area10 = xf * (16 - yf);
+            area01 = (16 - xf) * yf;
+            area11 = xf * yf;
+            v00r = area00 * ((pixels1 >> L_RED_SHIFT) & 0xff);
+            v00g = area00 * ((pixels1 >> L_GREEN_SHIFT) & 0xff);
+            v00b = area00 * ((pixels1 >> L_BLUE_SHIFT) & 0xff);
+            v10r = area10 * ((pixels2 >> L_RED_SHIFT) & 0xff);
+            v10g = area10 * ((pixels2 >> L_GREEN_SHIFT) & 0xff);
+            v10b = area10 * ((pixels2 >> L_BLUE_SHIFT) & 0xff);
+            v01r = area01 * ((pixels3 >> L_RED_SHIFT) & 0xff);
+            v01g = area01 * ((pixels3 >> L_GREEN_SHIFT) & 0xff);
+            v01b = area01 * ((pixels3 >> L_BLUE_SHIFT) & 0xff);
+            v11r = area11 * ((pixels4 >> L_RED_SHIFT) & 0xff);
+            v11g = area11 * ((pixels4 >> L_GREEN_SHIFT) & 0xff);
+            v11b = area11 * ((pixels4 >> L_BLUE_SHIFT) & 0xff);
+            pixel = (((v00r + v10r + v01r + v11r + 128) << 16) & 0xff000000) |
+                    (((v00g + v10g + v01g + v11g + 128) << 8) & 0x00ff0000) |
+                    ((v00b + v10b + v01b + v11b + 128) & 0x0000ff00);
+            *(lined + j) = pixel;
+        }
+    }
+
+    return;
+}
+
+
+/*------------------------------------------------------------------*
+ *            General linear interpolated gray scaling              *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleGrayLILow()
+ *
+ *  We choose to divide each pixel into 16 x 16 sub-pixels.
+ *  Linear interpolation is equivalent to finding the
+ *  fractional area (i.e., number of sub-pixels divided
+ *  by 256) associated with each of the four nearest src pixels,
+ *  and weighting each pixel value by this fractional area.
+ */
+void
+scaleGrayLILow(l_uint32  *datad,
+               l_int32    wd,
+               l_int32    hd,
+               l_int32    wpld,
+               l_uint32  *datas,
+               l_int32    ws,
+               l_int32    hs,
+               l_int32    wpls)
+{
+l_int32    i, j, wm2, hm2;
+l_int32    xpm, ypm;  /* location in src image, to 1/16 of a pixel */
+l_int32    xp, yp, xf, yf;  /* src pixel and pixel fraction coordinates */
+l_int32    v00, v01, v10, v11, v00_val, v01_val, v10_val, v11_val;
+l_uint8    val;
+l_uint32  *lines, *lined;
+l_float32  scx, scy;
+
+        /* (scx, scy) are scaling factors that are applied to the
+         * dest coords to get the corresponding src coords.
+         * We need them because we iterate over dest pixels
+         * and must find the corresponding set of src pixels. */
+    scx = 16. * (l_float32)ws / (l_float32)wd;
+    scy = 16. * (l_float32)hs / (l_float32)hd;
+    wm2 = ws - 2;
+    hm2 = hs - 2;
+
+        /* Iterate over the destination pixels */
+    for (i = 0; i < hd; i++) {
+        ypm = (l_int32)(scy * (l_float32)i);
+        yp = ypm >> 4;
+        yf = ypm & 0x0f;
+        lined = datad + i * wpld;
+        lines = datas + yp * wpls;
+        for (j = 0; j < wd; j++) {
+            xpm = (l_int32)(scx * (l_float32)j);
+            xp = xpm >> 4;
+            xf = xpm & 0x0f;
+
+                /* Do bilinear interpolation.  Without this, we could
+                 * simply subsample:
+                 *   SET_DATA_BYTE(lined, j, GET_DATA_BYTE(lines, xp));
+                 * which is faster but gives lousy results!  */
+            v00_val = GET_DATA_BYTE(lines, xp);
+            if (xp > wm2 || yp > hm2) {
+                if (yp > hm2 && xp <= wm2) {  /* pixels near bottom */
+                    v01_val = v00_val;
+                    v10_val = GET_DATA_BYTE(lines, xp + 1);
+                    v11_val = v10_val;
+                } else if (xp > wm2 && yp <= hm2) {  /* pixels near rt side */
+                    v01_val = GET_DATA_BYTE(lines + wpls, xp);
+                    v10_val = v00_val;
+                    v11_val = v01_val;
+                } else {  /* pixels at LR corner */
+                    v10_val = v01_val = v11_val = v00_val;
+                }
+            } else {
+                v10_val = GET_DATA_BYTE(lines, xp + 1);
+                v01_val = GET_DATA_BYTE(lines + wpls, xp);
+                v11_val = GET_DATA_BYTE(lines + wpls, xp + 1);
+            }
+
+            v00 = (16 - xf) * (16 - yf) * v00_val;
+            v10 = xf * (16 - yf) * v10_val;
+            v01 = (16 - xf) * yf * v01_val;
+            v11 = xf * yf * v11_val;
+
+            val = (l_uint8)((v00 + v01 + v10 + v11 + 128) / 256);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    return;
+}
+
+
+/*------------------------------------------------------------------*
+ *                2x linear interpolated color scaling              *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleColor2xLILow()
+ *
+ *  This is a special case of 2x expansion by linear
+ *  interpolation.  Each src pixel contains 4 dest pixels.
+ *  The 4 dest pixels in src pixel 1 are numbered at
+ *  their UL corners.  The 4 dest pixels in src pixel 1
+ *  are related to that src pixel and its 3 neighboring
+ *  src pixels as follows:
+ *
+ *             1-----2-----|-----|-----|
+ *             |     |     |     |     |
+ *             |     |     |     |     |
+ *  src 1 -->  3-----4-----|     |     |  <-- src 2
+ *             |     |     |     |     |
+ *             |     |     |     |     |
+ *             |-----|-----|-----|-----|
+ *             |     |     |     |     |
+ *             |     |     |     |     |
+ *  src 3 -->  |     |     |     |     |  <-- src 4
+ *             |     |     |     |     |
+ *             |     |     |     |     |
+ *             |-----|-----|-----|-----|
+ *
+ *           dest      src
+ *           ----      ---
+ *           dp1    =  sp1
+ *           dp2    =  (sp1 + sp2) / 2
+ *           dp3    =  (sp1 + sp3) / 2
+ *           dp4    =  (sp1 + sp2 + sp3 + sp4) / 4
+ *
+ *  We iterate over the src pixels, and unroll the calculation
+ *  for each set of 4 dest pixels corresponding to that src
+ *  pixel, caching pixels for the next src pixel whenever possible.
+ *  The method is exactly analogous to the one we use for
+ *  scaleGray2xLILow() and its line version.
+ *
+ *  P3 speed is about 5 x 10^7 dst pixels/sec/GHz
+ */
+void
+scaleColor2xLILow(l_uint32  *datad,
+                  l_int32    wpld,
+                  l_uint32  *datas,
+                  l_int32    ws,
+                  l_int32    hs,
+                  l_int32    wpls)
+{
+l_int32    i, hsm;
+l_uint32  *lines, *lined;
+
+    hsm = hs - 1;
+
+        /* We're taking 2 src and 2 dest lines at a time,
+         * and for each src line, we're computing 2 dest lines.
+         * Call these 2 dest lines:  destline1 and destline2.
+         * The first src line is used for destline 1.
+         * On all but the last src line, both src lines are
+         * used in the linear interpolation for destline2.
+         * On the last src line, both destline1 and destline2
+         * are computed using only that src line (because there
+         * isn't a lower src line). */
+
+        /* iterate over all but the last src line */
+    for (i = 0; i < hsm; i++) {
+        lines = datas + i * wpls;
+        lined = datad + 2 * i * wpld;
+        scaleColor2xLILineLow(lined, wpld, lines, ws, wpls, 0);
+    }
+
+        /* last src line */
+    lines = datas + hsm * wpls;
+    lined = datad + 2 * hsm * wpld;
+    scaleColor2xLILineLow(lined, wpld, lines, ws, wpls, 1);
+
+    return;
+}
+
+
+/*!
+ *  scaleColor2xLILineLow()
+ *
+ *      Input:  lined   (ptr to top destline, to be made from current src line)
+ *              wpld
+ *              lines   (ptr to current src line)
+ *              ws
+ *              wpls
+ *              lastlineflag  (1 if last src line; 0 otherwise)
+ *      Return: void
+ *
+ *  *** Warning: implicit assumption about RGB component ordering ***
+ */
+void
+scaleColor2xLILineLow(l_uint32  *lined,
+                      l_int32    wpld,
+                      l_uint32  *lines,
+                      l_int32    ws,
+                      l_int32    wpls,
+                      l_int32    lastlineflag)
+{
+l_int32    j, jd, wsm;
+l_int32    rval1, rval2, rval3, rval4, gval1, gval2, gval3, gval4;
+l_int32    bval1, bval2, bval3, bval4;
+l_uint32   pixels1, pixels2, pixels3, pixels4, pixel;
+l_uint32  *linesp, *linedp;
+
+    wsm = ws - 1;
+
+    if (lastlineflag == 0) {
+        linesp = lines + wpls;
+        linedp = lined + wpld;
+        pixels1 = *lines;
+        pixels3 = *linesp;
+
+            /* initialize with v(2) and v(4) */
+        rval2 = pixels1 >> 24;
+        gval2 = (pixels1 >> 16) & 0xff;
+        bval2 = (pixels1 >> 8) & 0xff;
+        rval4 = pixels3 >> 24;
+        gval4 = (pixels3 >> 16) & 0xff;
+        bval4 = (pixels3 >> 8) & 0xff;
+
+        for (j = 0, jd = 0; j < wsm; j++, jd += 2) {
+                /* shift in previous src values */
+            rval1 = rval2;
+            gval1 = gval2;
+            bval1 = bval2;
+            rval3 = rval4;
+            gval3 = gval4;
+            bval3 = bval4;
+                /* get new src values */
+            pixels2 = *(lines + j + 1);
+            pixels4 = *(linesp + j + 1);
+            rval2 = pixels2 >> 24;
+            gval2 = (pixels2 >> 16) & 0xff;
+            bval2 = (pixels2 >> 8) & 0xff;
+            rval4 = pixels4 >> 24;
+            gval4 = (pixels4 >> 16) & 0xff;
+            bval4 = (pixels4 >> 8) & 0xff;
+                /* save dest values */
+            pixel = (rval1 << 24 | gval1 << 16 | bval1 << 8);
+            *(lined + jd) = pixel;                               /* pix 1 */
+            pixel = ((((rval1 + rval2) << 23) & 0xff000000) |
+                     (((gval1 + gval2) << 15) & 0x00ff0000) |
+                     (((bval1 + bval2) << 7) & 0x0000ff00));
+            *(lined + jd + 1) = pixel;                           /* pix 2 */
+            pixel = ((((rval1 + rval3) << 23) & 0xff000000) |
+                     (((gval1 + gval3) << 15) & 0x00ff0000) |
+                     (((bval1 + bval3) << 7) & 0x0000ff00));
+            *(linedp + jd) = pixel;                              /* pix 3 */
+            pixel = ((((rval1 + rval2 + rval3 + rval4) << 22) & 0xff000000) |
+                     (((gval1 + gval2 + gval3 + gval4) << 14) & 0x00ff0000) |
+                     (((bval1 + bval2 + bval3 + bval4) << 6) & 0x0000ff00));
+            *(linedp + jd + 1) = pixel;                          /* pix 4 */
+        }
+            /* last src pixel on line */
+        rval1 = rval2;
+        gval1 = gval2;
+        bval1 = bval2;
+        rval3 = rval4;
+        gval3 = gval4;
+        bval3 = bval4;
+        pixel = (rval1 << 24 | gval1 << 16 | bval1 << 8);
+        *(lined + 2 * wsm) = pixel;                        /* pix 1 */
+        *(lined + 2 * wsm + 1) = pixel;                    /* pix 2 */
+        pixel = ((((rval1 + rval3) << 23) & 0xff000000) |
+                 (((gval1 + gval3) << 15) & 0x00ff0000) |
+                 (((bval1 + bval3) << 7) & 0x0000ff00));
+        *(linedp + 2 * wsm) = pixel;                       /* pix 3 */
+        *(linedp + 2 * wsm + 1) = pixel;                   /* pix 4 */
+    } else {   /* last row of src pixels: lastlineflag == 1 */
+        linedp = lined + wpld;
+        pixels2 = *lines;
+        rval2 = pixels2 >> 24;
+        gval2 = (pixels2 >> 16) & 0xff;
+        bval2 = (pixels2 >> 8) & 0xff;
+        for (j = 0, jd = 0; j < wsm; j++, jd += 2) {
+            rval1 = rval2;
+            gval1 = gval2;
+            bval1 = bval2;
+            pixels2 = *(lines + j + 1);
+            rval2 = pixels2 >> 24;
+            gval2 = (pixels2 >> 16) & 0xff;
+            bval2 = (pixels2 >> 8) & 0xff;
+            pixel = (rval1 << 24 | gval1 << 16 | bval1 << 8);
+            *(lined + jd) = pixel;                            /* pix 1 */
+            *(linedp + jd) = pixel;                           /* pix 2 */
+            pixel = ((((rval1 + rval2) << 23) & 0xff000000) |
+                     (((gval1 + gval2) << 15) & 0x00ff0000) |
+                     (((bval1 + bval2) << 7) & 0x0000ff00));
+            *(lined + jd + 1) = pixel;                        /* pix 3 */
+            *(linedp + jd + 1) = pixel;                       /* pix 4 */
+        }
+        rval1 = rval2;
+        gval1 = gval2;
+        bval1 = bval2;
+        pixel = (rval1 << 24 | gval1 << 16 | bval1 << 8);
+        *(lined + 2 * wsm) = pixel;                           /* pix 1 */
+        *(lined + 2 * wsm + 1) = pixel;                       /* pix 2 */
+        *(linedp + 2 * wsm) = pixel;                          /* pix 3 */
+        *(linedp + 2 * wsm + 1) = pixel;                      /* pix 4 */
+    }
+
+    return;
+}
+
+
+/*------------------------------------------------------------------*
+ *                2x linear interpolated gray scaling               *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleGray2xLILow()
+ *
+ *  This is a special case of 2x expansion by linear
+ *  interpolation.  Each src pixel contains 4 dest pixels.
+ *  The 4 dest pixels in src pixel 1 are numbered at
+ *  their UL corners.  The 4 dest pixels in src pixel 1
+ *  are related to that src pixel and its 3 neighboring
+ *  src pixels as follows:
+ *
+ *             1-----2-----|-----|-----|
+ *             |     |     |     |     |
+ *             |     |     |     |     |
+ *  src 1 -->  3-----4-----|     |     |  <-- src 2
+ *             |     |     |     |     |
+ *             |     |     |     |     |
+ *             |-----|-----|-----|-----|
+ *             |     |     |     |     |
+ *             |     |     |     |     |
+ *  src 3 -->  |     |     |     |     |  <-- src 4
+ *             |     |     |     |     |
+ *             |     |     |     |     |
+ *             |-----|-----|-----|-----|
+ *
+ *           dest      src
+ *           ----      ---
+ *           dp1    =  sp1
+ *           dp2    =  (sp1 + sp2) / 2
+ *           dp3    =  (sp1 + sp3) / 2
+ *           dp4    =  (sp1 + sp2 + sp3 + sp4) / 4
+ *
+ *  We iterate over the src pixels, and unroll the calculation
+ *  for each set of 4 dest pixels corresponding to that src
+ *  pixel, caching pixels for the next src pixel whenever possible.
+ */
+void
+scaleGray2xLILow(l_uint32  *datad,
+                 l_int32    wpld,
+                 l_uint32  *datas,
+                 l_int32    ws,
+                 l_int32    hs,
+                 l_int32    wpls)
+{
+l_int32    i, hsm;
+l_uint32  *lines, *lined;
+
+    hsm = hs - 1;
+
+        /* We're taking 2 src and 2 dest lines at a time,
+         * and for each src line, we're computing 2 dest lines.
+         * Call these 2 dest lines:  destline1 and destline2.
+         * The first src line is used for destline 1.
+         * On all but the last src line, both src lines are
+         * used in the linear interpolation for destline2.
+         * On the last src line, both destline1 and destline2
+         * are computed using only that src line (because there
+         * isn't a lower src line). */
+
+        /* iterate over all but the last src line */
+    for (i = 0; i < hsm; i++) {
+        lines = datas + i * wpls;
+        lined = datad + 2 * i * wpld;
+        scaleGray2xLILineLow(lined, wpld, lines, ws, wpls, 0);
+    }
+
+        /* last src line */
+    lines = datas + hsm * wpls;
+    lined = datad + 2 * hsm * wpld;
+    scaleGray2xLILineLow(lined, wpld, lines, ws, wpls, 1);
+    return;
+}
+
+
+/*!
+ *  scaleGray2xLILineLow()
+ *
+ *      Input:  lined   (ptr to top destline, to be made from current src line)
+ *              wpld
+ *              lines   (ptr to current src line)
+ *              ws
+ *              wpls
+ *              lastlineflag  (1 if last src line; 0 otherwise)
+ *      Return: void
+ */
+void
+scaleGray2xLILineLow(l_uint32  *lined,
+                     l_int32    wpld,
+                     l_uint32  *lines,
+                     l_int32    ws,
+                     l_int32    wpls,
+                     l_int32    lastlineflag)
+{
+l_int32    j, jd, wsm, w;
+l_int32    sval1, sval2, sval3, sval4;
+l_uint32  *linesp, *linedp;
+l_uint32   words, wordsp, wordd, worddp;
+
+    wsm = ws - 1;
+
+    if (lastlineflag == 0) {
+        linesp = lines + wpls;
+        linedp = lined + wpld;
+
+            /* Unroll the loop 4x and work on full words */
+        words = lines[0];
+        wordsp = linesp[0];
+        sval2 = (words >> 24) & 0xff;
+        sval4 = (wordsp >> 24) & 0xff;
+        for (j = 0, jd = 0, w = 0; j + 3 < wsm; j += 4, jd += 8, w++) {
+                /* At the top of the loop,
+                 * words == lines[w], wordsp == linesp[w]
+                 * and the top bytes of those have been loaded into
+                 * sval2 and sval4. */
+            sval1 = sval2;
+            sval2 = (words >> 16) & 0xff;
+            sval3 = sval4;
+            sval4 = (wordsp >> 16) & 0xff;
+            wordd = (sval1 << 24) | (((sval1 + sval2) >> 1) << 16);
+            worddp = (((sval1 + sval3) >> 1) << 24) |
+                (((sval1 + sval2 + sval3 + sval4) >> 2) << 16);
+
+            sval1 = sval2;
+            sval2 = (words >> 8) & 0xff;
+            sval3 = sval4;
+            sval4 = (wordsp >> 8) & 0xff;
+            wordd |= (sval1 << 8) | ((sval1 + sval2) >> 1);
+            worddp |= (((sval1 + sval3) >> 1) << 8) |
+                ((sval1 + sval2 + sval3 + sval4) >> 2);
+            lined[w * 2] = wordd;
+            linedp[w * 2] = worddp;
+
+            sval1 = sval2;
+            sval2 = words & 0xff;
+            sval3 = sval4;
+            sval4 = wordsp & 0xff;
+            wordd = (sval1 << 24) |                              /* pix 1 */
+                (((sval1 + sval2) >> 1) << 16);                  /* pix 2 */
+            worddp = (((sval1 + sval3) >> 1) << 24) |            /* pix 3 */
+                (((sval1 + sval2 + sval3 + sval4) >> 2) << 16);  /* pix 4 */
+
+                /* Load the next word as we need its first byte */
+            words = lines[w + 1];
+            wordsp = linesp[w + 1];
+            sval1 = sval2;
+            sval2 = (words >> 24) & 0xff;
+            sval3 = sval4;
+            sval4 = (wordsp >> 24) & 0xff;
+            wordd |= (sval1 << 8) |                              /* pix 1 */
+                ((sval1 + sval2) >> 1);                          /* pix 2 */
+            worddp |= (((sval1 + sval3) >> 1) << 8) |            /* pix 3 */
+                ((sval1 + sval2 + sval3 + sval4) >> 2);          /* pix 4 */
+            lined[w * 2 + 1] = wordd;
+            linedp[w * 2 + 1] = worddp;
+        }
+
+            /* Finish up the last word */
+        for (; j < wsm; j++, jd += 2) {
+            sval1 = sval2;
+            sval3 = sval4;
+            sval2 = GET_DATA_BYTE(lines, j + 1);
+            sval4 = GET_DATA_BYTE(linesp, j + 1);
+            SET_DATA_BYTE(lined, jd, sval1);                     /* pix 1 */
+            SET_DATA_BYTE(lined, jd + 1, (sval1 + sval2) / 2);   /* pix 2 */
+            SET_DATA_BYTE(linedp, jd, (sval1 + sval3) / 2);      /* pix 3 */
+            SET_DATA_BYTE(linedp, jd + 1,
+                          (sval1 + sval2 + sval3 + sval4) / 4);  /* pix 4 */
+        }
+        sval1 = sval2;
+        sval3 = sval4;
+        SET_DATA_BYTE(lined, 2 * wsm, sval1);                     /* pix 1 */
+        SET_DATA_BYTE(lined, 2 * wsm + 1, sval1);                 /* pix 2 */
+        SET_DATA_BYTE(linedp, 2 * wsm, (sval1 + sval3) / 2);      /* pix 3 */
+        SET_DATA_BYTE(linedp, 2 * wsm + 1, (sval1 + sval3) / 2);  /* pix 4 */
+
+#if DEBUG_UNROLLING
+#define CHECK_BYTE(a, b, c) if (GET_DATA_BYTE(a, b) != c) {\
+     fprintf(stderr, "Error: mismatch at %d, %d vs %d\n", \
+             j, GET_DATA_BYTE(a, b), c); }
+
+        sval2 = GET_DATA_BYTE(lines, 0);
+        sval4 = GET_DATA_BYTE(linesp, 0);
+        for (j = 0, jd = 0; j < wsm; j++, jd += 2) {
+            sval1 = sval2;
+            sval3 = sval4;
+            sval2 = GET_DATA_BYTE(lines, j + 1);
+            sval4 = GET_DATA_BYTE(linesp, j + 1);
+            CHECK_BYTE(lined, jd, sval1);                     /* pix 1 */
+            CHECK_BYTE(lined, jd + 1, (sval1 + sval2) / 2);   /* pix 2 */
+            CHECK_BYTE(linedp, jd, (sval1 + sval3) / 2);      /* pix 3 */
+            CHECK_BYTE(linedp, jd + 1,
+                          (sval1 + sval2 + sval3 + sval4) / 4);  /* pix 4 */
+        }
+        sval1 = sval2;
+        sval3 = sval4;
+        CHECK_BYTE(lined, 2 * wsm, sval1);                     /* pix 1 */
+        CHECK_BYTE(lined, 2 * wsm + 1, sval1);                 /* pix 2 */
+        CHECK_BYTE(linedp, 2 * wsm, (sval1 + sval3) / 2);      /* pix 3 */
+        CHECK_BYTE(linedp, 2 * wsm + 1, (sval1 + sval3) / 2);  /* pix 4 */
+#undef CHECK_BYTE
+#endif
+    } else {  /* last row of src pixels: lastlineflag == 1 */
+        linedp = lined + wpld;
+        sval2 = GET_DATA_BYTE(lines, 0);
+        for (j = 0, jd = 0; j < wsm; j++, jd += 2) {
+            sval1 = sval2;
+            sval2 = GET_DATA_BYTE(lines, j + 1);
+            SET_DATA_BYTE(lined, jd, sval1);                       /* pix 1 */
+            SET_DATA_BYTE(linedp, jd, sval1);                      /* pix 3 */
+            SET_DATA_BYTE(lined, jd + 1, (sval1 + sval2) / 2);     /* pix 2 */
+            SET_DATA_BYTE(linedp, jd + 1, (sval1 + sval2) / 2);    /* pix 4 */
+        }
+        sval1 = sval2;
+        SET_DATA_BYTE(lined, 2 * wsm, sval1);                     /* pix 1 */
+        SET_DATA_BYTE(lined, 2 * wsm + 1, sval1);                 /* pix 2 */
+        SET_DATA_BYTE(linedp, 2 * wsm, sval1);                    /* pix 3 */
+        SET_DATA_BYTE(linedp, 2 * wsm + 1, sval1);                /* pix 4 */
+    }
+
+    return;
+}
+
+
+/*------------------------------------------------------------------*
+ *               4x linear interpolated gray scaling                *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleGray4xLILow()
+ *
+ *  This is a special case of 4x expansion by linear
+ *  interpolation.  Each src pixel contains 16 dest pixels.
+ *  The 16 dest pixels in src pixel 1 are numbered at
+ *  their UL corners.  The 16 dest pixels in src pixel 1
+ *  are related to that src pixel and its 3 neighboring
+ *  src pixels as follows:
+ *
+ *             1---2---3---4---|---|---|---|---|
+ *             |   |   |   |   |   |   |   |   |
+ *             5---6---7---8---|---|---|---|---|
+ *             |   |   |   |   |   |   |   |   |
+ *  src 1 -->  9---a---b---c---|---|---|---|---|  <-- src 2
+ *             |   |   |   |   |   |   |   |   |
+ *             d---e---f---g---|---|---|---|---|
+ *             |   |   |   |   |   |   |   |   |
+ *             |===|===|===|===|===|===|===|===|
+ *             |   |   |   |   |   |   |   |   |
+ *             |---|---|---|---|---|---|---|---|
+ *             |   |   |   |   |   |   |   |   |
+ *  src 3 -->  |---|---|---|---|---|---|---|---|  <-- src 4
+ *             |   |   |   |   |   |   |   |   |
+ *             |---|---|---|---|---|---|---|---|
+ *             |   |   |   |   |   |   |   |   |
+ *             |---|---|---|---|---|---|---|---|
+ *
+ *           dest      src
+ *           ----      ---
+ *           dp1    =  sp1
+ *           dp2    =  (3 * sp1 + sp2) / 4
+ *           dp3    =  (sp1 + sp2) / 2
+ *           dp4    =  (sp1 + 3 * sp2) / 4
+ *           dp5    =  (3 * sp1 + sp3) / 4
+ *           dp6    =  (9 * sp1 + 3 * sp2 + 3 * sp3 + sp4) / 16
+ *           dp7    =  (3 * sp1 + 3 * sp2 + sp3 + sp4) / 8
+ *           dp8    =  (3 * sp1 + 9 * sp2 + 1 * sp3 + 3 * sp4) / 16
+ *           dp9    =  (sp1 + sp3) / 2
+ *           dp10   =  (3 * sp1 + sp2 + 3 * sp3 + sp4) / 8
+ *           dp11   =  (sp1 + sp2 + sp3 + sp4) / 4
+ *           dp12   =  (sp1 + 3 * sp2 + sp3 + 3 * sp4) / 8
+ *           dp13   =  (sp1 + 3 * sp3) / 4
+ *           dp14   =  (3 * sp1 + sp2 + 9 * sp3 + 3 * sp4) / 16
+ *           dp15   =  (sp1 + sp2 + 3 * sp3 + 3 * sp4) / 8
+ *           dp16   =  (sp1 + 3 * sp2 + 3 * sp3 + 9 * sp4) / 16
+ *
+ *  We iterate over the src pixels, and unroll the calculation
+ *  for each set of 16 dest pixels corresponding to that src
+ *  pixel, caching pixels for the next src pixel whenever possible.
+ */
+void
+scaleGray4xLILow(l_uint32  *datad,
+                 l_int32    wpld,
+                 l_uint32  *datas,
+                 l_int32    ws,
+                 l_int32    hs,
+                 l_int32    wpls)
+{
+l_int32    i, hsm;
+l_uint32  *lines, *lined;
+
+    hsm = hs - 1;
+
+        /* We're taking 2 src and 4 dest lines at a time,
+         * and for each src line, we're computing 4 dest lines.
+         * Call these 4 dest lines:  destline1 - destline4.
+         * The first src line is used for destline 1.
+         * Two src lines are used for all other dest lines,
+         * except for the last 4 dest lines, which are computed
+         * using only the last src line. */
+
+        /* iterate over all but the last src line */
+    for (i = 0; i < hsm; i++) {
+        lines = datas + i * wpls;
+        lined = datad + 4 * i * wpld;
+        scaleGray4xLILineLow(lined, wpld, lines, ws, wpls, 0);
+    }
+
+        /* last src line */
+    lines = datas + hsm * wpls;
+    lined = datad + 4 * hsm * wpld;
+    scaleGray4xLILineLow(lined, wpld, lines, ws, wpls, 1);
+    return;
+}
+
+
+/*!
+ *  scaleGray4xLILineLow()
+ *
+ *      Input:  lined   (ptr to top destline, to be made from current src line)
+ *              wpld
+ *              lines   (ptr to current src line)
+ *              ws
+ *              wpls
+ *              lastlineflag  (1 if last src line; 0 otherwise)
+ *      Return: void
+ */
+void
+scaleGray4xLILineLow(l_uint32  *lined,
+                     l_int32    wpld,
+                     l_uint32  *lines,
+                     l_int32    ws,
+                     l_int32    wpls,
+                     l_int32    lastlineflag)
+{
+l_int32    j, jd, wsm, wsm4;
+l_int32    s1, s2, s3, s4, s1t, s2t, s3t, s4t;
+l_uint32  *linesp, *linedp1, *linedp2, *linedp3;
+
+    wsm = ws - 1;
+    wsm4 = 4 * wsm;
+
+    if (lastlineflag == 0) {
+        linesp = lines + wpls;
+        linedp1 = lined + wpld;
+        linedp2 = lined + 2 * wpld;
+        linedp3 = lined + 3 * wpld;
+        s2 = GET_DATA_BYTE(lines, 0);
+        s4 = GET_DATA_BYTE(linesp, 0);
+        for (j = 0, jd = 0; j < wsm; j++, jd += 4) {
+            s1 = s2;
+            s3 = s4;
+            s2 = GET_DATA_BYTE(lines, j + 1);
+            s4 = GET_DATA_BYTE(linesp, j + 1);
+            s1t = 3 * s1;
+            s2t = 3 * s2;
+            s3t = 3 * s3;
+            s4t = 3 * s4;
+            SET_DATA_BYTE(lined, jd, s1);                             /* d1 */
+            SET_DATA_BYTE(lined, jd + 1, (s1t + s2) / 4);             /* d2 */
+            SET_DATA_BYTE(lined, jd + 2, (s1 + s2) / 2);              /* d3 */
+            SET_DATA_BYTE(lined, jd + 3, (s1 + s2t) / 4);             /* d4 */
+            SET_DATA_BYTE(linedp1, jd, (s1t + s3) / 4);                /* d5 */
+            SET_DATA_BYTE(linedp1, jd + 1, (9*s1 + s2t + s3t + s4) / 16); /*d6*/
+            SET_DATA_BYTE(linedp1, jd + 2, (s1t + s2t + s3 + s4) / 8); /* d7 */
+            SET_DATA_BYTE(linedp1, jd + 3, (s1t + 9*s2 + s3 + s4t) / 16);/*d8*/
+            SET_DATA_BYTE(linedp2, jd, (s1 + s3) / 2);                /* d9 */
+            SET_DATA_BYTE(linedp2, jd + 1, (s1t + s2 + s3t + s4) / 8);/* d10 */
+            SET_DATA_BYTE(linedp2, jd + 2, (s1 + s2 + s3 + s4) / 4);  /* d11 */
+            SET_DATA_BYTE(linedp2, jd + 3, (s1 + s2t + s3 + s4t) / 8);/* d12 */
+            SET_DATA_BYTE(linedp3, jd, (s1 + s3t) / 4);               /* d13 */
+            SET_DATA_BYTE(linedp3, jd + 1, (s1t + s2 + 9*s3 + s4t) / 16);/*d14*/
+            SET_DATA_BYTE(linedp3, jd + 2, (s1 + s2 + s3t + s4t) / 8); /* d15 */
+            SET_DATA_BYTE(linedp3, jd + 3, (s1 + s2t + s3t + 9*s4) / 16);/*d16*/
+        }
+        s1 = s2;
+        s3 = s4;
+        s1t = 3 * s1;
+        s3t = 3 * s3;
+        SET_DATA_BYTE(lined, wsm4, s1);                               /* d1 */
+        SET_DATA_BYTE(lined, wsm4 + 1, s1);                           /* d2 */
+        SET_DATA_BYTE(lined, wsm4 + 2, s1);                           /* d3 */
+        SET_DATA_BYTE(lined, wsm4 + 3, s1);                           /* d4 */
+        SET_DATA_BYTE(linedp1, wsm4, (s1t + s3) / 4);                 /* d5 */
+        SET_DATA_BYTE(linedp1, wsm4 + 1, (s1t + s3) / 4);             /* d6 */
+        SET_DATA_BYTE(linedp1, wsm4 + 2, (s1t + s3) / 4);             /* d7 */
+        SET_DATA_BYTE(linedp1, wsm4 + 3, (s1t + s3) / 4);             /* d8 */
+        SET_DATA_BYTE(linedp2, wsm4, (s1 + s3) / 2);                  /* d9 */
+        SET_DATA_BYTE(linedp2, wsm4 + 1, (s1 + s3) / 2);              /* d10 */
+        SET_DATA_BYTE(linedp2, wsm4 + 2, (s1 + s3) / 2);              /* d11 */
+        SET_DATA_BYTE(linedp2, wsm4 + 3, (s1 + s3) / 2);              /* d12 */
+        SET_DATA_BYTE(linedp3, wsm4, (s1 + s3t) / 4);                 /* d13 */
+        SET_DATA_BYTE(linedp3, wsm4 + 1, (s1 + s3t) / 4);             /* d14 */
+        SET_DATA_BYTE(linedp3, wsm4 + 2, (s1 + s3t) / 4);             /* d15 */
+        SET_DATA_BYTE(linedp3, wsm4 + 3, (s1 + s3t) / 4);             /* d16 */
+    } else {   /* last row of src pixels: lastlineflag == 1 */
+        linedp1 = lined + wpld;
+        linedp2 = lined + 2 * wpld;
+        linedp3 = lined + 3 * wpld;
+        s2 = GET_DATA_BYTE(lines, 0);
+        for (j = 0, jd = 0; j < wsm; j++, jd += 4) {
+            s1 = s2;
+            s2 = GET_DATA_BYTE(lines, j + 1);
+            s1t = 3 * s1;
+            s2t = 3 * s2;
+            SET_DATA_BYTE(lined, jd, s1);                            /* d1 */
+            SET_DATA_BYTE(lined, jd + 1, (s1t + s2) / 4 );           /* d2 */
+            SET_DATA_BYTE(lined, jd + 2, (s1 + s2) / 2 );            /* d3 */
+            SET_DATA_BYTE(lined, jd + 3, (s1 + s2t) / 4 );           /* d4 */
+            SET_DATA_BYTE(linedp1, jd, s1);                          /* d5 */
+            SET_DATA_BYTE(linedp1, jd + 1, (s1t + s2) / 4 );         /* d6 */
+            SET_DATA_BYTE(linedp1, jd + 2, (s1 + s2) / 2 );          /* d7 */
+            SET_DATA_BYTE(linedp1, jd + 3, (s1 + s2t) / 4 );         /* d8 */
+            SET_DATA_BYTE(linedp2, jd, s1);                          /* d9 */
+            SET_DATA_BYTE(linedp2, jd + 1, (s1t + s2) / 4 );         /* d10 */
+            SET_DATA_BYTE(linedp2, jd + 2, (s1 + s2) / 2 );          /* d11 */
+            SET_DATA_BYTE(linedp2, jd + 3, (s1 + s2t) / 4 );         /* d12 */
+            SET_DATA_BYTE(linedp3, jd, s1);                          /* d13 */
+            SET_DATA_BYTE(linedp3, jd + 1, (s1t + s2) / 4 );         /* d14 */
+            SET_DATA_BYTE(linedp3, jd + 2, (s1 + s2) / 2 );          /* d15 */
+            SET_DATA_BYTE(linedp3, jd + 3, (s1 + s2t) / 4 );         /* d16 */
+        }
+        s1 = s2;
+        SET_DATA_BYTE(lined, wsm4, s1);                              /* d1 */
+        SET_DATA_BYTE(lined, wsm4 + 1, s1);                          /* d2 */
+        SET_DATA_BYTE(lined, wsm4 + 2, s1);                          /* d3 */
+        SET_DATA_BYTE(lined, wsm4 + 3, s1);                          /* d4 */
+        SET_DATA_BYTE(linedp1, wsm4, s1);                            /* d5 */
+        SET_DATA_BYTE(linedp1, wsm4 + 1, s1);                        /* d6 */
+        SET_DATA_BYTE(linedp1, wsm4 + 2, s1);                        /* d7 */
+        SET_DATA_BYTE(linedp1, wsm4 + 3, s1);                        /* d8 */
+        SET_DATA_BYTE(linedp2, wsm4, s1);                            /* d9 */
+        SET_DATA_BYTE(linedp2, wsm4 + 1, s1);                        /* d10 */
+        SET_DATA_BYTE(linedp2, wsm4 + 2, s1);                        /* d11 */
+        SET_DATA_BYTE(linedp2, wsm4 + 3, s1);                        /* d12 */
+        SET_DATA_BYTE(linedp3, wsm4, s1);                            /* d13 */
+        SET_DATA_BYTE(linedp3, wsm4 + 1, s1);                        /* d14 */
+        SET_DATA_BYTE(linedp3, wsm4 + 2, s1);                        /* d15 */
+        SET_DATA_BYTE(linedp3, wsm4 + 3, s1);                        /* d16 */
+    }
+
+    return;
+}
+
+
+/*------------------------------------------------------------------*
+ *       Grayscale and color scaling by closest pixel sampling      *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleBySamplingLow()
+ *
+ *  Notes:
+ *      (1) The dest must be cleared prior to this operation,
+ *          and we clear it here in the low-level code.
+ *      (2) We reuse dest pixels and dest pixel rows whenever
+ *          possible.  This speeds the upscaling; downscaling
+ *          is done by strict subsampling and is unaffected.
+ *      (3) Because we are sampling and not interpolating, this
+ *          routine works directly, without conversion to full
+ *          RGB color, for 2, 4 or 8 bpp palette color images.
+ */
+l_int32
+scaleBySamplingLow(l_uint32  *datad,
+                   l_int32    wd,
+                   l_int32    hd,
+                   l_int32    wpld,
+                   l_uint32  *datas,
+                   l_int32    ws,
+                   l_int32    hs,
+                   l_int32    d,
+                   l_int32    wpls)
+{
+l_int32    i, j, bpld;
+l_int32    xs, prevxs, sval;
+l_int32   *srow, *scol;
+l_uint32   csval;
+l_uint32  *lines, *prevlines, *lined, *prevlined;
+l_float32  wratio, hratio;
+
+    PROCNAME("scaleBySamplingLow");
+
+    if (d != 2 && d != 4 && d !=8 && d != 16 && d != 32)
+        return ERROR_INT("pixel depth not supported", procName, 1);
+
+        /* clear dest */
+    bpld = 4 * wpld;
+    memset((char *)datad, 0, hd * bpld);
+
+        /* the source row corresponding to dest row i ==> srow[i]
+         * the source col corresponding to dest col j ==> scol[j]  */
+    if ((srow = (l_int32 *)LEPT_CALLOC(hd, sizeof(l_int32))) == NULL)
+        return ERROR_INT("srow not made", procName, 1);
+    if ((scol = (l_int32 *)LEPT_CALLOC(wd, sizeof(l_int32))) == NULL)
+        return ERROR_INT("scol not made", procName, 1);
+
+    wratio = (l_float32)ws / (l_float32)wd;
+    hratio = (l_float32)hs / (l_float32)hd;
+    for (i = 0; i < hd; i++)
+        srow[i] = L_MIN((l_int32)(hratio * i + 0.5), hs - 1);
+    for (j = 0; j < wd; j++)
+        scol[j] = L_MIN((l_int32)(wratio * j + 0.5), ws - 1);
+
+    prevlines = NULL;
+    for (i = 0; i < hd; i++) {
+        lines = datas + srow[i] * wpls;
+        lined = datad + i * wpld;
+        if (lines != prevlines) {  /* make dest from new source row */
+            prevxs = -1;
+            sval = 0;
+            csval = 0;
+            if (d == 2) {
+                for (j = 0; j < wd; j++) {
+                    xs = scol[j];
+                    if (xs != prevxs) {  /* get dest pix from source col */
+                        sval = GET_DATA_DIBIT(lines, xs);
+                        SET_DATA_DIBIT(lined, j, sval);
+                        prevxs = xs;
+                    } else {  /* copy prev dest pix */
+                        SET_DATA_DIBIT(lined, j, sval);
+                    }
+                }
+            } else if (d == 4) {
+                for (j = 0; j < wd; j++) {
+                    xs = scol[j];
+                    if (xs != prevxs) {  /* get dest pix from source col */
+                        sval = GET_DATA_QBIT(lines, xs);
+                        SET_DATA_QBIT(lined, j, sval);
+                        prevxs = xs;
+                    } else {  /* copy prev dest pix */
+                        SET_DATA_QBIT(lined, j, sval);
+                    }
+                }
+            } else if (d == 8) {
+                for (j = 0; j < wd; j++) {
+                    xs = scol[j];
+                    if (xs != prevxs) {  /* get dest pix from source col */
+                        sval = GET_DATA_BYTE(lines, xs);
+                        SET_DATA_BYTE(lined, j, sval);
+                        prevxs = xs;
+                    } else {  /* copy prev dest pix */
+                        SET_DATA_BYTE(lined, j, sval);
+                    }
+                }
+            } else if (d == 16) {
+                for (j = 0; j < wd; j++) {
+                    xs = scol[j];
+                    if (xs != prevxs) {  /* get dest pix from source col */
+                        sval = GET_DATA_TWO_BYTES(lines, xs);
+                        SET_DATA_TWO_BYTES(lined, j, sval);
+                        prevxs = xs;
+                    } else {  /* copy prev dest pix */
+                        SET_DATA_TWO_BYTES(lined, j, sval);
+                    }
+                }
+            } else {  /* d == 32 */
+                for (j = 0; j < wd; j++) {
+                    xs = scol[j];
+                    if (xs != prevxs) {  /* get dest pix from source col */
+                        csval = lines[xs];
+                        lined[j] = csval;
+                        prevxs = xs;
+                    } else {  /* copy prev dest pix */
+                        lined[j] = csval;
+                    }
+                }
+            }
+        } else {  /* lines == prevlines; copy prev dest row */
+            prevlined = lined - wpld;
+            memcpy((char *)lined, (char *)prevlined, bpld);
+        }
+        prevlines = lines;
+    }
+
+    LEPT_FREE(srow);
+    LEPT_FREE(scol);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *    Color and grayscale downsampling with (antialias) smoothing   *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleSmoothLow()
+ *
+ *  Notes:
+ *      (1) This function is called on 8 or 32 bpp src and dest images.
+ *      (2) size is the full width of the lowpass smoothing filter.
+ *          It is correlated with the reduction ratio, being the
+ *          nearest integer such that size is approximately equal to hs / hd.
+ */
+l_int32
+scaleSmoothLow(l_uint32  *datad,
+               l_int32    wd,
+               l_int32    hd,
+               l_int32    wpld,
+               l_uint32  *datas,
+               l_int32    ws,
+               l_int32    hs,
+               l_int32    d,
+               l_int32    wpls,
+               l_int32    size)
+{
+l_int32    i, j, m, n, xstart;
+l_int32    val, rval, gval, bval;
+l_int32   *srow, *scol;
+l_uint32  *lines, *lined, *line, *ppixel;
+l_uint32   pixel;
+l_float32  wratio, hratio, norm;
+
+    PROCNAME("scaleSmoothLow");
+
+        /* Clear dest */
+    memset((char *)datad, 0, 4 * wpld * hd);
+
+        /* Each dest pixel at (j,i) is computed as the average
+           of size^2 corresponding src pixels.
+           We store the UL corner location of the square of
+           src pixels that correspond to dest pixel (j,i).
+           The are labelled by the arrays srow[i] and scol[j]. */
+    if ((srow = (l_int32 *)LEPT_CALLOC(hd, sizeof(l_int32))) == NULL)
+        return ERROR_INT("srow not made", procName, 1);
+    if ((scol = (l_int32 *)LEPT_CALLOC(wd, sizeof(l_int32))) == NULL)
+        return ERROR_INT("scol not made", procName, 1);
+
+    norm = 1. / (l_float32)(size * size);
+    wratio = (l_float32)ws / (l_float32)wd;
+    hratio = (l_float32)hs / (l_float32)hd;
+    for (i = 0; i < hd; i++)
+        srow[i] = L_MIN((l_int32)(hratio * i), hs - size);
+    for (j = 0; j < wd; j++)
+        scol[j] = L_MIN((l_int32)(wratio * j), ws - size);
+
+        /* For each dest pixel, compute average */
+    if (d == 8) {
+        for (i = 0; i < hd; i++) {
+            lines = datas + srow[i] * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < wd; j++) {
+                xstart = scol[j];
+                val = 0;
+                for (m = 0; m < size; m++) {
+                    line = lines + m * wpls;
+                    for (n = 0; n < size; n++) {
+                        val += GET_DATA_BYTE(line, xstart + n);
+                    }
+                }
+                val = (l_int32)((l_float32)val * norm);
+                SET_DATA_BYTE(lined, j, val);
+            }
+        }
+    } else {  /* d == 32 */
+        for (i = 0; i < hd; i++) {
+            lines = datas + srow[i] * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < wd; j++) {
+                xstart = scol[j];
+                rval = gval = bval = 0;
+                for (m = 0; m < size; m++) {
+                    ppixel = lines + m * wpls + xstart;
+                    for (n = 0; n < size; n++) {
+                        pixel = *(ppixel + n);
+                        rval += (pixel >> L_RED_SHIFT) & 0xff;
+                        gval += (pixel >> L_GREEN_SHIFT) & 0xff;
+                        bval += (pixel >> L_BLUE_SHIFT) & 0xff;
+                    }
+                }
+                rval = (l_int32)((l_float32)rval * norm);
+                gval = (l_int32)((l_float32)gval * norm);
+                bval = (l_int32)((l_float32)bval * norm);
+                *(lined + j) = rval << L_RED_SHIFT |
+                               gval << L_GREEN_SHIFT |
+                               bval << L_BLUE_SHIFT;
+            }
+        }
+    }
+
+    LEPT_FREE(srow);
+    LEPT_FREE(scol);
+    return 0;
+}
+
+
+/*!
+ *  scaleRGBToGray2Low()
+ *
+ *  Notes:
+ *      (1) This function is called with 32 bpp RGB src and 8 bpp,
+ *          half-resolution dest.  The weights should add to 1.0.
+ */
+void
+scaleRGBToGray2Low(l_uint32  *datad,
+                   l_int32    wd,
+                   l_int32    hd,
+                   l_int32    wpld,
+                   l_uint32  *datas,
+                   l_int32    wpls,
+                   l_float32  rwt,
+                   l_float32  gwt,
+                   l_float32  bwt)
+{
+l_int32    i, j, val, rval, gval, bval;
+l_uint32  *lines, *lined;
+l_uint32   pixel;
+
+    rwt *= 0.25;
+    gwt *= 0.25;
+    bwt *= 0.25;
+    for (i = 0; i < hd; i++) {
+        lines = datas + 2 * i * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < wd; j++) {
+                /* Sum each of the color components from 4 src pixels */
+            pixel = *(lines + 2 * j);
+            rval = (pixel >> L_RED_SHIFT) & 0xff;
+            gval = (pixel >> L_GREEN_SHIFT) & 0xff;
+            bval = (pixel >> L_BLUE_SHIFT) & 0xff;
+            pixel = *(lines + 2 * j + 1);
+            rval += (pixel >> L_RED_SHIFT) & 0xff;
+            gval += (pixel >> L_GREEN_SHIFT) & 0xff;
+            bval += (pixel >> L_BLUE_SHIFT) & 0xff;
+            pixel = *(lines + wpls + 2 * j);
+            rval += (pixel >> L_RED_SHIFT) & 0xff;
+            gval += (pixel >> L_GREEN_SHIFT) & 0xff;
+            bval += (pixel >> L_BLUE_SHIFT) & 0xff;
+            pixel = *(lines + wpls + 2 * j + 1);
+            rval += (pixel >> L_RED_SHIFT) & 0xff;
+            gval += (pixel >> L_GREEN_SHIFT) & 0xff;
+            bval += (pixel >> L_BLUE_SHIFT) & 0xff;
+                /* Generate the dest byte as a weighted sum of the averages */
+            val = (l_int32)(rwt * rval + gwt * gval + bwt * bval);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+}
+
+
+/*------------------------------------------------------------------*
+ *                  General area mapped gray scaling                *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleColorAreaMapLow()
+ *
+ *  This should only be used for downscaling.
+ *  We choose to divide each pixel into 16 x 16 sub-pixels.
+ *  This is much slower than scaleSmoothLow(), but it gives a
+ *  better representation, esp. for downscaling factors between
+ *  1.5 and 5.  All src pixels are subdivided into 256 sub-pixels,
+ *  and are weighted by the number of sub-pixels covered by
+ *  the dest pixel.  This is about 2x slower than scaleSmoothLow(),
+ *  but the results are significantly better on small text.
+ */
+void
+scaleColorAreaMapLow(l_uint32  *datad,
+                    l_int32    wd,
+                    l_int32    hd,
+                    l_int32    wpld,
+                    l_uint32  *datas,
+                    l_int32    ws,
+                    l_int32    hs,
+                    l_int32    wpls)
+{
+l_int32    i, j, k, m, wm2, hm2;
+l_int32    area00, area10, area01, area11, areal, arear, areat, areab;
+l_int32    xu, yu;  /* UL corner in src image, to 1/16 of a pixel */
+l_int32    xl, yl;  /* LR corner in src image, to 1/16 of a pixel */
+l_int32    xup, yup, xuf, yuf;  /* UL src pixel: integer and fraction */
+l_int32    xlp, ylp, xlf, ylf;  /* LR src pixel: integer and fraction */
+l_int32    delx, dely, area;
+l_int32    v00r, v00g, v00b;  /* contrib. from UL src pixel */
+l_int32    v01r, v01g, v01b;  /* contrib. from LL src pixel */
+l_int32    v10r, v10g, v10b;  /* contrib from UR src pixel */
+l_int32    v11r, v11g, v11b;  /* contrib from LR src pixel */
+l_int32    vinr, ving, vinb;  /* contrib from all full interior src pixels */
+l_int32    vmidr, vmidg, vmidb;  /* contrib from side parts */
+l_int32    rval, gval, bval;
+l_uint32   pixel00, pixel10, pixel01, pixel11, pixel;
+l_uint32  *lines, *lined;
+l_float32  scx, scy;
+
+        /* (scx, scy) are scaling factors that are applied to the
+         * dest coords to get the corresponding src coords.
+         * We need them because we iterate over dest pixels
+         * and must find the corresponding set of src pixels. */
+    scx = 16. * (l_float32)ws / (l_float32)wd;
+    scy = 16. * (l_float32)hs / (l_float32)hd;
+    wm2 = ws - 2;
+    hm2 = hs - 2;
+
+        /* Iterate over the destination pixels */
+    for (i = 0; i < hd; i++) {
+        yu = (l_int32)(scy * i);
+        yl = (l_int32)(scy * (i + 1.0));
+        yup = yu >> 4;
+        yuf = yu & 0x0f;
+        ylp = yl >> 4;
+        ylf = yl & 0x0f;
+        dely = ylp - yup;
+        lined = datad + i * wpld;
+        lines = datas + yup * wpls;
+        for (j = 0; j < wd; j++) {
+            xu = (l_int32)(scx * j);
+            xl = (l_int32)(scx * (j + 1.0));
+            xup = xu >> 4;
+            xuf = xu & 0x0f;
+            xlp = xl >> 4;
+            xlf = xl & 0x0f;
+            delx = xlp - xup;
+
+                /* If near the edge, just use a src pixel value */
+            if (xlp > wm2 || ylp > hm2) {
+                *(lined + j) = *(lines + xup);
+                continue;
+            }
+
+                /* Area summed over, in subpixels.  This varies
+                 * due to the quantization, so we can't simply take
+                 * the area to be a constant: area = scx * scy. */
+            area = ((16 - xuf) + 16 * (delx - 1) + xlf) *
+                   ((16 - yuf) + 16 * (dely - 1) + ylf);
+
+                /* Do area map summation */
+            pixel00 = *(lines + xup);
+            pixel10 = *(lines + xlp);
+            pixel01 = *(lines + dely * wpls +  xup);
+            pixel11 = *(lines + dely * wpls +  xlp);
+            area00 = (16 - xuf) * (16 - yuf);
+            area10 = xlf * (16 - yuf);
+            area01 = (16 - xuf) * ylf;
+            area11 = xlf * ylf;
+            v00r = area00 * ((pixel00 >> L_RED_SHIFT) & 0xff);
+            v00g = area00 * ((pixel00 >> L_GREEN_SHIFT) & 0xff);
+            v00b = area00 * ((pixel00 >> L_BLUE_SHIFT) & 0xff);
+            v10r = area10 * ((pixel10 >> L_RED_SHIFT) & 0xff);
+            v10g = area10 * ((pixel10 >> L_GREEN_SHIFT) & 0xff);
+            v10b = area10 * ((pixel10 >> L_BLUE_SHIFT) & 0xff);
+            v01r = area01 * ((pixel01 >> L_RED_SHIFT) & 0xff);
+            v01g = area01 * ((pixel01 >> L_GREEN_SHIFT) & 0xff);
+            v01b = area01 * ((pixel01 >> L_BLUE_SHIFT) & 0xff);
+            v11r = area11 * ((pixel11 >> L_RED_SHIFT) & 0xff);
+            v11g = area11 * ((pixel11 >> L_GREEN_SHIFT) & 0xff);
+            v11b = area11 * ((pixel11 >> L_BLUE_SHIFT) & 0xff);
+            vinr = ving = vinb = 0;
+            for (k = 1; k < dely; k++) {  /* for full src pixels */
+                for (m = 1; m < delx; m++) {
+                    pixel = *(lines + k * wpls + xup + m);
+                    vinr += 256 * ((pixel >> L_RED_SHIFT) & 0xff);
+                    ving += 256 * ((pixel >> L_GREEN_SHIFT) & 0xff);
+                    vinb += 256 * ((pixel >> L_BLUE_SHIFT) & 0xff);
+                }
+            }
+            vmidr = vmidg = vmidb = 0;
+            areal = (16 - xuf) * 16;
+            arear = xlf * 16;
+            areat = 16 * (16 - yuf);
+            areab = 16 * ylf;
+            for (k = 1; k < dely; k++) {  /* for left side */
+                pixel = *(lines + k * wpls + xup);
+                vmidr += areal * ((pixel >> L_RED_SHIFT) & 0xff);
+                vmidg += areal * ((pixel >> L_GREEN_SHIFT) & 0xff);
+                vmidb += areal * ((pixel >> L_BLUE_SHIFT) & 0xff);
+            }
+            for (k = 1; k < dely; k++) {  /* for right side */
+                pixel = *(lines + k * wpls + xlp);
+                vmidr += arear * ((pixel >> L_RED_SHIFT) & 0xff);
+                vmidg += arear * ((pixel >> L_GREEN_SHIFT) & 0xff);
+                vmidb += arear * ((pixel >> L_BLUE_SHIFT) & 0xff);
+            }
+            for (m = 1; m < delx; m++) {  /* for top side */
+                pixel = *(lines + xup + m);
+                vmidr += areat * ((pixel >> L_RED_SHIFT) & 0xff);
+                vmidg += areat * ((pixel >> L_GREEN_SHIFT) & 0xff);
+                vmidb += areat * ((pixel >> L_BLUE_SHIFT) & 0xff);
+            }
+            for (m = 1; m < delx; m++) {  /* for bottom side */
+                pixel = *(lines + dely * wpls + xup + m);
+                vmidr += areab * ((pixel >> L_RED_SHIFT) & 0xff);
+                vmidg += areab * ((pixel >> L_GREEN_SHIFT) & 0xff);
+                vmidb += areab * ((pixel >> L_BLUE_SHIFT) & 0xff);
+            }
+
+                /* Sum all the contributions */
+            rval = (v00r + v01r + v10r + v11r + vinr + vmidr + 128) / area;
+            gval = (v00g + v01g + v10g + v11g + ving + vmidg + 128) / area;
+            bval = (v00b + v01b + v10b + v11b + vinb + vmidb + 128) / area;
+#if  DEBUG_OVERFLOW
+            if (rval > 255) fprintf(stderr, "rval ovfl: %d\n", rval);
+            if (gval > 255) fprintf(stderr, "gval ovfl: %d\n", gval);
+            if (bval > 255) fprintf(stderr, "bval ovfl: %d\n", bval);
+#endif  /* DEBUG_OVERFLOW */
+            composeRGBPixel(rval, gval, bval, lined + j);
+        }
+    }
+
+    return;
+}
+
+
+/*!
+ *  scaleGrayAreaMapLow()
+ *
+ *  This should only be used for downscaling.
+ *  We choose to divide each pixel into 16 x 16 sub-pixels.
+ *  This is about 2x slower than scaleSmoothLow(), but the results
+ *  are significantly better on small text, esp. for downscaling
+ *  factors between 1.5 and 5.  All src pixels are subdivided
+ *  into 256 sub-pixels, and are weighted by the number of
+ *  sub-pixels covered by the dest pixel.
+ */
+void
+scaleGrayAreaMapLow(l_uint32  *datad,
+                    l_int32    wd,
+                    l_int32    hd,
+                    l_int32    wpld,
+                    l_uint32  *datas,
+                    l_int32    ws,
+                    l_int32    hs,
+                    l_int32    wpls)
+{
+l_int32    i, j, k, m, wm2, hm2;
+l_int32    xu, yu;  /* UL corner in src image, to 1/16 of a pixel */
+l_int32    xl, yl;  /* LR corner in src image, to 1/16 of a pixel */
+l_int32    xup, yup, xuf, yuf;  /* UL src pixel: integer and fraction */
+l_int32    xlp, ylp, xlf, ylf;  /* LR src pixel: integer and fraction */
+l_int32    delx, dely, area;
+l_int32    v00;  /* contrib. from UL src pixel */
+l_int32    v01;  /* contrib. from LL src pixel */
+l_int32    v10;  /* contrib from UR src pixel */
+l_int32    v11;  /* contrib from LR src pixel */
+l_int32    vin;  /* contrib from all full interior src pixels */
+l_int32    vmid;  /* contrib from side parts that are full in 1 direction */
+l_int32    val;
+l_uint32  *lines, *lined;
+l_float32  scx, scy;
+
+        /* (scx, scy) are scaling factors that are applied to the
+         * dest coords to get the corresponding src coords.
+         * We need them because we iterate over dest pixels
+         * and must find the corresponding set of src pixels. */
+    scx = 16. * (l_float32)ws / (l_float32)wd;
+    scy = 16. * (l_float32)hs / (l_float32)hd;
+    wm2 = ws - 2;
+    hm2 = hs - 2;
+
+        /* Iterate over the destination pixels */
+    for (i = 0; i < hd; i++) {
+        yu = (l_int32)(scy * i);
+        yl = (l_int32)(scy * (i + 1.0));
+        yup = yu >> 4;
+        yuf = yu & 0x0f;
+        ylp = yl >> 4;
+        ylf = yl & 0x0f;
+        dely = ylp - yup;
+        lined = datad + i * wpld;
+        lines = datas + yup * wpls;
+        for (j = 0; j < wd; j++) {
+            xu = (l_int32)(scx * j);
+            xl = (l_int32)(scx * (j + 1.0));
+            xup = xu >> 4;
+            xuf = xu & 0x0f;
+            xlp = xl >> 4;
+            xlf = xl & 0x0f;
+            delx = xlp - xup;
+
+                /* If near the edge, just use a src pixel value */
+            if (xlp > wm2 || ylp > hm2) {
+                SET_DATA_BYTE(lined, j, GET_DATA_BYTE(lines, xup));
+                continue;
+            }
+
+                /* Area summed over, in subpixels.  This varies
+                 * due to the quantization, so we can't simply take
+                 * the area to be a constant: area = scx * scy. */
+            area = ((16 - xuf) + 16 * (delx - 1) + xlf) *
+                   ((16 - yuf) + 16 * (dely - 1) + ylf);
+
+                /* Do area map summation */
+            v00 = (16 - xuf) * (16 - yuf) * GET_DATA_BYTE(lines, xup);
+            v10 = xlf * (16 - yuf) * GET_DATA_BYTE(lines, xlp);
+            v01 = (16 - xuf) * ylf * GET_DATA_BYTE(lines + dely * wpls, xup);
+            v11 = xlf * ylf * GET_DATA_BYTE(lines + dely * wpls, xlp);
+            for (vin = 0, k = 1; k < dely; k++) {  /* for full src pixels */
+                 for (m = 1; m < delx; m++) {
+                     vin += 256 * GET_DATA_BYTE(lines + k * wpls, xup + m);
+                 }
+            }
+            for (vmid = 0, k = 1; k < dely; k++)  /* for left side */
+                vmid += (16 - xuf) * 16 * GET_DATA_BYTE(lines + k * wpls, xup);
+            for (k = 1; k < dely; k++)  /* for right side */
+                vmid += xlf * 16 * GET_DATA_BYTE(lines + k * wpls, xlp);
+            for (m = 1; m < delx; m++)  /* for top side */
+                vmid += 16 * (16 - yuf) * GET_DATA_BYTE(lines, xup + m);
+            for (m = 1; m < delx; m++)  /* for bottom side */
+                vmid += 16 * ylf * GET_DATA_BYTE(lines + dely * wpls, xup + m);
+            val = (v00 + v01 + v10 + v11 + vin + vmid + 128) / area;
+#if  DEBUG_OVERFLOW
+            if (val > 255) fprintf(stderr, "val overflow: %d\n", val);
+#endif  /* DEBUG_OVERFLOW */
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    return;
+}
+
+
+/*------------------------------------------------------------------*
+ *                     2x area mapped downscaling                   *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleAreaMapLow2()
+ *
+ *  Note: This function is called with either 8 bpp gray or 32 bpp RGB.
+ *        The result is a 2x reduced dest.
+ */
+void
+scaleAreaMapLow2(l_uint32  *datad,
+                 l_int32    wd,
+                 l_int32    hd,
+                 l_int32    wpld,
+                 l_uint32  *datas,
+                 l_int32    d,
+                 l_int32    wpls)
+{
+l_int32    i, j, val, rval, gval, bval;
+l_uint32  *lines, *lined;
+l_uint32   pixel;
+
+    if (d == 8) {
+        for (i = 0; i < hd; i++) {
+            lines = datas + 2 * i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < wd; j++) {
+                    /* Average each dest pixel using 4 src pixels */
+                val = GET_DATA_BYTE(lines, 2 * j);
+                val += GET_DATA_BYTE(lines, 2 * j + 1);
+                val += GET_DATA_BYTE(lines + wpls, 2 * j);
+                val += GET_DATA_BYTE(lines + wpls, 2 * j + 1);
+                val >>= 2;
+                SET_DATA_BYTE(lined, j, val);
+            }
+        }
+    } else {  /* d == 32 */
+        for (i = 0; i < hd; i++) {
+            lines = datas + 2 * i * wpls;
+            lined = datad + i * wpld;
+            for (j = 0; j < wd; j++) {
+                    /* Average each of the color components from 4 src pixels */
+                pixel = *(lines + 2 * j);
+                rval = (pixel >> L_RED_SHIFT) & 0xff;
+                gval = (pixel >> L_GREEN_SHIFT) & 0xff;
+                bval = (pixel >> L_BLUE_SHIFT) & 0xff;
+                pixel = *(lines + 2 * j + 1);
+                rval += (pixel >> L_RED_SHIFT) & 0xff;
+                gval += (pixel >> L_GREEN_SHIFT) & 0xff;
+                bval += (pixel >> L_BLUE_SHIFT) & 0xff;
+                pixel = *(lines + wpls + 2 * j);
+                rval += (pixel >> L_RED_SHIFT) & 0xff;
+                gval += (pixel >> L_GREEN_SHIFT) & 0xff;
+                bval += (pixel >> L_BLUE_SHIFT) & 0xff;
+                pixel = *(lines + wpls + 2 * j + 1);
+                rval += (pixel >> L_RED_SHIFT) & 0xff;
+                gval += (pixel >> L_GREEN_SHIFT) & 0xff;
+                bval += (pixel >> L_BLUE_SHIFT) & 0xff;
+                composeRGBPixel(rval >> 2, gval >> 2, bval >> 2, &pixel);
+                *(lined + j) = pixel;
+            }
+        }
+    }
+    return;
+}
+
+
+/*------------------------------------------------------------------*
+ *              Binary scaling by closest pixel sampling            *
+ *------------------------------------------------------------------*/
+/*
+ *  scaleBinaryLow()
+ *
+ *  Notes:
+ *      (1) The dest must be cleared prior to this operation,
+ *          and we clear it here in the low-level code.
+ *      (2) We reuse dest pixels and dest pixel rows whenever
+ *          possible for upscaling; downscaling is done by
+ *          strict subsampling.
+ */
+l_int32
+scaleBinaryLow(l_uint32  *datad,
+               l_int32    wd,
+               l_int32    hd,
+               l_int32    wpld,
+               l_uint32  *datas,
+               l_int32    ws,
+               l_int32    hs,
+               l_int32    wpls)
+{
+l_int32    i, j, bpld;
+l_int32    xs, prevxs, sval;
+l_int32   *srow, *scol;
+l_uint32  *lines, *prevlines, *lined, *prevlined;
+l_float32  wratio, hratio;
+
+    PROCNAME("scaleBinaryLow");
+
+        /* clear dest */
+    bpld = 4 * wpld;
+    memset((char *)datad, 0, hd * bpld);
+
+        /* The source row corresponding to dest row i ==> srow[i]
+         * The source col corresponding to dest col j ==> scol[j]  */
+    if ((srow = (l_int32 *)LEPT_CALLOC(hd, sizeof(l_int32))) == NULL)
+        return ERROR_INT("srow not made", procName, 1);
+    if ((scol = (l_int32 *)LEPT_CALLOC(wd, sizeof(l_int32))) == NULL)
+        return ERROR_INT("scol not made", procName, 1);
+
+    wratio = (l_float32)ws / (l_float32)wd;
+    hratio = (l_float32)hs / (l_float32)hd;
+    for (i = 0; i < hd; i++)
+        srow[i] = L_MIN((l_int32)(hratio * i + 0.5), hs - 1);
+    for (j = 0; j < wd; j++)
+        scol[j] = L_MIN((l_int32)(wratio * j + 0.5), ws - 1);
+
+    prevlines = NULL;
+    prevxs = -1;
+    sval = 0;
+    for (i = 0; i < hd; i++) {
+        lines = datas + srow[i] * wpls;
+        lined = datad + i * wpld;
+        if (lines != prevlines) {  /* make dest from new source row */
+            for (j = 0; j < wd; j++) {
+                xs = scol[j];
+                if (xs != prevxs) {  /* get dest pix from source col */
+                    if ((sval = GET_DATA_BIT(lines, xs)))
+                        SET_DATA_BIT(lined, j);
+                    prevxs = xs;
+                } else {  /* copy prev dest pix, if set */
+                    if (sval)
+                        SET_DATA_BIT(lined, j);
+                }
+            }
+        } else {  /* lines == prevlines; copy prev dest row */
+            prevlined = lined - wpld;
+            memcpy((char *)lined, (char *)prevlined, bpld);
+        }
+        prevlines = lines;
+    }
+
+    LEPT_FREE(srow);
+    LEPT_FREE(scol);
+    return 0;
+}
+
+
+/*------------------------------------------------------------------*
+ *                         Scale-to-gray 2x                         *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleToGray2Low()
+ *
+ *      Input:  usual image variables
+ *              sumtab  (made from makeSumTabSG2())
+ *              valtab  (made from makeValTabSG2())
+ *      Return: 0 if OK; 1 on error.
+ *
+ *  The output is processed in sets of 4 output bytes on a row,
+ *  corresponding to 4 2x2 bit-blocks in the input image.
+ *  Two lookup tables are used.  The first, sumtab, gets the
+ *  sum of ON pixels in 4 sets of two adjacent bits,
+ *  storing the result in 4 adjacent bytes.  After sums from
+ *  two rows have been added, the second table, valtab,
+ *  converts from the sum of ON pixels in the 2x2 block to
+ *  an 8 bpp grayscale value between 0 (for 4 bits ON)
+ *  and 255 (for 0 bits ON).
+ */
+void
+scaleToGray2Low(l_uint32  *datad,
+                l_int32    wd,
+                l_int32    hd,
+                l_int32    wpld,
+                l_uint32  *datas,
+                l_int32    wpls,
+                l_uint32  *sumtab,
+                l_uint8   *valtab)
+{
+l_int32    i, j, l, k, m, wd4, extra;
+l_uint32   sbyte1, sbyte2, sum;
+l_uint32  *lines, *lined;
+
+        /* i indexes the dest lines
+         * l indexes the source lines
+         * j indexes the dest bytes
+         * k indexes the source bytes
+         * We take two bytes from the source (in 2 lines of 8 pixels
+         * each) and convert them into four 8 bpp bytes of the dest. */
+    wd4 = wd & 0xfffffffc;
+    extra = wd - wd4;
+    for (i = 0, l = 0; i < hd; i++, l += 2) {
+        lines = datas + l * wpls;
+        lined = datad + i * wpld;
+        for (j = 0, k = 0; j < wd4; j += 4, k++) {
+            sbyte1 = GET_DATA_BYTE(lines, k);
+            sbyte2 = GET_DATA_BYTE(lines + wpls, k);
+            sum = sumtab[sbyte1] + sumtab[sbyte2];
+            SET_DATA_BYTE(lined, j, valtab[sum >> 24]);
+            SET_DATA_BYTE(lined, j + 1, valtab[(sum >> 16) & 0xff]);
+            SET_DATA_BYTE(lined, j + 2, valtab[(sum >> 8) & 0xff]);
+            SET_DATA_BYTE(lined, j + 3, valtab[sum & 0xff]);
+        }
+        if (extra > 0) {
+            sbyte1 = GET_DATA_BYTE(lines, k);
+            sbyte2 = GET_DATA_BYTE(lines + wpls, k);
+            sum = sumtab[sbyte1] + sumtab[sbyte2];
+            for (m = 0; m < extra; m++) {
+                SET_DATA_BYTE(lined, j + m,
+                              valtab[((sum >> (24 - 8 * m)) & 0xff)]);
+            }
+        }
+
+    }
+
+    return;
+}
+
+
+/*!
+ *  makeSumTabSG2()
+ *
+ *  Returns a table of 256 l_uint32s, giving the four output
+ *  8-bit grayscale sums corresponding to 8 input bits of a binary
+ *  image, for a 2x scale-to-gray op.  The sums from two
+ *  adjacent scanlines are then added and transformed to
+ *  output four 8 bpp pixel values, using makeValTabSG2().
+ */
+l_uint32  *
+makeSumTabSG2(void)
+{
+l_int32    i;
+l_int32    sum[] = {0, 1, 1, 2};
+l_uint32  *tab;
+
+    PROCNAME("makeSumTabSG2");
+
+    if ((tab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32))) == NULL)
+        return (l_uint32 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+
+        /* Pack the four sums separately in four bytes */
+    for (i = 0; i < 256; i++) {
+        tab[i] = (sum[i & 0x3] | sum[(i >> 2) & 0x3] << 8 |
+                  sum[(i >> 4) & 0x3] << 16 | sum[(i >> 6) & 0x3] << 24);
+    }
+
+    return tab;
+}
+
+
+/*!
+ *  makeValTabSG2()
+ *
+ *  Returns an 8 bit value for the sum of ON pixels
+ *  in a 2x2 square, according to
+ *
+ *         val = 255 - (255 * sum)/4
+ *
+ *  where sum is in set {0,1,2,3,4}
+ */
+l_uint8 *
+makeValTabSG2(void)
+{
+l_int32   i;
+l_uint8  *tab;
+
+    PROCNAME("makeValTabSG2");
+
+    if ((tab = (l_uint8 *)LEPT_CALLOC(5, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+
+    for (i = 0; i < 5; i++)
+        tab[i] = 255 - (i * 255) / 4;
+
+    return tab;
+}
+
+
+/*------------------------------------------------------------------*
+ *                         Scale-to-gray 3x                         *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleToGray3Low()
+ *
+ *      Input:  usual image variables
+ *              sumtab  (made from makeSumTabSG3())
+ *              valtab  (made from makeValTabSG3())
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Each set of 8 3x3 bit-blocks in the source image, which
+ *  consist of 72 pixels arranged 24 pixels wide by 3 scanlines,
+ *  is converted to a row of 8 8-bit pixels in the dest image.
+ *  These 72 pixels of the input image are runs of 24 pixels
+ *  in three adjacent scanlines.  Each run of 24 pixels is
+ *  stored in the 24 LSbits of a 32-bit word.  We use 2 LUTs.
+ *  The first, sumtab, takes 6 of these bits and stores
+ *  sum, taken 3 bits at a time, in two bytes.  (See
+ *  makeSumTabSG3).  This is done for each of the 3 scanlines,
+ *  and the results are added.  We now have the sum of ON pixels
+ *  in the first two 3x3 blocks in two bytes.  The valtab LUT
+ *  then converts these values (which go from 0 to 9) to
+ *  grayscale values between between 255 and 0.  (See makeValTabSG3).
+ *  This process is repeated for each of the other 3 sets of
+ *  6x3 input pixels, giving 8 output pixels in total.
+ *
+ *  Note: because the input image is processed in groups of
+ *        24 x 3 pixels, the process clips the input height to
+ *        (h - h % 3) and the input width to (w - w % 24).
+ */
+void
+scaleToGray3Low(l_uint32  *datad,
+                l_int32    wd,
+                l_int32    hd,
+                l_int32    wpld,
+                l_uint32  *datas,
+                l_int32    wpls,
+                l_uint32  *sumtab,
+                l_uint8   *valtab)
+{
+l_int32    i, j, l, k;
+l_uint32   threebytes1, threebytes2, threebytes3, sum;
+l_uint32  *lines, *lined;
+
+        /* i indexes the dest lines
+         * l indexes the source lines
+         * j indexes the dest bytes
+         * k indexes the source bytes
+         * We take 9 bytes from the source (72 binary pixels
+         * in three lines of 24 pixels each) and convert it
+         * into 8 bytes of the dest (8 8bpp pixels in one line)   */
+    for (i = 0, l = 0; i < hd; i++, l += 3) {
+        lines = datas + l * wpls;
+        lined = datad + i * wpld;
+        for (j = 0, k = 0; j < wd; j += 8, k += 3) {
+            threebytes1 = (GET_DATA_BYTE(lines, k) << 16) |
+                          (GET_DATA_BYTE(lines, k + 1) << 8) |
+                          GET_DATA_BYTE(lines, k + 2);
+            threebytes2 = (GET_DATA_BYTE(lines + wpls, k) << 16) |
+                          (GET_DATA_BYTE(lines + wpls, k + 1) << 8) |
+                          GET_DATA_BYTE(lines + wpls, k + 2);
+            threebytes3 = (GET_DATA_BYTE(lines + 2 * wpls, k) << 16) |
+                          (GET_DATA_BYTE(lines + 2 * wpls, k + 1) << 8) |
+                          GET_DATA_BYTE(lines + 2 * wpls, k + 2);
+
+            sum = sumtab[(threebytes1 >> 18)] +
+                  sumtab[(threebytes2 >> 18)] +
+                  sumtab[(threebytes3 >> 18)];
+            SET_DATA_BYTE(lined, j, valtab[GET_DATA_BYTE(&sum, 2)]);
+            SET_DATA_BYTE(lined, j + 1, valtab[GET_DATA_BYTE(&sum, 3)]);
+
+            sum = sumtab[((threebytes1 >> 12) & 0x3f)] +
+                  sumtab[((threebytes2 >> 12) & 0x3f)] +
+                  sumtab[((threebytes3 >> 12) & 0x3f)];
+            SET_DATA_BYTE(lined, j + 2, valtab[GET_DATA_BYTE(&sum, 2)]);
+            SET_DATA_BYTE(lined, j + 3, valtab[GET_DATA_BYTE(&sum, 3)]);
+
+            sum = sumtab[((threebytes1 >> 6) & 0x3f)] +
+                  sumtab[((threebytes2 >> 6) & 0x3f)] +
+                  sumtab[((threebytes3 >> 6) & 0x3f)];
+            SET_DATA_BYTE(lined, j + 4, valtab[GET_DATA_BYTE(&sum, 2)]);
+            SET_DATA_BYTE(lined, j + 5, valtab[GET_DATA_BYTE(&sum, 3)]);
+
+            sum = sumtab[(threebytes1 & 0x3f)] +
+                  sumtab[(threebytes2 & 0x3f)] +
+                  sumtab[(threebytes3 & 0x3f)];
+            SET_DATA_BYTE(lined, j + 6, valtab[GET_DATA_BYTE(&sum, 2)]);
+            SET_DATA_BYTE(lined, j + 7, valtab[GET_DATA_BYTE(&sum, 3)]);
+        }
+    }
+
+    return;
+}
+
+
+
+/*!
+ *  makeSumTabSG3()
+ *
+ *  Returns a table of 64 l_uint32s, giving the two output
+ *  8-bit grayscale sums corresponding to 6 input bits of a binary
+ *  image, for a 3x scale-to-gray op.  In practice, this would
+ *  be used three times (on adjacent scanlines), and the sums would
+ *  be added and then transformed to output 8 bpp pixel values,
+ *  using makeValTabSG3().
+ */
+l_uint32  *
+makeSumTabSG3(void)
+{
+l_int32    i;
+l_int32    sum[] = {0, 1, 1, 2, 1, 2, 2, 3};
+l_uint32  *tab;
+
+    PROCNAME("makeSumTabSG3");
+
+    if ((tab = (l_uint32 *)LEPT_CALLOC(64, sizeof(l_uint32))) == NULL)
+        return (l_uint32 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+
+        /* Pack the two sums separately in two bytes */
+    for (i = 0; i < 64; i++) {
+        tab[i] = (sum[i & 0x07]) | (sum[(i >> 3) & 0x07] << 8);
+    }
+
+    return tab;
+}
+
+
+/*!
+ *  makeValTabSG3()
+ *
+ *  Returns an 8 bit value for the sum of ON pixels
+ *  in a 3x3 square, according to
+ *      val = 255 - (255 * sum)/9
+ *  where sum is in set {0, ... ,9}
+ */
+l_uint8 *
+makeValTabSG3(void)
+{
+l_int32   i;
+l_uint8  *tab;
+
+    PROCNAME("makeValTabSG3");
+
+    if ((tab = (l_uint8 *)LEPT_CALLOC(10, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+
+    for (i = 0; i < 10; i++)
+        tab[i] = 0xff - (i * 255) / 9;
+
+    return tab;
+}
+
+
+/*------------------------------------------------------------------*
+ *                         Scale-to-gray 4x                         *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleToGray4Low()
+ *
+ *      Input:  usual image variables
+ *              sumtab  (made from makeSumTabSG4())
+ *              valtab  (made from makeValTabSG4())
+ *      Return: 0 if OK; 1 on error.
+ *
+ *  The output is processed in sets of 2 output bytes on a row,
+ *  corresponding to 2 4x4 bit-blocks in the input image.
+ *  Two lookup tables are used.  The first, sumtab, gets the
+ *  sum of ON pixels in two sets of four adjacent bits,
+ *  storing the result in 2 adjacent bytes.  After sums from
+ *  four rows have been added, the second table, valtab,
+ *  converts from the sum of ON pixels in the 4x4 block to
+ *  an 8 bpp grayscale value between 0 (for 16 bits ON)
+ *  and 255 (for 0 bits ON).
+ */
+void
+scaleToGray4Low(l_uint32  *datad,
+                l_int32    wd,
+                l_int32    hd,
+                l_int32    wpld,
+                l_uint32  *datas,
+                l_int32    wpls,
+                l_uint32  *sumtab,
+                l_uint8   *valtab)
+{
+l_int32    i, j, l, k;
+l_uint32   sbyte1, sbyte2, sbyte3, sbyte4, sum;
+l_uint32  *lines, *lined;
+
+        /* i indexes the dest lines
+         * l indexes the source lines
+         * j indexes the dest bytes
+         * k indexes the source bytes
+         * We take four bytes from the source (in 4 lines of 8 pixels
+         * each) and convert it into two 8 bpp bytes of the dest. */
+    for (i = 0, l = 0; i < hd; i++, l += 4) {
+        lines = datas + l * wpls;
+        lined = datad + i * wpld;
+        for (j = 0, k = 0; j < wd; j += 2, k++) {
+            sbyte1 = GET_DATA_BYTE(lines, k);
+            sbyte2 = GET_DATA_BYTE(lines + wpls, k);
+            sbyte3 = GET_DATA_BYTE(lines + 2 * wpls, k);
+            sbyte4 = GET_DATA_BYTE(lines + 3 * wpls, k);
+            sum = sumtab[sbyte1] + sumtab[sbyte2] +
+                  sumtab[sbyte3] + sumtab[sbyte4];
+            SET_DATA_BYTE(lined, j, valtab[GET_DATA_BYTE(&sum, 2)]);
+            SET_DATA_BYTE(lined, j + 1, valtab[GET_DATA_BYTE(&sum, 3)]);
+        }
+    }
+
+    return;
+}
+
+
+/*!
+ *  makeSumTabSG4()
+ *
+ *  Returns a table of 256 l_uint32s, giving the two output
+ *  8-bit grayscale sums corresponding to 8 input bits of a binary
+ *  image, for a 4x scale-to-gray op.  The sums from four
+ *  adjacent scanlines are then added and transformed to
+ *  output 8 bpp pixel values, using makeValTabSG4().
+ */
+l_uint32  *
+makeSumTabSG4(void)
+{
+l_int32    i;
+l_int32    sum[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
+l_uint32  *tab;
+
+    PROCNAME("makeSumTabSG4");
+
+    if ((tab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32))) == NULL)
+        return (l_uint32 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+
+        /* Pack the two sums separately in two bytes */
+    for (i = 0; i < 256; i++) {
+        tab[i] = (sum[i & 0xf]) | (sum[(i >> 4) & 0xf] << 8);
+    }
+
+    return tab;
+}
+
+
+/*!
+ *  makeValTabSG4()
+ *
+ *  Returns an 8 bit value for the sum of ON pixels
+ *  in a 4x4 square, according to
+ *
+ *         val = 255 - (255 * sum)/16
+ *
+ *  where sum is in set {0, ... ,16}
+ */
+l_uint8 *
+makeValTabSG4(void)
+{
+l_int32   i;
+l_uint8  *tab;
+
+    PROCNAME("makeValTabSG4");
+
+    if ((tab = (l_uint8 *)LEPT_CALLOC(17, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+
+    for (i = 0; i < 17; i++)
+        tab[i] = 0xff - (i * 255) / 16;
+
+    return tab;
+}
+
+
+/*------------------------------------------------------------------*
+ *                         Scale-to-gray 6x                         *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleToGray6Low()
+ *
+ *      Input:  usual image variables
+ *              tab8  (made from makePixelSumTab8())
+ *              valtab  (made from makeValTabSG6())
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Each set of 4 6x6 bit-blocks in the source image, which
+ *  consist of 144 pixels arranged 24 pixels wide by 6 scanlines,
+ *  is converted to a row of 4 8-bit pixels in the dest image.
+ *  These 144 pixels of the input image are runs of 24 pixels
+ *  in six adjacent scanlines.  Each run of 24 pixels is
+ *  stored in the 24 LSbits of a 32-bit word.  We use 2 LUTs.
+ *  The first, tab8, takes 6 of these bits and stores
+ *  sum in one byte.  This is done for each of the 6 scanlines,
+ *  and the results are added.
+ *  We now have the sum of ON pixels in the first 6x6 block.  The
+ *  valtab LUT then converts these values (which go from 0 to 36) to
+ *  grayscale values between between 255 and 0.  (See makeValTabSG6).
+ *  This process is repeated for each of the other 3 sets of
+ *  6x6 input pixels, giving 4 output pixels in total.
+ *
+ *  Note: because the input image is processed in groups of
+ *        24 x 6 pixels, the process clips the input height to
+ *        (h - h % 6) and the input width to (w - w % 24).
+ *
+ */
+void
+scaleToGray6Low(l_uint32  *datad,
+                l_int32    wd,
+                l_int32    hd,
+                l_int32    wpld,
+                l_uint32  *datas,
+                l_int32    wpls,
+                l_int32   *tab8,
+                l_uint8   *valtab)
+{
+l_int32    i, j, l, k;
+l_uint32   threebytes1, threebytes2, threebytes3;
+l_uint32   threebytes4, threebytes5, threebytes6, sum;
+l_uint32  *lines, *lined;
+
+        /* i indexes the dest lines
+         * l indexes the source lines
+         * j indexes the dest bytes
+         * k indexes the source bytes
+         * We take 18 bytes from the source (144 binary pixels
+         * in six lines of 24 pixels each) and convert it
+         * into 4 bytes of the dest (four 8 bpp pixels in one line)   */
+    for (i = 0, l = 0; i < hd; i++, l += 6) {
+        lines = datas + l * wpls;
+        lined = datad + i * wpld;
+        for (j = 0, k = 0; j < wd; j += 4, k += 3) {
+                /* First grab the 18 bytes, 3 at a time, and put each set
+                 * of 3 bytes into the LS bytes of a 32-bit word. */
+            threebytes1 = (GET_DATA_BYTE(lines, k) << 16) |
+                          (GET_DATA_BYTE(lines, k + 1) << 8) |
+                          GET_DATA_BYTE(lines, k + 2);
+            threebytes2 = (GET_DATA_BYTE(lines + wpls, k) << 16) |
+                          (GET_DATA_BYTE(lines + wpls, k + 1) << 8) |
+                          GET_DATA_BYTE(lines + wpls, k + 2);
+            threebytes3 = (GET_DATA_BYTE(lines + 2 * wpls, k) << 16) |
+                          (GET_DATA_BYTE(lines + 2 * wpls, k + 1) << 8) |
+                          GET_DATA_BYTE(lines + 2 * wpls, k + 2);
+            threebytes4 = (GET_DATA_BYTE(lines + 3 * wpls, k) << 16) |
+                          (GET_DATA_BYTE(lines + 3 * wpls, k + 1) << 8) |
+                          GET_DATA_BYTE(lines + 3 * wpls, k + 2);
+            threebytes5 = (GET_DATA_BYTE(lines + 4 * wpls, k) << 16) |
+                          (GET_DATA_BYTE(lines + 4 * wpls, k + 1) << 8) |
+                          GET_DATA_BYTE(lines + 4 * wpls, k + 2);
+            threebytes6 = (GET_DATA_BYTE(lines + 5 * wpls, k) << 16) |
+                          (GET_DATA_BYTE(lines + 5 * wpls, k + 1) << 8) |
+                          GET_DATA_BYTE(lines + 5 * wpls, k + 2);
+
+                /* Sum first set of 36 bits and convert to 0-255 */
+            sum = tab8[(threebytes1 >> 18)] +
+                  tab8[(threebytes2 >> 18)] +
+                  tab8[(threebytes3 >> 18)] +
+                  tab8[(threebytes4 >> 18)] +
+                  tab8[(threebytes5 >> 18)] +
+                   tab8[(threebytes6 >> 18)];
+            SET_DATA_BYTE(lined, j, valtab[GET_DATA_BYTE(&sum, 3)]);
+
+                /* Ditto for second set */
+            sum = tab8[((threebytes1 >> 12) & 0x3f)] +
+                  tab8[((threebytes2 >> 12) & 0x3f)] +
+                  tab8[((threebytes3 >> 12) & 0x3f)] +
+                  tab8[((threebytes4 >> 12) & 0x3f)] +
+                  tab8[((threebytes5 >> 12) & 0x3f)] +
+                  tab8[((threebytes6 >> 12) & 0x3f)];
+            SET_DATA_BYTE(lined, j + 1, valtab[GET_DATA_BYTE(&sum, 3)]);
+
+            sum = tab8[((threebytes1 >> 6) & 0x3f)] +
+                  tab8[((threebytes2 >> 6) & 0x3f)] +
+                  tab8[((threebytes3 >> 6) & 0x3f)] +
+                  tab8[((threebytes4 >> 6) & 0x3f)] +
+                  tab8[((threebytes5 >> 6) & 0x3f)] +
+                  tab8[((threebytes6 >> 6) & 0x3f)];
+            SET_DATA_BYTE(lined, j + 2, valtab[GET_DATA_BYTE(&sum, 3)]);
+
+            sum = tab8[(threebytes1 & 0x3f)] +
+                  tab8[(threebytes2 & 0x3f)] +
+                  tab8[(threebytes3 & 0x3f)] +
+                  tab8[(threebytes4 & 0x3f)] +
+                  tab8[(threebytes5 & 0x3f)] +
+                  tab8[(threebytes6 & 0x3f)];
+            SET_DATA_BYTE(lined, j + 3, valtab[GET_DATA_BYTE(&sum, 3)]);
+        }
+    }
+
+    return;
+}
+
+
+/*!
+ *  makeValTabSG6()
+ *
+ *  Returns an 8 bit value for the sum of ON pixels
+ *  in a 6x6 square, according to
+ *      val = 255 - (255 * sum)/36
+ *  where sum is in set {0, ... ,36}
+ */
+l_uint8 *
+makeValTabSG6(void)
+{
+l_int32   i;
+l_uint8  *tab;
+
+    PROCNAME("makeValTabSG6");
+
+    if ((tab = (l_uint8 *)LEPT_CALLOC(37, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+
+    for (i = 0; i < 37; i++)
+        tab[i] = 0xff - (i * 255) / 36;
+
+    return tab;
+}
+
+
+/*------------------------------------------------------------------*
+ *                         Scale-to-gray 8x                         *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleToGray8Low()
+ *
+ *      Input:  usual image variables
+ *              tab8  (made from makePixelSumTab8())
+ *              valtab  (made from makeValTabSG8())
+ *      Return: 0 if OK; 1 on error.
+ *
+ *  The output is processed one dest byte at a time,
+ *  corresponding to 8 rows of src bytes in the input image.
+ *  Two lookup tables are used.  The first, tab8, gets the
+ *  sum of ON pixels in a byte.  After sums from 8 rows have
+ *  been added, the second table, valtab, converts from this
+ *  value (which is between 0 and 64) to an 8 bpp grayscale
+ *  value between 0 (for all 64 bits ON) and 255 (for 0 bits ON).
+ */
+void
+scaleToGray8Low(l_uint32  *datad,
+                l_int32    wd,
+                l_int32    hd,
+                l_int32    wpld,
+                l_uint32  *datas,
+                l_int32    wpls,
+                l_int32   *tab8,
+                l_uint8   *valtab)
+{
+l_int32    i, j, k;
+l_int32    sbyte0, sbyte1, sbyte2, sbyte3, sbyte4, sbyte5, sbyte6, sbyte7, sum;
+l_uint32  *lines, *lined;
+
+        /* i indexes the dest lines
+         * k indexes the source lines
+         * j indexes the src and dest bytes
+         * We take 8 bytes from the source (in 8 lines of 8 pixels
+         * each) and convert it into one 8 bpp byte of the dest. */
+    for (i = 0, k = 0; i < hd; i++, k += 8) {
+        lines = datas + k * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < wd; j++) {
+            sbyte0 = GET_DATA_BYTE(lines, j);
+            sbyte1 = GET_DATA_BYTE(lines + wpls, j);
+            sbyte2 = GET_DATA_BYTE(lines + 2 * wpls, j);
+            sbyte3 = GET_DATA_BYTE(lines + 3 * wpls, j);
+            sbyte4 = GET_DATA_BYTE(lines + 4 * wpls, j);
+            sbyte5 = GET_DATA_BYTE(lines + 5 * wpls, j);
+            sbyte6 = GET_DATA_BYTE(lines + 6 * wpls, j);
+            sbyte7 = GET_DATA_BYTE(lines + 7 * wpls, j);
+            sum = tab8[sbyte0] + tab8[sbyte1] +
+                  tab8[sbyte2] + tab8[sbyte3] +
+                  tab8[sbyte4] + tab8[sbyte5] +
+                  tab8[sbyte6] + tab8[sbyte7];
+            SET_DATA_BYTE(lined, j, valtab[sum]);
+        }
+    }
+
+    return;
+}
+
+
+/*!
+ *  makeValTabSG8()
+ *
+ *  Returns an 8 bit value for the sum of ON pixels
+ *  in an 8x8 square, according to
+ *      val = 255 - (255 * sum)/64
+ *  where sum is in set {0, ... ,64}
+ */
+l_uint8 *
+makeValTabSG8(void)
+{
+l_int32   i;
+l_uint8  *tab;
+
+    PROCNAME("makeValTabSG8");
+
+    if ((tab = (l_uint8 *)LEPT_CALLOC(65, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("calloc fail for tab", procName, NULL);
+
+    for (i = 0; i < 65; i++)
+        tab[i] = 0xff - (i * 255) / 64;
+    return tab;
+}
+
+
+/*------------------------------------------------------------------*
+ *                         Scale-to-gray 16x                        *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleToGray16Low()
+ *
+ *      Input:  usual image variables
+ *              tab8  (made from makePixelSumTab8())
+ *      Return: 0 if OK; 1 on error.
+ *
+ *  The output is processed one dest byte at a time, corresponding
+ *  to 16 rows consisting each of 2 src bytes in the input image.
+ *  This uses one lookup table, tab8, which gives the sum of
+ *  ON pixels in a byte.  After summing for all ON pixels in the
+ *  32 src bytes, which is between 0 and 256, this is converted
+ *  to an 8 bpp grayscale value between 0 (for 255 or 256 bits ON)
+ *  and 255 (for 0 bits ON).
+ */
+void
+scaleToGray16Low(l_uint32  *datad,
+                  l_int32    wd,
+                 l_int32    hd,
+                 l_int32    wpld,
+                 l_uint32  *datas,
+                 l_int32    wpls,
+                 l_int32   *tab8)
+{
+l_int32    i, j, k, m;
+l_int32    sum;
+l_uint32  *lines, *lined;
+
+        /* i indexes the dest lines
+         * k indexes the source lines
+         * j indexes the dest bytes
+         * m indexes the src bytes
+         * We take 32 bytes from the source (in 16 lines of 16 pixels
+         * each) and convert it into one 8 bpp byte of the dest. */
+    for (i = 0, k = 0; i < hd; i++, k += 16) {
+        lines = datas + k * wpls;
+        lined = datad + i * wpld;
+        for (j = 0; j < wd; j++) {
+            m = 2 * j;
+            sum = tab8[GET_DATA_BYTE(lines, m)];
+            sum += tab8[GET_DATA_BYTE(lines, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 2 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 2 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 3 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 3 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 4 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 4 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 5 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 5 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 6 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 6 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 7 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 7 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 8 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 8 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 9 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 9 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 10 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 10 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 11 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 11 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 12 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 12 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 13 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 13 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 14 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 14 * wpls, m + 1)];
+            sum += tab8[GET_DATA_BYTE(lines + 15 * wpls, m)];
+            sum += tab8[GET_DATA_BYTE(lines + 15 * wpls, m + 1)];
+            sum = L_MIN(sum, 255);
+            SET_DATA_BYTE(lined, j, 255 - sum);
+        }
+    }
+
+    return;
+}
+
+
+
+/*------------------------------------------------------------------*
+ *                         Grayscale mipmap                         *
+ *------------------------------------------------------------------*/
+/*!
+ *  scaleMipmapLow()
+ *
+ *  See notes in scale.c for pixScaleToGrayMipmap().  This function
+ *  is here for pedagogical reasons.  It gives poor results on document
+ *  images because of aliasing.
+ */
+l_int32
+scaleMipmapLow(l_uint32  *datad,
+               l_int32    wd,
+               l_int32    hd,
+               l_int32    wpld,
+               l_uint32  *datas1,
+               l_int32    wpls1,
+               l_uint32  *datas2,
+               l_int32    wpls2,
+               l_float32  red)
+{
+l_int32    i, j, val1, val2, val, row2, col2;
+l_int32   *srow, *scol;
+l_uint32  *lines1, *lines2, *lined;
+l_float32  ratio, w1, w2;
+
+    PROCNAME("scaleMipmapLow");
+
+        /* Clear dest */
+    memset((char *)datad, 0, 4 * wpld * hd);
+
+        /* Each dest pixel at (j,i) is computed by interpolating
+           between the two src images at the corresponding location.
+           We store the UL corner locations of the square of
+           src pixels in thelower-resolution image that correspond
+           to dest pixel (j,i).  The are labelled by the arrays
+           srow[i], scol[j].  The UL corner locations of the higher
+           resolution src pixels are obtained from these arrays
+           by multiplying by 2. */
+    if ((srow = (l_int32 *)LEPT_CALLOC(hd, sizeof(l_int32))) == NULL)
+        return ERROR_INT("srow not made", procName, 1);
+    if ((scol = (l_int32 *)LEPT_CALLOC(wd, sizeof(l_int32))) == NULL)
+        return ERROR_INT("scol not made", procName, 1);
+    ratio = 1. / (2. * red);  /* 0.5 for red = 1, 1 for red = 0.5 */
+    for (i = 0; i < hd; i++)
+        srow[i] = (l_int32)(ratio * i);
+    for (j = 0; j < wd; j++)
+        scol[j] = (l_int32)(ratio * j);
+
+        /* Get weights for linear interpolation: these are the
+         * 'distances' of the dest image plane from the two
+         * src image planes. */
+    w1 = 2. * red - 1.;   /* w1 --> 1 as red --> 1 */
+    w2 = 1. - w1;
+
+        /* For each dest pixel, compute linear interpolation */
+    for (i = 0; i < hd; i++) {
+        row2 = srow[i];
+        lines1 = datas1 + 2 * row2 * wpls1;
+        lines2 = datas2 + row2 * wpls2;
+        lined = datad + i * wpld;
+        for (j = 0; j < wd; j++) {
+            col2 = scol[j];
+            val1 = GET_DATA_BYTE(lines1, 2 * col2);
+            val2 = GET_DATA_BYTE(lines2, col2);
+            val = (l_int32)(w1 * val1 + w2 * val2);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    LEPT_FREE(srow);
+    LEPT_FREE(scol);
+    return 0;
+}
diff --git a/src/seedfill.c b/src/seedfill.c
new file mode 100644 (file)
index 0000000..4e99900
--- /dev/null
@@ -0,0 +1,1623 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  seedfill.c
+ *
+ *      Binary seedfill (source: Luc Vincent)
+ *               PIX      *pixSeedfillBinary()
+ *               PIX      *pixSeedfillBinaryRestricted()
+ *
+ *      Applications of binary seedfill to find and fill holes,
+ *      remove c.c. touching the border and fill bg from border:
+ *               PIX      *pixHolesByFilling()
+ *               PIX      *pixFillClosedBorders()
+ *               PIX      *pixExtractBorderConnComps()
+ *               PIX      *pixRemoveBorderConnComps()
+ *               PIX      *pixFillBgFromBorder()
+ *
+ *      Hole-filling of components to bounding rectangle
+ *               PIX      *pixFillHolesToBoundingRect()
+ *
+ *      Gray seedfill (source: Luc Vincent:fast-hybrid-grayscale-reconstruction)
+ *               l_int32   pixSeedfillGray()
+ *               l_int32   pixSeedfillGrayInv()
+ *
+ *      Gray seedfill (source: Luc Vincent: sequential-reconstruction algorithm)
+ *               l_int32   pixSeedfillGraySimple()
+ *               l_int32   pixSeedfillGrayInvSimple()
+ *
+ *      Gray seedfill variations
+ *               PIX      *pixSeedfillGrayBasin()
+ *
+ *      Distance function (source: Luc Vincent)
+ *               PIX      *pixDistanceFunction()
+ *
+ *      Seed spread (based on distance function)
+ *               PIX      *pixSeedspread()
+ *
+ *      Local extrema:
+ *               l_int32   pixLocalExtrema()
+ *        static l_int32   pixQualifyLocalMinima()
+ *               l_int32   pixSelectedLocalExtrema()
+ *               PIX      *pixFindEqualValues()
+ *
+ *      Selection of minima in mask of connected components
+ *               PTA      *pixSelectMinInConnComp()
+ *
+ *      Removal of seeded connected components from a mask
+ *               PIX      *pixRemoveSeededComponents()
+ *
+ *
+ *           ITERATIVE RASTER-ORDER SEEDFILL
+ *
+ *      The basic method in the Vincent seedfill (aka reconstruction)
+ *      algorithm is simple.  We describe here the situation for
+ *      binary seedfill.  Pixels are sampled in raster order in
+ *      the seed image.  If they are 4-connected to ON pixels
+ *      either directly above or to the left, and are not masked
+ *      out by the mask image, they are turned on (or remain on).
+ *      (Ditto for 8-connected, except you need to check 3 pixels
+ *      on the previous line as well as the pixel to the left
+ *      on the current line.  This is extra computational work
+ *      for relatively little gain, so it is preferable
+ *      in most situations to use the 4-connected version.)
+ *      The algorithm proceeds from UR to LL of the image, and
+ *      then reverses and sweeps up from LL to UR.
+ *      These double sweeps are iterated until there is no change.
+ *      At this point, the seed has entirely filled the region it
+ *      is allowed to, as delimited by the mask image.
+ *
+ *      The grayscale seedfill is a straightforward generalization
+ *      of the binary seedfill, and is described in seedfillLowGray().
+ *
+ *      For some applications, the filled seed will later be OR'd
+ *      with the negative of the mask.   This is used, for example,
+ *      when you flood fill into a 4-connected region of OFF pixels
+ *      and you want the result after those pixels are turned ON.
+ *
+ *      Note carefully that the mask we use delineates which pixels
+ *      are allowed to be ON as the seed is filled.  We will call this
+ *      a "filling mask".  As the seed expands, it is repeatedly
+ *      ANDed with the filling mask: s & fm.  The process can equivalently
+ *      be formulated using the inverse of the filling mask, which
+ *      we will call a "blocking mask": bm = ~fm.   As the seed
+ *      expands, the blocking mask is repeatedly used to prevent
+ *      the seed from expanding into the blocking mask.  This is done
+ *      by set subtracting the blocking mask from the expanded seed:
+ *      s - bm.  Set subtraction of the blocking mask is equivalent
+ *      to ANDing with the inverse of the blocking mask: s & (~bm).
+ *      But from the inverse relation between blocking and filling
+ *      masks, this is equal to s & fm, which proves the equivalence.
+ *
+ *      For efficiency, the pixels can be taken in larger units
+ *      for processing, but still in raster order.  It is natural
+ *      to take them in 32-bit words.  The outline of the work
+ *      to be done for 4-cc (not including special cases for boundary
+ *      words, such as the first line or the last word in each line)
+ *      is as follows.  Let the filling mask be m.  The
+ *      seed is to fill "under" the mask; i.e., limited by an AND
+ *      with the mask.  Let the current word be w, the word
+ *      in the line above be wa, and the previous word in the
+ *      current line be wp.   Let t be a temporary word that
+ *      is used in computation.  Note that masking is performed by
+ *      w & m.  (If we had instead used a "blocking" mask, we
+ *      would perform masking by the set subtraction operation,
+ *      w - m, which is defined to be w & ~m.)
+ *
+ *      The entire operation can be implemented with shifts,
+ *      logical operations and tests.  For each word in the seed image
+ *      there are two steps.  The first step is to OR the word with
+ *      the word above and with the rightmost pixel in wp (call it "x").
+ *      Because wp is shifted one pixel to its right, "x" is ORed
+ *      to the leftmost pixel of w.  We then clip to the ON pixels in
+ *      the mask.  The result is
+ *               t  <--  (w | wa | x000... ) & m
+ *      We've now finished taking data from above and to the left.
+ *      The second step is to allow filling to propagate horizontally
+ *      in t, always making sure that it is properly masked at each
+ *      step.  So if filling can be done (i.e., t is neither all 0s
+ *      nor all 1s), iteratively take:
+ *           t  <--  (t | (t >> 1) | (t << 1)) & m
+ *      until t stops changing.  Then write t back into w.
+ *
+ *      Finally, the boundary conditions require we note that in doing
+ *      the above steps:
+ *          (a) The words in the first row have no wa
+ *          (b) The first word in each row has no wp in that row
+ *          (c) The last word in each row must be masked so that
+ *              pixels don't propagate beyond the right edge of the
+ *              actual image.  (This is easily accomplished by
+ *              setting the out-of-bound pixels in m to OFF.)
+ */
+
+#include "allheaders.h"
+
+#ifndef  NO_CONSOLE_IO
+#define   DEBUG_PRINT_ITERS    0
+#endif  /* ~NO_CONSOLE_IO */
+
+  /* Two-way (UL --> LR, LR --> UL) sweep iterations; typically need only 4 */
+static const l_int32  MAX_ITERS = 40;
+
+    /* Static function */
+static l_int32 pixQualifyLocalMinima(PIX *pixs, PIX *pixm, l_int32 maxval);
+
+
+/*-----------------------------------------------------------------------*
+ *              Vincent's Iterative Binary Seedfill method               *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixSeedfillBinary()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs; 1 bpp)
+ *              pixs  (1 bpp seed)
+ *              pixm  (1 bpp filling mask)
+ *              connectivity  (4 or 8)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) This is for binary seedfill (aka "binary reconstruction").
+ *      (2) There are 3 cases:
+ *            (a) pixd == null (make a new pixd)
+ *            (b) pixd == pixs (in-place)
+ *            (c) pixd != pixs
+ *      (3) If you know the case, use these patterns for clarity:
+ *            (a) pixd = pixSeedfillBinary(NULL, pixs, ...);
+ *            (b) pixSeedfillBinary(pixs, pixs, ...);
+ *            (c) pixSeedfillBinary(pixd, pixs, ...);
+ *      (4) The resulting pixd contains the filled seed.  For some
+ *          applications you want to OR it with the inverse of
+ *          the filling mask.
+ *      (5) The input seed and mask images can be different sizes, but
+ *          in typical use the difference, if any, would be only
+ *          a few pixels in each direction.  If the sizes differ,
+ *          the clipping is handled by the low-level function
+ *          seedfillBinaryLow().
+ */
+PIX *
+pixSeedfillBinary(PIX     *pixd,
+                  PIX     *pixs,
+                  PIX     *pixm,
+                  l_int32  connectivity)
+{
+l_int32    i, boolval;
+l_int32    hd, hm, wpld, wplm;
+l_uint32  *datad, *datam;
+PIX       *pixt;
+
+    PROCNAME("pixSeedfillBinary");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, pixd);
+    if (!pixm || pixGetDepth(pixm) != 1)
+        return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", procName, pixd);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not in {4,8}", procName, pixd);
+
+        /* Prepare pixd as a copy of pixs if not identical */
+    if ((pixd = pixCopy(pixd, pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+
+        /* pixt is used to test for completion */
+    if ((pixt = pixCreateTemplate(pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixt not made", procName, pixd);
+
+    hd = pixGetHeight(pixd);
+    hm = pixGetHeight(pixm);  /* included so seedfillBinaryLow() can clip */
+    datad = pixGetData(pixd);
+    datam = pixGetData(pixm);
+    wpld = pixGetWpl(pixd);
+    wplm = pixGetWpl(pixm);
+
+    pixSetPadBits(pixm, 0);
+
+    for (i = 0; i < MAX_ITERS; i++) {
+        pixCopy(pixt, pixd);
+        seedfillBinaryLow(datad, hd, wpld, datam, hm, wplm, connectivity);
+        pixEqual(pixd, pixt, &boolval);
+        if (boolval == 1) {
+#if DEBUG_PRINT_ITERS
+            fprintf(stderr, "Binary seed fill converged: %d iters\n", i + 1);
+#endif  /* DEBUG_PRINT_ITERS */
+            break;
+        }
+    }
+
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+/*!
+ *  pixSeedfillBinaryRestricted()
+ *
+ *      Input:  pixd  (<optional>; this can be null, equal to pixs,
+ *                     or different from pixs; 1 bpp)
+ *              pixs  (1 bpp seed)
+ *              pixm  (1 bpp filling mask)
+ *              connectivity  (4 or 8)
+ *              xmax (max distance in x direction of fill into the mask)
+ *              ymax (max distance in y direction of fill into the mask)
+ *      Return: pixd always
+ *
+ *  Notes:
+ *      (1) See usage for pixSeedfillBinary(), which has unrestricted fill.
+ *          In pixSeedfillBinary(), the filling distance is unrestricted
+ *          and can be larger than pixs, depending on the topology of
+ *          th mask.
+ *      (2) There are occasions where it is useful not to permit the
+ *          fill to go more than a certain distance into the mask.
+ *          @xmax specifies the maximum horizontal distance allowed
+ *          in the fill; @ymax does likewise in the vertical direction.
+ *      (3) Operationally, the max "distance" allowed for the fill
+ *          is a linear distance from the original seed, independent
+ *          of the actual mask topology.
+ *      (4) Another formulation of this problem, not implemented,
+ *          would use the manhattan distance from the seed, as
+ *          determined by a breadth-first search starting at the seed
+ *          boundaries and working outward where the mask fg allows.
+ *          How this might use the constraints of separate xmax and ymax
+ *          is not clear.
+ */
+PIX *
+pixSeedfillBinaryRestricted(PIX     *pixd,
+                            PIX     *pixs,
+                            PIX     *pixm,
+                            l_int32  connectivity,
+                            l_int32  xmax,
+                            l_int32  ymax)
+{
+l_int32  w, h;
+PIX     *pix1, *pix2;
+
+    PROCNAME("pixSeedfillBinaryRestricted");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, pixd);
+    if (!pixm || pixGetDepth(pixm) != 1)
+        return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", procName, pixd);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not in {4,8}", procName, pixd);
+    if (xmax == 0 && ymax == 0)  /* no filling permitted */
+        return pixClone(pixs);
+    if (xmax < 0 || ymax < 0) {
+        L_ERROR("xmax and ymax must be non-negative", procName);
+        return pixClone(pixs);
+    }
+
+        /* Full fill from the seed into the mask. */
+    if ((pix1 = pixSeedfillBinary(NULL, pixs, pixm, connectivity)) == NULL)
+        return (PIX *)ERROR_PTR("pix1 not made", procName, pixd);
+
+        /* Dilate the seed.  This gives the maximal region where changes
+         * are permitted.  Invert to get the region where pixs is
+         * not allowed to change.  */
+    pix2 = pixDilateCompBrick(NULL, pixs, 2 * xmax + 1, 2 * ymax + 1);
+    pixInvert(pix2, pix2);
+
+        /* Blank the region of pix1 specified by the fg of pix2.
+         * This is not yet the final result, because it may have fg pixels
+         * that are not accessible from the seed in the restricted distance.
+         * For example, such pixels may be connected to the original seed,
+         * but through a path that goes outside the permitted region. */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    pixRasterop(pix1, 0, 0, w, h, PIX_DST & PIX_NOT(PIX_SRC), pix2, 0, 0);
+
+        /* To get the accessible pixels in the restricted region, do
+         * a second seedfill from the original seed, using pix1 as
+         * a mask.  The result, in pixd, will not have any bad fg
+         * pixels that were in pix1. */
+    pixd = pixSeedfillBinary(pixd, pixs, pix1, connectivity);
+
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    return pixd;
+}
+
+
+/*!
+ *  pixHolesByFilling()
+ *
+ *      Input:  pixs (1 bpp)
+ *              connectivity (4 or 8)
+ *      Return: pixd  (inverted image of all holes), or null on error
+ *
+ * Action:
+ *     (1) Start with 1-pixel black border on otherwise white pixd
+ *     (2) Use the inverted pixs as the filling mask to fill in
+ *         all the pixels from the border to the pixs foreground
+ *     (3) OR the result with pixs to have an image with all
+ *         ON pixels except for the holes.
+ *     (4) Invert the result to get the holes as foreground
+ *
+ * Notes:
+ *     (1) To get 4-c.c. holes of the 8-c.c. as foreground, use
+ *         4-connected filling; to get 8-c.c. holes of the 4-c.c.
+ *         as foreground, use 8-connected filling.
+ */
+PIX *
+pixHolesByFilling(PIX     *pixs,
+                  l_int32  connectivity)
+{
+PIX  *pixsi, *pixd;
+
+    PROCNAME("pixHolesByFilling");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+    if ((pixd = pixCreateTemplate(pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    if ((pixsi = pixInvert(NULL, pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixsi not made", procName, NULL);
+
+    pixSetOrClearBorder(pixd, 1, 1, 1, 1, PIX_SET);
+    pixSeedfillBinary(pixd, pixd, pixsi, connectivity);
+    pixOr(pixd, pixd, pixs);
+    pixInvert(pixd, pixd);
+    pixDestroy(&pixsi);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixFillClosedBorders()
+ *
+ *      Input:  pixs (1 bpp)
+ *              filling connectivity (4 or 8)
+ *      Return: pixd  (all topologically outer closed borders are filled
+ *                     as connected comonents), or null on error
+ *
+ *  Notes:
+ *      (1) Start with 1-pixel black border on otherwise white pixd
+ *      (2) Subtract input pixs to remove border pixels that were
+ *          also on the closed border
+ *      (3) Use the inverted pixs as the filling mask to fill in
+ *          all the pixels from the outer border to the closed border
+ *          on pixs
+ *      (4) Invert the result to get the filled component, including
+ *          the input border
+ *      (5) If the borders are 4-c.c., use 8-c.c. filling, and v.v.
+ *      (6) Closed borders within c.c. that represent holes, etc., are filled.
+ */
+PIX *
+pixFillClosedBorders(PIX     *pixs,
+                     l_int32  connectivity)
+{
+PIX  *pixsi, *pixd;
+
+    PROCNAME("pixFillClosedBorders");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+    if ((pixd = pixCreateTemplate(pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixSetOrClearBorder(pixd, 1, 1, 1, 1, PIX_SET);
+    pixSubtract(pixd, pixd, pixs);
+    if ((pixsi = pixInvert(NULL, pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixsi not made", procName, NULL);
+
+    pixSeedfillBinary(pixd, pixd, pixsi, connectivity);
+    pixInvert(pixd, pixd);
+    pixDestroy(&pixsi);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixExtractBorderConnComps()
+ *
+ *      Input:  pixs (1 bpp)
+ *              filling connectivity (4 or 8)
+ *      Return: pixd  (all pixels in the src that are in connected
+ *                     components touching the border), or null on error
+ */
+PIX *
+pixExtractBorderConnComps(PIX     *pixs,
+                          l_int32  connectivity)
+{
+PIX  *pixd;
+
+    PROCNAME("pixExtractBorderConnComps");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+        /* Start with 1 pixel wide black border as seed in pixd */
+    if ((pixd = pixCreateTemplate(pixs)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    pixSetOrClearBorder(pixd, 1, 1, 1, 1, PIX_SET);
+
+       /* Fill in pixd from the seed, using pixs as the filling mask.
+        * This fills all components from pixs that are touching the border. */
+    pixSeedfillBinary(pixd, pixd, pixs, connectivity);
+
+    return pixd;
+}
+
+
+/*!
+ *  pixRemoveBorderConnComps()
+ *
+ *      Input:  pixs (1 bpp)
+ *              filling connectivity (4 or 8)
+ *      Return: pixd  (all pixels in the src that are not touching the
+ *                     border) or null on error
+ *
+ *  Notes:
+ *      (1) This removes all fg components touching the border.
+ */
+PIX *
+pixRemoveBorderConnComps(PIX     *pixs,
+                         l_int32  connectivity)
+{
+PIX  *pixd;
+
+    PROCNAME("pixRemoveBorderConnComps");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+       /* Fill from a 1 pixel wide seed at the border into all components
+        * in pixs (the filling mask) that are touching the border */
+    pixd = pixExtractBorderConnComps(pixs, connectivity);
+
+       /* Save in pixd only those components in pixs not touching the border */
+    pixXor(pixd, pixd, pixs);
+    return pixd;
+}
+
+
+/*!
+ *  pixFillBgFromBorder()
+ *
+ *      Input:  pixs (1 bpp)
+ *              filling connectivity (4 or 8)
+ *      Return: pixd (with the background c.c. touching the border
+ *                    filled to foreground), or null on error
+ *
+ *  Notes:
+ *      (1) This fills all bg components touching the border to fg.
+ *          It is the photometric inverse of pixRemoveBorderConnComps().
+ *      (2) Invert the result to get the "holes" left after this fill.
+ *          This can be done multiple times, extracting holes within
+ *          holes after each pair of fillings.  Specifically, this code
+ *          peels away n successive embeddings of components:
+ *              pix1 = <initial image>
+ *              for (i = 0; i < 2 * n; i++) {
+ *                   pix2 = pixFillBgFromBorder(pix1, 8);
+ *                   pixInvert(pix2, pix2);
+ *                   pixDestroy(&pix1);
+ *                   pix1 = pix2;
+ *              }
+
+ */
+PIX *
+pixFillBgFromBorder(PIX     *pixs,
+                    l_int32  connectivity)
+{
+PIX  *pixd;
+
+    PROCNAME("pixFillBgFromBorder");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+       /* Invert to turn bg touching the border to a fg component.
+        * Extract this by filling from a 1 pixel wide seed at the border. */
+    pixInvert(pixs, pixs);
+    pixd = pixExtractBorderConnComps(pixs, connectivity);
+    pixInvert(pixs, pixs);  /* restore pixs */
+
+       /* Bit-or the filled bg component with pixs */
+    pixOr(pixd, pixd, pixs);
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *            Hole-filling of components to bounding rectangle           *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixFillHolesToBoundingRect()
+ *
+ *      Input:  pixs (1 bpp)
+ *              minsize (min number of pixels in the hole)
+ *              maxhfract (max hole area as fraction of fg pixels in the cc)
+ *              minfgfract (min fg area as fraction of bounding rectangle)
+ *      Return: pixd (pixs, with some holes possibly filled and some c.c.
+ *                    possibly expanded to their bounding rects),
+ *                    or null on error
+ *
+ *  Notes:
+ *      (1) This does not fill holes that are smaller in area than 'minsize'.
+ *      (2) This does not fill holes with an area larger than
+ *          'maxhfract' times the fg area of the c.c.
+ *      (3) This does not expand the fg of the c.c. to bounding rect if
+ *          the fg area is less than 'minfgfract' times the area of the
+ *          bounding rect.
+ *      (4) The decisions are made as follows:
+ *           - Decide if we are filling the holes; if so, when using
+ *             the fg area, include the filled holes.
+ *           - Decide based on the fg area if we are filling to a bounding rect.
+ *             If so, do it.
+ *             If not, fill the holes if the condition is satisfied.
+ *      (5) The choice of minsize depends on the resolution.
+ *      (6) For solidifying image mask regions on printed materials,
+ *          which tend to be rectangular, values for maxhfract
+ *          and minfgfract around 0.5 are reasonable.
+ */
+PIX *
+pixFillHolesToBoundingRect(PIX       *pixs,
+                           l_int32    minsize,
+                           l_float32  maxhfract,
+                           l_float32  minfgfract)
+{
+l_int32    i, x, y, w, h, n, nfg, nh, ntot, area;
+l_int32   *tab;
+l_float32  hfract;  /* measured hole fraction */
+l_float32  fgfract;  /* measured fg fraction */
+BOXA      *boxa;
+PIX       *pixd, *pixfg, *pixh;
+PIXA      *pixa;
+
+    PROCNAME("pixFillHolesToBoundingRect");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, NULL);
+
+    pixd = pixCopy(NULL, pixs);
+    boxa = pixConnComp(pixd, &pixa, 8);
+    n = boxaGetCount(boxa);
+    tab = makePixelSumTab8();
+    for (i = 0; i < n; i++) {
+        boxaGetBoxGeometry(boxa, i, &x, &y, &w, &h);
+        area = w * h;
+        if (area < minsize)
+            continue;
+        pixfg = pixaGetPix(pixa, i, L_COPY);
+        pixh = pixHolesByFilling(pixfg, 4);  /* holes only */
+        pixCountPixels(pixfg, &nfg, tab);
+        pixCountPixels(pixh, &nh, tab);
+        hfract = (l_float32)nh / (l_float32)nfg;
+        ntot = nfg;
+        if (hfract <= maxhfract)  /* we will fill the holes (at least) */
+            ntot = nfg + nh;
+        fgfract = (l_float32)ntot / (l_float32)area;
+        if (fgfract >= minfgfract) {  /* fill to bounding rect */
+            pixSetAll(pixfg);
+            pixRasterop(pixd, x, y, w, h, PIX_SRC, pixfg, 0, 0);
+        } else if (hfract <= maxhfract) {  /* fill just the holes */
+            pixRasterop(pixd, x, y, w, h, PIX_DST | PIX_SRC , pixh, 0, 0);
+        }
+        pixDestroy(&pixfg);
+        pixDestroy(&pixh);
+    }
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixa);
+    LEPT_FREE(tab);
+
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *             Vincent's hybrid Grayscale Seedfill method             *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixSeedfillGray()
+ *
+ *      Input:  pixs  (8 bpp seed; filled in place)
+ *              pixm  (8 bpp filling mask)
+ *              connectivity  (4 or 8)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place filling operation on the seed, pixs,
+ *          where the clipping mask is always above or at the level
+ *          of the seed as it is filled.
+ *      (2) For details of the operation, see the description in
+ *          seedfillGrayLow() and the code there.
+ *      (3) As an example of use, see the description in pixHDome().
+ *          There, the seed is an image where each pixel is a fixed
+ *          amount smaller than the corresponding mask pixel.
+ *      (4) Reference paper :
+ *            L. Vincent, Morphological grayscale reconstruction in image
+ *            analysis: applications and efficient algorithms, IEEE Transactions
+ *            on  Image Processing, vol. 2, no. 2, pp. 176-201, 1993.
+ */
+l_int32
+pixSeedfillGray(PIX     *pixs,
+                PIX     *pixm,
+                l_int32  connectivity)
+{
+l_int32    h, w, wpls, wplm;
+l_uint32  *datas, *datam;
+
+    PROCNAME("pixSeedfillGray");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (!pixm || pixGetDepth(pixm) != 8)
+        return ERROR_INT("pixm not defined or not 8 bpp", procName, 1);
+    if (connectivity != 4 && connectivity != 8)
+        return ERROR_INT("connectivity not in {4,8}", procName, 1);
+
+        /* Make sure the sizes of seed and mask images are the same */
+    if (pixSizesEqual(pixs, pixm) == 0)
+        return ERROR_INT("pixs and pixm sizes differ", procName, 1);
+
+    datas = pixGetData(pixs);
+    datam = pixGetData(pixm);
+    wpls = pixGetWpl(pixs);
+    wplm = pixGetWpl(pixm);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    seedfillGrayLow(datas, w, h, wpls, datam, wplm, connectivity);
+
+    return 0;
+}
+
+
+/*!
+ *  pixSeedfillGrayInv()
+ *
+ *      Input:  pixs  (8 bpp seed; filled in place)
+ *              pixm  (8 bpp filling mask)
+ *              connectivity  (4 or 8)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place filling operation on the seed, pixs,
+ *          where the clipping mask is always below or at the level
+ *          of the seed as it is filled.  Think of filling up a basin
+ *          to a particular level, given by the maximum seed value
+ *          in the basin.  Outside the filled region, the mask
+ *          is above the filling level.
+ *      (2) Contrast this with pixSeedfillGray(), where the clipping mask
+ *          is always above or at the level of the fill.  An example
+ *          of its use is the hdome fill, where the seed is an image
+ *          where each pixel is a fixed amount smaller than the
+ *          corresponding mask pixel.
+ *      (3) The basin fill, pixSeedfillGrayBasin(), is a special case
+ *          where the seed pixel values are generated from the mask,
+ *          and where the implementation uses pixSeedfillGray() by
+ *          inverting both the seed and mask.
+ */
+l_int32
+pixSeedfillGrayInv(PIX     *pixs,
+                   PIX     *pixm,
+                   l_int32  connectivity)
+{
+l_int32    h, w, wpls, wplm;
+l_uint32  *datas, *datam;
+
+    PROCNAME("pixSeedfillGrayInv");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (!pixm || pixGetDepth(pixm) != 8)
+        return ERROR_INT("pixm not defined or not 8 bpp", procName, 1);
+    if (connectivity != 4 && connectivity != 8)
+        return ERROR_INT("connectivity not in {4,8}", procName, 1);
+
+        /* Make sure the sizes of seed and mask images are the same */
+    if (pixSizesEqual(pixs, pixm) == 0)
+        return ERROR_INT("pixs and pixm sizes differ", procName, 1);
+
+    datas = pixGetData(pixs);
+    datam = pixGetData(pixm);
+    wpls = pixGetWpl(pixs);
+    wplm = pixGetWpl(pixm);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    seedfillGrayInvLow(datas, w, h, wpls, datam, wplm, connectivity);
+
+    return 0;
+}
+
+/*-----------------------------------------------------------------------*
+ *             Vincent's Iterative Grayscale Seedfill method             *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixSeedfillGraySimple()
+ *
+ *      Input:  pixs  (8 bpp seed; filled in place)
+ *              pixm  (8 bpp filling mask)
+ *              connectivity  (4 or 8)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place filling operation on the seed, pixs,
+ *          where the clipping mask is always above or at the level
+ *          of the seed as it is filled.
+ *      (2) For details of the operation, see the description in
+ *          seedfillGrayLowSimple() and the code there.
+ *      (3) As an example of use, see the description in pixHDome().
+ *          There, the seed is an image where each pixel is a fixed
+ *          amount smaller than the corresponding mask pixel.
+ *      (4) Reference paper :
+ *            L. Vincent, Morphological grayscale reconstruction in image
+ *            analysis: applications and efficient algorithms, IEEE Transactions
+ *            on  Image Processing, vol. 2, no. 2, pp. 176-201, 1993.
+ */
+l_int32
+pixSeedfillGraySimple(PIX     *pixs,
+                      PIX     *pixm,
+                      l_int32  connectivity)
+{
+l_int32    i, h, w, wpls, wplm, boolval;
+l_uint32  *datas, *datam;
+PIX       *pixt;
+
+    PROCNAME("pixSeedfillGraySimple");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (!pixm || pixGetDepth(pixm) != 8)
+        return ERROR_INT("pixm not defined or not 8 bpp", procName, 1);
+    if (connectivity != 4 && connectivity != 8)
+        return ERROR_INT("connectivity not in {4,8}", procName, 1);
+
+        /* Make sure the sizes of seed and mask images are the same */
+    if (pixSizesEqual(pixs, pixm) == 0)
+        return ERROR_INT("pixs and pixm sizes differ", procName, 1);
+
+        /* This is used to test for completion */
+    if ((pixt = pixCreateTemplate(pixs)) == NULL)
+        return ERROR_INT("pixt not made", procName, 1);
+
+    datas = pixGetData(pixs);
+    datam = pixGetData(pixm);
+    wpls = pixGetWpl(pixs);
+    wplm = pixGetWpl(pixm);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    for (i = 0; i < MAX_ITERS; i++) {
+        pixCopy(pixt, pixs);
+        seedfillGrayLowSimple(datas, w, h, wpls, datam, wplm, connectivity);
+        pixEqual(pixs, pixt, &boolval);
+        if (boolval == 1) {
+#if DEBUG_PRINT_ITERS
+            L_INFO("Gray seed fill converged: %d iters\n", procName, i + 1);
+#endif  /* DEBUG_PRINT_ITERS */
+            break;
+        }
+    }
+
+    pixDestroy(&pixt);
+    return 0;
+}
+
+
+/*!
+ *  pixSeedfillGrayInvSimple()
+ *
+ *      Input:  pixs  (8 bpp seed; filled in place)
+ *              pixm  (8 bpp filling mask)
+ *              connectivity  (4 or 8)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place filling operation on the seed, pixs,
+ *          where the clipping mask is always below or at the level
+ *          of the seed as it is filled.  Think of filling up a basin
+ *          to a particular level, given by the maximum seed value
+ *          in the basin.  Outside the filled region, the mask
+ *          is above the filling level.
+ *      (2) Contrast this with pixSeedfillGraySimple(), where the clipping mask
+ *          is always above or at the level of the fill.  An example
+ *          of its use is the hdome fill, where the seed is an image
+ *          where each pixel is a fixed amount smaller than the
+ *          corresponding mask pixel.
+ */
+l_int32
+pixSeedfillGrayInvSimple(PIX     *pixs,
+                         PIX     *pixm,
+                         l_int32  connectivity)
+{
+l_int32    i, h, w, wpls, wplm, boolval;
+l_uint32  *datas, *datam;
+PIX       *pixt;
+
+    PROCNAME("pixSeedfillGrayInvSimple");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (!pixm || pixGetDepth(pixm) != 8)
+        return ERROR_INT("pixm not defined or not 8 bpp", procName, 1);
+    if (connectivity != 4 && connectivity != 8)
+        return ERROR_INT("connectivity not in {4,8}", procName, 1);
+
+        /* Make sure the sizes of seed and mask images are the same */
+    if (pixSizesEqual(pixs, pixm) == 0)
+        return ERROR_INT("pixs and pixm sizes differ", procName, 1);
+
+        /* This is used to test for completion */
+    if ((pixt = pixCreateTemplate(pixs)) == NULL)
+        return ERROR_INT("pixt not made", procName, 1);
+
+    datas = pixGetData(pixs);
+    datam = pixGetData(pixm);
+    wpls = pixGetWpl(pixs);
+    wplm = pixGetWpl(pixm);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    for (i = 0; i < MAX_ITERS; i++) {
+        pixCopy(pixt, pixs);
+        seedfillGrayInvLowSimple(datas, w, h, wpls, datam, wplm, connectivity);
+        pixEqual(pixs, pixt, &boolval);
+        if (boolval == 1) {
+#if DEBUG_PRINT_ITERS
+            L_INFO("Gray seed fill converged: %d iters\n", procName, i + 1);
+#endif  /* DEBUG_PRINT_ITERS */
+            break;
+        }
+    }
+
+    pixDestroy(&pixt);
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                         Gray seedfill variations                      *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixSeedfillGrayBasin()
+ *
+ *      Input:  pixb  (binary mask giving seed locations)
+ *              pixm  (8 bpp basin-type filling mask)
+ *              delta (amount of seed value above mask)
+ *              connectivity  (4 or 8)
+ *      Return: pixd (filled seed) if OK, null on error
+ *
+ *  Notes:
+ *      (1) This fills from a seed within basins defined by a filling mask.
+ *          The seed value(s) are greater than the corresponding
+ *          filling mask value, and the result has the bottoms of
+ *          the basins raised by the initial seed value.
+ *      (2) The seed has value 255 except where pixb has fg (1), which
+ *          are the seed 'locations'.  At the seed locations, the seed
+ *          value is the corresponding value of the mask pixel in pixm
+ *          plus @delta.  If @delta == 0, we return a copy of pixm.
+ *      (3) The actual filling is done using the standard grayscale filling
+ *          operation on the inverse of the mask and using the inverse
+ *          of the seed image.  After filling, we return the inverse of
+ *          the filled seed.
+ *      (4) As an example of use: pixm can describe a grayscale image
+ *          of text, where the (dark) text pixels are basins of
+ *          low values; pixb can identify the local minima in pixm (say, at
+ *          the bottom of the basins); and delta is the amount that we wish
+ *          to raise (lighten) the basins.  We construct the seed
+ *          (a.k.a marker) image from pixb, pixm and @delta.
+ */
+PIX *
+pixSeedfillGrayBasin(PIX     *pixb,
+                     PIX     *pixm,
+                     l_int32  delta,
+                     l_int32  connectivity)
+{
+PIX  *pixbi, *pixmi, *pixsd;
+
+    PROCNAME("pixSeedfillGrayBasin");
+
+    if (!pixb || pixGetDepth(pixb) != 1)
+        return (PIX *)ERROR_PTR("pixb undefined or not 1 bpp", procName, NULL);
+    if (!pixm || pixGetDepth(pixm) != 8)
+        return (PIX *)ERROR_PTR("pixm undefined or not 8 bpp", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not in {4,8}", procName, NULL);
+
+    if (delta <= 0) {
+        L_WARNING("delta <= 0; returning a copy of pixm\n", procName);
+        return pixCopy(NULL, pixm);
+    }
+
+        /* Add delta to every pixel in pixm */
+    pixsd = pixCopy(NULL, pixm);
+    pixAddConstantGray(pixsd, delta);
+
+        /* Prepare the seed.  Write 255 in all pixels of
+         * ([pixm] + delta) where pixb is 0. */
+    pixbi = pixInvert(NULL, pixb);
+    pixSetMasked(pixsd, pixbi, 255);
+
+        /* Fill the inverse seed, using the inverse clipping mask */
+    pixmi = pixInvert(NULL, pixm);
+    pixInvert(pixsd, pixsd);
+    pixSeedfillGray(pixsd, pixmi, connectivity);
+
+        /* Re-invert the filled seed */
+    pixInvert(pixsd, pixsd);
+
+    pixDestroy(&pixbi);
+    pixDestroy(&pixmi);
+    return pixsd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                   Vincent's Distance Function method                  *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixDistanceFunction()
+ *
+ *      Input:  pixs  (1 bpp source)
+ *              connectivity  (4 or 8)
+ *              outdepth (8 or 16 bits for pixd)
+ *              boundcond (L_BOUNDARY_BG, L_BOUNDARY_FG)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This computes the distance of each pixel from the nearest
+ *          background pixel.  All bg pixels therefore have a distance of 0,
+ *          and the fg pixel distances increase linearly from 1 at the
+ *          boundary.  It can also be used to compute the distance of
+ *          each pixel from the nearest fg pixel, by inverting the input
+ *          image before calling this function.  Then all fg pixels have
+ *          a distance 0 and the bg pixel distances increase linearly
+ *          from 1 at the boundary.
+ *      (2) The algorithm, described in Leptonica on the page on seed
+ *          filling and connected components, is due to Luc Vincent.
+ *          In brief, we generate an 8 or 16 bpp image, initialized
+ *          with the fg pixels of the input pix set to 1 and the
+ *          1-boundary pixels (i.e., the boundary pixels of width 1 on
+ *          the four sides set as either:
+ *            * L_BOUNDARY_BG: 0
+ *            * L_BOUNDARY_FG:  max
+ *          where max = 0xff for 8 bpp and 0xffff for 16 bpp.
+ *          Then do raster/anti-raster sweeps over all pixels interior
+ *          to the 1-boundary, where the value of each new pixel is
+ *          taken to be 1 more than the minimum of the previously-seen
+ *          connected pixels (using either 4 or 8 connectivity).
+ *          Finally, set the 1-boundary pixels using the mirrored method;
+ *          this removes the max values there.
+ *      (3) Using L_BOUNDARY_BG clamps the distance to 0 at the
+ *          boundary.  Using L_BOUNDARY_FG allows the distance
+ *          at the image boundary to "float".
+ *      (4) For 4-connected, one could initialize only the left and top
+ *          1-boundary pixels, and go all the way to the right
+ *          and bottom; then coming back reset left and top.  But we
+ *          instead use a method that works for both 4- and 8-connected.
+ */
+PIX *
+pixDistanceFunction(PIX     *pixs,
+                    l_int32  connectivity,
+                    l_int32  outdepth,
+                    l_int32  boundcond)
+{
+l_int32    w, h, wpld;
+l_uint32  *datad;
+PIX       *pixd;
+
+    PROCNAME("pixDistanceFunction");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("!pixs or pixs not 1 bpp", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+    if (outdepth != 8 && outdepth != 16)
+        return (PIX *)ERROR_PTR("outdepth not 8 or 16 bpp", procName, NULL);
+    if (boundcond != L_BOUNDARY_BG && boundcond != L_BOUNDARY_FG)
+        return (PIX *)ERROR_PTR("invalid boundcond", procName, NULL);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if ((pixd = pixCreate(w, h, outdepth)) == NULL)
+        return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+        /* Initialize the fg pixels to 1 and the bg pixels to 0 */
+    pixSetMasked(pixd, pixs, 1);
+
+    if (boundcond == L_BOUNDARY_BG) {
+        distanceFunctionLow(datad, w, h, outdepth, wpld, connectivity);
+    } else {  /* L_BOUNDARY_FG: set boundary pixels to max val */
+        pixRasterop(pixd, 0, 0, w, 1, PIX_SET, NULL, 0, 0);   /* top */
+        pixRasterop(pixd, 0, h - 1, w, 1, PIX_SET, NULL, 0, 0);   /* bot */
+        pixRasterop(pixd, 0, 0, 1, h, PIX_SET, NULL, 0, 0);   /* left */
+        pixRasterop(pixd, w - 1, 0, 1, h, PIX_SET, NULL, 0, 0);   /* right */
+
+        distanceFunctionLow(datad, w, h, outdepth, wpld, connectivity);
+
+            /* Set each boundary pixel equal to the pixel next to it */
+        pixSetMirroredBorder(pixd, 1, 1, 1, 1);
+    }
+
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                Seed spread (based on distance function)               *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixSeedspread()
+ *
+ *      Input:  pixs  (8 bpp source)
+ *              connectivity  (4 or 8)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) The raster/anti-raster method for implementing this filling
+ *          operation was suggested by Ray Smith.
+ *      (2) This takes an arbitrary set of nonzero pixels in pixs, which
+ *          can be sparse, and spreads (extrapolates) the values to
+ *          fill all the pixels in pixd with the nonzero value it is
+ *          closest to in pixs.  This is similar (though not completely
+ *          equivalent) to doing a Voronoi tiling of the image, with a
+ *          tile surrounding each pixel that has a nonzero value.
+ *          All pixels within a tile are then closer to its "central"
+ *          pixel than to any others.  Then assign the value of the
+ *          "central" pixel to each pixel in the tile.
+ *      (3) This is implemented by computing a distance function in parallel
+ *          with the fill.  The distance function uses free boundary
+ *          conditions (assumed maxval outside), and it controls the
+ *          propagation of the pixels in pixd away from the nonzero
+ *          (seed) values.  This is done in 2 traversals (raster/antiraster).
+ *          In the raster direction, whenever the distance function
+ *          is nonzero, the spread pixel takes on the value of its
+ *          predecessor that has the minimum distance value.  In the
+ *          antiraster direction, whenever the distance function is nonzero
+ *          and its value is replaced by a smaller value, the spread
+ *          pixel takes the value of the predecessor with the minimum
+ *          distance value.
+ *      (4) At boundaries where a pixel is equidistant from two
+ *          nearest nonzero (seed) pixels, the decision of which value
+ *          to use is arbitrary (greedy in search for minimum distance).
+ *          This can give rise to strange-looking results, particularly
+ *          for 4-connectivity where the L1 distance is computed from
+ *          steps in N,S,E and W directions (no diagonals).
+ */
+PIX *
+pixSeedspread(PIX     *pixs,
+              l_int32  connectivity)
+{
+l_int32    w, h, wplt, wplg;
+l_uint32  *datat, *datag;
+PIX       *pixm, *pixt, *pixg, *pixd;
+
+    PROCNAME("pixSeedspread");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return (PIX *)ERROR_PTR("!pixs or pixs not 8 bpp", procName, NULL);
+    if (connectivity != 4 && connectivity != 8)
+        return (PIX *)ERROR_PTR("connectivity not 4 or 8", procName, NULL);
+
+        /* Add a 4 byte border to pixs.  This simplifies the computation. */
+    pixg = pixAddBorder(pixs, 4, 0);
+    pixGetDimensions(pixg, &w, &h, NULL);
+
+        /* Initialize distance function pixt.  Threshold pixs to get
+         * a 0 at the seed points where the pixs pixel is nonzero, and
+         * a 1 at all points that need to be filled.  Use this as a
+         * mask to set a 1 in pixt at all non-seed points.  Also, set all
+         * pixt pixels in an interior boundary of width 1 to the
+         * maximum value.   For debugging, to view the distance function,
+         * use pixConvert16To8(pixt, 0) on small images.  */
+    pixm = pixThresholdToBinary(pixg, 1);
+    pixt = pixCreate(w, h, 16);
+    pixSetMasked(pixt, pixm, 1);
+    pixRasterop(pixt, 0, 0, w, 1, PIX_SET, NULL, 0, 0);   /* top */
+    pixRasterop(pixt, 0, h - 1, w, 1, PIX_SET, NULL, 0, 0);   /* bot */
+    pixRasterop(pixt, 0, 0, 1, h, PIX_SET, NULL, 0, 0);   /* left */
+    pixRasterop(pixt, w - 1, 0, 1, h, PIX_SET, NULL, 0, 0);   /* right */
+    datat = pixGetData(pixt);
+    wplt = pixGetWpl(pixt);
+
+        /* Do the interpolation and remove the border. */
+    datag = pixGetData(pixg);
+    wplg = pixGetWpl(pixg);
+    seedspreadLow(datag, w, h, wplg, datat, wplt, connectivity);
+    pixd = pixRemoveBorder(pixg, 4);
+
+    pixDestroy(&pixm);
+    pixDestroy(&pixg);
+    pixDestroy(&pixt);
+    return pixd;
+}
+
+
+
+/*-----------------------------------------------------------------------*
+ *                              Local extrema                            *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixLocalExtrema()
+ *
+ *      Input:  pixs  (8 bpp)
+ *              maxmin (max allowed for the min in a 3x3 neighborhood;
+ *                      use 0 for default which is to have no upper bound)
+ *              minmax (min allowed for the max in a 3x3 neighborhood;
+ *                      use 0 for default which is to have no lower bound)
+ *              &ppixmin (<optional return> mask of local minima)
+ *              &ppixmax (<optional return> mask of local maxima)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This gives the actual local minima and maxima.
+ *          A local minimum is a pixel whose surrounding pixels all
+ *          have values at least as large, and likewise for a local
+ *          maximum.  For the local minima, @maxmin is the upper
+ *          bound for the value of pixs.  Likewise, for the local maxima,
+ *          @minmax is the lower bound for the value of pixs.
+ *      (2) The minima are found by starting with the erosion-and-equality
+ *          approach of pixSelectedLocalExtrema().  This is followed
+ *          by a qualification step, where each c.c. in the resulting
+ *          minimum mask is extracted, the pixels bordering it are
+ *          located, and they are queried.  If all of those pixels
+ *          are larger than the value of that minimum, it is a true
+ *          minimum and its c.c. is saved; otherwise the c.c. is
+ *          rejected.  Note that if a bordering pixel has the
+ *          same value as the minimum, it must then have a
+ *          neighbor that is smaller, so the component is not a
+ *          true minimum.
+ *      (3) The maxima are found by inverting the image and looking
+ *          for the minima there.
+ *      (4) The generated masks can be used as markers for
+ *          further operations.
+ */
+l_int32
+pixLocalExtrema(PIX     *pixs,
+                l_int32  maxmin,
+                l_int32  minmax,
+                PIX    **ppixmin,
+                PIX    **ppixmax)
+{
+PIX  *pixmin, *pixmax, *pixt1, *pixt2;
+
+    PROCNAME("pixLocalExtrema");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (!ppixmin && !ppixmax)
+        return ERROR_INT("neither &pixmin, &pixmax are defined", procName, 1);
+    if (maxmin <= 0) maxmin = 254;
+    if (minmax <= 0) minmax = 1;
+
+    if (ppixmin) {
+        pixt1 = pixErodeGray(pixs, 3, 3);
+        pixmin = pixFindEqualValues(pixs, pixt1);
+        pixDestroy(&pixt1);
+        pixQualifyLocalMinima(pixs, pixmin, maxmin);
+        *ppixmin = pixmin;
+    }
+
+    if (ppixmax) {
+        pixt1 = pixInvert(NULL, pixs);
+        pixt2 = pixErodeGray(pixt1, 3, 3);
+        pixmax = pixFindEqualValues(pixt1, pixt2);
+        pixDestroy(&pixt2);
+        pixQualifyLocalMinima(pixt1, pixmax, 255 - minmax);
+        *ppixmax = pixmax;
+        pixDestroy(&pixt1);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixQualifyLocalMinima()
+ *
+ *      Input:  pixs  (8 bpp image from which pixm has been extracted)
+ *              pixm  (1 bpp mask of values equal to min in 3x3 neighborhood)
+ *              maxval (max allowed for the min in a 3x3 neighborhood;
+ *                      use 0 for default which is to have no upper bound)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function acts in-place to remove all c.c. in pixm
+ *          that are not true local minima in pixs.  As seen in
+ *          pixLocalExtrema(), the input pixm are found by selecting those
+ *          pixels of pixs whose values do not change with a 3x3
+ *          grayscale erosion.  Here, we require that for each c.c.
+ *          in pixm, all pixels in pixs that correspond to the exterior
+ *          boundary pixels of the c.c. have values that are greater
+ *          than the value within the c.c.
+ *      (2) The maximum allowed value for each local minimum can be
+ *          bounded with @maxval.  Use 0 for default, which is to have
+ *          no upper bound (equivalent to maxval == 254).
+ */
+static l_int32
+pixQualifyLocalMinima(PIX     *pixs,
+                      PIX     *pixm,
+                      l_int32  maxval)
+{
+l_int32    n, i, j, k, x, y, w, h, xc, yc, wc, hc, xon, yon;
+l_int32    vals, wpls, wplc, ismin;
+l_uint32   val;
+l_uint32  *datas, *datac, *lines, *linec;
+BOXA      *boxa;
+PIX       *pixt1, *pixt2, *pixc;
+PIXA      *pixa;
+
+    PROCNAME("pixQualifyLocalMinima");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (!pixm || pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm not defined or not 1 bpp", procName, 1);
+    if (maxval <= 0) maxval = 254;
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    boxa = pixConnComp(pixm, &pixa, 8);
+    n = pixaGetCount(pixa);
+    for (k = 0; k < n; k++) {
+        boxaGetBoxGeometry(boxa, k, &xc, &yc, &wc, &hc);
+        pixt1 = pixaGetPix(pixa, k, L_COPY);
+        pixt2 = pixAddBorder(pixt1, 1, 0);
+        pixc = pixDilateBrick(NULL, pixt2, 3, 3);
+        pixXor(pixc, pixc, pixt2);  /* exterior boundary pixels */
+        datac = pixGetData(pixc);
+        wplc = pixGetWpl(pixc);
+        nextOnPixelInRaster(pixt1, 0, 0, &xon, &yon);
+        pixGetPixel(pixs, xc + xon, yc + yon, &val);
+        if (val > maxval) {  /* too large; erase */
+            pixRasterop(pixm, xc, yc, wc, hc, PIX_XOR, pixt1, 0, 0);
+            pixDestroy(&pixt1);
+            pixDestroy(&pixt2);
+            pixDestroy(&pixc);
+            continue;
+        }
+        ismin = TRUE;
+
+            /* Check all values in pixs that correspond to the exterior
+             * boundary pixels of the c.c. in pixm.  Verify that the
+             * value in the c.c. is always less. */
+        for (i = 0, y = yc - 1; i < hc + 2 && y >= 0 && y < h; i++, y++) {
+            lines = datas + y * wpls;
+            linec = datac + i * wplc;
+            for (j = 0, x = xc - 1; j < wc + 2 && x >= 0 && x < w; j++, x++) {
+                if (GET_DATA_BIT(linec, j)) {
+                    vals = GET_DATA_BYTE(lines, x);
+                    if (vals <= val) {  /* not a minimum! */
+                        ismin = FALSE;
+                        break;
+                    }
+                }
+            }
+            if (!ismin)
+                break;
+        }
+        if (!ismin)  /* erase it */
+            pixRasterop(pixm, xc, yc, wc, hc, PIX_XOR, pixt1, 0, 0);
+        pixDestroy(&pixt1);
+        pixDestroy(&pixt2);
+        pixDestroy(&pixc);
+    }
+
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixa);
+    return 0;
+}
+
+
+/*!
+ *  pixSelectedLocalExtrema()
+ *
+ *      Input:  pixs  (8 bpp)
+ *              mindist (-1 for keeping all pixels; >= 0 specifies distance)
+ *              &ppixmin (<return> mask of local minima)
+ *              &ppixmax (<return> mask of local maxima)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This selects those local 3x3 minima that are at least a
+ *          specified distance from the nearest local 3x3 maxima, and v.v.
+ *          for the selected set of local 3x3 maxima.
+ *          The local 3x3 minima is the set of pixels whose value equals
+ *          the value after a 3x3 brick erosion, and the local 3x3 maxima
+ *          is the set of pixels whose value equals the value after
+ *          a 3x3 brick dilation.
+ *      (2) mindist is the minimum distance allowed between
+ *          local 3x3 minima and local 3x3 maxima, in an 8-connected sense.
+ *          mindist == 1 keeps all pixels found in step 1.
+ *          mindist == 0 removes all pixels from each mask that are
+ *          both a local 3x3 minimum and a local 3x3 maximum.
+ *          mindist == 1 removes any local 3x3 minimum pixel that touches a
+ *          local 3x3 maximum pixel, and likewise for the local maxima.
+ *          To make the decision, visualize each local 3x3 minimum pixel
+ *          as being surrounded by a square of size (2 * mindist + 1)
+ *          on each side, such that no local 3x3 maximum pixel is within
+ *          that square; and v.v.
+ *      (3) The generated masks can be used as markers for further operations.
+ */
+l_int32
+pixSelectedLocalExtrema(PIX     *pixs,
+                        l_int32  mindist,
+                        PIX    **ppixmin,
+                        PIX    **ppixmax)
+{
+PIX  *pixmin, *pixmax, *pixt, *pixtmin, *pixtmax;
+
+    PROCNAME("pixSelectedLocalExtrema");
+
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs not defined or not 8 bpp", procName, 1);
+    if (!ppixmin || !ppixmax)
+        return ERROR_INT("&pixmin and &pixmax not both defined", procName, 1);
+
+    pixt = pixErodeGray(pixs, 3, 3);
+    pixmin = pixFindEqualValues(pixs, pixt);
+    pixDestroy(&pixt);
+    pixt = pixDilateGray(pixs, 3, 3);
+    pixmax = pixFindEqualValues(pixs, pixt);
+    pixDestroy(&pixt);
+
+        /* Remove all points that are within the prescribed distance
+         * from each other. */
+    if (mindist < 0) {  /* remove no points */
+        *ppixmin = pixmin;
+        *ppixmax = pixmax;
+    } else if (mindist == 0) {  /* remove points belonging to both sets */
+        pixt = pixAnd(NULL, pixmin, pixmax);
+        *ppixmin = pixSubtract(pixmin, pixmin, pixt);
+        *ppixmax = pixSubtract(pixmax, pixmax, pixt);
+        pixDestroy(&pixt);
+    } else {
+        pixtmin = pixDilateBrick(NULL, pixmin,
+                                 2 * mindist + 1, 2 * mindist + 1);
+        pixtmax = pixDilateBrick(NULL, pixmax,
+                                 2 * mindist + 1, 2 * mindist + 1);
+        *ppixmin = pixSubtract(pixmin, pixmin, pixtmax);
+        *ppixmax = pixSubtract(pixmax, pixmax, pixtmin);
+        pixDestroy(&pixtmin);
+        pixDestroy(&pixtmax);
+    }
+    return 0;
+}
+
+
+/*!
+ *  pixFindEqualValues()
+ *
+ *      Input:  pixs1 (8 bpp)
+ *              pixs2 (8 bpp)
+ *      Return: pixd (1 bpp mask), or null on error
+ *
+ *  Notes:
+ *      (1) The two images are aligned at the UL corner, and the returned
+ *          image has ON pixels where the pixels in pixs1 and pixs2
+ *          have equal values.
+ */
+PIX *
+pixFindEqualValues(PIX  *pixs1,
+                   PIX  *pixs2)
+{
+l_int32    w1, h1, w2, h2, w, h;
+l_int32    i, j, val1, val2, wpls1, wpls2, wpld;
+l_uint32  *datas1, *datas2, *datad, *lines1, *lines2, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixFindEqualValues");
+
+    if (!pixs1 || pixGetDepth(pixs1) != 8)
+        return (PIX *)ERROR_PTR("pixs1 undefined or not 8 bpp", procName, NULL);
+    if (!pixs2 || pixGetDepth(pixs2) != 8)
+        return (PIX *)ERROR_PTR("pixs2 undefined or not 8 bpp", procName, NULL);
+    pixGetDimensions(pixs1, &w1, &h1, NULL);
+    pixGetDimensions(pixs2, &w2, &h2, NULL);
+    w = L_MIN(w1, w2);
+    h = L_MIN(h1, h2);
+    pixd = pixCreate(w, h, 1);
+    datas1 = pixGetData(pixs1);
+    datas2 = pixGetData(pixs2);
+    datad = pixGetData(pixd);
+    wpls1 = pixGetWpl(pixs1);
+    wpls2 = pixGetWpl(pixs2);
+    wpld = pixGetWpl(pixd);
+
+    for (i = 0; i < h; i++) {
+        lines1 = datas1 + i * wpls1;
+        lines2 = datas2 + i * wpls2;
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            val1 = GET_DATA_BYTE(lines1, j);
+            val2 = GET_DATA_BYTE(lines2, j);
+            if (val1 == val2)
+                SET_DATA_BIT(lined, j);
+        }
+    }
+
+    return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *             Selection of minima in mask connected components          *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixSelectMinInConnComp()
+ *
+ *      Input:  pixs (8 bpp)
+ *              pixm (1 bpp)
+ *              &pta (<return> pta of min pixel locations)
+ *              &nav (<optional return> numa of minima values)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) For each 8 connected component in pixm, this finds
+ *          a pixel in pixs that has the lowest value, and saves
+ *          it in a Pta.  If several pixels in pixs have the same
+ *          minimum value, it picks the first one found.
+ *      (2) For a mask pixm of true local minima, all pixels in each
+ *          connected component have the same value in pixs, so it is
+ *          fastest to select one of them using a special seedfill
+ *          operation.  Not yet implemented.
+ */
+l_int32
+pixSelectMinInConnComp(PIX    *pixs,
+                       PIX    *pixm,
+                       PTA   **ppta,
+                       NUMA  **pnav)
+{
+l_int32    bx, by, bw, bh, i, j, c, n;
+l_int32    xs, ys, minx, miny, wpls, wplt, val, minval;
+l_uint32  *datas, *datat, *lines, *linet;
+BOXA      *boxa;
+NUMA      *nav;
+PIX       *pixt, *pixs2, *pixm2;
+PIXA      *pixa;
+PTA       *pta;
+
+    PROCNAME("pixSelectMinInConnComp");
+
+    if (!ppta)
+        return ERROR_INT("&pta not defined", procName, 1);
+    *ppta = NULL;
+    if (pnav) *pnav = NULL;
+    if (!pixs || pixGetDepth(pixs) != 8)
+        return ERROR_INT("pixs undefined or not 8 bpp", procName, 1);
+    if (!pixm || pixGetDepth(pixm) != 1)
+        return ERROR_INT("pixm undefined or not 1 bpp", procName, 1);
+
+        /* Crop to the min size if necessary */
+    if (pixCropToMatch(pixs, pixm, &pixs2, &pixm2)) {
+        pixDestroy(&pixs2);
+        pixDestroy(&pixm2);
+        return ERROR_INT("cropping failure", procName, 1);
+    }
+
+        /* Find value and location of min value pixel in each component */
+    boxa = pixConnComp(pixm2, &pixa, 8);
+    n = boxaGetCount(boxa);
+    pta = ptaCreate(n);
+    *ppta = pta;
+    nav = numaCreate(n);
+    datas = pixGetData(pixs2);
+    wpls = pixGetWpl(pixs2);
+    for (c = 0; c < n; c++) {
+        pixt = pixaGetPix(pixa, c, L_CLONE);
+        boxaGetBoxGeometry(boxa, c, &bx, &by, &bw, &bh);
+        if (bw == 1 && bh == 1) {
+            ptaAddPt(pta, bx, by);
+            numaAddNumber(nav, GET_DATA_BYTE(datas + by * wpls, bx));
+            pixDestroy(&pixt);
+            continue;
+        }
+        datat = pixGetData(pixt);
+        wplt = pixGetWpl(pixt);
+        minx = miny = 1000000;
+        minval = 256;
+        for (i = 0; i < bh; i++) {
+            ys = by + i;
+            lines = datas + ys * wpls;
+            linet = datat + i * wplt;
+            for (j = 0; j < bw; j++) {
+                xs = bx + j;
+                if (GET_DATA_BIT(linet, j)) {
+                    val = GET_DATA_BYTE(lines, xs);
+                    if (val < minval) {
+                        minval = val;
+                        minx = xs;
+                        miny = ys;
+                    }
+                }
+            }
+        }
+        ptaAddPt(pta, minx, miny);
+        numaAddNumber(nav, GET_DATA_BYTE(datas + miny * wpls, minx));
+        pixDestroy(&pixt);
+    }
+
+    boxaDestroy(&boxa);
+    pixaDestroy(&pixa);
+    if (pnav)
+        *pnav = nav;
+    else
+        numaDestroy(&nav);
+    pixDestroy(&pixs2);
+    pixDestroy(&pixm2);
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *            Removal of seeded connected components from a mask         *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixRemoveSeededComponents()
+ *
+ *      Input:  pixd  (<optional>; this can be null or equal to pixm; 1 bpp)
+ *              pixs  (1 bpp seed)
+ *              pixm  (1 bpp filling mask)
+ *              connectivity  (4 or 8)
+ *              bordersize (amount of border clearing)
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) This removes each component in pixm for which there is
+ *          at least one seed in pixs.  If pixd == NULL, this returns
+ *          the result in a new pixd.  Otherwise, it is an in-place
+ *          operation on pixm.  In no situation is pixs altered,
+ *          because we do the filling with a copy of pixs.
+ *      (2) If bordersize > 0, it also clears all pixels within a
+ *          distance @bordersize of the edge of pixd.  This is here
+ *          because pixLocalExtrema() typically finds local minima
+ *          at the border.  Use @bordersize >= 2 to remove these.
+ */
+PIX *
+pixRemoveSeededComponents(PIX     *pixd,
+                          PIX     *pixs,
+                          PIX     *pixm,
+                          l_int32  connectivity,
+                          l_int32  bordersize)
+{
+PIX  *pixt;
+
+    PROCNAME("pixRemoveSeededComponents");
+
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs undefined or not 1 bpp", procName, pixd);
+    if (!pixm || pixGetDepth(pixm) != 1)
+        return (PIX *)ERROR_PTR("pixm undefined or not 1 bpp", procName, pixd);
+    if (pixd && pixd != pixm)
+        return (PIX *)ERROR_PTR("operation not inplace", procName, pixd);
+
+    pixt = pixCopy(NULL, pixs);
+    pixSeedfillBinary(pixt, pixt, pixm, connectivity);
+    pixd = pixXor(pixd, pixm, pixt);
+    if (bordersize > 0)
+        pixSetOrClearBorder(pixd, bordersize, bordersize, bordersize,
+                            bordersize, PIX_CLR);
+    pixDestroy(&pixt);
+    return pixd;
+}
diff --git a/src/seedfilllow.c b/src/seedfilllow.c
new file mode 100644 (file)
index 0000000..0e5e116
--- /dev/null
@@ -0,0 +1,1831 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  seedfilllow.c
+ *
+ *      Seedfill:
+ *      Gray seedfill (source: Luc Vincent:fast-hybrid-grayscale-reconstruction)
+ *               void   seedfillBinaryLow()
+ *               void   seedfillGrayLow()
+ *               void   seedfillGrayInvLow()
+ *               void   seedfillGrayLowSimple()
+ *               void   seedfillGrayInvLowSimple()
+ *
+ *      Distance function:
+ *               void   distanceFunctionLow()
+ *
+ *      Seed spread:
+ *               void   seedspreadLow()
+ *
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+struct L_Pixel
+{
+    l_int32    x;
+    l_int32    y;
+};
+typedef struct L_Pixel  L_PIXEL;
+
+
+/*-----------------------------------------------------------------------*
+ *                 Vincent's Iterative Binary Seedfill                   *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  seedfillBinaryLow()
+ *
+ *  Notes:
+ *      (1) This is an in-place fill, where the seed image is
+ *          filled, clipping to the filling mask, in one full
+ *          cycle of UL -> LR and LR -> UL raster scans.
+ *      (2) Assume the mask is a filling mask, not a blocking mask.
+ *      (3) Assume that the RHS pad bits of the mask
+ *          are properly set to 0.
+ *      (4) Clip to the smallest dimensions to avoid invalid reads.
+ */
+void
+seedfillBinaryLow(l_uint32  *datas,
+                  l_int32    hs,
+                  l_int32    wpls,
+                  l_uint32  *datam,
+                  l_int32    hm,
+                  l_int32    wplm,
+                  l_int32    connectivity)
+{
+l_int32    i, j, h, wpl;
+l_uint32   word, mask;
+l_uint32   wordabove, wordleft, wordbelow, wordright;
+l_uint32   wordprev;  /* test against this in previous iteration */
+l_uint32  *lines, *linem;
+
+    PROCNAME("seedfillBinaryLow");
+
+    h = L_MIN(hs, hm);
+    wpl = L_MIN(wpls, wplm);
+
+    switch (connectivity)
+    {
+    case 4:
+            /* UL --> LR scan */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < wpl; j++) {
+                word = *(lines + j);
+                mask = *(linem + j);
+
+                    /* OR from word above and from word to left; mask */
+                if (i > 0) {
+                    wordabove = *(lines - wpls + j);
+                    word |= wordabove;
+                }
+                if (j > 0) {
+                    wordleft = *(lines + j - 1);
+                    word |= wordleft << 31;
+                }
+                word &= mask;
+
+                    /* No need to fill horizontally? */
+                if (!word || !(~word)) {
+                    *(lines + j) = word;
+                    continue;
+                }
+
+                while (1) {
+                    wordprev = word;
+                    word = (word | (word >> 1) | (word << 1)) & mask;
+                    if ((word ^ wordprev) == 0) {
+                        *(lines + j) = word;
+                        break;
+                    }
+                }
+            }
+        }
+
+            /* LR --> UL scan */
+        for (i = h - 1; i >= 0; i--) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = wpl - 1; j >= 0; j--) {
+                word = *(lines + j);
+                mask = *(linem + j);
+
+                    /* OR from word below and from word to right; mask */
+                if (i < h - 1) {
+                    wordbelow = *(lines + wpls + j);
+                    word |= wordbelow;
+                }
+                if (j < wpl - 1) {
+                    wordright = *(lines + j + 1);
+                    word |= wordright >> 31;
+                }
+                word &= mask;
+
+                    /* No need to fill horizontally? */
+                if (!word || !(~word)) {
+                    *(lines + j) = word;
+                    continue;
+                }
+
+                while (1) {
+                    wordprev = word;
+                    word = (word | (word >> 1) | (word << 1)) & mask;
+                    if ((word ^ wordprev) == 0) {
+                        *(lines + j) = word;
+                        break;
+                    }
+                }
+            }
+        }
+        break;
+
+    case 8:
+            /* UL --> LR scan */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < wpl; j++) {
+                word = *(lines + j);
+                mask = *(linem + j);
+
+                    /* OR from words above and from word to left; mask */
+                if (i > 0) {
+                    wordabove = *(lines - wpls + j);
+                    word |= (wordabove | (wordabove << 1) | (wordabove >> 1));
+                    if (j > 0)
+                        word |= (*(lines - wpls + j - 1)) << 31;
+                    if (j < wpl - 1)
+                        word |= (*(lines - wpls + j + 1)) >> 31;
+                }
+                if (j > 0) {
+                    wordleft = *(lines + j - 1);
+                    word |= wordleft << 31;
+                }
+                word &= mask;
+
+                    /* No need to fill horizontally? */
+                if (!word || !(~word)) {
+                    *(lines + j) = word;
+                    continue;
+                }
+
+                while (1) {
+                    wordprev = word;
+                    word = (word | (word >> 1) | (word << 1)) & mask;
+                    if ((word ^ wordprev) == 0) {
+                        *(lines + j) = word;
+                        break;
+                    }
+                }
+            }
+        }
+
+            /* LR --> UL scan */
+        for (i = h - 1; i >= 0; i--) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = wpl - 1; j >= 0; j--) {
+                word = *(lines + j);
+                mask = *(linem + j);
+
+                    /* OR from words below and from word to right; mask */
+                if (i < h - 1) {
+                    wordbelow = *(lines + wpls + j);
+                    word |= (wordbelow | (wordbelow << 1) | (wordbelow >> 1));
+                    if (j > 0)
+                        word |= (*(lines + wpls + j - 1)) << 31;
+                    if (j < wpl - 1)
+                        word |= (*(lines + wpls + j + 1)) >> 31;
+                }
+                if (j < wpl - 1) {
+                    wordright = *(lines + j + 1);
+                    word |= wordright >> 31;
+                }
+                word &= mask;
+
+                    /* No need to fill horizontally? */
+                if (!word || !(~word)) {
+                    *(lines + j) = word;
+                    continue;
+                }
+
+                while (1) {
+                    wordprev = word;
+                    word = (word | (word >> 1) | (word << 1)) & mask;
+                    if ((word ^ wordprev) == 0) {
+                        *(lines + j) = word;
+                        break;
+                    }
+                }
+            }
+        }
+        break;
+
+    default:
+        L_ERROR("connectivity must be 4 or 8\n", procName);
+        return;
+    }
+
+    return;
+}
+
+
+
+/*-----------------------------------------------------------------------*
+ *                 Vincent's Hybrid Grayscale Seedfill                *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  seedfillGrayLow()
+ *
+ *  Notes:
+ *      (1) The pixels are numbered as follows:
+ *              1  2  3
+ *              4  x  5
+ *              6  7  8
+ *          This low-level filling operation consists of two scans,
+ *          raster and anti-raster, covering the entire seed image.
+ *          This is followed by a breadth-first propagation operation to
+ *          complete the fill.
+ *          During the anti-raster scan, every pixel p whose current value
+ *          could still be propagated after the anti-raster scan is put into
+ *          the FIFO queue.
+ *          The propagation step is a breadth-first fill to completion.
+ *          Unlike the simple grayscale seedfill pixSeedfillGraySimple(),
+ *          where at least two full raster/anti-raster iterations are required
+ *          for completion and verification, the hybrid method uses only a
+ *          single raster/anti-raster set of scans.
+ *      (2) The filling action can be visualized from the following example.
+ *          Suppose the mask, which clips the fill, is a sombrero-shaped
+ *          surface, where the highest point is 200 and the low pixels
+ *          around the rim are 30.  Beyond the rim, the mask goes up a bit.
+ *          Suppose the seed, which is filled, consists of a single point
+ *          of height 150, located below the max of the mask, with
+ *          the rest 0.  Then in the raster scan, nothing happens until
+ *          the high seed point is encountered, and then this value is
+ *          propagated right and down, until it hits the side of the
+ *          sombrero.   The seed can never exceed the mask, so it fills
+ *          to the rim, going lower along the mask surface.  When it
+ *          passes the rim, the seed continues to fill at the rim
+ *          height to the edge of the seed image.  Then on the
+ *          anti-raster scan, the seed fills flat inside the
+ *          sombrero to the upper and left, and then out from the
+ *          rim as before.  The final result has a seed that is
+ *          flat outside the rim, and inside it fills the sombrero
+ *          but only up to 150.  If the rim height varies, the
+ *          filled seed outside the rim will be at the highest
+ *          point on the rim, which is a saddle point on the rim.
+ *      (3) Reference paper :
+ *            L. Vincent, Morphological grayscale reconstruction in image
+ *            analysis: applications and efficient algorithms, IEEE Transactions
+ *            on  Image Processing, vol. 2, no. 2, pp. 176-201, 1993.
+ */
+void
+seedfillGrayLow(l_uint32  *datas,
+                l_int32    w,
+                l_int32    h,
+                l_int32    wpls,
+                l_uint32  *datam,
+                l_int32    wplm,
+                l_int32    connectivity)
+{
+l_uint8    val1, val2, val3, val4, val5, val6, val7, val8;
+l_uint8    val, maxval, maskval, boolval;
+l_int32    i, j, imax, jmax, queue_size;
+l_uint32  *lines, *linem;
+L_PIXEL *pixel;
+L_QUEUE  *lq_pixel;
+
+    PROCNAME("seedfillGrayLow");
+
+    if (connectivity != 4 && connectivity != 8) {
+        L_ERROR("connectivity must be 4 or 8\n", procName);
+        return;
+    }
+
+    imax = h - 1;
+    jmax = w - 1;
+
+        /* In the worst case, most of the pixels could be pushed
+         * onto the FIFO queue during anti-raster scan.  However this
+         * will rarely happen, and we initialize the queue ptr size to
+         * the image perimeter. */
+    lq_pixel = lqueueCreate(2 * (w + h));
+
+    switch (connectivity)
+    {
+    case 4:
+            /* UL --> LR scan  (Raster Order)
+             * If I : mask image
+             *    J : marker image
+             * Let p be the currect pixel;
+             * J(p) <- (max{J(p) union J(p) neighbors in raster order})
+             *          intersection I(p) */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < w; j++) {
+                if ((maskval = GET_DATA_BYTE(linem, j)) > 0) {
+                    maxval = 0;
+                    if (i > 0)
+                        maxval = GET_DATA_BYTE(lines - wpls, j);
+                    if (j > 0) {
+                        val4 = GET_DATA_BYTE(lines, j - 1);
+                        maxval = L_MAX(maxval, val4);
+                    }
+                    val = GET_DATA_BYTE(lines, j);
+                    maxval = L_MAX(maxval, val);
+                    val = L_MIN(maxval, maskval);
+                    SET_DATA_BYTE(lines, j, val);
+                }
+            }
+        }
+
+            /* LR --> UL scan (anti-raster order)
+             * Let p be the currect pixel;
+             * J(p) <- (max{J(p) union J(p) neighbors in anti-raster order})
+             *          intersection I(p) */
+        for (i = imax; i >= 0; i--) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = jmax; j >= 0; j--) {
+                boolval = FALSE;
+                if ((maskval = GET_DATA_BYTE(linem, j)) > 0) {
+                    maxval = 0;
+                    if (i < imax)
+                        maxval = GET_DATA_BYTE(lines + wpls, j);
+                    if (j < jmax) {
+                        val5 = GET_DATA_BYTE(lines, j + 1);
+                        maxval = L_MAX(maxval, val5);
+                    }
+                    val = GET_DATA_BYTE(lines, j);
+                    maxval = L_MAX(maxval, val);
+                    val = L_MIN(maxval, maskval);
+                    SET_DATA_BYTE(lines, j, val);
+
+                        /*
+                         * If there exists a point (q) which belongs to J(p)
+                         * neighbors in anti-raster order such that J(q) < J(p)
+                         * and J(q) < I(q) then
+                         * fifo_add(p) */
+                    if (i < imax) {
+                        val7 = GET_DATA_BYTE(lines + wpls, j);
+                        if ((val7 < val) &&
+                            (val7 < GET_DATA_BYTE(linem + wplm, j))) {
+                            boolval = TRUE;
+                        }
+                    }
+                    if (j < jmax) {
+                        val5 = GET_DATA_BYTE(lines, j + 1);
+                        if (!boolval && (val5 < val) &&
+                            (val5 < GET_DATA_BYTE(linem, j + 1))) {
+                            boolval = TRUE;
+                        }
+                    }
+                    if (boolval) {
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i;
+                        pixel->y = j;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+            }
+        }
+
+            /* Propagation step:
+             *        while fifo_empty = false
+             *          p <- fifo_first()
+             *          for every pixel (q) belong to neighbors of (p)
+             *            if J(q) < J(p) and I(q) != J(q)
+             *              J(q) <- min(J(p), I(q));
+             *              fifo_add(q);
+             *            end
+             *          end
+             *        end */
+        queue_size = lqueueGetCount(lq_pixel);
+        while (queue_size) {
+            pixel = (L_PIXEL *)lqueueRemove(lq_pixel);
+            i = pixel->x;
+            j = pixel->y;
+            LEPT_FREE(pixel);
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+
+            if ((val = GET_DATA_BYTE(lines, j)) > 0) {
+                if (i > 0) {
+                    val2 = GET_DATA_BYTE(lines - wpls, j);
+                    maskval = GET_DATA_BYTE(linem - wplm, j);
+                    if (val > val2 && val2 != maskval) {
+                        SET_DATA_BYTE(lines - wpls, j, L_MIN(val, maskval));
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i - 1;
+                        pixel->y = j;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+
+                }
+                if (j > 0) {
+                    val4 = GET_DATA_BYTE(lines, j - 1);
+                    maskval = GET_DATA_BYTE(linem, j - 1);
+                    if (val > val4 && val4 != maskval) {
+                        SET_DATA_BYTE(lines, j - 1, L_MIN(val, maskval));
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i;
+                        pixel->y = j - 1;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+                if (i < imax) {
+                    val7 = GET_DATA_BYTE(lines + wpls, j);
+                    maskval = GET_DATA_BYTE(linem + wplm, j);
+                    if (val > val7 && val7 != maskval) {
+                        SET_DATA_BYTE(lines + wpls, j, L_MIN(val, maskval));
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i + 1;
+                        pixel->y = j;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+                if (j < jmax) {
+                    val5 = GET_DATA_BYTE(lines, j + 1);
+                    maskval = GET_DATA_BYTE(linem, j + 1);
+                    if (val > val5 && val5 != maskval) {
+                        SET_DATA_BYTE(lines, j + 1, L_MIN(val, maskval));
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i;
+                        pixel->y = j + 1;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+            }
+
+            queue_size = lqueueGetCount(lq_pixel);
+        }
+
+        break;
+
+    case 8:
+            /* UL --> LR scan  (Raster Order)
+             * If I : mask image
+             *    J : marker image
+             * Let p be the currect pixel;
+             * J(p) <- (max{J(p) union J(p) neighbors in raster order})
+             *          intersection I(p) */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < w; j++) {
+                if ((maskval = GET_DATA_BYTE(linem, j)) > 0) {
+                    maxval = 0;
+                    if (i > 0) {
+                        if (j > 0)
+                            maxval = GET_DATA_BYTE(lines - wpls, j - 1);
+                        if (j < jmax) {
+                            val3 = GET_DATA_BYTE(lines - wpls, j + 1);
+                            maxval = L_MAX(maxval, val3);
+                        }
+                        val2 = GET_DATA_BYTE(lines - wpls, j);
+                        maxval = L_MAX(maxval, val2);
+                    }
+                    if (j > 0) {
+                        val4 = GET_DATA_BYTE(lines, j - 1);
+                        maxval = L_MAX(maxval, val4);
+                    }
+                    val = GET_DATA_BYTE(lines, j);
+                    maxval = L_MAX(maxval, val);
+                    val = L_MIN(maxval, maskval);
+                    SET_DATA_BYTE(lines, j, val);
+                }
+            }
+        }
+
+            /* LR --> UL scan (anti-raster order)
+             * Let p be the currect pixel;
+             * J(p) <- (max{J(p) union J(p) neighbors in anti-raster order})
+             *          intersection I(p) */
+        for (i = imax; i >= 0; i--) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = jmax; j >= 0; j--) {
+                boolval = FALSE;
+                if ((maskval = GET_DATA_BYTE(linem, j)) > 0) {
+                    maxval = 0;
+                    if (i < imax) {
+                        if (j > 0) {
+                            maxval = GET_DATA_BYTE(lines + wpls, j - 1);
+                        }
+                        if (j < jmax) {
+                            val8 = GET_DATA_BYTE(lines + wpls, j + 1);
+                            maxval = L_MAX(maxval, val8);
+                        }
+                        val7 = GET_DATA_BYTE(lines + wpls, j);
+                        maxval = L_MAX(maxval, val7);
+                    }
+                    if (j < jmax) {
+                        val5 = GET_DATA_BYTE(lines, j + 1);
+                        maxval = L_MAX(maxval, val5);
+                    }
+                    val = GET_DATA_BYTE(lines, j);
+                    maxval = L_MAX(maxval, val);
+                    val = L_MIN(maxval, maskval);
+                    SET_DATA_BYTE(lines, j, val);
+
+                        /* If there exists a point (q) which belongs to J(p)
+                         * neighbors in anti-raster order such that J(q) < J(p)
+                         * and J(q) < I(q) then
+                         * fifo_add(p) */
+                    if (i < imax) {
+                        if (j > 0) {
+                            val6 = GET_DATA_BYTE(lines + wpls, j - 1);
+                            if ((val6 < val) &&
+                                (val6 < GET_DATA_BYTE(linem + wplm, j - 1))) {
+                                boolval = TRUE;
+                            }
+                        }
+                        if (j < jmax) {
+                            val8 = GET_DATA_BYTE(lines + wpls, j + 1);
+                            if (!boolval && (val8 < val) &&
+                                (val8 < GET_DATA_BYTE(linem + wplm, j + 1))) {
+                                boolval = TRUE;
+                            }
+                        }
+                        val7 = GET_DATA_BYTE(lines + wpls, j);
+                        if (!boolval && (val7 < val) &&
+                            (val7 < GET_DATA_BYTE(linem + wplm, j))) {
+                            boolval = TRUE;
+                        }
+                    }
+                    if (j < jmax) {
+                        val5 = GET_DATA_BYTE(lines, j + 1);
+                        if (!boolval && (val5 < val) &&
+                            (val5 < GET_DATA_BYTE(linem, j + 1))) {
+                            boolval = TRUE;
+                        }
+                    }
+                    if (boolval) {
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i;
+                        pixel->y = j;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+            }
+        }
+
+            /* Propagation step:
+             *        while fifo_empty = false
+             *          p <- fifo_first()
+             *          for every pixel (q) belong to neighbors of (p)
+             *            if J(q) < J(p) and I(q) != J(q)
+             *              J(q) <- min(J(p), I(q));
+             *              fifo_add(q);
+             *            end
+             *          end
+             *        end */
+        queue_size = lqueueGetCount(lq_pixel);
+        while (queue_size) {
+            pixel = (L_PIXEL *)lqueueRemove(lq_pixel);
+            i = pixel->x;
+            j = pixel->y;
+            LEPT_FREE(pixel);
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+
+            if ((val = GET_DATA_BYTE(lines, j)) > 0) {
+                if (i > 0) {
+                    if (j > 0) {
+                        val1 = GET_DATA_BYTE(lines - wpls, j - 1);
+                        maskval = GET_DATA_BYTE(linem - wplm, j - 1);
+                        if (val > val1 && val1 != maskval) {
+                            SET_DATA_BYTE(lines - wpls, j - 1,
+                                          L_MIN(val, maskval));
+                            pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                            pixel->x = i - 1;
+                            pixel->y = j - 1;
+                            lqueueAdd(lq_pixel, pixel);
+                        }
+                    }
+                    if (j < jmax) {
+                        val3 = GET_DATA_BYTE(lines - wpls, j + 1);
+                        maskval = GET_DATA_BYTE(linem - wplm, j + 1);
+                        if (val > val3 && val3 != maskval) {
+                            SET_DATA_BYTE(lines - wpls, j + 1,
+                                          L_MIN(val, maskval));
+                            pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                            pixel->x = i - 1;
+                            pixel->y = j + 1;
+                            lqueueAdd(lq_pixel, pixel);
+                        }
+                    }
+                    val2 = GET_DATA_BYTE(lines - wpls, j);
+                    maskval = GET_DATA_BYTE(linem - wplm, j);
+                    if (val > val2 && val2 != maskval) {
+                        SET_DATA_BYTE(lines - wpls, j, L_MIN(val, maskval));
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i - 1;
+                        pixel->y = j;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+
+                }
+                if (j > 0) {
+                    val4 = GET_DATA_BYTE(lines, j - 1);
+                    maskval = GET_DATA_BYTE(linem, j - 1);
+                    if (val > val4 && val4 != maskval) {
+                        SET_DATA_BYTE(lines, j - 1, L_MIN(val, maskval));
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i;
+                        pixel->y = j - 1;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+                if (i < imax) {
+                    if (j > 0) {
+                        val6 = GET_DATA_BYTE(lines + wpls, j - 1);
+                        maskval = GET_DATA_BYTE(linem + wplm, j - 1);
+                        if (val > val6 && val6 != maskval) {
+                            SET_DATA_BYTE(lines + wpls, j - 1,
+                                          L_MIN(val, maskval));
+                            pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                            pixel->x = i + 1;
+                            pixel->y = j - 1;
+                            lqueueAdd(lq_pixel, pixel);
+                        }
+                    }
+                    if (j < jmax) {
+                        val8 = GET_DATA_BYTE(lines + wpls, j + 1);
+                        maskval = GET_DATA_BYTE(linem + wplm, j + 1);
+                        if (val > val8 && val8 != maskval) {
+                            SET_DATA_BYTE(lines + wpls, j + 1,
+                                          L_MIN(val, maskval));
+                            pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                            pixel->x = i + 1;
+                            pixel->y = j + 1;
+                            lqueueAdd(lq_pixel, pixel);
+                        }
+                    }
+                    val7 = GET_DATA_BYTE(lines + wpls, j);
+                    maskval = GET_DATA_BYTE(linem + wplm, j);
+                    if (val > val7 && val7 != maskval) {
+                        SET_DATA_BYTE(lines + wpls, j, L_MIN(val, maskval));
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i + 1;
+                        pixel->y = j;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+                if (j < jmax) {
+                    val5 = GET_DATA_BYTE(lines, j + 1);
+                    maskval = GET_DATA_BYTE(linem, j + 1);
+                    if (val > val5 && val5 != maskval) {
+                        SET_DATA_BYTE(lines, j + 1, L_MIN(val, maskval));
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i;
+                        pixel->y = j + 1;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+            }
+
+            queue_size = lqueueGetCount(lq_pixel);
+        }
+        break;
+
+    default:
+        L_ERROR("shouldn't get here!\n", procName);
+        break;
+    }
+
+    lqueueDestroy(&lq_pixel, TRUE);
+    return;
+}
+
+
+/*!
+ *  seedfillGrayInvLow()
+ *
+ *  Notes:
+ *      (1) The pixels are numbered as follows:
+ *              1  2  3
+ *              4  x  5
+ *              6  7  8
+ *          This low-level filling operation consists of two scans,
+ *          raster and anti-raster, covering the entire seed image.
+ *          During the anti-raster scan, every pixel p such that its
+ *          current value could still be propogated during the next
+ *          raster scanning is put into the FIFO-queue.
+ *          Next step is the propagation step where where we update
+ *          and propagate the values using FIFO structure created in
+ *          anti-raster scan.
+ *      (2) The "Inv" signifies the fact that in this case, filling
+ *          of the seed only takes place when the seed value is
+ *          greater than the mask value.  The mask will act to stop
+ *          the fill when it is higher than the seed level.  (This is
+ *          in contrast to conventional grayscale filling where the
+ *          seed always fills below the mask.)
+ *      (3) An example of use is a basin, described by the mask (pixm),
+ *          where within the basin, the seed pix (pixs) gets filled to the
+ *          height of the highest seed pixel that is above its
+ *          corresponding max pixel.  Filling occurs while the
+ *          propagating seed pixels in pixs are larger than the
+ *          corresponding mask values in pixm.
+ *      (4) Reference paper :
+ *            L. Vincent, Morphological grayscale reconstruction in image
+ *            analysis: applications and efficient algorithms, IEEE Transactions
+ *            on  Image Processing, vol. 2, no. 2, pp. 176-201, 1993.
+ */
+void
+seedfillGrayInvLow(l_uint32  *datas,
+                   l_int32    w,
+                   l_int32    h,
+                   l_int32    wpls,
+                   l_uint32  *datam,
+                   l_int32    wplm,
+                   l_int32    connectivity)
+{
+l_uint8    val1, val2, val3, val4, val5, val6, val7, val8;
+l_uint8    val, maxval, maskval, boolval;
+l_int32    i, j, imax, jmax, queue_size;
+l_uint32  *lines, *linem;
+L_PIXEL *pixel;
+L_QUEUE  *lq_pixel;
+
+    PROCNAME("seedfillGrayInvLow");
+
+    if (connectivity != 4 && connectivity != 8) {
+        L_ERROR("connectivity must be 4 or 8\n", procName);
+        return;
+    }
+
+    imax = h - 1;
+    jmax = w - 1;
+
+        /* In the worst case, most of the pixels could be pushed
+         * onto the FIFO queue during anti-raster scan.  However this
+         * will rarely happen, and we initialize the queue ptr size to
+         * the image perimeter. */
+    lq_pixel = lqueueCreate(2 * (w + h));
+
+    switch (connectivity)
+    {
+    case 4:
+            /* UL --> LR scan  (Raster Order)
+             * If I : mask image
+             *    J : marker image
+             * Let p be the currect pixel;
+             * tmp <- max{J(p) union J(p) neighbors in raster order}
+             * if (tmp > I(p))
+             *   J(p) <- tmp
+             * end */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < w; j++) {
+                if ((maskval = GET_DATA_BYTE(linem, j)) < 255) {
+                    maxval = GET_DATA_BYTE(lines, j);
+                    if (i > 0) {
+                        val2 = GET_DATA_BYTE(lines - wpls, j);
+                        maxval = L_MAX(maxval, val2);
+                    }
+                    if (j > 0) {
+                        val4 = GET_DATA_BYTE(lines, j - 1);
+                        maxval = L_MAX(maxval, val4);
+                    }
+                    if (maxval > maskval)
+                        SET_DATA_BYTE(lines, j, maxval);
+                }
+            }
+        }
+
+            /* LR --> UL scan (anti-raster order)
+             * If I : mask image
+             *    J : marker image
+             * Let p be the currect pixel;
+             * tmp <- max{J(p) union J(p) neighbors in anti-raster order}
+             * if (tmp > I(p))
+             *   J(p) <- tmp
+             * end */
+        for (i = imax; i >= 0; i--) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = jmax; j >= 0; j--) {
+                boolval = FALSE;
+                if ((maskval = GET_DATA_BYTE(linem, j)) < 255) {
+                    val = maxval = GET_DATA_BYTE(lines, j);
+                    if (i < imax) {
+                        val7 = GET_DATA_BYTE(lines + wpls, j);
+                        maxval = L_MAX(maxval, val7);
+                    }
+                    if (j < jmax) {
+                        val5 = GET_DATA_BYTE(lines, j + 1);
+                        maxval = L_MAX(maxval, val5);
+                    }
+                    if (maxval > maskval)
+                        SET_DATA_BYTE(lines, j, maxval);
+                    val = GET_DATA_BYTE(lines, j);
+
+                        /*
+                         * If there exists a point (q) which belongs to J(p)
+                         * neighbors in anti-raster order such that J(q) < J(p)
+                         * and J(p) > I(q) then
+                         * fifo_add(p) */
+                    if (i < imax) {
+                        val7 = GET_DATA_BYTE(lines + wpls, j);
+                        if ((val7 < val) &&
+                            (val > GET_DATA_BYTE(linem + wplm, j))) {
+                            boolval = TRUE;
+                        }
+                    }
+                    if (j < jmax) {
+                        val5 = GET_DATA_BYTE(lines, j + 1);
+                        if (!boolval && (val5 < val) &&
+                            (val > GET_DATA_BYTE(linem, j + 1))) {
+                            boolval = TRUE;
+                        }
+                    }
+                    if (boolval) {
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i;
+                        pixel->y = j;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+            }
+        }
+
+            /* Propagation step:
+             *        while fifo_empty = false
+             *          p <- fifo_first()
+             *          for every pixel (q) belong to neighbors of (p)
+             *            if J(q) < J(p) and J(p) > I(q)
+             *              J(q) <- min(J(p), I(q));
+             *              fifo_add(q);
+             *            end
+             *          end
+             *        end */
+        queue_size = lqueueGetCount(lq_pixel);
+        while (queue_size) {
+            pixel = (L_PIXEL *)lqueueRemove(lq_pixel);
+            i = pixel->x;
+            j = pixel->y;
+            LEPT_FREE(pixel);
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+
+            if ((val = GET_DATA_BYTE(lines, j)) > 0) {
+                if (i > 0) {
+                    val2 = GET_DATA_BYTE(lines - wpls, j);
+                    maskval = GET_DATA_BYTE(linem - wplm, j);
+                    if (val > val2 && val > maskval) {
+                        SET_DATA_BYTE(lines - wpls, j, val);
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i - 1;
+                        pixel->y = j;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+
+                }
+                if (j > 0) {
+                    val4 = GET_DATA_BYTE(lines, j - 1);
+                    maskval = GET_DATA_BYTE(linem, j - 1);
+                    if (val > val4 && val > maskval) {
+                        SET_DATA_BYTE(lines, j - 1, val);
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i;
+                        pixel->y = j - 1;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+                if (i < imax) {
+                    val7 = GET_DATA_BYTE(lines + wpls, j);
+                    maskval = GET_DATA_BYTE(linem + wplm, j);
+                    if (val > val7 && val > maskval) {
+                        SET_DATA_BYTE(lines + wpls, j, val);
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i + 1;
+                        pixel->y = j;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+                if (j < jmax) {
+                    val5 = GET_DATA_BYTE(lines, j + 1);
+                    maskval = GET_DATA_BYTE(linem, j + 1);
+                    if (val > val5 && val > maskval) {
+                        SET_DATA_BYTE(lines, j + 1, val);
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i;
+                        pixel->y = j + 1;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+            }
+
+            queue_size = lqueueGetCount(lq_pixel);
+        }
+
+        break;
+
+    case 8:
+            /* UL --> LR scan  (Raster Order)
+             * If I : mask image
+             *    J : marker image
+             * Let p be the currect pixel;
+             * tmp <- max{J(p) union J(p) neighbors in raster order}
+             * if (tmp > I(p))
+             *   J(p) <- tmp
+             * end */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < w; j++) {
+                if ((maskval = GET_DATA_BYTE(linem, j)) < 255) {
+                    maxval = GET_DATA_BYTE(lines, j);
+                    if (i > 0) {
+                        if (j > 0) {
+                            val1 = GET_DATA_BYTE(lines - wpls, j - 1);
+                            maxval = L_MAX(maxval, val1);
+                        }
+                        if (j < jmax) {
+                            val3 = GET_DATA_BYTE(lines - wpls, j + 1);
+                            maxval = L_MAX(maxval, val3);
+                        }
+                        val2 = GET_DATA_BYTE(lines - wpls, j);
+                        maxval = L_MAX(maxval, val2);
+                    }
+                    if (j > 0) {
+                        val4 = GET_DATA_BYTE(lines, j - 1);
+                        maxval = L_MAX(maxval, val4);
+                    }
+                    if (maxval > maskval)
+                        SET_DATA_BYTE(lines, j, maxval);
+                }
+            }
+        }
+
+            /* LR --> UL scan (anti-raster order)
+             * If I : mask image
+             *    J : marker image
+             * Let p be the currect pixel;
+             * tmp <- max{J(p) union J(p) neighbors in anti-raster order}
+             * if (tmp > I(p))
+             *   J(p) <- tmp
+             * end */
+        for (i = imax; i >= 0; i--) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = jmax; j >= 0; j--) {
+                boolval = FALSE;
+                if ((maskval = GET_DATA_BYTE(linem, j)) < 255) {
+                    maxval = GET_DATA_BYTE(lines, j);
+                    if (i < imax) {
+                        if (j > 0) {
+                            val6 = GET_DATA_BYTE(lines + wpls, j - 1);
+                            maxval = L_MAX(maxval, val6);
+                        }
+                        if (j < jmax) {
+                            val8 = GET_DATA_BYTE(lines + wpls, j + 1);
+                            maxval = L_MAX(maxval, val8);
+                        }
+                        val7 = GET_DATA_BYTE(lines + wpls, j);
+                        maxval = L_MAX(maxval, val7);
+                    }
+                    if (j < jmax) {
+                        val5 = GET_DATA_BYTE(lines, j + 1);
+                        maxval = L_MAX(maxval, val5);
+                    }
+                    if (maxval > maskval)
+                        SET_DATA_BYTE(lines, j, maxval);
+                    val = GET_DATA_BYTE(lines, j);
+
+                        /*
+                         * If there exists a point (q) which belongs to J(p)
+                         * neighbors in anti-raster order such that J(q) < J(p)
+                         * and J(p) > I(q) then
+                         * fifo_add(p) */
+                    if (i < imax) {
+                        if (j > 0) {
+                            val6 = GET_DATA_BYTE(lines + wpls, j - 1);
+                            if ((val6 < val) &&
+                                (val > GET_DATA_BYTE(linem + wplm, j - 1))) {
+                                boolval = TRUE;
+                            }
+                        }
+                        if (j < jmax) {
+                            val8 = GET_DATA_BYTE(lines + wpls, j + 1);
+                            if (!boolval && (val8 < val) &&
+                                (val > GET_DATA_BYTE(linem + wplm, j + 1))) {
+                                boolval = TRUE;
+                            }
+                        }
+                        val7 = GET_DATA_BYTE(lines + wpls, j);
+                        if (!boolval && (val7 < val) &&
+                            (val > GET_DATA_BYTE(linem + wplm, j))) {
+                            boolval = TRUE;
+                        }
+                    }
+                    if (j < jmax) {
+                        val5 = GET_DATA_BYTE(lines, j + 1);
+                        if (!boolval && (val5 < val) &&
+                            (val > GET_DATA_BYTE(linem, j + 1))) {
+                            boolval = TRUE;
+                        }
+                    }
+                    if (boolval) {
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i;
+                        pixel->y = j;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+            }
+        }
+
+            /* Propagation step:
+             *        while fifo_empty = false
+             *          p <- fifo_first()
+             *          for every pixel (q) belong to neighbors of (p)
+             *            if J(q) < J(p) and J(p) > I(q)
+             *              J(q) <- min(J(p), I(q));
+             *              fifo_add(q);
+             *            end
+             *          end
+             *        end */
+        queue_size = lqueueGetCount(lq_pixel);
+        while (queue_size) {
+            pixel = (L_PIXEL *)lqueueRemove(lq_pixel);
+            i = pixel->x;
+            j = pixel->y;
+            LEPT_FREE(pixel);
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+
+            if ((val = GET_DATA_BYTE(lines, j)) > 0) {
+                if (i > 0) {
+                    if (j > 0) {
+                        val1 = GET_DATA_BYTE(lines - wpls, j - 1);
+                        maskval = GET_DATA_BYTE(linem - wplm, j - 1);
+                        if (val > val1 && val > maskval) {
+                            SET_DATA_BYTE(lines - wpls, j - 1, val);
+                            pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                            pixel->x = i - 1;
+                            pixel->y = j - 1;
+                            lqueueAdd(lq_pixel, pixel);
+                        }
+                    }
+                    if (j < jmax) {
+                        val3 = GET_DATA_BYTE(lines - wpls, j + 1);
+                        maskval = GET_DATA_BYTE(linem - wplm, j + 1);
+                        if (val > val3 && val > maskval) {
+                            SET_DATA_BYTE(lines - wpls, j + 1, val);
+                            pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                            pixel->x = i - 1;
+                            pixel->y = j + 1;
+                            lqueueAdd(lq_pixel, pixel);
+                        }
+                    }
+                    val2 = GET_DATA_BYTE(lines - wpls, j);
+                    maskval = GET_DATA_BYTE(linem - wplm, j);
+                    if (val > val2 && val > maskval) {
+                        SET_DATA_BYTE(lines - wpls, j, val);
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i - 1;
+                        pixel->y = j;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+
+                }
+                if (j > 0) {
+                    val4 = GET_DATA_BYTE(lines, j - 1);
+                    maskval = GET_DATA_BYTE(linem, j - 1);
+                    if (val > val4 && val > maskval) {
+                        SET_DATA_BYTE(lines, j - 1, val);
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i;
+                        pixel->y = j - 1;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+                if (i < imax) {
+                    if (j > 0) {
+                        val6 = GET_DATA_BYTE(lines + wpls, j - 1);
+                        maskval = GET_DATA_BYTE(linem + wplm, j - 1);
+                        if (val > val6 && val > maskval) {
+                            SET_DATA_BYTE(lines + wpls, j - 1, val);
+                            pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                            pixel->x = i + 1;
+                            pixel->y = j - 1;
+                            lqueueAdd(lq_pixel, pixel);
+                        }
+                    }
+                    if (j < jmax) {
+                        val8 = GET_DATA_BYTE(lines + wpls, j + 1);
+                        maskval = GET_DATA_BYTE(linem + wplm, j + 1);
+                        if (val > val8 && val > maskval) {
+                            SET_DATA_BYTE(lines + wpls, j + 1, val);
+                            pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                            pixel->x = i + 1;
+                            pixel->y = j + 1;
+                            lqueueAdd(lq_pixel, pixel);
+                        }
+                    }
+                    val7 = GET_DATA_BYTE(lines + wpls, j);
+                    maskval = GET_DATA_BYTE(linem + wplm, j);
+                    if (val > val7 && val > maskval) {
+                        SET_DATA_BYTE(lines + wpls, j, val);
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i + 1;
+                        pixel->y = j;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+                if (j < jmax) {
+                    val5 = GET_DATA_BYTE(lines, j + 1);
+                    maskval = GET_DATA_BYTE(linem, j + 1);
+                    if (val > val5 && val > maskval) {
+                        SET_DATA_BYTE(lines, j + 1, val);
+                        pixel = (L_PIXEL *)LEPT_CALLOC(1, sizeof(L_PIXEL));
+                        pixel->x = i;
+                        pixel->y = j + 1;
+                        lqueueAdd(lq_pixel, pixel);
+                    }
+                }
+            }
+
+            queue_size = lqueueGetCount(lq_pixel);
+        }
+        break;
+
+    default:
+        L_ERROR("shouldn't get here!\n", procName);
+        break;
+    }
+
+    lqueueDestroy(&lq_pixel, TRUE);
+    return;
+}
+
+/*-----------------------------------------------------------------------*
+ *                 Vincent's Iterative Grayscale Seedfill                *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  seedfillGrayLowSimple()
+ *
+ *  Notes:
+ *      (1) The pixels are numbered as follows:
+ *              1  2  3
+ *              4  x  5
+ *              6  7  8
+ *          This low-level filling operation consists of two scans,
+ *          raster and anti-raster, covering the entire seed image.
+ *          The caller typically iterates until the filling is
+ *          complete.
+ *      (2) The filling action can be visualized from the following example.
+ *          Suppose the mask, which clips the fill, is a sombrero-shaped
+ *          surface, where the highest point is 200 and the low pixels
+ *          around the rim are 30.  Beyond the rim, the mask goes up a bit.
+ *          Suppose the seed, which is filled, consists of a single point
+ *          of height 150, located below the max of the mask, with
+ *          the rest 0.  Then in the raster scan, nothing happens until
+ *          the high seed point is encountered, and then this value is
+ *          propagated right and down, until it hits the side of the
+ *          sombrero.   The seed can never exceed the mask, so it fills
+ *          to the rim, going lower along the mask surface.  When it
+ *          passes the rim, the seed continues to fill at the rim
+ *          height to the edge of the seed image.  Then on the
+ *          anti-raster scan, the seed fills flat inside the
+ *          sombrero to the upper and left, and then out from the
+ *          rim as before.  The final result has a seed that is
+ *          flat outside the rim, and inside it fills the sombrero
+ *          but only up to 150.  If the rim height varies, the
+ *          filled seed outside the rim will be at the highest
+ *          point on the rim, which is a saddle point on the rim.
+ */
+void
+seedfillGrayLowSimple(l_uint32  *datas,
+                      l_int32    w,
+                      l_int32    h,
+                      l_int32    wpls,
+                      l_uint32  *datam,
+                      l_int32    wplm,
+                      l_int32    connectivity)
+{
+l_uint8    val2, val3, val4, val5, val7, val8;
+l_uint8    val, maxval, maskval;
+l_int32    i, j, imax, jmax;
+l_uint32  *lines, *linem;
+
+    PROCNAME("seedfillGrayLowSimple");
+
+    imax = h - 1;
+    jmax = w - 1;
+
+    switch (connectivity)
+    {
+    case 4:
+            /* UL --> LR scan */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < w; j++) {
+                if ((maskval = GET_DATA_BYTE(linem, j)) > 0) {
+                    maxval = 0;
+                    if (i > 0)
+                        maxval = GET_DATA_BYTE(lines - wpls, j);
+                    if (j > 0) {
+                        val4 = GET_DATA_BYTE(lines, j - 1);
+                        maxval = L_MAX(maxval, val4);
+                    }
+                    val = GET_DATA_BYTE(lines, j);
+                    maxval = L_MAX(maxval, val);
+                    val = L_MIN(maxval, maskval);
+                    SET_DATA_BYTE(lines, j, val);
+                }
+            }
+        }
+
+            /* LR --> UL scan */
+        for (i = imax; i >= 0; i--) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = jmax; j >= 0; j--) {
+                if ((maskval = GET_DATA_BYTE(linem, j)) > 0) {
+                    maxval = 0;
+                    if (i < imax)
+                        maxval = GET_DATA_BYTE(lines + wpls, j);
+                    if (j < jmax) {
+                        val5 = GET_DATA_BYTE(lines, j + 1);
+                        maxval = L_MAX(maxval, val5);
+                    }
+                    val = GET_DATA_BYTE(lines, j);
+                    maxval = L_MAX(maxval, val);
+                    val = L_MIN(maxval, maskval);
+                    SET_DATA_BYTE(lines, j, val);
+                }
+            }
+        }
+        break;
+
+    case 8:
+            /* UL --> LR scan */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < w; j++) {
+                if ((maskval = GET_DATA_BYTE(linem, j)) > 0) {
+                    maxval = 0;
+                    if (i > 0) {
+                        if (j > 0)
+                            maxval = GET_DATA_BYTE(lines - wpls, j - 1);
+                        if (j < jmax) {
+                            val2 = GET_DATA_BYTE(lines - wpls, j + 1);
+                            maxval = L_MAX(maxval, val2);
+                        }
+                        val3 = GET_DATA_BYTE(lines - wpls, j);
+                        maxval = L_MAX(maxval, val3);
+                    }
+                    if (j > 0) {
+                        val4 = GET_DATA_BYTE(lines, j - 1);
+                        maxval = L_MAX(maxval, val4);
+                    }
+                    val = GET_DATA_BYTE(lines, j);
+                    maxval = L_MAX(maxval, val);
+                    val = L_MIN(maxval, maskval);
+                    SET_DATA_BYTE(lines, j, val);
+                }
+            }
+        }
+
+            /* LR --> UL scan */
+        for (i = imax; i >= 0; i--) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = jmax; j >= 0; j--) {
+                if ((maskval = GET_DATA_BYTE(linem, j)) > 0) {
+                    maxval = 0;
+                    if (i < imax) {
+                        if (j > 0)
+                            maxval = GET_DATA_BYTE(lines + wpls, j - 1);
+                        if (j < jmax) {
+                            val8 = GET_DATA_BYTE(lines + wpls, j + 1);
+                            maxval = L_MAX(maxval, val8);
+                        }
+                        val7 = GET_DATA_BYTE(lines + wpls, j);
+                        maxval = L_MAX(maxval, val7);
+                    }
+                    if (j < jmax) {
+                        val5 = GET_DATA_BYTE(lines, j + 1);
+                        maxval = L_MAX(maxval, val5);
+                    }
+                    val = GET_DATA_BYTE(lines, j);
+                    maxval = L_MAX(maxval, val);
+                    val = L_MIN(maxval, maskval);
+                    SET_DATA_BYTE(lines, j, val);
+                }
+            }
+        }
+        break;
+
+    default:
+        L_ERROR("connectivity must be 4 or 8\n", procName);
+    }
+
+    return;
+}
+
+
+/*!
+ *  seedfillGrayInvLowSimple()
+ *
+ *  Notes:
+ *      (1) The pixels are numbered as follows:
+ *              1  2  3
+ *              4  x  5
+ *              6  7  8
+ *          This low-level filling operation consists of two scans,
+ *          raster and anti-raster, covering the entire seed image.
+ *          The caller typically iterates until the filling is
+ *          complete.
+ *      (2) The "Inv" signifies the fact that in this case, filling
+ *          of the seed only takes place when the seed value is
+ *          greater than the mask value.  The mask will act to stop
+ *          the fill when it is higher than the seed level.  (This is
+ *          in contrast to conventional grayscale filling where the
+ *          seed always fills below the mask.)
+ *      (3) An example of use is a basin, described by the mask (pixm),
+ *          where within the basin, the seed pix (pixs) gets filled to the
+ *          height of the highest seed pixel that is above its
+ *          corresponding max pixel.  Filling occurs while the
+ *          propagating seed pixels in pixs are larger than the
+ *          corresponding mask values in pixm.
+ */
+void
+seedfillGrayInvLowSimple(l_uint32  *datas,
+                         l_int32    w,
+                         l_int32    h,
+                         l_int32    wpls,
+                         l_uint32  *datam,
+                         l_int32    wplm,
+                         l_int32    connectivity)
+{
+l_uint8    val1, val2, val3, val4, val5, val6, val7, val8;
+l_uint8    maxval, maskval;
+l_int32    i, j, imax, jmax;
+l_uint32  *lines, *linem;
+
+    PROCNAME("seedfillGrayInvLowSimple");
+
+    imax = h - 1;
+    jmax = w - 1;
+
+    switch (connectivity)
+    {
+    case 4:
+            /* UL --> LR scan */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < w; j++) {
+                if ((maskval = GET_DATA_BYTE(linem, j)) < 255) {
+                    maxval = GET_DATA_BYTE(lines, j);
+                    if (i > 0) {
+                        val2 = GET_DATA_BYTE(lines - wpls, j);
+                        maxval = L_MAX(maxval, val2);
+                    }
+                    if (j > 0) {
+                        val4 = GET_DATA_BYTE(lines, j - 1);
+                        maxval = L_MAX(maxval, val4);
+                    }
+                    if (maxval > maskval)
+                        SET_DATA_BYTE(lines, j, maxval);
+                }
+            }
+        }
+
+            /* LR --> UL scan */
+        for (i = imax; i >= 0; i--) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = jmax; j >= 0; j--) {
+                if ((maskval = GET_DATA_BYTE(linem, j)) < 255) {
+                    maxval = GET_DATA_BYTE(lines, j);
+                    if (i < imax) {
+                        val7 = GET_DATA_BYTE(lines + wpls, j);
+                        maxval = L_MAX(maxval, val7);
+                    }
+                    if (j < jmax) {
+                        val5 = GET_DATA_BYTE(lines, j + 1);
+                        maxval = L_MAX(maxval, val5);
+                    }
+                    if (maxval > maskval)
+                        SET_DATA_BYTE(lines, j, maxval);
+                }
+            }
+        }
+        break;
+
+    case 8:
+            /* UL --> LR scan */
+        for (i = 0; i < h; i++) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = 0; j < w; j++) {
+                if ((maskval = GET_DATA_BYTE(linem, j)) < 255) {
+                    maxval = GET_DATA_BYTE(lines, j);
+                    if (i > 0) {
+                        if (j > 0) {
+                            val1 = GET_DATA_BYTE(lines - wpls, j - 1);
+                            maxval = L_MAX(maxval, val1);
+                        }
+                        if (j < jmax) {
+                            val2 = GET_DATA_BYTE(lines - wpls, j + 1);
+                            maxval = L_MAX(maxval, val2);
+                        }
+                        val3 = GET_DATA_BYTE(lines - wpls, j);
+                        maxval = L_MAX(maxval, val3);
+                    }
+                    if (j > 0) {
+                        val4 = GET_DATA_BYTE(lines, j - 1);
+                        maxval = L_MAX(maxval, val4);
+                    }
+                    if (maxval > maskval)
+                        SET_DATA_BYTE(lines, j, maxval);
+                }
+            }
+        }
+
+            /* LR --> UL scan */
+        for (i = imax; i >= 0; i--) {
+            lines = datas + i * wpls;
+            linem = datam + i * wplm;
+            for (j = jmax; j >= 0; j--) {
+                if ((maskval = GET_DATA_BYTE(linem, j)) < 255) {
+                    maxval = GET_DATA_BYTE(lines, j);
+                    if (i < imax) {
+                        if (j > 0) {
+                            val6 = GET_DATA_BYTE(lines + wpls, j - 1);
+                            maxval = L_MAX(maxval, val6);
+                        }
+                        if (j < jmax) {
+                            val8 = GET_DATA_BYTE(lines + wpls, j + 1);
+                            maxval = L_MAX(maxval, val8);
+                        }
+                        val7 = GET_DATA_BYTE(lines + wpls, j);
+                        maxval = L_MAX(maxval, val7);
+                    }
+                    if (j < jmax) {
+                        val5 = GET_DATA_BYTE(lines, j + 1);
+                        maxval = L_MAX(maxval, val5);
+                    }
+                    if (maxval > maskval)
+                        SET_DATA_BYTE(lines, j, maxval);
+                }
+            }
+        }
+        break;
+
+    default:
+        L_ERROR("connectivity must be 4 or 8\n", procName);
+    }
+
+    return;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                   Vincent's Distance Function method                  *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  distanceFunctionLow()
+ */
+void
+distanceFunctionLow(l_uint32  *datad,
+                    l_int32    w,
+                    l_int32    h,
+                    l_int32    d,
+                    l_int32    wpld,
+                    l_int32    connectivity)
+{
+l_int32    val1, val2, val3, val4, val5, val6, val7, val8, minval, val;
+l_int32    i, j, imax, jmax;
+l_uint32  *lined;
+
+    PROCNAME("distanceFunctionLow");
+
+        /* One raster scan followed by one anti-raster scan.
+         * This does not re-set the 1-boundary of pixels that
+         * were initialized to either 0 or maxval. */
+    imax = h - 1;
+    jmax = w - 1;
+    switch (connectivity)
+    {
+    case 4:
+        if (d == 8) {
+                /* UL --> LR scan */
+            for (i = 1; i < imax; i++) {
+                lined = datad + i * wpld;
+                for (j = 1; j < jmax; j++) {
+                    if ((val = GET_DATA_BYTE(lined, j)) > 0) {
+                        val2 = GET_DATA_BYTE(lined - wpld, j);
+                        val4 = GET_DATA_BYTE(lined, j - 1);
+                        minval = L_MIN(val2, val4);
+                        minval = L_MIN(minval, 254);
+                        SET_DATA_BYTE(lined, j, minval + 1);
+                    }
+                }
+            }
+
+                /* LR --> UL scan */
+            for (i = imax - 1; i > 0; i--) {
+                lined = datad + i * wpld;
+                for (j = jmax - 1; j > 0; j--) {
+                    if ((val = GET_DATA_BYTE(lined, j)) > 0) {
+                        val7 = GET_DATA_BYTE(lined + wpld, j);
+                        val5 = GET_DATA_BYTE(lined, j + 1);
+                        minval = L_MIN(val5, val7);
+                        minval = L_MIN(minval + 1, val);
+                        SET_DATA_BYTE(lined, j, minval);
+                    }
+                }
+            }
+        } else {  /* d == 16 */
+                /* UL --> LR scan */
+            for (i = 1; i < imax; i++) {
+                lined = datad + i * wpld;
+                for (j = 1; j < jmax; j++) {
+                    if ((val = GET_DATA_TWO_BYTES(lined, j)) > 0) {
+                        val2 = GET_DATA_TWO_BYTES(lined - wpld, j);
+                        val4 = GET_DATA_TWO_BYTES(lined, j - 1);
+                        minval = L_MIN(val2, val4);
+                        minval = L_MIN(minval, 0xfffe);
+                        SET_DATA_TWO_BYTES(lined, j, minval + 1);
+                    }
+                }
+            }
+
+                /* LR --> UL scan */
+            for (i = imax - 1; i > 0; i--) {
+                lined = datad + i * wpld;
+                for (j = jmax - 1; j > 0; j--) {
+                    if ((val = GET_DATA_TWO_BYTES(lined, j)) > 0) {
+                        val7 = GET_DATA_TWO_BYTES(lined + wpld, j);
+                        val5 = GET_DATA_TWO_BYTES(lined, j + 1);
+                        minval = L_MIN(val5, val7);
+                        minval = L_MIN(minval + 1, val);
+                        SET_DATA_TWO_BYTES(lined, j, minval);
+                    }
+                }
+            }
+        }
+        break;
+
+    case 8:
+        if (d == 8) {
+                /* UL --> LR scan */
+            for (i = 1; i < imax; i++) {
+                lined = datad + i * wpld;
+                for (j = 1; j < jmax; j++) {
+                    if ((val = GET_DATA_BYTE(lined, j)) > 0) {
+                        val1 = GET_DATA_BYTE(lined - wpld, j - 1);
+                        val2 = GET_DATA_BYTE(lined - wpld, j);
+                        val3 = GET_DATA_BYTE(lined - wpld, j + 1);
+                        val4 = GET_DATA_BYTE(lined, j - 1);
+                        minval = L_MIN(val1, val2);
+                        minval = L_MIN(minval, val3);
+                        minval = L_MIN(minval, val4);
+                        minval = L_MIN(minval, 254);
+                        SET_DATA_BYTE(lined, j, minval + 1);
+                    }
+                }
+            }
+
+                /* LR --> UL scan */
+            for (i = imax - 1; i > 0; i--) {
+                lined = datad + i * wpld;
+                for (j = jmax - 1; j > 0; j--) {
+                    if ((val = GET_DATA_BYTE(lined, j)) > 0) {
+                        val8 = GET_DATA_BYTE(lined + wpld, j + 1);
+                        val7 = GET_DATA_BYTE(lined + wpld, j);
+                        val6 = GET_DATA_BYTE(lined + wpld, j - 1);
+                        val5 = GET_DATA_BYTE(lined, j + 1);
+                        minval = L_MIN(val8, val7);
+                        minval = L_MIN(minval, val6);
+                        minval = L_MIN(minval, val5);
+                        minval = L_MIN(minval + 1, val);
+                        SET_DATA_BYTE(lined, j, minval);
+                    }
+                }
+            }
+        } else {  /* d == 16 */
+                /* UL --> LR scan */
+            for (i = 1; i < imax; i++) {
+                lined = datad + i * wpld;
+                for (j = 1; j < jmax; j++) {
+                    if ((val = GET_DATA_TWO_BYTES(lined, j)) > 0) {
+                        val1 = GET_DATA_TWO_BYTES(lined - wpld, j - 1);
+                        val2 = GET_DATA_TWO_BYTES(lined - wpld, j);
+                        val3 = GET_DATA_TWO_BYTES(lined - wpld, j + 1);
+                        val4 = GET_DATA_TWO_BYTES(lined, j - 1);
+                        minval = L_MIN(val1, val2);
+                        minval = L_MIN(minval, val3);
+                        minval = L_MIN(minval, val4);
+                        minval = L_MIN(minval, 0xfffe);
+                        SET_DATA_TWO_BYTES(lined, j, minval + 1);
+                    }
+                }
+            }
+
+                /* LR --> UL scan */
+            for (i = imax - 1; i > 0; i--) {
+                lined = datad + i * wpld;
+                for (j = jmax - 1; j > 0; j--) {
+                    if ((val = GET_DATA_TWO_BYTES(lined, j)) > 0) {
+                        val8 = GET_DATA_TWO_BYTES(lined + wpld, j + 1);
+                        val7 = GET_DATA_TWO_BYTES(lined + wpld, j);
+                        val6 = GET_DATA_TWO_BYTES(lined + wpld, j - 1);
+                        val5 = GET_DATA_TWO_BYTES(lined, j + 1);
+                        minval = L_MIN(val8, val7);
+                        minval = L_MIN(minval, val6);
+                        minval = L_MIN(minval, val5);
+                        minval = L_MIN(minval + 1, val);
+                        SET_DATA_TWO_BYTES(lined, j, minval);
+                    }
+                }
+            }
+        }
+        break;
+
+    default:
+        L_ERROR("connectivity must be 4 or 8\n", procName);
+        break;
+    }
+
+    return;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                 Seed spread (based on distance function)              *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  seedspreadLow()
+ *
+ *    See pixSeedspread() for a brief description of the algorithm here.
+ */
+void
+seedspreadLow(l_uint32  *datad,
+              l_int32    w,
+              l_int32    h,
+              l_int32    wpld,
+              l_uint32  *datat,
+              l_int32    wplt,
+              l_int32    connectivity)
+{
+l_int32    val1t, val2t, val3t, val4t, val5t, val6t, val7t, val8t;
+l_int32    i, j, imax, jmax, minval, valt, vald;
+l_uint32  *linet, *lined;
+
+    PROCNAME("seedspreadLow");
+
+        /* One raster scan followed by one anti-raster scan.
+         * pixt is initialized to have 0 on pixels where the
+         * input is specified in pixd, and to have 1 on all
+         * other pixels.  We only change pixels in pixt and pixd
+         * that are non-zero in pixt. */
+    imax = h - 1;
+    jmax = w - 1;
+    switch (connectivity)
+    {
+    case 4:
+            /* UL --> LR scan */
+        for (i = 1; i < h; i++) {
+            linet = datat + i * wplt;
+            lined = datad + i * wpld;
+            for (j = 1; j < jmax; j++) {
+                if ((valt = GET_DATA_TWO_BYTES(linet, j)) > 0) {
+                    val2t = GET_DATA_TWO_BYTES(linet - wplt, j);
+                    val4t = GET_DATA_TWO_BYTES(linet, j - 1);
+                    minval = L_MIN(val2t, val4t);
+                    minval = L_MIN(minval, 0xfffe);
+                    SET_DATA_TWO_BYTES(linet, j, minval + 1);
+                    if (val2t < val4t)
+                        vald = GET_DATA_BYTE(lined - wpld, j);
+                    else
+                        vald = GET_DATA_BYTE(lined, j - 1);
+                    SET_DATA_BYTE(lined, j, vald);
+                }
+            }
+        }
+
+            /* LR --> UL scan */
+        for (i = imax - 1; i > 0; i--) {
+            linet = datat + i * wplt;
+            lined = datad + i * wpld;
+            for (j = jmax - 1; j > 0; j--) {
+                if ((valt = GET_DATA_TWO_BYTES(linet, j)) > 0) {
+                    val7t = GET_DATA_TWO_BYTES(linet + wplt, j);
+                    val5t = GET_DATA_TWO_BYTES(linet, j + 1);
+                    minval = L_MIN(val5t, val7t);
+                    minval = L_MIN(minval + 1, valt);
+                    if (valt > minval) {  /* replace */
+                        SET_DATA_TWO_BYTES(linet, j, minval);
+                        if (val5t < val7t)
+                            vald = GET_DATA_BYTE(lined, j + 1);
+                        else
+                            vald = GET_DATA_BYTE(lined + wplt, j);
+                        SET_DATA_BYTE(lined, j, vald);
+                    }
+                }
+            }
+        }
+        break;
+    case 8:
+            /* UL --> LR scan */
+        for (i = 1; i < h; i++) {
+            linet = datat + i * wplt;
+            lined = datad + i * wpld;
+            for (j = 1; j < jmax; j++) {
+                if ((valt = GET_DATA_TWO_BYTES(linet, j)) > 0) {
+                    val1t = GET_DATA_TWO_BYTES(linet - wplt, j - 1);
+                    val2t = GET_DATA_TWO_BYTES(linet - wplt, j);
+                    val3t = GET_DATA_TWO_BYTES(linet - wplt, j + 1);
+                    val4t = GET_DATA_TWO_BYTES(linet, j - 1);
+                    minval = L_MIN(val1t, val2t);
+                    minval = L_MIN(minval, val3t);
+                    minval = L_MIN(minval, val4t);
+                    minval = L_MIN(minval, 0xfffe);
+                    SET_DATA_TWO_BYTES(linet, j, minval + 1);
+                    if (minval == val1t)
+                        vald = GET_DATA_BYTE(lined - wpld, j - 1);
+                    else if (minval == val2t)
+                        vald = GET_DATA_BYTE(lined - wpld, j);
+                    else if (minval == val3t)
+                        vald = GET_DATA_BYTE(lined - wpld, j + 1);
+                    else  /* minval == val4t */
+                        vald = GET_DATA_BYTE(lined, j - 1);
+                    SET_DATA_BYTE(lined, j, vald);
+                }
+            }
+        }
+
+            /* LR --> UL scan */
+        for (i = imax - 1; i > 0; i--) {
+            linet = datat + i * wplt;
+            lined = datad + i * wpld;
+            for (j = jmax - 1; j > 0; j--) {
+                if ((valt = GET_DATA_TWO_BYTES(linet, j)) > 0) {
+                    val8t = GET_DATA_TWO_BYTES(linet + wplt, j + 1);
+                    val7t = GET_DATA_TWO_BYTES(linet + wplt, j);
+                    val6t = GET_DATA_TWO_BYTES(linet + wplt, j - 1);
+                    val5t = GET_DATA_TWO_BYTES(linet, j + 1);
+                    minval = L_MIN(val8t, val7t);
+                    minval = L_MIN(minval, val6t);
+                    minval = L_MIN(minval, val5t);
+                    minval = L_MIN(minval + 1, valt);
+                    if (valt > minval) {  /* replace */
+                        SET_DATA_TWO_BYTES(linet, j, minval);
+                        if (minval == val5t + 1)
+                            vald = GET_DATA_BYTE(lined, j + 1);
+                        else if (minval == val6t + 1)
+                            vald = GET_DATA_BYTE(lined + wpld, j - 1);
+                        else if (minval == val7t + 1)
+                            vald = GET_DATA_BYTE(lined + wpld, j);
+                        else  /* minval == val8t + 1 */
+                            vald = GET_DATA_BYTE(lined + wpld, j + 1);
+                        SET_DATA_BYTE(lined, j, vald);
+                    }
+                }
+            }
+        }
+        break;
+    default:
+        L_ERROR("connectivity must be 4 or 8\n", procName);
+        break;
+    }
+
+    return;
+}
diff --git a/src/sel1.c b/src/sel1.c
new file mode 100644 (file)
index 0000000..94e8570
--- /dev/null
@@ -0,0 +1,2282 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  sel1.c
+ *
+ *      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()
+ *
+ *         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)
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  L_BUF_SIZE = 256;
+static const l_int32  INITIAL_PTR_ARRAYSIZE = 50;  /* n'import quoi */
+static const l_int32  MANY_SELS = 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                           *
+ *------------------------------------------------------------------------*/
+/*!
+ *  selaCreate()
+ *
+ *      Input:  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 = INITIAL_PTR_ARRAYSIZE;
+    if (n > MANY_SELS)
+        L_WARNING("%d sels\n", procName, n);
+
+    if ((sela = (SELA *)LEPT_CALLOC(1, sizeof(SELA))) == NULL)
+        return (SELA *)ERROR_PTR("sela not made", procName, NULL);
+
+    sela->nalloc = n;
+    sela->n = 0;
+
+        /* make array of se ptrs */
+    if ((sela->sel = (SEL **)LEPT_CALLOC(n, sizeof(SEL *))) == NULL)
+        return (SELA *)ERROR_PTR("sel ptrs not made", procName, NULL);
+
+    return sela;
+}
+
+
+/*!
+ *  selaDestroy()
+ *
+ *      Input:  &sela (<to be nulled>)
+ *      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;
+    return;
+}
+
+
+/*!
+ *  selCreate()
+ *
+ *      Input:  height, width
+ *              name (<optional> sel name; can be null)
+ *      Return: sel, or null on error
+ *
+ *  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.
+ */
+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)
+        return (SEL *)ERROR_PTR("data not allocated", procName, NULL);
+
+    return sel;
+}
+
+
+/*!
+ *  selDestroy()
+ *
+ *      Input:  &sel (<to be nulled>)
+ *      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;
+    return;
+}
+
+
+/*!
+ *  selCopy()
+ *
+ *      Input:  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)
+        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;
+}
+
+
+/*!
+ *  selCreateBrick()
+ *
+ *      Input:  height, width
+ *              cy, cx  (origin, relative to UL corner at 0,0)
+ *              type  (SEL_HIT, SEL_MISS, or SEL_DONT_CARE)
+ *      Return: sel, or null on error
+ *
+ *  Notes:
+ *      (1) This is a rectangular sel of all hits, misses or don't cares.
+ */
+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;
+}
+
+
+/*!
+ *  selCreateComb()
+ *
+ *      Input:  factor1 (contiguous space between comb tines)
+ *              factor2 (number of comb tines)
+ *              direction (L_HORIZ, L_VERT)
+ *      Return: sel, or null on error
+ *
+ *  Notes:
+ *      (1) This generates a comb Sel of hits with the origin as
+ *          near the center as possible.
+ */
+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) {
+        sel = selCreate(1, size, NULL);
+        selSetOrigin(sel, 0, size / 2);
+    } else {
+        sel = selCreate(size, 1, NULL);
+        selSetOrigin(sel, size / 2, 0);
+    }
+
+    for (i = 0; i < factor2; i++) {
+        if (factor2 & 1)  /* odd */
+            z = factor1 / 2 + i * factor1;
+        else
+            z = factor1 / 2 + i * factor1;
+/*        fprintf(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;
+}
+
+
+/*!
+ *  create2dIntArray()
+ *
+ *      Input:  sy (rows == height)
+ *              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)
+ *
+ *  Notes:
+ *      (1) The array[sy][sx] is indexed in standard "matrix notation",
+ *          with the row index first.
+ */
+l_int32 **
+create2dIntArray(l_int32  sy,
+                 l_int32  sx)
+{
+l_int32    i;
+l_int32  **array;
+
+    PROCNAME("create2dIntArray");
+
+    if ((array = (l_int32 **)LEPT_CALLOC(sy, sizeof(l_int32 *))) == NULL)
+        return (l_int32 **)ERROR_PTR("ptr array not made", procName, NULL);
+
+    for (i = 0; i < sy; i++) {
+        if ((array[i] = (l_int32 *)LEPT_CALLOC(sx, sizeof(l_int32))) == NULL)
+            return (l_int32 **)ERROR_PTR("array not made", procName, NULL);
+    }
+
+    return array;
+}
+
+
+
+/*------------------------------------------------------------------------*
+ *                           Extension of sela                            *
+ *------------------------------------------------------------------------*/
+/*!
+ *  selaAddSel()
+ *
+ *      Input:  sela
+ *              sel to be added
+ *              selname (ignored if already defined in sel;
+ *                       req'd in sel when added to a sela)
+ *              copyflag (for sel: 0 inserts, 1 copies)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  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.
+ */
+l_int32
+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 == TRUE) {
+        if ((csel = selCopy(sel)) == NULL)
+            return ERROR_INT("csel not made", procName, 1);
+    } else {  /* copyflag is false; insert directly */
+        csel = sel;
+    }
+    if (!csel->name)
+        csel->name = stringNew(selname);
+
+    n = selaGetCount(sela);
+    if (n >= sela->nalloc)
+        selaExtendArray(sela);
+    sela->sel[n] = csel;
+    sela->n++;
+
+    return 0;
+}
+
+
+/*!
+ *  selaExtendArray()
+ *
+ *      Input:  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                              *
+ *----------------------------------------------------------------------*/
+/*!
+ *  selaGetCount()
+ *
+ *      Input:  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;
+}
+
+
+/*!
+ *  selaGetSel()
+ *
+ *      Input:  sela
+ *              index of sel to be retrieved (not copied)
+ *      Return: sel, or null on error
+ *
+ *  Notes:
+ *      (1) This returns a ptr to the sel, not a copy, so the caller
+ *          must not destroy it!
+ */
+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];
+}
+
+
+/*!
+ *  selGetName()
+ *
+ *      Input:  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;
+}
+
+
+/*!
+ *  selSetName()
+ *
+ *      Input:  sel
+ *              name (<optional>; can be null)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Always frees the existing sel name, if defined.
+ *      (2) If name is not defined, just clears any existing sel name.
+ */
+l_int32
+selSetName(SEL         *sel,
+           const char  *name)
+{
+    PROCNAME("selSetName");
+
+    if (!sel)
+        return ERROR_INT("sel not defined", procName, 1);
+
+    return stringReplace(&sel->name, name);
+}
+
+
+/*!
+ *  selaFindSelByName()
+ *
+ *      Input:  sela
+ *              sel name
+ *              &index (<optional, return>)
+ *              &sel  (<optional, return> sel (not a copy))
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+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;
+}
+
+
+/*!
+ *  selGetElement()
+ *
+ *      Input:  sel
+ *              row
+ *              col
+ *              &type  (<return> SEL_HIT, SEL_MISS, SEL_DONT_CARE)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+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;
+}
+
+
+/*!
+ *  selSetElement()
+ *
+ *      Input:  sel
+ *              row
+ *              col
+ *              type  (SEL_HIT, SEL_MISS, SEL_DONT_CARE)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  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.
+ */
+l_int32
+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;
+}
+
+
+/*!
+ *  selGetParameters()
+ *
+ *      Input:  sel
+ *              &sy, &sx, &cy, &cx (<optional return>; each can be null)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+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;
+}
+
+
+/*!
+ *  selSetOrigin()
+ *
+ *      Input:  sel
+ *              cy, cx
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+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;
+}
+
+
+/*!
+ *  selGetTypeAtOrigin()
+ *
+ *      Input:  sel
+ *              &type  (<return> SEL_HIT, SEL_MISS, SEL_DONT_CARE)
+ *      Return: 0 if OK; 1 on error or if origin is not found
+ */
+l_int32
+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);
+}
+
+
+/*!
+ *  selaGetBrickName()
+ *
+ *      Input:  sela
+ *              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);
+}
+
+
+/*!
+ *  selaGetCombName()
+ *
+ *      Input:  sela
+ *              size (the product of sizes of the brick and comb parts)
+ *              direction (L_HORIZ, L_VERT)
+ *      Return: sel name (new string), or null if name not found or on error
+ *
+ *  Notes:
+ *      (1) Combs are by definition 1-dimensional, either horiz or vert.
+ *      (2) Use this with comb Sels; e.g., from selaAddDwaCombs().
+ */
+char *
+selaGetCombName(SELA    *sela,
+                l_int32  size,
+                l_int32  direction)
+{
+char    *selname;
+char     combname[L_BUF_SIZE];
+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, L_BUF_SIZE, "sel_comb_%dh", size);
+    else  /* direction == L_VERT */
+        snprintf(combname, L_BUF_SIZE, "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);
+
+/*!
+ *  selaComputeCompParameters()
+ *
+ *      Input:  output filename
+ *      Return: void
+ *
+ *  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.
+ */
+static void
+selaComputeCompositeParameters(const char  *fileout)
+{
+char    *str, *nameh1, *nameh2, *namev1, *namev2;
+char     buf[L_BUF_SIZE];
+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, L_BUF_SIZE,
+                 "      { %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);
+    return;
+}
+#endif
+/* -------------------------------------------------------------------- */
+
+
+/*!
+ *  getCompositeParameters()
+ *
+ *      Input:  size
+ *              &size1 (<optional return> brick factor size)
+ *              &size2 (<optional return> comb factor size)
+ *              &nameh1 (<optional return> name of horiz brick)
+ *              &nameh2 (<optional return> name of horiz comb)
+ *              &namev1 (<optional return> name of vert brick)
+ *              &namev2 (<optional return> name of vert comb)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This uses the big lookup table at the top of this file.
+ *      (2) All returned strings are copies that must be freed.
+ */
+l_int32
+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;
+}
+
+
+/*!
+ *  selaGetSelnames()
+ *
+ *      Input:  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                  *
+ *----------------------------------------------------------------------*/
+/*!
+ *  selFindMaxTranslations()
+ *
+ *      Input:  sel
+ *              &xp, &yp, &xn, &yn  (<return> max shifts)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Note: 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.
+ */
+l_int32
+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                *
+ *----------------------------------------------------------------------*/
+/*!
+ *  selRotateOrth()
+ *
+ *      Input:  sel
+ *              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                    *
+ *----------------------------------------------------------------------*/
+/*!
+ *  selaRead()
+ *
+ *      Input:  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)
+        return (SELA *)ERROR_PTR("sela not returned", procName, NULL);
+    fclose(fp);
+
+    return sela;
+}
+
+
+/*!
+ *  selaReadStream()
+ *
+ *      Input:  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)
+            return (SELA *)ERROR_PTR("sel not made", procName, NULL);
+        selaAddSel(sela, sel, NULL, 0);
+    }
+
+    return sela;
+}
+
+
+/*!
+ *  selRead()
+ *
+ *      Input:  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)
+        return (SEL *)ERROR_PTR("sela not returned", procName, NULL);
+    fclose(fp);
+
+    return sel;
+}
+
+
+/*!
+ *  selReadStream()
+ *
+ *      Input:  stream
+ *      Return: sel, or null on error
+ */
+SEL  *
+selReadStream(FILE  *fp)
+{
+char    *selname;
+char     linebuf[L_BUF_SIZE];
+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, L_BUF_SIZE, fp) == NULL)
+        return (SEL *)ERROR_PTR("error reading into linebuf", procName, NULL);
+    selname = stringNew(linebuf);
+    sscanf(linebuf, "  ------  %s  ------", 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");
+
+    LEPT_FREE(selname);
+    return sel;
+}
+
+
+/*!
+ *  selaWrite()
+ *
+ *      Input:  filename
+ *              sela
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+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;
+}
+
+
+/*!
+ *  selaWriteStream()
+ *
+ *      Input:  stream
+ *              sela
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+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;
+}
+
+
+/*!
+ *  selWrite()
+ *
+ *      Input:  filename
+ *              sel
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+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;
+}
+
+
+/*!
+ *  selWriteStream()
+ *
+ *      Input:  stream
+ *              sel
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+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        *
+ *----------------------------------------------------------------------*/
+/*!
+ *  selCreateFromString()
+ *
+ *      Input:  text
+ *              height, width
+ *              name (<optional> sel name; can be null)
+ *      Return: sel of the given size, or null on error
+ *
+ *  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) Use an upper case char to indicate the origin of the Sel.
+ *          When the origin falls on a don't-care, use 'C' as the uppecase
+ *          for ' '.
+ *      (3) The text can be input in a format that shows the 2D layout; e.g.,
+ *              static const char *seltext = "x    "
+ *                                           "x Oo "
+ *                                           "x    "
+ *                                           "xxxxx";
+ */
+SEL *
+selCreateFromString(const char  *text,
+                    l_int32      h,
+                    l_int32      w,
+                    const char  *name)
+{
+SEL     *sel;
+l_int32  y, x;
+char     ch;
+
+    PROCNAME("selCreateFromString");
+
+    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);
+
+    sel = selCreate(h, w, name);
+
+    for (y = 0; y < h; ++y) {
+        for (x = 0; x < w; ++x) {
+            ch = *(text++);
+            switch (ch)
+            {
+                case 'X':
+                    selSetOrigin(sel, y, x);
+                case 'x':
+                    selSetElement(sel, y, x, SEL_HIT);
+                    break;
+
+                case 'O':
+                    selSetOrigin(sel, y, x);
+                case 'o':
+                    selSetElement(sel, y, x, SEL_MISS);
+                    break;
+
+                case 'C':
+                    selSetOrigin(sel, y, x);
+                case ' ':
+                    selSetElement(sel, y, x, SEL_DONT_CARE);
+                    break;
+
+                case '\n':
+                    /* ignored */
+                    continue;
+
+                default:
+                    selDestroy(&sel);
+                    return (SEL *)ERROR_PTR("unknown char", procName, NULL);
+            }
+        }
+    }
+
+    return sel;
+}
+
+
+/*!
+ *  selPrintToString()
+ *
+ *      Input:  sel
+ *      Return: str (string; caller must free)
+ *
+ *  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().
+ */
+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      *
+ *----------------------------------------------------------------------*/
+/*!
+ *  selaCreateFromFile()
+ *
+ *      Input:  filename
+ *      Return: sela, or null on error
+ *
+ *  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"
+ */
+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) {
+            fprintf(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;
+}
+
+
+/*!
+ *  selCreateFromSArray()
+ *
+ *      Input:  sa
+ *              first (line of sarray where Sel begins)
+ *              last (line of sarray where Sel ends)
+ *      Return: sela, or null on error
+ *
+ *  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"
+ */
+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);
+                case 'x':
+                    selSetElement(sel, y, x, SEL_HIT);
+                    break;
+
+                case 'O':
+                    selSetOrigin(sel, y, x);
+                case 'o':
+                    selSetElement(sel, y, x, SEL_MISS);
+                    break;
+
+                case 'C':
+                    selSetOrigin(sel, y, x);
+                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                  *
+ *----------------------------------------------------------------------*/
+/*!
+ *  selCreateFromPta()
+ *
+ *      Input:  pta
+ *              cy, cx (origin of sel)
+ *              name (<optional> sel name; can be null)
+ *      Return: sel (of minimum required size), or null on error
+ *
+ *  Notes:
+ *      (1) The origin and all points in the pta must be positive.
+ */
+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;
+}
+
+
+/*!
+ *  selCreateFromPix()
+ *
+ *      Input:  pix
+ *              cy, cx (origin of sel)
+ *              name (<optional> sel name; can be null)
+ *      Return: sel, or null on error
+ *
+ *  Notes:
+ *      (1) The origin must be positive.
+ */
+SEL *
+selCreateFromPix(PIX         *pix,
+                 l_int32      cy,
+                 l_int32      cx,
+                 const char  *name)
+{
+SEL      *sel;
+l_int32   i, j, w, h, d;
+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);
+
+    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()
+ *
+ *      Input:  pathname
+ *      Return: sel if OK; null on error
+ *
+ *  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.
+ */
+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)
+        return (SEL *)ERROR_PTR("pix not returned", procName, NULL);
+    if ((sel = selCreateFromColorPix(pix, selname)) == NULL)
+        return (SEL *)ERROR_PTR("sel not made", procName, NULL);
+    LEPT_FREE(selname);
+    pixDestroy(&pix);
+
+    return sel;
+}
+
+
+/*!
+ *
+ *  selCreateFromColorPix()
+ *
+ *      Input:  pixs (cmapped or rgb)
+ *              selname (<optional> sel name; can be null)
+ *      Return: sel if OK, null on error
+ *
+ *  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.
+ */
+SEL *
+selCreateFromColorPix(PIX   *pixs,
+                      char  *selname)
+{
+PIXCMAP  *cmap;
+SEL      *sel;
+l_int32   hascolor, hasorigin, 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);
+    selSetName(sel, selname);
+
+    hasorigin = FALSE;
+    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) {
+                if (hasorigin)
+                    L_WARNING("multiple origins in sel image\n", procName);
+                selSetOrigin (sel, i, j);
+                hasorigin = TRUE;
+            }
+            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;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                     Printable display of sel                         *
+ *----------------------------------------------------------------------*/
+/*!
+ *  selDisplayInPix()
+ *
+ *      Input:  sel
+ *              size (of grid interiors; odd; minimum size of 13 is enforced)
+ *              gthick (grid thickness; minimum size of 2 is enforced)
+ *      Return: pix (display of sel), or null on error
+ *
+ *  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)
+ */
+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.5);  /* of hit */
+    radius2 = (l_int32)(0.65 * ((size - 1) / 2) + 0.5);  /* inner miss radius */
+    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;
+}
+
+
+/*!
+ *  selaDisplayInPix()
+ *
+ *      Input:  sela
+ *              size (of grid interiors; odd; minimum size of 13 is enforced)
+ *              gthick (grid thickness; minimum size of 2 is enforced)
+ *              spacing (between sels, both horizontally and vertically)
+ *              ncols (number of sels per "line")
+ *      Return: pix (display of all sels in sela), or null on error
+ *
+ *  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.
+ */
+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;
+}
diff --git a/src/sel2.c b/src/sel2.c
new file mode 100644 (file)
index 0000000..094855f
--- /dev/null
@@ -0,0 +1,621 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  sel2.c
+ *
+ *      Contains definitions of simple structuring elements
+ *
+ *          SELA    *selaAddBasic()
+ *               Linear horizontal and vertical
+ *               Square
+ *               Diagonals
+ *
+ *          SELA    *selaAddHitMiss()
+ *               Isolated foreground pixel
+ *               Horizontal and vertical edges
+ *               Slanted edge
+ *               Corners
+ *
+ *          SELA    *selaAddDwaLinear()
+ *          SELA    *selaAddDwaCombs()
+ *          SELA    *selaAddCrossJunctions()
+ *          SELA    *selaAddTJunctions()
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+static const l_int32  L_BUF_SIZE = 512;
+
+    /* Linear brick sel sizes, including all those that are required
+     * for decomposable sels up to size 63. */
+static const l_int32  num_linear = 25;
+static const l_int32  basic_linear[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+       12, 13, 14, 15, 20, 21, 25, 30, 31, 35, 40, 41, 45, 50, 51};
+
+
+/*!
+ *  selaAddBasic()
+ *
+ *      Input:  sela (<optional>)
+ *      Return: sela with additional sels, or null on error
+ *
+ *  Notes:
+ *      (1) Adds the following sels:
+ *            - all linear (horiz, vert) brick sels that are
+ *              necessary for decomposable sels up to size 63
+ *            - square brick sels up to size 10
+ *            - 4 diagonal sels
+ */
+SELA *
+selaAddBasic(SELA  *sela)
+{
+char     name[L_BUF_SIZE];
+l_int32  i, size;
+SEL     *sel;
+
+    PROCNAME("selaAddBasic");
+
+    if (!sela) {
+        if ((sela = selaCreate(0)) == NULL)
+            return (SELA *)ERROR_PTR("sela not made", procName, NULL);
+    }
+
+    /*--------------------------------------------------------------*
+     *             Linear horizontal and vertical sels              *
+     *--------------------------------------------------------------*/
+    for (i = 0; i < num_linear; i++) {
+        size = basic_linear[i];
+        sel = selCreateBrick(1, size, 0, size / 2, 1);
+        snprintf(name, L_BUF_SIZE, "sel_%dh", size);
+        selaAddSel(sela, sel, name, 0);
+    }
+    for (i = 0; i < num_linear; i++) {
+        size = basic_linear[i];
+        sel = selCreateBrick(size, 1, size / 2, 0, 1);
+        snprintf(name, L_BUF_SIZE, "sel_%dv", size);
+        selaAddSel(sela, sel, name, 0);
+    }
+
+    /*-----------------------------------------------------------*
+     *                      2-d Bricks                           *
+     *-----------------------------------------------------------*/
+    for (i = 2; i <= 5; i++) {
+        sel = selCreateBrick(i, i, i / 2, i / 2, 1);
+        snprintf(name, L_BUF_SIZE, "sel_%d", i);
+        selaAddSel(sela, sel, name, 0);
+    }
+
+    /*-----------------------------------------------------------*
+     *                        Diagonals                          *
+     *-----------------------------------------------------------*/
+        /*  0c  1
+            1   0  */
+    sel = selCreateBrick(2, 2, 0, 0, 1);
+    selSetElement(sel, 0, 0, 0);
+    selSetElement(sel, 1, 1, 0);
+    selaAddSel(sela, sel, "sel_2dp", 0);
+
+        /*  1c  0
+            0   1   */
+    sel = selCreateBrick(2, 2, 0, 0, 1);
+    selSetElement(sel, 0, 1, 0);
+    selSetElement(sel, 1, 0, 0);
+    selaAddSel(sela, sel, "sel_2dm", 0);
+
+        /*  Diagonal, slope +, size 5 */
+    sel = selCreate(5, 5, "sel_5dp");
+    sel->cy = 2;
+    sel->cx = 2;
+    selSetElement(sel, 0, 4, 1);
+    selSetElement(sel, 1, 3, 1);
+    selSetElement(sel, 2, 2, 1);
+    selSetElement(sel, 3, 1, 1);
+    selSetElement(sel, 4, 0, 1);
+    selaAddSel(sela, sel, "sel_5dp", 0);
+
+        /*  Diagonal, slope -, size 5 */
+    sel = selCreate(5, 5, "sel_5dm");
+    sel->cy = 2;
+    sel->cx = 2;
+    selSetElement(sel, 0, 0, 1);
+    selSetElement(sel, 1, 1, 1);
+    selSetElement(sel, 2, 2, 1);
+    selSetElement(sel, 3, 3, 1);
+    selSetElement(sel, 4, 4, 1);
+    selaAddSel(sela, sel, "sel_5dm", 0);
+
+    return sela;
+}
+
+
+/*!
+ *  selaAddHitMiss()
+ *
+ *      Input:  sela  (<optional>)
+ *      Return: sela with additional sels, or null on error
+ */
+SELA *
+selaAddHitMiss(SELA  *sela)
+{
+SEL  *sel;
+
+    PROCNAME("selaAddHitMiss");
+
+    if (!sela) {
+        if ((sela = selaCreate(0)) == NULL)
+            return (SELA *)ERROR_PTR("sela not made", procName, NULL);
+    }
+
+#if 0   /*  use just for testing */
+    sel = selCreateBrick(3, 3, 1, 1, 2);
+    selaAddSel(sela, sel, "sel_bad", 0);
+#endif
+
+
+    /*--------------------------------------------------------------*
+     *                   Isolated foreground pixel                  *
+     *--------------------------------------------------------------*/
+    sel = selCreateBrick(3, 3, 1, 1, SEL_MISS);
+    selSetElement(sel, 1, 1, SEL_HIT);
+    selaAddSel(sela, sel, "sel_3hm", 0);
+
+    /*--------------------------------------------------------------*
+     *                Horizontal and vertical edges                 *
+     *--------------------------------------------------------------*/
+    sel = selCreateBrick(2, 3, 0, 1, SEL_HIT);
+    selSetElement(sel, 1, 0, SEL_MISS);
+    selSetElement(sel, 1, 1, SEL_MISS);
+    selSetElement(sel, 1, 2, SEL_MISS);
+    selaAddSel(sela, sel, "sel_3de", 0);
+
+    sel = selCreateBrick(2, 3, 1, 1, SEL_HIT);
+    selSetElement(sel, 0, 0, SEL_MISS);
+    selSetElement(sel, 0, 1, SEL_MISS);
+    selSetElement(sel, 0, 2, SEL_MISS);
+    selaAddSel(sela, sel, "sel_3ue", 0);
+
+    sel = selCreateBrick(3, 2, 1, 0, SEL_HIT);
+    selSetElement(sel, 0, 1, SEL_MISS);
+    selSetElement(sel, 1, 1, SEL_MISS);
+    selSetElement(sel, 2, 1, SEL_MISS);
+    selaAddSel(sela, sel, "sel_3re", 0);
+
+    sel = selCreateBrick(3, 2, 1, 1, SEL_HIT);
+    selSetElement(sel, 0, 0, SEL_MISS);
+    selSetElement(sel, 1, 0, SEL_MISS);
+    selSetElement(sel, 2, 0, SEL_MISS);
+    selaAddSel(sela, sel, "sel_3le", 0);
+
+    /*--------------------------------------------------------------*
+     *                        Slanted edge                          *
+     *--------------------------------------------------------------*/
+    sel = selCreateBrick(13, 6, 6, 2, SEL_DONT_CARE);
+    selSetElement(sel, 0, 3, SEL_MISS);
+    selSetElement(sel, 0, 5, SEL_HIT);
+    selSetElement(sel, 4, 2, SEL_MISS);
+    selSetElement(sel, 4, 4, SEL_HIT);
+    selSetElement(sel, 8, 1, SEL_MISS);
+    selSetElement(sel, 8, 3, SEL_HIT);
+    selSetElement(sel, 12, 0, SEL_MISS);
+    selSetElement(sel, 12, 2, SEL_HIT);
+    selaAddSel(sela, sel, "sel_sl1", 0);
+
+    /*--------------------------------------------------------------*
+     *                           Corners                            *
+     *  This allows for up to 3 missing edge pixels at the corner   *
+     *--------------------------------------------------------------*/
+    sel = selCreateBrick(4, 4, 1, 1, SEL_MISS);
+    selSetElement(sel, 1, 1, SEL_DONT_CARE);
+    selSetElement(sel, 1, 2, SEL_DONT_CARE);
+    selSetElement(sel, 2, 1, SEL_DONT_CARE);
+    selSetElement(sel, 1, 3, SEL_HIT);
+    selSetElement(sel, 2, 2, SEL_HIT);
+    selSetElement(sel, 2, 3, SEL_HIT);
+    selSetElement(sel, 3, 1, SEL_HIT);
+    selSetElement(sel, 3, 2, SEL_HIT);
+    selSetElement(sel, 3, 3, SEL_HIT);
+    selaAddSel(sela, sel, "sel_ulc", 0);
+
+    sel = selCreateBrick(4, 4, 1, 2, SEL_MISS);
+    selSetElement(sel, 1, 1, SEL_DONT_CARE);
+    selSetElement(sel, 1, 2, SEL_DONT_CARE);
+    selSetElement(sel, 2, 2, SEL_DONT_CARE);
+    selSetElement(sel, 1, 0, SEL_HIT);
+    selSetElement(sel, 2, 0, SEL_HIT);
+    selSetElement(sel, 2, 1, SEL_HIT);
+    selSetElement(sel, 3, 0, SEL_HIT);
+    selSetElement(sel, 3, 1, SEL_HIT);
+    selSetElement(sel, 3, 2, SEL_HIT);
+    selaAddSel(sela, sel, "sel_urc", 0);
+
+    sel = selCreateBrick(4, 4, 2, 1, SEL_MISS);
+    selSetElement(sel, 1, 1, SEL_DONT_CARE);
+    selSetElement(sel, 2, 1, SEL_DONT_CARE);
+    selSetElement(sel, 2, 2, SEL_DONT_CARE);
+    selSetElement(sel, 0, 1, SEL_HIT);
+    selSetElement(sel, 0, 2, SEL_HIT);
+    selSetElement(sel, 0, 3, SEL_HIT);
+    selSetElement(sel, 1, 2, SEL_HIT);
+    selSetElement(sel, 1, 3, SEL_HIT);
+    selSetElement(sel, 2, 3, SEL_HIT);
+    selaAddSel(sela, sel, "sel_llc", 0);
+
+    sel = selCreateBrick(4, 4, 2, 2, SEL_MISS);
+    selSetElement(sel, 1, 2, SEL_DONT_CARE);
+    selSetElement(sel, 2, 1, SEL_DONT_CARE);
+    selSetElement(sel, 2, 2, SEL_DONT_CARE);
+    selSetElement(sel, 0, 0, SEL_HIT);
+    selSetElement(sel, 0, 1, SEL_HIT);
+    selSetElement(sel, 0, 2, SEL_HIT);
+    selSetElement(sel, 1, 0, SEL_HIT);
+    selSetElement(sel, 1, 1, SEL_HIT);
+    selSetElement(sel, 2, 0, SEL_HIT);
+    selaAddSel(sela, sel, "sel_lrc", 0);
+
+    return sela;
+}
+
+
+/*!
+ *  selaAddDwaLinear()
+ *
+ *      Input:  sela (<optional>)
+ *      Return: sela with additional sels, or null on error
+ *
+ *  Notes:
+ *      (1) Adds all linear (horizontal, vertical) sels from
+ *          2 to 63 pixels in length, which are the sizes over
+ *          which dwa code can be generated.
+ */
+SELA *
+selaAddDwaLinear(SELA  *sela)
+{
+char     name[L_BUF_SIZE];
+l_int32  i;
+SEL     *sel;
+
+    PROCNAME("selaAddDwaLinear");
+
+    if (!sela) {
+        if ((sela = selaCreate(0)) == NULL)
+            return (SELA *)ERROR_PTR("sela not made", procName, NULL);
+    }
+
+    for (i = 2; i < 64; i++) {
+        sel = selCreateBrick(1, i, 0, i / 2, 1);
+        snprintf(name, L_BUF_SIZE, "sel_%dh", i);
+        selaAddSel(sela, sel, name, 0);
+    }
+    for (i = 2; i < 64; i++) {
+        sel = selCreateBrick(i, 1, i / 2, 0, 1);
+        snprintf(name, L_BUF_SIZE, "sel_%dv", i);
+        selaAddSel(sela, sel, name, 0);
+    }
+    return sela;
+}
+
+
+/*!
+ *  selaAddDwaCombs()
+ *
+ *      Input:  sela (<optional>)
+ *      Return: sela with additional sels, or null on error
+ *
+ *  Notes:
+ *      (1) Adds all comb (horizontal, vertical) Sels that are
+ *          used in composite linear morphological operations
+ *          up to 63 pixels in length, which are the sizes over
+ *          which dwa code can be generated.
+ */
+SELA *
+selaAddDwaCombs(SELA  *sela)
+{
+char     name[L_BUF_SIZE];
+l_int32  i, f1, f2, prevsize, size;
+SEL     *selh, *selv;
+
+    PROCNAME("selaAddDwaCombs");
+
+    if (!sela) {
+        if ((sela = selaCreate(0)) == NULL)
+            return (SELA *)ERROR_PTR("sela not made", procName, NULL);
+    }
+
+    prevsize = 0;
+    for (i = 4; i < 64; i++) {
+        selectComposableSizes(i, &f1, &f2);
+        size = f1 * f2;
+        if (size == prevsize)
+            continue;
+        selectComposableSels(i, L_HORIZ, NULL, &selh);
+        selectComposableSels(i, L_VERT, NULL, &selv);
+        snprintf(name, L_BUF_SIZE, "sel_comb_%dh", size);
+        selaAddSel(sela, selh, name, 0);
+        snprintf(name, L_BUF_SIZE, "sel_comb_%dv", size);
+        selaAddSel(sela, selv, name, 0);
+        prevsize = size;
+    }
+
+    return sela;
+}
+
+
+/*!
+ *  selaAddCrossJunctions()
+ *
+ *      Input:  sela (<optional>)
+ *              hlsize (length of each line of hits from origin)
+ *              mdist (distance of misses from the origin)
+ *              norient (number of orientations; max of 8)
+ *              debugflag (1 for debug output)
+ *      Return: sela with additional sels, or null on error
+ *
+ *  Notes:
+ *      (1) Adds hitmiss Sels for the intersection of two lines.
+ *          If the lines are very thin, they must be nearly orthogonal
+ *          to register.
+ *      (2) The number of Sels generated is equal to @norient.
+ *      (3) If @norient == 2, this generates 2 Sels of crosses, each with
+ *          two perpendicular lines of hits.  One Sel has horizontal and
+ *          vertical hits; the other has hits along lines at +-45 degrees.
+ *          Likewise, if @norient == 3, this generates 3 Sels of crosses
+ *          oriented at 30 degrees with each other.
+ *      (4) It is suggested that @hlsize be chosen at least 1 greater
+ *          than @mdist.  Try values of (@hlsize, @mdist) such as
+ *          (6,5), (7,6), (8,7), (9,7), etc.
+ */
+SELA *
+selaAddCrossJunctions(SELA      *sela,
+                      l_float32  hlsize,
+                      l_float32  mdist,
+                      l_int32    norient,
+                      l_int32    debugflag)
+{
+char       name[L_BUF_SIZE];
+l_int32    i, j, w, xc, yc;
+l_float64  pi, halfpi, radincr, radang;
+l_float64  angle;
+PIX       *pixc, *pixm, *pixt;
+PIXA      *pixa;
+PTA       *pta1, *pta2, *pta3, *pta4;
+SEL       *sel;
+
+    PROCNAME("selaAddCrossJunctions");
+
+    if (hlsize <= 0)
+        return (SELA *)ERROR_PTR("hlsize not > 0", procName, NULL);
+    if (norient < 1 || norient > 8)
+        return (SELA *)ERROR_PTR("norient not in [1, ... 8]", procName, NULL);
+
+    if (!sela) {
+        if ((sela = selaCreate(0)) == NULL)
+            return (SELA *)ERROR_PTR("sela not made", procName, NULL);
+    }
+
+    pi = 3.1415926535;
+    halfpi = 3.1415926535 / 2.0;
+    radincr = halfpi / (l_float64)norient;
+    w = (l_int32)(2.2 * (L_MAX(hlsize, mdist) + 0.5));
+    if (w % 2 == 0)
+        w++;
+    xc = w / 2;
+    yc = w / 2;
+
+    pixa = pixaCreate(norient);
+    for (i = 0; i < norient; i++) {
+
+            /* Set the don't cares */
+        pixc = pixCreate(w, w, 32);
+        pixSetAll(pixc);
+
+            /* Add the green lines of hits */
+        pixm = pixCreate(w, w, 1);
+        radang = (l_float32)i * radincr;
+        pta1 = generatePtaLineFromPt(xc, yc, hlsize + 1, radang);
+        pta2 = generatePtaLineFromPt(xc, yc, hlsize + 1, radang + halfpi);
+        pta3 = generatePtaLineFromPt(xc, yc, hlsize + 1, radang + pi);
+        pta4 = generatePtaLineFromPt(xc, yc, hlsize + 1, radang + pi + halfpi);
+        ptaJoin(pta1, pta2, 0, -1);
+        ptaJoin(pta1, pta3, 0, -1);
+        ptaJoin(pta1, pta4, 0, -1);
+        pixRenderPta(pixm, pta1, L_SET_PIXELS);
+        pixPaintThroughMask(pixc, pixm, 0, 0, 0x00ff0000);
+        ptaDestroy(&pta1);
+        ptaDestroy(&pta2);
+        ptaDestroy(&pta3);
+        ptaDestroy(&pta4);
+
+            /* Add red misses between the lines */
+        for (j = 0; j < 4; j++) {
+            angle = radang + (j - 0.5) * halfpi;
+            pixSetPixel(pixc, xc + (l_int32)(mdist * cos(angle)),
+                        yc + (l_int32)(mdist * sin(angle)), 0xff000000);
+        }
+
+            /* Add dark green for origin */
+        pixSetPixel(pixc, xc, yc, 0x00550000);
+
+            /* Generate the sel */
+        sel = selCreateFromColorPix(pixc, NULL);
+        sprintf(name, "sel_cross_%d", i);
+        selaAddSel(sela, sel, name, 0);
+
+        if (debugflag) {
+            pixt = pixScaleBySampling(pixc, 10.0, 10.0);
+            pixaAddPix(pixa, pixt, L_INSERT);
+        }
+        pixDestroy(&pixm);
+        pixDestroy(&pixc);
+    }
+
+    if (debugflag) {
+        l_int32  w;
+        pixaGetPixDimensions(pixa, 0, &w, NULL, NULL);
+        pixt = pixaDisplayTiledAndScaled(pixa, 32, w, 1, 0, 10, 2);
+        pixWriteTempfile("/tmp", "xsel1.png", pixt, IFF_PNG, 0);
+        pixDisplay(pixt, 0, 100);
+        pixDestroy(&pixt);
+        pixt = selaDisplayInPix(sela, 15, 2, 20, 1);
+        pixWriteTempfile("/tmp", "xsel2.png", pixt, IFF_PNG, 0);
+        pixDisplay(pixt, 500, 100);
+        pixDestroy(&pixt);
+        selaWriteStream(stderr, sela);
+    }
+    pixaDestroy(&pixa);
+
+    return sela;
+}
+
+
+/*!
+ *  selaAddTJunctions()
+ *
+ *      Input:  sela (<optional>)
+ *              hlsize (length of each line of hits from origin)
+ *              mdist (distance of misses from the origin)
+ *              norient (number of orientations; max of 8)
+ *              debugflag (1 for debug output)
+ *      Return: sela with additional sels, or null on error
+ *
+ *  Notes:
+ *      (1) Adds hitmiss Sels for the T-junction of two lines.
+ *          If the lines are very thin, they must be nearly orthogonal
+ *          to register.
+ *      (2) The number of Sels generated is 4 * @norient.
+ *      (3) It is suggested that @hlsize be chosen at least 1 greater
+ *          than @mdist.  Try values of (@hlsize, @mdist) such as
+ *          (6,5), (7,6), (8,7), (9,7), etc.
+ */
+SELA *
+selaAddTJunctions(SELA      *sela,
+                  l_float32  hlsize,
+                  l_float32  mdist,
+                  l_int32    norient,
+                  l_int32    debugflag)
+{
+char       name[L_BUF_SIZE];
+l_int32    i, j, k, w, xc, yc;
+l_float64  pi, halfpi, radincr, jang, radang;
+l_float64  angle[3], dist[3];
+PIX       *pixc, *pixm, *pixt;
+PIXA      *pixa;
+PTA       *pta1, *pta2, *pta3;
+SEL       *sel;
+
+    PROCNAME("selaAddTJunctions");
+
+    if (hlsize <= 2)
+        return (SELA *)ERROR_PTR("hlsizel not > 1", procName, NULL);
+    if (norient < 1 || norient > 8)
+        return (SELA *)ERROR_PTR("norient not in [1, ... 8]", procName, NULL);
+
+    if (!sela) {
+        if ((sela = selaCreate(0)) == NULL)
+            return (SELA *)ERROR_PTR("sela not made", procName, NULL);
+    }
+
+    pi = 3.1415926535;
+    halfpi = 3.1415926535 / 2.0;
+    radincr = halfpi / (l_float32)norient;
+    w = (l_int32)(2.4 * (L_MAX(hlsize, mdist) + 0.5));
+    if (w % 2 == 0)
+        w++;
+    xc = w / 2;
+    yc = w / 2;
+
+    pixa = pixaCreate(4 * norient);
+    for (i = 0; i < norient; i++) {
+        for (j = 0; j < 4; j++) {  /* 4 orthogonal orientations */
+            jang = (l_float32)j * halfpi;
+
+                /* Set the don't cares */
+            pixc = pixCreate(w, w, 32);
+            pixSetAll(pixc);
+
+                /* Add the green lines of hits */
+            pixm = pixCreate(w, w, 1);
+            radang = (l_float32)i * radincr;
+            pta1 = generatePtaLineFromPt(xc, yc, hlsize + 1, jang + radang);
+            pta2 = generatePtaLineFromPt(xc, yc, hlsize + 1,
+                                         jang + radang + halfpi);
+            pta3 = generatePtaLineFromPt(xc, yc, hlsize + 1,
+                                         jang + radang + pi);
+            ptaJoin(pta1, pta2, 0, -1);
+            ptaJoin(pta1, pta3, 0, -1);
+            pixRenderPta(pixm, pta1, L_SET_PIXELS);
+            pixPaintThroughMask(pixc, pixm, 0, 0, 0x00ff0000);
+            ptaDestroy(&pta1);
+            ptaDestroy(&pta2);
+            ptaDestroy(&pta3);
+
+                /* Add red misses between the lines */
+            angle[0] = radang + jang - halfpi;
+            angle[1] = radang + jang + 0.5 * halfpi;
+            angle[2] = radang + jang + 1.5 * halfpi;
+            dist[0] = 0.8 * mdist;
+            dist[1] = dist[2] = mdist;
+            for (k = 0; k < 3; k++) {
+                pixSetPixel(pixc, xc + (l_int32)(dist[k] * cos(angle[k])),
+                            yc + (l_int32)(dist[k] * sin(angle[k])),
+                            0xff000000);
+            }
+
+                /* Add dark green for origin */
+            pixSetPixel(pixc, xc, yc, 0x00550000);
+
+                /* Generate the sel */
+            sel = selCreateFromColorPix(pixc, NULL);
+            sprintf(name, "sel_cross_%d", 4 * i + j);
+            selaAddSel(sela, sel, name, 0);
+
+            if (debugflag) {
+                pixt = pixScaleBySampling(pixc, 10.0, 10.0);
+                pixaAddPix(pixa, pixt, L_INSERT);
+            }
+            pixDestroy(&pixm);
+            pixDestroy(&pixc);
+        }
+    }
+
+    if (debugflag) {
+        l_int32  w;
+        pixaGetPixDimensions(pixa, 0, &w, NULL, NULL);
+        pixt = pixaDisplayTiledAndScaled(pixa, 32, w, 4, 0, 10, 2);
+        pixWriteTempfile("/tmp", "tsel1.png", pixt, IFF_PNG, 0);
+        pixDisplay(pixt, 0, 100);
+        pixDestroy(&pixt);
+        pixt = selaDisplayInPix(sela, 15, 2, 20, 4);
+        pixWriteTempfile("/tmp", "tsel2.png", pixt, IFF_PNG, 0);
+        pixDisplay(pixt, 500, 100);
+        pixDestroy(&pixt);
+        selaWriteStream(stderr, sela);
+    }
+    pixaDestroy(&pixa);
+
+    return sela;
+}
diff --git a/src/selgen.c b/src/selgen.c
new file mode 100644 (file)
index 0000000..ab14838
--- /dev/null
@@ -0,0 +1,964 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  selgen.c
+ *
+ *      This file contains functions that generate hit-miss Sels
+ *      for doing a loose match to a small bitmap.  The hit-miss
+ *      Sel is made from a given bitmap.  Several "knobs"
+ *      are available to control the looseness of the match.
+ *      In general, a tight match will have fewer false positives
+ *      (bad matches) but more false negatives (missed patterns).
+ *      The values to be used depend on the quality and variation
+ *      of the image in which the pattern is to be searched,
+ *      and the relative penalties of false positives and
+ *      false negatives.  Default values for the three knobs --
+ *      minimum distance to boundary pixels, number of extra pixels
+ *      added to selected sides, and minimum acceptable runlength
+ *      in eroded version -- are provided.
+ *
+ *      The generated hit-miss Sels can always be used in the
+ *      rasterop implementation of binary morphology (in morph.h).
+ *      If they are small enough (not more than 31 pixels extending
+ *      in any direction from the Sel origin), they can also be used
+ *      to auto-generate dwa code (fmorphauto.c).
+ *
+ *
+ *      Generate a subsampled structuring element
+ *            SEL     *pixGenerateSelWithRuns()
+ *            SEL     *pixGenerateSelRandom()
+ *            SEL     *pixGenerateSelBoundary()
+ *
+ *      Accumulate data on runs along lines
+ *            NUMA    *pixGetRunCentersOnLine()
+ *            NUMA    *pixGetRunsOnLine()
+ *
+ *      Subsample boundary pixels in relatively ordered way
+ *            PTA     *pixSubsampleBoundaryPixels()
+ *            PTA     *adjacentOnPixelInRaster()
+ *
+ *      Display generated sel with originating image
+ *            PIX     *pixDisplayHitMissSel()
+ */
+
+#include "allheaders.h"
+
+
+    /* default minimum distance of a hit-miss pixel element to
+     * a boundary pixel of its color. */
+static const l_int32  DEFAULT_DISTANCE_TO_BOUNDARY = 1;
+static const l_int32  MAX_DISTANCE_TO_BOUNDARY = 4;
+
+    /* default min runlength to accept a hit or miss element located
+     * at its center */
+static const l_int32  DEFAULT_MIN_RUNLENGTH = 3;
+
+
+    /* default scalefactor for displaying image and hit-miss sel
+     * that is derived from it */
+static const l_int32  DEFAULT_SEL_SCALEFACTOR = 7;
+static const l_int32  MAX_SEL_SCALEFACTOR = 31;  /* should be big enough */
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_DISPLAY_HM_SEL   0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*-----------------------------------------------------------------*
+ *           Generate a subsampled structuring element             *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixGenerateSelWithRuns()
+ *
+ *      Input:  pix (1 bpp, typically small, to be used as a pattern)
+ *              nhlines (number of hor lines along which elements are found)
+ *              nvlines (number of vert lines along which elements are found)
+ *              distance (min distance from boundary pixel; use 0 for default)
+ *              minlength (min runlength to set hit or miss; use 0 for default)
+ *              toppix (number of extra pixels of bg added above)
+ *              botpix (number of extra pixels of bg added below)
+ *              leftpix (number of extra pixels of bg added to left)
+ *              rightpix (number of extra pixels of bg added to right)
+ *              &pixe (<optional return> input pix expanded by extra pixels)
+ *      Return: sel (hit-miss for input pattern), or null on error
+ *
+ *  Notes:
+ *    (1) The horizontal and vertical lines along which elements are
+ *        selected are roughly equally spaced.  The actual locations of
+ *        the hits and misses are the centers of respective run-lengths.
+ *    (2) No elements are selected that are less than 'distance' pixels away
+ *        from a boundary pixel of the same color.  This makes the
+ *        match much more robust to edge noise.  Valid inputs of
+ *        'distance' are 0, 1, 2, 3 and 4.  If distance is either 0 or
+ *        greater than 4, we reset it to the default value.
+ *    (3) The 4 numbers for adding rectangles of pixels outside the fg
+ *        can be use if the pattern is expected to be surrounded by bg
+ *        (white) pixels.  On the other hand, if the pattern may be near
+ *        other fg (black) components on some sides, use 0 for those sides.
+ *    (4) The pixels added to a side allow you to have miss elements there.
+ *        There is a constraint between distance, minlength, and
+ *        the added pixels for this to work.  We illustrate using the
+ *        default values.  If you add 5 pixels to the top, and use a
+ *        distance of 1, then you end up with a vertical run of at least
+ *        4 bg pixels along the top edge of the image.  If you use a
+ *        minimum runlength of 3, each vertical line will always find
+ *        a miss near the center of its run.  However, if you use a
+ *        minimum runlength of 5, you will not get a miss on every vertical
+ *        line.  As another example, if you have 7 added pixels and a
+ *        distance of 2, you can use a runlength up to 5 to guarantee
+ *        that the miss element is recorded.  We give a warning if the
+ *        contraint does not guarantee a miss element outside the
+ *        image proper.
+ *    (5) The input pix, as extended by the extra pixels on selected sides,
+ *        can optionally be returned.  For debugging, call
+ *        pixDisplayHitMissSel() to visualize the hit-miss sel superimposed
+ *        on the generating bitmap.
+ */
+SEL *
+pixGenerateSelWithRuns(PIX     *pixs,
+                       l_int32  nhlines,
+                       l_int32  nvlines,
+                       l_int32  distance,
+                       l_int32  minlength,
+                       l_int32  toppix,
+                       l_int32  botpix,
+                       l_int32  leftpix,
+                       l_int32  rightpix,
+                       PIX    **ppixe)
+{
+l_int32    ws, hs, w, h, x, y, xval, yval, i, j, nh, nm;
+l_float32  delh, delw;
+NUMA      *nah, *nam;
+PIX       *pixt1, *pixt2, *pixfg, *pixbg;
+PTA       *ptah, *ptam;
+SEL       *seld, *sel;
+
+    PROCNAME("pixGenerateSelWithRuns");
+
+    if (ppixe) *ppixe = NULL;
+    if (!pixs)
+        return (SEL *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (SEL *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (nhlines < 1 && nvlines < 1)
+        return (SEL *)ERROR_PTR("nvlines and nhlines both < 1", procName, NULL);
+
+    if (distance <= 0)
+        distance = DEFAULT_DISTANCE_TO_BOUNDARY;
+    if (minlength <= 0)
+        minlength = DEFAULT_MIN_RUNLENGTH;
+    if (distance > MAX_DISTANCE_TO_BOUNDARY) {
+        L_WARNING("distance too large; setting to max value\n", procName);
+        distance = MAX_DISTANCE_TO_BOUNDARY;
+    }
+
+        /* Locate the foreground */
+    pixClipToForeground(pixs, &pixt1, NULL);
+    if (!pixt1)
+        return (SEL *)ERROR_PTR("pixt1 not made", procName, NULL);
+    ws = pixGetWidth(pixt1);
+    hs = pixGetHeight(pixt1);
+    w = ws;
+    h = hs;
+
+        /* Crop out a region including the foreground, and add pixels
+         * on sides depending on the side flags */
+    if (toppix || botpix || leftpix || rightpix) {
+        x = y = 0;
+        if (toppix) {
+            h += toppix;
+            y = toppix;
+            if (toppix < distance + minlength)
+                L_WARNING("no miss elements in added top pixels\n", procName);
+        }
+        if (botpix) {
+            h += botpix;
+            if (botpix < distance + minlength)
+                L_WARNING("no miss elements in added bot pixels\n", procName);
+        }
+        if (leftpix) {
+            w += leftpix;
+            x = leftpix;
+            if (leftpix < distance + minlength)
+                L_WARNING("no miss elements in added left pixels\n", procName);
+        }
+        if (rightpix) {
+            w += rightpix;
+            if (rightpix < distance + minlength)
+                L_WARNING("no miss elements in added right pixels\n", procName);
+        }
+        pixt2 = pixCreate(w, h, 1);
+        pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0);
+    } else {
+        pixt2 = pixClone(pixt1);
+    }
+    if (ppixe)
+        *ppixe = pixClone(pixt2);
+    pixDestroy(&pixt1);
+
+        /* Identify fg and bg pixels that are at least 'distance' pixels
+         * away from the boundary pixels in their set */
+    seld = selCreateBrick(2 * distance + 1, 2 * distance + 1,
+                          distance, distance, SEL_HIT);
+    pixfg = pixErode(NULL, pixt2, seld);
+    pixbg = pixDilate(NULL, pixt2, seld);
+    pixInvert(pixbg, pixbg);
+    selDestroy(&seld);
+    pixDestroy(&pixt2);
+
+        /* Accumulate hit and miss points */
+    ptah = ptaCreate(0);
+    ptam = ptaCreate(0);
+    if (nhlines >= 1) {
+        delh = (l_float32)h / (l_float32)(nhlines + 1);
+        for (i = 0, y = 0; i < nhlines; i++) {
+            y += (l_int32)(delh + 0.5);
+            nah = pixGetRunCentersOnLine(pixfg, -1, y, minlength);
+            nam = pixGetRunCentersOnLine(pixbg, -1, y, minlength);
+            nh = numaGetCount(nah);
+            nm = numaGetCount(nam);
+            for (j = 0; j < nh; j++) {
+                numaGetIValue(nah, j, &xval);
+                ptaAddPt(ptah, xval, y);
+            }
+            for (j = 0; j < nm; j++) {
+                numaGetIValue(nam, j, &xval);
+                ptaAddPt(ptam, xval, y);
+            }
+            numaDestroy(&nah);
+            numaDestroy(&nam);
+        }
+    }
+    if (nvlines >= 1) {
+        delw = (l_float32)w / (l_float32)(nvlines + 1);
+        for (i = 0, x = 0; i < nvlines; i++) {
+            x += (l_int32)(delw + 0.5);
+            nah = pixGetRunCentersOnLine(pixfg, x, -1, minlength);
+            nam = pixGetRunCentersOnLine(pixbg, x, -1, minlength);
+            nh = numaGetCount(nah);
+            nm = numaGetCount(nam);
+            for (j = 0; j < nh; j++) {
+                numaGetIValue(nah, j, &yval);
+                ptaAddPt(ptah, x, yval);
+            }
+            for (j = 0; j < nm; j++) {
+                numaGetIValue(nam, j, &yval);
+                ptaAddPt(ptam, x, yval);
+            }
+            numaDestroy(&nah);
+            numaDestroy(&nam);
+        }
+    }
+
+        /* Make the Sel with those points */
+    sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE);
+    nh = ptaGetCount(ptah);
+    for (i = 0; i < nh; i++) {
+        ptaGetIPt(ptah, i, &x, &y);
+        selSetElement(sel, y, x, SEL_HIT);
+    }
+    nm = ptaGetCount(ptam);
+    for (i = 0; i < nm; i++) {
+        ptaGetIPt(ptam, i, &x, &y);
+        selSetElement(sel, y, x, SEL_MISS);
+    }
+
+    pixDestroy(&pixfg);
+    pixDestroy(&pixbg);
+    ptaDestroy(&ptah);
+    ptaDestroy(&ptam);
+    return sel;
+}
+
+
+/*!
+ *  pixGenerateSelRandom()
+ *
+ *      Input:  pix (1 bpp, typically small, to be used as a pattern)
+ *              hitfract (fraction of allowable fg pixels that are hits)
+ *              missfract (fraction of allowable bg pixels that are misses)
+ *              distance (min distance from boundary pixel; use 0 for default)
+ *              toppix (number of extra pixels of bg added above)
+ *              botpix (number of extra pixels of bg added below)
+ *              leftpix (number of extra pixels of bg added to left)
+ *              rightpix (number of extra pixels of bg added to right)
+ *              &pixe (<optional return> input pix expanded by extra pixels)
+ *      Return: sel (hit-miss for input pattern), or null on error
+ *
+ *  Notes:
+ *    (1) Either of hitfract and missfract can be zero.  If both are zero,
+ *        the sel would be empty, and NULL is returned.
+ *    (2) No elements are selected that are less than 'distance' pixels away
+ *        from a boundary pixel of the same color.  This makes the
+ *        match much more robust to edge noise.  Valid inputs of
+ *        'distance' are 0, 1, 2, 3 and 4.  If distance is either 0 or
+ *        greater than 4, we reset it to the default value.
+ *    (3) The 4 numbers for adding rectangles of pixels outside the fg
+ *        can be use if the pattern is expected to be surrounded by bg
+ *        (white) pixels.  On the other hand, if the pattern may be near
+ *        other fg (black) components on some sides, use 0 for those sides.
+ *    (4) The input pix, as extended by the extra pixels on selected sides,
+ *        can optionally be returned.  For debugging, call
+ *        pixDisplayHitMissSel() to visualize the hit-miss sel superimposed
+ *        on the generating bitmap.
+ */
+SEL *
+pixGenerateSelRandom(PIX       *pixs,
+                     l_float32  hitfract,
+                     l_float32  missfract,
+                     l_int32    distance,
+                     l_int32    toppix,
+                     l_int32    botpix,
+                     l_int32    leftpix,
+                     l_int32    rightpix,
+                     PIX      **ppixe)
+{
+l_int32    ws, hs, w, h, x, y, i, j, thresh;
+l_uint32   val;
+PIX       *pixt1, *pixt2, *pixfg, *pixbg;
+SEL       *seld, *sel;
+
+    PROCNAME("pixGenerateSelRandom");
+
+    if (ppixe) *ppixe = NULL;
+    if (!pixs)
+        return (SEL *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (SEL *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (hitfract <= 0.0 && missfract <= 0.0)
+        return (SEL *)ERROR_PTR("no hits or misses", procName, NULL);
+    if (hitfract > 1.0 || missfract > 1.0)
+        return (SEL *)ERROR_PTR("fraction can't be > 1.0", procName, NULL);
+
+    if (distance <= 0)
+        distance = DEFAULT_DISTANCE_TO_BOUNDARY;
+    if (distance > MAX_DISTANCE_TO_BOUNDARY) {
+        L_WARNING("distance too large; setting to max value\n", procName);
+        distance = MAX_DISTANCE_TO_BOUNDARY;
+    }
+
+        /* Locate the foreground */
+    pixClipToForeground(pixs, &pixt1, NULL);
+    if (!pixt1)
+        return (SEL *)ERROR_PTR("pixt1 not made", procName, NULL);
+    ws = pixGetWidth(pixt1);
+    hs = pixGetHeight(pixt1);
+    w = ws;
+    h = hs;
+
+        /* Crop out a region including the foreground, and add pixels
+         * on sides depending on the side flags */
+    if (toppix || botpix || leftpix || rightpix) {
+        x = y = 0;
+        if (toppix) {
+            h += toppix;
+            y = toppix;
+        }
+        if (botpix)
+            h += botpix;
+        if (leftpix) {
+            w += leftpix;
+            x = leftpix;
+        }
+        if (rightpix)
+            w += rightpix;
+        pixt2 = pixCreate(w, h, 1);
+        pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0);
+    } else {
+        pixt2 = pixClone(pixt1);
+    }
+    if (ppixe)
+        *ppixe = pixClone(pixt2);
+    pixDestroy(&pixt1);
+
+        /* Identify fg and bg pixels that are at least 'distance' pixels
+         * away from the boundary pixels in their set */
+    seld = selCreateBrick(2 * distance + 1, 2 * distance + 1,
+                          distance, distance, SEL_HIT);
+    pixfg = pixErode(NULL, pixt2, seld);
+    pixbg = pixDilate(NULL, pixt2, seld);
+    pixInvert(pixbg, pixbg);
+    selDestroy(&seld);
+    pixDestroy(&pixt2);
+
+        /* Generate the sel from a random selection of these points */
+    sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE);
+    if (hitfract > 0.0) {
+        thresh = (l_int32)(hitfract * (l_float64)RAND_MAX);
+        for (i = 0; i < h; i++) {
+            for (j = 0; j < w; j++) {
+                pixGetPixel(pixfg, j, i, &val);
+                if (val) {
+                    if (rand() < thresh)
+                        selSetElement(sel, i, j, SEL_HIT);
+                }
+            }
+        }
+    }
+    if (missfract > 0.0) {
+        thresh = (l_int32)(missfract * (l_float64)RAND_MAX);
+        for (i = 0; i < h; i++) {
+            for (j = 0; j < w; j++) {
+                pixGetPixel(pixbg, j, i, &val);
+                if (val) {
+                    if (rand() < thresh)
+                        selSetElement(sel, i, j, SEL_MISS);
+                }
+            }
+        }
+    }
+
+    pixDestroy(&pixfg);
+    pixDestroy(&pixbg);
+    return sel;
+}
+
+
+/*!
+ *  pixGenerateSelBoundary()
+ *
+ *      Input:  pix (1 bpp, typically small, to be used as a pattern)
+ *              hitdist (min distance from fg boundary pixel)
+ *              missdist (min distance from bg boundary pixel)
+ *              hitskip (number of boundary pixels skipped between hits)
+ *              missskip (number of boundary pixels skipped between misses)
+ *              topflag (flag for extra pixels of bg added above)
+ *              botflag (flag for extra pixels of bg added below)
+ *              leftflag (flag for extra pixels of bg added to left)
+ *              rightflag (flag for extra pixels of bg added to right)
+ *              &pixe (<optional return> input pix expanded by extra pixels)
+ *      Return: sel (hit-miss for input pattern), or null on error
+ *
+ *  Notes:
+ *    (1) All fg elements selected are exactly hitdist pixels away from
+ *        the nearest fg boundary pixel, and ditto for bg elements.
+ *        Valid inputs of hitdist and missdist are 0, 1, 2, 3 and 4.
+ *        For example, a hitdist of 0 puts the hits at the fg boundary.
+ *        Usually, the distances should be > 0 avoid the effect of
+ *        noise at the boundary.
+ *    (2) Set hitskip < 0 if no hits are to be used.  Ditto for missskip.
+ *        If both hitskip and missskip are < 0, the sel would be empty,
+ *        and NULL is returned.
+ *    (3) The 4 flags determine whether the sel is increased on that side
+ *        to allow bg misses to be placed all along that boundary.
+ *        The increase in sel size on that side is the minimum necessary
+ *        to allow the misses to be placed at mindist.  For text characters,
+ *        the topflag and botflag are typically set to 1, and the leftflag
+ *        and rightflag to 0.
+ *    (4) The input pix, as extended by the extra pixels on selected sides,
+ *        can optionally be returned.  For debugging, call
+ *        pixDisplayHitMissSel() to visualize the hit-miss sel superimposed
+ *        on the generating bitmap.
+ *    (5) This is probably the best of the three sel generators, in the
+ *        sense that you have the most flexibility with the smallest number
+ *        of hits and misses.
+ */
+SEL *
+pixGenerateSelBoundary(PIX     *pixs,
+                       l_int32  hitdist,
+                       l_int32  missdist,
+                       l_int32  hitskip,
+                       l_int32  missskip,
+                       l_int32  topflag,
+                       l_int32  botflag,
+                       l_int32  leftflag,
+                       l_int32  rightflag,
+                       PIX      **ppixe)
+{
+l_int32  ws, hs, w, h, x, y, ix, iy, i, npt;
+PIX     *pixt1, *pixt2, *pixt3, *pixfg, *pixbg;
+SEL     *selh, *selm, *sel_3, *sel;
+PTA     *ptah, *ptam;
+
+    PROCNAME("pixGenerateSelBoundary");
+
+    if (ppixe) *ppixe = NULL;
+    if (!pixs)
+        return (SEL *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (SEL *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (hitdist < 0 || hitdist > 4 || missdist < 0 || missdist > 4)
+        return (SEL *)ERROR_PTR("dist not in {0 .. 4}", procName, NULL);
+    if (hitskip < 0 && missskip < 0)
+        return (SEL *)ERROR_PTR("no hits or misses", procName, NULL);
+
+        /* Locate the foreground */
+    pixClipToForeground(pixs, &pixt1, NULL);
+    if (!pixt1)
+        return (SEL *)ERROR_PTR("pixt1 not made", procName, NULL);
+    ws = pixGetWidth(pixt1);
+    hs = pixGetHeight(pixt1);
+    w = ws;
+    h = hs;
+
+        /* Crop out a region including the foreground, and add pixels
+         * on sides depending on the side flags */
+    if (topflag || botflag || leftflag || rightflag) {
+        x = y = 0;
+        if (topflag) {
+            h += missdist + 1;
+            y = missdist + 1;
+        }
+        if (botflag)
+            h += missdist + 1;
+        if (leftflag) {
+            w += missdist + 1;
+            x = missdist + 1;
+        }
+        if (rightflag)
+            w += missdist + 1;
+        pixt2 = pixCreate(w, h, 1);
+        pixRasterop(pixt2, x, y, ws, hs, PIX_SRC, pixt1, 0, 0);
+    } else {
+        pixt2 = pixClone(pixt1);
+    }
+    if (ppixe)
+        *ppixe = pixClone(pixt2);
+    pixDestroy(&pixt1);
+
+        /* Identify fg and bg pixels that are exactly hitdist and
+         * missdist (rsp) away from the boundary pixels in their set.
+         * Then get a subsampled set of these points. */
+    sel_3 = selCreateBrick(3, 3, 1, 1, SEL_HIT);
+    if (hitskip >= 0) {
+        selh = selCreateBrick(2 * hitdist + 1, 2 * hitdist + 1,
+                              hitdist, hitdist, SEL_HIT);
+        pixt3 = pixErode(NULL, pixt2, selh);
+        pixfg = pixErode(NULL, pixt3, sel_3);
+        pixXor(pixfg, pixfg, pixt3);
+        ptah = pixSubsampleBoundaryPixels(pixfg, hitskip);
+        pixDestroy(&pixt3);
+        pixDestroy(&pixfg);
+        selDestroy(&selh);
+    }
+    if (missskip >= 0) {
+        selm = selCreateBrick(2 * missdist + 1, 2 * missdist + 1,
+                              missdist, missdist, SEL_HIT);
+        pixt3 = pixDilate(NULL, pixt2, selm);
+        pixbg = pixDilate(NULL, pixt3, sel_3);
+        pixXor(pixbg, pixbg, pixt3);
+        ptam = pixSubsampleBoundaryPixels(pixbg, missskip);
+        pixDestroy(&pixt3);
+        pixDestroy(&pixbg);
+        selDestroy(&selm);
+    }
+    selDestroy(&sel_3);
+    pixDestroy(&pixt2);
+
+        /* Generate the hit-miss sel from these point */
+    sel = selCreateBrick(h, w, h / 2, w / 2, SEL_DONT_CARE);
+    if (hitskip >= 0) {
+        npt = ptaGetCount(ptah);
+        for (i = 0; i < npt; i++) {
+            ptaGetIPt(ptah, i, &ix, &iy);
+            selSetElement(sel, iy, ix, SEL_HIT);
+        }
+    }
+    if (missskip >= 0) {
+        npt = ptaGetCount(ptam);
+        for (i = 0; i < npt; i++) {
+            ptaGetIPt(ptam, i, &ix, &iy);
+            selSetElement(sel, iy, ix, SEL_MISS);
+        }
+    }
+
+    ptaDestroy(&ptah);
+    ptaDestroy(&ptam);
+    return sel;
+}
+
+
+/*-----------------------------------------------------------------*
+ *              Accumulate data on runs along lines                *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixGetRunCentersOnLine()
+ *
+ *      Input:  pixs (1 bpp)
+ *              x, y (set one of these to -1; see notes)
+ *              minlength (minimum length of acceptable run)
+ *      Return: numa of fg runs, or null on error
+ *
+ *  Notes:
+ *      (1) Action: this function computes the fg (black) and bg (white)
+ *          pixel runlengths along the specified horizontal or vertical line,
+ *          and returns a Numa of the "center" pixels of each fg run
+ *          whose length equals or exceeds the minimum length.
+ *      (2) This only works on horizontal and vertical lines.
+ *      (3) For horizontal runs, set x = -1 and y to the value
+ *          for all points along the raster line.  For vertical runs,
+ *          set y = -1 and x to the value for all points along the
+ *          pixel column.
+ *      (4) For horizontal runs, the points in the Numa are the x
+ *          values in the center of fg runs that are of length at
+ *          least 'minlength'.  For vertical runs, the points in the
+ *          Numa are the y values in the center of fg runs, again
+ *          of length 'minlength' or greater.
+ *      (5) If there are no fg runs along the line that satisfy the
+ *          minlength constraint, the returned Numa is empty.  This
+ *          is not an error.
+ */
+NUMA *
+pixGetRunCentersOnLine(PIX     *pixs,
+                       l_int32  x,
+                       l_int32  y,
+                       l_int32  minlength)
+{
+l_int32   w, h, i, r, nruns, len;
+NUMA     *naruns, *nad;
+
+    PROCNAME("pixGetRunCentersOnLine");
+
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (NUMA *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (x != -1 && y != -1)
+        return (NUMA *)ERROR_PTR("x or y must be -1", procName, NULL);
+    if (x == -1 && y == -1)
+        return (NUMA *)ERROR_PTR("x or y cannot both be -1", procName, NULL);
+
+    if ((nad = numaCreate(0)) == NULL)
+        return (NUMA *)ERROR_PTR("nad not made", procName, NULL);
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    if (x == -1) {  /* horizontal run */
+        if (y < 0 || y >= h)
+            return nad;
+        naruns = pixGetRunsOnLine(pixs, 0, y, w - 1, y);
+    } else {  /* vertical run */
+        if (x < 0 || x >= w)
+            return nad;
+        naruns = pixGetRunsOnLine(pixs, x, 0, x, h - 1);
+    }
+    nruns = numaGetCount(naruns);
+
+        /* extract run center values; the first run is always bg */
+    r = 0;  /* cumulative distance along line */
+    for (i = 0; i < nruns; i++) {
+        if (i % 2 == 0) {  /* bg run */
+            numaGetIValue(naruns, i, &len);
+            r += len;
+            continue;
+        } else {
+            numaGetIValue(naruns, i, &len);
+            if (len >= minlength)
+                numaAddNumber(nad, r + len / 2);
+            r += len;
+        }
+    }
+
+    numaDestroy(&naruns);
+    return nad;
+}
+
+
+/*!
+ *  pixGetRunsOnLine()
+ *
+ *      Input:  pixs (1 bpp)
+ *              x1, y1, x2, y2
+ *      Return: numa, or null on error
+ *
+ *  Notes:
+ *      (1) Action: this function uses the bresenham algorithm to compute
+ *          the pixels along the specified line.  It returns a Numa of the
+ *          runlengths of the fg (black) and bg (white) runs, always
+ *          starting with a white run.
+ *      (2) If the first pixel on the line is black, the length of the
+ *          first returned run (which is white) is 0.
+ */
+NUMA *
+pixGetRunsOnLine(PIX     *pixs,
+                 l_int32  x1,
+                 l_int32  y1,
+                 l_int32  x2,
+                 l_int32  y2)
+{
+l_int32   w, h, x, y, npts;
+l_int32   i, runlen, preval;
+l_uint32  val;
+NUMA     *numa;
+PTA      *pta;
+
+    PROCNAME("pixGetRunsOnLine");
+
+    if (!pixs)
+        return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (NUMA *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    if (x1 < 0 || x1 >= w)
+        return (NUMA *)ERROR_PTR("x1 not valid", procName, NULL);
+    if (x2 < 0 || x2 >= w)
+        return (NUMA *)ERROR_PTR("x2 not valid", procName, NULL);
+    if (y1 < 0 || y1 >= h)
+        return (NUMA *)ERROR_PTR("y1 not valid", procName, NULL);
+    if (y2 < 0 || y2 >= h)
+        return (NUMA *)ERROR_PTR("y2 not valid", procName, NULL);
+
+    if ((pta = generatePtaLine(x1, y1, x2, y2)) == NULL)
+        return (NUMA *)ERROR_PTR("pta not made", procName, NULL);
+    if ((npts = ptaGetCount(pta)) == 0)
+        return (NUMA *)ERROR_PTR("pta has no pts", procName, NULL);
+
+    if ((numa = numaCreate(0)) == NULL)
+        return (NUMA *)ERROR_PTR("numa not made", procName, NULL);
+
+    for (i = 0; i < npts; i++) {
+        ptaGetIPt(pta, i, &x, &y);
+        pixGetPixel(pixs, x, y, &val);
+        if (i == 0) {
+            if (val == 1) {  /* black pixel; append white run of size 0 */
+                numaAddNumber(numa, 0);
+            }
+            preval = val;
+            runlen = 1;
+            continue;
+        }
+        if (val == preval) {  /* extend current run */
+            preval = val;
+            runlen++;
+        } else {  /* end previous run */
+            numaAddNumber(numa, runlen);
+            preval = val;
+            runlen = 1;
+        }
+    }
+    numaAddNumber(numa, runlen);  /* append last run */
+
+    ptaDestroy(&pta);
+    return numa;
+}
+
+
+/*-----------------------------------------------------------------*
+ *        Subsample boundary pixels in relatively ordered way      *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixSubsampleBoundaryPixels()
+ *
+ *      Input:  pixs (1 bpp, with only boundary pixels in fg)
+ *              skip (number to skip between samples as you traverse boundary)
+ *      Return: pta, or null on error
+ *
+ *  Notes:
+ *      (1) If skip = 0, we take all the fg pixels.
+ *      (2) We try to traverse the boundaries in a regular way.
+ *          Some pixels may be missed, and these are then subsampled
+ *          randomly with a fraction determined by 'skip'.
+ *      (3) The most natural approach is to use a depth first (stack-based)
+ *          method to find the fg pixels.  However, the pixel runs are
+ *          4-connected and there are relatively few branches.  So
+ *          instead of doing a proper depth-first search, we get nearly
+ *          the same result using two nested while loops: the outer
+ *          one continues a raster-based search for the next fg pixel,
+ *          and the inner one does a reasonable job running along
+ *          each 4-connected coutour.
+ */
+PTA *
+pixSubsampleBoundaryPixels(PIX     *pixs,
+                           l_int32  skip)
+{
+l_int32  x, y, xn, yn, xs, ys, xa, ya, count;
+PIX     *pixt;
+PTA     *pta;
+
+    PROCNAME("pixSubsampleBoundaryPixels");
+
+    if (!pixs)
+        return (PTA *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PTA *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (skip < 0)
+        return (PTA *)ERROR_PTR("skip < 0", procName, NULL);
+
+    if (skip == 0)
+        return ptaGetPixelsFromPix(pixs, NULL);
+
+    pta = ptaCreate(0);
+    pixt = pixCopy(NULL, pixs);
+    xs = ys = 0;
+    while (nextOnPixelInRaster(pixt, xs, ys, &xn, &yn)) {  /* new series */
+        xs = xn;
+        ys = yn;
+
+            /* Add first point in this series */
+        ptaAddPt(pta, xs, ys);
+
+            /* Trace out boundary, erasing all and saving every (skip + 1)th */
+        x = xs;
+        y = ys;
+        pixSetPixel(pixt, x, y, 0);
+        count = 0;
+        while (adjacentOnPixelInRaster(pixt, x, y, &xa, &ya)) {
+            x = xa;
+            y = ya;
+            pixSetPixel(pixt, x, y, 0);
+            if (count == skip) {
+                ptaAddPt(pta, x, y);
+                count = 0;
+            } else {
+                count++;
+            }
+        }
+    }
+
+    pixDestroy(&pixt);
+    return pta;
+}
+
+
+/*!
+ *  adjacentOnPixelInRaster()
+ *
+ *      Input:  pixs (1 bpp)
+ *              x, y (current pixel)
+ *              xa, ya (adjacent ON pixel, found by simple CCW search)
+ *      Return: 1 if a pixel is found; 0 otherwise or on error
+ *
+ *  Notes:
+ *      (1) Search is in 4-connected directions first; then on diagonals.
+ *          This allows traversal along a 4-connected boundary.
+ */
+l_int32
+adjacentOnPixelInRaster(PIX      *pixs,
+                        l_int32   x,
+                        l_int32   y,
+                        l_int32  *pxa,
+                        l_int32  *pya)
+{
+l_int32   w, h, i, xa, ya, found;
+l_int32   xdel[] = {-1, 0, 1, 0, -1, 1, 1, -1};
+l_int32   ydel[] = {0, 1, 0, -1, 1, 1, -1, -1};
+l_uint32  val;
+
+    PROCNAME("adjacentOnPixelInRaster");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 0);
+    if (pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not 1 bpp", procName, 0);
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    found = 0;
+    for (i = 0; i < 8; i++) {
+        xa = x + xdel[i];
+        ya = y + ydel[i];
+        if (xa < 0 || xa >= w || ya < 0 || ya >= h)
+            continue;
+        pixGetPixel(pixs, xa, ya, &val);
+        if (val == 1) {
+            found = 1;
+            *pxa = xa;
+            *pya = ya;
+            break;
+        }
+    }
+    return found;
+}
+
+
+
+/*-----------------------------------------------------------------*
+ *          Display generated sel with originating image           *
+ *-----------------------------------------------------------------*/
+/*!
+ *  pixDisplayHitMissSel()
+ *
+ *      Input:  pixs (1 bpp)
+ *              sel (hit-miss in general)
+ *              scalefactor (an integer >= 1; use 0 for default)
+ *              hitcolor (RGB0 color for center of hit pixels)
+ *              misscolor (RGB0 color for center of miss pixels)
+ *      Return: pixd (RGB showing both pixs and sel), or null on error
+ *  Notes:
+ *    (1) We don't allow scalefactor to be larger than MAX_SEL_SCALEFACTOR
+ *    (2) The colors are conveniently given as 4 bytes in hex format,
+ *        such as 0xff008800.  The least significant byte is ignored.
+ */
+PIX *
+pixDisplayHitMissSel(PIX      *pixs,
+                     SEL      *sel,
+                     l_int32   scalefactor,
+                     l_uint32  hitcolor,
+                     l_uint32  misscolor)
+{
+l_int32    i, j, type;
+l_float32  fscale;
+PIX       *pixt, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixDisplayHitMissSel");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 1)
+        return (PIX *)ERROR_PTR("pixs not 1 bpp", procName, NULL);
+    if (!sel)
+        return (PIX *)ERROR_PTR("sel not defined", procName, NULL);
+
+    if (scalefactor <= 0)
+        scalefactor = DEFAULT_SEL_SCALEFACTOR;
+    if (scalefactor > MAX_SEL_SCALEFACTOR) {
+        L_WARNING("scalefactor too large; using max value\n", procName);
+        scalefactor = MAX_SEL_SCALEFACTOR;
+    }
+
+        /* Generate a version of pixs with a colormap */
+    pixt = pixConvert1To8(NULL, pixs, 0, 1);
+    cmap = pixcmapCreate(8);
+    pixcmapAddColor(cmap, 255, 255, 255);
+    pixcmapAddColor(cmap, 0, 0, 0);
+    pixcmapAddColor(cmap, hitcolor >> 24, (hitcolor >> 16) & 0xff,
+                    (hitcolor >> 8) & 0xff);
+    pixcmapAddColor(cmap, misscolor >> 24, (misscolor >> 16) & 0xff,
+                    (misscolor >> 8) & 0xff);
+    pixSetColormap(pixt, cmap);
+
+        /* Color the hits and misses */
+    for (i = 0; i < sel->sy; i++) {
+        for (j = 0; j < sel->sx; j++) {
+            selGetElement(sel, i, j, &type);
+            if (type == SEL_DONT_CARE)
+                continue;
+            if (type == SEL_HIT)
+                pixSetPixel(pixt, j, i, 2);
+            else  /* type == SEL_MISS */
+                pixSetPixel(pixt, j, i, 3);
+        }
+    }
+
+        /* Scale it up */
+    fscale = (l_float32)scalefactor;
+    pixd = pixScaleBySampling(pixt, fscale, fscale);
+
+    pixDestroy(&pixt);
+    return pixd;
+}
diff --git a/src/shear.c b/src/shear.c
new file mode 100644 (file)
index 0000000..e741a2d
--- /dev/null
@@ -0,0 +1,820 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  shear.c
+ *
+ *    About arbitrary lines
+ *           PIX      *pixHShear()
+ *           PIX      *pixVShear()
+ *
+ *    About special 'points': UL corner and center
+ *           PIX      *pixHShearCorner()
+ *           PIX      *pixVShearCorner()
+ *           PIX      *pixHShearCenter()
+ *           PIX      *pixVShearCenter()
+ *
+ *    In place about arbitrary lines
+ *           l_int32   pixHShearIP()
+ *           l_int32   pixVShearIP()
+ *
+ *    Linear interpolated shear about arbitrary lines
+ *           PIX      *pixHShearLI()
+ *           PIX      *pixVShearLI()
+ *
+ *    Static helper
+ *      static l_float32  normalizeAngleForShear()
+ */
+
+#include <string.h>
+#include <math.h>
+#include "allheaders.h"
+
+    /* Shear angle must not get too close to -pi/2 or pi/2 */
+static const l_float32   MIN_DIFF_FROM_HALF_PI = 0.04;
+
+static l_float32 normalizeAngleForShear(l_float32 radang, l_float32 mindif);
+
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG     0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*-------------------------------------------------------------*
+ *                    About arbitrary lines                    *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixHShear()
+ *
+ *      Input:  pixd (<optional>, this can be null, equal to pixs,
+ *                    or different from pixs)
+ *              pixs (no restrictions on depth)
+ *              yloc (location of horizontal line, measured from origin)
+ *              angle (in radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: pixd, always
+ *
+ *  Notes:
+ *      (1) There are 3 cases:
+ *            (a) pixd == null (make a new pixd)
+ *            (b) pixd == pixs (in-place)
+ *            (c) pixd != pixs
+ *      (2) For these three cases, use these patterns, respectively:
+ *              pixd = pixHShear(NULL, pixs, ...);
+ *              pixHShear(pixs, pixs, ...);
+ *              pixHShear(pixd, pixs, ...);
+ *      (3) This shear leaves the horizontal line of pixels at y = yloc
+ *          invariant.  For a positive shear angle, pixels above this
+ *          line are shoved to the right, and pixels below this line
+ *          move to the left.
+ *      (4) With positive shear angle, this can be used, along with
+ *          pixVShear(), to perform a cw rotation, either with 2 shears
+ *          (for small angles) or in the general case with 3 shears.
+ *      (5) Changing the value of yloc is equivalent to translating
+ *          the result horizontally.
+ *      (6) This brings in 'incolor' pixels from outside the image.
+ *      (7) For in-place operation, pixs cannot be colormapped,
+ *          because the in-place operation only blits in 0 or 1 bits,
+ *          not an arbitrary colormap index.
+ *      (8) The angle is brought into the range [-pi, -pi].  It is
+ *          not permitted to be within MIN_DIFF_FROM_HALF_PI radians
+ *          from either -pi/2 or pi/2.
+ */
+PIX *
+pixHShear(PIX       *pixd,
+          PIX       *pixs,
+          l_int32    yloc,
+          l_float32  radang,
+          l_int32    incolor)
+{
+l_int32    sign, w, h;
+l_int32    y, yincr, inityincr, hshift;
+l_float32  tanangle, invangle;
+
+    PROCNAME("pixHShear");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor value", procName, pixd);
+
+    if (pixd == pixs) {  /* in place */
+        if (pixGetColormap(pixs))
+            return (PIX *)ERROR_PTR("pixs is colormapped", procName, pixd);
+        pixHShearIP(pixd, yloc, radang, incolor);
+        return pixd;
+    }
+
+        /* Make sure pixd exists and is same size as pixs */
+    if (!pixd) {
+        if ((pixd = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    } else {  /* pixd != pixs */
+        pixResizeImageData(pixd, pixs);
+    }
+
+        /* Normalize angle.  If no rotation, return a copy */
+    radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI);
+    if (radang == 0.0 || tan(radang) == 0.0)
+        return pixCopy(pixd, pixs);
+
+        /* Initialize to value of incoming pixels */
+    pixSetBlackOrWhite(pixd, incolor);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    sign = L_SIGN(radang);
+    tanangle = tan(radang);
+    invangle = L_ABS(1. / tanangle);
+    inityincr = (l_int32)(invangle / 2.);
+    yincr = (l_int32)invangle;
+    pixRasterop(pixd, 0, yloc - inityincr, w, 2 * inityincr, PIX_SRC,
+                pixs, 0, yloc - inityincr);
+
+    for (hshift = 1, y = yloc + inityincr; y < h; hshift++) {
+        yincr = (l_int32)(invangle * (hshift + 0.5) + 0.5) - (y - yloc);
+        if (h - y < yincr)  /* reduce for last one if req'd */
+            yincr = h - y;
+        pixRasterop(pixd, -sign*hshift, y, w, yincr, PIX_SRC, pixs, 0, y);
+#if DEBUG
+        fprintf(stderr, "y = %d, hshift = %d, yincr = %d\n", y, hshift, yincr);
+#endif /* DEBUG */
+        y += yincr;
+    }
+
+    for (hshift = -1, y = yloc - inityincr; y > 0; hshift--) {
+        yincr = (y - yloc) - (l_int32)(invangle * (hshift - 0.5) + 0.5);
+        if (y < yincr)  /* reduce for last one if req'd */
+            yincr = y;
+        pixRasterop(pixd, -sign*hshift, y - yincr, w, yincr, PIX_SRC,
+            pixs, 0, y - yincr);
+#if DEBUG
+        fprintf(stderr, "y = %d, hshift = %d, yincr = %d\n",
+                y - yincr, hshift, yincr);
+#endif /* DEBUG */
+        y -= yincr;
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixVShear()
+ *
+ *      Input:  pixd (<optional>, this can be null, equal to pixs,
+ *                    or different from pixs)
+ *              pixs (no restrictions on depth)
+ *              xloc (location of vertical line, measured from origin)
+ *              angle (in radians; not too close to +-(pi / 2))
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: pixd, or null on error
+ *
+ *  Notes:
+ *      (1) There are 3 cases:
+ *            (a) pixd == null (make a new pixd)
+ *            (b) pixd == pixs (in-place)
+ *            (c) pixd != pixs
+ *      (2) For these three cases, use these patterns, respectively:
+ *              pixd = pixVShear(NULL, pixs, ...);
+ *              pixVShear(pixs, pixs, ...);
+ *              pixVShear(pixd, pixs, ...);
+ *      (3) This shear leaves the vertical line of pixels at x = xloc
+ *          invariant.  For a positive shear angle, pixels to the right
+ *          of this line are shoved downward, and pixels to the left
+ *          of the line move upward.
+ *      (4) With positive shear angle, this can be used, along with
+ *          pixHShear(), to perform a cw rotation, either with 2 shears
+ *          (for small angles) or in the general case with 3 shears.
+ *      (5) Changing the value of xloc is equivalent to translating
+ *          the result vertically.
+ *      (6) This brings in 'incolor' pixels from outside the image.
+ *      (7) For in-place operation, pixs cannot be colormapped,
+ *          because the in-place operation only blits in 0 or 1 bits,
+ *          not an arbitrary colormap index.
+ *      (8) The angle is brought into the range [-pi, -pi].  It is
+ *          not permitted to be within MIN_DIFF_FROM_HALF_PI radians
+ *          from either -pi/2 or pi/2.
+ */
+PIX *
+pixVShear(PIX       *pixd,
+          PIX       *pixs,
+          l_int32    xloc,
+          l_float32  radang,
+          l_int32    incolor)
+{
+l_int32    sign, w, h;
+l_int32    x, xincr, initxincr, vshift;
+l_float32  tanangle, invangle;
+
+    PROCNAME("pixVShear");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor value", procName, NULL);
+
+    if (pixd == pixs) {  /* in place */
+        if (pixGetColormap(pixs))
+            return (PIX *)ERROR_PTR("pixs is colormapped", procName, pixd);
+        pixVShearIP(pixd, xloc, radang, incolor);
+        return pixd;
+    }
+
+        /* Make sure pixd exists and is same size as pixs */
+    if (!pixd) {
+        if ((pixd = pixCreateTemplate(pixs)) == NULL)
+            return (PIX *)ERROR_PTR("pixd not made", procName, NULL);
+    } else {  /* pixd != pixs */
+        pixResizeImageData(pixd, pixs);
+    }
+
+        /* Normalize angle.  If no rotation, return a copy */
+    radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI);
+    if (radang == 0.0 || tan(radang) == 0.0)
+        return pixCopy(pixd, pixs);
+
+        /* Initialize to value of incoming pixels */
+    pixSetBlackOrWhite(pixd, incolor);
+
+    pixGetDimensions(pixs, &w, &h, NULL);
+    sign = L_SIGN(radang);
+    tanangle = tan(radang);
+    invangle = L_ABS(1. / tanangle);
+    initxincr = (l_int32)(invangle / 2.);
+    xincr = (l_int32)invangle;
+    pixRasterop(pixd, xloc - initxincr, 0, 2 * initxincr, h, PIX_SRC,
+                pixs, xloc - initxincr, 0);
+
+    for (vshift = 1, x = xloc + initxincr; x < w; vshift++) {
+        xincr = (l_int32)(invangle * (vshift + 0.5) + 0.5) - (x - xloc);
+        if (w - x < xincr)  /* reduce for last one if req'd */
+            xincr = w - x;
+        pixRasterop(pixd, x, sign*vshift, xincr, h, PIX_SRC, pixs, x, 0);
+#if DEBUG
+        fprintf(stderr, "x = %d, vshift = %d, xincr = %d\n", x, vshift, xincr);
+#endif /* DEBUG */
+        x += xincr;
+    }
+
+    for (vshift = -1, x = xloc - initxincr; x > 0; vshift--) {
+        xincr = (x - xloc) - (l_int32)(invangle * (vshift - 0.5) + 0.5);
+        if (x < xincr)  /* reduce for last one if req'd */
+            xincr = x;
+        pixRasterop(pixd, x - xincr, sign*vshift, xincr, h, PIX_SRC,
+            pixs, x - xincr, 0);
+#if DEBUG
+        fprintf(stderr, "x = %d, vshift = %d, xincr = %d\n",
+                x - xincr, vshift, xincr);
+#endif /* DEBUG */
+        x -= xincr;
+    }
+
+    return pixd;
+}
+
+
+
+/*-------------------------------------------------------------*
+ *             Shears about UL corner and center               *
+ *-------------------------------------------------------------*/
+/*!
+ *  pixHShearCorner()
+ *
+ *      Input:  pixd (<optional>, if not null, must be equal to pixs)
+ *              pixs
+ *              angle (in radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: pixd, or null on error.
+ *
+ *  Notes:
+ *      (1) See pixHShear() for usage.
+ *      (2) This does a horizontal shear about the UL corner, with (+) shear
+ *          pushing increasingly leftward (-x) with increasing y.
+ */
+PIX *
+pixHShearCorner(PIX       *pixd,
+                PIX       *pixs,
+                l_float32  radang,
+                l_int32    incolor)
+{
+    PROCNAME("pixHShearCorner");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+
+    return pixHShear(pixd, pixs, 0, radang, incolor);
+}
+
+
+/*!
+ *  pixVShearCorner()
+ *
+ *      Input:  pixd (<optional>, if not null, must be equal to pixs)
+ *              pixs
+ *              angle (in radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: pixd, or null on error.
+ *
+ *  Notes:
+ *      (1) See pixVShear() for usage.
+ *      (2) This does a vertical shear about the UL corner, with (+) shear
+ *          pushing increasingly downward (+y) with increasing x.
+ */
+PIX *
+pixVShearCorner(PIX       *pixd,
+                PIX       *pixs,
+                l_float32  radang,
+                l_int32    incolor)
+{
+    PROCNAME("pixVShearCorner");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+
+    return pixVShear(pixd, pixs, 0, radang, incolor);
+}
+
+
+/*!
+ *  pixHShearCenter()
+ *
+ *      Input:  pixd (<optional>, if not null, must be equal to pixs)
+ *              pixs
+ *              angle (in radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: pixd, or null on error.
+ *
+ *  Notes:
+ *      (1) See pixHShear() for usage.
+ *      (2) This does a horizontal shear about the center, with (+) shear
+ *          pushing increasingly leftward (-x) with increasing y.
+ */
+PIX *
+pixHShearCenter(PIX       *pixd,
+                PIX       *pixs,
+                l_float32  radang,
+                l_int32    incolor)
+{
+    PROCNAME("pixHShearCenter");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+
+    return pixHShear(pixd, pixs, pixGetHeight(pixs) / 2, radang, incolor);
+}
+
+
+/*!
+ *  pixVShearCenter()
+ *
+ *      Input:  pixd (<optional>, if not null, must be equal to pixs)
+ *              pixs
+ *              angle (in radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: pixd, or null on error.
+ *
+ *  Notes:
+ *      (1) See pixVShear() for usage.
+ *      (2) This does a vertical shear about the center, with (+) shear
+ *          pushing increasingly downward (+y) with increasing x.
+ */
+PIX *
+pixVShearCenter(PIX       *pixd,
+                PIX       *pixs,
+                l_float32  radang,
+                l_int32    incolor)
+{
+    PROCNAME("pixVShearCenter");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, pixd);
+
+    return pixVShear(pixd, pixs, pixGetWidth(pixs) / 2, radang, incolor);
+}
+
+
+
+/*--------------------------------------------------------------------------*
+ *                       In place about arbitrary lines                     *
+ *--------------------------------------------------------------------------*/
+/*!
+ *  pixHShearIP()
+ *
+ *      Input:  pixs
+ *              yloc (location of horizontal line, measured from origin)
+ *              angle (in radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place version of pixHShear(); see comments there.
+ *      (2) This brings in 'incolor' pixels from outside the image.
+ *      (3) pixs cannot be colormapped, because the in-place operation
+ *          only blits in 0 or 1 bits, not an arbitrary colormap index.
+ *      (4) Does a horizontal full-band shear about the line with (+) shear
+ *          pushing increasingly leftward (-x) with increasing y.
+ */
+l_int32
+pixHShearIP(PIX       *pixs,
+            l_int32    yloc,
+            l_float32  radang,
+            l_int32    incolor)
+{
+l_int32    sign, w, h;
+l_int32    y, yincr, inityincr, hshift;
+l_float32  tanangle, invangle;
+
+    PROCNAME("pixHShearIP");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return ERROR_INT("invalid incolor value", procName, 1);
+    if (pixGetColormap(pixs))
+        return ERROR_INT("pixs is colormapped", procName, 1);
+
+        /* Normalize angle */
+    radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI);
+    if (radang == 0.0 || tan(radang) == 0.0)
+        return 0;
+
+    sign = L_SIGN(radang);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    tanangle = tan(radang);
+    invangle = L_ABS(1. / tanangle);
+    inityincr = (l_int32)(invangle / 2.);
+    yincr = (l_int32)invangle;
+
+    if (inityincr > 0)
+        pixRasteropHip(pixs, yloc - inityincr, 2 * inityincr, 0, incolor);
+
+    for (hshift = 1, y = yloc + inityincr; y < h; hshift++) {
+        yincr = (l_int32)(invangle * (hshift + 0.5) + 0.5) - (y - yloc);
+        if (yincr == 0) continue;
+        if (h - y < yincr)  /* reduce for last one if req'd */
+            yincr = h - y;
+        pixRasteropHip(pixs, y, yincr, -sign*hshift, incolor);
+        y += yincr;
+    }
+
+    for (hshift = -1, y = yloc - inityincr; y > 0; hshift--) {
+        yincr = (y - yloc) - (l_int32)(invangle * (hshift - 0.5) + 0.5);
+        if (yincr == 0) continue;
+        if (y < yincr)  /* reduce for last one if req'd */
+            yincr = y;
+        pixRasteropHip(pixs, y - yincr, yincr, -sign*hshift, incolor);
+        y -= yincr;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixVShearIP()
+ *
+ *      Input:  pixs (all depths; not colormapped)
+ *              xloc  (location of vertical line, measured from origin)
+ *              angle (in radians)
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This is an in-place version of pixVShear(); see comments there.
+ *      (2) This brings in 'incolor' pixels from outside the image.
+ *      (3) pixs cannot be colormapped, because the in-place operation
+ *          only blits in 0 or 1 bits, not an arbitrary colormap index.
+ *      (4) Does a vertical full-band shear about the line with (+) shear
+ *          pushing increasingly downward (+y) with increasing x.
+ */
+l_int32
+pixVShearIP(PIX       *pixs,
+            l_int32    xloc,
+            l_float32  radang,
+            l_int32    incolor)
+{
+l_int32    sign, w, h;
+l_int32    x, xincr, initxincr, vshift;
+l_float32  tanangle, invangle;
+
+    PROCNAME("pixVShearIP");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return ERROR_INT("invalid incolor value", procName, 1);
+    if (pixGetColormap(pixs))
+        return ERROR_INT("pixs is colormapped", procName, 1);
+
+        /* Normalize angle */
+    radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI);
+    if (radang == 0.0 || tan(radang) == 0.0)
+        return 0;
+
+    sign = L_SIGN(radang);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    tanangle = tan(radang);
+    invangle = L_ABS(1. / tanangle);
+    initxincr = (l_int32)(invangle / 2.);
+    xincr = (l_int32)invangle;
+
+    if (initxincr > 0)
+        pixRasteropVip(pixs, xloc - initxincr, 2 * initxincr, 0, incolor);
+
+    for (vshift = 1, x = xloc + initxincr; x < w; vshift++) {
+        xincr = (l_int32)(invangle * (vshift + 0.5) + 0.5) - (x - xloc);
+        if (xincr == 0) continue;
+        if (w - x < xincr)  /* reduce for last one if req'd */
+            xincr = w - x;
+        pixRasteropVip(pixs, x, xincr, sign*vshift, incolor);
+        x += xincr;
+    }
+
+    for (vshift = -1, x = xloc - initxincr; x > 0; vshift--) {
+        xincr = (x - xloc) - (l_int32)(invangle * (vshift - 0.5) + 0.5);
+        if (xincr == 0) continue;
+        if (x < xincr)  /* reduce for last one if req'd */
+            xincr = x;
+        pixRasteropVip(pixs, x - xincr, xincr, sign*vshift, incolor);
+        x -= xincr;
+    }
+
+    return 0;
+}
+
+
+/*-------------------------------------------------------------------------*
+ *              Linear interpolated shear about arbitrary lines            *
+ *-------------------------------------------------------------------------*/
+/*!
+ *  pixHShearLI()
+ *
+ *      Input:  pixs (8 bpp or 32 bpp, or colormapped)
+ *              yloc (location of horizontal line, measured from origin)
+ *              angle (in radians, in range (-pi/2 ... pi/2))
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: pixd (sheared), or null on error
+ *
+ *  Notes:
+ *      (1) This does horizontal shear with linear interpolation for
+ *          accurate results on 8 bpp gray, 32 bpp rgb, or cmapped images.
+ *          It is relatively slow compared to the sampled version
+ *          implemented by rasterop, but the result is much smoother.
+ *      (2) This shear leaves the horizontal line of pixels at y = yloc
+ *          invariant.  For a positive shear angle, pixels above this
+ *          line are shoved to the right, and pixels below this line
+ *          move to the left.
+ *      (3) Any colormap is removed.
+ *      (4) The angle is brought into the range [-pi/2 + del, pi/2 - del],
+ *          where del == MIN_DIFF_FROM_HALF_PI.
+ */
+PIX *
+pixHShearLI(PIX       *pixs,
+            l_int32    yloc,
+            l_float32  radang,
+            l_int32    incolor)
+{
+l_int32    i, jd, x, xp, xf, w, h, d, wm, wpls, wpld, val, rval, gval, bval;
+l_uint32   word0, word1;
+l_uint32  *datas, *datad, *lines, *lined;
+l_float32  tanangle, xshift;
+PIX       *pix, *pixd;
+
+    PROCNAME("pixHShearLI");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 32 && !pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs not 8, 32 bpp, or cmap", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor value", procName, NULL);
+    if (yloc < 0 || yloc >= h)
+        return (PIX *)ERROR_PTR("yloc not in [0 ... h-1]", procName, NULL);
+
+    if (pixGetColormap(pixs))
+        pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    else
+        pix = pixClone(pixs);
+
+        /* Normalize angle.  If no rotation, return a copy */
+    radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI);
+    if (radang == 0.0 || tan(radang) == 0.0) {
+        pixDestroy(&pix);
+        return pixCopy(NULL, pixs);
+    }
+
+        /* Initialize to value of incoming pixels */
+    pixd = pixCreateTemplate(pix);
+    pixSetBlackOrWhite(pixd, incolor);
+
+        /* Standard linear interp: subdivide each pixel into 64 parts */
+    d = pixGetDepth(pixd);  /* 8 or 32 */
+    datas = pixGetData(pix);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pix);
+    wpld = pixGetWpl(pixd);
+    tanangle = tan(radang);
+    for (i = 0; i < h; i++) {
+        lines = datas + i * wpls;
+        lined = datad + i * wpld;
+        xshift = (yloc - i) * tanangle;
+        for (jd = 0; jd < w; jd++) {
+            x = (l_int32)(64.0 * (-xshift + jd) + 0.5);
+            xp = x / 64;
+            xf = x & 63;
+            wm = w - 1;
+            if (xp < 0 || xp > wm) continue;
+            if (d == 8) {
+                if (xp < wm) {
+                    val = ((63 - xf) * GET_DATA_BYTE(lines, xp) +
+                           xf * GET_DATA_BYTE(lines, xp + 1) + 31) / 63;
+                } else {  /* xp == wm */
+                    val = GET_DATA_BYTE(lines, xp);
+                }
+                SET_DATA_BYTE(lined, jd, val);
+            } else {  /* d == 32 */
+                if (xp < wm) {
+                    word0 = *(lines + xp);
+                    word1 = *(lines + xp + 1);
+                    rval = ((63 - xf) * ((word0 >> L_RED_SHIFT) & 0xff) +
+                           xf * ((word1 >> L_RED_SHIFT) & 0xff) + 31) / 63;
+                    gval = ((63 - xf) * ((word0 >> L_GREEN_SHIFT) & 0xff) +
+                           xf * ((word1 >> L_GREEN_SHIFT) & 0xff) + 31) / 63;
+                    bval = ((63 - xf) * ((word0 >> L_BLUE_SHIFT) & 0xff) +
+                           xf * ((word1 >> L_BLUE_SHIFT) & 0xff) + 31) / 63;
+                    composeRGBPixel(rval, gval, bval, lined + jd);
+                } else {  /* xp == wm */
+                    lined[jd] = lines[xp];
+                }
+            }
+        }
+    }
+
+    pixDestroy(&pix);
+    return pixd;
+}
+
+
+/*!
+ *  pixVShearLI()
+ *
+ *      Input:  pixs (8 bpp or 32 bpp, or colormapped)
+ *              xloc  (location of vertical line, measured from origin)
+ *              angle (in radians, in range (-pi/2 ... pi/2))
+ *              incolor (L_BRING_IN_WHITE, L_BRING_IN_BLACK);
+ *      Return: pixd (sheared), or null on error
+ *
+ *  Notes:
+ *      (1) This does vertical shear with linear interpolation for
+ *          accurate results on 8 bpp gray, 32 bpp rgb, or cmapped images.
+ *          It is relatively slow compared to the sampled version
+ *          implemented by rasterop, but the result is much smoother.
+ *      (2) This shear leaves the vertical line of pixels at x = xloc
+ *          invariant.  For a positive shear angle, pixels to the right
+ *          of this line are shoved downward, and pixels to the left
+ *          of the line move upward.
+ *      (3) Any colormap is removed.
+ *      (4) The angle is brought into the range [-pi/2 + del, pi/2 - del],
+ *          where del == MIN_DIFF_FROM_HALF_PI.
+ */
+PIX *
+pixVShearLI(PIX       *pixs,
+            l_int32    xloc,
+            l_float32  radang,
+            l_int32    incolor)
+{
+l_int32    id, y, yp, yf, j, w, h, d, hm, wpls, wpld, val, rval, gval, bval;
+l_uint32   word0, word1;
+l_uint32  *datas, *datad, *lines, *lined;
+l_float32  tanangle, yshift;
+PIX       *pix, *pixd;
+
+    PROCNAME("pixVShearLI");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 32 && !pixGetColormap(pixs))
+        return (PIX *)ERROR_PTR("pixs not 8, 32 bpp, or cmap", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor value", procName, NULL);
+    if (xloc < 0 || xloc >= w)
+        return (PIX *)ERROR_PTR("xloc not in [0 ... w-1]", procName, NULL);
+
+    if (pixGetColormap(pixs))
+        pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    else
+        pix = pixClone(pixs);
+
+        /* Normalize angle.  If no rotation, return a copy */
+    radang = normalizeAngleForShear(radang, MIN_DIFF_FROM_HALF_PI);
+    if (radang == 0.0 || tan(radang) == 0.0) {
+        pixDestroy(&pix);
+        return pixCopy(NULL, pixs);
+    }
+
+        /* Initialize to value of incoming pixels */
+    pixd = pixCreateTemplate(pix);
+    pixSetBlackOrWhite(pixd, incolor);
+
+        /* Standard linear interp: subdivide each pixel into 64 parts */
+    d = pixGetDepth(pixd);  /* 8 or 32 */
+    datas = pixGetData(pix);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pix);
+    wpld = pixGetWpl(pixd);
+    tanangle = tan(radang);
+    for (j = 0; j < w; j++) {
+        yshift = (j - xloc) * tanangle;
+        for (id = 0; id < h; id++) {
+            y = (l_int32)(64.0 * (-yshift + id) + 0.5);
+            yp = y / 64;
+            yf = y & 63;
+            hm = h - 1;
+            if (yp < 0 || yp > hm) continue;
+            lines = datas + yp * wpls;
+            lined = datad + id * wpld;
+            if (d == 8) {
+                if (yp < hm) {
+                    val = ((63 - yf) * GET_DATA_BYTE(lines, j) +
+                           yf * GET_DATA_BYTE(lines + wpls, j) + 31) / 63;
+                } else {  /* yp == hm */
+                    val = GET_DATA_BYTE(lines, j);
+                }
+                SET_DATA_BYTE(lined, j, val);
+            } else {  /* d == 32 */
+                if (yp < hm) {
+                    word0 = *(lines + j);
+                    word1 = *(lines + wpls + j);
+                    rval = ((63 - yf) * ((word0 >> L_RED_SHIFT) & 0xff) +
+                           yf * ((word1 >> L_RED_SHIFT) & 0xff) + 31) / 63;
+                    gval = ((63 - yf) * ((word0 >> L_GREEN_SHIFT) & 0xff) +
+                           yf * ((word1 >> L_GREEN_SHIFT) & 0xff) + 31) / 63;
+                    bval = ((63 - yf) * ((word0 >> L_BLUE_SHIFT) & 0xff) +
+                           yf * ((word1 >> L_BLUE_SHIFT) & 0xff) + 31) / 63;
+                    composeRGBPixel(rval, gval, bval, lined + j);
+                } else {  /* yp == hm */
+                    lined[j] = lines[j];
+                }
+            }
+        }
+    }
+
+    pixDestroy(&pix);
+    return pixd;
+}
+
+
+/*-------------------------------------------------------------------------*
+ *                           Angle normalization                           *
+ *-------------------------------------------------------------------------*/
+static l_float32
+normalizeAngleForShear(l_float32  radang,
+                       l_float32  mindif)
+{
+l_float32  pi2;
+
+    PROCNAME("normalizeAngleForShear");
+
+       /* Bring angle into range [-pi/2, pi/2] */
+    pi2 = 3.14159265 / 2.0;
+    if (radang < -pi2 || radang > pi2)
+        radang = radang - (l_int32)(radang / pi2) * pi2;
+
+       /* If angle is too close to pi/2 or -pi/2, move it */
+    if (radang > pi2 - mindif) {
+        L_WARNING("angle close to pi/2; shifting away\n", procName);
+        radang = pi2 - mindif;
+    } else if (radang < -pi2 + mindif) {
+        L_WARNING("angle close to -pi/2; shifting away\n", procName);
+        radang = -pi2 + mindif;
+    }
+
+    return radang;
+}
diff --git a/src/skew.c b/src/skew.c
new file mode 100644 (file)
index 0000000..86e4c23
--- /dev/null
@@ -0,0 +1,1174 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  skew.c
+ *
+ *      Top-level deskew interfaces
+ *          PIX       *pixDeskew()
+ *          PIX       *pixFindSkewAndDeskew()
+ *          PIX       *pixDeskewGeneral()
+ *
+ *      Top-level angle-finding interface
+ *          l_int32    pixFindSkew()
+ *
+ *      Basic angle-finding functions
+ *          l_int32    pixFindSkewSweep()
+ *          l_int32    pixFindSkewSweepAndSearch()
+ *          l_int32    pixFindSkewSweepAndSearchScore()
+ *          l_int32    pixFindSkewSweepAndSearchScorePivot()
+ *
+ *      Search over arbitrary range of angles in orthogonal directions
+ *          l_int32    pixFindSkewOrthogonalRange()
+ *
+ *      Differential square sum function for scoring
+ *          l_int32    pixFindDifferentialSquareSum()
+ *
+ *      Measures of variance of row sums
+ *          l_int32    pixFindNormalizedSquareSum()
+ *
+ *
+ *      ==============================================================
+ *      Page skew detection
+ *
+ *      Skew is determined by pixel profiles, which are computed
+ *      as pixel sums along the raster line for each line in the
+ *      image.  By vertically shearing the image by a given angle,
+ *      the sums can be computed quickly along the raster lines
+ *      rather than along lines at that angle.  The score is
+ *      computed from these line sums by taking the square of
+ *      the DIFFERENCE between adjacent line sums, summed over
+ *      all lines.  The skew angle is then found as the angle
+ *      that maximizes the score.  The actual computation for
+ *      any sheared image is done in the function
+ *      pixFindDifferentialSquareSum().
+ *
+ *      The search for the angle that maximizes this score is
+ *      most efficiently performed by first sweeping coarsely
+ *      over angles, using a significantly reduced image (say, 4x
+ *      reduction), to find the approximate maximum within a half
+ *      degree or so, and then doing an interval-halving binary
+ *      search at higher resolution to get the skew angle to
+ *      within 1/20 degree or better.
+ *
+ *      The differential signal is used (rather than just using
+ *      that variance of line sums) because it rejects the
+ *      background noise due to total number of black pixels,
+ *      and has maximum contributions from the baselines and
+ *      x-height lines of text when the textlines are aligned
+ *      with the raster lines.  It also works well in multicolumn
+ *      pages where the textlines do not line up across columns.
+ *
+ *      The method is fast, accurate to within an angle (in radians)
+ *      of approximately the inverse width in pixels of the image,
+ *      and will work on a surprisingly small amount of text data
+ *      (just a couple of text lines).  Consequently, it can
+ *      also be used to find local skew if the skew were to vary
+ *      significantly over the page.  Local skew determination
+ *      is not very important except for locating lines of
+ *      handwritten text that may be mixed with printed text.
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+    /* Default sweep angle parameters for pixFindSkew() */
+static const l_float32  DEFAULT_SWEEP_RANGE = 7.;    /* degrees */
+static const l_float32  DEFAULT_SWEEP_DELTA = 1.;    /* degrees */
+
+    /* Default final angle difference parameter for binary
+     * search in pixFindSkew().  The expected accuracy is
+     * not better than the inverse image width in pixels,
+     * say, 1/2000 radians, or about 0.03 degrees. */
+static const l_float32  DEFAULT_MINBS_DELTA = 0.01;  /* degrees */
+
+    /* Default scale factors for pixFindSkew() */
+static const l_int32  DEFAULT_SWEEP_REDUCTION = 4;  /* sweep part; 4 is good */
+static const l_int32  DEFAULT_BS_REDUCTION = 2;  /* binary search part */
+
+    /* Minimum angle for deskewing in pixDeskew() */
+static const l_float32  MIN_DESKEW_ANGLE = 0.1;  /* degree */
+
+    /* Minimum allowed confidence (ratio) for deskewing in pixDeskew() */
+static const l_float32  MIN_ALLOWED_CONFIDENCE = 3.0;
+
+    /* Minimum allowed maxscore to give nonzero confidence */
+static const l_int32  MIN_VALID_MAXSCORE = 10000;
+
+    /* Constant setting threshold for minimum allowed minscore
+     * to give nonzero confidence; multiply this constant by
+     *  (height * width^2) */
+static const l_float32  MINSCORE_THRESHOLD_CONSTANT = 0.000002;
+
+    /* Default binarization threshold value */
+static const l_int32  DEFAULT_BINARY_THRESHOLD = 130;
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_PRINT_SCORES     0
+#define  DEBUG_PRINT_SWEEP      0
+#define  DEBUG_PRINT_BINARY     0
+#define  DEBUG_PRINT_ORTH       0
+#define  DEBUG_THRESHOLD        0
+#define  DEBUG_PLOT_SCORES      0  /* requires the gnuplot executable */
+#endif  /* ~NO_CONSOLE_IO */
+
+
+
+/*-----------------------------------------------------------------------*
+ *                       Top-level deskew interfaces                     *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixDeskew()
+ *
+ *      Input:  pixs (any depth)
+ *              redsearch (for binary search: reduction factor = 1, 2 or 4;
+ *                         use 0 for default)
+ *      Return: pixd (deskewed pix), or null on error
+ *
+ *  Notes:
+ *      (1) This binarizes if necessary and finds the skew angle.  If the
+ *          angle is large enough and there is sufficient confidence,
+ *          it returns a deskewed image; otherwise, it returns a clone.
+ */
+PIX *
+pixDeskew(PIX     *pixs,
+          l_int32  redsearch)
+{
+    PROCNAME("pixDeskew");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (redsearch == 0)
+        redsearch = DEFAULT_BS_REDUCTION;
+    else if (redsearch != 1 && redsearch != 2 && redsearch != 4)
+        return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL);
+
+    return pixDeskewGeneral(pixs, 0, 0.0, 0.0, redsearch, 0, NULL, NULL);
+}
+
+
+/*!
+ *  pixFindSkewAndDeskew()
+ *
+ *      Input:  pixs (any depth)
+ *              redsearch (for binary search: reduction factor = 1, 2 or 4;
+ *                         use 0 for default)
+ *              &angle   (<optional return> angle required to deskew,
+ *                        in degrees; use NULL to skip)
+ *              &conf    (<optional return> conf value is ratio
+ *                        of max/min scores; use NULL to skip)
+ *      Return: pixd (deskewed pix), or null on error
+ *
+ *  Notes:
+ *      (1) This binarizes if necessary and finds the skew angle.  If the
+ *          angle is large enough and there is sufficient confidence,
+ *          it returns a deskewed image; otherwise, it returns a clone.
+ */
+PIX *
+pixFindSkewAndDeskew(PIX        *pixs,
+                     l_int32     redsearch,
+                     l_float32  *pangle,
+                     l_float32  *pconf)
+{
+    PROCNAME("pixFindSkewAndDeskew");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (redsearch == 0)
+        redsearch = DEFAULT_BS_REDUCTION;
+    else if (redsearch != 1 && redsearch != 2 && redsearch != 4)
+        return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL);
+
+    return pixDeskewGeneral(pixs, 0, 0.0, 0.0, redsearch, 0, pangle, pconf);
+}
+
+
+/*!
+ *  pixDeskewGeneral()
+ *
+ *      Input:  pixs  (any depth)
+ *              redsweep  (for linear search: reduction factor = 1, 2 or 4;
+ *                         use 0 for default)
+ *              sweeprange (in degrees in each direction from 0;
+ *                          use 0.0 for default)
+ *              sweepdelta (in degrees; use 0.0 for default)
+ *              redsearch  (for binary search: reduction factor = 1, 2 or 4;
+ *                          use 0 for default;)
+ *              thresh (for binarizing the image; use 0 for default)
+ *              &angle   (<optional return> angle required to deskew,
+ *                        in degrees; use NULL to skip)
+ *              &conf    (<optional return> conf value is ratio
+ *                        of max/min scores; use NULL to skip)
+ *      Return: pixd (deskewed pix), or null on error
+ *
+ *  Notes:
+ *      (1) This binarizes if necessary and finds the skew angle.  If the
+ *          angle is large enough and there is sufficient confidence,
+ *          it returns a deskewed image; otherwise, it returns a clone.
+ */
+PIX *
+pixDeskewGeneral(PIX        *pixs,
+                 l_int32     redsweep,
+                 l_float32   sweeprange,
+                 l_float32   sweepdelta,
+                 l_int32     redsearch,
+                 l_int32     thresh,
+                 l_float32  *pangle,
+                 l_float32  *pconf)
+{
+l_int32    ret, depth;
+l_float32  angle, conf, deg2rad;
+PIX       *pixb, *pixd;
+
+    PROCNAME("pixDeskewGeneral");
+
+    if (pangle) *pangle = 0.0;
+    if (pconf) *pconf = 0.0;
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (redsweep == 0)
+        redsweep = DEFAULT_SWEEP_REDUCTION;
+    else if (redsweep != 1 && redsweep != 2 && redsweep != 4)
+        return (PIX *)ERROR_PTR("redsweep not in {1,2,4}", procName, NULL);
+    if (sweeprange == 0.0)
+        sweeprange = DEFAULT_SWEEP_RANGE;
+    if (sweepdelta == 0.0)
+        sweepdelta = DEFAULT_SWEEP_DELTA;
+    if (redsearch == 0)
+        redsearch = DEFAULT_BS_REDUCTION;
+    else if (redsearch != 1 && redsearch != 2 && redsearch != 4)
+        return (PIX *)ERROR_PTR("redsearch not in {1,2,4}", procName, NULL);
+    if (thresh == 0)
+        thresh = DEFAULT_BINARY_THRESHOLD;
+
+    deg2rad = 3.1415926535 / 180.;
+
+        /* Binarize if necessary */
+    depth = pixGetDepth(pixs);
+    if (depth == 1)
+        pixb = pixClone(pixs);
+    else
+        pixb = pixConvertTo1(pixs, thresh);
+
+        /* Use the 1 bpp image to find the skew */
+    ret = pixFindSkewSweepAndSearch(pixb, &angle, &conf, redsweep, redsearch,
+                                    sweeprange, sweepdelta,
+                                    DEFAULT_MINBS_DELTA);
+    pixDestroy(&pixb);
+    if (pangle) *pangle = angle;
+    if (pconf) *pconf = conf;
+    if (ret)
+        return pixClone(pixs);
+
+    if (L_ABS(angle) < MIN_DESKEW_ANGLE || conf < MIN_ALLOWED_CONFIDENCE)
+        return pixClone(pixs);
+
+    if ((pixd = pixRotate(pixs, deg2rad * angle, L_ROTATE_AREA_MAP,
+                          L_BRING_IN_WHITE, 0, 0)) == NULL)
+        return pixClone(pixs);
+    else
+        return pixd;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                  Simple top-level angle-finding interface             *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixFindSkew()
+ *
+ *      Input:  pixs  (1 bpp)
+ *              &angle   (<return> angle required to deskew, in degrees)
+ *              &conf    (<return> confidence value is ratio max/min scores)
+ *      Return: 0 if OK, 1 on error or if angle measurment not valid
+ *
+ *  Notes:
+ *      (1) This is a simple high-level interface, that uses default
+ *          values of the parameters for reasonable speed and accuracy.
+ *      (2) The angle returned is the negative of the skew angle of
+ *          the image.  It is the angle required for deskew.
+ *          Clockwise rotations are positive angles.
+ */
+l_int32
+pixFindSkew(PIX        *pixs,
+            l_float32  *pangle,
+            l_float32  *pconf)
+{
+    PROCNAME("pixFindSkew");
+
+    if (pangle) *pangle = 0.0;
+    if (pconf) *pconf = 0.0;
+    if (!pangle || !pconf)
+        return ERROR_INT("&angle and/or &conf not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not 1 bpp", procName, 1);
+
+    return pixFindSkewSweepAndSearch(pixs, pangle, pconf,
+                                     DEFAULT_SWEEP_REDUCTION,
+                                     DEFAULT_BS_REDUCTION,
+                                     DEFAULT_SWEEP_RANGE,
+                                     DEFAULT_SWEEP_DELTA,
+                                     DEFAULT_MINBS_DELTA);
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                       Basic angle-finding functions                   *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixFindSkewSweep()
+ *
+ *      Input:  pixs  (1 bpp)
+ *              &angle   (<return> angle required to deskew, in degrees)
+ *              reduction  (factor = 1, 2, 4 or 8)
+ *              sweeprange   (half the full range; assumed about 0; in degrees)
+ *              sweepdelta   (angle increment of sweep; in degrees)
+ *      Return: 0 if OK, 1 on error or if angle measurment not valid
+ *
+ *  Notes:
+ *      (1) This examines the 'score' for skew angles with equal intervals.
+ *      (2) Caller must check the return value for validity of the result.
+ */
+l_int32
+pixFindSkewSweep(PIX        *pixs,
+                 l_float32  *pangle,
+                 l_int32     reduction,
+                 l_float32   sweeprange,
+                 l_float32   sweepdelta)
+{
+l_int32    ret, bzero, i, nangles;
+l_float32  deg2rad, theta;
+l_float32  sum, maxscore, maxangle;
+NUMA      *natheta, *nascore;
+PIX       *pix, *pixt;
+
+    PROCNAME("pixFindSkewSweep");
+
+    if (!pangle)
+        return ERROR_INT("&angle not defined", procName, 1);
+    *pangle = 0.0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not 1 bpp", procName, 1);
+    if (reduction != 1 && reduction != 2 && reduction != 4 && reduction != 8)
+        return ERROR_INT("reduction must be in {1,2,4,8}", procName, 1);
+
+    deg2rad = 3.1415926535 / 180.;
+    ret = 0;
+
+        /* Generate reduced image, if requested */
+    if (reduction == 1)
+        pix = pixClone(pixs);
+    else if (reduction == 2)
+        pix = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
+    else if (reduction == 4)
+        pix = pixReduceRankBinaryCascade(pixs, 1, 1, 0, 0);
+    else /* reduction == 8 */
+        pix = pixReduceRankBinaryCascade(pixs, 1, 1, 2, 0);
+
+    pixZero(pix, &bzero);
+    if (bzero) {
+        pixDestroy(&pix);
+        return 1;
+    }
+
+    nangles = (l_int32)((2. * sweeprange) / sweepdelta + 1);
+    natheta = numaCreate(nangles);
+    nascore = numaCreate(nangles);
+    pixt = pixCreateTemplate(pix);
+
+    if (!pix || !pixt) {
+        ret = ERROR_INT("pix and pixt not both made", procName, 1);
+        goto cleanup;
+    }
+    if (!natheta || !nascore) {
+        ret = ERROR_INT("natheta and nascore not both made", procName, 1);
+        goto cleanup;
+    }
+
+    for (i = 0; i < nangles; i++) {
+        theta = -sweeprange + i * sweepdelta;   /* degrees */
+
+            /* Shear pix about the UL corner and put the result in pixt */
+        pixVShearCorner(pixt, pix, deg2rad * theta, L_BRING_IN_WHITE);
+
+            /* Get score */
+        pixFindDifferentialSquareSum(pixt, &sum);
+
+#if  DEBUG_PRINT_SCORES
+        L_INFO("sum(%7.2f) = %7.0f\n", procName, theta, sum);
+#endif  /* DEBUG_PRINT_SCORES */
+
+            /* Save the result in the output arrays */
+        numaAddNumber(nascore, sum);
+        numaAddNumber(natheta, theta);
+    }
+
+        /* Find the location of the maximum (i.e., the skew angle)
+         * by fitting the largest data point and its two neighbors
+         * to a quadratic, using lagrangian interpolation.  */
+    numaFitMax(nascore, &maxscore, natheta, &maxangle);
+    *pangle = maxangle;
+
+#if  DEBUG_PRINT_SWEEP
+    L_INFO(" From sweep: angle = %7.3f, score = %7.3f\n", procName,
+           maxangle, maxscore);
+#endif  /* DEBUG_PRINT_SWEEP */
+
+#if  DEBUG_PLOT_SCORES
+        /* Plot the result -- the scores versus rotation angle --
+         * using gnuplot with GPLOT_LINES (lines connecting data points).
+         * The GPLOT data structure is first created, with the
+         * appropriate data incorporated from the two input NUMAs,
+         * and then the function gplotMakeOutput() uses gnuplot to
+         * generate the output plot.  This can be either a .png file
+         * or a .ps file, depending on whether you use GPLOT_PNG
+         * or GPLOT_PS.  */
+    {GPLOT  *gplot;
+        gplot = gplotCreate("sweep_output", GPLOT_PNG,
+                    "Sweep. Variance of difference of ON pixels vs. angle",
+                    "angle (deg)", "score");
+        gplotAddPlot(gplot, natheta, nascore, GPLOT_LINES, "plot1");
+        gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot2");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+    }
+#endif  /* DEBUG_PLOT_SCORES */
+
+cleanup:
+    pixDestroy(&pix);
+    pixDestroy(&pixt);
+    numaDestroy(&nascore);
+    numaDestroy(&natheta);
+    return ret;
+}
+
+
+/*!
+ *  pixFindSkewSweepAndSearch()
+ *
+ *      Input:  pixs  (1 bpp)
+ *              &angle   (<return> angle required to deskew; in degrees)
+ *              &conf    (<return> confidence given by ratio of max/min score)
+ *              redsweep  (sweep reduction factor = 1, 2, 4 or 8)
+ *              redsearch  (binary search reduction factor = 1, 2, 4 or 8;
+ *                          and must not exceed redsweep)
+ *              sweeprange   (half the full range, assumed about 0; in degrees)
+ *              sweepdelta   (angle increment of sweep; in degrees)
+ *              minbsdelta   (min binary search increment angle; in degrees)
+ *      Return: 0 if OK, 1 on error or if angle measurment not valid
+ *
+ *  Notes:
+ *      (1) This finds the skew angle, doing first a sweep through a set
+ *          of equal angles, and then doing a binary search until
+ *          convergence.
+ *      (2) Caller must check the return value for validity of the result.
+ *      (3) In computing the differential line sum variance score, we sum
+ *          the result over scanlines, but we always skip:
+ *           - at least one scanline
+ *           - not more than 10% of the image height
+ *           - not more than 5% of the image width
+ *      (4) See also notes in pixFindSkewSweepAndSearchScore()
+ */
+l_int32
+pixFindSkewSweepAndSearch(PIX        *pixs,
+                          l_float32  *pangle,
+                          l_float32  *pconf,
+                          l_int32     redsweep,
+                          l_int32     redsearch,
+                          l_float32   sweeprange,
+                          l_float32   sweepdelta,
+                          l_float32   minbsdelta)
+{
+    return pixFindSkewSweepAndSearchScore(pixs, pangle, pconf, NULL,
+                                          redsweep, redsearch, 0.0, sweeprange,
+                                          sweepdelta, minbsdelta);
+}
+
+
+/*!
+ *  pixFindSkewSweepAndSearchScore()
+ *
+ *      Input:  pixs  (1 bpp)
+ *              &angle   (<return> angle required to deskew; in degrees)
+ *              &conf    (<return> confidence given by ratio of max/min score)
+ *              &endscore (<optional return> max score; use NULL to ignore)
+ *              redsweep  (sweep reduction factor = 1, 2, 4 or 8)
+ *              redsearch  (binary search reduction factor = 1, 2, 4 or 8;
+ *                          and must not exceed redsweep)
+ *              sweepcenter  (angle about which sweep is performed; in degrees)
+ *              sweeprange   (half the full range, taken about sweepcenter;
+ *                            in degrees)
+ *              sweepdelta   (angle increment of sweep; in degrees)
+ *              minbsdelta   (min binary search increment angle; in degrees)
+ *      Return: 0 if OK, 1 on error or if angle measurment not valid
+ *
+ *  Notes:
+ *      (1) This finds the skew angle, doing first a sweep through a set
+ *          of equal angles, and then doing a binary search until convergence.
+ *      (2) There are two built-in constants that determine if the
+ *          returned confidence is nonzero:
+ *            - MIN_VALID_MAXSCORE (minimum allowed maxscore)
+ *            - MINSCORE_THRESHOLD_CONSTANT (determines minimum allowed
+ *                 minscore, by multiplying by (height * width^2)
+ *          If either of these conditions is not satisfied, the returned
+ *          confidence value will be zero.  The maxscore is optionally
+ *          returned in this function to allow evaluation of the
+ *          resulting angle by a method that is independent of the
+ *          returned confidence value.
+ *      (3) The larger the confidence value, the greater the probability
+ *          that the proper alignment is given by the angle that maximizes
+ *          variance.  It should be compared to a threshold, which depends
+ *          on the application.  Values between 3.0 and 6.0 are common.
+ *      (4) By default, the shear is about the UL corner.
+ */
+l_int32
+pixFindSkewSweepAndSearchScore(PIX        *pixs,
+                               l_float32  *pangle,
+                               l_float32  *pconf,
+                               l_float32  *pendscore,
+                               l_int32     redsweep,
+                               l_int32     redsearch,
+                               l_float32   sweepcenter,
+                               l_float32   sweeprange,
+                               l_float32   sweepdelta,
+                               l_float32   minbsdelta)
+{
+    return pixFindSkewSweepAndSearchScorePivot(pixs, pangle, pconf, pendscore,
+                                               redsweep, redsearch, 0.0,
+                                               sweeprange, sweepdelta,
+                                               minbsdelta,
+                                               L_SHEAR_ABOUT_CORNER);
+}
+
+
+/*!
+ *  pixFindSkewSweepAndSearchScorePivot()
+ *
+ *      Input:  pixs  (1 bpp)
+ *              &angle   (<return> angle required to deskew; in degrees)
+ *              &conf    (<return> confidence given by ratio of max/min score)
+ *              &endscore (<optional return> max score; use NULL to ignore)
+ *              redsweep  (sweep reduction factor = 1, 2, 4 or 8)
+ *              redsearch  (binary search reduction factor = 1, 2, 4 or 8;
+ *                          and must not exceed redsweep)
+ *              sweepcenter  (angle about which sweep is performed; in degrees)
+ *              sweeprange   (half the full range, taken about sweepcenter;
+ *                            in degrees)
+ *              sweepdelta   (angle increment of sweep; in degrees)
+ *              minbsdelta   (min binary search increment angle; in degrees)
+ *              pivot  (L_SHEAR_ABOUT_CORNER, L_SHEAR_ABOUT_CENTER)
+ *      Return: 0 if OK, 1 on error or if angle measurment not valid
+ *
+ *  Notes:
+ *      (1) See notes in pixFindSkewSweepAndSearchScore().
+ *      (2) This allows choice of shear pivoting from either the UL corner
+ *          or the center.  For small angles, the ability to discriminate
+ *          angles is better with shearing from the UL corner.  However,
+ *          for large angles (say, greater than 20 degrees), it is better
+ *          to shear about the center because a shear from the UL corner
+ *          loses too much of the image.
+ */
+l_int32
+pixFindSkewSweepAndSearchScorePivot(PIX        *pixs,
+                                    l_float32  *pangle,
+                                    l_float32  *pconf,
+                                    l_float32  *pendscore,
+                                    l_int32     redsweep,
+                                    l_int32     redsearch,
+                                    l_float32   sweepcenter,
+                                    l_float32   sweeprange,
+                                    l_float32   sweepdelta,
+                                    l_float32   minbsdelta,
+                                    l_int32     pivot)
+{
+l_int32    ret, bzero, i, nangles, n, ratio, maxindex, minloc;
+l_int32    width, height;
+l_float32  deg2rad, theta, delta;
+l_float32  sum, maxscore, maxangle;
+l_float32  centerangle, leftcenterangle, rightcenterangle;
+l_float32  lefttemp, righttemp;
+l_float32  bsearchscore[5];
+l_float32  minscore, minthresh;
+l_float32  rangeleft;
+NUMA      *natheta, *nascore;
+PIX       *pixsw, *pixsch, *pixt1, *pixt2;
+
+    PROCNAME("pixFindSkewSweepAndSearchScorePivot");
+
+    if (pendscore) *pendscore = 0.0;
+    if (pangle) *pangle = 0.0;
+    if (pconf) *pconf = 0.0;
+    if (!pangle || !pconf)
+        return ERROR_INT("&angle and/or &conf not defined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    if (redsweep != 1 && redsweep != 2 && redsweep != 4 && redsweep != 8)
+        return ERROR_INT("redsweep must be in {1,2,4,8}", procName, 1);
+    if (redsearch != 1 && redsearch != 2 && redsearch != 4 && redsearch != 8)
+        return ERROR_INT("redsearch must be in {1,2,4,8}", procName, 1);
+    if (redsearch > redsweep)
+        return ERROR_INT("redsearch must not exceed redsweep", procName, 1);
+    if (pivot != L_SHEAR_ABOUT_CORNER && pivot != L_SHEAR_ABOUT_CENTER)
+        return ERROR_INT("invalid pivot", procName, 1);
+
+    deg2rad = 3.1415926535 / 180.;
+    ret = 0;
+
+        /* Generate reduced image for binary search, if requested */
+    if (redsearch == 1)
+        pixsch = pixClone(pixs);
+    else if (redsearch == 2)
+        pixsch = pixReduceRankBinaryCascade(pixs, 1, 0, 0, 0);
+    else if (redsearch == 4)
+        pixsch = pixReduceRankBinaryCascade(pixs, 1, 1, 0, 0);
+    else  /* redsearch == 8 */
+        pixsch = pixReduceRankBinaryCascade(pixs, 1, 1, 2, 0);
+
+    pixZero(pixsch, &bzero);
+    if (bzero) {
+        pixDestroy(&pixsch);
+        return 1;
+    }
+
+        /* Generate reduced image for sweep, if requested */
+    ratio = redsweep / redsearch;
+    if (ratio == 1) {
+        pixsw = pixClone(pixsch);
+    } else {  /* ratio > 1 */
+        if (ratio == 2)
+            pixsw = pixReduceRankBinaryCascade(pixsch, 1, 0, 0, 0);
+        else if (ratio == 4)
+            pixsw = pixReduceRankBinaryCascade(pixsch, 1, 2, 0, 0);
+        else  /* ratio == 8 */
+            pixsw = pixReduceRankBinaryCascade(pixsch, 1, 2, 2, 0);
+    }
+
+    pixt1 = pixCreateTemplate(pixsw);
+    if (ratio == 1)
+        pixt2 = pixClone(pixt1);
+    else
+        pixt2 = pixCreateTemplate(pixsch);
+
+    nangles = (l_int32)((2. * sweeprange) / sweepdelta + 1);
+    natheta = numaCreate(nangles);
+    nascore = numaCreate(nangles);
+
+    if (!pixsch || !pixsw) {
+        ret = ERROR_INT("pixsch and pixsw not both made", procName, 1);
+        goto cleanup;
+    }
+    if (!pixt1 || !pixt2) {
+        ret = ERROR_INT("pixt1 and pixt2 not both made", procName, 1);
+        goto cleanup;
+    }
+    if (!natheta || !nascore) {
+        ret = ERROR_INT("natheta and nascore not both made", procName, 1);
+        goto cleanup;
+    }
+
+        /* Do sweep */
+    rangeleft = sweepcenter - sweeprange;
+    for (i = 0; i < nangles; i++) {
+        theta = rangeleft + i * sweepdelta;   /* degrees */
+
+            /* Shear pix and put the result in pixt1 */
+        if (pivot == L_SHEAR_ABOUT_CORNER)
+            pixVShearCorner(pixt1, pixsw, deg2rad * theta, L_BRING_IN_WHITE);
+        else
+            pixVShearCenter(pixt1, pixsw, deg2rad * theta, L_BRING_IN_WHITE);
+
+            /* Get score */
+        pixFindDifferentialSquareSum(pixt1, &sum);
+
+#if  DEBUG_PRINT_SCORES
+        L_INFO("sum(%7.2f) = %7.0f\n", procName, theta, sum);
+#endif  /* DEBUG_PRINT_SCORES */
+
+            /* Save the result in the output arrays */
+        numaAddNumber(nascore, sum);
+        numaAddNumber(natheta, theta);
+    }
+
+        /* Find the largest of the set (maxscore at maxangle) */
+    numaGetMax(nascore, &maxscore, &maxindex);
+    numaGetFValue(natheta, maxindex, &maxangle);
+
+#if  DEBUG_PRINT_SWEEP
+    L_INFO(" From sweep: angle = %7.3f, score = %7.3f\n", procName,
+           maxangle, maxscore);
+#endif  /* DEBUG_PRINT_SWEEP */
+
+#if  DEBUG_PLOT_SCORES
+        /* Plot the sweep result -- the scores versus rotation angle --
+         * using gnuplot with GPLOT_LINES (lines connecting data points). */
+    {GPLOT  *gplot;
+        gplot = gplotCreate("sweep_output", GPLOT_PNG,
+                    "Sweep. Variance of difference of ON pixels vs. angle",
+                    "angle (deg)", "score");
+        gplotAddPlot(gplot, natheta, nascore, GPLOT_LINES, "plot1");
+        gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot2");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+    }
+#endif  /* DEBUG_PLOT_SCORES */
+
+        /* Check if the max is at the end of the sweep. */
+    n = numaGetCount(natheta);
+    if (maxindex == 0 || maxindex == n - 1) {
+        L_WARNING("max found at sweep edge\n", procName);
+        goto cleanup;
+    }
+
+        /* Empty the numas for re-use */
+    numaEmpty(nascore);
+    numaEmpty(natheta);
+
+        /* Do binary search to find skew angle.
+         * First, set up initial three points. */
+    centerangle = maxangle;
+    if (pivot == L_SHEAR_ABOUT_CORNER) {
+        pixVShearCorner(pixt2, pixsch, deg2rad * centerangle, L_BRING_IN_WHITE);
+        pixFindDifferentialSquareSum(pixt2, &bsearchscore[2]);
+        pixVShearCorner(pixt2, pixsch, deg2rad * (centerangle - sweepdelta),
+                        L_BRING_IN_WHITE);
+        pixFindDifferentialSquareSum(pixt2, &bsearchscore[0]);
+        pixVShearCorner(pixt2, pixsch, deg2rad * (centerangle + sweepdelta),
+                        L_BRING_IN_WHITE);
+        pixFindDifferentialSquareSum(pixt2, &bsearchscore[4]);
+    } else {
+        pixVShearCenter(pixt2, pixsch, deg2rad * centerangle, L_BRING_IN_WHITE);
+        pixFindDifferentialSquareSum(pixt2, &bsearchscore[2]);
+        pixVShearCenter(pixt2, pixsch, deg2rad * (centerangle - sweepdelta),
+                        L_BRING_IN_WHITE);
+        pixFindDifferentialSquareSum(pixt2, &bsearchscore[0]);
+        pixVShearCenter(pixt2, pixsch, deg2rad * (centerangle + sweepdelta),
+                        L_BRING_IN_WHITE);
+        pixFindDifferentialSquareSum(pixt2, &bsearchscore[4]);
+    }
+
+    numaAddNumber(nascore, bsearchscore[2]);
+    numaAddNumber(natheta, centerangle);
+    numaAddNumber(nascore, bsearchscore[0]);
+    numaAddNumber(natheta, centerangle - sweepdelta);
+    numaAddNumber(nascore, bsearchscore[4]);
+    numaAddNumber(natheta, centerangle + sweepdelta);
+
+        /* Start the search */
+    delta = 0.5 * sweepdelta;
+    while (delta >= minbsdelta)
+    {
+            /* Get the left intermediate score */
+        leftcenterangle = centerangle - delta;
+        if (pivot == L_SHEAR_ABOUT_CORNER)
+            pixVShearCorner(pixt2, pixsch, deg2rad * leftcenterangle,
+                            L_BRING_IN_WHITE);
+        else
+            pixVShearCenter(pixt2, pixsch, deg2rad * leftcenterangle,
+                            L_BRING_IN_WHITE);
+        pixFindDifferentialSquareSum(pixt2, &bsearchscore[1]);
+        numaAddNumber(nascore, bsearchscore[1]);
+        numaAddNumber(natheta, leftcenterangle);
+
+            /* Get the right intermediate score */
+        rightcenterangle = centerangle + delta;
+        if (pivot == L_SHEAR_ABOUT_CORNER)
+            pixVShearCorner(pixt2, pixsch, deg2rad * rightcenterangle,
+                            L_BRING_IN_WHITE);
+        else
+            pixVShearCenter(pixt2, pixsch, deg2rad * rightcenterangle,
+                            L_BRING_IN_WHITE);
+        pixFindDifferentialSquareSum(pixt2, &bsearchscore[3]);
+        numaAddNumber(nascore, bsearchscore[3]);
+        numaAddNumber(natheta, rightcenterangle);
+
+            /* Find the maximum of the five scores and its location.
+             * Note that the maximum must be in the center
+             * three values, not in the end two. */
+        maxscore = bsearchscore[1];
+        maxindex = 1;
+        for (i = 2; i < 4; i++) {
+            if (bsearchscore[i] > maxscore) {
+                maxscore = bsearchscore[i];
+                maxindex = i;
+            }
+        }
+
+            /* Set up score array to interpolate for the next iteration */
+        lefttemp = bsearchscore[maxindex - 1];
+        righttemp = bsearchscore[maxindex + 1];
+        bsearchscore[2] = maxscore;
+        bsearchscore[0] = lefttemp;
+        bsearchscore[4] = righttemp;
+
+            /* Get new center angle and delta for next iteration */
+        centerangle = centerangle + delta * (maxindex - 2);
+        delta = 0.5 * delta;
+    }
+    *pangle = centerangle;
+
+#if  DEBUG_PRINT_SCORES
+    L_INFO(" Binary search score = %7.3f\n", procName, bsearchscore[2]);
+#endif  /* DEBUG_PRINT_SCORES */
+
+    if (pendscore)  /* save if requested */
+        *pendscore = bsearchscore[2];
+
+        /* Return the ratio of Max score over Min score
+         * as a confidence value.  Don't trust if the Min score
+         * is too small, which can happen if the image is all black
+         * with only a few white pixels interspersed.  In that case,
+         * we get a contribution from the top and bottom edges when
+         * vertically sheared, but this contribution becomes zero when
+         * the shear angle is zero.  For zero shear angle, the only
+         * contribution will be from the white pixels.  We expect that
+         * the signal goes as the product of the (height * width^2),
+         * so we compute a (hopefully) normalized minimum threshold as
+         * a function of these dimensions.  */
+    numaGetMin(nascore, &minscore, &minloc);
+    width = pixGetWidth(pixsch);
+    height = pixGetHeight(pixsch);
+    minthresh = MINSCORE_THRESHOLD_CONSTANT * width * width * height;
+
+#if  DEBUG_THRESHOLD
+    L_INFO(" minthresh = %10.2f, minscore = %10.2f\n", procName,
+           minthresh, minscore);
+    L_INFO(" maxscore = %10.2f\n", procName, maxscore);
+#endif  /* DEBUG_THRESHOLD */
+
+    if (minscore > minthresh)
+        *pconf = maxscore / minscore;
+    else
+        *pconf = 0.0;
+
+        /* Don't trust it if too close to the edge of the sweep
+         * range or if maxscore is small */
+    if ((centerangle > rangeleft + 2 * sweeprange - sweepdelta) ||
+        (centerangle < rangeleft + sweepdelta) ||
+        (maxscore < MIN_VALID_MAXSCORE))
+        *pconf = 0.0;
+
+#if  DEBUG_PRINT_BINARY
+    fprintf(stderr, "Binary search: angle = %7.3f, score ratio = %6.2f\n",
+            *pangle, *pconf);
+    fprintf(stderr, "               max score = %8.0f\n", maxscore);
+#endif  /* DEBUG_PRINT_BINARY */
+
+#if  DEBUG_PLOT_SCORES
+        /* Plot the result -- the scores versus rotation angle --
+         * using gnuplot with GPLOT_POINTS.  Because the data
+         * points are not ordered by theta (increasing or decreasing),
+         * using GPLOT_LINES would be confusing! */
+    {GPLOT  *gplot;
+        gplot = gplotCreate("search_output", GPLOT_PNG,
+                "Binary search.  Variance of difference of ON pixels vs. angle",
+                "angle (deg)", "score");
+        gplotAddPlot(gplot, natheta, nascore, GPLOT_POINTS, "plot1");
+        gplotMakeOutput(gplot);
+        gplotDestroy(&gplot);
+    }
+#endif  /* DEBUG_PLOT_SCORES */
+
+cleanup:
+    pixDestroy(&pixsw);
+    pixDestroy(&pixsch);
+    pixDestroy(&pixt1);
+    pixDestroy(&pixt2);
+    numaDestroy(&nascore);
+    numaDestroy(&natheta);
+    return ret;
+}
+
+
+/*---------------------------------------------------------------------*
+ *    Search over arbitrary range of angles in orthogonal directions   *
+ *---------------------------------------------------------------------*/
+/*
+ *   pixFindSkewOrthogonalRange()
+ *
+ *      Input:  pixs  (1 bpp)
+ *              &angle  (<return> angle required to deskew; in degrees cw)
+ *              &conf   (<return> confidence given by ratio of max/min score)
+ *              redsweep  (sweep reduction factor = 1, 2, 4 or 8)
+ *              redsearch  (binary search reduction factor = 1, 2, 4 or 8;
+ *                          and must not exceed redsweep)
+ *              sweeprange  (half the full range in each orthogonal
+ *                           direction, taken about 0, in degrees)
+ *              sweepdelta   (angle increment of sweep; in degrees)
+ *              minbsdelta   (min binary search increment angle; in degrees)
+ *              confprior  (amount by which confidence of 90 degree rotated
+ *                          result is reduced when comparing with unrotated
+ *                          confidence value)
+ *      Return: 0 if OK, 1 on error or if angle measurment not valid
+ *
+ *  Notes:
+ *      (1) This searches for the skew angle, first in the range
+ *          [-sweeprange, sweeprange], and then in
+ *          [90 - sweeprange, 90 + sweeprange], with angles measured
+ *          clockwise.  For exploring the full range of possibilities,
+ *          suggest using sweeprange = 47.0 degrees, giving some overlap
+ *          at 45 and 135 degrees.  From these results, and discounting
+ *          the the second confidence by @confprior, it selects the
+ *          angle for maximal differential variance.  If the angle
+ *          is larger than pi/4, the angle found after 90 degree rotation
+ *          is selected.
+ *      (2) The larger the confidence value, the greater the probability
+ *          that the proper alignment is given by the angle that maximizes
+ *          variance.  It should be compared to a threshold, which depends
+ *          on the application.  Values between 3.0 and 6.0 are common.
+ *      (3) Allowing for both portrait and landscape searches is more
+ *          difficult, because if the signal from the text lines is weak,
+ *          a signal from vertical rules can be larger!
+ *          The most difficult documents to deskew have some or all of:
+ *            (a) Multiple columns, not aligned
+ *            (b) Black lines along the vertical edges
+ *            (c) Text from two pages, and at different angles
+ *          Rule of thumb for resolution:
+ *            (a) If the margins are clean, you can work at 75 ppi,
+ *                although 100 ppi is safer.
+ *            (b) If there are vertical lines in the margins, do not
+ *                work below 150 ppi.  The signal from the text lines must
+ *                exceed that from the margin lines.
+ *      (4) Choosing the @confprior parameter depends on knowing something
+ *          about the source of image.  However, we're not using
+ *          real probabilities here, so its use is qualitative.
+ *          If landscape and portrait are equally likely, use
+ *          @confprior = 0.0.  If the likelihood of portrait (non-rotated)
+ *          is 100 times higher than that of landscape, we want to reduce
+ *          the chance that we rotate to landscape in a situation where
+ *          the landscape signal is accidentally larger than the
+ *          portrait signal.  To do this use a positive value of
+ *          @confprior; say 1.5.
+ */
+l_int32
+pixFindSkewOrthogonalRange(PIX        *pixs,
+                           l_float32  *pangle,
+                           l_float32  *pconf,
+                           l_int32     redsweep,
+                           l_int32     redsearch,
+                           l_float32   sweeprange,
+                           l_float32   sweepdelta,
+                           l_float32   minbsdelta,
+                           l_float32   confprior)
+{
+l_float32  angle1, conf1, score1, angle2, conf2, score2;
+PIX       *pixr;
+
+    PROCNAME("pixFindSkewOrthogonalRange");
+
+    if (pangle) *pangle = 0.0;
+    if (pconf) *pconf = 0.0;
+    if (!pangle || !pconf)
+        return ERROR_INT("&angle and/or &conf not defined", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+
+    pixFindSkewSweepAndSearchScorePivot(pixs, &angle1, &conf1, &score1,
+                                        redsweep, redsearch, 0.0,
+                                        sweeprange, sweepdelta, minbsdelta,
+                                        L_SHEAR_ABOUT_CORNER);
+    pixr = pixRotateOrth(pixs, 1);
+    pixFindSkewSweepAndSearchScorePivot(pixr, &angle2, &conf2, &score2,
+                                        redsweep, redsearch, 0.0,
+                                        sweeprange, sweepdelta, minbsdelta,
+                                        L_SHEAR_ABOUT_CORNER);
+    pixDestroy(&pixr);
+
+    if (conf1 > conf2 - confprior) {
+        *pangle = angle1;
+        *pconf = conf1;
+    } else {
+        *pangle = -90.0 + angle2;
+        *pconf = conf2;
+    }
+
+#if  DEBUG_PRINT_ORTH
+    fprintf(stderr, " About 0:  angle1 = %7.3f, conf1 = %7.3f, score1 = %f\n",
+            angle1, conf1, score1);
+    fprintf(stderr, " About 90: angle2 = %7.3f, conf2 = %7.3f, score2 = %f\n",
+            angle2, conf2, score2);
+    fprintf(stderr, " Final:    angle = %7.3f, conf = %7.3f\n", *pangle, *pconf);
+#endif  /* DEBUG_PRINT_ORTH */
+
+    return 0;
+}
+
+
+
+/*----------------------------------------------------------------*
+ *                  Differential square sum function              *
+ *----------------------------------------------------------------*/
+/*!
+ *  pixFindDifferentialSquareSum()
+ *
+ *      Input:  pixs
+ *              &sum  (<return> result)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) At the top and bottom, we skip:
+ *           - at least one scanline
+ *           - not more than 10% of the image height
+ *           - not more than 5% of the image width
+ */
+l_int32
+pixFindDifferentialSquareSum(PIX        *pixs,
+                             l_float32  *psum)
+{
+l_int32    i, n;
+l_int32    w, h, skiph, skip, nskip;
+l_float32  val1, val2, diff, sum;
+NUMA      *na;
+
+    PROCNAME("pixFindDifferentialSquareSum");
+
+    if (!psum)
+        return ERROR_INT("&sum not defined", procName, 1);
+    *psum = 0.0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+        /* Generate a number array consisting of the sum
+         * of pixels in each row of pixs */
+    if ((na = pixCountPixelsByRow(pixs, NULL)) == NULL)
+        return ERROR_INT("na not made", procName, 1);
+
+        /* Compute the number of rows at top and bottom to omit.
+         * We omit these to avoid getting a spurious signal from
+         * the top and bottom of a (nearly) all black image. */
+    w = pixGetWidth(pixs);
+    h = pixGetHeight(pixs);
+    skiph = (l_int32)(0.05 * w);  /* skip for max shear of 0.025 radians */
+    skip = L_MIN(h / 10, skiph);  /* don't remove more than 10% of image */
+    nskip = L_MAX(skip / 2, 1);  /* at top & bot; skip at least one line */
+
+        /* Sum the squares of differential row sums, on the
+         * allowed rows.  Note that nskip must be >= 1. */
+    n = numaGetCount(na);
+    sum = 0.0;
+    for (i = nskip; i < n - nskip; i++) {
+        numaGetFValue(na, i - 1, &val1);
+        numaGetFValue(na, i, &val2);
+        diff = val2 - val1;
+        sum += diff * diff;
+    }
+    numaDestroy(&na);
+    *psum = sum;
+    return 0;
+}
+
+
+/*----------------------------------------------------------------*
+ *                        Normalized square sum                   *
+ *----------------------------------------------------------------*/
+/*!
+ *  pixFindNormalizedSquareSum()
+ *
+ *      Input:  pixs
+ *              &hratio (<optional return> ratio of normalized horiz square sum
+ *                       to result if the pixel distribution were uniform)
+ *              &vratio (<optional return> ratio of normalized vert square sum
+ *                       to result if the pixel distribution were uniform)
+ *              &fract  (<optional return> ratio of fg pixels to total pixels)
+ *      Return: 0 if OK, 1 on error or if there are no fg pixels
+ *
+ *  Notes:
+ *      (1) Let the image have h scanlines and N fg pixels.
+ *          If the pixels were uniformly distributed on scanlines,
+ *          the sum of squares of fg pixels on each scanline would be
+ *          h * (N / h)^2.  However, if the pixels are not uniformly
+ *          distributed (e.g., for text), the sum of squares of fg
+ *          pixels will be larger.  We return in hratio and vratio the
+ *          ratio of these two values.
+ *      (2) If there are no fg pixels, hratio and vratio are returned as 0.0.
+ */
+l_int32
+pixFindNormalizedSquareSum(PIX        *pixs,
+                           l_float32  *phratio,
+                           l_float32  *pvratio,
+                           l_float32  *pfract)
+{
+l_int32    i, w, h, empty;
+l_float32  sum, sumsq, uniform, val;
+NUMA      *na;
+PIX       *pixt;
+
+    PROCNAME("pixFindNormalizedSquareSum");
+
+    if (phratio) *phratio = 0.0;
+    if (pvratio) *pvratio = 0.0;
+    if (pfract) *pfract = 0.0;
+    if (!phratio && !pvratio)
+        return ERROR_INT("nothing to do", procName, 1);
+    if (!pixs || pixGetDepth(pixs) != 1)
+        return ERROR_INT("pixs not defined or not 1 bpp", procName, 1);
+    pixGetDimensions(pixs, &w, &h, NULL);
+
+    empty = 0;
+    if (phratio) {
+        na = pixCountPixelsByRow(pixs, NULL);
+        numaGetSum(na, &sum);  /* fg pixels */
+        if (pfract) *pfract = sum / (l_float32)(w * h);
+        if (sum != 0.0) {
+            uniform = sum * sum / h;   /*  h*(sum / h)^2  */
+            sumsq = 0.0;
+            for (i = 0; i < h; i++) {
+                numaGetFValue(na, i, &val);
+                sumsq += val * val;
+            }
+            *phratio = sumsq / uniform;
+        } else {
+            empty = 1;
+        }
+        numaDestroy(&na);
+    }
+
+    if (pvratio) {
+        if (empty == 1) return 1;
+        pixt = pixRotateOrth(pixs, 1);
+        na = pixCountPixelsByRow(pixt, NULL);
+        numaGetSum(na, &sum);
+        if (pfract) *pfract = sum / (l_float32)(w * h);
+        if (sum != 0.0) {
+            uniform = sum * sum / w;
+            sumsq = 0.0;
+            for (i = 0; i < w; i++) {
+                numaGetFValue(na, i, &val);
+                sumsq += val * val;
+            }
+            *pvratio = sumsq / uniform;
+        } else {
+            empty = 1;
+        }
+        pixDestroy(&pixt);
+        numaDestroy(&na);
+    }
+
+    return empty;
+}
diff --git a/src/spixio.c b/src/spixio.c
new file mode 100644 (file)
index 0000000..7396cf8
--- /dev/null
@@ -0,0 +1,480 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  spixio.c
+ *
+ *    This does fast serialization of a pix in memory to file,
+ *    copying the raw data for maximum speed.  The underlying
+ *    function serializes it to memory, and it is wrapped to be
+ *    callable from standard pixRead() and pixWrite() file functions.
+ *
+ *      Reading spix from file
+ *           PIX        *pixReadStreamSpix()
+ *           l_int32     readHeaderSpix()
+ *           l_int32     freadHeaderSpix()
+ *           l_int32     sreadHeaderSpix()
+ *
+ *      Writing spix to file
+ *           l_int32     pixWriteStreamSpix()
+ *
+ *      Low-level serialization of pix to/from memory (uncompressed)
+ *           PIX        *pixReadMemSpix()
+ *           l_int32     pixWriteMemSpix()
+ *           l_int32     pixSerializeToMemory()
+ *           PIX        *pixDeserializeFromMemory()
+ *
+ *    Note: these functions have not been extensively tested for fuzzing
+ *    (bad input data that can result in, e.g., memory faults).
+ *    The spix serialization format is only defined here, in leptonica.
+ *    The image data is uncompressed and the serialization is not intended
+ *    to be a secure file format from untrusted sources.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+    /* Image dimension limits */
+static const l_int32  L_MAX_ALLOWED_WIDTH = 1000000;
+static const l_int32  L_MAX_ALLOWED_HEIGHT = 1000000;
+static const l_int64  L_MAX_ALLOWED_AREA = 400000000LL;
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_SERIALIZE      0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*-----------------------------------------------------------------------*
+ *                          Reading spix from file                       *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixReadStreamSpix()
+ *
+ *      Input:  stream
+ *      Return: pix, or null on error.
+ *
+ *  Notes:
+ *      (1) If called from pixReadStream(), the stream is positioned
+ *          at the beginning of the file.
+ */
+PIX *
+pixReadStreamSpix(FILE  *fp)
+{
+size_t    nbytes;
+l_uint8  *data;
+PIX      *pix;
+
+    PROCNAME("pixReadStreamSpix");
+
+    if (!fp)
+        return (PIX *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if ((data = l_binaryReadStream(fp, &nbytes)) == NULL)
+        return (PIX *)ERROR_PTR("data not read", procName, NULL);
+    if ((pix = pixReadMemSpix(data, nbytes)) == NULL) {
+        LEPT_FREE(data);
+        return (PIX *)ERROR_PTR("pix not made", procName, NULL);
+    }
+
+    LEPT_FREE(data);
+    return pix;
+}
+
+
+/*!
+ *  readHeaderSpix()
+ *
+ *      Input:  filename
+ *              &width (<return>)
+ *              &height (<return>)
+ *              &bps (<return>, bits/sample)
+ *              &spp (<return>, samples/pixel)
+ *              &iscmap (<optional return>; input NULL to ignore)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If there is a colormap, iscmap is returned as 1; else 0.
+ */
+l_int32
+readHeaderSpix(const char *filename,
+               l_int32    *pwidth,
+               l_int32    *pheight,
+               l_int32    *pbps,
+               l_int32    *pspp,
+               l_int32    *piscmap)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("readHeaderSpix");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!pwidth || !pheight || !pbps || !pspp)
+        return ERROR_INT("input ptr(s) not defined", procName, 1);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT("image file not found", procName, 1);
+    ret = freadHeaderSpix(fp, pwidth, pheight, pbps, pspp, piscmap);
+    fclose(fp);
+    return ret;
+}
+
+
+/*!
+ *  freadHeaderSpix()
+ *
+ *      Input:  stream
+ *              &width (<return>)
+ *              &height (<return>)
+ *              &bps (<return>, bits/sample)
+ *              &spp (<return>, samples/pixel)
+ *              &iscmap (<optional return>; input NULL to ignore)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If there is a colormap, iscmap is returned as 1; else 0.
+ */
+l_int32
+freadHeaderSpix(FILE     *fp,
+                l_int32  *pwidth,
+                l_int32  *pheight,
+                l_int32  *pbps,
+                l_int32  *pspp,
+                l_int32  *piscmap)
+{
+l_int32    nbytes, ret;
+l_uint32  *data;
+
+    PROCNAME("freadHeaderSpix");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!pwidth || !pheight || !pbps || !pspp)
+        return ERROR_INT("input ptr(s) not defined", procName, 1);
+
+    nbytes = fnbytesInFile(fp);
+    if (nbytes < 32)
+        return ERROR_INT("file too small to be spix", procName, 1);
+    if ((data = (l_uint32 *)LEPT_CALLOC(6, sizeof(l_uint32))) == NULL)
+        return ERROR_INT("LEPT_CALLOC fail for data", procName, 1);
+    if (fread(data, 4, 6, fp) != 6)
+        return ERROR_INT("error reading data", procName, 1);
+    ret = sreadHeaderSpix(data, pwidth, pheight, pbps, pspp, piscmap);
+    LEPT_FREE(data);
+    return ret;
+}
+
+
+/*!
+ *  sreadHeaderSpix()
+ *
+ *      Input:  data
+ *              &width (<return>)
+ *              &height (<return>)
+ *              &bps (<return>, bits/sample)
+ *              &spp (<return>, samples/pixel)
+ *              &iscmap (<optional return>; input NULL to ignore)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If there is a colormap, iscmap is returned as 1; else 0.
+ */
+l_int32
+sreadHeaderSpix(const l_uint32  *data,
+                l_int32         *pwidth,
+                l_int32         *pheight,
+                l_int32         *pbps,
+                l_int32         *pspp,
+                l_int32         *piscmap)
+{
+char    *id;
+l_int32  d, ncolors;
+
+    PROCNAME("sreadHeaderSpix");
+
+    if (!data)
+        return ERROR_INT("data not defined", procName, 1);
+    if (!pwidth || !pheight || !pbps || !pspp)
+        return ERROR_INT("input ptr(s) not defined", procName, 1);
+    *pwidth = *pheight = *pbps = *pspp = 0;
+    if (piscmap)
+      *piscmap = 0;
+
+        /* Check file id */
+    id = (char *)data;
+    if (id[0] != 's' || id[1] != 'p' || id[2] != 'i' || id[3] != 'x')
+        return ERROR_INT("not a valid spix file", procName, 1);
+
+    *pwidth = data[1];
+    *pheight = data[2];
+    d = data[3];
+    if (d <= 16) {
+      *pbps = d;
+      *pspp = 1;
+    } else {
+      *pbps = 8;
+      *pspp = d / 8;  /* if the pix is 32 bpp, call it 4 samples */
+    }
+    ncolors = data[5];
+    if (piscmap)
+        *piscmap = (ncolors == 0) ? 0 : 1;
+
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                            Writing spix to file                       *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixWriteStreamSpix()
+ *
+ *      Input:  stream
+ *              pix
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixWriteStreamSpix(FILE  *fp,
+                   PIX   *pix)
+{
+l_uint8  *data;
+size_t    size;
+
+    PROCNAME("pixWriteStreamSpix");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    if (pixWriteMemSpix(&data, &size, pix))
+        return ERROR_INT("failure to write pix to memory", procName, 1);
+    fwrite(data, 1, size, fp);
+    LEPT_FREE(data);
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *       Low-level serialization of pix to/from memory (uncompressed)    *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  pixReadMemSpix()
+ *
+ *      Input:  data (const; uncompressed)
+ *              size (of data)
+ *      Return: pix, or null on error
+ */
+PIX *
+pixReadMemSpix(const l_uint8  *data,
+               size_t          size)
+{
+    return pixDeserializeFromMemory((l_uint32 *)data, size);
+}
+
+
+/*!
+ *  pixWriteMemSpix()
+ *
+ *      Input:  &data (<return> data of serialized, uncompressed pix)
+ *              &size (<return> size of returned data)
+ *              pix (all depths; colormap OK)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+pixWriteMemSpix(l_uint8  **pdata,
+                size_t    *psize,
+                PIX       *pix)
+{
+    return pixSerializeToMemory(pix, (l_uint32 **)pdata, psize);
+}
+
+
+/*!
+ *  pixSerializeToMemory()
+ *
+ *      Input:  pixs (all depths, colormap OK)
+ *              &data (<return> serialized data in memory)
+ *              &nbytes (<return> number of bytes in data string)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This does a fast serialization of the principal elements
+ *          of the pix, as follows:
+ *            "spix"    (4 bytes) -- ID for file type
+ *            w         (4 bytes)
+ *            h         (4 bytes)
+ *            d         (4 bytes)
+ *            wpl       (4 bytes)
+ *            ncolors   (4 bytes) -- in colormap; 0 if there is no colormap
+ *            cdata     (4 * ncolors)  -- size of serialized colormap array
+ *            rdatasize (4 bytes) -- size of serialized raster data
+ *                                   = 4 * wpl * h
+ *            rdata     (rdatasize)
+ */
+l_int32
+pixSerializeToMemory(PIX        *pixs,
+                     l_uint32  **pdata,
+                     size_t     *pnbytes)
+{
+char      *id;
+l_int32    w, h, d, wpl, rdatasize, ncolors, nbytes, index;
+l_uint8   *cdata;  /* data in colormap array (4 bytes/color table entry) */
+l_uint32  *data;
+l_uint32  *rdata;  /* data in pix raster */
+PIXCMAP   *cmap;
+
+    PROCNAME("pixSerializeToMemory");
+
+    if (!pdata || !pnbytes)
+        return ERROR_INT("&data and &nbytes not both defined", procName, 1);
+    *pdata = NULL;
+    *pnbytes = 0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    pixGetDimensions(pixs, &w, &h, &d);
+    wpl = pixGetWpl(pixs);
+    rdata = pixGetData(pixs);
+    rdatasize = 4 * wpl * h;
+    ncolors = 0;
+    cdata = NULL;
+    if ((cmap = pixGetColormap(pixs)) != NULL)
+        pixcmapSerializeToMemory(cmap, 4, &ncolors, &cdata);
+
+    nbytes = 24 + 4 * ncolors + 4 + rdatasize;
+    if ((data = (l_uint32 *)LEPT_CALLOC(nbytes / 4, sizeof(l_uint32))) == NULL)
+        return ERROR_INT("data not made", procName, 1);
+    *pdata = data;
+    *pnbytes = nbytes;
+    id = (char *)data;
+    id[0] = 's';
+    id[1] = 'p';
+    id[2] = 'i';
+    id[3] = 'x';
+    data[1] = w;
+    data[2] = h;
+    data[3] = d;
+    data[4] = wpl;
+    data[5] = ncolors;
+    if (ncolors > 0)
+        memcpy((char *)(data + 6), (char *)cdata, 4 * ncolors);
+    index = 6 + ncolors;
+    data[index] = rdatasize;
+    memcpy((char *)(data + index + 1), (char *)rdata, rdatasize);
+
+#if  DEBUG_SERIALIZE
+    fprintf(stderr, "Serialize:   "
+            "raster size = %d, ncolors in cmap = %d, total bytes = %d\n",
+            rdatasize, ncolors, nbytes);
+#endif  /* DEBUG_SERIALIZE */
+
+    LEPT_FREE(cdata);
+    return 0;
+}
+
+
+/*!
+ *  pixDeserializeFromMemory()
+ *
+ *      Input:  data (serialized data in memory)
+ *              nbytes (number of bytes in data string)
+ *      Return: pix, or NULL on error
+ *
+ *  Notes:
+ *      (1) See pixSerializeToMemory() for the binary format.
+ *      (2) Note the image size limits.
+ */
+PIX *
+pixDeserializeFromMemory(const l_uint32  *data,
+                         size_t           nbytes)
+{
+char      *id;
+l_int32    w, h, d, pixdata_size, memdata_size, imdata_size, ncolors;
+l_uint32  *imdata;  /* data in pix raster */
+PIX       *pix1, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixDeserializeFromMemory");
+
+    if (!data)
+        return (PIX *)ERROR_PTR("data not defined", procName, NULL);
+    if (nbytes < 28)
+        return (PIX *)ERROR_PTR("invalid data", procName, NULL);
+
+    id = (char *)data;
+    if (id[0] != 's' || id[1] != 'p' || id[2] != 'i' || id[3] != 'x')
+        return (PIX *)ERROR_PTR("invalid id string", procName, NULL);
+    w = data[1];
+    h = data[2];
+    d = data[3];
+    ncolors = data[5];
+    imdata_size = data[6 + ncolors];
+
+        /* Sanity checks on the amount of image data */
+    if (w < 1 || w > L_MAX_ALLOWED_WIDTH)
+        return (PIX *)ERROR_PTR("invalid width", procName, NULL);
+    if (h < 1 || h > L_MAX_ALLOWED_HEIGHT)
+        return (PIX *)ERROR_PTR("invalid height", procName, NULL);
+    if (1LL * w * h > L_MAX_ALLOWED_AREA)
+        return (PIX *)ERROR_PTR("area too large", procName, NULL);
+    if (ncolors < 0 || ncolors > 256)
+        return (PIX *)ERROR_PTR("invalid ncolors", procName, NULL);
+    pix1 = pixCreateHeader(w, h, d);  /* just make the header */
+    if (!pix1)
+        return (PIX *)ERROR_PTR("failed to make header", procName, NULL);
+    pixdata_size = 4 * h * pixGetWpl(pix1);
+    memdata_size = nbytes - 24 - 4 * ncolors - 4;
+    imdata_size = data[6 + ncolors];
+    pixDestroy(&pix1);
+    if (pixdata_size != memdata_size || pixdata_size != imdata_size) {
+        L_ERROR("pixdata_size = %d, memdata_size = %d, imdata_size = %d "
+                "not all equal!\n", procName, pixdata_size, memdata_size,
+                imdata_size);
+        return NULL;
+    }
+
+    if ((pixd = pixCreate(w, h, d)) == NULL)
+        return (PIX *)ERROR_PTR("pix not made", procName, NULL);
+
+    if (ncolors > 0) {
+        cmap = pixcmapDeserializeFromMemory((l_uint8 *)(&data[6]), 4, ncolors);
+        if (!cmap)
+            return (PIX *)ERROR_PTR("cmap not made", procName, NULL);
+        pixSetColormap(pixd, cmap);
+    }
+
+    imdata = pixGetData(pixd);
+    memcpy((char *)imdata, (char *)(data + 7 + ncolors), imdata_size);
+
+#if  DEBUG_SERIALIZE
+    fprintf(stderr, "Deserialize: "
+            "raster size = %d, ncolors in cmap = %d, total bytes = %lu\n",
+            imdata_size, ncolors, nbytes);
+#endif  /* DEBUG_SERIALIZE */
+
+    return pixd;
+}
diff --git a/src/stack.c b/src/stack.c
new file mode 100644 (file)
index 0000000..0c7ee40
--- /dev/null
@@ -0,0 +1,283 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  stack.c
+ *
+ *      Generic stack
+ *
+ *      The lstack is an array of void * ptrs, onto which
+ *      objects can be stored.  At any time, the number of
+ *      stored objects is lstack->n.  The object at the bottom
+ *      of the lstack is at array[0]; the object at the top of
+ *      the lstack is at array[n-1].  New objects are added
+ *      to the top of the lstack; i.e., the first available
+ *      location, which is at array[n].  The lstack is expanded
+ *      by doubling, when needed.  Objects are removed
+ *      from the top of the lstack.  When an attempt is made
+ *      to remove an object from an empty lstack, the result is null.
+ *
+ *      Create/Destroy
+ *           L_STACK        *lstackCreate()
+ *           void            lstackDestroy()
+ *
+ *      Accessors
+ *           l_int32         lstackAdd()
+ *           void           *lstackRemove()
+ *           static l_int32  lstackExtendArray()
+ *           l_int32         lstackGetCount()
+ *
+ *      Text description
+ *           l_int32         lstackPrint()
+ */
+
+#include "allheaders.h"
+
+static const l_int32  INITIAL_PTR_ARRAYSIZE = 20;
+
+    /* Static function */
+static l_int32 lstackExtendArray(L_STACK *lstack);
+
+
+/*---------------------------------------------------------------------*
+ *                          Create/Destroy                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  lstackCreate()
+ *
+ *      Input:  nalloc (initial ptr array size; use 0 for default)
+ *      Return: lstack, or null on error
+ */
+L_STACK *
+lstackCreate(l_int32  nalloc)
+{
+L_STACK  *lstack;
+
+    PROCNAME("lstackCreate");
+
+    if (nalloc <= 0)
+        nalloc = INITIAL_PTR_ARRAYSIZE;
+
+    if ((lstack = (L_STACK *)LEPT_CALLOC(1, sizeof(L_STACK))) == NULL)
+        return (L_STACK *)ERROR_PTR("lstack not made", procName, NULL);
+
+    if ((lstack->array = (void **)LEPT_CALLOC(nalloc, sizeof(void *))) == NULL)
+        return (L_STACK *)ERROR_PTR("lstack array not made", procName, NULL);
+
+    lstack->nalloc = nalloc;
+    lstack->n = 0;
+
+    return lstack;
+}
+
+
+/*!
+ *  lstackDestroy()
+ *
+ *      Input:  &lstack (<to be nulled>)
+ *              freeflag (TRUE to free each remaining struct in the array)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) If freeflag is TRUE, frees each struct in the array.
+ *      (2) If freeflag is FALSE but there are elements on the array,
+ *          gives a warning and destroys the array.  This will
+ *          cause a memory leak of all the items that were on the lstack.
+ *          So if the items require their own destroy function, they
+ *          must be destroyed before the lstack.
+ *      (3) To destroy the lstack, we destroy the ptr array, then
+ *          the lstack, and then null the contents of the input ptr.
+ */
+void
+lstackDestroy(L_STACK  **plstack,
+              l_int32    freeflag)
+{
+void     *item;
+L_STACK  *lstack;
+
+    PROCNAME("lstackDestroy");
+
+    if (plstack == NULL) {
+        L_WARNING("ptr address is NULL\n", procName);
+        return;
+    }
+    if ((lstack = *plstack) == NULL)
+        return;
+
+    if (freeflag) {
+        while(lstack->n > 0) {
+            item = lstackRemove(lstack);
+            LEPT_FREE(item);
+        }
+    } else if (lstack->n > 0) {
+        L_WARNING("memory leak of %d items in lstack\n", procName, lstack->n);
+    }
+
+    if (lstack->auxstack)
+        lstackDestroy(&lstack->auxstack, freeflag);
+
+    if (lstack->array)
+        LEPT_FREE(lstack->array);
+    LEPT_FREE(lstack);
+    *plstack = NULL;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                               Accessors                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  lstackAdd()
+ *
+ *      Input:  lstack
+ *              item to be added to the lstack
+ *      Return: 0 if OK; 1 on error.
+ */
+l_int32
+lstackAdd(L_STACK  *lstack,
+          void     *item)
+{
+    PROCNAME("lstackAdd");
+
+    if (!lstack)
+        return ERROR_INT("lstack not defined", procName, 1);
+    if (!item)
+        return ERROR_INT("item not defined", procName, 1);
+
+        /* Do we need to extend the array? */
+    if (lstack->n >= lstack->nalloc)
+        lstackExtendArray(lstack);
+
+        /* Store the new pointer */
+    lstack->array[lstack->n] = (void *)item;
+    lstack->n++;
+
+    return 0;
+}
+
+
+/*!
+ *  lstackRemove()
+ *
+ *      Input:  lstack
+ *      Return: ptr to item popped from the top of the lstack,
+ *              or null if the lstack is empty or on error
+ */
+void *
+lstackRemove(L_STACK  *lstack)
+{
+void  *item;
+
+    PROCNAME("lstackRemove");
+
+    if (!lstack)
+        return ERROR_PTR("lstack not defined", procName, NULL);
+
+    if (lstack->n == 0)
+        return NULL;
+
+    lstack->n--;
+    item = lstack->array[lstack->n];
+
+    return item;
+}
+
+
+/*!
+ *  lstackExtendArray()
+ *
+ *      Input:  lstack
+ *      Return: 0 if OK; 1 on error
+ */
+static l_int32
+lstackExtendArray(L_STACK  *lstack)
+{
+    PROCNAME("lstackExtendArray");
+
+    if (!lstack)
+        return ERROR_INT("lstack not defined", procName, 1);
+
+    if ((lstack->array = (void **)reallocNew((void **)&lstack->array,
+                              sizeof(void *) * lstack->nalloc,
+                              2 * sizeof(void *) * lstack->nalloc)) == NULL)
+        return ERROR_INT("new lstack array not defined", procName, 1);
+
+    lstack->nalloc = 2 * lstack->nalloc;
+    return 0;
+}
+
+
+/*!
+ *  lstackGetCount()
+ *
+ *      Input:  lstack
+ *      Return: count, or 0 on error
+ */
+l_int32
+lstackGetCount(L_STACK  *lstack)
+{
+    PROCNAME("lstackGetCount");
+
+    if (!lstack)
+        return ERROR_INT("lstack not defined", procName, 1);
+
+    return lstack->n;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                            Debug output                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  lstackPrint()
+ *
+ *      Input:  stream
+ *              lstack
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+lstackPrint(FILE     *fp,
+            L_STACK  *lstack)
+{
+l_int32  i;
+
+    PROCNAME("lstackPrint");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!lstack)
+        return ERROR_INT("lstack not defined", procName, 1);
+
+    fprintf(fp, "\n Stack: nalloc = %d, n = %d, array = %p\n",
+            lstack->nalloc, lstack->n, lstack->array);
+    for (i = 0; i < lstack->n; i++)
+        fprintf(fp,   "array[%d] = %p\n", i, lstack->array[i]);
+
+    return 0;
+}
diff --git a/src/stack.h b/src/stack.h
new file mode 100644 (file)
index 0000000..a200c53
--- /dev/null
@@ -0,0 +1,66 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_STACK_H
+#define  LEPTONICA_STACK_H
+
+/*
+ *  stack.h
+ *
+ *       Expandable pointer stack for arbitrary void* data.
+ *
+ *       The L_Stack is an array of void * ptrs, onto which arbitrary
+ *       objects can be stored.  At any time, the number of
+ *       stored objects is stack->n.  The object at the bottom
+ *       of the stack is at array[0]; the object at the top of
+ *       the stack is at array[n-1].  New objects are added
+ *       to the top of the stack, at the first available location,
+ *       which is array[n].  Objects are removed from the top of the
+ *       stack.  When an attempt is made to remove an object from an
+ *       empty stack, the result is null.   When the stack becomes
+ *       filled, so that n = nalloc, the size is doubled.
+ *
+ *       The auxiliary stack can be used to store and remove
+ *       objects for re-use.  It must be created by a separate
+ *       call to pstackCreate().  [Just imagine the chaos if
+ *       pstackCreate() created the auxiliary stack!]   
+ *       pstackDestroy() checks for the auxiliary stack and removes it.
+ */
+
+
+    /* Note that array[n] is the first null ptr in the array */
+struct L_Stack
+{
+    l_int32          nalloc;       /* size of ptr array              */
+    l_int32          n;            /* number of stored elements      */
+    void           **array;        /* ptr array                      */
+    struct L_Stack  *auxstack;     /* auxiliary stack                */
+};
+typedef struct L_Stack  L_STACK;
+
+
+#endif /*  LEPTONICA_STACK_H */
+
diff --git a/src/stringcode.c b/src/stringcode.c
new file mode 100644 (file)
index 0000000..d3cc4f8
--- /dev/null
@@ -0,0 +1,783 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  stringcode.c
+ *
+ *   Generation of code for storing and extracting serializable
+ *   leptonica objects (such as pixa, recog, ...).
+ *
+ *   The input is a set of files with serialized data.
+ *   The output is two files, that must be compiled and linked:
+ *     - autogen.*.c: code for base64 unencoding the strings and
+ *                    deserializing the result.
+ *     - autogen.*.h: function prototypes and base64 encoded strings
+ *                    of the input data
+ *
+ *   This should work for any data structures in leptonica that have
+ *   *Write() and *Read() serialization functions.  An array of 20
+ *   of these, including the Pix, is given below.  (The Pix is a special
+ *   case, because it is serialized by standardized compression
+ *   techniques, instead of a file format determined by leptonica.)
+ *
+ *   Each time the generator function is invoked, three sets of strings are
+ *   produced, which are written into their respective string arrays:
+ *     - string of serialized, gzipped and base 64 encoded data
+ *     - case string for base64 decoding, gunzipping and deserialization,
+ *       to return the data struct in memory
+ *     - description string for selecting which struct to return
+ *   To create the two output files, a finalize function is invoked.
+ *
+ *   There are two ways to do this, both shown in prog/autogentest1.c.
+ *     - Explicitly call strcodeGenerate() for each file with the
+ *       serialized data structure, followed by strcodeFinalize().
+ *     - Put the filenames of the serialized data structures in a file,
+ *       and call strcodeCreateFromFile().
+ *
+ *   The generated code in autogen.X.c and autogen.X.h (where X is an
+ *   integer supplied to strcodeCreate()) is then compiled, and the
+ *   original data can be regenerated using the function l_autodecode_X().
+ *   A test example is found in the two prog files:
+ *       prog/autogentest1.c  -- generates autogen.137.c, autogen.137.h
+ *       prog/autogentest2.c  -- uses autogen.137.c, autogen.137.h
+ *   In general, the generator (e.g., autogentest1) would be compiled and
+ *   run before compiling and running the application (e.g., autogentest2).
+ *
+ *       L_STRCODE       *strcodeCreate()
+ *       static void      strcodeDestroy()    (called as part of finalize)
+ *       void             strcodeCreateFromFile()
+ *       l_int32          strcodeGenerate()
+ *       void             strcodeFinalize()
+ *       l_int32          l_getStructnameFromFile()   (useful externally)
+ *
+ *   Static helpers
+ *       static l_int32   l_getIndexFromType()
+ *       static l_int32   l_getIndexFromStructname()
+ *       static l_int32   l_getIndexFromFile()
+ *       static char     *l_genDataString()
+ *       static char     *l_genCaseString()
+ *       static char     *l_genDescrString()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+#include "stringcode.h"
+
+#define TEMPLATE1  "stringtemplate1.txt"  /* for assembling autogen.*.c */
+#define TEMPLATE2  "stringtemplate2.txt"  /* for assembling autogen.*.h */
+
+    /* Associations between names and functions */
+struct L_GenAssoc
+{
+    l_int32  index;
+    char     type[16];        /* e.g., "PIXA" */
+    char     structname[16];  /* e.g., "Pixa" */
+    char     reader[16];      /* e.g., "pixaRead" */
+};
+
+    /* Serializable data types */
+static const l_int32  l_ntypes = 20;
+static const struct L_GenAssoc l_assoc[] = {
+    {0,  "INVALID",     "invalid",     "invalid"       },
+    {1,  "BOXA",        "Boxa",        "boxaRead"      },
+    {2,  "BOXAA",       "Boxaa",       "boxaaRead"     },
+    {3,  "L_DEWARP",    "Dewarp",      "dewarpRead"    },
+    {4,  "L_DEWARPA",   "Dewarpa",     "dewarpaRead"   },
+    {5,  "L_DNA",       "L_Dna",       "l_dnaRead"     },
+    {6,  "L_DNAA",      "L_Dnaa",      "l_dnaaRead"    },
+    {7,  "DPIX",        "DPix",        "dpixRead"      },
+    {8,  "FPIX",        "FPix",        "fpixRead"      },
+    {9,  "NUMA",        "Numa",        "numaRead"      },
+    {10, "NUMAA",       "Numaa",       "numaaRead"     },
+    {11, "PIX",         "Pix",         "pixRead"       },
+    {12, "PIXA",        "Pixa",        "pixaRead"      },
+    {13, "PIXAA",       "Pixaa",       "pixaaRead"     },
+    {14, "PIXACOMP",    "Pixacomp",    "pixacompRead"  },
+    {15, "PIXCMAP",     "Pixcmap",     "pixcmapRead"   },
+    {16, "PTA",         "Pta",         "ptaRead"       },
+    {17, "PTAA",        "Ptaa",        "ptaaRead"      },
+    {18, "RECOG",       "Recog",       "recogRead"     },
+    {19, "RECOGA",      "Recoga",      "recogaRead"    },
+    {20, "SARRAY",      "Sarray",      "sarrayRead"    }
+};
+
+static l_int32 l_getIndexFromType(const char *type, l_int32 *pindex);
+static l_int32 l_getIndexFromStructname(const char *sn, l_int32 *pindex);
+static l_int32 l_getIndexFromFile(const char *file, l_int32 *pindex);
+static char *l_genDataString(const char *filein, l_int32 ifunc);
+static char *l_genCaseString(l_int32 ifunc, l_int32 itype);
+static char *l_genDescrString(const char *filein, l_int32 ifunc, l_int32 itype);
+
+
+/*---------------------------------------------------------------------*/
+/*                         Stringcode functions                        */
+/*---------------------------------------------------------------------*/
+/*!
+ *  strcodeCreate()
+ *
+ *      Input:  fileno (integer that labels the two output files)
+ *      Return: initialized L_StrCode, or null on error
+ *
+ *  Notes:
+ *      (1) This struct exists to build two files containing code for
+ *          any number of data objects.  The two files are named
+ *             autogen.<fileno>.c
+ *             autogen.<fileno>.h
+ */
+L_STRCODE *
+strcodeCreate(l_int32  fileno)
+{
+L_STRCODE  *strcode;
+
+    PROCNAME("strcodeCreate");
+
+    lept_mkdir("lept/auto");
+
+    if ((strcode = (L_STRCODE *)LEPT_CALLOC(1, sizeof(L_STRCODE))) == NULL)
+        return (L_STRCODE *)ERROR_PTR("strcode not made", procName, NULL);
+
+    strcode->fileno = fileno;
+    strcode->function = sarrayCreate(0);
+    strcode->data = sarrayCreate(0);
+    strcode->descr = sarrayCreate(0);
+    return strcode;
+}
+
+
+/*!
+ *  strcodeDestroy()
+ *
+ *      Input:  &strcode (strcode is set to null after destroying the sarrays)
+ *      Return: void
+ */
+static void
+strcodeDestroy(L_STRCODE  **pstrcode)
+{
+L_STRCODE  *strcode;
+
+    PROCNAME("strcodeDestroy");
+
+    if (pstrcode == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((strcode = *pstrcode) == NULL)
+        return;
+
+    sarrayDestroy(&strcode->function);
+    sarrayDestroy(&strcode->data);
+    sarrayDestroy(&strcode->descr);
+    LEPT_FREE(strcode);
+    *pstrcode = NULL;
+    return;
+}
+
+
+/*!
+ *  strcodeCreateFromFile()
+ *
+ *      Input:  filein (containing filenames of serialized data)
+ *              fileno (integer that labels the two output files)
+ *              outdir (<optional> if null, files are made in /tmp/lept/auto)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The @filein has one filename on each line.
+ *          Comment lines begin with "#".
+ *      (2) The output is 2 files:
+ *             autogen.<fileno>.c
+ *             autogen.<fileno>.h
+ */
+l_int32
+strcodeCreateFromFile(const char  *filein,
+                      l_int32      fileno,
+                      const char  *outdir)
+{
+char        *fname;
+const char  *type;
+l_uint8     *data;
+size_t       nbytes;
+l_int32      i, n, index;
+SARRAY      *sa;
+L_STRCODE   *strcode;
+
+    PROCNAME("strcodeCreateFromFile");
+
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+
+    if ((data = l_binaryRead(filein, &nbytes)) == NULL)
+        return ERROR_INT("data not read from file", procName, 1);
+    sa = sarrayCreateLinesFromString((char *)data, 0);
+    LEPT_FREE(data);
+    if (!sa)
+        return ERROR_INT("sa not made", procName, 1);
+    if ((n = sarrayGetCount(sa)) == 0) {
+        sarrayDestroy(&sa);
+        return ERROR_INT("no filenames in the file", procName, 1);
+    }
+
+    strcode = strcodeCreate(fileno);
+
+    for (i = 0; i < n; i++) {
+        fname = sarrayGetString(sa, i, L_NOCOPY);
+        if (fname[0] == '#') continue;
+        if (l_getIndexFromFile(fname, &index)) {
+            L_ERROR("File %s has no recognizable type\n", procName, fname);
+        } else {
+            type = l_assoc[index].type;
+            L_INFO("File %s is type %s\n", procName, fname, type);
+            strcodeGenerate(strcode, fname, type);
+        }
+    }
+    strcodeFinalize(&strcode, outdir);
+    return 0;
+}
+
+
+/*!
+ *  strcodeGenerate()
+ *
+ *      Input:  strcode (for accumulating data)
+ *              filein (input file with serialized data)
+ *              type (of data; use the typedef string)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) The generated function name is
+ *            l_autodecode_<fileno>()
+ *          where <fileno> is the index label for the pair of output files.
+ *      (2) To deserialize this data, the function is called with the
+ *          argument 'ifunc', which increments each time strcodeGenerate()
+ *          is called.
+ */
+l_int32
+strcodeGenerate(L_STRCODE   *strcode,
+                const char  *filein,
+                const char  *type)
+{
+char    *strdata, *strfunc, *strdescr;
+l_int32  itype;
+
+    PROCNAME("strcodeGenerate");
+
+    if (!strcode)
+        return ERROR_INT("strcode not defined", procName, 1);
+    if (!filein)
+        return ERROR_INT("filein not defined", procName, 1);
+    if (!type)
+        return ERROR_INT("type not defined", procName, 1);
+
+        /* Get the index corresponding to type and validate */
+    if (l_getIndexFromType(type, &itype) == 1)
+        return ERROR_INT("data type unknown", procName, 1);
+
+        /* Generate the encoded data string */
+    if ((strdata = l_genDataString(filein, strcode->ifunc)) == NULL)
+        return ERROR_INT("strdata not made", procName, 1);
+    sarrayAddString(strcode->data, strdata, L_INSERT);
+
+        /* Generate the case data for the decoding function */
+    strfunc = l_genCaseString(strcode->ifunc, itype);
+    sarrayAddString(strcode->function, strfunc, L_INSERT);
+
+        /* Generate row of table for function type selection */
+    strdescr = l_genDescrString(filein, strcode->ifunc, itype);
+    sarrayAddString(strcode->descr, strdescr, L_INSERT);
+
+    strcode->n++;
+    strcode->ifunc++;
+    return 0;
+}
+
+
+/*!
+ *  strcodeFinalize()
+ *
+ *      Input: &strcode (destroys after .c and .h files have been generated)
+ *              outdir (<optional> if null, files are made in /tmp/lept/auto)
+ *      Return: void
+ */
+l_int32
+strcodeFinalize(L_STRCODE  **pstrcode,
+                const char  *outdir)
+{
+char        buf[256];
+char       *filestr, *casestr, *descr, *datastr, *realoutdir;
+l_int32     actstart, end, newstart, fileno, nbytes;
+size_t      size;
+L_STRCODE  *strcode;
+SARRAY     *sa1, *sa2, *sa3;
+
+    PROCNAME("strcodeFinalize");
+
+    lept_mkdir("lept/auto");
+
+    if (!pstrcode || *pstrcode == NULL)
+        return ERROR_INT("No input data", procName, 1);
+    strcode = *pstrcode;
+    if (!outdir) {
+        L_INFO("no outdir specified; writing to /tmp/lept/auto\n", procName);
+        realoutdir = stringNew("/tmp/lept/auto");
+    } else {
+        realoutdir = stringNew(outdir);
+    }
+
+    /* ------------------------------------------------------- */
+    /*              Make the output autogen.*.c file           */
+    /* ------------------------------------------------------- */
+
+       /* Make array of textlines from TEMPLATE1 */
+    if ((filestr = (char *)l_binaryRead(TEMPLATE1, &size)) == NULL)
+        return ERROR_INT("filestr not made", procName, 1);
+    if ((sa1 = sarrayCreateLinesFromString(filestr, 1)) == NULL)
+        return ERROR_INT("sa1 not made", procName, 1);
+    LEPT_FREE(filestr);
+
+    if ((sa3 = sarrayCreate(0)) == NULL)
+        return ERROR_INT("sa3 not made", procName, 1);
+
+        /* Copyright notice */
+    sarrayParseRange(sa1, 0, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa1, actstart, end);
+
+        /* File name comment */
+    fileno = strcode->fileno;
+    snprintf(buf, sizeof(buf), " *   autogen.%d.c", fileno);
+    sarrayAddString(sa3, buf, L_COPY);
+
+        /* More text */
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa1, actstart, end);
+
+        /* Description of function types by index */
+    descr = sarrayToString(strcode->descr, 1);
+    descr[strlen(descr) - 1] = '\0';
+    sarrayAddString(sa3, descr, L_INSERT);
+
+        /* Includes */
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa1, actstart, end);
+    snprintf(buf, sizeof(buf), "#include \"autogen.%d.h\"", fileno);
+    sarrayAddString(sa3, buf, L_COPY);
+
+        /* Header for auto-generated deserializers */
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa1, actstart, end);
+
+        /* Function name (as comment) */
+    snprintf(buf, sizeof(buf), " *  l_autodecode_%d()", fileno);
+    sarrayAddString(sa3, buf, L_COPY);
+
+        /* Input and return values */
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa1, actstart, end);
+
+        /* Function name */
+    snprintf(buf, sizeof(buf), "l_autodecode_%d(l_int32 index)", fileno);
+    sarrayAddString(sa3, buf, L_COPY);
+
+        /* Stack vars */
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa1, actstart, end);
+
+        /* Declaration of nfunc on stack */
+    snprintf(buf, sizeof(buf), "l_int32   nfunc = %d;\n", strcode->n);
+    sarrayAddString(sa3, buf, L_COPY);
+
+        /* Declaration of PROCNAME */
+    snprintf(buf, sizeof(buf), "    PROCNAME(\"l_autodecode_%d\");", fileno);
+    sarrayAddString(sa3, buf, L_COPY);
+
+        /* Test input variables */
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa1, actstart, end);
+
+        /* Insert case string */
+    casestr = sarrayToString(strcode->function, 0);
+    casestr[strlen(casestr) - 1] = '\0';
+    sarrayAddString(sa3, casestr, L_INSERT);
+
+        /* End of function */
+    sarrayParseRange(sa1, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa1, actstart, end);
+
+        /* Flatten to string and output to autogen*.c file */
+    if ((filestr = sarrayToString(sa3, 1)) == NULL)
+        return ERROR_INT("filestr from sa3 not made", procName, 1);
+    nbytes = strlen(filestr);
+    snprintf(buf, sizeof(buf), "%s/autogen.%d.c", realoutdir, fileno);
+    l_binaryWrite(buf, "w", filestr, nbytes);
+    LEPT_FREE(filestr);
+    sarrayDestroy(&sa1);
+    sarrayDestroy(&sa3);
+
+    /* ------------------------------------------------------- */
+    /*              Make the output autogen.*.h file           */
+    /* ------------------------------------------------------- */
+
+       /* Make array of textlines from TEMPLATE2 */
+    if ((filestr = (char *)l_binaryRead(TEMPLATE2, &size)) == NULL)
+        return ERROR_INT("filestr not made", procName, 1);
+    if ((sa2 = sarrayCreateLinesFromString(filestr, 1)) == NULL)
+        return ERROR_INT("sa2 not made", procName, 1);
+    LEPT_FREE(filestr);
+
+    if ((sa3 = sarrayCreate(0)) == NULL)
+        return ERROR_INT("sa3 not made", procName, 1);
+
+        /* Copyright notice */
+    sarrayParseRange(sa2, 0, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* File name comment */
+    snprintf(buf, sizeof(buf), " *   autogen.%d.h", fileno);
+    sarrayAddString(sa3, buf, L_COPY);
+
+        /* More text */
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Beginning header protection */
+    snprintf(buf, sizeof(buf), "#ifndef  LEPTONICA_AUTOGEN_%d_H\n"
+                               "#define  LEPTONICA_AUTOGEN_%d_H",
+             fileno, fileno);
+    sarrayAddString(sa3, buf, L_COPY);
+
+        /* Prototype header text */
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Prototype declaration */
+    snprintf(buf, sizeof(buf), "void *l_autodecode_%d(l_int32 index);", fileno);
+    sarrayAddString(sa3, buf, L_COPY);
+
+        /* Prototype trailer text */
+    sarrayParseRange(sa2, newstart, &actstart, &end, &newstart, "--", 0);
+    sarrayAppendRange(sa3, sa2, actstart, end);
+
+        /* Insert serialized data strings */
+    datastr = sarrayToString(strcode->data, 1);
+    datastr[strlen(datastr) - 1] = '\0';
+    sarrayAddString(sa3, datastr, L_INSERT);
+
+        /* End header protection */
+    snprintf(buf, sizeof(buf), "#endif  /* LEPTONICA_AUTOGEN_%d_H */", fileno);
+    sarrayAddString(sa3, buf, L_COPY);
+
+        /* Flatten to string and output to autogen*.h file */
+    if ((filestr = sarrayToString(sa3, 1)) == NULL)
+        return ERROR_INT("filestr from sa3 not made", procName, 1);
+    nbytes = strlen(filestr);
+    snprintf(buf, sizeof(buf), "%s/autogen.%d.h", realoutdir, fileno);
+    l_binaryWrite(buf, "w", filestr, nbytes);
+    LEPT_FREE(filestr);
+    LEPT_FREE(realoutdir);
+    sarrayDestroy(&sa2);
+    sarrayDestroy(&sa3);
+
+        /* Cleanup */
+    strcodeDestroy(pstrcode);
+    return 0;
+}
+
+
+/*!
+ *  l_getStructnameFromFile()
+ *
+ *      Input:  filename
+ *              &sn (<return> structname; e.g., "Pixa")
+ *      Return: 0 if found, 1 on error.
+ */
+l_int32
+l_getStructnameFromFile(const char  *filename,
+                        char       **psn)
+{
+l_int32  index;
+
+
+    PROCNAME("l_getStructnameFromFile");
+
+    if (!psn)
+        return ERROR_INT("&sn not defined", procName, 1);
+    *psn = NULL;
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    if (l_getIndexFromFile(filename, &index))
+        return ERROR_INT("index not retrieved", procName, 1);
+    *psn = stringNew(l_assoc[index].structname);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*/
+/*                           Static helpers                            */
+/*---------------------------------------------------------------------*/
+/*!
+ *  l_getIndexFromType()
+ *
+ *      Input:  type (e.g., "PIXA")
+ *              &index (<return>)
+ *      Return: 0 if found, 1 if not.
+ *
+ *  Notes:
+ *      (1) For valid type, @found == true and @index > 0.
+ */
+static l_int32
+l_getIndexFromType(const char  *type,
+                    l_int32     *pindex)
+{
+l_int32  i, found;
+
+    PROCNAME("l_getIndexFromType");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = 0;
+    if (!type)
+        return ERROR_INT("type string not defined", procName, 1);
+
+    found = 0;
+    for (i = 1; i <= l_ntypes; i++) {
+        if (strcmp(type, l_assoc[i].type) == 0) {
+            found = 1;
+            *pindex = i;
+            break;
+        }
+    }
+    return !found;
+}
+
+
+/*!
+ *  l_getIndexFromStructname()
+ *
+ *      Input:  structname (e.g., "Pixa")
+ *              &index (<return>)
+ *      Return: 0 if found, 1 if not.
+ *
+ *  Notes:
+ *      (1) This is used to identify the type of serialized file;
+ *          the first word in the file is the structname.
+ *      (2) For valid structname, @found == true and @index > 0.
+ */
+static l_int32
+l_getIndexFromStructname(const char  *sn,
+                         l_int32     *pindex)
+{
+l_int32  i, found;
+
+    PROCNAME("l_getIndexFromStructname");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = 0;
+    if (!sn)
+        return ERROR_INT("sn string not defined", procName, 1);
+
+    found = 0;
+    for (i = 1; i <= l_ntypes; i++) {
+        if (strcmp(sn, l_assoc[i].structname) == 0) {
+            found = 1;
+            *pindex = i;
+            break;
+        }
+    }
+    return !found;
+}
+
+
+/*!
+ *  l_getIndexFromFile()
+ *
+ *      Input:  filename
+ *              &index (<return>)
+ *      Return: 0 if found, 1 on error.
+ */
+static l_int32
+l_getIndexFromFile(const char  *filename,
+                   l_int32     *pindex)
+{
+char     buf[256];
+char    *word;
+FILE    *fp;
+l_int32  notfound, format;
+SARRAY  *sa;
+
+    PROCNAME("l_getIndexFromFile");
+
+    if (!pindex)
+        return ERROR_INT("&index not defined", procName, 1);
+    *pindex = 0;
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+        /* Open the stream, read lines until you find one with more
+         * than a newline, and grab the first word. */
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    do {
+        if ((fgets(buf, sizeof(buf), fp)) == NULL) {
+            fclose(fp);
+            return ERROR_INT("fgets read fail", procName, 1);
+        }
+    } while (buf[0] == '\n');
+    fclose(fp);
+    sa = sarrayCreateWordsFromString(buf);
+    word = sarrayGetString(sa, 0, L_NOCOPY);
+
+        /* Find the index associated with the word.  If it is not
+         * found, test to see if the file is a compressed pix. */
+    notfound = l_getIndexFromStructname(word, pindex);
+    sarrayDestroy(&sa);
+    if (notfound) {  /* maybe a Pix */
+        if (findFileFormat(filename, &format) == 0) {
+            l_getIndexFromStructname("Pix", pindex);
+        } else {
+            return ERROR_INT("no file type identified", procName, 1);
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  l_genDataString()
+ *
+ *      Input:  filein (input file of serialized data)
+ *              ifunc (index into set of functions in output file)
+ *      Return: encoded ascii data string, or null on error reading from file
+ */
+static char *
+l_genDataString(const char  *filein,
+                l_int32      ifunc)
+{
+char      buf[80];
+char     *cdata1, *cdata2, *cdata3;
+l_uint8  *data1, *data2;
+l_int32   csize1, csize2;
+size_t    size1, size2;
+SARRAY   *sa;
+
+    PROCNAME("l_genDataString");
+
+    if (!filein)
+        return (char *)ERROR_PTR("filein not defined", procName, NULL);
+
+        /* Read it in, gzip it, encode, and reformat.  We gzip because some
+         * serialized data has a significant amount of ascii content. */
+    if ((data1 = l_binaryRead(filein, &size1)) == NULL)
+        return (char *)ERROR_PTR("bindata not returned", procName, NULL);
+    data2 = zlibCompress(data1, size1, &size2);
+    cdata1 = encodeBase64(data2, size2, &csize1);
+    cdata2 = reformatPacked64(cdata1, csize1, 4, 72, 1, &csize2);
+    LEPT_FREE(data1);
+    LEPT_FREE(data2);
+    LEPT_FREE(cdata1);
+
+        /* Prepend the string declaration signature and put it together */
+    sa = sarrayCreate(3);
+    snprintf(buf, sizeof(buf), "static const char *l_strdata_%d =\n", ifunc);
+    sarrayAddString(sa, buf, L_COPY);
+    sarrayAddString(sa, cdata2, L_INSERT);
+    sarrayAddString(sa, (char *)";\n", L_COPY);
+    cdata3 = sarrayToString(sa, 0);
+    sarrayDestroy(&sa);
+    return cdata3;
+}
+
+
+/*!
+ *  l_genCaseString()
+ *
+ *      Input:  ifunc (index into set of functions in generated file)
+ *              itype (index into type of function to be used)
+ *      Return: case string for this decoding function
+ *
+ *  Notes:
+ *      (1) @ifunc and @itype have been validated, so no error can occur
+ */
+static char *
+l_genCaseString(l_int32  ifunc,
+                l_int32  itype)
+{
+char   buf[256];
+char  *code = NULL;
+
+    snprintf(buf, sizeof(buf), "    case %d:\n", ifunc);
+    stringJoinIP(&code, buf);
+    snprintf(buf, sizeof(buf),
+        "        data1 = decodeBase64(l_strdata_%d, strlen(l_strdata_%d), "
+        "&size1);\n", ifunc, ifunc);
+    stringJoinIP(&code, buf);
+    stringJoinIP(&code,
+                 "        data2 = zlibUncompress(data1, size1, &size2);\n");
+    stringJoinIP(&code,
+        "        l_binaryWrite(\"/tmp/lept/auto/data.bin\","
+        "\"w\", data2, size2);\n");
+    snprintf(buf, sizeof(buf),
+             "        result = (void *)%s(\"/tmp/lept/auto/data.bin\");\n",
+             l_assoc[itype].reader);
+    stringJoinIP(&code, buf);
+    stringJoinIP(&code, "        lept_free(data1);\n");
+    stringJoinIP(&code, "        lept_free(data2);\n");
+    stringJoinIP(&code, "        break;\n");
+    return code;
+}
+
+
+/*!
+ *  l_genDescrString()
+ *
+ *      Input:  filein (input file of serialized data)
+ *              ifunc (index into set of functions in generated file)
+ *              itype (index into type of function to be used)
+ *      Return: description string for this decoding function
+ */
+static char *
+l_genDescrString(const char  *filein,
+                 l_int32      ifunc,
+                 l_int32      itype)
+{
+char   buf[256];
+char  *tail;
+
+    PROCNAME("l_genDescrString");
+
+    if (!filein)
+        return (char *)ERROR_PTR("filein not defined", procName, NULL);
+
+    splitPathAtDirectory(filein, NULL, &tail);
+    snprintf(buf, sizeof(buf), " *     %-2d       %-10s    %-14s   %s",
+             ifunc, l_assoc[itype].type, l_assoc[itype].reader, tail);
+
+    LEPT_FREE(tail);
+    return stringNew(buf);
+}
+
diff --git a/src/stringcode.h b/src/stringcode.h
new file mode 100644 (file)
index 0000000..6c8d9d1
--- /dev/null
@@ -0,0 +1,48 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_STRINGCODE_H
+#define  LEPTONICA_STRINGCODE_H
+
+/*
+ *  stringcode.h
+ *
+ *     Data structure to hold accumulating generated code for storing
+ *     and extracing serializable leptonica objects (e.g., pixa, recog).
+ */
+
+struct L_StrCode
+{
+    l_int32       fileno;      /* index for function and output file names   */
+    l_int32       ifunc;       /* index into struct currently being stored   */
+    SARRAY       *function;    /* store case code for extraction             */
+    SARRAY       *data;        /* store base64 encoded data as strings       */
+    SARRAY       *descr;       /* store line in description table            */
+    l_int32       n;           /* number of data strings                     */
+};
+typedef struct L_StrCode  L_STRCODE;
+
+#endif  /* LEPTONICA_STRINGCODE_H */
diff --git a/src/stringtemplate1.txt b/src/stringtemplate1.txt
new file mode 100644 (file)
index 0000000..9591ad7
--- /dev/null
@@ -0,0 +1,96 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+--- *   autogen.*.c
+ *
+ *   Automatically generated code for deserializing data from
+ *   compiled strings.
+ *
+ *     Index    Type          Deserializer     Filename
+ *     -----    ----          ------------     --------
+--- *       0         PIXA         pixaRead       chars-6.pa
+--- *       1         PIXA         pixaRead       chars-10.pa
+ */
+
+#include <string.h>
+#include "allheaders.h"
+--- #include "autogen.*.h"
+
+/*---------------------------------------------------------------------*/
+/*                      Auto-generated deserializers                   */
+/*---------------------------------------------------------------------*/
+/*!
+--- *  l_autodecode_*()
+ *
+ *      Input:  index into array of functions
+ *      Return: data struct (e.g., pixa) in memory
+ */
+void *
+--- l_autodecode_*(l_int32  index)
+{
+l_uint8  *data1, *data2;
+l_int32   size1;
+size_t    size2;
+void     *result = NULL;
+--- l_int32   nfunc = 2;
+---
+---    PROCNAME("l_autodecode_*");
+
+    if (index < 0 || index >= nfunc) {
+        L_ERROR("invalid index = %d; must be less than %d\n", procName,
+                index, nfunc);
+        return NULL;
+    }
+
+    lept_mkdir("lept/auto");
+
+        /* Unencode selected string, write to file, and read it */
+    switch (index) {
+---    case 0:
+---        data1 = decodeBase64(l_strdata_0, strlen(l_strdata_0), &size1);
+---        data2 = zlibUncompress(data1, size1, &size2);
+---        l_binaryWrite("/tmp/lept/auto/data.bin", "w", data2, size2);
+---        result = (void *)pixaRead("/tmp/lept/auto/data.bin");
+---        lept_free(data1);
+---        lept_free(data2);
+---        break;
+---    case 1:
+---        data1 = decodeBase64(l_strdata_1, strlen(l_strdata_1), &size1);
+---        data2 = zlibUncompress(data1, size1, &size2);
+---        l_binaryWrite("/tmp/lept/auto/data.bin", "w", data2, size2);
+---        result = (void *)pixaRead("/tmp/lept/auto/data.bin");
+---        lept_free(data1);
+---        lept_free(data2);
+---        break;
+    default:
+        L_ERROR("invalid index", procName);
+    }
+
+    return result;
+}
+
+
diff --git a/src/stringtemplate2.txt b/src/stringtemplate2.txt
new file mode 100644 (file)
index 0000000..20c853a
--- /dev/null
@@ -0,0 +1,61 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+--- *   autogen.*.h
+ *
+ *   Automatically generated function prototype and associated
+ *   encoded serialized strings.
+ */
+
+--- #ifndef  LEPTONICA_AUTOGEN_*_H
+--- #define  LEPTONICA_AUTOGEN_*_H
+
+/*---------------------------------------------------------------------*/
+/*                         Function prototype                          */
+/*---------------------------------------------------------------------*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif  /* __cplusplus */
+
+--- void *l_autodecode_*(l_int32  index);
+
+#ifdef __cplusplus
+}
+#endif  /* __cplusplus */
+
+/*---------------------------------------------------------------------*/
+/*                         Serialized strings                          */
+/*---------------------------------------------------------------------*/
+--- static const char *l_strdata_0 =
+---     "...";
+--- static const char *l_strdata_1 =
+---     "...";
+---   [etc]
+---
+---#endif  /* LEPTONICA_AUTOGEN_*_H */
+
diff --git a/src/sudoku.c b/src/sudoku.c
new file mode 100644 (file)
index 0000000..9f55160
--- /dev/null
@@ -0,0 +1,860 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  sudoku.c
+ *
+ *      Solve a sudoku by brute force search
+ *
+ *      Read input data from file or string
+ *          l_int32         *sudokuReadFile()
+ *          l_int32         *sudokuReadString()
+ *
+ *      Create/destroy
+ *          L_SUDOKU        *sudokuCreate()
+ *          void             sudokuDestroy()
+ *
+ *      Solve the puzzle
+ *          l_int32          sudokuSolve()
+ *          static l_int32   sudokuValidState()
+ *          static l_int32   sudokuNewGuess()
+ *          static l_int32   sudokuTestState()
+ *
+ *      Test for uniqueness
+ *          l_int32          sudokuTestUniqueness()
+ *          static l_int32   sudokuCompareState()
+ *          static l_int32  *sudokuRotateArray()
+ *
+ *      Generation
+ *          L_SUDOKU        *sudokuGenerate()
+ *
+ *      Output
+ *          l_int32          sudokuOutput()
+ *
+ *  Solving sudokus is a somewhat addictive pastime.  The rules are
+ *  simple but it takes just enough concentration to make it rewarding
+ *  when you find a number.  And you get 50 to 60 such rewards each time
+ *  you complete one.  The downside is that you could have been doing
+ *  something more creative, like keying out a new plant, staining
+ *  the deck, or even writing a computer program to discourage your
+ *  wife from doing sudokus.
+ *
+ *  My original plan for the sudoku solver was somewhat grandiose.
+ *  The program would model the way a person solves the problem.
+ *  It would examine each empty position and determine how many possible
+ *  numbers could fit.  The empty positions would be entered in a priority
+ *  queue keyed on the number of possible numbers that could fit.
+ *  If there existed a position where only a single number would work,
+ *  it would greedily take it.  Otherwise it would consider a
+ *  positions that could accept two and make a guess, with backtracking
+ *  if an impossible state were reached.  And so on.
+ *
+ *  Then one of my colleagues announced she had solved the problem
+ *  by brute force and it was fast.  At that point the original plan was
+ *  dead in the water, because the two top requirements for a leptonica
+ *  algorithm are (1) as simple as possible and (2) fast.  The brute
+ *  force approach starts at the UL corner, and in succession at each
+ *  blank position it finds the first valid number (testing in
+ *  sequence from 1 to 9).  When no number will fit a blank position
+ *  it backtracks, choosing the next valid number in the previous
+ *  blank position.
+ *
+ *  This is an inefficient method for pruning the space of solutions
+ *  (imagine backtracking from the LR corner back to the UL corner
+ *  and starting over with a new guess), but it nevertheless gets
+ *  the job done quickly.  I have made no effort to optimize
+ *  it, because it is fast: a 5-star (highest difficulty) sudoku might
+ *  require a million guesses and take 0.05 sec.  (This BF implementation
+ *  does about 20M guesses/sec at 3 GHz.)
+ *
+ *  Proving uniqueness of a sudoku solution is tricker than finding
+ *  a solution (or showing that no solution exists).  A good indication
+ *  that a solution is unique is if we get the same result solving
+ *  by brute force when the puzzle is also rotated by 90, 180 and 270
+ *  degrees.  If there are multiple solutions, it seems unlikely
+ *  that you would get the same solution four times in a row, using a
+ *  brute force method that increments guesses and scans LR/TB.
+ *  The function sudokuTestUniqueness() does this.
+ *
+ *  And given a function that can determine uniqueness, it is
+ *  easy to generate valid sudokus.  We provide sudokuGenerate(),
+ *  which starts with some valid initial solution, and randomly
+ *  removes numbers, stopping either when a minimum number of non-zero
+ *  elements are left, or when it becomes difficult to remove another
+ *  element without destroying the uniqueness of the solution.
+ *
+ *  For further reading, see the Wikipedia articles:
+ *     (1) http://en.wikipedia.org/wiki/Algorithmics_of_sudoku
+ *     (2) http://en.wikipedia.org/wiki/Sudoku
+ *
+ *  How many 9x9 sudokus are there?  Here are the numbers.
+ *   - From ref(1), there are about 6 x 10^27 "latin squares", where
+ *     each row and column has all 9 digits.
+ *   - There are 7.2 x 10^21 actual solutions, having the added
+ *     constraint in each of the 9 3x3 squares.  (The constraint
+ *     reduced the number by the fraction 1.2 x 10^(-6).)
+ *   - There are a mere 5.5 billion essentially different solutions (EDS),
+ *     when symmetries (rotation, reflection, permutation and relabelling)
+ *     are removed.
+ *   - Thus there are 1.3 x 10^12 solutions that can be derived by
+ *     symmetry from each EDS.  Can we account for these?
+ *   - Sort-of.  From an EDS, you can derive (3!)^8 = 1.7 million solutions
+ *     by simply permuting rows and columns.  (Do you see why it is
+ *     not (3!)^6 ?)
+ *   - Also from an EDS, you can derive 9! solutions by relabelling,
+ *     and 4 solutions by rotation, for a total of 1.45 million solutions
+ *     by relabelling and rotation.  Then taking the product, by symmetry
+ *     we can derive 1.7M x 1.45M = 2.45 trillion solutions from each EDS.
+ *     (Something is off by about a factor of 2 -- close enough.)
+ *
+ *  Another interesting fact is that there are apparently 48K EDS sudokus
+ *  (with unique solutions) that have only 17 givens.  No sudokus are known
+ *  with less than 17, but there exists no proof that this is the minimum.
+ */
+
+#include "allheaders.h"
+
+
+static l_int32 sudokuValidState(l_int32  *state);
+static l_int32 sudokuNewGuess(L_SUDOKU  *sud);
+static l_int32 sudokuTestState(l_int32  *state, l_int32  index);
+static l_int32 sudokuCompareState(L_SUDOKU  *sud1, L_SUDOKU  *sud2,
+                                  l_int32  quads, l_int32  *psame);
+static l_int32 *sudokuRotateArray(l_int32  *array, l_int32  quads);
+
+    /* An example of a valid solution */
+static const char valid_solution[] = "3 8 7 2 6 4 1 9 5 "
+                                     "2 6 5 8 9 1 4 3 7 "
+                                     "1 4 9 5 3 7 6 8 2 "
+                                     "5 2 3 7 1 6 8 4 9 "
+                                     "7 1 6 9 4 8 2 5 3 "
+                                     "8 9 4 3 5 2 7 1 6 "
+                                     "9 7 2 1 8 5 3 6 4 "
+                                     "4 3 1 6 7 9 5 2 8 "
+                                     "6 5 8 4 2 3 9 7 1 ";
+
+
+/*---------------------------------------------------------------------*
+ *               Read input data from file or string                   *
+ *---------------------------------------------------------------------*/
+/*!
+ *  sudokuReadFile()
+ *
+ *      Input:  filename (of formatted sudoku file)
+ *      Return: array (of 81 numbers), or null on error
+ *
+ *  Notes:
+ *      (1) The file format has:
+ *          * any number of comment lines beginning with '#'
+ *          * a set of 9 lines, each having 9 digits (0-9) separated
+ *            by a space
+ */
+l_int32 *
+sudokuReadFile(const char  *filename)
+{
+char     *str, *strj;
+l_uint8  *data;
+l_int32   i, j, nlines, val, index, error;
+l_int32  *array;
+size_t    size;
+SARRAY   *saline, *sa1, *sa2;
+
+    PROCNAME("sudokuReadFile");
+
+    if (!filename)
+        return (l_int32 *)ERROR_PTR("filename not defined", procName, NULL);
+    data = l_binaryRead(filename, &size);
+    sa1 = sarrayCreateLinesFromString((char *)data, 0);
+    sa2 = sarrayCreate(9);
+
+        /* Filter out the comment lines; verify that there are 9 data lines */
+    nlines = sarrayGetCount(sa1);
+    for (i = 0; i < nlines; i++) {
+        str = sarrayGetString(sa1, i, L_NOCOPY);
+        if (str[0] != '#')
+            sarrayAddString(sa2, str, L_COPY);
+    }
+    LEPT_FREE(data);
+    sarrayDestroy(&sa1);
+    nlines = sarrayGetCount(sa2);
+    if (nlines != 9) {
+        sarrayDestroy(&sa2);
+        L_ERROR("file has %d lines\n", procName, nlines);
+        return (l_int32 *)ERROR_PTR("invalid file", procName, NULL);
+    }
+
+        /* Read the data into the array, verifying that each data
+         * line has 9 numbers. */
+    error = FALSE;
+    array = (l_int32 *)LEPT_CALLOC(81, sizeof(l_int32));
+    for (i = 0, index = 0; i < 9; i++) {
+        str = sarrayGetString(sa2, i, L_NOCOPY);
+        saline = sarrayCreateWordsFromString(str);
+        if (sarrayGetCount(saline) != 9) {
+            error = TRUE;
+            sarrayDestroy(&saline);
+            break;
+        }
+        for (j = 0; j < 9; j++) {
+            strj = sarrayGetString(saline, j, L_NOCOPY);
+            if (sscanf(strj, "%d", &val) != 1)
+                error = TRUE;
+            else
+                array[index++] = val;
+        }
+        sarrayDestroy(&saline);
+        if (error) break;
+    }
+    sarrayDestroy(&sa2);
+
+    if (error) {
+        LEPT_FREE(array);
+        return (l_int32 *)ERROR_PTR("invalid data", procName, NULL);
+    }
+
+    return array;
+}
+
+
+/*!
+ *  sudokuReadString()
+ *
+ *      Input:  str (of input data)
+ *      Return: array (of 81 numbers), or null on error
+ *
+ *  Notes:
+ *      (1) The string is formatted as 81 single digits, each separated
+ *          by 81 spaces.
+ */
+l_int32 *
+sudokuReadString(const char  *str)
+{
+l_int32   i;
+l_int32  *array;
+
+    PROCNAME("sudokuReadString");
+
+    if (!str)
+        return (l_int32 *)ERROR_PTR("str not defined", procName, NULL);
+
+        /* Read in the initial solution */
+    array = (l_int32 *)LEPT_CALLOC(81, sizeof(l_int32));
+    for (i = 0; i < 81; i++) {
+        if (sscanf(str + 2 * i, "%d ", &array[i]) != 1)
+            return (l_int32 *)ERROR_PTR("invalid format", procName, NULL);
+    }
+
+    return array;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Create/destroy sudoku                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  sudokuCreate()
+ *
+ *      Input:  array (of 81 numbers, 9 rows of 9 numbers each)
+ *      Return: l_sudoku, or null on error
+ *
+ *  Notes:
+ *      (1) The input array has 0 for the unknown values, and 1-9
+ *          for the known initial values.  It is generated from
+ *          a file using sudokuReadInput(), which checks that the file
+ *          data has 81 numbers in 9 rows.
+ */
+L_SUDOKU *
+sudokuCreate(l_int32  *array)
+{
+l_int32    i, val, locs_index;
+L_SUDOKU  *sud;
+
+    PROCNAME("sudokuCreate");
+
+    if (!array)
+        return (L_SUDOKU *)ERROR_PTR("array not defined", procName, NULL);
+
+    locs_index = 0;  /* into locs array */
+    if ((sud = (L_SUDOKU *)LEPT_CALLOC(1, sizeof(L_SUDOKU))) == NULL)
+        return (L_SUDOKU *)ERROR_PTR("sud not made", procName, NULL);
+    if ((sud->locs = (l_int32 *)LEPT_CALLOC(81, sizeof(l_int32))) == NULL)
+        return (L_SUDOKU *)ERROR_PTR("su state array not made", procName, NULL);
+    if ((sud->init = (l_int32 *)LEPT_CALLOC(81, sizeof(l_int32))) == NULL)
+        return (L_SUDOKU *)ERROR_PTR("su init array not made", procName, NULL);
+    if ((sud->state = (l_int32 *)LEPT_CALLOC(81, sizeof(l_int32))) == NULL)
+        return (L_SUDOKU *)ERROR_PTR("su state array not made", procName, NULL);
+    for (i = 0; i < 81; i++) {
+        val = array[i];
+        sud->init[i] = val;
+        sud->state[i] = val;
+        if (val == 0)
+            sud->locs[locs_index++] = i;
+    }
+    sud->num = locs_index;
+    sud->failure = FALSE;
+    sud->finished = FALSE;
+    return sud;
+}
+
+
+/*!
+ *  sudokuDestroy()
+ *
+ *      Input:  &l_sudoku (<to be nulled>)
+ *      Return: void
+ */
+void
+sudokuDestroy(L_SUDOKU  **psud)
+{
+L_SUDOKU  *sud;
+
+    PROCNAME("sudokuDestroy");
+
+    if (psud == NULL) {
+        L_WARNING("ptr address is NULL\n", procName);
+        return;
+    }
+    if ((sud = *psud) == NULL)
+        return;
+
+    LEPT_FREE(sud->locs);
+    LEPT_FREE(sud->init);
+    LEPT_FREE(sud->state);
+    LEPT_FREE(sud);
+
+    *psud = NULL;
+    return;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                           Solve the puzzle                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  sudokuSolve()
+ *
+ *      Input:  l_sudoku (starting in initial state)
+ *      Return: 1 on success, 0 on failure to solve (note reversal of
+ *              typical unix returns)
+ */
+l_int32
+sudokuSolve(L_SUDOKU  *sud)
+{
+    PROCNAME("sudokuSolve");
+
+    if (!sud)
+        return ERROR_INT("sud not defined", procName, 0);
+
+    if (!sudokuValidState(sud->init))
+        return ERROR_INT("initial state not valid", procName, 0);
+
+    while (1) {
+        if (sudokuNewGuess(sud))
+            break;
+        if (sud->finished == TRUE)
+            break;
+    }
+
+    if (sud->failure == TRUE) {
+        fprintf(stderr, "Failure after %d guesses\n", sud->nguess);
+        return 0;
+    }
+
+    fprintf(stderr, "Solved after %d guesses\n", sud->nguess);
+    return 1;
+}
+
+
+/*!
+ *  sudokuValidState()
+ *
+ *      Input:  state (array of size 81)
+ *      Return: 1 if valid, 0 if invalid
+ *
+ *  Notes:
+ *      (1) This can be used on either the initial state (init)
+ *          or on the current state (state) of the l_soduku.
+ *          All values of 0 are ignored.
+ */
+static l_int32
+sudokuValidState(l_int32  *state)
+{
+l_int32  i;
+
+    PROCNAME("sudokuValidState");
+
+    if (!state)
+        return ERROR_INT("state not defined", procName, 0);
+
+    for (i = 0; i < 81; i++) {
+        if (!sudokuTestState(state, i))
+            return 0;
+    }
+
+    return 1;
+}
+
+
+/*!
+ *  sudokuNewGuess()
+ *
+ *      Input:  l_sudoku
+ *      Return: 0 if OK; 1 if no solution is possible
+ *
+ *  Notes:
+ *      (1) This attempts to increment the number in the current
+ *          location.  If it can't, it backtracks (sets the number
+ *          in the current location to zero and decrements the
+ *          current location).  If it can, it tests that number,
+ *          and if the number is valid, moves forward to the next
+ *          empty location (increments the current location).
+ *      (2) If there is no solution, backtracking will eventually
+ *          exhaust possibilities for the first location.
+ */
+static l_int32
+sudokuNewGuess(L_SUDOKU  *sud)
+{
+l_int32   index, val, valid;
+l_int32  *locs, *state;
+
+    locs = sud->locs;
+    state = sud->state;
+    index = locs[sud->current];  /* 0 to 80 */
+    val = state[index];
+    if (val == 9) {  /* backtrack or give up */
+        if (sud->current == 0) {
+            sud->failure = TRUE;
+            return 1;
+        }
+        state[index] = 0;
+        sud->current--;
+    } else {  /* increment current value and test */
+        sud->nguess++;
+        state[index]++;
+        valid = sudokuTestState(state, index);
+        if (valid) {
+            if (sud->current == sud->num - 1) {  /* we're done */
+                sud->finished = TRUE;
+                return 0;
+            } else {  /* advance to next position */
+                sud->current++;
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  sudokuTestState()
+ *
+ *      Input:  state (current state: array of 81 values)
+ *              index (into state element that we are testing)
+ *      Return: 1 if valid; 0 if invalid (no error checking)
+ */
+static l_int32
+sudokuTestState(l_int32  *state,
+                l_int32   index)
+{
+l_int32  i, j, val, row, rowstart, rowend, col;
+l_int32  blockrow, blockcol, blockstart, rowindex, locindex;
+
+    if ((val = state[index]) == 0)  /* automatically valid */
+        return 1;
+
+        /* Test row.  Test val is at (x, y) = (index % 9, index / 9)  */
+    row = index / 9;
+    rowstart = 9 * row;
+    for (i = rowstart; i < index; i++) {
+        if (state[i] == val)
+            return 0;
+    }
+    rowend = rowstart + 9;
+    for (i = index + 1; i < rowend; i++) {
+        if (state[i] == val)
+            return 0;
+    }
+
+        /* Test column */
+    col = index % 9;
+    for (j = col; j < index; j += 9) {
+        if (state[j] == val)
+            return 0;
+    }
+    for (j = index + 9; j < 81; j += 9) {
+        if (state[j] == val)
+            return 0;
+    }
+
+        /* Test local 3x3 block */
+    blockrow = 3 * (row / 3);
+    blockcol = 3 * (col / 3);
+    blockstart = 9 * blockrow + blockcol;
+    for (i = 0; i < 3; i++) {
+        rowindex = blockstart + 9 * i;
+        for (j = 0; j < 3; j++) {
+            locindex = rowindex + j;
+            if (index == locindex) continue;
+            if (state[locindex] == val)
+                return 0;
+        }
+    }
+
+    return 1;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Test for uniqueness                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  sudokuTestUniqueness()
+ *
+ *      Input:  array (of 81 numbers, 9 lines of 9 numbers each)
+ *              &punique (<return> 1 if unique, 0 if not)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This applies the brute force method to all four 90 degree
+ *          rotations.  If there is more than one solution, it is highly
+ *          unlikely that all four results will be the same;
+ *          consequently, if they are the same, the solution is
+ *          most likely to be unique.
+ */
+l_int32
+sudokuTestUniqueness(l_int32  *array,
+                     l_int32  *punique)
+{
+l_int32    same1, same2, same3;
+l_int32   *array1, *array2, *array3;
+L_SUDOKU  *sud, *sud1, *sud2, *sud3;
+
+    PROCNAME("sudokuTestUniqueness");
+
+    if (!punique)
+        return ERROR_INT("&unique not defined", procName, 1);
+    *punique = 0;
+    if (!array)
+        return ERROR_INT("array not defined", procName, 1);
+
+    sud = sudokuCreate(array);
+    sudokuSolve(sud);
+    array1 = sudokuRotateArray(array, 1);
+    sud1 = sudokuCreate(array1);
+    sudokuSolve(sud1);
+    array2 = sudokuRotateArray(array, 2);
+    sud2 = sudokuCreate(array2);
+    sudokuSolve(sud2);
+    array3 = sudokuRotateArray(array, 3);
+    sud3 = sudokuCreate(array3);
+    sudokuSolve(sud3);
+
+    sudokuCompareState(sud, sud1, 1, &same1);
+    sudokuCompareState(sud, sud2, 2, &same2);
+    sudokuCompareState(sud, sud3, 3, &same3);
+    *punique = (same1 && same2 && same3);
+
+    sudokuDestroy(&sud);
+    sudokuDestroy(&sud1);
+    sudokuDestroy(&sud2);
+    sudokuDestroy(&sud3);
+    LEPT_FREE(array1);
+    LEPT_FREE(array2);
+    LEPT_FREE(array3);
+    return 0;
+}
+
+
+/*!
+ *  sudokuCompareState()
+ *
+ *      Input:  sud1, sud2
+ *              quads (rotation of sud2 input with respect to sud1,
+ *                    in units of 90 degrees cw)
+ *              &same (<return> 1 if all 4 results are identical; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The input to sud2 has been rotated by @quads relative to the
+ *          input to sud1.  Therefore, we must rotate the solution to
+ *          sud1 by the same amount before comparing it to the
+ *          solution to sud2.
+ */
+static l_int32
+sudokuCompareState(L_SUDOKU  *sud1,
+                   L_SUDOKU  *sud2,
+                   l_int32    quads,
+                   l_int32   *psame)
+{
+l_int32   i, same;
+l_int32  *array;
+
+    PROCNAME("sudokuCompareState");
+
+    if (!psame)
+        return ERROR_INT("&same not defined", procName, 1);
+    *psame = 0;
+    if (!sud1)
+        return ERROR_INT("sud1 not defined", procName, 1);
+    if (!sud2)
+        return ERROR_INT("sud1 not defined", procName, 1);
+    if (quads < 1 || quads > 3)
+        return ERROR_INT("valid quads in {1,2,3}", procName, 1);
+
+    same = TRUE;
+    if ((array = sudokuRotateArray(sud1->state, quads)) == NULL)
+        return ERROR_INT("array not made", procName, 1);
+    for (i = 0; i < 81; i++) {
+        if (array[i] != sud2->state[i]) {
+            same = FALSE;
+            break;
+        }
+    }
+    *psame = same;
+    LEPT_FREE(array);
+    return 0;
+}
+
+
+/*!
+ *  sudokuRotateArray()
+ *
+ *      Input:  array (of 81 numbers; 9 lines of 9 numbers each)
+ *              quads (1-3; number of 90 degree cw rotations)
+ *      Return: rarray (rotated array), or null on error
+ */
+static l_int32 *
+sudokuRotateArray(l_int32  *array,
+                  l_int32   quads)
+{
+l_int32   i, j, sindex, dindex;
+l_int32  *rarray;
+
+    PROCNAME("sudokuRotateArray");
+
+    if (!array)
+        return (l_int32 *)ERROR_PTR("array not defined", procName, NULL);
+    if (quads < 1 || quads > 3)
+        return (l_int32 *)ERROR_PTR("valid quads in {1,2,3}", procName, NULL);
+
+    rarray = (l_int32 *)LEPT_CALLOC(81, sizeof(l_int32));
+    if (quads == 1) {
+        for (j = 0, dindex = 0; j < 9; j++) {
+             for (i = 8; i >= 0; i--) {
+                 sindex = 9 * i + j;
+                 rarray[dindex++] = array[sindex];
+             }
+        }
+    } else if (quads == 2) {
+        for (i = 8, dindex = 0; i >= 0; i--) {
+             for (j = 8; j >= 0; j--) {
+                 sindex = 9 * i + j;
+                 rarray[dindex++] = array[sindex];
+             }
+        }
+    } else {  /* quads == 3 */
+        for (j = 8, dindex = 0; j >= 0; j--) {
+             for (i = 0; i < 9; i++) {
+                 sindex = 9 * i + j;
+                 rarray[dindex++] = array[sindex];
+             }
+        }
+    }
+
+    return rarray;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                              Generation                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  sudokuGenerate()
+ *
+ *      Input:  array (of 81 numbers, 9 rows of 9 numbers each)
+ *              seed (random number)
+ *              minelems (min non-zero elements allowed; <= 80)
+ *              maxtries (max tries to remove a number and get a valid sudoku)
+ *      Return: l_sudoku, or null on error
+ *
+ *  Notes:
+ *      (1) This is a brute force generator.  It starts with a completed
+ *          sudoku solution and, by removing elements (setting them to 0),
+ *          generates a valid (unique) sudoku initial condition.
+ *      (2) The process stops when either @minelems, the minimum
+ *          number of non-zero elements, is reached, or when the
+ *          number of attempts to remove the next element exceeds @maxtries.
+ *      (3) No sudoku is known with less than 17 nonzero elements.
+ */
+L_SUDOKU *
+sudokuGenerate(l_int32  *array,
+               l_int32   seed,
+               l_int32   minelems,
+               l_int32   maxtries)
+{
+l_int32    index, sector, nzeros, removefirst, tries, val, oldval, unique;
+L_SUDOKU  *sud, *testsud;
+
+    PROCNAME("sudokuGenerate");
+
+    if (!array)
+        return (L_SUDOKU *)ERROR_PTR("array not defined", procName, NULL);
+    if (minelems > 80)
+        return (L_SUDOKU *)ERROR_PTR("minelems must be < 81", procName, NULL);
+
+        /* Remove up to 30 numbers at random from the solution.
+         * Test if the solution is valid -- the initial 'solution' may
+         * have been invalid.  Then test if the sudoku with 30 zeroes
+         * is unique -- it almost always will be. */
+    srand(seed);
+    nzeros = 0;
+    sector = 0;
+    removefirst = L_MIN(30, 81 - minelems);
+    while (nzeros < removefirst) {
+        genRandomIntegerInRange(9, 0, &val);
+        index = 27 * (sector / 3) + 3 * (sector % 3) +
+                9 * (val / 3) + (val % 3);
+        if (array[index] == 0) continue;
+        array[index] = 0;
+        nzeros++;
+        sector++;
+        sector %= 9;
+    }
+    testsud = sudokuCreate(array);
+    sudokuSolve(testsud);
+    if (testsud->failure) {
+        sudokuDestroy(&testsud);
+        L_ERROR("invalid initial solution\n", procName);
+        return NULL;
+    }
+    sudokuTestUniqueness(testsud->init, &unique);
+    sudokuDestroy(&testsud);
+    if (!unique) {
+        L_ERROR("non-unique result with 30 zeroes\n", procName);
+        return NULL;
+    }
+
+        /* Remove more numbers, testing at each removal for uniqueness. */
+    tries = 0;
+    sector = 0;
+    while (1) {
+        if (tries > maxtries) break;
+        if (81 - nzeros <= minelems) break;
+
+        if (tries == 0) {
+            fprintf(stderr, "Trying %d zeros\n", nzeros);
+            tries = 1;
+        }
+
+            /* Choose an element to be zeroed.  We choose one
+             * at random in succession from each of the nine sectors. */
+        genRandomIntegerInRange(9, 0, &val);
+        index = 27 * (sector / 3) + 3 * (sector % 3) +
+                9 * (val / 3) + (val % 3);
+        sector++;
+        sector %= 9;
+        if (array[index] == 0) continue;
+
+            /* Save the old value in case we need to revert */
+        oldval = array[index];
+
+            /* Is there a solution?  If not, try again. */
+        array[index] = 0;
+        testsud = sudokuCreate(array);
+        sudokuSolve(testsud);
+        if (testsud->failure == TRUE) {
+            sudokuDestroy(&testsud);
+            array[index] = oldval;  /* revert */
+            tries++;
+            continue;
+        }
+
+            /* Is the solution unique?  If not, try again. */
+        sudokuTestUniqueness(testsud->init, &unique);
+        sudokuDestroy(&testsud);
+        if (!unique) {  /* revert and try again */
+            array[index] = oldval;
+            tries++;
+        } else {  /* accept this */
+            tries = 0;
+            fprintf(stderr, "Have %d zeros\n", nzeros);
+            nzeros++;
+        }
+    }
+    fprintf(stderr, "Final: nelems = %d\n", 81 - nzeros);
+
+        /* Show that we can recover the solution */
+    sud = sudokuCreate(array);
+    sudokuOutput(sud, L_SUDOKU_INIT);
+    sudokuSolve(sud);
+    sudokuOutput(sud, L_SUDOKU_STATE);
+
+    return sud;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                               Output                                *
+ *---------------------------------------------------------------------*/
+/*!
+ *  sudokuOutput()
+ *
+ *      Input:  l_sudoku (at any stage)
+ *              arraytype (L_SUDOKU_INIT, L_SUDOKU_STATE)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Prints either the initial array or the current state
+ *          of the solution.
+ */
+l_int32
+sudokuOutput(L_SUDOKU  *sud,
+             l_int32    arraytype)
+{
+l_int32   i, j;
+l_int32  *array;
+
+    PROCNAME("sudokuOutput");
+
+    if (!sud)
+        return ERROR_INT("sud not defined", procName, 1);
+    if (arraytype == L_SUDOKU_INIT)
+        array = sud->init;
+    else if (arraytype == L_SUDOKU_STATE)
+        array = sud->state;
+    else
+        return ERROR_INT("invalid arraytype", procName, 1);
+
+    for (i = 0; i < 9; i++) {
+        for (j = 0; j < 9; j++)
+            fprintf(stderr, "%d ", array[9 * i + j]);
+        fprintf(stderr, "\n");
+    }
+
+    return 0;
+}
diff --git a/src/sudoku.h b/src/sudoku.h
new file mode 100644 (file)
index 0000000..8cacf9d
--- /dev/null
@@ -0,0 +1,73 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef SUDOKU_H_INCLUDED
+#define SUDOKU_H_INCLUDED
+
+/*
+ *  sudoku.h
+ *
+ *    The L_Sudoku holds all the information of the current state.
+ *
+ *    The input to sudokuCreate() is a file with any number of lines
+ *    starting with '#', followed by 9 lines consisting of 9 numbers
+ *    in each line.  These have the known values and use 0 for the unknowns.
+ *    Blank lines are ignored.
+ *
+ *    The @locs array holds the indices of the unknowns, numbered
+ *    left-to-right and top-to-bottom from 0 to 80.  The array size
+ *    is initialized to @num.  @current is the index into the @locs
+ *    array of the current guess: locs[current].
+ *
+ *    The @state array is used to determine the validity of each guess.
+ *    It is of size 81, and is initialized by setting the unknowns to 0
+ *    and the knowns to their input values.
+ */
+struct L_Sudoku
+{
+    l_int32        num;         /* number of unknowns                     */
+    l_int32       *locs;        /* location of unknowns                   */
+    l_int32        current;     /* index into @locs of current location   */
+    l_int32       *init;        /* initial state, with 0 representing     */
+                                /* the unknowns                           */
+    l_int32       *state;       /* present state, including inits and     */
+                                /* guesses of unknowns up to @current     */
+    l_int32        nguess;      /* shows current number of guesses        */
+    l_int32        finished;    /* set to 1 when solved                   */
+    l_int32        failure;     /* set to 1 if no solution is possible    */
+};
+typedef struct L_Sudoku  L_SUDOKU;
+
+
+    /* For printing out array data */
+enum {
+    L_SUDOKU_INIT = 0,
+    L_SUDOKU_STATE = 1
+};
+
+#endif /* SUDOKU_H_INCLUDED */
+
+
diff --git a/src/textops.c b/src/textops.c
new file mode 100644 (file)
index 0000000..f1a22f5
--- /dev/null
@@ -0,0 +1,1094 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *  textops.c
+ *
+ *    Font layout
+ *       PIX             *pixAddSingleTextblock()
+ *       PIX             *pixAddTextlines()
+ *       l_int32          pixSetTextblock()
+ *       l_int32          pixSetTextline()
+ *       PIXA            *pixaAddTextNumber()
+ *       PIXA            *pixaAddTextlines()
+ *       l_int32          pixaAddPixWithText()
+ *
+ *    Text size estimation and partitioning
+ *       SARRAY          *bmfGetLineStrings()
+ *       NUMA            *bmfGetWordWidths()
+ *       l_int32          bmfGetStringWidth()
+ *
+ *    Text splitting
+ *       SARRAY          *splitStringToParagraphs()
+ *       static l_int32   stringAllWhitespace()
+ *       static l_int32   stringLeadingWhitespace()
+ *
+ *    This is a simple utility to put text on images.  One font and style
+ *    is provided, with a variety of pt sizes.  For example, to put a
+ *    line of green 10 pt text on an image, with the beginning baseline
+ *    at (50, 50):
+ *        L_Bmf  *bmf = bmfCreate(NULL, 10);
+ *        const char *textstr = "This is a funny cat";
+ *        pixSetTextline(pixs, bmf, textstr, 0x00ff0000, 50, 50, NULL, NULL);
+ *
+ *    The simplest interfaces for adding text to an image are
+ *    pixAddTextlines() and pixAddSingleTextblock().
+ *    For example, to add the same text in red, centered, below the image:
+ *        Pix *pixd = pixAddTextlines(pixs, bmf, textstr, 0xff000000,
+ *                                    L_ADD_BELOW);  // red text
+ *
+ *    To add text to all pix in a pixa, generating a new pixa, use
+ *    either an sarray to hold the strings for each pix, or use the
+ *    strings in the text field of each pix; e.g.,
+ *        Pixa *pixa2 = pixaAddTextlines(pixa1, bmf, sa, 0x0000ff00,
+ *                                    L_ADD_LEFT);  // blue text
+ *        Pixa *pixa2 = pixaAddTextlines(pixa1, bmf, NULL, 0x00ff0000,
+ *                                    L_ADD_RIGHT);  // green text
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static l_int32 stringAllWhitespace(char *textstr, l_int32 *pval);
+static l_int32 stringLeadingWhitespace(char *textstr, l_int32 *pval);
+
+
+/*---------------------------------------------------------------------*
+ *                                 Font layout                         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixAddSingleTextblock()
+ *
+ *      Input:  pixs (input pix; colormap ok)
+ *              bmf (bitmap font data)
+ *              textstr (<optional> text string to be added)
+ *              val (color to set the text)
+ *              location (L_ADD_ABOVE, L_ADD_AT_TOP, L_ADD_AT_BOT, L_ADD_BELOW)
+ *              &overflow (<optional return> 1 if text overflows
+ *                         allocated region and is clipped; 0 otherwise)
+ *      Return: pixd (new pix with rendered text), or either a copy
+ *                    or null on error
+ *
+ *  Notes:
+ *      (1) This function paints a set of lines of text over an image.
+ *          If @location is L_ADD_ABOVE or L_ADD_BELOW, the pix size
+ *          is expanded with a border and rendered over the border.
+ *      (2) @val is the pixel value to be painted through the font mask.
+ *          It should be chosen to agree with the depth of pixs.
+ *          If it is out of bounds, an intermediate value is chosen.
+ *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
+ *          hex representation of the red intensity, etc.
+ *      (3) If textstr == NULL, use the text field in the pix.
+ *      (4) If there is a colormap, this does the best it can to use
+ *          the requested color, or something similar to it.
+ *      (5) Typical usage is for labelling a pix with some text data.
+ */
+PIX *
+pixAddSingleTextblock(PIX         *pixs,
+                      L_BMF       *bmf,
+                      const char  *textstr,
+                      l_uint32     val,
+                      l_int32      location,
+                      l_int32     *poverflow)
+{
+char     *linestr;
+l_int32   w, h, d, i, y, xstart, ystart, extra, spacer, rval, gval, bval;
+l_int32   nlines, htext, ovf, overflow, offset, index;
+l_uint32  textcolor;
+PIX      *pixd;
+PIXCMAP  *cmap, *cmapd;
+SARRAY   *salines;
+
+    PROCNAME("pixAddSingleTextblock");
+
+    if (poverflow) *poverflow = 0;
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (location != L_ADD_ABOVE && location != L_ADD_AT_TOP &&
+        location != L_ADD_AT_BOT && location != L_ADD_BELOW)
+        return (PIX *)ERROR_PTR("invalid location", procName, NULL);
+    if (!bmf) {
+        L_ERROR("no bitmap fonts; returning a copy\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+    if (!textstr)
+        textstr = pixGetText(pixs);
+    if (!textstr) {
+        L_ERROR("no textstring defined; returning a copy\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+        /* Make sure the "color" value for the text will work
+         * for the pix.  If the pix is not colormapped and the
+         * value is out of range, set it to mid-range. */
+    pixGetDimensions(pixs, &w, &h, &d);
+    cmap = pixGetColormap(pixs);
+    if (d == 1 && val > 1)
+        val = 1;
+    else if (d == 2 && val > 3 && !cmap)
+        val = 2;
+    else if (d == 4 && val > 15 && !cmap)
+        val = 8;
+    else if (d == 8 && val > 0xff && !cmap)
+        val = 128;
+    else if (d == 16 && val > 0xffff)
+        val = 0x8000;
+    else if (d == 32 && val < 256)
+        val = 0x80808000;
+
+    xstart = (l_int32)(0.1 * w);
+    salines = bmfGetLineStrings(bmf, textstr, w - 2 * xstart, 0, &htext);
+    if (!salines)
+        return (PIX *)ERROR_PTR("line string sa not made", procName, NULL);
+    nlines = sarrayGetCount(salines);
+
+        /* Add white border if required */
+    spacer = 10;  /* pixels away from image boundary or added border */
+    if (location == L_ADD_ABOVE || location == L_ADD_BELOW) {
+        extra = htext + 2 * spacer;
+        pixd = pixCreate(w, h + extra, d);
+        pixCopyColormap(pixd, pixs);
+        pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
+        if (location == L_ADD_ABOVE)
+            pixRasterop(pixd, 0, extra, w, h, PIX_SRC, pixs, 0, 0);
+        else  /* add below */
+            pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0);
+    } else {
+        pixd = pixCopy(NULL, pixs);
+    }
+    cmapd = pixGetColormap(pixd);
+
+        /* bmf->baselinetab[93] is the approximate distance from
+         * the top of the tallest character to the baseline.  93 was chosen
+         * at random, as all the baselines are essentially equal for
+         * each character in a font. */
+    offset = bmf->baselinetab[93];
+    if (location == L_ADD_ABOVE || location == L_ADD_AT_TOP)
+        ystart = offset + spacer;
+    else if (location == L_ADD_AT_BOT)
+        ystart = h - htext - spacer + offset;
+    else   /* add below */
+        ystart = h + offset + spacer;
+
+        /* If cmapped, add the color if necessary to the cmap.  If the
+         * cmap is full, use the nearest color to the requested color. */
+    if (cmapd) {
+        extractRGBValues(val, &rval, &gval, &bval);
+        pixcmapAddNearestColor(cmapd, rval, gval, bval, &index);
+        pixcmapGetColor(cmapd, index, &rval, &gval, &bval);
+        composeRGBPixel(rval, gval, bval, &textcolor);
+    } else {
+        textcolor = val;
+    }
+
+        /* Keep track of overflow condition on line width */
+    overflow = 0;
+    for (i = 0, y = ystart; i < nlines; i++) {
+        linestr = sarrayGetString(salines, i, L_NOCOPY);
+        pixSetTextline(pixd, bmf, linestr, textcolor,
+                       xstart, y, NULL, &ovf);
+        y += bmf->lineheight + bmf->vertlinesep;
+        if (ovf)
+            overflow = 1;
+    }
+
+       /* Also consider vertical overflow where there is too much text to
+        * fit inside the image: the cases L_ADD_AT_TOP and L_ADD_AT_BOT.
+        *  The text requires a total of htext + 2 * spacer vertical pixels. */
+    if (location == L_ADD_AT_TOP || location == L_ADD_AT_BOT) {
+        if (h < htext + 2 * spacer)
+            overflow = 1;
+    }
+    if (poverflow) *poverflow = overflow;
+
+    sarrayDestroy(&salines);
+    return pixd;
+}
+
+
+/*!
+ *  pixAddTextlines()
+ *
+ *      Input:  pixs (input pix; colormap ok)
+ *              bmf (bitmap font data)
+ *              textstr (<optional> text string to be added)
+ *              val (color to set the text)
+ *              location (L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT)
+ *      Return: pixd (new pix with rendered text), or either a copy
+ *                    or null on error
+ *
+ *  Notes:
+ *      (1) This function expands an image as required to paint one or
+ *          more lines of text adjacent to the image.  If @bmf == NULL,
+ *          this returns a copy.  If above or below, the lines are
+ *          centered with respect to the image; if left or right, they
+ *          are left justified.
+ *      (2) @val is the pixel value to be painted through the font mask.
+ *          It should be chosen to agree with the depth of pixs.
+ *          If it is out of bounds, an intermediate value is chosen.
+ *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
+ *          hex representation of the red intensity, etc.
+ *      (3) If textstr == NULL, use the text field in the pix.  The
+ *          text field contains one or most "lines" of text, where newlines
+ *          are used as line separators.
+ *      (4) If there is a colormap, this does the best it can to use
+ *          the requested color, or something similar to it.
+ *      (5) Typical usage is for labelling a pix with some text data.
+ */
+PIX *
+pixAddTextlines(PIX         *pixs,
+                L_BMF       *bmf,
+                const char  *textstr,
+                l_uint32     val,
+                l_int32      location)
+{
+char     *str;
+l_int32   i, w, h, d, rval, gval, bval, index;
+l_int32   wline, wtext, htext, wadd, hadd, spacer, hbaseline, nlines;
+l_uint32  textcolor;
+PIX      *pixd;
+PIXCMAP  *cmap, *cmapd;
+SARRAY   *sa;
+
+    PROCNAME("pixAddTextlines");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (location != L_ADD_ABOVE && location != L_ADD_BELOW &&
+        location != L_ADD_LEFT && location != L_ADD_RIGHT)
+        return (PIX *)ERROR_PTR("invalid location", procName, NULL);
+    if (!bmf) {
+        L_ERROR("no bitmap fonts; returning a copy\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+    if (!textstr)
+        textstr = pixGetText(pixs);
+    if (!textstr) {
+        L_ERROR("no textstring defined; returning a copy\n", procName);
+        return pixCopy(NULL, pixs);
+    }
+
+        /* Make sure the "color" value for the text will work
+         * for the pix.  If the pix is not colormapped and the
+         * value is out of range, set it to mid-range. */
+    pixGetDimensions(pixs, &w, &h, &d);
+    cmap = pixGetColormap(pixs);
+    if (d == 1 && val > 1)
+        val = 1;
+    else if (d == 2 && val > 3 && !cmap)
+        val = 2;
+    else if (d == 4 && val > 15 && !cmap)
+        val = 8;
+    else if (d == 8 && val > 0xff && !cmap)
+        val = 128;
+    else if (d == 16 && val > 0xffff)
+        val = 0x8000;
+    else if (d == 32 && val < 256)
+        val = 0x80808000;
+
+        /* Get the text in each line */
+    sa = sarrayCreateLinesFromString(textstr, 0);
+    nlines = sarrayGetCount(sa);
+
+        /* Get the necessary text size */
+    wtext = 0;
+    for (i = 0; i < nlines; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        bmfGetStringWidth(bmf, str, &wline);
+        if (wline > wtext)
+            wtext = wline;
+    }
+    hbaseline = bmf->baselinetab[93];
+    htext = 1.5 * hbaseline * nlines;
+
+        /* Add white border */
+    spacer = 10;  /* pixels away from the added border */
+    if (location == L_ADD_ABOVE || location == L_ADD_BELOW) {
+        hadd = htext + 2 * spacer;
+        pixd = pixCreate(w, h + hadd, d);
+        pixCopyColormap(pixd, pixs);
+        pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
+        if (location == L_ADD_ABOVE)
+            pixRasterop(pixd, 0, hadd, w, h, PIX_SRC, pixs, 0, 0);
+        else  /* add below */
+            pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0);
+    } else {  /*  L_ADD_LEFT or L_ADD_RIGHT */
+        wadd = wtext + 2 * spacer;
+        pixd = pixCreate(w + wadd, h, d);
+        pixCopyColormap(pixd, pixs);
+        pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
+        if (location == L_ADD_LEFT)
+            pixRasterop(pixd, wadd, 0, w, h, PIX_SRC, pixs, 0, 0);
+        else  /* add to right */
+            pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0);
+    }
+
+        /* If cmapped, add the color if necessary to the cmap.  If the
+         * cmap is full, use the nearest color to the requested color. */
+    cmapd = pixGetColormap(pixd);
+    if (cmapd) {
+        extractRGBValues(val, &rval, &gval, &bval);
+        pixcmapAddNearestColor(cmapd, rval, gval, bval, &index);
+        pixcmapGetColor(cmapd, index, &rval, &gval, &bval);
+        composeRGBPixel(rval, gval, bval, &textcolor);
+    } else {
+        textcolor = val;
+    }
+
+        /* Add the text */
+    for (i = 0; i < nlines; i++) {
+        str = sarrayGetString(sa, i, L_NOCOPY);
+        bmfGetStringWidth(bmf, str, &wtext);
+        if (location == L_ADD_ABOVE)
+            pixSetTextline(pixd, bmf, str, textcolor,
+                           (w - wtext) / 2, spacer + hbaseline * (1 + 1.5 * i),
+                           NULL, NULL);
+        else if (location == L_ADD_BELOW)
+            pixSetTextline(pixd, bmf, str, textcolor,
+                           (w - wtext) / 2, h + spacer +
+                           hbaseline * (1 + 1.5 * i), NULL, NULL);
+        else if (location == L_ADD_LEFT)
+            pixSetTextline(pixd, bmf, str, textcolor,
+                           spacer, (h - htext) / 2 + hbaseline * (1 + 1.5 * i),
+                           NULL, NULL);
+        else  /* location == L_ADD_RIGHT */
+            pixSetTextline(pixd, bmf, str, textcolor,
+                           w + spacer, (h - htext) / 2 +
+                           hbaseline * (1 + 1.5 * i), NULL, NULL);
+    }
+
+    sarrayDestroy(&sa);
+    return pixd;
+}
+
+
+/*!
+ *  pixSetTextblock()
+ *
+ *      Input:  pixs (input image)
+ *              bmf (bitmap font data)
+ *              textstr (block text string to be set)
+ *              val (color to set the text)
+ *              x0 (left edge for each line of text)
+ *              y0 (baseline location for the first text line)
+ *              wtext (max width of each line of generated text)
+ *              firstindent (indentation of first line, in x-widths)
+ *              &overflow (<optional return> 0 if text is contained in
+ *                         input pix; 1 if it is clipped)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function paints a set of lines of text over an image.
+ *      (2) @val is the pixel value to be painted through the font mask.
+ *          It should be chosen to agree with the depth of pixs.
+ *          If it is out of bounds, an intermediate value is chosen.
+ *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
+ *          hex representation of the red intensity, etc.
+ *          The last two hex digits are 00 (byte value 0), assigned to
+ *          the A component.  Note that, as usual, RGBA proceeds from
+ *          left to right in the order from MSB to LSB (see pix.h
+ *          for details).
+ *      (3) If there is a colormap, this does the best it can to use
+ *          the requested color, or something similar to it.
+ */
+l_int32
+pixSetTextblock(PIX         *pixs,
+                L_BMF       *bmf,
+                const char  *textstr,
+                l_uint32     val,
+                l_int32      x0,
+                l_int32      y0,
+                l_int32      wtext,
+                l_int32      firstindent,
+                l_int32     *poverflow)
+{
+char     *linestr;
+l_int32   d, h, i, w, x, y, nlines, htext, xwidth, wline, ovf, overflow;
+SARRAY   *salines;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixSetTextblock");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!bmf)
+        return ERROR_INT("bmf not defined", procName, 1);
+    if (!textstr)
+        return ERROR_INT("textstr not defined", procName, 1);
+
+        /* Make sure the "color" value for the text will work
+         * for the pix.  If the pix is not colormapped and the
+         * value is out of range, set it to mid-range. */
+    pixGetDimensions(pixs, &w, &h, &d);
+    cmap = pixGetColormap(pixs);
+    if (d == 1 && val > 1)
+        val = 1;
+    else if (d == 2 && val > 3 && !cmap)
+        val = 2;
+    else if (d == 4 && val > 15 && !cmap)
+        val = 8;
+    else if (d == 8 && val > 0xff && !cmap)
+        val = 128;
+    else if (d == 16 && val > 0xffff)
+        val = 0x8000;
+    else if (d == 32 && val < 256)
+        val = 0x80808000;
+
+    if (w < x0 + wtext) {
+        L_WARNING("reducing width of textblock\n", procName);
+        wtext = w - x0 - w / 10;
+        if (wtext <= 0)
+            return ERROR_INT("wtext too small; no room for text", procName, 1);
+    }
+
+    salines = bmfGetLineStrings(bmf, textstr, wtext, firstindent, &htext);
+    if (!salines)
+        return ERROR_INT("line string sa not made", procName, 1);
+    nlines = sarrayGetCount(salines);
+    bmfGetWidth(bmf, 'x', &xwidth);
+
+    y = y0;
+    overflow = 0;
+    for (i = 0; i < nlines; i++) {
+        if (i == 0)
+            x = x0 + firstindent * xwidth;
+        else
+            x = x0;
+        linestr = sarrayGetString(salines, i, L_NOCOPY);
+        pixSetTextline(pixs, bmf, linestr, val, x, y, &wline, &ovf);
+        y += bmf->lineheight + bmf->vertlinesep;
+        if (ovf)
+            overflow = 1;
+    }
+
+       /* (y0 - baseline) is the top of the printed text.  Character
+        * 93 was chosen at random, as all the baselines are essentially
+        * equal for each character in a font. */
+    if (h < y0 - bmf->baselinetab[93] + htext)
+        overflow = 1;
+    if (poverflow)
+        *poverflow = overflow;
+
+    sarrayDestroy(&salines);
+    return 0;
+}
+
+
+/*!
+ *  pixSetTextline()
+ *
+ *      Input:  pixs (input image)
+ *              bmf (bitmap font data)
+ *              textstr (text string to be set on the line)
+ *              val (color to set the text)
+ *              x0 (left edge for first char)
+ *              y0 (baseline location for all text on line)
+ *              &width (<optional return> width of generated text)
+ *              &overflow (<optional return> 0 if text is contained in
+ *                         input pix; 1 if it is clipped)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This function paints a line of text over an image.
+ *      (2) @val is the pixel value to be painted through the font mask.
+ *          It should be chosen to agree with the depth of pixs.
+ *          If it is out of bounds, an intermediate value is chosen.
+ *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
+ *          hex representation of the red intensity, etc.
+ *          The last two hex digits are 00 (byte value 0), assigned to
+ *          the A component.  Note that, as usual, RGBA proceeds from
+ *          left to right in the order from MSB to LSB (see pix.h
+ *          for details).
+ *      (3) If there is a colormap, this does the best it can to use
+ *          the requested color, or something similar to it.
+ */
+l_int32
+pixSetTextline(PIX         *pixs,
+               L_BMF       *bmf,
+               const char  *textstr,
+               l_uint32     val,
+               l_int32      x0,
+               l_int32      y0,
+               l_int32     *pwidth,
+               l_int32     *poverflow)
+{
+char      chr;
+l_int32   d, i, x, w, nchar, baseline, index, rval, gval, bval;
+l_uint32  textcolor;
+PIX      *pix;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixSetTextline");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!bmf)
+        return ERROR_INT("bmf not defined", procName, 1);
+    if (!textstr)
+        return ERROR_INT("teststr not defined", procName, 1);
+
+    d = pixGetDepth(pixs);
+    cmap = pixGetColormap(pixs);
+    if (d == 1 && val > 1)
+        val = 1;
+    else if (d == 2 && val > 3 && !cmap)
+        val = 2;
+    else if (d == 4 && val > 15 && !cmap)
+        val = 8;
+    else if (d == 8 && val > 0xff && !cmap)
+        val = 128;
+    else if (d == 16 && val > 0xffff)
+        val = 0x8000;
+    else if (d == 32 && val < 256)
+        val = 0x80808000;
+
+        /* If cmapped, add the color if necessary to the cmap.  If the
+         * cmap is full, use the nearest color to the requested color. */
+    if (cmap) {
+        extractRGBValues(val, &rval, &gval, &bval);
+        pixcmapAddNearestColor(cmap, rval, gval, bval, &index);
+        pixcmapGetColor(cmap, index, &rval, &gval, &bval);
+        composeRGBPixel(rval, gval, bval, &textcolor);
+    } else
+        textcolor = val;
+
+    nchar = strlen(textstr);
+    x = x0;
+    for (i = 0; i < nchar; i++) {
+        chr = textstr[i];
+        if ((l_int32)chr == 10) continue;  /* NL */
+        pix = bmfGetPix(bmf, chr);
+        bmfGetBaseline(bmf, chr, &baseline);
+        pixPaintThroughMask(pixs, pix, x, y0 - baseline, textcolor);
+        w = pixGetWidth(pix);
+        x += w + bmf->kernwidth;
+        pixDestroy(&pix);
+    }
+
+    if (pwidth)
+        *pwidth = x - bmf->kernwidth - x0;
+    if (poverflow)
+        *poverflow = (x > pixGetWidth(pixs) - 1) ? 1 : 0;
+    return 0;
+}
+
+
+/*!
+ *  pixaAddTextNumber()
+ *
+ *      Input:  pixas (input pixa; colormap ok)
+ *              bmf (bitmap font data)
+ *              numa (<optional> number array; use 1 ... n if null)
+ *              val (color to set the text)
+ *              location (L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT)
+ *      Return: pixad (new pixa with rendered numbers), or null on error
+ *
+ *  Notes:
+ *      (1) Typical usage is for labelling each pix in a pixa with a number.
+ *      (2) This function paints numbers external to each pix, in a position
+ *          given by @location.  In all cases, the pix is expanded on
+ *          on side and the number is painted over white in the added region.
+ *      (3) @val is the pixel value to be painted through the font mask.
+ *          It should be chosen to agree with the depth of pixs.
+ *          If it is out of bounds, an intermediate value is chosen.
+ *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
+ *          hex representation of the red intensity, etc.
+ *      (4) If na == NULL, number each pix sequentially, starting with 1.
+ *      (5) If there is a colormap, this does the best it can to use
+ *          the requested color, or something similar to it.
+ */
+PIXA *
+pixaAddTextNumber(PIXA     *pixas,
+                  L_BMF    *bmf,
+                  NUMA     *na,
+                  l_uint32  val,
+                  l_int32   location)
+{
+char     textstr[128];
+l_int32  i, n, index;
+PIX     *pix1, *pix2;
+PIXA    *pixad;
+
+    PROCNAME("pixaAddTextNumber");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (!bmf)
+        return (PIXA *)ERROR_PTR("bmf not defined", procName, NULL);
+    if (location != L_ADD_ABOVE && location != L_ADD_BELOW &&
+        location != L_ADD_LEFT && location != L_ADD_RIGHT)
+        return (PIXA *)ERROR_PTR("invalid location", procName, NULL);
+
+    n = pixaGetCount(pixas);
+    pixad = pixaCreate(n);
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixas, i, L_CLONE);
+        if (na)
+            numaGetIValue(na, i, &index);
+        else
+            index = i + 1;
+        snprintf(textstr, sizeof(textstr), "%d", index);
+        pix2 = pixAddTextlines(pix1, bmf, textstr, val, location);
+        pixaAddPix(pixad, pix2, L_INSERT);
+        pixDestroy(&pix1);
+    }
+
+    return pixad;
+}
+
+
+/*!
+ *  pixaAddTextlines()
+ *
+ *      Input:  pixas (input pixa; colormap ok)
+ *              bmf (bitmap font data)
+ *              sa (<optional> sarray; use text embedded in each pix if null)
+ *              val (color to set the text)
+ *              location (L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT)
+ *      Return: pixad (new pixa with rendered text), or null on error
+ *
+ *  Notes:
+ *      (1) This function adds one or more lines of text externally to
+ *          each pix, in a position given by @location.  In all cases,
+ *          the pix is expanded as necessary to accommodate the text.
+ *      (2) @val is the pixel value to be painted through the font mask.
+ *          It should be chosen to agree with the depth of pixs.
+ *          If it is out of bounds, an intermediate value is chosen.
+ *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
+ *          hex representation of the red intensity, etc.
+ *      (3) If sa == NULL, use the text embedded in each pix.  In all
+ *          cases, newlines in the text string are used to separate the
+ *          lines of text that are added to the pix.
+ *      (4) If sa has a smaller count than pixa, issue a warning
+ *          and do not use any embedded text.
+ *      (5) If there is a colormap, this does the best it can to use
+ *          the requested color, or something similar to it.
+ */
+PIXA *
+pixaAddTextlines(PIXA     *pixas,
+                 L_BMF    *bmf,
+                 SARRAY   *sa,
+                 l_uint32  val,
+                 l_int32   location)
+{
+char    *textstr;
+l_int32  i, n, nstr;
+PIX     *pix1, *pix2;
+PIXA    *pixad;
+
+    PROCNAME("pixaAddTextlines");
+
+    if (!pixas)
+        return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
+    if (!bmf)
+        return (PIXA *)ERROR_PTR("bmf not defined", procName, NULL);
+    if (location != L_ADD_ABOVE && location != L_ADD_BELOW &&
+        location != L_ADD_LEFT && location != L_ADD_RIGHT)
+        return (PIXA *)ERROR_PTR("invalid location", procName, NULL);
+
+    n = pixaGetCount(pixas);
+    pixad = pixaCreate(n);
+    nstr = (sa) ? sarrayGetCount(sa) : 0;
+    if (nstr > 0 && nstr < n)
+        L_WARNING("There are %d strings and %d pix\n", procName, nstr, n);
+    for (i = 0; i < n; i++) {
+        pix1 = pixaGetPix(pixas, i, L_CLONE);
+        if (i < nstr)
+            textstr = sarrayGetString(sa, i, L_NOCOPY);
+        else
+            textstr = pixGetText(pix1);
+        pix2 = pixAddTextlines(pix1, bmf, textstr, val, location);
+        pixaAddPix(pixad, pix2, L_INSERT);
+        pixDestroy(&pix1);
+    }
+
+    return pixad;
+}
+
+
+/*!
+ *  pixaAddPixWithText()
+ *
+ *      Input:  pixa
+ *              pixs (any depth, colormap ok)
+ *              reduction (integer subsampling factor)
+ *              bmf (<optional> bitmap font data)
+ *              textstr (<optional> text string to be added)
+ *              val (color to set the text)
+ *              location (L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) This function generates a new pix with added text, and adds
+ *          it by insertion into the pixa.
+ *      (2) If the input pixs is not cmapped and not 32 bpp, it is
+ *          converted to 32 bpp rgb.  @val is a standard 32 bpp pixel,
+ *          expressed as 0xrrggbb00.  If there is a colormap, this does
+ *          the best it can to use the requested color, or something close.
+ *      (3) if @bmf == NULL, generate an 8 pt font; this takes about 5 msec.
+ *      (4) If @textstr == NULL, use the text field in the pix.
+ *      (5) In general, the text string can be written in multiple lines;
+ *          use newlines as the separators.
+ *      (6) Typical usage is for debugging, where the pixa of labelled images
+ *          is used to generate a pdf.  Suggest using 1.0 for scalefactor.
+ */
+l_int32
+pixaAddPixWithText(PIXA        *pixa,
+                   PIX         *pixs,
+                   l_int32      reduction,
+                   L_BMF       *bmf,
+                   const char  *textstr,
+                   l_uint32     val,
+                   l_int32      location)
+{
+l_int32   d;
+L_BMF    *bmf8;
+PIX      *pix1, *pix2, *pix3;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixaAddPixWithText");
+
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!textstr) {
+        textstr = pixGetText(pixs);
+        if (!textstr) {
+            L_ERROR("no textstring defined; inserting copy", procName);
+            pixaAddPix(pixa, pixs, L_COPY);
+            return 1;
+        }
+    }
+    if (location != L_ADD_ABOVE && location != L_ADD_BELOW &&
+        location != L_ADD_LEFT && location != L_ADD_RIGHT)
+        return ERROR_INT("invalid location", procName, 1);
+
+        /* Default font size is 8. */
+    bmf8 = (bmf) ? bmf : bmfCreate(NULL, 8);
+
+    if (reduction != 1)
+        pix1 = pixScaleByIntSampling(pixs, reduction);
+    else
+        pix1 = pixClone(pixs);
+
+        /* We want the text to be rendered in color.  This works
+         * automatically if pixs is cmapped or 32 bpp rgb; otherwise,
+         * we need to convert to rgb. */
+    cmap = pixGetColormap(pix1);
+    d = pixGetDepth(pix1);
+    if (!cmap && d != 32)
+        pix2 = pixConvertTo32(pix1);
+    else
+        pix2 = pixClone(pix1);
+
+    pix3 = pixAddTextlines(pix2, bmf, textstr, val, location);
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    if (!pix3)
+        return ERROR_INT("pix3 not made", procName, 1);
+
+    pixaAddPix(pixa, pix3, L_INSERT);
+    if (!bmf) bmfDestroy(&bmf8);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                   Text size estimation and partitioning             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  bmfGetLineStrings()
+ *
+ *      Input:  bmf
+ *              textstr
+ *              maxw (max width of a text line in pixels)
+ *              firstindent (indentation of first line, in x-widths)
+ *              &h (<return> height required to hold text bitmap)
+ *      Return: sarray of text strings for each line, or null on error
+ *
+ *  Notes:
+ *      (1) Divides the input text string into an array of text strings,
+ *          each of which will fit within maxw bits of width.
+ */
+SARRAY *
+bmfGetLineStrings(L_BMF       *bmf,
+                  const char  *textstr,
+                  l_int32      maxw,
+                  l_int32      firstindent,
+                  l_int32     *ph)
+{
+char    *linestr;
+l_int32  i, ifirst, sumw, newsum, w, nwords, nlines, len, xwidth;
+NUMA    *na;
+SARRAY  *sa, *sawords;
+
+    PROCNAME("bmfGetLineStrings");
+
+    if (!bmf)
+        return (SARRAY *)ERROR_PTR("bmf not defined", procName, NULL);
+    if (!textstr)
+        return (SARRAY *)ERROR_PTR("teststr not defined", procName, NULL);
+
+    if ((sawords = sarrayCreateWordsFromString(textstr)) == NULL)
+        return (SARRAY *)ERROR_PTR("sawords not made", procName, NULL);
+
+    if ((na = bmfGetWordWidths(bmf, textstr, sawords)) == NULL)
+        return (SARRAY *)ERROR_PTR("na not made", procName, NULL);
+    nwords = numaGetCount(na);
+    if (nwords == 0)
+        return (SARRAY *)ERROR_PTR("no words in textstr", procName, NULL);
+    bmfGetWidth(bmf, 'x', &xwidth);
+
+    if ((sa = sarrayCreate(0)) == NULL)
+        return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+
+    ifirst = 0;
+    numaGetIValue(na, 0, &w);
+    sumw = firstindent * xwidth + w;
+    for (i = 1; i < nwords; i++) {
+        numaGetIValue(na, i, &w);
+        newsum = sumw + bmf->spacewidth + w;
+        if (newsum > maxw) {
+            linestr = sarrayToStringRange(sawords, ifirst, i - ifirst, 2);
+            if (!linestr)
+                continue;
+            len = strlen(linestr);
+            if (len > 0)  /* it should always be */
+                linestr[len - 1] = '\0';  /* remove the last space */
+            sarrayAddString(sa, linestr, L_INSERT);
+            ifirst = i;
+            sumw = w;
+        }
+        else
+            sumw += bmf->spacewidth + w;
+    }
+    linestr = sarrayToStringRange(sawords, ifirst, nwords - ifirst, 2);
+    if (linestr)
+        sarrayAddString(sa, linestr, L_INSERT);
+    nlines = sarrayGetCount(sa);
+    *ph = nlines * bmf->lineheight + (nlines - 1) * bmf->vertlinesep;
+
+    sarrayDestroy(&sawords);
+    numaDestroy(&na);
+    return sa;
+}
+
+
+/*!
+ *  bmfGetWordWidths()
+ *
+ *      Input:  bmf
+ *              textstr
+ *              sa (of individual words)
+ *      Return: numa (of word lengths in pixels for the font represented
+ *                    by the bmf), or null on error
+ */
+NUMA *
+bmfGetWordWidths(L_BMF       *bmf,
+                 const char  *textstr,
+                 SARRAY      *sa)
+{
+char    *wordstr;
+l_int32  i, nwords, width;
+NUMA    *na;
+
+    PROCNAME("bmfGetWordWidths");
+
+    if (!bmf)
+        return (NUMA *)ERROR_PTR("bmf not defined", procName, NULL);
+    if (!textstr)
+        return (NUMA *)ERROR_PTR("teststr not defined", procName, NULL);
+    if (!sa)
+        return (NUMA *)ERROR_PTR("sa not defined", procName, NULL);
+
+    nwords = sarrayGetCount(sa);
+    if ((na = numaCreate(nwords)) == NULL)
+        return (NUMA *)ERROR_PTR("na not made", procName, NULL);
+
+    for (i = 0; i < nwords; i++) {
+        wordstr = sarrayGetString(sa, i, L_NOCOPY);
+        bmfGetStringWidth(bmf, wordstr, &width);
+        numaAddNumber(na, width);
+    }
+
+    return na;
+}
+
+
+/*!
+ *  bmfGetStringWidth()
+ *
+ *      Input:  bmf
+ *              textstr
+ *              &w (<return> width of text string, in pixels for the
+ *                 font represented by the bmf)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+bmfGetStringWidth(L_BMF       *bmf,
+                  const char  *textstr,
+                  l_int32     *pw)
+{
+char     chr;
+l_int32  i, w, width, nchar;
+
+    PROCNAME("bmfGetStringWidth");
+
+    if (!bmf)
+        return ERROR_INT("bmf not defined", procName, 1);
+    if (!textstr)
+        return ERROR_INT("teststr not defined", procName, 1);
+    if (!pw)
+        return ERROR_INT("&w not defined", procName, 1);
+
+    nchar = strlen(textstr);
+    w = 0;
+    for (i = 0; i < nchar; i++) {
+        chr = textstr[i];
+        bmfGetWidth(bmf, chr, &width);
+        if (width != UNDEF)
+            w += width + bmf->kernwidth;
+    }
+    w -= bmf->kernwidth;  /* remove last one */
+
+    *pw = w;
+    return 0;
+}
+
+
+
+/*---------------------------------------------------------------------*
+ *                             Text splitting                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  splitStringToParagraphs()
+ *
+ *      Input:  textstring
+ *              splitting flag (see enum in bmf.h; valid values in {1,2,3})
+ *      Return: sarray (where each string is a paragraph of the input),
+ *                      or null on error.
+ */
+SARRAY *
+splitStringToParagraphs(char    *textstr,
+                        l_int32  splitflag)
+{
+char    *linestr, *parastring;
+l_int32  nlines, i, allwhite, leadwhite;
+SARRAY  *salines, *satemp, *saout;
+
+    PROCNAME("splitStringToParagraphs");
+
+    if (!textstr)
+        return (SARRAY *)ERROR_PTR("textstr not defined", procName, NULL);
+
+    if ((salines = sarrayCreateLinesFromString(textstr, 1)) == NULL)
+        return (SARRAY *)ERROR_PTR("salines not made", procName, NULL);
+    nlines = sarrayGetCount(salines);
+    saout = sarrayCreate(0);
+    satemp = sarrayCreate(0);
+
+    linestr = sarrayGetString(salines, 0, L_NOCOPY);
+    sarrayAddString(satemp, linestr, L_COPY);
+    for (i = 1; i < nlines; i++) {
+        linestr = sarrayGetString(salines, i, L_NOCOPY);
+        stringAllWhitespace(linestr, &allwhite);
+        stringLeadingWhitespace(linestr, &leadwhite);
+        if ((splitflag == SPLIT_ON_LEADING_WHITE && leadwhite) ||
+            (splitflag == SPLIT_ON_BLANK_LINE && allwhite) ||
+            (splitflag == SPLIT_ON_BOTH && (allwhite || leadwhite))) {
+            parastring = sarrayToString(satemp, 1);  /* add nl to each line */
+            sarrayAddString(saout, parastring, L_INSERT);
+            sarrayDestroy(&satemp);
+            satemp = sarrayCreate(0);
+        }
+        sarrayAddString(satemp, linestr, L_COPY);
+    }
+    parastring = sarrayToString(satemp, 1);  /* add nl to each line */
+    sarrayAddString(saout, parastring, L_INSERT);
+    sarrayDestroy(&satemp);
+
+    return saout;
+}
+
+
+/*!
+ *  stringAllWhitespace()
+ *
+ *      Input:  textstring
+ *              &val (<return> 1 if all whitespace; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+stringAllWhitespace(char     *textstr,
+                    l_int32  *pval)
+{
+l_int32  len, i;
+
+    PROCNAME("stringAllWhitespace");
+
+    if (!textstr)
+        return ERROR_INT("textstr not defined", procName, 1);
+    if (!pval)
+        return ERROR_INT("&va not defined", procName, 1);
+
+    len = strlen(textstr);
+    *pval = 1;
+    for (i = 0; i < len; i++) {
+        if (textstr[i] != ' ' && textstr[i] != '\t' && textstr[i] != '\n') {
+            *pval = 0;
+            return 0;
+        }
+    }
+    return 0;
+}
+
+
+/*!
+ *  stringLeadingWhitespace()
+ *
+ *      Input:  textstring
+ *              &val (<return> 1 if leading char is ' ' or '\t'; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+stringLeadingWhitespace(char     *textstr,
+                        l_int32  *pval)
+{
+    PROCNAME("stringLeadingWhitespace");
+
+    if (!textstr)
+        return ERROR_INT("textstr not defined", procName, 1);
+    if (!pval)
+        return ERROR_INT("&va not defined", procName, 1);
+
+    *pval = 0;
+    if (textstr[0] == ' ' || textstr[0] == '\t')
+        *pval = 1;
+
+    return 0;
+}
diff --git a/src/tiffio.c b/src/tiffio.c
new file mode 100644 (file)
index 0000000..fd8485b
--- /dev/null
@@ -0,0 +1,2318 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  tiffio.c
+ *
+ *     TIFFClientOpen() wrappers for FILE*:
+ *      static tsize_t    lept_read_proc()
+ *      static tsize_t    lept_write_proc()
+ *      static toff_t     lept_seek_proc()
+ *      static int        lept_close_proc()
+ *      static toff_t     lept_size_proc()
+ *
+ *     Reading tiff:
+ *             PIX       *pixReadTiff()    [ special top level ]
+ *             PIX       *pixReadStreamTiff()
+ *      static PIX       *pixReadFromTiffStream()
+ *
+ *     Writing tiff:
+ *             l_int32    pixWriteTiff()   [ special top level ]
+ *             l_int32    pixWriteTiffCustom()   [ special top level ]
+ *             l_int32    pixWriteStreamTiff()
+ *      static l_int32    pixWriteToTiffStream()
+ *      static l_int32    writeCustomTiffTags()
+ *
+ *     Reading and writing multipage tiff
+ *             PIXA       pixaReadMultipageTiff()
+ *             l_int32    writeMultipageTiff()  [ special top level ]
+ *             l_int32    writeMultipageTiffSA()
+ *
+ *     Information about tiff file
+ *             l_int32    fprintTiffInfo()
+ *             l_int32    tiffGetCount()
+ *             l_int32    getTiffResolution()
+ *      static l_int32    getTiffStreamResolution()
+ *             l_int32    readHeaderTiff()
+ *             l_int32    freadHeaderTiff()
+ *             l_int32    readHeaderMemTiff()
+ *      static l_int32    tiffReadHeaderTiff()
+ *             l_int32    findTiffCompression()
+ *      static l_int32    getTiffCompressedFormat()
+ *
+ *     Extraction of tiff g4 data:
+ *             l_int32    extractG4DataFromFile()
+ *
+ *     Open tiff stream from file stream
+ *      static TIFF      *fopenTiff()
+ *
+ *     Wrapper for TIFFOpen:
+ *      static TIFF      *openTiff()
+ *
+ *     Memory I/O: reading memory --> pix and writing pix --> memory
+ *             [10 static helper functions]
+ *             l_int32    pixReadMemTiff();
+ *             l_int32    pixWriteMemTiff();
+ *             l_int32    pixWriteMemTiffCustom();
+ *
+ *  Note:  To include all necessary functions, use libtiff version 3.7.4
+ *         (or later)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <sys/types.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#else  /* _MSC_VER */
+#include <io.h>
+#endif  /* _MSC_VER */
+#include <fcntl.h>
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  HAVE_LIBTIFF   /* defined in environ.h */
+/* --------------------------------------------*/
+
+#include "tiff.h"
+#include "tiffio.h"
+
+static const l_int32  DEFAULT_RESOLUTION = 300;   /* ppi */
+static const l_int32  MAX_PAGES_IN_TIFF_FILE = 3000;  /* should be enough */
+
+
+    /* All functions with TIFF interfaces are static. */
+static PIX      *pixReadFromTiffStream(TIFF *tif);
+static l_int32   getTiffStreamResolution(TIFF *tif, l_int32 *pxres,
+                                         l_int32 *pyres);
+static l_int32   tiffReadHeaderTiff(TIFF *tif, l_int32 *pwidth,
+                                    l_int32 *pheight, l_int32 *pbps,
+                                    l_int32 *pspp, l_int32 *pres,
+                                    l_int32 *pcmap, l_int32 *pformat);
+static l_int32   writeCustomTiffTags(TIFF *tif, NUMA *natags,
+                                     SARRAY *savals, SARRAY  *satypes,
+                                     NUMA *nasizes);
+static l_int32   pixWriteToTiffStream(TIFF *tif, PIX *pix, l_int32 comptype,
+                                      NUMA *natags, SARRAY *savals,
+                                      SARRAY *satypes, NUMA *nasizes);
+static TIFF     *fopenTiff(FILE *fp, const char *modestring);
+static TIFF     *openTiff(const char *filename, const char *modestring);
+
+    /* Static helper for tiff compression type */
+static l_int32   getTiffCompressedFormat(l_uint16 tiffcomp);
+
+    /* Static function for memory I/O */
+static TIFF     *fopenTiffMemstream(const char *filename, const char *operation,
+                                    l_uint8 **pdata, size_t *pdatasize);
+
+    /* This structure defines a transform to be performed on a TIFF image
+     * (note that the same transformation can be represented in
+     * several different ways using this structure since
+     * vflip + hflip + counterclockwise == clockwise). */
+struct tiff_transform {
+    int vflip;    /* if non-zero, image needs a vertical fip */
+    int hflip;    /* if non-zero, image needs a horizontal flip */
+    int rotate;   /* -1 -> counterclockwise 90-degree rotation,
+                      0 -> no rotation
+                      1 -> clockwise 90-degree rotation */
+};
+
+    /* This describes the transformations needed for a given orientation
+     * tag.  The tag values start at 1, so you need to subtract 1 to get a
+     * valid index into this array.  It is only valid when not using
+     * TIFFReadRGBAImageOriented(). */
+static struct tiff_transform tiff_orientation_transforms[] = {
+    {0, 0, 0},
+    {0, 1, 0},
+    {1, 1, 0},
+    {1, 0, 0},
+    {0, 1, -1},
+    {0, 0, 1},
+    {0, 1, 1},
+    {0, 0, -1}
+};
+
+    /* Same as above, except that test transformations are only valid
+     * when using TIFFReadRGBAImageOriented().  Transformations
+     * were determined empirically.  See the libtiff mailing list for
+     * more discussion: http://www.asmail.be/msg0054683875.html  */
+static struct tiff_transform tiff_partial_orientation_transforms[] = {
+    {0, 0, 0},
+    {0, 0, 0},
+    {0, 0, 0},
+    {0, 0, 0},
+    {0, 1, -1},
+    {0, 1, 1},
+    {1, 0, 1},
+    {0, 1, -1}
+};
+
+
+/*-----------------------------------------------------------------------*
+ *             TIFFClientOpen() wrappers for FILE*                       *
+ *             Provided by Jürgen Buchmüller                             *
+ *                                                                       *
+ *  We previously used TIFFFdOpen(), which used low-level file           *
+ *  descriptors.  It had portability issues with Windows, along          *
+ *  with other limitations from lack of stream control operations.       *
+ *  These callbacks to TIFFClientOpen() avoid the problems.              *
+ *                                                                       *
+ *  Jürgen made the functions use 64 bit file operations where possible  *
+ *  or required, namely for seek and size. On Windows there are specific *
+ *  _fseeki64() and _ftelli64() functions, whereas on unix it is         *
+ *  common to look for a macro _LARGEFILE_SOURCE being defined and       *
+ *  use fseeko() and ftello() in this case.                              *
+ *-----------------------------------------------------------------------*/
+static tsize_t
+lept_read_proc(thandle_t  cookie,
+               tdata_t    buff,
+               tsize_t    size)
+{
+    FILE* fp = (FILE *)cookie;
+    tsize_t done;
+    if (!buff || !cookie || !fp)
+        return (tsize_t)-1;
+    done = fread(buff, 1, size, fp);
+    return done;
+}
+
+static tsize_t
+lept_write_proc(thandle_t  cookie,
+                tdata_t    buff,
+                tsize_t    size)
+{
+    FILE* fp = (FILE *)cookie;
+    tsize_t done;
+    if (!buff || !cookie || !fp)
+        return (tsize_t)-1;
+    done = fwrite(buff, 1, size, fp);
+    return done;
+}
+
+static toff_t
+lept_seek_proc(thandle_t  cookie,
+               toff_t     offs,
+               int        whence)
+{
+    FILE* fp = (FILE *)cookie;
+#if defined(_MSC_VER)
+    __int64 pos = 0;
+    if (!cookie || !fp)
+        return (tsize_t)-1;
+    switch (whence) {
+    case SEEK_SET:
+        pos = 0;
+        break;
+    case SEEK_CUR:
+        pos = ftell(fp);
+        break;
+    case SEEK_END:
+        _fseeki64(fp, 0, SEEK_END);
+        pos = _ftelli64(fp);
+        break;
+    }
+    pos = (__int64)(pos + offs);
+    _fseeki64(fp, pos, SEEK_SET);
+    if (pos == _ftelli64(fp))
+        return (tsize_t)pos;
+#elif defined(_LARGEFILE_SOURCE)
+    off64_t pos = 0;
+    if (!cookie || !fp)
+        return (tsize_t)-1;
+    switch (whence) {
+    case SEEK_SET:
+        pos = 0;
+        break;
+    case SEEK_CUR:
+        pos = ftello(fp);
+        break;
+    case SEEK_END:
+        fseeko(fp, 0, SEEK_END);
+        pos = ftello(fp);
+        break;
+    }
+    pos = (off64_t)(pos + offs);
+    fseeko(fp, pos, SEEK_SET);
+    if (pos == ftello(fp))
+        return (tsize_t)pos;
+#else
+    off_t pos = 0;
+    if (!cookie || !fp)
+        return (tsize_t)-1;
+    switch (whence) {
+    case SEEK_SET:
+        pos = 0;
+        break;
+    case SEEK_CUR:
+        pos = ftell(fp);
+        break;
+    case SEEK_END:
+        fseek(fp, 0, SEEK_END);
+        pos = ftell(fp);
+        break;
+    }
+    pos = (off_t)(pos + offs);
+    fseek(fp, pos, SEEK_SET);
+    if (pos == ftell(fp))
+        return (tsize_t)pos;
+#endif
+    return (tsize_t)-1;
+}
+
+static int
+lept_close_proc(thandle_t  cookie)
+{
+    FILE* fp = (FILE *)cookie;
+    if (!cookie || !fp)
+        return 0;
+    fseek(fp, 0, SEEK_SET);
+    return 0;
+}
+
+static toff_t
+lept_size_proc(thandle_t  cookie)
+{
+    FILE* fp = (FILE *)cookie;
+#if defined(_MSC_VER)
+    __int64 pos;
+    __int64 size;
+    if (!cookie || !fp)
+        return (tsize_t)-1;
+    pos = _ftelli64(fp);
+    _fseeki64(fp, 0, SEEK_END);
+    size = _ftelli64(fp);
+    _fseeki64(fp, pos, SEEK_SET);
+#elif defined(_LARGEFILE_SOURCE)
+    off64_t pos;
+    off64_t size;
+    if (!fp)
+        return (tsize_t)-1;
+    pos = ftello(fp);
+    fseeko(fp, 0, SEEK_END);
+    size = ftello(fp);
+    fseeko(fp, pos, SEEK_SET);
+#else
+    off_t pos;
+    off_t size;
+    if (!cookie || !fp)
+        return (tsize_t)-1;
+    pos = ftell(fp);
+    fseek(fp, 0, SEEK_END);
+    size = ftell(fp);
+    fseek(fp, pos, SEEK_SET);
+#endif
+    return (toff_t)size;
+}
+
+
+/*--------------------------------------------------------------*
+ *                      Reading from file                       *
+ *--------------------------------------------------------------*/
+/*!
+ *  pixReadTiff()
+ *
+ *      Input:  filename
+ *              page number (0 based)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) This is a version of pixRead(), specialized for tiff
+ *          files, that allows specification of the page to be returned
+ *      (2) No warning messages on failure, because of how multi-page
+ *          TIFF reading works. You are supposed to keep trying until
+ *          it stops working.
+ */
+PIX *
+pixReadTiff(const char  *filename,
+            l_int32      n)
+{
+FILE  *fp;
+PIX   *pix;
+
+    PROCNAME("pixReadTiff");
+
+    if (!filename)
+        return (PIX *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PIX *)ERROR_PTR("image file not found", procName, NULL);
+    pix = pixReadStreamTiff(fp, n);
+    fclose(fp);
+    return pix;
+}
+
+
+/*--------------------------------------------------------------*
+ *                     Reading from stream                      *
+ *--------------------------------------------------------------*/
+/*!
+ *  pixReadStreamTiff()
+ *
+ *      Input:  stream
+ *              n (page number: 0 based)
+ *      Return: pix, or null on error (e.g., if the page number is invalid)
+ *
+ *  Notes:
+ *      (1) No warning messages on failure, because of how multi-page
+ *          TIFF reading works. You are supposed to keep trying until
+ *          it stops working.
+ */
+PIX *
+pixReadStreamTiff(FILE    *fp,
+                  l_int32  n)
+{
+l_int32  i;
+PIX     *pix;
+TIFF    *tif;
+
+    PROCNAME("pixReadStreamTiff");
+
+    if (!fp)
+        return (PIX *)ERROR_PTR("stream not defined", procName, NULL);
+
+    if ((tif = fopenTiff(fp, "r")) == NULL)
+        return (PIX *)ERROR_PTR("tif not opened", procName, NULL);
+
+    pix = NULL;
+    for (i = 0; i < MAX_PAGES_IN_TIFF_FILE; i++) {
+        TIFFSetDirectory(tif, i);
+        if (i == n) {
+            if ((pix = pixReadFromTiffStream(tif)) == NULL) {
+                TIFFCleanup(tif);
+                return NULL;
+            }
+            break;
+        }
+        if (TIFFReadDirectory(tif) == 0)
+            break;
+    }
+
+    TIFFCleanup(tif);
+    return pix;
+}
+
+
+/*!
+ *  pixReadFromTiffStream()
+ *
+ *      Input:  stream
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) We handle pixels up to 32 bits.  This includes:
+ *          1 spp (grayscale): 1, 2, 4, 8, 16 bpp
+ *          1 spp (colormapped): 1, 2, 4, 8 bpp
+ *          3 spp (color): 8 bpp
+ *          We do not handle 3 spp, 16 bpp (48 bits/pixel)
+ *      (2) For colormapped images, we support 8 bits/color in the palette.
+ *          Tiff colormaps have 16 bits/color, and we reduce them to 8.
+ *      (3) Quoting the libtiff documenation at
+ *               http://libtiff.maptools.org/libtiff.html
+ *          "libtiff provides a high-level interface for reading image data
+ *          from a TIFF file. This interface handles the details of data
+ *          organization and format for a wide variety of TIFF files;
+ *          at least the large majority of those files that one would
+ *          normally encounter. Image data is, by default, returned as
+ *          ABGR pixels packed into 32-bit words (8 bits per sample).
+ *          Rectangular rasters can be read or data can be intercepted
+ *          at an intermediate level and packed into memory in a format
+ *          more suitable to the application. The library handles all
+ *          the details of the format of data stored on disk and,
+ *          in most cases, if any colorspace conversions are required:
+ *          bilevel to RGB, greyscale to RGB, CMYK to RGB, YCbCr to RGB,
+ *          16-bit samples to 8-bit samples, associated/unassociated alpha,
+ *          etc."
+ */
+static PIX *
+pixReadFromTiffStream(TIFF  *tif)
+{
+l_uint8   *linebuf, *data;
+l_uint16   spp, bps, bpp, photometry, tiffcomp, orientation;
+l_uint16  *redmap, *greenmap, *bluemap;
+l_int32    d, wpl, bpl, comptype, i, j, ncolors, rval, gval, bval;
+l_int32    xres, yres;
+l_uint32   w, h, tiffbpl, tiffword;
+l_uint32  *line, *ppixel, *tiffdata;
+l_uint32   read_oriented;
+PIX       *pix;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixReadFromTiffStream");
+
+    if (!tif)
+        return (PIX *)ERROR_PTR("tif not defined", procName, NULL);
+
+    read_oriented = 0;
+
+        /* Use default fields for bps and spp */
+    TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bps);
+    TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &spp);
+    bpp = bps * spp;
+    if (bpp > 32)
+        L_WARNING("bpp = %d; stripping 16 bit rgb samples down to 8\n",
+                  procName, bpp);
+    if (spp == 1)
+        d = bps;
+    else if (spp == 3 || spp == 4)
+        d = 32;
+    else
+        return (PIX *)ERROR_PTR("spp not in set {1,3,4}", procName, NULL);
+
+    TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w);
+    TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h);
+    tiffbpl = TIFFScanlineSize(tif);
+
+    if ((pix = pixCreate(w, h, d)) == NULL)
+        return (PIX *)ERROR_PTR("pix not made", procName, NULL);
+    pixSetInputFormat(pix, IFF_TIFF);
+    data = (l_uint8 *)pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    bpl = 4 * wpl;
+
+        /* Read the data */
+    if (spp == 1) {
+        if ((linebuf = (l_uint8 *)LEPT_CALLOC(tiffbpl + 1, sizeof(l_uint8)))
+            == NULL)
+            return (PIX *)ERROR_PTR("calloc fail for linebuf", procName, NULL);
+
+        for (i = 0 ; i < h ; i++) {
+            if (TIFFReadScanline(tif, linebuf, i, 0) < 0) {
+                LEPT_FREE(linebuf);
+                pixDestroy(&pix);
+                return (PIX *)ERROR_PTR("line read fail", procName, NULL);
+            }
+            memcpy((char *)data, (char *)linebuf, tiffbpl);
+            data += bpl;
+        }
+        if (bps <= 8)
+            pixEndianByteSwap(pix);
+        else   /* bps == 16 */
+            pixEndianTwoByteSwap(pix);
+        LEPT_FREE(linebuf);
+    }
+    else {  /* rgb */
+        if ((tiffdata = (l_uint32 *)LEPT_CALLOC(w * h, sizeof(l_uint32)))
+            == NULL) {
+            pixDestroy(&pix);
+            return (PIX *)ERROR_PTR("calloc fail for tiffdata", procName, NULL);
+        }
+            /* TIFFReadRGBAImageOriented() converts to 8 bps */
+        if (!TIFFReadRGBAImageOriented(tif, w, h, (uint32 *)tiffdata,
+                                       ORIENTATION_TOPLEFT, 0)) {
+            LEPT_FREE(tiffdata);
+            pixDestroy(&pix);
+            return (PIX *)ERROR_PTR("failed to read tiffdata", procName, NULL);
+        } else {
+            read_oriented = 1;
+        }
+
+        line = pixGetData(pix);
+        for (i = 0 ; i < h ; i++, line += wpl) {
+            for (j = 0, ppixel = line; j < w; j++) {
+                    /* TIFFGet* are macros */
+                tiffword = tiffdata[i * w + j];
+                rval = TIFFGetR(tiffword);
+                gval = TIFFGetG(tiffword);
+                bval = TIFFGetB(tiffword);
+                composeRGBPixel(rval, gval, bval, ppixel);
+                ppixel++;
+            }
+        }
+        LEPT_FREE(tiffdata);
+    }
+
+    if (getTiffStreamResolution(tif, &xres, &yres) == 0) {
+        pixSetXRes(pix, xres);
+        pixSetYRes(pix, yres);
+    }
+
+        /* Find and save the compression type */
+    TIFFGetFieldDefaulted(tif, TIFFTAG_COMPRESSION, &tiffcomp);
+    comptype = getTiffCompressedFormat(tiffcomp);
+    pixSetInputFormat(pix, comptype);
+
+    if (TIFFGetField(tif, TIFFTAG_COLORMAP, &redmap, &greenmap, &bluemap)) {
+            /* Save the colormap as a pix cmap.  Because the
+             * tiff colormap components are 16 bit unsigned,
+             * and go from black (0) to white (0xffff), the
+             * the pix cmap takes the most significant byte. */
+        if (bps > 8) {
+            pixDestroy(&pix);
+            return (PIX *)ERROR_PTR("invalid bps; > 8", procName, NULL);
+        }
+        if ((cmap = pixcmapCreate(bps)) == NULL) {
+            pixDestroy(&pix);
+            return (PIX *)ERROR_PTR("cmap not made", procName, NULL);
+        }
+        ncolors = 1 << bps;
+        for (i = 0; i < ncolors; i++)
+            pixcmapAddColor(cmap, redmap[i] >> 8, greenmap[i] >> 8,
+                            bluemap[i] >> 8);
+        pixSetColormap(pix, cmap);
+    } else {   /* No colormap: check photometry and invert if necessary */
+        if (!TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometry)) {
+                /* Guess default photometry setting.  Assume min_is_white
+                 * if compressed 1 bpp; min_is_black otherwise. */
+            if (tiffcomp == COMPRESSION_CCITTFAX3 ||
+                tiffcomp == COMPRESSION_CCITTFAX4 ||
+                tiffcomp == COMPRESSION_CCITTRLE ||
+                tiffcomp == COMPRESSION_CCITTRLEW) {
+                photometry = PHOTOMETRIC_MINISWHITE;
+            } else {
+                photometry = PHOTOMETRIC_MINISBLACK;
+            }
+        }
+        if ((d == 1 && photometry == PHOTOMETRIC_MINISBLACK) ||
+            (d == 8 && photometry == PHOTOMETRIC_MINISWHITE))
+            pixInvert(pix, pix);
+    }
+
+    if (TIFFGetField(tif, TIFFTAG_ORIENTATION, &orientation)) {
+        if (orientation >= 1 && orientation <= 8) {
+            struct tiff_transform *transform = (read_oriented) ?
+                &tiff_partial_orientation_transforms[orientation - 1] :
+                &tiff_orientation_transforms[orientation - 1];
+            if (transform->vflip) pixFlipTB(pix, pix);
+            if (transform->hflip) pixFlipLR(pix, pix);
+            if (transform->rotate) {
+                PIX *oldpix = pix;
+                pix = pixRotate90(oldpix, transform->rotate);
+                pixDestroy(&oldpix);
+            }
+        }
+    }
+
+    return pix;
+}
+
+
+/*--------------------------------------------------------------*
+ *                       Writing to file                        *
+ *--------------------------------------------------------------*/
+/*!
+ *  pixWriteTiff()
+ *
+ *      Input:  filename (to write to)
+ *              pix
+ *              comptype (IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS,
+ *                        IFF_TIFF_G3, IFF_TIFF_G4,
+ *                        IFF_TIFF_LZW, IFF_TIFF_ZIP)
+ *              modestring ("a" or "w")
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For multi-page tiff, write the first pix with mode "w" and
+ *          all subsequent pix with mode "a".
+ */
+l_int32
+pixWriteTiff(const char  *filename,
+             PIX         *pix,
+             l_int32      comptype,
+             const char  *modestring)
+{
+    return pixWriteTiffCustom(filename, pix, comptype, modestring,
+                              NULL, NULL, NULL, NULL);
+}
+
+
+/*!
+ *  pixWriteTiffCustom()
+ *
+ *      Input:  filename (to write to)
+ *              pix
+ *              comptype (IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS,
+ *                        IFF_TIFF_G3, IFF_TIFF_G4)
+ *                        IFF_TIFF_LZW, IFF_TIFF_ZIP)
+ *              modestring ("a" or "w")
+ *              natags (<optional> NUMA of custom tiff tags)
+ *              savals (<optional> SARRAY of values)
+ *              satypes (<optional> SARRAY of types)
+ *              nasizes (<optional> NUMA of sizes)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Usage:
+ *      (1) This writes a page image to a tiff file, with optional
+ *          extra tags defined in tiff.h
+ *      (2) For multi-page tiff, write the first pix with mode "w" and
+ *          all subsequent pix with mode "a".
+ *      (3) For the custom tiff tags:
+ *          (a) The three arrays {natags, savals, satypes} must all be
+ *              either NULL or defined and of equal size.
+ *          (b) If they are defined, the tags are an array of integers,
+ *              the vals are an array of values in string format, and
+ *              the types are an array of types in string format.
+ *          (c) All valid tags are definined in tiff.h.
+ *          (d) The types allowed are the set of strings:
+ *                "char*"
+ *                "l_uint8*"
+ *                "l_uint16"
+ *                "l_uint32"
+ *                "l_int32"
+ *                "l_float64"
+ *                "l_uint16-l_uint16" (note the dash; use it between the
+ *                                    two l_uint16 vals in the val string)
+ *              Of these, "char*" and "l_uint16" are the most commonly used.
+ *          (e) The last array, nasizes, is also optional.  It is for
+ *              tags that take an array of bytes for a value, a number of
+ *              elements in the array, and a type that is either "char*"
+ *              or "l_uint8*" (probably either will work).
+ *              Use NULL if there are no such tags.
+ *          (f) VERY IMPORTANT: if there are any tags that require the
+ *              extra size value, stored in nasizes, they must be
+ *              written first!
+ */
+l_int32
+pixWriteTiffCustom(const char  *filename,
+                   PIX         *pix,
+                   l_int32      comptype,
+                   const char  *modestring,
+                   NUMA        *natags,
+                   SARRAY      *savals,
+                   SARRAY      *satypes,
+                   NUMA        *nasizes)
+{
+l_int32  ret;
+TIFF    *tif;
+
+    PROCNAME("pixWriteTiffCustom");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    if ((tif = openTiff(filename, modestring)) == NULL)
+        return ERROR_INT("tif not opened", procName, 1);
+    ret = pixWriteToTiffStream(tif, pix, comptype, natags, savals,
+                               satypes, nasizes);
+    TIFFClose(tif);
+
+    return ret;
+}
+
+
+/*--------------------------------------------------------------*
+ *                       Writing to stream                      *
+ *--------------------------------------------------------------*/
+/*!
+ *  pixWriteStreamTiff()
+ *
+ *      Input:  stream (opened for append or write)
+ *              pix
+ *              comptype (IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS,
+ *                        IFF_TIFF_G3, IFF_TIFF_G4,
+ *                        IFF_TIFF_LZW, IFF_TIFF_ZIP)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For images with bpp > 1, this resets the comptype, if
+ *          necessary, to write uncompressed data.
+ *      (2) G3 and G4 are only defined for 1 bpp.
+ *      (3) We only allow PACKBITS for bpp = 1, because for bpp > 1
+ *          it typically expands images that are not synthetically generated.
+ *      (4) G4 compression is typically about twice as good as G3.
+ *          G4 is excellent for binary compression of text/line-art,
+ *          but terrible for halftones and dithered patterns.  (In
+ *          fact, G4 on halftones can give a file that is larger
+ *          than uncompressed!)  If a binary image has dithered
+ *          regions, it is usually better to compress with png.
+ */
+l_int32
+pixWriteStreamTiff(FILE    *fp,
+                   PIX     *pix,
+                   l_int32  comptype)
+{
+TIFF  *tif;
+
+    PROCNAME("pixWriteStreamTiff");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1 );
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1 );
+
+    if (pixGetDepth(pix) != 1 && comptype != IFF_TIFF &&
+        comptype != IFF_TIFF_LZW && comptype != IFF_TIFF_ZIP) {
+        L_WARNING("invalid compression type for bpp > 1\n", procName);
+        comptype = IFF_TIFF_ZIP;
+    }
+
+    if ((tif = fopenTiff(fp, "w")) == NULL)
+        return ERROR_INT("tif not opened", procName, 1);
+
+    if (pixWriteToTiffStream(tif, pix, comptype, NULL, NULL, NULL, NULL)) {
+        TIFFCleanup(tif);
+        return ERROR_INT("tif write error", procName, 1);
+    }
+
+    TIFFCleanup(tif);
+    return 0;
+}
+
+
+/*!
+ *  pixWriteToTiffStream()
+ *
+ *      Input:  tif (data structure, opened to a file)
+ *              pix
+ *              comptype  (IFF_TIFF: for any image; no compression
+ *                         IFF_TIFF_RLE, IFF_TIFF_PACKBITS: for 1 bpp only
+ *                         IFF_TIFF_G4 and IFF_TIFF_G3: for 1 bpp only
+ *                         IFF_TIFF_LZW, IFF_TIFF_ZIP: for any image
+ *              natags (<optional> NUMA of custom tiff tags)
+ *              savals (<optional> SARRAY of values)
+ *              satypes (<optional> SARRAY of types)
+ *              nasizes (<optional> NUMA of sizes)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This static function should only be called through higher
+ *          level functions in this file; namely, pixWriteTiffCustom(),
+ *          pixWriteTiff(), pixWriteStreamTiff(), pixWriteMemTiff()
+ *          and pixWriteMemTiffCustom().
+ *      (2) We only allow PACKBITS for bpp = 1, because for bpp > 1
+ *          it typically expands images that are not synthetically generated.
+ *      (3) See pixWriteTiffCustom() for details on how to use
+ *          the last four parameters for customized tiff tags.
+ *      (4) The only valid pixel depths in leptonica are 1, 2, 4, 8, 16
+ *          and 32.  However, it is possible, and in some cases desirable,
+ *          to write out a tiff file using an rgb pix that has 24 bpp.
+ *          This can be created by appending the raster data for a 24 bpp
+ *          image (with proper scanline padding) directly to a 24 bpp
+ *          pix that was created without a data array.  See note in
+ *          pixWriteStreamPng() for an example.
+ */
+static l_int32
+pixWriteToTiffStream(TIFF    *tif,
+                     PIX     *pix,
+                     l_int32  comptype,
+                     NUMA    *natags,
+                     SARRAY  *savals,
+                     SARRAY  *satypes,
+                     NUMA    *nasizes)
+{
+l_uint8   *linebuf, *data;
+l_uint16   redmap[256], greenmap[256], bluemap[256];
+l_int32    w, h, d, i, j, k, wpl, bpl, tiffbpl, ncolors, cmapsize;
+l_int32   *rmap, *gmap, *bmap;
+l_int32    xres, yres;
+l_uint32  *line, *ppixel;
+PIX       *pixt;
+PIXCMAP   *cmap;
+char      *text;
+
+    PROCNAME("pixWriteToTiffStream");
+
+    if (!tif)
+        return ERROR_INT("tif stream not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT( "pix not defined", procName, 1 );
+
+    pixGetDimensions(pix, &w, &h, &d);
+    xres = pixGetXRes(pix);
+    yres = pixGetYRes(pix);
+    if (xres == 0) xres = DEFAULT_RESOLUTION;
+    if (yres == 0) yres = DEFAULT_RESOLUTION;
+
+        /* ------------------ Write out the header -------------  */
+    TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, (l_uint32)RESUNIT_INCH);
+    TIFFSetField(tif, TIFFTAG_XRESOLUTION, (l_float64)xres);
+    TIFFSetField(tif, TIFFTAG_YRESOLUTION, (l_float64)yres);
+
+    TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, (l_uint32)w);
+    TIFFSetField(tif, TIFFTAG_IMAGELENGTH, (l_uint32)h);
+    TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
+
+    if ((text = pixGetText(pix)) != NULL)
+        TIFFSetField(tif, TIFFTAG_IMAGEDESCRIPTION, text);
+
+    if (d == 1)
+        TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
+    else if (d == 32 || d == 24) {
+        TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
+        TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE,
+                       (l_uint16)8, (l_uint16)8, (l_uint16)8);
+        TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, (l_uint16)3);
+    } else if ((cmap = pixGetColormap(pix)) == NULL) {
+        TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
+    } else {  /* Save colormap in the tiff; not more than 256 colors */
+        pixcmapToArrays(cmap, &rmap, &gmap, &bmap, NULL);
+        ncolors = pixcmapGetCount(cmap);
+        ncolors = L_MIN(256, ncolors);  /* max 256 */
+        cmapsize = 1 << d;
+        cmapsize = L_MIN(256, cmapsize);  /* power of 2; max 256 */
+        if (ncolors > cmapsize) {
+            L_WARNING("too many colors in cmap for tiff; truncating\n",
+                      procName);
+            ncolors = cmapsize;
+        }
+        for (i = 0; i < ncolors; i++) {
+            redmap[i] = (rmap[i] << 8) | rmap[i];
+            greenmap[i] = (gmap[i] << 8) | gmap[i];
+            bluemap[i] = (bmap[i] << 8) | bmap[i];
+        }
+        for (i = ncolors; i < cmapsize; i++)  /* init, even though not used */
+            redmap[i] = greenmap[i] = bluemap[i] = 0;
+        LEPT_FREE(rmap);
+        LEPT_FREE(gmap);
+        LEPT_FREE(bmap);
+
+        TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE);
+        TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, (l_uint16)1);
+        TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, (l_uint16)d);
+        TIFFSetField(tif, TIFFTAG_COLORMAP, redmap, greenmap, bluemap);
+    }
+
+    if (d != 24 && d != 32) {
+        TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, (l_uint16)d);
+        TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, (l_uint16)1);
+    }
+
+    TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+    if (comptype == IFF_TIFF) {  /* no compression */
+        TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
+    } else if (comptype == IFF_TIFF_G4) {
+        TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX4);
+    } else if (comptype == IFF_TIFF_G3) {
+        TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX3);
+    } else if (comptype == IFF_TIFF_RLE) {
+        TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_CCITTRLE);
+    } else if (comptype == IFF_TIFF_PACKBITS) {
+        TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_PACKBITS);
+    } else if (comptype == IFF_TIFF_LZW) {
+        TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_LZW);
+    } else if (comptype == IFF_TIFF_ZIP) {
+        TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_ADOBE_DEFLATE);
+    } else {
+        L_WARNING("unknown tiff compression; using none\n", procName);
+        TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
+    }
+
+        /* This is a no-op if arrays are NULL */
+    writeCustomTiffTags(tif, natags, savals, satypes, nasizes);
+
+        /* ------------- Write out the image data -------------  */
+    tiffbpl = TIFFScanlineSize(tif);
+    wpl = pixGetWpl(pix);
+    bpl = 4 * wpl;
+    if (tiffbpl > bpl)
+        fprintf(stderr, "Big trouble: tiffbpl = %d, bpl = %d\n", tiffbpl, bpl);
+    if ((linebuf = (l_uint8 *)LEPT_CALLOC(1, bpl)) == NULL)
+        return ERROR_INT("calloc fail for linebuf", procName, 1);
+
+        /* Use single strip for image */
+    TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, h);
+
+    if (d != 24 && d != 32) {
+        if (d == 16)
+            pixt = pixEndianTwoByteSwapNew(pix);
+        else
+            pixt = pixEndianByteSwapNew(pix);
+        data = (l_uint8 *)pixGetData(pixt);
+        for (i = 0; i < h; i++, data += bpl) {
+            memcpy((char *)linebuf, (char *)data, tiffbpl);
+            if (TIFFWriteScanline(tif, linebuf, i, 0) < 0)
+                break;
+        }
+        pixDestroy(&pixt);
+    } else if (d == 24) {  /* See note 4 above: special case of 24 bpp rgb */
+        for (i = 0; i < h; i++) {
+            line = pixGetData(pix) + i * wpl;
+            if (TIFFWriteScanline(tif, (l_uint8 *)line, i, 0) < 0)
+                break;
+        }
+    } else {  /* standard 32 bpp rgb */
+        for (i = 0; i < h; i++) {
+            line = pixGetData(pix) + i * wpl;
+            for (j = 0, k = 0, ppixel = line; j < w; j++) {
+                linebuf[k++] = GET_DATA_BYTE(ppixel, COLOR_RED);
+                linebuf[k++] = GET_DATA_BYTE(ppixel, COLOR_GREEN);
+                linebuf[k++] = GET_DATA_BYTE(ppixel, COLOR_BLUE);
+                ppixel++;
+            }
+            if (TIFFWriteScanline(tif, linebuf, i, 0) < 0)
+                break;
+        }
+    }
+
+/*    TIFFWriteDirectory(tif); */
+    LEPT_FREE(linebuf);
+
+    return 0;
+}
+
+
+/*!
+ *  writeCustomTiffTags()
+ *
+ *      Input:  tif
+ *              natags (<optional> NUMA of custom tiff tags)
+ *              savals (<optional> SARRAY of values)
+ *              satypes (<optional> SARRAY of types)
+ *              nasizes (<optional> NUMA of sizes)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This static function should be called indirectly through
+ *          higher level functions, such as pixWriteTiffCustom(),
+ *          which call pixWriteToTiffStream().  See details in
+ *          pixWriteTiffCustom() for using the 4 input arrays.
+ *      (2) This is a no-op if the first 3 arrays are all NULL.
+ *      (3) Otherwise, the first 3 arrays must be defined and all
+ *          of equal size.
+ *      (4) The fourth array is always optional.
+ *      (5) The most commonly used types are "char*" and "u_int16".
+ *          See tiff.h for a full listing of the tiff tags.
+ *          Note that many of these tags, in particular the bit tags,
+ *          are intended to be private, and cannot be set by this function.
+ *          Examples are the STRIPOFFSETS and STRIPBYTECOUNTS tags,
+ *          which are bit tags that are automatically set in the header,
+ *          and can be extracted using tiffdump.
+ */
+static l_int32
+writeCustomTiffTags(TIFF    *tif,
+                    NUMA    *natags,
+                    SARRAY  *savals,
+                    SARRAY  *satypes,
+                    NUMA    *nasizes)
+{
+char      *sval, *type;
+l_int32    i, n, ns, size, tagval, val;
+l_float64  dval;
+l_uint32   uval, uval2;
+
+    PROCNAME("writeCustomTiffTags");
+
+    if (!tif)
+        return ERROR_INT("tif stream not defined", procName, 1);
+    if (!natags && !savals && !satypes)
+        return 0;
+    if (!natags || !savals || !satypes)
+        return ERROR_INT("not all arrays defined", procName, 1);
+    n = numaGetCount(natags);
+    if ((sarrayGetCount(savals) != n) || (sarrayGetCount(satypes) != n))
+        return ERROR_INT("not all sa the same size", procName, 1);
+
+        /* The sized arrays (4 args to TIFFSetField) are written first */
+    if (nasizes) {
+        ns = numaGetCount(nasizes);
+        if (ns > n)
+            return ERROR_INT("too many 4-arg tag calls", procName, 1);
+        for (i = 0; i < ns; i++) {
+            numaGetIValue(natags, i, &tagval);
+            sval = sarrayGetString(savals, i, L_NOCOPY);
+            type = sarrayGetString(satypes, i, L_NOCOPY);
+            numaGetIValue(nasizes, i, &size);
+            if (strcmp(type, "char*") && strcmp(type, "l_uint8*"))
+                L_WARNING("array type not char* or l_uint8*; ignore\n",
+                          procName);
+            TIFFSetField(tif, tagval, size, sval);
+        }
+    } else {
+        ns = 0;
+    }
+
+        /* The typical tags (3 args to TIFFSetField) are now written */
+    for (i = ns; i < n; i++) {
+        numaGetIValue(natags, i, &tagval);
+        sval = sarrayGetString(savals, i, L_NOCOPY);
+        type = sarrayGetString(satypes, i, L_NOCOPY);
+        if (!strcmp(type, "char*")) {
+            TIFFSetField(tif, tagval, sval);
+        } else if (!strcmp(type, "l_uint16")) {
+            if (sscanf(sval, "%u", &uval) == 1) {
+                TIFFSetField(tif, tagval, (l_uint16)uval);
+            } else {
+                fprintf(stderr, "val %s not of type %s\n", sval, type);
+                return ERROR_INT("custom tag(s) not written", procName, 1);
+            }
+        } else if (!strcmp(type, "l_uint32")) {
+            if (sscanf(sval, "%u", &uval) == 1) {
+                TIFFSetField(tif, tagval, uval);
+            } else {
+                fprintf(stderr, "val %s not of type %s\n", sval, type);
+                return ERROR_INT("custom tag(s) not written", procName, 1);
+            }
+        } else if (!strcmp(type, "l_int32")) {
+            if (sscanf(sval, "%d", &val) == 1) {
+                TIFFSetField(tif, tagval, val);
+            } else {
+                fprintf(stderr, "val %s not of type %s\n", sval, type);
+                return ERROR_INT("custom tag(s) not written", procName, 1);
+            }
+        } else if (!strcmp(type, "l_float64")) {
+            if (sscanf(sval, "%lf", &dval) == 1) {
+                TIFFSetField(tif, tagval, dval);
+            } else {
+                fprintf(stderr, "val %s not of type %s\n", sval, type);
+                return ERROR_INT("custom tag(s) not written", procName, 1);
+            }
+        } else if (!strcmp(type, "l_uint16-l_uint16")) {
+            if (sscanf(sval, "%u-%u", &uval, &uval2) == 2) {
+                TIFFSetField(tif, tagval, (l_uint16)uval, (l_uint16)uval2);
+            } else {
+                fprintf(stderr, "val %s not of type %s\n", sval, type);
+                return ERROR_INT("custom tag(s) not written", procName, 1);
+            }
+        } else {
+            return ERROR_INT("unknown type; tag(s) not written", procName, 1);
+        }
+    }
+    return 0;
+}
+
+
+/*--------------------------------------------------------------*
+ *               Reading and writing multipage tiff             *
+ *--------------------------------------------------------------*/
+/*
+ *  pixaReadMultipageTiff()
+ *
+ *      Input:  filename (input tiff file)
+ *      Return: pixa (of page images), or null on error
+ */
+PIXA *
+pixaReadMultipageTiff(const char  *filename)
+{
+l_int32  i, npages;
+FILE    *fp;
+PIX     *pix;
+PIXA    *pixa;
+
+    PROCNAME("pixaReadMultipageTiff");
+
+    if (!filename)
+        return (PIXA *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PIXA *)ERROR_PTR("stream not opened", procName, NULL);
+    if (fileFormatIsTiff(fp)) {
+        tiffGetCount(fp, &npages);
+        L_INFO(" Tiff: %d pages\n", procName, npages);
+    } else {
+        return (PIXA *)ERROR_PTR("file not tiff", procName, NULL);
+    }
+    fclose(fp);
+
+    pixa = pixaCreate(npages);
+    for (i = 0; i < npages; i++) {
+        pix = pixReadTiff(filename, i);
+        if (!pix) {
+            L_WARNING("pix not read for page %d\n", procName, i);
+            continue;
+        }
+        pixaAddPix(pixa, pix, L_INSERT);
+    }
+
+    return pixa;
+}
+
+
+/*
+ *  writeMultipageTiff()
+ *
+ *      Input:  dirin (input directory)
+ *              substr (<optional> substring filter on filenames; can be NULL)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This writes a set of image files in a directory out
+ *          as a multipage tiff file.  The images can be in any
+ *          initial file format.
+ *      (2) Images with a colormap have the colormap removed before
+ *          re-encoding as tiff.
+ *      (3) All images are encoded losslessly.  Those with 1 bpp are
+ *          encoded 'g4'.  The rest are encoded as 'zip' (flate encoding).
+ *          Because it is lossless, this is an expensive method for
+ *          saving most rgb images.
+ */
+l_int32
+writeMultipageTiff(const char  *dirin,
+                   const char  *substr,
+                   const char  *fileout)
+{
+SARRAY  *sa;
+
+    PROCNAME("writeMultipageTiff");
+
+    if (!dirin)
+        return ERROR_INT("dirin not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+        /* Get all filtered and sorted full pathnames. */
+    sa = getSortedPathnamesInDirectory(dirin, substr, 0, 0);
+
+        /* Generate the tiff file */
+    writeMultipageTiffSA(sa, fileout);
+    sarrayDestroy(&sa);
+    return 0;
+}
+
+
+/*
+ *  writeMultipageTiffSA()
+ *
+ *      Input:  sarray (of full path names)
+ *              fileout (output ps file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See writeMultipageTiff()
+ */
+l_int32
+writeMultipageTiffSA(SARRAY      *sa,
+                     const char  *fileout)
+{
+char        *fname;
+const char  *op;
+l_int32      i, nfiles, firstfile, format;
+PIX         *pix, *pixt;
+
+    PROCNAME("writeMultipageTiffSA");
+
+    if (!sa)
+        return ERROR_INT("sa not defined", procName, 1);
+    if (!fileout)
+        return ERROR_INT("fileout not defined", procName, 1);
+
+    nfiles = sarrayGetCount(sa);
+    firstfile = TRUE;
+    for (i = 0; i < nfiles; i++) {
+        op = (firstfile) ? "w" : "a";
+        fname = sarrayGetString(sa, i, L_NOCOPY);
+        findFileFormat(fname, &format);
+        if (format == IFF_UNKNOWN) {
+            L_INFO("format of %s not known\n", procName, fname);
+            continue;
+        }
+
+        if ((pix = pixRead(fname)) == NULL) {
+            L_WARNING("pix not made for file: %s\n", procName, fname);
+            continue;
+        }
+        if (pixGetDepth(pix) == 1) {
+            pixWriteTiff(fileout, pix, IFF_TIFF_G4, op);
+        } else {
+            if (pixGetColormap(pix)) {
+                pixt = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
+            } else {
+                pixt = pixClone(pix);
+            }
+            pixWriteTiff(fileout, pixt, IFF_TIFF_ZIP, op);
+            pixDestroy(&pixt);
+        }
+        firstfile = FALSE;
+        pixDestroy(&pix);
+    }
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------*
+ *                    Print info to stream                      *
+ *--------------------------------------------------------------*/
+/*
+ *  fprintTiffInfo()
+ *
+ *      Input:  stream (for output of tag data)
+ *              tiffile (input)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+fprintTiffInfo(FILE        *fpout,
+               const char  *tiffile)
+{
+TIFF  *tif;
+
+    PROCNAME("fprintTiffInfo");
+
+    if (!tiffile)
+        return ERROR_INT("tiffile not defined", procName, 1);
+    if (!fpout)
+        return ERROR_INT("stream out not defined", procName, 1);
+
+    if ((tif = openTiff(tiffile, "rb")) == NULL)
+        return ERROR_INT("tif not open for read", procName, 1);
+
+    TIFFPrintDirectory(tif, fpout, 0);
+    TIFFClose(tif);
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------*
+ *                        Get page count                        *
+ *--------------------------------------------------------------*/
+/*
+ *  tiffGetCount()
+ *
+ *      Input:  stream (opened for read)
+ *              &n (<return> number of images)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+tiffGetCount(FILE     *fp,
+             l_int32  *pn)
+{
+l_int32  i;
+TIFF    *tif;
+
+    PROCNAME("tiffGetCount");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!pn)
+        return ERROR_INT("&n not defined", procName, 1);
+    *pn = 0;
+
+    if ((tif = fopenTiff(fp, "r")) == NULL)
+        return ERROR_INT("tif not open for read", procName, 1);
+
+    for (i = 1; i < MAX_PAGES_IN_TIFF_FILE; i++) {
+        if (TIFFReadDirectory(tif) == 0)
+            break;
+    }
+    *pn = i;
+    TIFFCleanup(tif);
+    return 0;
+}
+
+
+/*--------------------------------------------------------------*
+ *                   Get resolution from tif                    *
+ *--------------------------------------------------------------*/
+/*
+ *  getTiffResolution()
+ *
+ *      Input:  stream (opened for read)
+ *              &xres, &yres (<return> resolution in ppi)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) If neither resolution field is set, this is not an error;
+ *          the returned resolution values are 0 (designating 'unknown').
+ */
+l_int32
+getTiffResolution(FILE     *fp,
+                  l_int32  *pxres,
+                  l_int32  *pyres)
+{
+TIFF  *tif;
+
+    PROCNAME("getTiffResolution");
+
+    if (!pxres || !pyres)
+        return ERROR_INT("&xres and &yres not both defined", procName, 1);
+    *pxres = *pyres = 0;
+    if (!fp)
+        return ERROR_INT("stream not opened", procName, 1);
+
+    if ((tif = fopenTiff(fp, "r")) == NULL)
+        return ERROR_INT("tif not open for read", procName, 1);
+    getTiffStreamResolution(tif, pxres, pyres);
+    TIFFCleanup(tif);
+    return 0;
+}
+
+
+/*
+ *  getTiffStreamResolution()
+ *
+ *      Input:  tiff stream (opened for read)
+ *              &xres, &yres (<return> resolution in ppi)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) If neither resolution field is set, this is not an error;
+ *          the returned resolution values are 0 (designating 'unknown').
+ */
+static l_int32
+getTiffStreamResolution(TIFF     *tif,
+                        l_int32  *pxres,
+                        l_int32  *pyres)
+{
+l_uint16   resunit;
+l_int32    foundxres, foundyres;
+l_float32  fxres, fyres;
+
+    PROCNAME("getTiffStreamResolution");
+
+    if (!tif)
+        return ERROR_INT("tif not opened", procName, 1);
+    if (!pxres || !pyres)
+        return ERROR_INT("&xres and &yres not both defined", procName, 1);
+    *pxres = *pyres = 0;
+
+    TIFFGetFieldDefaulted(tif, TIFFTAG_RESOLUTIONUNIT, &resunit);
+    foundxres = TIFFGetField(tif, TIFFTAG_XRESOLUTION, &fxres);
+    foundyres = TIFFGetField(tif, TIFFTAG_YRESOLUTION, &fyres);
+    if (!foundxres && !foundyres) return 1;
+    if (!foundxres && foundyres)
+        fxres = fyres;
+    else if (foundxres && !foundyres)
+        fyres = fxres;
+
+    if (resunit == RESUNIT_CENTIMETER) {  /* convert to ppi */
+        *pxres = (l_int32)(2.54 * fxres + 0.5);
+        *pyres = (l_int32)(2.54 * fyres + 0.5);
+    } else {
+        *pxres = (l_int32)fxres;
+        *pyres = (l_int32)fyres;
+    }
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------*
+ *              Get some tiff header information                *
+ *--------------------------------------------------------------*/
+/*!
+ *  readHeaderTiff()
+ *
+ *      Input:  filename
+ *              n (page image number: 0-based)
+ *              &width (<return>)
+ *              &height (<return>)
+ *              &bps (<return> bits per sample -- 1, 2, 4 or 8)
+ *              &spp (<return>; samples per pixel -- 1 or 3)
+ *              &res (<optional return>; resolution in x dir; NULL to ignore)
+ *              &cmap (<optional return>; colormap exists; input NULL to ignore)
+ *              &format (<optional return>; tiff format; input NULL to ignore)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If there is a colormap, cmap is returned as 1; else 0.
+ *      (2) If @n is equal to or greater than the number of images, returns 1.
+ */
+l_int32
+readHeaderTiff(const char *filename,
+               l_int32     n,
+               l_int32    *pwidth,
+               l_int32    *pheight,
+               l_int32    *pbps,
+               l_int32    *pspp,
+               l_int32    *pres,
+               l_int32    *pcmap,
+               l_int32    *pformat)
+{
+l_int32  ret;
+FILE    *fp;
+
+    PROCNAME("readHeaderTiff");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!pwidth || !pheight || !pbps || !pspp)
+        return ERROR_INT("input ptr(s) not all defined", procName, 1);
+    *pwidth = *pheight = *pbps = *pspp = 0;
+    if (pres) *pres = 0;
+    if (pcmap) *pcmap = 0;
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT("image file not found", procName, 1);
+    ret = freadHeaderTiff(fp, n, pwidth, pheight, pbps, pspp,
+                          pres, pcmap, pformat);
+    fclose(fp);
+    return ret;
+}
+
+
+/*!
+ *  freadHeaderTiff()
+ *
+ *      Input:  stream
+ *              n (page image number: 0-based)
+ *              &width (<return>)
+ *              &height (<return>)
+ *              &bps (<return> bits per sample -- 1, 2, 4 or 8)
+ *              &spp (<return>; samples per pixel -- 1 or 3)
+ *              &res (<optional return>; resolution in x dir; NULL to ignore)
+ *              &cmap (<optional return>; colormap exists; input NULL to ignore)
+ *              &format (<optional return>; tiff format; input NULL to ignore)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If there is a colormap, cmap is returned as 1; else 0.
+ *      (2) If @n is equal to or greater than the number of images, returns 1.
+ */
+l_int32
+freadHeaderTiff(FILE     *fp,
+                l_int32   n,
+                l_int32  *pwidth,
+                l_int32  *pheight,
+                l_int32  *pbps,
+                l_int32  *pspp,
+                l_int32  *pres,
+                l_int32  *pcmap,
+                l_int32  *pformat)
+{
+l_int32  i, ret, format;
+TIFF    *tif;
+
+    PROCNAME("freadHeaderTiff");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (n < 0)
+        return ERROR_INT("image index must be >= 0", procName, 1);
+    if (!pwidth || !pheight || !pbps || !pspp)
+        return ERROR_INT("input ptr(s) not all defined", procName, 1);
+    *pwidth = *pheight = *pbps = *pspp = 0;
+    if (pres) *pres = 0;
+    if (pcmap) *pcmap = 0;
+    if (pformat) *pformat = 0;
+
+    findFileFormatStream(fp, &format);
+    if (format != IFF_TIFF &&
+        format != IFF_TIFF_G3 && format != IFF_TIFF_G4 &&
+        format != IFF_TIFF_RLE && format != IFF_TIFF_PACKBITS &&
+        format != IFF_TIFF_LZW && format != IFF_TIFF_ZIP)
+        return ERROR_INT("file not tiff format", procName, 1);
+
+    if ((tif = fopenTiff(fp, "r")) == NULL)
+        return ERROR_INT("tif not open for read", procName, 1);
+
+    for (i = 0; i < n; i++) {
+        if (TIFFReadDirectory(tif) == 0)
+            return ERROR_INT("image n not found in file", procName, 1);
+    }
+
+    ret = tiffReadHeaderTiff(tif, pwidth, pheight, pbps, pspp,
+                             pres, pcmap, pformat);
+    TIFFCleanup(tif);
+    return ret;
+}
+
+
+/*!
+ *  readHeaderMemTiff()
+ *
+ *      Input:  cdata (const; tiff-encoded)
+ *              size (size of data)
+ *              n (page image number: 0-based)
+ *              &width (<return>)
+ *              &height (<return>)
+ *              &bps (<return> bits per sample -- 1, 2, 4 or 8)
+ *              &spp (<return>; samples per pixel -- 1 or 3)
+ *              &res (<optional return>; resolution in x dir; NULL to ignore)
+ *              &cmap (<optional return>; colormap exists; input NULL to ignore)
+ *              &format (<optional return>; tiff format; input NULL to ignore)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Use TIFFClose(); TIFFCleanup() doesn't free internal memstream.
+ */
+l_int32
+readHeaderMemTiff(const l_uint8  *cdata,
+                  size_t          size,
+                  l_int32         n,
+                  l_int32        *pwidth,
+                  l_int32        *pheight,
+                  l_int32        *pbps,
+                  l_int32        *pspp,
+                  l_int32        *pres,
+                  l_int32        *pcmap,
+                  l_int32        *pformat)
+{
+l_uint8  *data;
+l_int32   i, ret;
+TIFF     *tif;
+
+    PROCNAME("readHeaderMemTiff");
+
+    if (!cdata)
+        return ERROR_INT("cdata not defined", procName, 1);
+    if (!pwidth || !pheight || !pbps || !pspp)
+        return ERROR_INT("input ptr(s) not all defined", procName, 1);
+    *pwidth = *pheight = *pbps = *pspp = 0;
+    if (pres) *pres = 0;
+    if (pcmap) *pcmap = 0;
+    if (pformat) *pformat = 0;
+
+        /* Open a tiff stream to memory */
+    data = (l_uint8 *)cdata;  /* we're really not going to change this */
+    if ((tif = fopenTiffMemstream("tifferror", "r", &data, &size)) == NULL)
+        return ERROR_INT("tiff stream not opened", procName, 1);
+
+    for (i = 0; i < n; i++) {
+        if (TIFFReadDirectory(tif) == 0) {
+            TIFFClose(tif);
+            return ERROR_INT("image n not found in file", procName, 1);
+        }
+    }
+
+    ret = tiffReadHeaderTiff(tif, pwidth, pheight, pbps, pspp,
+                             pres, pcmap, pformat);
+    TIFFClose(tif);
+    return ret;
+}
+
+
+/*!
+ *  tiffReadHeaderTiff()
+ *
+ *      Input:  tif
+ *              &width (<return>)
+ *              &height (<return>)
+ *              &bps (<return> bits per sample -- 1, 2, 4 or 8)
+ *              &spp (<return>; samples per pixel -- 1 or 3)
+ *              &res (<optional return>; resolution in x dir; NULL to ignore)
+ *              &cmap (<optional return>; cmap exists; input NULL to ignore)
+ *              &format (<optional return>; tiff format; input NULL to ignore)
+ *      Return: 0 if OK, 1 on error
+ */
+static l_int32
+tiffReadHeaderTiff(TIFF     *tif,
+                   l_int32  *pwidth,
+                   l_int32  *pheight,
+                   l_int32  *pbps,
+                   l_int32  *pspp,
+                   l_int32  *pres,
+                   l_int32  *pcmap,
+                   l_int32  *pformat)
+{
+l_uint16   tiffcomp;
+l_uint16   bps, spp;
+l_uint16  *rmap, *gmap, *bmap;
+l_int32    xres, yres;
+l_uint32   w, h;
+
+    PROCNAME("tiffReadHeaderTiff");
+
+    if (!tif)
+        return ERROR_INT("tif not opened", procName, 1);
+
+    TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w);
+    *pwidth = w;
+    TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h);
+    *pheight = h;
+    TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bps);
+    *pbps = bps;
+    TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &spp);
+    *pspp = spp;
+
+    if (pres) {
+        *pres = 300;  /* default ppi */
+        if (getTiffStreamResolution(tif, &xres, &yres) == 0)
+            *pres = (l_int32)xres;
+    }
+
+    if (pcmap) {
+        *pcmap = 0;
+        if (TIFFGetField(tif, TIFFTAG_COLORMAP, &rmap, &gmap, &bmap))
+            *pcmap = 1;
+    }
+
+    if (pformat) {
+        TIFFGetFieldDefaulted(tif, TIFFTAG_COMPRESSION, &tiffcomp);
+        *pformat = getTiffCompressedFormat(tiffcomp);
+    }
+    return 0;
+}
+
+
+/*!
+ *  findTiffCompression()
+ *
+ *      Input:  stream (must be rewound to BOF)
+ *              &comptype (<return> compression type)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The returned compression type is that defined in
+ *          the enum in imageio.h.  It is not the tiff flag value.
+ *      (2) The compression type is initialized to IFF_UNKNOWN.
+ *          If it is not one of the specified types, the returned
+ *          type is IFF_TIFF, which indicates no compression.
+ *      (3) When this function is called, the stream must be at BOF.
+ *          If the opened stream is to be used again to read the
+ *          file, it must be rewound to BOF after calling this function.
+ */
+l_int32
+findTiffCompression(FILE     *fp,
+                    l_int32  *pcomptype)
+{
+l_uint16  tiffcomp;
+TIFF     *tif;
+
+    PROCNAME("findTiffCompression");
+
+    if (!pcomptype)
+        return ERROR_INT("&comptype not defined", procName, 1);
+    *pcomptype = IFF_UNKNOWN;  /* init */
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+
+    if ((tif = fopenTiff(fp, "r")) == NULL)
+        return ERROR_INT("tif not opened", procName, 1);
+    TIFFGetFieldDefaulted(tif, TIFFTAG_COMPRESSION, &tiffcomp);
+    *pcomptype = getTiffCompressedFormat(tiffcomp);
+    TIFFCleanup(tif);
+    return 0;
+}
+
+
+/*!
+ *  getTiffCompressedFormat()
+ *
+ *      Input:  tiffcomp (defined in tiff.h)
+ *      Return: compression format (defined in imageio.h)
+ *
+ *  Notes:
+ *      (1) The input must be the actual tiff compression type
+ *          returned by a tiff library call.  It should always be
+ *          a valid tiff type.
+ *      (2) The return type is defined in the enum in imageio.h.
+ */
+static l_int32
+getTiffCompressedFormat(l_uint16  tiffcomp)
+{
+l_int32  comptype;
+
+    switch (tiffcomp)
+    {
+    case COMPRESSION_CCITTFAX4:
+        comptype = IFF_TIFF_G4;
+        break;
+    case COMPRESSION_CCITTFAX3:
+        comptype = IFF_TIFF_G3;
+        break;
+    case COMPRESSION_CCITTRLE:
+        comptype = IFF_TIFF_RLE;
+        break;
+    case COMPRESSION_PACKBITS:
+        comptype = IFF_TIFF_PACKBITS;
+        break;
+    case COMPRESSION_LZW:
+        comptype = IFF_TIFF_LZW;
+        break;
+    case COMPRESSION_ADOBE_DEFLATE:
+        comptype = IFF_TIFF_ZIP;
+        break;
+    default:
+        comptype = IFF_TIFF;
+        break;
+    }
+    return comptype;
+}
+
+
+/*--------------------------------------------------------------*
+ *                   Extraction of tiff g4 data                 *
+ *--------------------------------------------------------------*/
+/*!
+ *  extractG4DataFromFile()
+ *
+ *      Input:  filein
+ *              &data (<return> binary data of ccitt g4 encoded stream)
+ *              &nbytes (<return> size of binary data)
+ *              &w (<return optional> image width)
+ *              &h (<return optional> image height)
+ *              &minisblack (<return optional> boolean)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+extractG4DataFromFile(const char  *filein,
+                      l_uint8    **pdata,
+                      size_t      *pnbytes,
+                      l_int32     *pw,
+                      l_int32     *ph,
+                      l_int32     *pminisblack)
+{
+l_uint8  *inarray, *data;
+l_uint16  minisblack, comptype;  /* accessors require l_uint16 */
+l_int32   istiff;
+l_uint32  w, h, rowsperstrip;  /* accessors require l_uint32 */
+l_uint32  diroff;
+size_t    fbytes, nbytes;
+FILE     *fpin;
+TIFF     *tif;
+
+    PROCNAME("extractG4DataFromFile");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    if (!pnbytes)
+        return ERROR_INT("&nbytes not defined", procName, 1);
+    if (!pw && !ph && !pminisblack)
+        return ERROR_INT("no output data requested", procName, 1);
+    *pdata = NULL;
+    *pnbytes = 0;
+
+    if ((fpin = fopenReadStream(filein)) == NULL)
+        return ERROR_INT("stream not opened to file", procName, 1);
+    istiff = fileFormatIsTiff(fpin);
+    fclose(fpin);
+    if (!istiff)
+        return ERROR_INT("filein not tiff", procName, 1);
+
+    if ((inarray = l_binaryRead(filein, &fbytes)) == NULL)
+        return ERROR_INT("inarray not made", procName, 1);
+
+        /* Get metadata about the image */
+    if ((tif = openTiff(filein, "rb")) == NULL)
+        return ERROR_INT("tif not open for read", procName, 1);
+    TIFFGetField(tif, TIFFTAG_COMPRESSION, &comptype);
+    if (comptype != COMPRESSION_CCITTFAX4) {
+        LEPT_FREE(inarray);
+        TIFFClose(tif);
+        return ERROR_INT("filein is not g4 compressed", procName, 1);
+    }
+
+    TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w);
+    TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h);
+    TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
+    if (h != rowsperstrip)
+        L_WARNING("more than 1 strip\n", procName);
+    TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &minisblack);  /* for 1 bpp */
+/*    TIFFPrintDirectory(tif, stderr, 0); */
+    TIFFClose(tif);
+    if (pw) *pw = (l_int32)w;
+    if (ph) *ph = (l_int32)h;
+    if (pminisblack) *pminisblack = (l_int32)minisblack;
+
+        /* The header has 8 bytes: the first 2 are the magic number,
+         * the next 2 are the version, and the last 4 are the
+         * offset to the first directory.  That's what we want here.
+         * We have to test the byte order before decoding 4 bytes! */
+    if (inarray[0] == 0x4d) {  /* big-endian */
+        diroff = (inarray[4] << 24) | (inarray[5] << 16) |
+                 (inarray[6] << 8) | inarray[7];
+    } else  {   /* inarray[0] == 0x49 :  little-endian */
+        diroff = (inarray[7] << 24) | (inarray[6] << 16) |
+                 (inarray[5] << 8) | inarray[4];
+    }
+/*    fprintf(stderr, " diroff = %d, %x\n", diroff, diroff); */
+
+        /* Extract the ccittg4 encoded data from the tiff file.
+         * We skip the 8 byte header and take nbytes of data,
+         * up to the beginning of the directory (at diroff)  */
+    nbytes = diroff - 8;
+    *pnbytes = nbytes;
+    if ((data = (l_uint8 *)LEPT_CALLOC(nbytes, sizeof(l_uint8))) == NULL) {
+        LEPT_FREE(inarray);
+        return ERROR_INT("data not allocated", procName, 1);
+    }
+    *pdata = data;
+    memcpy(data, inarray + 8, nbytes);
+    LEPT_FREE(inarray);
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------*
+ *               Open tiff stream from file stream              *
+ *--------------------------------------------------------------*/
+/*!
+ *  fopenTiff()
+ *
+ *      Input:  stream
+ *              modestring ("r", "w", ...)
+ *      Return: tiff (data structure, opened for a file descriptor)
+ *
+ *  Notes:
+ *      (1) Why is this here?  Leffler did not provide a function that
+ *          takes a stream and gives a TIFF.  He only gave one that
+ *          generates a TIFF starting with a file descriptor.  So we
+ *          need to make it here, because it is useful to have functions
+ *          that take a stream as input.
+ *      (2) We use TIFFClientOpen() together with a set of static wrapper
+ *          functions which map TIFF read, write, seek, close and size.
+ *          to functions expecting a cookie of type stream (i.e. FILE *).
+ *          This implementation was contributed by Jürgen Buchmüller.
+ */
+static TIFF *
+fopenTiff(FILE        *fp,
+          const char  *modestring)
+{
+    PROCNAME("fopenTiff");
+
+    if (!fp)
+        return (TIFF *)ERROR_PTR("stream not opened", procName, NULL);
+    if (!modestring)
+        return (TIFF *)ERROR_PTR("modestring not defined", procName, NULL);
+
+    fseek(fp, 0, SEEK_SET);
+    return TIFFClientOpen("TIFFstream", modestring, (thandle_t)fp,
+                          lept_read_proc, lept_write_proc, lept_seek_proc,
+                          lept_close_proc, lept_size_proc, NULL, NULL);
+}
+
+
+/*--------------------------------------------------------------*
+ *                      Wrapper for TIFFOpen                    *
+ *--------------------------------------------------------------*/
+/*!
+ *  openTiff()
+ *
+ *      Input:  filename
+ *              modestring ("r", "w", ...)
+ *      Return: tiff (data structure)
+ *
+ *  Notes:
+ *      (1) This handles multi-platform file naming.
+ */
+static TIFF *
+openTiff(const char  *filename,
+         const char  *modestring)
+{
+char  *fname;
+TIFF  *tif;
+
+    PROCNAME("openTiff");
+
+    if (!filename)
+        return (TIFF *)ERROR_PTR("filename not defined", procName, NULL);
+    if (!modestring)
+        return (TIFF *)ERROR_PTR("modestring not defined", procName, NULL);
+
+    fname = genPathname(filename, NULL);
+    tif = TIFFOpen(fname, modestring);
+    LEPT_FREE(fname);
+    return tif;
+}
+
+
+/*----------------------------------------------------------------------*
+ *     Memory I/O: reading memory --> pix and writing pix --> memory    *
+ *----------------------------------------------------------------------*/
+/*  It would be nice to use open_memstream() and fmemopen()
+ *  for writing and reading to memory, rsp.  These functions manage
+ *  memory for writes and reads that use a file streams interface.
+ *  Unfortunately, the tiff library only has an interface for reading
+ *  and writing to file descriptors, not to file streams.  The tiff
+ *  library procedure is to open a "tiff stream" and read/write to it.
+ *  The library provides a client interface for managing the I/O
+ *  from memory, which requires seven callbacks.  See the TIFFClientOpen
+ *  man page for callback signatures.  Adam Langley provided the code
+ *  to do this.  */
+
+/*
+ *  The L_Memstram @buffer has different functions in writing and reading.
+ *
+ *     * In reading, it is assigned to the data and read from as
+ *       the tiff library uncompresses the data and generates the pix.
+ *       The @offset points to the current read position in the data,
+ *       and the @hw always gives the number of bytes of data.
+ *       The @outdata and @outsize ptrs are not used.
+ *       When finished, tiffCloseCallback() simply frees the L_Memstream.
+ *
+ *     * In writing, it accepts the data that the tiff library
+ *       produces when a pix is compressed.  the buffer points to a
+ *       malloced area of @bufsize bytes.  The current writing position
+ *       in the buffer is @offset and the most ever written is @hw.
+ *       The buffer is expanded as necessary.  When finished,
+ *       tiffCloseCallback() assigns the @outdata and @outsize ptrs
+ *       to the @buffer and @bufsize results, and frees the L_Memstream.
+ */
+struct L_Memstream
+{
+    l_uint8   *buffer;    /* expands to hold data when written to;         */
+                          /* fixed size when read from.                    */
+    size_t     bufsize;   /* current size allocated when written to;       */
+                          /* fixed size of input data when read from.      */
+    size_t     offset;    /* byte offset from beginning of buffer.         */
+    size_t     hw;        /* high-water mark; max bytes in buffer.         */
+    l_uint8  **poutdata;  /* input param for writing; data goes here.      */
+    size_t    *poutsize;  /* input param for writing; data size goes here. */
+};
+typedef struct L_Memstream  L_MEMSTREAM;
+
+
+    /* These are static functions for memory I/O */
+static L_MEMSTREAM *memstreamCreateForRead(l_uint8 *indata, size_t pinsize);
+static L_MEMSTREAM *memstreamCreateForWrite(l_uint8 **poutdata,
+                                            size_t *poutsize);
+static tsize_t tiffReadCallback(thandle_t handle, tdata_t data, tsize_t length);
+static tsize_t tiffWriteCallback(thandle_t handle, tdata_t data,
+                                 tsize_t length);
+static toff_t tiffSeekCallback(thandle_t handle, toff_t offset, l_int32 whence);
+static l_int32 tiffCloseCallback(thandle_t handle);
+static toff_t tiffSizeCallback(thandle_t handle);
+static l_int32 tiffMapCallback(thandle_t handle, tdata_t *data, toff_t *length);
+static void tiffUnmapCallback(thandle_t handle, tdata_t data, toff_t length);
+
+
+static L_MEMSTREAM *
+memstreamCreateForRead(l_uint8  *indata,
+                       size_t    insize)
+{
+L_MEMSTREAM  *mstream;
+
+    mstream = (L_MEMSTREAM *)LEPT_CALLOC(1, sizeof(L_MEMSTREAM));
+    mstream->buffer = indata;   /* handle to input data array */
+    mstream->bufsize = insize;  /* amount of input data */
+    mstream->hw = insize;       /* high-water mark fixed at input data size */
+    mstream->offset = 0;        /* offset always starts at 0 */
+    return mstream;
+}
+
+
+static L_MEMSTREAM *
+memstreamCreateForWrite(l_uint8  **poutdata,
+                        size_t    *poutsize)
+{
+L_MEMSTREAM  *mstream;
+
+    mstream = (L_MEMSTREAM *)LEPT_CALLOC(1, sizeof(L_MEMSTREAM));
+    mstream->buffer = (l_uint8 *)LEPT_CALLOC(8 * 1024, 1);
+    mstream->bufsize = 8 * 1024;
+    mstream->poutdata = poutdata;  /* used only at end of write */
+    mstream->poutsize = poutsize;  /* ditto  */
+    mstream->hw = mstream->offset = 0;
+    return mstream;
+}
+
+
+static tsize_t
+tiffReadCallback(thandle_t  handle,
+                 tdata_t    data,
+                 tsize_t    length)
+{
+L_MEMSTREAM  *mstream;
+size_t        amount;
+
+    mstream = (L_MEMSTREAM *)handle;
+    amount = L_MIN((size_t)length, mstream->hw - mstream->offset);
+
+        /* Fuzzed files can create this condition! */
+    if (mstream->offset + amount > mstream->hw) {
+        fprintf(stderr, "Bad file: amount too big: %lu\n",
+                (unsigned long)amount);
+        return 0;
+    }
+
+    memcpy(data, mstream->buffer + mstream->offset, amount);
+    mstream->offset += amount;
+    return amount;
+}
+
+
+static tsize_t
+tiffWriteCallback(thandle_t  handle,
+                  tdata_t    data,
+                  tsize_t    length)
+{
+L_MEMSTREAM  *mstream;
+size_t        newsize;
+
+        /* reallocNew() uses calloc to initialize the array.
+         * If malloc is used instead, for some of the encoding methods,
+         * not all the data in 'bufsize' bytes in the buffer will
+         * have been initialized by the end of the compression. */
+    mstream = (L_MEMSTREAM *)handle;
+    if (mstream->offset + length > mstream->bufsize) {
+        newsize = 2 * (mstream->offset + length);
+        mstream->buffer = (l_uint8 *)reallocNew((void **)&mstream->buffer,
+                                                mstream->hw, newsize);
+        mstream->bufsize = newsize;
+    }
+
+    memcpy(mstream->buffer + mstream->offset, data, length);
+    mstream->offset += length;
+    mstream->hw = L_MAX(mstream->offset, mstream->hw);
+    return length;
+}
+
+
+static toff_t
+tiffSeekCallback(thandle_t  handle,
+                 toff_t     offset,
+                 l_int32    whence)
+{
+L_MEMSTREAM  *mstream;
+
+    PROCNAME("tiffSeekCallback");
+    mstream = (L_MEMSTREAM *)handle;
+    switch (whence) {
+        case SEEK_SET:
+/*            fprintf(stderr, "seek_set: offset = %d\n", offset); */
+            mstream->offset = offset;
+            break;
+        case SEEK_CUR:
+/*            fprintf(stderr, "seek_cur: offset = %d\n", offset); */
+            mstream->offset += offset;
+            break;
+        case SEEK_END:
+/*            fprintf(stderr, "seek end: hw = %d, offset = %d\n",
+                    mstream->hw, offset); */
+            mstream->offset = mstream->hw - offset;  /* offset >= 0 */
+            break;
+        default:
+            return (toff_t)ERROR_INT("bad whence value", procName,
+                                     mstream->offset);
+    }
+
+    return mstream->offset;
+}
+
+
+static l_int32
+tiffCloseCallback(thandle_t  handle)
+{
+L_MEMSTREAM  *mstream;
+
+    mstream = (L_MEMSTREAM *)handle;
+    if (mstream->poutdata) {   /* writing: save the output data */
+        *mstream->poutdata = mstream->buffer;
+        *mstream->poutsize = mstream->hw;
+    }
+    LEPT_FREE(mstream);  /* never free the buffer! */
+    return 0;
+}
+
+
+static toff_t
+tiffSizeCallback(thandle_t  handle)
+{
+L_MEMSTREAM  *mstream;
+
+    mstream = (L_MEMSTREAM *)handle;
+    return mstream->hw;
+}
+
+
+static l_int32
+tiffMapCallback(thandle_t  handle,
+                tdata_t   *data,
+                toff_t    *length)
+{
+L_MEMSTREAM  *mstream;
+
+    mstream = (L_MEMSTREAM *)handle;
+    *data = mstream->buffer;
+    *length = mstream->hw;
+    return 0;
+}
+
+
+static void
+tiffUnmapCallback(thandle_t  handle,
+                  tdata_t    data,
+                  toff_t     length)
+{
+    return;
+}
+
+
+/*!
+ *  fopenTiffMemstream()
+ *
+ *      Input:  filename (for error output; can be "")
+ *              operation ("w" for write, "r" for read)
+ *              &data (<return> written data)
+ *              &datasize (<return> size of written data)
+ *      Return: tiff (data structure, opened for write to memory)
+ *
+ *  Notes:
+ *      (1) This wraps up a number of callbacks for either:
+ *            * reading from tiff in memory buffer --> pix
+ *            * writing from pix --> tiff in memory buffer
+ *      (2) After use, the memstream is automatically destroyed when
+ *          TIFFClose() is called.  TIFFCleanup() doesn't free the memstream.
+ */
+static TIFF *
+fopenTiffMemstream(const char  *filename,
+                   const char  *operation,
+                   l_uint8    **pdata,
+                   size_t      *pdatasize)
+{
+L_MEMSTREAM  *mstream;
+
+    PROCNAME("fopenTiffMemstream");
+
+    if (!filename)
+        return (TIFF *)ERROR_PTR("filename not defined", procName, NULL);
+    if (!operation)
+        return (TIFF *)ERROR_PTR("operation not defined", procName, NULL);
+    if (!pdata)
+        return (TIFF *)ERROR_PTR("&data not defined", procName, NULL);
+    if (!pdatasize)
+        return (TIFF *)ERROR_PTR("&datasize not defined", procName, NULL);
+    if (!strcmp(operation, "r") && !strcmp(operation, "w"))
+        return (TIFF *)ERROR_PTR("operation not 'r' or 'w'}", procName, NULL);
+
+    if (!strcmp(operation, "r"))
+        mstream = memstreamCreateForRead(*pdata, *pdatasize);
+    else
+        mstream = memstreamCreateForWrite(pdata, pdatasize);
+
+    return TIFFClientOpen(filename, operation, mstream,
+                          tiffReadCallback, tiffWriteCallback,
+                          tiffSeekCallback, tiffCloseCallback,
+                          tiffSizeCallback, tiffMapCallback,
+                          tiffUnmapCallback);
+}
+
+
+/*!
+ *  pixReadMemTiff()
+ *
+ *      Input:  data (const; tiff-encoded)
+ *              datasize (size of data)
+ *              n (page image number: 0-based)
+ *      Return: pix, or null on error
+ *
+ *  Notes:
+ *      (1) This is a version of pixReadTiff(), where the data is read
+ *          from a memory buffer and uncompressed.
+ *      (2) Use TIFFClose(); TIFFCleanup() doesn't free internal memstream.
+ *      (3) No warning messages on failure, because of how multi-page
+ *          TIFF reading works. You are supposed to keep trying until
+ *          it stops working.
+ */
+PIX *
+pixReadMemTiff(const l_uint8  *cdata,
+               size_t          size,
+               l_int32         n)
+{
+l_uint8  *data;
+l_int32   i;
+PIX      *pix;
+TIFF     *tif;
+
+    PROCNAME("pixReadMemTiff");
+
+    if (!cdata)
+        return (PIX *)ERROR_PTR("cdata not defined", procName, NULL);
+
+    data = (l_uint8 *)cdata;  /* we're really not going to change this */
+    if ((tif = fopenTiffMemstream("tifferror", "r", &data, &size)) == NULL)
+        return (PIX *)ERROR_PTR("tiff stream not opened", procName, NULL);
+
+    pix = NULL;
+    for (i = 0; i < MAX_PAGES_IN_TIFF_FILE; i++) {
+        if (i == n) {
+            if ((pix = pixReadFromTiffStream(tif)) == NULL) {
+                TIFFClose(tif);
+                return NULL;
+            }
+            pixSetInputFormat(pix, IFF_TIFF);
+            break;
+        }
+        if (TIFFReadDirectory(tif) == 0)
+            break;
+    }
+
+    TIFFClose(tif);
+    return pix;
+}
+
+
+/*!
+ *  pixWriteMemTiff()
+ *
+ *      Input:  &data (<return> data of tiff compressed image)
+ *              &size (<return> size of returned data)
+ *              pix
+ *              comptype (IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS,
+ *                        IFF_TIFF_G3, IFF_TIFF_G4,
+ *                        IFF_TIFF_LZW, IFF_TIFF_ZIP)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Usage:
+ *      (1) See pixWriteTiff().  This version writes to
+ *          memory instead of to a file.
+ */
+l_int32
+pixWriteMemTiff(l_uint8  **pdata,
+                size_t    *psize,
+                PIX       *pix,
+                l_int32    comptype)
+{
+    return pixWriteMemTiffCustom(pdata, psize, pix, comptype,
+                                 NULL, NULL, NULL, NULL);
+}
+
+
+/*!
+ *  pixWriteMemTiffCustom()
+ *
+ *      Input:  &data (<return> data of tiff compressed image)
+ *              &size (<return> size of returned data)
+ *              pix
+ *              comptype (IFF_TIFF, IFF_TIFF_RLE, IFF_TIFF_PACKBITS,
+ *                        IFF_TIFF_G3, IFF_TIFF_G4,
+ *                        IFF_TIFF_LZW, IFF_TIFF_ZIP)
+ *              natags (<optional> NUMA of custom tiff tags)
+ *              savals (<optional> SARRAY of values)
+ *              satypes (<optional> SARRAY of types)
+ *              nasizes (<optional> NUMA of sizes)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Usage:
+ *      (1) See pixWriteTiffCustom().  This version writes to
+ *          memory instead of to a file.
+ *      (2) Use TIFFClose(); TIFFCleanup() doesn't free internal memstream.
+ */
+l_int32
+pixWriteMemTiffCustom(l_uint8  **pdata,
+                      size_t    *psize,
+                      PIX       *pix,
+                      l_int32    comptype,
+                      NUMA      *natags,
+                      SARRAY    *savals,
+                      SARRAY    *satypes,
+                      NUMA      *nasizes)
+{
+l_int32  ret;
+TIFF    *tif;
+
+    PROCNAME("pixWriteMemTiffCustom");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1);
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("&pix not defined", procName, 1);
+    if (pixGetDepth(pix) != 1 && comptype != IFF_TIFF &&
+        comptype != IFF_TIFF_LZW && comptype != IFF_TIFF_ZIP) {
+        L_WARNING("invalid compression type for bpp > 1\n", procName);
+        comptype = IFF_TIFF_ZIP;
+    }
+
+    if ((tif = fopenTiffMemstream("tifferror", "w", pdata, psize)) == NULL)
+        return ERROR_INT("tiff stream not opened", procName, 1);
+    ret = pixWriteToTiffStream(tif, pix, comptype, natags, savals,
+                               satypes, nasizes);
+
+    TIFFClose(tif);
+    return ret;
+}
+
+/* --------------------------------------------*/
+#endif  /* HAVE_LIBTIFF */
+/* --------------------------------------------*/
diff --git a/src/tiffiostub.c b/src/tiffiostub.c
new file mode 100644 (file)
index 0000000..2c54c7e
--- /dev/null
@@ -0,0 +1,193 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  tiffiostub.c
+ *
+ *     Stubs for tiffio.c functions
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !HAVE_LIBTIFF   /* defined in environ.h */
+/* --------------------------------------------*/
+
+PIX * pixReadTiff(const char *filename, l_int32 n)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadTiff", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadStreamTiff(FILE *fp, l_int32 n)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadStreamTiff", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteTiff(const char *filename, PIX *pix, l_int32 comptype,
+                     const char *modestring)
+{
+    return ERROR_INT("function not present", "pixWriteTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteTiffCustom(const char *filename, PIX *pix, l_int32 comptype,
+                           const char *modestring, NUMA *natags,
+                           SARRAY *savals, SARRAY *satypes, NUMA *nasizes)
+{
+    return ERROR_INT("function not present", "pixWriteTiffCustom", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteStreamTiff(FILE *fp, PIX *pix, l_int32 comptype)
+{
+    return ERROR_INT("function not present", "pixWriteStreamTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIXA * pixaReadMultipageTiff(const char *filename)
+{
+    return (PIXA * )ERROR_PTR("function not present",
+                              "pixaReadMultipageTiff", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 writeMultipageTiff(const char *dirin, const char *substr,
+                           const char *fileout)
+{
+    return ERROR_INT("function not present", "writeMultipageTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 writeMultipageTiffSA(SARRAY *sa, const char *fileout)
+{
+    return ERROR_INT("function not present", "writeMultipageTiffSA", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 fprintTiffInfo(FILE *fpout, const char *tiffile)
+{
+    return ERROR_INT("function not present", "fprintTiffInfo", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 tiffGetCount(FILE *fp, l_int32 *pn)
+{
+    return ERROR_INT("function not present", "tiffGetCount", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 getTiffResolution(FILE *fp, l_int32 *pxres, l_int32 *pyres)
+{
+    return ERROR_INT("function not present", "getTiffResolution", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 readHeaderTiff(const char *filename, l_int32 n, l_int32 *pwidth,
+                       l_int32 *pheight, l_int32 *pbps, l_int32 *pspp,
+                       l_int32 *pres, l_int32 *pcmap, l_int32 *pformat)
+{
+    return ERROR_INT("function not present", "readHeaderTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 freadHeaderTiff(FILE *fp, l_int32 n, l_int32 *pwidth,
+                        l_int32 *pheight, l_int32 *pbps, l_int32 *pspp,
+                        l_int32 *pres, l_int32 *pcmap, l_int32 *pformat)
+{
+    return ERROR_INT("function not present", "freadHeaderTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 readHeaderMemTiff(const l_uint8 *cdata, size_t size, l_int32 n,
+                          l_int32 *pwidth, l_int32 *pheight, l_int32 *pbps,
+                          l_int32 *pspp, l_int32 *pres, l_int32 *pcmap,
+                          l_int32 *pformat)
+{
+    return ERROR_INT("function not present", "readHeaderMemTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 findTiffCompression(FILE *fp, l_int32 *pcomptype)
+{
+    return ERROR_INT("function not present", "findTiffCompression", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 extractG4DataFromFile(const char *filein, l_uint8 **pdata,
+                              size_t *pnbytes, l_int32 *pw,
+                              l_int32 *ph, l_int32 *pminisblack)
+{
+    return ERROR_INT("function not present", "extractG4DataFromFile", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadMemTiff(const l_uint8 *cdata, size_t size, l_int32 n)
+{
+    return (PIX *)ERROR_PTR("function not present", "pixReadMemTiff", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteMemTiff(l_uint8 **pdata, size_t *psize, PIX *pix,
+                        l_int32 comptype)
+{
+    return ERROR_INT("function not present", "pixWriteMemTiff", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteMemTiffCustom(l_uint8 **pdata, size_t *psize, PIX *pix,
+                              l_int32 comptype, NUMA *natags, SARRAY *savals,
+                              SARRAY *satypes, NUMA *nasizes)
+{
+    return ERROR_INT("function not present", "pixWriteMemTiffCustom", 1);
+}
+
+/* --------------------------------------------*/
+#endif  /* !HAVE_LIBTIFF */
+/* --------------------------------------------*/
diff --git a/src/utils.c b/src/utils.c
new file mode 100644 (file)
index 0000000..b46680f
--- /dev/null
@@ -0,0 +1,3862 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  utils.c
+ *
+ *       Control of error, warning and info messages
+ *           l_int32    setMsgSeverity()
+ *
+ *       Error return functions, invoked by macros
+ *           l_int32    returnErrorInt()
+ *           l_float32  returnErrorFloat()
+ *           void      *returnErrorPtr()
+ *
+ *       Safe string procs
+ *           char      *stringNew()
+ *           l_int32    stringCopy()
+ *           l_int32    stringReplace()
+ *           l_int32    stringLength()
+ *           l_int32    stringCat()
+ *           char      *stringConcatNew()
+ *           char      *stringJoin()
+ *           l_int32    stringJoinIP()
+ *           char      *stringReverse()
+ *           char      *strtokSafe()
+ *           l_int32    stringSplitOnToken()
+ *
+ *       Find and replace string and array procs
+ *           char      *stringRemoveChars()
+ *           l_int32    stringFindSubstr()
+ *           char      *stringReplaceSubstr()
+ *           char      *stringReplaceEachSubstr()
+ *           L_DNA     *arrayFindEachSequence()
+ *           l_int32    arrayFindSequence()
+ *
+ *       Safe realloc
+ *           void      *reallocNew()
+ *
+ *       Read and write between file and memory
+ *           l_uint8   *l_binaryRead()
+ *           l_uint8   *l_binaryReadStream()
+ *           l_uint8   *l_binaryReadSelect()
+ *           l_uint8   *l_binaryReadSelectStream()
+ *           l_int32    l_binaryWrite()
+ *           l_int32    nbytesInFile()
+ *           l_int32    fnbytesInFile()
+ *
+ *       Copy in memory
+ *           l_uint8   *l_binaryCopy()
+ *
+ *       File copy operations
+ *           l_int32    fileCopy()
+ *           l_int32    fileConcatenate()
+ *           l_int32    fileAppendString()
+ *
+ *       Test files for equivalence
+ *           l_int32    filesAreIdentical()
+ *
+ *       Byte-swapping data conversion
+ *           l_uint16   convertOnBigEnd16()
+ *           l_uint32   convertOnBigEnd32()
+ *           l_uint16   convertOnLittleEnd16()
+ *           l_uint32   convertOnLittleEnd32()
+ *
+ *       Cross-platform functions for opening file streams
+ *           FILE      *fopenReadStream()
+ *           FILE      *fopenWriteStream()
+ *
+ *       Cross-platform functions that avoid C-runtime boundary crossing
+ *       with Windows DLLs
+ *           FILE      *lept_fopen()
+ *           l_int32    lept_fclose()
+ *           void       lept_calloc()
+ *           void       lept_free()
+ *
+ *       Cross-platform file system operations in temp directories
+ *           l_int32    lept_mkdir()
+ *           l_int32    lept_rmdir()
+ *           l_int32    lept_direxists()
+ *           l_int32    lept_mv()
+ *           l_int32    lept_rm_match()
+ *           l_int32    lept_rm()
+ *           l_int32    lept_rmfile()
+ *           l_int32    lept_cp()
+ *
+ *       General file name operations
+ *           l_int32    splitPathAtDirectory()
+ *           l_int32    splitPathAtExtension()
+ *           char      *pathJoin()
+ *           char      *appendSubdirs()
+ *
+ *       Special file name operations
+  *          l_int32    convertSepCharsInPath()
+ *           char      *genPathname()
+ *           l_int32    makeTempDirname()
+ *           l_int32    modifyTrailingSlash()
+ *           char      *genTempFilename()
+ *           l_int32    extractNumberFromFilename()
+ *
+ *       File corruption operation
+ *           l_int32    fileCorruptByDeletion()
+ *           l_int32    fileCorruptByMutation()
+ *
+ *       Generate random integer in given range
+ *           l_int32    genRandomIntegerInRange()
+ *
+ *       Simple math function
+ *           l_int32    lept_roundftoi()
+ *
+ *       64-bit hash functions
+ *           l_int32    l_hashStringToUint64()
+ *           l_int32    l_hashPtToUint64()
+ *           l_int32    l_hashPtToUint64Fast()
+ *
+ *       Prime finders
+ *           l_int32    findNextLargerPrime()
+ *           l_int32    lept_isPrime()
+  *
+ *       Gray code conversion
+ *           l_uint32   convertBinaryToGrayCode()
+ *           l_uint32   convertGrayToBinaryCode()
+ *
+ *       Leptonica version number
+ *           char      *getLeptonicaVersion()
+ *
+ *       Timing
+ *           void       startTimer()
+ *           l_float32  stopTimer()
+ *           L_TIMER    startTimerNested()
+ *           l_float32  stopTimerNested()
+ *           void       l_getCurrentTime()
+ *           L_WALLTIMER  *startWallTimer()
+ *           l_float32  stopWallTimer()
+ *           void       l_getFormattedDate()
+ *
+ *  Notes on cross-platform development
+ *  -----------------------------------
+ *  This is important:
+ *  (1) With the exception of splitPathAtDirectory(), splitPathAtExtension()
+  *     and genPathname(), all input pathnames must have unix separators.
+ *  (2) On Windows, when you specify a read or write to "/tmp/...",
+ *      the filename is rewritten to use the Windows temp directory:
+ *         /tmp  ==>    <Temp>...    (windows)
+ *  (3) This filename rewrite, along with the conversion from unix
+ *      to windows pathnames, happens in genPathname().
+ *  (4) Use fopenReadStream() and fopenWriteStream() to open files,
+ *      because these use genPathname() to find the platform-dependent
+ *      filenames.  Likewise for l_binaryRead() and l_binaryWrite().
+ *  (5) For moving, copying and removing files and directories that are in
+ *      subdirectories of /tmp, use the lept_*() file system shell wrappers:
+ *         lept_mkdir(), lept_rmdir(), lept_mv(), lept_rm() and lept_cp().
+ *  (6) Use the lept_*() C library wrappers.  These work properly on
+ *      Windows, where the same DLL must perform complementary operations
+ *      on file streams (open/close) and heap memory (malloc/free):
+ *         lept_fopen(), lept_fclose(), lept_calloc() and lept_free().
+ */
+
+#include <string.h>
+#include <time.h>
+#ifdef _MSC_VER
+#include <process.h>
+#include <direct.h>
+#else
+#include <unistd.h>
+#endif   /* _MSC_VER */
+#include "allheaders.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <sys/stat.h>  /* for stat, mkdir(2) */
+#include <sys/types.h>
+#endif
+
+#include <math.h>
+#include <stddef.h>
+
+
+    /* Global for controlling message output at runtime */
+LEPT_DLL l_int32  LeptMsgSeverity = DEFAULT_SEVERITY;
+
+
+/*----------------------------------------------------------------------*
+ *                Control of error, warning and info messages           *
+ *----------------------------------------------------------------------*/
+/*!
+ *  setMsgSeverity()
+ *
+ *      Input:  newsev
+ *      Return: oldsev
+ *
+ *  Notes:
+ *      (1) setMsgSeverity() allows the user to specify the desired
+ *          message severity threshold.  Messages of equal or greater
+ *          severity will be output.  The previous message severity is
+ *          returned when the new severity is set.
+ *      (2) If L_SEVERITY_EXTERNAL is passed, then the severity will be
+ *          obtained from the LEPT_MSG_SEVERITY environment variable.
+ *          If the environmental variable is not set, a warning is issued.
+ */
+l_int32
+setMsgSeverity(l_int32  newsev)
+{
+l_int32  oldsev;
+char    *envsev;
+
+    PROCNAME("setMsgSeverity");
+
+    oldsev = LeptMsgSeverity;
+    if (newsev == L_SEVERITY_EXTERNAL) {
+        envsev = getenv("LEPT_MSG_SEVERITY");
+        if (envsev) {
+            LeptMsgSeverity = atoi(envsev);
+            L_INFO("message severity set to external\n", procName);
+        } else {
+            L_WARNING("environment var LEPT_MSG_SEVERITY not defined\n",
+                      procName);
+        }
+    } else {
+        LeptMsgSeverity = newsev;
+        L_INFO("message severity set to %d\n", procName, newsev);
+    }
+
+    return oldsev;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                Error return functions, invoked by macros             *
+ *                                                                      *
+ *    (1) These error functions print messages to stderr and allow      *
+ *        exit from the function that called them.                      *
+ *    (2) They must be invoked only by the macros ERROR_INT,            *
+ *        ERROR_FLOAT and ERROR_PTR, which are in environ.h             *
+ *    (3) The print output can be disabled at compile time, either      *
+ *        by using -DNO_CONSOLE_IO or by setting LeptMsgSeverity.       *
+ *----------------------------------------------------------------------*/
+/*!
+ *  returnErrorInt()
+ *
+ *      Input:  msg (error message)
+ *              procname
+ *              ival (return val)
+ *      Return: ival (typically 1 for an error return)
+ */
+l_int32
+returnErrorInt(const char  *msg,
+               const char  *procname,
+               l_int32      ival)
+{
+    fprintf(stderr, "Error in %s: %s\n", procname, msg);
+    return ival;
+}
+
+
+/*!
+ *  returnErrorFloat()
+ *
+ *      Input:  msg (error message)
+ *              procname
+ *              fval (return val)
+ *      Return: fval
+ */
+l_float32
+returnErrorFloat(const char  *msg,
+                 const char  *procname,
+                 l_float32    fval)
+{
+    fprintf(stderr, "Error in %s: %s\n", procname, msg);
+    return fval;
+}
+
+
+/*!
+ *  returnErrorPtr()
+ *
+ *      Input:  msg (error message)
+ *              procname
+ *              pval  (return val)
+ *      Return: pval (typically null)
+ */
+void *
+returnErrorPtr(const char  *msg,
+               const char  *procname,
+               void        *pval)
+{
+    fprintf(stderr, "Error in %s: %s\n", procname, msg);
+    return pval;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                       Safe string operations                       *
+ *--------------------------------------------------------------------*/
+/*!
+ *  stringNew()
+ *
+ *      Input:  src string
+ *      Return: dest copy of src string, or null on error
+ */
+char *
+stringNew(const char  *src)
+{
+l_int32  len;
+char    *dest;
+
+    PROCNAME("stringNew");
+
+    if (!src) {
+        L_WARNING("src not defined\n", procName);
+        return NULL;
+    }
+
+    len = strlen(src);
+    if ((dest = (char *)LEPT_CALLOC(len + 1, sizeof(char))) == NULL)
+        return (char *)ERROR_PTR("dest not made", procName, NULL);
+
+    stringCopy(dest, src, len);
+    return dest;
+}
+
+
+/*!
+ *  stringCopy()
+ *
+ *      Input:  dest (existing byte buffer)
+ *              src string (<optional> can be null)
+ *              n (max number of characters to copy)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Relatively safe wrapper for strncpy, that checks the input,
+ *          and does not complain if @src is null or @n < 1.
+ *          If @n < 1, this is a no-op.
+ *      (2) @dest needs to be at least @n bytes in size.
+ *      (3) We don't call strncpy() because valgrind complains about
+ *          use of uninitialized values.
+ */
+l_int32
+stringCopy(char        *dest,
+           const char  *src,
+           l_int32      n)
+{
+l_int32  i;
+
+    PROCNAME("stringCopy");
+
+    if (!dest)
+        return ERROR_INT("dest not defined", procName, 1);
+    if (!src || n < 1)
+        return 0;
+
+        /* Implementation of strncpy that valgrind doesn't complain about */
+    for (i = 0; i < n && src[i] != '\0'; i++)
+        dest[i] = src[i];
+    for (; i < n; i++)
+        dest[i] = '\0';
+    return 0;
+}
+
+
+/*!
+ *  stringReplace()
+ *
+ *      Input:  &dest string (<return> copy)
+ *              src string (<optional> can be null)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Frees any existing dest string
+ *      (2) Puts a copy of src string in the dest
+ *      (3) If either or both strings are null, does something reasonable.
+ */
+l_int32
+stringReplace(char       **pdest,
+              const char  *src)
+{
+char    *scopy;
+l_int32  len;
+
+    PROCNAME("stringReplace");
+
+    if (!pdest)
+        return ERROR_INT("pdest not defined", procName, 1);
+
+    if (*pdest)
+        LEPT_FREE(*pdest);
+
+    if (src) {
+        len = strlen(src);
+        if ((scopy = (char *)LEPT_CALLOC(len + 1, sizeof(char))) == NULL)
+            return ERROR_INT("scopy not made", procName, 1);
+        stringCopy(scopy, src, len);
+        *pdest = scopy;
+    } else {
+        *pdest = NULL;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  stringLength()
+ *
+ *      Input:  src string (can be null or null-terminated string)
+ *              size (size of src buffer)
+ *      Return: length of src in bytes.
+ *
+ *  Notes:
+ *      (1) Safe implementation of strlen that only checks size bytes
+ *          for trailing NUL.
+ *      (2) Valid returned string lengths are between 0 and size - 1.
+ *          If size bytes are checked without finding a NUL byte, then
+ *          an error is indicated by returning size.
+ */
+l_int32
+stringLength(const char  *src,
+             size_t       size)
+{
+l_int32  i;
+
+    PROCNAME("stringLength");
+
+    if (!src)
+        return ERROR_INT("src not defined", procName, 0);
+    if (size < 1)
+        return 0;
+
+    for (i = 0; i < size; i++) {
+        if (src[i] == '\0')
+            return i;
+    }
+    return size;  /* didn't find a NUL byte */
+}
+
+
+/*!
+ *  stringCat()
+ *
+ *      Input:  dest (null-terminated byte buffer)
+ *              size (size of dest)
+ *              src string (can be null or null-terminated string)
+ *      Return: number of bytes added to dest; -1 on error
+ *
+ *  Notes:
+ *      (1) Alternative implementation of strncat, that checks the input,
+ *          is easier to use (since the size of the dest buffer is specified
+ *          rather than the number of bytes to copy), and does not complain
+ *          if @src is null.
+ *      (2) Never writes past end of dest.
+ *      (3) If it can't append src (an error), it does nothing.
+ *      (4) N.B. The order of 2nd and 3rd args is reversed from that in
+ *          strncat, as in the Windows function strcat_s().
+ */
+l_int32
+stringCat(char        *dest,
+          size_t       size,
+          const char  *src)
+{
+l_int32  i, n;
+l_int32  lendest, lensrc;
+
+    PROCNAME("stringCat");
+
+    if (!dest)
+        return ERROR_INT("dest not defined", procName, -1);
+    if (size < 1)
+        return ERROR_INT("size < 1; too small", procName, -1);
+    if (!src)
+        return 0;
+
+    lendest = stringLength(dest, size);
+    if (lendest == size)
+        return ERROR_INT("no terminating nul byte", procName, -1);
+    lensrc = stringLength(src, size);
+    if (lensrc == 0)
+        return 0;
+    n = (lendest + lensrc > size - 1 ? size - lendest - 1 : lensrc);
+    if (n < 1)
+        return ERROR_INT("dest too small for append", procName, -1);
+
+    for (i = 0; i < n; i++)
+        dest[lendest + i] = src[i];
+    dest[lendest + n] = '\0';
+    return n;
+}
+
+
+/*!
+ *  stringConcatNew()
+ *
+ *      Input:  first (first string in list)
+ *              varargs  (NULL-terminated list of strings)
+ *      Return: result (new string concatenating the input strings), or
+ *                      NULL if first == NULL
+ *
+ *  Notes:
+ *      (1) The last arg in the list of strings must be NULL.
+ *      (2) Caller must free the returned string.
+ */
+char *
+stringConcatNew(const char  *first, ...)
+{
+size_t       len;
+char        *result, *ptr;
+const char  *arg;
+va_list      args;
+
+    if (!first) return NULL;
+
+        /* Find the length of the output string */
+    va_start(args, first);
+    len = strlen(first);
+    while ((arg = va_arg(args, const char *)) != NULL)
+        len += strlen(arg);
+    va_end(args);
+    result = (char *)LEPT_CALLOC(len + 1, sizeof(char));
+
+        /* Concatenate the args */
+    va_start(args, first);
+    ptr = result;
+    arg = first;
+    while (*arg)
+        *ptr++ = *arg++;
+    while ((arg = va_arg(args, const char *)) != NULL) {
+        while (*arg)
+            *ptr++ = *arg++;
+    }
+    va_end(args);
+    return result;
+}
+
+
+/*!
+ *  stringJoin()
+ *
+ *      Input:  src1 string (<optional> can be null)
+ *              src2 string (<optional> can be null)
+ *      Return: concatenated string, or null on error
+ *
+ *  Notes:
+ *      (1) This is a safe version of strcat; it makes a new string.
+ *      (2) It is not an error if either or both of the strings
+ *          are empty, or if either or both of the pointers are null.
+ */
+char *
+stringJoin(const char  *src1,
+           const char  *src2)
+{
+char    *dest;
+l_int32  srclen1, srclen2, destlen;
+
+    PROCNAME("stringJoin");
+
+    srclen1 = (src1) ? strlen(src1) : 0;
+    srclen2 = (src2) ? strlen(src2) : 0;
+    destlen = srclen1 + srclen2 + 3;
+
+    if ((dest = (char *)LEPT_CALLOC(destlen, sizeof(char))) == NULL)
+        return (char *)ERROR_PTR("calloc fail for dest", procName, NULL);
+
+    if (src1)
+        stringCopy(dest, src1, srclen1);
+    if (src2)
+        strncat(dest, src2, srclen2);
+    return dest;
+}
+
+
+/*!
+ *  stringJoinIP()
+ *
+ *      Input:  &src1 string (address of src1; cannot be on the stack)
+ *              src2 string (<optional> can be null)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a safe in-place version of strcat.  The contents of
+ *          src1 is replaced by the concatenation of src1 and src2.
+ *      (2) It is not an error if either or both of the strings
+ *          are empty (""), or if the pointers to the strings (*psrc1, src2)
+ *          are null.
+ *      (3) src1 should be initialized to null or an empty string
+ *          before the first call.  Use one of these:
+ *              char *src1 = NULL;
+ *              char *src1 = stringNew("");
+ *          Then call with:
+ *              stringJoinIP(&src1, src2);
+ *      (4) This can also be implemented as a macro:
+ *              #define stringJoinIP(src1, src2) \
+ *                  {tmpstr = stringJoin((src1),(src2)); \
+ *                  LEPT_FREE(src1); \
+ *                  (src1) = tmpstr;}
+ *      (5) Another function to consider for joining many strings is
+ *          stringConcatNew().
+ */
+l_int32
+stringJoinIP(char       **psrc1,
+             const char  *src2)
+{
+char  *tmpstr;
+
+    PROCNAME("stringJoinIP");
+
+    if (!psrc1)
+        return ERROR_INT("&src1 not defined", procName, 1);
+
+    tmpstr = stringJoin(*psrc1, src2);
+    LEPT_FREE(*psrc1);
+    *psrc1 = tmpstr;
+    return 0;
+}
+
+
+/*!
+ *  stringReverse()
+ *
+ *      Input:  src (string)
+ *      Return: dest (newly-allocated reversed string)
+ */
+char *
+stringReverse(const char  *src)
+{
+char    *dest;
+l_int32  i, len;
+
+    PROCNAME("stringReverse");
+
+    if (!src)
+        return (char *)ERROR_PTR("src not defined", procName, NULL);
+    len = strlen(src);
+    if ((dest = (char *)LEPT_CALLOC(len + 1, sizeof(char))) == NULL)
+        return (char *)ERROR_PTR("calloc fail for dest", procName, NULL);
+    for (i = 0; i < len; i++)
+        dest[i] = src[len - 1 - i];
+
+    return dest;
+}
+
+
+/*!
+ *  strtokSafe()
+ *
+ *      Input:  cstr (input string to be sequentially parsed;
+ *                    use NULL after the first call)
+ *              seps (a string of character separators)
+ *              &saveptr (<return> ptr to the next char after
+ *                        the last encountered separator)
+ *      Return: substr (a new string that is copied from the previous
+ *                      saveptr up to but not including the next
+ *                      separator character), or NULL if end of cstr.
+ *
+ *  Notes:
+ *      (1) This is a thread-safe implementation of strtok.
+ *      (2) It has the same interface as strtok_r.
+ *      (3) It differs from strtok_r in usage in two respects:
+ *          (a) the input string is not altered
+ *          (b) each returned substring is newly allocated and must
+ *              be freed after use.
+ *      (4) Let me repeat that.  This is "safe" because the input
+ *          string is not altered and because each returned string
+ *          is newly allocated on the heap.
+ *      (5) It is here because, surprisingly, some C libraries don't
+ *          include strtok_r.
+ *      (6) Important usage points:
+ *          - Input the string to be parsed on the first invocation.
+ *          - Then input NULL after that; the value returned in saveptr
+ *            is used in all subsequent calls.
+ *      (7) This is only slightly slower than strtok_k.
+ */
+char *
+strtokSafe(char        *cstr,
+           const char  *seps,
+           char       **psaveptr)
+{
+char     nextc;
+char    *start, *substr;
+l_int32  istart, i, j, nchars;
+
+    PROCNAME("strtokSafe");
+
+    if (!seps)
+        return (char *)ERROR_PTR("seps not defined", procName, NULL);
+    if (!psaveptr)
+        return (char *)ERROR_PTR("&saveptr not defined", procName, NULL);
+
+    if (!cstr)
+        start = *psaveptr;
+    else
+        start = cstr;
+    if (!start)  /* nothing to do */
+        return NULL;
+
+        /* First time, scan for the first non-sep character */
+    istart = 0;
+    if (cstr) {
+        for (istart = 0;; istart++) {
+            if ((nextc = start[istart]) == '\0') {
+                *psaveptr = NULL;  /* in case caller doesn't check ret value */
+                return NULL;
+            }
+            if (!strchr(seps, nextc))
+                break;
+        }
+    }
+
+        /* Scan through, looking for a sep character; if none is
+         * found, 'i' will be at the end of the string. */
+    for (i = istart;; i++) {
+        if ((nextc = start[i]) == '\0')
+            break;
+        if (strchr(seps, nextc))
+            break;
+    }
+
+        /* Save the substring */
+    nchars = i - istart;
+    substr = (char *)LEPT_CALLOC(nchars + 1, sizeof(char));
+    stringCopy(substr, start + istart, nchars);
+
+        /* Look for the next non-sep character.
+         * If this is the last substring, return a null saveptr. */
+    for (j = i;; j++) {
+        if ((nextc = start[j]) == '\0') {
+            *psaveptr = NULL;  /* no more non-sep characters */
+            break;
+        }
+        if (!strchr(seps, nextc)) {
+            *psaveptr = start + j;  /* start here on next call */
+                break;
+        }
+    }
+
+    return substr;
+}
+
+
+/*!
+ *  stringSplitOnToken()
+ *
+ *      Input:  cstr (input string to be split; not altered)
+ *              seps (a string of character separators)
+ *              &head (<return> ptr to copy of the input string, up to
+ *                     the first separator token encountered)
+ *              &tail (<return> ptr to copy of the part of the input string
+ *                     starting with the first non-separator character
+ *                     that occurs after the first separator is found)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The input string is not altered; all split parts are new strings.
+ *      (2) The split occurs around the first consecutive sequence of
+ *          tokens encountered.
+ *      (3) The head goes from the beginning of the string up to
+ *          but not including the first token found.
+ *      (4) The tail contains the second part of the string, starting
+ *          with the first char in that part that is NOT a token.
+ *      (5) If no separator token is found, 'head' contains a copy
+ *          of the input string and 'tail' is null.
+ */
+l_int32
+stringSplitOnToken(char        *cstr,
+                   const char  *seps,
+                   char       **phead,
+                   char       **ptail)
+{
+char  *saveptr;
+
+    PROCNAME("stringSplitOnToken");
+
+    if (!phead)
+        return ERROR_INT("&head not defined", procName, 1);
+    if (!ptail)
+        return ERROR_INT("&tail not defined", procName, 1);
+    *phead = *ptail = NULL;
+    if (!cstr)
+        return ERROR_INT("cstr not defined", procName, 1);
+    if (!seps)
+        return ERROR_INT("seps not defined", procName, 1);
+
+    *phead = strtokSafe(cstr, seps, &saveptr);
+    if (saveptr)
+        *ptail = stringNew(saveptr);
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                       Find and replace procs                       *
+ *--------------------------------------------------------------------*/
+/*!
+ *  stringRemoveChars()
+ *
+ *      Input:  src (input string; can be of zero length)
+ *              remchars  (string of chars to be removed from src)
+ *      Return: dest (string with specified chars removed), or null on error
+ */
+char *
+stringRemoveChars(const char  *src,
+                  const char  *remchars)
+{
+char     ch;
+char    *dest;
+l_int32  nsrc, i, k;
+
+    PROCNAME("stringRemoveChars");
+
+    if (!src)
+        return (char *)ERROR_PTR("src not defined", procName, NULL);
+    if (!remchars)
+        return stringNew(src);
+
+    if ((dest = (char *)LEPT_CALLOC(strlen(src) + 1, sizeof(char))) == NULL)
+        return (char *)ERROR_PTR("dest not made", procName, NULL);
+    nsrc = strlen(src);
+    for (i = 0, k = 0; i < nsrc; i++) {
+        ch = src[i];
+        if (!strchr(remchars, ch))
+            dest[k++] = ch;
+    }
+
+    return dest;
+}
+
+
+/*!
+ *  stringFindSubstr()
+ *
+ *      Input:  src (input string; can be of zero length)
+ *              sub (substring to be searched for)
+ *              &loc (<return optional> location of substring in src)
+ *      Return: 1 if found; 0 if not found or on error
+ *
+ *  Notes:
+ *      (1) This is a wrapper around strstr().
+ *      (2) Both @src and @sub must be defined, and @sub must have
+ *          length of at least 1.
+ *      (3) If the substring is not found and loc is returned, it has
+ *          the value -1.
+ */
+l_int32
+stringFindSubstr(const char  *src,
+                 const char  *sub,
+                 l_int32     *ploc)
+{
+char  *ptr;
+
+    PROCNAME("stringFindSubstr");
+
+    if (!src)
+        return ERROR_INT("src not defined", procName, 0);
+    if (!sub)
+        return ERROR_INT("sub not defined", procName, 0);
+    if (ploc) *ploc = -1;
+    if (strlen(sub) == 0)
+        return ERROR_INT("substring length 0", procName, 0);
+    if (strlen(src) == 0)
+        return 0;
+
+    if ((ptr = (char *)strstr(src, sub)) == NULL)  /* not found */
+        return 0;
+
+    if (ploc)
+        *ploc = ptr - src;
+    return 1;
+}
+
+
+/*!
+ *  stringReplaceSubstr()
+ *
+ *      Input:  src (input string; can be of zero length)
+ *              sub1 (substring to be replaced)
+ *              sub2 (substring to put in; can be "")
+ *              &found (<return optional> 1 if sub1 is found; 0 otherwise)
+ *              &loc (<return optional> location of ptr after replacement)
+ *      Return: dest (string with substring replaced), or null if the
+ *              substring not found or on error.
+ *
+ *  Notes:
+ *      (1) Replaces the first instance.
+ *      (2) To only remove sub1, use "" for sub2
+ *      (3) Returns a new string if sub1 and sub2 are the same.
+ *      (4) The optional loc is input as the byte offset within the src
+ *          from which the search starts, and after the search it is the
+ *          char position in the string of the next character after
+ *          the substituted string.
+ *      (5) N.B. If ploc is not null, loc must always be initialized.
+ *          To search the string from the beginning, set loc = 0.
+ */
+char *
+stringReplaceSubstr(const char  *src,
+                    const char  *sub1,
+                    const char  *sub2,
+                    l_int32     *pfound,
+                    l_int32     *ploc)
+{
+char    *ptr, *dest;
+l_int32  nsrc, nsub1, nsub2, len, npre, loc;
+
+    PROCNAME("stringReplaceSubstr");
+
+    if (!src)
+        return (char *)ERROR_PTR("src not defined", procName, NULL);
+    if (!sub1)
+        return (char *)ERROR_PTR("sub1 not defined", procName, NULL);
+    if (!sub2)
+        return (char *)ERROR_PTR("sub2 not defined", procName, NULL);
+
+    if (pfound)
+        *pfound = 0;
+    if (ploc)
+        loc = *ploc;
+    else
+        loc = 0;
+    if ((ptr = (char *)strstr(src + loc, sub1)) == NULL) {
+        return NULL;
+    }
+
+    if (pfound)
+        *pfound = 1;
+    nsrc = strlen(src);
+    nsub1 = strlen(sub1);
+    nsub2 = strlen(sub2);
+    len = nsrc + nsub2 - nsub1;
+    if ((dest = (char *)LEPT_CALLOC(len + 1, sizeof(char))) == NULL)
+        return (char *)ERROR_PTR("dest not made", procName, NULL);
+    npre = ptr - src;
+    memcpy(dest, src, npre);
+    strcpy(dest + npre, sub2);
+    strcpy(dest + npre + nsub2, ptr + nsub1);
+    if (ploc)
+        *ploc = npre + nsub2;
+
+    return dest;
+}
+
+
+/*!
+ *  stringReplaceEachSubstr()
+ *
+ *      Input:  src (input string; can be of zero length)
+ *              sub1 (substring to be replaced)
+ *              sub2 (substring to put in; can be "")
+ *              &count (<optional return > the number of times that sub1
+ *                      is found in src; 0 if not found)
+ *      Return: dest (string with substring replaced), or null if the
+ *              substring not found or on error.
+ *
+ *  Notes:
+ *      (1) Replaces every instance.
+ *      (2) To only remove each instance of sub1, use "" for sub2
+ *      (3) Returns NULL if sub1 and sub2 are the same.
+ */
+char *
+stringReplaceEachSubstr(const char  *src,
+                        const char  *sub1,
+                        const char  *sub2,
+                        l_int32     *pcount)
+{
+char    *currstr, *newstr;
+l_int32  loc;
+
+    PROCNAME("stringReplaceEachSubstr");
+
+    if (pcount) *pcount = 0;
+    if (!src)
+        return (char *)ERROR_PTR("src not defined", procName, NULL);
+    if (!sub1)
+        return (char *)ERROR_PTR("sub1 not defined", procName, NULL);
+    if (!sub2)
+        return (char *)ERROR_PTR("sub2 not defined", procName, NULL);
+
+    loc = 0;
+    if ((newstr = stringReplaceSubstr(src, sub1, sub2, NULL, &loc)) == NULL)
+        return NULL;
+
+    if (pcount)
+        (*pcount)++;
+    while (1) {
+        currstr = newstr;
+        newstr = stringReplaceSubstr(currstr, sub1, sub2, NULL, &loc);
+        if (!newstr)
+            return currstr;
+        LEPT_FREE(currstr);
+        if (pcount)
+            (*pcount)++;
+    }
+}
+
+
+/*!
+ *  arrayFindEachSequence()
+ *
+ *      Input:  data (byte array)
+ *              datalen (length of data, in bytes)
+ *              sequence (subarray of bytes to find in data)
+ *              seqlen (length of sequence, in bytes)
+ *      Return: dna of offsets where the sequence is found, or null if
+ *              none are found or on error
+ *
+ *  Notes:
+ *      (1) The byte arrays @data and @sequence are not C strings,
+ *          as they can contain null bytes.  Therefore, for each
+ *          we must give the length of the array.
+ *      (2) This finds every occurrence in @data of @sequence.
+ */
+L_DNA *
+arrayFindEachSequence(const l_uint8  *data,
+                      size_t          datalen,
+                      const l_uint8  *sequence,
+                      size_t          seqlen)
+{
+l_int32  start, offset, realoffset, found;
+L_DNA   *da;
+
+    PROCNAME("arrayFindEachSequence");
+
+    if (!data || !sequence)
+        return (L_DNA *)ERROR_PTR("data & sequence not both defined",
+                                  procName, NULL);
+
+    da = l_dnaCreate(0);
+    start = 0;
+    while (1) {
+        arrayFindSequence(data + start, datalen - start, sequence, seqlen,
+                          &offset, &found);
+        if (found == FALSE)
+            break;
+
+        realoffset = start + offset;
+        l_dnaAddNumber(da, realoffset);
+        start = realoffset + seqlen;
+        if (start >= datalen)
+            break;
+    }
+
+    if (l_dnaGetCount(da) == 0)
+        l_dnaDestroy(&da);
+    return da;
+}
+
+
+/*!
+ *  arrayFindSequence()
+ *
+ *      Input:  data (byte array)
+ *              datalen (length of data, in bytes)
+ *              sequence (subarray of bytes to find in data)
+ *              seqlen (length of sequence, in bytes)
+ *              &offset (return> offset from beginning of
+ *                       data where the sequence begins)
+ *              &found (<return> 1 if sequence is found; 0 otherwise)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The byte arrays 'data' and 'sequence' are not C strings,
+ *          as they can contain null bytes.  Therefore, for each
+ *          we must give the length of the array.
+ *      (2) This searches for the first occurrence in @data of @sequence,
+ *          which consists of @seqlen bytes.  The parameter @seqlen
+ *          must not exceed the actual length of the @sequence byte array.
+ *      (3) If the sequence is not found, the offset will be 0, so you
+ *          must check @found.
+ */
+l_int32
+arrayFindSequence(const l_uint8  *data,
+                  size_t          datalen,
+                  const l_uint8  *sequence,
+                  size_t          seqlen,
+                  l_int32        *poffset,
+                  l_int32        *pfound)
+{
+l_int32  i, j, found, lastpos;
+
+    PROCNAME("arrayFindSequence");
+
+    if (poffset) *poffset = 0;
+    if (pfound) *pfound = FALSE;
+    if (!data || !sequence)
+        return ERROR_INT("data & sequence not both defined", procName, 1);
+    if (!poffset || !pfound)
+        return ERROR_INT("&offset and &found not defined", procName, 1);
+
+    lastpos = datalen - seqlen + 1;
+    found = FALSE;
+    for (i = 0; i < lastpos; i++) {
+        for (j = 0; j < seqlen; j++) {
+            if (data[i + j] != sequence[j])
+                 break;
+            if (j == seqlen - 1)
+                 found = TRUE;
+        }
+        if (found == TRUE)
+            break;
+    }
+
+    if (found == TRUE) {
+        *poffset = i;
+        *pfound = TRUE;
+    }
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                             Safe realloc                           *
+ *--------------------------------------------------------------------*/
+/*!
+ *  reallocNew()
+ *
+ *      Input:  &indata (<optional>; nulls indata)
+ *              oldsize (size of input data to be copied, in bytes)
+ *              newsize (size of data to be reallocated in bytes)
+ *      Return: ptr to new data, or null on error
+ *
+ *  Action: !N.B. (3) and (4)!
+ *      (1) Allocates memory, initialized to 0
+ *      (2) Copies as much of the input data as possible
+ *          to the new block, truncating the copy if necessary
+ *      (3) Frees the input data
+ *      (4) Zeroes the input data ptr
+ *
+ *  Notes:
+ *      (1) If newsize <=0, just frees input data and nulls ptr
+ *      (2) If input ptr is null, just callocs new memory
+ *      (3) This differs from realloc in that it always allocates
+ *          new memory (if newsize > 0) and initializes it to 0,
+ *          it requires the amount of old data to be copied,
+ *          and it takes the address of the input ptr and
+ *          nulls the handle.
+ */
+void *
+reallocNew(void   **pindata,
+           l_int32  oldsize,
+           l_int32  newsize)
+{
+l_int32  minsize;
+void    *indata;
+void    *newdata;
+
+    PROCNAME("reallocNew");
+
+    if (!pindata)
+        return ERROR_PTR("input data not defined", procName, NULL);
+    indata = *pindata;
+
+    if (newsize <= 0) {   /* nonstandard usage */
+        if (indata) {
+            LEPT_FREE(indata);
+            *pindata = NULL;
+        }
+        return NULL;
+    }
+
+    if (!indata) {  /* nonstandard usage */
+        if ((newdata = (void *)LEPT_CALLOC(1, newsize)) == NULL)
+            return ERROR_PTR("newdata not made", procName, NULL);
+        return newdata;
+    }
+
+        /* Standard usage */
+    if ((newdata = (void *)LEPT_CALLOC(1, newsize)) == NULL)
+        return ERROR_PTR("newdata not made", procName, NULL);
+    minsize = L_MIN(oldsize, newsize);
+    memcpy((char *)newdata, (char *)indata, minsize);
+
+    LEPT_FREE(indata);
+    *pindata = NULL;
+
+    return newdata;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                 Read and write between file and memory             *
+ *--------------------------------------------------------------------*/
+/*!
+ *  l_binaryRead()
+ *
+ *      Input:  filename
+ *              &nbytes (<return> number of bytes read)
+ *      Return: data, or null on error
+ */
+l_uint8 *
+l_binaryRead(const char  *filename,
+             size_t      *pnbytes)
+{
+l_uint8  *data;
+FILE     *fp;
+
+    PROCNAME("l_binaryRead");
+
+    if (!pnbytes)
+        return (l_uint8 *)ERROR_PTR("pnbytes not defined", procName, NULL);
+    *pnbytes = 0;
+    if (!filename)
+        return (l_uint8 *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (l_uint8 *)ERROR_PTR("file stream not opened", procName, NULL);
+    data = l_binaryReadStream(fp, pnbytes);
+    fclose(fp);
+    return data;
+}
+
+
+/*!
+ *  l_binaryReadStream()
+ *
+ *      Input:  fp (stream opened to read; can be stdin)
+ *              &nbytes (<return> number of bytes read)
+ *      Return: null-terminated array, or null on error
+ *              (reading 0 bytes is not an error)
+ *
+ *  Notes:
+ *      (1) The returned array is terminated with a null byte so that it can
+ *          be used to read ascii data from a file into a proper C string.
+ *      (2) This can be used to capture data that is piped in via stdin,
+ *          because it does not require seeking within the file.
+ *      (3) For example, you can read an image from stdin into memory
+ *          using shell redirection, with one of these shell commands:
+ *             cat <imagefile> | readprog
+ *             readprog < <imagefile>
+ *          where readprog is:
+ *             l_uint8 *data = l_binaryReadStream(stdin, &nbytes);
+ *             Pix *pix = pixReadMem(data, nbytes);
+ */
+l_uint8 *
+l_binaryReadStream(FILE    *fp,
+                   size_t  *pnbytes)
+{
+l_uint8    *data;
+l_int32     seekable, navail, nadd, nread;
+L_BBUFFER  *bb;
+
+    PROCNAME("l_binaryReadStream");
+
+    if (!pnbytes)
+        return (l_uint8 *)ERROR_PTR("&nbytes not defined", procName, NULL);
+    *pnbytes = 0;
+    if (!fp)
+        return (l_uint8 *)ERROR_PTR("fp not defined", procName, NULL);
+
+        /* Test if the stream is seekable, by attempting to seek to
+         * the start of data.  This is a no-op.  If it is seekable, use
+         * l_binaryReadSelectStream() to determine the size of the
+         * data to be read in advance. */
+    seekable = (ftell(fp) == 0) ? 1 : 0;
+    if (seekable)
+        return l_binaryReadSelectStream(fp, 0, 0, pnbytes);
+
+        /* If it is not seekable, use the bbuffer to realloc memory
+         * as needed during reading. */
+    bb = bbufferCreate(NULL, 4096);
+    while (1) {
+        navail = bb->nalloc - bb->n;
+        if (navail < 4096) {
+             nadd = L_MAX(bb->nalloc, 4096);
+             bbufferExtendArray(bb, nadd);
+        }
+        nread = fread((void *)(bb->array + bb->n), 1, 4096, fp);
+        bb->n += nread;
+        if (nread != 4096) break;
+    }
+
+        /* Copy the data to a new array sized for the data, because
+         * the bbuffer array can be nearly twice the size we need. */
+    if ((data = (l_uint8 *)LEPT_CALLOC(bb->n + 1, sizeof(l_uint8))) != NULL) {
+        memcpy(data, bb->array, bb->n);
+        *pnbytes = bb->n;
+    } else {
+        L_ERROR("calloc fail for data\n", procName);
+    }
+
+    bbufferDestroy(&bb);
+    return data;
+}
+
+
+/*!
+ *  l_binaryReadSelect()
+ *
+ *      Input:  filename
+ *              start (first byte to read)
+ *              nbytes (number of bytes to read; use 0 to read to end of file)
+ *              &nread (<return> number of bytes actually read)
+ *      Return: data, or null on error
+ *
+ *  Notes:
+ *      (1) The returned array is terminated with a null byte so that it can
+ *          be used to read ascii data from a file into a proper C string.
+ */
+l_uint8 *
+l_binaryReadSelect(const char  *filename,
+                   size_t       start,
+                   size_t       nbytes,
+                   size_t      *pnread)
+{
+l_uint8  *data;
+FILE     *fp;
+
+    PROCNAME("l_binaryReadSelect");
+
+    if (!pnread)
+        return (l_uint8 *)ERROR_PTR("pnread not defined", procName, NULL);
+    *pnread = 0;
+    if (!filename)
+        return (l_uint8 *)ERROR_PTR("filename not defined", procName, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (l_uint8 *)ERROR_PTR("file stream not opened", procName, NULL);
+    data = l_binaryReadSelectStream(fp, start, nbytes, pnread);
+    fclose(fp);
+    return data;
+}
+
+
+/*!
+ *  l_binaryReadSelectStream()
+ *
+ *      Input:  stream
+ *              start (first byte to read)
+ *              nbytes (number of bytes to read; use 0 to read to end of file)
+ *              &nread (<return> number of bytes actually read)
+ *      Return: null-terminated array, or null on error
+ *              (reading 0 bytes is not an error)
+ *
+ *  Notes:
+ *      (1) The returned array is terminated with a null byte so that it can
+ *          be used to read ascii data from a file into a proper C string.
+ *          If the file to be read is empty and @start == 0, an array
+ *          with a single null byte is returned.
+ *      (2) Side effect: the stream pointer is re-positioned to the
+ *          beginning of the file.
+ */
+l_uint8 *
+l_binaryReadSelectStream(FILE    *fp,
+                         size_t   start,
+                         size_t   nbytes,
+                         size_t  *pnread)
+{
+l_uint8  *data;
+size_t    bytesleft, bytestoread, nread, filebytes;
+
+    PROCNAME("l_binaryReadSelectStream");
+
+    if (!pnread)
+        return (l_uint8 *)ERROR_PTR("&nread not defined", procName, NULL);
+    *pnread = 0;
+    if (!fp)
+        return (l_uint8 *)ERROR_PTR("stream not defined", procName, NULL);
+
+        /* Verify and adjust the parameters if necessary */
+    fseek(fp, 0, SEEK_END);  /* EOF */
+    filebytes = ftell(fp);
+    fseek(fp, 0, SEEK_SET);
+    if (start > filebytes) {
+        L_ERROR("start = %lu but filebytes = %lu\n", procName,
+                (unsigned long)start, (unsigned long)filebytes);
+        return NULL;
+    }
+    if (filebytes == 0)  /* start == 0; nothing to read; return null byte */
+        return (l_uint8 *)LEPT_CALLOC(1, 1);
+    bytesleft = filebytes - start;  /* greater than 0 */
+    if (nbytes == 0) nbytes = bytesleft;
+    bytestoread = (bytesleft >= nbytes) ? nbytes : bytesleft;
+
+        /* Read the data */
+    if ((data = (l_uint8 *)LEPT_CALLOC(1, bytestoread + 1)) == NULL)
+        return (l_uint8 *)ERROR_PTR("calloc fail for data", procName, NULL);
+    fseek(fp, start, SEEK_SET);
+    nread = fread(data, 1, bytestoread, fp);
+    if (nbytes != nread)
+        L_INFO("%lu bytes requested; %lu bytes read\n", procName,
+               (unsigned long)nbytes, (unsigned long)nread);
+    *pnread = nread;
+    fseek(fp, 0, SEEK_SET);
+    return data;
+}
+
+
+/*!
+ *  l_binaryWrite()
+ *
+ *      Input:  filename (output)
+ *              operation  ("w" for write; "a" for append)
+ *              data  (binary data to be written)
+ *              nbytes  (size of data array)
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+l_binaryWrite(const char  *filename,
+              const char  *operation,
+              void        *data,
+              size_t       nbytes)
+{
+char   actualOperation[20];
+FILE  *fp;
+
+    PROCNAME("l_binaryWrite");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!operation)
+        return ERROR_INT("operation not defined", procName, 1);
+    if (!data)
+        return ERROR_INT("data not defined", procName, 1);
+    if (nbytes <= 0)
+        return ERROR_INT("nbytes must be > 0", procName, 1);
+
+    if (!strcmp(operation, "w") && !strcmp(operation, "a"))
+        return ERROR_INT("operation not one of {'w','a'}", procName, 1);
+
+        /* The 'b' flag to fopen() is ignored for all POSIX
+         * conforming systems.  However, Windows needs the 'b' flag. */
+    stringCopy(actualOperation, operation, 2);
+    strncat(actualOperation, "b", 2);
+
+    if ((fp = fopenWriteStream(filename, actualOperation)) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    fwrite(data, 1, nbytes, fp);
+    fclose(fp);
+    return 0;
+}
+
+
+/*!
+ *  nbytesInFile()
+ *
+ *      Input:  filename
+ *      Return: nbytes in file; 0 on error
+ */
+size_t
+nbytesInFile(const char  *filename)
+{
+size_t  nbytes;
+FILE   *fp;
+
+    PROCNAME("nbytesInFile");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 0);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT("stream not opened", procName, 0);
+    nbytes = fnbytesInFile(fp);
+    fclose(fp);
+    return nbytes;
+}
+
+
+/*!
+ *  fnbytesInFile()
+ *
+ *      Input:  file stream
+ *      Return: nbytes in file; 0 on error
+ */
+size_t
+fnbytesInFile(FILE  *fp)
+{
+size_t  nbytes, pos;
+
+    PROCNAME("fnbytesInFile");
+
+    if (!fp)
+        return ERROR_INT("stream not open", procName, 0);
+
+    pos = ftell(fp);          /* initial position */
+    fseek(fp, 0, SEEK_END);   /* EOF */
+    nbytes = ftell(fp);
+    fseek(fp, pos, SEEK_SET);        /* back to initial position */
+    return nbytes;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                            Copy in memory                          *
+ *--------------------------------------------------------------------*/
+/*!
+ *  l_binaryCopy()
+ *
+ *      Input:  datas
+ *              size (of data array)
+ *      Return: datad (on heap), or null on error
+ *
+ *  Notes:
+ *      (1) We add 4 bytes to the zeroed output because in some cases
+ *          (e.g., string handling) it is important to have the data
+ *          be null terminated.  This guarantees that after the memcpy,
+ *          the result is automatically null terminated.
+ */
+l_uint8 *
+l_binaryCopy(l_uint8  *datas,
+             size_t    size)
+{
+l_uint8  *datad;
+
+    PROCNAME("l_binaryCopy");
+
+    if (!datas)
+        return (l_uint8 *)ERROR_PTR("datas not defined", procName, NULL);
+
+    if ((datad = (l_uint8 *)LEPT_CALLOC(size + 4, sizeof(l_uint8))) == NULL)
+        return (l_uint8 *)ERROR_PTR("datad not made", procName, NULL);
+    memcpy(datad, datas, size);
+    return datad;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                         File copy operations                       *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fileCopy()
+ *
+ *      Input:  srcfile (copy this file)
+ *              newfile (to this file)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+fileCopy(const char  *srcfile,
+         const char  *newfile)
+{
+l_int32   ret;
+size_t    nbytes;
+l_uint8  *data;
+
+    PROCNAME("fileCopy");
+
+    if (!srcfile)
+        return ERROR_INT("srcfile not defined", procName, 1);
+    if (!newfile)
+        return ERROR_INT("newfile not defined", procName, 1);
+
+    if ((data = l_binaryRead(srcfile, &nbytes)) == NULL)
+        return ERROR_INT("data not returned", procName, 1);
+    ret = l_binaryWrite(newfile, "w", data, nbytes);
+    LEPT_FREE(data);
+    return ret;
+}
+
+
+/*!
+ *  fileConcatenate()
+ *
+ *      Input:  srcfile (file to append)
+ *              destfile (file to add to)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+fileConcatenate(const char  *srcfile,
+                const char  *destfile)
+{
+size_t    nbytes;
+l_uint8  *data;
+
+    PROCNAME("fileConcatenate");
+
+    if (!srcfile)
+        return ERROR_INT("srcfile not defined", procName, 1);
+    if (!destfile)
+        return ERROR_INT("destfile not defined", procName, 1);
+
+    data = l_binaryRead(srcfile, &nbytes);
+    l_binaryWrite(destfile, "a", data, nbytes);
+    LEPT_FREE(data);
+    return 0;
+}
+
+
+/*!
+ *  fileAppendString()
+ *
+ *      Input:  filename
+ *              str (string to append to file)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+fileAppendString(const char  *filename,
+                 const char  *str)
+{
+FILE  *fp;
+
+    PROCNAME("fileAppendString");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!str)
+        return ERROR_INT("str not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "a")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    fprintf(fp, "%s", str);
+    fclose(fp);
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                      Test files for equivalence                    *
+ *--------------------------------------------------------------------*/
+/*!
+ *  filesAreIdentical()
+ *
+ *      Input:  fname1
+ *              fname2
+ *              &same (<return> 1 if identical; 0 if different)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+filesAreIdentical(const char  *fname1,
+                  const char  *fname2,
+                  l_int32     *psame)
+{
+l_int32   i, same;
+size_t    nbytes1, nbytes2;
+l_uint8  *array1, *array2;
+
+    PROCNAME("filesAreIdentical");
+
+    if (!psame)
+        return ERROR_INT("&same not defined", procName, 1);
+    *psame = 0;
+    if (!fname1 || !fname2)
+        return ERROR_INT("both names not defined", procName, 1);
+
+    nbytes1 = nbytesInFile(fname1);
+    nbytes2 = nbytesInFile(fname2);
+    if (nbytes1 != nbytes2)
+        return 0;
+
+    if ((array1 = l_binaryRead(fname1, &nbytes1)) == NULL)
+        return ERROR_INT("array1 not read", procName, 1);
+    if ((array2 = l_binaryRead(fname2, &nbytes2)) == NULL)
+        return ERROR_INT("array2 not read", procName, 1);
+    same = 1;
+    for (i = 0; i < nbytes1; i++) {
+        if (array1[i] != array2[i]) {
+            same = 0;
+            break;
+        }
+    }
+    LEPT_FREE(array1);
+    LEPT_FREE(array2);
+    *psame = same;
+
+    return 0;
+}
+
+
+/*--------------------------------------------------------------------------*
+ *   16 and 32 bit byte-swapping on big endian and little  endian machines  *
+ *                                                                          *
+ *   These are typically used for I/O conversions:                          *
+ *      (1) endian conversion for data that was read from a file            *
+ *      (2) endian conversion on data before it is written to a file        *
+ *--------------------------------------------------------------------------*/
+
+/*--------------------------------------------------------------------*
+ *                        16-bit byte swapping                        *
+ *--------------------------------------------------------------------*/
+#ifdef L_BIG_ENDIAN
+
+l_uint16
+convertOnBigEnd16(l_uint16  shortin)
+{
+    return ((shortin << 8) | (shortin >> 8));
+}
+
+l_uint16
+convertOnLittleEnd16(l_uint16  shortin)
+{
+    return  shortin;
+}
+
+#else     /* L_LITTLE_ENDIAN */
+
+l_uint16
+convertOnLittleEnd16(l_uint16  shortin)
+{
+    return ((shortin << 8) | (shortin >> 8));
+}
+
+l_uint16
+convertOnBigEnd16(l_uint16  shortin)
+{
+    return  shortin;
+}
+
+#endif  /* L_BIG_ENDIAN */
+
+
+/*--------------------------------------------------------------------*
+ *                        32-bit byte swapping                        *
+ *--------------------------------------------------------------------*/
+#ifdef L_BIG_ENDIAN
+
+l_uint32
+convertOnBigEnd32(l_uint32  wordin)
+{
+    return ((wordin << 24) | ((wordin << 8) & 0x00ff0000) |
+            ((wordin >> 8) & 0x0000ff00) | (wordin >> 24));
+}
+
+l_uint32
+convertOnLittleEnd32(l_uint32  wordin)
+{
+    return wordin;
+}
+
+#else  /*  L_LITTLE_ENDIAN */
+
+l_uint32
+convertOnLittleEnd32(l_uint32  wordin)
+{
+    return ((wordin << 24) | ((wordin << 8) & 0x00ff0000) |
+            ((wordin >> 8) & 0x0000ff00) | (wordin >> 24));
+}
+
+l_uint32
+convertOnBigEnd32(l_uint32  wordin)
+{
+    return wordin;
+}
+
+#endif  /* L_BIG_ENDIAN */
+
+
+
+/*--------------------------------------------------------------------*
+ *                        Opening file streams                        *
+ *--------------------------------------------------------------------*/
+/*!
+ *  fopenReadStream()
+ *
+ *      Input:  filename
+ *      Return: stream, or null on error
+ *
+ *  Notes:
+ *      (1) This should be used whenever you want to run fopen() to
+ *          read from a stream.  Never call fopen() directory.
+ *      (2) This also handles pathname conversions, if necessary:
+ *           ==>   /tmp              (unix)  [default]
+ *           ==>   /tmp/leptonica    (unix)  [if ADD_LEPTONICA_SUBDIR == 1]
+ *           ==>   <Temp>/leptonica  (windows)
+ */
+FILE *
+fopenReadStream(const char  *filename)
+{
+char  *fname, *tail;
+FILE  *fp;
+
+    PROCNAME("fopenReadStream");
+
+    if (!filename)
+        return (FILE *)ERROR_PTR("filename not defined", procName, NULL);
+
+        /* Try input filename */
+    fname = genPathname(filename, NULL);
+    fp = fopen(fname, "rb");
+    LEPT_FREE(fname);
+    if (fp) return fp;
+
+        /* Else, strip directory and try locally */
+    splitPathAtDirectory(filename, NULL, &tail);
+    fp = fopen(tail, "rb");
+    LEPT_FREE(tail);
+
+    if (!fp)
+        return (FILE *)ERROR_PTR("file not found", procName, NULL);
+    return fp;
+}
+
+
+/*!
+ *  fopenWriteStream()
+ *
+ *      Input:  filename
+ *              modestring
+ *      Return: stream, or null on error
+ *
+ *  Notes:
+ *      (1) This should be used whenever you want to run fopen() to
+ *          write or append to a stream.  Never call fopen() directory.
+ *      (2) This also handles pathname conversions, if necessary:
+ *           ==>   /tmp              (unix)  [default]
+ *           ==>   /tmp/leptonica    (unix)  [if ADD_LEPTONICA_SUBDIR == 1]
+ *           ==>   <Temp>/leptonica  (windows)
+ */
+FILE *
+fopenWriteStream(const char  *filename,
+                 const char  *modestring)
+{
+char  *fname;
+FILE  *fp;
+
+    PROCNAME("fopenWriteStream");
+
+    if (!filename)
+        return (FILE *)ERROR_PTR("filename not defined", procName, NULL);
+
+    fname = genPathname(filename, NULL);
+    fp = fopen(fname, modestring);
+    LEPT_FREE(fname);
+    if (!fp)
+        return (FILE *)ERROR_PTR("stream not opened", procName, NULL);
+    return fp;
+}
+
+
+/*--------------------------------------------------------------------*
+ *      Functions to avoid C-runtime boundary crossing with dlls      *
+ *--------------------------------------------------------------------*/
+/*
+ *  Problems arise when pointers to streams and data are passed
+ *  between two Windows DLLs that have been generated with different
+ *  C runtimes.  To avoid this, leptonica provides wrappers for
+ *  several C library calls.
+ */
+/*!
+ *  lept_fopen()
+ *
+ *      Input:  filename
+ *              mode (same as for fopen(); e.g., "rb")
+ *      Return: stream or null on error
+ *
+ *  Notes:
+ *      (1) This must be used by any application that passes
+ *          a file handle to a leptonica Windows DLL.
+ */
+FILE *
+lept_fopen(const char  *filename,
+           const char  *mode)
+{
+    PROCNAME("lept_fopen");
+
+    if (!filename)
+        return (FILE *)ERROR_PTR("filename not defined", procName, NULL);
+    if (!mode)
+        return (FILE *)ERROR_PTR("mode not defined", procName, NULL);
+
+    if (stringFindSubstr(mode, "r", NULL))
+        return fopenReadStream(filename);
+    else
+        return fopenWriteStream(filename, mode);
+}
+
+
+/*!
+ *  lept_fclose()
+ *
+ *      Input:  fp (stream handle)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This should be used by any application that accepts
+ *          a file handle generated by a leptonica Windows DLL.
+ */
+l_int32
+lept_fclose(FILE *fp)
+{
+    PROCNAME("lept_fclose");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+
+    return fclose(fp);
+}
+
+
+/*!
+ *  lept_calloc()
+ *
+ *      Input:  nmemb (number of members)
+ *              size (of each member)
+ *      Return: void ptr, or null on error
+ *
+ *  Notes:
+ *      (1) For safety with windows DLLs, this can be used in conjunction
+ *          with lept_free() to avoid C-runtime boundary problems.
+ *          Just use these two functions throughout your application.
+ */
+void *
+lept_calloc(size_t  nmemb,
+            size_t  size)
+{
+    if (nmemb <= 0 || size <= 0)
+        return NULL;
+    return LEPT_CALLOC(nmemb, size);
+}
+
+
+/*!
+ *  lept_free()
+ *
+ *      Input:  void ptr
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This should be used by any application that accepts
+ *          heap data allocated by a leptonica Windows DLL.
+ */
+void
+lept_free(void *ptr)
+{
+    if (!ptr) return;
+    LEPT_FREE(ptr);
+    return;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                Cross-platform file system operations               *
+ *         [ These only write to /tmp or its subdirectories ]         *
+ *--------------------------------------------------------------------*/
+/*!
+ *  lept_mkdir()
+ *
+ *      Input:  subdir (of /tmp or its equivalent on Windows)
+ *      Return: 0 on success, non-zero on failure
+ *
+ *  Notes:
+ *      (1) @subdir is a partial path that can consist of one or more
+ *          directories.
+ *      (2) This makes any subdirectories of /tmp that are required.
+ *      (3) The root temp directory is:
+ *            /tmp    (unix)  [default]
+ *            <Temp>  (windows)
+ */
+l_int32
+lept_mkdir(const char  *subdir)
+{
+char     *dir, *tmpdir;
+l_int32   i, n;
+l_int32   ret = 0;
+SARRAY   *sa;
+#ifdef  _WIN32
+l_uint32  attributes;
+#endif  /* _WIN32 */
+
+    PROCNAME("lept_mkdir");
+
+    if (!subdir)
+        return ERROR_INT("subdir not defined", procName, 1);
+    if ((strlen(subdir) == 0) || (subdir[0] == '.') || (subdir[0] == '/'))
+        return ERROR_INT("subdir not an actual subdirectory", procName, 1);
+
+    sa = sarrayCreate(0);
+    sarraySplitString(sa, subdir, "/");
+    n = sarrayGetCount(sa);
+    dir = genPathname("/tmp", NULL);
+       /* Make sure the tmp directory exists */
+#ifndef _WIN32
+    ret = mkdir(dir, 0777);
+#else
+    attributes = GetFileAttributes(dir);
+    if (attributes == INVALID_FILE_ATTRIBUTES)
+        ret = (CreateDirectory(dir, NULL) ? 0 : 1);
+#endif
+        /* Make all the subdirectories */
+    for (i = 0; i < n; i++) {
+        tmpdir = pathJoin(dir, sarrayGetString(sa, i, L_NOCOPY));
+#ifndef _WIN32
+        ret += mkdir(tmpdir, 0777);
+#else
+        ret += (CreateDirectory(tmpdir, NULL) ? 0 : 1);
+#endif
+        LEPT_FREE(dir);
+        dir = tmpdir;
+    }
+    LEPT_FREE(dir);
+    sarrayDestroy(&sa);
+    return ret;
+}
+
+
+/*!
+ *  lept_rmdir()
+ *
+ *      Input:  subdir (of /tmp or its equivalent on Windows)
+ *      Return: 0 on success, non-zero on failure
+ *
+ *  Notes:
+ *      (1) @subdir is a partial path that can consist of one or more
+ *          directories.
+ *      (2) This removes all files from the specified subdirectory of
+ *          the root temp directory:
+ *            /tmp    (unix)
+ *            <Temp>  (windows)
+ *          and then removes the subdirectory.
+ *      (3) The combination
+ *            lept_rmdir(subdir);
+ *            lept_mkdir(subdir);
+ *          is guaranteed to give you an empty subdirectory.
+ */
+l_int32
+lept_rmdir(const char  *subdir)
+{
+char    *rootdir, *dir, *fname, *fullname;
+l_int32  exists, ret, i, nfiles;
+SARRAY  *sa;
+#ifdef _WIN32
+char    *newpath;
+#endif  /* _WIN32 */
+
+    PROCNAME("lept_rmdir");
+
+    if (!subdir)
+        return ERROR_INT("subdir not defined", procName, 1);
+    if ((strlen(subdir) == 0) || (subdir[0] == '.') || (subdir[0] == '/'))
+        return ERROR_INT("subdir not an actual subdirectory", procName, 1);
+
+        /* Find the temp subdirectory */
+    rootdir = genPathname("/tmp", NULL);
+    dir = appendSubdirs(rootdir, subdir);
+    LEPT_FREE(rootdir);
+    if (!dir)
+        return ERROR_INT("directory name not made", procName, 1);
+    lept_direxists(dir, &exists);
+    if (!exists) {  /* fail silently */
+        LEPT_FREE(dir);
+        return 0;
+    }
+
+        /* List all the files */
+    if ((sa = getFilenamesInDirectory(dir)) == NULL) {
+        L_ERROR("directory %s does not exist!\n", procName, dir);
+        LEPT_FREE(dir);
+        return 1;
+    }
+    nfiles = sarrayGetCount(sa);
+
+    for (i = 0; i < nfiles; i++) {
+        fname = sarrayGetString(sa, i, L_NOCOPY);
+        fullname = genPathname(dir, fname);
+        remove(fullname);
+        LEPT_FREE(fullname);
+    }
+#ifndef _WIN32
+    ret = rmdir(dir);
+#else
+    newpath = genPathname(dir, NULL);
+    remove(newpath);
+    LEPT_FREE(newpath);
+#endif  /* !_WIN32 */
+
+    sarrayDestroy(&sa);
+    LEPT_FREE(dir);
+    return ret;
+}
+
+
+/*!
+ *  lept_direxists()
+ *
+ *      Input:  dir
+ *              &exists (<return> 1 if it exists; 0 otherwise)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) Always use unix pathname separators.
+ *      (2) By calling genPathname(), if the pathname begins with "/tmp"
+ *          this does an automatic directory translation on windows
+ *          to a path in the windows <Temp> directory:
+ *             "/tmp"  ==>  <Temp> (windows)
+ */
+void
+lept_direxists(const char  *dir,
+               l_int32     *pexists)
+{
+char  *realdir;
+
+    if (!pexists) return;
+    *pexists = 0;
+    if (!dir) return;
+    if ((realdir = genPathname(dir, NULL)) == NULL)
+        return;
+
+#ifndef _WIN32
+    {
+    struct stat s;
+    l_int32 err = stat(realdir, &s);
+    if (err != -1 && S_ISDIR(s.st_mode))
+        *pexists = 1;
+    }
+#else  /* _WIN32 */
+    l_uint32  attributes;
+    attributes = GetFileAttributes(realdir);
+    if (attributes != INVALID_FILE_ATTRIBUTES &&
+        (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
+        *pexists = 1;
+    }
+#endif  /* _WIN32 */
+
+    LEPT_FREE(realdir);
+    return;
+}
+
+
+/*!
+ *  lept_rm_match()
+ *
+ *      Input:  subdir (<optional>  If NULL, the removed files are in /tmp)
+ *              substr (<optional> pattern to match in filename)
+ *      Return: 0 on success, non-zero on failure
+ *
+ *  Notes:
+ *      (1) This removes the matched files in /tmp or a subdirectory of /tmp.
+ *          Use NULL for @subdir if the files are in /tmp.
+ *      (2) If @substr == NULL, this removes all files in the directory.
+ *          If @substr == "" (empty), this removes no files.
+ *          If both @subdir == NULL and @substr == NULL, this removes
+ *          all files in /tmp.
+ *      (3) Use unix pathname separators.
+ *      (4) By calling genPathname(), if the pathname begins with "/tmp"
+ *          this does an automatic directory translation on windows
+ *          to a path in the windows <Temp> directory:
+ *             "/tmp"  ==>  <Temp> (windows)
+ *      (5) Error conditions:
+ *            * returns -1 if the directory is not found
+ *            * returns the number of files (> 0) that it was unable to remove.
+ */
+l_int32
+lept_rm_match(const char  *subdir,
+              const char  *substr)
+{
+char    *path, *fname;
+char     tempdir[256];
+l_int32  i, n, ret;
+SARRAY  *sa;
+
+    PROCNAME("lept_rm_match");
+
+    makeTempDirname(tempdir, 256, subdir);
+    if ((sa = getSortedPathnamesInDirectory(tempdir, substr, 0, 0)) == NULL)
+        return ERROR_INT("sa not made", procName, -1);
+    n = sarrayGetCount(sa);
+    if (n == 0) {
+        L_WARNING("no matching files found\n", procName);
+        sarrayDestroy(&sa);
+        return 0;
+    }
+
+    ret = 0;
+    for (i = 0; i < n; i++) {
+        fname = sarrayGetString(sa, i, L_NOCOPY);
+        path = genPathname(fname, NULL);
+        if (lept_rmfile(path) != 0) {
+            L_ERROR("failed to remove %s\n", procName, path);
+            ret++;
+        }
+        LEPT_FREE(path);
+    }
+    sarrayDestroy(&sa);
+    return ret;
+}
+
+
+/*!
+ *  lept_rm()
+ *
+ *      Input:  subdir (<optional> of '/tmp'; can be NULL)
+ *              tail (filename without the directory)
+ *      Return: 0 on success, non-zero on failure
+ *
+ *  Notes:
+ *      (1) By calling genPathname(), this does an automatic directory
+ *          translation on windows to a path in the windows <Temp> directory:
+ *             "/tmp/..."  ==>  <Temp>/... (windows)
+ */
+l_int32
+lept_rm(const char  *subdir,
+        const char  *tail)
+{
+char    *path;
+char     newtemp[256];
+l_int32  ret;
+
+    PROCNAME("lept_rm");
+
+    if (!tail || strlen(tail) == 0)
+        return ERROR_INT("tail undefined or empty", procName, 1);
+
+    if (makeTempDirname(newtemp, 256, subdir))
+        return ERROR_INT("temp dirname not made", procName, 1);
+    path = genPathname(newtemp, tail);
+    ret = lept_rmfile(path);
+    LEPT_FREE(path);
+    return ret;
+}
+
+
+/*!
+ *  TODO: Remove this function ?
+ *
+ *  lept_rmfile()
+ *
+ *      Input:  filepath (full path to file including the directory)
+ *      Return: 0 on success, non-zero on failure
+ *
+ *  Notes:
+ *      (1) This removes the named file.
+ *      (2) Use unix pathname separators.
+ *      (3) Unlike the other lept_* functions in this section, this can remove
+ *          any file -- it is not restricted to files that are in /tmp or a
+ *          subdirectory of it.
+ */
+l_int32
+lept_rmfile(const char  *filepath)
+{
+l_int32  ret;
+
+    PROCNAME("lept_rmfile");
+
+    if (!filepath || strlen(filepath) == 0)
+        return ERROR_INT("filepath undefined or empty", procName, 1);
+
+#ifndef _WIN32
+    ret = remove(filepath);
+#else
+        /* Set attributes to allow deletion of read-only files */
+    SetFileAttributes(filepath, FILE_ATTRIBUTE_NORMAL);
+    ret = DeleteFile(filepath) ? 0 : 1;
+#endif  /* !_WIN32 */
+
+    return ret;
+}
+
+
+/*!
+ *  lept_mv()
+ *
+ *      Input:  srcfile
+ *              newdir (<optional>; can be NULL)
+ *              newtail (<optional>; can be NULL)
+ *              &newpath (<optional return> of actual path; can be NULL)
+ *      Return: 0 on success, non-zero on failure
+ *
+ *  Notes:
+ *      (1) This moves @srcfile to /tmp or to a subdirectory of /tmp.
+ *      (2) @srcfile can either be a full path or relative to the
+ *          current directory.
+ *      (3) @newdir can either specify an existing subdirectory of /tmp
+ *          or can be NULL.  In the latter case, the file will be written
+ *          into /tmp.
+ *      (4) @newtail can either specify a filename tail or, if NULL,
+ *          the filename is taken from src-tail, the tail of @srcfile.
+ *      (5) For debugging, the computed newpath can be returned.  It must
+ *          be freed by the caller.
+ *      (6) Reminders:
+ *          (a) specify files using unix pathnames
+ *          (b) for windows, translates
+ *                 /tmp  ==>  <Temp>
+ *              where <Temp> is the windows temp directory
+ *      (7) Examples:
+ *          * newdir = NULL,    newtail = NULL    ==> /tmp/src-tail
+ *          * newdir = NULL,    newtail = abc     ==> /tmp/abc
+ *          * newdir = def/ghi, newtail = NULL    ==> /tmp/def/ghi/src-tail
+ *          * newdir = def/ghi, newtail = abc     ==> /tmp/def/ghi/abc
+ */
+l_int32
+lept_mv(const char  *srcfile,
+        const char  *newdir,
+        const char  *newtail,
+        char       **pnewpath)
+{
+char    *srcpath, *newpath, *dir, *srctail;
+char     newtemp[256];
+l_int32  ret;
+
+    PROCNAME("lept_mv");
+
+    if (!srcfile)
+        return ERROR_INT("srcfile not defined", procName, 1);
+
+        /* Require output pathname to be in /tmp/ or a subdirectory */
+    if (makeTempDirname(newtemp, 256, newdir) == 1)
+        return ERROR_INT("newdir not NULL or a subdir of /tmp", procName, 1);
+
+        /* Get canonical src pathname */
+    splitPathAtDirectory(srcfile, &dir, &srctail);
+    srcpath = genPathname(dir, srctail);
+    LEPT_FREE(dir);
+
+        /* Generate output pathname */
+    if (!newtail || newtail[0] == '\0')
+        newpath = genPathname(newtemp, srctail);
+    else
+        newpath = genPathname(newtemp, newtail);
+    LEPT_FREE(srctail);
+
+        /* Overwrite any existing file at 'newpath' */
+#ifndef _WIN32
+    ret = fileCopy(srcpath, newpath);
+    if (!ret)
+        remove(srcpath);
+#else
+    ret = MoveFileEx(srcpath, newpath,
+                     MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING) ? 0 : 1;
+#endif
+
+    LEPT_FREE(srcpath);
+    if (pnewpath)
+        *pnewpath = newpath;
+    else
+        LEPT_FREE(newpath);
+    return ret;
+}
+
+
+/*!
+ *  lept_cp()
+ *
+ *      Input:  srcfile
+ *              newdir (<optional>; can be NULL)
+ *              newtail (<optional>; can be NULL)
+ *              &newpath (<optional return> of actual path; can be NULL)
+ *      Return: 0 on success, non-zero on failure
+ *
+ *  Notes:
+ *      (1) This copies @srcfile to /tmp or to a subdirectory of /tmp.
+ *      (2) @srcfile can either be a full path or relative to the
+ *          current directory.
+ *      (3) @newdir can either specify an existing subdirectory of /tmp,
+ *          or can be NULL.  In the latter case, the file will be written
+ *          into /tmp.
+ *      (4) @newtail can either specify a filename tail or, if NULL,
+ *          the filename is taken from src-tail, the tail of @srcfile.
+ *      (5) For debugging, the computed newpath can be returned.  It must
+ *          be freed by the caller.
+ *      (6) Reminders:
+ *          (a) specify files using unix pathnames
+ *          (b) for windows, translates
+ *                 /tmp  ==>  <Temp>
+ *              where <Temp> is the windows temp directory
+ *      (7) Examples:
+ *          * newdir = NULL,    newtail = NULL    ==> /tmp/src-tail
+ *          * newdir = NULL,    newtail = abc     ==> /tmp/abc
+ *          * newdir = def/ghi, newtail = NULL    ==> /tmp/def/ghi/src-tail
+ *          * newdir = def/ghi, newtail = abc     ==> /tmp/def/ghi/abc
+ *
+ */
+l_int32
+lept_cp(const char  *srcfile,
+        const char  *newdir,
+        const char  *newtail,
+        char       **pnewpath)
+{
+char    *srcpath, *newpath, *dir, *srctail;
+char     newtemp[256];
+l_int32  ret;
+
+    PROCNAME("lept_cp");
+
+    if (!srcfile)
+        return ERROR_INT("srcfile not defined", procName, 1);
+
+        /* Require output pathname to be in /tmp or a subdirectory */
+    if (makeTempDirname(newtemp, 256, newdir) == 1)
+        return ERROR_INT("newdir not NULL or a subdir of /tmp", procName, 1);
+
+       /* Get canonical src pathname */
+    splitPathAtDirectory(srcfile, &dir, &srctail);
+    srcpath = genPathname(dir, srctail);
+    LEPT_FREE(dir);
+
+        /* Generate output pathname */
+    if (!newtail || newtail[0] == '\0')
+        newpath = genPathname(newtemp, srctail);
+    else
+        newpath = genPathname(newtemp, newtail);
+    LEPT_FREE(srctail);
+
+        /* Overwrite any existing file at 'newpath' */
+#ifndef _WIN32
+    ret = fileCopy(srcpath, newpath);
+#else
+    ret = CopyFile(srcpath, newpath, FALSE) ? 0 : 1;
+#endif
+
+    LEPT_FREE(srcpath);
+    if (pnewpath)
+        *pnewpath = newpath;
+    else
+        LEPT_FREE(newpath);
+    return ret;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                     General file name operations                   *
+ *--------------------------------------------------------------------*/
+/*!
+ *  splitPathAtDirectory()
+ *
+ *      Input:  pathname  (full path; can be a directory)
+ *              &dir  (<optional return> root directory name of
+ *                     input path, including trailing '/')
+ *              &tail (<optional return> path tail, which is either
+ *                     the file name within the root directory or
+ *                     the last sub-directory in the path)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If you only want the tail, input null for the root directory ptr.
+ *      (2) If you only want the root directory name, input null for the
+ *          tail ptr.
+ *      (3) This function makes decisions based only on the lexical
+ *          structure of the input.  Examples:
+ *            /usr/tmp/abc  -->  dir: /usr/tmp/       tail: abc
+ *            /usr/tmp/     -->  dir: /usr/tmp/       tail: [empty string]
+ *            /usr/tmp      -->  dir: /usr/           tail: tmp
+ *            abc           -->  dir: [empty string]  tail: abc
+ *      (4) The input can have either forward (unix) or backward (win)
+ *          slash separators.  The output has unix separators.
+ *          Note that Win32 pathname functions generally accept both
+ *          slash forms, but the windows command line interpreter
+ *          only accepts backward slashes, because forward slashes are
+ *          used to demarcate switches (vs. dashes in unix).
+ */
+l_int32
+splitPathAtDirectory(const char  *pathname,
+                     char       **pdir,
+                     char       **ptail)
+{
+char  *cpathname, *lastslash;
+
+    PROCNAME("splitPathAtDirectory");
+
+    if (!pdir && !ptail)
+        return ERROR_INT("null input for both strings", procName, 1);
+    if (pdir) *pdir = NULL;
+    if (ptail) *ptail = NULL;
+    if (!pathname)
+        return ERROR_INT("pathname not defined", procName, 1);
+
+    cpathname = stringNew(pathname);
+    convertSepCharsInPath(cpathname, UNIX_PATH_SEPCHAR);
+    lastslash = strrchr(cpathname, '/');
+    if (lastslash) {
+        if (ptail)
+            *ptail = stringNew(lastslash + 1);
+        if (pdir) {
+            *(lastslash + 1) = '\0';
+            *pdir = cpathname;
+        } else {
+            LEPT_FREE(cpathname);
+        }
+    } else {  /* no directory */
+        if (pdir)
+            *pdir = stringNew("");
+        if (ptail)
+            *ptail = cpathname;
+        else
+            LEPT_FREE(cpathname);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  splitPathAtExtension()
+ *
+ *      Input:  pathname (full path; can be a directory)
+ *              &basename (<optional return> pathname not including the
+ *                        last dot and characters after that)
+ *              &extension (<optional return> path extension, which is
+ *                        the last dot and the characters after it.  If
+ *                        there is no extension, it returns the empty string)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) If you only want the extension, input null for the basename ptr.
+ *      (2) If you only want the basename without extension, input null
+ *          for the extension ptr.
+ *      (3) This function makes decisions based only on the lexical
+ *          structure of the input.  Examples:
+ *            /usr/tmp/abc.jpg  -->  basename: /usr/tmp/abc    ext: .jpg
+ *            /usr/tmp/.jpg     -->  basename: /usr/tmp/       ext: .jpg
+ *            /usr/tmp.jpg/     -->  basename: /usr/tmp.jpg/   ext: [empty str]
+ *            ./.jpg            -->  basename: ./              ext: .jpg
+ *      (4) The input can have either forward (unix) or backward (win)
+ *          slash separators.  The output has unix separators.
+ */
+l_int32
+splitPathAtExtension(const char  *pathname,
+                     char       **pbasename,
+                     char       **pextension)
+{
+char  *tail, *dir, *lastdot;
+char   empty[4] = "";
+
+    PROCNAME("splitPathExtension");
+
+    if (!pbasename && !pextension)
+        return ERROR_INT("null input for both strings", procName, 1);
+    if (pbasename) *pbasename = NULL;
+    if (pextension) *pextension = NULL;
+    if (!pathname)
+        return ERROR_INT("pathname not defined", procName, 1);
+
+        /* Split out the directory first */
+    splitPathAtDirectory(pathname, &dir, &tail);
+
+        /* Then look for a "." in the tail part.
+         * This way we ignore all "." in the directory. */
+    if ((lastdot = strrchr(tail, '.'))) {
+        if (pextension)
+            *pextension = stringNew(lastdot);
+        if (pbasename) {
+            *lastdot = '\0';
+            *pbasename = stringJoin(dir, tail);
+        }
+    } else {
+        if (pextension)
+            *pextension = stringNew(empty);
+        if (pbasename)
+            *pbasename = stringNew(pathname);
+    }
+    LEPT_FREE(dir);
+    LEPT_FREE(tail);
+    return 0;
+}
+
+
+/*!
+ *  pathJoin()
+ *
+ *      Input:  dir (<optional> can be null)
+ *              fname (<optional> can be null)
+ *      Return: specially concatenated path, or null on error
+ *
+ *  Notes:
+ *      (1) Use unix-style pathname separators ('/').
+ *      (2) @fname can be the entire path, or part of the path containing
+ *          at least one directory, or a tail without a directory, or null.
+ *      (3) It produces a path that strips multiple slashes to a single
+ *          slash, joins @dir and @fname by a slash, and has no trailing
+ *          slashes (except in the cases where @dir == "/" and
+ *          @fname == NULL, or v.v.).
+ *      (4) If both @dir and @fname are null, produces an empty string.
+ *      (5) Neither @dir nor @fname can begin with '.'.
+ *      (6) The result is not canonicalized or tested for correctness:
+ *          garbage in (e.g., /&%), garbage out.
+ *      (7) Examples:
+ *             //tmp// + //abc/  -->  /tmp/abc
+ *             tmp/ + /abc/      -->  tmp/abc
+ *             tmp/ + abc/       -->  tmp/abc
+ *             /tmp/ + ///       -->  /tmp
+ *             /tmp/ + NULL      -->  /tmp
+ *             // + /abc//       -->  /abc
+ *             // + NULL         -->  /
+ *             NULL + /abc/def/  -->  /abc/def
+ *             NULL + abc//      -->  abc
+ *             NULL + //         -->  /
+ *             NULL + NULL       -->  (empty string)
+ *             "" + ""           -->  (empty string)
+ *             "" + /            -->  /
+ *             ".." + /etc/foo   -->  NULL
+ *             /tmp + ".."       -->  NULL
+ */
+char *
+pathJoin(const char  *dir,
+         const char  *fname)
+{
+char     *slash = (char *)"/";
+char     *str, *dest;
+l_int32   i, n1, n2, emptydir;
+size_t    size;
+SARRAY   *sa1, *sa2;
+L_BYTEA  *ba;
+
+    PROCNAME("pathJoin");
+
+    if (!dir && !fname)
+        return stringNew("");
+    if (dir && dir[0] == '.')
+        return (char *)ERROR_PTR("dir starts with '.'", procName, NULL);
+    if (fname && fname[0] == '.')
+        return (char *)ERROR_PTR("fname starts with '.'", procName, NULL);
+
+    sa1 = sarrayCreate(0);
+    sa2 = sarrayCreate(0);
+    ba = l_byteaCreate(4);
+
+        /* Process @dir */
+    if (dir && strlen(dir) > 0) {
+        if (dir[0] == '/')
+            l_byteaAppendString(ba, slash);
+        sarraySplitString(sa1, dir, "/");  /* removes all slashes */
+        n1 = sarrayGetCount(sa1);
+        for (i = 0; i < n1; i++) {
+            str = sarrayGetString(sa1, i, L_NOCOPY);
+            l_byteaAppendString(ba, str);
+            l_byteaAppendString(ba, slash);
+        }
+    }
+
+        /* Special case to add leading slash: dir NULL or empty string  */
+    emptydir = dir && strlen(dir) == 0;
+    if ((!dir || emptydir) && fname && strlen(fname) > 0 && fname[0] == '/')
+        l_byteaAppendString(ba, slash);
+
+        /* Process @fname */
+    if (fname && strlen(fname) > 0) {
+        sarraySplitString(sa2, fname, "/");
+        n2 = sarrayGetCount(sa2);
+        for (i = 0; i < n2; i++) {
+            str = sarrayGetString(sa2, i, L_NOCOPY);
+            l_byteaAppendString(ba, str);
+            l_byteaAppendString(ba, slash);
+        }
+    }
+
+        /* Remove trailing slash */
+    dest = (char *)l_byteaCopyData(ba, &size);
+    if (size > 1 && dest[size - 1] == '/')
+        dest[size - 1] = '\0';
+
+    sarrayDestroy(&sa1);
+    sarrayDestroy(&sa2);
+    l_byteaDestroy(&ba);
+    return dest;
+}
+
+
+/*!
+ *  appendSubdirs()
+ *
+ *      Input:  basedir
+ *              subdirs
+ *      Return: concatenated full directory path without trailing slash,
+ *              or null on error
+ *
+ *  Notes:
+ *      (1) Use unix pathname separators
+ *      (2) Allocates a new string:  <basedir>/<subdirs>
+ */
+char *
+appendSubdirs(const char  *basedir,
+              const char  *subdirs)
+{
+char   *newdir;
+size_t  len1, len2, len3, len4;
+
+    PROCNAME("appendSubdirs");
+
+    if (!basedir || !subdirs)
+        return (char *)ERROR_PTR("basedir and subdirs not both defined",
+                                 procName, NULL);
+
+    len1 = strlen(basedir);
+    len2 = strlen(subdirs);
+    len3 = len1 + len2 + 6;
+    newdir = (char *)LEPT_CALLOC(len3, 1);
+    strncat(newdir, basedir, len3);  /* add basedir */
+    if (newdir[len1 - 1] != '/')  /* add '/' if necessary */
+        newdir[len1] = '/';
+    if (subdirs[0] == '/')  /* add subdirs, stripping leading '/' */
+        strncat(newdir, subdirs + 1, len3);
+    else
+        strncat(newdir, subdirs, len3);
+    len4 = strlen(newdir);
+    if (newdir[len4 - 1] == '/')  /* strip trailing '/' */
+        newdir[len4 - 1] = '\0';
+
+    return newdir;
+}
+
+
+/*--------------------------------------------------------------------*
+ *                     Special file name operations                   *
+ *--------------------------------------------------------------------*/
+/*!
+ *  convertSepCharsInPath()
+ *
+ *      Input:  path
+ *              type (UNIX_PATH_SEPCHAR, WIN_PATH_SEPCHAR)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) In-place conversion.
+ *      (2) Type is the resulting type:
+ *            * UNIX_PATH_SEPCHAR:  '\\' ==> '/'
+ *            * WIN_PATH_SEPCHAR:   '/' ==> '\\'
+ *      (3) Virtually all path operations in leptonica use unix separators.
+ */
+l_int32
+convertSepCharsInPath(char    *path,
+                      l_int32  type)
+{
+l_int32  i;
+size_t   len;
+
+    PROCNAME("convertSepCharsInPath");
+    if (!path)
+        return ERROR_INT("path not defined", procName, 1);
+    if (type != UNIX_PATH_SEPCHAR && type != WIN_PATH_SEPCHAR)
+        return ERROR_INT("invalid type", procName, 1);
+
+    len = strlen(path);
+    if (type == UNIX_PATH_SEPCHAR) {
+        for (i = 0; i < len; i++) {
+            if (path[i] == '\\')
+                path[i] = '/';
+        }
+    } else {  /* WIN_PATH_SEPCHAR */
+        for (i = 0; i < len; i++) {
+            if (path[i] == '/')
+                path[i] = '\\';
+        }
+    }
+    return 0;
+}
+
+
+/*!
+ *  genPathname()
+ *
+ *      Input:  dir (<optional> directory or full path name, with or without
+ *                   trailing '/')
+ *              fname (<optional> file name within a directory)
+ *      Return: pathname (either a directory or full path), or null on error
+ *
+ *  Notes:
+ *      (1) This function generates actual paths in the following ways:
+ *            * from two sub-parts (e.g., a directory and a file name).
+ *            * from a single path full path, placed in @dir, with
+ *              @fname == NULL.
+ *            * from the name of a file in the local directory placed in
+ *              @fname, with @dir == NULL.
+ *            * if in a "/tmp" directory and on windows, the windows
+ *              temp directory is used.
+ *      (2) If the root of @dir is '/tmp', this does a name translation:
+ *             "/tmp"  ==>  <Temp> (windows)
+ *          where <Temp> is the windows temp directory.
+ *      (3) There are four cases for the input:
+ *          (a) @dir is a directory and @fname is defined: result is a full path
+ *          (b) @dir is a directory and @fname is null: result is a directory
+ *          (c) @dir is a full path and @fname is null: result is a full path
+ *          (d) @dir is null or an empty string: start in the current dir;
+ *              result is a full path
+ *      (4) In all cases, the resulting pathname is not terminated with a slash
+ *      (5) The caller is responsible for freeing the returned pathname.
+ */
+char *
+genPathname(const char  *dir,
+            const char  *fname)
+{
+char    *cdir, *pathout;
+l_int32  dirlen, namelen, size;
+
+    PROCNAME("genPathname");
+
+    if (!dir && !fname)
+        return (char *)ERROR_PTR("no input", procName, NULL);
+
+        /* Handle the case where we start from the current directory */
+    if (!dir || dir[0] == '\0') {
+        if ((cdir = getcwd(NULL, 0)) == NULL)
+            return (char *)ERROR_PTR("no current dir found", procName, NULL);
+    } else {
+        cdir = stringNew(dir);
+    }
+
+        /* Convert to unix path separators, and remove the trailing
+         * slash in the directory, except when dir == "/"  */
+    convertSepCharsInPath(cdir, UNIX_PATH_SEPCHAR);
+    dirlen = strlen(cdir);
+    if (cdir[dirlen - 1] == '/' && dirlen != 1) {
+        cdir[dirlen - 1] = '\0';
+        dirlen--;
+    }
+
+    namelen = (fname) ? strlen(fname) : 0;
+    size = dirlen + namelen + 256;
+    if ((pathout = (char *)LEPT_CALLOC(size, sizeof(char))) == NULL)
+        return (char *)ERROR_PTR("pathout not made", procName, NULL);
+
+        /* First handle @dir (which may be a full pathname) */
+    if (strncmp(cdir, "/tmp", 4) != 0) {  /* not in /tmp; OK as is */
+        stringCopy(pathout, cdir, dirlen);
+    } else {  /* in /tmp */
+            /* Start with the temp dir */
+#ifdef _WIN32
+        char tmpdir[MAX_PATH];
+        GetTempPath(sizeof(tmpdir), tmpdir);  /* get the windows temp dir */
+#else  /* unix */
+        const char *tmpdir = getenv("TMPDIR");
+        if (tmpdir == NULL) tmpdir = "/tmp";
+#endif  /* _WIN32 */
+        stringCopy(pathout, tmpdir, strlen(tmpdir));
+
+            /* Add the rest of cdir */
+        if (dirlen > 4)
+            stringCat(pathout, size, cdir + 4);
+    }
+
+       /* Now handle @fname */
+    if (fname && strlen(fname) > 0) {
+        dirlen = strlen(pathout);
+        pathout[dirlen] = '/';
+        strncat(pathout, fname, namelen);
+    }
+
+    LEPT_FREE(cdir);
+    return pathout;
+}
+
+
+/*!
+ *  makeTempDirname()
+ *
+ *      Input:  result (preallocated on stack or heap and passed in)
+ *              nbytes (size of @result array, in bytes)
+ *              subdirs (<optional>; can be NULL or an empty string)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This generates the directory path for output temp files,
+ *          written into @result with unix separators.
+ *      (2) Caller allocates @result, large enough to hold the path,
+ *          which is:
+ *            /tmp/@subdirs       (unix)
+ *            <Temp>/@subdirs     (windows)
+ *          where <Temp> is a path on windows determined by GenTempPath().
+ *      (3) Usage example:
+ *           char  result[256];
+ *           makeTempDirname(result, 256, "lept/golden");
+ */
+l_int32
+makeTempDirname(char        *result,
+                size_t       nbytes,
+                const char  *subdir)
+{
+char    *dir, *path;
+l_int32  ret = 0;
+size_t   pathlen;
+
+    PROCNAME("makeTempDirname");
+
+    if (!result)
+        return ERROR_INT("result not defined", procName, 1);
+    if (subdir && ((subdir[0] == '.') || (subdir[0] == '/')))
+        return ERROR_INT("subdir not an actual subdirectory", procName, 1);
+
+    memset(result, 0, nbytes);
+    dir = pathJoin("/tmp", subdir);
+    path = genPathname(dir, NULL);
+    pathlen = strlen(path);
+    if (pathlen < nbytes - 1) {
+        strncpy(result, path, pathlen);
+    } else {
+        L_ERROR("result array too small for path\n", procName);
+        ret = 1;
+    }
+
+    LEPT_FREE(dir);
+    LEPT_FREE(path);
+    return ret;
+}
+
+
+/*!
+ *  modifyTrailingSlash()
+ *
+ *      Input:  path (preallocated on stack or heap and passed in)
+ *              nbytes (size of @path array, in bytes)
+ *              flag (L_ADD_TRAIL_SLASH or L_REMOVE_TRAIL_SLASH)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This carries out the requested action if necessary.
+ */
+l_int32
+modifyTrailingSlash(char    *path,
+                    size_t   nbytes,
+                    l_int32  flag)
+{
+char    lastchar;
+size_t  len;
+
+    PROCNAME("modifyTrailingSlash");
+
+    if (!path)
+        return ERROR_INT("path not defined", procName, 1);
+    if (flag != L_ADD_TRAIL_SLASH && flag != L_REMOVE_TRAIL_SLASH)
+        return ERROR_INT("invalid flag", procName, 1);
+
+    len = strlen(path);
+    lastchar = path[len - 1];
+    if (flag == L_ADD_TRAIL_SLASH && lastchar != '/' && len < nbytes - 2) {
+        path[len] = '/';
+        path[len + 1] = '\0';
+    } else if (flag == L_REMOVE_TRAIL_SLASH && lastchar == '/') {
+        path[len - 1] = '\0';
+    }
+    return 0;
+}
+
+
+/*!
+ *  genTempFilename()
+ *
+ *      Input:  dir (directory name; use '.' for local dir;
+ *                   no trailing '/' and @dir == "/" is invalid)
+ *              tail (<optional>  tailname, including extension if any;
+ *                    can be null or empty but can't contain '/')
+ *              usetime (1 to include current time in microseconds in
+ *                       the filename; 0 to omit.
+ *              usepid (1 to include pid in filename; 0 to omit.
+ *      Return: temp filename, or null on error
+ *
+ *  Notes:
+ *      (1) This makes a filename that is as unique as desired, and which
+ *          can optionally include both the time and pid in the name.
+ *      (2) Use unix-style pathname separators ('/').
+ *      (3) Specifying the root directory (@dir == "/") is invalid.
+ *      (4) Specifying a @tail containing '/' is invalid.
+ *      (5) The most general form (@usetime = @usepid = 1) is:
+ *              <dir>/<usec>_<pid>_<tail>
+ *          When @usetime = 1, @usepid = 0, the output filename is:
+ *              <dir>/<usec>_<tail>
+ *          When @usepid = 0, @usepid = 1, the output filename is:
+ *              <dir>/<pid>_<tail>
+ *          When @usetime = @usepid = 0, the output filename is:
+ *              <dir>/<tail>
+ *          Note: It is not valid to have @tail = null or empty and have
+ *          both @usetime = @usepid = 0.  That is, there must be
+ *          some non-empty tail name.
+ *      (6) N.B. The caller is responsible for freeing the returned filename.
+ *          For windows, to avoid C-runtime boundary crossing problems
+ *          when using DLLs, you must use lept_free() to free the name.
+ *      (7) When @dir is /tmp or a subdirectory of /tmp, genPathname()
+ *          does a name translation for '/tmp':
+ *            ==> /tmp              (unix)  [default]
+ *            ==> /tmp/leptonica    (unix)  [if ADD_LEPTONICA_SUBDIR == 1]
+ *            ==> <Temp>/leptonica  (windows)
+ *          where <Temp> is a path on windows determined by GenTempPath().
+ *      (8) Set @usetime = @usepid = 1 when
+ *          (a) more than one process is writing and reading temp files, or
+ *          (b) multiple threads from a single process call this function, or
+ *          (c) there is the possibility of an attack where the intruder
+ *              is logged onto the server and might try to guess filenames.
+ */
+char *
+genTempFilename(const char  *dir,
+                const char  *tail,
+                l_int32      usetime,
+                l_int32      usepid)
+{
+char     buf[256];
+char    *newpath;
+l_int32  i, buflen, usec, pid, emptytail;
+
+    PROCNAME("genTempFilename");
+
+    if (!dir)
+        return (char *)ERROR_PTR("dir not defined", procName, NULL);
+    if (dir && strlen(dir) == 1 && dir[0] == '/')
+        return (char *)ERROR_PTR("dir == '/' not permitted", procName, NULL);
+    if (tail && strlen(tail) > 0 && stringFindSubstr(tail, "/", NULL))
+        return (char *)ERROR_PTR("tail can't contain '/'", procName, NULL);
+    emptytail = tail && (strlen(tail) == 0);
+    if (!usetime && !usepid && (!tail || emptytail))
+        return (char *)ERROR_PTR("name can't be a directory", procName, NULL);
+
+    if (usepid) pid = getpid();
+    buflen = sizeof(buf);
+    for (i = 0; i < buflen; i++)
+        buf[i] = 0;
+    l_getCurrentTime(NULL, &usec);
+
+    newpath = genPathname(dir, NULL);
+    if (usetime && usepid)
+        snprintf(buf, buflen, "%s/%d_%d_", newpath, usec, pid);
+    else if (usetime)
+        snprintf(buf, buflen, "%s/%d_", newpath, usec);
+    else if (usepid)
+        snprintf(buf, buflen, "%s/%d_", newpath, pid);
+    else
+        snprintf(buf, buflen, "%s/", newpath);
+    LEPT_FREE(newpath);
+
+    return stringJoin(buf, tail);
+}
+
+
+/*!
+ *  extractNumberFromFilename()
+ *
+ *      Input:  fname
+ *              numpre (number of characters before the digits to be found)
+ *              numpost (number of characters after the digits to be found)
+ *      Return: num (number embedded in the filename); -1 on error or if
+ *                   not found
+ *
+ *  Notes:
+ *      (1) The number is to be found in the basename, which is the
+ *          filename without either the directory or the last extension.
+ *      (2) When a number is found, it is non-negative.  If no number
+ *          is found, this returns -1, without an error message.  The
+ *          caller needs to check.
+ */
+l_int32
+extractNumberFromFilename(const char  *fname,
+                          l_int32      numpre,
+                          l_int32      numpost)
+{
+char    *tail, *basename;
+l_int32  len, nret, num;
+
+    PROCNAME("extractNumberFromFilename");
+
+    if (!fname)
+        return ERROR_INT("fname not defined", procName, -1);
+
+    splitPathAtDirectory(fname, NULL, &tail);
+    splitPathAtExtension(tail, &basename, NULL);
+    LEPT_FREE(tail);
+
+    len = strlen(basename);
+    if (numpre + numpost > len - 1) {
+        LEPT_FREE(basename);
+        return ERROR_INT("numpre + numpost too big", procName, -1);
+    }
+
+    basename[len - numpost] = '\0';
+    nret = sscanf(basename + numpre, "%d", &num);
+    LEPT_FREE(basename);
+
+    if (nret == 1)
+        return num;
+    else
+        return -1;  /* not found */
+}
+
+
+/*---------------------------------------------------------------------*
+ *                       File corruption operations                    *
+ *---------------------------------------------------------------------*/
+/*!
+ *  fileCorruptByDeletion()
+ *
+ *      Input:  filein
+ *              loc (fractional location of start of deletion)
+ *              size (fractional size of deletion)
+ *              fileout (corrupted file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) @loc and @size are expressed as a fraction of the file size.
+ *      (2) This makes a copy of the data in @filein, where bytes in the
+ *          specified region have deleted.
+ *      (3) If (@loc + @size) >= 1.0, this deletes from the position
+ *          represented by @loc to the end of the file.
+ *      (4) It is useful for testing robustness of I/O wrappers when the
+ *          data is corrupted, by simulating data corruption by deletion.
+ */
+l_int32
+fileCorruptByDeletion(const char  *filein,
+                      l_float32    loc,
+                      l_float32    size,
+                      const char  *fileout)
+{
+l_int32   i, locb, sizeb, rembytes;
+size_t    inbytes, outbytes;
+l_uint8  *datain, *dataout;
+
+    PROCNAME("fileCorruptByDeletion");
+
+    if (!filein || !fileout)
+        return ERROR_INT("filein and fileout not both specified", procName, 1);
+    if (loc < 0.0 || loc >= 1.0)
+        return ERROR_INT("loc must be in [0.0 ... 1.0)", procName, 1);
+    if (size <= 0.0)
+        return ERROR_INT("size must be > 0.0", procName, 1);
+    if (loc + size > 1.0)
+        size = 1.0 - loc;
+
+    datain = l_binaryRead(filein, &inbytes);
+    locb = (l_int32)(loc * inbytes + 0.5);
+    locb = L_MIN(locb, inbytes - 1);
+    sizeb = (l_int32)(size * inbytes + 0.5);
+    sizeb = L_MAX(1, sizeb);
+    sizeb = L_MIN(sizeb, inbytes - locb);  /* >= 1 */
+    L_INFO("Removed %d bytes at location %d\n", procName, sizeb, locb);
+    rembytes = inbytes - locb - sizeb;  /* >= 0; to be copied, after excision */
+
+    outbytes = inbytes - sizeb;
+    dataout = (l_uint8 *)LEPT_CALLOC(outbytes, 1);
+    for (i = 0; i < locb; i++)
+        dataout[i] = datain[i];
+    for (i = 0; i < rembytes; i++)
+        dataout[locb + i] = datain[locb + sizeb + i];
+    l_binaryWrite(fileout, "w", dataout, outbytes);
+
+    LEPT_FREE(datain);
+    LEPT_FREE(dataout);
+    return 0;
+}
+
+
+/*!
+ *  fileCorruptByMutation()
+ *
+ *      Input:  filein
+ *              loc (fractional location of start of randomization)
+ *              size (fractional size of randomization)
+ *              fileout (corrupted file)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) @loc and @size are expressed as a fraction of the file size.
+ *      (2) This makes a copy of the data in @filein, where bytes in the
+ *          specified region have been replaced by random data.
+ *      (3) If (@loc + @size) >= 1.0, this modifies data from the position
+ *          represented by @loc to the end of the file.
+ *      (4) It is useful for testing robustness of I/O wrappers when the
+ *          data is corrupted, by simulating data corruption.
+ */
+l_int32
+fileCorruptByMutation(const char  *filein,
+                      l_float32    loc,
+                      l_float32    size,
+                      const char  *fileout)
+{
+l_int32   i, locb, sizeb;
+size_t    bytes;
+l_uint8  *data;
+
+    PROCNAME("fileCorruptByMutation");
+
+    if (!filein || !fileout)
+        return ERROR_INT("filein and fileout not both specified", procName, 1);
+    if (loc < 0.0 || loc >= 1.0)
+        return ERROR_INT("loc must be in [0.0 ... 1.0)", procName, 1);
+    if (size <= 0.0)
+        return ERROR_INT("size must be > 0.0", procName, 1);
+    if (loc + size > 1.0)
+        size = 1.0 - loc;
+
+    data = l_binaryRead(filein, &bytes);
+    locb = (l_int32)(loc * bytes + 0.5);
+    locb = L_MIN(locb, bytes - 1);
+    sizeb = (l_int32)(size * bytes + 0.5);
+    sizeb = L_MAX(1, sizeb);
+    sizeb = L_MIN(sizeb, bytes - locb);  /* >= 1 */
+    L_INFO("Randomizing %d bytes at location %d\n", procName, sizeb, locb);
+
+        /* Make an array of random bytes and do the substitution */
+    for (i = 0; i < sizeb; i++) {
+        data[locb + i] =
+            (l_uint8)(255.9 * ((l_float64)rand() / (l_float64)RAND_MAX));
+    }
+
+    l_binaryWrite(fileout, "w", data, bytes);
+    LEPT_FREE(data);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                Generate random integer in given range               *
+ *---------------------------------------------------------------------*/
+/*!
+ *  genRandomIntegerInRange()
+ *
+ *      Input:  range (size of range; must be >= 2)
+ *              seed (use 0 to skip; otherwise call srand)
+ *              val (<return> random integer in range {0 ... range-1}
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) For example, to choose a rand integer between 0 and 99,
+ *          use @range = 100.
+ */
+l_int32
+genRandomIntegerInRange(l_int32   range,
+                        l_int32   seed,
+                        l_int32  *pval)
+{
+    PROCNAME("genRandomIntegerInRange");
+
+    if (!pval)
+        return ERROR_INT("&val not defined", procName, 1);
+    *pval = 0;
+    if (range < 2)
+        return ERROR_INT("range must be >= 2", procName, 1);
+
+    if (seed > 0) srand(seed);
+    *pval = (l_int32)((l_float64)range *
+                       ((l_float64)rand() / (l_float64)RAND_MAX));
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Simple math function                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  lept_roundftoi()
+ *
+ *      Input:  fval
+ *      Return: value rounded to int
+ *
+ *  Notes:
+ *      (1) For fval >= 0, fval --> round(fval) == floor(fval + 0.5)
+ *          For fval < 0, fval --> -round(-fval))
+ *          This is symmetric around 0.
+ *          e.g., for fval in (-0.5 ... 0.5), fval --> 0
+ */
+l_int32
+lept_roundftoi(l_float32  fval)
+{
+    return (fval >= 0.0) ? (l_int32)(fval + 0.5) : (l_int32)(fval - 0.5);
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        64-bit hash functions                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  l_hashStringToUint64()
+ *
+ *      Input:  str
+ *              &hash (<return>)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The intent of the hash is to avoid collisions by mapping
+ *          the string as randomly as possible into 64 bits.
+ *      (2) To the extent that the hashes are random, the probability of
+ *          a collision can be approximated by the square of the number
+ *          of strings divided by 2^64.  For 1 million strings, the
+ *          collision probability is about 1 in 16 million.
+ *      (3) I expect non-randomness of the distribution to be most evident
+ *          for small text strings.  This hash function has been tested
+ *          for all 5-character text strings composed of 26 letters,
+ *          of which there are 26^5 = 12356630.  There are no hash
+ *          collisions for this set.
+ */
+l_int32
+l_hashStringToUint64(const char  *str,
+                     l_uint64    *phash)
+{
+l_uint64  hash, mulp;
+
+    PROCNAME("l_hashStringToUint64");
+
+    if (phash) *phash = 0;
+    if (!str || (str[0] == '\0'))
+        return ERROR_INT("str not defined or empty", procName, 1);
+    if (!phash)
+        return ERROR_INT("&hash not defined", procName, 1);
+
+    mulp = 26544357894361247;  /* prime, about 1/700 of the max uint64 */
+    hash = 104395301;
+    while (*str) {
+        hash += (*str++ * mulp) ^ (hash >> 7);   /* shift [1...23] are ok */
+    }
+    *phash = hash ^ (hash << 37);
+    return 0;
+}
+
+
+/*!
+ *  l_hashPtToUint64()
+ *
+ *      Input:  x, y
+ *              &hash (<return>)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) I just made up a hash function and fiddled with it to get
+ *          decent coverage over the 2^64 values.  There are no collisions
+ *          for any of 100 million points with x and y up to 10000.
+ */
+l_int32
+l_hashPtToUint64(l_int32    x,
+                 l_int32    y,
+                 l_uint64  *phash)
+{
+l_uint64  hash, mulp;
+
+    PROCNAME("l_hashPtToUint64");
+
+    if (!phash)
+        return ERROR_INT("&hash not defined", procName, 1);
+    *phash = 0;
+
+    mulp = 26544357894361;
+    hash = 104395301;
+    hash += (x * mulp) ^ (hash >> 5);
+    hash ^= (hash << 7);
+    hash += (y * mulp) ^ (hash >> 7);
+    *phash = hash ^ (hash << 11);
+    return 0;
+}
+
+
+/*!
+ *  l_hashPtToUint64Fast()
+ *
+ *      Input:  nbuckets
+ *              x, y
+ *              &hash (<return>)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a simple, fast hash that is used with the dna hash map,
+ *          which takes the mod with a prime number of buckets.  The
+ *          number of buckets is selected so that collisions occur, aiming
+ *          for about 20 results in each bucket.  The design goal is
+ *          that the hash is fast (mult/add) and approximately the same
+ *          number of points are hashed to each bucket.
+ */
+l_int32
+l_hashPtToUint64Fast(l_int32    nbuckets,
+                     l_int32    x,
+                     l_int32    y,
+                     l_uint64  *phash)
+{
+    PROCNAME("l_hashPtToUint64Fast");
+
+    if (!phash)
+        return ERROR_INT("&hash not defined", procName, 1);
+    *phash = (l_uint64)((21.732491 * nbuckets) * x + y);
+    return 0;
+}
+
+
+/*!
+ *  l_hashFloat64ToUint64()
+ *
+ *      Input:  nbuckets
+ *              val
+ *              &hash (<return>)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Simple, fast hash for using dnaHash with 64-bit data
+ *          (e.g., sets and histograms).
+ *      (2) The resulting hash is called a "key" in a lookup
+ *          operation.  The bucket for @val in a dnaHash is simply
+ *          found by taking the mod of the hash with the number of
+ *          buckets (which is prime).  What gets stored in the
+ *          dna in that bucket could depend on use, but for the most
+ *          flexibility, we store an index into the associated dna.
+ *          This is all that is required for generating either a hash set
+ *          or a histogram (an example of a hash map).
+ *      (3) For example, to generate a histogram, the histogram dna,
+ *          a histogram of unique values aligned with the histogram dna,
+ *          and a dnahash hashmap are built.  See l_dnaHashHistoFromDna().
+ */
+l_int32
+l_hashFloat64ToUint64(l_int32    nbuckets,
+                      l_float64  val,
+                      l_uint64  *phash)
+{
+    PROCNAME("l_hashFloatToUint64");
+
+    if (!phash)
+        return ERROR_INT("&hash not defined", procName, 1);
+    *phash = (l_uint64)((21.732491 * nbuckets) * val);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                           Prime finders                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  findNextLargerPrime()
+ *
+ *      Input:  start
+ *              &prime (<return> first prime larger than @start)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+findNextLargerPrime(l_int32    start,
+                    l_uint32  *pprime)
+{
+l_int32  i, is_prime;
+
+    PROCNAME("findNextLargerPrime");
+
+    if (!pprime)
+        return ERROR_INT("&prime not defined", procName, 1);
+    *pprime = 0;
+    if (start <= 0)
+        return ERROR_INT("start must be > 0", procName, 1);
+
+    for (i = start + 1; ; i++) {
+        lept_isPrime(i, &is_prime, NULL);
+        if (is_prime) {
+            *pprime = i;
+            return 0;
+        }
+    }
+
+    return ERROR_INT("prime not found!", procName, 1);
+}
+
+
+/*!
+ *  lept_isPrime()
+ *
+ *      Input:  n (64-bit unsigned)
+ *              &is_prime (<return> 1 if prime, 0 otherwise)
+ *              &factor (<optional return> smallest divisor,
+ *                       or 0 on error or if prime)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+lept_isPrime(l_uint64   n,
+             l_int32   *pis_prime,
+             l_uint32  *pfactor)
+{
+l_uint32  div;
+l_uint64  limit, ratio;
+
+    PROCNAME("lept_isPrime");
+
+    if (pis_prime) *pis_prime = 0;
+    if (pfactor) *pfactor = 0;
+    if (!pis_prime)
+        return ERROR_INT("&is_prime not defined", procName, 1);
+    if (n <= 0)
+        return ERROR_INT("n must be > 0", procName, 1);
+
+    if (n % 2 == 0) {
+        if (pfactor) *pfactor = 2;
+        return 0;
+    }
+
+    limit = sqrt(n);
+    for (div = 3; div < limit; div += 2) {
+       ratio = n / div;
+       if (ratio * div == n) {
+           if (pfactor) *pfactor = div;
+           return 0;
+       }
+    }
+
+    *pis_prime = 1;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Gray code conversion                        *
+ *---------------------------------------------------------------------*/
+/*!
+ *  convertBinaryToGrayCode()
+ *
+ *      Input:  val
+ *      Return: gray code value
+ *
+ *  Notes:
+ *      (1) Gray code values corresponding to integers differ by
+ *          only one bit transition between successive integers.
+ */
+l_uint32
+convertBinaryToGrayCode(l_uint32 val)
+{
+    return (val >> 1) ^ val;
+}
+
+
+/*!
+ *  convertGrayCodeToBinary()
+ *
+ *      Input:  gray code value
+ *      Return: binary value
+ */
+l_uint32
+convertGrayCodeToBinary(l_uint32 val)
+{
+l_uint32  shift;
+
+    for (shift = 1; shift < 32; shift <<= 1)
+        val ^= val >> shift;
+    return val;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                       Leptonica version number                      *
+ *---------------------------------------------------------------------*/
+/*!
+ *  getLeptonicaVersion()
+ *
+ *      Return: string of version number (e.g., 'leptonica-1.68')
+ *
+ *  Notes:
+ *      (1) The caller has responsibility to free the memory.
+ */
+char *
+getLeptonicaVersion()
+{
+    char *version = (char *)LEPT_CALLOC(100, sizeof(char));
+
+#ifdef _MSC_VER
+  #ifdef _USRDLL
+    char dllStr[] = "DLL";
+  #else
+    char dllStr[] = "LIB";
+  #endif
+  #ifdef _DEBUG
+    char debugStr[] = "Debug";
+  #else
+    char debugStr[] = "Release";
+  #endif
+  #ifdef _M_IX86
+    char bitStr[] = " x86";
+  #elif _M_X64
+    char bitStr[] = " x64";
+  #else
+    char bitStr[] = "";
+  #endif
+    snprintf(version, 100, "leptonica-%d.%d (%s, %s) [MSC v.%d %s %s%s]",
+             LIBLEPT_MAJOR_VERSION, LIBLEPT_MINOR_VERSION,
+             __DATE__, __TIME__, _MSC_VER, dllStr, debugStr, bitStr);
+
+#else
+
+    snprintf(version, 100, "leptonica-%d.%d", LIBLEPT_MAJOR_VERSION,
+             LIBLEPT_MINOR_VERSION);
+
+#endif   /* _MSC_VER */
+    return version;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                           Timing procs                              *
+ *---------------------------------------------------------------------*/
+#ifndef _WIN32
+
+#include <sys/time.h>
+#include <sys/resource.h>
+
+static struct rusage rusage_before;
+static struct rusage rusage_after;
+
+/*!
+ *  startTimer(), stopTimer()
+ *
+ *  Notes:
+ *      (1) These measure the cpu time elapsed between the two calls:
+ *            startTimer();
+ *            ....
+ *            fprintf(stderr, "Elapsed time = %7.3f sec\n", stopTimer());
+ */
+void
+startTimer(void)
+{
+    getrusage(RUSAGE_SELF, &rusage_before);
+}
+
+l_float32
+stopTimer(void)
+{
+l_int32  tsec, tusec;
+
+    getrusage(RUSAGE_SELF, &rusage_after);
+
+    tsec = rusage_after.ru_utime.tv_sec - rusage_before.ru_utime.tv_sec;
+    tusec = rusage_after.ru_utime.tv_usec - rusage_before.ru_utime.tv_usec;
+    return (tsec + ((l_float32)tusec) / 1000000.0);
+}
+
+
+/*!
+ *  startTimerNested(), stopTimerNested()
+ *
+ *  Example of usage:
+ *
+ *      L_TIMER  t1 = startTimerNested();
+ *      ....
+ *      L_TIMER  t2 = startTimerNested();
+ *      ....
+ *      fprintf(stderr, "Elapsed time 2 = %7.3f sec\n", stopTimerNested(t2));
+ *      ....
+ *      fprintf(stderr, "Elapsed time 1 = %7.3f sec\n", stopTimerNested(t1));
+ */
+L_TIMER
+startTimerNested(void)
+{
+struct rusage  *rusage_start;
+
+    rusage_start = (struct rusage *)LEPT_CALLOC(1, sizeof(struct rusage));
+    getrusage(RUSAGE_SELF, rusage_start);
+    return rusage_start;
+}
+
+l_float32
+stopTimerNested(L_TIMER  rusage_start)
+{
+l_int32        tsec, tusec;
+struct rusage  rusage_stop;
+
+    getrusage(RUSAGE_SELF, &rusage_stop);
+
+    tsec = rusage_stop.ru_utime.tv_sec -
+           ((struct rusage *)rusage_start)->ru_utime.tv_sec;
+    tusec = rusage_stop.ru_utime.tv_usec -
+           ((struct rusage *)rusage_start)->ru_utime.tv_usec;
+    LEPT_FREE(rusage_start);
+    return (tsec + ((l_float32)tusec) / 1000000.0);
+}
+
+
+/*!
+ *  l_getCurrentTime()
+ *
+ *      Input:  &sec (<optional return> in seconds since birth of Unix)
+ *              &usec (<optional return> in microseconds since birth of Unix)
+ *      Return: void
+ */
+void
+l_getCurrentTime(l_int32  *sec,
+                 l_int32  *usec)
+{
+struct timeval tv;
+
+    gettimeofday(&tv, NULL);
+    if (sec) *sec = (l_int32)tv.tv_sec;
+    if (usec) *usec = (l_int32)tv.tv_usec;
+    return;
+}
+
+
+#else   /* _WIN32 : resource.h not implemented under Windows */
+
+    /* Note: if division by 10^7 seems strange, the time is expressed
+     * as the number of 100-nanosecond intervals that have elapsed
+     * since 12:00 A.M. January 1, 1601.  */
+
+static ULARGE_INTEGER utime_before;
+static ULARGE_INTEGER utime_after;
+
+void
+startTimer(void)
+{
+HANDLE    this_process;
+FILETIME  start, stop, kernel, user;
+
+    this_process = GetCurrentProcess();
+
+    GetProcessTimes(this_process, &start, &stop, &kernel, &user);
+
+    utime_before.LowPart  = user.dwLowDateTime;
+    utime_before.HighPart = user.dwHighDateTime;
+}
+
+l_float32
+stopTimer(void)
+{
+HANDLE     this_process;
+FILETIME   start, stop, kernel, user;
+ULONGLONG  hnsec;  /* in units of hecto-nanosecond (100 ns) intervals */
+
+    this_process = GetCurrentProcess();
+
+    GetProcessTimes(this_process, &start, &stop, &kernel, &user);
+
+    utime_after.LowPart  = user.dwLowDateTime;
+    utime_after.HighPart = user.dwHighDateTime;
+    hnsec = utime_after.QuadPart - utime_before.QuadPart;
+    return (l_float32)(signed)hnsec / 10000000.0;
+}
+
+L_TIMER
+startTimerNested(void)
+{
+HANDLE           this_process;
+FILETIME         start, stop, kernel, user;
+ULARGE_INTEGER  *utime_start;
+
+    this_process = GetCurrentProcess();
+
+    GetProcessTimes (this_process, &start, &stop, &kernel, &user);
+
+    utime_start = (ULARGE_INTEGER *)LEPT_CALLOC(1, sizeof(ULARGE_INTEGER));
+    utime_start->LowPart  = user.dwLowDateTime;
+    utime_start->HighPart = user.dwHighDateTime;
+    return utime_start;
+}
+
+l_float32
+stopTimerNested(L_TIMER  utime_start)
+{
+HANDLE          this_process;
+FILETIME        start, stop, kernel, user;
+ULARGE_INTEGER  utime_stop;
+ULONGLONG       hnsec;  /* in units of 100 ns intervals */
+
+    this_process = GetCurrentProcess ();
+
+    GetProcessTimes (this_process, &start, &stop, &kernel, &user);
+
+    utime_stop.LowPart  = user.dwLowDateTime;
+    utime_stop.HighPart = user.dwHighDateTime;
+    hnsec = utime_stop.QuadPart - ((ULARGE_INTEGER *)utime_start)->QuadPart;
+    LEPT_FREE(utime_start);
+    return (l_float32)(signed)hnsec / 10000000.0;
+}
+
+void
+l_getCurrentTime(l_int32  *sec,
+                 l_int32  *usec)
+{
+ULARGE_INTEGER  utime, birthunix;
+FILETIME        systemtime;
+LONGLONG        birthunixhnsec = 116444736000000000;  /*in units of 100 ns */
+LONGLONG        usecs;
+
+    GetSystemTimeAsFileTime(&systemtime);
+    utime.LowPart  = systemtime.dwLowDateTime;
+    utime.HighPart = systemtime.dwHighDateTime;
+
+    birthunix.LowPart = (DWORD) birthunixhnsec;
+    birthunix.HighPart = birthunixhnsec >> 32;
+
+    usecs = (LONGLONG) ((utime.QuadPart - birthunix.QuadPart) / 10);
+
+    if (sec) *sec = (l_int32) (usecs / 1000000);
+    if (usec) *usec = (l_int32) (usecs % 1000000);
+    return;
+}
+
+#endif
+
+
+/*!
+ *  startWallTimer()
+ *      Input:  void
+ *      Return: walltimer-ptr
+ *
+ *  stopWallTimer()
+ *      Input:  &walltimer-ptr
+ *      Return: time (wall time elapsed in seconds)
+ *
+ *  Notes:
+ *      (1) These measure the wall clock time  elapsed between the two calls:
+ *            L_WALLTIMER *timer = startWallTimer();
+ *            ....
+ *            fprintf(stderr, "Elapsed time = %f sec\n", stopWallTimer(&timer);
+ *      (2) Note that the timer object is destroyed by stopWallTimer().
+ */
+L_WALLTIMER *
+startWallTimer(void)
+{
+L_WALLTIMER  *timer;
+
+    timer = (L_WALLTIMER *)LEPT_CALLOC(1, sizeof(L_WALLTIMER));
+    l_getCurrentTime(&timer->start_sec, &timer->start_usec);
+    return timer;
+}
+
+l_float32
+stopWallTimer(L_WALLTIMER  **ptimer)
+{
+l_int32       tsec, tusec;
+L_WALLTIMER  *timer;
+
+    PROCNAME("stopWallTimer");
+
+    if (!ptimer)
+        return (l_float32)ERROR_FLOAT("&timer not defined", procName, 0.0);
+    timer = *ptimer;
+    if (!timer)
+        return (l_float32)ERROR_FLOAT("timer not defined", procName, 0.0);
+
+    l_getCurrentTime(&timer->stop_sec, &timer->stop_usec);
+    tsec = timer->stop_sec - timer->start_sec;
+    tusec = timer->stop_usec - timer->start_usec;
+    LEPT_FREE(timer);
+    *ptimer = NULL;
+    return (tsec + ((l_float32)tusec) / 1000000.0);
+}
+
+
+/*!
+ *  l_getFormattedDate()
+ *
+ *      Input:  (none)
+ *      Return: formatted date string, or null on error
+ *
+ *  Notes:
+ *      (1) This is used in pdf, in the form specified in section 3.8.2 of
+ *          http://partners.adobe.com/public/developer/en/pdf/PDFReference.pdf
+ *      (2) Contributed by Dave Bryan.  Works on all platforms.
+ */
+char *
+l_getFormattedDate()
+{
+char        buf[sizeof "199812231952SS-08'00'"] = "", sep = 'Z';
+l_int32     gmt_offset, relh, relm;
+time_t      ut, lt;
+int         dst;
+struct tm  *tptr;
+
+    ut = time(NULL);
+
+        /* This generates a second "time_t" value by calling "gmtime" to
+           fill in a "tm" structure expressed as UTC and then calling
+           "mktime", which expects a "tm" structure expressed as the
+           local time.  The result is a value that is offset from the
+           value returned by the "time" function by the local UTC offset.
+           "tm_isdst" is set to -1 to tell "mktime" to determine for
+           itself whether DST is in effect.  This is necessary because
+           "gmtime" always sets "tm_isdst" to 0, which would tell
+           "mktime" to presume that DST is not in effect. */
+    tptr = gmtime(&ut);
+    tptr->tm_isdst = -1;
+    lt = mktime(tptr);
+
+        /* Calls "difftime" to obtain the resulting difference in seconds,
+         * because "time_t" is an opaque type, per the C standard. */
+    gmt_offset = (l_int32) difftime(ut, lt);
+
+    if (gmt_offset > 0)
+        sep = '+';
+    else if (gmt_offset < 0)
+        sep = '-';
+
+    relh = L_ABS(gmt_offset) / 3600;
+    relm = (L_ABS(gmt_offset) % 3600) / 60;
+
+    strftime(buf, sizeof(buf), "%Y%m%d%H%M%S", localtime(&ut));
+    sprintf(buf + 14, "%c%02d'%02d'", sep, relh, relm);
+    return stringNew(buf);
+}
+
diff --git a/src/viewfiles.c b/src/viewfiles.c
new file mode 100644 (file)
index 0000000..f5ebb49
--- /dev/null
@@ -0,0 +1,237 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *   viewfiles.c
+ *
+ *     Generate smaller images for viewing and write html
+ *        l_int32    pixHtmlViewer()
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+#ifdef _WIN32
+#include <windows.h>   /* for CreateDirectory() */
+#endif
+
+static const l_int32  L_BUF_SIZE = 512;
+static const l_int32  DEFAULT_THUMB_WIDTH = 120;
+static const l_int32  DEFAULT_VIEW_WIDTH = 800;
+static const l_int32  MIN_THUMB_WIDTH = 50;
+static const l_int32  MIN_VIEW_WIDTH = 300;
+
+
+/*---------------------------------------------------------------------*
+ *            Generate smaller images for viewing and write html       *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixHtmlViewer()
+ *
+ *      Input:  dirin:  directory of input image files
+ *              dirout: directory for output files
+ *              rootname: root name for output files
+ *              thumbwidth:  width of thumb images
+ *                           (in pixels; use 0 for default)
+ *              viewwidth:  maximum width of view images (no up-scaling)
+ *                           (in pixels; use 0 for default)
+ *              copyorig:  1 to copy originals to dirout; 0 otherwise
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) The thumb and view reduced images are generated,
+ *          along with two html files:
+ *             <rootname>.html and <rootname>-links.html
+ *      (2) The thumb and view files are named
+ *             <rootname>_thumb_xxx.jpg
+ *             <rootname>_view_xxx.jpg
+ *          With this naming scheme, any number of input directories
+ *          of images can be processed into views and thumbs
+ *          and placed in the same output directory.
+ */
+l_int32
+pixHtmlViewer(const char  *dirin,
+              const char  *dirout,
+              const char  *rootname,
+              l_int32      thumbwidth,
+              l_int32      viewwidth,
+              l_int32      copyorig)
+{
+char      *fname, *fullname, *outname;
+char      *mainname, *linkname, *linknameshort;
+char      *viewfile, *thumbfile;
+char      *shtml, *slink;
+char       charbuf[L_BUF_SIZE];
+char       htmlstring[] = "<html>";
+char       framestring[] = "</frameset></html>";
+l_int32    i, nfiles, index, w, nimages, ret;
+l_float32  factor;
+PIX       *pix, *pixthumb, *pixview;
+SARRAY    *safiles, *sathumbs, *saviews, *sahtml, *salink;
+
+    PROCNAME("pixHtmlViewer");
+
+    if (!dirin)
+        return ERROR_INT("dirin not defined", procName, 1);
+    if (!dirout)
+        return ERROR_INT("dirout not defined", procName, 1);
+    if (!rootname)
+        return ERROR_INT("rootname not defined", procName, 1);
+
+    if (thumbwidth == 0)
+        thumbwidth = DEFAULT_THUMB_WIDTH;
+    if (thumbwidth < MIN_THUMB_WIDTH) {
+        L_WARNING("thumbwidth too small; using min value\n", procName);
+        thumbwidth = MIN_THUMB_WIDTH;
+    }
+    if (viewwidth == 0)
+        viewwidth = DEFAULT_VIEW_WIDTH;
+    if (viewwidth < MIN_VIEW_WIDTH) {
+        L_WARNING("viewwidth too small; using min value\n", procName);
+        viewwidth = MIN_VIEW_WIDTH;
+    }
+
+        /* Make the output directory if it doesn't already exist */
+#ifndef _WIN32
+    sprintf(charbuf, "mkdir -p %s", dirout);
+    ret = system(charbuf);
+#else
+    ret = CreateDirectory(dirout, NULL) ? 0 : 1;
+#endif  /* !_WIN32 */
+    if (ret) {
+        L_ERROR("output directory %s not made\n", procName, dirout);
+        return 1;
+    }
+
+        /* Capture the filenames in the input directory */
+    if ((safiles = getFilenamesInDirectory(dirin)) == NULL)
+        return ERROR_INT("safiles not made", procName, 1);
+
+        /* Generate output text file names */
+    sprintf(charbuf, "%s/%s.html", dirout, rootname);
+    mainname = stringNew(charbuf);
+    sprintf(charbuf, "%s/%s-links.html", dirout, rootname);
+    linkname = stringNew(charbuf);
+    linknameshort = stringJoin(rootname, "-links.html");
+
+    if ((sathumbs = sarrayCreate(0)) == NULL)
+        return ERROR_INT("sathumbs not made", procName, 1);
+    if ((saviews = sarrayCreate(0)) == NULL)
+        return ERROR_INT("saviews not made", procName, 1);
+
+        /* Generate the thumbs and views */
+    nfiles = sarrayGetCount(safiles);
+    index = 0;
+    for (i = 0; i < nfiles; i++) {
+        fname = sarrayGetString(safiles, i, L_NOCOPY);
+        fullname = genPathname(dirin, fname);
+        fprintf(stderr, "name: %s\n", fullname);
+        if ((pix = pixRead(fullname)) == NULL) {
+            fprintf(stderr, "file %s not a readable image\n", fullname);
+            LEPT_FREE(fullname);
+            continue;
+        }
+        LEPT_FREE(fullname);
+        if (copyorig) {
+            outname = genPathname(dirout, fname);
+            pixWrite(outname, pix, IFF_JFIF_JPEG);
+            LEPT_FREE(outname);
+        }
+
+            /* Make and store the thumb */
+        w = pixGetWidth(pix);
+        factor = (l_float32)thumbwidth / (l_float32)w;
+        if ((pixthumb = pixScale(pix, factor, factor)) == NULL)
+            return ERROR_INT("pixthumb not made", procName, 1);
+        sprintf(charbuf, "%s_thumb_%03d.jpg", rootname, index);
+        sarrayAddString(sathumbs, charbuf, L_COPY);
+        outname = genPathname(dirout, charbuf);
+        pixWrite(outname, pixthumb, IFF_JFIF_JPEG);
+        LEPT_FREE(outname);
+        pixDestroy(&pixthumb);
+
+            /* Make and store the view */
+        factor = (l_float32)viewwidth / (l_float32)w;
+        if (factor >= 1.0) {
+            pixview = pixClone(pix);   /* no upscaling */
+        } else {
+            if ((pixview = pixScale(pix, factor, factor)) == NULL)
+                return ERROR_INT("pixview not made", procName, 1);
+        }
+        sprintf(charbuf, "%s_view_%03d.jpg", rootname, index);
+        sarrayAddString(saviews, charbuf, L_COPY);
+        outname = genPathname(dirout, charbuf);
+        pixWrite(outname, pixview, IFF_JFIF_JPEG);
+        LEPT_FREE(outname);
+        pixDestroy(&pixview);
+
+        pixDestroy(&pix);
+        index++;
+    }
+
+        /* Generate the main html file */
+    if ((sahtml = sarrayCreate(0)) == NULL)
+        return ERROR_INT("sahtml not made", procName, 1);
+    sarrayAddString(sahtml, htmlstring, L_COPY);
+    sprintf(charbuf, "<frameset cols=\"%d, *\">", thumbwidth + 30);
+    sarrayAddString(sahtml, charbuf, L_COPY);
+    sprintf(charbuf, "<frame name=\"thumbs\" src=\"%s\">", linknameshort);
+    sarrayAddString(sahtml, charbuf, L_COPY);
+    sprintf(charbuf, "<frame name=\"views\" src=\"%s\">",
+            sarrayGetString(saviews, 0, L_NOCOPY));
+    sarrayAddString(sahtml, charbuf, L_COPY);
+    sarrayAddString(sahtml, framestring, L_COPY);
+    shtml = sarrayToString(sahtml, 1);
+    l_binaryWrite(mainname, "w", shtml, strlen(shtml));
+    LEPT_FREE(shtml);
+    LEPT_FREE(mainname);
+
+        /* Generate the link html file */
+    nimages = sarrayGetCount(saviews);
+    fprintf(stderr, "num. images = %d\n", nimages);
+    if ((salink = sarrayCreate(0)) == NULL)
+        return ERROR_INT("salink not made", procName, 1);
+    for (i = 0; i < nimages; i++) {
+        viewfile = sarrayGetString(saviews, i, L_NOCOPY);
+        thumbfile = sarrayGetString(sathumbs, i, L_NOCOPY);
+        sprintf(charbuf, "<a href=\"%s\" TARGET=views><img src=\"%s\"></a>",
+            viewfile, thumbfile);
+        sarrayAddString(salink, charbuf, L_COPY);
+    }
+    slink = sarrayToString(salink, 1);
+    l_binaryWrite(linkname, "w", slink, strlen(slink));
+    LEPT_FREE(slink);
+    LEPT_FREE(linkname);
+    LEPT_FREE(linknameshort);
+
+    sarrayDestroy(&safiles);
+    sarrayDestroy(&sathumbs);
+    sarrayDestroy(&saviews);
+    sarrayDestroy(&sahtml);
+    sarrayDestroy(&salink);
+
+    return 0;
+}
diff --git a/src/warper.c b/src/warper.c
new file mode 100644 (file)
index 0000000..7854076
--- /dev/null
@@ -0,0 +1,1366 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  warper.c
+ *
+ *      High-level captcha interface
+ *          PIX               *pixSimpleCaptcha()
+ *
+ *      Random sinusoidal warping
+ *          PIX               *pixRandomHarmonicWarp()
+ *
+ *      Helper functions
+ *          static l_float64  *generateRandomNumberArray()
+ *          static l_int32     applyWarpTransform()
+ *
+ *      Version using a LUT for sin
+ *          PIX               *pixRandomHarmonicWarpLUT()
+ *          static l_int32     applyWarpTransformLUT()
+ *          static l_int32     makeSinLUT()
+ *          static l_float32   getSinFromLUT()
+ *
+ *      Stereoscopic warping
+ *          PIX               *pixWarpStereoscopic()
+ *
+ *      Linear and quadratic horizontal stretching
+ *          PIX               *pixStretchHorizontal()
+ *          PIX               *pixStretchHorizontalSampled()
+ *          PIX               *pixStretchHorizontalLI()
+ *
+ *      Quadratic vertical shear
+ *          PIX               *pixQuadraticVShear()
+ *          PIX               *pixQuadraticVShearSampled()
+ *          PIX               *pixQuadraticVShearLI()
+ *
+ *      Stereo from a pair of images
+ *          PIX               *pixStereoFromPair()
+ */
+
+#include <math.h>
+#include "allheaders.h"
+
+static l_float64 *generateRandomNumberArray(l_int32 size);
+static l_int32 applyWarpTransform(l_float32 xmag, l_float32 ymag,
+                                l_float32 xfreq, l_float32 yfreq,
+                                l_float64 *randa, l_int32 nx, l_int32 ny,
+                                l_int32 xp, l_int32 yp,
+                                l_float32 *px, l_float32 *py);
+
+#define  USE_SIN_TABLE    0
+
+    /* Suggested input to pixStereoFromPair().  These are weighting
+     * factors for input to the red channel from the left image. */
+static const l_float32  L_DEFAULT_RED_WEIGHT   = 0.0;
+static const l_float32  L_DEFAULT_GREEN_WEIGHT = 0.7;
+static const l_float32  L_DEFAULT_BLUE_WEIGHT  = 0.3;
+
+
+/*----------------------------------------------------------------------*
+ *                High-level example captcha interface                  *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixSimpleCaptcha()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              border (added white pixels on each side)
+ *              nterms (number of x and y harmonic terms)
+ *              seed (of random number generator)
+ *              color (for colorizing; in 0xrrggbb00 format; use 0 for black)
+ *              cmapflag (1 for colormap output; 0 for rgb)
+ *      Return: pixd (8 bpp cmap or 32 bpp rgb), or null on error
+ *
+ *  Notes:
+ *      (1) This uses typical default values for generating captchas.
+ *          The magnitudes of the harmonic warp are typically to be
+ *          smaller when more terms are used, even though the phases
+ *          are random.  See, for example, prog/warptest.c.
+ */
+PIX *
+pixSimpleCaptcha(PIX      *pixs,
+                 l_int32   border,
+                 l_int32   nterms,
+                 l_uint32  seed,
+                 l_uint32  color,
+                 l_int32   cmapflag)
+{
+l_int32    k;
+l_float32  xmag[] = {7.0, 5.0, 4.0, 3.0};
+l_float32  ymag[] = {10.0, 8.0, 6.0, 5.0};
+l_float32  xfreq[] = {0.12, 0.10, 0.10, 0.11};
+l_float32  yfreq[] = {0.15, 0.13, 0.13, 0.11};
+PIX       *pixg, *pixgb, *pixw, *pixd;
+
+    PROCNAME("pixSimpleCaptcha");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    if (nterms < 1 || nterms > 4)
+        return (PIX *)ERROR_PTR("nterms must be in {1,2,3,4}", procName, NULL);
+
+    k = nterms - 1;
+    pixg = pixConvertTo8(pixs, 0);
+    pixgb = pixAddBorder(pixg, border, 255);
+    pixw = pixRandomHarmonicWarp(pixgb, xmag[k], ymag[k], xfreq[k], yfreq[k],
+                                 nterms, nterms, seed, 255);
+    pixd = pixColorizeGray(pixw, color, cmapflag);
+
+    pixDestroy(&pixg);
+    pixDestroy(&pixgb);
+    pixDestroy(&pixw);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                     Random sinusoidal warping                        *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixRandomHarmonicWarp()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              xmag, ymag (maximum magnitude of x and y distortion)
+ *              xfreq, yfreq (maximum magnitude of x and y frequency)
+ *              nx, ny (number of x and y harmonic terms)
+ *              seed (of random number generator)
+ *              grayval (color brought in from the outside;
+ *                       0 for black, 255 for white)
+ *      Return: pixd (8 bpp; no colormap), or null on error
+ *
+ *  Notes:
+ *      (1) To generate the warped image p(x',y'), set up the transforms
+ *          that are in getWarpTransform().  For each (x',y') in the
+ *          dest, the warp function computes the originating location
+ *          (x, y) in the src.  The differences (x - x') and (y - y')
+ *          are given as a sum of products of sinusoidal terms.  Each
+ *          term is multiplied by a maximum amplitude (in pixels), and the
+ *          angle is determined by a frequency and phase, and depends
+ *          on the (x', y') value of the dest.  Random numbers with
+ *          a variable input seed are used to allow the warping to be
+ *          unpredictable.  A linear interpolation is used to find
+ *          the value for the source at (x, y); this value is written
+ *          into the dest.
+ *      (2) This can be used to generate 'captcha's, which are somewhat
+ *          randomly distorted images of text.  A typical set of parameters
+ *          for a captcha are:
+ *                    xmag = 4.0     ymag = 6.0
+ *                    xfreq = 0.10   yfreq = 0.13
+ *                    nx = 3         ny = 3
+ *          Other examples can be found in prog/warptest.c.
+ */
+PIX *
+pixRandomHarmonicWarp(PIX       *pixs,
+                      l_float32  xmag,
+                      l_float32  ymag,
+                      l_float32  xfreq,
+                      l_float32  yfreq,
+                      l_int32    nx,
+                      l_int32    ny,
+                      l_uint32   seed,
+                      l_int32    grayval)
+{
+l_int32     w, h, d, i, j, wpls, wpld, val;
+l_uint32   *datas, *datad, *lined;
+l_float32   x, y;
+l_float64  *randa;
+PIX        *pixd;
+
+    PROCNAME("pixRandomHarmonicWarp");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+
+        /* Compute filter output at each location.  We iterate over
+         * the destination pixels.  For each dest pixel, use the
+         * warp function to compute the four source pixels that
+         * contribute, at the location (x, y).  Each source pixel
+         * is divided into 16 x 16 subpixels to get an approximate value. */
+    srand(seed);
+    randa = generateRandomNumberArray(5 * (nx + ny));
+    pixd = pixCreateTemplate(pixs);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            applyWarpTransform(xmag, ymag, xfreq, yfreq, randa, nx, ny,
+                               j, i, &x, &y);
+            linearInterpolatePixelGray(datas, wpls, w, h, x, y, grayval, &val);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    LEPT_FREE(randa);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                         Static helper functions                      *
+ *----------------------------------------------------------------------*/
+static l_float64 *
+generateRandomNumberArray(l_int32  size)
+{
+l_int32     i;
+l_float64  *randa;
+
+    PROCNAME("generateRandomNumberArray");
+
+    if ((randa = (l_float64 *)LEPT_CALLOC(size, sizeof(l_float64))) == NULL)
+        return (l_float64 *)ERROR_PTR("calloc fail for randa", procName, NULL);
+
+        /* Return random values between 0.5 and 1.0 */
+    for (i = 0; i < size; i++)
+        randa[i] = 0.5 * (1.0 + (l_float64)rand() / (l_float64)RAND_MAX);
+    return randa;
+}
+
+
+/*!
+ *  applyWarpTransform()
+ *
+ *  Notes:
+ *      (1) Uses the internal sin function.
+ */
+static l_int32
+applyWarpTransform(l_float32   xmag,
+                   l_float32   ymag,
+                   l_float32   xfreq,
+                   l_float32   yfreq,
+                   l_float64  *randa,
+                   l_int32     nx,
+                   l_int32     ny,
+                   l_int32     xp,
+                   l_int32     yp,
+                   l_float32  *px,
+                   l_float32  *py)
+{
+l_int32    i;
+l_float64  twopi, x, y, anglex, angley;
+
+    twopi = 6.283185;
+    for (i = 0, x = xp; i < nx; i++) {
+        anglex = xfreq * randa[3 * i + 1] * xp + twopi * randa[3 * i + 2];
+        angley = yfreq * randa[3 * i + 3] * yp + twopi * randa[3 * i + 4];
+        x += xmag * randa[3 * i] * sin(anglex) * sin(angley);
+    }
+    for (i = nx, y = yp; i < nx + ny; i++) {
+        angley = yfreq * randa[3 * i + 1] * yp + twopi * randa[3 * i + 2];
+        anglex = xfreq * randa[3 * i + 3] * xp + twopi * randa[3 * i + 4];
+        y += ymag * randa[3 * i] * sin(angley) * sin(anglex);
+    }
+
+    *px = (l_float32)x;
+    *py = (l_float32)y;
+    return 0;
+}
+
+
+#if  USE_SIN_TABLE
+/*----------------------------------------------------------------------*
+ *                       Version using a LUT for sin                    *
+ *----------------------------------------------------------------------*/
+static l_int32 applyWarpTransformLUT(l_float32 xmag, l_float32 ymag,
+                                l_float32 xfreq, l_float32 yfreq,
+                                l_float64 *randa, l_int32 nx, l_int32 ny,
+                                l_int32 xp, l_int32 yp, l_float32 *lut,
+                                l_int32 npts, l_float32 *px, l_float32 *py);
+static l_int32 makeSinLUT(l_int32 npts, NUMA **pna);
+static l_float32 getSinFromLUT(l_float32 *tab, l_int32 npts,
+                               l_float32 radang);
+
+/*!
+ *  pixRandomHarmonicWarpLUT()
+ *
+ *      Input:  pixs (8 bpp; no colormap)
+ *              xmag, ymag (maximum magnitude of x and y distortion)
+ *              xfreq, yfreq (maximum magnitude of x and y frequency)
+ *              nx, ny (number of x and y harmonic terms)
+ *              seed (of random number generator)
+ *              grayval (color brought in from the outside;
+ *                       0 for black, 255 for white)
+ *      Return: pixd (8 bpp; no colormap), or null on error
+ *
+ *  Notes:
+ *      (1) See notes and inline comments in pixRandomHarmonicWarp().
+ *          This version uses a LUT for the sin function.  It is not
+ *          appreciably faster than using the built-in sin function,
+ *          and is here for comparison only.
+ */
+PIX *
+pixRandomHarmonicWarpLUT(PIX       *pixs,
+                         l_float32  xmag,
+                         l_float32  ymag,
+                         l_float32  xfreq,
+                         l_float32  yfreq,
+                         l_int32    nx,
+                         l_int32    ny,
+                         l_uint32   seed,
+                         l_int32    grayval)
+{
+l_int32     w, h, d, i, j, wpls, wpld, val, npts;
+l_uint32   *datas, *datad, *lined;
+l_float32   x, y;
+l_float32  *lut;
+l_float64  *randa;
+NUMA       *na;
+PIX        *pixd;
+
+    PROCNAME("pixRandomHarmonicWarp");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8)
+        return (PIX *)ERROR_PTR("pixs not 8 bpp", procName, NULL);
+
+        /* Compute filter output at each location.  We iterate over
+         * the destination pixels.  For each dest pixel, use the
+         * warp function to compute the four source pixels that
+         * contribute, at the location (x, y).  Each source pixel
+         * is divided into 16 x 16 subpixels to get an approximate value. */
+    srand(seed);
+    randa = generateRandomNumberArray(5 * (nx + ny));
+    pixd = pixCreateTemplate(pixs);
+    datas = pixGetData(pixs);
+    wpls = pixGetWpl(pixs);
+    datad = pixGetData(pixd);
+    wpld = pixGetWpl(pixd);
+
+    npts = 100;
+    makeSinLUT(npts, &na);
+    lut = numaGetFArray(na, L_NOCOPY);
+    for (i = 0; i < h; i++) {
+        lined = datad + i * wpld;
+        for (j = 0; j < w; j++) {
+            applyWarpTransformLUT(xmag, ymag, xfreq, yfreq, randa, nx, ny,
+                                  j, i, lut, npts, &x, &y);
+            linearInterpolatePixelGray(datas, wpls, w, h, x, y, grayval, &val);
+            SET_DATA_BYTE(lined, j, val);
+        }
+    }
+
+    numaDestroy(&na);
+    LEPT_FREE(randa);
+    return pixd;
+}
+
+
+/*!
+ *  applyWarpTransformLUT()
+ *
+ *  Notes:
+ *      (1) Uses an LUT for computing sin(theta).  There is little speed
+ *          advantage to using the LUT.
+ */
+static l_int32
+applyWarpTransformLUT(l_float32   xmag,
+                      l_float32   ymag,
+                      l_float32   xfreq,
+                      l_float32   yfreq,
+                      l_float64  *randa,
+                      l_int32     nx,
+                      l_int32     ny,
+                      l_int32     xp,
+                      l_int32     yp,
+                      l_float32  *lut,
+                      l_int32     npts,
+                      l_float32  *px,
+                      l_float32  *py)
+{
+l_int32    i;
+l_float64  twopi, x, y, anglex, angley, sanglex, sangley;
+
+    twopi = 6.283185;
+    for (i = 0, x = xp; i < nx; i++) {
+        anglex = xfreq * randa[3 * i + 1] * xp + twopi * randa[3 * i + 2];
+        angley = yfreq * randa[3 * i + 3] * yp + twopi * randa[3 * i + 4];
+        sanglex = getSinFromLUT(lut, npts, anglex);
+        sangley = getSinFromLUT(lut, npts, angley);
+        x += xmag * randa[3 * i] * sanglex * sangley;
+    }
+    for (i = nx, y = yp; i < nx + ny; i++) {
+        angley = yfreq * randa[3 * i + 1] * yp + twopi * randa[3 * i + 2];
+        anglex = xfreq * randa[3 * i + 3] * xp + twopi * randa[3 * i + 4];
+        sanglex = getSinFromLUT(lut, npts, anglex);
+        sangley = getSinFromLUT(lut, npts, angley);
+        y += ymag * randa[3 * i] * sangley * sanglex;
+    }
+
+    *px = (l_float32)x;
+    *py = (l_float32)y;
+    return 0;
+}
+
+
+static l_int32
+makeSinLUT(l_int32  npts,
+           NUMA   **pna)
+{
+l_int32    i, n;
+l_float32  delx, fval;
+NUMA      *na;
+
+    PROCNAME("makeSinLUT");
+
+    if (!pna)
+        return ERROR_INT("&na not defined", procName, 1);
+    *pna = NULL;
+    if (npts < 2)
+        return ERROR_INT("npts < 2", procName, 1);
+    n = 2 * npts + 1;
+    na = numaCreate(n);
+    *pna = na;
+    delx = 3.14159265 / (l_float32)npts;
+    numaSetParameters(na, 0.0, delx);
+    for (i = 0; i < n / 2; i++)
+         numaAddNumber(na, (l_float32)sin((l_float64)i * delx));
+    for (i = 0; i < n / 2; i++) {
+         numaGetFValue(na, i, &fval);
+         numaAddNumber(na, -fval);
+    }
+    numaAddNumber(na, 0);
+
+    return 0;
+}
+
+
+static l_float32
+getSinFromLUT(l_float32  *tab,
+              l_int32     npts,
+              l_float32   radang)
+{
+l_int32    index;
+l_float32  twopi, invtwopi, findex, diff;
+
+        /* Restrict radang to [0, 2pi] */
+    twopi = 6.283185;
+    invtwopi = 0.1591549;
+    if (radang < 0.0)
+        radang += twopi * (1.0 - (l_int32)(-radang * invtwopi));
+    else if (radang > 0.0)
+        radang -= twopi * (l_int32)(radang * invtwopi);
+
+        /* Interpolate */
+    findex = (2.0 * (l_float32)npts) * (radang * invtwopi);
+    index = (l_int32)findex;
+    if (index == 2 * npts)
+        return tab[index];
+    diff = findex - index;
+    return (1.0 - diff) * tab[index] + diff * tab[index + 1];
+}
+#endif  /* USE_SIN_TABLE */
+
+
+
+/*---------------------------------------------------------------------------*
+ *                          Stereoscopic warping                             *
+ *---------------------------------------------------------------------------*/
+/*!
+ *  pixWarpStereoscopic()
+ *
+ *      Input:  pixs (any depth, colormap ok)
+ *              zbend (horizontal separation in pixels of red and cyan
+ *                    at the left and right sides, that gives rise to
+ *                    quadratic curvature out of the image plane)
+ *              zshiftt (uniform pixel translation difference between
+ *                      red and cyan, that pushes the top of the image
+ *                      plane away from the viewer (zshiftt > 0) or
+ *                      towards the viewer (zshiftt < 0))
+ *              zshiftb (uniform pixel translation difference between
+ *                      red and cyan, that pushes the bottom of the image
+ *                      plane away from the viewer (zshiftb > 0) or
+ *                      towards the viewer (zshiftb < 0))
+ *              ybendt (multiplicative parameter for in-plane vertical
+ *                      displacement at the left or right edge at the top:
+ *                        y = ybendt * (2x/w - 1)^2 )
+ *              ybendb (same as ybendt, except at the left or right edge
+ *                      at the bottom)
+ *              redleft (1 if the red filter is on the left; 0 otherwise)
+ *      Return: pixd (32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) This function splits out the red channel, mucks around with
+ *          it, then recombines with the unmolested cyan channel.
+ *      (2) By using a quadratically increasing shift of the red
+ *          pixels horizontally and away from the vertical centerline,
+ *          the image appears to bend quadratically out of the image
+ *          plane, symmetrically with respect to the vertical center
+ *          line.  A positive value of @zbend causes the plane to be
+ *          curved away from the viewer.  We use linearly interpolated
+ *          stretching to avoid the appearance of kinks in the curve.
+ *      (3) The parameters @zshiftt and @zshiftb tilt the image plane
+ *          about a horizontal line through the center, and at the
+ *          same time move that line either in toward the viewer or away.
+ *          This is implemented by a combination of horizontal shear
+ *          about the center line (for the tilt) and horizontal
+ *          translation (to move the entire plane in or out).
+ *          A positive value of @zshiftt moves the top of the plane
+ *          away from the viewer, and a positive value of @zshiftb
+ *          moves the bottom of the plane away.  We use linear interpolated
+ *          shear to avoid visible vertical steps in the tilted image.
+ *      (4) The image can be bent in the plane and about the vertical
+ *          centerline.  The centerline does not shift, and the
+ *          parameter @ybend gives the relative shift at left and right
+ *          edges, with a downward shift for positive values of @ybend.
+ *      (6) When writing out a steroscopic (red/cyan) image in jpeg,
+ *          first call pixSetChromaSampling(pix, 0) to get sufficient
+ *          resolution in the red channel.
+ *      (7) Typical values are:
+ *             zbend = 20
+ *             zshiftt = 15
+ *             zshiftb = -15
+ *             ybendt = 30
+ *             ybendb = 0
+ *          If the disparity z-values are too large, it is difficult for
+ *          the brain to register the two images.
+ *      (8) This function has been cleverly reimplemented by Jeff Breidenbach.
+ *          The original implementation used two 32 bpp rgb images,
+ *          and merged them at the end.  The result is somewhat faded,
+ *          and has a parameter "thresh" that controls the amount of
+ *          color in the result.  (The present implementation avoids these
+ *          two problems, skipping both the colorization and the alpha
+ *          blending at the end, and is about 3x faster)
+ *          The basic operations with 32 bpp are as follows:
+ *               // Immediate conversion to 32 bpp
+ *            Pix *pixt1 = pixConvertTo32(pixs);
+ *               // Do vertical shear
+ *            Pix *pixr = pixQuadraticVerticalShear(pixt1, L_WARP_TO_RIGHT,
+ *                                                  ybendt, ybendb,
+ *                                                  L_BRING_IN_WHITE);
+ *               // Colorize two versions, toward red and cyan
+ *            Pix *pixc = pixCopy(NULL, pixr);
+ *            l_int32 thresh = 150;  // if higher, get less original color
+ *            pixColorGray(pixr, NULL, L_PAINT_DARK, thresh, 255, 0, 0);
+ *            pixColorGray(pixc, NULL, L_PAINT_DARK, thresh, 0, 255, 255);
+ *               // Shift the red pixels; e.g., by stretching
+ *            Pix *pixrs = pixStretchHorizontal(pixr, L_WARP_TO_RIGHT,
+ *                                              L_QUADRATIC_WARP, zbend,
+ *                                              L_INTERPOLATED,
+ *                                              L_BRING_IN_WHITE);
+ *               // Blend the shifted red and unshifted cyan 50:50
+ *            Pix *pixg = pixCreate(w, h, 8);
+ *            pixSetAllArbitrary(pixg, 128);
+ *            pixd = pixBlendWithGrayMask(pixrs, pixc, pixg, 0, 0);
+ */
+PIX *
+pixWarpStereoscopic(PIX     *pixs,
+                    l_int32  zbend,
+                    l_int32  zshiftt,
+                    l_int32  zshiftb,
+                    l_int32  ybendt,
+                    l_int32  ybendb,
+                    l_int32  redleft)
+{
+l_int32    w, h, zshift;
+l_float32  angle;
+BOX       *boxleft, *boxright;
+PIX       *pixt, *pixt2, *pixt3;
+PIX       *pixr, *pixg, *pixb;
+PIX       *pixv1, *pixv2, *pixv3, *pixv4;
+PIX       *pixr1, *pixr2, *pixr3, *pixr4, *pixrs, *pixrss;
+PIX       *pixd;
+
+    PROCNAME("pixWarpStereoscopic");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+
+        /* Convert to the output depth, 32 bpp. */
+    pixt = pixConvertTo32(pixs);
+
+        /* If requested, do a quad vertical shearing, pushing pixels up
+         * or down, depending on their distance from the centerline. */
+    pixGetDimensions(pixs, &w, &h, NULL);
+    boxleft = boxCreate(0, 0, w / 2, h);
+    boxright = boxCreate(w / 2, 0, w - w / 2, h);
+    if (ybendt != 0 || ybendb != 0) {
+        pixv1 = pixClipRectangle(pixt, boxleft, NULL);
+        pixv2 = pixClipRectangle(pixt, boxright, NULL);
+        pixv3 = pixQuadraticVShear(pixv1, L_WARP_TO_LEFT, ybendt,
+                                          ybendb, L_INTERPOLATED,
+                                          L_BRING_IN_WHITE);
+        pixv4 = pixQuadraticVShear(pixv2, L_WARP_TO_RIGHT, ybendt,
+                                          ybendb, L_INTERPOLATED,
+                                          L_BRING_IN_WHITE);
+        pixt2 = pixCreate(w, h, 32);
+        pixRasterop(pixt2, 0, 0, w / 2, h, PIX_SRC, pixv3, 0, 0);
+        pixRasterop(pixt2, w / 2, 0, w - w / 2, h, PIX_SRC, pixv4, 0, 0);
+        pixDestroy(&pixv1);
+        pixDestroy(&pixv2);
+        pixDestroy(&pixv3);
+        pixDestroy(&pixv4);
+    } else {
+        pixt2 = pixClone(pixt);
+    }
+
+        /* Split out the 3 components */
+    pixr = pixGetRGBComponent(pixt2, COLOR_RED);
+    pixg = pixGetRGBComponent(pixt2, COLOR_GREEN);
+    pixb = pixGetRGBComponent(pixt2, COLOR_BLUE);
+    pixDestroy(&pixt);
+    pixDestroy(&pixt2);
+
+        /* The direction of the stereo disparity below is set
+         * for the red filter to be over the left eye.  If the red
+         * filter is over the right eye, invert the horizontal shifts. */
+    if (redleft) {
+        zbend = -zbend;
+        zshiftt = -zshiftt;
+        zshiftb = -zshiftb;
+    }
+
+        /* Shift the red pixels horizontally by an amount that
+         * increases quadratically from the centerline. */
+    if (zbend == 0) {
+        pixrs = pixClone(pixr);
+    } else {
+        pixr1 = pixClipRectangle(pixr, boxleft, NULL);
+        pixr2 = pixClipRectangle(pixr, boxright, NULL);
+        pixr3 = pixStretchHorizontal(pixr1, L_WARP_TO_LEFT, L_QUADRATIC_WARP,
+                                     zbend, L_INTERPOLATED, L_BRING_IN_WHITE);
+        pixr4 = pixStretchHorizontal(pixr2, L_WARP_TO_RIGHT, L_QUADRATIC_WARP,
+                                     zbend, L_INTERPOLATED, L_BRING_IN_WHITE);
+        pixrs = pixCreate(w, h, 8);
+        pixRasterop(pixrs, 0, 0, w / 2, h, PIX_SRC, pixr3, 0, 0);
+        pixRasterop(pixrs, w / 2, 0, w - w / 2, h, PIX_SRC, pixr4, 0, 0);
+        pixDestroy(&pixr1);
+        pixDestroy(&pixr2);
+        pixDestroy(&pixr3);
+        pixDestroy(&pixr4);
+    }
+
+        /* Perform a combination of horizontal shift and shear of
+         * red pixels.  The causes the plane of the image to tilt and
+         * also move forward or backward. */
+    if (zshiftt == 0 && zshiftb == 0) {
+        pixrss = pixClone(pixrs);
+    } else if (zshiftt == zshiftb) {
+        pixrss = pixTranslate(NULL, pixrs, zshiftt, 0, L_BRING_IN_WHITE);
+    } else {
+        angle = (l_float32)(zshiftb - zshiftt) / (l_float32)pixGetHeight(pixrs);
+        zshift = (zshiftt + zshiftb) / 2;
+        pixt3 = pixTranslate(NULL, pixrs, zshift, 0, L_BRING_IN_WHITE);
+        pixrss = pixHShearLI(pixt3, h / 2, angle, L_BRING_IN_WHITE);
+        pixDestroy(&pixt3);
+    }
+
+        /* Combine the unchanged cyan (g,b) image with the shifted red */
+    pixd = pixCreateRGBImage(pixrss, pixg, pixb);
+
+    boxDestroy(&boxleft);
+    boxDestroy(&boxright);
+    pixDestroy(&pixrs);
+    pixDestroy(&pixrss);
+    pixDestroy(&pixr);
+    pixDestroy(&pixg);
+    pixDestroy(&pixb);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *              Linear and quadratic horizontal stretching              *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixStretchHorizontal()
+ *
+ *      Input:  pixs (1, 8 or 32 bpp)
+ *              dir (L_WARP_TO_LEFT or L_WARP_TO_RIGHT)
+ *              type (L_LINEAR_WARP or L_QUADRATIC_WARP)
+ *              hmax (horizontal displacement at edge)
+ *              operation (L_SAMPLED or L_INTERPOLATED)
+ *              incolor (L_BRING_IN_WHITE or L_BRING_IN_BLACK)
+ *      Return: pixd (stretched/compressed), or null on error
+ *
+ *  Notes:
+ *      (1) If @hmax > 0, this is an increase in the coordinate value of
+ *          pixels in pixd, relative to the same pixel in pixs.
+ *      (2) If @dir == L_WARP_TO_LEFT, the pixels on the right edge of
+ *          the image are not moved. So, for example, if @hmax > 0
+ *          and @dir == L_WARP_TO_LEFT, the pixels in pixd are
+ *          contracted toward the right edge of the image, relative
+ *          to those in pixs.
+ *      (3) If @type == L_LINEAR_WARP, the pixel positions are moved
+ *          to the left or right by an amount that varies linearly with
+ *          the horizontal location.
+ *      (4) If @operation == L_SAMPLED, the dest pixels are taken from
+ *          the nearest src pixel.  Otherwise, we use linear interpolation
+ *          between pairs of sampled pixels.
+ */
+PIX *
+pixStretchHorizontal(PIX     *pixs,
+                     l_int32  dir,
+                     l_int32  type,
+                     l_int32  hmax,
+                     l_int32  operation,
+                     l_int32  incolor)
+{
+l_int32  d;
+
+    PROCNAME("pixStretchHorizontal");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    d = pixGetDepth(pixs);
+    if (d != 1 && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 1, 8 or 32 bpp", procName, NULL);
+    if (dir != L_WARP_TO_LEFT && dir != L_WARP_TO_RIGHT)
+        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
+    if (type != L_LINEAR_WARP && type != L_QUADRATIC_WARP)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    if (operation != L_SAMPLED && operation != L_INTERPOLATED)
+        return (PIX *)ERROR_PTR("invalid operation", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+    if (d == 1 && operation == L_INTERPOLATED) {
+        L_WARNING("Using sampling for 1 bpp\n", procName);
+        operation = L_INTERPOLATED;
+    }
+
+    if (operation == L_SAMPLED)
+        return pixStretchHorizontalSampled(pixs, dir, type, hmax, incolor);
+    else
+        return pixStretchHorizontalLI(pixs, dir, type, hmax, incolor);
+}
+
+
+/*!
+ *  pixStretchHorizontalSampled()
+ *
+ *      Input:  pixs (1, 8 or 32 bpp)
+ *              dir (L_WARP_TO_LEFT or L_WARP_TO_RIGHT)
+ *              type (L_LINEAR_WARP or L_QUADRATIC_WARP)
+ *              hmax (horizontal displacement at edge)
+ *              incolor (L_BRING_IN_WHITE or L_BRING_IN_BLACK)
+ *      Return: pixd (stretched/compressed), or null on error
+ *
+ *  Notes:
+ *      (1) See pixStretchHorizontal() for details.
+ */
+PIX *
+pixStretchHorizontalSampled(PIX     *pixs,
+                            l_int32  dir,
+                            l_int32  type,
+                            l_int32  hmax,
+                            l_int32  incolor)
+{
+l_int32    i, j, jd, w, wm, h, d, wpls, wpld, val;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixStretchHorizontalSampled");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 1, 8 or 32 bpp", procName, NULL);
+    if (dir != L_WARP_TO_LEFT && dir != L_WARP_TO_RIGHT)
+        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
+    if (type != L_LINEAR_WARP && type != L_QUADRATIC_WARP)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+
+    pixd = pixCreateTemplate(pixs);
+    pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    wm = w - 1;
+    for (jd = 0; jd < w; jd++) {
+        if (dir == L_WARP_TO_LEFT) {
+            if (type == L_LINEAR_WARP)
+                j = jd - (hmax * (wm - jd)) / wm;
+            else  /* L_QUADRATIC_WARP */
+                j = jd - (hmax * (wm - jd) * (wm - jd)) / (wm * wm);
+        } else if (dir == L_WARP_TO_RIGHT) {
+            if (type == L_LINEAR_WARP)
+                j = jd - (hmax * jd) / wm;
+            else  /* L_QUADRATIC_WARP */
+                j = jd - (hmax * jd * jd) / (wm * wm);
+        }
+        if (j < 0 || j > w - 1) continue;
+
+        switch (d)
+        {
+        case 1:
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                val = GET_DATA_BIT(lines, j);
+                if (val)
+                    SET_DATA_BIT(lined, jd);
+            }
+            break;
+        case 8:
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                val = GET_DATA_BYTE(lines, j);
+                SET_DATA_BYTE(lined, jd, val);
+            }
+            break;
+        case 32:
+            for (i = 0; i < h; i++) {
+                lines = datas + i * wpls;
+                lined = datad + i * wpld;
+                lined[jd] = lines[j];
+            }
+            break;
+        default:
+            L_ERROR("invalid depth: %d\n", procName, d);
+            pixDestroy(&pixd);
+            return NULL;
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixStretchHorizontalLI()
+ *
+ *      Input:  pixs (1, 8 or 32 bpp)
+ *              dir (L_WARP_TO_LEFT or L_WARP_TO_RIGHT)
+ *              type (L_LINEAR_WARP or L_QUADRATIC_WARP)
+ *              hmax (horizontal displacement at edge)
+ *              incolor (L_BRING_IN_WHITE or L_BRING_IN_BLACK)
+ *      Return: pixd (stretched/compressed), or null on error
+ *
+ *  Notes:
+ *      (1) See pixStretchHorizontal() for details.
+ */
+PIX *
+pixStretchHorizontalLI(PIX     *pixs,
+                       l_int32  dir,
+                       l_int32  type,
+                       l_int32  hmax,
+                       l_int32  incolor)
+{
+l_int32    i, j, jd, jp, jf, w, wm, h, d, wpls, wpld, val, rval, gval, bval;
+l_uint32   word0, word1;
+l_uint32  *datas, *datad, *lines, *lined;
+PIX       *pixd;
+
+    PROCNAME("pixStretchHorizontalLI");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 8 or 32 bpp", procName, NULL);
+    if (dir != L_WARP_TO_LEFT && dir != L_WARP_TO_RIGHT)
+        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
+    if (type != L_LINEAR_WARP && type != L_QUADRATIC_WARP)
+        return (PIX *)ERROR_PTR("invalid type", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+
+        /* Standard linear interpolation, subdividing each pixel into 64 */
+    pixd = pixCreateTemplate(pixs);
+    pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    wm = w - 1;
+    for (jd = 0; jd < w; jd++) {
+        if (dir == L_WARP_TO_LEFT) {
+            if (type == L_LINEAR_WARP)
+                j = 64 * jd - 64 * (hmax * (wm - jd)) / wm;
+            else  /* L_QUADRATIC_WARP */
+                j = 64 * jd - 64 * (hmax * (wm - jd) * (wm - jd)) / (wm * wm);
+        } else if (dir == L_WARP_TO_RIGHT) {
+            if (type == L_LINEAR_WARP)
+                j = 64 * jd - 64 * (hmax * jd) / wm;
+            else  /* L_QUADRATIC_WARP */
+                j = 64 * jd - 64 * (hmax * jd * jd) / (wm * wm);
+        }
+        jp = j / 64;
+        jf = j & 0x3f;
+        if (jp < 0 || jp > wm) continue;
+
+        switch (d)
+        {
+        case 8:
+            if (jp < wm) {
+                for (i = 0; i < h; i++) {
+                    lines = datas + i * wpls;
+                    lined = datad + i * wpld;
+                    val = ((63 - jf) * GET_DATA_BYTE(lines, jp) +
+                           jf * GET_DATA_BYTE(lines, jp + 1) + 31) / 63;
+                    SET_DATA_BYTE(lined, jd, val);
+                }
+            } else {  /* jp == wm */
+                for (i = 0; i < h; i++) {
+                    lines = datas + i * wpls;
+                    lined = datad + i * wpld;
+                    val = GET_DATA_BYTE(lines, jp);
+                    SET_DATA_BYTE(lined, jd, val);
+                }
+            }
+            break;
+        case 32:
+            if (jp < wm) {
+                for (i = 0; i < h; i++) {
+                    lines = datas + i * wpls;
+                    lined = datad + i * wpld;
+                    word0 = *(lines + jp);
+                    word1 = *(lines + jp + 1);
+                    rval = ((63 - jf) * ((word0 >> L_RED_SHIFT) & 0xff) +
+                           jf * ((word1 >> L_RED_SHIFT) & 0xff) + 31) / 63;
+                    gval = ((63 - jf) * ((word0 >> L_GREEN_SHIFT) & 0xff) +
+                           jf * ((word1 >> L_GREEN_SHIFT) & 0xff) + 31) / 63;
+                    bval = ((63 - jf) * ((word0 >> L_BLUE_SHIFT) & 0xff) +
+                           jf * ((word1 >> L_BLUE_SHIFT) & 0xff) + 31) / 63;
+                    composeRGBPixel(rval, gval, bval, lined + jd);
+                }
+            } else {  /* jp == wm */
+                for (i = 0; i < h; i++) {
+                    lines = datas + i * wpls;
+                    lined = datad + i * wpld;
+                    lined[jd] = lines[jp];
+                }
+            }
+            break;
+        default:
+            L_ERROR("invalid depth: %d\n", procName, d);
+            pixDestroy(&pixd);
+            return NULL;
+        }
+    }
+
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                       Quadratic vertical shear                       *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixQuadraticVShear()
+ *
+ *      Input:  pixs (1, 8 or 32 bpp)
+ *              dir (L_WARP_TO_LEFT or L_WARP_TO_RIGHT)
+ *              vmaxt (max vertical displacement at edge and at top)
+ *              vmaxb (max vertical displacement at edge and at bottom)
+ *              operation (L_SAMPLED or L_INTERPOLATED)
+ *              incolor (L_BRING_IN_WHITE or L_BRING_IN_BLACK)
+ *      Return: pixd (stretched), or null on error
+ *
+ *  Notes:
+ *      (1) This gives a quadratic bending, upward or downward, as you
+ *          move to the left or right.
+ *      (2) If @dir == L_WARP_TO_LEFT, the right edge is unchanged, and
+ *          the left edge pixels are moved maximally up or down.
+ *      (3) Parameters @vmaxt and @vmaxb control the maximum amount of
+ *          vertical pixel shear at the top and bottom, respectively.
+ *          If @vmaxt > 0, the vertical displacement of pixels at the
+ *          top is downward.  Likewise, if @vmaxb > 0, the vertical
+ *          displacement of pixels at the bottom is downward.
+ *      (4) If @operation == L_SAMPLED, the dest pixels are taken from
+ *          the nearest src pixel.  Otherwise, we use linear interpolation
+ *          between pairs of sampled pixels.
+ *      (5) This is for quadratic shear.  For uniform (linear) shear,
+ *          use the standard shear operators.
+ */
+PIX *
+pixQuadraticVShear(PIX     *pixs,
+                   l_int32  dir,
+                   l_int32  vmaxt,
+                   l_int32  vmaxb,
+                   l_int32  operation,
+                   l_int32  incolor)
+{
+l_int32    w, h, d;
+
+    PROCNAME("pixQuadraticVShear");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 1, 8 or 32 bpp", procName, NULL);
+    if (dir != L_WARP_TO_LEFT && dir != L_WARP_TO_RIGHT)
+        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
+    if (operation != L_SAMPLED && operation != L_INTERPOLATED)
+        return (PIX *)ERROR_PTR("invalid operation", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+
+    if (vmaxt == 0 && vmaxb == 0)
+        return pixCopy(NULL, pixs);
+
+    if (operation == L_INTERPOLATED && d == 1) {
+        L_WARNING("no interpolation for 1 bpp; using sampling\n", procName);
+        operation = L_SAMPLED;
+    }
+
+    if (operation == L_SAMPLED)
+        return pixQuadraticVShearSampled(pixs, dir, vmaxt, vmaxb, incolor);
+    else  /* operation == L_INTERPOLATED */
+        return pixQuadraticVShearLI(pixs, dir, vmaxt, vmaxb, incolor);
+}
+
+
+/*!
+ *  pixQuadraticVShearSampled()
+ *
+ *      Input:  pixs (1, 8 or 32 bpp)
+ *              dir (L_WARP_TO_LEFT or L_WARP_TO_RIGHT)
+ *              vmaxt (max vertical displacement at edge and at top)
+ *              vmaxb (max vertical displacement at edge and at bottom)
+ *              incolor (L_BRING_IN_WHITE or L_BRING_IN_BLACK)
+ *      Return: pixd (stretched), or null on error
+ *
+ *  Notes:
+ *      (1) See pixQuadraticVShear() for details.
+ */
+PIX *
+pixQuadraticVShearSampled(PIX     *pixs,
+                          l_int32  dir,
+                          l_int32  vmaxt,
+                          l_int32  vmaxb,
+                          l_int32  incolor)
+{
+l_int32    i, j, id, w, h, d, wm, hm, wpls, wpld, val;
+l_uint32  *datas, *datad, *lines, *lined;
+l_float32  delrowt, delrowb, denom1, denom2, dely;
+PIX       *pixd;
+
+    PROCNAME("pixQuadraticVShearSampled");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d != 1 && d != 8 && d != 32)
+        return (PIX *)ERROR_PTR("pixs not 1, 8 or 32 bpp", procName, NULL);
+    if (dir != L_WARP_TO_LEFT && dir != L_WARP_TO_RIGHT)
+        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+
+    if (vmaxt == 0 && vmaxb == 0)
+        return pixCopy(NULL, pixs);
+
+    pixd = pixCreateTemplate(pixs);
+    pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
+    datas = pixGetData(pixs);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pixs);
+    wpld = pixGetWpl(pixd);
+    wm = w - 1;
+    hm = h - 1;
+    denom1 = 1. / (l_float32)h;
+    denom2 = 1. / (l_float32)(wm * wm);
+    for (j = 0; j < w; j++) {
+        if (dir == L_WARP_TO_LEFT) {
+            delrowt = (l_float32)(vmaxt * (wm - j) * (wm - j)) * denom2;
+            delrowb = (l_float32)(vmaxb * (wm - j) * (wm - j)) * denom2;
+        } else if (dir == L_WARP_TO_RIGHT) {
+            delrowt = (l_float32)(vmaxt * j * j) * denom2;
+            delrowb = (l_float32)(vmaxb * j * j) * denom2;
+        }
+        switch (d)
+        {
+        case 1:
+            for (id = 0; id < h; id++) {
+                dely = (delrowt * (hm - id) + delrowb * id) * denom1;
+                i = id - (l_int32)(dely + 0.5);
+                if (i < 0 || i > hm) continue;
+                lines = datas + i * wpls;
+                lined = datad + id * wpld;
+                val = GET_DATA_BIT(lines, j);
+                if (val)
+                    SET_DATA_BIT(lined, j);
+            }
+            break;
+        case 8:
+            for (id = 0; id < h; id++) {
+                dely = (delrowt * (hm - id) + delrowb * id) * denom1;
+                i = id - (l_int32)(dely + 0.5);
+                if (i < 0 || i > hm) continue;
+                lines = datas + i * wpls;
+                lined = datad + id * wpld;
+                val = GET_DATA_BYTE(lines, j);
+                SET_DATA_BYTE(lined, j, val);
+            }
+            break;
+        case 32:
+            for (id = 0; id < h; id++) {
+                dely = (delrowt * (hm - id) + delrowb * id) * denom1;
+                i = id - (l_int32)(dely + 0.5);
+                if (i < 0 || i > hm) continue;
+                lines = datas + i * wpls;
+                lined = datad + id * wpld;
+                lined[j] = lines[j];
+            }
+            break;
+        default:
+            L_ERROR("invalid depth: %d\n", procName, d);
+            pixDestroy(&pixd);
+            return NULL;
+        }
+    }
+
+    return pixd;
+}
+
+
+/*!
+ *  pixQuadraticVShearLI()
+ *
+ *      Input:  pixs (8 or 32 bpp, or colormapped)
+ *              dir (L_WARP_TO_LEFT or L_WARP_TO_RIGHT)
+ *              vmaxt (max vertical displacement at edge and at top)
+ *              vmaxb (max vertical displacement at edge and at bottom)
+ *              incolor (L_BRING_IN_WHITE or L_BRING_IN_BLACK)
+ *      Return: pixd (stretched), or null on error
+ *
+ *  Notes:
+ *      (1) See pixQuadraticVShear() for details.
+ */
+PIX *
+pixQuadraticVShearLI(PIX     *pixs,
+                     l_int32  dir,
+                     l_int32  vmaxt,
+                     l_int32  vmaxb,
+                     l_int32  incolor)
+{
+l_int32    i, j, id, yp, yf, w, h, d, wm, hm, wpls, wpld;
+l_int32    val, rval, gval, bval;
+l_uint32   word0, word1;
+l_uint32  *datas, *datad, *lines, *lined;
+l_float32  delrowt, delrowb, denom1, denom2, dely;
+PIX       *pix, *pixd;
+PIXCMAP   *cmap;
+
+    PROCNAME("pixQuadraticVShearLI");
+
+    if (!pixs)
+        return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, &d);
+    if (d == 1)
+        return (PIX *)ERROR_PTR("pixs is 1 bpp", procName, NULL);
+    cmap = pixGetColormap(pixs);
+    if (d != 8 && d != 32 && !cmap)
+        return (PIX *)ERROR_PTR("pixs not 8, 32 bpp, or cmap", procName, NULL);
+    if (dir != L_WARP_TO_LEFT && dir != L_WARP_TO_RIGHT)
+        return (PIX *)ERROR_PTR("invalid direction", procName, NULL);
+    if (incolor != L_BRING_IN_WHITE && incolor != L_BRING_IN_BLACK)
+        return (PIX *)ERROR_PTR("invalid incolor", procName, NULL);
+
+    if (vmaxt == 0 && vmaxb == 0)
+        return pixCopy(NULL, pixs);
+
+        /* Remove any existing colormap */
+    if (cmap)
+        pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    else
+        pix = pixClone(pixs);
+    d = pixGetDepth(pix);
+    if (d != 8 && d != 32) {
+        pixDestroy(&pix);
+        return (PIX *)ERROR_PTR("invalid depth", procName, NULL);
+    }
+
+        /* Standard linear interp: subdivide each pixel into 64 parts */
+    pixd = pixCreateTemplate(pix);
+    pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
+    datas = pixGetData(pix);
+    datad = pixGetData(pixd);
+    wpls = pixGetWpl(pix);
+    wpld = pixGetWpl(pixd);
+    wm = w - 1;
+    hm = h - 1;
+    denom1 = 1.0 / (l_float32)h;
+    denom2 = 1.0 / (l_float32)(wm * wm);
+    for (j = 0; j < w; j++) {
+        if (dir == L_WARP_TO_LEFT) {
+            delrowt = (l_float32)(vmaxt * (wm - j) * (wm - j)) * denom2;
+            delrowb = (l_float32)(vmaxb * (wm - j) * (wm - j)) * denom2;
+        } else if (dir == L_WARP_TO_RIGHT) {
+            delrowt = (l_float32)(vmaxt * j * j) * denom2;
+            delrowb = (l_float32)(vmaxb * j * j) * denom2;
+        }
+        switch (d)
+        {
+        case 8:
+            for (id = 0; id < h; id++) {
+                dely = (delrowt * (hm - id) + delrowb * id) * denom1;
+                i = 64 * id - (l_int32)(64.0 * dely);
+                yp = i / 64;
+                yf = i & 63;
+                if (yp < 0 || yp > hm) continue;
+                lines = datas + yp * wpls;
+                lined = datad + id * wpld;
+                if (yp < hm) {
+                    val = ((63 - yf) * GET_DATA_BYTE(lines, j) +
+                           yf * GET_DATA_BYTE(lines + wpls, j) + 31) / 63;
+                } else {  /* yp == hm */
+                    val = GET_DATA_BYTE(lines, j);
+                }
+                SET_DATA_BYTE(lined, j, val);
+            }
+            break;
+        case 32:
+            for (id = 0; id < h; id++) {
+                dely = (delrowt * (hm - id) + delrowb * id) * denom1;
+                i = 64 * id - (l_int32)(64.0 * dely);
+                yp = i / 64;
+                yf = i & 63;
+                if (yp < 0 || yp > hm) continue;
+                lines = datas + yp * wpls;
+                lined = datad + id * wpld;
+                if (yp < hm) {
+                    word0 = *(lines + j);
+                    word1 = *(lines + wpls + j);
+                    rval = ((63 - yf) * ((word0 >> L_RED_SHIFT) & 0xff) +
+                           yf * ((word1 >> L_RED_SHIFT) & 0xff) + 31) / 63;
+                    gval = ((63 - yf) * ((word0 >> L_GREEN_SHIFT) & 0xff) +
+                           yf * ((word1 >> L_GREEN_SHIFT) & 0xff) + 31) / 63;
+                    bval = ((63 - yf) * ((word0 >> L_BLUE_SHIFT) & 0xff) +
+                           yf * ((word1 >> L_BLUE_SHIFT) & 0xff) + 31) / 63;
+                    composeRGBPixel(rval, gval, bval, lined + j);
+                } else {  /* yp == hm */
+                    lined[j] = lines[j];
+                }
+            }
+            break;
+        default:
+            L_ERROR("invalid depth: %d\n", procName, d);
+            pixDestroy(&pix);
+            pixDestroy(&pixd);
+            return NULL;
+        }
+    }
+
+    pixDestroy(&pix);
+    return pixd;
+}
+
+
+/*----------------------------------------------------------------------*
+ *                     Stereo from a pair of images                     *
+ *----------------------------------------------------------------------*/
+/*!
+ *  pixStereoFromPair()
+ *
+ *      Input:  pix1 (32 bpp rgb)
+ *              pix2 (32 bpp rgb)
+ *              rwt, gwt, bwt (weighting factors used for each component in
+                               pix1 to determine the output red channel)
+ *      Return: pixd (stereo enhanced), or null on error
+ *
+ *  Notes:
+ *      (1) pix1 and pix2 are a pair of stereo images, ideally taken
+ *          concurrently in the same plane, with some lateral translation.
+ *      (2) The output red channel is determined from @pix1.
+ *          The output green and blue channels are taken from the green
+ *          and blue channels, respectively, of @pix2.
+ *      (3) The weights determine how much of each component in @pix1
+ *          goes into the output red channel.  The sum of weights
+ *          must be 1.0.  If it's not, we scale the weights to
+ *          satisfy this criterion.
+ *      (4) The most general pixel mapping allowed here is:
+ *            rval = rwt * r1 + gwt * g1 + bwt * b1  (from pix1)
+ *            gval = g2   (from pix2)
+ *            bval = b2   (from pix2)
+ *      (5) The simplest method is to use rwt = 1.0, gwt = 0.0, bwt = 0.0,
+ *          but this causes unpleasant visual artifacts with red in the image.
+ *          Use of green and blue from @pix1 in the red channel,
+ *          instead of red, tends to fix that problem.
+ */
+PIX *
+pixStereoFromPair(PIX       *pix1,
+                  PIX       *pix2,
+                  l_float32  rwt,
+                  l_float32  gwt,
+                  l_float32  bwt)
+{
+l_int32    i, j, w, h, wpl1, wpl2, rval, gval, bval;
+l_uint32   word1, word2;
+l_uint32  *data1, *data2, *datad, *line1, *line2, *lined;
+l_float32  sum;
+PIX       *pixd;
+
+    PROCNAME("pixStereoFromPair");
+
+    if (!pix1 || !pix2)
+        return (PIX *)ERROR_PTR("pix1, pix2 not both defined", procName, NULL);
+    if (pixGetDepth(pix1) != 32 || pixGetDepth(pix2) != 32)
+        return (PIX *)ERROR_PTR("pix1, pix2 not both 32 bpp", procName, NULL);
+
+        /* Make sure the sum of weights is 1.0; otherwise, you can get
+         * overflow in the gray value. */
+    if (rwt == 0.0 && gwt == 0.0 && bwt == 0.0) {
+        rwt = L_DEFAULT_RED_WEIGHT;
+        gwt = L_DEFAULT_GREEN_WEIGHT;
+        bwt = L_DEFAULT_BLUE_WEIGHT;
+    }
+    sum = rwt + gwt + bwt;
+    if (L_ABS(sum - 1.0) > 0.0001) {  /* maintain ratios with sum == 1.0 */
+        L_WARNING("weights don't sum to 1; maintaining ratios\n", procName);
+        rwt = rwt / sum;
+        gwt = gwt / sum;
+        bwt = bwt / sum;
+    }
+
+    pixGetDimensions(pix1, &w, &h, NULL);
+    pixd = pixCreateTemplate(pix1);
+    data1 = pixGetData(pix1);
+    data2 = pixGetData(pix2);
+    datad = pixGetData(pixd);
+    wpl1 = pixGetWpl(pix1);
+    wpl2 = pixGetWpl(pix2);
+    for (i = 0; i < h; i++) {
+        line1 = data1 + i * wpl1;
+        line2 = data2 + i * wpl2;
+        lined = datad + i * wpl1;  /* wpl1 works for pixd */
+        for (j = 0; j < w; j++) {
+            word1 = *(line1 + j);
+            word2 = *(line2 + j);
+            rval = (l_int32)(rwt * ((word1 >> L_RED_SHIFT) & 0xff) +
+                             gwt * ((word1 >> L_GREEN_SHIFT) & 0xff) +
+                             bwt * ((word1 >> L_BLUE_SHIFT) & 0xff) + 0.5);
+            gval = (word2 >> L_GREEN_SHIFT) & 0xff;
+            bval = (word2 >> L_BLUE_SHIFT) & 0xff;
+            composeRGBPixel(rval, gval, bval, lined + j);
+        }
+    }
+
+    return pixd;
+}
diff --git a/src/watershed.c b/src/watershed.c
new file mode 100644 (file)
index 0000000..5ee792a
--- /dev/null
@@ -0,0 +1,1106 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  watershed.c
+ *
+ *      Top-level
+ *            L_WSHED         *wshedCreate()
+ *            void             wshedDestroy()
+ *            l_int32          wshedApply()
+ *
+ *      Helpers
+ *            static l_int32   identifyWatershedBasin()
+ *            static l_int32   mergeLookup()
+ *            static l_int32   wshedGetHeight()
+ *            static void      pushNewPixel()
+ *            static void      popNewPixel()
+ *            static void      pushWSPixel()
+ *            static void      popWSPixel()
+ *            static void      debugPrintLUT()
+ *            static void      debugWshedMerge()
+ *
+ *      Output
+ *            l_int32          wshedBasins()
+ *            PIX             *wshedRenderFill()
+ *            PIX             *wshedRenderColors()
+ *
+ *  The watershed function identifies the "catch basins" of the input
+ *  8 bpp image, with respect to the specified seeds or "markers".
+ *  The use is in segmentation, but the selection of the markers is
+ *  critical to getting meaningful results.
+ *
+ *  How are the markers selected?  You can't simply use the local
+ *  minima, because a typical image has sufficient noise so that
+ *  a useful catch basin can easily have multiple local minima.  However
+ *  they are selected, the question for the watershed function is
+ *  how to handle local minima that are not markers.  The reason
+ *  this is important is because of the algorithm used to find the
+ *  watersheds, which is roughly like this:
+ *
+ *    (1) Identify the markers and the local minima, and enter them
+ *        into a priority queue based on the pixel value.  Each marker
+ *        is shrunk to a single pixel, if necessary, before the
+ *        operation starts.
+ *    (2) Feed the priority queue with neighbors of pixels that are
+ *        popped off the queue.  Each of these queue pixels is labelled
+ *        with the index value of its parent.
+ *    (3) Each pixel is also labelled, in a 32-bit image, with the marker
+ *        or local minimum index, from which it was originally derived.
+ *    (4) There are actually 3 classes of labels: seeds, minima, and
+ *        fillers.  The fillers are labels of regions that have already
+ *        been identified as watersheds and are continuing to fill, for
+ *        the purpose of finding higher watersheds.
+ *    (5) When a pixel is popped that has already been labelled in the
+ *        32-bit image and that label differs from the label of its
+ *        parent (stored in the queue pixel), a boundary has been crossed.
+ *        There are several cases:
+ *         (a) Both parents are derived from markers but at least one
+ *             is not deep enough to become a watershed.  Absorb the
+ *             shallower basin into the deeper one, fixing the LUT to
+ *             redirect the shallower index to the deeper one.
+ *         (b) Both parents are derived from markers and both are deep
+ *             enough.  Identify and save the watershed for each marker.
+ *         (c) One parent was derived from a marker and the other from
+ *             a minima: absorb the minima basin into the marker basin.
+ *         (d) One parent was derived from a marker and the other is
+ *             a filler: identify and save the watershed for the marker.
+ *         (e) Both parents are derived from minima: merge them.
+ *         (f) One parent is a filler and the other is derived from a
+ *             minima: merge the minima into the filler.
+ *    (6) The output of the watershed operation consists of:
+ *         - a pixa of the basins
+ *         - a pta of the markers
+ *         - a numa of the watershed levels
+ *
+ *  Typical usage:
+ *      L_WShed *wshed = wshedCreate(pixs, pixseed, mindepth, 0);
+ *      wshedApply(wshed);
+*
+ *      wshedBasins(wshed, &pixa, &nalevels);
+ *        ... do something with pixa, nalevels ...
+ *      pixaDestroy(&pixa);
+ *      numaDestroy(&nalevels);
+ *
+ *      Pix *pixd = wshedRenderFill(wshed);
+ *
+ *      wshedDestroy(&wshed);
+ */
+
+#include "allheaders.h"
+
+#ifndef  NO_CONSOLE_IO
+#define   DEBUG_WATERSHED     0
+#endif  /* ~NO_CONSOLE_IO */
+
+static const l_uint32  MAX_LABEL_VALUE = 0x7fffffff;  /* largest l_int32 */
+
+struct L_NewPixel
+{
+    l_int32    x;
+    l_int32    y;
+};
+typedef struct L_NewPixel  L_NEWPIXEL;
+
+struct L_WSPixel
+{
+    l_float32  val;    /* pixel value */
+    l_int32    x;
+    l_int32    y;
+    l_int32    index;  /* label for set to which pixel belongs */
+};
+typedef struct L_WSPixel  L_WSPIXEL;
+
+
+    /* Static functions for obtaining bitmap of watersheds  */
+static void wshedSaveBasin(L_WSHED *wshed, l_int32 index, l_int32 level);
+
+static l_int32 identifyWatershedBasin(L_WSHED *wshed,
+                                      l_int32 index, l_int32 level,
+                                      BOX **pbox, PIX **ppixd);
+
+    /* Static function for merging lut and backlink arrays */
+static l_int32 mergeLookup(L_WSHED *wshed, l_int32 sindex, l_int32 dindex);
+
+    /* Static function for finding the height of the current pixel
+       above its seed or minima in the watershed.  */
+static l_int32 wshedGetHeight(L_WSHED *wshed, l_int32 val, l_int32 label,
+                              l_int32 *pheight);
+
+    /* Static accessors for NewPixel on a queue */
+static void pushNewPixel(L_QUEUE *lq, l_int32 x, l_int32 y,
+                         l_int32 *pminx, l_int32 *pmaxx,
+                         l_int32 *pminy, l_int32 *pmaxy);
+static void popNewPixel(L_QUEUE *lq, l_int32 *px, l_int32 *py);
+
+    /* Static accessors for WSPixel on a heap */
+static void pushWSPixel(L_HEAP *lh, L_STACK *stack, l_int32 val,
+                        l_int32 x, l_int32 y, l_int32 index);
+static void popWSPixel(L_HEAP *lh, L_STACK *stack, l_int32 *pval,
+                       l_int32 *px, l_int32 *py, l_int32 *pindex);
+
+    /* Static debug print output */
+static void debugPrintLUT(l_int32 *lut, l_int32 size, l_int32 debug);
+
+static void debugWshedMerge(L_WSHED *wshed, char *descr, l_int32 x,
+                            l_int32 y, l_int32 label, l_int32 index);
+
+
+/*-----------------------------------------------------------------------*
+ *                        Top-level watershed                            *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  wshedCreate()
+ *
+ *      Input:  pixs  (8 bpp source)
+ *              pixm  (1 bpp 'marker' seed)
+ *              mindepth (minimum depth; anything less is not saved)
+ *              debugflag (1 for debug output)
+ *      Return: WShed, or null on error
+ *
+ *  Notes:
+ *      (1) It is not necessary for the fg pixels in the seed image
+ *          be at minima, or that they be isolated.  We extract a
+ *          single pixel from each connected component, and a seed
+ *          anywhere in a watershed will eventually label the watershed
+ *          when the filling level reaches it.
+ *      (2) Set mindepth to some value to ignore noise in pixs that
+ *          can create small local minima.  Any watershed shallower
+ *          than mindepth, even if it has a seed, will not be saved;
+ *          It will either be incorporated in another watershed or
+ *          eliminated.
+ */
+L_WSHED *
+wshedCreate(PIX     *pixs,
+            PIX     *pixm,
+            l_int32  mindepth,
+            l_int32  debugflag)
+{
+l_int32   w, h;
+L_WSHED  *wshed;
+
+    PROCNAME("wshedCreate");
+
+    if (!pixs)
+        return (L_WSHED *)ERROR_PTR("pixs is not defined", procName, NULL);
+    if (pixGetDepth(pixs) != 8)
+        return (L_WSHED *)ERROR_PTR("pixs is not 8 bpp", procName, NULL);
+    if (!pixm)
+        return (L_WSHED *)ERROR_PTR("pixm is not defined", procName, NULL);
+    if (pixGetDepth(pixm) != 1)
+        return (L_WSHED *)ERROR_PTR("pixm is not 1 bpp", procName, NULL);
+    pixGetDimensions(pixs, &w, &h, NULL);
+    if (pixGetWidth(pixm) != w || pixGetHeight(pixm) != h)
+        return (L_WSHED *)ERROR_PTR("pixs/m sizes are unequal", procName, NULL);
+
+    if ((wshed = (L_WSHED *)LEPT_CALLOC(1, sizeof(L_WSHED))) == NULL)
+        return (L_WSHED *)ERROR_PTR("wshed not made", procName, NULL);
+
+    wshed->pixs = pixClone(pixs);
+    wshed->pixm = pixClone(pixm);
+    wshed->mindepth = L_MAX(1, mindepth);
+    wshed->pixlab = pixCreate(w, h, 32);
+    pixSetAllArbitrary(wshed->pixlab, MAX_LABEL_VALUE);
+    wshed->pixt = pixCreate(w, h, 1);
+    wshed->lines8 = pixGetLinePtrs(pixs, NULL);
+    wshed->linem1 = pixGetLinePtrs(pixm, NULL);
+    wshed->linelab32 = pixGetLinePtrs(wshed->pixlab, NULL);
+    wshed->linet1 = pixGetLinePtrs(wshed->pixt, NULL);
+    wshed->debug = debugflag;
+    return wshed;
+}
+
+
+/*!
+ *  wshedDestroy()
+ *
+ *      Input:  &wshed (<will be set to null before returning>)
+ *      Return: void
+ */
+void
+wshedDestroy(L_WSHED  **pwshed)
+{
+l_int32   i;
+L_WSHED  *wshed;
+
+    PROCNAME("wshedDestroy");
+
+    if (pwshed == NULL) {
+        L_WARNING("ptr address is null!\n", procName);
+        return;
+    }
+
+    if ((wshed = *pwshed) == NULL)
+        return;
+
+    pixDestroy(&wshed->pixs);
+    pixDestroy(&wshed->pixm);
+    pixDestroy(&wshed->pixlab);
+    pixDestroy(&wshed->pixt);
+    if (wshed->lines8) LEPT_FREE(wshed->lines8);
+    if (wshed->linem1) LEPT_FREE(wshed->linem1);
+    if (wshed->linelab32) LEPT_FREE(wshed->linelab32);
+    if (wshed->linet1) LEPT_FREE(wshed->linet1);
+    pixaDestroy(&wshed->pixad);
+    ptaDestroy(&wshed->ptas);
+    numaDestroy(&wshed->nash);
+    numaDestroy(&wshed->nasi);
+    numaDestroy(&wshed->namh);
+    numaDestroy(&wshed->nalevels);
+    if (wshed->lut)
+         LEPT_FREE(wshed->lut);
+    if (wshed->links) {
+        for (i = 0; i < wshed->arraysize; i++)
+            numaDestroy(&wshed->links[i]);
+        LEPT_FREE(wshed->links);
+    }
+    LEPT_FREE(wshed);
+    *pwshed = NULL;
+    return;
+}
+
+
+/*!
+ *  wshedApply()
+ *
+ *      Input:  wshed (generated from wshedCreate())
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Iportant note:
+ *      (1) This is buggy.  It seems to locate watersheds that are
+ *          duplicates.  The watershed extraction after complete fill
+ *          grabs some regions belonging to existing watersheds.
+ *          See prog/watershedtest.c for testing.
+ */
+l_int32
+wshedApply(L_WSHED  *wshed)
+{
+char      two_new_watersheds[] = "Two new watersheds";
+char      seed_absorbed_into_seeded_basin[] = "Seed absorbed into seeded basin";
+char      one_new_watershed_label[] = "One new watershed (label)";
+char      one_new_watershed_index[] = "One new watershed (index)";
+char      minima_absorbed_into_seeded_basin[] =
+                 "Minima absorbed into seeded basin";
+char      minima_absorbed_by_filler_or_another[] =
+                 "Minima absorbed by filler or another";
+l_int32   nseeds, nother, nboth, arraysize;
+l_int32   i, j, val, x, y, w, h, index, mindepth;
+l_int32   imin, imax, jmin, jmax, cindex, clabel, nindex;
+l_int32   hindex, hlabel, hmin, hmax, minhindex, maxhindex;
+l_int32  *lut;
+l_uint32  ulabel, uval;
+void    **lines8, **linelab32;
+NUMA     *nalut, *nalevels, *nash, *namh, *nasi;
+NUMA    **links;
+L_HEAP   *lh;
+PIX      *pixmin, *pixsd;
+PIXA     *pixad;
+L_STACK  *rstack;
+PTA      *ptas, *ptao;
+
+    PROCNAME("wshedApply");
+
+    if (!wshed)
+        return ERROR_INT("wshed not defined", procName, 1);
+
+    /* ------------------------------------------------------------ *
+     *  Initialize priority queue and pixlab with seeds and minima  *
+     * ------------------------------------------------------------ */
+
+    lh = lheapCreate(0, L_SORT_INCREASING);  /* remove lowest values first */
+    rstack = lstackCreate(0);  /* for reusing the WSPixels */
+    pixGetDimensions(wshed->pixs, &w, &h, NULL);
+    lines8 = wshed->lines8;  /* wshed owns this */
+    linelab32 = wshed->linelab32;  /* ditto */
+
+        /* Identify seed (marker) pixels, 1 for each c.c. in pixm */
+    pixSelectMinInConnComp(wshed->pixs, wshed->pixm, &ptas, &nash);
+    pixsd = pixGenerateFromPta(ptas, w, h);
+    nseeds = ptaGetCount(ptas);
+    for (i = 0; i < nseeds; i++) {
+        ptaGetIPt(ptas, i, &x, &y);
+        uval = GET_DATA_BYTE(lines8[y], x);
+        pushWSPixel(lh, rstack, (l_int32)uval, x, y, i);
+    }
+    wshed->ptas = ptas;
+    nasi = numaMakeConstant(1, nseeds);  /* indicator array */
+    wshed->nasi = nasi;
+    wshed->nash = nash;
+    wshed->nseeds = nseeds;
+
+        /* Identify minima that are not seeds.  Use these 4 steps:
+         *  (1) Get the local minima, which can have components
+         *      of arbitrary size.  This will be a clipping mask.
+         *  (2) Get the image of the actual seeds (pixsd)
+         *  (3) Remove all elements of the clipping mask that have a seed.
+         *  (4) Shrink each of the remaining elements of the minima mask
+         *      to a single pixel.  */
+    pixLocalExtrema(wshed->pixs, 200, 0, &pixmin, NULL);
+    pixRemoveSeededComponents(pixmin, pixsd, pixmin, 8, 2);
+    pixSelectMinInConnComp(wshed->pixs, pixmin, &ptao, &namh);
+    nother = ptaGetCount(ptao);
+    for (i = 0; i < nother; i++) {
+        ptaGetIPt(ptao, i, &x, &y);
+        uval = GET_DATA_BYTE(lines8[y], x);
+        pushWSPixel(lh, rstack, (l_int32)uval, x, y, nseeds + i);
+    }
+    wshed->namh = namh;
+
+    /* ------------------------------------------------------------ *
+     *                Initialize merging lookup tables              *
+     * ------------------------------------------------------------ */
+
+        /* nalut should always give the current after-merging index.
+         * links are effectively backpointers: they are numas associated with
+         * a dest index of all indices in nalut that point to that index. */
+    mindepth = wshed->mindepth;
+    nboth = nseeds + nother;
+    arraysize = 2 * nboth;
+    wshed->arraysize = arraysize;
+    nalut = numaMakeSequence(0, 1, arraysize);
+    lut = numaGetIArray(nalut);
+    wshed->lut = lut;  /* wshed owns this */
+    links = (NUMA **)LEPT_CALLOC(arraysize, sizeof(NUMA *));
+    wshed->links = links;  /* wshed owns this */
+    nindex = nseeds + nother;  /* the next unused index value */
+
+    /* ------------------------------------------------------------ *
+     *              Fill the basins, using the priority queue       *
+     * ------------------------------------------------------------ */
+
+    pixad = pixaCreate(nseeds);
+    wshed->pixad = pixad;  /* wshed owns this */
+    nalevels = numaCreate(nseeds);
+    wshed->nalevels = nalevels;  /* wshed owns this */
+    L_INFO("nseeds = %d, nother = %d\n", procName, nseeds, nother);
+    while (lheapGetCount(lh) > 0) {
+        popWSPixel(lh, rstack, &val, &x, &y, &index);
+/*        fprintf(stderr, "x = %d, y = %d, index = %d\n", x, y, index); */
+        ulabel = GET_DATA_FOUR_BYTES(linelab32[y], x);
+        if (ulabel == MAX_LABEL_VALUE)
+            clabel = ulabel;
+        else
+            clabel = lut[ulabel];
+        cindex = lut[index];
+        if (clabel == cindex) continue;  /* have already seen this one */
+        if (clabel == MAX_LABEL_VALUE) {  /* new one; assign index and try to
+                                           * propagate to all neighbors */
+            SET_DATA_FOUR_BYTES(linelab32[y], x, cindex);
+            imin = L_MAX(0, y - 1);
+            imax = L_MIN(h - 1, y + 1);
+            jmin = L_MAX(0, x - 1);
+            jmax = L_MIN(w - 1, x + 1);
+            for (i = imin; i <= imax; i++) {
+                for (j = jmin; j <= jmax; j++) {
+                    if (i == y && j == x) continue;
+                    uval = GET_DATA_BYTE(lines8[i], j);
+                    pushWSPixel(lh, rstack, (l_int32)uval, j, i, cindex);
+                }
+            }
+        } else {  /* pixel is already labeled (differently); must resolve */
+
+                /* If both indices are seeds, check if the min height is
+                 * greater than mindepth.  If so, we have two new watersheds;
+                 * locate them and assign to both regions a new index
+                 * for further waterfill.  If not, absorb the shallower
+                 * watershed into the deeper one and continue filling it. */
+            pixGetPixel(pixsd, x, y, &uval);
+            if (clabel < nseeds && cindex < nseeds) {
+                wshedGetHeight(wshed, val, clabel, &hlabel);
+                wshedGetHeight(wshed, val, cindex, &hindex);
+                hmin = L_MIN(hlabel, hindex);
+                hmax = L_MAX(hlabel, hindex);
+                if (hmin == hmax) {
+                    hmin = hlabel;
+                    hmax = hindex;
+                }
+                if (wshed->debug) {
+                    fprintf(stderr, "clabel,hlabel = %d,%d\n", clabel, hlabel);
+                    fprintf(stderr, "hmin = %d, hmax = %d\n", hmin, hmax);
+                    fprintf(stderr, "cindex,hindex = %d,%d\n", cindex, hindex);
+                    if (hmin < mindepth)
+                        fprintf(stderr, "Too shallow!\n");
+                }
+
+                if (hmin >= mindepth) {
+                    debugWshedMerge(wshed, two_new_watersheds,
+                                    x, y, clabel, cindex);
+                    wshedSaveBasin(wshed, cindex, val - 1);
+                    wshedSaveBasin(wshed, clabel, val - 1);
+                    numaSetValue(nasi, cindex, 0);
+                    numaSetValue(nasi, clabel, 0);
+
+                    if (wshed->debug) fprintf(stderr, "nindex = %d\n", nindex);
+                    debugPrintLUT(lut, nindex, wshed->debug);
+                    mergeLookup(wshed, clabel, nindex);
+                    debugPrintLUT(lut, nindex, wshed->debug);
+                    mergeLookup(wshed, cindex, nindex);
+                    debugPrintLUT(lut, nindex, wshed->debug);
+                    nindex++;
+                } else  /* extraneous seed within seeded basin; absorb */ {
+                    debugWshedMerge(wshed, seed_absorbed_into_seeded_basin,
+                                    x, y, clabel, cindex);
+                }
+                maxhindex = clabel;  /* TODO: is this part of above 'else'? */
+                minhindex = cindex;
+                if (hindex > hlabel) {
+                    maxhindex = cindex;
+                    minhindex = clabel;
+                }
+                mergeLookup(wshed, minhindex, maxhindex);
+            } else if (clabel < nseeds && cindex >= nboth) {
+                /* If one index is a seed and the other is a merge of
+                 * 2 watersheds, generate a single watershed. */
+                debugWshedMerge(wshed, one_new_watershed_label,
+                                x, y, clabel, cindex);
+                wshedSaveBasin(wshed, clabel, val - 1);
+                numaSetValue(nasi, clabel, 0);
+                mergeLookup(wshed, clabel, cindex);
+            } else if (cindex < nseeds && clabel >= nboth) {
+                debugWshedMerge(wshed, one_new_watershed_index,
+                                x, y, clabel, cindex);
+                wshedSaveBasin(wshed, cindex, val - 1);
+                numaSetValue(nasi, cindex, 0);
+                mergeLookup(wshed, cindex, clabel);
+            } else if (clabel < nseeds) {  /* cindex from minima; absorb */
+                /* If one index is a seed and the other is from a minimum,
+                 * merge the minimum wshed into the seed wshed. */
+                debugWshedMerge(wshed, minima_absorbed_into_seeded_basin,
+                                x, y, clabel, cindex);
+                mergeLookup(wshed, cindex, clabel);
+            } else if (cindex < nseeds) {  /* clabel from minima; absorb */
+                debugWshedMerge(wshed, minima_absorbed_into_seeded_basin,
+                                x, y, clabel, cindex);
+                mergeLookup(wshed, clabel, cindex);
+            } else {  /* If neither index is a seed, just merge */
+                debugWshedMerge(wshed, minima_absorbed_by_filler_or_another,
+                                x, y, clabel, cindex);
+                mergeLookup(wshed, clabel, cindex);
+            }
+        }
+    }
+
+#if 0
+        /*  Use the indicator array to save any watersheds that fill
+         *  to the maximum value.  This seems to screw things up!  */
+    for (i = 0; i < nseeds; i++) {
+        numaGetIValue(nasi, i, &ival);
+        if (ival == 1) {
+            wshedSaveBasin(wshed, lut[i], val - 1);
+            numaSetValue(nasi, i, 0);
+        }
+    }
+#endif
+
+    numaDestroy(&nalut);
+    pixDestroy(&pixmin);
+    pixDestroy(&pixsd);
+    ptaDestroy(&ptao);
+    lheapDestroy(&lh, TRUE);
+    lstackDestroy(&rstack, TRUE);
+    return 0;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                               Helpers                                 *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  wshedSaveBasin()
+ *
+ *      Input:  wshed
+ *              index (index of basin to be located)
+ *              level (filling level reached at the time this function
+ *                     is called)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This identifies a single watershed.  It does not change
+ *          the LUT, which must be done subsequently.
+ *      (2) The fill level of a basin is taken to be @level - 1.
+ */
+static void
+wshedSaveBasin(L_WSHED  *wshed,
+               l_int32   index,
+               l_int32   level)
+{
+BOX  *box;
+PIX  *pix;
+
+    PROCNAME("wshedSaveBasin");
+
+    if (!wshed) {
+        L_ERROR("wshed not defined\n", procName);
+        return;
+    }
+
+    if (identifyWatershedBasin(wshed, index, level, &box, &pix) == 0) {
+        pixaAddPix(wshed->pixad, pix, L_INSERT);
+        pixaAddBox(wshed->pixad, box, L_INSERT);
+        numaAddNumber(wshed->nalevels, level - 1);
+    }
+    return;
+}
+
+
+/*!
+ *  identifyWatershedBasin()
+ *
+ *      Input:  wshed
+ *              index (index of basin to be located)
+ *              level (of basin at point at which the two basins met)
+ *              &box (<return> bounding box of basin)
+ *              &pixd (<return> pix of basin, cropped to its bounding box)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) This is a static function, so we assume pixlab, pixs and pixt
+ *          exist and are the same size.
+ *      (2) It selects all pixels that have the label @index in pixlab
+ *          and that have a value in pixs that is less than @level.
+ *      (3) It is used whenever two seeded basins meet (typically at a saddle),
+ *          or when one seeded basin meets a 'filler'.  All identified
+ *          basins are saved as a watershed.
+ */
+static l_int32
+identifyWatershedBasin(L_WSHED  *wshed,
+                       l_int32   index,
+                       l_int32   level,
+                       BOX     **pbox,
+                       PIX     **ppixd)
+{
+l_int32   imin, imax, jmin, jmax, minx, miny, maxx, maxy;
+l_int32   bw, bh, i, j, w, h, x, y;
+l_int32  *lut;
+l_uint32  label, bval, lval;
+void    **lines8, **linelab32, **linet1;
+BOX      *box;
+PIX      *pixs, *pixt, *pixd;
+L_QUEUE  *lq;
+
+    PROCNAME("identifyWatershedBasin");
+
+    if (!pbox)
+        return ERROR_INT("&box not defined", procName, 1);
+    *pbox = NULL;
+    if (!ppixd)
+        return ERROR_INT("&pixd not defined", procName, 1);
+    *ppixd = NULL;
+    if (!wshed)
+        return ERROR_INT("wshed not defined", procName, 1);
+
+        /* Make a queue and an auxiliary stack */
+    lq = lqueueCreate(0);
+    lq->stack = lstackCreate(0);
+
+    pixs = wshed->pixs;
+    pixt = wshed->pixt;
+    lines8 = wshed->lines8;
+    linelab32 = wshed->linelab32;
+    linet1 = wshed->linet1;
+    lut = wshed->lut;
+    pixGetDimensions(pixs, &w, &h, NULL);
+
+        /* Prime the queue with the seed pixel for this watershed. */
+    minx = miny = 1000000;
+    maxx = maxy = 0;
+    ptaGetIPt(wshed->ptas, index, &x, &y);
+    pixSetPixel(pixt, x, y, 1);
+    pushNewPixel(lq, x, y, &minx, &maxx, &miny, &maxy);
+    if (wshed->debug) fprintf(stderr, "prime: (x,y) = (%d, %d)\n", x, y);
+
+        /* Each pixel in a spreading breadth-first search is inspected.
+         * It is accepted as part of this watershed, and pushed on
+         * the search queue, if:
+         *     (1) It has a label value equal to @index
+         *     (2) The pixel value is less than @level, the overflow
+         *         height at which the two basins join.
+         *     (3) It has not yet been seen in this search.  */
+    while (lqueueGetCount(lq) > 0) {
+        popNewPixel(lq, &x, &y);
+        imin = L_MAX(0, y - 1);
+        imax = L_MIN(h - 1, y + 1);
+        jmin = L_MAX(0, x - 1);
+        jmax = L_MIN(w - 1, x + 1);
+        for (i = imin; i <= imax; i++) {
+            for (j = jmin; j <= jmax; j++) {
+                if (j == x && i == y) continue;  /* parent */
+                label = GET_DATA_FOUR_BYTES(linelab32[i], j);
+                if (label == MAX_LABEL_VALUE || lut[label] != index) continue;
+                bval = GET_DATA_BIT(linet1[i], j);
+                if (bval == 1) continue;  /* already seen */
+                lval = GET_DATA_BYTE(lines8[i], j);
+                if (lval >= level) continue;  /* too high */
+                SET_DATA_BIT(linet1[i], j);
+                pushNewPixel(lq, j, i, &minx, &maxx, &miny, &maxy);
+            }
+        }
+    }
+
+        /* Extract the box and pix, and clear pixt */
+    bw = maxx - minx + 1;
+    bh = maxy - miny + 1;
+    box = boxCreate(minx, miny, bw, bh);
+    pixd = pixClipRectangle(pixt, box, NULL);
+    pixRasterop(pixt, minx, miny, bw, bh, PIX_SRC ^ PIX_DST, pixd, 0, 0);
+    *pbox = box;
+    *ppixd = pixd;
+
+    lqueueDestroy(&lq, 1);
+    return 0;
+}
+
+
+/*!
+ *  mergeLookup()
+ *
+ *      Input:  wshed
+ *              sindex (primary index being changed in the merge)
+ *              dindex (index that @sindex will point to after the merge)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The links are a sparse array of Numas showing current back-links.
+ *          The lut gives the current index (of the seed or the minima
+ *          for the wshed  in which it is located.
+ *      (2) Think of each entry in the lut.  There are two types:
+ *             owner:     lut[index] = index
+ *             redirect:  lut[index] != index
+ *      (3) This is called each time a merge occurs.  It puts the lut
+ *          and backlinks in a canonical form after the merge, where
+ *          all entries in the lut point to the current "owner", which
+ *          has all backlinks.  That is, every "redirect" in the lut
+ *          points to an "owner".  The lut always gives the index of
+ *          the current owner.
+ */
+static l_int32
+mergeLookup(L_WSHED  *wshed,
+            l_int32   sindex,
+            l_int32   dindex)
+{
+l_int32   i, n, size, index;
+l_int32  *lut;
+NUMA     *na;
+NUMA    **links;
+
+    PROCNAME("mergeLookup");
+
+    if (!wshed)
+        return ERROR_INT("wshed not defined", procName, 1);
+    size = wshed->arraysize;
+    if (sindex < 0 || sindex >= size)
+        return ERROR_INT("invalid sindex", procName, 1);
+    if (dindex < 0 || dindex >= size)
+        return ERROR_INT("invalid dindex", procName, 1);
+
+        /* Redirect links in the lut */
+    n = 0;
+    links = wshed->links;
+    lut = wshed->lut;
+    if ((na = links[sindex]) != NULL) {
+        n = numaGetCount(na);
+        for (i = 0; i < n; i++) {
+            numaGetIValue(na, i, &index);
+            lut[index] = dindex;
+        }
+    }
+    lut[sindex] = dindex;
+
+        /* Shift the backlink arrays from sindex to dindex.
+         * sindex should have no backlinks because all entries in the
+         * lut that were previously pointing to it have been redirected
+         * to dindex. */
+    if (!links[dindex])
+        links[dindex] = numaCreate(n);
+    numaJoin(links[dindex], links[sindex], 0, -1);
+    numaAddNumber(links[dindex], sindex);
+    numaDestroy(&links[sindex]);
+
+    return 0;
+}
+
+
+/*!
+ *  wshedGetHeight()
+ *
+ *      Input:  wshed (array of current indices)
+ *              val (value of current pixel popped off queue)
+ *              label (of pixel or 32 bpp label image)
+ *              &height (<return> height of current value from seed
+ *                       or minimum of watershed)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) It is only necessary to find the height for a watershed
+ *          that is indexed by a seed or a minima.  This function should
+ *          not be called on a finished watershed (that continues to fill).
+ */
+static l_int32
+wshedGetHeight(L_WSHED  *wshed,
+               l_int32   val,
+               l_int32   label,
+               l_int32  *pheight)
+{
+l_int32  minval;
+
+    PROCNAME("wshedGetHeight");
+
+    if (!pheight)
+        return ERROR_INT("&height not defined", procName, 1);
+    *pheight = 0;
+    if (!wshed)
+        return ERROR_INT("wshed not defined", procName, 1);
+
+    if (label < wshed->nseeds)
+        numaGetIValue(wshed->nash, label, &minval);
+    else if (label < wshed->nseeds + wshed->nother)
+        numaGetIValue(wshed->namh, label, &minval);
+    else
+        return ERROR_INT("finished watershed; should not call", procName, 1);
+
+    *pheight = val - minval;
+    return 0;
+}
+
+
+/*
+ *  pushNewPixel()
+ *
+ *      Input:  lqueue
+ *              x, y   (pixel coordinates)
+ *              &minx, &maxx, &miny, &maxy  (<return> bounding box update)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This is a wrapper for adding a NewPixel to a queue, which
+ *          updates the bounding box for all pixels on that queue and
+ *          uses the storage stack to retrieve a NewPixel.
+ */
+static void
+pushNewPixel(L_QUEUE  *lq,
+             l_int32   x,
+             l_int32   y,
+             l_int32  *pminx,
+             l_int32  *pmaxx,
+             l_int32  *pminy,
+             l_int32  *pmaxy)
+{
+L_NEWPIXEL  *np;
+
+    PROCNAME("pushNewPixel");
+
+    if (!lq) {
+        L_ERROR("queue not defined\n", procName);
+        return;
+    }
+
+        /* Adjust bounding box */
+    *pminx = L_MIN(*pminx, x);
+    *pmaxx = L_MAX(*pmaxx, x);
+    *pminy = L_MIN(*pminy, y);
+    *pmaxy = L_MAX(*pmaxy, y);
+
+        /* Get a newpixel to use */
+    if (lstackGetCount(lq->stack) > 0)
+        np = (L_NEWPIXEL *)lstackRemove(lq->stack);
+    else
+        np = (L_NEWPIXEL *)LEPT_CALLOC(1, sizeof(L_NEWPIXEL));
+
+    np->x = x;
+    np->y = y;
+    lqueueAdd(lq, np);
+    return;
+}
+
+
+/*
+ *  popNewPixel()
+ *
+ *      Input:  lqueue
+ *              &x, &y   (<return> pixel coordinates)
+ *      Return: void
+ *
+ *   Notes:
+ *       (1) This is a wrapper for removing a NewPixel from a queue,
+ *           which returns the pixel coordinates and saves the NewPixel
+ *           on the storage stack.
+ */
+static void
+popNewPixel(L_QUEUE  *lq,
+            l_int32  *px,
+            l_int32  *py)
+{
+L_NEWPIXEL  *np;
+
+    PROCNAME("popNewPixel");
+
+    if (!lq) {
+        L_ERROR("lqueue not defined\n", procName);
+        return;
+    }
+
+    if ((np = (L_NEWPIXEL *)lqueueRemove(lq)) == NULL)
+        return;
+    *px = np->x;
+    *py = np->y;
+    lstackAdd(lq->stack, np);  /* save for re-use */
+    return;
+}
+
+
+/*
+ *  pushWSPixel()
+ *
+ *      Input:  lh  (priority queue)
+ *              stack  (of reusable WSPixels)
+ *              val  (pixel value: used for ordering the heap)
+ *              x, y  (pixel coordinates)
+ *              index  (label for set to which pixel belongs)
+ *      Return: void
+ *
+ *  Notes:
+ *      (1) This is a wrapper for adding a WSPixel to a heap.  It
+ *          uses the storage stack to retrieve a WSPixel.
+ */
+static void
+pushWSPixel(L_HEAP   *lh,
+            L_STACK  *stack,
+            l_int32   val,
+            l_int32   x,
+            l_int32   y,
+            l_int32   index)
+{
+L_WSPIXEL  *wsp;
+
+    PROCNAME("pushWSPixel");
+
+    if (!lh) {
+        L_ERROR("heap not defined\n", procName);
+        return;
+    }
+    if (!stack) {
+        L_ERROR("stack not defined\n", procName);
+        return;
+    }
+
+        /* Get a wspixel to use */
+    if (lstackGetCount(stack) > 0)
+        wsp = (L_WSPIXEL *)lstackRemove(stack);
+    else
+        wsp = (L_WSPIXEL *)LEPT_CALLOC(1, sizeof(L_WSPIXEL));
+
+    wsp->val = (l_float32)val;
+    wsp->x = x;
+    wsp->y = y;
+    wsp->index = index;
+    lheapAdd(lh, wsp);
+    return;
+}
+
+
+/*
+ *  popWSPixel()
+ *
+ *      Input:  lh  (priority queue)
+ *              stack  (of reusable WSPixels)
+ *              &val  (<return> pixel value)
+ *              &x, &y  (<return> pixel coordinates)
+ *              &index  (<return> label for set to which pixel belongs)
+ *      Return: void
+ *
+ *   Notes:
+ *       (1) This is a wrapper for removing a WSPixel from a heap,
+ *           which returns the WSPixel data and saves the WSPixel
+ *           on the storage stack.
+ */
+static void
+popWSPixel(L_HEAP   *lh,
+           L_STACK  *stack,
+           l_int32  *pval,
+           l_int32  *px,
+           l_int32  *py,
+           l_int32  *pindex)
+{
+L_WSPIXEL  *wsp;
+
+    PROCNAME("popWSPixel");
+
+    if (!lh) {
+        L_ERROR("lheap not defined\n", procName);
+        return;
+    }
+    if (!stack) {
+        L_ERROR("stack not defined\n", procName);
+        return;
+    }
+    if (!pval || !px || !py || !pindex) {
+        L_ERROR("data can't be returned\n", procName);
+        return;
+    }
+
+    if ((wsp = (L_WSPIXEL *)lheapRemove(lh)) == NULL)
+        return;
+    *pval = (l_int32)wsp->val;
+    *px = wsp->x;
+    *py = wsp->y;
+    *pindex = wsp->index;
+    lstackAdd(stack, wsp);  /* save for re-use */
+    return;
+}
+
+
+static void
+debugPrintLUT(l_int32  *lut,
+              l_int32   size,
+              l_int32   debug)
+{
+l_int32  i;
+
+    if (!debug) return;
+    fprintf(stderr, "lut: ");
+    for (i = 0; i < size; i++)
+        fprintf(stderr, "%d ", lut[i]);
+    fprintf(stderr, "\n");
+    return;
+}
+
+
+static void
+debugWshedMerge(L_WSHED *wshed,
+                char    *descr,
+                l_int32  x,
+                l_int32  y,
+                l_int32  label,
+                l_int32  index)
+{
+    if (!wshed || (wshed->debug == 0))
+         return;
+    fprintf(stderr, "%s:\n", descr);
+    fprintf(stderr, "   (x, y) = (%d, %d)\n", x, y);
+    fprintf(stderr, "   clabel = %d, cindex = %d\n", label, index);
+    return;
+}
+
+
+/*-----------------------------------------------------------------------*
+ *                                 Output                                *
+ *-----------------------------------------------------------------------*/
+/*!
+ *  wshedBasins()
+ *
+ *      Input:  wshed
+ *              &pixa  (<optional return> mask of watershed basins)
+ *              &nalevels   (<optional return> watershed levels)
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+wshedBasins(L_WSHED  *wshed,
+            PIXA    **ppixa,
+            NUMA    **pnalevels)
+{
+    PROCNAME("wshedBasins");
+
+    if (!wshed)
+        return ERROR_INT("wshed not defined", procName, 1);
+
+    if (ppixa)
+        *ppixa = pixaCopy(wshed->pixad, L_CLONE);
+    if (pnalevels)
+        *pnalevels = numaClone(wshed->nalevels);
+    return 0;
+}
+
+
+/*!
+ *  wshedRenderFill()
+ *
+ *      Input:  wshed
+ *      Return: pixd (initial image with all basins filled), or null on error
+ */
+PIX *
+wshedRenderFill(L_WSHED  *wshed)
+{
+l_int32  i, n, level, bx, by;
+NUMA    *na;
+PIX     *pix, *pixd;
+PIXA    *pixa;
+
+    PROCNAME("wshedRenderFill");
+
+    if (!wshed)
+        return (PIX *)ERROR_PTR("wshed not defined", procName, NULL);
+
+    wshedBasins(wshed, &pixa, &na);
+    pixd = pixCopy(NULL, wshed->pixs);
+    n = pixaGetCount(pixa);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        pixaGetBoxGeometry(pixa, i, &bx, &by, NULL, NULL);
+        numaGetIValue(na, i, &level);
+        pixPaintThroughMask(pixd, pix, bx, by, level);
+        pixDestroy(&pix);
+    }
+
+    pixaDestroy(&pixa);
+    numaDestroy(&na);
+    return pixd;
+}
+
+
+/*!
+ *  wshedRenderColors()
+ *
+ *      Input:  wshed
+ *      Return: pixd (initial image with all basins filled), or null on error
+ */
+PIX *
+wshedRenderColors(L_WSHED  *wshed)
+{
+l_int32  w, h;
+PIX     *pixg, *pixt, *pixc, *pixm, *pixd;
+PIXA    *pixa;
+
+    PROCNAME("wshedRenderColors");
+
+    if (!wshed)
+        return (PIX *)ERROR_PTR("wshed not defined", procName, NULL);
+
+    wshedBasins(wshed, &pixa, NULL);
+    pixg = pixCopy(NULL, wshed->pixs);
+    pixGetDimensions(wshed->pixs, &w, &h, NULL);
+    pixd = pixConvertTo32(pixg);
+    pixt = pixaDisplayRandomCmap(pixa, w, h);
+    pixc = pixConvertTo32(pixt);
+    pixm = pixaDisplay(pixa, w, h);
+    pixCombineMasked(pixd, pixc, pixm);
+
+    pixDestroy(&pixg);
+    pixDestroy(&pixt);
+    pixDestroy(&pixc);
+    pixDestroy(&pixm);
+    pixaDestroy(&pixa);
+    return pixd;
+}
diff --git a/src/watershed.h b/src/watershed.h
new file mode 100644 (file)
index 0000000..91ca355
--- /dev/null
@@ -0,0 +1,63 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+#ifndef  LEPTONICA_WATERSHED_H
+#define  LEPTONICA_WATERSHED_H
+
+/*
+ *  watershed.h
+ *
+ *     Simple data structure to hold watershed data.
+ *     All data here is owned by the L_WShed and must be freed.
+ */
+
+struct L_WShed
+{
+    struct Pix    *pixs;        /* clone of input 8 bpp pixs                */
+    struct Pix    *pixm;        /* clone of input 1 bpp seed (marker) pixm  */
+    l_int32        mindepth;    /* minimum depth allowed for a watershed    */
+    struct Pix    *pixlab;      /* 16 bpp label pix                         */
+    struct Pix    *pixt;        /* scratch pix for computing wshed regions  */
+    void         **lines8;      /* line ptrs for pixs                       */
+    void         **linem1;      /* line ptrs for pixm                       */
+    void         **linelab32;   /* line ptrs for pixlab                     */
+    void         **linet1;      /* line ptrs for pixt                       */
+    struct Pixa   *pixad;       /* result: 1 bpp pixa of watersheds         */
+    struct Pta    *ptas;        /* pta of initial seed pixels               */
+    struct Numa   *nasi;        /* numa of seed indicators; 0 if completed  */
+    struct Numa   *nash;        /* numa of initial seed heights             */
+    struct Numa   *namh;        /* numa of initial minima heights           */
+    struct Numa   *nalevels;    /* result: numa of watershed levels         */
+    l_int32        nseeds;      /* number of seeds (markers)                */
+    l_int32        nother;      /* number of minima different from seeds    */
+    l_int32       *lut;         /* lut for pixel indices                    */
+    struct Numa  **links;       /* back-links into lut, for updates         */
+    l_int32        arraysize;   /* size of links array                      */
+    l_int32        debug;       /* set to 1 for debug output                */
+};
+typedef struct L_WShed L_WSHED;
+
+#endif  /* LEPTONICA_WATERSHED_H */
diff --git a/src/webpio.c b/src/webpio.c
new file mode 100644 (file)
index 0000000..a0fa1fc
--- /dev/null
@@ -0,0 +1,398 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  webpio.c
+ *
+ *    Reading WebP
+ *          PIX             *pixReadStreamWebP()
+ *          PIX             *pixReadMemWebP()
+ *
+ *    Reading WebP header
+ *          l_int32          readHeaderWebP()
+ *          l_int32          readHeaderMemWebP()
+ *
+ *    Writing WebP
+ *          l_int32          pixWriteWebP()  [ special top level ]
+ *          l_int32          pixWriteStreamWebP()
+ *          l_int32          pixWriteMemWebP()
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  HAVE_LIBWEBP   /* defined in environ.h */
+/* --------------------------------------------*/
+#include "webp/decode.h"
+#include "webp/encode.h"
+
+/*---------------------------------------------------------------------*
+ *                             Reading WebP                            *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixReadStreamWebP()
+ *
+ *      Input:  stream corresponding to WebP image
+ *      Return: pix (32 bpp), or null on error
+ */
+PIX *
+pixReadStreamWebP(FILE  *fp)
+{
+l_uint8  *filedata;
+size_t    filesize;
+PIX      *pix;
+
+    PROCNAME("pixReadStreamWebP");
+
+    if (!fp)
+        return (PIX *)ERROR_PTR("fp not defined", procName, NULL);
+
+        /* Read data from file and decode into Y,U,V arrays */
+    rewind(fp);
+    if ((filedata = l_binaryReadStream(fp, &filesize)) == NULL)
+        return (PIX *)ERROR_PTR("filedata not read", procName, NULL);
+
+    pix = pixReadMemWebP(filedata, filesize);
+    LEPT_FREE(filedata);
+    return pix;
+}
+
+
+/*!
+ *  pixReadMemWebP()
+ *
+ *      Input:  filedata (webp compressed data in memory)
+ *              filesize (number of bytes in data)
+ *      Return: pix (32 bpp), or null on error
+ *
+ *  Notes:
+ *      (1) When the encoded data only has 3 channels (no alpha),
+ *          WebPDecodeRGBAInto() generates a raster of 32-bit pixels, with
+ *          the alpha channel set to opaque (255).
+ *      (2) We don't need to use the gnu runtime functions like fmemopen()
+ *          for redirecting data from a stream to memory, because
+ *          the webp library has been written with memory-to-memory
+ *          functions at the lowest level (which is good!).  And, in
+ *          any event, fmemopen() doesn't work with l_binaryReadStream().
+ */
+PIX *
+pixReadMemWebP(const l_uint8  *filedata,
+               size_t          filesize)
+{
+l_uint8   *out = NULL;
+l_int32    w, h, has_alpha, wpl, stride;
+l_uint32  *data;
+size_t     size;
+PIX       *pix;
+WebPBitstreamFeatures  features;
+
+    PROCNAME("pixReadMemWebP");
+
+    if (!filedata)
+        return (PIX *)ERROR_PTR("filedata not defined", procName, NULL);
+
+    if (WebPGetFeatures(filedata, filesize, &features))
+        return (PIX *)ERROR_PTR("Invalid WebP file", procName, NULL);
+    w = features.width;
+    h = features.height;
+    has_alpha = features.has_alpha;
+
+        /* Write from compressed Y,U,V arrays to pix raster data */
+    pix = pixCreate(w, h, 32);
+    pixSetInputFormat(pix, IFF_WEBP);
+    if (has_alpha) pixSetSpp(pix, 4);
+    data = pixGetData(pix);
+    wpl = pixGetWpl(pix);
+    stride = wpl * 4;
+    size = stride * h;
+    out = WebPDecodeRGBAInto(filedata, filesize, (uint8_t *)data, size,
+                             stride);
+    if (out == NULL) {  /* error: out should also point to data */
+        pixDestroy(&pix);
+        return (PIX *)ERROR_PTR("WebP decode failed", procName, NULL);
+    }
+
+        /* WebP decoder emits opposite byte order for RGBA components */
+    pixEndianByteSwap(pix);
+    return pix;
+}
+
+
+/*!
+ *  readHeaderWebP()
+ *
+ *      Input:  filename
+ *              &w (<return> width)
+ *              &h (<return> height)
+ *              &spp (<return> spp (3 or 4))
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+readHeaderWebP(const char *filename,
+               l_int32    *pw,
+               l_int32    *ph,
+               l_int32    *pspp)
+{
+l_uint8  data[100];  /* expect size info within the first 50 bytes or so */
+l_int32  nbytes, bytesread;
+size_t   filesize;
+FILE    *fp;
+
+    PROCNAME("readHeaderWebP");
+
+    if (!pw || !ph || !pspp)
+        return ERROR_INT("input ptr(s) not defined", procName, 1);
+    *pw = *ph = *pspp = 0;
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+        /* Read no more than 100 bytes from the file */
+    if ((filesize = nbytesInFile(filename)) == 0)
+        return ERROR_INT("no file size found", procName, 1);
+    if (filesize < 100)
+        L_WARNING("very small webp file\n", procName);
+    nbytes = L_MIN(filesize, 100);
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT("image file not found", procName, 1);
+    bytesread = fread((char *)data, 1, nbytes, fp);
+    fclose(fp);
+    if (bytesread != nbytes)
+        return ERROR_INT("failed to read requested data", procName, 1);
+
+    return readHeaderMemWebP(data, nbytes, pw, ph, pspp);
+}
+
+
+/*!
+ *  readHeaderMemWebP()
+ *
+ *      Input:  data
+ *              size (100 bytes is sufficient)
+ *              &w (<return> width)
+ *              &h (<return> height)
+ *              &spp (<return> spp (3 or 4))
+ *      Return: 0 if OK, 1 on error
+ */
+l_int32
+readHeaderMemWebP(const l_uint8  *data,
+                  size_t          size,
+                  l_int32        *pw,
+                  l_int32        *ph,
+                  l_int32        *pspp)
+{
+WebPBitstreamFeatures  features;
+
+    PROCNAME("readHeaderWebP");
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pspp) *pspp = 0;
+    if (!data)
+        return ERROR_INT("data not defined", procName, 1);
+    if (!pw || !ph || !pspp)
+        return ERROR_INT("input ptr(s) not defined", procName, 1);
+
+    if (WebPGetFeatures(data, (l_int32)size, &features))
+        return ERROR_INT("invalid WebP file", procName, 1);
+    *pw = features.width;
+    *ph = features.height;
+    *pspp = (features.has_alpha) ? 4 : 3;
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                            Writing WebP                             *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixWriteWebP()
+ *
+ *      Input:  filename
+ *              pixs
+ *              quality (0 - 100; default ~80)
+ *              lossless (use 1 for lossless; 0 for lossy)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Special top-level function allowing specification of quality.
+ */
+l_int32
+pixWriteWebP(const char  *filename,
+             PIX         *pixs,
+             l_int32      quality,
+             l_int32      lossless)
+{
+FILE  *fp;
+
+    PROCNAME("pixWriteWebP");
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb+")) == NULL)
+        return ERROR_INT("stream not opened", procName, 1);
+    if (pixWriteStreamWebP(fp, pixs, quality, lossless) != 0) {
+        fclose(fp);
+        return ERROR_INT("pixs not compressed to stream", procName, 1);
+    }
+    fclose(fp);
+    return 0;
+}
+
+
+/*!
+ *  pixWriteStreampWebP()
+ *
+ *      Input:  stream
+ *              pixs  (all depths)
+ *              quality (0 - 100; default ~80)
+ *              lossless (use 1 for lossless; 0 for lossy)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) See pixWriteMemWebP() for details.
+ *      (2) Use 'free', and not leptonica's 'LEPT_FREE', for all heap data
+ *          that is returned from the WebP library.
+ */
+l_int32
+pixWriteStreamWebP(FILE    *fp,
+                   PIX     *pixs,
+                   l_int32  quality,
+                   l_int32  lossless)
+{
+l_uint8  *filedata;
+size_t    filebytes, nbytes;
+
+    PROCNAME("pixWriteStreamWebP");
+
+    if (!fp)
+        return ERROR_INT("stream not open", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    pixWriteMemWebP(&filedata, &filebytes, pixs, quality, lossless);
+    rewind(fp);
+    nbytes = fwrite(filedata, 1, filebytes, fp);
+    free(filedata);
+    if (nbytes != filebytes)
+        return ERROR_INT("Write error", procName, 1);
+    return 0;
+}
+
+
+/*!
+ *  pixWriteMemWebP()
+ *
+ *      Input:  &encdata (<return> webp encoded data of pixs)
+ *              &encsize (<return> size of webp encoded data)
+ *              pixs (any depth, cmapped OK)
+ *              quality (0 - 100; default ~80)
+ *              lossless (use 1 for lossless; 0 for lossy)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) Lossless and lossy encoding are entirely different in webp.
+ *          @quality applies to lossy, and is ignored for lossless.
+ *      (2) The input image is converted to RGB if necessary.  If spp == 3,
+ *          we set the alpha channel to fully opaque (255), and
+ *          WebPEncodeRGBA() then removes the alpha chunk when encoding,
+ *          setting the internal header field has_alpha to 0.
+ */
+l_int32
+pixWriteMemWebP(l_uint8  **pencdata,
+                size_t    *pencsize,
+                PIX       *pixs,
+                l_int32    quality,
+                l_int32    lossless)
+{
+l_int32    w, h, d, wpl, stride;
+l_uint32  *data;
+PIX       *pix1, *pix2;
+
+    PROCNAME("pixWriteMemWebP");
+
+    if (!pencdata)
+        return ERROR_INT("&encdata not defined", procName, 1);
+    *pencdata = NULL;
+    if (!pencsize)
+        return ERROR_INT("&encsize not defined", procName, 1);
+    *pencsize = 0;
+    if (!pixs)
+        return ERROR_INT("&pixs not defined", procName, 1);
+    if (lossless == 0 && (quality < 0 || quality > 100))
+        return ERROR_INT("quality not in [0 ... 100]", procName, 1);
+
+    if ((pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR)) == NULL)
+        return ERROR_INT("failure to remove color map", procName, 1);
+
+        /* Convert to rgb if not 32 bpp; pix2 must not be a clone of pixs. */
+    if (pixGetDepth(pix1) != 32)
+        pix2 = pixConvertTo32(pix1);
+    else
+        pix2 = pixCopy(NULL, pix1);
+    pixDestroy(&pix1);
+    pixGetDimensions(pix2, &w, &h, &d);
+    if (w <= 0 || h <= 0 || d != 32) {
+        pixDestroy(&pix2);
+        return ERROR_INT("pix2 not 32 bpp or of 0 size", procName, 1);
+    }
+
+        /* If spp == 3, need to set alpha layer to opaque (all 1s). */
+    if (pixGetSpp(pix2) == 3)
+        pixSetComponentArbitrary(pix2, L_ALPHA_CHANNEL, 255);
+
+        /* Webp encoder assumes big-endian byte order for RGBA components */
+    pixEndianByteSwap(pix2);
+    wpl = pixGetWpl(pix2);
+    data = pixGetData(pix2);
+    stride = wpl * 4;
+    if (lossless) {
+        *pencsize = WebPEncodeLosslessRGBA((uint8_t *)data, w, h,
+                                           stride, pencdata);
+    } else {
+        *pencsize = WebPEncodeRGBA((uint8_t *)data, w, h, stride,
+                                   quality, pencdata);
+    }
+    pixDestroy(&pix2);
+
+    if (*pencsize == 0) {
+        free(pencdata);
+        *pencdata = NULL;
+        return ERROR_INT("webp encoding failed", procName, 1);
+    }
+
+    return 0;
+}
+
+/* --------------------------------------------*/
+#endif  /* HAVE_LIBWEBP */
+/* --------------------------------------------*/
diff --git a/src/webpiostub.c b/src/webpiostub.c
new file mode 100644 (file)
index 0000000..ef17e2e
--- /dev/null
@@ -0,0 +1,97 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  webpiostub.c
+ *
+ *     Stubs for webpio.c functions
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !HAVE_LIBWEBP   /* defined in environ.h */
+/* --------------------------------------------*/
+
+PIX * pixReadStreamWebP(FILE *fp)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadStreamWebP", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+PIX * pixReadMemWebP(const l_uint8 *filedata, size_t filesize)
+{
+    return (PIX * )ERROR_PTR("function not present", "pixReadMemWebP", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 readHeaderWebP(const char *filename, l_int32 *pw, l_int32 *ph,
+                       l_int32 *pspp)
+{
+    return ERROR_INT("function not present", "readHeaderWebP", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 readHeaderMemWebP(const l_uint8 *data, size_t size,
+                          l_int32 *pw, l_int32 *ph, l_int32 *pspp)
+{
+    return ERROR_INT("function not present", "readHeaderMemWebP", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteWebP(const char *filename, PIX *pixs, l_int32 quality,
+                     l_int32 lossless)
+{
+    return ERROR_INT("function not present", "pixWriteWebP", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteStreamWebP(FILE *fp, PIX *pixs, l_int32 quality,
+                           l_int32 lossless)
+{
+    return ERROR_INT("function not present", "pixWriteStreamWebP", 1);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_int32 pixWriteMemWebP(l_uint8 **pencdata, size_t *pencsize, PIX *pixs,
+                        l_int32 quality, l_int32 lossless)
+{
+    return ERROR_INT("function not present", "pixWriteMemWebP", 1);
+}
+
+/* --------------------------------------------*/
+#endif  /* !HAVE_LIBWEBP */
+/* --------------------------------------------*/
diff --git a/src/writefile.c b/src/writefile.c
new file mode 100644 (file)
index 0000000..6f02fd8
--- /dev/null
@@ -0,0 +1,1378 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ * writefile.c
+ *
+ *     High-level procedures for writing images to file:
+ *        l_int32     pixaWriteFiles()
+ *        l_int32     pixWrite()    [behavior depends on WRITE_AS_NAMED]
+ *        l_int32     pixWriteAutoFormat()
+ *        l_int32     pixWriteStream()
+ *        l_int32     pixWriteImpliedFormat()
+ *        l_int32     pixWriteTempfile()
+ *
+ *     Selection of output format if default is requested
+ *        l_int32     pixChooseOutputFormat()
+ *        l_int32     getImpliedFileFormat()
+ *        l_int32     pixGetAutoFormat()
+ *        const char *getFormatExtension()
+ *
+ *     Write to memory
+ *        l_int32     pixWriteMem()
+ *
+ *     Image display for debugging
+ *        l_int32     pixDisplay()
+ *        l_int32     pixDisplayWithTitle()
+ *        l_int32     pixDisplayMultiple()
+ *        l_int32     pixDisplayWrite()
+ *        l_int32     pixDisplayWriteFormat()
+ *        l_int32     pixSaveTiled()
+ *        l_int32     pixSaveTiledOutline()
+ *        l_int32     pixSaveTiledWithText()
+ *        void        l_chooseDisplayProg()
+ *
+ *  Supported file formats:
+ *  (1) Writing is supported without any external libraries:
+ *          bmp
+ *          pnm   (including pbm, pgm, etc)
+ *          spix  (raw serialized)
+ *  (2) Writing is supported with installation of external libraries:
+ *          png
+ *          jpg   (standard jfif version)
+ *          tiff  (including most varieties of compression)
+ *          gif
+ *          webp
+ *          jp2 (jpeg2000)
+ *  (3) Writing is supported through special interfaces:
+ *          ps (PostScript, in psio1.c, psio2.c):
+ *              level 1 (uncompressed)
+ *              level 2 (g4 and dct encoding: requires tiff, jpg)
+ *              level 3 (g4, dct and flate encoding: requires tiff, jpg, zlib)
+ *          pdf (PDF, in pdfio.c):
+ *              level 1 (g4 and dct encoding: requires tiff, jpg)
+ *              level 2 (g4, dct and flate encoding: requires tiff, jpg, zlib)
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+    /*   Special flag for pixWrite().  The default for both unix and     */
+    /*   windows is to use whatever filename is given, as opposed to     */
+    /*   insuring the filename extension matches the image compression.  */
+#define  WRITE_AS_NAMED    1
+
+    /* Display program (xv, xli, xzgv, open) to be invoked by pixDisplay()  */
+#ifdef _WIN32
+static l_int32  var_DISPLAY_PROG = L_DISPLAY_WITH_IV;  /* default */
+#elif  defined(__APPLE__)
+static l_int32  var_DISPLAY_PROG = L_DISPLAY_WITH_OPEN;  /* default */
+#else
+static l_int32  var_DISPLAY_PROG = L_DISPLAY_WITH_XZGV;  /* default */
+#endif  /* _WIN32 */
+
+static const l_int32  L_BUF_SIZE = 512;
+static const l_int32  MAX_DISPLAY_WIDTH = 1000;
+static const l_int32  MAX_DISPLAY_HEIGHT = 800;
+static const l_int32  MAX_SIZE_FOR_PNG = 200;
+
+    /* PostScript output for printing */
+static const l_float32  DEFAULT_SCALING = 1.0;
+
+    /* Global array of image file format extension names.                */
+    /* This is in 1-1 corrspondence with format enum in imageio.h.       */
+    /* The empty string at the end represents the serialized format,     */
+    /* which has no recognizable extension name, but the array must      */
+    /* be padded to agree with the format enum.                          */
+    /* (Note on 'const': The size of the array can't be defined 'const'  */
+    /* because that makes it static.  The 'const' in the definition of   */
+    /* the array refers to the strings in the array; the ptr to the      */
+    /* array is not const and can be used 'extern' in other files.)      */
+LEPT_DLL l_int32  NumImageFileFormatExtensions = 19;  /* array size */
+LEPT_DLL const char *ImageFileFormatExtensions[] =
+         {"unknown",
+          "bmp",
+          "jpg",
+          "png",
+          "tif",
+          "tif",
+          "tif",
+          "tif",
+          "tif",
+          "tif",
+          "tif",
+          "pnm",
+          "ps",
+          "gif",
+          "jp2",
+          "webp",
+          "pdf",
+          "default",
+          ""};
+
+    /* Local map of image file name extension to output format */
+struct ExtensionMap
+{
+    char     extension[8];
+    l_int32  format;
+};
+static const struct ExtensionMap extension_map[] =
+                            { { ".bmp",  IFF_BMP       },
+                              { ".jpg",  IFF_JFIF_JPEG },
+                              { ".jpeg", IFF_JFIF_JPEG },
+                              { ".png",  IFF_PNG       },
+                              { ".tif",  IFF_TIFF      },
+                              { ".tiff", IFF_TIFF      },
+                              { ".pnm",  IFF_PNM       },
+                              { ".gif",  IFF_GIF       },
+                              { ".jp2",  IFF_JP2       },
+                              { ".ps",   IFF_PS        },
+                              { ".pdf",  IFF_LPDF      },
+                              { ".webp", IFF_WEBP      } };
+
+
+/*---------------------------------------------------------------------*
+ *           Top-level procedures for writing images to file           *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixaWriteFiles()
+ *
+ *      Input:  rootname
+ *              pixa
+ *              format  (defined in imageio.h; see notes for default)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Use @format = IFF_DEFAULT to decide the output format
+ *          individually for each pix.
+ */
+l_int32
+pixaWriteFiles(const char  *rootname,
+               PIXA        *pixa,
+               l_int32      format)
+{
+char     bigbuf[L_BUF_SIZE];
+l_int32  i, n, pixformat;
+PIX     *pix;
+
+    PROCNAME("pixaWriteFiles");
+
+    if (!rootname)
+        return ERROR_INT("rootname not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+    if (format < 0 || format == IFF_UNKNOWN ||
+        format >= NumImageFileFormatExtensions)
+        return ERROR_INT("invalid format", procName, 1);
+
+    n = pixaGetCount(pixa);
+    for (i = 0; i < n; i++) {
+        pix = pixaGetPix(pixa, i, L_CLONE);
+        if (format == IFF_DEFAULT)
+            pixformat = pixChooseOutputFormat(pix);
+        else
+            pixformat = format;
+        snprintf(bigbuf, L_BUF_SIZE, "%s%03d.%s", rootname, i,
+                 ImageFileFormatExtensions[pixformat]);
+        pixWrite(bigbuf, pix, pixformat);
+        pixDestroy(&pix);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixWrite()
+ *
+ *      Input:  filename
+ *              pix
+ *              format  (defined in imageio.h)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) Open for write using binary mode (with the "b" flag)
+ *          to avoid having Windows automatically translate the NL
+ *          into CRLF, which corrupts image files.  On non-windows
+ *          systems this flag should be ignored, per ISO C90.
+ *          Thanks to Dave Bryan for pointing this out.
+ *      (2) If the default image format IFF_DEFAULT is requested:
+ *          use the input format if known; otherwise, use a lossless format.
+ *      (3) There are two modes with respect to file naming.
+ *          (a) The default code writes to @filename.
+ *          (b) If WRITE_AS_NAMED is defined to 0, it's a bit fancier.
+ *              Then, if @filename does not have a file extension, one is
+ *              automatically appended, depending on the requested format.
+ *          The original intent for providing option (b) was to insure
+ *          that filenames on Windows have an extension that matches
+ *          the image compression.  However, this is not the default.
+ */
+l_int32
+pixWrite(const char  *filename,
+         PIX         *pix,
+         l_int32      format)
+{
+char  *fname;
+FILE  *fp;
+
+    PROCNAME("pixWrite");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    fname = genPathname(filename, NULL);
+
+#if  WRITE_AS_NAMED  /* Default */
+
+    if ((fp = fopenWriteStream(fname, "wb+")) == NULL) {
+        LEPT_FREE(fname);
+        return ERROR_INT("stream not opened", procName, 1);
+    }
+
+#else  /* Add an extension to the output name if none exists */
+
+    {l_int32  extlen;
+     char    *extension, *filebuf;
+        splitPathAtExtension(fname, NULL, &extension);
+        extlen = strlen(extension);
+        LEPT_FREE(extension);
+        if (extlen == 0) {
+            if (format == IFF_DEFAULT || format == IFF_UNKNOWN)
+                format = pixChooseOutputFormat(pix);
+
+            filebuf = (char *)LEPT_CALLOC(strlen(fname) + 10, sizeof(char));
+            if (!filebuf) {
+                return ERROR_INT("filebuf not made", procName, 1);
+                LEPT_FREE(fname);
+            }
+            strncpy(filebuf, fname, strlen(fname));
+            strcat(filebuf, ".");
+            strcat(filebuf, ImageFileFormatExtensions[format]);
+        } else {
+            filebuf = (char *)fname;
+        }
+
+        fp = fopenWriteStream(filebuf, "wb+");
+        if (filebuf != fname)
+            LEPT_FREE(filebuf);
+        if (fp == NULL) {
+            LEPT_FREE(fname);
+            return ERROR_INT("stream not opened", procName, 1);
+        }
+    }
+
+#endif  /* WRITE_AS_NAMED */
+
+    LEPT_FREE(fname);
+    if (pixWriteStream(fp, pix, format)) {
+        fclose(fp);
+        return ERROR_INT("pix not written to stream", procName, 1);
+    }
+    fclose(fp);
+
+    return 0;
+}
+
+
+/*!
+ *  pixWriteAutoFormat()
+ *
+ *      Input:  filename
+ *              pix
+ *      Return: 0 if OK; 1 on error
+ */
+l_int32
+pixWriteAutoFormat(const char  *filename,
+                   PIX         *pix)
+{
+l_int32  format;
+
+    PROCNAME("pixWriteAutoFormat");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+
+    if (pixGetAutoFormat(pix, &format))
+        return ERROR_INT("auto format not returned", procName, 1);
+    return pixWrite(filename, pix, format);
+}
+
+
+/*!
+ *  pixWriteStream()
+ *
+ *      Input:  stream
+ *              pix
+ *              format
+ *      Return: 0 if OK; 1 on error.
+ */
+l_int32
+pixWriteStream(FILE    *fp,
+               PIX     *pix,
+               l_int32  format)
+{
+    PROCNAME("pixWriteStream");
+
+    if (!fp)
+        return ERROR_INT("stream not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    if (format == IFF_DEFAULT)
+        format = pixChooseOutputFormat(pix);
+
+    switch(format)
+    {
+    case IFF_BMP:
+        pixWriteStreamBmp(fp, pix);
+        break;
+
+    case IFF_JFIF_JPEG:   /* default quality; baseline sequential */
+        return pixWriteStreamJpeg(fp, pix, 75, 0);
+        break;
+
+    case IFF_PNG:   /* no gamma value stored */
+        return pixWriteStreamPng(fp, pix, 0.0);
+        break;
+
+    case IFF_TIFF:           /* uncompressed */
+    case IFF_TIFF_PACKBITS:  /* compressed, binary only */
+    case IFF_TIFF_RLE:       /* compressed, binary only */
+    case IFF_TIFF_G3:        /* compressed, binary only */
+    case IFF_TIFF_G4:        /* compressed, binary only */
+    case IFF_TIFF_LZW:       /* compressed, all depths */
+    case IFF_TIFF_ZIP:       /* compressed, all depths */
+        return pixWriteStreamTiff(fp, pix, format);
+        break;
+
+    case IFF_PNM:
+        return pixWriteStreamPnm(fp, pix);
+        break;
+
+    case IFF_PS:
+        return pixWriteStreamPS(fp, pix, NULL, 0, DEFAULT_SCALING);
+        break;
+
+    case IFF_GIF:
+        return pixWriteStreamGif(fp, pix);
+        break;
+
+    case IFF_JP2:
+        return pixWriteStreamJp2k(fp, pix, 34, 4, 0, 0);
+        break;
+
+    case IFF_WEBP:
+        return pixWriteStreamWebP(fp, pix, 80, 0);
+        break;
+
+    case IFF_LPDF:
+        return pixWriteStreamPdf(fp, pix, 0, NULL);
+        break;
+
+    case IFF_SPIX:
+        return pixWriteStreamSpix(fp, pix);
+        break;
+
+    default:
+        return ERROR_INT("unknown format", procName, 1);
+        break;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixWriteImpliedFormat()
+ *
+ *      Input:  filename
+ *              pix
+ *              quality (iff JPEG; 1 - 100, 0 for default)
+ *              progressive (iff JPEG; 0 for baseline seq., 1 for progressive)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This determines the output format from the filename extension.
+ *      (2) The last two args are ignored except for requests for jpeg files.
+ *      (3) The jpeg default quality is 75.
+ */
+l_int32
+pixWriteImpliedFormat(const char  *filename,
+                      PIX         *pix,
+                      l_int32      quality,
+                      l_int32      progressive)
+{
+l_int32  format;
+
+    PROCNAME("pixWriteImpliedFormat");
+
+    if (!filename)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+        /* Determine output format */
+    format = getImpliedFileFormat(filename);
+    if (format == IFF_UNKNOWN) {
+        format = IFF_PNG;
+    } else if (format == IFF_TIFF) {
+        if (pixGetDepth(pix) == 1)
+            format = IFF_TIFF_G4;
+        else
+#ifdef _WIN32
+            format = IFF_TIFF_LZW;  /* poor compression */
+#else
+            format = IFF_TIFF_ZIP;  /* native windows tools can't handle this */
+#endif  /* _WIN32 */
+    }
+
+    if (format == IFF_JFIF_JPEG) {
+        quality = L_MIN(quality, 100);
+        quality = L_MAX(quality, 0);
+        if (progressive != 0 && progressive != 1) {
+            progressive = 0;
+            L_WARNING("invalid progressive; setting to baseline\n", procName);
+        }
+        if (quality == 0)
+            quality = 75;
+        pixWriteJpeg (filename, pix, quality, progressive);
+    } else {
+        pixWrite(filename, pix, format);
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  pixWriteTempfile()
+ *
+ *      Input:  dir (directory name; use '.' for local dir; no trailing '/')
+ *              tail (<optional> tailname, including extension if any)
+ *              pix
+ *              format
+ *              &filename (<optional> return actual filename used; use
+ *                         null to skip)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This generates a temp filename, writes the pix to it,
+ *          and optionally returns the temp filename.
+ *      (2) If the filename is returned to a windows program from a DLL,
+ *          use lept_free() to free it.
+ *      (3) See genTempFilename() for details.  We omit the time and pid
+ *          here.
+ */
+l_int32
+pixWriteTempfile(const char  *dir,
+                 const char  *tail,
+                 PIX         *pix,
+                 l_int32      format,
+                 char       **pfilename)
+{
+char    *filename;
+l_int32  ret;
+
+    PROCNAME("pixWriteTempfile");
+
+    if (!dir)
+        return ERROR_INT("filename not defined", procName, 1);
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 1);
+
+    if ((filename = genTempFilename(dir, tail, 0, 0)) == NULL)
+        return ERROR_INT("temp filename not made", procName, 1);
+
+    ret = pixWrite(filename, pix, format);
+    if (pfilename)
+        *pfilename = filename;
+    else
+        LEPT_FREE(filename);
+
+    return ret;
+}
+
+
+/*---------------------------------------------------------------------*
+ *          Selection of output format if default is requested         *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixChooseOutputFormat()
+ *
+ *      Input:  pix
+ *      Return: output format, or 0 on error
+ *
+ *  Notes:
+ *      (1) This should only be called if the requested format is IFF_DEFAULT.
+ *      (2) If the pix wasn't read from a file, its input format value
+ *          will be IFF_UNKNOWN, and in that case it is written out
+ *          in a compressed but lossless format.
+ */
+l_int32
+pixChooseOutputFormat(PIX  *pix)
+{
+l_int32  d, format;
+
+    PROCNAME("pixChooseOutputFormat");
+
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 0);
+
+    d = pixGetDepth(pix);
+    format = pixGetInputFormat(pix);
+    if (format == IFF_UNKNOWN) {  /* output lossless */
+        if (d == 1)
+            format = IFF_TIFF_G4;
+        else
+            format = IFF_PNG;
+    }
+
+    return format;
+}
+
+
+/*!
+ *  getImpliedFileFormat()
+ *
+ *      Input:  filename
+ *      Return: output format, or IFF_UNKNOWN on error or invalid extension.
+ *
+ *  Notes:
+ *      (1) This determines the output file format from the extension
+ *          of the input filename.
+ */
+l_int32
+getImpliedFileFormat(const char  *filename)
+{
+char    *extension;
+int      i, numext;
+l_int32  format = IFF_UNKNOWN;
+
+    if (splitPathAtExtension (filename, NULL, &extension))
+        return IFF_UNKNOWN;
+
+    numext = sizeof(extension_map) / sizeof(extension_map[0]);
+    for (i = 0; i < numext; i++) {
+        if (!strcmp(extension, extension_map[i].extension)) {
+            format = extension_map[i].format;
+            break;
+        }
+    }
+
+    LEPT_FREE(extension);
+    return format;
+}
+
+
+/*!
+ *  pixGetAutoFormat()
+ *
+ *      Input:  pix
+ *              &format
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) The output formats are restricted to tiff, jpeg and png
+ *          because these are the most commonly used image formats and
+ *          the ones that are typically installed with leptonica.
+ *      (2) This decides what compression to use based on the pix.
+ *          It chooses tiff-g4 if 1 bpp without a colormap, jpeg with
+ *          quality 75 if grayscale, rgb or rgba (where it loses
+ *          the alpha layer), and lossless png for all other situations.
+ */
+l_int32
+pixGetAutoFormat(PIX      *pix,
+                 l_int32  *pformat)
+{
+l_int32   d;
+PIXCMAP  *cmap;
+
+    PROCNAME("pixGetAutoFormat");
+
+    if (!pformat)
+        return ERROR_INT("&format not defined", procName, 0);
+    *pformat = IFF_UNKNOWN;
+    if (!pix)
+        return ERROR_INT("pix not defined", procName, 0);
+
+    d = pixGetDepth(pix);
+    cmap = pixGetColormap(pix);
+    if (d == 1 && !cmap) {
+        *pformat = IFF_TIFF_G4;
+    } else if ((d == 8 && !cmap) || d == 24 || d == 32) {
+        *pformat = IFF_JFIF_JPEG;
+    } else {
+        *pformat = IFF_PNG;
+    }
+
+    return 0;
+}
+
+
+/*!
+ *  getFormatExtension()
+ *
+ *      Input:  format (integer)
+ *      Return: extension (string), or null if format is out of range
+ *
+ *  Notes:
+ *      (1) This string is NOT owned by the caller; it is just a pointer
+ *          to a global string.  Do not free it.
+ */
+const char *
+getFormatExtension(l_int32  format)
+{
+    PROCNAME("getFormatExtension");
+
+    if (format < 0 || format >= NumImageFileFormatExtensions)
+        return (const char *)ERROR_PTR("invalid format", procName, NULL);
+
+    return ImageFileFormatExtensions[format];
+}
+
+
+/*---------------------------------------------------------------------*
+ *                            Write to memory                          *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixWriteMem()
+ *
+ *      Input:  &data (<return> data of tiff compressed image)
+ *              &size (<return> size of returned data)
+ *              pix
+ *              format  (defined in imageio.h)
+ *      Return: 0 if OK, 1 on error
+ *
+ *  Notes:
+ *      (1) On windows, this will only write tiff and PostScript to memory.
+ *          For other formats, it requires open_memstream(3).
+ *      (2) PostScript output is uncompressed, in hex ascii.
+ *          Most printers support level 2 compression (tiff_g4 for 1 bpp,
+ *          jpeg for 8 and 32 bpp).
+ */
+l_int32
+pixWriteMem(l_uint8  **pdata,
+            size_t    *psize,
+            PIX       *pix,
+            l_int32    format)
+{
+l_int32  ret;
+
+    PROCNAME("pixWriteMem");
+
+    if (!pdata)
+        return ERROR_INT("&data not defined", procName, 1 );
+    if (!psize)
+        return ERROR_INT("&size not defined", procName, 1 );
+    if (!pix)
+        return ERROR_INT("&pix not defined", procName, 1 );
+
+    if (format == IFF_DEFAULT)
+        format = pixChooseOutputFormat(pix);
+
+    switch(format)
+    {
+    case IFF_BMP:
+        ret = pixWriteMemBmp(pdata, psize, pix);
+        break;
+
+    case IFF_JFIF_JPEG:   /* default quality; baseline sequential */
+        ret = pixWriteMemJpeg(pdata, psize, pix, 75, 0);
+        break;
+
+    case IFF_PNG:   /* no gamma value stored */
+        ret = pixWriteMemPng(pdata, psize, pix, 0.0);
+        break;
+
+    case IFF_TIFF:           /* uncompressed */
+    case IFF_TIFF_PACKBITS:  /* compressed, binary only */
+    case IFF_TIFF_RLE:       /* compressed, binary only */
+    case IFF_TIFF_G3:        /* compressed, binary only */
+    case IFF_TIFF_G4:        /* compressed, binary only */
+    case IFF_TIFF_LZW:       /* compressed, all depths */
+    case IFF_TIFF_ZIP:       /* compressed, all depths */
+        ret = pixWriteMemTiff(pdata, psize, pix, format);
+        break;
+
+    case IFF_PNM:
+        ret = pixWriteMemPnm(pdata, psize, pix);
+        break;
+
+    case IFF_PS:
+        ret = pixWriteMemPS(pdata, psize, pix, NULL, 0, DEFAULT_SCALING);
+        break;
+
+    case IFF_GIF:
+        ret = pixWriteMemGif(pdata, psize, pix);
+        break;
+
+    case IFF_JP2:
+        ret = pixWriteMemJp2k(pdata, psize, pix, 34, 0, 0, 0);
+        break;
+
+    case IFF_WEBP:
+        ret = pixWriteMemWebP(pdata, psize, pix, 80, 0);
+        break;
+
+    case IFF_LPDF:
+        ret = pixWriteMemPdf(pdata, psize, pix, 0, NULL);
+        break;
+
+    case IFF_SPIX:
+        ret = pixWriteMemSpix(pdata, psize, pix);
+        break;
+
+    default:
+        return ERROR_INT("unknown format", procName, 1);
+        break;
+    }
+
+    return ret;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                       Image display for debugging                   *
+ *---------------------------------------------------------------------*/
+/*!
+ *  pixDisplay()
+ *
+ *      Input:  pix (1, 2, 4, 8, 16, 32 bpp)
+ *              x, y  (location of display frame on the screen)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This displays the image using xzgv, xli or xv on Unix,
+ *          or i_view on Windows.  The display program must be on
+ *          your $PATH variable.  It is chosen by setting the global
+ *          var_DISPLAY_PROG, using l_chooseDisplayProg().
+ *          Default on Unix is xzgv.
+ *      (2) Images with dimensions larger than MAX_DISPLAY_WIDTH or
+ *          MAX_DISPLAY_HEIGHT are downscaled to fit those constraints.
+ *          This is particularly important for displaying 1 bpp images
+ *          with xv, because xv automatically downscales large images
+ *          by subsampling, which looks poor.  For 1 bpp, we use
+ *          scale-to-gray to get decent-looking anti-aliased images.
+ *          In all cases, we write a temporary file to /tmp/lept/disp,
+ *          that is read by the display program.
+ *      (3) For spp == 4, we call pixDisplayLayersRGBA() to show 3
+ *          versions of the image: the image with a fully opaque
+ *          alpha, the alpha, and the image as it would appear with
+ *          a white background.
+ *      (4) Note: this function uses a static internal variable to number
+ *          output files written by a single process.  Behavior with a
+ *          shared library may be unpredictable.
+ */
+l_int32
+pixDisplay(PIX     *pixs,
+           l_int32  x,
+           l_int32  y)
+{
+    return pixDisplayWithTitle(pixs, x, y, NULL, 1);
+}
+
+
+/*!
+ *  pixDisplayWithTitle()
+ *
+ *      Input:  pix (1, 2, 4, 8, 16, 32 bpp)
+ *              x, y  (location of display frame)
+ *              title (<optional> on frame; can be NULL);
+ *              dispflag (1 to write, else disabled)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) See notes for pixDisplay().
+ *      (2) This displays the image if dispflag == 1.
+ */
+l_int32
+pixDisplayWithTitle(PIX         *pixs,
+                    l_int32      x,
+                    l_int32      y,
+                    const char  *title,
+                    l_int32      dispflag)
+{
+char           *tempname;
+char            buffer[L_BUF_SIZE];
+static l_int32  index = 0;  /* caution: not .so or thread safe */
+l_int32         w, h, d, spp, maxheight, opaque, threeviews, ignore;
+l_float32       ratw, rath, ratmin;
+PIX            *pix0, *pix1, *pix2;
+PIXCMAP        *cmap;
+#ifndef _WIN32
+l_int32         wt, ht;
+#else
+char           *pathname;
+char            fullpath[_MAX_PATH];
+#endif  /* _WIN32 */
+
+    PROCNAME("pixDisplayWithTitle");
+
+    if (dispflag != 1) return 0;
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (var_DISPLAY_PROG != L_DISPLAY_WITH_XZGV &&
+        var_DISPLAY_PROG != L_DISPLAY_WITH_XLI &&
+        var_DISPLAY_PROG != L_DISPLAY_WITH_XV &&
+        var_DISPLAY_PROG != L_DISPLAY_WITH_IV &&
+        var_DISPLAY_PROG != L_DISPLAY_WITH_OPEN) {
+        return ERROR_INT("no program chosen for display", procName, 1);
+    }
+
+        /* Display with three views if either spp = 4 or if colormapped
+         * and the alpha component is not fully opaque */
+    opaque = TRUE;
+    if ((cmap = pixGetColormap(pixs)) != NULL)
+        pixcmapIsOpaque(cmap, &opaque);
+    spp = pixGetSpp(pixs);
+    threeviews = (spp == 4 || !opaque) ? TRUE : FALSE;
+
+        /* If colormapped and not opaque, remove the colormap to RGBA */
+    if (!opaque)
+        pix0 = pixRemoveColormap(pixs, REMOVE_CMAP_WITH_ALPHA);
+    else
+        pix0 = pixClone(pixs);
+
+        /* Scale if necessary; this will also remove a colormap */
+    pixGetDimensions(pix0, &w, &h, &d);
+    maxheight = (threeviews) ? MAX_DISPLAY_HEIGHT / 3 : MAX_DISPLAY_HEIGHT;
+    if (w <= MAX_DISPLAY_WIDTH && h <= maxheight) {
+        if (d == 16)  /* take MSB */
+            pix1 = pixConvert16To8(pix0, 1);
+        else
+            pix1 = pixClone(pix0);
+    } else {
+        ratw = (l_float32)MAX_DISPLAY_WIDTH / (l_float32)w;
+        rath = (l_float32)maxheight / (l_float32)h;
+        ratmin = L_MIN(ratw, rath);
+        if (ratmin < 0.125 && d == 1)
+            pix1 = pixScaleToGray8(pix0);
+        else if (ratmin < 0.25 && d == 1)
+            pix1 = pixScaleToGray4(pix0);
+        else if (ratmin < 0.33 && d == 1)
+            pix1 = pixScaleToGray3(pix0);
+        else if (ratmin < 0.5 && d == 1)
+            pix1 = pixScaleToGray2(pix0);
+        else
+            pix1 = pixScale(pix0, ratmin, ratmin);
+    }
+    pixDestroy(&pix0);
+    if (!pix1)
+        return ERROR_INT("pix1 not made", procName, 1);
+
+        /* Generate the three views if required */
+    if (threeviews)
+        pix2 = pixDisplayLayersRGBA(pix1, 0xffffff00, 0);
+    else
+        pix2 = pixClone(pix1);
+
+    if (index == 0) {
+        lept_rmdir("lept/disp");
+        lept_mkdir("lept/disp");
+    }
+
+    index++;
+    if (pixGetDepth(pix2) < 8 ||
+        (w < MAX_SIZE_FOR_PNG && h < MAX_SIZE_FOR_PNG)) {
+        snprintf(buffer, L_BUF_SIZE, "/tmp/lept/disp/write.%03d.png", index);
+        pixWrite(buffer, pix2, IFF_PNG);
+    } else {
+        snprintf(buffer, L_BUF_SIZE, "/tmp/lept/disp/write.%03d.jpg", index);
+        pixWrite(buffer, pix2, IFF_JFIF_JPEG);
+    }
+    tempname = genPathname(buffer, NULL);
+
+#ifndef _WIN32
+
+        /* Unix */
+    if (var_DISPLAY_PROG == L_DISPLAY_WITH_XZGV) {
+            /* no way to display title */
+        pixGetDimensions(pix2, &wt, &ht, NULL);
+        snprintf(buffer, L_BUF_SIZE,
+                 "xzgv --geometry %dx%d+%d+%d %s &", wt + 10, ht + 10,
+                 x, y, tempname);
+    } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_XLI) {
+        if (title) {
+            snprintf(buffer, L_BUF_SIZE,
+               "xli -dispgamma 1.0 -quiet -geometry +%d+%d -title \"%s\" %s &",
+               x, y, title, tempname);
+        } else {
+            snprintf(buffer, L_BUF_SIZE,
+               "xli -dispgamma 1.0 -quiet -geometry +%d+%d %s &",
+               x, y, tempname);
+        }
+    } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_XV) {
+        if (title) {
+            snprintf(buffer, L_BUF_SIZE,
+                     "xv -quit -geometry +%d+%d -name \"%s\" %s &",
+                     x, y, title, tempname);
+        } else {
+            snprintf(buffer, L_BUF_SIZE,
+                     "xv -quit -geometry +%d+%d %s &", x, y, tempname);
+        }
+    } else if (var_DISPLAY_PROG == L_DISPLAY_WITH_OPEN) {
+        snprintf(buffer, L_BUF_SIZE, "open %s &", tempname);
+    }
+    ignore = system(buffer);
+
+#else  /* _WIN32 */
+
+        /* Windows: L_DISPLAY_WITH_IV */
+    pathname = genPathname(tempname, NULL);
+    _fullpath(fullpath, pathname, sizeof(fullpath));
+    if (title) {
+        snprintf(buffer, L_BUF_SIZE,
+                 "i_view32.exe \"%s\" /pos=(%d,%d) /title=\"%s\"",
+                 fullpath, x, y, title);
+    } else {
+        snprintf(buffer, L_BUF_SIZE, "i_view32.exe \"%s\" /pos=(%d,%d)",
+                 fullpath, x, y);
+    }
+    ignore = system(buffer);
+    LEPT_FREE(pathname);
+
+#endif  /* _WIN32 */
+
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    LEPT_FREE(tempname);
+    return 0;
+}
+
+
+/*!
+ *  pixDisplayMultiple()
+ *
+ *      Input:  filepattern
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This allows display of multiple images using gthumb on unix
+ *          and i_view32 on windows.  The @filepattern is a regular
+ *          expression that is expanded by the shell.
+ *      (2) _fullpath automatically changes '/' to '\' if necessary.
+ */
+l_int32
+pixDisplayMultiple(const char  *filepattern)
+{
+char     buffer[L_BUF_SIZE];
+l_int32  ignore;
+#ifdef _WIN32
+char    *pathname;
+char    *dir, *tail;
+char     fullpath[_MAX_PATH];
+#endif  /* _WIN32 */
+
+    PROCNAME("pixDisplayMultiple");
+
+    if (!filepattern || strlen(filepattern) == 0)
+        return ERROR_INT("filepattern not defined", procName, 1);
+
+#ifndef _WIN32
+    snprintf(buffer, L_BUF_SIZE, "gthumb %s &", filepattern);
+#else
+        /* irFanView wants absolute path for directory */
+    pathname = genPathname(filepattern, NULL);
+    splitPathAtDirectory(pathname, &dir, &tail);
+    _fullpath(fullpath, dir, sizeof(fullpath));
+
+    snprintf(buffer, L_BUF_SIZE,
+             "i_view32.exe \"%s\" /filepattern=\"%s\" /thumbs", fullpath, tail);
+    LEPT_FREE(pathname);
+    LEPT_FREE(dir);
+    LEPT_FREE(tail);
+#endif  /* _WIN32 */
+
+    ignore = system(buffer);  /* gthumb || i_view32.exe */
+    return 0;
+}
+
+
+/*!
+ *  pixDisplayWrite()
+ *
+ *      Input:  pix (1, 2, 4, 8, 16, 32 bpp)
+ *              reduction (-1 to reset/erase; 0 to disable;
+ *                         otherwise this is a reduction factor)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This defaults to jpeg output for pix that are 32 bpp or
+ *          8 bpp without a colormap.  If you want to write all images
+ *          losslessly, use format == IFF_PNG in pixDisplayWriteFormat().
+ *      (2) See pixDisplayWriteFormat() for usage details.
+ */
+l_int32
+pixDisplayWrite(PIX     *pixs,
+                l_int32  reduction)
+{
+    return pixDisplayWriteFormat(pixs, reduction, IFF_JFIF_JPEG);
+}
+
+
+/*!
+ *  pixDisplayWriteFormat()
+ *
+ *      Input:  pix (1, 2, 4, 8, 16, 32 bpp)
+ *              reduction (-1 to reset/erase; 0 to disable;
+ *                         otherwise this is a reduction factor)
+ *              format (IFF_PNG or IFF_JFIF_JPEG)
+ *      Return: 0 if OK; 1 on error
+ *
+ *  Notes:
+ *      (1) This writes files if reduction > 0.  These can be displayed using
+ *            pixDisplayMultiple("/tmp/lept/display/file*");
+ *      (2) All previously written files can be erased by calling with
+ *          reduction < 0; the value of pixs is ignored.
+ *      (3) If reduction > 1 and depth == 1, this does a scale-to-gray
+ *          reduction.
+ *      (4) This function uses a static internal variable to number
+ *          output files written by a single process.  Behavior
+ *          with a shared library may be unpredictable.
+ *      (5) Output file format is as follows:
+ *            format == IFF_JFIF_JPEG:
+ *                png if d < 8 or d == 16 or if the output pix
+ *                has a colormap.   Otherwise, output is jpg.
+ *            format == IFF_PNG:
+ *                png (lossless) on all images.
+ *      (6) For 16 bpp, the choice of full dynamic range with log scale
+ *          is the best for displaying these images.  Alternative outputs are
+ *             pix8 = pixMaxDynamicRange(pixt, L_LINEAR_SCALE);
+ *             pix8 = pixConvert16To8(pixt, 0);  // low order byte
+ *             pix8 = pixConvert16To8(pixt, 1);  // high order byte
+ */
+l_int32
+pixDisplayWriteFormat(PIX     *pixs,
+                      l_int32  reduction,
+                      l_int32  format)
+{
+char            buf[L_BUF_SIZE];
+char           *fname;
+l_float32       scale;
+PIX            *pixt, *pix8;
+static l_int32  index = 0;  /* caution: not .so or thread safe */
+
+    PROCNAME("pixDisplayWriteFormat");
+
+    if (reduction == 0) return 0;
+
+    if (reduction < 0) {
+        index = 0;  /* reset; this will cause erasure at next call to write */
+        return 0;
+    }
+
+    if (format != IFF_JFIF_JPEG && format != IFF_PNG)
+        return ERROR_INT("invalid format", procName, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+
+    if (index == 0) {
+        lept_rmdir("lept/display");
+        lept_mkdir("lept/display");
+    }
+    index++;
+
+    if (reduction == 1) {
+        pixt = pixClone(pixs);
+    } else {
+        scale = 1. / (l_float32)reduction;
+        if (pixGetDepth(pixs) == 1)
+            pixt = pixScaleToGray(pixs, scale);
+        else
+            pixt = pixScale(pixs, scale, scale);
+    }
+
+    if (pixGetDepth(pixt) == 16) {
+        pix8 = pixMaxDynamicRange(pixt, L_LOG_SCALE);
+        snprintf(buf, L_BUF_SIZE, "file.%03d.png", index);
+        fname = genPathname("/tmp/lept/display", buf);
+        pixWrite(fname, pix8, IFF_PNG);
+        pixDestroy(&pix8);
+    } else if (pixGetDepth(pixt) < 8 || pixGetColormap(pixt) ||
+             format == IFF_PNG) {
+        snprintf(buf, L_BUF_SIZE, "file.%03d.png", index);
+        fname = genPathname("/tmp/lept/display", buf);
+        pixWrite(fname, pixt, IFF_PNG);
+    } else {
+        snprintf(buf, L_BUF_SIZE, "file.%03d.jpg", index);
+        fname = genPathname("/tmp/lept/display", buf);
+        pixWrite(fname, pixt, format);
+    }
+    LEPT_FREE(fname);
+    pixDestroy(&pixt);
+
+    return 0;
+}
+
+
+/*!
+ *  pixSaveTiled()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 32 bpp)
+ *              pixa (the pix are accumulated here)
+ *              scalefactor (0.0 to disable; otherwise this is a scale factor)
+ *              newrow (0 if placed on the same row as previous; 1 otherwise)
+ *              space (horizontal and vertical spacing, in pixels)
+ *              dp (depth of pixa; 8 or 32 bpp; only used on first call)
+ *      Return: 0 if OK, 1 on error.
+ */
+l_int32
+pixSaveTiled(PIX       *pixs,
+             PIXA      *pixa,
+             l_float32  scalefactor,
+             l_int32    newrow,
+             l_int32    space,
+             l_int32    dp)
+{
+        /* Save without an outline */
+    return pixSaveTiledOutline(pixs, pixa, scalefactor, newrow, space, 0, dp);
+}
+
+
+/*!
+ *  pixSaveTiledOutline()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 32 bpp)
+ *              pixa (the pix are accumulated here)
+ *              scalefactor (0.0 to disable; otherwise this is a scale factor)
+ *              newrow (0 if placed on the same row as previous; 1 otherwise)
+ *              space (horizontal and vertical spacing, in pixels)
+ *              linewidth (width of added outline for image; 0 for no outline)
+ *              dp (depth of pixa; 8 or 32 bpp; only used on first call)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) Before calling this function for the first time, use
+ *          pixaCreate() to make the @pixa that will accumulate the pix.
+ *          This is passed in each time pixSaveTiled() is called.
+ *      (2) @scalefactor scales the input image.  After scaling and
+ *          possible depth conversion, the image is saved in the input
+ *          pixa, along with a box that specifies the location to
+ *          place it when tiled later.  Disable saving the pix by
+ *          setting @scalefactor == 0.0.
+ *      (3) @newrow and @space specify the location of the new pix
+ *          with respect to the last one(s) that were entered.
+ *      (4) @dp specifies the depth at which all pix are saved.  It can
+ *          be only 8 or 32 bpp.  Any colormap is removed.  This is only
+ *          used at the first invocation.
+ *      (5) This function uses two variables from call to call.
+ *          If they were static, the function would not be .so or thread
+ *          safe, and furthermore, there would be interference with two or
+ *          more pixa accumulating images at a time.  Consequently,
+ *          we use the first pix in the pixa to store and obtain both
+ *          the depth and the current position of the bottom (one pixel
+ *          below the lowest image raster line when laid out using
+ *          the boxa).  The bottom variable is stored in the input format
+ *          field, which is the only field available for storing an int.
+ */
+l_int32
+pixSaveTiledOutline(PIX       *pixs,
+                    PIXA      *pixa,
+                    l_float32  scalefactor,
+                    l_int32    newrow,
+                    l_int32    space,
+                    l_int32    linewidth,
+                    l_int32    dp)
+{
+l_int32  n, top, left, bx, by, bw, w, h, depth, bottom;
+BOX     *box;
+PIX     *pix1, *pix2, *pix3, *pix4;
+
+    PROCNAME("pixSaveTiledOutline");
+
+    if (scalefactor == 0.0) return 0;
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    n = pixaGetCount(pixa);
+    if (n == 0) {
+        bottom = 0;
+        if (dp != 8 && dp != 32) {
+            L_WARNING("dp not 8 or 32 bpp; using 32\n", procName);
+            depth = 32;
+        } else {
+            depth = dp;
+        }
+    } else {  /* extract the depth and bottom params from the first pix */
+        pix1 = pixaGetPix(pixa, 0, L_CLONE);
+        depth = pixGetDepth(pix1);
+        bottom = pixGetInputFormat(pix1);  /* not typical usage! */
+        pixDestroy(&pix1);
+    }
+
+        /* Remove colormap if it exists; otherwise a copy.  This
+         * guarantees that pix4 is not a clone of pixs. */
+    pix1 = pixRemoveColormapGeneral(pixs, REMOVE_CMAP_BASED_ON_SRC, L_COPY);
+
+        /* Scale and convert to output depth */
+    if (scalefactor == 1.0) {
+        pix2 = pixClone(pix1);
+    } else if (scalefactor > 1.0) {
+        pix2 = pixScale(pix1, scalefactor, scalefactor);
+    } else if (scalefactor < 1.0) {
+        if (pixGetDepth(pix1) == 1)
+            pix2 = pixScaleToGray(pix1, scalefactor);
+        else
+            pix2 = pixScale(pix1, scalefactor, scalefactor);
+    }
+    pixDestroy(&pix1);
+    if (depth == 8)
+        pix3 = pixConvertTo8(pix2, 0);
+    else
+        pix3 = pixConvertTo32(pix2);
+    pixDestroy(&pix2);
+
+        /* Add black outline */
+    if (linewidth > 0)
+        pix4 = pixAddBorder(pix3, linewidth, 0);
+    else
+        pix4 = pixClone(pix3);
+    pixDestroy(&pix3);
+
+        /* Find position of current pix (UL corner plus size) */
+    if (n == 0) {
+        top = 0;
+        left = 0;
+    } else if (newrow == 1) {
+        top = bottom + space;
+        left = 0;
+    } else if (n > 0) {
+        pixaGetBoxGeometry(pixa, n - 1, &bx, &by, &bw, NULL);
+        top = by;
+        left = bx + bw + space;
+    }
+
+    pixGetDimensions(pix4, &w, &h, NULL);
+    bottom = L_MAX(bottom, top + h);
+    box = boxCreate(left, top, w, h);
+    pixaAddPix(pixa, pix4, L_INSERT);
+    pixaAddBox(pixa, box, L_INSERT);
+
+        /* Save the new bottom value */
+    pix1 = pixaGetPix(pixa, 0, L_CLONE);
+    pixSetInputFormat(pix1, bottom);  /* not typical usage! */
+    pixDestroy(&pix1);
+    return 0;
+}
+
+
+/*!
+ *  pixSaveTiledWithText()
+ *
+ *      Input:  pixs (1, 2, 4, 8, 32 bpp)
+ *              pixa (the pix are accumulated here; as 32 bpp)
+ *              outwidth (in pixels; use 0 to disable entirely)
+ *              newrow (1 to start a new row; 0 to go on same row as previous)
+ *              space (horizontal and vertical spacing, in pixels)
+ *              linewidth (width of added outline for image; 0 for no outline)
+ *              bmf (<optional> font struct)
+ *              textstr (<optional> text string to be added)
+ *              val (color to set the text)
+ *              location (L_ADD_ABOVE, L_ADD_AT_TOP, L_ADD_AT_BOT, L_ADD_BELOW)
+ *      Return: 0 if OK, 1 on error.
+ *
+ *  Notes:
+ *      (1) Before calling this function for the first time, use
+ *          pixaCreate() to make the @pixa that will accumulate the pix.
+ *          This is passed in each time pixSaveTiled() is called.
+ *      (2) @outwidth is the scaled width.  After scaling, the image is
+ *          saved in the input pixa, along with a box that specifies
+ *          the location to place it when tiled later.  Disable saving
+ *          the pix by setting @outwidth == 0.
+ *      (3) @newrow and @space specify the location of the new pix
+ *          with respect to the last one(s) that were entered.
+ *      (4) All pix are saved as 32 bpp RGB.
+ *      (5) If both @bmf and @textstr are defined, this generates a pix
+ *          with the additional text; otherwise, no text is written.
+ *      (6) The text is written before scaling, so it is properly
+ *          antialiased in the scaled pix.  However, if the pix on
+ *          different calls have different widths, the size of the
+ *          text will vary.
+ *      (7) See pixSaveTiledOutline() for other implementation details.
+ */
+l_int32
+pixSaveTiledWithText(PIX         *pixs,
+                     PIXA        *pixa,
+                     l_int32      outwidth,
+                     l_int32      newrow,
+                     l_int32      space,
+                     l_int32      linewidth,
+                     L_BMF       *bmf,
+                     const char  *textstr,
+                     l_uint32     val,
+                     l_int32      location)
+{
+PIX  *pix1, *pix2, *pix3, *pix4;
+
+    PROCNAME("pixSaveTiledWithText");
+
+    if (outwidth == 0) return 0;
+
+    if (!pixs)
+        return ERROR_INT("pixs not defined", procName, 1);
+    if (!pixa)
+        return ERROR_INT("pixa not defined", procName, 1);
+
+    pix1 = pixConvertTo32(pixs);
+    if (linewidth > 0)
+        pix2 = pixAddBorder(pix1, linewidth, 0);
+    else
+        pix2 = pixClone(pix1);
+    if (bmf && textstr)
+        pix3 = pixAddSingleTextblock(pix2, bmf, textstr, val, location, NULL);
+    else
+        pix3 = pixClone(pix2);
+    pix4 = pixScaleToSize(pix3, outwidth, 0);
+    pixSaveTiled(pix4, pixa, 1.0, newrow, space, 32);
+    pixDestroy(&pix1);
+    pixDestroy(&pix2);
+    pixDestroy(&pix3);
+    pixDestroy(&pix4);
+    return 0;
+}
+
+
+void
+l_chooseDisplayProg(l_int32  selection)
+{
+    if (selection == L_DISPLAY_WITH_XLI ||
+        selection == L_DISPLAY_WITH_XZGV ||
+        selection == L_DISPLAY_WITH_XV ||
+        selection == L_DISPLAY_WITH_IV ||
+        selection == L_DISPLAY_WITH_OPEN) {
+        var_DISPLAY_PROG = selection;
+    } else {
+        L_ERROR("invalid display program\n", "l_chooseDisplayProg");
+    }
+    return;
+}
diff --git a/src/xtractprotos.c b/src/xtractprotos.c
new file mode 100644 (file)
index 0000000..6791612
--- /dev/null
@@ -0,0 +1,255 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ * xtractprotos.c
+ *
+ *   This program accepts a list of C files on the command line
+ *   and outputs the C prototypes to stdout.  It uses cpp to
+ *   handle the preprocessor macros, and then parses the cpp output.
+ *   In leptonica, it is used to make allheaders.h (and optionally
+ *   leptprotos.h, which contains just the function prototypes.)
+ *   In leptonica, only the file allheaders.h is included with
+ *   source files.
+ *
+ *   An optional 'prestring' can be prepended to each declaration.
+ *   And the function prototypes can either be sent to stdout, written
+ *   to a named file, or placed in-line within allheaders.h.
+ *
+ *   The signature is:
+ *
+ *     xtractprotos [-prestring=<string>] [-protos=<where>] [list of C files]
+ *
+ *   Without -protos, the prototypes are written to stdout.
+ *   With -protos, allheaders.h is rewritten:
+ *      * if you use -protos=inline, the prototypes are placed within
+ *        allheaders.h.
+ *      * if you use -protos=leptprotos.h, the prototypes written to
+ *        the file leptprotos.h, and alltypes.h has
+ *           #include "leptprotos.h"
+ *
+ *   For constructing allheaders.h, two text files are provided:
+ *      allheaders_top.txt
+ *      allheaders_bot.txt
+ *   The former contains the leptonica version number, so it must
+ *   be updated when a new version is made.
+ *
+ *   For simple C prototype extraction, xtractprotos has essentially
+ *   the same functionality as Adam Bryant's cextract, but the latter
+ *   has not been officially supported for over 15 years, has been
+ *   patched numerous times, and doesn't work with sys/sysmacros.h
+ *   for 64 bit architecture.
+ *
+ *   This is used to extract all prototypes in liblept.
+ *   The function that does all the work is parseForProtos(),
+ *   which takes as input the output from cpp.
+ *
+ *   xtractprotos can run in leptonica to do an 'ab initio' generation
+ *   of allheaders.h; that is, it can make allheaders.h without
+ *   leptprotos.h and with an allheaders.h file of 0 length.
+ *   Of course, the usual situation is to run it with a valid allheaders.h,
+ *   which includes all the function prototypes.  To avoid including
+ *   all the prototypes in the input for each file, cpp runs here
+ *   with -DNO_PROTOS, so the prototypes are not included -- this is
+ *   much faster.
+ *
+ *   The xtractprotos version number, defined below, is incremented
+ *   whenever a new version is made.
+ *
+ *   N.B. This uses cpp to preprocess the input.
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static const l_int32  L_BUF_SIZE = 512;
+
+    /* Cygwin needs an extension to prevent it from appending
+     * ".exe" to the filename */
+static const char *tempfile = "/tmp/temp_cpp_output.txt";
+static const char *version = "1.5";
+
+
+int main(int    argc,
+         char **argv)
+{
+char        *filein, *str, *prestring, *outprotos, *protostr;
+const char  *spacestr = " ";
+char         buf[L_BUF_SIZE];
+l_uint8     *allheaders;
+l_int32      i, maxindex, in_line, nflags, protos_added, firstfile, len, ret;
+size_t       nbytes;
+L_BYTEA     *ba, *ba2;
+SARRAY      *sa, *safirst;
+static char  mainName[] = "xtractprotos";
+
+    if (argc == 1) {
+        fprintf(stderr,
+                "xtractprotos [-prestring=<string>] [-protos=<where>] "
+                "[list of C files]\n"
+                "where the prestring is prepended to each prototype, and \n"
+                "protos can be either 'inline' or the name of an output "
+                "prototype file\n");
+        return 1;
+    }
+
+    /* ---------------------------------------------------------------- */
+    /* Parse input flags and find prestring and outprotos, if requested */
+    /* ---------------------------------------------------------------- */
+    prestring = outprotos = NULL;
+    in_line = FALSE;
+    nflags = 0;
+    maxindex = L_MIN(3, argc);
+    for (i = 1; i < maxindex; i++) {
+        if (argv[i][0] == '-') {
+            if (!strncmp(argv[i], "-prestring", 10)) {
+                nflags++;
+                ret = sscanf(argv[i] + 1, "prestring=%s", buf);
+                if (ret != 1) {
+                    fprintf(stderr, "parse failure for prestring\n");
+                    return 1;
+                }
+                if ((len = strlen(buf)) > L_BUF_SIZE - 3) {
+                    L_WARNING("prestring too large; omitting!\n", mainName);
+                } else {
+                    buf[len] = ' ';
+                    buf[len + 1] = '\0';
+                    prestring = stringNew(buf);
+                }
+            } else if (!strncmp(argv[i], "-protos", 7)) {
+                nflags++;
+                ret = sscanf(argv[i] + 1, "protos=%s", buf);
+                if (ret != 1) {
+                    fprintf(stderr, "parse failure for protos\n");
+                    return 1;
+                }
+                outprotos = stringNew(buf);
+                if (!strncmp(outprotos, "inline", 7))
+                    in_line = TRUE;
+            }
+        }
+    }
+
+    if (argc - nflags < 2) {
+        fprintf(stderr, "no files specified!\n");
+        return 1;
+    }
+
+
+    /* ---------------------------------------------------------------- */
+    /*                   Generate the prototype string                  */
+    /* ---------------------------------------------------------------- */
+    ba = l_byteaCreate(500);
+
+        /* First the extern C head */
+    sa = sarrayCreate(0);
+    sarrayAddString(sa, (char *)"/*", L_COPY);
+    snprintf(buf, L_BUF_SIZE,
+             " *  These prototypes were autogen'd by xtractprotos, v. %s",
+             version);
+    sarrayAddString(sa, buf, L_COPY);
+    sarrayAddString(sa, (char *)" */", L_COPY);
+    sarrayAddString(sa, (char *)"#ifdef __cplusplus", L_COPY);
+    sarrayAddString(sa, (char *)"extern \"C\" {", L_COPY);
+    sarrayAddString(sa, (char *)"#endif  /* __cplusplus */\n", L_COPY);
+    str = sarrayToString(sa, 1);
+    l_byteaAppendString(ba, str);
+    lept_free(str);
+    sarrayDestroy(&sa);
+
+        /* Then the prototypes */
+    firstfile = 1 + nflags;
+    protos_added = FALSE;
+    for (i = firstfile; i < argc; i++) {
+        filein = argv[i];
+        len = strlen(filein);
+        if (filein[len - 1] == 'h')  /* skip .h files */
+            continue;
+        snprintf(buf, L_BUF_SIZE, "cpp -ansi -DNO_PROTOS %s %s",
+                 filein, tempfile);
+        ret = system(buf);  /* cpp */
+        if (ret) {
+            fprintf(stderr, "cpp failure for %s; continuing\n", filein);
+            continue;
+        }
+
+        if ((str = parseForProtos(tempfile, prestring)) == NULL) {
+            fprintf(stderr, "parse failure for %s; continuing\n", filein);
+            continue;
+        }
+        if (strlen(str) > 1) {  /* strlen(str) == 1 is a file without protos */
+            l_byteaAppendString(ba, str);
+            protos_added = TRUE;
+        }
+        lept_free(str);
+    }
+
+        /* Lastly the extern C tail */
+    sa = sarrayCreate(0);
+    sarrayAddString(sa, (char *)"\n#ifdef __cplusplus", L_COPY);
+    sarrayAddString(sa, (char *)"}", L_COPY);
+    sarrayAddString(sa, (char *)"#endif  /* __cplusplus */", L_COPY);
+    str = sarrayToString(sa, 1);
+    l_byteaAppendString(ba, str);
+    lept_free(str);
+    sarrayDestroy(&sa);
+
+    protostr = (char *)l_byteaCopyData(ba, &nbytes);
+    l_byteaDestroy(&ba);
+
+
+    /* ---------------------------------------------------------------- */
+    /*                       Generate the output                        */
+    /* ---------------------------------------------------------------- */
+    if (!outprotos) {  /* just write to stdout */
+        fprintf(stderr, "%s\n", protostr);
+        lept_free(protostr);
+        return 0;
+    }
+
+        /* If no protos were found, do nothing further */
+    if (!protos_added) {
+        fprintf(stderr, "No protos found\n");
+        lept_free(protostr);
+        return 1;
+    }
+
+        /* Make the output files */
+    ba = l_byteaInitFromFile("allheaders_top.txt");
+    if (!in_line) {
+        snprintf(buf, sizeof(buf), "#include \"%s\"\n", outprotos);
+        l_byteaAppendString(ba, buf);
+        l_binaryWrite(outprotos, "w", protostr, nbytes);
+    } else {
+        l_byteaAppendString(ba, protostr);
+    }
+    ba2 = l_byteaInitFromFile("allheaders_bot.txt");
+    l_byteaJoin(ba, &ba2);
+    l_byteaWrite("allheaders.h", ba, 0, 0);
+    l_byteaDestroy(&ba);
+    lept_free(protostr);
+    return 0;
+}
diff --git a/src/zlibmem.c b/src/zlibmem.c
new file mode 100644 (file)
index 0000000..a2b5c0d
--- /dev/null
@@ -0,0 +1,254 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+
+/*
+ *   zlibmem.c
+ *
+ *      zlib operations in memory, using bbuffer
+ *          l_uint8   *zlibCompress()
+ *          l_uint8   *zlibUncompress()
+ *
+ *
+ *    This provides an example use of the byte buffer utility
+ *    (see bbuffer.c for details of how the bbuffer works internally).
+ *    We use zlib to compress and decompress a byte array from
+ *    one memory buffer to another.  The standard method uses streams,
+ *    but here we use the bbuffer as an expandable queue of pixels
+ *    for both the reading and writing sides of each operation.
+ *
+ *    With memory mapping, one should be able to compress between
+ *    memory buffers by using the file system to buffer everything in
+ *    the background, but the bbuffer implementation is more portable.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  HAVE_LIBZ   /* defined in environ.h */
+/* --------------------------------------------*/
+
+#include "zlib.h"
+
+static const l_int32  L_BUF_SIZE = 32768;
+static const l_int32  ZLIB_COMPRESSION_LEVEL = 6;
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG     0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*!
+ *  zlibCompress()
+ *
+ *      Input:  datain (byte buffer with input data)
+ *              nin    (number of bytes of input data)
+ *              &nout  (<return> number of bytes of output data)
+ *      Return: dataout (compressed data), or null on error
+ *
+ *  Notes:
+ *      (1) We repeatedly read in and fill up an input buffer,
+ *          compress the data, and read it back out.  zlib
+ *          uses two byte buffers internally in the z_stream
+ *          data structure.  We use the bbuffers to feed data
+ *          into the fixed bufferin, and feed it out of bufferout,
+ *          in the same way that a pair of streams would normally
+ *          be used if the data were being read from one file
+ *          and written to another.  This is done iteratively,
+ *          compressing L_BUF_SIZE bytes of input data at a time.
+ */
+l_uint8 *
+zlibCompress(l_uint8  *datain,
+             size_t    nin,
+             size_t   *pnout)
+{
+l_uint8    *dataout;
+l_int32     status;
+l_int32     flush;
+size_t      nbytes;
+l_uint8    *bufferin, *bufferout;
+L_BBUFFER  *bbin, *bbout;
+z_stream    z;
+
+    PROCNAME("zlibCompress");
+
+    if (!datain)
+        return (l_uint8 *)ERROR_PTR("datain not defined", procName, NULL);
+
+        /* Set up fixed size buffers used in z_stream */
+    if ((bufferin = (l_uint8 *)LEPT_CALLOC(L_BUF_SIZE, sizeof(l_uint8)))
+        == NULL)
+        return (l_uint8 *)ERROR_PTR("bufferin not made", procName, NULL);
+    if ((bufferout = (l_uint8 *)LEPT_CALLOC(L_BUF_SIZE, sizeof(l_uint8)))
+        == NULL)
+        return (l_uint8 *)ERROR_PTR("bufferout not made", procName, NULL);
+
+        /* Set up bbuffers and load bbin with the data */
+    if ((bbin = bbufferCreate(datain, nin)) == NULL)
+        return (l_uint8 *)ERROR_PTR("bbin not made", procName, NULL);
+    if ((bbout = bbufferCreate(NULL, 0)) == NULL)
+        return (l_uint8 *)ERROR_PTR("bbout not made", procName, NULL);
+
+    z.zalloc = (alloc_func)0;
+    z.zfree = (free_func)0;
+    z.opaque = (voidpf)0;
+
+    z.next_in = bufferin;
+    z.avail_in = 0;
+    z.next_out = bufferout;
+    z.avail_out = L_BUF_SIZE;
+
+    status = deflateInit(&z, ZLIB_COMPRESSION_LEVEL);
+    if (status != Z_OK)
+      return (l_uint8 *)ERROR_PTR("deflateInit failed", procName, NULL);
+
+    do {
+        if (z.avail_in == 0) {
+            z.next_in = bufferin;
+            bbufferWrite(bbin, bufferin, L_BUF_SIZE, &nbytes);
+#if DEBUG
+            fprintf(stderr, " wrote %lu bytes to bufferin\n",
+                    (unsigned long)nbytes);
+#endif  /* DEBUG */
+            z.avail_in = nbytes;
+        }
+        flush = (bbin->n) ? Z_SYNC_FLUSH : Z_FINISH;
+        status = deflate(&z, flush);
+#if DEBUG
+        fprintf(stderr, " status is %d, bytesleft = %u, totalout = %lu\n",
+                  status, z.avail_out, z.total_out);
+#endif  /* DEBUG */
+        nbytes = L_BUF_SIZE - z.avail_out;
+        if (nbytes) {
+            bbufferRead(bbout, bufferout, nbytes);
+#if DEBUG
+            fprintf(stderr, " read %lu bytes from bufferout\n",
+                    (unsigned long)nbytes);
+#endif  /* DEBUG */
+        }
+        z.next_out = bufferout;
+        z.avail_out = L_BUF_SIZE;
+    } while (flush != Z_FINISH);
+
+    deflateEnd(&z);
+
+    bbufferDestroy(&bbin);
+    dataout = bbufferDestroyAndSaveData(&bbout, pnout);
+
+    LEPT_FREE(bufferin);
+    LEPT_FREE(bufferout);
+    return dataout;
+}
+
+
+/*!
+ *  zlibUncompress()
+ *
+ *      Input:  datain (byte buffer with compressed input data)
+ *              nin    (number of bytes of input data)
+ *              &nout  (<return> number of bytes of output data)
+ *      Return: dataout (uncompressed data), or null on error
+ *
+ *  Notes:
+ *      (1) See zlibCompress().
+ */
+l_uint8 *
+zlibUncompress(l_uint8  *datain,
+               size_t    nin,
+               size_t   *pnout)
+{
+l_uint8    *dataout;
+l_uint8    *bufferin, *bufferout;
+l_int32     status;
+size_t      nbytes;
+L_BBUFFER  *bbin, *bbout;
+z_stream    z;
+
+    PROCNAME("zlibUncompress");
+
+    if (!datain)
+        return (l_uint8 *)ERROR_PTR("datain not defined", procName, NULL);
+
+    if ((bufferin = (l_uint8 *)LEPT_CALLOC(L_BUF_SIZE, sizeof(l_uint8)))
+        == NULL)
+        return (l_uint8 *)ERROR_PTR("bufferin not made", procName, NULL);
+    if ((bufferout = (l_uint8 *)LEPT_CALLOC(L_BUF_SIZE, sizeof(l_uint8)))
+        == NULL)
+        return (l_uint8 *)ERROR_PTR("bufferout not made", procName, NULL);
+
+    if ((bbin = bbufferCreate(datain, nin)) == NULL)
+        return (l_uint8 *)ERROR_PTR("bbin not made", procName, NULL);
+    if ((bbout = bbufferCreate(NULL, 0)) == NULL)
+        return (l_uint8 *)ERROR_PTR("bbout not made", procName, NULL);
+
+    z.zalloc = (alloc_func)0;
+    z.zfree = (free_func)0;
+
+    z.next_in = bufferin;
+    z.avail_in = 0;
+    z.next_out = bufferout;
+    z.avail_out = L_BUF_SIZE;
+
+    inflateInit(&z);
+
+    for ( ; ; ) {
+        if (z.avail_in == 0) {
+            z.next_in = bufferin;
+            bbufferWrite(bbin, bufferin, L_BUF_SIZE, &nbytes);
+/*            fprintf(stderr, " wrote %d bytes to bufferin\n", nbytes); */
+            z.avail_in = nbytes;
+        }
+        if (z.avail_in == 0)
+            break;
+        status = inflate(&z, Z_SYNC_FLUSH);
+/*        fprintf(stderr, " status is %d, bytesleft = %d, totalout = %d\n",
+                  status, z.avail_out, z.total_out); */
+        nbytes = L_BUF_SIZE - z.avail_out;
+        if (nbytes) {
+            bbufferRead(bbout, bufferout, nbytes);
+/*            fprintf(stderr, " read %d bytes from bufferout\n", nbytes); */
+        }
+        z.next_out = bufferout;
+        z.avail_out = L_BUF_SIZE;
+    }
+
+    inflateEnd(&z);
+
+    bbufferDestroy(&bbin);
+    dataout = bbufferDestroyAndSaveData(&bbout, pnout);
+
+    LEPT_FREE(bufferin);
+    LEPT_FREE(bufferout);
+    return dataout;
+}
+
+/* --------------------------------------------*/
+#endif  /* HAVE_LIBZ */
+/* --------------------------------------------*/
diff --git a/src/zlibmemstub.c b/src/zlibmemstub.c
new file mode 100644 (file)
index 0000000..f049fbd
--- /dev/null
@@ -0,0 +1,57 @@
+/*====================================================================*
+ -  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.
+ *====================================================================*/
+
+/*
+ *  zlibmemstub.c
+ *
+ *     Stubs for zlibmem.c functions
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config_auto.h"
+#endif  /* HAVE_CONFIG_H */
+
+#include "allheaders.h"
+
+/* --------------------------------------------*/
+#if  !HAVE_LIBZ   /* defined in environ.h */
+/* --------------------------------------------*/
+
+l_uint8 * zlibCompress(l_uint8 *datain, size_t nin, size_t *pnout)
+{
+    return (l_uint8 *)ERROR_PTR("function not present", "zlibCompress", NULL);
+}
+
+/* ----------------------------------------------------------------------*/
+
+l_uint8 * zlibUncompress(l_uint8 *datain, size_t nin, size_t *pnout)
+{
+    return (l_uint8 *)ERROR_PTR("function not present", "zlibUncompress", NULL);
+}
+
+/* --------------------------------------------*/
+#endif  /* !HAVE_LIBZ */
+/* --------------------------------------------*/
diff --git a/style-guide.txt b/style-guide.txt
new file mode 100644 (file)
index 0000000..107a107
--- /dev/null
@@ -0,0 +1,193 @@
+/*====================================================================*
+ -  Copyright (C) 2001 Leptonica.  All rights reserved.
+ -  This software is distributed in the hope that it will be
+ -  useful, but with NO WARRANTY OF ANY KIND.
+ -  No author or distributor accepts responsibility to anyone for the
+ -  consequences of using this software, or for whether it serves any
+ -  particular purpose or works at all, unless he or she says so in
+ -  writing.  Everyone is granted permission to copy, modify and
+ -  redistribute this source code, for commercial or non-commercial
+ -  purposes, with the following restrictions: (1) the origin of this
+ -  source code must not be misrepresented; (2) modified versions must
+ -  be plainly marked as such; and (3) this notice may not be removed
+ -  or altered from any source or modified source distribution.
+ *====================================================================*/
+
+                        style-guide.txt
+
+                         24 Jan 2014
+
+   [This is not complete.  You need to look at existing code to verify
+    your code meets the style guidelines.  And if you find any aberrant
+    code, please let me know!]
+
+
+The C code in leptonica follows these conventions:
+
+(1) ANSI C, with no exceptions
+
+   (a) C-style comments only:   /*  */
+
+   (b) Variables are declared at the beginning of a function.
+       [This is more strict than ANSI C, which only requires declarations
+       to be at the beginning of a scope delineated by braces.]
+
+   (c) Use typedefs for structs like Pix; e.g.,
+          function(PIX  *pixs)
+       Do not do this; it is valid C++, but not C:
+          function(Pix  *pixs)
+
+(2) Formatting
+
+   (a) White space:  4 space indentation.  No tabs, ever.  No trailing spaces.
+
+   (b) Function header is in this format:
+
+      /*!
+       *  boxContains()
+       *
+       *      Input:  box1, box2
+       *              &result (<return> 1 if box2 is entirely contained within
+       *                       box1, and 0 otherwise)
+       *      Return: 0 if OK, 1 on error
+       */
+
+   (c) Function definition has return value on separate line and starting
+       brace on separate line.
+     
+       PIX *
+       function(...)
+       {
+
+   (d) Function arguments and local variables line up vertically;
+       allow at least 2 spaces between type and variable name (including '*')
+
+       function(PIX        *pixs,
+                l_int32     factor,
+                l_float32  *pave)
+       {
+       char        buf[BUF_SIZE];
+       l_int32     w, h, d;
+       l_float32  *vect;
+
+   (e) Braces are placed like this for 'if', 'while', 'do':
+
+       if (...) {
+           ...
+       } else if (...) {
+           ...
+       }
+  
+       The exceptions are for the beginning of a function and for the switch:
+
+       switch (x)
+       {
+       case 1:
+           ...
+       ...
+       }
+
+       Braces are required if any of the clauses have a single statement:
+       
+       if (...) {
+           x = 0;
+       } else {
+           x++;
+           y = 3.0 * x;
+       }
+
+   (f) Section headers should look like this:
+
+   /*----------------------------------------------------------------------*
+    *                  Statistics in an arbitrary rectangle                *
+    *----------------------------------------------------------------------*/
+
+   (g) Major inline comments (starting a section) should be indented
+       4 extra spaces and start with a capital.  Multiple line comments
+       should be formatted like this:
+
+           /* If w and h not input, determine the minimum size required
+            * to contain the origin and all c.c. */
+
+   (h) Minor inline comments (e.g., at the end of a line) should have
+       2 spaces and no leading capital; e.g.
+
+          if (i && ((i % ncols) == 0)) {  /* start new row */
+          
+(3) Naming
+
+   (a) Function names begin with lower case and successive words have
+       the first letter capitalized; e.g., boxIntersects().
+
+   (b) The first word in the function name is the name of the primary
+       input data structure (if there is one).
+
+   (c) Variable names are as short as possible, without causing confusion.
+
+   (d) Pointers to data structures are typically named by the type of
+       struct, without a leading 'p'; e.g., pixt, boxt.
+
+   (e) When ptrs are input to a function, in order to return a value,
+       if the local name would be 'ave', the pointer is 'pave'.
+
+   (f) Preprocessor variables and enums are named all caps,
+       with '_' between parts.
+
+   (g) There are very few globals in the library.  Of these, there
+       are just a handful of static globals that can be changed.
+       Globals are named with each word beginning with a capital; e.g.,
+            ImageFileFormatExtensions[]
+       Static globals are named like preprocessor variables, except
+       they are prepended by 'var_'; e.g.,
+            var_PNG_WRITE_ALPHA
+       Functions that set globals are named with a pre-pended 'l_'; e.g.,
+            l_pngSetWriteAlpha()
+
+(4) Arg checking
+
+   Both number values and ptrs can be returned in function arguments.
+   The following applies equally to both types, and happens at the
+   beginning of the function.  We distinguish between returned entities
+   that are optional and required.
+
+   (a) First, all optional values are initialized if possible:
+   
+          if (ppixd) *ppixd = NULL;  // Pix **ppixd is optional
+
+   (b) Second, if there is more than 1 required value, each is
+       initialized if possible:
+   
+          if (pnar) *pnar = NULL;  // Numa **pnar is required
+          if (pnag) *pnag = NULL;  // Numa **pnag is required
+
+       Then all required arguments are tested in arbitrary order.
+
+       But if there is exactly 1 required value, it is checked and
+       initialized if possible:
+
+          if (!ppixd)
+              return ERROR_INT("&pixd not defined, procName, 1);
+          *ppixd = NULL;
+
+(5) Miscellaneous
+
+   (a) Look around at the code after reviewing the guidelines.
+
+   (b) Return nothing on stdout.
+
+   (c) Returns to stderr should be blockable by compiler flags, such
+       as NO_CONSOLE_IO, and by setting message severity thresholds
+       both at compile and at run time.  Naked fprintf(stderr, ...)
+       should be avoided in the library.
+
+   (d) Applications (in prog) that hand a FILE ptr to a library function,
+       or accept heap-allocated data from a library function, should
+       use special wrappers.  See lept_*() functions in utils.c.
+       
+   (e) Changes to existing data structures and API changes should be
+       avoided if possible.
+
+   (f) Accessors are typically provided for struct fields that have
+       extensive use.
+
+
diff --git a/version-notes.html b/version-notes.html
new file mode 100644 (file)
index 0000000..156529a
--- /dev/null
@@ -0,0 +1,1201 @@
+<html>
+<body BGCOLOR=FFFFE4>
+
+<!-- JS Window Closer -----
+<form>
+<center>
+<input type="button" onclick="window.close();" value="Close this window">
+</center>
+</form>
+----- JS Window Closer -->
+
+
+<h2 align=center>Version Notes for Leptonica</h2>
+
+<hr>
+<h2 align=center>  <IMG SRC="moller52.jpg" border=1 ALIGN_MIDDLE> </h2>
+<hr>
+
+<p>
+Note: The following are highlights of the changes in each version.
+They are <i>not</i> a complete listing of the modifications.
+
+<pre>
+
+1.73   25 Jan 16
+       All lept_* functions have been rewritten to avoid path rewrites for
+       output to temp files, which were introduced in 1.72.
+       Now, (1) files are written to the directory specified and (2) we
+       are careful to write to subdirectories of /tmp/lept/ for all test
+       programs, starting with the reg tests and prog/dewarp* and
+       prog/recog*.  This also required re-writing stringcode.c and
+       stringtemplate1.txt to write temp files to subdirectories.
+       Goal is to write to the specified path while not spamming the
+       /tmp and /tmp/lept directories.  This is particularly important
+       on windows because files in the <TEMP> directory are not cleared
+       on reboot.
+       Naming changes (to avoid collisions):
+         #defines MALLOC --> LEPT_MALLOC, CALLOC --> LEPT_CALLOC, etc.
+         ByteBuffer --> L_ByteBuffer
+       Added grayscale histogram functions that can be used to compare images.
+       Added functions to determine if an image region has horizontal
+       text lines.
+       Added functions to compare photo regions of images to determine
+       if they're essentially the same.
+       Added red-black tree utility functions to implement maps and sets.
+       The keys for maps and sets can be 64-bit entities (signed and
+       unsigned integers and doubles).
+       Implemented hashsets and hashmaps, using 64 bit keys.
+       Replaced the numaHash by l_dnaHash; removed numa2d
+       Improved security of tiff and gif reading, to prevent memory corruption
+       when reading bad data.
+       Removed src files: bootnumgen.c
+       Added src files: rbtree.c, rbtree.h, map.c, bootnumgen1.c, bootnumgen2.c
+       Added prog files: rbtreetest.c, maptest.c, settest.c, hashtest.c,
+                         recog_bootnum.c, percolatetest.c
+       Added files for building using cmake (Egor Pugin)
+
+1.72   5 Apr 15
+       Better handling of 1 bpp colormap read/write with png so that
+       they are losseless.  The colormap is always removed on read and
+       the conversion is to the simplest non-cmapped pix that can fully
+       represent the input -- both with and without alpha.
+       Fixed overflow bug in pixCorrelationBinary().
+       Fixed orientation flags and handling of 16 bit RGB in tiff.
+       Also new wrappers to TIFFClientOpen(), so we no longer go through
+       the file descriptor for memory operations.
+       Improvements in the dewarp functions.
+       New box sequence smoothings.
+       New antialiased painting through mask; previously it was only
+       implemented for connected components in a mask.
+       Better error handling and debug output with jpeg2000 read/write.
+       Implemented base64 encoding.  This allows binary data to be represented
+       as a C string that can be compiled.  Used this in bmf utility.
+       Implemented automatic code generation for deserialization from
+       compiled strings (stringcode.*)
+       Regression tests write to leptonica subdir of <Temp> in windows; in
+       unix it is optional.  This avoids spamming the <Temp> directory.
+       Added new colorspace conversions (XYZ, LAB).
+       New source files: encoding.c, bmfdata.h, stringcode.c, stringcode.h,
+         bootnumgen.c.
+       Removed source files: convolvelow.c, graymorphlow.c
+       New programs: genfonts_reg, colorize_reg, texturefill_reg,
+         autogentest1, autogentest2.
+       alltests_reg now has 66 tests.
+
+1.71   18 Jun 14
+       This version supports tesseract 3.0.4.  In particular, 3.0.4
+        has automatic conversion of a set of scanned images, either in a
+        directory or coming directly from a scanner, into pdf with injected
+        text.  This is something we've wanted to do for several years!
+       Improved jp2k header reading, including resolution.
+       Removed src files: rotateorthlow.c, pdfio.c, pdfiostub.c
+       Renamed jp2kio.c, jp2kiostub.c ==> jp2kheader.c, jp2kheaderstub.c.
+        These header reading functions parse the jp2k files, and
+        don't require a jpeg2000 library.
+       New jp2kio.c, jp2kiostub.c, that uses openjpeg-2.X to read
+       and write jp2k.  We now support I/O from these formats:
+         png, tiff, jpeg, bmp, pnm, webp, gif and jp2k
+        as well as writing to PostScript and pdf.
+       New pdfio1.c, pdfio1stub.c, pdfio2.c pdfio2stub.c, where we've
+        split functions into high and low level.
+       Fixed memory bug in bilateral.c
+       Improved reading/write of binary data from file.  For example,
+        l_binaryReadStream() can now be used to capture data piped
+        in via stdin.
+       Font directory now arg passed in everywhere (not hardcoded)
+       Don't write temporary files to /tmp; only to a small number of
+       subdirectories, to avoid spamming the /tmp directory.  E.g.,
+       for regression tests, the current output is now to /tmp/regout/.
+       For jpeg reading modify pixReadJpeg() to take as a hint
+        a bit flag that allows extraction of only the luminance channel.
+       Allow wrapping of pdf objects from png images without transcoding
+        (thanks to Jeff Breidenbach)
+       Better support for alpha on read/write with png, including
+       1 bpp with colormap, alpha (supported in png with transparency array)
+
+1.70   3 Feb 14   (distribution to debian; ubuntu 14-04; 4.1.0)
+       New bilateral filtering.
+       New simple character recognition utility.
+       Improved dewarping functionality, in model building and rendering.
+        More flexible use of ref models.
+       Better and more consistent handling of alpha layer in RGBA, though
+        use of the spp field.  Ability to handle more png files with alpha,
+        including palette with alpha.
+       New fast converters from jpeg and jpeg2000 to pdf, without transcoding.
+       Made bmp reader (and pix reading in general) more robust; avoid
+        size overflow errors.
+       New text labelling operations; depth conversion of a set of images
+       New license (essentially BSD 2-clause), to specify conditions
+        for both source and binary distribution.
+       Improved auto make: make all progs, install just 11, test 61.
+       New src files: bilateral.{c,h}, dewarp1.c, dewarp2.c,
+           dewarp3.c, dewarp4.c, jp2kio.c, jp2kiostub.c,
+           pixlabel.c, recogbasic.c, recogdid.c, recogident.c,
+           recogtrain.c, recog.h
+       New prog files: adaptmap_dark.c, alphaxform_ret.c,
+           bilateral_reg.c, binarize_reg.c, binarize_set.c,
+           blackwhite_reg.c, blend1_reg.c, blend3_reg.c, blend4_reg.c,
+           boxa1_reg.c, colorcontent_reg.c, coloring_reg.c,
+           colorspace_reg.c, compare_reg.c, converttopdf.c,
+           croptest.c, dewarprules.c, dewarptest1.c, dewarptest2.c,
+           dewarptest3.c, dewarptest4.c, displayboxa.c, displaypix.c,
+           displaypixa.c, findcorners_reg.c, fpix1_reg.c,
+           fpix2_reg.c, fpixcontours.c, insert_reg.c, italictest.c,
+           jpegio_reg.c, label_reg.c, multitype_reg.c, nearline_reg.c,
+           newspaper_reg.c, numa1_reg.c, numa2_reg.c, recogsort.c,
+           recogtest1.c, shear1_reg.c, webpio_reg.c, wordboxes_reg.c
+       Removed src files: arithlow.c, binexpandlow.c, binreducelow.c,
+           dewarp.c
+       Removed prog files: blend_reg.c, blendtest1.c,
+           dewarptest.c, fpix_reg.c, inserttest.c, numa_reg.c, rotatetest2.c
+           shear_reg, xvdisp.c
+
+1.69   16 Jan 12   (distribution to debian; ubuntu 12-04; 3.0.0)
+       Fixed bug in pdf generation for large files, using a new
+        double array (dnabasic.c).  Added several new modes for pdf
+        generation from sets of images.
+       Dewarp based on image content now aligns to left and right margins;
+        works at book level; is more robust to bad disparity models;
+        version 2 serialization.
+       Fixed regutils to return the actual number of errors.
+       Improved sorting efficiency of numas in cases where binning,
+        which is order N, makes sense.
+       Fixed fpix serialization (now version 2).
+       New version (5) of xtractprotos, allows putting prototypes in-line in
+        allheaders.h.  Having them separately in leptprotos.h still an option
+       New copyright (BDS, 2 clause) on src files.
+       Removed all trailing whitespace in src files.
+       New src files: boxfunc4.c coloring.c, dnabasic.c
+       New prog files: dna_reg.c, alphaops_reg.c
+       Removed prog file: alphaclean_reg.c
+
+1.68   10 Mar 11
+       Fixed windows issues with passing pointers across C-runtime boundaries
+       when using dlls, by providing special functions (e.g., lept_fopen()).
+       Proper version numbers are now set with automake.
+       New utility (quadtree.c) for generating quadtree statistics.
+       New utility (in colorspace.c) for conversions to and from YUV.
+       Refactored functions for assembling image data for generating
+       either PS or PDF images using g4, jpeg or flate encoding.
+       Better tempfile names, using current time in microseconds.
+       Functions for getting resolution from jpeg and png files.
+       Use size_t throughout for reading and writing binary data.
+       Deprecate arrayRead*() and arrayWrite() functions; replace in
+       the library with l_binaryRead*() and l_binaryWrite().
+       Better handling of colormap images for in-place rasterop and shear.
+       New utility (bytearray.c) for parsing and handling binary data;
+       used for generating PDF files.
+       New utility (pdfio.c) for generating PDF files.
+       Refactored regutils functions to make them simpler to use.
+       Top-level deskew now works on any image.
+       Added functions in utils.c for cross-platform development, mostly
+       for functions that make and remove directories, copy, move
+       and delete files, etc.  It should now be straightforward to write
+       programs that will compile and run on windows.
+       Reg tests have better printout; all give timings.
+       New utility program: convertfilestopdf
+
+1.67   9 Nov 10
+       Autoconf: now built with James Cuirot's config files that
+         build the library and all 200 progs.
+       New sudoku solver.  Just a game, but there are interesting aspects.
+       Modified parseprotos.c to reject a type of "extern" decl.
+       Add faster implementation for very small gray morphology
+         operations (3x1, 1x3, 3x3).
+       Eliminate warnings on recent gcc if you don't check return values
+         from fread, fscanf, fgets, system, etc.
+       Convolution: new functions for windowed variance and stdev; allow
+         non-square kernel for windowed mean square.
+       Put stdio.h and stdlib.h in alltypes.h, so they're not required
+         in any .c files.
+       Replace numaConvolve(), which is just a windowed mean, by
+         windowed statistics functions (mean, mean square, variance).
+       Generalize pixExtractOnLine() for arbitrary lines.
+       Add pix interface to webp (webpio.c, webpiostub.c).  This is a
+         new open source codec, based on the video codec vpx (webm).
+       Serialization of FPix and DPix
+       Interconversion between FPix and DPix
+       Integer scaling of FPix and DPix; includes the last row and column.
+       New convertfiles.c: depth conversion on files in a directory.
+       Testing programs in prog:
+           convolve_reg.c, numa_reg.c: expanded test set
+           projection_reg.c (tests pixRowStats(), pixColumnStats())
+           dewarptest.c: output ps and pdf files
+           writemtiff.c: simple driver to write images to a single file
+
+1.66   3 Aug 10
+       More tweaks for including (or not) bounding box hints for
+         PS wrapping.  Default is to write b.b., but not in functions
+         that wrap images as full pages (psio1.c, psio2.c)
+       pix4.c split in two files, and added function to identify c.c.
+         that are sufficiently similar in shape to rectangles (pix5.c)
+       Modify 2 and 4 bit setters to clip the input value so that it can
+         only affect the pixel requested (arrayaccess.c, arrayaccess,h)
+       New pseudorandom sequence functions (numafunc1.c)
+       Dewarping camera-based images using textlines (dewarp.c, prog/dewarp*)
+       Geometrical function for aggregating overlapping bounding boxes.
+       Programs to generate figures for book chapter "Document Image
+         Applications" in "Mathematical Morphology: theory and applications"
+         (see: http:/www.leptonica.org/najman-talbot-book-chapter.html)
+         (prog/livre*.c)
+       Functions that do affine and other operations in images with
+         alpha blending at edges: pix*WithAlpha().  Also do this
+         with a gamma/inverse-gamma wrapper to further reduce edge aliasing.
+          (rotate.c, scale.c, projective.c, affine.c, bilinear.c,
+           prog/alphaxform_reg.c)
+       Improved color segmentation (fixed bugs; made faster)
+       Higher order least square fits: quadratic, cubic, quartic. (pts.c)
+       Various mods for otsu binarization and the *SplitDistribution*()
+         functions (numafunc2.c, prog/otsutest2.c)
+       Control sampling in convolution output (convolve.c, prog/fpix_reg.c)
+       Morphological operations on numas (numafunc1.c, numafunc2.c,
+         prog/numa_reg.c)
+       Pix serialization wrapped so we can use pixRead(), etc on these
+         files (spixio.c, readfile.c, writefile.c)
+       Gif read/write to memory fixed (and cheated -- using files) (gifio.c)
+       New fpix and dpix accessors; contour rendering on fpix (fpix1.c, fpix2.c)
+       Various functions for linearly mapping colors and displaying arrays
+         of colors (pix4.c, blend.c, prog/rankhisto_reg.c)
+       Functions for getting approximate ranges of colors and color
+         components in an image (pix4.c, colormap.c)
+       Cleaned up windows platform and compiler defines and macros.
+
+1.65   5 Apr 10
+       Added regression test utility functions for standardizing and
+         automating construction and running of regression tests.  Makes the
+         golden files when the 2nd arg to the reg test is 'generate'.
+         (regutils.{c,h})
+         Converted 22 reg tests in prog to use this; invoked with alltests_reg.
+         Goal is to put all prog/*_reg.c into this format and put a set
+         of golden files on the web.
+       Small fixes in gifio for handling streams properly.
+       New functions for shifting colors, hue invariant transforms, etc
+         (blend.c)
+       prog/dwamorph*.c: rename *1_reg.c to dwalineargen.c; others
+         converted to standard reg tests.
+       New rgb convolution functions.
+       For PS output, write all images with a bounding box hint and with
+         page numbers, which works for both embedded (e.g., in tex) and
+         full page generated PS.  Once converted to pdf, this is fine
+         in all situation.
+       New functions for initialization and random insertion with pixcomp.
+       For color quantization, make the lightest color white if sufficiently
+         close; ditto for black (colorquant1.c, colorquant2.c).
+       Rank binning of 8 bpp and rgb images (numafunc2.c, pix4.c)
+       A function to rank colors by the intensity of the minimum comp (pix4.c)
+       New pixRotateBinaryNice(), rotates 1 bpp pix in such a way that
+         the shear lines aren't visible. (rotate.c)
+       New pixSaveTiledWithText(), a convenience function to append text
+         to images that are being tiled. (writefile.c)
+       Stereoscopic warping functions and stereo pair functions (warper.c)
+       Linear interpolated shear -- better than rasterop shear (shear.c)
+       Option to use higher quality chroma (U,V) sampling in jpeg (jpegio.c)
+       Rename Bmf --> L_Bmf.
+       New tests in prog:
+         alltests_reg.c alphaclean_reg.c, psio_reg.c, rankbin_reg.c,
+         rankhisto_reg.c, warpertest.c
+
+1.64   3 Jan 10
+       Easy setup for standard byte processing on 8 bpp pix (pix2.c)
+       Evaluation of difference between similar images; test for
+       similar images and (compare.c)
+       Subpixel scaling, with color input and separate scale factors (pixconv.c)
+       Fix tiff header reader to get correct format (tiffio.c)
+       Enable pixDisplay() to work with i_view (windows) and with
+         xzgv and xli as well as xv; allow application to choose
+         which to use (writefile.c).
+       Use a mask to specify regions that are changed by a morphological
+         operation (morphapp.c).
+       Improve the default sharpening for scaling (scale.c)
+       Function to test for equivalence of file data (utils.c)
+       Select and read image files with embedded index (readfile.c)
+       Fix box size calculation in pixEmbedForRotation(); solution
+         provided by Brent Sundheimer.
+       New pixDisplayMultiple(), instead of calling gthumb directly; this
+         is now set up to use i_view for windows.
+       Changed criteria for determining if an image is color (colorcontent.c,
+         colorquant{1,2}.c.
+       Optional mode where the filename extension is automatically written
+         to output image files; particularly useful for windows.
+       Initialize boxa and pixa as full, with minimal placeholders.
+       Get rank valued numbers and boxes in numa and boxa.
+       Cute implementation for finding largest solid rectangle (maze.c)
+       New median cut quantization for mixed (color/gray) images (colorquant2.c)
+       Many changes to allow the library and applications be built easily
+         in windows.  There is now a thorough windows readme, written by Tom
+         Powers, for doing this.  The windows build information and
+         project files are now in a new vs2008 directory.
+
+1.63   8 Nov 09
+       Added pixScaleToGrayFast(), a faster version with very similar quality.
+       Fixed scaleGrayLILow() to handle edge pixels more accurately
+       Text processing:
+         new text application (finditalic.c, prog/finditalic.c) for locating
+           words in italic type style.
+         Easier to add text to a pix using the bitmap font stored in
+           the font directory; see, e.g., prog/writetxt_reg.c.
+       Blending of 2 images with an alpha channel: pixBlendWithGrayMask()
+       Fixed bug in color segmentation; it now (again) works properly.
+       New utility (pixcomp.c) for handling compressed pix arrays in
+          memory; new PixComp and PixaComp structs.
+       Fast serialization of pix without compressing (pixSerializeToMemory
+          and pixDeserializeFromMemory); required serialized colormaps
+       FileI/O: new functions for reading file headers.
+          PostScript generation modernized; split psio.c into psio1.c
+            and psio2.c; added level 3 (flate) encoding.
+          new functions for reading and writing multipage tiffs, for
+            arbitrary input images.  For writing, compression is lossless
+            (either g4 or zip)
+          update all I/O stub files
+       Miscellaneous: new pixaAddBorderGeneral(); new functions in pix3.c
+          for counting fg pixels and summing 8 bpp pixels by column and row;
+          new numaUniformSampling() for resampling with interpolation;
+          subpixel scaling.
+       New or improved regression tests in prog:
+          extrema_reg, pixalloc_reg, blend2_reg, rotateorth_reg,
+          ioformats_reg, colorseg_reg, pixcomp_reg, pixserial_reg,
+          writetext_reg, psioseg_reg, subpixel_reg.
+       Interface changes:
+         findFileFormat() and findFileFormatBuffer(): now returns format
+             using input ptr.  The function return value is 0 if OK; 1 on error
+         rename: pixThresholdPixels() --> pixThresholdPixelSum()
+
+1.62   26 Jul 09
+       Expanded composite Dwa implementation as a sequence of operations,
+       so that it now works beyond a size of 63.  It's typically about 2x
+       faster than the composite rasterop implementation (with help
+       from Ankur Jain).  Also use data transfer instead of data copy
+       whenever possible.  Thorough tests with binmorph4_reg and binmorph5_reg.
+       New functions in colorseg.c for masking and histogramming in HSV
+       color space.
+       Treat string constants rigorously as const char*, initializing
+       to char[] if to be used as non-const, or in some cases casting
+       to char*.  This avoids compiler warnings.
+       Improved color quantization using existing colormap for octcubes
+       and a new version for grayscale.  This will rigorously map most
+       black and most white octcubes (rsp) to black and white
+       if they exist in the colormap.
+       Fast quantization to an existing colormap for color and grayscale.
+       Fixed some bugs; e.g., in pixAffineSampled() for 1 bpp with
+       L_BRING_IN_BLACK; reading and writing pnm for 2 and 4 bpp.
+       In pngio.c, enable compile time control over these settings:
+         converting 16 bpp --> 8 bpp on read
+         removing alpha channel on read
+         setting zlib compression on write
+       For general scaling, allow sharpening to be optional, and provided
+       faster sharpening operations.
+       Improve support for 16 bpp grayscale.
+       For scaleToGray* functions, reduce the width truncation.
+       In psio.c, new functions for converting segmented page images
+       (text and image) into level 2 PostScript.
+       Removed all implicit casting to const char*.
+       New custom pix memory allocator, designed for large pix whose
+       memory needs to be reused many times.
+       In xtractprotos, we now allow prepending an arbitrary string to
+       each prototype.
+       In environ.h, additions for MSVC to work with VC++6, including
+       prototpye strings for dll import and export (thanks to Ray Smith).
+       In colorseg.c, new functions for building HSV histograms, finding 
+       peaks, and generating masks based on the peaks.
+       New or improved regression tests:
+         pixalloc_reg, binmorph4_reg, binmorph5_reg, conversion_reg,
+         scale_reg, cmapquant_reg, 
+
+1.61   26 Apr 09
+       New histo-based grayscale quantization: pixGrayQuantizeFromHisto(),
+       that is used in new pixQuanitzeIfFewColorsMixed().
+       Made final fix in pixBlockconv().  No underflows; no more overflows!
+       More efficient rgb write with pnm.
+       Add proto to jpegiostub.c, allowing proper use of the stubber.
+       Fix several filter functions to use proper test on filter size; viz.,
+         pixMinMaxTiles(), several functions in convolve.c.
+       Redo shear implementation to handle arbitrary angles, to handle
+         colormapped images, and to avoid the singularity at pi/2.
+       Removed both static vars from pixSaveTiled().
+       Generalized pixRotate() to handle colormapped images, and to use
+         pixRotateBySampling() in place of the removed pixRotateEuclidean().
+       New skew finder for full angle range, pixFindSkewOrthogonalRange().
+       For skew detection, now allow shear about image center as well as
+         about the UL corner.
+       New rotation reg tests: rotate1_reg.c and rotate2_reg.c.
+       Better serialization format for boxaa; introduce new version numbers
+         for boxaa, pixa, and boxa, as required.
+       Proper init in boxGetGeometry(), boxaGetBoxGeometry(), and the
+         accessors in sel1.c and kernel.c.
+       Improved Numa functions in numafunc1.c and numafunc2.c; in
+         particular, numaMakeHistogramAuto() and numaGetStatsUsingHistogram().
+         With all histo generators, make sure the start and binsize params
+         are properly set and are used.
+         Interface change: Use these parameters implicitly in
+         numaHistogramGetRankFromVal() and numaHistogramGetValFromRank().
+       Interface change to ptaGetLinearLSF(): add 1 optional parameter.
+       In several pixaDisplay*() functions, handle colormaps properly.
+       pixafunc.c split to pixafunc1.c and pixafunc2.c.
+         New connected component selections and options in pixaSort.
+       Patch from Tony Dovgal for reading tiff rgba files.
+       Added new logical operation options for numas.
+       New pixConvertRGBToGrayMinMax() that chooses min or max of 3 components.
+       Computation of pixelwise aligned stats between multiple images
+         of the same size (e.g., video), in pix4.c.
+       Very fast binsort implemented for boxa and pixa.
+       Cleanup and rename stack, queue, heap and ptra functions:
+         all structs and typedefs start with "L_"
+         all functions start with "l"
+       Sel creation for crosses and T junctions.
+       New thresholding operations to binary; split out from adaptmap.c
+         into binarize.c.
+       Implementation of sauvola binarization, including use of pixtiling.
+       Added composite parallel union and intersection morphological operations.
+       Small changes to scaling and rotation to improve accuracy; only
+         visible on very tiny, symmetric images.
+       Implemented DPix (double precision data); useful for the mean
+         square accumulator for sauvola binarization.
+       New fast hybrid grayscale seedfill, in addition to the interative
+         version (contributed by Ankur Jain).
+       New or improved regression tests:
+         rotate1_reg, rotate2_reg, shear_reg, numa_reg, skew_reg,
+         ptra1_reg, ptra2_reg, paint_reg, smallpix_reg, pta_reg,
+         pixmem_reg, binarize_reg, grayfill_reg.
+
+
+1.60   19 Jan 09
+       Fixed bug in pixBlockconv(), introduced in 1.59, that causes
+       overflow when convolving with an image that has white (255)
+       at the edges.  [quickly found by Dave Bryan]
+       Include function to display freetype fonts in a pix.
+       The files freetype.c and freetype.h are in the distribution, but are not
+       yet linked into the library.  This is contributed by Tony Dovgal,
+       and this version works only for MSVC.
+       Found that the problems with binary compression in giflib are fixed
+       with giflib 4.1.6.
+
+1.59   11 Jan 09
+       Lots of changes since 1.58.
+       New files: affinecompose.c, ptra.c, warper.c, watershed.{h,c}.
+          Split: boxfunc.c --> (boxfunc1.c, boxfunc2.c, boxfunc3.c)
+       Improved connected component filtering, with logical functions
+       applied to indicator arrays (pix4.c, pixafunc.c, numafunc1.c).
+       Function to determine if an image can be quantized nicely with
+       only a few colors (colorcontent.c, pixconv.c).
+       New gray seed-filling functions (seedfill.c, seedfilllow.c).
+       Fixed bugs in tophats and hdome, due to misuse of pixSubtractGray()
+       (morphapp.c).
+       New function for improving contrast (adaptmap.c)
+       Watershed transform (still slightly buggy) (watershed.c,h).
+       Fast random access into a pix using line pointers (pix1.c, arrayaccess.*)
+       Conversions of colormaps from gray to color and v.v. (colormap.c)
+       Seedfill function that applies an upper limit to the fill
+       distance (seedfill.c)
+       New function for warping images with random harmonic distortion
+       (with help from Tony Dovgal).
+       New generic ptr array utility: all O(1) functions of a stack plus
+       random replace, insert and delete (ptra.c).
+       Simple functions for colorizing a grayscale image with an arbitrary
+       color (pixconv.c, colormap.c)
+       Flexible affine transforms (translation, scale, rotation) on pta
+       and boxa (affinecompose.c).
+       Clipping of foreground (both exact and approximate) starting from
+       within a rectangular region of the image (pix4.c)
+       Blending a colored rectangle over an image (pix2.c, boxfunc3.c)
+       Generation of rectangle covering of mask components (boxfunc3.c).
+       Block convolution using tiles (for very large images)  (convolve.c)
+       New or improved regression tests in prog:
+          locminmax_reg, lowaccess_reg, grayfill_reg, adaptnorm_reg,
+          xformbox_reg, warper_reg, cmapquant_reg, compfilter_reg,
+          splitcomp_reg, affine_reg, bilinear_reg, projective_reg
+       Acknowledgments:
+       (1) Big thanks to Tony Dovgal for helping with the warping
+           (e.g. for captcha).  Tony also provided an implementation that
+           allows rendering truetype fonts into a Pix on windows.
+           This is not yet incorporated, because it opens a huge
+           "can of worms," which is OK if you're going fishing but
+           maybe not if you're trying to support leptonica on many platforms.
+           TBD.
+       (2) David Shao provided a libtools build system that includes
+           building the prog directory!  I believe this will work, but it
+           is is not yet included because of problems I continue to have
+           with macros in version 2.61 of gnu libtools.
+       (3) Steve Rogers is working on a MSVC build for the prog directory.
+           I hope to have this available for 1.60.
+
+1.58   27 Sept 08
+       Added serialization for numaa.
+       New octree quantizer pixOctreeQuantByPopulation(), that uses a
+       mixture of level2 and level4 octcubes.  Renamed many functions
+       in colorquant1.c, and arranged/documented them more carefully.
+       Revised documentation in leptonica.org/papers/colorquant.pdf.
+       Simplified customization for I/O libraries and fmemopen() in environ.h.
+       Fixed bugs in colormap.c, viewfiles.c, pixarith.c.
+       Verified Adam Langley's jbig2enc (encoding jbig2 and generating pdf from
+       these encoded files) works properly with the current version -- see
+       Section 24 of README.html for usage and build hints.
+       New separable convolution; let pixConvolve() take 8, 16 and 32 bpp input.
+       New floating pt pix (FPix) utility, which allows convolution and
+       arithmetic operations on FPix; also interconversion to Pix.
+       Ability to read headers on multipage tiff.
+       More robust updown text detection in flipdetect.c.
+       Use of sharpening to improve scaling when the scale factor is near 1.0.
+       See prog/fpix_reg.c for regression test and usage.
+       See prog/blend_reg.c for blending regression test, with new functions.
+
+1.57   13 Jul 08
+       New Debian distribution for 1.57 (thanks to Jeff Breidenbach).
+       Improved the Otsu-type approach for finding a binarization threshold,
+       by choosing the min in the histogram subject to constraints
+       (numafunc2.c, adaptmap.c)
+       New function pixSeedspread() in seedfill.c, similar to a voronoi tiling,
+       that is used for adaptive thresholding in pixThresholdSpreadNorm().
+       In the process, fixed a small bug in pixDistanceFunction().
+       (The approach was suggested by Ray Smith, and uses the fast
+       Vincent distance function method to expand each seed.)
+       Generalized the functions in kernel.c to use float weights
+       for general convolution (Version 2 for kernel), and added
+       gaussian kernel generation.
+       Put all jpeg header functions into jpegio.c, where they belong.
+       Fixed bugs in pixaInsertPix() and pixaRemovePix().
+       Added read/write serialization for Numaa.
+       New functions for comparing two images using bounding boxes (classapp.c).
+
+1.56   12 May 08
+       Added several new 1d barcode decoders.  The functional interface
+       is still in flux.
+       Autoconf!   To get this working, it was necessary to: determine and
+       set the endian flag; select which libraries are to be linked;
+       determine if stream-based memory I/O is enabled.
+       This required a major cleanup of the include files, minimizing
+       dependencies on external library header files, and getting everything
+       to work with both autoconf (HAVE_CONFIG_H) and the old
+       customized makefile.  Customization is now all in environ.h.
+       pixSaveTiled(): a new way to display tiled images.
+       pixtiling.c: interface for splitting an image into a set of
+       overlapping tiles, using mirrored borders for tiles touching the
+       image boundary.
+       pixBlendHardLight(): new blending mode with nice visual effects.
+       pixColorFraction(): determines extent of color in image
+       Both octree and median-cut color quantization check first if
+       image is essentially grayscale; improvements to both algorithms.
+       box*TransformOrdered(): general sequence of linear transforms.
+       colorquant_reg.c, xformbox_reg.c, hardlight_reg.c: new regr tests.
+
+1.55   16 Mar 08
+       New functions for combining two images arbitrarily through a mask,
+       including mirrored tiling (pix3.c)
+       Modify pixSetMasked*() to work on all images (pix3.c)
+       New functions for extracting masked regions such as pixClipMasked()
+       (pix3.c) and pixMaskConnComp() and pixMaskBoxa() (boxfunc.c).
+       New functions to separate fg from bg (pix3.c), one of which is supported
+       by numaSplitDistribution (numafunc.c).
+       Modify sobel edge detector to take another parameter (edge.c)
+       Support for 4 bpp cmyk color space in jpeg (jpegio.c)
+       Modified median cut color quantization (colorquant2.c)
+       Renamed colorquant.c (for octree quant) --> colorquant1.c.
+       Absorbed conncomp.h and colorquant.h into specific files that
+       depend on them (colorquant1.c, conncomp.c, pix.h)
+       General convolution with utility for building kernels
+       (convolve.c, kernel.c)
+       Initial implementation of 1D barcode reader.  So far, we just have the
+       signal processing to locate barcodes on a page, deskew them, and
+       find the bar widths, along with decoders for two formats.
+       (readbarcode.*, prog/barcodetest.c)
+       Made the default to stub out read/write for non-tiff image formats
+       to memory; it doesn't work on Macs & they were complaining (*io.c)
+       Include MSVC project files for building leptonlib under
+       windows (leptonlib.*)
+
+1.54   21 Jan 08
+       Histogram equalization (enhance.c).
+       New functions for pixaa: serialization (r/w), creation
+       from pixa, and a tiled/scaled display into a pixa (pixabasic.c,
+       pixafunc.c).
+       Read/write of tiff to memory (instead of a file, using
+       the TIFFClientOpen() callback interface), contributed by Adam
+       Langley (tiffio.c, testing in prog/ioformats_reg).
+       Improved image statistics functions, both over tiles and
+       through a mask over the entire image.  Added standard deviation
+       and variance; enable statistics for rgb and colormapped images,
+       in addition to 8 bpp grayscale (pix3.c).  New function to
+       extract rgb components from a colormapped image (pix2.c).
+       Fix pixWriteStringPS() to work with all depths and colormap (psio.c)
+       Enable all non-tiff formats to also write and read to/from memory (*io.c)
+       Added support for read/write to gif, contributed by Tony Dovgal
+       (gifio.c, gifiostub.c, imageio.h).  See Makefile for instructions
+       on enabling this.
+
+1.53   29 Dec 07
+       Add 4th arg to pixDistanceFunction() to specify b.c.,
+       and fixed output to 16 bpp grayscale pix. (seedfill*.c)
+       New un-normalized block grayscale convolution (convolve.c)
+       Fixed bug in getLogBase2(), so that pixMaxDynamicRange() works
+       properly.  This is useful for displaying a 16 bpp pix as
+       8 bpp (pixarith.c).  New function for getting rank val for
+       rgb over a region specified by a mask (pix3.c).  New function
+       for extremem values of rgb colormap (colormap.c).  New
+       function pixGlobalNormNoSatRGB(), a variant of pixGlobalNormRGB()
+       that prevents saturation for any component above a specified
+       rank value (adaptmap.c).  Added mechanism for memory
+       management of pix (pix1.c).  Added selective morphology by
+       region given by a mask (morphapp.c).  Fixed prototype extracdtion
+       to work properly with function prototypes as args; released
+       version 1.2 of xtractprotos (parseprotos.c, xtractprotos.c).
+       Add a boxa field for pixaa, along with serialization (pixabasic.c),
+       and modified display of pixaa to include this (pixafunc.c).
+       Coalesced the version numbers for pixa, pixaa, boxa, and boxaa
+       serialization (pix.h).
+       New progs: modifyhuesat displays modified versions on a grid;
+       textlinemask shows simple methods for extracting textline masks.
+
+1.52   25 Nov 07
+       Implemented Breuel's whitespace partitioning algorithm (partition.c).
+       Generalized pixColorMagnitude() to allow different methods
+       for computing the color amount of a pixel (colorcontent.c).
+       New methods for computing overlap of boxes (boxfunc.c).
+       New methods for painting (solid) and drawing (outline) of boxes,
+       replacing boxaDisplay() with pixDrawBoxa*() and pixPaintBoxa*()
+       (pix2.c, boxfunc.c).
+       Ray Smith fixed bug in the distance function (seedfilllow.c).
+       For pixConvertTo1() and pixConvertTo8(), treat input pixs as a
+       const and never return a clone or altered cmap (pixconv.c).
+       Make pixGlobalNormalRGB() crash-proof (adaptmap.c).
+       Tony Dovgal added ability to read jpeg comment (jpegio.c).
+
+1.51   21 Oct 07
+       Improved histogramming of gray and color images (pix3.c)
+       Histogram statistics (numafunc.c).  Better handling of tiff
+       formats, testing rle and packbits output and improving
+       level 2 postscript conversion efficiency (readfile.c, psio.c).
+       Test program for r/w and display of Sels (prog/seliotest.c).
+       Use endiantest to determine automatically which flags to set
+       when compiling for big- or little-endians (endiantest.c)
+       Compute a color magnitude for each rgb pixel (colorcontent.c).
+       Allow separate modification of hue and saturation (enhance.c).
+       Global transform of color image for arbitrary white point (adaptmap.c).
+   
+1.50   07 Oct 07
+       |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+       NOTE CAREFULLY: The  image format enum in imageio.h has
+       changed.  This is an ABI change, and it requires
+       recompilation of the library.
+       |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+       Suggestions by David Bryan again resulted in several changes,
+       including improvements to the dwa generating functions and interfaces.
+       Major improvements for dwa code generation, including an
+       optional filename for the output code, adding function prototypes
+       to the code so it can easily be linked outside the library.
+       Addition of 2-way composable dwa functions for bricks, with
+       code addition to the library, and a new interpreter for dwa
+       composable brick sequences  (fmorphauto.c, fhmtauto.c,
+       morphtemplate1.c, hmttemplate1.c, morphdwa.c, dwacomb*.2.c, morphseq.c)
+       Exhaustively tested in six programs (prog/binmorph*_reg,
+       prog/dwamorph*_reg).
+       New input modes for Sels, from both color bitmap editors
+       and a simple file format (sel1.c).
+       Better Sel generation functions in sel2.c, including combs for
+       composable brick operations and linear bricks for comparison.
+       Removed unnecessary copies for more efficient border add'n & removal. 
+       Added RLE basline enc/dec for tiff.
+       Binary morphology documentation on the web page updated for these
+       changes/additions.
+       William Rucklidge unrolled inner loops and added LUTs to
+       speed up several more functions, including correlation
+       (correlscore.c), centroid calculation (morphapp.c),
+       2x linear interp grayscale scanning (scalelow.c),
+       thresholding to binary (grayquantlow.c), and removal
+       of colormaps to gray (pixconv.c).
+
+1.49   23 Sep 07
+       |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+       NOTE CAREFULLY: The  image format enum in imageio.h has
+       changed.  This is an ABI change, and it requires
+       recompilation of the library.
+       |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+       Suggestions by David Bryan resulted in several changes.
+       pixUnpackBinary() unpacks to all depths.
+       Can now write and read tiff in LZW and ZIP (gzip) formats.
+       These, like uncompressed tiff, work on all bit depths.
+       Also enabled pnm 16 bpp r/w, both non-ascii and ascii.
+       ioFormatTest() now has better coverage and clarity; this is
+       used in prog/ioformats_reg.c.
+       Rewrite of morphautogen code to implement opening and closing atomically.
+       Cleaner interaction with new text templates (fmorphauto.c,
+       fhmtauto.c, sarray.c, *template*.txt,).
+       More regression testing (e.g., binmorph1_reg.c, binmorph3_reg.c).
+
+1.48   30 Aug 07
+       William Rucklidge sped up pixCorrelationScore() by in-lining
+       all bit operations (jbclass.c).
+       Generalized rank filtering from 8 bpp to color (rank.c).
+       Fixed many functions that take a dest pix so that they don't fail if
+       the dimensions or depth are not consistent with the src pix.
+       The underlying change for this is to pixCopy() (pix1.c).
+       Improved display of Sel as a pix; added selaDisplayInPix() to
+       display all Sels in a Sela, orthogonal rotations of Sels (sel1.c).
+       New functions for thinning and thickening while preserving connectivity
+       and avoiding both free end erosion and dendritic cruft (ccthin.c,
+       prog/ccthin1_reg.c, prog/ccthin2_reg.c).
+       New function pixaDisplayTiledInRows() for compactly tiling pix
+       in a pixa, plus documentation of different existing methods. (pixafunc.c)
+
+1.47   22 Jul 07
+       New brick rank order filter (rank.c, prog/ranktest.c, prog/rank_reg.c).
+       Use mirror reflection b.c. to avoid special processing at
+       boundaries (pix2.c).  Simple sobel edge detector (edge.c).
+       Utility for assempling level 2 compressed images in PostScript
+       (psio.c, prog/convertfilestops.c).  Enable read/write of 16 bpp,
+       grayscale tiff (tiffio.c, pix2.c).
+       New function for finding the number of c.c., which is a bit
+         faster than finding the b.b. or the component images (conncomp.c)
+       New functions for finding local extrema in grayscale image (seedfill.c)
+
+1.46   28 Jun 07
+       Added interpreted mode for color morphology (morphseq.c).
+       In functions, make effort to consistently do early initialization
+       of ptrs to objects returned on the heap.  This is to try to
+       avoid letting functions return uninitialized objects, even if
+       the return early because of bad input.
+       Split pixa.c into 2 files; revised the component filtering
+       in both pixafunc.c and boxfunc.c.  Added component filtering
+       for "thin" components.
+       Added subsampling functions for numa and pta.
+       Word segmentation now works at both full and half resolution.
+       Better methods for displaying and tiling (for debugging),
+       using pixDisplayWrite(), pixaReadFiles() and pixaDisplayTiledAndScaled().
+
+1.45   27 May 07
+       Further improvements of orientation and mirror flip detection
+       (flipdetect.c).  Added 2x rank downscaling and general integer
+       replicative expansion (scale.c).  Simplified interface for
+       averaging, and included tiled averaging, which is yet another
+       integer reduction scaling function (pix3.c).
+
+1.44   1 May 07
+       Split pix2.c into (pix2.c, pix3.c), with basic housekeeping
+       functions (e.g., ops on borders, padding) in pix2.c.
+       Split numarray.c into (numabasic.c, numafunc.c), with
+       constructors and accessors in numabasic.c.  Added a number
+       of histogram, rank value and interpolation functions to numafunc.c.
+       Add rms and rank difference comparison functions (compare.c).
+       Separated orientation and mirror flip detection; fixed the latter
+       (flipdetect.c).
+
+1.43   24 Mar 07
+       New and fixed functions for handling word boxes (classapp.c)
+       More consistent use of L_* flags (e.g., sarray.h, morph.h)
+       Morphology on color images (gray ops on each component) (colormorph.c)
+       New methods for generating sels; we now have five methods in
+       sel1.c and 3 others in selgen.c.  Also a function that
+       displays Sels as an image, for use in documentation (sel1.c)
+       New high-level converters, such as pixConvertTo8(), pixConvertTo32(),
+        pixConvertLossless()   (pixconv.c)
+       Identify regression tests, and rename them as prog/*_reg.c.
+       Complete revision of plotting package (gplot.c)
+       New functions for comparing pix (compare.c)
+       New morph application functions, such as the ability to run a
+       morph sequence separately on selected c.c. in an image, and
+       a fast, quasi-tophat function (morphapp.c)
+       Cleanup and new interfaces to border representations of c.c. (ccbord.c)
+       Page segmentation application (pageseg.c)
+       Better serialization with version control for all major structs.
+       Morphological brick operations with 2-way composite sels (morph.c)
+
+1.42   26 Dec 06
+       New sorting functions, including 2-d sorting, for boxa and pixa,
+       and functions that sort by index (e.g., pixa --> pixa and
+       for 2d, pixa --> pixaa; ditto for boxa).  
+       New accessors for pix dimensions.  A new strtokSafe() to
+       substitute for strtok_r (utils.c).
+       Page flip detection, using both rasterop and dwa morphology
+       (flipdetect.c), with dwa generation (fliphmtgen.c) and testing
+       (prog/fliptest.c).
+       Increased basic sels from 42 to 52 (sel2.c).
+       Better high-level interfaces for binary morphology with
+       brick (separable) sels, both for rasterop (morph.c) and for
+       dwa (morphdwa.c); fully tested for both asymmetric and
+       symmetric b.c. (prog/morphtest3.c).  Faster area mapping
+       reduction for power-of-2 scaling.
+
+1.41   5 Nov 06
+       Simplified morph enums, removing all unused ones (morph.h).
+       Added new high-level interfaces for adaptive mapping (adaptmap.c).
+       New method to extract color content of images (colorcontent.c).
+       New method to generate sels from text strings, and to identify
+       roman text that is not properly oriented (thanks to Adam Langley).
+       Fast grayscale min/max (rank) scale reduction by integer factors.
+       New accessors for box and sel, that should be used when possible.
+       Thresholding grayscale mask by bandpass (grayquant.c).
+       Use of strtok_r() for thread safety.
+
+1.40   15 Oct 06
+       Fixed xtractprotos for cygwin.  Minor fixes and improved documentation
+       (baseline.c, conncomp.c, pix2.c, morphseq.c, pts.c, numarray.c,
+       utils.c, skew.c).  Add ability to quantize an rgb image to a
+       specified colormap (colorquant.c); tested in prog/cmapquanttest.c.
+       Modifications to allow conditional compilation on MS VC++,
+       and to allow I/O calls to be stubbed out (new files: *iostub.c,
+       zlibmemstub.c, pstdint.h, arrayaccess.h.ms60)
+1.39   31 Aug 06
+       |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+       NOTE CAREFULLY:  There has been an interface change to make
+       affine, bilinear and projective transforms more general.
+       The implementation has been changed to allow them to handle
+       all image types and to make them faster (esp. with both sampled
+       and interpolated mapping on color images).
+       |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+       Added prog/Makefile.mingw to build executables.  This is still
+       in a relatively raw state.  It is necessary to download
+       gnuwin32 packages for 4 libraries (jpeg, png, zlib, tiff)
+       to link with leptonlib and the main, and I still have not
+       been able to build static executables (they require jpeg2b.dll, etc.).
+
+1.38   8 Aug 06
+       |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+       NOTE CAREFULLY: There has been an interface change to both
+       simplify and generalize the grayscale morphology operations:
+           pixErodeGray(), pixDilateGray(), pixOpenGray(),
+          pixCloseGray(), pixTophat() and pixMorphGradient().
+       The prototypes are not changed; old code will compile, but
+       it will be wrong!  The old interface had a size and a type
+       (horizontal, vertical, square).  The new interface takes
+       horizontal and vertical Sel dimensions.
+       |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+       For cross-compilation to make windows programs, you can use
+       src/Makefile.mingw to make a windows version of the library.
+       6x scale-to-gray function donated by Alberto Accomazzi.
+       Interpreter added for sequence of grayscale morphological
+       operations, including the tophat (morphseq.c).
+       Pixacc container added to simplify the interface
+       for accumulator arithmetic using Pix.
+       Removed fmorph.c and fmorphlow.c from the library.  These are
+       very limited and were deprecated in favor of fmorphauto(), which
+       autogens the code from (nearly) any Sel.
+       Fixed some of the gray morphology operations, which had errors
+       on the boundary.  All gray morph ops should now be rigorously
+       OK (graymorph.c).  For testing of graymorph dualities, the 
+       the graymorph interpreter, etc., see prog/morphgraytest.c. 
+
+1.37   10 Jul 06
+       [After v.36 was released, Jeff Breidenbach built a Debian
+       distribution of Leptonica based on v.36, and you can now get Leptonica
+       as a Debian package.  Use "apt-cache search leptonica" to see
+       what is available.]  The libraries are now combined into a single
+       library (liblept.a, liblept.so) and the function prototypes are
+       also in a single file (leptprotos.h).  cextract was found not
+       to work on recent versions of linux that support 64 bit data types,
+       and it is no longer distributed with leptonica.  Instead, I wrote
+       a prototype extractor in leptonica (xtractprotos).  When you
+       'make allprotos', it now uses this program.  The shared libraries
+       now have major and minor numbers corresponding to the version.
+
+1.36   17 Jun 06
+       Line graphics generation (graphics.c) reorganized; separated out pta
+       generation from rendering.  Can now render with alpha blending.
+       Examples of use are given in prog/graphicstest.c.
+       Sort functions for basic geometrical objects now have the option
+       of returning a numa giving the sort order on the original array.
+       The pixa sort can sort with either clones or copies of the pix.
+
+1.35   21 May 06
+       The efficiency of the multipage jbig unsupervised classifier is
+       significantly improved due to a NumaHash struture implemented
+       by Adam Langley.  Functions for computing runlength in 1 bpp
+       images have been added.
+
+1.34   7 May 06
+       Completely rewrote the jbig unsupervised classifier.
+       It now works on multiple pages, and is more accurate in performing
+       visually lossless substitutions.  You can classify by connected
+       components, characters, or words.  The old data structures
+       and interfaces have been removed.  New unpackers from 1 to 2 and
+       1 to 4 bpp, with and without colormaps in the dest.
+
+1.33   18 Mar 06
+       Generalized color snap to have different src and target colors,
+       and to include colormaps (blend.c).  Distribute into root directory
+       that specifies the version number (e.g., 1.33).  Add color
+       space conversion between rgb and hsv.  Re-bundle thresholding
+       code from (binarize*.c, dibitize*.c) to grayquant*.c.
+       pixThreshold8() now also quantizes 8 bpp --> 8 bpp.
+       High-level pixRotate() that optionally expands image sufficiently
+       so that no pixels are lost in any sequence of rotations (rotate.c).
+       Generalize shear to specify color of pixels brought in, including
+       for in-place operation (shear.c, rotateshear.c).  Faster version of
+       color rotation by area mapping, both about center and about UL corner.
+       You can now use the standard color rotator (pixRotateAM) and get
+       nearly the same speed as with the "Fast" one.
+
+1.32   4 Feb 06
+       Ability to specify a sequence of binary morphological
+       (& binary reduction/expansion) operations in a single
+       function (morphseq.c).  Fast downscaling combined with conversion
+       from rbg to gray and to binary (scale.c).  Utility for
+       segmenting images by color (colorseg.c).
+
+1.31   7 Jan 06
+       Remove more complicated functions that threshold to 2 bpp, 
+       retaining the simplest interface.  Retest all thresholding and
+       dithering.  Add "ascii" write of PNM.  Improve graphics writing
+       of lines; generalize to colormaps.  New colorization functions
+       (paintcmap.c, blend.c).
+
+1.30   22 Dec 05
+       Remove most instances of fprintf(stderr, ...), except within
+       DEBUG or encapsulated in error, warning or info macros. 
+       As a result, there is no output to stderr if NO_CONSOLE_IO is defined. 
+       Adaptive mapping to make bg uniform (adaptmap.c).  A few bug fixes.
+       New PostScript output functions for embedding PS files
+       (prog/converttops).  Generalized some image enhancement functions.
+       New functions for generating hit-miss sels.
+
+1.29   12 Nov 05
+       More flexible blending of two images, with and without colormaps
+       (see blend.c).  Painting colormapped images through masks, etc
+       (see paintcmap.c).  More flexible interface for gamma and
+       contrast enhancement (see enhance.c).
+
+1.28   8 Oct 05
+       Removed all pix colormaps for 1 bpp.  Allow programmatic resetting
+       of binary morphology boundary conditions.  Added (yet) another
+       simple octcube color quantizer.  New colormap operations.
+
+1.27   24 Sep 05
+       Renamed many of the enums and typedefs to avoid namespace
+       collisions.  This includes structs and typedefs for BMP.
+       Interface change to pixClipRectangle(); apologies to everyone
+       whose code is broken by these changes -- I hope it's worth it.
+       Removed colormap.h; simplified all colormap usage, hiding details
+       from all but a few colormap functions.  Am now saving file format
+       in the pix when an image is read, and can by default write
+       out in this format.  Resolution info added for jpeg and png.
+       Added L_INFO* macros and l_info* fctns for printing
+       (e.g., debugging) info.  Suggestions and code kindly
+       supplied by Dave Bryan, who helped solve compatibility issues
+       with MINGW32 (e.g., in timing and directory functions).
+       Added some blending and linear TRC functions.
+       Generalized pixEqual() to include all cases with and without
+       colormaps.  New regression tests in prog: ioformats, equaltest.
+
+1.26   24 Jul 05
+       Generalized affine pointwise to do interpolation as well as
+       sampling.  For both projective and bilinear transforms,
+       implemented using both sampling and interpolation.
+       Added function to remove keystoning by computing the necessary
+       projective transform and doing it.  Also find baselines in text images.
+       Added downscaling using accurate area-mapping over subpixels.
+
+1.25   25 Jun 05
+       Better endian conversion fctns for 2 and 4 byte words.
+       Remove colormaps before converting by thresholds.
+       Added functions to read header parameters for png and tiff.
+
+1.24   5 Jun 05
+       Added image splitting to allow printing in tiles (as several pages).
+       Added new octree quantization function to generate 4 and 8 bpp
+       colormapped output (not dithered).  Fixed bmp resolution.
+       Added new flag for colormap removal (using dest depth based on
+       src colormap).  Added I/O tests (prog/ioformats.c)
+
+1.23   10 Apr 05
+       Added thresholding from 8 bpp to 2 and 4 bpp, allowing specification
+       of both the number of output levels and whether or not a colormap
+       is made.
+
+1.22   27 Mar 05
+       Add pointer queue facility.  To demonstrate it, you can now
+       generate a binary maze using a cellular automaton and find
+       the shortest path between two points in the maze.  Add heap
+       of pointers (keyed on the first field), which is used to
+       implement a priority queue.  This is applied to search for
+       a "least cost" path on a grayscale image (a generalization
+       of a binary maze).
+
+1.21   28 Feb 05
+       Read/write of colormaps to file.  For gplot, add a new
+       latex output terminal.  Bring ptrs into 21st century by
+       including stdint.h, and using uintptr_t for the ptr address
+       arithmetic in arrayaccess.*.  This seems to be OK back to
+       RH 7.0, but if you run into trouble with an earlier
+       C compiler, let me know.  Also, use enums for global
+       constants whenever possible, and qualify named constants
+       (e.g., ADD --> ARITH_ADD, HORIZ --> MORPH_HORIZ) to avoid
+       possible interactions with other libraries.
+
+1.20   31 Jan 05
+       Speed up of tiffio and pngio with byte swap generating new pix.
+       In textops.c, ability to split string into paragraphs, 
+       in preparation for more general typesetting.
+       Automatic hit-miss Sel generation for pattern matching.
+       Fast downscaling using a lowpass filter and subsampling.
+       Generalization of several grayscale and color operations
+       to work on colormapped images.  Improved scale-to-gray and
+       scaling reduction operations to be antialiased for best results.
+
+1.19   30 Nov 04
+       Additions to fileIO: (1) new jpeg reading options, such as
+       returning warnings and scaled raster; (2) ability to write
+       custom tiff flags.  Better tiling functions.
+       Edge extraction, both with grayscale morphology
+       and clipped convolution filters.  More general painting
+       through a binary mask: pixSetMaskedGeneral().
+       Unpacking from binary to 8, 16 and 32 bpp.  Thresholding
+       and dithering from 8 bpp to 2 bpp ("dibitization").  New bitmap
+       font facility, using a single rendered font in a variety of
+       sizes: allows painting the text on an image (binary, gray, RGB).
+       (People have asked for the ability to write text on images).
+
+1.18   25 Aug 04
+       Changed typedefs of built-in types to avoid possible conflicts.
+       Cleaned up and tested all programs in the prog directory.
+       Simplified and fixed the pixSetMasked() and pixCombineMasked()
+       functions.
+
+1.17   31 May 04
+       Implemented distance function for 16 bpp.  We can now generate
+       out 16 bpp PNG.  Simple programs for generating PS from a
+       directory of g4tiff or jpeg images.  Changed implementation of
+       erosion to allow either asymmetric or symmetric boundary conditions.
+       The distinction is described on the binary morphology web page.
+       Allow read/write of multipage TIFF files.  Implemented
+       read/write of PNM files.
+
+1.16   31 Mar 04
+       New depth conversion functions, improved conversion to false color,
+       new contour rendering (onto 1 bpp or onto the src grayscale image),
+       new orthogonal rotations, better interface for doing arithmetic
+       on 2-d arrays using a pix, improved distance function.
+
+1.15   31 Jan 04
+       Fast interpolated color rotation with 4x4 subpixels; has
+       nearly the accuracy of the slower method using 16x16 subpixels.
+       Demonstration of line removal from grayscale sketch in
+       prog/lineremoval.c.  Conversion of grayscale to false color.
+       Fixed shear and rotation functions to handle angle = 0.0 properly.
+       Other small fixes and interface improvements.
+
+1.14   30 Nov 03
+       Small implementation changes to list.c.  Better sorting
+       routines for number arrays (numa), plus sorting for box
+       arrays (boxa) and pix arrays (pixa).  PostScript wrapper
+       for jpeg.  Better handling of colormaps, and a simple
+       function to convert an RGB pix with not more than 256
+       colors to the smallest colormapped pix.  PS output wrappers
+       for JFIF JPEG and TIFF G4 files.  Comments compatible
+       with doxygen for automatic documentation.
+
+1.13   31 Oct 03
+       Cleaned up documentation in src.  Made libraries and test programs
+       ANSI C++ compliant.  Added special cases to rasterops for
+       alignment to word boundaries.  Fixed pngio.c to work with
+       most recent libpng (1.2.5).
+
+1.12   30 Jun 03
+       Implemented border chain representation from a binary image,
+       writes/reads a compressed version, and renders the original
+       image back from the borders.   Also writes outline file out
+       in svg format.  Number arrays (numa) and point arrays (pta)
+       are also extended to 2nd level arrays (numaa, ptaa).
+       Serialized I/O for boxa, pta, and pixa.
+
+1.11   31 May 03
+       Implemented generic list handling, for doubly-linked
+       list cons cells and arbitrary objects.
+
+1.10   14 Apr 03
+       Implemented simple image enhancements in gray and color:
+       gamma correction, contrast enhancement, unsharp masking.
+       Extended smoothing via block convolution to color.
+       Implemented auto-gen'd DWA version of hit-miss transform;
+       the code for generating these hmt routines is very similar to
+       that for DWA auto-gen'd erosion and dilation.
+       
+1.9    28 Feb 03
+       Implemented a safe, expandable byte queue.  As an example of
+       its use, implemented memory-to-memory compression and decompression
+       using zlib.  Generalized PS write to include RGB color.
+       Implemented a method to find image skew.
+
+1.8    31 Jan 03
+       Implemented a simple 1-pass color quantization with dithering,
+       and improved the 2-pass octree color quantization.
+       Documented with an application page, that includes jbig2.
+       Added new general sampling operations and made a table
+       that summarizes the available scaling operations.
+
+1.7    31 Dec 02
+       Added pixHtmlViewer(), a formatter that allows portable viewing of
+       a set of images (like a slide show) in a browser.
+       Implemented better octree color quantization, with variable
+       number of colors, pruning the octree for good color clusters,
+       and fast traversal for pixel assignment to colormap.
+
+1.6    30 Nov 02
+       Generalized shear and shear rotation to arbitrary locations
+       about which the operation is performed.  Implemented in-place
+       translation using pixRasteropIP().  Implemented arbitrary
+       affine transform of image two ways: pointwise and sequential.
+       Added binarization by error diffusion.  Added simple color
+       quantization by octree.
+
+1.5    31 Oct 02
+       Put jpeglib.h in local directory.  This, along with the jmorecfg.h
+       file there prevents compiler warnings about redefined typedefs.
+       Compiled everything with g++ to make strictly ansi C compatible.
+       Added interface gplotFromFile() for simple file-based plotting with 
+       gnuplot 3.7.2.   Added functions to convert 2, 4 and 8 bpp
+       color-mapped (i.e., palletted) images to 24 bpp color or
+       8 bpp grayscale.  Added several jbig2 application cores that
+       only require a simple wrapper to make into programs.
+
+1.4    30 Sep 02
+       Added interface to gnuplot 3.7.2 and to x11 display of images. 
+       Added new functions with arrays of images for use in applications
+       such as jbig2 encoders, along with a simple jbig2 implementation
+       using either hausdorff or correlation scoring.  Added centroid
+       finder for images.  For accessing image arrays from arrays of
+       image arrays, added a "new reference" (NEW_REF) flag, with a
+       ref count attached to the array.  Added power-of-2 binary
+       expansion and reduction.
+
+1.3    30 Jun 02
+       Extended connected components to 8.  Added morphological
+       operations tophat and hdome, along with clipped arithmetic
+       operators on grayscale images.  Fixed memory error in
+       rasteropGeneralLow() that was found using valgrind.
+       Tested most operations with valgrind for memory errors.
+       Replaced integer arrays with number arrays, to include floats.
+       Added arithmetic functions on grayscale images.
+
+1.2    30 May 02
+       Added connected component utility, stack utility, pix arrays,
+       line drawing and seed filling.  Binary reconstruction,
+       both morphological and raster-oriented, are now supported
+       for 4 and 8 connected fills.  Added the distance function
+       on binary images, grayscale reconstruction, and grayscale
+       morphology using the Gil-Werman method.
+
+1.1    30 Apr 02
+       Added orthogonal rotations, binary scaling, PS output,
+       binary reconstruction, integer arrays, structuring element
+       input/output.
+
+1.0    25 Feb 02
+       Initial distribution, with rasterops, binary morphology (two
+       implementations: rasterops and dwa), affine transforms
+       (translation, shear, scaling, rotation), fast convolution,
+       and basic i/o (BMP, PNG and JPEG).
+
+
+
+</pre>
+
+<!-- JS Window Closer -----
+<form>
+<center>
+<input type="button" onclick="window.close();" value="Close this window">
+</center>
+</form>
+----- JS Window Closer -->
+
+</body>
+</html>
+